Optimizer - Control flow graph construction
This commit is contained in:
parent
c7e4ccf67e
commit
b6f9d06be0
4 changed files with 370 additions and 6 deletions
319
c-opopt.cc
319
c-opopt.cc
|
@ -9,12 +9,14 @@ using namespace opast;
|
||||||
using namespace opopt;
|
using namespace opopt;
|
||||||
|
|
||||||
|
|
||||||
#define M_LOGSTR_( S , L ) \
|
|
||||||
oData.logger( [](){ return T_StringBuilder{ S }; } , L )
|
|
||||||
|
|
||||||
|
|
||||||
/*= T_OptData ================================================================*/
|
/*= T_OptData ================================================================*/
|
||||||
|
|
||||||
|
#define M_LOGSTR_( S , L ) \
|
||||||
|
logger( [](){ return T_StringBuilder{ S }; } , L )
|
||||||
|
|
||||||
|
|
||||||
|
/*= T_OptData - INPUT DECLARATIONS ===========================================*/
|
||||||
|
|
||||||
void T_OptData::findInputDecls(
|
void T_OptData::findInputDecls(
|
||||||
T_OpsParserOutput& program ) noexcept
|
T_OpsParserOutput& program ) noexcept
|
||||||
{
|
{
|
||||||
|
@ -38,6 +40,308 @@ void T_OptData::findInputDecls(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*= 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{ 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{ 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 - CONSTROL FLOW GRAPH CONSTRUCTION =============================*/
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#warning Remove this later
|
||||||
|
#define LL1 1
|
||||||
|
#define LL2 1
|
||||||
|
|
||||||
|
using T_CFN_ = T_OptData::T_CtrlFlowNode;
|
||||||
|
using P_CFN_ = T_OptData::P_CtrlFlowNode;
|
||||||
|
using RP_CFN_ = T_OptData::RP_CtrlFlowNode;
|
||||||
|
|
||||||
|
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[ ctrlFlowGraph.add( M_NEWNODE_( ) ) ].get( )
|
||||||
|
|
||||||
|
} // 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 ) };
|
||||||
|
M_LOGSTR_( "Building control flow graph" , LL1 );
|
||||||
|
|
||||||
|
// Create special nodes
|
||||||
|
M_ADDNEW_( );
|
||||||
|
const RP_CFN_ nMainLoop { M_ADDNEW_( ) };
|
||||||
|
const RP_CFN_ nExit { M_ADDNEW_( ) };
|
||||||
|
nMainLoop->outbound.add( nExit );
|
||||||
|
nExit->inbound.add( nMainLoop );
|
||||||
|
|
||||||
|
// Data structure to handle conditionals
|
||||||
|
struct T_StackEntry_ {
|
||||||
|
RP_CFN_ condBlock;
|
||||||
|
bool hasDefault{ false };
|
||||||
|
T_AutoArray< RP_CFN_ , 8 > caseBlocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Data structure for call sites
|
||||||
|
struct T_CallSite_ {
|
||||||
|
T_String name;
|
||||||
|
RP_CFN_ callBlock;
|
||||||
|
RP_CFN_ retBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate control flow graph for each function
|
||||||
|
T_AutoArray< T_StackEntry_ , 8 > stack;
|
||||||
|
T_Array< T_CallSite_ > callSites;
|
||||||
|
RP_CFN_ cNode{ nullptr };
|
||||||
|
visitor.visit( program.root , [&]( auto& node , const bool exit ) {
|
||||||
|
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 ) {
|
||||||
|
auto& n{ dynamic_cast< A_FuncNode& >( node ) };
|
||||||
|
auto const& fn{ n.name( ) };
|
||||||
|
if ( exit ) {
|
||||||
|
assert( stack.empty( ) );
|
||||||
|
logger( [&](){
|
||||||
|
T_StringBuilder sb;
|
||||||
|
sb << "Function ended; last block had "
|
||||||
|
<< ( cNode->instructions
|
||||||
|
? cNode->instructions->count
|
||||||
|
: 0 )
|
||||||
|
<< " instructions";
|
||||||
|
return sb;
|
||||||
|
} , LL1 );
|
||||||
|
auto* frec{ cfgFunctions.get( fn ) };
|
||||||
|
assert( frec );
|
||||||
|
frec->count = ctrlFlowGraph.size( ) - frec->first;
|
||||||
|
cNode = nullptr;
|
||||||
|
} else {
|
||||||
|
logger( [&](){
|
||||||
|
T_StringBuilder sb;
|
||||||
|
sb << "Starting function '" << fn << "' at "
|
||||||
|
<< ctrlFlowGraph.size( );
|
||||||
|
return sb;
|
||||||
|
} , LL1 );
|
||||||
|
cfgFunctions.add( fn , T_BasicBlock{ ctrlFlowGraph.size( ) } );
|
||||||
|
cNode = M_ADDNEW_( );
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All instructions: continue the current basic block
|
||||||
|
auto* const iptr{ dynamic_cast< A_InstructionNode* >( &node ) };
|
||||||
|
if ( iptr && !exit ) {
|
||||||
|
assert( cNode );
|
||||||
|
if ( cNode->instructions ) {
|
||||||
|
cNode->instructions->count ++;
|
||||||
|
} else {
|
||||||
|
cNode->instructions = T_BasicBlock{ indexOf( *iptr ) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle conditionals
|
||||||
|
if ( nt == A_Node::OP_COND ) {
|
||||||
|
if ( exit ) {
|
||||||
|
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* cb{ se.caseBlocks[ i ] };
|
||||||
|
cb->inbound.add( se.condBlock );
|
||||||
|
se.condBlock->outbound.add( cb );
|
||||||
|
cb->outbound.add( cNode );
|
||||||
|
cNode->inbound.add( cb );
|
||||||
|
}
|
||||||
|
if ( !se.hasDefault ) {
|
||||||
|
cNode->inbound.add( se.condBlock );
|
||||||
|
se.condBlock->outbound.add( cNode );
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.removeLast( );
|
||||||
|
logger( [&](){
|
||||||
|
T_StringBuilder sb;
|
||||||
|
sb << "Exiting conditional instruction, stack size "
|
||||||
|
<< stack.size( );
|
||||||
|
return sb;
|
||||||
|
} , LL2 );
|
||||||
|
} else {
|
||||||
|
auto& se{ stack.addNew( ) };
|
||||||
|
se.condBlock = cNode;
|
||||||
|
cNode = nullptr;
|
||||||
|
logger( [&](){
|
||||||
|
T_StringBuilder sb;
|
||||||
|
sb << "Entering conditional instruction, stack size "
|
||||||
|
<< stack.size( )
|
||||||
|
<< ", block had "
|
||||||
|
<< ( se.condBlock->instructions
|
||||||
|
? se.condBlock->instructions->count
|
||||||
|
: 0 )
|
||||||
|
<< " instructions";
|
||||||
|
return sb;
|
||||||
|
} , LL2 );
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls also break the flow
|
||||||
|
if ( nt == A_Node::OP_CALL && !exit ) {
|
||||||
|
T_CallInstrNode& ci{ *dynamic_cast< T_CallInstrNode* >( iptr ) };
|
||||||
|
logger( [&](){
|
||||||
|
T_StringBuilder sb;
|
||||||
|
sb << "Call to " << ci.id( ) << ", block had "
|
||||||
|
<< ( cNode->instructions
|
||||||
|
? cNode->instructions->count
|
||||||
|
: 0 )
|
||||||
|
<< " instructions";
|
||||||
|
return sb;
|
||||||
|
} , LL2 );
|
||||||
|
|
||||||
|
auto& cs{ callSites.addNew( ) };
|
||||||
|
cs.name = ci.id( );
|
||||||
|
cs.callBlock = cNode;
|
||||||
|
cNode = cs.retBlock = M_ADDNEW_( );
|
||||||
|
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 ) {
|
||||||
|
logger( [&](){
|
||||||
|
T_StringBuilder sb;
|
||||||
|
sb << "Case block added ("
|
||||||
|
<< ( cNode->instructions
|
||||||
|
? cNode->instructions->count
|
||||||
|
: 0 )
|
||||||
|
<< " instructions)";
|
||||||
|
return sb;
|
||||||
|
} , LL2 );
|
||||||
|
cNode = nullptr;
|
||||||
|
} else {
|
||||||
|
stack.last( ).hasDefault = stack.last( ).hasDefault
|
||||||
|
|| ( nt == A_Node::TN_DEFAULT );
|
||||||
|
cNode = M_ADDNEW_( );
|
||||||
|
stack.last( ).caseBlocks.add( cNode );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !dynamic_cast< A_ExpressionNode* >( &node );
|
||||||
|
} );
|
||||||
|
assert( cfgFunctions.contains( "*init*" ) && cfgFunctions.contains( "*frame*" ) );
|
||||||
|
|
||||||
|
// Add fake call sites for *init* and *frame*
|
||||||
|
{
|
||||||
|
auto& cs{ callSites.addNew( ) };
|
||||||
|
cs.callBlock = ctrlFlowGraph[ CFG_ENTER ].get( );
|
||||||
|
cs.retBlock = ctrlFlowGraph[ CFG_MAINLOOP ].get( );
|
||||||
|
cs.name = "*init*";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto& cs{ callSites.addNew( ) };
|
||||||
|
cs.callBlock = ctrlFlowGraph[ CFG_MAINLOOP ].get( );
|
||||||
|
cs.retBlock = ctrlFlowGraph[ CFG_MAINLOOP ].get( );
|
||||||
|
cs.name = "*frame*";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle calls
|
||||||
|
for ( auto const& cs : callSites ) {
|
||||||
|
auto const* frec{ cfgFunctions.get( cs.name ) };
|
||||||
|
assert( frec );
|
||||||
|
{
|
||||||
|
auto& entry{ ctrlFlowGraph[ frec->first ] };
|
||||||
|
entry->inbound.add( cs.callBlock );
|
||||||
|
cs.callBlock->outbound.add( entry.get( ) );
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto& exit{ ctrlFlowGraph[ frec->first + frec->count - 1 ] };
|
||||||
|
exit->outbound.add( cs.retBlock );
|
||||||
|
cs.retBlock->inbound.add( exit.get( ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*============================================================================*/
|
||||||
|
|
||||||
|
#undef M_LOGSTR_
|
||||||
|
#define M_LOGSTR_( S , L ) \
|
||||||
|
oData.logger( [](){ return T_StringBuilder{ S }; } , L )
|
||||||
|
|
||||||
|
|
||||||
/*= CONSTANT FOLDING =========================================================*/
|
/*= CONSTANT FOLDING =========================================================*/
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -396,7 +700,7 @@ bool opopt::FoldConstants(
|
||||||
|
|
||||||
bool opopt::PropagateConstants(
|
bool opopt::PropagateConstants(
|
||||||
T_OpsParserOutput& program ,
|
T_OpsParserOutput& program ,
|
||||||
T_OptData& optData ) noexcept
|
T_OptData& oData ) noexcept
|
||||||
{
|
{
|
||||||
// We need to follow the general execution flow of the program. This is
|
// We need to follow the general execution flow of the program. This is
|
||||||
// not as straightforward as it seems.
|
// not as straightforward as it seems.
|
||||||
|
@ -409,6 +713,11 @@ bool opopt::PropagateConstants(
|
||||||
// For example, if a variable is set to a constant during init, but is
|
// 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
|
// updated at the end of the frame function, the value cannot be
|
||||||
// propagated.
|
// propagated.
|
||||||
|
|
||||||
|
oData.numberInstructions( program );
|
||||||
|
oData.buildControlFlowGraph( program );
|
||||||
|
M_LOGSTR_( "... Propagating constants" , 2 );
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
53
c-opopt.hh
53
c-opopt.hh
|
@ -27,6 +27,8 @@ struct T_OptData
|
||||||
// A visitor to be used for the tree
|
// A visitor to be used for the tree
|
||||||
ebcl::T_Visitor< opast::A_Node > visitor{ opast::ASTVisitorBrowser };
|
ebcl::T_Visitor< opast::A_Node > visitor{ opast::ASTVisitorBrowser };
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
// Table of input declarations; used to fold constant inputs.
|
// Table of input declarations; used to fold constant inputs.
|
||||||
struct T_InputDecl {
|
struct T_InputDecl {
|
||||||
ebcl::T_SRDLocation location;
|
ebcl::T_SRDLocation location;
|
||||||
|
@ -34,9 +36,58 @@ struct T_OptData
|
||||||
};
|
};
|
||||||
T_Optional< T_KeyValueTable< T_String , T_Array< T_InputDecl > > > inputDecls;
|
T_Optional< T_KeyValueTable< T_String , T_Array< T_InputDecl > > > inputDecls;
|
||||||
|
|
||||||
|
void findInputDecls( T_OpsParserOutput& program ) noexcept;
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
void findInputDecls( T_OpsParserOutput& program ) noexcept;
|
// Data for instruction numbering
|
||||||
|
struct T_InstrPos {
|
||||||
|
uint32_t index;
|
||||||
|
opast::A_InstructionNode* node;
|
||||||
|
bool lastOfSequence;
|
||||||
|
uint32_t funcIndex;
|
||||||
|
};
|
||||||
|
T_HashIndex instrIndex;
|
||||||
|
T_Array< T_InstrPos > instructions;
|
||||||
|
|
||||||
|
void numberInstructions( T_OpsParserOutput& program ) noexcept;
|
||||||
|
uint32_t indexOf( opast::A_InstructionNode const& instr ) noexcept;
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Basic block of consecutive instructions
|
||||||
|
struct T_BasicBlock
|
||||||
|
{
|
||||||
|
uint32_t first;
|
||||||
|
uint32_t count;
|
||||||
|
|
||||||
|
explicit T_BasicBlock( uint32_t first ) noexcept
|
||||||
|
: first( first ) , count( 1 )
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Control flow graph node
|
||||||
|
struct T_CtrlFlowNode;
|
||||||
|
using RP_CtrlFlowNode = T_CtrlFlowNode*;
|
||||||
|
struct T_CtrlFlowNode
|
||||||
|
{
|
||||||
|
T_Optional< T_BasicBlock > instructions;
|
||||||
|
T_AutoArray< RP_CtrlFlowNode , 16 > inbound;
|
||||||
|
T_AutoArray< RP_CtrlFlowNode , 16 > outbound;
|
||||||
|
};
|
||||||
|
using P_CtrlFlowNode = T_OwnPtr< T_CtrlFlowNode >;
|
||||||
|
|
||||||
|
// Special nodes in the graph
|
||||||
|
static constexpr uint32_t CFG_ENTER = 0;
|
||||||
|
static constexpr uint32_t CFG_MAINLOOP = 1;
|
||||||
|
static constexpr uint32_t CFG_END = 2;
|
||||||
|
|
||||||
|
// Control flow graph
|
||||||
|
T_Array< P_CtrlFlowNode > ctrlFlowGraph;
|
||||||
|
T_KeyValueTable< T_String , T_BasicBlock > cfgFunctions;
|
||||||
|
void buildControlFlowGraph(
|
||||||
|
T_OpsParserOutput& program ) noexcept;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,9 @@ int main( int argc , char** argv )
|
||||||
if ( cfg.constantFolding && opopt::FoldConstants( *parsed , od ) ) {
|
if ( cfg.constantFolding && opopt::FoldConstants( *parsed , od ) ) {
|
||||||
doneStuff = true;
|
doneStuff = true;
|
||||||
}
|
}
|
||||||
|
if ( cfg.constantPropagation && opopt::PropagateConstants( *parsed , od ) ) {
|
||||||
|
doneStuff = true;
|
||||||
|
}
|
||||||
if ( cfg.deadCodeElimination && opopt::RemoveDeadCode( *parsed , od ) ) {
|
if ( cfg.deadCodeElimination && opopt::RemoveDeadCode( *parsed , od ) ) {
|
||||||
doneStuff = true;
|
doneStuff = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
)
|
)
|
||||||
(optimizer on
|
(optimizer on
|
||||||
(constant-folding on)
|
(constant-folding on)
|
||||||
|
(constant-propagation on)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue