#include "execute.h"
#include "builtins/builtins.h"
using namespace goose;
using namespace goose::execute;
using namespace goose::builtins;
uint32_t VM::ms_remainingBranchInstExecutions = 1 << 24;
VM::StackFrameHelper::StackFrameHelper( VM& vmIn, CFG& cfg, size_t numArgs ) :
vm( vmIn )
{
savedStackSize = vm.m_stack.size() - numArgs;
savedFrameStart = vm.m_currentFrameStart;
// Reserve stack space for the temporaries + some more for local scratch space
// The stack will be extended as necessary if that's not enough
// (TODO define that as a constant somewhere)
vm.m_stack.reserve( savedStackSize + cfg.temporariesCount() + 16 );
vm.m_stack.resize( savedStackSize + cfg.temporariesCount() );
vm.m_currentFrameStart = savedStackSize;
}
VM::StackFrameHelper::~StackFrameHelper()
{
vm.m_stack.resize( savedStackSize );
vm.m_currentFrameStart = savedFrameStart;
}
optional< Value > VM::execute( CFG& cfg )
{
StackFrameHelper sfh( *this, cfg );
if( !execute( cfg.entryBB() ) )
return nullopt;
return pop();
}
bool VM::execute( ptr< BasicBlock > bb )
{
m_retVal = nullopt;
auto pbbBackup = m_pPreviousBB;
while( bb )
{
const auto* pInstrs = bb->runnableInstructions();
if( !pInstrs )
return false;
for( auto&& instr : *pInstrs )
{
if( !execute( instr ) )
return false;
}
if( !bb->terminator() )
break;
m_pPreviousBB = bb;
bb = executeTerminator( *bb->terminator() );
}
m_pPreviousBB = pbbBackup;
if( m_retVal )
push( *m_retVal );
return true;
}
bool VM::execute( const cir::InstrSeq& is )
{
for( const auto& instr : is )
{
if( !execute( instr ) )
return false;
}
return true;
}
bool VM::execute( const cir::Instruction& instr )
{
return visit( [&]( auto&& e )
{
return execute( e );
}, instr.content() );
}
bool VM::execute( const cir::Call& call )
{
if( !( ms_remainingBranchInstExecutions ) )
{
DiagnosticsManager::GetInstance().emitErrorMessage( 0,
"execute: compilation time execution budget exceeded." );
return false;
}
--ms_remainingBranchInstExecutions;
auto callFunc = pop();
if( !callFunc )
return false;
if( callFunc->isPoison() )
DiagnosticsManager::GetInstance().setCurrentVerbosityLevel( Verbosity::Silent );
auto func = Evaluate( *callFunc, *this );
if( func.isPoison() )
return false;
if( !func.isConstant() )
{
DiagnosticsManager::GetInstance().emitErrorMessage( 0,
"execute: function evaluation failed." );
return false;
}
if( IsExternalFunc( func ) )
return false;
auto argCount = call.numArgs();
if( IsBuiltinFunc( func ) )
{
auto newVec = make_shared< Vector >();
newVec->resize( argCount );
for( uint32_t argIndex = 0; argIndex < argCount; ++argIndex )
{
auto arg = popTerm();
if( !arg )
return false;
newVec->set( argCount - argIndex - 1, move( *arg ) );
}
auto result = ExecuteBuiltinFuncCall( func, TERM( newVec ) );
if( result )
push( *result );
return true;
}
const auto* pFunc = GetFuncCIR( func );
if( !pFunc || !pFunc->isValid() )
return false;
optional< Value > result;
{
StackFrameHelper sfh( *this, *pFunc->body(), argCount );
// Directly execute the entry BB rather than the CFG: we already have setup the stack frame
// execute( CFG ) is intended for situations where we need to execute a CFG directly without
// it being part of a function
if( !execute( pFunc->body()->entryBB() ) )
return false;
if( *GetFuncRType( func ) != GetValueType< void >() )
result = pop();
}
if( result )
push( *result );
return true;
}
optional< Value > VM::ExecuteBuiltinFuncCall( const Value& func, const Term& args )
{
const auto& f = GetBuiltinFuncWrapper( func );
auto result = f( args );
if( result.type() == GetValueType< void >() )
return nullopt;
return result;
}
bool VM::execute( const cir::Constant& cst )
{
push( cst.value() );
return true;
}
bool VM::execute( const cir::VarAddr& va )
{
auto stackIndex = m_currentFrameStart + ( va.varIndex() & 0x7fffffff );
if( stackIndex >= m_stack.size() )
return false;
if( !m_stack[stackIndex] )
return false;
push( ToValue( m_stack[stackIndex].get() ) );
return true;
}
bool VM::execute( const cir::TempAddr& ta )
{
auto initVal = pop();
if( !initVal )
return false;
auto stackIndex = m_currentFrameStart + ( ta.tempIndex() & 0x7fffffff );
if( stackIndex >= m_stack.size() )
return false;
if( !m_stack[stackIndex] )
m_stack[stackIndex] = make_shared< Term >( ValueToEIR( Evaluate( *initVal, *this ) ) );
push( ToValue( m_stack[stackIndex].get() ) );
return true;
}
bool VM::execute( const cir::Select& s )
{
auto baseAddr = pop();
if( !baseAddr )
return false;
auto pvoid = FromValue< Term* >( *baseAddr );
if( !pvoid )
return false;
auto val = EIRToValue( *static_cast< Term* >( pvoid ) );
if( !val )
return false;
// We only support tuples now. In the future, Select will also be used to
// add an offset to another pointer.
// Everything else (structs, classes, containers, etc.) should build on top
// of those two fundamental types.
if( !IsTuple( *val ) )
{
G_VAL_ERROR( *val, "execute: select: value is not a tuple" );
return false;
}
assert( val->isConstant() );
push( ToValue( &GetConstantTupleElement( *val, s.memberIndex() ) ) );
return true;
}
bool VM::execute( const cir::CreateTemporary& ct )
{
auto val = pop();
if( !val )
return false;
auto stackIndex = m_currentFrameStart + ( ct.index() & 0x7fffffff );
if( m_stack.size() <= stackIndex )
m_stack.resize( stackIndex + 1 );
m_stack[stackIndex] = make_shared< Term >( ValueToEIR( Evaluate( *val, *this ) ) );
return true;
}
bool VM::execute( const cir::GetTemporary& gt )
{
auto stackIndex = ( gt.index() & 0x7fffffff ) + m_currentFrameStart;
if( stackIndex >= m_stack.size() )
return false;
assert( m_stack[stackIndex] );
auto val = EIRToValue( *m_stack[stackIndex] );
assert( val );
push( *val );
return true;
}
bool VM::execute( const cir::AllocVar& av )
{
auto stackIndex = m_currentFrameStart + ( av.index() & 0x7fffffff );
if( m_stack.size() <= stackIndex )
m_stack.resize( stackIndex + 1 );
m_stack[stackIndex] = BuildUninitializedValue( av.type() );
return true;
}
bool VM::execute( const cir::Load& l )
{
auto baseAddr = pop();
if( !baseAddr )
return false;
auto addr = FromValue< Term* >( *baseAddr );
if( !addr )
return false;
if( *addr == TSID( UNINITIALIZED ) )
{
DiagnosticsManager::GetInstance().emitErrorMessage( l.locationId(),
"execute: attempting to load from an uninitialized address" );
return false;
}
auto val = EIRToValue( *addr );
if( !val )
return false;
push( *val );
return true;
}
bool VM::execute( const cir::Store& s )
{
auto val = pop();
if( !val )
return false;
auto result = Evaluate( *val, *this );
if( !result.isConstant() )
return false;
auto baseAddr = pop();
if( !baseAddr )
return false;
auto addr = FromValue< Term* >( *baseAddr );
if( !addr )
return false;
*addr = ValueToEIR( result );
return true;
}
bool VM::execute( const cir::Phi& p )
{
auto stackIndex = m_currentFrameStart + p.destIndex();
if( m_stack.size() <= stackIndex )
m_stack.resize( stackIndex + 1 );
bool success = false;
p.forAllIncomings( [&]( auto&& bb, auto&& val )
{
if( bb == m_pPreviousBB )
{
m_stack[stackIndex] = make_shared< Term >( ValueToEIR( Evaluate( val, *this ) ) );
success = true;
return false;
}
return true;
} );
return success;
}
bool VM::execute( const cir::Not& uo )
{
auto operand = pop();
if( !operand || operand->isPoison() )
return false;
auto opval = Evaluate( *operand, *this );
if( opval.isPoison() )
return false;
if( !opval.isConstant() )
return false;
auto boolVal = FromValue< bool >( opval );
if( !boolVal )
{
G_TRACE_VAL( *operand );
G_TRACE_VAL( opval );
return false;
}
push( ToValue( !*boolVal ) );
return true;
}
ptr< BasicBlock > VM::executeTerminator( const cir::Terminator& terminator )
{
return visit( [&]( auto&& e )
{
return executeTerminator( e );
}, terminator.content() );
}
ptr< BasicBlock > VM::executeTerminator( const cir::RetVoid& r )
{
return nullptr;
}
ptr< BasicBlock > VM::executeTerminator( const cir::Ret& r )
{
auto retVal = pop();
if( !retVal )
return nullptr;
m_retVal = Evaluate( *retVal, *this );
return nullptr;
}
ptr< BasicBlock > VM::executeTerminator( const cir::Branch& b )
{
if( !( ms_remainingBranchInstExecutions ) )
{
DiagnosticsManager::GetInstance().emitErrorMessage( 0,
"execute: compilation time execution budget exceeded." );
m_retVal = PoisonValue();
return nullptr;
}
--ms_remainingBranchInstExecutions;
return b.dest().lock();
}
ptr< BasicBlock > VM::executeTerminator( const cir::CondBranch& cb )
{
if( !( ms_remainingBranchInstExecutions ) )
{
DiagnosticsManager::GetInstance().emitErrorMessage( 0,
"execute: compilation time execution budget exceeded." );
m_retVal = PoisonValue();
return nullptr;
}
--ms_remainingBranchInstExecutions;
auto condVal = pop();
if( !condVal )
return nullptr;
auto cond = Evaluate( *condVal, *this );
if( cond.isPoison() )
return nullptr;
if( !cond.isConstant() )
{
DiagnosticsManager::GetInstance().emitErrorMessage( condVal->locationId(),
"execute: branch condition evaluation failed." );
m_retVal = PoisonValue();
return nullptr;
}
if( *FromValue< bool >( cond ) )
return cb.trueDest().lock();
return cb.falseDest().lock();
}
ptr< BasicBlock > VM::executeTerminator( const cir::GhostBranch& gb )
{
if( !( ms_remainingBranchInstExecutions ) )
{
DiagnosticsManager::GetInstance().emitErrorMessage( 0,
"execute: compilation time execution budget exceeded." );
m_retVal = PoisonValue();
return nullptr;
}
--ms_remainingBranchInstExecutions;
return gb.continuation().lock();
}
ptr< Term > VM::BuildUninitializedValue( const Value& type )
{
if( !IsTupleType( type ) )
return make_shared< Term >( TSID( UNINITIALIZED ) );
auto tupContent = make_shared< Vector >();
tupContent->reserve( TupleTypeSize( type ) );
ForEachInTupleType( type, [&]( auto&& t )
{
tupContent->append( *BuildUninitializedValue( *EIRToValue( t ) ) );
return true;
} );
return make_shared< Term >( ValueToEIR( Value( ValueToEIR( type ), TERM( move( tupContent ) ) ) ) );
}