From 32abec651d9775ca338da1d8a43c6363fd7a1190 Mon Sep 17 00:00:00 2001 From: "Enzo Persillet (Tutez)" Date: Sat, 3 Oct 2026 17:08:17 +0200 Subject: [PATCH] Align the objects that jitted cppia code allocates in the nursery. With HXCPP_GC_GENERATIONAL, ImmixAllocator::alloc skips 4 bytes when needed so that each nursery object is 8-byte aligned, and the nursery scan that finds conservatively referenced objects (GetEnclosingNurseryType) walks the holes with that alignment. The inline allocation that the JIT emits for a new did not align. After an object it placed off alignment, the scan read a wrong header and missed the objects that follow, so an object referenced only from the stack was freed by the next collection. The cppia host now builds with HXCPP_GC_GENERATIONAL. The new test has jitted code allocate two objects, then keeps an array only on the stack across a generational collection. Without the fix, the -jit run crashes. Co-Authored-By: Claude Opus 5.5 --- src/hx/cppia/Cppia.cpp | 6 ++++++ test/cppia/Client.hx | 13 +++++++++++++ test/cppia/cases/TestCommon.hx | 14 ++++++++++++++ test/cppia/compile-host.hxml | 1 + 4 files changed, 34 insertions(+) diff --git a/src/hx/cppia/Cppia.cpp b/src/hx/cppia/Cppia.cpp index ba68a29fa..ea5d1f43c 100644 --- a/src/hx/cppia/Cppia.cpp +++ b/src/hx/cppia/Cppia.cpp @@ -1619,6 +1619,12 @@ struct NewExpr : public CppiaDynamicExpr // sJitReturnReg = alloc->spaceFirst - will be the result if all goes well compiler->move(sJitReturnReg, sJitCtx.star(jtPointer, offsetof(hx::StackContext,spaceFirst) ) ); + #ifdef HXCPP_ALIGN_ALLOC + // As ImmixAllocator::alloc: skip 4 bytes to align the object, which the nursery scan expects + compiler->bitOp(bitOpAnd, sJitTemp1.as(jtInt), sJitReturnReg.as(jtInt), (int)4 ); + compiler->add(sJitReturnReg.as(jtPointer), sJitReturnReg.as(jtPointer), sJitTemp1.as(jtPointer) ); + #endif + // sJitTemp1 = end = spaceFirst + size + sizeof(int) compiler->add(sJitTemp1.as(jtPointer), sJitReturnReg.as(jtPointer), (int)(size + sizeof(int) ) ); diff --git a/test/cppia/Client.hx b/test/cppia/Client.hx index 710373f4b..36e73f776 100644 --- a/test/cppia/Client.hx +++ b/test/cppia/Client.hx @@ -14,6 +14,19 @@ class ClientFoo implements IFoo { } } +class ClientNursery { + + public var value:Dynamic; + + public function new() {} + + // With -jit, the jitted code allocates these in the nursery itself. + public static function allocate() { + new ClientNursery(); + new ClientNursery(); + } +} + class Client { public static var clientBool0 = true; diff --git a/test/cppia/cases/TestCommon.hx b/test/cppia/cases/TestCommon.hx index eaacb5a76..b317566aa 100644 --- a/test/cppia/cases/TestCommon.hx +++ b/test/cppia/cases/TestCommon.hx @@ -1,6 +1,7 @@ package cases; import cpp.cppia.Host; +import cpp.vm.Gc; import utest.Test; import utest.Assert; @@ -59,6 +60,19 @@ class TestCommon extends Test { Assert.equals(2, Common.callbackSet, 'Bad cppia closure'); } + @:depends(testStatus) + function testStackObjectAfterScriptAllocations() { + final nursery = Type.resolveClass('ClientNursery'); + Reflect.callMethod(nursery, Reflect.field(nursery, 'allocate'), []); + // Only the stack references it, so only the nursery scan finds it. + final kept = [42]; + + Gc.run(false); + final junk = [for (i in 0...100000) [i]]; + + Assert.equals(42, kept[0]); + } + @:depends(testStatus) function testInterfaceCalling() { final obj : IFoo = Type.createInstance(Type.resolveClass('ClientFoo'), []); diff --git a/test/cppia/compile-host.hxml b/test/cppia/compile-host.hxml index 76bf2d790..051e26186 100644 --- a/test/cppia/compile-host.hxml +++ b/test/cppia/compile-host.hxml @@ -1,6 +1,7 @@ -m CppiaHost HostExtendedRoot -D scriptable +-D HXCPP_GC_GENERATIONAL -D dll_export=host_classes.info -L utest --dce no