Skip to content

Commit 7beb1da

Browse files
authored
Merge pull request #661 from scratchcpp/fix_threads_stopping_from_procedures
Fix #660: Fix threads not stopping when stopped from procedures
2 parents 5c402bb + e1b7e11 commit 7beb1da

27 files changed

+548
-45
lines changed

include/scratchcpp/compiler.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,11 @@ class LIBSCRATCHCPP_EXPORT Compiler
149149
void warp();
150150

151151
void createYield();
152+
152153
void createStop();
153-
void createStopWithoutSync();
154+
void createThreadStop();
155+
156+
void invalidateTarget();
154157

155158
void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args);
156159

src/blocks/controlblocks.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ CompilerValue *ControlBlocks::compileStop(Compiler *compiler)
9090

9191
if (str == "all") {
9292
compiler->addFunctionCallWithCtx("control_stop_all", Compiler::StaticType::Void);
93-
compiler->createStop();
93+
compiler->invalidateTarget(); // if this is a clone, it doesn't exist anymore
94+
compiler->createThreadStop();
9495
} else if (str == "this script")
9596
compiler->createStop();
9697
else if (str == "other scripts in sprite" || str == "other scripts in stage")
@@ -181,7 +182,8 @@ CompilerValue *ControlBlocks::compileDeleteThisClone(Compiler *compiler)
181182
{
182183
CompilerValue *deleted = compiler->addTargetFunctionCall("control_delete_this_clone", Compiler::StaticType::Bool);
183184
compiler->beginIfStatement(deleted);
184-
compiler->createStopWithoutSync(); // sync happens before the function call
185+
compiler->invalidateTarget(); // the sprite doesn't exist anymore
186+
compiler->createThreadStop(); // callers should be stopped too
185187
compiler->endIf();
186188
return nullptr;
187189
}

src/engine/compiler.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -705,14 +705,19 @@ void Compiler::createStop()
705705
impl->builder->createStop();
706706
}
707707

708+
/*! Creates a stop thread (current script and procedure callers) instruction. */
709+
void Compiler::createThreadStop()
710+
{
711+
impl->builder->createThreadStop();
712+
}
713+
708714
/*!
709-
* Creates a stop script without synchronization instruction.\n
710-
* Use this if synchronization is not possible at the stop point.
711-
* \note Only use this when everything is synchronized, e. g. after a function call.
715+
* Creates a sprite/stage invalidation point.\n
716+
* Use this if synchronization is not possible because the target has been deleted.
712717
*/
713-
void Compiler::createStopWithoutSync()
718+
void Compiler::invalidateTarget()
714719
{
715-
impl->builder->createStopWithoutSync();
720+
impl->builder->invalidateTarget();
716721
}
717722

718723
/*! Creates a call to the procedure with the given prototype. */

src/engine/internal/icodebuilder.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ class ICodeBuilder
9999
virtual void yield() = 0;
100100

101101
virtual void createStop() = 0;
102-
virtual void createStopWithoutSync() = 0;
102+
virtual void createThreadStop() = 0;
103+
104+
virtual void invalidateTarget() = 0;
103105

104106
virtual void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) = 0;
105107
};

src/engine/internal/llvm/instructions/control.cpp

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,12 @@ ProcessResult Control::process(LLVMInstruction *ins)
6060
ret.next = buildStop(ins);
6161
break;
6262

63-
case LLVMInstruction::Type::StopWithoutSync:
64-
ret.next = buildStopWithoutSync(ins);
63+
case LLVMInstruction::Type::ThreadStop:
64+
ret.next = buildThreadStop(ins);
65+
break;
66+
67+
case LLVMInstruction::Type::InvalidateTarget:
68+
ret.next = buildInvalidateTarget(ins);
6569
break;
6670

