#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 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 /*----------------------------------------------------------------------------*/ 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 /*----------------------------------------------------------------------------*/ 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 /*----------------------------------------------------------------------------*/ 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 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 /*----------------------------------------------------------------------------*/ 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 /*----------------------------------------------------------------------------*/ 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; }