From c8d69237d71b4653152c55699686309255c1907b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:30:30 +0100 Subject: [PATCH 1/2] LLVMCodeBuilder: Store return type in return register --- src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 7 +++---- src/dev/engine/internal/llvm/llvminstruction.h | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index 0ad66c9f..07008993 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -107,12 +107,13 @@ std::shared_ptr LLVMCodeBuilder::finalize() args.push_back(castValue(arg.second, arg.first)); } - llvm::Value *ret = m_builder.CreateCall(resolveFunction(step.functionName, llvm::FunctionType::get(getType(step.functionReturnType), types, false)), args); + llvm::Type *retType = getType(step.functionReturnReg ? step.functionReturnReg->type : Compiler::StaticType::Void); + llvm::Value *ret = m_builder.CreateCall(resolveFunction(step.functionName, llvm::FunctionType::get(retType, types, false)), args); if (step.functionReturnReg) { step.functionReturnReg->value = ret; - if (step.functionReturnType == Compiler::StaticType::String) + if (step.functionReturnReg->type == Compiler::StaticType::String) m_heap.push_back(step.functionReturnReg->value); } @@ -705,8 +706,6 @@ void LLVMCodeBuilder::addFunctionCall(const std::string &functionName, Compiler: m_tmpRegs.erase(m_tmpRegs.end() - argTypes.size(), m_tmpRegs.end()); - ins.functionReturnType = returnType; - if (returnType != Compiler::StaticType::Void) { auto reg = std::make_shared(returnType); reg->isRawValue = true; diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h index 81fed9cd..41186cad 100644 --- a/src/dev/engine/internal/llvm/llvminstruction.h +++ b/src/dev/engine/internal/llvm/llvminstruction.h @@ -61,7 +61,6 @@ struct LLVMInstruction Type type; std::string functionName; std::vector> args; // target type, register - Compiler::StaticType functionReturnType = Compiler::StaticType::Void; LLVMRegisterPtr functionReturnReg; Variable *workVariable = nullptr; // for variables }; From 48cf8d30b3cb2e573e70599acc6a692ce980a9df Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:26:14 +0100 Subject: [PATCH 2/2] LLVMCodeBuilder: Implement variable script type prediction --- .../engine/internal/llvm/llvmcodebuilder.cpp | 169 +++++++++-- .../engine/internal/llvm/llvmcodebuilder.h | 7 +- .../engine/internal/llvm/llvmvariableptr.h | 3 +- test/dev/llvm/llvmcodebuilder_test.cpp | 271 ++++++++++++++++++ 4 files changed, 422 insertions(+), 28 deletions(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index 07008993..5a304a71 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -80,16 +80,18 @@ std::shared_ptr LLVMCodeBuilder::finalize() for (auto &[var, varPtr] : m_variablePtrs) { llvm::Value *ptr = getVariablePtr(targetVariables, var); - // Access variable directly (slow?) - // varPtr.ptr = ptr; + // Direct access + varPtr.heapPtr = ptr; - // All variables are currently copied to the stack and synced later (seems to be faster) + // All variables are currently created on the stack and synced later (seems to be faster) // NOTE: Strings are NOT copied, only the pointer and string size are copied - varPtr.ptr = m_builder.CreateAlloca(m_valueDataType); - varPtr.onStack = true; - createValueCopy(ptr, varPtr.ptr); + varPtr.stackPtr = m_builder.CreateAlloca(m_valueDataType); + varPtr.onStack = false; // use heap before the first assignment } + m_scopeVariables.clear(); + m_scopeVariables.push_back({}); + // Execute recorded steps for (const LLVMInstruction &step : m_instructions) { switch (step.type) { @@ -430,16 +432,41 @@ std::shared_ptr LLVMCodeBuilder::finalize() assert(step.args.size() == 1); assert(m_variablePtrs.find(step.workVariable) != m_variablePtrs.cend()); const auto &arg = step.args[0]; + Compiler::StaticType type = optimizeRegisterType(arg.second); LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable]; varPtr.changed = true; - createValueStore(arg.second, varPtr.ptr); + + // Initialize stack variable on first assignment + if (!varPtr.onStack) { + varPtr.onStack = true; + varPtr.type = type; // don't care about unknown type on first assignment + + ValueType mappedType; + + if (type == Compiler::StaticType::String || type == Compiler::StaticType::Unknown) { + // Value functions are used for these types, so don't break them + mappedType = ValueType::Number; + } else { + auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [type](const std::pair &pair) { return pair.second == type; }); + assert(it != TYPE_MAP.cend()); + mappedType = it->first; + } + + llvm::Value *typeField = m_builder.CreateStructGEP(m_valueDataType, varPtr.stackPtr, 1); + m_builder.CreateStore(m_builder.getInt32(static_cast(mappedType)), typeField); + } + + createValueStore(arg.second, varPtr.stackPtr, type, varPtr.type); + varPtr.type = type; + m_scopeVariables.back()[&varPtr] = varPtr.type; break; } case LLVMInstruction::Type::ReadVariable: { assert(step.args.size() == 0); const LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable]; - step.functionReturnReg->value = varPtr.ptr; + step.functionReturnReg->value = varPtr.onStack ? varPtr.stackPtr : varPtr.heapPtr; + step.functionReturnReg->type = varPtr.type; break; } @@ -448,6 +475,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() freeHeap(); syncVariables(targetVariables); coro->createSuspend(); + reloadVariables(targetVariables); } break; @@ -468,6 +496,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() m_builder.SetInsertPoint(statement.body); ifStatements.push_back(statement); + pushScopeLevel(); break; } @@ -475,6 +504,15 @@ std::shared_ptr LLVMCodeBuilder::finalize() assert(!ifStatements.empty()); LLVMIfStatement &statement = ifStatements.back(); + // Restore types from parent scope + std::unordered_map parentScopeVariables = m_scopeVariables[m_scopeVariables.size() - 2]; // no reference! + popScopeLevel(); + + for (auto &[ptr, type] : parentScopeVariables) + ptr->type = type; + + pushScopeLevel(); + // Jump to the branch after the if statement assert(!statement.afterIf); statement.afterIf = llvm::BasicBlock::Create(m_ctx, "", func); @@ -516,6 +554,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() m_builder.SetInsertPoint(statement.afterIf); ifStatements.pop_back(); + popScopeLevel(); break; } @@ -569,6 +608,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() m_builder.SetInsertPoint(body); loops.push_back(loop); + pushScopeLevel(); break; } @@ -590,6 +630,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() // Switch to body branch m_builder.SetInsertPoint(body); + pushScopeLevel(); break; } @@ -611,6 +652,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() // Switch to body branch m_builder.SetInsertPoint(body); + pushScopeLevel(); break; } @@ -644,6 +686,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() m_builder.SetInsertPoint(loop.afterLoop); loops.pop_back(); + popScopeLevel(); break; } } @@ -728,7 +771,6 @@ void LLVMCodeBuilder::addConstValue(const Value &value) void LLVMCodeBuilder::addVariableValue(Variable *variable) { - // TODO: Implement type prediction LLVMInstruction ins(LLVMInstruction::Type::ReadVariable); ins.workVariable = variable; m_variablePtrs[variable] = LLVMVariablePtr(); @@ -989,6 +1031,23 @@ void LLVMCodeBuilder::createVariableMap() } } +void LLVMCodeBuilder::pushScopeLevel() +{ + m_scopeVariables.push_back({}); +} + +void LLVMCodeBuilder::popScopeLevel() +{ + for (size_t i = 0; i < m_scopeVariables.size() - 1; i++) { + for (auto &[ptr, type] : m_scopeVariables[i]) { + if (ptr->type != type) + ptr->type = Compiler::StaticType::Unknown; + } + } + + m_scopeVariables.pop_back(); +} + void LLVMCodeBuilder::verifyFunction(llvm::Function *func) { if (llvm::verifyFunction(*func, &llvm::errs())) { @@ -1198,6 +1257,17 @@ llvm::Constant *LLVMCodeBuilder::castConstValue(const Value &value, Compiler::St } } +Compiler::StaticType LLVMCodeBuilder::optimizeRegisterType(LLVMRegisterPtr reg) +{ + Compiler::StaticType ret = reg->type; + + // Optimize string constants that represent numbers + if (reg->isConstValue && reg->type == Compiler::StaticType::String && reg->constValue.isValidNumber()) + ret = Compiler::StaticType::Number; + + return ret; +} + llvm::Type *LLVMCodeBuilder::getType(Compiler::StaticType type) { switch (type) { @@ -1247,14 +1317,25 @@ llvm::Value *LLVMCodeBuilder::getVariablePtr(llvm::Value *targetVariables, Varia void LLVMCodeBuilder::syncVariables(llvm::Value *targetVariables) { + // Copy stack variables to the actual variables for (auto &[var, varPtr] : m_variablePtrs) { if (varPtr.onStack && varPtr.changed) - createValueCopy(varPtr.ptr, getVariablePtr(targetVariables, var)); + createValueCopy(varPtr.stackPtr, getVariablePtr(targetVariables, var)); varPtr.changed = false; } } +void LLVMCodeBuilder::reloadVariables(llvm::Value *targetVariables) +{ + // Reset variables to use heap + for (auto &[var, varPtr] : m_variablePtrs) { + varPtr.onStack = false; + varPtr.changed = false; + varPtr.type = Compiler::StaticType::Unknown; + } +} + LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount) { LLVMInstruction ins(type); @@ -1279,33 +1360,69 @@ LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler: return m_instructions.back(); } -void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr) +void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType) { - // TODO: Implement type prediction - Compiler::StaticType type = reg->type; llvm::Value *converted = nullptr; - // Optimize string constants that represent numbers - if (reg->isConstValue && reg->type == Compiler::StaticType::String && reg->constValue.isValidNumber()) - type = Compiler::StaticType::Number; + if (sourceType != Compiler::StaticType::Unknown) + converted = castValue(reg, sourceType); - switch (type) { + auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [sourceType](const std::pair &pair) { return pair.second == sourceType; }); + const ValueType mappedType = it == TYPE_MAP.cend() ? ValueType::Number : it->first; // unknown type can be ignored + + switch (sourceType) { case Compiler::StaticType::Number: - converted = castValue(reg, type); - m_builder.CreateCall(resolve_value_assign_double(), { targetPtr, converted }); - /*{ - llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); - m_builder.CreateStore(converted, ptr); - }*/ + switch (targetType) { + case Compiler::StaticType::Number: { + // Write number to number directly + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + m_builder.CreateStore(converted, ptr); + break; + } + + case Compiler::StaticType::Bool: { + // Write number to bool value directly and change type + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + m_builder.CreateStore(converted, ptr); + m_builder.CreateStore(m_builder.getInt32(static_cast(mappedType)), typePtr); + break; + } + + default: + m_builder.CreateCall(resolve_value_assign_double(), { targetPtr, converted }); + break; + } + break; case Compiler::StaticType::Bool: - converted = castValue(reg, type); - m_builder.CreateCall(resolve_value_assign_bool(), { targetPtr, converted }); + switch (targetType) { + case Compiler::StaticType::Number: { + // Write bool to number value directly and change type + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + m_builder.CreateStore(converted, ptr); + llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + m_builder.CreateStore(converted, ptr); + m_builder.CreateStore(m_builder.getInt32(static_cast(mappedType)), typePtr); + break; + } + + case Compiler::StaticType::Bool: { + // Write bool to bool directly + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + m_builder.CreateStore(converted, ptr); + break; + } + + default: + m_builder.CreateCall(resolve_value_assign_bool(), { targetPtr, converted }); + break; + } + break; case Compiler::StaticType::String: - converted = castValue(reg, type); m_builder.CreateCall(resolve_value_assign_cstring(), { targetPtr, converted }); break; diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h index 2a89753b..6117b02f 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -83,6 +83,8 @@ class LLVMCodeBuilder : public ICodeBuilder void initTypes(); void createVariableMap(); + void pushScopeLevel(); + void popScopeLevel(); void verifyFunction(llvm::Function *func); void optimize(); @@ -91,16 +93,18 @@ class LLVMCodeBuilder : public ICodeBuilder llvm::Value *castValue(LLVMRegisterPtr reg, Compiler::StaticType targetType); llvm::Value *castRawValue(LLVMRegisterPtr reg, Compiler::StaticType targetType); llvm::Constant *castConstValue(const Value &value, Compiler::StaticType targetType); + Compiler::StaticType optimizeRegisterType(LLVMRegisterPtr reg); llvm::Type *getType(Compiler::StaticType type); llvm::Value *isNaN(llvm::Value *num); llvm::Value *removeNaN(llvm::Value *num); llvm::Value *getVariablePtr(llvm::Value *targetVariables, Variable *variable); void syncVariables(llvm::Value *targetVariables); + void reloadVariables(llvm::Value *targetVariables); LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount); - void createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr); + void createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType); void createValueCopy(llvm::Value *source, llvm::Value *target); void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType); llvm::Value *createValue(LLVMRegisterPtr reg); @@ -130,6 +134,7 @@ class LLVMCodeBuilder : public ICodeBuilder Target *m_target = nullptr; std::unordered_map m_targetVariableMap; std::unordered_map m_variablePtrs; + std::vector> m_scopeVariables; std::string m_id; llvm::LLVMContext m_ctx; diff --git a/src/dev/engine/internal/llvm/llvmvariableptr.h b/src/dev/engine/internal/llvm/llvmvariableptr.h index bfc65e3f..c2525dfd 100644 --- a/src/dev/engine/internal/llvm/llvmvariableptr.h +++ b/src/dev/engine/internal/llvm/llvmvariableptr.h @@ -16,7 +16,8 @@ namespace libscratchcpp struct LLVMVariablePtr { - llvm::Value *ptr = nullptr; + llvm::Value *stackPtr = nullptr; + llvm::Value *heapPtr = nullptr; Compiler::StaticType type = Compiler::StaticType::Unknown; bool onStack = false; bool changed = false; diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index b0597e2a..58e467d4 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -1973,6 +1973,53 @@ TEST_F(LLVMCodeBuilderTest, Yield) ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); } +TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", "", 87); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", "test"); + sprite.addVariable(localVar); + + createBuilder(&sprite, false); + + m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get()); + + m_builder->addConstValue(true); + m_builder->createVariableWrite(localVar.get()); + + m_builder->yield(); + + m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + std::string expected = + "hello world\n" + "-4.8\n"; + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&sprite); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + globalVar->setValue("hello world"); + localVar->setValue(-4.8); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + TEST_F(LLVMCodeBuilderTest, IfStatement) { createBuilder(true); @@ -2144,6 +2191,100 @@ TEST_F(LLVMCodeBuilderTest, IfStatement) ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); } +TEST_F(LLVMCodeBuilderTest, IfStatementVariables) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", "", "test"); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", 87); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get()); + + m_builder->addConstValue(true); + m_builder->createVariableWrite(localVar.get()); + + m_builder->addConstValue(true); + m_builder->beginIfStatement(); + { + m_builder->addConstValue("hello world"); + m_builder->createVariableWrite(globalVar.get()); + } + m_builder->endIf(); + + m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get()); + + m_builder->addConstValue(false); + m_builder->beginIfStatement(); + { + m_builder->addConstValue("hello world"); + m_builder->createVariableWrite(globalVar.get()); + + m_builder->addConstValue(0); + m_builder->createVariableWrite(localVar.get()); + } + m_builder->endIf(); + + m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(true); + m_builder->beginIfStatement(); + { + m_builder->addConstValue(true); + m_builder->beginIfStatement(); + { + m_builder->addConstValue(true); + m_builder->createVariableWrite(globalVar.get()); + } + m_builder->endIf(); + + m_builder->addConstValue(false); + m_builder->beginIfStatement(); + { + m_builder->addConstValue(-8.2); + m_builder->createVariableWrite(localVar.get()); + } + m_builder->endIf(); + } + m_builder->endIf(); + + m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + std::string expected = + "hello world\n" + "12.5\n" + "true\n" + "true\n" + "true\n"; + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&sprite); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + TEST_F(LLVMCodeBuilderTest, RepeatLoop) { createBuilder(true); @@ -2486,3 +2627,133 @@ TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop) ASSERT_FALSE(code->isFinished(ctx.get())); } } + +TEST_F(LLVMCodeBuilderTest, LoopVariables) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", "", "test"); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", 87); + auto counter1 = std::make_shared("", ""); + auto counter2 = std::make_shared("", ""); + sprite.addVariable(localVar); + sprite.addVariable(counter1); + sprite.addVariable(counter2); + + createBuilder(&sprite, true); + + m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get()); + + m_builder->addConstValue(true); + m_builder->createVariableWrite(localVar.get()); + + m_builder->addConstValue(2); + m_builder->beginRepeatLoop(); + { + m_builder->addConstValue("hello world"); + m_builder->createVariableWrite(globalVar.get()); + } + m_builder->endLoop(); + + m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get()); + + m_builder->addConstValue(0); + m_builder->beginRepeatLoop(); + { + m_builder->addConstValue("hello world"); + m_builder->createVariableWrite(globalVar.get()); + + m_builder->addConstValue(0); + m_builder->createVariableWrite(localVar.get()); + } + m_builder->endLoop(); + + m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addConstValue(0); + m_builder->createVariableWrite(counter1.get()); + + m_builder->beginLoopCondition(); + m_builder->addVariableValue(counter1.get()); + m_builder->addConstValue(5); + m_builder->createCmpLT(); + m_builder->beginWhileLoop(); + { + m_builder->addConstValue(0); + m_builder->createVariableWrite(counter2.get()); + + m_builder->beginLoopCondition(); + m_builder->addVariableValue(counter2.get()); + m_builder->addConstValue(3); + m_builder->createCmpEQ(); + m_builder->beginRepeatUntilLoop(); + { + m_builder->addConstValue(true); + m_builder->createVariableWrite(globalVar.get()); + + m_builder->addVariableValue(counter2.get()); + m_builder->addConstValue(1); + m_builder->createAdd(); + m_builder->createVariableWrite(counter2.get()); + } + m_builder->endLoop(); + + m_builder->beginLoopCondition(); + m_builder->addConstValue(false); + m_builder->beginWhileLoop(); + { + m_builder->addConstValue(-8.2); + m_builder->createVariableWrite(localVar.get()); + } + m_builder->endLoop(); + + m_builder->addVariableValue(counter1.get()); + m_builder->addConstValue(1); + m_builder->createAdd(); + m_builder->createVariableWrite(counter1.get()); + } + m_builder->endLoop(); + + m_builder->beginLoopCondition(); + m_builder->addConstValue(true); + m_builder->beginRepeatUntilLoop(); + { + m_builder->addConstValue(-8.2); + m_builder->createVariableWrite(localVar.get()); + } + m_builder->endLoop(); + + m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + + std::string expected = + "hello world\n" + "12.5\n" + "true\n" + "true\n" + "true\n"; + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&sprite); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +}