From 2b5a14a57a0fb8ed4d7985b6709b4086f994feba Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 29 Mar 2026 16:13:54 +0200 Subject: [PATCH 1/8] Add stop thread LLVM instruction --- src/engine/internal/icodebuilder.h | 1 + .../internal/llvm/instructions/control.cpp | 15 ++ .../internal/llvm/instructions/control.h | 1 + .../internal/llvm/instructions/procedures.cpp | 8 ++ src/engine/internal/llvm/llvmbuildutils.cpp | 41 +++++- src/engine/internal/llvm/llvmbuildutils.h | 4 + src/engine/internal/llvm/llvmcodebuilder.cpp | 6 + src/engine/internal/llvm/llvmcodebuilder.h | 1 + src/engine/internal/llvm/llvmcoroutine.cpp | 45 +++--- src/engine/internal/llvm/llvmcoroutine.h | 3 + .../internal/llvm/llvmexecutablecode.cpp | 9 +- src/engine/internal/llvm/llvminstruction.h | 1 + test/llvm/llvmcodebuilder_test.cpp | 134 ++++++++++++++++++ test/mocks/codebuildermock.h | 1 + 14 files changed, 248 insertions(+), 22 deletions(-) diff --git a/src/engine/internal/icodebuilder.h b/src/engine/internal/icodebuilder.h index 1c793c1d1..fa50ae181 100644 --- a/src/engine/internal/icodebuilder.h +++ b/src/engine/internal/icodebuilder.h @@ -99,6 +99,7 @@ class ICodeBuilder virtual void yield() = 0; virtual void createStop() = 0; + virtual void createThreadStop() = 0; virtual void createStopWithoutSync() = 0; virtual void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) = 0; diff --git a/src/engine/internal/llvm/instructions/control.cpp b/src/engine/internal/llvm/instructions/control.cpp index 5afc458c8..7f71376b5 100644 --- a/src/engine/internal/llvm/instructions/control.cpp +++ b/src/engine/internal/llvm/instructions/control.cpp @@ -60,6 +60,10 @@ ProcessResult Control::process(LLVMInstruction *ins) ret.next = buildStop(ins); break; + case LLVMInstruction::Type::ThreadStop: + ret.next = buildThreadStop(ins); + break; + case LLVMInstruction::Type::StopWithoutSync: ret.next = buildStopWithoutSync(ins); break; @@ -340,6 +344,17 @@ LLVMInstruction *Control::buildStop(LLVMInstruction *ins) return buildStopWithoutSync(ins); } +LLVMInstruction *Control::buildThreadStop(LLVMInstruction *ins) +{ + m_utils.syncVariables(); + m_builder.CreateBr(m_utils.endThreadBranch()); + + llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_utils.llvmCtx(), "", m_utils.function()); + m_builder.SetInsertPoint(nextBranch); + + return ins->next; +} + LLVMInstruction *Control::buildStopWithoutSync(LLVMInstruction *ins) { m_builder.CreateBr(m_utils.endBranch()); diff --git a/src/engine/internal/llvm/instructions/control.h b/src/engine/internal/llvm/instructions/control.h index bb294f9b5..aefddd8eb 100644 --- a/src/engine/internal/llvm/instructions/control.h +++ b/src/engine/internal/llvm/instructions/control.h @@ -27,6 +27,7 @@ class Control : public InstructionGroup LLVMInstruction *buildBeginLoopCondition(LLVMInstruction *ins); LLVMInstruction *buildEndLoop(LLVMInstruction *ins); LLVMInstruction *buildStop(LLVMInstruction *ins); + LLVMInstruction *buildThreadStop(LLVMInstruction *ins); LLVMInstruction *buildStopWithoutSync(LLVMInstruction *ins); }; diff --git a/src/engine/internal/llvm/instructions/procedures.cpp b/src/engine/internal/llvm/instructions/procedures.cpp index 0b9c9b645..682aefcd5 100644 --- a/src/engine/internal/llvm/instructions/procedures.cpp +++ b/src/engine/internal/llvm/instructions/procedures.cpp @@ -66,9 +66,17 @@ LLVMInstruction *Procedures::buildCallProcedure(LLVMInstruction *ins) args.push_back(m_utils.createValue(arg.second)); } + // Call the procedure llvm::Value *handle = m_builder.CreateCall(m_utils.functions().resolveFunction(name, type), args); + // Check for end thread sentinel value + llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(llvmCtx, "", function); + llvm::Value *endThread = m_builder.CreateICmpEQ(handle, m_utils.threadEndSentinel()); + m_builder.CreateCondBr(endThread, m_utils.endThreadBranch(), nextBranch); + m_builder.SetInsertPoint(nextBranch); + if (!m_utils.warp() && !ins->procedurePrototype->warp()) { + // Handle suspend llvm::BasicBlock *suspendBranch = llvm::BasicBlock::Create(llvmCtx, "", function); llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(llvmCtx, "", function); m_builder.CreateCondBr(m_builder.CreateIsNull(handle), nextBranch, suspendBranch); diff --git a/src/engine/internal/llvm/llvmbuildutils.cpp b/src/engine/internal/llvm/llvmbuildutils.cpp index 717626a4d..206a16e96 100644 --- a/src/engine/internal/llvm/llvmbuildutils.cpp +++ b/src/engine/internal/llvm/llvmbuildutils.cpp @@ -161,8 +161,9 @@ void LLVMBuildUtils::init(llvm::Function *function, BlockPrototype *procedurePro reloadVariables(); reloadLists(); - // Create end branch + // Create end branches m_endBranch = llvm::BasicBlock::Create(m_llvmCtx, "end", m_function); + m_endThreadBranch = llvm::BasicBlock::Create(m_llvmCtx, "endThread", m_function); } void LLVMBuildUtils::end(LLVMInstruction *lastInstruction, LLVMRegister *lastConstant) @@ -184,9 +185,9 @@ void LLVMBuildUtils::end(LLVMInstruction *lastInstruction, LLVMRegister *lastCon syncVariables(); m_builder.CreateBr(m_endBranch); + // End branch m_builder.SetInsertPoint(m_endBranch); - // End the script function llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); switch (m_codeType) { @@ -216,6 +217,31 @@ void LLVMBuildUtils::end(LLVMInstruction *lastInstruction, LLVMRegister *lastCon m_builder.CreateRet(castValue(lastConstant, Compiler::StaticType::Bool)); break; } + + // Thread end branch (stop the entire thread, including procedure callers) + m_builder.SetInsertPoint(m_endThreadBranch); + + switch (m_codeType) { + case Compiler::CodeType::Script: + // Return a sentinel value (special pointer) to terminate any procedure callers + if (m_warp) + m_builder.CreateRet(threadEndSentinel()); + else if (m_procedurePrototype) + m_coroutine->endWithSentinel(threadEndSentinel()); + else { + // There's no need to return the sentinel value in standard scripts because they don't have any callers + m_coroutine->endWithSentinel(threadEndSentinel()); + // m_coroutine->end(); + } + + break; + + case Compiler::CodeType::Reporter: + case Compiler::CodeType::HatPredicate: + // Procedures cannot be called by these scripts, so we don't have to return the sentinel value + m_builder.CreateBr(m_endBranch); + break; + } } LLVMCompilerContext *LLVMBuildUtils::compilerCtx() const @@ -323,6 +349,17 @@ llvm::BasicBlock *LLVMBuildUtils::endBranch() const return m_endBranch; } +llvm::BasicBlock *LLVMBuildUtils::endThreadBranch() const +{ + return m_endThreadBranch; +} + +llvm::Value *LLVMBuildUtils::threadEndSentinel() const +{ + llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + return m_builder.CreateIntToPtr(m_builder.getInt64(1), pointerType, "threadEndSentinel"); +} + BlockPrototype *LLVMBuildUtils::procedurePrototype() const { return m_procedurePrototype; diff --git a/src/engine/internal/llvm/llvmbuildutils.h b/src/engine/internal/llvm/llvmbuildutils.h index 20a0a1a82..9bd7b9dc1 100644 --- a/src/engine/internal/llvm/llvmbuildutils.h +++ b/src/engine/internal/llvm/llvmbuildutils.h @@ -56,6 +56,9 @@ class LIBSCRATCHCPP_TEST_EXPORT LLVMBuildUtils size_t stringCount() const; llvm::BasicBlock *endBranch() const; + llvm::BasicBlock *endThreadBranch() const; + + llvm::Value *threadEndSentinel() const; BlockPrototype *procedurePrototype() const; bool warp() const; @@ -166,6 +169,7 @@ class LIBSCRATCHCPP_TEST_EXPORT LLVMBuildUtils llvm::Value *m_functionIdValue = nullptr; llvm::BasicBlock *m_endBranch = nullptr; + llvm::BasicBlock *m_endThreadBranch = nullptr; llvm::StructType *m_valueDataType = nullptr; llvm::StructType *m_stringPtrType = nullptr; diff --git a/src/engine/internal/llvm/llvmcodebuilder.cpp b/src/engine/internal/llvm/llvmcodebuilder.cpp index aa7682929..914230343 100644 --- a/src/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/engine/internal/llvm/llvmcodebuilder.cpp @@ -567,6 +567,12 @@ void LLVMCodeBuilder::createStop() m_instructions.addInstruction(ins); } +void LLVMCodeBuilder::createThreadStop() +{ + auto ins = std::make_shared(LLVMInstruction::Type::ThreadStop, m_loopCondition); + m_instructions.addInstruction(ins); +} + void LLVMCodeBuilder::createStopWithoutSync() { auto ins = std::make_shared(LLVMInstruction::Type::StopWithoutSync, m_loopCondition); diff --git a/src/engine/internal/llvm/llvmcodebuilder.h b/src/engine/internal/llvm/llvmcodebuilder.h index 7a85eb34f..b9894c558 100644 --- a/src/engine/internal/llvm/llvmcodebuilder.h +++ b/src/engine/internal/llvm/llvmcodebuilder.h @@ -113,6 +113,7 @@ class LIBSCRATCHCPP_TEST_EXPORT LLVMCodeBuilder : public ICodeBuilder void yield() override; void createStop() override; + void createThreadStop() override; void createStopWithoutSync() override; void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) override; diff --git a/src/engine/internal/llvm/llvmcoroutine.cpp b/src/engine/internal/llvm/llvmcoroutine.cpp index 02c4ecb16..74069a416 100644 --- a/src/engine/internal/llvm/llvmcoroutine.cpp +++ b/src/engine/internal/llvm/llvmcoroutine.cpp @@ -35,21 +35,32 @@ LLVMCoroutine::LLVMCoroutine(llvm::Module *module, llvm::IRBuilder<> *builder, l // Begin m_handle = builder->CreateCall(coroBegin, { coroIdRet, alloc }); + m_didSuspendVar = builder->CreateAlloca(builder->getInt1Ty(), nullptr, "didSuspend"); builder->CreateStore(builder->getInt1(false), m_didSuspendVar); + + m_sentinelVar = builder->CreateAlloca(pointerType, nullptr, "sentinel"); + builder->CreateStore(nullPointer, m_sentinelVar); + llvm::BasicBlock *entry = builder->GetInsertBlock(); // Create suspend branch m_suspendBlock = llvm::BasicBlock::Create(ctx, "suspend", func); builder->SetInsertPoint(m_suspendBlock); + + llvm::Value *sentinelValue = builder->CreateLoad(pointerType, m_sentinelVar); + llvm::Value *sentinelIsNull = builder->CreateIsNull(sentinelValue); builder->CreateCall(coroEnd, { m_handle, builder->getInt1(false), llvm::ConstantTokenNone::get(ctx) }); - builder->CreateRet(m_handle); + builder->CreateRet(builder->CreateSelect(sentinelIsNull, m_handle, sentinelValue)); // Create free branches m_freeMemRetBlock = llvm::BasicBlock::Create(ctx, "freeMemRet", func); builder->SetInsertPoint(m_freeMemRetBlock); + + sentinelValue = builder->CreateLoad(pointerType, m_sentinelVar); + sentinelIsNull = builder->CreateIsNull(sentinelValue); builder->CreateFree(alloc); - builder->CreateRet(llvm::ConstantPointerNull::get(pointerType)); + builder->CreateRet(builder->CreateSelect(sentinelIsNull, llvm::ConstantPointerNull::get(pointerType), sentinelValue)); llvm::BasicBlock *freeBranch = llvm::BasicBlock::Create(ctx, "free", func); builder->SetInsertPoint(freeBranch); @@ -63,6 +74,15 @@ LLVMCoroutine::LLVMCoroutine(llvm::Module *module, llvm::IRBuilder<> *builder, l llvm::Value *needFree = builder->CreateIsNotNull(mem); builder->CreateCondBr(needFree, freeBranch, m_suspendBlock); + // Create final suspend point + m_finalSuspendBlock = llvm::BasicBlock::Create(ctx, "finalSuspend", m_function); + + m_builder->SetInsertPoint(m_finalSuspendBlock); + llvm::Value *suspendResult = m_builder->CreateCall(llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::coro_suspend), { llvm::ConstantTokenNone::get(ctx), m_builder->getInt1(true) }); + llvm::SwitchInst *sw = m_builder->CreateSwitch(suspendResult, m_suspendBlock, 2); + sw->addCase(m_builder->getInt8(0), m_freeMemRetBlock); + sw->addCase(m_builder->getInt8(1), m_cleanupBlock); + builder->SetInsertPoint(entry); } @@ -135,20 +155,11 @@ llvm::Value *LLVMCoroutine::createResume(llvm::Module *module, llvm::IRBuilder<> void LLVMCoroutine::end() { - llvm::LLVMContext &ctx = m_builder->getContext(); - - // Add final suspend point - llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(ctx, "end", m_function); - llvm::BasicBlock *finalSuspendBranch = llvm::BasicBlock::Create(ctx, "finalSuspend", m_function); - m_builder->CreateCondBr(m_builder->CreateLoad(m_builder->getInt1Ty(), m_didSuspendVar), finalSuspendBranch, endBranch); - - m_builder->SetInsertPoint(finalSuspendBranch); - llvm::Value *suspendResult = m_builder->CreateCall(llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::coro_suspend), { llvm::ConstantTokenNone::get(ctx), m_builder->getInt1(true) }); - llvm::SwitchInst *sw = m_builder->CreateSwitch(suspendResult, m_suspendBlock, 2); - sw->addCase(m_builder->getInt8(0), endBranch); // unreachable - sw->addCase(m_builder->getInt8(1), m_cleanupBlock); + m_builder->CreateCondBr(m_builder->CreateLoad(m_builder->getInt1Ty(), m_didSuspendVar), m_finalSuspendBlock, m_freeMemRetBlock); +} - // Jump to "free and return" branch - m_builder->SetInsertPoint(endBranch); - m_builder->CreateBr(m_freeMemRetBlock); +void LLVMCoroutine::endWithSentinel(llvm::Value *sentinel) +{ + m_builder->CreateStore(sentinel, m_sentinelVar); + end(); } diff --git a/src/engine/internal/llvm/llvmcoroutine.h b/src/engine/internal/llvm/llvmcoroutine.h index fa3d79fd9..a04a2ad10 100644 --- a/src/engine/internal/llvm/llvmcoroutine.h +++ b/src/engine/internal/llvm/llvmcoroutine.h @@ -24,6 +24,7 @@ class LLVMCoroutine void createSuspend(); static llvm::Value *createResume(llvm::Module *module, llvm::IRBuilder<> *builder, llvm::Function *function, llvm::Value *coroHandle); void end(); + void endWithSentinel(llvm::Value *sentinel); private: llvm::Module *m_module = nullptr; @@ -31,9 +32,11 @@ class LLVMCoroutine llvm::Function *m_function = nullptr; llvm::Value *m_handle = nullptr; llvm::BasicBlock *m_suspendBlock = nullptr; + llvm::BasicBlock *m_finalSuspendBlock = nullptr; llvm::BasicBlock *m_cleanupBlock = nullptr; llvm::BasicBlock *m_freeMemRetBlock = nullptr; llvm::Value *m_didSuspendVar = nullptr; + llvm::Value *m_sentinelVar = nullptr; }; } // namespace libscratchcpp diff --git a/src/engine/internal/llvm/llvmexecutablecode.cpp b/src/engine/internal/llvm/llvmexecutablecode.cpp index 1acb79f42..ce645a007 100644 --- a/src/engine/internal/llvm/llvmexecutablecode.cpp +++ b/src/engine/internal/llvm/llvmexecutablecode.cpp @@ -16,6 +16,8 @@ using namespace libscratchcpp; +static const void *END_THREAD_SENTINEL = (void *)0x1; + LLVMExecutableCode::LLVMExecutableCode( LLVMCompilerContext *ctx, function_id_t functionId, @@ -70,10 +72,11 @@ void LLVMExecutableCode::run(ExecutionContext *context) MainFunctionType f = std::get(m_mainFunction); void *handle = f(context, target, target->variableData(), target->listData()); - if (!handle) + if (!handle || handle == END_THREAD_SENTINEL) { ctx->setFinished(true); - - ctx->setCoroutineHandle(handle); + ctx->setCoroutineHandle(nullptr); + } else + ctx->setCoroutineHandle(handle); } } diff --git a/src/engine/internal/llvm/llvminstruction.h b/src/engine/internal/llvm/llvminstruction.h index 558258aa6..56a1880f4 100644 --- a/src/engine/internal/llvm/llvminstruction.h +++ b/src/engine/internal/llvm/llvminstruction.h @@ -77,6 +77,7 @@ struct LLVMInstruction BeginLoopCondition, EndLoop, Stop, + ThreadStop, StopWithoutSync, CallProcedure, ProcedureArg diff --git a/test/llvm/llvmcodebuilder_test.cpp b/test/llvm/llvmcodebuilder_test.cpp index 498e79a92..df6d70f6f 100644 --- a/test/llvm/llvmcodebuilder_test.cpp +++ b/test/llvm/llvmcodebuilder_test.cpp @@ -3552,6 +3552,140 @@ TEST_F(LLVMCodeBuilderTest, UndefinedProcedure) ASSERT_TRUE(code->isFinished(ctx.get())); } +TEST_F(LLVMCodeBuilderTest, ProcedureThreadStop_Warp) +{ + Sprite sprite; + + // Inner procedure (proc2): prints "inner_before", then stops the thread + BlockPrototype prototype2; + prototype2.setProcCode("proc2"); + prototype2.setWarp(true); + LLVMCodeBuilder *builder = m_utils.createBuilder(&sprite, &prototype2); + + CompilerValue *v = builder->addConstValue("inner_before"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + builder->createThreadStop(); + + // This should NOT execute + v = builder->addConstValue("inner_after"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + auto proc2Code = builder->build(); + + // Outer procedure (proc1): prints "outer_before", calls proc2, then prints "outer_after" + BlockPrototype prototype1; + prototype1.setProcCode("proc1"); + prototype1.setWarp(true); + builder = m_utils.createBuilder(&sprite, &prototype1); + + v = builder->addConstValue("outer_before"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + builder->createProcedureCall(&prototype2, {}); + + // This should NOT execute (thread was stopped by proc2) + v = builder->addConstValue("outer_after"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + auto proc1Code = builder->build(); + + // Root script: prints "script_before", calls proc1, then prints "script_after" + builder = m_utils.createBuilder(&sprite, true); + + v = builder->addConstValue("script_before"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + builder->createProcedureCall(&prototype1, {}); + + // This should NOT execute (thread was stopped by proc2 via proc1) + v = builder->addConstValue("script_after"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + std::string expected = + "script_before\n" + "outer_before\n" + "inner_before\n"; + + auto code = builder->build(); + 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); + ASSERT_TRUE(code->isFinished(ctx.get())); +} + +TEST_F(LLVMCodeBuilderTest, ProcedureThreadStop_NonWarp) +{ + Sprite sprite; + + // Inner procedure (proc2): prints "inner_before", then stops the thread + BlockPrototype prototype2; + prototype2.setProcCode("proc2"); + prototype2.setWarp(false); + LLVMCodeBuilder *builder = m_utils.createBuilder(&sprite, &prototype2); + + CompilerValue *v = builder->addConstValue("inner_before"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + builder->createThreadStop(); + + // This should NOT execute + v = builder->addConstValue("inner_after"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + auto proc2Code = builder->build(); + + // Outer procedure (proc1): prints "outer_before", calls proc2, then prints "outer_after" + BlockPrototype prototype1; + prototype1.setProcCode("proc1"); + prototype1.setWarp(false); + builder = m_utils.createBuilder(&sprite, &prototype1); + + v = builder->addConstValue("outer_before"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + builder->createProcedureCall(&prototype2, {}); + + // This should NOT execute (thread was stopped by proc2) + v = builder->addConstValue("outer_after"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + auto proc1Code = builder->build(); + + // Root script: prints "script_before", calls proc1, then prints "script_after" + builder = m_utils.createBuilder(&sprite, false); + + v = builder->addConstValue("script_before"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + builder->createProcedureCall(&prototype1, {}); + + // This should NOT execute (thread was stopped by proc2 via proc1) + v = builder->addConstValue("script_after"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + std::string expected = + "script_before\n" + "outer_before\n" + "inner_before\n"; + + auto code = builder->build(); + 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); + ASSERT_TRUE(code->isFinished(ctx.get())); +} + TEST_F(LLVMCodeBuilderTest, HatPredicates) { Sprite sprite; diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h index 1d9225a23..ed4f9ba43 100644 --- a/test/mocks/codebuildermock.h +++ b/test/mocks/codebuildermock.h @@ -88,6 +88,7 @@ class CodeBuilderMock : public ICodeBuilder MOCK_METHOD(void, yield, (), (override)); MOCK_METHOD(void, createStop, (), (override)); + MOCK_METHOD(void, createThreadStop, (), (override)); MOCK_METHOD(void, createStopWithoutSync, (), (override)); MOCK_METHOD(void, createProcedureCall, (BlockPrototype *, const Compiler::Args &), (override)); From c99bb9fb0b9c898e285fa3a3a502632bc4393459 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 29 Mar 2026 16:24:52 +0200 Subject: [PATCH 2/8] Compiler: Add createThreadStop() method --- include/scratchcpp/compiler.h | 1 + src/engine/compiler.cpp | 6 ++++++ test/compiler/compiler_test.cpp | 14 ++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/include/scratchcpp/compiler.h b/include/scratchcpp/compiler.h index 7cc8d44d9..82d0afaa6 100644 --- a/include/scratchcpp/compiler.h +++ b/include/scratchcpp/compiler.h @@ -150,6 +150,7 @@ class LIBSCRATCHCPP_EXPORT Compiler void createYield(); void createStop(); + void createThreadStop(); void createStopWithoutSync(); void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args); diff --git a/src/engine/compiler.cpp b/src/engine/compiler.cpp index f113998e4..40770b23a 100644 --- a/src/engine/compiler.cpp +++ b/src/engine/compiler.cpp @@ -705,6 +705,12 @@ void Compiler::createStop() impl->builder->createStop(); } +/*! Creates a stop thread (current script and procedure callers) instruction. */ +void Compiler::createThreadStop() +{ + impl->builder->createThreadStop(); +} + /*! * Creates a stop script without synchronization instruction.\n * Use this if synchronization is not possible at the stop point. diff --git a/test/compiler/compiler_test.cpp b/test/compiler/compiler_test.cpp index dfb14143e..50aaf445a 100644 --- a/test/compiler/compiler_test.cpp +++ b/test/compiler/compiler_test.cpp @@ -1673,6 +1673,20 @@ TEST_F(CompilerTest, CreateStop) compile(m_compiler.get(), block.get()); } +TEST_F(CompilerTest, CreateThreadStop) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + EXPECT_CALL(*m_builder, createThreadStop()); + compiler->createThreadStop(); + return nullptr; + }); + + compile(m_compiler.get(), block.get()); +} + TEST_F(CompilerTest, CreateStopWithoutSync) { From da1bac86ca03cfbcc108e7cc9a628d9125126bee Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 29 Mar 2026 17:12:48 +0200 Subject: [PATCH 3/8] Add invalidate target LLVM instruction --- src/engine/internal/icodebuilder.h | 2 ++ .../internal/llvm/instructions/control.cpp | 10 ++++++++++ .../internal/llvm/instructions/control.h | 1 + src/engine/internal/llvm/llvmbuildutils.cpp | 18 ++++++++++++++++++ src/engine/internal/llvm/llvmbuildutils.h | 2 ++ src/engine/internal/llvm/llvmcodebuilder.cpp | 6 ++++++ src/engine/internal/llvm/llvmcodebuilder.h | 2 ++ src/engine/internal/llvm/llvminstruction.h | 1 + test/mocks/codebuildermock.h | 2 ++ 9 files changed, 44 insertions(+) diff --git a/src/engine/internal/icodebuilder.h b/src/engine/internal/icodebuilder.h index fa50ae181..3d9d661d6 100644 --- a/src/engine/internal/icodebuilder.h +++ b/src/engine/internal/icodebuilder.h @@ -102,6 +102,8 @@ class ICodeBuilder virtual void createThreadStop() = 0; virtual void createStopWithoutSync() = 0; + virtual void invalidateTarget() = 0; + virtual void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) = 0; }; diff --git a/src/engine/internal/llvm/instructions/control.cpp b/src/engine/internal/llvm/instructions/control.cpp index 7f71376b5..715bd9e68 100644 --- a/src/engine/internal/llvm/instructions/control.cpp +++ b/src/engine/internal/llvm/instructions/control.cpp @@ -68,6 +68,10 @@ ProcessResult Control::process(LLVMInstruction *ins) ret.next = buildStopWithoutSync(ins); break; + case LLVMInstruction::Type::InvalidateTarget: + ret.next = buildInvalidateTarget(ins); + break; + default: ret.match = false; break; @@ -363,3 +367,9 @@ LLVMInstruction *Control::buildStopWithoutSync(LLVMInstruction *ins) return ins->next; } + +LLVMInstruction *Control::buildInvalidateTarget(LLVMInstruction *ins) +{ + m_utils.invalidateTarget(); + return ins->next; +} diff --git a/src/engine/internal/llvm/instructions/control.h b/src/engine/internal/llvm/instructions/control.h index aefddd8eb..fbcb30fc9 100644 --- a/src/engine/internal/llvm/instructions/control.h +++ b/src/engine/internal/llvm/instructions/control.h @@ -29,6 +29,7 @@ class Control : public InstructionGroup LLVMInstruction *buildStop(LLVMInstruction *ins); LLVMInstruction *buildThreadStop(LLVMInstruction *ins); LLVMInstruction *buildStopWithoutSync(LLVMInstruction *ins); + LLVMInstruction *buildInvalidateTarget(LLVMInstruction *ins); }; } // namespace libscratchcpp::llvmins diff --git a/src/engine/internal/llvm/llvmbuildutils.cpp b/src/engine/internal/llvm/llvmbuildutils.cpp index 206a16e96..0e64a8a9b 100644 --- a/src/engine/internal/llvm/llvmbuildutils.cpp +++ b/src/engine/internal/llvm/llvmbuildutils.cpp @@ -161,6 +161,10 @@ void LLVMBuildUtils::init(llvm::Function *function, BlockPrototype *procedurePro reloadVariables(); reloadLists(); + // Mark the target as valid + m_targetValidFlag = m_builder.CreateAlloca(m_builder.getInt1Ty()); + m_builder.CreateStore(m_builder.getInt1(true), m_targetValidFlag); + // Create end branches m_endBranch = llvm::BasicBlock::Create(m_llvmCtx, "end", m_function); m_endThreadBranch = llvm::BasicBlock::Create(m_llvmCtx, "endThread", m_function); @@ -438,6 +442,12 @@ LLVMListPtr &LLVMBuildUtils::listPtr(List *list) void LLVMBuildUtils::syncVariables() { + llvm::BasicBlock *syncBlock = llvm::BasicBlock::Create(m_llvmCtx, "syncVariables", m_function); + llvm::BasicBlock *syncNextBlock = llvm::BasicBlock::Create(m_llvmCtx, "syncVariables.next", m_function); + m_builder.CreateCondBr(m_builder.CreateLoad(m_builder.getInt1Ty(), m_targetValidFlag), syncBlock, syncNextBlock); + + m_builder.SetInsertPoint(syncBlock); + // Copy stack variables to the actual variables for (auto &[var, varPtr] : m_variablePtrs) { llvm::BasicBlock *copyBlock = llvm::BasicBlock::Create(m_llvmCtx, "syncVar", m_function); @@ -451,6 +461,9 @@ void LLVMBuildUtils::syncVariables() m_builder.SetInsertPoint(nextBlock); } + + m_builder.CreateBr(syncNextBlock); + m_builder.SetInsertPoint(syncNextBlock); } void LLVMBuildUtils::reloadVariables() @@ -481,6 +494,11 @@ void LLVMBuildUtils::reloadLists() } } +void LLVMBuildUtils::invalidateTarget() +{ + m_builder.CreateStore(m_builder.getInt1(false), m_targetValidFlag); +} + std::vector &LLVMBuildUtils::ifStatements() { return m_ifStatements; diff --git a/src/engine/internal/llvm/llvmbuildutils.h b/src/engine/internal/llvm/llvmbuildutils.h index 9bd7b9dc1..2a7938081 100644 --- a/src/engine/internal/llvm/llvmbuildutils.h +++ b/src/engine/internal/llvm/llvmbuildutils.h @@ -82,6 +82,7 @@ class LIBSCRATCHCPP_TEST_EXPORT LLVMBuildUtils void syncVariables(); void reloadVariables(); void reloadLists(); + void invalidateTarget(); std::vector &ifStatements(); std::vector &loops(); @@ -186,6 +187,7 @@ class LIBSCRATCHCPP_TEST_EXPORT LLVMBuildUtils llvm::Value *m_targetVariables = nullptr; llvm::Value *m_targetLists = nullptr; llvm::Value *m_warpArg = nullptr; + llvm::Value *m_targetValidFlag = nullptr; std::unique_ptr m_coroutine; diff --git a/src/engine/internal/llvm/llvmcodebuilder.cpp b/src/engine/internal/llvm/llvmcodebuilder.cpp index 914230343..c57c3a8d6 100644 --- a/src/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/engine/internal/llvm/llvmcodebuilder.cpp @@ -579,6 +579,12 @@ void LLVMCodeBuilder::createStopWithoutSync() m_instructions.addInstruction(ins); } +void LLVMCodeBuilder::invalidateTarget() +{ + auto ins = std::make_shared(LLVMInstruction::Type::InvalidateTarget, m_loopCondition); + m_instructions.addInstruction(ins); +} + void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) { assert(prototype); diff --git a/src/engine/internal/llvm/llvmcodebuilder.h b/src/engine/internal/llvm/llvmcodebuilder.h index b9894c558..7c2549eaa 100644 --- a/src/engine/internal/llvm/llvmcodebuilder.h +++ b/src/engine/internal/llvm/llvmcodebuilder.h @@ -116,6 +116,8 @@ class LIBSCRATCHCPP_TEST_EXPORT LLVMCodeBuilder : public ICodeBuilder void createThreadStop() override; void createStopWithoutSync() override; + void invalidateTarget() override; + void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) override; private: diff --git a/src/engine/internal/llvm/llvminstruction.h b/src/engine/internal/llvm/llvminstruction.h index 56a1880f4..8525ecccd 100644 --- a/src/engine/internal/llvm/llvminstruction.h +++ b/src/engine/internal/llvm/llvminstruction.h @@ -79,6 +79,7 @@ struct LLVMInstruction Stop, ThreadStop, StopWithoutSync, + InvalidateTarget, CallProcedure, ProcedureArg }; diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h index ed4f9ba43..383ad3af1 100644 --- a/test/mocks/codebuildermock.h +++ b/test/mocks/codebuildermock.h @@ -91,5 +91,7 @@ class CodeBuilderMock : public ICodeBuilder MOCK_METHOD(void, createThreadStop, (), (override)); MOCK_METHOD(void, createStopWithoutSync, (), (override)); + MOCK_METHOD(void, invalidateTarget, (), (override)); + MOCK_METHOD(void, createProcedureCall, (BlockPrototype *, const Compiler::Args &), (override)); }; From 9f3749d02b02c6569c8e154d80263fe4477a72ca Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 29 Mar 2026 17:27:39 +0200 Subject: [PATCH 4/8] Compiler: Add invalidateTarget() method --- include/scratchcpp/compiler.h | 2 ++ src/engine/compiler.cpp | 9 +++++++++ test/compiler/compiler_test.cpp | 14 ++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/include/scratchcpp/compiler.h b/include/scratchcpp/compiler.h index 82d0afaa6..b2f98078c 100644 --- a/include/scratchcpp/compiler.h +++ b/include/scratchcpp/compiler.h @@ -153,6 +153,8 @@ class LIBSCRATCHCPP_EXPORT Compiler void createThreadStop(); void createStopWithoutSync(); + void invalidateTarget(); + void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args); Input *input(const std::string &name) const; diff --git a/src/engine/compiler.cpp b/src/engine/compiler.cpp index 40770b23a..c94cde6e6 100644 --- a/src/engine/compiler.cpp +++ b/src/engine/compiler.cpp @@ -721,6 +721,15 @@ void Compiler::createStopWithoutSync() impl->builder->createStopWithoutSync(); } +/*! + * Creates a sprite/stage invalidation point.\n + * Use this if synchronization is not possible because the target has been deleted. + */ +void Compiler::invalidateTarget() +{ + impl->builder->invalidateTarget(); +} + /*! Creates a call to the procedure with the given prototype. */ void Compiler::createProcedureCall(BlockPrototype *prototype, const libscratchcpp::Compiler::Args &args) { diff --git a/test/compiler/compiler_test.cpp b/test/compiler/compiler_test.cpp index 50aaf445a..171211989 100644 --- a/test/compiler/compiler_test.cpp +++ b/test/compiler/compiler_test.cpp @@ -1701,6 +1701,20 @@ TEST_F(CompilerTest, CreateStopWithoutSync) compile(m_compiler.get(), block.get()); } +TEST_F(CompilerTest, InvalidateTarget) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + EXPECT_CALL(*m_builder, invalidateTarget()); + compiler->invalidateTarget(); + return nullptr; + }); + + compile(m_compiler.get(), block.get()); +} + TEST_F(CompilerTest, CreateProcedureCall) { From daba5a4d066b9fa32e0640a40010d7a2baa2bc5c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 29 Mar 2026 17:28:34 +0200 Subject: [PATCH 5/8] fix #660: Stop the entire thread in stop all and delete this clone --- src/blocks/controlblocks.cpp | 6 +- test/engine/engine_test.cpp | 102 ++++++++++++++++++ .../660_stop_script_from_procedure_clone.sb3 | Bin 0 -> 2355 bytes ...60_stop_script_from_procedure_stop_all.sb3 | Bin 0 -> 1908 bytes ..._script_from_procedure_stop_all_nested.sb3 | Bin 0 -> 1849 bytes ...cript_from_procedure_stop_all_non_warp.sb3 | Bin 0 -> 1794 bytes ...ript_from_procedure_stop_other_scripts.sb3 | Bin 0 -> 1958 bytes ...script_from_procedure_stop_this_script.sb3 | Bin 0 -> 1910 bytes 8 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 test/regtest_projects/660_stop_script_from_procedure_clone.sb3 create mode 100644 test/regtest_projects/660_stop_script_from_procedure_stop_all.sb3 create mode 100644 test/regtest_projects/660_stop_script_from_procedure_stop_all_nested.sb3 create mode 100644 test/regtest_projects/660_stop_script_from_procedure_stop_all_non_warp.sb3 create mode 100644 test/regtest_projects/660_stop_script_from_procedure_stop_other_scripts.sb3 create mode 100644 test/regtest_projects/660_stop_script_from_procedure_stop_this_script.sb3 diff --git a/src/blocks/controlblocks.cpp b/src/blocks/controlblocks.cpp index 807d1b78e..e340ef415 100644 --- a/src/blocks/controlblocks.cpp +++ b/src/blocks/controlblocks.cpp @@ -90,7 +90,8 @@ CompilerValue *ControlBlocks::compileStop(Compiler *compiler) if (str == "all") { compiler->addFunctionCallWithCtx("control_stop_all", Compiler::StaticType::Void); - compiler->createStop(); + compiler->invalidateTarget(); // if this is a clone, it doesn't exist anymore + compiler->createThreadStop(); } else if (str == "this script") compiler->createStop(); else if (str == "other scripts in sprite" || str == "other scripts in stage") @@ -181,7 +182,8 @@ CompilerValue *ControlBlocks::compileDeleteThisClone(Compiler *compiler) { CompilerValue *deleted = compiler->addTargetFunctionCall("control_delete_this_clone", Compiler::StaticType::Bool); compiler->beginIfStatement(deleted); - compiler->createStopWithoutSync(); // sync happens before the function call + compiler->invalidateTarget(); // the sprite doesn't exist anymore + compiler->createThreadStop(); // callers should be stopped too compiler->endIf(); return nullptr; } diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index 66121708b..ebeab4a7d 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -2280,3 +2280,105 @@ TEST(EngineTest, BroadcastAndWaitCaseInsensitive) ASSERT_VAR(stage, "passed"); ASSERT_TRUE(GET_VAR(stage, "passed")->value().toBool()); } + +TEST(EngineTest, CloneScriptsAreStoppedFromProcedureDeletingTheClone) +{ + // Regtest for #660 + Project p("regtest_projects/660_stop_script_from_procedure_clone.sb3"); + ASSERT_TRUE(p.load()); + + auto engine = p.engine(); + + Stage *stage = engine->stage(); + ASSERT_TRUE(stage); + + engine->run(); + + ASSERT_VAR(stage, "passed"); + ASSERT_TRUE(GET_VAR(stage, "passed")->value().toBool()); +} + +TEST(EngineTest, ScriptsAreNotStoppedFromProcedureStoppingTheProcedureScript) +{ + // Regtest for #660 + Project p("regtest_projects/660_stop_script_from_procedure_stop_this_script.sb3"); + ASSERT_TRUE(p.load()); + + auto engine = p.engine(); + + Stage *stage = engine->stage(); + ASSERT_TRUE(stage); + + engine->run(); + + ASSERT_VAR(stage, "passed"); + ASSERT_TRUE(GET_VAR(stage, "passed")->value().toBool()); +} + +TEST(EngineTest, ScriptsAreNotStoppedFromProcedureStoppingOtherScripts) +{ + // Regtest for #660 + Project p("regtest_projects/660_stop_script_from_procedure_stop_other_scripts.sb3"); + ASSERT_TRUE(p.load()); + + auto engine = p.engine(); + + Stage *stage = engine->stage(); + ASSERT_TRUE(stage); + + engine->run(); + + ASSERT_VAR(stage, "passed"); + ASSERT_TRUE(GET_VAR(stage, "passed")->value().toBool()); +} + +TEST(EngineTest, ScriptsAreStoppedFromProcedureStoppingAll) +{ + // Regtest for #660 + Project p("regtest_projects/660_stop_script_from_procedure_stop_all.sb3"); + ASSERT_TRUE(p.load()); + + auto engine = p.engine(); + + Stage *stage = engine->stage(); + ASSERT_TRUE(stage); + + engine->run(); + + ASSERT_VAR(stage, "passed"); + ASSERT_TRUE(GET_VAR(stage, "passed")->value().toBool()); +} + +TEST(EngineTest, ScriptsAreStoppedFromProcedureStoppingAll_NonWarp) +{ + // Regtest for #660 + Project p("regtest_projects/660_stop_script_from_procedure_stop_all_non_warp.sb3"); + ASSERT_TRUE(p.load()); + + auto engine = p.engine(); + + Stage *stage = engine->stage(); + ASSERT_TRUE(stage); + + engine->run(); + + ASSERT_VAR(stage, "passed"); + ASSERT_TRUE(GET_VAR(stage, "passed")->value().toBool()); +} + +TEST(EngineTest, ScriptsAreStoppedFromProcedureStoppingAll_Nested) +{ + // Regtest for #660 + Project p("regtest_projects/660_stop_script_from_procedure_stop_all_nested.sb3"); + ASSERT_TRUE(p.load()); + + auto engine = p.engine(); + + Stage *stage = engine->stage(); + ASSERT_TRUE(stage); + + engine->run(); + + ASSERT_VAR(stage, "passed"); + ASSERT_TRUE(GET_VAR(stage, "passed")->value().toBool()); +} diff --git a/test/regtest_projects/660_stop_script_from_procedure_clone.sb3 b/test/regtest_projects/660_stop_script_from_procedure_clone.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..54065eab716e97cc6d819a93123bb1c9f7d7ce06 GIT binary patch literal 2355 zcma)8c`zIJ7Y{)d>!_R7Oq4k)~!LYMV&p|$D)cl zN=O|Ww5-;B#kEL_V(ssr-R;b8X20M0%$xU}@B7Sr-g`6eSs;N-Yybd&6`*kQ{+%`J zGdf?G0RSt00D$B4)fbP$qFn=2u>LroH@7{lCZT*sN-r-DRCSBeU=oAor9Z`3%9{zL z3E0U6^4kX)d66^c^`GrFZf1&iAY56KH%O0OF8fj5EKMt|h#&?Wq?oa}4$_Lv=USK` zGRg5oMw+3h?0pdtSM%+5X*IgYtHd1X2(xJ%b!?n6N&oQG8b*Lv#K%f#j_kG(S1a}4 zhpj$Nn+``|egc}oS`Fq@AxYaN!WEpXo{&>Un|DN5ump=9*VwUbn8yC6<{VB89{w${ z3i?xVR<@>M-dNjim)*i@?{lhd%QwHo;L_3gl*9lVpOMEzm?GbTr#mO~T|;a-ZufVG zu$JbN#l2}HwzWJr=l~b&L^vzMb5427K=gJ0a3e6U;pDhxc)Z{7(D-1}b+3LJ zfz&yg3Vn09?|gq#T624#O8Bzc%=e?4O-1BNYWn)H)8sk@K_X|L)s}*z=vfexZTo?* zh?MGTDawpz+*x6Tr4_9HlsPQ7IUwp)>Y%NA`=aKPZx`ltZR``P*&X_C;Xe~`WED|J z5q7OUab_Y(L!Lwq&0OxP;AwH}R-0(_oVQc~HDoALvb)l1oX_~4<0Q!cythBCT88>< zPjsu&`KGfHZAeT*RSvzZ*71(FQ)fyJhEYfmSO0AyW-;qi%pR1}D855zj0u9Z)pEC0 z1XcmcwMjL-&61L1Tij=S_&oLEVVl>;%+b+TQXdjitPqd(LnPi#c`K<$v_Wkv!EKH9 zgH*a|v7urROdusJfb>1uK5mAixMG=Q*YMG<;jY{z=jI3MwiMc&@ofswZl^`{tsv|`X446~*((BqI*N^zha z7wgXn4+*f#+|Bp!V>EzVs&DPfwV#(sp31-4=0D-FnUhyE9Z`CLSAVJO?Mqg5=n}ad zBsphfWtu=Md8Vc~^aZBxcEEcT)@zhwCxv@ul=ikr8W|Q6lPGZop7~*BW|$C?e=RbV zzmH>nea^hLew0QZIHC`iI7tO67Am*agc5ZdzJVr!D$FAsC+04bsFdKM?Py{ZV0oPBoUuf#YdaBY-G> zm*?gpx7L#=2fwOVi$tz5y_kESK6=bPU>I1&@N$`@{g7S5tfsa+NV7Ii+?!-jz$LI@ zmRC5~`q608qmgdKLxOa13soL^v`l(#gu|Ps;x0OB0uy3mT>F|35k_jySjU}NeqM6_ z+Vs&!II4hgg~4}<)RxAqFh<%eTQ7-T=kj z8`vmf{-$TN7+w`iK0up0ziTT#R|c4d+kLwprv~_OJMr;ZlJ#K-GYnM_FH*=l(;0@ zS7-y|?3qJe|DyXTKNqrZq*r0=ua}G(Z?P48}!WU0n@~ zRztf&&~8vLO2t3uuD2PIGg^XgDlBol@l4liaAzUn#t?6vNzAS8_7_8w<8~x73@95X zI?iCjTewqe&?jqvzhWE9Pk{JT)s0fK1Ga4?)2j*yX5>-m_1Guw9x-r*?SMsHjzUGU zrPsUb&-;p={&GU7dF@%wH&Y0&hvs~9YX42K3Er~qqY$~|^ z6&VTmw4x*E6KdQ7?KUmQdN^5=RD0zvcl^CO4QjkaAKt8ap055PK?uf57)^aV`C(L?yDy&SDmRZtcDgi9qW!HC;~~Lzii?>-*VbK4gi_jqVFFFMPq#PvTPdA4G(jhOMMN*a)X6mniMM-)zz> z&@fsoTmbHv@j7R>Y$sW}hSnkY9Z>LraMtpEszY1e1QMIK%6G|ALpOz#E+3bHr(G~Y%Kq+ j{eEQpvD$w#em-UVFSWNoGN1kg0Dz}=`qb*N{^k7%An7J( literal 0 HcmV?d00001 diff --git a/test/regtest_projects/660_stop_script_from_procedure_stop_all.sb3 b/test/regtest_projects/660_stop_script_from_procedure_stop_all.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..a52437581029ca5affee4637d5e35935134b1ed9 GIT binary patch literal 1908 zcma)7c`zGj8;`pUE!za^zV#DI53Txndj$>5fTOh0DvO^ zmdj=DU{OMjnlJzW6bAq#_^)9R)F2WuNN<^-I?ydb6|XcY*Mr1{XP~F3 zOK)6yepGx;j4(y#gi23u*+tOdyYFY4-i?=(bv>QA+*f0)ySCo&Y<(}!<1UeKOwN1e z#6?HFOc6N`JdphgA;duz+mhWdD6GijZ#f6cDtSrkyy<;|PCOobJ|S4)^@gZUC5!=j zrt-`@$DV00$3TuGAt7M@VmKQwD-aEW4Y zNP9nU+t>15<}Z%%A8NQToU|?i+wIU14?|6V9CV5``Q$j)`Qp&(WK_4q)R{NZ4;uBl zT5T;3Y2NR4w(B-RK5yl(-Cmfw(kw0;qK*x9f^NBS2T72U5CsiBU96W(bk8x+aNjN%Sjg787`zjSWMOh$h{n3VoTPr zLP`2h;&{?UH10oe53z9VR%Z=tq^mQ4Ih*xeor_Sop5UzrMr`exT`}0Y{#Dj;xaT*g z3`U~Xn+jxyMjI>t8+Z{NmY!%yz_t6NEV3a5i#sybmI9T9`8A9Sk?G6ehY;FnK*E(L zKMcEkj9T4zW=_}K&=+)2LNQs{GudFBI;@3qNT|ws({54{bKX4HxwiIyBp}NSY?wU` zbnsJqXRWIpSCz_vKHfo*hpus&i4?I8OPca*|s{Fo{%XL`pup^-m#^^wR#S^I;Y?6m9CKh2|fD*yQfP#oGvV> z*QE+xX!FAlafZV*{FM4NhS-exrn%Lq{mgB_#NL=G!4B%aIc-;@CK1md=ACj9Dpi-J?0Z7 z{q-|-!C_@n*Ef>2LPFU`ZQRimKvtVDr+)0fSiMkftE~NP-QJE?R(H4Wnw!`TxzbB+ z1XGns_l&F3epxx{8l4u|qj5*HH22|)9*OaBy#ppFzE}a}4`KiuW&}e7K#dGxKjMKI8mFP zYPt0#4D7%N(l#|O@M4Oc&SuoIg+wW2;QNpVwq<>{3vV7Wey$DXwpWVNw;z7%&07@W zC@WK2q8A*CMMP~)4umaC7t525`Z>t1eZnLG{weBo&C6b80RUi$k3U4g5F~^#iD*cI z`x6i*Fe01~Kqeu{FqkRS2yO^9`BPMOH-!n6Nb}X|Csf?AiiHFt-*r6jQ9C8tk(Odw zUQ)chj!AaEi&&ES%u`vt-yr!KSClLmUqRyPloiCUGV+vo&SXzkILO!d#*2``*O&4> zRF@U$wT&e8_}`x&zzQ;u_bq@-CLhG6EU zXYVG{;vEuJvXj>n2ldKzKlZ+FF)lGYvs}6)#KVI8IPII)+==mx=fEs+2!PNav)HEk zy2>ObRiLhq#=XuU_>*S1ydRYtN~Ttpj4A9~lt)<8+snu9c$fu@9c=rK88~AE1i^s+ z??C)d{@eMxAAQaIdf@(|Vfc>rZ^rNIieGp8uZpT7|E~AW7!lDwSVH{xjepbs%zg)# C-85SO literal 0 HcmV?d00001 diff --git a/test/regtest_projects/660_stop_script_from_procedure_stop_all_nested.sb3 b/test/regtest_projects/660_stop_script_from_procedure_stop_all_nested.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..35213c4196c9c5f9a0da773bac2ea2ef8f4dbd05 GIT binary patch literal 1849 zcma)7c~H|=7fnEs%?cA15dkYumLO&$grXJ@s}O>e?*jqR5RzYnXcCsN2!RlGT)==J z0V#_Rf;b2QB|;*bR8&-21Vn@?n503C#2`!2*tGw&b*3|Y@142x{y1mm+E|@1+ z-EcO~1aLnIW^(&rQN_hm0d*dMiGhQk(vs?5KA~O4g!b(IQ898!`hBXLft};u&1okk z_t^YAi>c5XSdW@+Kj2l8!|vN{7`eM|Fs_P9Is*!j121pU)sQWnK`%EGnV z7i890sN&%~Ej|#WZUTeco|moUJ-FX|itc=*6)T)7dC}Weyq!Yui=IUTlFXGMk$VK) zE}YhJ#ed)*gCOQ8n#wby!8wF-*$|2z8F}wud;W< z<=RC&v^7m3MMXn(Nv)d@ue)f6^7d;md=^JU=CVp(ZFEl^aD08Kv@@_d%DUPtDABsM zl;dyX?ywhz&z>u4DVHQ40ZWOmg6W1vmAx>u5}E=bAJ5PR&p8x&=S@n?hK~ z))H?UYkq~F?oj24H|OF+A)B5v5^N=2ow_2sLAfl{JEOY9(sCwmvtQ%Pe%pyrjS3TU zPqS;aZ@`AL%-f?Gjtdu(nC8ExTOB%{>Uj&6;g35ntlHnS zpbKCpf5EU%g+aTPck`S2zRf3XELZOOs@%EiCraiPIWeNU2pos(R>5wnVBNy>B3n6l z{)Hl5C&kIEy>91i1k+XTd1}P zX)>a$vV02MuvqnTTFkE3_l{at6k2eU(szb7D?|t)4Yx9aAK%;iAy3X-J{+7pcywAi zD^ajI9$g6xkt*MbdiWSVb9T{TE%v?A$!uC7N?4*WF&=&m(5Uy{g_NJvkiX1{b&?g# z7DOE5_(#7*5gf$att6i~la(M>+uNz3iYVzn+a0;#@Knw(X;r6))WIxPgG8B<|L0R;#{^{*QM!Go%zPwr>m}0 zQ)`coMmEwrsd{3mU2o0Rnx}3-3ByZXajwb7e7&>tyVVB?EjI?|iFu!D=G>pYyu)8%fdl?A5Aq)xfJ@YK;GP;b zY7j{XxCugWMR1N!JVC`_zsTBQ^epwvWY?zKEvSJy zpTnb&4&Utm5A`*VPEUqi!ja)zy7?qH7*eNf_quEA6}LAu$2il4*4i;P&SfYhi;B)ZhI+pnsa$b8UXu5)1-~)%e2{90j1z01*iwA_*v0 zI1xc0kpT=D4tImOAdoQEKTUD^X<0D+T)gc)LSutRlH*CtYvJJWFL!APbF>hnU_x2@xa5+OKD0;} zg?BH{glCw9AkHsZAIO@Z$b?z8!KkqEe&b=RVfhCgl@XQ~e0a#hra11h+>~7(`6z@G&~4=LjOSo6L3ZkT$s{=76u? zO>ZmBBt!zw+LRY9+iRy}Vl=I&_)9WSeoiPtBwc${QVB6b}^Lz literal 0 HcmV?d00001 diff --git a/test/regtest_projects/660_stop_script_from_procedure_stop_all_non_warp.sb3 b/test/regtest_projects/660_stop_script_from_procedure_stop_all_non_warp.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..32e404135e3670b859b05992a12958eaca187f74 GIT binary patch literal 1794 zcmWIWW@h1HU|`^2c$HHdlj6WWdp$D)LlRJk2Pj%ll%JKFT%wm%oS(NfEI0qQnZQ47 zR(tNgl+&HB7s_uklWj`%y;yO#?SO&Cq)9si7C8i!1s(psw|vQy=_+}57X3|{kpAh% zo#OL%GhJm&TMMT3U(YHxgi91W>?39Mh+D28Mmp}gB|KnA^ z{(jy3*Ppv5Z>~SczH;T(rpX%f)+sMq;(A^?BF!!M+>5gHCcCq*`%GWDVpn}cGS8cP zS+AvP?2j?m$vSLXVRY)@`y@{m>pOlHerqZqng-AM1>Kg5>zwJx~$35 z>bsG+_rfXmsYePNFR~}D;}t*V{?a8f+sLlJq*D^vv%(Z&U;YSwbg!FRb{zW^VAh>9ZN; ztXqG?X@%dy?2FMlZ|D|3L&d-+{wU9$^Jv-qSPC36$gla%2&)%S$; zKHidg4wd{h3io%0$lT);atP6N=-6N9I@M|KtVIWvCj8LYIrrTRgKYmE2eaHMw{J|p za%ID}2`8i+KHF-|SU2ZlR-1mEIqQ?OqQ8Y@e+!E$|1xIZQ9gG1a(?QI<2Pmo-}XwE z_Sz@TRk8j~j-`0$x{QNUQp#@>SRGAm7ybVCmQ?Q2sc#z(^cgqvT^5~iIhrMhoiXA5 z)IZNZuime}=UTq-nu>}JpYJC8?sl>B{@0amyzMjp{Wks0{dd0vxNcamTda6vx25$W zvAUpL{d#Tczx4Xf9#@{aIbT&}$N#C4KQ9(uzqKJV{nFGe|5S9&OmZmwbcn+zXyQCB zGl9Q7%hEYt2hZycoTsw>abmr|{I`7vrk|6OYCO+-;PLVJro(5wPTcYPd34{e2iH&O z-&NJ?O;CJweTV55jTQT^3EgYlQOKWhH0^7b)DZ^(-<63jnV0L{D_EXS6XIt}Y_;QV zt6f{_#9iuq+TH7AOHIxrlq8%B^enR8JMIRq$ZoDrdXIH>J^uz=lXi{ zH7khRsqVVIo&Cr4> z#jBo5T)xzyTr1n+&7Q+?in?rj+FC4^Eu8!9op)pCDwC%Izdy^r-oBatdDxsrrmE$s z8#R~AsCwNrP4;s@T4Yb5aJ<>PTRF3zS5Et~ddVD}U6<<*C2hZ*S{&NdVOk#bEU2U* z_jqxITStDK*T>H_DO>Yhb8oCT$m8%zEbHgrFQ!#>M?WN9Fx>U+ZcKw%gi-GYW!L6w zSugiAtA?3G%{VA}!dr00AI^Uw-pj)LkIJg7E--1H);{rPV_lU`^~;Hk?`qEJEYtpa z;{6V@g~l$=7CmMD>?@owbN9!-(B!Huo+3TmS`3Ms#UAZac)rrStd(*7$-0eGn-Y^! z?`-@$MQ-!Ln=hZ5<@HatiYVCns+MIZl;GKnzYt`C5Lj0BKt2Xvk2 o2@av{Ca``XCi$V8h#uPr6W6g27xw|)tZX1%%s_Y#Nb`Yt0G*r&qW}N^ literal 0 HcmV?d00001 diff --git a/test/regtest_projects/660_stop_script_from_procedure_stop_other_scripts.sb3 b/test/regtest_projects/660_stop_script_from_procedure_stop_other_scripts.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..b0f80633f34d0aabc1ee0d5111f0fcf9fb27d8be GIT binary patch literal 1958 zcma)7c{CL28y*a@ri{8JTSL|~yIrnrtdW?DA6aI}5@Q*SkeERT9b_z*EFp!^WLJo> zMOiLl=vW%EWVwS$YP#V0^8tkc0KgGI@a0=> zPtJ+AUjYID91#FO^zbz(gy@IIk~IB7iGd7EfSt6C1Xo?>m#n567uJ{JM=hjS)dmu_ z5|*(i;`6b3t%f%y)tS;oZrp>RDy#(URv$PO7Wq56X>WVCbK=fhBCd8^e^cz@Mtf{_6{MlZJ8L1drtM?>ix&MI zJZF-)W>RyE>@jpJ#mObL2ila>=uf2^Yc-x}0mK6;-EOz%y0&$a6V^MUKaFT=>q7@F(jQTGnj9i0_~E7xRAYg7pJZE6^7&Qe9M*FJdCbVO}y-pGa+ zzu~J%I#woSS#EKSk9MU${=lERUK}_)?F!<3&k|85$wRHumwA z?SMMP&h1qH0WJJa>7F5Du#F(vr3V{;~-p@_>z-HpxaqE(BYGqNEAMn*~vz5JL= z-yx8Ti7+V#`@+K1STn)1rChhmk1Ak7a5hn2_Hhs+f2E(1%KqM{ny1l`@ZCYL<>Z0^ z!I8|5_#fa!=qU3&b9UpXx!~+#4fS~RoWW|49qJWh3llq-{W$q**ikIn>rzS!I>gP!%Q)4G#2mI6h(5DzN;NH3mr^@ z!HO|Ub?&a_+KQ7l)utJJDyRj+TBz4kjp_~vHWaK*GV}?FS6A7NS zBR`Ezw8kJ+3KNGcCE7U5)bj67bw3rd-BF(k^L8ukz5te+(ueP;SMBrrZux(>x38dC z@2R=UX&Og}`UY3wZi|-?i6d4;b>kK0qQDVF;V<__S!k6EHtw1^TI-x!XN*^H$Y5`R zq>!yFiFK*wgFz5@2p)^X+b#;3wZ#hEKz?Um4OKb|8 zzf{04fY#yy4RsdEZwq+}S{k~mr_i~BGxE08MZyaB_&wW1NC6~`mf73X*-pr`5G-_ zw_kZTGV!5K$%#m^>yiU0CZIPo+&qyOZMDsg!qpSa0^U`0iW;q+@Z0~kgN%q~Ysbk-lr58`Fuuy^bGIgDU>Q`@xl{xF-#<_bQ;}`6$d3Zs9|L<9cpZt&W wPY3&v`QyO-O}lmIf&XRveysRoxBsr_!2jQRZ*R>n@F$D!Fy0^D&;H8(0A;FQUH||9 literal 0 HcmV?d00001 diff --git a/test/regtest_projects/660_stop_script_from_procedure_stop_this_script.sb3 b/test/regtest_projects/660_stop_script_from_procedure_stop_this_script.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..0a2303e80cd0eb25f2ee482eea7bd219e3d65db2 GIT binary patch literal 1910 zcma)7do&d28y-f|j@7uO)@3oqx;N%xT&lIADPziQtf-h95k`oXF&NC)p?mxTRIlpuEd(U&e^M2<%?>W!+zUO_Mu|SzG004j- zz||%8oRJBz9wGw(=z;(MdC6;N7&VYWiZBZdr-nT62%xH?!8^ugiGYy18BtgM0E6{5 z1ivIe4mB>7GH<);MdhkS-PgH)SM~U(2Z$KUtl;-@&*+N%f()8?e(1>z7=zO*fC>lI zbyt2Y&D1{cA2yqrqu-!kJ_KOhl6EZ5G-{~Eo#-8?~G`)z5;>S(;p$rE*| z1y^@NR~4bAGc@2}qaX+2wL0)p@PReXT6-BA4N2Up6Ys8fcdB0<*th&VZ^ZD}7C+Ue zZNR4}c4GT?BIzv(zlL8S6%IeIa($&gQ@?+B^MQDDCu(nH(M$RVJ50m?uFW$KHJJ9~ z)#db$;_b^nFSLC-<=>X=6B)D9r2Lwr(Be7CFVYSxpD1H97WEVZG?fRhB?y1|*$h)b zi!gW&EmVyo@%5+Qn#A{6U3rHNR$`x-43N>+d!iZk0x=_R{ayZcp$GUkmxw}Z8@X$ z+4vF!;R6%oX1{2k6)+-961j&J?`2k4l-kPXXNFW-B!+4z-Ew32*9&cn$aW2Xq*S)C1nG~`ygvFZ zI86-T%JnzSX4u_Pc~qzBXv|@EA1^qmcf(m4nO3h9pAKOSnNtV*(xDYCI}e9I-Z#|D?6`b(t&z=apv&A5Ty7c&PL zJ>70K>;oQ8e0*786x0wgIRtNH<2U6+In>>?u4>$itkpva5jTyST`99l5U0xFi>NM^ z&l4G|L+{ctJFhzl4_RJb(${m3m=>vJtGam`S6{RezkjOG$!FHlOwQXi-RbH|GHjGN zBj0<=bTXg8vk!Ng@|L>fs>NP`$whuV-6W!#D)d&;zW=h9)jKC<5#u3G9p7qzc(ZCZ zq#kBPmD= z3JF1h6A4JPISEc6`%x_Y%+0M}C^!O!{!3J5XGkJUp6LVS6DrGXql_+D{@UJsLB~|K zotb1+QCzsWicP@ZMvB1O;;)yRoAxhw6eLJTS5g`c%krX^85~Wqv)|dY%gR0$KTQVZ zFZ{rHS5sDC(mEQ~MQom;gyY#Ms3@;_*NFJwzVNFjQmAyt4Ka<(qdEk?sOprrT{;l> ze&-!3n%=WccxK+ZDLfZ{z!QFL9paGqJaDl+5$b{T%Ij3^$13K1+_$TOt?)S0qp4RD zicI9NGT$fAqn%GN+k_s1RyjW zm}}Kus76O7OWhfuH{M_nh?MC@agJ6~@x-FYA|yTA+B1~56#Helrww^*chhI=urpRl zS_Sa`JxKD&|2Y42q|ce358U50oW#@q%lLg>@$+u~T~T+>f9t(7c8~0zETAN2Bsb%) F?0+WEJnH}e literal 0 HcmV?d00001 From 276222f3119e67b3ffa0f18750fe685548dce24e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 29 Mar 2026 17:32:14 +0200 Subject: [PATCH 6/8] Remove stop without sync instruction --- include/scratchcpp/compiler.h | 2 +- src/engine/compiler.cpp | 10 ---------- src/engine/internal/icodebuilder.h | 1 - .../internal/llvm/instructions/control.cpp | 18 +++++------------- .../internal/llvm/instructions/control.h | 1 - src/engine/internal/llvm/llvmcodebuilder.cpp | 6 ------ src/engine/internal/llvm/llvmcodebuilder.h | 1 - src/engine/internal/llvm/llvminstruction.h | 1 - test/compiler/compiler_test.cpp | 14 -------------- test/mocks/codebuildermock.h | 1 - 10 files changed, 6 insertions(+), 49 deletions(-) diff --git a/include/scratchcpp/compiler.h b/include/scratchcpp/compiler.h index b2f98078c..fea2a29ca 100644 --- a/include/scratchcpp/compiler.h +++ b/include/scratchcpp/compiler.h @@ -149,9 +149,9 @@ class LIBSCRATCHCPP_EXPORT Compiler void warp(); void createYield(); + void createStop(); void createThreadStop(); - void createStopWithoutSync(); void invalidateTarget(); diff --git a/src/engine/compiler.cpp b/src/engine/compiler.cpp index c94cde6e6..e87311771 100644 --- a/src/engine/compiler.cpp +++ b/src/engine/compiler.cpp @@ -711,16 +711,6 @@ void Compiler::createThreadStop() impl->builder->createThreadStop(); } -/*! - * Creates a stop script without synchronization instruction.\n - * Use this if synchronization is not possible at the stop point. - * \note Only use this when everything is synchronized, e. g. after a function call. - */ -void Compiler::createStopWithoutSync() -{ - impl->builder->createStopWithoutSync(); -} - /*! * Creates a sprite/stage invalidation point.\n * Use this if synchronization is not possible because the target has been deleted. diff --git a/src/engine/internal/icodebuilder.h b/src/engine/internal/icodebuilder.h index 3d9d661d6..16e81384a 100644 --- a/src/engine/internal/icodebuilder.h +++ b/src/engine/internal/icodebuilder.h @@ -100,7 +100,6 @@ class ICodeBuilder virtual void createStop() = 0; virtual void createThreadStop() = 0; - virtual void createStopWithoutSync() = 0; virtual void invalidateTarget() = 0; diff --git a/src/engine/internal/llvm/instructions/control.cpp b/src/engine/internal/llvm/instructions/control.cpp index 715bd9e68..22a34cfd8 100644 --- a/src/engine/internal/llvm/instructions/control.cpp +++ b/src/engine/internal/llvm/instructions/control.cpp @@ -64,10 +64,6 @@ ProcessResult Control::process(LLVMInstruction *ins) ret.next = buildThreadStop(ins); break; - case LLVMInstruction::Type::StopWithoutSync: - ret.next = buildStopWithoutSync(ins); - break; - case LLVMInstruction::Type::InvalidateTarget: ret.next = buildInvalidateTarget(ins); break; @@ -345,23 +341,19 @@ LLVMInstruction *Control::buildEndLoop(LLVMInstruction *ins) LLVMInstruction *Control::buildStop(LLVMInstruction *ins) { m_utils.syncVariables(); - return buildStopWithoutSync(ins); -} - -LLVMInstruction *Control::buildThreadStop(LLVMInstruction *ins) -{ - m_utils.syncVariables(); - m_builder.CreateBr(m_utils.endThreadBranch()); + m_builder.CreateBr(m_utils.endBranch()); llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_utils.llvmCtx(), "", m_utils.function()); m_builder.SetInsertPoint(nextBranch); return ins->next; } -LLVMInstruction *Control::buildStopWithoutSync(LLVMInstruction *ins) +LLVMInstruction *Control::buildThreadStop(LLVMInstruction *ins) { - m_builder.CreateBr(m_utils.endBranch()); + m_utils.syncVariables(); + m_builder.CreateBr(m_utils.endThreadBranch()); + llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_utils.llvmCtx(), "", m_utils.function()); m_builder.SetInsertPoint(nextBranch); diff --git a/src/engine/internal/llvm/instructions/control.h b/src/engine/internal/llvm/instructions/control.h index fbcb30fc9..9b18fdeb7 100644 --- a/src/engine/internal/llvm/instructions/control.h +++ b/src/engine/internal/llvm/instructions/control.h @@ -28,7 +28,6 @@ class Control : public InstructionGroup LLVMInstruction *buildEndLoop(LLVMInstruction *ins); LLVMInstruction *buildStop(LLVMInstruction *ins); LLVMInstruction *buildThreadStop(LLVMInstruction *ins); - LLVMInstruction *buildStopWithoutSync(LLVMInstruction *ins); LLVMInstruction *buildInvalidateTarget(LLVMInstruction *ins); }; diff --git a/src/engine/internal/llvm/llvmcodebuilder.cpp b/src/engine/internal/llvm/llvmcodebuilder.cpp index c57c3a8d6..d6f6bb1c8 100644 --- a/src/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/engine/internal/llvm/llvmcodebuilder.cpp @@ -573,12 +573,6 @@ void LLVMCodeBuilder::createThreadStop() m_instructions.addInstruction(ins); } -void LLVMCodeBuilder::createStopWithoutSync() -{ - auto ins = std::make_shared(LLVMInstruction::Type::StopWithoutSync, m_loopCondition); - m_instructions.addInstruction(ins); -} - void LLVMCodeBuilder::invalidateTarget() { auto ins = std::make_shared(LLVMInstruction::Type::InvalidateTarget, m_loopCondition); diff --git a/src/engine/internal/llvm/llvmcodebuilder.h b/src/engine/internal/llvm/llvmcodebuilder.h index 7c2549eaa..3fb986e5e 100644 --- a/src/engine/internal/llvm/llvmcodebuilder.h +++ b/src/engine/internal/llvm/llvmcodebuilder.h @@ -114,7 +114,6 @@ class LIBSCRATCHCPP_TEST_EXPORT LLVMCodeBuilder : public ICodeBuilder void createStop() override; void createThreadStop() override; - void createStopWithoutSync() override; void invalidateTarget() override; diff --git a/src/engine/internal/llvm/llvminstruction.h b/src/engine/internal/llvm/llvminstruction.h index 8525ecccd..98d815bd0 100644 --- a/src/engine/internal/llvm/llvminstruction.h +++ b/src/engine/internal/llvm/llvminstruction.h @@ -78,7 +78,6 @@ struct LLVMInstruction EndLoop, Stop, ThreadStop, - StopWithoutSync, InvalidateTarget, CallProcedure, ProcedureArg diff --git a/test/compiler/compiler_test.cpp b/test/compiler/compiler_test.cpp index 171211989..cb876c31f 100644 --- a/test/compiler/compiler_test.cpp +++ b/test/compiler/compiler_test.cpp @@ -1687,20 +1687,6 @@ TEST_F(CompilerTest, CreateThreadStop) compile(m_compiler.get(), block.get()); } -TEST_F(CompilerTest, CreateStopWithoutSync) -{ - - auto block = std::make_shared("", ""); - - block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { - EXPECT_CALL(*m_builder, createStopWithoutSync()); - compiler->createStopWithoutSync(); - return nullptr; - }); - - compile(m_compiler.get(), block.get()); -} - TEST_F(CompilerTest, InvalidateTarget) { diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h index 383ad3af1..9cdeba022 100644 --- a/test/mocks/codebuildermock.h +++ b/test/mocks/codebuildermock.h @@ -89,7 +89,6 @@ class CodeBuilderMock : public ICodeBuilder MOCK_METHOD(void, createStop, (), (override)); MOCK_METHOD(void, createThreadStop, (), (override)); - MOCK_METHOD(void, createStopWithoutSync, (), (override)); MOCK_METHOD(void, invalidateTarget, (), (override)); From 2557cd82fce7091ec8c974d5d6a52597a29722fe Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:23:51 +0200 Subject: [PATCH 7/8] LLVMBuildUtils: Do not return sentinel in non-standard scripts --- src/engine/internal/llvm/llvmbuildutils.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/engine/internal/llvm/llvmbuildutils.cpp b/src/engine/internal/llvm/llvmbuildutils.cpp index 0e64a8a9b..0e1ad5068 100644 --- a/src/engine/internal/llvm/llvmbuildutils.cpp +++ b/src/engine/internal/llvm/llvmbuildutils.cpp @@ -234,8 +234,7 @@ void LLVMBuildUtils::end(LLVMInstruction *lastInstruction, LLVMRegister *lastCon m_coroutine->endWithSentinel(threadEndSentinel()); else { // There's no need to return the sentinel value in standard scripts because they don't have any callers - m_coroutine->endWithSentinel(threadEndSentinel()); - // m_coroutine->end(); + m_coroutine->end(); } break; From e1b7e115e55780abf418599e2259f7ebd924064c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:24:40 +0200 Subject: [PATCH 8/8] Fix threads not stopping when stopped from resumed coroutines --- .../internal/llvm/instructions/procedures.cpp | 7 ++ src/engine/internal/llvm/llvmbuildutils.cpp | 3 + src/engine/internal/llvm/llvmfunctions.cpp | 22 +++++ src/engine/internal/llvm/llvmfunctions.h | 2 + test/llvm/llvmcodebuilder_test.cpp | 93 +++++++++++++++++++ 5 files changed, 127 insertions(+) diff --git a/src/engine/internal/llvm/instructions/procedures.cpp b/src/engine/internal/llvm/instructions/procedures.cpp index 682aefcd5..cac59650a 100644 --- a/src/engine/internal/llvm/instructions/procedures.cpp +++ b/src/engine/internal/llvm/instructions/procedures.cpp @@ -87,6 +87,13 @@ LLVMInstruction *Procedures::buildCallProcedure(LLVMInstruction *ins) m_builder.CreateCondBr(done, nextBranch, suspendBranch); m_builder.SetInsertPoint(nextBranch); + + // The thread could be stopped from the coroutine + llvm::BasicBlock *afterResumeBranch = llvm::BasicBlock::Create(llvmCtx, "", function); + llvm::Value *isFinished = m_builder.CreateCall(m_utils.functions().resolve_llvm_is_thread_finished(), m_utils.executionContextPtr()); + m_builder.CreateCondBr(isFinished, m_utils.endThreadBranch(), afterResumeBranch); + + m_builder.SetInsertPoint(afterResumeBranch); } m_utils.reloadVariables(); diff --git a/src/engine/internal/llvm/llvmbuildutils.cpp b/src/engine/internal/llvm/llvmbuildutils.cpp index 0e1ad5068..8dc05aaff 100644 --- a/src/engine/internal/llvm/llvmbuildutils.cpp +++ b/src/engine/internal/llvm/llvmbuildutils.cpp @@ -227,6 +227,9 @@ void LLVMBuildUtils::end(LLVMInstruction *lastInstruction, LLVMRegister *lastCon switch (m_codeType) { case Compiler::CodeType::Script: + // Mark the thread as finished + m_builder.CreateCall(m_functions.resolve_llvm_mark_thread_as_finished(), { m_executionContextPtr }); + // Return a sentinel value (special pointer) to terminate any procedure callers if (m_warp) m_builder.CreateRet(threadEndSentinel()); diff --git a/src/engine/internal/llvm/llvmfunctions.cpp b/src/engine/internal/llvm/llvmfunctions.cpp index b5f90d550..4681d846d 100644 --- a/src/engine/internal/llvm/llvmfunctions.cpp +++ b/src/engine/internal/llvm/llvmfunctions.cpp @@ -35,6 +35,16 @@ extern "C" { return static_cast(ctx)->getStringArray(functionId); } + + LIBSCRATCHCPP_EXPORT void llvm_mark_thread_as_finished(ExecutionContext *ctx) + { + static_cast(ctx)->setFinished(true); + } + + LIBSCRATCHCPP_EXPORT bool llvm_is_thread_finished(ExecutionContext *ctx) + { + return static_cast(ctx)->finished(); + } } LLVMFunctions::LLVMFunctions(LLVMCompilerContext *ctx, llvm::IRBuilder<> *builder) : @@ -282,6 +292,18 @@ llvm::FunctionCallee LLVMFunctions::resolve_llvm_get_string_array() return callee; } +llvm::FunctionCallee LLVMFunctions::resolve_llvm_mark_thread_as_finished() +{ + llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(*m_ctx->llvmCtx()), 0); + return resolveFunction("llvm_mark_thread_as_finished", llvm::FunctionType::get(m_builder->getVoidTy(), { pointerType }, false)); +} + +llvm::FunctionCallee LLVMFunctions::resolve_llvm_is_thread_finished() +{ + llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(*m_ctx->llvmCtx()), 0); + return resolveFunction("llvm_is_thread_finished", llvm::FunctionType::get(m_builder->getInt1Ty(), { pointerType }, false)); +} + llvm::FunctionCallee LLVMFunctions::resolve_string_pool_new() { return resolveFunction("string_pool_new", llvm::FunctionType::get(m_stringPtrType->getPointerTo(), false)); diff --git a/src/engine/internal/llvm/llvmfunctions.h b/src/engine/internal/llvm/llvmfunctions.h index 3aba66916..cf608c744 100644 --- a/src/engine/internal/llvm/llvmfunctions.h +++ b/src/engine/internal/llvm/llvmfunctions.h @@ -48,6 +48,8 @@ class LLVMFunctions llvm::FunctionCallee resolve_llvm_random_int64(); llvm::FunctionCallee resolve_llvm_random_bool(); llvm::FunctionCallee resolve_llvm_get_string_array(); + llvm::FunctionCallee resolve_llvm_mark_thread_as_finished(); + llvm::FunctionCallee resolve_llvm_is_thread_finished(); llvm::FunctionCallee resolve_string_pool_new(); llvm::FunctionCallee resolve_string_pool_free(); llvm::FunctionCallee resolve_string_alloc(); diff --git a/test/llvm/llvmcodebuilder_test.cpp b/test/llvm/llvmcodebuilder_test.cpp index df6d70f6f..82340901d 100644 --- a/test/llvm/llvmcodebuilder_test.cpp +++ b/test/llvm/llvmcodebuilder_test.cpp @@ -3686,6 +3686,99 @@ TEST_F(LLVMCodeBuilderTest, ProcedureThreadStop_NonWarp) ASSERT_TRUE(code->isFinished(ctx.get())); } +TEST_F(LLVMCodeBuilderTest, ProcedureThreadStop_NonWarp_AfterYield) +{ + Sprite sprite; + + // Inner procedure (proc2): yields via a repeat loop, then stops the thread + // This exercises the coroutine resume path where the sentinel must propagate + BlockPrototype prototype2; + prototype2.setProcCode("proc2"); + prototype2.setWarp(false); + + LLVMCodeBuilder *builder = m_utils.createBuilder(&sprite, &prototype2); + CompilerValue *v = builder->addConstValue("inner_before"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + // This repeat loop causes the coroutine to suspend (yield) on each iteration + v = builder->addConstValue(2); + builder->beginRepeatLoop(v); + { + v = builder->addConstValue("inner_loop"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + } + builder->endLoop(); + + // After the loop completes, stop the thread + builder->createThreadStop(); + + // This should NOT execute + v = builder->addConstValue("inner_after"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + auto proc2Code = builder->build(); + + // Outer procedure (proc1): calls proc2 + BlockPrototype prototype1; + prototype1.setProcCode("proc1"); + prototype1.setWarp(false); + + builder = m_utils.createBuilder(&sprite, &prototype1); + + v = builder->addConstValue("outer_before"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + builder->createProcedureCall(&prototype2, {}); + + // This should NOT execute (thread was stopped by proc2) + v = builder->addConstValue("outer_after"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + auto proc1Code = builder->build(); + + // Root script: calls proc1 + builder = m_utils.createBuilder(&sprite, false); + v = builder->addConstValue("script_before"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + builder->createProcedureCall(&prototype1, {}); + + // This should NOT execute (thread was stopped by proc2 via proc1) + v = builder->addConstValue("script_after"); + builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + auto code = builder->build(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + + // First run: enters the repeat loop in proc2, yields after first iteration + std::string expected1 = + "script_before\n" + "outer_before\n" + "inner_before\n" + "inner_loop\n"; + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected1); + ASSERT_FALSE(code->isFinished(ctx.get())); + + // Second run: second iteration of the repeat loop, yields again + std::string expected2 = "inner_loop\n"; + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected2); + ASSERT_FALSE(code->isFinished(ctx.get())); + + // Third run: loop is done, createThreadStop() fires, sentinel must propagate + // through the coroutine resume path back to the caller + // Neither "inner_after", "outer_after", nor "script_after" should print + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_TRUE(code->isFinished(ctx.get())); +} + TEST_F(LLVMCodeBuilderTest, HatPredicates) { Sprite sprite;