demotool/c-opopt.cc

1558 lines
40 KiB
C++
Raw Normal View History

#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;
}
2017-12-08 07:38:59 +01:00
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 ( inputDecls ) {
return;
}
inputDecls = T_KeyValueTable< T_String , T_Array< T_InputDecl > >{ };
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;
} );
}
/*= 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
{
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 );
} );
}
}
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_() \
2017-12-08 07:38:59 +01:00
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;
2017-12-14 18:51:14 +01:00
} , 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;
2017-12-14 18:51:14 +01:00
} , 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;
2017-12-14 18:51:14 +01:00
} , 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;
2017-12-14 18:51:14 +01:00
} , 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;
2017-12-14 18:51:14 +01:00
} , 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
2017-12-08 07:38:59 +01:00
: 0 )
<< " instructions)";
return sb;
2017-12-14 18:51:14 +01:00
} , 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
{
// Keep the old array, we'll reuse its contents
T_Array< P_CtrlFlowNode > old{ std::move( ctrlFlowGraph ) };
2017-12-14 18:51:14 +01:00
M_LOGSTR_( "Building control flow graph" , 4 );
// 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{ };
callSites.clear( );
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 );
2017-12-14 18:51:14 +01:00
} , 6 );
}
#undef M_ADDNEW_
#undef M_NEWNODE_
2017-12-08 07:38:59 +01:00
#undef M_NODE_
/*= T_OptData - USE/DEFINE CHAINS ============================================*/
2017-12-04 07:03:54 +01:00
namespace {
void BUDCAddRecord_(
2017-12-04 07:03:54 +01:00
A_Node& n ,
2017-12-04 07:16:32 +01:00
T_String const& id ,
const bool use ,
2017-12-04 07:03:54 +01:00
T_OptData& od ,
T_RootNode& root ,
T_OptData::T_VarId const* extVarId = nullptr ) noexcept
2017-12-04 07:03:54 +01:00
{
// Find instruction and function index
A_FuncNode* func{ nullptr };
T_Optional< uint32_t > instrId;
2017-12-04 07:16:32 +01:00
A_Node* pn{ &n };
2017-12-04 07:03:54 +01:00
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 : [&]() {
2017-12-04 07:16:32 +01:00
auto const& n{ id };
if ( func->hasLocal( id ) ) {
2017-12-04 07:03:54 +01:00
return T_OptData::T_VarId{ n , func->name( ) ,
2017-12-04 07:16:32 +01:00
func->isArgument( id ) };
2017-12-04 07:03:54 +01:00
}
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( ) );
2017-12-04 07:03:54 +01:00
od.logger( [&](){
T_StringBuilder sb;
sb << ( use ? "use " : "def " ) << varId.name << "(#"
<< ( use ? varRec->uses.size( ) : varRec->defines.size( ) ) - 1
<< ") at " << n.location( ) << " (";
2017-12-04 07:03:54 +01:00
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;
2017-12-04 07:03:54 +01:00
return sb;
2017-12-14 18:51:14 +01:00
} , 6 );
2017-12-04 07:03:54 +01:00
}
2017-12-06 07:05:26 +01:00
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 );
}
2017-12-06 07:05:26 +01:00
break;
}
2017-12-06 07:05:26 +01:00
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_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;
2017-12-14 18:51:14 +01:00
} , 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;
2017-12-14 18:51:14 +01:00
} , 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;
2017-12-14 18:51:14 +01:00
} , 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;
2017-12-14 18:51:14 +01:00
} , 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;
2017-12-14 18:51:14 +01:00
} , 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;
}
}
}
}
2017-12-04 07:03:54 +01:00
} // namespace <anon>
/*----------------------------------------------------------------------------*/
void T_OptData::buildUseDefineChains(
T_OpsParserOutput& program ) noexcept
{
2017-12-14 18:51:14 +01:00
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 ) {
2017-12-06 07:05:26 +01:00
if ( !exit ) {
BUDCVisitor_( program.root , *this , n );
2017-12-04 07:16:32 +01:00
}
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;
}
}
}
}
/*============================================================================*/
#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 };
2017-12-02 12:18:31 +01:00
auto& child{ get( node ) };
auto r{ checkExpression( child ) };
if ( r ) {
2017-12-02 12:18:31 +01:00
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 );
}
assert( oData.inputDecls );
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 );
}
2017-12-02 12:18:31 +01:00
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;
2017-12-14 18:51:14 +01:00
sb << "...... Propagated constant from " << var.name
<< " at " << instruction.location( )
<< " (value " << value << ")";
return sb;
2017-12-14 18:51:14 +01:00
} , 3 );
return true;
} );
}
} // namespace <anon>
bool opopt::PropagateConstants(
T_OpsParserOutput& program ,
T_OptData& oData ) noexcept
{
// We need to follow the general execution flow of the program. This is
// not as straightforward as it seems.
// - Handling locals is rather easy as they "die" at the end of
// the function in which they are defined.
// - Handling variables in init and functions that are called only
// from init is not too hard: once a variable is set to a constant, it
// can be substituted until the next set instruction.
// - Other variables need additional checks before propagating.
// For example, if a variable is set to a constant during init, but is
// updated at the end of the frame function, the value cannot be
// propagated.
oData.numberInstructions( program );
oData.buildControlFlowGraph( program );
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 );
udc.uses.removeSwap( i );
changesMade = true;
}
}
return changesMade;
}
/*= DEAD CODE REMOVAL ========================================================*/
bool opopt::RemoveDeadCode(
T_OpsParserOutput& program ,
T_OptData& oData ) noexcept
{
return false;
}