diff --git a/src/hx/cppia/CppiaModule.cpp b/src/hx/cppia/CppiaModule.cpp index 2d029c52b..134929b1c 100644 --- a/src/hx/cppia/CppiaModule.cpp +++ b/src/hx/cppia/CppiaModule.cpp @@ -355,6 +355,39 @@ class CppiaObject : public hx::CppiaLoadedModule_obj }; +/* + A loaded module is an ordinary GC object, so a generational collection stops traversing it once it + is old. What it holds can still change after that: a literal boxes itself on first use, the JIT + embeds that box in its code, a script static is assigned. None of those writes has a barrier, so a + minor collection freed the young object while the old module still pointed at it. + + So every loaded module is marked on every collection, the way class statics are. +*/ +void scriptMarkModules(hx::MarkContext *__inCtx) +{ + if (!gAllCppiaModules.mPtr) + return; + for(int i=0;ilength;i++) + { + CppiaObject *module = static_cast(gAllCppiaModules[i].mPtr); + if (module) + module->cppia->mark(__inCtx); + } +} + +#ifdef HXCPP_VISIT_ALLOCS +void scriptVisitModules(hx::VisitContext *__inCtx) +{ + if (!gAllCppiaModules.mPtr) + return; + for(int i=0;ilength;i++) + { + CppiaObject *module = static_cast(gAllCppiaModules[i].mPtr); + if (module) + module->cppia->visit(__inCtx); + } +} +#endif CppiaLoadedModule LoadCppia(const unsigned char *inData, int inDataLength) diff --git a/src/hx/gc/Immix.cpp b/src/hx/gc/Immix.cpp index 9eb91603e..995041e34 100644 --- a/src/hx/gc/Immix.cpp +++ b/src/hx/gc/Immix.cpp @@ -459,6 +459,10 @@ DECLARE_FAST_TLS_DATA(StackContext, tlsStackContext); #ifdef HXCPP_SCRIPTABLE extern void scriptMarkStack(hx::MarkContext *); +extern void scriptMarkModules(hx::MarkContext *); +#ifdef HXCPP_VISIT_ALLOCS +extern void scriptVisitModules(hx::VisitContext *); +#endif #endif } @@ -4268,6 +4272,9 @@ class GlobalAllocator VisitLocalAlloc(mLocalAllocs[i], inCtx); hx::VisitClassStatics(inCtx); + #ifdef HXCPP_SCRIPTABLE + hx::scriptVisitModules(inCtx); + #endif for(hx::RootSet::iterator i = hx::sgRootSet.begin(); i!=hx::sgRootSet.end(); ++i) { @@ -4712,6 +4719,9 @@ class GlobalAllocator mMarker.init(); hx::MarkClassStatics(&mMarker); + #ifdef HXCPP_SCRIPTABLE + hx::scriptMarkModules(&mMarker); + #endif { hx::AutoMarkPush info(&mMarker,"Roots","root"); diff --git a/test/cppia/Client.hx b/test/cppia/Client.hx index 710373f4b..840274396 100644 --- a/test/cppia/Client.hx +++ b/test/cppia/Client.hx @@ -14,6 +14,24 @@ class ClientFoo implements IFoo { } } +class ClientModuleValues { + + static var kept:Array; + + // Read as Dynamic, the literal is boxed on first use, and the module keeps the box. + public static function literal():Dynamic { + var boxed:Dynamic = "a string literal"; + return boxed; + } + + // Assigned after load: the module holds the only reference. + public static function assigned():Dynamic { + if (kept == null) + kept = ["assigned after load"]; + return kept; + } +} + class Client { public static var clientBool0 = true; diff --git a/test/cppia/cases/TestCommon.hx b/test/cppia/cases/TestCommon.hx index eaacb5a76..d6eef8058 100644 --- a/test/cppia/cases/TestCommon.hx +++ b/test/cppia/cases/TestCommon.hx @@ -1,6 +1,8 @@ package cases; import cpp.cppia.Host; +import cpp.vm.Gc; +import cpp.vm.WeakRef; import utest.Test; import utest.Assert; @@ -59,6 +61,27 @@ class TestCommon extends Test { Assert.equals(2, Common.callbackSet, 'Bad cppia closure'); } + @:depends(testStatus) + function testModuleValuesCreatedWhenOld() { + final values = Type.resolveClass('ClientModuleValues'); + + // Full collections make the module old: a generational collection no longer traverses it. + for (_ in 0...3) { + Gc.run(true); + } + final created = createValues(values); + Gc.run(false); + + for (value in created) { + Assert.notNull(value.get(), 'Freed a value that the old module holds'); + } + } + + // Only weak references to the values leave this frame, so that the stack does not keep them. + static function createValues(values:Dynamic):Array> { + return [for (name in ['literal', 'assigned']) new WeakRef(Reflect.callMethod(values, Reflect.field(values, name), []))]; + } + @: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