#include "externals.hh"

#include "c-opopt.hh"
#include "c-ops.hh"
#include "c-sync.hh"

using namespace ebcl;
using namespace opast;
using namespace opopt;


/*= T_OptData ================================================================*/

#define M_LOGSTR_( S , L ) \
	logger( [](){ return T_StringBuilder{ S }; } , L )

uint32_t opopt::ComputeHash(
		T_OptData::T_VarId const& id ) noexcept
{
	const uint32_t nh{ ComputeHash( id.name ) };
	const uint32_t oh{ id.type != T_OptData::E_UDVarType::GLOBAL
		? ComputeHash( id.name )
		: 0
	};
	return ( uint32_t( id.type ) << 8 )
		^ nh
		^ ( ( oh << 29 ) | ( oh >> 3 ) );
}

T_StringBuilder& opopt::operator<<(
		T_StringBuilder&			obj ,
		T_OptData::T_CtrlFlowEdge const&	value ) noexcept
{
	obj << value.target;
	switch ( value.type ) {
	    case T_OptData::T_CtrlFlowEdge::CALL:
		obj << "{c}";
		break;
	    case T_OptData::T_CtrlFlowEdge::RET:
		obj << "{r}";
		break;
	    case T_OptData::T_CtrlFlowEdge::BYPASS:
		obj << "{b}";
		break;

	    case T_OptData::T_CtrlFlowEdge::FLOW:
		break;
	}
	return obj;
}

constexpr uint32_t T_OptData::CFG_ENTER;
constexpr uint32_t T_OptData::CFG_MAINLOOP;
constexpr uint32_t T_OptData::CFG_END;

/*= T_OptData - INPUT DECLARATIONS ===========================================*/

void T_OptData::findInputDecls(
		T_OpsParserOutput& program ) noexcept
{
	if ( state & E_StateItem::INPUTS ) {
		return;
	}

	inputDecls.clear( );
	visitor.visit( program.root , [this]( A_Node& node , const bool exit ) {
		if ( exit && node.type( ) == A_Node::OP_INPUT ) {
			auto& input{ (T_InputInstrNode&) node };
			auto* da{ inputDecls.get( input.id( ) ) };
			if ( !da ) {
				inputDecls.add( input.id( ) , T_Array< T_InputDecl >{ } );
				da = inputDecls.get( input.id( ) );
			}
			da->add( T_InputDecl{ input.location( ) , input.defValue( ) } );
		}
		return true;
	} );

	state = state | E_StateItem::INPUTS;
}


/*= T_OptData - INSTRUCTION NUMBERING ========================================*/

namespace {

bool ODNIVisitor_(
		A_Node& node ,
		const bool exit ,
		T_OptData& oData ,
		const uint32_t fnIndex ) noexcept
{
	if ( dynamic_cast< A_ExpressionNode* >( &node ) ) {
		return false;
	}
	auto* const iptr{
		dynamic_cast< A_InstructionNode* >( &node ) };
	if ( iptr && !exit ) {
		auto const& il{ dynamic_cast< T_InstrListNode& >( iptr->parent( ) ) };
		const auto hash{ ebcl::ComputeHash( (uint64_t)iptr ) };
		oData.instrIndex.add( hash );
		oData.instructions.add( T_OptData::T_InstrPos{
			oData.instructions.size( ) , iptr ,
			iptr == &il.node( il.size( ) - 1 ) ,
			fnIndex } );
	}
	return true;
}

} // namespace <anon>

void T_OptData::numberInstructions(
		T_OpsParserOutput& program ) noexcept
{
	if ( state & E_StateItem::NUMBERING ) {
		return;
	}

	instructions.clear( );
	instrIndex.clear( );

	const auto nf{ program.root.nFunctions( ) };
	for ( auto i = 0u ; i < nf ; i ++ ) {
		visitor.visit( program.root.function( i ) ,
			[&]( A_Node& node , const bool exit ) {
				return ODNIVisitor_( node , exit , *this , i );
			} );
	}

	state = state | E_StateItem::NUMBERING;
}

uint32_t T_OptData::indexOf(
		opast::A_InstructionNode const& instr ) noexcept
{
	const auto hash{ ebcl::ComputeHash( (uint64_t)&instr ) };
	uint32_t existing{ instrIndex.first( hash ) };
	while ( existing != T_HashIndex::INVALID_INDEX ) {
		if ( &instr == instructions[ existing ].node ) {
			break;
		}
		existing = instrIndex.next( existing );
	}
	assert( existing != T_HashIndex::INVALID_INDEX );
	return existing;
}


