Goose  Artifact [480a07324b]

Artifact 480a07324bb720ebd58cf75f6fe910c9f9d1cd7992181c6f5c1f90a00b33fe23:

  • File bs/execute/vm.cpp — part of check-in [914b2e2af1] at 2023-07-05 22:01:09 on branch trunk — Some builtins to work with references during verification:
    • RefToAddr conversion function
    • dot operator overloads to extract the lifetime, origin and path of an address
    (user: zlodo size: 11615)

#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 ) ) ) ) );
}