6771
default:
@@ -337,14 +341,27 @@ LLVMInstruction *Control::buildEndLoop(LLVMInstruction *ins)
337341
LLVMInstruction *Control::buildStop(LLVMInstruction *ins)
338342
{
339343
m_utils.syncVariables();
340-
return buildStopWithoutSync(ins);
344+
345+
m_builder.CreateBr(m_utils.endBranch());
346+
llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_utils.llvmCtx(), "", m_utils.function());
347+
m_builder.SetInsertPoint(nextBranch);
348+
349+
return ins->next;
341350
}
342351

343-
LLVMInstruction *Control::buildStopWithoutSync(LLVMInstruction *ins)
352+
LLVMInstruction *Control::buildThreadStop(LLVMInstruction *ins)
344353
{
345-
m_builder.CreateBr(m_utils.endBranch());
354+
m_utils.syncVariables();
355+
m_builder.CreateBr(m_utils.endThreadBranch());
356+
346357
llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_utils.llvmCtx(), "", m_utils.function());
347358
m_builder.SetInsertPoint(nextBranch);
348359

349360
return ins->next;
350361
}
362+
363+
LLVMInstruction *Control::buildInvalidateTarget(LLVMInstruction *ins)
364+
{
365+
m_utils.invalidateTarget();
366+
return ins->next;
367+
}

src/engine/internal/llvm/instructions/control.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ class Control : public InstructionGroup
2727
LLVMInstruction *buildBeginLoopCondition(LLVMInstruction *ins);
2828
LLVMInstruction *buildEndLoop(LLVMInstruction *ins);
2929
LLVMInstruction *buildStop(LLVMInstruction *ins);
30-
LLVMInstruction *buildStopWithoutSync(LLVMInstruction *ins);
30+
LLVMInstruction *buildThreadStop(LLVMInstruction *ins);
31+
LLVMInstruction *buildInvalidateTarget(LLVMInstruction *ins);
3132
};
3233

3334
} // namespace libscratchcpp::llvmins

src/engine/internal/llvm/instructions/procedures.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,17 @@ LLVMInstruction *Procedures::buildCallProcedure(LLVMInstruction *ins)
6666
args.push_back(m_utils.createValue(arg.second));
6767
}
6868

69+
// Call the procedure
6970
llvm::Value *handle = m_builder.CreateCall(m_utils.functions().resolveFunction(name, type), args);
7071

72+
// Check for end thread sentinel value
73+
llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(llvmCtx, "", function);
74+
llvm::Value *endThread = m_builder.CreateICmpEQ(handle, m_utils.threadEndSentinel());
75+
m_builder.CreateCondBr(endThread, m_utils.endThreadBranch(), nextBranch);
76+
m_builder.SetInsertPoint(nextBranch);
77+
7178
if (!m_utils.warp() && !ins->procedurePrototype->warp()) {
79+
// Handle suspend
7280
llvm::BasicBlock *suspendBranch = llvm::BasicBlock::Create(llvmCtx, "", function);
7381
llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(llvmCtx, "", function);
7482
m_builder.CreateCondBr(m_builder.CreateIsNull(handle), nextBranch, suspendBranch);
@@ -79,6 +87,13 @@ LLVMInstruction *Procedures::buildCallProcedure(LLVMInstruction *ins)
7987
m_builder.CreateCondBr(done, nextBranch, suspendBranch);
8088

8189
m_builder.SetInsertPoint(nextBranch);
90+
91+
// The thread could be stopped from the coroutine
92+
llvm::BasicBlock *afterResumeBranch = llvm::BasicBlock::Create(llvmCtx, "", function);
93+
llvm::Value *isFinished = m_builder.CreateCall(m_utils.functions().resolve_llvm_is_thread_finished(), m_utils.executionContextPtr());
94+
m_builder.CreateCondBr(isFinished, m_utils.endThreadBranch(), afterResumeBranch);
95+
96+
m_builder.SetInsertPoint(afterResumeBranch);
8297
}
8398

8499
m_utils.reloadVariables();