/*= T_OptData - CONTROL FLOW GRAPH CONSTRUCTION ==============================*/
namespace {

// CFG type shortcuts
using T_CFN_ = T_OptData::T_CtrlFlowNode;
using P_CFN_ = T_OptData::P_CtrlFlowNode;

// Helpers to create or re-use CFG nodes
T_OptData::P_CtrlFlowNode BCFGNewNode_(
		T_Array< P_CFN_ >& pool ) noexcept
{
	if ( pool.empty( ) ) {
		return NewOwned< T_CFN_ >( );
	}

	auto r{ std::move( pool.last( ) ) };
	pool.removeLast( );
	r->instructions.clear( );
	r->inbound.clear( );
	r->outbound.clear( );
	return r;
}

#define M_NEWNODE_() BCFGNewNode_( old )
#define M_ADDNEW_() \
	ctrlFlowGraph.add( M_NEWNODE_( ) )
#define M_NODE_(i) \
	ctrlFlowGraph[i]

/*----------------------------------------------------------------------------*/

using T_BCFGFunctions_ = T_KeyValueTable< T_String , T_OptData::T_BasicBlock >;

inline void BCFGFuncEnter_(
		A_Node&					node ,
		T_Optional< uint32_t >&			cNode ,
		T_Array< T_OptData::P_CtrlFlowNode >&	ctrlFlowGraph ,
		T_Array< T_OptData::P_CtrlFlowNode >&	old ,
		T_BCFGFunctions_&			cfgFunctions ,
		F_OPLogger const&			logger
		) noexcept
{
	auto& n{ dynamic_cast< A_FuncNode& >( node ) };
	auto const& fn{ n.name( ) };
	logger( [&](){
		T_StringBuilder sb;
		sb << "Starting function '" << fn << "' at "
			<< ctrlFlowGraph.size( );
		return sb;
	} , 5 );
	cfgFunctions.add( fn , T_OptData::T_BasicBlock{
			ctrlFlowGraph.size( ) } );
	cNode = M_ADDNEW_( );
}

inline void BCFGFuncExit_(
		A_Node&					node ,
		T_Optional< uint32_t >&			cNode ,
		T_Array< T_OptData::P_CtrlFlowNode >&	ctrlFlowGraph ,
		T_BCFGFunctions_&			cfgFunctions ,
		F_OPLogger const&			logger
		) noexcept
{
	auto& n{ dynamic_cast< A_FuncNode& >( node ) };
	auto const& fn{ n.name( ) };
	logger( [&](){
		T_StringBuilder sb;
		sb << "Function ended; last block had "
			<< ( M_NODE_( *cNode )->instructions
				? M_NODE_( *cNode )->instructions->count
				: 0 )
			<< " instructions";
		return sb;
	} , 5 );
	auto* frec{ cfgFunctions.get( fn ) };
	assert( frec );
	frec->count = ctrlFlowGraph.size( ) - frec->first;
	cNode.clear( );
}

/*----------------------------------------------------------------------------*/

// Data structure to handle conditionals
struct T_BCFGStackEntry_ {
	uint32_t condBlock;
	bool hasDefault{ false };
	T_AutoArray< uint32_t , 8 > caseBlocks;
};
using T_BCFGStack_ = T_AutoArray< T_BCFGStackEntry_ , 8 >;

inline void BCFGCondEnter_(
		T_BCFGStack_& stack ,
		T_Optional< uint32_t >& cNode ,
		T_Array< T_OptData::P_CtrlFlowNode >& ctrlFlowGraph ,
		F_OPLogger const& logger ) noexcept
{
	auto& se{ stack.addNew( ) };
	se.condBlock = *cNode;
	cNode.clear( );
	logger( [&](){
		T_StringBuilder sb;
		sb << "Entering conditional instruction, stack size "
			<< stack.size( )
			<< ", block had "
			<< ( M_NODE_( se.condBlock )->instructions
				? M_NODE_( se.condBlock )->instructions->count
				: 0 )
			<< " instructions";
		return sb;
	} , 6 );
}

inline void BCFGCondExit_(
		T_BCFGStack_& stack ,
		T_Optional< uint32_t >& cNode ,
		T_Array< T_OptData::P_CtrlFlowNode >& ctrlFlowGraph ,
		T_Array< T_OptData::P_CtrlFlowNode >& old ,
		F_OPLogger const& logger ) noexcept
{
	auto& se{ stack.last( ) };
	cNode = M_ADDNEW_( );

	// Connect each case block to both the condition
	// and the next block
	const auto ncb{ se.caseBlocks.size( ) };
	for ( auto i = 0u ; i < ncb ; i ++ ) {
		auto& cbi{ se.caseBlocks[ i ] };
		auto& cb{ *M_NODE_( cbi ) };
		cb.inbound.add( se.condBlock );
		M_NODE_( se.condBlock )->outbound.add( cbi );
		cb.outbound.add( *cNode );
		M_NODE_( *cNode )->inbound.add( cbi );
	}
	if ( !se.hasDefault ) {
		M_NODE_( *cNode )->inbound.add( se.condBlock );
		M_NODE_( se.condBlock )->outbound.add( *cNode );
	}

	stack.removeLast( );
	logger( [&](){
		T_StringBuilder sb;
		sb << "Exiting conditional instruction, stack size "
			<< stack.size( );
		return sb;
	} , 6 );
}

/*----------------------------------------------------------------------------*/

inline bool BCFGVisitor_(
		A_Node&					node ,
		const bool				exit ,
		T_OptData&				data ,
		T_BCFGStack_&				stack ,
		T_Optional< uint32_t >&			cNode ,
		T_Array< T_OptData::P_CtrlFlowNode >&	old
		) noexcept
{
	const auto nt{ node.type( ) };

	// Handle start/end of functions
	if ( nt == A_Node::DECL_FN || nt == A_Node::DECL_INIT
			|| nt == A_Node::DECL_FRAME ) {
		if ( exit ) {
			assert( stack.empty( ) );
			BCFGFuncExit_( node , cNode , data.ctrlFlowGraph ,
					data.cfgFunctions , data.logger );
		} else {
			BCFGFuncEnter_( node , cNode , data.ctrlFlowGraph ,
					old , data.cfgFunctions ,
					data.logger );
		}
		return true;
	}

	// All instructions: continue the current basic block
	auto* const iptr{ dynamic_cast< A_InstructionNode* >( &node ) };
	if ( iptr && !exit ) {
		assert( cNode );
		auto& n{ *data.ctrlFlowGraph[ *cNode ] };
		if ( n.instructions ) {
			n.instructions->count ++;
		} else {
			n.instructions = T_OptData::T_BasicBlock{
				data.indexOf( *iptr ) };
		}
	}

	// Handle conditionals
	if ( nt == A_Node::OP_COND ) {
		if ( exit ) {
			BCFGCondExit_( stack , cNode , data.ctrlFlowGraph ,
					old , data.logger );
		} else {
			BCFGCondEnter_( stack , cNode , data.ctrlFlowGraph ,
					data.logger );
		}
		return true;
	}

	// Calls also break the flow
	if ( nt == A_Node::OP_CALL && !exit ) {
		T_CallInstrNode& ci{ *dynamic_cast< T_CallInstrNode* >( iptr ) };
		data.logger( [&](){
			T_StringBuilder sb;
			auto const& node{ *data.ctrlFlowGraph[ *cNode ] };
			sb << "Call to " << ci.id( ) << ", block had "
				<< ( node.instructions
					? node.instructions->count
					: 0 )
				<< " instructions";
			return sb;
		} , 6 );

		auto& cs{ data.callSites.addNew( ) };
		cs.name = ci.id( );
		cs.callBlock = *cNode;
		cNode = cs.retBlock = data.ctrlFlowGraph.add( M_NEWNODE_( ) );
		return true;
	}

	// Condition case nodes: create new basic block, add to stack's list
	if ( nt == A_Node::TN_CASE || nt == A_Node::TN_DEFAULT ) {
		if ( exit ) {
			data.logger( [&](){
				T_StringBuilder sb;
				auto const& node{ *data.ctrlFlowGraph[ *cNode ] };
				sb << "Case block added ("
					<< ( node.instructions
						? node.instructions->count
						: 0 )
					<< " instructions)";
				return sb;
			} , 6 );
			cNode.clear( );
		} else {
			stack.last( ).hasDefault = stack.last( ).hasDefault
				|| ( nt == A_Node::TN_DEFAULT );
			cNode = data.ctrlFlowGraph.add( M_NEWNODE_( ) );
			stack.last( ).caseBlocks.add( *cNode );
		}
	}

	return !dynamic_cast< A_ExpressionNode* >( &node );
}

/*----------------------------------------------------------------------------*/

inline void BCFGHandleCalls_(
		T_OptData& data ) noexcept
{
	// Add fake call sites for *init* and *frame*
	{
		auto& cs{ data.callSites.addNew( ) };
		cs.callBlock = T_OptData::CFG_ENTER;
		cs.retBlock = T_OptData::CFG_MAINLOOP;
		cs.name = "*init*";
	}
	{
		auto& cs{ data.callSites.addNew( ) };
		cs.callBlock = T_OptData::CFG_MAINLOOP;
		cs.retBlock = T_OptData::CFG_MAINLOOP;
		cs.name = "*frame*";
	}

	// Handle calls
	constexpr auto tCall{ T_OptData::T_CtrlFlowEdge::CALL };
	constexpr auto tRet{ T_OptData::T_CtrlFlowEdge::RET };
	constexpr auto tBypass{ T_OptData::T_CtrlFlowEdge::BYPASS };
	for ( auto const& cs : data.callSites ) {
		auto const* frec{ data.cfgFunctions.get( cs.name ) };
		assert( frec );


		const auto nExit{ frec->first + frec->count - 1 };
		auto& bCall{ *data.ctrlFlowGraph[ cs.callBlock ] };
		auto& bRet{ *data.ctrlFlowGraph[ cs.retBlock ] };
		auto& bEntry{ *data.ctrlFlowGraph[ frec->first ] };
		auto& bExit{ *data.ctrlFlowGraph[ nExit ] };

		// Call
		bEntry.inbound.addNew( cs.callBlock , tCall );
		bCall.outbound.addNew( frec->first , tCall );

		// Return
		bExit.outbound.addNew( cs.retBlock , tRet );
		bRet.inbound.addNew( nExit , tRet );

		// Call bypass
		bCall.outbound.addNew( cs.retBlock , tBypass );
		bRet.inbound.addNew( cs.callBlock , tBypass );
	}
}

/*----------------------------------------------------------------------------*/

inline T_StringBuilder BCFGDumpAll_(
		T_OptData const& data ) noexcept
{
	T_StringBuilder dump;

	int i{ 0 };
	dump << "Control flow graph dump\n";
	for ( auto const& p : data.ctrlFlowGraph ) {
		auto const& e{ *p };
		dump << "\nNode " << i++ << "\n\t";
		if ( e.instructions ) {
			dump << e.instructions->count
				<< " instruction(s) at index "
				<< e.instructions->first;
		} else {
			dump << "No instructions";
		}
		dump << "\n\tInbound:";
		{
			const auto ni{ e.inbound.size( ) };
			if ( ni == 0 ) {
				dump << " NONE";
			}
			for ( auto idx = 0u ; idx < ni ; idx ++ ) {
				dump << ' ' << e.inbound[ idx ];
			}
		}
		dump << "\n\tOutbound:";
		{
			const auto no{ e.outbound.size( ) };
			if ( no == 0 ) {
				dump << " NONE";
			}
			for ( auto idx = 0u ; idx < no ; idx ++ ) {
				dump << ' ' << e.outbound[ idx ];
			}
		}
		dump << '\n';
	}
	dump << '\n';

	return dump;
}


} // namespace <anon>
/*----------------------------------------------------------------------------*/

void T_OptData::buildControlFlowGraph(
		T_OpsParserOutput& program ) noexcept
{
	if ( state & E_StateItem::CFG ) {
		return;
	}
	numberInstructions( program );
	M_LOGSTR_( "Building control flow graph" , 4 );

	// Keep the old array, we'll reuse its contents
	T_Array< P_CtrlFlowNode > old{ std::move( ctrlFlowGraph ) };
	callSites.clear( );
	cfgFunctions.clear( );

	// Create special nodes
	M_ADDNEW_( );
	M_ADDNEW_( );
	M_ADDNEW_( );
	M_NODE_( CFG_MAINLOOP )->outbound.add( CFG_END );
	M_NODE_( CFG_END )->inbound.add( CFG_MAINLOOP );

	// Generate control flow graph for each function
	T_BCFGStack_ stack;
	T_Optional< uint32_t > cNode{ };
	visitor.visit( program.root , [&]( A_Node& node , const bool exit ) {
		return BCFGVisitor_( node , exit , *this ,
				stack , cNode , old );
	} );
	assert( cfgFunctions.contains( "*init*" )
			&& cfgFunctions.contains( "*frame*" ) );

	BCFGHandleCalls_( *this );
	logger( [this](){
		return BCFGDumpAll_( *this );
	} , 6 );

	state = state | E_StateItem::CFG;
}

#undef M_ADDNEW_
#undef M_NEWNODE_
#undef M_NODE_


