#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;
optional< Value > VM::execute( CFG& cfg )
{
return execute( cfg.entryBB() );
}
optional< Value > VM::execute( ptr< BasicBlock > bb )
{
m_retVal = nullopt;
auto pbbBackup = m_pPreviousBB;
while( bb )
{
for( auto&& instr : *bb )
execute( instr );
if( !bb->terminator() )
break;
m_pPreviousBB = bb;
bb = executeTerminator( *bb->terminator() );
}
m_pPreviousBB = pbbBackup;
return m_retVal;
}
optional< Value > VM::execute( const cir::Instruction& instr )
{
return visit( [&]( auto&& e )
{
return execute( e );
}, instr.content() );
}
optional< Value > VM::execute( const cir::Call& call )
{
if( !( ms_remainingBranchInstExecutions ) )
{
DiagnosticsManager::GetInstance().emitErrorMessage( 0,
"execute: compilation time execution budget exceeded." );
return PoisonValue();
}
--ms_remainingBranchInstExecutions;
if( call.func().isPoison() )
DiagnosticsManager::GetInstance().setCurrentVerbosityLevel( Verbosity::Silent );
auto func = Evaluate( call.func(), *this );
if( func.isPoison() )
return PoisonValue();
if( !func.isConstant() )
{
DiagnosticsManager::GetInstance().emitErrorMessage( 0,
"execute: function evaluation failed." );
return PoisonValue();
}
if( IsExternalFunc( func ) )
{
DiagnosticsManager::GetInstance().emitErrorMessage( 0,
"execute: can't call external functions." );
return PoisonValue();
}
bool poisoned = false;
const auto& vec = *get< pvec >( call.args() );
if( IsBuiltinFunc( func ) )
{
auto newVec = vec.transform( [&]( auto&& x ) -> optional< Term >
{
auto val = EIRToValue( x );
assert( val );
auto newVal = Evaluate( *val, *this );
if( newVal.isPoison() )
poisoned = true;
if( !newVal.isConstant() )
{
poisoned = true;
return ValueToEIR( PoisonValue() );
}
return ValueToEIR( newVal );
} );
if( poisoned )
return PoisonValue();
if( !newVec )
return nullopt;
return ExecuteBuiltinFuncCall( func, TERM( newVec ) );
}
const auto* pFunc = GetFuncCIR( func );
if( !pFunc || !pFunc->isValid() )
return PoisonValue();
auto savedStackSize = m_stack.size();
for( auto&& a : vec.terms() )
{
auto val = EIRToValue( a );
assert( val );
auto newVal = Evaluate( *val, *this );
if( newVal.isPoison() )
{
m_stack.resize( savedStackSize );
return PoisonValue();
}
if( !newVal.isConstant() )
{
m_stack.resize( savedStackSize );
return nullopt;
}
m_stack.emplace_back( make_shared< Term >( ValueToEIR( newVal ) ) );
}
swap( m_currentFrameStart, savedStackSize );
auto result = execute( *pFunc->body() );
swap( m_currentFrameStart, savedStackSize );
m_stack.resize( savedStackSize );
return result;
}
optional< Value > VM::ExecuteBuiltinFuncCall( const Value& func, const Term& args )
{
const auto& f = GetBuiltinFuncWrapper( func );
return f( args );
}
optional< Value > VM::execute( const cir::VarAddr& va )
{
auto stackIndex = m_currentFrameStart + va.varIndex();
if( stackIndex >= m_stack.size() )
return nullopt;
if( !m_stack[stackIndex] )
return nullopt;
return ToValue( m_stack[stackIndex].get() );
}
optional< Value > VM::execute( const cir::TempAddr& ta )
{
auto stackIndex = m_currentFrameStart + ta.tempIndex();
if( m_stack.size() <= stackIndex )
m_stack.resize( stackIndex + 1 );
if( stackIndex >= m_stack.size() )
return nullopt;
if( !m_stack[stackIndex] )
m_stack[stackIndex] = make_shared< Term >( ValueToEIR( Evaluate( ta.initValue(), *this ) ) );
return ToValue( m_stack[stackIndex].get() );
}
optional< Value > VM::execute( const cir::Select& s )
{
auto baseAddr = execute( *s.baseAddr() );
if( !baseAddr || baseAddr->isPoison() )
return nullopt;
auto pvoid = FromValue< Term* >( *baseAddr );
if( !pvoid )
return nullopt;
auto val = EIRToValue( *static_cast< Term* >( pvoid ) );
if( !val )
return nullopt;
// 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 ) )
{
cout << "execute: select: value is not a tuple\n";
return nullopt;
}
assert( val->isConstant() );
return ToValue( &GetTupleElement( *val, s.memberIndex() ) );
}
optional< Value > VM::execute( const cir::CreateTemporary& ct )
{
auto stackIndex = m_currentFrameStart + ct.index();
if( m_stack.size() <= stackIndex )
m_stack.resize( stackIndex + 1 );
m_stack[stackIndex] = make_shared< Term >( ValueToEIR( Evaluate( ct.value(), *this ) ) );
return nullopt;
}
optional< Value > VM::execute( const cir::GetTemporary& gt )
{
auto stackIndex = gt.index() + m_currentFrameStart;
if( stackIndex >= m_stack.size() )
return nullopt;
return EIRToValue( *m_stack[stackIndex] );
}
optional< Value > VM::execute( const cir::AllocVar& av )
{
auto stackIndex = m_currentFrameStart + av.index();
if( m_stack.size() <= stackIndex )
m_stack.resize( stackIndex + 1 );
m_stack[stackIndex] = BuildUninitializedValue( av.type() );
return nullopt;
}
optional< Value > VM::execute( const cir::Load& l )
{
auto baseAddr = execute( *l.addr() );
if( !baseAddr || baseAddr->isPoison() )
return nullopt;
auto addr = FromValue< Term* >( *baseAddr );
if( !addr )
return nullopt;
return EIRToValue( *addr );
}
optional< Value > VM::execute( const cir::Store& s )
{
auto baseAddr = execute( *s.addr() );
if( !baseAddr || baseAddr->isPoison() )
return nullopt;
auto addr = FromValue< Term* >( *baseAddr );
if( !addr )
return nullopt;
auto result = Evaluate( s.val(), *this );
if( !result.isConstant() )
return PoisonValue();
*addr = ValueToEIR( result );
return nullopt;
}
optional< Value > VM::execute( const cir::Phi& p )
{
auto stackIndex = m_currentFrameStart + p.destIndex();
if( m_stack.size() <= stackIndex )
m_stack.resize( stackIndex + 1 );
p.forAllIncomings( [&]( auto&& bb, auto&& val )
{
if( bb == m_pPreviousBB )
{
m_stack[stackIndex] = make_shared< Term >( ValueToEIR( Evaluate( val, *this ) ) );
return false;
}
return true;
} );
return PoisonValue();
}
optional< Value > VM::execute( const cir::Not& uo )
{
if( uo.operand().isPoison() )
return PoisonValue();
auto opval = Evaluate( uo.operand(), *this );
if( opval.isPoison() )
return PoisonValue();
if( !opval.isConstant() )
return nullopt;
auto boolVal = FromValue< bool >( opval );
if( !boolVal )
return PoisonValue();
return ToValue( !*boolVal );
}
ptr< BasicBlock > VM::executeTerminator( const cir::Terminator& terminator )
{
return visit( [&]( auto&& e )
{
return executeTerminator( e );
}, terminator.content() );
}
ptr< BasicBlock > VM::executeTerminator( const cir::Ret& r )
{
if( r.value() )
m_retVal = Evaluate( *r.value(), *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 cond = Evaluate( cb.cond(), *this );
if( cond.isPoison() )
return nullptr;
if( !cond.isConstant() )
{
DiagnosticsManager::GetInstance().emitErrorMessage( cb.cond().locationId(),
"execute: branch condition evaluation failed." );
m_retVal = PoisonValue();
return nullptr;
}
if( *FromValue< bool >( cond ) )
return cb.trueDest().lock();
return cb.falseDest().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 ) ) ) ) );
}