src/engine/internal/llvm/llvmbuildutils.cpp

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,13 @@ void LLVMBuildUtils::init(llvm::Function *function, BlockPrototype *procedurePro
161161
reloadVariables();
162162
reloadLists();
163163

164-
// Create end branch
164+
// Mark the target as valid
165+
m_targetValidFlag = m_builder.CreateAlloca(m_builder.getInt1Ty());
166+
m_builder.CreateStore(m_builder.getInt1(true), m_targetValidFlag);
167+
168+
// Create end branches
165169
m_endBranch = llvm::BasicBlock::Create(m_llvmCtx, "end", m_function);
170+
m_endThreadBranch = llvm::BasicBlock::Create(m_llvmCtx, "endThread", m_function);
166171
}
167172

168173
void LLVMBuildUtils::end(LLVMInstruction *lastInstruction, LLVMRegister *lastConstant)
@@ -184,9 +189,9 @@ void LLVMBuildUtils::end(LLVMInstruction *lastInstruction, LLVMRegister *lastCon
184189
syncVariables();
185190
m_builder.CreateBr(m_endBranch);
186191

192+
// End branch
187193
m_builder.SetInsertPoint(m_endBranch);
188194

189-
// End the script function
190195
llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
191196

192197
switch (m_codeType) {
@@ -216,6 +221,33 @@ void LLVMBuildUtils::end(LLVMInstruction *lastInstruction, LLVMRegister *lastCon
216221
m_builder.CreateRet(castValue(lastConstant, Compiler::StaticType::Bool));
217222
break;
218223
}
224+
225+
// Thread end branch (stop the entire thread, including procedure callers)
226+
m_builder.SetInsertPoint(m_endThreadBranch);
227+
228+
switch (m_codeType) {
229+
case Compiler::CodeType::Script:
230+
// Mark the thread as finished
231+
m_builder.CreateCall(m_functions.resolve_llvm_mark_thread_as_finished(), { m_executionContextPtr });
232+
233+
// Return a sentinel value (special pointer) to terminate any procedure callers
234+
if (m_warp)
235+
m_builder.CreateRet(threadEndSentinel());
236+
else if (m_procedurePrototype)
237+
m_coroutine->endWithSentinel(threadEndSentinel());
238+
else {
239+
// There's no need to return the sentinel value in standard scripts because they don't have any callers
240+
m_coroutine->end();
241+
}
242+
243+
break;
244+
245+
case Compiler::CodeType::Reporter:
246+
case Compiler::CodeType::HatPredicate:
247+
// Procedures cannot be called by these scripts, so we don't have to return the sentinel value
248+
m_builder.CreateBr(m_endBranch);
249+
break;
250+
}
219251
}
220252

221253
LLVMCompilerContext *LLVMBuildUtils::compilerCtx() const
@@ -323,6 +355,17 @@ llvm::BasicBlock *LLVMBuildUtils::endBranch() const
323355
return m_endBranch;
324356
}
325357