/*= T_OptData - USE/DEFINE CHAINS ============================================*/
namespace {

void BUDCAddRecord_(
		A_Node& n ,
		T_String const& id ,
		const bool use ,
		T_OptData& od ,
		T_RootNode& root ,
		T_OptData::T_VarId const* extVarId = nullptr ) noexcept
{

	// Find instruction and function index
	A_FuncNode* func{ nullptr };
	T_Optional< uint32_t > instrId;
	A_Node* pn{ &n };
	while ( pn ) {
		auto* const asInstr{ dynamic_cast< A_InstructionNode* >( pn ) };
		func = dynamic_cast< A_FuncNode* >( pn );
		if ( !instrId && asInstr ) {
			instrId = od.indexOf( *asInstr );
		} else if ( func ) {
			break;
		}
		pn = &pn->parent( );
	}
	assert( func && instrId );

	// Generate the identifier
	const T_OptData::T_VarId varId{ extVarId ? *extVarId : [&]() {
		auto const& n{ id };
		if ( func->hasLocal( id ) ) {
			return T_OptData::T_VarId{ n , func->name( ) ,
				func->isArgument( id ) };
		}
		return T_OptData::T_VarId{ n };
	} () };

	// Access or create the record
	auto* const varRec{ [&]() {
		auto* const x{ od.varUDChains.get( varId ) };
		if ( x ) {
			return x;
		}
		od.varUDChains.add( T_OptData::T_VarUseDefine{ varId } );
		return od.varUDChains.get( varId );
	} () };
	assert( varRec );

	// Add use/define record
	auto& udRec{ use ? varRec->uses.addNew( ) : varRec->defines.addNew( ) };
	udRec.node = *instrId;
	udRec.fnIndex = root.functionIndex( func->name( ) );

	od.logger( [&](){
		T_StringBuilder sb;
		sb << ( use ? "use " : "def " ) << varId.name << "(#"
			<< ( use ? varRec->uses.size( ) : varRec->defines.size( ) ) - 1
			<< ") at " << n.location( ) << " (";
		if ( varId.type == T_OptData::E_UDVarType::GLOBAL ) {
			sb << "global";
		} else {
			if ( varId.type == T_OptData::E_UDVarType::LOCAL ) {
				sb << "local";
			} else {
				sb << "argument";
			}
			sb << " of " << varId.owner;
		}
		sb << "), instr #" << *instrId;
		return sb;
	} , 6 );
}

void BUDCVisitor_(
		T_RootNode& root ,
		T_OptData& od ,
		A_Node& n )
{
	switch ( n.type( ) ) {

	    default: break;

	    case A_Node::EXPR_ID: {
		auto const& id{ dynamic_cast< T_IdentifierExprNode& >( n ).id( ) };
		if ( id != "width" && id != "height" && id != "time" ) {
			BUDCAddRecord_( n , id , true , od , root );
		}
		break;
	    }

	    case A_Node::OP_UNIFORMS:
		BUDCAddRecord_( n ,
			dynamic_cast< T_UniformsInstrNode& >( n ).progId( ) ,
			true , od , root );
		break;

	    case A_Node::OP_USE_TEXTURE:
		BUDCAddRecord_( n ,
			dynamic_cast< T_UseTextureInstrNode& >( n ).samplerId( ) ,
			true , od , root );
		// fallthrough
	    case A_Node::OP_USE_PROGRAM:
	    case A_Node::OP_USE_PIPELINE:
	    case A_Node::OP_USE_FRAMEBUFFER:
		BUDCAddRecord_( n ,
			dynamic_cast< T_UseInstrNode& >( n ).id( ) ,
			true , od , root );
		break;

	    case A_Node::OP_IMAGE:
		BUDCAddRecord_( n ,
			dynamic_cast< T_ImageInstrNode& >( n ).id( ) ,
			true , od , root );
		break;

	    case A_Node::OP_PIPELINE: {
		auto& pln{ dynamic_cast< T_PipelineInstrNode& >( n ) };
		BUDCAddRecord_( n , pln.id( ) , false , od , root );
		const auto np{ pln.size( ) };
		for ( auto i = 0u ; i < np ; i ++ ) {
			BUDCAddRecord_( n , pln.program( i ) ,
				true , od , root );
		}
		break;
	    }

	    case A_Node::TN_FBATT:
		BUDCAddRecord_( n , dynamic_cast< T_FramebufferInstrNode::T_Attachment& >( n ).id( ) ,
			true , od , root );
		break;

	    case A_Node::OP_FRAMEBUFFER:
	    case A_Node::OP_TEXTURE:
	    case A_Node::OP_SAMPLER:
	    case A_Node::OP_PROGRAM:
		BUDCAddRecord_( n , dynamic_cast< A_ResourceDefInstrNode& >( n ).id( ) ,
			false , od , root );
		break;

	    case A_Node::OP_SET:
		BUDCAddRecord_( n , dynamic_cast< T_SetInstrNode& >( n ).id( ) ,
			false , od , root );
		break;

	    case A_Node::OP_CALL: {
		auto& cn{ dynamic_cast< T_CallInstrNode& >( n ) };
		auto& callee{ root.function(
				root.functionIndex( cn.id( ) ) ) };
		const auto nlocs{ callee.locals( ) };
		for ( auto i = 0u ; i < nlocs ; i ++ ) {
			auto const& name{ callee.getLocalName( i ) };
			if ( !callee.isArgument( name ) ) {
				continue;
			}
			const T_OptData::T_VarId vid{ name , callee.name( ) , true };
			BUDCAddRecord_( n , name , false , od ,
					root , &vid );
		}
		break;
	    }

	}
}

/*----------------------------------------------------------------------------*/

struct T_UDEntry_
{
	uint32_t entry;
	bool isUse;
	uint32_t index;
};

using T_UDEPerInstr_ = T_KeyValueTable< uint32_t , T_AutoArray< T_UDEntry_ , 8 > >;

template< uint32_t S >
void BUDCAddEntries_(
		T_UDEPerInstr_& out ,
		const uint32_t mainEntry ,
		const bool isUse ,
		T_AutoArray< T_OptData::T_VarUDRecord , S > const& entries ) noexcept
{
	const auto na{ entries.size( ) };
	for ( auto j = 0u ; j < na ; j ++ ) {
		auto const& use{ entries[ j ] };
		auto* rec{ out.get( use.node ) };
		if ( !rec ) {
			out.add( use.node , T_AutoArray< T_UDEntry_ , 8 >{ } );
			rec = out.get( use.node );
		}
		assert( rec );
		
		auto& ne{ rec->addNew( ) };
		ne.entry = mainEntry;
		ne.isUse = isUse;
		ne.index = j;
	}
}

/*----------------------------------------------------------------------------*/

void BUDCLink_(
		T_OptData::T_VarUseDefine&	var ,
		const uint32_t			def ,
		const uint32_t			use ,
		F_OPLogger const&		logger
		) noexcept
{
	var.uses[ use ].refs.add( def );
	if ( def != T_HashIndex::INVALID_INDEX ) {
		var.defines[ def ].refs.add( use );
	}
	logger( [&](){
		T_StringBuilder sb;
		sb << var.var.name << " (";
		switch ( var.var.type ) {
		    case T_OptData::E_UDVarType::GLOBAL:
			sb << "global";
			break;
		    case T_OptData::E_UDVarType::ARGUMENT:
			sb << "argument of " << var.var.owner;
			break;
		    case T_OptData::E_UDVarType::LOCAL:
			sb << "local variable of " << var.var.owner;
			break;
		}
		sb << ") ";
		if ( def == T_HashIndex::INVALID_INDEX ) {
			sb << "UNDEFINED";
		} else {
			sb << "DEF " << def;
		}
		sb << " USE " << use;

		return sb;
	} , 6 );
}

/*----------------------------------------------------------------------------*/

struct T_BUDCDefSet_
{
	T_Set< uint32_t > set{ UseTag< ArrayBacked< 4 > >( ) };
	T_AutoArray< uint8_t , 1 > bitmap;

	explicit T_BUDCDefSet_( const uint32_t max ) noexcept
	{
		const auto nBytesRaw{ max >> 3 };
		const auto nBytes{ nBytesRaw + ( ( max & 7 ) != 0 ? 1 : 0 ) };
		for ( auto i = 0u ; i < nBytes ; i ++ ) {
			bitmap.add( 0 );
		}
	}

	void clear( ) noexcept
	{
		set.clear( );
		for ( auto i = 0u ; i < bitmap.size( ) ; i ++ ) {
			bitmap[ i ] = 0;
		}
	}

	void add( const uint32_t item ) noexcept
	{
		const auto byte{ item >> 3 };
		const auto bit{ item & 7 };
		const auto mask{ 1 << bit };
		if ( ( bitmap[ byte ] & mask ) == 0 ) {
			bitmap[ byte ] |= mask;
			set.add( item );
		}
	}

