#include "externals.hh" #include "common.hh" #include "c-project.hh" #include "c-ops.hh" #include "c-opcomp.hh" #include using namespace ebcl; using namespace ops; using namespace opast; #define M_LOGSTR_( S , L ) \ logger( [](){ return T_StringBuilder{ S }; } , L ) namespace { struct T_CompilerImpl_ { T_CompilerImpl_( F_OPLogger* const logger , const bool noUIInstructions ) noexcept : logger( *logger ) , noUIInstructions( noUIInstructions ) {} P_OpProgram compile( T_OpsParserOutput const& input ) noexcept; private: F_OPLogger& logger; const bool noUIInstructions; T_Visitor< A_Node > astVisitor{ ASTVisitorBrowser }; T_Set< uint32_t > constants{ UseTag< IndexBacked< > >( ) }; T_KeyValueTable< T_String , uint32_t > locations; T_OpsParserOutput* input; P_OpProgram output; uint32_t fiVariables , fiFramebuffers , fiPipelines , fiPrograms , fiSamplers , fiTextures; uint32_t sdMain , sdFPU; /* * Conditionals look something like this: * * 01 { compute expression and push it } * 02 skip-cond value-for-case-1 2 { 5 - 2 - 1 } * 03 { case 1 code } * 04 skip 6 { 11 - 4 - 1 } * 05 skip-cond value-for-case-2 3 { 9 - 5 - 1 } * 06 { case 2 code } * 07 { case 2 code } * 08 skip 2 { 11 - 8 - 1 } * 09 { default case code } * 10 { default case code } * 11 pop 1 * * So, in order to adjust the skips once we know about them, * we need to store the location of the previous skip-cond, * and the locations of all end-of-case skips. */ struct T_CondInfo_ { uint32_t prevCase; T_AutoArray< uint32_t , 16 > caseEnds; }; T_AutoArray< T_CondInfo_ , 16 > condJumps; uint32_t fbCurrent; // Current framebuffer attachment void gatherConstants( ) noexcept; void countAssets( ) noexcept; bool compileNode( uint32_t funcIndex , A_Node& node , bool exit ) noexcept; void processFunction( bool exit , uint32_t args , uint32_t lvars , T_SRDLocation const& location ) noexcept; bool processIdentifier( uint32_t funcIndex , T_IdentifierExprNode const& node ) noexcept; bool processIdentifier( uint32_t funcIndex , T_String const& id , T_SRDLocation const& location ) noexcept; void addInstruction( E_OpType op , T_SRDLocation const& location ) noexcept; void addInstruction( E_OpType op , uint32_t arg0 , T_SRDLocation const& location ) noexcept; void addInstruction( E_OpType op , std::initializer_list< uint32_t > args , T_SRDLocation const& location ) noexcept; void applyStackEffects( T_Op const& op ) noexcept; }; P_OpProgram T_CompilerImpl_::compile( T_OpsParserOutput const& in ) noexcept { input = const_cast< T_OpsParserOutput* >( &in ); output = NewOwned< T_OpProgram >( ); // Gather all constants used in expressions, count resources gatherConstants( ); countAssets( ); // Get function indices // FIXME ideally we should remap functions so that init is always 0 // and frame is always 1 output->init = input->root.functionIndex( "*init*" ); output->frame = input->root.functionIndex( "*frame*" ); logger( [this]( ) { T_StringBuilder sb; sb << "Function indices\n\tinit: " << output->init << "\n\tframe: " << output->frame; return sb; } , 3 ); // Compile each function uint32_t nInstr = 0; uint32_t cfi; for ( cfi = 0u ; cfi < input->root.nFunctions( ) ; cfi ++ ) { output->ops.next( ); auto& func( input->root.function( cfi ) ); logger( [&]( ) { T_StringBuilder sb; sb << "... Compiling function " << func.name( ); return sb; } , 3 ); sdMain = sdFPU = 0; astVisitor.visit( func , [=]( A_Node& node , const bool exit ) -> bool { return compileNode( cfi , node , exit ); } ); logger( [&]( ) { T_StringBuilder sb , temp; sb << "...... Compiled function " << func.name( ); for ( auto i = 0u ; i < output->ops.sizeOf( cfi ) ; i ++ ) { temp << "(" << output->ops.get( cfi , i ) << ")"; while ( temp.length( ) < 30 ) { temp << ' '; } sb << "\n\t\t" << temp << "{ " << output->ops.get( cfi , i ).location << " }"; temp.clear( ); } return sb; } , 6 ); logger( [&]( ) { T_StringBuilder sb; sb << "...... " << output->ops.sizeOf( cfi ) << " instructions"; return sb; } , 3 ); nInstr += output->ops.sizeOf( cfi ); } logger( [=]( ) { T_StringBuilder sb; sb << "... Generated " << nInstr << " instructions"; return sb; } , 2 ); return std::move( output ); } void T_CompilerImpl_::gatherConstants( ) noexcept { M_LOGSTR_( "... Gathering constants" , 2 ); constants.clear( ); astVisitor.visit( input->root , [&]( A_Node& node , const bool exit ) { if ( exit ) { return true; } T_OpValue value; if ( node.type( ) == A_Node::EXPR_CONST ) { value.f = dynamic_cast< T_ConstantExprNode& >( node ).floatValue( ); } else if ( node.type( ) == A_Node::OP_INPUT ) { value.f = dynamic_cast< T_InputInstrNode& >( node ).defValue( ); } else { return true; } constants.add( value.u ); return true; } ); for ( auto i = 0u ; i < constants.size( ) ; i ++ ) { output->constants.add( constants[ i ] ); } logger( [&]() { T_StringBuilder sb; char temp[ 9 ]; sb << constants.size( ) << " constants\n\t"; for ( auto i = 0u ; i < constants.size( ) ; i ++ ) { snprintf( temp , 9 , "%08x" , constants[ i ] ); sb << " " << temp; if ( i % 4 == 3 && i != constants.size( ) - 1 ) { sb << "\n\t"; } } return sb; } , 3 ); } void T_CompilerImpl_::countAssets( ) noexcept { M_LOGSTR_( "... Counting resources" , 2 ); locations.clear( ); locations.add( T_String::Pooled( "time" ) , 0u ); locations.add( T_String::Pooled( "width" ) , 1u ); locations.add( T_String::Pooled( "height" ) , 2u ); auto const nt{ input->types.size( ) }; for ( auto i = 0u ; i < nt ; i ++ ) { const auto t{ input->types.values( )[ i ] }; auto const& n{ input->types.keys( )[ i ] }; switch ( t ) { case E_DataType::FRAMEBUFFER: locations.add( n , output->nFramebuffers ); output->nFramebuffers ++; break; case E_DataType::PIPELINE: locations.add( n , output->nPipelines ); output->nPipelines ++; break; case E_DataType::PROGRAM: locations.add( n , output->nPrograms ); output->nPrograms ++; break; case E_DataType::SAMPLER: locations.add( n , output->nSamplers ); output->nSamplers ++; break; case E_DataType::TEXTURE: locations.add( n , output->nTextures ); output->nTextures ++; break; case E_DataType::VARIABLE: locations.add( n , output->nVariables + 3 ); output->nVariables ++; break; case E_DataType::INPUT: assert( !output->inputs.contains( input->types.keys( )[ i ] ) ); output->inputs.add( input->types.keys( )[ i ] ); break; case E_DataType::BUILTIN: case E_DataType::UNKNOWN: break; } } fiVariables = 3 + constants.size( ); fiFramebuffers = fiVariables + output->nVariables; fiPipelines = fiFramebuffers + output->nFramebuffers; fiPrograms = fiPipelines + output->nPipelines; fiSamplers = fiPrograms + output->nPrograms; fiTextures = fiSamplers + output->nSamplers; for ( auto i = 0u ; i < nt ; i ++ ) { const auto li{ locations.indexOf( input->types.keys( )[ i ] ) }; if ( li == T_HashIndex::INVALID_INDEX ) { continue; } const auto t{ input->types.values( )[ i ] }; auto& pos{ locations[ li ] }; switch ( t ) { case E_DataType::FRAMEBUFFER: pos += fiFramebuffers; break; case E_DataType::PIPELINE: pos += fiPipelines; break; case E_DataType::PROGRAM: pos += fiPrograms; break; case E_DataType::SAMPLER: pos += fiSamplers; break; case E_DataType::TEXTURE: pos += fiTextures; break; case E_DataType::VARIABLE: case E_DataType::INPUT: case E_DataType::BUILTIN: case E_DataType::UNKNOWN: break; } } logger( [&]() { T_StringBuilder sb; sb << "... Resource counts:\n" << "\t\tFramebuffers:\t" << output->nFramebuffers << '\n' << "\t\tPipelines: \t" << output->nPipelines << '\n' << "\t\tPrograms: \t" << output->nPrograms << '\n' << "\t\tSamplers: \t" << output->nSamplers << '\n' << "\t\tTextures: \t" << output->nTextures << '\n' << "\t\tInputs: \t" << output->inputs.size( ) << '\n' << "\tTable ranges:\n" << "\t\tBuilt-ins \t0\t2\n" << "\t\tConstants \t3\t" << fiVariables - 1 << '\n' << "\t\tVariables \t" << fiVariables << '\t' << fiFramebuffers - 1 << '\n' << "\t\tFramebuffers \t" << fiFramebuffers << '\t' << fiPipelines - 1 << '\n' << "\t\tPipelines \t" << fiPipelines << '\t' << fiPrograms - 1 << '\n' << "\t\tPrograms \t" << fiPrograms << '\t' << fiSamplers - 1 << '\n' << "\t\tSamplers \t" << fiSamplers << '\t' << fiTextures - 1 << '\n' << "\t\tTextures \t" << fiTextures << '\t' << fiTextures + output->nTextures - 1 ; return sb; } , 3 ); logger( [&]() { T_StringBuilder sb; T_Array< uint32_t > indices( locations.size( ) ); indices.ensureCapacity( locations.size( ) ); for ( auto i = 0u ; i < locations.size( ) ; i ++ ) { indices.add( i ); } indices.sort( [this]( uint32_t a , uint32_t b ) { return T_Comparator< uint32_t >::compare( locations.values( )[ a ] , locations.values( )[ b ] ); } ); sb << "... Location map (constants not included)"; for ( auto idx : indices ) { sb << "\n\t" << locations.values( )[ idx ] << '\t' << locations.keys( )[ idx ]; } return sb; } , 4 ); } bool T_CompilerImpl_::compileNode( const uint32_t funcIndex , A_Node& node , const bool exit ) noexcept { switch ( node.type( ) ) { //- PROGRAM STRUCTURE ----------------------------------------------------------------- case A_Node::ROOT: fprintf( stderr , "Internal error: root node found during compilation\n" ); std::abort( ); break; case A_Node::ILIST: break; case A_Node::OP_LOCALS: break; case A_Node::DECL_FN: { T_FuncNode& fn( (T_FuncNode&) node ); const auto args( fn.arguments( ) ); processFunction( exit , args , fn.locals( ) - args , node.location( ) ); break; } case A_Node::DECL_INIT: { A_FuncNode& fn( (A_FuncNode&) node ); processFunction( exit , 0 , fn.locals( ) , node.location( ) ); if ( output->nFramebuffers ) { addInstruction( OP_OFFSET , fiFramebuffers , node.location( ) ); addInstruction( OP_GEN_ASSETS , { 0 , output->nFramebuffers } , node.location( ) ); } if ( output->nPipelines ) { addInstruction( OP_OFFSET , fiPipelines , node.location( ) ); addInstruction( OP_GEN_ASSETS , { 1 , output->nPipelines } , node.location( ) ); } if ( output->nSamplers ) { addInstruction( OP_OFFSET , fiSamplers , node.location( ) ); addInstruction( OP_GEN_ASSETS , { 2 , output->nSamplers } , node.location( ) ); } if ( output->nTextures ) { addInstruction( OP_OFFSET , fiTextures , node.location( ) ); addInstruction( OP_GEN_ASSETS , { 3 , output->nTextures } , node.location( ) ); } break; } case A_Node::DECL_FRAME: { A_FuncNode& fn( (A_FuncNode&) node ); processFunction( exit , 0 , fn.locals( ) , node.location( ) ); break; } //- GENERAL / FLOW CONTROL INSTRUCTIONS ----------------------------------------------- case A_Node::OP_CALL: if ( exit ) { auto& call( (T_CallInstrNode&) node ); const auto fi( input->root.functionIndex( call.id( ) ) ); assert( fi >= 0 ); auto& callee( input->root.function( fi ) ); assert( callee.type( ) == A_Node::DECL_FN ); auto& fcallee( (T_FuncNode&) callee ); const auto args( fcallee.arguments( ) ); assert( sdMain > args ); addInstruction( OP_CALL , fi , node.location( ) ); sdMain -= args; } break; case A_Node::OP_SET: if ( exit ) { auto& id( ((T_SetInstrNode&)node).id( ) ); auto& func{ input->root.function( funcIndex ) }; if ( func.hasLocal( id ) ) { const auto pos( func.getLocalIndex( id ) + 1 ); addInstruction( OP_FP_SSTORE , sdMain - pos - 1 , node.location( ) ); } else { addInstruction( OP_FP_STORE , *locations.get( ((T_SetInstrNode&)node).id( ) ) , node.location( ) ); } } break; case A_Node::OP_COND: if ( exit ) { // Update all skips const auto cpos( output->ops.sizeOf( funcIndex ) ); auto const& skips( condJumps.last( ).caseEnds ); for ( auto i = 0u ; i < skips.size( ) ; i ++ ) { const auto skip( skips[ i ] ); assert( output->ops.get( funcIndex , skip ).op == OP_SKIP ); output->ops.get( funcIndex , skip ).args[ 0 ] = ( cpos - skip - 1 ); } addInstruction( OP_POP , 0 , node.location( ) ); sdMain --; condJumps.removeLast( ); } else { condJumps.addNew( ); } break; case A_Node::TN_CONDITION: if ( exit ) { addInstruction( OP_PUSH , node.location( ) ); addInstruction( OP_FP_SSTORE_INT , 0 , node.location( ) ); } break; case A_Node::TN_CASE: if ( exit ) { // Store a potential skip location condJumps.last( ).caseEnds.add( output->ops.sizeOf( funcIndex ) ); // Update the initial skip const auto cpos( output->ops.sizeOf( funcIndex ) ); const auto ppos( condJumps.last( ).prevCase ); const auto diff( cpos - ppos ); output->ops.get( funcIndex , ppos ).args[ 0 ] = diff - 1; } else { // If there is a previous skip location, insert the skip instruction if ( !condJumps.last( ).caseEnds.empty( ) ) { addInstruction( OP_SKIP , 0 , node.location( ) ); const auto ppos( condJumps.last( ).prevCase ); assert( output->ops.get( funcIndex , ppos ).op == OP_COND_SKIP ); output->ops.get( funcIndex , ppos ).args[ 0 ] ++; } // Add the conditional skip auto& c( (T_CondInstrNode::T_ValuedCase&) node ); condJumps.last( ).prevCase = output->ops.sizeOf( funcIndex ); addInstruction( OP_COND_SKIP , { 0 , uint32_t( c.value( ) ) } , node.location( ) ); } break; case A_Node::TN_DEFAULT: // If there is a previous skip location, insert the skip instruction // and update the previous conditional skip if ( !( exit || condJumps.last( ).caseEnds.empty( ) ) ) { addInstruction( OP_SKIP , 0 , node.location( ) ); const auto ppos( condJumps.last( ).prevCase ); assert( output->ops.get( funcIndex , ppos ).op == OP_COND_SKIP ); output->ops.get( funcIndex , ppos ).args[ 0 ] ++; } break; //- ASSET DECLARATIONS ---------------------------------------------------------------- case A_Node::OP_PIPELINE: if ( exit ) { auto& pn( (T_PipelineInstrNode&) node ); for ( auto i = 0u ; i < pn.size( ) ; i ++ ) { processIdentifier( funcIndex , pn.program( i ) , pn.pLocation( i ) ); } processIdentifier( funcIndex , pn.id( ) , pn.location( ) ); addInstruction( OP_INIT_PIPELINE , pn.size( ) - 1 , pn.location( ) ); sdMain -= pn.size( ); } break; case A_Node::OP_PROGRAM: if ( exit ) { auto& pn( (T_ProgramInstrNode&) node ); const auto p{ pn.path( ).makeRelative( Common::Project( ).basePath( ) ) }; if ( !output->progNames.contains( p ) ) { output->progNames.add( p ); } assert( locations.contains( pn.id( ) ) ); addInstruction( OP_INIT_PROGRAM , output->progNames.indexOf( p ) , pn.location( ) ); addInstruction( OP_STORE , *locations.get( pn.id( ) ) , pn.idLocation( ) ); } break; case A_Node::OP_SAMPLER: if ( exit ) { auto& sn( (T_SamplerInstrNode&) node ); if ( !sn.minLod( ) ) { // XXX maybe use OP_RES_STACK addInstruction( OP_CONST , 0 , node.location( ) ); addInstruction( OP_PUSH , node.location( ) ); addInstruction( OP_PUSH , node.location( ) ); } processIdentifier( funcIndex , sn.id( ) , sn.idLocation( ) ); addInstruction( OP_INIT_SAMPLER , { ( uint32_t( sn.sampling( ) ) << 2 ) | ( ( sn.mipmap( ) ? 1 : 0 ) << 1 ) | ( sn.mipmap( ) ? uint32_t( *sn.mipmap( ) ) : 0 ) , uint32_t( sn.wrapping( ) ) } , node.location( ) ); } break; case A_Node::OP_TEXTURE: if ( exit ) { auto& tn( (T_TextureInstrNode&) node ); const auto hasLOD( tn.lods( ) ); processIdentifier( funcIndex , tn.id( ) , tn.idLocation( ) ); addInstruction( OP_INIT_TEXTURE , { uint32_t( tn.texType( ) ) , hasLOD ? 1u : 0u } , tn.location( ) ); if ( hasLOD ) { sdMain --; } } break; case A_Node::OP_FRAMEBUFFER: if ( !exit ) { auto& fbn( (T_FramebufferInstrNode&) node ); processIdentifier( funcIndex , fbn.id( ) , fbn.idLocation( ) ); addInstruction( OP_USE_FRAMEBUFFER , fbn.location( ) ); fbCurrent = 0; } break; case A_Node::TN_FBATT: if ( exit ) { auto& fan( (T_FramebufferInstrNode::T_Attachment&) node ); processIdentifier( funcIndex , fan.id( ) , fan.location( ) ); const uint32_t lod( fan.lod( ) ? 1 : 0 ) , id( fan.isDepth( ) ? 0 : ++fbCurrent ); addInstruction( OP_FB_ATTACH , { id , lod } , fan.location( ) ); if ( lod ) { sdMain --; } if ( id ) { addInstruction( OP_FB_TOGGLE , { id - 1 , 1 } , fan.location( ) ); } } break; //- STATE ----------------------------------------------------------------------------- case A_Node::OP_MAINOUT: if ( exit ) { addInstruction( OP_CONST , 0 , node.location( ) ); addInstruction( OP_PUSH , node.location( ) ); addInstruction( OP_USE_FRAMEBUFFER , node.location( ) ); } break; case A_Node::OP_IMAGE: if ( exit ) { auto& imn{ (T_ImageInstrNode&) node }; processIdentifier( funcIndex , imn.id( ) , imn.idLocation( ) ); addInstruction( OP_IMAGE , { uint32_t( imn.accessMode( ) ) , imn.hasParameter( T_ImageInstrNode::P_LAYER ) ? 1u : 0u } , node.location( ) ); } break; case A_Node::OP_USE_FRAMEBUFFER: if ( exit ) { auto& fbn( (T_UseInstrNode&) node ); processIdentifier( funcIndex , fbn.id( ) , fbn.idLocation( ) ); addInstruction( OP_USE_FRAMEBUFFER , fbn.location( ) ); } break; case A_Node::OP_USE_PIPELINE: if ( exit ) { auto& fbn( (T_UseInstrNode&) node ); processIdentifier( funcIndex , fbn.id( ) , fbn.idLocation( ) ); addInstruction( OP_USE_PIPELINE , fbn.location( ) ); } break; case A_Node::OP_USE_PROGRAM: if ( exit ) { auto& fbn( (T_UseInstrNode&) node ); processIdentifier( funcIndex , fbn.id( ) , fbn.idLocation( ) ); addInstruction( OP_USE_PROGRAM , fbn.location( ) ); } break; case A_Node::OP_USE_TEXTURE: if ( exit ) { auto& tn( (T_UseTextureInstrNode&) node ); processIdentifier( funcIndex , tn.samplerId( ) , tn.samplerIdLocation( ) ); processIdentifier( funcIndex , tn.id( ) , tn.idLocation( ) ); addInstruction( OP_USE_TEXTURE , tn.bank( ) , tn.location( ) ); } break; case A_Node::OP_UNIFORMS: if ( exit ) { auto& un( (T_UniformsInstrNode&) node ); addInstruction( OP_CONST , un.uloc( ) , un.ulocLocation( ) ); addInstruction( OP_PUSH , un.ulocLocation( ) ); processIdentifier( funcIndex , un.progId( ) , un.progIdLocation( ) ); addInstruction( OP_UNIFORMS , { un.size( ) - 1 , un.integers( ) ? 1u : 0u } , un.location( ) ); sdMain -= un.size( ); } break; case A_Node::OP_VIEWPORT: if ( exit ) { addInstruction( OP_VIEWPORT , node.location( ) ); } break; //- RENDERING ------------------------------------------------------------------------- case A_Node::OP_FULLSCREEN: if ( exit ) { addInstruction( OP_FULLSCREEN , node.location( ) ); } break; case A_Node::OP_CLEAR: if ( exit ) { addInstruction( OP_CLEAR , node.location( ) ); } break; case A_Node::OP_COMPUTE: if ( exit ) { addInstruction( OP_COMPUTE , node.location( ) ); } break; //- DEBUGGING / UI CONTROLS ----------------------------------------------------------- case A_Node::OP_PROFILE: if ( noUIInstructions ) { break; } if ( exit ) { addInstruction( OP_UI_PEXIT , node.location( ) ); } else { auto& pn( (T_ProfileInstrNode&) node ); if ( ! output->uiStrings.contains( pn.text( ) ) ) { output->uiStrings.add( pn.text( ) ); } addInstruction( OP_UI_PENTER , output->uiStrings.indexOf( pn.text( ) ) , pn.location( ) ); } break; case A_Node::OP_INPUT: if ( noUIInstructions ) { break; } if ( exit ) { auto& in( (T_InputInstrNode&) node ); T_OpValue value; value.f = in.defValue( ); addInstruction( OP_UI_INPUT_DFT , { uint32_t( output->inputs.indexOf( in.id( ) ) ) , uint32_t( constants.indexOf( value.u ) + 3 + output->nVariables ) } , node.location( ) ); } break; case A_Node::OP_ODBG: if ( noUIInstructions ) { break; } if ( exit ) { auto& n( (T_OutputDebugInstrNode&) node ); if ( ! output->uiStrings.contains( n.description( ) ) ) { output->uiStrings.add( n.description( ) ); } processIdentifier( funcIndex , n.texture( ) , n.textureLocation( ) ); addInstruction( OP_UI_ODBG , { uint32_t( n.mode( ) ) , uint32_t( output->uiStrings.indexOf( n.description( ) ) ) } , node.location( ) ); } break; case A_Node::OP_OVERRIDES: if ( noUIInstructions ) { break; } if ( exit ) { auto& n{ (T_OverridesInstrNode&) node }; const auto idx{ output->overrides.add( n.extractRoot( ) ) }; addInstruction( OP_UI_INPUT_OVR , idx , node.location( ) ); } break; //- EXPRESSIONS - ARGUMENTS ----------------------------------------------------------- case A_Node::TN_ARG: { auto& n( (T_ArgumentNode&)node ); if ( n.isIdentifier( ) && !exit ) { const bool main{ processIdentifier( funcIndex , (T_IdentifierExprNode&) n.expression( ) ) }; if ( !main ) { addInstruction( OP_PUSH , node.location( ) ); addInstruction( OP_FP_SSTORE , 0 , node.location( ) ); } return false; } else if ( exit && !n.isIdentifier( ) ) { addInstruction( OP_PUSH , node.location( ) ); addInstruction( OP_FP_SSTORE , 0 , node.location( ) ); } break; } //- EXPRESSIONS - OPERATORS ----------------------------------------------------------- case A_Node::EXPR_CMP_EQ: case A_Node::EXPR_CMP_NE: case A_Node::EXPR_CMP_GT: case A_Node::EXPR_CMP_GE: case A_Node::EXPR_CMP_LT: case A_Node::EXPR_CMP_LE: if ( exit ) { const uint32_t op( dynamic_cast< T_BinaryOperatorNode& >( node ).op( ) - T_BinaryOperatorNode::CMP_EQ ); addInstruction( OP_FP_CMP , op , node.location( ) ); } break; case A_Node::EXPR_ADD: if ( exit ) { addInstruction( OP_FP_ADD , node.location( ) ); } break; case A_Node::EXPR_SUB: if ( exit ) { addInstruction( OP_FP_SUB , node.location( ) ); } break; case A_Node::EXPR_MUL: if ( exit ) { addInstruction( OP_FP_MUL , node.location( ) ); } break; case A_Node::EXPR_DIV: if ( exit ) { addInstruction( OP_FP_DIV , node.location( ) ); } break; case A_Node::EXPR_POW: if ( exit ) { addInstruction( OP_FP_POW , node.location( ) ); } break; case A_Node::EXPR_NEG: if ( exit ) { addInstruction( OP_FP_NEG , node.location( ) ); } break; case A_Node::EXPR_INV: if ( exit ) { addInstruction( OP_FP_INV , node.location( ) ); } break; case A_Node::EXPR_NOT: if ( exit ) { addInstruction( OP_FP_NOT , node.location( ) ); } break; case A_Node::EXPR_SIN: if ( exit ) { addInstruction( OP_FP_SIN , node.location( ) ); } break; case A_Node::EXPR_COS: if ( exit ) { addInstruction( OP_FP_COS , node.location( ) ); } break; case A_Node::EXPR_TAN: if ( exit ) { addInstruction( OP_FP_TAN , node.location( ) ); } break; case A_Node::EXPR_SQRT: if ( exit ) { addInstruction( OP_FP_SQRT , node.location( ) ); } break; case A_Node::EXPR_EXP: if ( exit ) { addInstruction( OP_FP_EXP , node.location( ) ); } break; case A_Node::EXPR_LN: if ( exit ) { addInstruction( OP_FP_LN , node.location( ) ); } break; //- EXPRESSIONS - TERMINAL NODES ------------------------------------------------------ case A_Node::EXPR_CONST: if ( !exit ) { T_OpValue value; value.f = dynamic_cast< T_ConstantExprNode& >( node ).floatValue( ); addInstruction( OP_FP_LOAD , constants.indexOf( value.u ) + 3 + output->nVariables , node.location( ) ); } break; case A_Node::EXPR_ID: if ( !exit ) { processIdentifier( funcIndex , dynamic_cast< T_IdentifierExprNode& >( node ) ); } break; case A_Node::EXPR_INPUT: if ( !exit ) { auto& in( (T_InputExprNode&) node ); assert( output->inputs.contains( in.id( ) ) ); addInstruction( OP_GET_INPUT , output->inputs.indexOf( in.id( ) ) , in.location( ) ); } break; } return true; } void T_CompilerImpl_::processFunction( const bool exit , const uint32_t args , const uint32_t lvars , T_SRDLocation const& location ) noexcept { if ( exit ) { assert( sdMain == args + lvars + 1 ); assert( sdFPU == 0 ); if ( lvars ) { addInstruction( OP_POP , lvars - 1 , location ); sdMain -= lvars; } sdMain -= args; addInstruction( OP_RET , args , location ); assert( sdMain == 0 ); } else { if ( lvars ) { addInstruction( OP_RES_STACK , lvars - 1 , location ); sdMain += lvars; } sdMain += 1 + args; } } // Returns true if the identifier caused a push to the main stack, false // if it pushed to the FPU stack. bool T_CompilerImpl_::processIdentifier( const uint32_t funcIndex , T_IdentifierExprNode const& node ) noexcept { return processIdentifier( funcIndex , node.id( ) , node.location( ) ); } bool T_CompilerImpl_::processIdentifier( uint32_t funcIndex , T_String const& id , T_SRDLocation const& location ) noexcept { auto& func{ input->root.function( funcIndex ) }; if ( func.hasLocal( id ) ) { const E_DataType dt{ func.getLocalType( id ) }; assert( dt != E_DataType::UNKNOWN ); uint32_t stackPos; if ( func.isArgument( id ) ) { auto const& fn( (T_FuncNode&) func ); const auto nArgs( fn.arguments( ) ); stackPos = nArgs - 1 - func.getLocalIndex( id ); } else { stackPos = func.getLocalIndex( id ) + 1; } assert( stackPos < sdMain ); const auto p( sdMain - ( stackPos + 1 ) ); if ( dt == E_DataType::VARIABLE ) { addInstruction( OP_FP_SLOAD , p , location ); return false; } addInstruction( OP_SLOAD , p , location ); addInstruction( OP_PUSH , location ); return true; } assert( input->types.contains( id ) ); const E_DataType dt{ *( input->types.get( id ) ) }; assert( dt != E_DataType::UNKNOWN ); assert( locations.contains( id ) ); if ( dt == E_DataType::VARIABLE || dt == E_DataType::BUILTIN ) { addInstruction( OP_FP_LOAD , *locations.get( id ) , location ); return false; } addInstruction( OP_LOAD , *locations.get( id ) , location ); addInstruction( OP_PUSH , location ); return true; } void T_CompilerImpl_::addInstruction( const E_OpType op , T_SRDLocation const& location ) noexcept { assert( ArgumentsFor( op ) == 0 ); applyStackEffects( output->ops.addNew( op , location ) ); } void T_CompilerImpl_::addInstruction( const E_OpType op , const uint32_t arg0 , T_SRDLocation const& location ) noexcept { assert( ArgumentsFor( op ) == 1 ); applyStackEffects( output->ops.addNew( op , location , arg0 ) ); } void T_CompilerImpl_::addInstruction( const E_OpType op , std::initializer_list< uint32_t > args , T_SRDLocation const& location ) noexcept { assert( ArgumentsFor( op ) == args.size( ) ); applyStackEffects( output->ops.addNew( op , location , args ) ); } void T_CompilerImpl_::applyStackEffects( T_Op const& op ) noexcept { const auto m( DeltaMainStack( op.op ) ); const auto f( DeltaFPUStack( op.op ) ); logger( [&]() { T_StringBuilder sb; sb << "applying stack effects for (" << op << ") - sdMain " << sdMain << " (" << m << ") sdFPU " << sdFPU << " (" << f << ')'; return sb; } , 5 ); if ( m ) { assert( m > 0 || sdMain >= uint32_t( -m ) ); sdMain += m; } if ( f ) { assert( ( f > 0 && sdFPU + f < 8 ) || ( f < 0 && sdFPU >= uint32_t( -m ) ) ); sdFPU += m; } } } /*= T_OpsCompiler ==============================================================*/ T_OpsCompiler::T_OpsCompiler( const bool noUIInstructions ) noexcept : A_PrivateImplementation( new T_CompilerImpl_( &logger_ , noUIInstructions ) ) { } P_OpProgram T_OpsCompiler::compile( T_OpsParserOutput const& input ) noexcept { return p< T_CompilerImpl_ >( ).compile( input ); }