From 89f44259f04e2b7c506bb20185ffd8305af0f08a Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:06:46 +0100 Subject: [PATCH 01/38] LLVMCodeBuilder: Do not recreate var pointers when vars are used --- src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index e300f7055..74a56115f 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -1251,7 +1251,9 @@ CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable) { LLVMInstruction ins(LLVMInstruction::Type::ReadVariable); ins.workVariable = variable; - m_variablePtrs[variable] = LLVMVariablePtr(); + + if (m_variablePtrs.find(variable) == m_variablePtrs.cend()) + m_variablePtrs[variable] = LLVMVariablePtr(); auto ret = std::make_shared(Compiler::StaticType::Unknown); ret->isRawValue = false; @@ -1506,8 +1508,10 @@ void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *val { LLVMInstruction ins(LLVMInstruction::Type::WriteVariable); ins.workVariable = variable; - m_variablePtrs[variable] = LLVMVariablePtr(); createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { value }); + + if (m_variablePtrs.find(variable) == m_variablePtrs.cend()) + m_variablePtrs[variable] = LLVMVariablePtr(); } void LLVMCodeBuilder::createListClear(List *list) From 1fa99e70731e826633c654de2d9ea8e69667c337 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:10:33 +0100 Subject: [PATCH 02/38] LLVMCodeBuilder: Do not recreate list pointers when lists are used --- .../engine/internal/llvm/llvmcodebuilder.cpp | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index 74a56115f..fec1b92c4 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -1267,7 +1267,10 @@ CompilerValue *LLVMCodeBuilder::addListContents(List *list) { LLVMInstruction ins(LLVMInstruction::Type::GetListContents); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + return createOp(ins, Compiler::StaticType::String); } @@ -1275,7 +1278,9 @@ CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index) { LLVMInstruction ins(LLVMInstruction::Type::GetListItem); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); ins.args.push_back({ Compiler::StaticType::Number, static_cast(index) }); @@ -1291,7 +1296,10 @@ CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item { LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + return createOp(ins, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { item }); } @@ -1299,7 +1307,10 @@ CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item) { LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + return createOp(ins, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, { item }); } @@ -1307,7 +1318,10 @@ CompilerValue *LLVMCodeBuilder::addListSize(List *list) { LLVMInstruction ins(LLVMInstruction::Type::GetListSize); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + return createOp(ins, Compiler::StaticType::Number); } @@ -1518,40 +1532,50 @@ void LLVMCodeBuilder::createListClear(List *list) { LLVMInstruction ins(LLVMInstruction::Type::ClearList); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); createOp(ins, Compiler::StaticType::Void); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); } void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index) { LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Number, { index }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); } void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item) { LLVMInstruction ins(LLVMInstruction::Type::AppendToList); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { item }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); } void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, CompilerValue *item) { LLVMInstruction ins(LLVMInstruction::Type::InsertToList); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); } void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, CompilerValue *item) { LLVMInstruction ins(LLVMInstruction::Type::ListReplace); ins.workList = list; - m_listPtrs[list] = LLVMListPtr(); createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); } void LLVMCodeBuilder::beginIfStatement(CompilerValue *cond) From 6727c57c743d7a8084b26de43ab12d3501d616ca Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 16 Jan 2025 23:05:28 +0100 Subject: [PATCH 03/38] LLVMCodeBuilder: Add loop scopes --- src/dev/engine/internal/llvm/CMakeLists.txt | 1 + .../engine/internal/llvm/llvmcodebuilder.cpp | 159 ++++++++++++------ .../engine/internal/llvm/llvmcodebuilder.h | 12 +- .../engine/internal/llvm/llvminstruction.h | 7 +- src/dev/engine/internal/llvm/llvmloopscope.h | 18 ++ 5 files changed, 145 insertions(+), 52 deletions(-) create mode 100644 src/dev/engine/internal/llvm/llvmloopscope.h diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt index e1bb3ef58..38b7e4d2d 100644 --- a/src/dev/engine/internal/llvm/CMakeLists.txt +++ b/src/dev/engine/internal/llvm/CMakeLists.txt @@ -16,6 +16,7 @@ target_sources(scratchcpp llvmtypes.cpp llvmtypes.h llvmfunctions.cpp + llvmloopscope.h llvmcompilercontext.cpp llvmcompilercontext.h llvmexecutablecode.cpp diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index fec1b92c4..0d52e115b 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -16,6 +16,7 @@ #include "llvmifstatement.h" #include "llvmloop.h" #include "llvmtypes.h" +#include "llvmloopscope.h" using namespace libscratchcpp; @@ -44,8 +45,8 @@ std::shared_ptr LLVMCodeBuilder::finalize() { if (!m_warp) { // Do not create coroutine if there are no yield instructions nor non-warp procedure calls - auto it = std::find_if(m_instructions.begin(), m_instructions.end(), [](const LLVMInstruction &step) { - return step.type == LLVMInstruction::Type::Yield || (step.type == LLVMInstruction::Type::CallProcedure && step.procedurePrototype && !step.procedurePrototype->warp()); + auto it = std::find_if(m_instructions.begin(), m_instructions.end(), [](const std::shared_ptr &step) { + return step->type == LLVMInstruction::Type::Yield || (step->type == LLVMInstruction::Type::CallProcedure && step->procedurePrototype && !step->procedurePrototype->warp()); }); if (it == m_instructions.end()) @@ -122,12 +123,16 @@ std::shared_ptr LLVMCodeBuilder::finalize() m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty); } + assert(m_loopScope == -1); + m_loopScope = -1; m_scopeVariables.clear(); m_scopeLists.clear(); pushScopeLevel(); // Execute recorded steps - for (const LLVMInstruction &step : m_instructions) { + for (const auto insPtr : m_instructions) { + const LLVMInstruction &step = *insPtr; + switch (step.type) { case LLVMInstruction::Type::FunctionCall: { std::vector types; @@ -1007,6 +1012,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() loops.push_back(loop); pushScopeLevel(); + pushLoopScope(true); break; } @@ -1036,6 +1042,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() // Switch to body branch m_builder.SetInsertPoint(body); pushScopeLevel(); + pushLoopScope(true); break; } @@ -1057,6 +1064,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() // Switch to body branch m_builder.SetInsertPoint(body); pushScopeLevel(); + pushLoopScope(true); break; } @@ -1090,6 +1098,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() loops.pop_back(); popScopeLevel(); + popLoopScope(); break; } @@ -1198,16 +1207,16 @@ CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName, { assert(argTypes.size() == args.size()); - LLVMInstruction ins(LLVMInstruction::Type::FunctionCall); - ins.functionName = functionName; + auto ins = std::make_shared(LLVMInstruction::Type::FunctionCall, currentLoopScope()); + ins->functionName = functionName; for (size_t i = 0; i < args.size(); i++) - ins.args.push_back({ argTypes[i], static_cast(args[i]) }); + ins->args.push_back({ argTypes[i], static_cast(args[i]) }); if (returnType != Compiler::StaticType::Void) { auto reg = std::make_shared(returnType); reg->isRawValue = true; - ins.functionReturnReg = reg.get(); + ins->functionReturnReg = reg.get(); m_instructions.push_back(ins); return addReg(reg); } @@ -1219,14 +1228,14 @@ CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName, CompilerValue *LLVMCodeBuilder::addTargetFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) { CompilerValue *ret = addFunctionCall(functionName, returnType, argTypes, args); - m_instructions.back().functionTargetArg = true; + m_instructions.back()->functionTargetArg = true; return ret; } CompilerValue *LLVMCodeBuilder::addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) { CompilerValue *ret = addFunctionCall(functionName, returnType, argTypes, args); - m_instructions.back().functionCtxArg = true; + m_instructions.back()->functionCtxArg = true; return ret; } @@ -1249,15 +1258,15 @@ CompilerValue *LLVMCodeBuilder::addLocalVariableValue(CompilerLocalVariable *var CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable) { - LLVMInstruction ins(LLVMInstruction::Type::ReadVariable); - ins.workVariable = variable; + auto ins = std::make_shared(LLVMInstruction::Type::ReadVariable, currentLoopScope()); + ins->workVariable = variable; if (m_variablePtrs.find(variable) == m_variablePtrs.cend()) m_variablePtrs[variable] = LLVMVariablePtr(); auto ret = std::make_shared(Compiler::StaticType::Unknown); ret->isRawValue = false; - ins.functionReturnReg = ret.get(); + ins->functionReturnReg = ret.get(); m_instructions.push_back(ins); return addReg(ret); @@ -1265,7 +1274,7 @@ CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable) CompilerValue *LLVMCodeBuilder::addListContents(List *list) { - LLVMInstruction ins(LLVMInstruction::Type::GetListContents); + LLVMInstruction ins(LLVMInstruction::Type::GetListContents, currentLoopScope()); ins.workList = list; if (m_listPtrs.find(list) == m_listPtrs.cend()) @@ -1276,17 +1285,17 @@ CompilerValue *LLVMCodeBuilder::addListContents(List *list) CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index) { - LLVMInstruction ins(LLVMInstruction::Type::GetListItem); - ins.workList = list; + auto ins = std::make_shared(LLVMInstruction::Type::GetListItem, currentLoopScope()); + ins->workList = list; if (m_listPtrs.find(list) == m_listPtrs.cend()) m_listPtrs[list] = LLVMListPtr(); - ins.args.push_back({ Compiler::StaticType::Number, static_cast(index) }); + ins->args.push_back({ Compiler::StaticType::Number, static_cast(index) }); auto ret = std::make_shared(Compiler::StaticType::Unknown); ret->isRawValue = false; - ins.functionReturnReg = ret.get(); + ins->functionReturnReg = ret.get(); m_instructions.push_back(ins); return addReg(ret); @@ -1294,7 +1303,7 @@ CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index) CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex); + LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex, currentLoopScope()); ins.workList = list; if (m_listPtrs.find(list) == m_listPtrs.cend()) @@ -1305,7 +1314,7 @@ CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem); + LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem, currentLoopScope()); ins.workList = list; if (m_listPtrs.find(list) == m_listPtrs.cend()) @@ -1316,7 +1325,7 @@ CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item) CompilerValue *LLVMCodeBuilder::addListSize(List *list) { - LLVMInstruction ins(LLVMInstruction::Type::GetListSize); + LLVMInstruction ins(LLVMInstruction::Type::GetListSize, currentLoopScope()); ins.workList = list; if (m_listPtrs.find(list) == m_listPtrs.cend()) @@ -1340,11 +1349,11 @@ CompilerValue *LLVMCodeBuilder::addProcedureArgument(const std::string &name) const auto index = it - argNames.begin(); const Compiler::StaticType type = getProcedureArgType(m_procedurePrototype->argumentTypes()[index]); - LLVMInstruction ins(LLVMInstruction::Type::ProcedureArg); + auto ins = std::make_shared(LLVMInstruction::Type::ProcedureArg, currentLoopScope()); auto ret = std::make_shared(type); ret->isRawValue = (type != Compiler::StaticType::Unknown); - ins.functionReturnReg = ret.get(); - ins.procedureArgIndex = index; + ins->functionReturnReg = ret.get(); + ins->procedureArgIndex = index; m_instructions.push_back(ins); return addReg(ret); @@ -1520,7 +1529,7 @@ void LLVMCodeBuilder::createLocalVariableWrite(CompilerLocalVariable *variable, void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *value) { - LLVMInstruction ins(LLVMInstruction::Type::WriteVariable); + LLVMInstruction ins(LLVMInstruction::Type::WriteVariable, currentLoopScope()); ins.workVariable = variable; createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { value }); @@ -1530,7 +1539,7 @@ void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *val void LLVMCodeBuilder::createListClear(List *list) { - LLVMInstruction ins(LLVMInstruction::Type::ClearList); + LLVMInstruction ins(LLVMInstruction::Type::ClearList, currentLoopScope()); ins.workList = list; createOp(ins, Compiler::StaticType::Void); @@ -1540,7 +1549,7 @@ void LLVMCodeBuilder::createListClear(List *list) void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index) { - LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem); + LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem, currentLoopScope()); ins.workList = list; createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Number, { index }); @@ -1550,7 +1559,7 @@ void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index) void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::AppendToList); + LLVMInstruction ins(LLVMInstruction::Type::AppendToList, currentLoopScope()); ins.workList = list; createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { item }); @@ -1560,7 +1569,7 @@ void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item) void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::InsertToList); + LLVMInstruction ins(LLVMInstruction::Type::InsertToList, currentLoopScope()); ins.workList = list; createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item }); @@ -1570,7 +1579,7 @@ void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, Compile void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::ListReplace); + LLVMInstruction ins(LLVMInstruction::Type::ListReplace, currentLoopScope()); ins.workList = list; createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item }); @@ -1580,63 +1589,70 @@ void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, Compil void LLVMCodeBuilder::beginIfStatement(CompilerValue *cond) { - LLVMInstruction ins(LLVMInstruction::Type::BeginIf); - ins.args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); + auto ins = std::make_shared(LLVMInstruction::Type::BeginIf, currentLoopScope()); + ins->args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); m_instructions.push_back(ins); } void LLVMCodeBuilder::beginElseBranch() { - m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::BeginElse)); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::BeginElse, currentLoopScope())); } void LLVMCodeBuilder::endIf() { - m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::EndIf)); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::EndIf, currentLoopScope())); } void LLVMCodeBuilder::beginRepeatLoop(CompilerValue *count) { - LLVMInstruction ins(LLVMInstruction::Type::BeginRepeatLoop); - ins.args.push_back({ Compiler::StaticType::Number, static_cast(count) }); + auto ins = std::make_shared(LLVMInstruction::Type::BeginRepeatLoop, currentLoopScope()); + ins->args.push_back({ Compiler::StaticType::Number, static_cast(count) }); m_instructions.push_back(ins); + pushLoopScope(false); } void LLVMCodeBuilder::beginWhileLoop(CompilerValue *cond) { - LLVMInstruction ins(LLVMInstruction::Type::BeginWhileLoop); - ins.args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); + auto ins = std::make_shared(LLVMInstruction::Type::BeginWhileLoop, currentLoopScope()); + ins->args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); m_instructions.push_back(ins); + pushLoopScope(false); } void LLVMCodeBuilder::beginRepeatUntilLoop(CompilerValue *cond) { - LLVMInstruction ins(LLVMInstruction::Type::BeginRepeatUntilLoop); - ins.args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); + auto ins = std::make_shared(LLVMInstruction::Type::BeginRepeatUntilLoop, currentLoopScope()); + ins->args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); m_instructions.push_back(ins); + pushLoopScope(false); } void LLVMCodeBuilder::beginLoopCondition() { - m_instructions.push_back({ LLVMInstruction::Type::BeginLoopCondition }); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::BeginLoopCondition, currentLoopScope())); } void LLVMCodeBuilder::endLoop() { if (!m_warp) - m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::Yield)); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Yield, currentLoopScope())); - m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::EndLoop)); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::EndLoop, currentLoopScope())); + popLoopScope(); } void LLVMCodeBuilder::yield() { - m_instructions.push_back({ LLVMInstruction::Type::Yield }); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Yield, currentLoopScope())); + + if (m_loopScope >= 0) + m_loopScopes[m_loopScope]->containsYield = true; } void LLVMCodeBuilder::createStop() { - m_instructions.push_back({ LLVMInstruction::Type::Stop }); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Stop, currentLoopScope())); } void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) @@ -1649,7 +1665,7 @@ void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compi for (BlockPrototype::ArgType type : procedureArgs) types.push_back(getProcedureArgType(type)); - LLVMInstruction ins(LLVMInstruction::Type::CallProcedure); + LLVMInstruction ins(LLVMInstruction::Type::CallProcedure, currentLoopScope()); ins.procedurePrototype = prototype; createOp(ins, Compiler::StaticType::Void, types, args); } @@ -1762,6 +1778,36 @@ void LLVMCodeBuilder::popScopeLevel() m_heap.pop_back(); } +void LLVMCodeBuilder::pushLoopScope(bool buildPhase) +{ + if (buildPhase) + m_loopScope = m_loopScopeCounter++; + else { + auto scope = std::make_shared(); + m_loopScopes.push_back(scope); + + if (m_loopScope >= 0) { + auto currentScope = m_loopScopes[m_loopScope]; + currentScope->childScopes.push_back(scope); + scope->parentScope = currentScope; + } + + m_loopScope = m_loopScopes.size() - 1; + } + + m_loopScopeTree.push_back(m_loopScope); +} + +void LLVMCodeBuilder::popLoopScope() +{ + m_loopScopeTree.pop_back(); + + if (m_loopScopeTree.empty()) { + m_loopScope = -1; + } else + m_loopScope = m_loopScopeTree.back(); +} + std::string LLVMCodeBuilder::getMainFunctionName(BlockPrototype *procedurePrototype) { return procedurePrototype ? "proc." + procedurePrototype->procCode() : "script"; @@ -2147,6 +2193,16 @@ void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr) m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty); } +LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args) +{ + return createOp({ type, currentLoopScope() }, retType, argType, args); +} + +LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) +{ + return createOp({ type, currentLoopScope() }, retType, argTypes, args); +} + LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args) { std::vector types; @@ -2160,22 +2216,27 @@ LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::St LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) { - m_instructions.push_back(ins); - LLVMInstruction &createdIns = m_instructions.back(); + auto createdIns = std::make_shared(ins); + m_instructions.push_back(createdIns); for (size_t i = 0; i < args.size(); i++) - createdIns.args.push_back({ argTypes[i], static_cast(args[i]) }); + createdIns->args.push_back({ argTypes[i], static_cast(args[i]) }); if (retType != Compiler::StaticType::Void) { auto ret = std::make_shared(retType); ret->isRawValue = true; - createdIns.functionReturnReg = ret.get(); + createdIns->functionReturnReg = ret.get(); return addReg(ret); } return nullptr; } +std::shared_ptr LLVMCodeBuilder::currentLoopScope() const +{ + return m_loopScope >= 0 ? m_loopScopes[m_loopScope] : nullptr; +} + void LLVMCodeBuilder::createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType) { llvm::Value *converted = nullptr; diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h index f6a6142ee..bd5affe11 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -20,6 +20,7 @@ namespace libscratchcpp class LLVMCompilerContext; class LLVMConstantRegister; +class LLVMLoopScope; class LLVMCodeBuilder : public ICodeBuilder { @@ -119,6 +120,8 @@ class LLVMCodeBuilder : public ICodeBuilder void createListMap(); void pushScopeLevel(); void popScopeLevel(); + void pushLoopScope(bool buildPhase); + void popLoopScope(); std::string getMainFunctionName(BlockPrototype *procedurePrototype); std::string getResumeFunctionName(BlockPrototype *procedurePrototype); @@ -147,8 +150,11 @@ class LLVMCodeBuilder : public ICodeBuilder void reloadLists(); void updateListDataPtr(const LLVMListPtr &listPtr); + LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args); + LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {}); LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args); LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {}); + std::shared_ptr currentLoopScope() const; void createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType); void createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType); @@ -215,7 +221,7 @@ class LLVMCodeBuilder : public ICodeBuilder llvm::StructType *m_valueDataType = nullptr; llvm::FunctionType *m_resumeFuncType = nullptr; - std::vector m_instructions; + std::vector> m_instructions; std::vector> m_regs; std::vector> m_localVars; BlockPrototype *m_procedurePrototype = nullptr; @@ -223,6 +229,10 @@ class LLVMCodeBuilder : public ICodeBuilder bool m_warp = false; int m_defaultArgCount = 0; + long m_loopScope = -1; // index + std::vector> m_loopScopes; + long m_loopScopeCounter = 0; // replacement for m_loopScopes size in build phase + std::vector m_loopScopeTree; std::vector> m_heap; // scopes std::shared_ptr m_output; diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h index ddb39677b..4f1787f53 100644 --- a/src/dev/engine/internal/llvm/llvminstruction.h +++ b/src/dev/engine/internal/llvm/llvminstruction.h @@ -10,6 +10,7 @@ namespace libscratchcpp { class BlockPrototype; +class LLVMLoopScope; struct LLVMInstruction { @@ -77,8 +78,9 @@ struct LLVMInstruction ProcedureArg }; - LLVMInstruction(Type type) : - type(type) + LLVMInstruction(Type type, std::shared_ptr loopScope) : + type(type), + loopScope(loopScope) { } @@ -92,6 +94,7 @@ struct LLVMInstruction List *workList = nullptr; // for lists BlockPrototype *procedurePrototype = nullptr; size_t procedureArgIndex = 0; + std::shared_ptr loopScope; }; } // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmloopscope.h b/src/dev/engine/internal/llvm/llvmloopscope.h new file mode 100644 index 000000000..859c2cb1f --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmloopscope.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +namespace libscratchcpp +{ + +struct LLVMLoopScope +{ + bool containsYield = false; + std::shared_ptr parentScope; + std::vector> childScopes; +}; + +} // namespace libscratchcpp From df3c103835046aa8b44143fca365e5244427432e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 16 Jan 2025 23:13:17 +0100 Subject: [PATCH 04/38] LLVMCodeBuilder: Store source instruction in registers --- src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 15 ++++++++------- src/dev/engine/internal/llvm/llvmcodebuilder.h | 2 +- src/dev/engine/internal/llvm/llvmregisterbase.h | 3 +++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index 0d52e115b..4e241b0cd 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -1218,7 +1218,7 @@ CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName, reg->isRawValue = true; ins->functionReturnReg = reg.get(); m_instructions.push_back(ins); - return addReg(reg); + return addReg(reg, ins); } m_instructions.push_back(ins); @@ -1243,7 +1243,7 @@ CompilerConstant *LLVMCodeBuilder::addConstValue(const Value &value) { auto constReg = std::make_shared(TYPE_MAP[value.type()], value); auto reg = std::reinterpret_pointer_cast(constReg); - return static_cast(static_cast(addReg(reg))); + return static_cast(static_cast(addReg(reg, nullptr))); } CompilerValue *LLVMCodeBuilder::addLoopIndex() @@ -1269,7 +1269,7 @@ CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable) ins->functionReturnReg = ret.get(); m_instructions.push_back(ins); - return addReg(ret); + return addReg(ret, ins); } CompilerValue *LLVMCodeBuilder::addListContents(List *list) @@ -1298,7 +1298,7 @@ CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index) ins->functionReturnReg = ret.get(); m_instructions.push_back(ins); - return addReg(ret); + return addReg(ret, ins); } CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item) @@ -1356,7 +1356,7 @@ CompilerValue *LLVMCodeBuilder::addProcedureArgument(const std::string &name) ins->procedureArgIndex = index; m_instructions.push_back(ins); - return addReg(ret); + return addReg(ret, ins); } CompilerValue *LLVMCodeBuilder::createAdd(CompilerValue *operand1, CompilerValue *operand2) @@ -1857,8 +1857,9 @@ void LLVMCodeBuilder::verifyFunction(llvm::Function *func) } } -LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr reg) +LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr reg, std::shared_ptr ins) { + reg->instruction = ins; m_regs.push_back(reg); return reg.get(); } @@ -2226,7 +2227,7 @@ LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::St auto ret = std::make_shared(retType); ret->isRawValue = true; createdIns->functionReturnReg = ret.get(); - return addReg(ret); + return addReg(ret, createdIns); } return nullptr; diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h index bd5affe11..0cec7fb9d 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -129,7 +129,7 @@ class LLVMCodeBuilder : public ICodeBuilder llvm::Function *getOrCreateFunction(const std::string &name, llvm::FunctionType *type); void verifyFunction(llvm::Function *func); - LLVMRegister *addReg(std::shared_ptr reg); + LLVMRegister *addReg(std::shared_ptr reg, std::shared_ptr ins); llvm::Value *addAlloca(llvm::Type *type); void freeLater(llvm::Value *value); diff --git a/src/dev/engine/internal/llvm/llvmregisterbase.h b/src/dev/engine/internal/llvm/llvmregisterbase.h index 2267a6dfb..33aace036 100644 --- a/src/dev/engine/internal/llvm/llvmregisterbase.h +++ b/src/dev/engine/internal/llvm/llvmregisterbase.h @@ -15,12 +15,15 @@ class Value; namespace libscratchcpp { +class LLVMInstruction; + struct LLVMRegisterBase { virtual const Value &constValue() const = 0; llvm::Value *value = nullptr; bool isRawValue = false; + std::shared_ptr instruction; }; } // namespace libscratchcpp From fa9de140766f216a34065aa47d303e9094c0f49d Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 16 Jan 2025 23:16:42 +0100 Subject: [PATCH 05/38] LLVMCodeBuilder: Store variable instructions --- src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 3 +++ src/dev/engine/internal/llvm/llvmcodebuilder.h | 1 + 2 files changed, 4 insertions(+) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index 4e241b0cd..132472ede 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -1269,6 +1269,7 @@ CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable) ins->functionReturnReg = ret.get(); m_instructions.push_back(ins); + m_variableInstructions.push_back(m_instructions.back()); return addReg(ret, ins); } @@ -1535,6 +1536,8 @@ void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *val if (m_variablePtrs.find(variable) == m_variablePtrs.cend()) m_variablePtrs[variable] = LLVMVariablePtr(); + + m_variableInstructions.push_back(m_instructions.back()); } void LLVMCodeBuilder::createListClear(List *list) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h index 0cec7fb9d..4a34ffd2d 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -233,6 +233,7 @@ class LLVMCodeBuilder : public ICodeBuilder std::vector> m_loopScopes; long m_loopScopeCounter = 0; // replacement for m_loopScopes size in build phase std::vector m_loopScopeTree; + std::vector> m_variableInstructions; std::vector> m_heap; // scopes std::shared_ptr m_output; From 469c3e8af90461687c82003baaa7bcf3bacae07c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 16 Jan 2025 23:19:22 +0100 Subject: [PATCH 06/38] LLVMCodeBuilder: Initialize variable stack copies outside loops --- src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index 132472ede..e6d5cf828 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -106,7 +106,18 @@ std::shared_ptr LLVMCodeBuilder::finalize() // 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.stackPtr = m_builder.CreateAlloca(m_valueDataType); - varPtr.onStack = false; // use heap before the first assignment + + // If there are no write operations outside loops, initialize the stack variable now + Variable *variable = var; + auto it = std::find_if(m_variableInstructions.begin(), m_variableInstructions.end(), [variable](const std::shared_ptr &ins) { + return ins->type == LLVMInstruction::Type::WriteVariable && ins->workVariable == variable && !ins->loopScope; + }); + + if (it == m_variableInstructions.end()) { + createValueCopy(ptr, varPtr.stackPtr); + varPtr.onStack = true; + } else + varPtr.onStack = false; // use heap before the first assignment } // Create list pointers From 9f5f45ce0eb756d1ede89a718679f715993877ca Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 16 Jan 2025 23:22:19 +0100 Subject: [PATCH 07/38] LLVMCodeBuilder: Store loop variable write instructions --- src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 5 +++++ src/dev/engine/internal/llvm/llvmvariableptr.h | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index e6d5cf828..491c30360 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -1548,6 +1548,11 @@ void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *val if (m_variablePtrs.find(variable) == m_variablePtrs.cend()) m_variablePtrs[variable] = LLVMVariablePtr(); + if (m_loopScope >= 0) { + auto scope = m_loopScopes[m_loopScope]; + m_variablePtrs[variable].loopVariableWrites[scope].push_back(m_instructions.back()); + } + m_variableInstructions.push_back(m_instructions.back()); } diff --git a/src/dev/engine/internal/llvm/llvmvariableptr.h b/src/dev/engine/internal/llvm/llvmvariableptr.h index c2525dfd6..28105f191 100644 --- a/src/dev/engine/internal/llvm/llvmvariableptr.h +++ b/src/dev/engine/internal/llvm/llvmvariableptr.h @@ -3,6 +3,7 @@ #pragma once #include +#include namespace llvm { @@ -14,6 +15,9 @@ class Value; namespace libscratchcpp { +class LLVMLoopScope; +class LLVMInstruction; + struct LLVMVariablePtr { llvm::Value *stackPtr = nullptr; @@ -21,6 +25,9 @@ struct LLVMVariablePtr Compiler::StaticType type = Compiler::StaticType::Unknown; bool onStack = false; bool changed = false; + + // Used in build phase to check the type safety of variables in loops + std::unordered_map, std::vector>> loopVariableWrites; // loop scope, write instructions }; } // namespace libscratchcpp From f6e001345080cddb3fce047d6ac6f1130b92bdf7 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 17 Jan 2025 00:19:09 +0100 Subject: [PATCH 08/38] LLVMCodeBuilder: Implement loop type analysis for variables --- .../engine/internal/llvm/llvmcodebuilder.cpp | 173 ++++- .../engine/internal/llvm/llvmcodebuilder.h | 5 +- test/dev/llvm/llvmcodebuilder_test.cpp | 672 ++++++++++++++++++ 3 files changed, 847 insertions(+), 3 deletions(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index 491c30360..d674bc44e 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -632,6 +632,8 @@ std::shared_ptr LLVMCodeBuilder::finalize() LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable]; varPtr.changed = true; + const bool safe = isVariableTypeSafe(insPtr, varPtr.type); + // Initialize stack variable on first assignment if (!varPtr.onStack) { varPtr.onStack = true; @@ -652,6 +654,9 @@ std::shared_ptr LLVMCodeBuilder::finalize() m_builder.CreateStore(m_builder.getInt32(static_cast(mappedType)), typeField); } + if (!safe) + varPtr.type = Compiler::StaticType::Unknown; + createValueStore(arg.second, varPtr.stackPtr, type, varPtr.type); varPtr.type = type; m_scopeVariables.back()[&varPtr] = varPtr.type; @@ -660,7 +665,11 @@ std::shared_ptr LLVMCodeBuilder::finalize() case LLVMInstruction::Type::ReadVariable: { assert(step.args.size() == 0); - const LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable]; + LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable]; + + if (!isVariableTypeSafe(insPtr, varPtr.type)) + varPtr.type = Compiler::StaticType::Unknown; + step.functionReturnReg->value = varPtr.onStack ? varPtr.stackPtr : varPtr.heapPtr; step.functionReturnReg->setType(varPtr.type); break; @@ -2091,7 +2100,7 @@ llvm::Constant *LLVMCodeBuilder::castConstValue(const Value &value, Compiler::St } } -Compiler::StaticType LLVMCodeBuilder::optimizeRegisterType(LLVMRegister *reg) +Compiler::StaticType LLVMCodeBuilder::optimizeRegisterType(LLVMRegister *reg) const { Compiler::StaticType ret = reg->type(); @@ -2213,6 +2222,166 @@ void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr) m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty); } +bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType) const +{ + std::unordered_set processed; + return isVariableTypeSafe(ins, expectedType, processed); +} + +bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &processed) const +{ + /* + * The main part of the loop type analyzer. + * + * This is a recursive function which is called when variable read + * instruction is created. It checks the last write to the + * variable in one of the loop scopes. + * + * If the last write operation writes a value with a different + * type, it will return false, otherwise true. + * + * If the last written value is from a variable, this function + * is called for it to check its type safety (that's why it is + * recursive). + * + * If the variable had a write operation before (in the same, + * parent or child loop scope), it is checked recursively. + */ + + if (!ins) + return false; + + /* + * If we are processing something that has been already + * processed, give up to avoid infinite recursion. + * + * This can happen in edge cases like this: + * var = var + * + * or this: + * x = y + * y = x + * + * This code isn't considered valid, so don't bother + * optimizing. + */ + if (processed.find(ins.get()) != processed.cend()) + return false; + + processed.insert(ins.get()); + + assert(std::find(m_instructions.begin(), m_instructions.end(), ins) != m_instructions.end()); + const LLVMVariablePtr &varPtr = m_variablePtrs.at(ins->workVariable); + auto scope = ins->loopScope; + + // If we aren't in a loop, we're safe + if (!scope) + return true; + + // If the loop scope contains a suspend and this is a non-warp script, the type may change between suspend and resume + if (scope->containsYield && !m_warp) + return false; + + std::shared_ptr write; + + // Find this instruction + auto it = std::find(m_variableInstructions.begin(), m_variableInstructions.end(), ins); + assert(it != m_variableInstructions.end()); + + // Find previous write instruction in this, parent or child loop scope + size_t index = it - m_variableInstructions.begin(); + + if (index > 0) { + bool found = false; + + do { + index--; + write = m_variableInstructions[index]; + found = (write->loopScope && write->type == LLVMInstruction::Type::WriteVariable && write->workVariable == ins->workVariable); + } while (index > 0 && !found); + + if (found) { + // Check if the write operation is in this or child scope + auto parentScope = write->loopScope; + + while (parentScope && parentScope != scope) + parentScope = parentScope->parentScope; + + if (!parentScope) { + // Check if the write operation is in any of the parent scopes + parentScope = scope; + + do { + parentScope = parentScope->parentScope; + } while (parentScope && parentScope != write->loopScope); + } + + // If there was a write operation before this instruction (in this, parent or child scope), check it + if (parentScope) { + if (parentScope == scope) + return isVariableWriteResultTypeSafe(write, expectedType, true, processed); + else + return isVariableTypeSafe(write, expectedType, processed); + } + } + } + + // Get last write operation + write = nullptr; + + // Find root loop scope + auto checkScope = scope; + + while (checkScope->parentScope) { + checkScope = checkScope->parentScope; + } + + // Find last loop scope (may be a parent or child scope) + while (checkScope) { + auto it = varPtr.loopVariableWrites.find(checkScope); + + if (it != varPtr.loopVariableWrites.cend()) { + assert(!it->second.empty()); + write = it->second.back(); + } + + if (checkScope->childScopes.empty()) + checkScope = nullptr; + else + checkScope = checkScope->childScopes.back(); + } + + // If there aren't any write operations, we're safe + if (!write) + return true; + + bool safe = true; + + if (ins->type == LLVMInstruction::Type::WriteVariable) + safe = isVariableWriteResultTypeSafe(ins, expectedType, false, processed); + + if (safe) + return isVariableWriteResultTypeSafe(write, expectedType, false, processed); + else + return false; +} + +bool LLVMCodeBuilder::isVariableWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &processed) + const +{ + const LLVMVariablePtr &varPtr = m_variablePtrs.at(ins->workVariable); + + // If the write operation writes the value of another variable, recursively check its type safety + // TODO: Check get list item instruction + auto argIns = ins->args[0].second->instruction; + + if (argIns && argIns->type == LLVMInstruction::Type::ReadVariable) + return isVariableTypeSafe(argIns, expectedType, processed); + + // Check written type + return optimizeRegisterType(ins->args[0].second) == expectedType && (varPtr.type == expectedType || ignoreSavedType); +} + LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args) { return createOp({ type, currentLoopScope() }, retType, argType, args); diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h index 4a34ffd2d..79977e290 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -137,7 +137,7 @@ class LLVMCodeBuilder : public ICodeBuilder llvm::Value *castValue(LLVMRegister *reg, Compiler::StaticType targetType); llvm::Value *castRawValue(LLVMRegister *reg, Compiler::StaticType targetType); llvm::Constant *castConstValue(const Value &value, Compiler::StaticType targetType); - Compiler::StaticType optimizeRegisterType(LLVMRegister *reg); + Compiler::StaticType optimizeRegisterType(LLVMRegister *reg) const; llvm::Type *getType(Compiler::StaticType type); Compiler::StaticType getProcedureArgType(BlockPrototype::ArgType type); llvm::Value *isNaN(llvm::Value *num); @@ -149,6 +149,9 @@ class LLVMCodeBuilder : public ICodeBuilder void reloadVariables(llvm::Value *targetVariables); void reloadLists(); void updateListDataPtr(const LLVMListPtr &listPtr); + bool isVariableTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType) const; + bool isVariableTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &processed) const; + bool isVariableWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &processed) const; LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args); LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {}); diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index 723c81011..c0a8ece86 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -2832,6 +2832,17 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend) v = m_builder->addVariableValue(localVar.get()); m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + std::string expected = "hello world\n" "-4.8\n"; @@ -2851,6 +2862,20 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend) testing::internal::CaptureStdout(); code->run(ctx.get()); ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + ASSERT_FALSE(code->isFinished(ctx.get())); + + globalVar->setValue("test"); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_FALSE(code->isFinished(ctx.get())); + + globalVar->setValue(true); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "true\n"); } TEST_F(LLVMCodeBuilderTest, ListsAfterSuspend) @@ -3893,6 +3918,653 @@ TEST_F(LLVMCodeBuilderTest, LoopVariables) ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); } +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis1) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a string is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis2) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a number is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "12.5\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis3) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(0); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable with unknown type is assigned (the variable has unknown type because a string is assigned later) + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis4) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(0); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a variable is assigned which has a number assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "12.5\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis5) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a string variable is assigned later which has a number assigned as well + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "12.5\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis6) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a variable with known type is assigned later (even though the variable has a string assigned later, there's a number assigned before the read operation) + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(10); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "10\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis7) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable with a known, but different type is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(10); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis8) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable with a known, but different type is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis9) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", "", "123"); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + + // Type is unknown here because a variable of unknown type is assigned before the read operation + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(10); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5\n" + "5\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis10) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", "123"); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable of unknown type is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "0\n" + "12.5\n" + "12.5\n" + "0\n" + "12.5\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis11) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", "123"); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because the variable is assigned to itself later + // This case is not checked because the code isn't considered valid + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "123\n" + "123\n" + "123\n" + "123\n" + "123\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis12) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", "123"); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because another variable is assigned later, but it has this variable assigned + // This case is not checked because the code isn't considered valid + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + TEST_F(LLVMCodeBuilderTest, LoopLists) { Stage stage; From 30c7ad9295e229f5d3af0a7dc2b0b9c4a8e07a77 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:35:37 +0100 Subject: [PATCH 09/38] LLVMCodeBuilder: Store loop condition info in instructions --- .../engine/internal/llvm/llvmcodebuilder.cpp | 66 +++++++++++-------- .../engine/internal/llvm/llvmcodebuilder.h | 1 + .../engine/internal/llvm/llvminstruction.h | 6 +- 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index d674bc44e..d3a82b902 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -1227,7 +1227,7 @@ CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName, { assert(argTypes.size() == args.size()); - auto ins = std::make_shared(LLVMInstruction::Type::FunctionCall, currentLoopScope()); + auto ins = std::make_shared(LLVMInstruction::Type::FunctionCall, currentLoopScope(), m_loopCondition); ins->functionName = functionName; for (size_t i = 0; i < args.size(); i++) @@ -1278,7 +1278,7 @@ CompilerValue *LLVMCodeBuilder::addLocalVariableValue(CompilerLocalVariable *var CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable) { - auto ins = std::make_shared(LLVMInstruction::Type::ReadVariable, currentLoopScope()); + auto ins = std::make_shared(LLVMInstruction::Type::ReadVariable, currentLoopScope(), m_loopCondition); ins->workVariable = variable; if (m_variablePtrs.find(variable) == m_variablePtrs.cend()) @@ -1295,7 +1295,7 @@ CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable) CompilerValue *LLVMCodeBuilder::addListContents(List *list) { - LLVMInstruction ins(LLVMInstruction::Type::GetListContents, currentLoopScope()); + LLVMInstruction ins(LLVMInstruction::Type::GetListContents, currentLoopScope(), m_loopCondition); ins.workList = list; if (m_listPtrs.find(list) == m_listPtrs.cend()) @@ -1306,7 +1306,7 @@ CompilerValue *LLVMCodeBuilder::addListContents(List *list) CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index) { - auto ins = std::make_shared(LLVMInstruction::Type::GetListItem, currentLoopScope()); + auto ins = std::make_shared(LLVMInstruction::Type::GetListItem, currentLoopScope(), m_loopCondition); ins->workList = list; if (m_listPtrs.find(list) == m_listPtrs.cend()) @@ -1324,7 +1324,7 @@ CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index) CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex, currentLoopScope()); + LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex, currentLoopScope(), m_loopCondition); ins.workList = list; if (m_listPtrs.find(list) == m_listPtrs.cend()) @@ -1335,7 +1335,7 @@ CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem, currentLoopScope()); + LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem, currentLoopScope(), m_loopCondition); ins.workList = list; if (m_listPtrs.find(list) == m_listPtrs.cend()) @@ -1346,7 +1346,7 @@ CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item) CompilerValue *LLVMCodeBuilder::addListSize(List *list) { - LLVMInstruction ins(LLVMInstruction::Type::GetListSize, currentLoopScope()); + LLVMInstruction ins(LLVMInstruction::Type::GetListSize, currentLoopScope(), m_loopCondition); ins.workList = list; if (m_listPtrs.find(list) == m_listPtrs.cend()) @@ -1370,7 +1370,7 @@ CompilerValue *LLVMCodeBuilder::addProcedureArgument(const std::string &name) const auto index = it - argNames.begin(); const Compiler::StaticType type = getProcedureArgType(m_procedurePrototype->argumentTypes()[index]); - auto ins = std::make_shared(LLVMInstruction::Type::ProcedureArg, currentLoopScope()); + auto ins = std::make_shared(LLVMInstruction::Type::ProcedureArg, currentLoopScope(), m_loopCondition); auto ret = std::make_shared(type); ret->isRawValue = (type != Compiler::StaticType::Unknown); ins->functionReturnReg = ret.get(); @@ -1550,7 +1550,7 @@ void LLVMCodeBuilder::createLocalVariableWrite(CompilerLocalVariable *variable, void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *value) { - LLVMInstruction ins(LLVMInstruction::Type::WriteVariable, currentLoopScope()); + LLVMInstruction ins(LLVMInstruction::Type::WriteVariable, currentLoopScope(), m_loopCondition); ins.workVariable = variable; createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { value }); @@ -1567,7 +1567,7 @@ void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *val void LLVMCodeBuilder::createListClear(List *list) { - LLVMInstruction ins(LLVMInstruction::Type::ClearList, currentLoopScope()); + LLVMInstruction ins(LLVMInstruction::Type::ClearList, currentLoopScope(), m_loopCondition); ins.workList = list; createOp(ins, Compiler::StaticType::Void); @@ -1577,7 +1577,7 @@ void LLVMCodeBuilder::createListClear(List *list) void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index) { - LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem, currentLoopScope()); + LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem, currentLoopScope(), m_loopCondition); ins.workList = list; createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Number, { index }); @@ -1587,7 +1587,7 @@ void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index) void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::AppendToList, currentLoopScope()); + LLVMInstruction ins(LLVMInstruction::Type::AppendToList, currentLoopScope(), m_loopCondition); ins.workList = list; createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { item }); @@ -1597,7 +1597,7 @@ void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item) void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::InsertToList, currentLoopScope()); + LLVMInstruction ins(LLVMInstruction::Type::InsertToList, currentLoopScope(), m_loopCondition); ins.workList = list; createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item }); @@ -1607,7 +1607,7 @@ void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, Compile void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, CompilerValue *item) { - LLVMInstruction ins(LLVMInstruction::Type::ListReplace, currentLoopScope()); + LLVMInstruction ins(LLVMInstruction::Type::ListReplace, currentLoopScope(), m_loopCondition); ins.workList = list; createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item }); @@ -1617,24 +1617,26 @@ void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, Compil void LLVMCodeBuilder::beginIfStatement(CompilerValue *cond) { - auto ins = std::make_shared(LLVMInstruction::Type::BeginIf, currentLoopScope()); + auto ins = std::make_shared(LLVMInstruction::Type::BeginIf, currentLoopScope(), m_loopCondition); ins->args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); m_instructions.push_back(ins); } void LLVMCodeBuilder::beginElseBranch() { - m_instructions.push_back(std::make_shared(LLVMInstruction::Type::BeginElse, currentLoopScope())); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::BeginElse, currentLoopScope(), m_loopCondition)); } void LLVMCodeBuilder::endIf() { - m_instructions.push_back(std::make_shared(LLVMInstruction::Type::EndIf, currentLoopScope())); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::EndIf, currentLoopScope(), m_loopCondition)); } void LLVMCodeBuilder::beginRepeatLoop(CompilerValue *count) { - auto ins = std::make_shared(LLVMInstruction::Type::BeginRepeatLoop, currentLoopScope()); + assert(!m_loopCondition); + + auto ins = std::make_shared(LLVMInstruction::Type::BeginRepeatLoop, currentLoopScope(), m_loopCondition); ins->args.push_back({ Compiler::StaticType::Number, static_cast(count) }); m_instructions.push_back(ins); pushLoopScope(false); @@ -1642,7 +1644,10 @@ void LLVMCodeBuilder::beginRepeatLoop(CompilerValue *count) void LLVMCodeBuilder::beginWhileLoop(CompilerValue *cond) { - auto ins = std::make_shared(LLVMInstruction::Type::BeginWhileLoop, currentLoopScope()); + assert(m_loopCondition); + m_loopCondition = false; + + auto ins = std::make_shared(LLVMInstruction::Type::BeginWhileLoop, currentLoopScope(), m_loopCondition); ins->args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); m_instructions.push_back(ins); pushLoopScope(false); @@ -1650,7 +1655,10 @@ void LLVMCodeBuilder::beginWhileLoop(CompilerValue *cond) void LLVMCodeBuilder::beginRepeatUntilLoop(CompilerValue *cond) { - auto ins = std::make_shared(LLVMInstruction::Type::BeginRepeatUntilLoop, currentLoopScope()); + assert(m_loopCondition); + m_loopCondition = false; + + auto ins = std::make_shared(LLVMInstruction::Type::BeginRepeatUntilLoop, currentLoopScope(), m_loopCondition); ins->args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); m_instructions.push_back(ins); pushLoopScope(false); @@ -1658,21 +1666,23 @@ void LLVMCodeBuilder::beginRepeatUntilLoop(CompilerValue *cond) void LLVMCodeBuilder::beginLoopCondition() { - m_instructions.push_back(std::make_shared(LLVMInstruction::Type::BeginLoopCondition, currentLoopScope())); + assert(!m_loopCondition); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::BeginLoopCondition, currentLoopScope(), m_loopCondition)); + m_loopCondition = true; } void LLVMCodeBuilder::endLoop() { if (!m_warp) - m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Yield, currentLoopScope())); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Yield, currentLoopScope(), m_loopCondition)); - m_instructions.push_back(std::make_shared(LLVMInstruction::Type::EndLoop, currentLoopScope())); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::EndLoop, currentLoopScope(), m_loopCondition)); popLoopScope(); } void LLVMCodeBuilder::yield() { - m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Yield, currentLoopScope())); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Yield, currentLoopScope(), m_loopCondition)); if (m_loopScope >= 0) m_loopScopes[m_loopScope]->containsYield = true; @@ -1680,7 +1690,7 @@ void LLVMCodeBuilder::yield() void LLVMCodeBuilder::createStop() { - m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Stop, currentLoopScope())); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Stop, currentLoopScope(), m_loopCondition)); } void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) @@ -1693,7 +1703,7 @@ void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compi for (BlockPrototype::ArgType type : procedureArgs) types.push_back(getProcedureArgType(type)); - LLVMInstruction ins(LLVMInstruction::Type::CallProcedure, currentLoopScope()); + LLVMInstruction ins(LLVMInstruction::Type::CallProcedure, currentLoopScope(), m_loopCondition); ins.procedurePrototype = prototype; createOp(ins, Compiler::StaticType::Void, types, args); } @@ -2384,12 +2394,12 @@ bool LLVMCodeBuilder::isVariableWriteResultTypeSafe(std::shared_ptr> m_loopScopes; long m_loopScopeCounter = 0; // replacement for m_loopScopes size in build phase std::vector m_loopScopeTree; + bool m_loopCondition = false; // whether we're currently compiling a loop condition std::vector> m_variableInstructions; std::vector> m_heap; // scopes diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h index 4f1787f53..8e88e8876 100644 --- a/src/dev/engine/internal/llvm/llvminstruction.h +++ b/src/dev/engine/internal/llvm/llvminstruction.h @@ -78,9 +78,10 @@ struct LLVMInstruction ProcedureArg }; - LLVMInstruction(Type type, std::shared_ptr loopScope) : + LLVMInstruction(Type type, std::shared_ptr loopScope, bool loopCondition) : type(type), - loopScope(loopScope) + loopScope(loopScope), + loopCondition(loopCondition) { } @@ -95,6 +96,7 @@ struct LLVMInstruction BlockPrototype *procedurePrototype = nullptr; size_t procedureArgIndex = 0; std::shared_ptr loopScope; + bool loopCondition = false; // whether the instruction is part of a loop condition }; } // namespace libscratchcpp From 3bc6370c125b60c146afd1fde5c5450828cf1bdf Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:36:24 +0100 Subject: [PATCH 10/38] LLVMCodeBuilder: Force variable heap pointer when in non-warp loop cond --- .../engine/internal/llvm/llvmcodebuilder.cpp | 2 +- test/dev/llvm/llvmcodebuilder_test.cpp | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index d3a82b902..a0166674f 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -670,7 +670,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() if (!isVariableTypeSafe(insPtr, varPtr.type)) varPtr.type = Compiler::StaticType::Unknown; - step.functionReturnReg->value = varPtr.onStack ? varPtr.stackPtr : varPtr.heapPtr; + step.functionReturnReg->value = varPtr.onStack && !(step.loopCondition && !m_warp) ? varPtr.stackPtr : varPtr.heapPtr; step.functionReturnReg->setType(varPtr.type); break; } diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index c0a8ece86..2789deec4 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -2843,6 +2843,28 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend) v = m_builder->addVariableValue(globalVar.get()); m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(localVar.get(), v); + + m_builder->beginLoopCondition(); + v = m_builder->createCmpLT(m_builder->addVariableValue(localVar.get()), m_builder->addConstValue(3)); + m_builder->beginWhileLoop(v); + m_builder->endLoop(); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(localVar.get(), v); + + m_builder->beginLoopCondition(); + v = m_builder->createCmpEQ(m_builder->addVariableValue(localVar.get()), m_builder->addConstValue(2)); + m_builder->beginRepeatUntilLoop(v); + m_builder->endLoop(); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + std::string expected = "hello world\n" "-4.8\n"; @@ -2876,6 +2898,41 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend) testing::internal::CaptureStdout(); code->run(ctx.get()); ASSERT_EQ(testing::internal::GetCapturedStdout(), "true\n"); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue(1); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue("2"); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue(3); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "3\n"); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue(1); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue(2); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "2\n"); } TEST_F(LLVMCodeBuilderTest, ListsAfterSuspend) From ad04bcef40821f80dcaf90633a1c5eb39b1b52a5 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 18 Jan 2025 11:24:47 +0100 Subject: [PATCH 11/38] LLVMCodeBuilder: Store list read/write instructions --- src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 15 +++++++++++++-- src/dev/engine/internal/llvm/llvmcodebuilder.h | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index a0166674f..a8e9ec6f1 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -1319,6 +1319,7 @@ CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index) ins->functionReturnReg = ret.get(); m_instructions.push_back(ins); + m_listInstructions.push_back(m_instructions.back()); return addReg(ret, ins); } @@ -1330,7 +1331,9 @@ CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item if (m_listPtrs.find(list) == m_listPtrs.cend()) m_listPtrs[list] = LLVMListPtr(); - return createOp(ins, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { item }); + auto ret = createOp(ins, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { item }); + m_listInstructions.push_back(m_instructions.back()); + return ret; } CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item) @@ -1341,7 +1344,9 @@ CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item) if (m_listPtrs.find(list) == m_listPtrs.cend()) m_listPtrs[list] = LLVMListPtr(); - return createOp(ins, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, { item }); + auto ret = createOp(ins, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, { item }); + m_listInstructions.push_back(m_instructions.back()); + return ret; } CompilerValue *LLVMCodeBuilder::addListSize(List *list) @@ -1593,6 +1598,8 @@ void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item) if (m_listPtrs.find(list) == m_listPtrs.cend()) m_listPtrs[list] = LLVMListPtr(); + + m_listInstructions.push_back(m_instructions.back()); } void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, CompilerValue *item) @@ -1603,6 +1610,8 @@ void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, Compile if (m_listPtrs.find(list) == m_listPtrs.cend()) m_listPtrs[list] = LLVMListPtr(); + + m_listInstructions.push_back(m_instructions.back()); } void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, CompilerValue *item) @@ -1613,6 +1622,8 @@ void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, Compil if (m_listPtrs.find(list) == m_listPtrs.cend()) m_listPtrs[list] = LLVMListPtr(); + + m_listInstructions.push_back(m_instructions.back()); } void LLVMCodeBuilder::beginIfStatement(CompilerValue *cond) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h index 7a657d201..d8d7b6f97 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -238,6 +238,7 @@ class LLVMCodeBuilder : public ICodeBuilder std::vector m_loopScopeTree; bool m_loopCondition = false; // whether we're currently compiling a loop condition std::vector> m_variableInstructions; + std::vector> m_listInstructions; std::vector> m_heap; // scopes std::shared_ptr m_output; From 2ba3e126d1ddef3f28902898c2e013e8d831bb7b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 18 Jan 2025 11:33:23 +0100 Subject: [PATCH 12/38] LLVMCodeBuilder: Store loop list write instructions --- src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 15 +++++++++++++++ src/dev/engine/internal/llvm/llvmlistptr.h | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index a8e9ec6f1..a0bc7d1ea 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -1599,6 +1599,11 @@ void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item) if (m_listPtrs.find(list) == m_listPtrs.cend()) m_listPtrs[list] = LLVMListPtr(); + if (m_loopScope >= 0) { + auto scope = m_loopScopes[m_loopScope]; + m_listPtrs[list].loopListWrites[scope].push_back(m_instructions.back()); + } + m_listInstructions.push_back(m_instructions.back()); } @@ -1611,6 +1616,11 @@ void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, Compile if (m_listPtrs.find(list) == m_listPtrs.cend()) m_listPtrs[list] = LLVMListPtr(); + if (m_loopScope >= 0) { + auto scope = m_loopScopes[m_loopScope]; + m_listPtrs[list].loopListWrites[scope].push_back(m_instructions.back()); + } + m_listInstructions.push_back(m_instructions.back()); } @@ -1623,6 +1633,11 @@ void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, Compil if (m_listPtrs.find(list) == m_listPtrs.cend()) m_listPtrs[list] = LLVMListPtr(); + if (m_loopScope >= 0) { + auto scope = m_loopScopes[m_loopScope]; + m_listPtrs[list].loopListWrites[scope].push_back(m_instructions.back()); + } + m_listInstructions.push_back(m_instructions.back()); } diff --git a/src/dev/engine/internal/llvm/llvmlistptr.h b/src/dev/engine/internal/llvm/llvmlistptr.h index 6b427572e..ca97bc0e8 100644 --- a/src/dev/engine/internal/llvm/llvmlistptr.h +++ b/src/dev/engine/internal/llvm/llvmlistptr.h @@ -3,6 +3,7 @@ #pragma once #include +#include namespace llvm { @@ -14,6 +15,9 @@ class Value; namespace libscratchcpp { +class LLVMLoopScope; +class LLVMInstruction; + struct LLVMListPtr { llvm::Value *ptr = nullptr; @@ -22,6 +26,9 @@ struct LLVMListPtr llvm::Value *allocatedSizePtr = nullptr; llvm::Value *dataPtrDirty = nullptr; Compiler::StaticType type = Compiler::StaticType::Unknown; + + // Used in build phase to check the type safety of lists in loops + std::unordered_map, std::vector>> loopListWrites; // loop scope, write instructions }; } // namespace libscratchcpp From 1ee060a64bfcb0fe327a221b18bf72c8789bad17 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 18 Jan 2025 12:48:08 +0100 Subject: [PATCH 13/38] LLVMCodeBuilder: Implement loop type analysis for lists --- .../engine/internal/llvm/llvmcodebuilder.cpp | 116 ++- .../engine/internal/llvm/llvmcodebuilder.h | 6 +- test/dev/llvm/llvmcodebuilder_test.cpp | 825 ++++++++++++++++++ 3 files changed, 907 insertions(+), 40 deletions(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index a0bc7d1ea..c19e4bcb2 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -23,6 +23,9 @@ using namespace libscratchcpp; static std::unordered_map TYPE_MAP = { { ValueType::Number, Compiler::StaticType::Number }, { ValueType::Bool, Compiler::StaticType::Bool }, { ValueType::String, Compiler::StaticType::String } }; +static const std::unordered_set + VAR_LIST_READ_INSTRUCTIONS = { LLVMInstruction::Type::ReadVariable, LLVMInstruction::Type::GetListItem, LLVMInstruction::Type::GetListItemIndex, LLVMInstruction::Type::ListContainsItem }; + LLVMCodeBuilder::LLVMCodeBuilder(LLVMCompilerContext *ctx, BlockPrototype *procedurePrototype) : m_ctx(ctx), m_target(ctx->target()), @@ -632,7 +635,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable]; varPtr.changed = true; - const bool safe = isVariableTypeSafe(insPtr, varPtr.type); + const bool safe = isVarOrListTypeSafe(insPtr, varPtr.type); // Initialize stack variable on first assignment if (!varPtr.onStack) { @@ -667,7 +670,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() assert(step.args.size() == 0); LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable]; - if (!isVariableTypeSafe(insPtr, varPtr.type)) + if (!isVarOrListTypeSafe(insPtr, varPtr.type)) varPtr.type = Compiler::StaticType::Unknown; step.functionReturnReg->value = varPtr.onStack && !(step.loopCondition && !m_warp) ? varPtr.stackPtr : varPtr.heapPtr; @@ -693,7 +696,10 @@ std::shared_ptr LLVMCodeBuilder::finalize() case LLVMInstruction::Type::RemoveListItem: { assert(step.args.size() == 1); const auto &arg = step.args[0]; - const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; // Range check llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); @@ -722,6 +728,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() Compiler::StaticType type = optimizeRegisterType(arg.second); LLVMListPtr &listPtr = m_listPtrs[step.workList]; + const bool safe = isVarOrListTypeSafe(insPtr, listPtr.type); auto &typeMap = m_scopeLists.back(); if (typeMap.find(&listPtr) == typeMap.cend()) { @@ -732,6 +739,9 @@ std::shared_ptr LLVMCodeBuilder::finalize() typeMap[&listPtr] = listPtr.type; } + if (!safe) + listPtr.type = Compiler::StaticType::Unknown; + // Check if enough space is allocated llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr); llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); @@ -767,6 +777,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() Compiler::StaticType type = optimizeRegisterType(valueArg.second); LLVMListPtr &listPtr = m_listPtrs[step.workList]; + const bool safe = isVarOrListTypeSafe(insPtr, listPtr.type); auto &typeMap = m_scopeLists.back(); if (typeMap.find(&listPtr) == typeMap.cend()) { @@ -777,6 +788,9 @@ std::shared_ptr LLVMCodeBuilder::finalize() typeMap[&listPtr] = listPtr.type; } + if (!safe) + listPtr.type = Compiler::StaticType::Unknown; + llvm::Value *oldAllocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr); // Range check @@ -813,6 +827,9 @@ std::shared_ptr LLVMCodeBuilder::finalize() Compiler::StaticType type = optimizeRegisterType(valueArg.second); LLVMListPtr &listPtr = m_listPtrs[step.workList]; + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + // Range check llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); @@ -856,7 +873,10 @@ std::shared_ptr LLVMCodeBuilder::finalize() case LLVMInstruction::Type::GetListItem: { assert(step.args.size() == 1); const auto &arg = step.args[0]; - const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); @@ -884,7 +904,11 @@ std::shared_ptr LLVMCodeBuilder::finalize() case LLVMInstruction::Type::GetListItemIndex: { assert(step.args.size() == 1); const auto &arg = step.args[0]; - const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + step.functionReturnReg->value = m_builder.CreateSIToFP(getListItemIndex(listPtr, arg.second), m_builder.getDoubleTy()); break; } @@ -892,7 +916,11 @@ std::shared_ptr LLVMCodeBuilder::finalize() case LLVMInstruction::Type::ListContainsItem: { assert(step.args.size() == 1); const auto &arg = step.args[0]; - const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + llvm::Value *index = getListItemIndex(listPtr, arg.second); step.functionReturnReg->value = m_builder.CreateICmpSGT(index, llvm::ConstantInt::get(m_builder.getInt64Ty(), -1, true)); break; @@ -2258,30 +2286,31 @@ void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr) m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty); } -bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType) const +bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType) const { std::unordered_set processed; - return isVariableTypeSafe(ins, expectedType, processed); + return isVarOrListTypeSafe(ins, expectedType, processed); } -bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &processed) const +bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &processed) const { /* * The main part of the loop type analyzer. * - * This is a recursive function which is called when variable read - * instruction is created. It checks the last write to the - * variable in one of the loop scopes. + * This is a recursive function which is called when variable + * or list instruction is created. It checks the last write to + * the variable or list in one of the loop scopes. * * If the last write operation writes a value with a different * type, it will return false, otherwise true. * - * If the last written value is from a variable, this function - * is called for it to check its type safety (that's why it is - * recursive). + * If the last written value is from a variable or list, this + * function is called for it to check its type safety (that's + * why it is recursive). * - * If the variable had a write operation before (in the same, - * parent or child loop scope), it is checked recursively. + * If the variable or list had a write operation before (in + * the same, parent or child loop scope), it is checked + * recursively. */ if (!ins) @@ -2307,7 +2336,9 @@ bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr ins, C processed.insert(ins.get()); assert(std::find(m_instructions.begin(), m_instructions.end(), ins) != m_instructions.end()); - const LLVMVariablePtr &varPtr = m_variablePtrs.at(ins->workVariable); + const LLVMVariablePtr *varPtr = ins->workVariable ? &m_variablePtrs.at(ins->workVariable) : nullptr; + const LLVMListPtr *listPtr = ins->workList ? &m_listPtrs.at(ins->workList) : nullptr; + assert((varPtr || listPtr) && !(varPtr && listPtr)); auto scope = ins->loopScope; // If we aren't in a loop, we're safe @@ -2319,21 +2350,23 @@ bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr ins, C return false; std::shared_ptr write; + const auto &instructions = varPtr ? m_variableInstructions : m_listInstructions; // Find this instruction - auto it = std::find(m_variableInstructions.begin(), m_variableInstructions.end(), ins); - assert(it != m_variableInstructions.end()); + auto it = std::find(instructions.begin(), instructions.end(), ins); + assert(it != instructions.end()); // Find previous write instruction in this, parent or child loop scope - size_t index = it - m_variableInstructions.begin(); + size_t index = it - instructions.begin(); if (index > 0) { bool found = false; do { index--; - write = m_variableInstructions[index]; - found = (write->loopScope && write->type == LLVMInstruction::Type::WriteVariable && write->workVariable == ins->workVariable); + write = instructions[index]; + const bool isWrite = (VAR_LIST_READ_INSTRUCTIONS.find(write->type) == VAR_LIST_READ_INSTRUCTIONS.cend()); + found = (write->loopScope && isWrite && write->workVariable == ins->workVariable); } while (index > 0 && !found); if (found) { @@ -2355,13 +2388,15 @@ bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr ins, C // If there was a write operation before this instruction (in this, parent or child scope), check it if (parentScope) { if (parentScope == scope) - return isVariableWriteResultTypeSafe(write, expectedType, true, processed); + return isVarOrListWriteResultTypeSafe(write, expectedType, true, processed); else - return isVariableTypeSafe(write, expectedType, processed); + return isVarOrListTypeSafe(write, expectedType, processed); } } } + const auto &loopWrites = varPtr ? varPtr->loopVariableWrites : listPtr->loopListWrites; + // Get last write operation write = nullptr; @@ -2374,9 +2409,9 @@ bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr ins, C // Find last loop scope (may be a parent or child scope) while (checkScope) { - auto it = varPtr.loopVariableWrites.find(checkScope); + auto it = loopWrites.find(checkScope); - if (it != varPtr.loopVariableWrites.cend()) { + if (it != loopWrites.cend()) { assert(!it->second.empty()); write = it->second.back(); } @@ -2393,29 +2428,36 @@ bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr ins, C bool safe = true; - if (ins->type == LLVMInstruction::Type::WriteVariable) - safe = isVariableWriteResultTypeSafe(ins, expectedType, false, processed); + if (VAR_LIST_READ_INSTRUCTIONS.find(ins->type) == VAR_LIST_READ_INSTRUCTIONS.cend()) // write + safe = isVarOrListWriteResultTypeSafe(ins, expectedType, false, processed); if (safe) - return isVariableWriteResultTypeSafe(write, expectedType, false, processed); + return isVarOrListWriteResultTypeSafe(write, expectedType, false, processed); else return false; } -bool LLVMCodeBuilder::isVariableWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &processed) +bool LLVMCodeBuilder::isVarOrListWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &processed) const { - const LLVMVariablePtr &varPtr = m_variablePtrs.at(ins->workVariable); + const LLVMVariablePtr *varPtr = ins->workVariable ? &m_variablePtrs.at(ins->workVariable) : nullptr; + const LLVMListPtr *listPtr = ins->workList ? &m_listPtrs.at(ins->workList) : nullptr; + assert((varPtr || listPtr) && !(varPtr && listPtr)); // If the write operation writes the value of another variable, recursively check its type safety - // TODO: Check get list item instruction - auto argIns = ins->args[0].second->instruction; + const auto arg = ins->args.back().second; // value is always the last argument + auto argIns = arg->instruction; - if (argIns && argIns->type == LLVMInstruction::Type::ReadVariable) - return isVariableTypeSafe(argIns, expectedType, processed); + if (argIns && (argIns->type == LLVMInstruction::Type::ReadVariable || argIns->type == LLVMInstruction::Type::GetListItem)) + return isVarOrListTypeSafe(argIns, expectedType, processed); // Check written type - return optimizeRegisterType(ins->args[0].second) == expectedType && (varPtr.type == expectedType || ignoreSavedType); + const bool typeMatches = (optimizeRegisterType(arg) == expectedType); + + if (varPtr) + return typeMatches && (varPtr->type == expectedType || ignoreSavedType); + else + return typeMatches && (listPtr->type == expectedType || ignoreSavedType); } LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h index d8d7b6f97..22fa1609c 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -149,9 +149,9 @@ class LLVMCodeBuilder : public ICodeBuilder void reloadVariables(llvm::Value *targetVariables); void reloadLists(); void updateListDataPtr(const LLVMListPtr &listPtr); - bool isVariableTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType) const; - bool isVariableTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &processed) const; - bool isVariableWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &processed) const; + bool isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType) const; + bool isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &processed) const; + bool isVarOrListWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &processed) const; LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args); LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {}); diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index 2789deec4..e2ebe5d11 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -4815,6 +4815,831 @@ TEST_F(LLVMCodeBuilderTest, LoopLists) ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); } +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis1) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a string is added later + v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue("test"); + m_builder->createListAppend(localList.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "0\n" + "1\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis2) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a number is assigned later + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(12.5); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "12.5\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis3) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList.get()); + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(0); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with unknown type is inserted (the item has unknown type because a string is assigned later) + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListInsert(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue("test"); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "2\n" + "1\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis4) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList.get()); + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(0); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a number list item is added later + v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "12.5\n" + "0\n" + "1\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis5) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList.get()); + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a string list item is assigned later which becomes a number later + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(12.5); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis6) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with unknown type is added later (even though the list item has the original type assigned, it cannot be determined at compile time) + v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(10); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "10\n" + "0\n" + "1\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis7) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with unknown type is assigned later + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(10); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis8) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with a unknown type is assigned later + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis9) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(5); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + + // Type is unknown here because a list item with unknown type is assigned before the read operation + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(10); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue("test"); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5\n" + "0\n" + "1\n" + "5\n" + "0\n" + "1\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis10) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + localList->append(123); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with unknown type is assigned later + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(12.5); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis11) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + localList->append(123); + localList->append("10"); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because an item from the same list is assigned later, but the list has unknown type + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(1)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(12.5); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "0\n" + "1\n" + "10\n" + "-1\n" + "0\n" + "10\n" + "-1\n" + "0\n" + "10\n" + "-1\n" + "0\n" + "10\n" + "-1\n" + "0\n" + "10\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis12) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + localList->append(123); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because an item from another list is assigned later, but it has an item from this list assigned + // This case is not checked because the code isn't considered valid + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\n" + "0\n" + "-1\n" + "0\n" + "0\n" + "-1\n" + "0\n" + "0\n" + "-1\n" + "0\n" + "0\n" + "-1\n" + "0\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + TEST_F(LLVMCodeBuilderTest, StopNoWarp) { Sprite sprite; From 17b37917793a92b07b35c56e3059e91ec58e3dea Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:55:45 +0100 Subject: [PATCH 14/38] LLVMCodeBuilder: Add test for variable and list loop type analysis --- test/dev/llvm/llvmcodebuilder_test.cpp | 66 ++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index e2ebe5d11..1da6bf082 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -5640,6 +5640,72 @@ TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis12) ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); } +TEST_F(LLVMCodeBuilderTest, VarAndListLoopTypeAnalysis) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto var = std::make_shared("", ""); + sprite.addVariable(var); + + auto list = std::make_shared("", ""); + sprite.addList(list); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(-2); + m_builder->createVariableWrite(var.get(), v); + + m_builder->createListClear(list.get()); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(list.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable value with unknown type is added later + v = m_builder->createSub(m_builder->addListSize(list.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(list.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(list.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(list.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addVariableValue(var.get()); + m_builder->createListAppend(list.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(var.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "-2\n" + "0\n" + "1\n" + "0\n" + "0\n" + "1\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + TEST_F(LLVMCodeBuilderTest, StopNoWarp) { Sprite sprite; From bd019d96ef6efff57e8edbec2a113d845b9e2892 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 18 Jan 2025 23:22:07 +0100 Subject: [PATCH 15/38] LLVMCodeBuilder: Optimize variables and lists recursively used in loops --- .../engine/internal/llvm/llvmcodebuilder.cpp | 59 ++++++++++--------- .../engine/internal/llvm/llvmcodebuilder.h | 4 +- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index c19e4bcb2..04d23b39e 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -728,7 +728,6 @@ std::shared_ptr LLVMCodeBuilder::finalize() Compiler::StaticType type = optimizeRegisterType(arg.second); LLVMListPtr &listPtr = m_listPtrs[step.workList]; - const bool safe = isVarOrListTypeSafe(insPtr, listPtr.type); auto &typeMap = m_scopeLists.back(); if (typeMap.find(&listPtr) == typeMap.cend()) { @@ -739,7 +738,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() typeMap[&listPtr] = listPtr.type; } - if (!safe) + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) listPtr.type = Compiler::StaticType::Unknown; // Check if enough space is allocated @@ -777,7 +776,6 @@ std::shared_ptr LLVMCodeBuilder::finalize() Compiler::StaticType type = optimizeRegisterType(valueArg.second); LLVMListPtr &listPtr = m_listPtrs[step.workList]; - const bool safe = isVarOrListTypeSafe(insPtr, listPtr.type); auto &typeMap = m_scopeLists.back(); if (typeMap.find(&listPtr) == typeMap.cend()) { @@ -788,7 +786,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() typeMap[&listPtr] = listPtr.type; } - if (!safe) + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) listPtr.type = Compiler::StaticType::Unknown; llvm::Value *oldAllocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr); @@ -2289,10 +2287,11 @@ void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr) bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType) const { std::unordered_set processed; - return isVarOrListTypeSafe(ins, expectedType, processed); + int counter = 0; + return isVarOrListTypeSafe(ins, expectedType, processed, counter); } -bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &processed) const +bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &log, int &c) const { /* * The main part of the loop type analyzer. @@ -2318,22 +2317,20 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, /* * If we are processing something that has been already - * processed, give up to avoid infinite recursion. - * - * This can happen in edge cases like this: - * var = var + * processed, it means there's a case like this: + * x = x * * or this: * x = y + * ... * y = x * - * This code isn't considered valid, so don't bother - * optimizing. + * Increment counter to ignore last n write operations. */ - if (processed.find(ins.get()) != processed.cend()) - return false; - - processed.insert(ins.get()); + if (log.find(ins.get()) != log.cend()) + c++; + else + log.insert(ins.get()); assert(std::find(m_instructions.begin(), m_instructions.end(), ins) != m_instructions.end()); const LLVMVariablePtr *varPtr = ins->workVariable ? &m_variablePtrs.at(ins->workVariable) : nullptr; @@ -2388,18 +2385,15 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, // If there was a write operation before this instruction (in this, parent or child scope), check it if (parentScope) { if (parentScope == scope) - return isVarOrListWriteResultTypeSafe(write, expectedType, true, processed); + return isVarOrListWriteResultTypeSafe(write, expectedType, true, log, c); else - return isVarOrListTypeSafe(write, expectedType, processed); + return isVarOrListTypeSafe(write, expectedType, log, c); } } } const auto &loopWrites = varPtr ? varPtr->loopVariableWrites : listPtr->loopListWrites; - // Get last write operation - write = nullptr; - // Find root loop scope auto checkScope = scope; @@ -2407,13 +2401,18 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, checkScope = checkScope->parentScope; } - // Find last loop scope (may be a parent or child scope) + // Find n-th last write operation based on counter (may be in a parent or child scope) + std::vector> lastWrites; + while (checkScope) { auto it = loopWrites.find(checkScope); if (it != loopWrites.cend()) { assert(!it->second.empty()); - write = it->second.back(); + const auto &writes = it->second; + + for (auto w : writes) + lastWrites.push_back(w); } if (checkScope->childScopes.empty()) @@ -2422,22 +2421,24 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, checkScope = checkScope->childScopes.back(); } - // If there aren't any write operations, we're safe - if (!write) + // If there aren't any write operations or all of them are ignored, we're safe + if (c >= lastWrites.size()) return true; + write = lastWrites[lastWrites.size() - c - 1]; // Ignore last c writes + bool safe = true; if (VAR_LIST_READ_INSTRUCTIONS.find(ins->type) == VAR_LIST_READ_INSTRUCTIONS.cend()) // write - safe = isVarOrListWriteResultTypeSafe(ins, expectedType, false, processed); + safe = isVarOrListWriteResultTypeSafe(ins, expectedType, false, log, c); if (safe) - return isVarOrListWriteResultTypeSafe(write, expectedType, false, processed); + return isVarOrListWriteResultTypeSafe(write, expectedType, false, log, c); else return false; } -bool LLVMCodeBuilder::isVarOrListWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &processed) +bool LLVMCodeBuilder::isVarOrListWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &log, int &c) const { const LLVMVariablePtr *varPtr = ins->workVariable ? &m_variablePtrs.at(ins->workVariable) : nullptr; @@ -2449,7 +2450,7 @@ bool LLVMCodeBuilder::isVarOrListWriteResultTypeSafe(std::shared_ptrinstruction; if (argIns && (argIns->type == LLVMInstruction::Type::ReadVariable || argIns->type == LLVMInstruction::Type::GetListItem)) - return isVarOrListTypeSafe(argIns, expectedType, processed); + return isVarOrListTypeSafe(argIns, expectedType, log, c); // Check written type const bool typeMatches = (optimizeRegisterType(arg) == expectedType); diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h index 22fa1609c..fb49fe64b 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -150,8 +150,8 @@ class LLVMCodeBuilder : public ICodeBuilder void reloadLists(); void updateListDataPtr(const LLVMListPtr &listPtr); bool isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType) const; - bool isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &processed) const; - bool isVarOrListWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &processed) const; + bool isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &log, int &c) const; + bool isVarOrListWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &log, int &c) const; LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args); LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {}); From b3117dc0ebdc72bd2d3800219867fca59c4b8b78 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 19 Jan 2025 00:29:33 +0100 Subject: [PATCH 16/38] LLVMCodeBuilder: Fix list type change before last write in loop --- .../engine/internal/llvm/llvmcodebuilder.cpp | 19 +++++++++++++++---- test/dev/llvm/llvmcodebuilder_test.cpp | 13 +++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index 04d23b39e..0c0a97434 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -2356,7 +2356,7 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, // Find previous write instruction in this, parent or child loop scope size_t index = it - instructions.begin(); - if (index > 0) { + if (varPtr && index > 0) { // this is only needed for variables bool found = false; do { @@ -2401,7 +2401,7 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, checkScope = checkScope->parentScope; } - // Find n-th last write operation based on counter (may be in a parent or child scope) + // Get all write operations in all loop scopes (from the root loop scope) std::vector> lastWrites; while (checkScope) { @@ -2425,7 +2425,18 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, if (c >= lastWrites.size()) return true; - write = lastWrites[lastWrites.size() - c - 1]; // Ignore last c writes + if (varPtr) + write = lastWrites[lastWrites.size() - c - 1]; // Ignore last c writes + else { + // If this is a list instruction, check last write operations except current + for (long i = lastWrites.size() - c - 1; i >= 0; i--) { // Ignore last c writes + if (lastWrites[i] == ins) + continue; + + if (!isVarOrListWriteResultTypeSafe(lastWrites[i], expectedType, false, log, c)) + return false; + } + } bool safe = true; @@ -2433,7 +2444,7 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, safe = isVarOrListWriteResultTypeSafe(ins, expectedType, false, log, c); if (safe) - return isVarOrListWriteResultTypeSafe(write, expectedType, false, log, c); + return write ? isVarOrListWriteResultTypeSafe(write, expectedType, false, log, c) : true; else return false; } diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index 1da6bf082..e107097b8 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -5149,15 +5149,19 @@ TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis6) createBuilder(&sprite, true); + m_builder->createListClear(globalList.get()); m_builder->createListClear(localList.get()); - CompilerValue *v = m_builder->addConstValue(5.25); + CompilerValue *v = m_builder->addConstValue(-156.07); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(5.25); m_builder->createListAppend(localList.get(), v); v = m_builder->addConstValue(2); m_builder->beginRepeatLoop(v); { - // Type is unknown here because a list item with unknown type is added later (even though the list item has the original type assigned, it cannot be determined at compile time) + // Type is unknown here because a list item with unknown type is added later v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1)); v = m_builder->addListItem(localList.get(), v); m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); @@ -5174,7 +5178,8 @@ TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis6) v = m_builder->addConstValue(10); m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); - v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + v = m_builder->createSub(m_builder->addListSize(globalList.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(globalList.get(), v); m_builder->createListAppend(localList.get(), v); v = m_builder->addConstValue("test"); @@ -5188,7 +5193,7 @@ TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis6) "5.25\n" "0\n" "1\n" - "10\n" + "0\n" "0\n" "1\n"; From 5d6f27351502d36ee4f9cac7c2fb8d5cc3f8a67c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 26 Jan 2025 15:37:59 +0100 Subject: [PATCH 17/38] Add color() method to extensions Resolves: #582 --- include/scratchcpp/iextension.h | 7 ++++--- src/blocks/controlblocks.cpp | 5 +++++ src/blocks/controlblocks.h | 1 + src/blocks/customblocks.cpp | 5 +++++ src/blocks/customblocks.h | 1 + src/blocks/eventblocks.cpp | 5 +++++ src/blocks/eventblocks.h | 1 + src/blocks/listblocks.cpp | 5 +++++ src/blocks/listblocks.h | 1 + src/blocks/looksblocks.cpp | 5 +++++ src/blocks/looksblocks.h | 1 + src/blocks/motionblocks.cpp | 5 +++++ src/blocks/motionblocks.h | 1 + src/blocks/operatorblocks.cpp | 5 +++++ src/blocks/operatorblocks.h | 1 + src/blocks/sensingblocks.cpp | 5 +++++ src/blocks/sensingblocks.h | 1 + src/blocks/soundblocks.cpp | 5 +++++ src/blocks/soundblocks.h | 1 + src/blocks/variableblocks.cpp | 5 +++++ src/blocks/variableblocks.h | 1 + src/dev/blocks/controlblocks.cpp | 5 +++++ src/dev/blocks/controlblocks.h | 1 + src/dev/blocks/customblocks.cpp | 5 +++++ src/dev/blocks/customblocks.h | 1 + src/dev/blocks/eventblocks.cpp | 5 +++++ src/dev/blocks/eventblocks.h | 1 + src/dev/blocks/listblocks.cpp | 5 +++++ src/dev/blocks/listblocks.h | 1 + src/dev/blocks/looksblocks.cpp | 5 +++++ src/dev/blocks/looksblocks.h | 1 + src/dev/blocks/motionblocks.cpp | 5 +++++ src/dev/blocks/motionblocks.h | 1 + src/dev/blocks/operatorblocks.cpp | 5 +++++ src/dev/blocks/operatorblocks.h | 1 + src/dev/blocks/sensingblocks.cpp | 5 +++++ src/dev/blocks/sensingblocks.h | 1 + src/dev/blocks/soundblocks.cpp | 5 +++++ src/dev/blocks/soundblocks.h | 1 + src/dev/blocks/variableblocks.cpp | 5 +++++ src/dev/blocks/variableblocks.h | 1 + test/compiler/testextension.cpp | 5 +++++ test/compiler/testextension.h | 1 + test/dev/test_api/testextension.cpp | 5 +++++ test/dev/test_api/testextension.h | 1 + test/extensions/testextension.cpp | 5 +++++ test/extensions/testextension.h | 1 + test/mocks/extensionmock.h | 1 + test/scratch_classes/testextension.cpp | 5 +++++ test/scratch_classes/testextension.h | 1 + test/scratchconfiguration/extension1.cpp | 5 +++++ test/scratchconfiguration/extension1.h | 1 + test/scratchconfiguration/extension2.cpp | 5 +++++ test/scratchconfiguration/extension2.h | 1 + test/scratchconfiguration/extension3.cpp | 5 +++++ test/scratchconfiguration/extension3.h | 1 + 56 files changed, 167 insertions(+), 3 deletions(-) diff --git a/include/scratchcpp/iextension.h b/include/scratchcpp/iextension.h index a53b83567..0a4bff08e 100644 --- a/include/scratchcpp/iextension.h +++ b/include/scratchcpp/iextension.h @@ -2,9 +2,7 @@ #pragma once -#include - -#include "global.h" +#include "value_functions.h" namespace libscratchcpp { @@ -27,6 +25,9 @@ class LIBSCRATCHCPP_EXPORT IExtension /*! Returns the description of the extension. */ virtual std::string description() const = 0; + /*! Returns the block color of the extension. */ + virtual Rgb color() const = 0; + /*! Override this method to register blocks. */ virtual void registerBlocks(IEngine *engine) = 0; diff --git a/src/blocks/controlblocks.cpp b/src/blocks/controlblocks.cpp index ab5aab69d..aa4ebbce2 100644 --- a/src/blocks/controlblocks.cpp +++ b/src/blocks/controlblocks.cpp @@ -25,6 +25,11 @@ std::string ControlBlocks::description() const return name() + " blocks"; } +Rgb ControlBlocks::color() const +{ + return rgb(255, 171, 25); +} + void ControlBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/controlblocks.h b/src/blocks/controlblocks.h index aa0c87104..623ab651f 100644 --- a/src/blocks/controlblocks.h +++ b/src/blocks/controlblocks.h @@ -43,6 +43,7 @@ class ControlBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/customblocks.cpp b/src/blocks/customblocks.cpp index aee3b7573..8066a025e 100644 --- a/src/blocks/customblocks.cpp +++ b/src/blocks/customblocks.cpp @@ -20,6 +20,11 @@ std::string CustomBlocks::description() const return name(); } +Rgb CustomBlocks::color() const +{ + return rgb(255, 102, 128); +} + void CustomBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/customblocks.h b/src/blocks/customblocks.h index a40e37c3f..234828860 100644 --- a/src/blocks/customblocks.h +++ b/src/blocks/customblocks.h @@ -24,6 +24,7 @@ class CustomBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/eventblocks.cpp b/src/blocks/eventblocks.cpp index 757e2bda7..85ec92354 100644 --- a/src/blocks/eventblocks.cpp +++ b/src/blocks/eventblocks.cpp @@ -30,6 +30,11 @@ std::string libscratchcpp::EventBlocks::description() const return "Event blocks"; } +Rgb EventBlocks::color() const +{ + return rgb(255, 191, 0); +} + void EventBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/eventblocks.h b/src/blocks/eventblocks.h index e4d28f4a6..9ddca5b60 100644 --- a/src/blocks/eventblocks.h +++ b/src/blocks/eventblocks.h @@ -38,6 +38,7 @@ class EventBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/listblocks.cpp b/src/blocks/listblocks.cpp index 135dd0bdb..4302c3bc8 100644 --- a/src/blocks/listblocks.cpp +++ b/src/blocks/listblocks.cpp @@ -23,6 +23,11 @@ std::string ListBlocks::description() const return "List blocks"; } +Rgb ListBlocks::color() const +{ + return rgb(255, 102, 26); +} + void ListBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/listblocks.h b/src/blocks/listblocks.h index 69e49aa39..ed621314f 100644 --- a/src/blocks/listblocks.h +++ b/src/blocks/listblocks.h @@ -28,6 +28,7 @@ class ListBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index f142f34c9..4a5ddf31c 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -42,6 +42,11 @@ std::string LooksBlocks::description() const return name() + " blocks"; } +Rgb LooksBlocks::color() const +{ + return rgb(153, 102, 255); +} + void LooksBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 8881d7eb0..eca733de1 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -60,6 +60,7 @@ class LooksBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index c5a96004a..62fd28ae7 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -29,6 +29,11 @@ std::string MotionBlocks::description() const return name() + " blocks"; } +Rgb MotionBlocks::color() const +{ + return rgb(76, 151, 255); +} + void MotionBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index b545822ea..b86e9481c 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -45,6 +45,7 @@ class MotionBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/src/blocks/operatorblocks.cpp b/src/blocks/operatorblocks.cpp index 376ae27a5..567d4a155 100644 --- a/src/blocks/operatorblocks.cpp +++ b/src/blocks/operatorblocks.cpp @@ -18,6 +18,11 @@ std::string OperatorBlocks::description() const return "Operator blocks"; } +Rgb OperatorBlocks::color() const +{ + return rgb(89, 192, 89); +} + void OperatorBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/operatorblocks.h b/src/blocks/operatorblocks.h index e9dd932aa..f2f5b7298 100644 --- a/src/blocks/operatorblocks.h +++ b/src/blocks/operatorblocks.h @@ -55,6 +55,7 @@ class OperatorBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp index af1349ed1..f5b18ce79 100644 --- a/src/blocks/sensingblocks.cpp +++ b/src/blocks/sensingblocks.cpp @@ -34,6 +34,11 @@ std::string SensingBlocks::description() const return name() + " blocks"; } +Rgb SensingBlocks::color() const +{ + return rgb(92, 177, 214); +} + void SensingBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/sensingblocks.h b/src/blocks/sensingblocks.h index 8153a18bd..5b3fc6380 100644 --- a/src/blocks/sensingblocks.h +++ b/src/blocks/sensingblocks.h @@ -60,6 +60,7 @@ class SensingBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index 6cc2aa7b8..f001fa7bb 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -34,6 +34,11 @@ std::string SoundBlocks::description() const return name() + " blocks"; } +Rgb SoundBlocks::color() const +{ + return rgb(207, 99, 207); +} + void SoundBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index d31b8a620..0535eb73e 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -37,6 +37,7 @@ class SoundBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/src/blocks/variableblocks.cpp b/src/blocks/variableblocks.cpp index 81961e49a..17236a304 100644 --- a/src/blocks/variableblocks.cpp +++ b/src/blocks/variableblocks.cpp @@ -23,6 +23,11 @@ std::string VariableBlocks::description() const return "Variable blocks"; } +Rgb VariableBlocks::color() const +{ + return rgb(255, 140, 26); +} + void VariableBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/variableblocks.h b/src/blocks/variableblocks.h index 2372660bb..f4b2922cc 100644 --- a/src/blocks/variableblocks.h +++ b/src/blocks/variableblocks.h @@ -27,6 +27,7 @@ class VariableBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp index 9567f69f7..0ca0524dc 100644 --- a/src/dev/blocks/controlblocks.cpp +++ b/src/dev/blocks/controlblocks.cpp @@ -26,6 +26,11 @@ std::string ControlBlocks::description() const return name() + " blocks"; } +Rgb ControlBlocks::color() const +{ + return rgb(255, 171, 25); +} + void ControlBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "control_forever", &compileForever); diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h index 178508249..0f44982c5 100644 --- a/src/dev/blocks/controlblocks.h +++ b/src/dev/blocks/controlblocks.h @@ -12,6 +12,7 @@ class ControlBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/blocks/customblocks.cpp b/src/dev/blocks/customblocks.cpp index e78048bcf..655e4d3b5 100644 --- a/src/dev/blocks/customblocks.cpp +++ b/src/dev/blocks/customblocks.cpp @@ -20,6 +20,11 @@ std::string CustomBlocks::description() const return name(); } +Rgb CustomBlocks::color() const +{ + return rgb(255, 102, 128); +} + void CustomBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "procedures_definition", [](Compiler *) -> CompilerValue * { return nullptr; }); diff --git a/src/dev/blocks/customblocks.h b/src/dev/blocks/customblocks.h index 55572ba94..b2a2613f7 100644 --- a/src/dev/blocks/customblocks.h +++ b/src/dev/blocks/customblocks.h @@ -12,6 +12,7 @@ class CustomBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp index a2b18b3ba..ba4d2663b 100644 --- a/src/dev/blocks/eventblocks.cpp +++ b/src/dev/blocks/eventblocks.cpp @@ -24,6 +24,11 @@ std::string libscratchcpp::EventBlocks::description() const return "Event blocks"; } +Rgb EventBlocks::color() const +{ + return rgb(255, 191, 0); +} + void EventBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObject); diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h index 34454ebc1..512bae0cc 100644 --- a/src/dev/blocks/eventblocks.h +++ b/src/dev/blocks/eventblocks.h @@ -12,6 +12,7 @@ class EventBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp index 653875e45..64b441357 100644 --- a/src/dev/blocks/listblocks.cpp +++ b/src/dev/blocks/listblocks.cpp @@ -20,6 +20,11 @@ std::string ListBlocks::description() const return "List blocks"; } +Rgb ListBlocks::color() const +{ + return rgb(255, 102, 26); +} + void ListBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "data_addtolist", &compileAddToList); diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h index 3a8d01261..02dbd79ce 100644 --- a/src/dev/blocks/listblocks.h +++ b/src/dev/blocks/listblocks.h @@ -14,6 +14,7 @@ class ListBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/blocks/looksblocks.cpp b/src/dev/blocks/looksblocks.cpp index 0d471fb04..8da59e7a9 100644 --- a/src/dev/blocks/looksblocks.cpp +++ b/src/dev/blocks/looksblocks.cpp @@ -14,6 +14,11 @@ std::string LooksBlocks::description() const return name() + " blocks"; } +Rgb LooksBlocks::color() const +{ + return rgb(153, 102, 255); +} + void LooksBlocks::registerBlocks(IEngine *engine) { } diff --git a/src/dev/blocks/looksblocks.h b/src/dev/blocks/looksblocks.h index 8bfa5f185..b75297549 100644 --- a/src/dev/blocks/looksblocks.h +++ b/src/dev/blocks/looksblocks.h @@ -12,6 +12,7 @@ class LooksBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/src/dev/blocks/motionblocks.cpp b/src/dev/blocks/motionblocks.cpp index e44a673c0..a249cdffa 100644 --- a/src/dev/blocks/motionblocks.cpp +++ b/src/dev/blocks/motionblocks.cpp @@ -14,6 +14,11 @@ std::string MotionBlocks::description() const return name() + " blocks"; } +Rgb MotionBlocks::color() const +{ + return rgb(76, 151, 255); +} + void MotionBlocks::registerBlocks(IEngine *engine) { } diff --git a/src/dev/blocks/motionblocks.h b/src/dev/blocks/motionblocks.h index 125b40738..44d7c0bd1 100644 --- a/src/dev/blocks/motionblocks.h +++ b/src/dev/blocks/motionblocks.h @@ -12,6 +12,7 @@ class MotionBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index b962d3c7d..508d41a1e 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -21,6 +21,11 @@ std::string OperatorBlocks::description() const return "Operator blocks"; } +Rgb OperatorBlocks::color() const +{ + return rgb(89, 192, 89); +} + void OperatorBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "operator_add", &compileAdd); diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 1804e9313..647512e6c 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -14,6 +14,7 @@ class OperatorBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/blocks/sensingblocks.cpp b/src/dev/blocks/sensingblocks.cpp index 42896ecb6..8e3b18cac 100644 --- a/src/dev/blocks/sensingblocks.cpp +++ b/src/dev/blocks/sensingblocks.cpp @@ -14,6 +14,11 @@ std::string SensingBlocks::description() const return name() + " blocks"; } +Rgb SensingBlocks::color() const +{ + return rgb(92, 177, 214); +} + void SensingBlocks::registerBlocks(IEngine *engine) { } diff --git a/src/dev/blocks/sensingblocks.h b/src/dev/blocks/sensingblocks.h index ab934fb08..9a0f367f5 100644 --- a/src/dev/blocks/sensingblocks.h +++ b/src/dev/blocks/sensingblocks.h @@ -12,6 +12,7 @@ class SensingBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/src/dev/blocks/soundblocks.cpp b/src/dev/blocks/soundblocks.cpp index 4a440a9a4..df7611ab0 100644 --- a/src/dev/blocks/soundblocks.cpp +++ b/src/dev/blocks/soundblocks.cpp @@ -14,6 +14,11 @@ std::string SoundBlocks::description() const return name() + " blocks"; } +Rgb SoundBlocks::color() const +{ + return rgb(207, 99, 207); +} + void SoundBlocks::registerBlocks(IEngine *engine) { } diff --git a/src/dev/blocks/soundblocks.h b/src/dev/blocks/soundblocks.h index afae6c9c5..f605c7b64 100644 --- a/src/dev/blocks/soundblocks.h +++ b/src/dev/blocks/soundblocks.h @@ -12,6 +12,7 @@ class SoundBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/src/dev/blocks/variableblocks.cpp b/src/dev/blocks/variableblocks.cpp index 177d8dabf..9e77c7bf0 100644 --- a/src/dev/blocks/variableblocks.cpp +++ b/src/dev/blocks/variableblocks.cpp @@ -21,6 +21,11 @@ std::string VariableBlocks::description() const return "Variable blocks"; } +Rgb VariableBlocks::color() const +{ + return rgb(255, 140, 26); +} + void VariableBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "data_variable", &compileVariable); diff --git a/src/dev/blocks/variableblocks.h b/src/dev/blocks/variableblocks.h index 9cb0bcc47..423085d33 100644 --- a/src/dev/blocks/variableblocks.h +++ b/src/dev/blocks/variableblocks.h @@ -12,6 +12,7 @@ class VariableBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/test/compiler/testextension.cpp b/test/compiler/testextension.cpp index cb001061d..ce26bbc3a 100644 --- a/test/compiler/testextension.cpp +++ b/test/compiler/testextension.cpp @@ -18,6 +18,11 @@ std::string TestExtension::description() const return ""; } +Rgb TestExtension::color() const +{ + return rgb(0, 0, 0); +} + void TestExtension::registerBlocks(IEngine *engine) { engine->addInput(this, "INPUT1", INPUT1); diff --git a/test/compiler/testextension.h b/test/compiler/testextension.h index c7bdf102d..6efc819d8 100644 --- a/test/compiler/testextension.h +++ b/test/compiler/testextension.h @@ -25,6 +25,7 @@ class TestExtension : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/test/dev/test_api/testextension.cpp b/test/dev/test_api/testextension.cpp index c637e522e..24eebec20 100644 --- a/test/dev/test_api/testextension.cpp +++ b/test/dev/test_api/testextension.cpp @@ -21,6 +21,11 @@ std::string TestExtension::description() const return ""; } +Rgb TestExtension::color() const +{ + return rgb(0, 0, 0); +} + void TestExtension::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "test_simple", &compileSimple); diff --git a/test/dev/test_api/testextension.h b/test/dev/test_api/testextension.h index e69bbcdd7..12f4e7297 100644 --- a/test/dev/test_api/testextension.h +++ b/test/dev/test_api/testextension.h @@ -10,6 +10,7 @@ class TestExtension : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/test/extensions/testextension.cpp b/test/extensions/testextension.cpp index d7a8dd5ba..5d122d483 100644 --- a/test/extensions/testextension.cpp +++ b/test/extensions/testextension.cpp @@ -14,6 +14,11 @@ std::string TestExtension::description() const return "Test extension"; } +Rgb TestExtension::color() const +{ + return rgb(0, 0, 0); +} + void TestExtension::registerBlocks(IEngine *engine) { engine->clear(); diff --git a/test/extensions/testextension.h b/test/extensions/testextension.h index 7bad056da..7e89e77d4 100644 --- a/test/extensions/testextension.h +++ b/test/extensions/testextension.h @@ -10,6 +10,7 @@ class TestExtension : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/test/mocks/extensionmock.h b/test/mocks/extensionmock.h index 19f47d6ee..e0750c374 100644 --- a/test/mocks/extensionmock.h +++ b/test/mocks/extensionmock.h @@ -10,6 +10,7 @@ class ExtensionMock : public IExtension public: MOCK_METHOD(std::string, name, (), (const, override)); MOCK_METHOD(std::string, description, (), (const, override)); + MOCK_METHOD(Rgb, color, (), (const, override)); MOCK_METHOD(void, registerBlocks, (IEngine *), (override)); MOCK_METHOD(void, onInit, (IEngine *), (override)); diff --git a/test/scratch_classes/testextension.cpp b/test/scratch_classes/testextension.cpp index cd848ca6f..9d21e10eb 100644 --- a/test/scratch_classes/testextension.cpp +++ b/test/scratch_classes/testextension.cpp @@ -14,6 +14,11 @@ std::string TestExtension::description() const return ""; } +Rgb TestExtension::color() const +{ + return rgb(0, 0, 0); +} + void TestExtension::registerBlocks(IEngine *engine) { } diff --git a/test/scratch_classes/testextension.h b/test/scratch_classes/testextension.h index 88162cbaf..d15b20945 100644 --- a/test/scratch_classes/testextension.h +++ b/test/scratch_classes/testextension.h @@ -10,6 +10,7 @@ class TestExtension : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/test/scratchconfiguration/extension1.cpp b/test/scratchconfiguration/extension1.cpp index 2e8fa183b..b49f2b0e7 100644 --- a/test/scratchconfiguration/extension1.cpp +++ b/test/scratchconfiguration/extension1.cpp @@ -6,3 +6,8 @@ std::string Extension1::name() const { return "ext 1"; } + +Rgb Extension1::color() const +{ + return rgb(0, 0, 0); +} diff --git a/test/scratchconfiguration/extension1.h b/test/scratchconfiguration/extension1.h index f7f8009e6..ea08ea875 100644 --- a/test/scratchconfiguration/extension1.h +++ b/test/scratchconfiguration/extension1.h @@ -9,6 +9,7 @@ class Extension1 : public ExtensionBase { public: std::string name() const override; + Rgb color() const override; }; } // namespace libscratchcpp diff --git a/test/scratchconfiguration/extension2.cpp b/test/scratchconfiguration/extension2.cpp index 5c58c11ed..5efe05614 100644 --- a/test/scratchconfiguration/extension2.cpp +++ b/test/scratchconfiguration/extension2.cpp @@ -6,3 +6,8 @@ std::string Extension2::name() const { return "ext 2"; } + +Rgb Extension2::color() const +{ + return rgb(0, 0, 0); +} diff --git a/test/scratchconfiguration/extension2.h b/test/scratchconfiguration/extension2.h index 22caf7f1f..a6e03cd81 100644 --- a/test/scratchconfiguration/extension2.h +++ b/test/scratchconfiguration/extension2.h @@ -9,6 +9,7 @@ class Extension2 : public ExtensionBase { public: std::string name() const override; + Rgb color() const override; }; } // namespace libscratchcpp diff --git a/test/scratchconfiguration/extension3.cpp b/test/scratchconfiguration/extension3.cpp index fb28af9b1..021524f1f 100644 --- a/test/scratchconfiguration/extension3.cpp +++ b/test/scratchconfiguration/extension3.cpp @@ -6,3 +6,8 @@ std::string Extension3::name() const { return "ext 3"; } + +Rgb Extension3::color() const +{ + return rgb(0, 0, 0); +} diff --git a/test/scratchconfiguration/extension3.h b/test/scratchconfiguration/extension3.h index d434d7541..5ebe8c4de 100644 --- a/test/scratchconfiguration/extension3.h +++ b/test/scratchconfiguration/extension3.h @@ -9,6 +9,7 @@ class Extension3 : public ExtensionBase { public: std::string name() const override; + Rgb color() const override; }; } // namespace libscratchcpp From a053380920c880541248147078a82e705c01a284 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:41:01 +0100 Subject: [PATCH 18/38] audio: Fix a race condition in miniaudio https://github.com/mackron/miniaudio/issues/932 --- .../internal/thirdparty/miniaudio/miniaudio.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/audio/internal/thirdparty/miniaudio/miniaudio.c b/src/audio/internal/thirdparty/miniaudio/miniaudio.c index 651843b24..077c396c4 100644 --- a/src/audio/internal/thirdparty/miniaudio/miniaudio.c +++ b/src/audio/internal/thirdparty/miniaudio/miniaudio.c @@ -18616,15 +18616,20 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) { static int g_StreamCounter = 0; + static ma_mutex g_StreamCounterMutex; char actualStreamName[256]; - if (pStreamName != NULL) { - ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1); - } else { - ma_strcpy_s(actualStreamName, sizeof(actualStreamName), "miniaudio:"); - ma_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10); /* 8 = strlen("miniaudio:") */ + ma_mutex_lock(&g_StreamCounterMutex); + { + if (pStreamName != NULL) { + ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1); + } else { + ma_strcpy_s(actualStreamName, sizeof(actualStreamName), "miniaudio:"); + ma_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10); /* 8 = strlen("miniaudio:") */ + } + g_StreamCounter += 1; } - g_StreamCounter += 1; + ma_mutex_unlock(&g_StreamCounterMutex); return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap); } From 7c1e08a5c2f0bc192e66c432fb5800d716db1bb2 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 25 Jan 2025 22:47:57 +0100 Subject: [PATCH 19/38] Refactor CMake configuration --- CMakeLists.txt | 28 ++++++++++++++++++---------- build/zip.cmake | 9 +++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 build/zip.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ca8c1aad..bd773c01d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,26 +95,27 @@ else() endif() include(FetchContent) -set(ZIP_SRC thirdparty/zip/src) -set(UTFCPP_SRC thirdparty/utfcpp/source) -add_library(zip SHARED - ${ZIP_SRC}/zip.c - ${ZIP_SRC}/zip.h - ${ZIP_SRC}/miniz.h -) -target_include_directories(scratchcpp PUBLIC ${ZIP_SRC}) +# zip +include(build/zip.cmake) +target_link_libraries(scratchcpp PRIVATE zip) +# utfcpp +set(UTFCPP_SRC thirdparty/utfcpp/source) target_include_directories(scratchcpp PUBLIC ${UTFCPP_SRC}) + +# spimpl include_directories(thirdparty/spimpl) +# JSON FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) FetchContent_MakeAvailable(json) - target_link_libraries(scratchcpp PRIVATE nlohmann_json::nlohmann_json) -target_link_libraries(scratchcpp PRIVATE zip) + +# Audio target_link_libraries(scratchcpp PRIVATE scratchcpp-audio) +# Network if (LIBSCRATCHCPP_NETWORK_SUPPORT) FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git GIT_TAG 225b7454877805f089b3895260438e929bd6d123) # 09-22-2024 @@ -123,18 +124,25 @@ if (LIBSCRATCHCPP_NETWORK_SUPPORT) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_NETWORK_SUPPORT) endif() +# LLVM if (LIBSCRATCHCPP_USE_LLVM) include(build/HunterPackages.cmake) include(build/LLVM.cmake) target_link_libraries(scratchcpp PRIVATE LLVM) endif() +if(LIBSCRATCHCPP_PRINT_LLVM_IR) + target_compile_definitions(scratchcpp PRIVATE PRINT_LLVM_IR) +endif() + +# Macros target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_LIBRARY) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION="${PROJECT_VERSION}") target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION_MINOR=${PROJECT_VERSION_MINOR}) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION_PATCH=${PROJECT_VERSION_PATCH}) +# Unit tests if (LIBSCRATCHCPP_BUILD_UNIT_TESTS) enable_testing() add_subdirectory(test) diff --git a/build/zip.cmake b/build/zip.cmake new file mode 100644 index 000000000..cf970fb84 --- /dev/null +++ b/build/zip.cmake @@ -0,0 +1,9 @@ +set(ZIP_SRC ${PROJECT_SOURCE_DIR}/thirdparty/zip/src) + +add_library(zip SHARED + ${ZIP_SRC}/zip.c + ${ZIP_SRC}/zip.h + ${ZIP_SRC}/miniz.h +) + +target_include_directories(zip PUBLIC ${ZIP_SRC}) From 30ca4d39b77648e35cee24feeff828d0a8abaf8e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 25 Jan 2025 22:59:02 +0100 Subject: [PATCH 20/38] Configure CMake installation --- CMakeLists.txt | 1 + build/zip.cmake | 1 + 2 files changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index bd773c01d..810a5382f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ add_library(scratchcpp SHARED) add_subdirectory(src) include_directories(src) # TODO: Remove this line include_directories(include) +install(TARGETS scratchcpp DESTINATION lib) if (LIBSCRATCHCPP_COMPUTED_GOTO) target_compile_definitions(scratchcpp PRIVATE ENABLE_COMPUTED_GOTO) diff --git a/build/zip.cmake b/build/zip.cmake index cf970fb84..1165aba84 100644 --- a/build/zip.cmake +++ b/build/zip.cmake @@ -7,3 +7,4 @@ add_library(zip SHARED ) target_include_directories(zip PUBLIC ${ZIP_SRC}) +install(TARGETS zip DESTINATION lib) From a5c4dbe9acc177af46c1e70c5a5817f55faed58b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 28 Jan 2025 15:21:32 +0100 Subject: [PATCH 21/38] Link compiler test against zip library --- test/compiler/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/compiler/CMakeLists.txt b/test/compiler/CMakeLists.txt index 9dc335dfb..0b7d501a9 100644 --- a/test/compiler/CMakeLists.txt +++ b/test/compiler/CMakeLists.txt @@ -9,6 +9,7 @@ target_link_libraries( compiler_test GTest::gtest_main scratchcpp + zip nlohmann_json::nlohmann_json ) From 9f26849014024eed1bb6f38215eb1fea826c24ba Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Feb 2025 12:09:34 +0100 Subject: [PATCH 22/38] Set version to 0.13.1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 810a5382f..f15bf70a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(libscratchcpp VERSION 0.13.0 LANGUAGES C CXX) +project(libscratchcpp VERSION 0.13.1 LANGUAGES C CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) From 8ed5355643f3b92cd672cda7673dba1b4105bbb1 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 1 Feb 2025 12:09:52 +0100 Subject: [PATCH 23/38] fix #624: Use the C locale in number to string conversion Resolves: #624 --- src/scratch/value_functions.cpp | 7 +++++++ test/scratch_classes/value_test.cpp | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp index 7dced3575..fb06b5190 100644 --- a/src/scratch/value_functions.cpp +++ b/src/scratch/value_functions.cpp @@ -382,6 +382,10 @@ extern "C" return ret; } + // snprintf() is locale-dependent + std::string oldLocale = std::setlocale(LC_NUMERIC, nullptr); + std::setlocale(LC_NUMERIC, "C"); + const int maxlen = 26; // should be enough for any number char *buffer = (char *)malloc((maxlen + 1) * sizeof(char)); @@ -429,6 +433,9 @@ extern "C" } } + // Restore old locale + std::setlocale(LC_NUMERIC, oldLocale.c_str()); + return buffer; } diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp index 76462ebed..50eb360b4 100644 --- a/test/scratch_classes/value_test.cpp +++ b/test/scratch_classes/value_test.cpp @@ -1342,6 +1342,9 @@ TEST(ValueTest, ToBool) TEST(ValueTest, ToString) { + std::string oldLocale = std::setlocale(LC_NUMERIC, nullptr); + std::setlocale(LC_NUMERIC, "sk_SK.UTF-8"); + std::vector cStrings; Value v = 2147483647; cStrings.push_back(value_toCString(&v.data())); @@ -1614,6 +1617,8 @@ TEST(ValueTest, ToString) for (char *s : cStrings) free(s); + + std::setlocale(LC_NUMERIC, oldLocale.c_str()); } TEST(ValueTest, ToRgba) @@ -2921,6 +2926,9 @@ TEST(ValueTest, DoubleIsInt) TEST(ValueTest, DoubleToCString) { + std::string oldLocale = std::setlocale(LC_NUMERIC, nullptr); + std::setlocale(LC_NUMERIC, "sk_SK.UTF-8"); + char *ret; ret = value_doubleToCString(0.0); ASSERT_EQ(strcmp(ret, "0"), 0); @@ -3021,6 +3029,8 @@ TEST(ValueTest, DoubleToCString) ret = value_doubleToCString(std::numeric_limits::quiet_NaN()); ASSERT_EQ(strcmp(ret, "NaN"), 0); free(ret); + + std::setlocale(LC_NUMERIC, oldLocale.c_str()); } TEST(ValueTest, BoolToCString) From 7c6bf999340eb4422bd0193a7e30363e0310c000 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 8 Feb 2025 10:42:43 +0100 Subject: [PATCH 24/38] Set version to 0.13.2 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f15bf70a0..8eff2a4a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(libscratchcpp VERSION 0.13.1 LANGUAGES C CXX) +project(libscratchcpp VERSION 0.13.2 LANGUAGES C CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) From cc8d45c9fa1618c07b9955593f6007f333190aef Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 8 Feb 2025 10:43:12 +0100 Subject: [PATCH 25/38] Add missing clocale include to value_functions.cpp --- src/scratch/value_functions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp index fb06b5190..3d0f3c8a1 100644 --- a/src/scratch/value_functions.cpp +++ b/src/scratch/value_functions.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include "value_functions_p.h" From 962abe22c6ffd9e6b26c9e5e696d2b75f22a76e9 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 23 Feb 2025 10:59:20 +0100 Subject: [PATCH 26/38] Revert "audio: Fix a race condition in miniaudio" This reverts commit e60f7c071fdfb035eb9525495c15fed77db31945. --- .../internal/thirdparty/miniaudio/miniaudio.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/audio/internal/thirdparty/miniaudio/miniaudio.c b/src/audio/internal/thirdparty/miniaudio/miniaudio.c index 077c396c4..651843b24 100644 --- a/src/audio/internal/thirdparty/miniaudio/miniaudio.c +++ b/src/audio/internal/thirdparty/miniaudio/miniaudio.c @@ -18616,20 +18616,15 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) { static int g_StreamCounter = 0; - static ma_mutex g_StreamCounterMutex; char actualStreamName[256]; - ma_mutex_lock(&g_StreamCounterMutex); - { - if (pStreamName != NULL) { - ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1); - } else { - ma_strcpy_s(actualStreamName, sizeof(actualStreamName), "miniaudio:"); - ma_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10); /* 8 = strlen("miniaudio:") */ - } - g_StreamCounter += 1; + if (pStreamName != NULL) { + ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1); + } else { + ma_strcpy_s(actualStreamName, sizeof(actualStreamName), "miniaudio:"); + ma_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10); /* 8 = strlen("miniaudio:") */ } - ma_mutex_unlock(&g_StreamCounterMutex); + g_StreamCounter += 1; return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap); } From fdff546c43a73c1f2871b3dfe786bfa6f5bc7713 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 23 Feb 2025 11:10:19 +0100 Subject: [PATCH 27/38] Build shared library of miniaudio https://github.com/mackron/miniaudio/issues/932 --- src/audio/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 536c266bc..f3319207d 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -4,11 +4,13 @@ add_library(scratchcpp-audio STATIC) set(MINIAUDIO_SRC internal/thirdparty/miniaudio) if (LIBSCRATCHCPP_AUDIO_SUPPORT) - add_library(miniaudio STATIC + add_library(miniaudio SHARED ${MINIAUDIO_SRC}/miniaudio.c ${MINIAUDIO_SRC}/miniaudio.h ) target_include_directories(scratchcpp-audio PUBLIC ${CMAKE_CURRENT_LIST_DIR}/${MINIAUDIO_SRC}) + install(TARGETS miniaudio DESTINATION lib) + target_link_libraries(scratchcpp-audio PRIVATE miniaudio) target_compile_definitions(scratchcpp-audio PUBLIC LIBSCRATCHCPP_AUDIO_SUPPORT) endif() From 0fbb679c012f5c62a7fbc5d388e20601ba1cb2ed Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:46:35 +0100 Subject: [PATCH 28/38] Deallocate asset data --- include/scratchcpp/asset.h | 2 +- src/project_p.cpp | 4 +++- src/scratch/asset.cpp | 14 +++++++++++++- src/scratch/asset_p.h | 2 +- test/assets/asset_test.cpp | 14 +++++++++++++- test/assets/sound_test.cpp | 22 +++++++++++++--------- 6 files changed, 44 insertions(+), 14 deletions(-) diff --git a/include/scratchcpp/asset.h b/include/scratchcpp/asset.h index c5c10d88d..03be48043 100644 --- a/include/scratchcpp/asset.h +++ b/include/scratchcpp/asset.h @@ -19,7 +19,7 @@ class LIBSCRATCHCPP_EXPORT Asset : public Entity Asset(const std::string &name, const std::string &id, const std::string &format); Asset(const Asset &) = delete; - virtual ~Asset() { } + virtual ~Asset(); void setId(const std::string &id); diff --git a/src/project_p.cpp b/src/project_p.cpp index c8c40e204..5dd383311 100644 --- a/src/project_p.cpp +++ b/src/project_p.cpp @@ -121,7 +121,9 @@ bool ProjectPrivate::tryLoad(IProjectReader *reader) // Load asset data for (size_t i = 0; i < assets.size(); i++) { const std::string &data = assetData[i]; - assets[assetNames[i]]->setData(data.size(), static_cast(const_cast(data.c_str()))); + char *ptr = (char *)malloc(data.size() * sizeof(char)); + strncpy(ptr, data.data(), data.size()); + assets[assetNames[i]]->setData(data.size(), ptr); } } else { diff --git a/src/scratch/asset.cpp b/src/scratch/asset.cpp index 79ffd4b1c..1731a828f 100644 --- a/src/scratch/asset.cpp +++ b/src/scratch/asset.cpp @@ -14,6 +14,15 @@ Asset::Asset(const std::string &name, const std::string &id, const std::string & impl->updateFileName(id); } +/*! Destroys Asset. */ +Asset::~Asset() +{ + if (impl->data) { + free(impl->data); + impl->data = nullptr; + } +} + /*! Sets the ID (MD5 hash) of the asset file. */ void Asset::setId(const std::string &id) { @@ -51,9 +60,12 @@ unsigned int Asset::dataSize() const return impl->dataSize; } -/*! Sets the asset data. */ +/*! Sets the asset data (will be deallocated when the object is destroyed). */ void Asset::setData(unsigned int size, void *data) { + if (impl->data) + free(impl->data); + impl->dataSize = size; impl->data = data; processData(size, data); diff --git a/src/scratch/asset_p.h b/src/scratch/asset_p.h index d004fb2f4..acfa4e863 100644 --- a/src/scratch/asset_p.h +++ b/src/scratch/asset_p.h @@ -19,7 +19,7 @@ struct AssetPrivate std::string name; std::string dataFormat; std::string fileName; - const void *data = nullptr; + void *data = nullptr; unsigned int dataSize = 0; Target *target = nullptr; }; diff --git a/test/assets/asset_test.cpp b/test/assets/asset_test.cpp index 5bd5920be..86dcfd84c 100644 --- a/test/assets/asset_test.cpp +++ b/test/assets/asset_test.cpp @@ -29,12 +29,24 @@ TEST(AssetTest, Data) TestAsset asset; ASSERT_EQ(asset.data(), nullptr); - static char data[5] = "abcd"; + char *data = (char *)malloc(4 * sizeof(char)); + strncpy(data, "abcd", 4); + asset.setData(5, data); ASSERT_EQ(asset.data(), data); ASSERT_EQ(asset.size, 5); ASSERT_EQ(asset.processedData, data); ASSERT_EQ(asset.callCount, 1); + + // Should deallocate in setData() + data = (char *)malloc(11 * sizeof(char)); + strncpy(data, "Hello world!", 11); + + asset.setData(5, data); + ASSERT_EQ(asset.data(), data); + ASSERT_EQ(asset.size, 5); + ASSERT_EQ(asset.processedData, data); + ASSERT_EQ(asset.callCount, 2); } TEST(AssetTest, Target) diff --git a/test/assets/sound_test.cpp b/test/assets/sound_test.cpp index d3b81bf2a..b83d5a999 100644 --- a/test/assets/sound_test.cpp +++ b/test/assets/sound_test.cpp @@ -59,16 +59,20 @@ TEST_F(SoundTest, ProcessData) Sound sound("sound1", "a", "wav"); sound.setRate(44100); - const char *data = "abc"; - void *dataPtr = const_cast(static_cast(data)); + char *data = (char *)malloc(4 * sizeof(char)); + strncpy(data, "abcd", 4); EXPECT_CALL(*m_player, isLoaded()).WillOnce(Return(false)); - EXPECT_CALL(*m_player, load(3, dataPtr, 44100)).WillOnce(Return(true)); - sound.setData(3, dataPtr); + EXPECT_CALL(*m_player, load(3, data, 44100)).WillOnce(Return(true)); + sound.setData(3, data); + + // Should deallocate in setData() + data = (char *)malloc(11 * sizeof(char)); + strncpy(data, "Hello world!", 11); EXPECT_CALL(*m_player, isLoaded()).WillOnce(Return(true)); EXPECT_CALL(*m_player, load).Times(0); - sound.setData(3, dataPtr); + sound.setData(3, data); } TEST_F(SoundTest, SetVolume) @@ -151,12 +155,12 @@ TEST_F(SoundTest, Clone) sound->setRate(44100); sound->setSampleCount(10000); - const char *data = "abc"; - void *dataPtr = const_cast(static_cast(data)); + char *data = (char *)malloc(4 * sizeof(char)); + strncpy(data, "abcd", 4); EXPECT_CALL(*m_player, isLoaded()).WillOnce(Return(false)); - EXPECT_CALL(*m_player, load(3, dataPtr, 44100)).WillOnce(Return(true)); - sound->setData(3, dataPtr); + EXPECT_CALL(*m_player, load(3, data, 44100)).WillOnce(Return(true)); + sound->setData(3, data); auto clonePlayer = std::make_shared(); EXPECT_CALL(m_playerFactory, createAudioPlayer()).WillOnce(Return(clonePlayer)); From b7f02623a94e4cc091818678be64fb7c014cc7e1 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 24 Feb 2025 16:04:00 +0100 Subject: [PATCH 29/38] ZipReader: Fix output when m_zip is null --- src/internal/zipreader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal/zipreader.cpp b/src/internal/zipreader.cpp index fd8a48db6..f4ee39a4e 100644 --- a/src/internal/zipreader.cpp +++ b/src/internal/zipreader.cpp @@ -36,7 +36,7 @@ void ZipReader::close() size_t ZipReader::readFile(const std::string &fileName, void **buf) { if (!m_zip) { - buf = nullptr; + *buf = nullptr; return 0; } From eba75086a1d96b9855ad868bd210721769a3b8b9 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 24 Feb 2025 16:04:30 +0100 Subject: [PATCH 30/38] Do not copy project.json to string when loading projects --- src/internal/scratch3reader.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/internal/scratch3reader.cpp b/src/internal/scratch3reader.cpp index 7e9033266..1d7168dce 100644 --- a/src/internal/scratch3reader.cpp +++ b/src/internal/scratch3reader.cpp @@ -562,9 +562,11 @@ void Scratch3Reader::read() if (m_zipReader->open()) { // Parse the JSON try { - std::string jsonStr; - m_zipReader->readFileToString("project.json", jsonStr); - m_json = json::parse(jsonStr); + char *buf; + const size_t size = m_zipReader->readFile("project.json", (void **)&buf); + assert(buf); + m_json = json::parse(buf, buf + size); + free(buf); } catch (std::exception &e) { printErr("invalid JSON file", e.what()); } From 52e165e94864d953bb11e3318048b9bf2d85d171 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 24 Feb 2025 20:15:20 +0100 Subject: [PATCH 31/38] Do not free cloned asset data --- include/scratchcpp/asset.h | 1 + include/scratchcpp/sound.h | 1 + src/scratch/asset.cpp | 3 ++- src/scratch/asset_p.h | 1 + src/scratch/sound.cpp | 5 +++++ 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/scratchcpp/asset.h b/include/scratchcpp/asset.h index 03be48043..9870c03a4 100644 --- a/include/scratchcpp/asset.h +++ b/include/scratchcpp/asset.h @@ -38,6 +38,7 @@ class LIBSCRATCHCPP_EXPORT Asset : public Entity protected: virtual void processData(unsigned int size, void *data) { } + virtual bool isClone() const { return false; } private: spimpl::unique_impl_ptr impl; diff --git a/include/scratchcpp/sound.h b/include/scratchcpp/sound.h index b4906b1ee..2c95d32d2 100644 --- a/include/scratchcpp/sound.h +++ b/include/scratchcpp/sound.h @@ -43,6 +43,7 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset protected: void processData(unsigned int size, void *data) override; + virtual bool isClone() const override; private: void stopCloneSounds(); diff --git a/src/scratch/asset.cpp b/src/scratch/asset.cpp index 1731a828f..d17b68bdf 100644 --- a/src/scratch/asset.cpp +++ b/src/scratch/asset.cpp @@ -17,7 +17,7 @@ Asset::Asset(const std::string &name, const std::string &id, const std::string & /*! Destroys Asset. */ Asset::~Asset() { - if (impl->data) { + if (impl->data && !impl->dataCloned) { free(impl->data); impl->data = nullptr; } @@ -68,6 +68,7 @@ void Asset::setData(unsigned int size, void *data) impl->dataSize = size; impl->data = data; + impl->dataCloned = isClone(); processData(size, data); } diff --git a/src/scratch/asset_p.h b/src/scratch/asset_p.h index acfa4e863..604eeb1e6 100644 --- a/src/scratch/asset_p.h +++ b/src/scratch/asset_p.h @@ -21,6 +21,7 @@ struct AssetPrivate std::string fileName; void *data = nullptr; unsigned int dataSize = 0; + bool dataCloned = false; Target *target = nullptr; }; diff --git a/src/scratch/sound.cpp b/src/scratch/sound.cpp index 64fcf35a0..cd3db865e 100644 --- a/src/scratch/sound.cpp +++ b/src/scratch/sound.cpp @@ -116,6 +116,11 @@ void Sound::processData(unsigned int size, void *data) std::cerr << "Failed to load sound " << name() << std::endl; } +bool Sound::isClone() const +{ + return impl->cloneRoot; +} + void Sound::stopCloneSounds() { Target *target = this->target(); From a0cd2ad5ffecb4c60d171e723b3747c489d62721 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 2 Mar 2025 11:23:32 +0100 Subject: [PATCH 32/38] Fix incorrect test case name in Drawable test --- test/scratch_classes/drawable_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scratch_classes/drawable_test.cpp b/test/scratch_classes/drawable_test.cpp index 407af05d1..86c227ef1 100644 --- a/test/scratch_classes/drawable_test.cpp +++ b/test/scratch_classes/drawable_test.cpp @@ -40,7 +40,7 @@ TEST(DrawableTest, LayerOrder) ASSERT_EQ(drawable.layerOrder(), 0); } -TEST(TargetTest, Engine) +TEST(DrawableTest, Engine) { Drawable drawable; ASSERT_EQ(drawable.engine(), nullptr); From a8c2b35fab40a2c2063c2a3f62423a7cba912571 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 2 Mar 2025 11:58:55 +0100 Subject: [PATCH 33/38] Engine: Remove duplicate event loop cleanup --- src/engine/internal/engine.cpp | 16 +++++----------- src/engine/internal/engine.h | 1 - 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index c0bd3173d..5f0da4bd7 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -636,7 +636,6 @@ void Engine::run() { start(); eventLoop(true); - finalize(); } void Engine::runEventLoop() @@ -777,7 +776,11 @@ void Engine::eventLoop(bool untilProjectStops) m_clock->sleep(sleepTime); } - finalize(); + m_eventLoopMutex.lock(); + m_threads.clear(); + m_running = false; + m_redrawRequested = false; + m_eventLoopMutex.unlock(); } bool Engine::isRunning() const @@ -1962,15 +1965,6 @@ void Engine::compileMonitor(std::shared_ptr monitor) #endif // USE_LLVM } -void Engine::finalize() -{ - m_eventLoopMutex.lock(); - m_threads.clear(); - m_running = false; - m_redrawRequested = false; - m_eventLoopMutex.unlock(); -} - void Engine::deleteClones() { m_eventLoopMutex.lock(); diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h index ed4b562dd..33cd172c6 100644 --- a/src/engine/internal/engine.h +++ b/src/engine/internal/engine.h @@ -206,7 +206,6 @@ class Engine : public IEngine std::vector> stepThreads(); void stepThread(std::shared_ptr thread); void eventLoop(bool untilProjectStops = false); - void finalize(); void deleteClones(); void removeExecutableClones(); void addVarOrListMonitor(std::shared_ptr monitor, Target *target); From ca5524344786afe5d703575ae02f202f375d1dff Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 2 Mar 2025 12:06:36 +0100 Subject: [PATCH 34/38] Engine: Ignore frame activity after project stops in event loop --- src/engine/internal/engine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 5f0da4bd7..59cc29e9b 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -779,6 +779,7 @@ void Engine::eventLoop(bool untilProjectStops) m_eventLoopMutex.lock(); m_threads.clear(); m_running = false; + m_frameActivity = false; m_redrawRequested = false; m_eventLoopMutex.unlock(); } From 3daacaa12e1b2906f5a7b48bf89df310167fcfd7 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 2 Mar 2025 12:07:00 +0100 Subject: [PATCH 35/38] Initialize variables in stop all bypass test --- test/stop_all_bypass.sb3 | Bin 1608 -> 1620 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/stop_all_bypass.sb3 b/test/stop_all_bypass.sb3 index b2c826897e2f2c5466081224c97e4b9c621388e8..6d42e0d3683fa2821b84f38fc2273c0972f2b5e2 100644 GIT binary patch delta 1345 zcmV-H1-|;o4AcyNP)h>@3IG5A2mr!XVp<9*tX;DN004Fl000aC003}uZ)#;@bS`Rh zZ*IL;Yfs}i6#XlaMP=6wxyK0ETrFCoxUp$aV@Hru*CX+DTut z6k3q>OXS#&bMCq4T-%RGVjdAGQ0M9qQ8AD>BB&#IK}?i?;S}1a>@(2>bBad^fw_<9 z@4KV%r}}q#_3n85@q_oU*ZpGsoKB7TBv=noWZE|-@{iNYDBiF2zeb;;_sziZ#;F>< zQ^PY8B~+;YKSFcF84i40z9V`5^`DFJ``q~`X1&wzH+z;D0Uk+&bO6s^@4n4Q=fmEw z_^0+oJ@kKnIXakD!!A;bCCne|g{XS4+-HG`hfGK&Z|`H0g$a)Qg!*F=zZkT8s) znPU!fP$w&r#6&d9i#fuOk|e0wBE|u`N1YHS0_c(~KY_KAIeibPQ};{+n3{E%jn_Z~ zSmIkj7xyLM1x`f{I-8S?DfwSg*Zu&sXl(0G83hR z#FDaqOuG(8XQlBMy+CP9lK>#V%HZf2i;6@2J(|HUf(;&95YLHE=w#JtWILy=WMdiL z^NHBSoV8XH;$vxA5uCI77$evGq!h|G(|Xu1hqxT$K?P2~JP^)A|7%kdkv zyjc=EZ8V;}s^wH^Ievvgq8$~I?XHmQ^$Lk!tB|*BE3*Z$dfBmh=o3aSsLT&(aJ`!4 zwd{7?Yqgz*U2B&W#sgsYG?0)A*Ricy%dXkB+jiScuhoDI5gNr1m{Yf{R^4v7ZnM#U zbYUQ+8BOUo(Aff2skUKEg&}-Sb7`oSBzaB(Ln+n3p(Nb!q5jh>GSpsTK-)A%g)~@} zOyS`ZODQ8=k0E+4hNkB&W3cTdX#KJ)(3EjvNJzH^Q$dz7F>+2OlobN{3CzY^3WG}P zI=jyFQrBoS&9M+fTqFI#JU&CmVd<@Z$S=53${`c7P#c-{gl?C|-0ECA!u~kmELYoz z(%&)TDORAxL{XvsA~i97;5rSb9@veV69!>8a$VQ16Px%A5_olW%t?brbpU!{u2nA1 zS-G_y0#3sJ02=G|kG+3_B0PHrW)rN)l$zDvJ7I|q%2B*0cuIJm2ZZY=B}sFC2DyUf zga(8SInIQ(jSneo%)8-0FBJiLXIF_DvO!LWAD@8#3Vi`kj#Gb5SwIBFQ!d7Bh z3Ms5zvjhMDc9XvaEd;_=Vp@~%1sVd~1e5IqCKgaj1qJ{B000620sxEv006QD00000 DuPAW6 delta 1310 zcmV+(1>ySC49E@3IG5A2mly;c36zV44tP0003DI000aC003}uZ)#;@bS`Rh zZ*IL;VQ=C%5dABWS1WCGIW7qS0{dZ0yHty=Emfh_-L-7bBu?Uz#F1@i*)I3nZ|npL zz0yJ}-IvI*C*wEIZ)ThaBr%VO6sUFefT-w891+x#d_qirl;IS{sF*X+6myD42?5&& z^!IINyzl;`S09eXUp{&Fd+p20uj$NK7=b@Tk!cT2edd z7MZo-+(Zc#YX1iqjyS`CkBfJ-XYNlgho4XV;X&)N6P|}>vs1v65+MMQ4Upv7_kYgE zA9MSQn0HTqeh&94W&{?J2&tC;`tV~;TA%j5;a{5PZs>pR9L&mP2dV86=8yGOR6SVk zvp_{mrX-U$cQMJr1V?^C{V@r^Uq;y2$d{92H(ko;Cs|^Jre<@x~t$k17gX@mrq?MPb*p?5au_D+>jUPiwQ&(%g9 z1j*90U~jBcaJv?a&oaqbawEXW5`6)9N%{zFLf;?s&Q^fqBtfS7_(hQAL^_dJQkE${ zkaDMgMfNRc0e4K303d*8NUIo&l3M*en!^@kI|YTS3JSJYkh3YLc2`ieyMp3(DrhtM zoif$4Ruz;e1%=Nlh?AUPxk*E0jrKwJq|-0R27%UqP!shp4*G-k;qNf*fI8659H(or z0xa>3ALA?{iy}RmZ9*5Wp}TA!Uvvs|u8Hh_C&vxT=iTjK?Gq74aP{XZJ;w}w7ig^u40 z{XGJhFYAJKJ5~>U!{`QOnT0Cic-4y6u$r#dXxcTa+AP|V2gwjO4&rfJOOLWr7);}tXyZ;nRa!JMzb6XLBuuEA1vZCbQ~4kC;bUm zQaNNoPE?oGC0&n;i*R}K9btbQaF(mPhZ5fr<0)34#Z*zDc9EJGKXB}t?FLq@YKK7> zjvU9aTw)QwMgq@OdQ5{_nPjinRto2w6@C8@a1#Cp(CAuEXa596c-9<>gkY0@OsQF2 z45lp6LD`D;1kVWX@qln0r6g(2;H+Rcr2%0Bjx(Y2#)l)UEvDhhFBJj$+My0=!1_5M zetZlCc%pw1R2@@0r!1qA@$v^kk@0HktB;C=CLx|Asy43Y_=+W9!|9VWg#x3pG3Qj0 z;+9kt)hSLU#bK5*ToIwAm0QzjO0B6V1=W8kMSB+sM9oiVrVYzCd|z@%#a9XnC9<@i zn`NZWR*H8=cl*BRs?K}EgHwegCz$}Dm6}S4)NMqNbgHC;suinZsh>i)WFV{J=ob)% zuD_6D{{m1;0|W{H000O87=3nFlj{W24H$iPSd7CAou>o<09ljE1uX;^eRf!r`~?~U U(FBwE1SXR{1`r0O1poj50Niz4R{#J2 From f03eb79b0c6ff20664d340cf04c05f24203d69ac Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 2 Mar 2025 12:07:31 +0100 Subject: [PATCH 36/38] Engine: Call onInit() for default extensions --- src/engine/internal/engine.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 59cc29e9b..4fca36c1a 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -1486,8 +1486,10 @@ void Engine::setExtensions(const std::vector &newExtensions) // Register blocks of default extensions const auto &defaultExtensions = Blocks::extensions(); - for (auto ext : defaultExtensions) + for (auto ext : defaultExtensions) { ext->registerBlocks(this); + ext->onInit(this); + } // Register blocks of custom extensions for (auto ext : m_extensions) { From 4484d245aa23d149ef8403534387a1886d6eef3f Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 5 Mar 2025 11:01:53 +0100 Subject: [PATCH 37/38] Engine: Fix use after free when using removed clone threads All threads are copied to m_threadsToStop, including clone threads that are removed later which can lead to use after free when the threadAboutToStop signal is emitted for these threads. --- src/engine/internal/engine.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 4fca36c1a..35a5d54bc 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -362,17 +362,13 @@ void Engine::stop() // https://github.com/scratchfoundation/scratch-vm/blob/f1aa92fad79af17d9dd1c41eeeadca099339a9f1/src/engine/runtime.js#L2057-L2081 if (m_activeThread) { stopThread(m_activeThread.get()); - // NOTE: The project should continue running even after "stop all" is called and the remaining threads should be stepped once. - // The remaining threads can even start new threads which will ignore the "stop all" call and will "restart" the project. - // This is probably a bug in the Scratch VM, but let's keep it here to keep it compatible. - m_threadsToStop = m_threads; // Remove threads owned by clones because clones are going to be deleted (#547) m_threads.erase( std::remove_if( m_threads.begin(), m_threads.end(), - [](std::shared_ptr thread) { + [this](std::shared_ptr thread) { assert(thread); Target *target = thread->target(); assert(target); @@ -380,13 +376,20 @@ void Engine::stop() if (!target->isStage()) { Sprite *sprite = static_cast(target); - if (sprite->isClone()) + if (sprite->isClone()) { + m_threadAboutToStop(thread.get()); return true; + } } return false; }), m_threads.end()); + + // NOTE: The project should continue running even after "stop all" is called and the remaining threads should be stepped once. + // The remaining threads can even start new threads which will ignore the "stop all" call and will "restart" the project. + // This is probably a bug in the Scratch VM, but let's keep it here to keep it compatible. + m_threadsToStop = m_threads; } else { // If there isn't any active thread, it means the project was stopped from the outside // In this case all threads should be removed and the project should be considered stopped From 064d1ca05d20e80a1b0cb4b93e6b72168efd51f4 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 25 Mar 2025 22:37:42 +0100 Subject: [PATCH 38/38] fix #637: Ignore invisible text bubbles when changing layer Resolves: #637 --- src/engine/internal/engine.cpp | 26 ++++++++++++++++------- test/engine/engine_test.cpp | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 35a5d54bc..e5d8f0cc0 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -1324,16 +1324,26 @@ void Engine::moveDrawableForwardLayers(Drawable *drawable, int layers) if (it == m_sortedDrawables.end()) return; - auto target = it + layers; + auto target = it; + int layersAbs = std::abs(layers); - if (target <= m_sortedDrawables.begin()) { - moveDrawableToBack(drawable); - return; - } + for (int i = 0; i < layersAbs; i++) { + if (target <= m_sortedDrawables.begin()) { + moveDrawableToBack(drawable); + return; + } - if (target >= m_sortedDrawables.end()) { - moveDrawableToFront(drawable); - return; + if (target >= m_sortedDrawables.end()) { + moveDrawableToFront(drawable); + return; + } + + Drawable *currentDrawable; + + do { + currentDrawable = *target; + target += layers / layersAbs; + } while (currentDrawable->isTextBubble() && static_cast(currentDrawable)->text().empty()); } if (layers > 0) diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index cb0c17b68..944f5e05c 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -1362,6 +1362,11 @@ TEST(EngineTest, MoveDrawableForwardLayers) std::vector sprites; createTargets(&engine, sprites); + const auto &targets = engine.targets(); + + for (auto target : targets) + target->bubble()->setText("test"); + engine.moveDrawableForwardLayers(sprites[4], 2); ASSERT_EQ(sprites[0]->layerOrder(), 1); ASSERT_EQ(sprites[1]->layerOrder(), 5); @@ -1421,6 +1426,20 @@ TEST(EngineTest, MoveDrawableForwardLayers) ASSERT_EQ(sprites[2]->bubble()->layerOrder(), 8); ASSERT_EQ(sprites[3]->bubble()->layerOrder(), 9); ASSERT_EQ(sprites[4]->bubble()->layerOrder(), 11); + + sprites[1]->bubble()->setText(""); + sprites[3]->bubble()->setText(""); + engine.moveDrawableForwardLayers(sprites[2], 8); + ASSERT_EQ(sprites[0]->layerOrder(), 1); + ASSERT_EQ(sprites[1]->layerOrder(), 4); + ASSERT_EQ(sprites[2]->layerOrder(), 11); + ASSERT_EQ(sprites[3]->layerOrder(), 3); + ASSERT_EQ(sprites[4]->layerOrder(), 2); + ASSERT_EQ(sprites[0]->bubble()->layerOrder(), 9); + ASSERT_EQ(sprites[1]->bubble()->layerOrder(), 6); + ASSERT_EQ(sprites[2]->bubble()->layerOrder(), 7); + ASSERT_EQ(sprites[3]->bubble()->layerOrder(), 8); + ASSERT_EQ(sprites[4]->bubble()->layerOrder(), 10); } TEST(EngineTest, MoveDrawableBackwardLayers) @@ -1429,6 +1448,11 @@ TEST(EngineTest, MoveDrawableBackwardLayers) std::vector sprites; createTargets(&engine, sprites); + const auto &targets = engine.targets(); + + for (auto target : targets) + target->bubble()->setText("test"); + engine.moveDrawableBackwardLayers(sprites[4], -2); ASSERT_EQ(sprites[0]->layerOrder(), 1); ASSERT_EQ(sprites[1]->layerOrder(), 5); @@ -1488,6 +1512,20 @@ TEST(EngineTest, MoveDrawableBackwardLayers) ASSERT_EQ(sprites[2]->bubble()->layerOrder(), 8); ASSERT_EQ(sprites[3]->bubble()->layerOrder(), 9); ASSERT_EQ(sprites[4]->bubble()->layerOrder(), 11); + + sprites[1]->bubble()->setText(""); + sprites[3]->bubble()->setText(""); + engine.moveDrawableBackwardLayers(sprites[2], -8); + ASSERT_EQ(sprites[0]->layerOrder(), 1); + ASSERT_EQ(sprites[1]->layerOrder(), 4); + ASSERT_EQ(sprites[2]->layerOrder(), 11); + ASSERT_EQ(sprites[3]->layerOrder(), 3); + ASSERT_EQ(sprites[4]->layerOrder(), 2); + ASSERT_EQ(sprites[0]->bubble()->layerOrder(), 9); + ASSERT_EQ(sprites[1]->bubble()->layerOrder(), 6); + ASSERT_EQ(sprites[2]->bubble()->layerOrder(), 7); + ASSERT_EQ(sprites[3]->bubble()->layerOrder(), 8); + ASSERT_EQ(sprites[4]->bubble()->layerOrder(), 10); } TEST(EngineTest, MoveDrawableBehindOther)