	bool operator ==( T_BUDCDefSet_ const& other ) noexcept
	{
		if ( bitmap.size( ) != other.bitmap.size( ) ) {
			return false;
		}
		for ( auto i = 0u ; i < bitmap.size( ) ; i ++ ) {
			if ( bitmap[ i ] != other.bitmap[ i ] ) {
				return false;
			}
		}
		return true;
	}
};

void BUDCWalkGraph_(
		T_OptData&			data ,
		T_OptData::T_VarUseDefine&	var ,
		T_UDEPerInstr_&			udPerInstr ,
		T_Optional< uint32_t >		fnIndex
		) noexcept
{
	T_Set< uint32_t > changed{ UseTag< ArrayBacked< 16 > >( ) }; // FIXME alloc
	T_Array< T_BUDCDefSet_ > defines; // FIXME alloc
	defines.resize( data.ctrlFlowGraph.size( ) , T_BUDCDefSet_{ 0 } );

	data.logger( [&]() {
		T_StringBuilder sb;
		sb << "Walking graph for " << var.var.name << " (";
		switch ( var.var.type ) {
		    case T_OptData::E_UDVarType::GLOBAL:
			sb << "global";
			break;
		    case T_OptData::E_UDVarType::ARGUMENT:
			sb << "argument of " << var.var.owner;
			break;
		    case T_OptData::E_UDVarType::LOCAL:
			sb << "local variable of " << var.var.owner;
			break;
		}
		sb << ')';
		return sb;
	} , 5 );

	auto const* const fn{ fnIndex
		? &data.cfgFunctions[ *fnIndex ]
		: nullptr };
	auto const nFirst{ fn ? fn->first : 0u };
	auto const nEnd{ fn ? ( fn->first + fn->count )
		: data.ctrlFlowGraph.size( ) };
	data.logger( [=](){
		T_StringBuilder sb{ "Nodes " };
		sb << nFirst << " ... " << ( nEnd - 1 );;
		return sb;
	} , 8 );
	changed.add( nFirst );
	for ( auto i = nEnd ; i > nFirst + 1 ; i -- ) {
		changed.add( i - 1 );
	}

	while ( changed.size( ) ) {
		const auto node{ changed[ 0 ] };
		data.logger( [=](){
			T_StringBuilder sb{ "Checking node " };
			sb << node;
			return sb;
		} , 8 );
		assert( node >= nFirst && node < nEnd );
		auto const& cn{ *data.ctrlFlowGraph[ node ] };
		changed.remove( node );

		// Assemble the set of possible definitions at the start of
		// the block.
		T_BUDCDefSet_ sInput{ var.defines.size( ) };
		const auto nIn{ cn.inbound.size( ) };
		for ( auto i = 0u ; i < nIn ; i ++ ) {
			auto const& ib{ cn.inbound[ i ] };
			// Make sure the inbound link type is right (FLOW always
			// is, BYPASS works in functions, CALL/RET at the global
			// level)
			if ( ib.type != ib.FLOW && (
					( fnIndex && ib.type != ib.BYPASS )
					|| ( !fnIndex && ib.type == ib.BYPASS ) ) ) {
				continue;
			}

			// Get defines from node
			auto const& sNode{ defines[ ib.target ] };
			const auto nIbIn{ sNode.set.size( ) };
			for ( auto j = 0u ; j < nIbIn ; j ++ ) {
				sInput.add( sNode.set[ j ] );
			}
		}

		if ( cn.instructions ) {
			// Check for defines in the instructions
			const auto is{ cn.instructions->first };
			const auto ie{ is + cn.instructions->count };
			for ( auto ii = is ; ii < ie ; ii ++ ) {
				auto const* const irec{ udPerInstr.get( ii ) };
				if ( !irec ) {
					continue;
				}
				const auto nrec{ irec->size( ) };

				for ( auto j = 0u ; j < nrec ; j ++ ) {
					auto const& rec{ (*irec)[ j ] };
					if ( rec.isUse || data.varUDChains[ rec.entry ].var != var.var ) {
						continue;
					}
					sInput.clear( );
					sInput.add( rec.index );
				}
			}
		}

		auto& sOut{ defines[ node ] };
		if ( sOut == sInput ) {
			continue;
		}
		data.logger( [&](){
			T_StringBuilder sb;
			sb << "Node " << node << ", setting output to {";
			for ( auto i = 0u ; i < sInput.set.size( ) ; i ++ ) {
				if ( i ) {
					sb << " ,";
				}
				sb << ' ' << sInput.set[ i ];
			}
			sb << " }";
			return sb;
		} , 7 );
		sOut = sInput;

		// Add the node's successors into the changed set
		const auto nOut{ cn.outbound.size( ) };
		for ( auto i = 0u ; i < nOut ; i ++ ) {
			auto const& ob{ cn.outbound[ i ] };
			// Make sure the outbound link type is right (FLOW
			// always is, BYPASS works in functions, CALL/RET at
			// the global level)
			if ( ob.type != ob.FLOW && (
					( fnIndex && ob.type != ob.BYPASS )
					|| ( !fnIndex && ob.type == ob.BYPASS ) ) ) {
				continue;
			}
			changed.add( ob.target );
		}
	}

	// Now we need to identify the blocks which contain var uses
	T_Set< uint32_t > blocks{ UseTag< ArrayBacked< 16 > >( ) }; // FIXME alloc
	for ( auto i = 0u ; i < var.uses.size( ) ; i ++ ) {
		const auto useInstr{ var.uses[ i ].node };
		for ( auto j = nFirst ; j < nEnd ; j ++ ) {
			auto const& block{ *data.ctrlFlowGraph[ j ] };
			if ( !block.instructions ) {
				continue;
			}
			const auto f{ block.instructions->first };
			const auto e{ block.instructions->count + f - 1 };
			if ( f <= useInstr && e >= useInstr ) {
				blocks.add( j );
				break;
			}
		}
	}

	// Now go through all the blocks instruction by instruction, updating
	// the active definition set if a definition is found, and associating
	// defines to uses.
	for ( auto i = 0u ; i < blocks.size( ) ; i ++ ) {
		const auto bid{ blocks[ i ] };
		auto const& cb{ *data.ctrlFlowGraph[ bid ] };
		T_BUDCDefSet_ defs{ defines[ bid ] };
		assert( cb.instructions );

		const auto is{ cb.instructions->first };
		const auto ie{ is + cb.instructions->count };
		for ( auto ii = is ; ii < ie ; ii ++ ) {
			auto const* const irec{ udPerInstr.get( ii ) };
			if ( !irec ) {
				continue;
			}
			const auto nrec{ irec->size( ) };

			// Handle uses first
			for ( auto j = 0u ; j < nrec ; j ++ ) {
				auto const& rec{ (*irec)[ j ] };
				auto const& ud{ data.varUDChains[ rec.entry ] };
				if ( !rec.isUse || ud.var != var.var ) {
					continue;
				}
				for ( auto di = 0u ; di < defs.set.size( ) ; di ++ ) {
					BUDCLink_( var , defs.set[ di ] ,
							rec.index , data.logger );
				}
				if ( !defs.set.size( ) ) {
					// FIXME: add warning
					BUDCLink_( var , T_HashIndex::INVALID_INDEX ,
							rec.index , data.logger );
				}
			}

			// Update the set
			for ( auto j = 0u ; j < nrec ; j ++ ) {
				auto const& rec{ (*irec)[ j ] };
				auto const& ud{ data.varUDChains[ rec.entry ] };
				if ( rec.isUse || ud.var != var.var ) {
					continue;
				}
				defs.clear( );
				defs.add( rec.index );
				break;
			}
		}
	}
}


} // namespace <anon>

/*----------------------------------------------------------------------------*/

void T_OptData::buildUseDefineChains(
		T_OpsParserOutput& program ) noexcept
{
	if ( state & E_StateItem::UDCHAINS ) {
		return;
	}
	buildControlFlowGraph( program );

	M_LOGSTR_( "Building use/define chains" , 4 );
	varUDChains.clear( );

	// Find all definitions and uses, add them to the table
	visitor.visit( program.root , [&]( auto& n , const bool exit ) {
		if ( !exit ) {
			BUDCVisitor_( program.root , *this , n );
		}
		return true;
	} );

	// Build a per-instruction table of all variable uses/defines that were
	// identified
	T_UDEPerInstr_ udPerInstr;
	auto const& udcEntries{ varUDChains.values( ) };
	const auto n{ udcEntries.size( ) };
	for ( auto i = 0u ; i < n ; i ++ ) {
		auto const& r{ udcEntries[ i ] };
		BUDCAddEntries_( udPerInstr , i , true , r.uses );
		BUDCAddEntries_( udPerInstr , i , false , r.defines );
	}

	// Proceed for each symbol
	for ( auto& sym : varUDChains.values( ) ) {
		switch ( sym.var.type ) {
		    case T_OptData::E_UDVarType::GLOBAL:
			BUDCWalkGraph_( *this , sym , udPerInstr , {} );
			break;

		    case T_OptData::E_UDVarType::ARGUMENT: {
			const auto nDefs{ sym.defines.size( ) };
			const auto nUses{ sym.uses.size( ) };
			for ( auto i = 0u ; i < nDefs ; i ++ ) {
				for ( auto j = 0u ; j < nUses ; j ++ ) {
					BUDCLink_( sym , i , j , logger );
				}
			}
			break;
		    }

		    case T_OptData::E_UDVarType::LOCAL: {
			assert( cfgFunctions.contains( sym.var.owner ) );
			BUDCWalkGraph_( *this , sym , udPerInstr ,
					cfgFunctions.indexOf( sym.var.owner ) );
			break;
		    }
		}
	}

	state = state | E_StateItem::UDCHAINS;
}


/*============================================================================*/

#undef M_LOGSTR_
#define M_LOGSTR_( S , L ) \
	oData.logger( [](){ return T_StringBuilder{ S }; } , L )


/*= CONSTANT FOLDING =========================================================*/

namespace {

struct T_ConstantFolder_
{
	T_ConstantFolder_( T_OptData& data ) noexcept
		: oData{ data }
	{}

	// Result
	bool didFold{ false };

	bool operator()( A_Node& node , bool exit ) noexcept;

    private:
	T_OptData& oData;

	template<
		typename T
	> void handleParentNode(
			A_Node& node ,
			std::function< A_ExpressionNode&( T& ) > get ,
			std::function< void( T& , P_ExpressionNode ) > set ) noexcept;

	P_ExpressionNode checkExpression(
			A_ExpressionNode& node ) noexcept;

	// Handle identifiers. If the size is fixed and the identifier is
	// either width or height, replace it with the appropriate value.
	P_ExpressionNode doIdExpr(
			T_IdentifierExprNode& node ) noexcept;

	// Handle reads from inputs. If there's a curve and it is a constant,
	// or if there's no curve and only one default value, then the
	// expression is constant.
	P_ExpressionNode doInputExpr(
			T_InputExprNode& node ) noexcept;

	// Transform an unary operator applied to a constant into a constant.
	P_ExpressionNode doUnaryOp(
			T_UnaryOperatorNode& node ,
			double value ) const noexcept;

