Skip to content

Mark every loaded cppia module on every collection - #1406

Open
Tutez64 wants to merge 2 commits into
HaxeFoundation:masterfrom
Tutez64:bugfix/cppia-generational-mark
Open

Tutez64 wants to merge 2 commits into
HaxeFoundation:masterfrom
Tutez64:bugfix/cppia-generational-mark

Conversation

@Tutez64

@Tutez64 Tutez64 commented Sep 29, 2026

Copy link
Copy Markdown

With HXCPP_GC_GENERATIONAL, objects held by a loaded cppia module can be freed while still in use.

CppiaObject::__Mark is the only thing that marks a module (CppiaModule::mark: strings, types, markable, classes). A generational collection stops traversing an object once it is old, and a module is old soon after it is loaded. But what a module holds keeps changing after that, with no write barrier:

  • StringVal::runObject / DataVal::runObject box the literal on first use (value = strVal), and StringVal::genCode embeds that box in JIT code;
  • a script static variable is assigned (cppia classes register no mMarkFunc, so their statics are also only marked through the module).

The young object is then reachable only from the old module, so the next minor collection frees it, and a later use reads reused memory. In practice, a closure passing a string literal to a dynamic call worked the first time and crashed or printed garbage (Abstract(cpp.Pointer:…)) a minute later; a script static assigned after load did the same.

A standalone reproduction is attached: a scriptable host with HXCPP_GC_GENERATIONAL loads a small ASCII cppia script, lets the module grow old with full collections, then calls two script closures (one boxes a string literal, one assigns a script static) with only generational collections in between. On master the first round prints the right values and the second crashes, every run; with this change all five rounds pass.
hxcpp-cppia-generational-module.zip

The fix marks every loaded module on every collection, next to MarkClassStatics (and visits them next to VisitClassStatics under HXCPP_VISIT_ALLOCS), via the existing gAllCppiaModules list. A full collection marks a module twice, which is harmless. The cost is proportional to what the modules hold, as for class statics.

Tested on Linux: the reproduction crashes on master and passes with the change, and a game embedding cppia mods (generational, big blocks, HXCPP_CHECK_POINTER) no longer crashes on its second use of a closure. hxScript's cppia suite (447 tests) and its conformance corpus (hxcpp-cppia and hxcpp-cppia-jit, 351 ok / 5 expected throws) pass against it.

Tutez64 and others added 2 commits September 29, 2026 18:39
A module is an ordinary GC object, so a generational collection stops traversing it once it is old. What it holds still changes after that: a literal boxes itself on first use, the JIT embeds that box, a script static is assigned. None of those writes has a barrier, so a minor collection freed the young object while the module still pointed at it, and the next use read reused memory. Modules are now marked with the class statics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cppia host now builds with HXCPP_GC_GENERATIONAL. After full
collections make the module old, ClientModuleValues boxes a string
literal and assigns a static array, which only the module references.
Weak references then check that a generational collection keeps them.
Without the fix, both are freed without the JIT, and the array with it
(the JIT boxes the literal at load time).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Tutez64

Tutez64 commented Oct 3, 2026

Copy link
Copy Markdown
Author

Added a test to test/cppia in 3b88426

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant