Goose  Artifact [942e494a43]

Artifact 942e494a432ecd86c200f481d6d02ec9c111326642b762bd71cdedae9b512577:

  • File bs/execute/vm.cpp — part of check-in [1ad61a2717] at 2021-11-11 20:05:58 on branch trunk — Refactored the code builder: it is now carried around as a Value, and accessed through a bunch of extension points, so we can have different builders (and even user defined ones) later to make classes etc. (user: zlodo size: 9670)

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