	// Transform a binary operator applied to a constant into a constant.
	P_ExpressionNode doBinaryOp(
			T_BinaryOperatorNode& node ,
			double left ,
			double right ) const noexcept;
};

/*----------------------------------------------------------------------------*/

bool T_ConstantFolder_::operator()(
		A_Node& node ,
		const bool exit ) noexcept
{
	if ( exit ) {
		return true;
	}

	switch ( node.type( ) ) {

	    case A_Node::TN_ARG:
		handleParentNode< T_ArgumentNode >(
			node ,
			[]( auto& n ) -> A_ExpressionNode& { return n.expression( ); } ,
			[]( auto& n , P_ExpressionNode e ) { n.expression( std::move( e ) ); }
		);
		return false;

	    case A_Node::TN_CONDITION:
		handleParentNode< T_CondInstrNode::T_Expression >( node ,
			[]( auto& n ) -> A_ExpressionNode& { return n.expression( ); } ,
			[]( auto& n , P_ExpressionNode e ) { n.expression( std::move( e ) ); }
		);
		return false;

	    case A_Node::OP_SET:
		handleParentNode< T_SetInstrNode >( node ,
			[]( auto& n ) -> A_ExpressionNode& { return n.expression( ); } ,
			[]( auto& n , P_ExpressionNode e ) { n.setExpression( std::move( e ) ); } );
		return false;

	    default:
		return true;
	}
}

/*----------------------------------------------------------------------------*/

template<
	typename T
> void T_ConstantFolder_::handleParentNode(
		A_Node& n ,
		std::function< A_ExpressionNode&( T& ) > get ,
		std::function< void( T& , P_ExpressionNode ) > set ) noexcept
{
	auto& node{ (T&) n };
	auto& child{ get( node ) };
	auto r{ checkExpression( child ) };
	if ( r ) {
		oData.logger( [&]() {
			T_StringBuilder sb;
			sb << "substituting node at " << child.location( );
			return sb;
		} , 3 );
		r->location( ) = node.location( );
		set( node , std::move( r ) );
		didFold = true;
	}
}

P_ExpressionNode T_ConstantFolder_::checkExpression(
		A_ExpressionNode& node ) noexcept
{
	// Already a constant
	if ( node.type( ) == A_Node::EXPR_CONST ) {
		return {};
	}

	// Replace $width/$height with value if fixedSize
	if ( node.type( ) == A_Node::EXPR_ID ) {
		return doIdExpr( (T_IdentifierExprNode&) node );
	}

	// Replace inputs with value if no curve/constant curve
	if ( node.type( ) == A_Node::EXPR_INPUT ) {
		return doInputExpr( (T_InputExprNode&) node );
	}

	// Replace UnOp( Cnst ) with result
	auto* const asUnary{ dynamic_cast< T_UnaryOperatorNode* >( &node ) };
	if ( asUnary ) {
		handleParentNode< T_UnaryOperatorNode >( *asUnary ,
			[]( auto& n ) -> A_ExpressionNode& { return n.argument( ); } ,
			[]( auto& n , P_ExpressionNode e ) { n.setArgument( std::move( e ) ); } );
		if ( asUnary->argument( ).type( ) == A_Node::EXPR_CONST ) {
			auto const& cn{ (T_ConstantExprNode const&) asUnary->argument( ) };
			return doUnaryOp( *asUnary , cn.floatValue( ) );
		}
		return {};
	}

	// Replace BinOp( Cnst , Cnst ) with result
	auto* const asBinary{ dynamic_cast< T_BinaryOperatorNode* >( &node ) };
	assert( asBinary && "Missing support for some expr subtype" );
	handleParentNode< T_BinaryOperatorNode >( *asBinary ,
		[]( auto& n ) -> A_ExpressionNode& { return n.left( ); } ,
		[]( auto& n , P_ExpressionNode e ) { n.setLeft( std::move( e ) ); } );
	handleParentNode< T_BinaryOperatorNode >( *asBinary ,
		[]( auto& n ) -> A_ExpressionNode& { return n.right( ); } ,
		[]( auto& n , P_ExpressionNode e ) { n.setRight( std::move( e ) ); } );

	if ( asBinary->left( ).type( ) == A_Node::EXPR_CONST
			&& asBinary->right( ).type( ) == A_Node::EXPR_CONST ) {
		auto const& l{ (T_ConstantExprNode const&) asBinary->left( ) };
		auto const& r{ (T_ConstantExprNode const&) asBinary->right( ) };
		return doBinaryOp( *asBinary , l.floatValue( ) , r.floatValue( ) );
	}
	return {};
}

/*----------------------------------------------------------------------------*/

P_ExpressionNode T_ConstantFolder_::doInputExpr(
		T_InputExprNode& node ) noexcept
{
	if ( !oData.curves ) {
		return {};
	}

	auto const* const curve{ oData.curves->curves.get( node.id( ) ) };
	if ( curve ) {
		// Curve present, check if it's constant
		const auto cval{ curve->isConstant( ) };
		if ( !cval ) {
			return {};
		}
		return NewOwned< T_ConstantExprNode >( node.parent( ) , *cval );
	}

	auto const* const dva{ oData.inputDecls.get( node.id( ) ) };
	assert( dva );
	if ( dva->size( ) == 1 ) {
		// If there's only one default value, that's a constant.
		return NewOwned< T_ConstantExprNode >( node.parent( ) ,
				(*dva)[ 0 ].value );
	}
	return {};
}

P_ExpressionNode T_ConstantFolder_::doIdExpr(
		T_IdentifierExprNode& node ) noexcept
{
	if ( !oData.fixedSize ) {
		return {};
	}

	if ( node.id( ) == "width" ) {
		M_LOGSTR_( "replacing $width with fixed width" , 3 );
		return NewOwned< T_ConstantExprNode >( node.parent( ) ,
				double( oData.fixedSize->first ) );
	}

	if ( node.id( ) == "height" ) {
		M_LOGSTR_( "replacing $height with fixed height" , 3 );
		return NewOwned< T_ConstantExprNode >( node.parent( ) ,
				float( oData.fixedSize->second ) );
	}

	return {};
}

P_ExpressionNode T_ConstantFolder_::doUnaryOp(
		T_UnaryOperatorNode& node ,
		const double value ) const noexcept
{
	const double rVal{ [this]( auto& node , const auto value ) {
		switch ( node.op( ) ) {

		    case T_UnaryOperatorNode::NEG:
			return -value;

		    case T_UnaryOperatorNode::NOT:
			return value ? 0. : 1.;

		    case T_UnaryOperatorNode::INV:
			if ( value == 0 ) {
				oData.errors.addNew( "math - 1/x, x=0" , node.location( ) );
				return 0.;
			}
			return 1. / value;

		    case T_UnaryOperatorNode::COS:
			return cos( value );

		    case T_UnaryOperatorNode::SIN:
			return sin( value );

		    case T_UnaryOperatorNode::TAN:
			if ( fabs( value - M_PI / 2 ) <= 1e-6 ) {
				oData.errors.addNew( "math - tan(x), x=~PI/2" ,
						node.location( ) , E_SRDErrorType::WARNING );
			}
			return tan( value );

		    case T_UnaryOperatorNode::SQRT:
			if ( value < 0 ) {
				oData.errors.addNew( "math - sqrt(x), x<0" , node.location( ) );
				return 0.;
			}
			return sqrt( value );

		    case T_UnaryOperatorNode::LN:
			if ( value <= 0 ) {
				oData.errors.addNew( "math - ln(x), x<=0" , node.location( ) );
				return 0.;
			}
			return log( value );

		    case T_UnaryOperatorNode::EXP:
			return exp( value );
		}

		fprintf( stderr , "invalid operator %d\n" , int( node.op( ) ) );
		std::abort( );
	}( node , value ) };

	return NewOwned< T_ConstantExprNode >( node.parent( ) , rVal );
}

P_ExpressionNode T_ConstantFolder_::doBinaryOp(
		T_BinaryOperatorNode& node ,
		const double left ,
		const double right ) const noexcept
{
	const double rVal{ [this]( auto& node , const auto l , const auto r ) {
		switch ( node.op( ) ) {

		    case T_BinaryOperatorNode::ADD:
			return l + r;

		    case T_BinaryOperatorNode::SUB:
			return l - r;

		    case T_BinaryOperatorNode::MUL:
			return l * r;

		    case T_BinaryOperatorNode::DIV:
			if ( r == 0 ) {
				oData.errors.addNew( "math - l/r, r=0" , node.location( ) );
				return 0.;
			}
			return l / r;

		    case T_BinaryOperatorNode::POW:
			if ( l == 0 && r == 0 ) {
				oData.errors.addNew( "math - l^r, l=r=0" , node.location( ) );
				return 0.;
			}
			if ( l == 0 && r < 0 ) {
				oData.errors.addNew( "math - l^r, l=0, r<0" , node.location( ) );
				return 0.;
			}
			if ( l < 0 && fmod( r , 1. ) != 0. ) {
				oData.errors.addNew( "math - l^r, l<0, r not integer" , node.location( ) );
				return 0.;
			}
			return pow( l , r );

		    case T_BinaryOperatorNode::CMP_EQ: return ( l == r ) ? 1. : 0.;
		    case T_BinaryOperatorNode::CMP_NE: return ( l != r ) ? 1. : 0.;
		    case T_BinaryOperatorNode::CMP_GT: return ( l >  r ) ? 1. : 0.;
		    case T_BinaryOperatorNode::CMP_GE: return ( l >= r ) ? 1. : 0.;
		    case T_BinaryOperatorNode::CMP_LT: return ( l <  r ) ? 1. : 0.;
		    case T_BinaryOperatorNode::CMP_LE: return ( l <= r ) ? 1. : 0.;
		}
		fprintf( stderr , "invalid operator %d\n" , int( node.op( ) ) );
		std::abort( );
	}( node , left , right ) };

	return NewOwned< T_ConstantExprNode >( node.parent( ) , rVal );
}

} // namespace <anon>

