From 2bbbaa905712e378048ff889d7d95ea1f79f9d2c Mon Sep 17 00:00:00 2001 From: suprstefan Date: Tue, 8 Sep 2026 21:13:52 +0200 Subject: [PATCH] Use different load instructions for "this" pointer in foreach based on the type of the container. --- sdk/angelscript/source/as_compiler.cpp | 12 ++- .../test_feature/source/test_foreach.cpp | 86 +++++++++++++++++++ 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/sdk/angelscript/source/as_compiler.cpp b/sdk/angelscript/source/as_compiler.cpp index 246fc8800..720d839c7 100644 --- a/sdk/angelscript/source/as_compiler.cpp +++ b/sdk/angelscript/source/as_compiler.cpp @@ -5345,6 +5345,10 @@ void asCCompiler::CompileForEachStatement(asCScriptNode* node, asCByteCode* bc) if (DeclareVariable("", rangeDt, rangeOffset, &rangeExpr.bc, node) < 0) return; + // The instruction used for loading the container "this" from rangeOffset. + // Object handles already contain a pointer while value types don't. + asEBCInstr rangeLoadInstruction = rangeDt.IsObjectHandle() ? asBC_PshVPtr : asBC_PSF; + CompileInitializationWithAssignment(&rangeExpr.bc, rangeDt, rangeNode, rangeOffset, 0, asVGM_VARIABLE, rangeNode, &rangeExpr); ProcessDeferredParams(&rangeExpr); rangeExpr.bc.OptimizeLocally(tempVariableOffsets); @@ -5545,7 +5549,7 @@ void asCCompiler::CompileForEachStatement(asCScriptNode* node, asCByteCode* bc) asCExprContext opForBeginExpr(engine); { - opForBeginExpr.bc.InstrSHORT(asBC_PshVPtr, short(rangeOffset)); + opForBeginExpr.bc.InstrSHORT(rangeLoadInstruction, short(rangeOffset)); asCArray args; int r = MakeFunctionCall( @@ -5567,7 +5571,7 @@ void asCCompiler::CompileForEachStatement(asCScriptNode* node, asCByteCode* bc) // Compile the condition statement asCExprContext opForEndExpr(engine); { - opForEndExpr.bc.InstrSHORT(asBC_PshVPtr, short(rangeOffset)); + opForEndExpr.bc.InstrSHORT(rangeLoadInstruction, short(rangeOffset)); asCArray args; asCExprContext arg(engine); @@ -5614,7 +5618,7 @@ void asCCompiler::CompileForEachStatement(asCScriptNode* node, asCByteCode* bc) asCExprContext next(engine); { asCExprContext opForNextExpr(engine); - opForNextExpr.bc.InstrSHORT(asBC_PshVPtr, short(rangeOffset)); + opForNextExpr.bc.InstrSHORT(rangeLoadInstruction, short(rangeOffset)); asCArray args; asCExprContext arg(engine); @@ -5691,7 +5695,7 @@ void asCCompiler::CompileForEachStatement(asCScriptNode* node, asCByteCode* bc) asCExprContext opForValueNExpr(engine); - opForValueNExpr.bc.InstrSHORT(asBC_PshVPtr, short(rangeOffset)); + opForValueNExpr.bc.InstrSHORT(rangeLoadInstruction, short(rangeOffset)); asCArray args; asCExprContext arg(engine); diff --git a/sdk/tests/test_feature/source/test_foreach.cpp b/sdk/tests/test_feature/source/test_foreach.cpp index de263b0e2..10ee8d984 100644 --- a/sdk/tests/test_feature/source/test_foreach.cpp +++ b/sdk/tests/test_feature/source/test_foreach.cpp @@ -88,6 +88,31 @@ int ScriptQueryOpForValue(const Query& q, const ScriptQueryIterator& it) return it.it; // Just returning the iterator index as a dummy value } +struct ValueContainer +{ + ValueContainer(asUINT* values, asUINT length) : values(values), length(length) {} + + asUINT length; + asUINT* values; +}; + +asUINT ValueContainerOpForBegin(ValueContainer&) +{ + return 0; +} +bool ValueContainerOpForEnd(ValueContainer& c, asUINT it) +{ + return it >= c.length; +} +asUINT ValueContainerOpForNext(ValueContainer&, asUINT it) +{ + return it + 1; +} +asUINT ValueContainerOpForValue(ValueContainer& c, asUINT it) +{ + return c.values[it]; +} + bool Test() { bool fail = false; @@ -887,6 +912,67 @@ bool Test() engine->ShutDownAndRelease(); } + // Foreach where the container is a value type + // https://github.com/anjo76/angelscript/issues/92 + { + asIScriptEngine* engine = asCreateScriptEngine(); + engine->SetMessageCallback(asMETHOD(CBufferedOutStream, Callback), &bout, asCALL_THISCALL); + bout.buffer = ""; + + RegisterStdString(engine); + + engine->RegisterGlobalFunction("void print(const string &in)", asFUNCTION(Print_Generic), asCALL_GENERIC); + g_printBuffer = ""; + + engine->RegisterObjectType("ValueContainer", sizeof(ValueContainer), asOBJ_VALUE | asOBJ_POD | asGetTypeTraits()); + engine->RegisterObjectMethod("ValueContainer", "uint opForBegin()", asFUNCTIONPR(ValueContainerOpForBegin, (ValueContainer&), asUINT), asCALL_CDECL_OBJFIRST); + engine->RegisterObjectMethod("ValueContainer", "bool opForEnd(uint it)", asFUNCTIONPR(ValueContainerOpForEnd, (ValueContainer&, asUINT), bool), asCALL_CDECL_OBJFIRST); + engine->RegisterObjectMethod("ValueContainer", "uint opForNext(uint it)", asFUNCTIONPR(ValueContainerOpForNext, (ValueContainer&, asUINT), asUINT), asCALL_CDECL_OBJFIRST); + engine->RegisterObjectMethod("ValueContainer", "uint opForValue(uint it)", asFUNCTIONPR(ValueContainerOpForValue, (ValueContainer&, asUINT), asUINT), asCALL_CDECL_OBJFIRST); + + asIScriptModule* mod = engine->GetModule(0, asGM_ALWAYS_CREATE); + mod->AddScriptSection("script", + "void main(ValueContainer c) {" + " foreach(auto a : c) {\n" + " print(format(\"{}\", a));\n" + " }\n" + "}\n"); + r = mod->Build(); + if (r < 0) + TEST_FAILED; + + asUINT values[] = { 4,3,2,1,0 }; + ValueContainer itr(values, (asUINT)(sizeof(values) / sizeof(values[0]))); + + asIScriptFunction* func = mod->GetFunctionByName("main"); + asIScriptContext* ctx = engine->RequestContext(); + r = ctx->Prepare(func); + if(r != asSUCCESS) + TEST_FAILED; + + r = ctx->SetArgObject(0, &itr); + if(r) + TEST_FAILED; + + r = ctx->Execute(); + if (r != asEXECUTION_FINISHED) + TEST_FAILED; + + if (g_printBuffer != "43210") + { + TEST_FAILED; + PRINTF("%s\n", g_printBuffer.c_str()); + } + + if (bout.buffer != "") + { + TEST_FAILED; + PRINTF("%s", bout.buffer.c_str()); + } + + engine->ShutDownAndRelease(); + } + // Success return fail; }