358+
llvm::BasicBlock *LLVMBuildUtils::endThreadBranch() const
359+
{
360+
return m_endThreadBranch;
361+
}
362+
363+
llvm::Value *LLVMBuildUtils::threadEndSentinel() const
364+
{
365+
llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
366+
return m_builder.CreateIntToPtr(m_builder.getInt64(1), pointerType, "threadEndSentinel");
367+
}
368+
326369
BlockPrototype *LLVMBuildUtils::procedurePrototype() const
327370
{
328371
return m_procedurePrototype;
@@ -401,6 +444,12 @@ LLVMListPtr &LLVMBuildUtils::listPtr(List *list)
401444

402445
void LLVMBuildUtils::syncVariables()
403446
{
447+
llvm::BasicBlock *syncBlock = llvm::BasicBlock::Create(m_llvmCtx, "syncVariables", m_function);
448+
llvm::BasicBlock *syncNextBlock = llvm::BasicBlock::Create(m_llvmCtx, "syncVariables.next", m_function);
449+
m_builder.CreateCondBr(m_builder.CreateLoad(m_builder.getInt1Ty(), m_targetValidFlag), syncBlock, syncNextBlock);
450+
451+
m_builder.SetInsertPoint(syncBlock);
452+
404453
// Copy stack variables to the actual variables
405454
for (auto &[var, varPtr] : m_variablePtrs) {
406455
llvm::BasicBlock *copyBlock = llvm::BasicBlock::Create(m_llvmCtx, "syncVar", m_function);
@@ -414,6 +463,9 @@ void LLVMBuildUtils::syncVariables()
414463

415464
m_builder.SetInsertPoint(nextBlock);
416465
}
466+
467+
m_builder.CreateBr(syncNextBlock);
468+
m_builder.SetInsertPoint(syncNextBlock);
417469
}
418470

419471
void LLVMBuildUtils::reloadVariables()
@@ -444,6 +496,11 @@ void LLVMBuildUtils::reloadLists()
444496
}
445497
}
446498

499+
void LLVMBuildUtils::invalidateTarget()
500+
{
501+
m_builder.CreateStore(m_builder.getInt1(false), m_targetValidFlag);
502+
}
503+
447504
std::vector<LLVMIfStatement> &LLVMBuildUtils::ifStatements()
448505
{
449506
return m_ifStatements;

src/engine/internal/llvm/llvmbuildutils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ class LIBSCRATCHCPP_TEST_EXPORT LLVMBuildUtils
5656
size_t stringCount() const;
5757

5858
llvm::BasicBlock *endBranch() const;
59+
llvm::BasicBlock *endThreadBranch() const;
60+
61+
llvm::Value *threadEndSentinel() const;
5962

6063
BlockPrototype *procedurePrototype() const;
6164
bool warp() const;
@@ -79,6 +82,7 @@ class LIBSCRATCHCPP_TEST_EXPORT LLVMBuildUtils
7982
void syncVariables();
8083
void reloadVariables();
8184
void reloadLists();
85+
void invalidateTarget();
8286

8387
std::vector<LLVMIfStatement> &ifStatements();
8488
std::vector<LLVMLoop> &loops();
@@ -166,6 +170,7 @@ class LIBSCRATCHCPP_TEST_EXPORT LLVMBuildUtils
166170
llvm::Value *m_functionIdValue = nullptr;
167171

168172
llvm::BasicBlock *m_endBranch = nullptr;
173+
llvm::BasicBlock *m_endThreadBranch = nullptr;
169174

170175
llvm::StructType *m_valueDataType = nullptr;
171176
llvm::StructType *m_stringPtrType = nullptr;
@@ -182,6 +187,7 @@ class LIBSCRATCHCPP_TEST_EXPORT LLVMBuildUtils
182187
llvm::Value *m_targetVariables = nullptr;
183188
llvm::Value *m_targetLists = nullptr;
184189
llvm::Value *m_warpArg = nullptr;
190+
llvm::Value *m_targetValidFlag = nullptr;
185191

186192
std::unique_ptr<LLVMCoroutine> m_coroutine;
187193

src/engine/internal/llvm/llvmcodebuilder.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -567,9 +567,15 @@ void LLVMCodeBuilder::createStop()
567567
m_instructions.addInstruction(ins);
568568
}
569569

570-
void LLVMCodeBuilder::createStopWithoutSync()
570+
void LLVMCodeBuilder::createThreadStop()
571571
{
572-
auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::StopWithoutSync, m_loopCondition);
572+
auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::ThreadStop, m_loopCondition);
573+
m_instructions.addInstruction(ins);
574+
}
575+
576+
void LLVMCodeBuilder::invalidateTarget()
577+
{
578+
auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::InvalidateTarget, m_loopCondition);
573579
m_instructions.addInstruction(ins);
574580
}
575581

0 commit comments

Comments
 (0)