/*----------------------------------------------------------------------------*/


bool opopt::FoldConstants(
		T_OpsParserOutput& program ,
		T_OptData& oData ) noexcept
{
	T_ConstantFolder_ folder{ oData };
	M_LOGSTR_( "... Folding constants" , 2 );
	if ( oData.curves ) {
		oData.findInputDecls( program );
	}
	oData.visitor.visit( program.root , [&]( auto& n , auto x ) {
		return folder( n , x );
	} );
	oData.logger( [&]() {
		T_StringBuilder sb{ "...... " };
		sb << ( folder.didFold
				? "Some constants were folded"
				: "No constants were folded" );
		return sb;
	} , 2 );
	return folder.didFold;
}


/*= CONSTANT PROPAGATION =====================================================*/
namespace {

void CPReplaceWithConstant_(
		A_InstructionNode&		instruction ,
		T_OptData::T_VarId const&	var ,
		const double			value ,
		T_OptData&			oData
		) noexcept
{
	oData.visitor.visit( instruction , [&]( A_Node& node , const bool exit ) {
		if ( !exit || node.type( ) != A_Node::EXPR_ID ) {
			return node.type( ) != A_Node::ILIST;
		}

		auto& eid{ (T_IdentifierExprNode&) node };
		if ( eid.id( ) != var.name ) {
			return true;
		}

		auto& p{ eid.parent( ) };
		T_OwnPtr< A_Node > replacement{
			NewOwned< T_ConstantExprNode >( p , value )
		};
		replacement->location( ) = eid.location( );
		p.replace( eid , replacement );
		oData.logger( [&]() {
			T_StringBuilder sb;
			sb << "...... Propagated constant from " << var.name
				<< " at " << instruction.location( )
				<< " (value " << value << ")";
			return sb;
		} , 3 );
		return true;
	} );
}

} // namespace <anon>

bool opopt::PropagateConstants(
		T_OpsParserOutput& program ,
		T_OptData& oData ) noexcept
{
	oData.buildUseDefineChains( program );
	M_LOGSTR_( "... Propagating constants" , 2 );

	T_Set< uint32_t > cDefs{ UseTag< ArrayBacked< 16 > >( ) };
	T_AutoArray< double , 16 > cValues;
	bool changesMade{ false };
	for ( auto& udc : oData.varUDChains.values( ) ) {
		cDefs.clear( );
		cValues.clear( );

		// Find all constant definitions
		const auto nDefs{ udc.defines.size( ) };
		for ( auto i = 0u ; i < nDefs ; i ++ ) {
			auto const& def{ udc.defines[ i ] };
			auto const* const ri{ oData.instructions[ def.node ].node };
			if ( ri->type( ) == A_Node::OP_SET ) {
				auto const* const si{
					dynamic_cast< T_SetInstrNode const* >( ri ) };
				if ( si->expression( ).type( ) == A_Node::EXPR_CONST ) {
					cDefs.add( i );
					cValues.add( ( (T_ConstantExprNode&) si->expression( ) ).floatValue( ) );
				}
			} else {
				// Instruction is not supported, exit
				assert( cDefs.size( ) == 0 );
				break;
			}
		}
		assert( cValues.size( ) == cDefs.size( ) );
		if ( cValues.empty( ) ) {
			continue;
		}

		for ( auto i = 0u ; i < udc.uses.size( ) ; ) {
			auto const& use{ udc.uses[ i ] };
			const auto nRefs{ use.refs.size( ) };
			T_Optional< double > repVal{ };
			for ( auto j = 0u ; j < nRefs ; j ++ ) {
				const auto dIdx{ cDefs.indexOf( use.refs[ j ] ) };
				if ( dIdx == -1 ) {
					repVal.clear( );
					break;
				}
				if ( !repVal ) {
					repVal = cValues[ dIdx ];
				} else if ( *repVal != cValues[ dIdx ] ) {
					repVal.clear( );
					break;
				}
			}
			if ( !repVal ) {
				i ++;
				continue;
			}

			CPReplaceWithConstant_( *oData.instructions[ use.node ].node ,
					udc.var , *repVal , oData );

			for ( auto j = 0u ; j < nRefs ; j ++ ) {
				auto& def{ udc.defines[ use.refs[ j ] ] };
				def.refs.remove( def.refs.indexOf( i ) );
				for ( auto k = 0u ; k < def.refs.size( ) ; k ++ ) {
					if ( def.refs[ k ] > i ) {
						def.refs[ k ] --;
					}
				}
			}
			udc.uses.removeSwap( i );
			changesMade = true;
		}
	}

	return changesMade;
}


/*= DEAD CODE REMOVAL ========================================================*/
namespace {

struct T_RDCConstCond_
{
	T_CondInstrNode* node;
	int64_t value;
};
using T_RDCConstCondList_ = T_AutoArray< T_RDCConstCond_ , 16 >;

bool RDCFindConditionals_(
		A_Node&			node ,
		const bool		exit ,
		T_RDCConstCondList_&	constConds
		) noexcept
{
	if ( exit || node.type( ) != A_Node::OP_COND ) {
		return !dynamic_cast< A_ExpressionNode* >( &node );
	}

	auto& cn{ dynamic_cast< T_CondInstrNode& >( node ) };
	auto const& ce{ cn.expression( ).expression( ) };
	if ( ce.type( ) == A_Node::EXPR_CONST ) {
		const int64_t ccv{ ( (T_ConstantExprNode const&) ce ).intValue( ) };
		constConds.add( T_RDCConstCond_{ &cn , ccv } );
		return false;
	}
	return true;
}

bool RDCConditionals_(
		T_OpsParserOutput&	program ,
		T_OptData&		oData
		) noexcept
{
	M_LOGSTR_( "...... Checking conditional instructions" , 3 );

	T_RDCConstCondList_ constConds;
	oData.visitor.visit( program.root , [&]( A_Node& node , const bool exit ) {
		return RDCFindConditionals_( node , exit , constConds );
	} );
	if ( !constConds.size( ) ) {
		return false;
	}

	const auto ncc{ constConds.size( ) };
	for ( auto i = 0u ; i < ncc ; i ++ ) {
		auto const& cc{ constConds[ i ] };
		auto& cn{ *cc.node };
		T_InstrListNode* replacement;
		oData.logger( [&](){
			T_StringBuilder sb;
			sb << "......... Conditional with constant argument at "
				<< cn.location( );
			return sb;
		} , 4 );
		if ( cn.hasCase( cc.value ) ) {
			replacement = &cn.getCase( cc.value ).instructions( );
			oData.logger( [&](){
				T_StringBuilder sb;
				sb << "Will replace with case for value "
					<< cc.value << " ("
					<< replacement->location( ) << ")";
				return sb;
			} , 5 );
		} else if ( cn.hasDefaultCase( ) ) {
			replacement = &cn.defaultCase( ).instructions( );
			oData.logger( [&](){
				T_StringBuilder sb;
				sb << "Will replace with default case ("
					<< replacement->location( ) << ")";
				return sb;
			} , 5 );
		} else {
			M_LOGSTR_( "Will delete whole instruction" , 5 );
			replacement = nullptr;
		}
		( (T_InstrListNode&) cn.parent( ) ).replaceMultiple( cn , replacement );
	}

	oData.state = {};
	return true;
}

/*----------------------------------------------------------------------------*/

bool RDCDeadStores_(
		T_OpsParserOutput&	program ,
		T_OptData&		oData
		) noexcept
{
	M_LOGSTR_( "...... Removing dead stores" , 3 );
	oData.buildUseDefineChains( program );

	T_AutoArray< A_InstructionNode* , 32 > deadSets;
	for ( auto& udc : oData.varUDChains.values( ) ) {
		const auto nDefs{ udc.defines.size( ) };
		for ( auto i = 0u ; i < nDefs ; i ++ ) {
			auto const& def{ udc.defines[ i ] };
			if ( !def.refs.size( ) ) {
				deadSets.add( oData.instructions[ def.node ].node );
			}
		}
	}
	if ( deadSets.empty( ) ) {
		return false;
	}
	oData.logger( [&](){
		T_StringBuilder sb;
		sb << "Found " << deadSets.size( ) << " dead store candidates";
		return sb;
	} , 5 );

	const auto nds{ deadSets.size( ) };
	bool changesMade{ false };
	for ( auto i = 0u ; i < nds ; i ++ ) {
		auto* const instr{ deadSets[ i ] };
		if ( instr->type( ) != A_Node::OP_CALL ) {
			oData.logger( [&](){
				T_StringBuilder sb;
				sb << "Removing dead store at "
					<< instr->location( );
				return sb;
			} , 4 );
			( (T_InstrListNode&) instr->parent( ) ).replaceMultiple(
					*instr , nullptr );
			changesMade = true;
		}
	}

	if ( changesMade ) {
		oData.state = {};
	}
	return changesMade;
}

/*----------------------------------------------------------------------------*/

bool RDCUnusedLocals_(
		T_OpsParserOutput&	program ,
		T_OptData&		oData
		) noexcept
{
	M_LOGSTR_( "...... Removing unused locals" , 3 );

	const auto nf{ program.root.nFunctions( ) };
	bool changed{ false };
	for ( auto f = 0u ; f < nf ; f ++ ) {
		auto& func{ program.root.function( f ) };
		bool restart{ false };
		for ( auto l = 0u ; l < func.locals( ) ;
				restart ? ( restart = false , l = 0 ) : l ++ ) {

			auto const& ln{ func.getLocalName( l ) };
			if ( func.isArgument( ln ) ) {
				// TODO
				continue;
			}

			const T_OptData::T_VarId vid{
				ln , func.name( ) , false
			};
			if ( oData.varUDChains.contains( vid ) ) {
				continue;
			}
			oData.logger( [&](){
				T_StringBuilder sb;
				sb << "Removing unused local variable "
					<< ln << " at "
					<< func.getLocalLocation( l );
				return sb;
			} , 4 );
			func.removeLocal( ln );
			restart = true;
			changed = true;
		}
	}
	return changed;
}

bool RDCUnusedGlobals_(
		T_OpsParserOutput&	program ,
		T_OptData&		oData
		) noexcept
{
	M_LOGSTR_( "...... Removing unused globals" , 3 );

	T_AutoArray< T_String , 16 > remove;
	for ( auto const& gn : program.types.keys( ) ) {
		const auto gt{ *program.types.get( gn ) };
		if ( gt == E_DataType::BUILTIN || gt == E_DataType::INPUT ) {
			continue;
		}
		const T_OptData::T_VarId vid{ gn };
		if ( !oData.varUDChains.contains( vid ) ) {
			remove.add( gn );
		}
	}

	const auto nr{ remove.size( ) };
	for ( auto i = 0u ; i < nr ; i ++ ) {
		auto const& gn{ remove[ i ] };
		oData.logger( [&](){
			T_StringBuilder sb;
			sb << "Removing unused global " << gn;
			return sb;
		} , 4 );
		program.types.remove( gn );
	}
	return remove.size( );
}

bool RDCUnused_(
		T_OpsParserOutput&	program ,
		T_OptData&		oData
		) noexcept
{
	oData.buildUseDefineChains( program );
	const bool locals{ RDCUnusedLocals_( program , oData ) };
	const bool globals{ RDCUnusedGlobals_( program , oData ) };
	return locals || globals;
}

} // namespace <anon>

/*----------------------------------------------------------------------------*/

bool opopt::RemoveDeadCode(
		T_OpsParserOutput& program ,
		T_OptData& oData ) noexcept
{
	M_LOGSTR_( "... Eliminating dead code" , 2 );

	bool didStuff{ false };
	bool tryDeadStoresFirst{ oData.state & T_OptData::E_StateItem::UDCHAINS };
	bool didStuffThisTime;
	do {
		if ( tryDeadStoresFirst ) {
			tryDeadStoresFirst = false;
			didStuff = RDCDeadStores_( program , oData );
			didStuffThisTime = true;
			continue;
		}

		didStuffThisTime = RDCConditionals_( program , oData )
			|| RDCDeadStores_( program , oData )
			|| RDCUnused_( program , oData );
		didStuff = didStuff || didStuffThisTime;
	} while ( didStuffThisTime );

	if ( didStuff ) {
		M_LOGSTR_( "...... Dead code removed" , 3 );
	} else {
		M_LOGSTR_( "...... No dead code found" , 3 );
	}
	return didStuff;
}


/*= FUNCTION INLINING ========================================================*/
namespace {

uint32_t IFFindFunctionForNode_(
		T_OptData const&	data ,
		const uint32_t		cfgNode
		) noexcept
{
	const auto ncf{ data.cfgFunctions.size( ) };
	for ( auto i = 0u ; i < ncf ; i ++ ) {
		auto const& block{ data.cfgFunctions[ i ] };
		if ( block.first <= cfgNode && block.first + block.count > cfgNode ) {
			return i;
		}
	}
	return T_HashIndex::INVALID_INDEX;
}

} // namespace <anon>
/*----------------------------------------------------------------------------*/

bool opopt::InlineFunctions(
		T_OpsParserOutput& program ,
		T_OptData& oData ) noexcept
{
	oData.buildControlFlowGraph( program );
	M_LOGSTR_( "... Inlining functions" , 3 );

	// Inline function data
	struct T_InlineFunction_ {
		T_String target;
		T_CallInstrNode* callInstrNode;
		uint32_t fnIndex;
		uint32_t nArgs{ 0 };
		uint32_t nLocals{ 0 };

		T_InlineFunction_(
				T_String target ,
				T_CallInstrNode* const call ,
				const uint32_t fnIndex ) noexcept
			: target{ std::move( target ) } ,
				callInstrNode{ call } , fnIndex{ fnIndex }
		{ }
	};
	// Inlining target function data
	struct T_InlineTarget_ {
		uint32_t count{ 1 };
		uint32_t extraLocals{ 0 };
		T_AutoArray< T_String , 16 > elNames;
	};

	// Find functions that can be inlined
	T_KeyValueTable< T_String , T_InlineFunction_ > inlineFunctions;
	T_KeyValueTable< T_String , T_InlineTarget_ > inlineTargets;
	auto const& cfg{ oData.ctrlFlowGraph };
	for ( auto it = cfg.begin( ) ; it != cfg.end( ) ; it ++ ) {
		auto const& node{ **it };

		// We need a single inbound edge, with CALL type
		if ( node.inbound.size( ) != 1 ) {
			continue;
		}
		if ( node.inbound[ 0 ].type != T_OptData::T_CtrlFlowEdge::CALL ) {
			continue;
		}

		// Find the function we're in and the function the call came from
		const auto calleeId{ IFFindFunctionForNode_(
				oData , it.pos( ) ) };
		const auto callerId{ IFFindFunctionForNode_(
				oData , node.inbound[ 0 ].target ) };

		// Ignore recursive functions or fake calls to *init*/*frame*
		if ( callerId == calleeId || callerId == T_HashIndex::INVALID_INDEX ) {
			continue;
		}

		// Find corresponding call instruction
		auto const& callNode{ *oData.ctrlFlowGraph[ node.inbound[ 0 ].target ] };
		assert( callNode.instructions );
		const uint32_t callInstrIdx{ callNode.instructions->first
				+ callNode.instructions->count - 1 };
		auto* const instr{ oData.instructions[ callInstrIdx ].node };
		assert( instr && dynamic_cast< T_CallInstrNode* >( instr ) );

		// Check call arguments
		auto& cInstr{ dynamic_cast< T_CallInstrNode& >( *instr ) };
		bool ok{ true };
		for ( auto i = 0u ; i < cInstr.size( ) && ok ; i ++ ) {
			auto const& arg{ cInstr.argument( i ) };
			const auto aet{ arg.expression( ).type( ) };
			ok = ( aet == A_Node::EXPR_CONST || aet == A_Node::EXPR_ID );
		}
		if ( !ok ) {
			continue;
		}

		// Add to inlineable functions list
		auto const& caller{ program.root.function( callerId ).name( ) };
		auto const& callee{ program.root.function( calleeId ).name( ) };
		inlineFunctions.add( callee , T_InlineFunction_{
				caller , &cInstr , calleeId } );
		auto* const callerCount{ inlineTargets.get( caller ) };
		if ( callerCount ) {
			callerCount->count ++;
		} else {
			inlineTargets.add( caller , T_InlineTarget_{} );
		}

		oData.logger( [&](){
			T_StringBuilder sb;
			sb << "Will inline function " << callee
				<< " into " << caller;
			return sb;
		} , 4 );
	}
	if ( inlineFunctions.size( ) == 0 ) {
		return false;
	}

	// Order inline-able functions
	T_AutoArray< uint32_t , 32 > ordered;
	ordered.ensureCapacity( inlineFunctions.size( ) );
	uint32_t index{ 0 };
	while ( inlineFunctions.size( ) > ordered.size( ) ) {
		auto const& ifd{ inlineFunctions.values( )[ index ] };
		auto const* const ifr{ inlineTargets.get( inlineFunctions.keys( )[ index ] ) };
		if ( !( ordered.contains( index ) || ( ifr && ifr->count > 0 ) ) ) {
			ordered.add( index );
			inlineTargets.get( ifd.target )->count --;
		}
		index = ( index + 1 ) % inlineFunctions.size( );
	}
	oData.logger( [&](){
		T_StringBuilder sb;
		sb << "Inlining order: ";
		for ( auto i = 0u ; i < ordered.size( ) ; i ++ ) {
			if ( i > 0 ) {
				sb << ", ";
			}
			sb << inlineFunctions.keys( )[ ordered[ i ] ];
		}
		return sb;
	} , 5 );

	// Compute extra locals required
	const auto nInlines{ ordered.size( ) };
	for ( auto i = 0u ; i < nInlines ; i ++ ) {
		auto& ifd{ inlineFunctions.values( )[ ordered[ i ] ] };
		auto const& fn{ program.root.function( ifd.fnIndex ) };
		uint32_t nLocals{ fn.locals( ) };
		for ( auto j = 0u ; j < fn.locals( ) ; j ++ ) {
			if ( fn.isArgument( fn.getLocalName( j ) ) ) {
				ifd.nArgs ++;
				nLocals --;
			}
		}

		auto const* const asTarget{ inlineTargets.get( fn.name( ) ) };
		if ( asTarget ) {
			nLocals += asTarget->extraLocals;
		}

		ifd.nLocals = nLocals;

		auto* target{ inlineTargets.get( ifd.target ) };
		target->extraLocals = std::max( target->extraLocals , nLocals );
	}

	// Add extra locals
	const auto nTargets{ inlineTargets.size( ) };
	T_StringBuilder idsb;
	for ( auto i = 0u ; i < nTargets ; i ++ ) {
		auto& itd{ inlineTargets.values( )[ i ] };
		auto& fn{ program.root.function( inlineTargets.keys( )[ i ] ) };
		for ( auto j = 0u , c = 0u ; c < itd.extraLocals ; j ++ ) {
			idsb.clear( ) << "*inline*" << j;
			T_String id{ idsb };
			if ( !fn.hasLocal( id ) ) {
				fn.addLocalVariable( id , T_SRDLocation{} );
				fn.setLocalType( fn.locals( ) - 1 ,
						opast::E_DataType::VARIABLE );
				itd.elNames.add( std::move( id ) );
				c ++;
			}
		}
	}

	// Merge (bottom to top)
	for ( auto i = 0u ; i < nInlines ; i ++ ) {
		auto const& ifd{ inlineFunctions.values( )[ i ] };
		auto const& itd{ *inlineTargets.get( ifd.target ) };
		auto& src{ program.root.function( ifd.fnIndex ) };
		auto& call{ *ifd.callInstrNode };
		oData.logger( [&]() {
			T_StringBuilder sb;
			sb << "Inlining function " << src.name( );
			return sb;
		} , 5 );

		// Replace arguments and locals
		oData.visitor.visit( src.instructions( ) , [&]( A_Node& node , const bool exit ) {
			if ( !exit ) {
				return true;
			}

			switch ( node.type( ) ) {

			    default: break;

			    case A_Node::EXPR_ID: {
				auto const& id{ (T_IdentifierExprNode&) node };
				if ( !src.hasLocal( id.id( ) ) ) {
					break;
				}
				T_OwnPtr< A_Node > nv;
				if ( src.isArgument( id.id( ) ) ) {
					const auto argIndex{ src.getLocalIndex( id.id( ) ) };
					auto const& callArg{ call.argument( argIndex ) };
					auto const& caValue{ callArg.expression( ) };
					if ( caValue.type( ) == A_Node::EXPR_CONST ) {
						nv = NewOwned< T_ConstantExprNode >(
								node.parent( ) ,
								( (T_ConstantExprNode&) caValue ).floatValue( ) );
					} else {
						nv = NewOwned< T_IdentifierExprNode >(
								node.parent( ) ,
								( (T_IdentifierExprNode&) caValue ).id( ) );
					}
				} else {
					nv = NewOwned< T_IdentifierExprNode >(
							node.parent( ) ,
							itd.elNames[ src.getLocalIndex( id.id( ) ) - ifd.nArgs ] );
				}
				oData.logger( [&]() {
					T_StringBuilder sb;
					sb << "Replacing identifier " << id.id( )
						<< " at " << id.location( )
						<< " with ";
					if ( nv->type( ) == A_Node::EXPR_ID ) {
						sb << "identifier " << ((T_IdentifierExprNode&)*nv).id( );
					} else {
						sb << "constant " << ((T_ConstantExprNode&)*nv).floatValue( );
					}
					return sb;
				} , 6 );
				node.parent( ).replace( node , nv );
				break;
			    }

			    case A_Node::OP_UNIFORMS: {
				auto& n{ (T_UniformsInstrNode&) node };
				if ( src.isArgument( n.progId( ) ) ) {
					const auto argIndex{ src.getLocalIndex( n.progId( ) ) };
					auto const& callArg{ call.argument( argIndex ) };
					auto const& caValue{ callArg.expression( ) };
					assert( caValue.type( ) == A_Node::EXPR_ID );
					auto const& repId{ ( (T_IdentifierExprNode&) caValue ).id( ) };
					oData.logger( [&]() {
						T_StringBuilder sb;
						sb << "Replacing identifier " << n.progId( )
							<< " at " << n.progIdLocation( )
							<< " with identifier " << repId;
						return sb;
					} , 6 );
					n.progId( repId , caValue.location( ) );
				}
				break;
			    }

			    case A_Node::OP_USE_TEXTURE: {
				auto& n{ (T_UseTextureInstrNode&) node };
				if ( src.isArgument( n.samplerId( ) ) ) {
					const auto argIndex{ src.getLocalIndex( n.samplerId( ) ) };
					auto const& callArg{ call.argument( argIndex ) };
					auto const& caValue{ callArg.expression( ) };
					assert( caValue.type( ) == A_Node::EXPR_ID );
					auto const& repId{ ( (T_IdentifierExprNode&) caValue ).id( ) };
					oData.logger( [&]() {
						T_StringBuilder sb;
						sb << "Replacing identifier " << n.samplerId( )
							<< " at " << n.samplerIdLocation( )
							<< " with identifier " << repId;
						return sb;
					} , 6 );
					n.samplerId( repId , caValue.location( ) );
				}
			    }	// fallthrough
			    case A_Node::OP_USE_PROGRAM:
			    case A_Node::OP_USE_PIPELINE:
			    case A_Node::OP_USE_FRAMEBUFFER: {
				auto& n{ (T_UseInstrNode&) node };
				if ( src.isArgument( n.id( ) ) ) {
					const auto argIndex{ src.getLocalIndex( n.id( ) ) };
					auto const& callArg{ call.argument( argIndex ) };
					auto const& caValue{ callArg.expression( ) };
					assert( caValue.type( ) == A_Node::EXPR_ID );
					auto const& repId{ ( (T_IdentifierExprNode&) caValue ).id( ) };
					oData.logger( [&]() {
						T_StringBuilder sb;
						sb << "Replacing identifier " << n.id( )
							<< " at " << n.idLocation( )
							<< " with identifier " << repId;
						return sb;
					} , 6 );
					n.id( repId , caValue.location( ) );
				}
				break;
			    }

			    case A_Node::OP_IMAGE: {
				auto& n{ (T_ImageInstrNode&) node };
				if ( src.isArgument( n.id( ) ) ) {
					const auto argIndex{ src.getLocalIndex( n.id( ) ) };
					auto const& callArg{ call.argument( argIndex ) };
					auto const& caValue{ callArg.expression( ) };
					assert( caValue.type( ) == A_Node::EXPR_ID );
					auto const& repId{ ( (T_IdentifierExprNode&) caValue ).id( ) };
					oData.logger( [&]() {
						T_StringBuilder sb;
						sb << "Replacing identifier " << n.id( )
							<< " at " << n.idLocation( )
							<< " with identifier " << repId;
						return sb;
					} , 6 );
					n.id( repId , caValue.location( ) );
				}
				break;
			    }

			    case A_Node::OP_PIPELINE: {
				auto& n{ (T_PipelineInstrNode&) node };
				const auto np{ n.size( ) };
				for ( auto i = 0u ; i < np ; i ++ ) {
					if ( src.isArgument( n.program( i ) ) ) {
						const auto argIndex{ src.getLocalIndex( n.program( i ) ) };
						auto const& callArg{ call.argument( argIndex ) };
						auto const& caValue{ callArg.expression( ) };
						assert( caValue.type( ) == A_Node::EXPR_ID );
						auto const& repId{ ( (T_IdentifierExprNode&) caValue ).id( ) };
						oData.logger( [&]() {
							T_StringBuilder sb;
							sb << "Replacing identifier " << n.program( i )
								<< " at " << n.pLocation( i )
								<< " with identifier " << repId;
							return sb;
						} , 6 );
						n.program( i , repId , caValue.location( ) );
					}
				}
				break;
			    }

			    case A_Node::TN_FBATT: {
				auto& n{ (T_FramebufferInstrNode::T_Attachment&) node };
				if ( src.isArgument( n.id( ) ) ) {
					const auto argIndex{ src.getLocalIndex( n.id( ) ) };
					auto const& callArg{ call.argument( argIndex ) };
					auto const& caValue{ callArg.expression( ) };
					assert( caValue.type( ) == A_Node::EXPR_ID );
					auto const& repId{ ( (T_IdentifierExprNode&) caValue ).id( ) };
					oData.logger( [&]() {
						T_StringBuilder sb;
						sb << "Replacing identifier " << n.id( )
							<< " at " << n.location( )
							<< " with identifier " << repId;
						return sb;
					} , 6 );
					n.id( repId , caValue.location( ) );
				}
				break;
			    }

			    case A_Node::OP_SET: {
				auto& n{ (T_SetInstrNode&) node };
				if ( !src.hasLocal( n.id( ) ) ) {
					break;
				}
				auto const& nid{
					itd.elNames[ src.getLocalIndex( n.id( ) ) - ifd.nArgs ]
				};
				oData.logger( [&]() {
					T_StringBuilder sb;
					sb << "Replacing identifier " << n.id( )
						<< " at " << n.location( )
						<< " with identifier " << nid;
					return sb;
				} , 6 );
				n.id( nid );
				break;
			    }

			}

			return true;
		} );

		((T_InstrListNode&) call.parent( )).replaceMultiple(
			call , &src.instructions( ) );
	}

	T_AutoArray< T_String , 32 > iffns;
	iffns.ensureCapacity( nInlines );
	for ( auto i = 0u ; i < nInlines ; i ++ ) {
		auto const& ifd{ inlineFunctions.values( )[ i ] };
		iffns.add( program.root.function( ifd.fnIndex ).name( ) );
	}
	for ( auto i = 0u ; i < nInlines ; i ++ ) {
		program.root.removeFunction( iffns[ i ] );
	}

	oData.state = {};
	return true;
}