hxcpp uses a conservative stop-the-world GC, where the threads need to co-operate.
cpp.vm.Gc.enterGCFreeZone()from Haxe before a potentially blocking system call (filesystem, network, etc).
cpp.vm.Gc.exitGCFreeZone()from Haxe before making more GC calls.
When you create a thread from Haxe, it starts attached. Before a non-Haxe created thread can interact with
hxcpp, some care must be taken, since GC allocations are done using a GC context per thread, and all threads must respect the stopped world.
SetTopOfStack(int * inTop,bool inPush)-
inTop- pointer to top of stack to attach, or
0to remove stack. -
true. Recursive attachment/detachment.
emscriptenwhich goes up - but still use same terminology/picture, just change the less-thans to greater-thans in code.
Say the system starts each program stack at 10000, the stack might look like this, with local variables and arguments pushed on the stack:
10000 ----------------------------------------------- 9996 startup temp variable 9992 startup temp variable -- main function -- 9988 main return address - order and details of this are ABI specific 9984 char ** argv 9980 int argc
hxcpp then runs the main code, which starts with the macro
HX_TOP_OF_STACK, which expands to something like:
int t0 = 99; hx::SetTopOfStack(&t0,false); ... __boot_all(); __hxcpp_main(); -- main function -- 9988 main return address order and details of this are ABI specific 9984 char ** argv 9980 int argc 9976 int t0 -- hx::SetTopOfStack -- records '9976' as top of stack for this thread
Later, many generated functions deep,
__hxcpp_main generates an allocation call which triggers a collection.
... 8100 Array<Bullet> bullets -- alloc Enemy -- ... -- Call collect -- 8050 int bottomOfStackTemp MarkConservative(&bottomOfStackTemp, 9976) -> scans stack from 8050 -> 9976 MarkConservative(Capture registers)
Enter/exit use a similar technique, where the registers are captured and the
bottomOfStack is 'locked-in' when the "enter GC-free zone" call is made.
8100 Array<Bullet> bullets -- EnterGCFreeZone -- 8088 int bottomOfStackTemp thread->setBottomOfStack(&bottomOfStackTemp) thread->captureRegisters() return * any changes here will not affect GC
Now, when another thread performs a collection, the GC-free thread can be scanned from 8088 to 9976, regardless of any stuff happening lower down the stack.
Top of stack can be tricky to get right when a GUI framework does not really have a "main".
10000 ----------------------------------------------- 9996 startup temp variable 9992 startup temp variable -- main function -- setupWindows(onReadyCallback)...... ... 8000 -- onReadyCallback -- 7976 int t0 SetTopOfStack(&t0,false) -> 7966 __hxcpp_main(); setOnFrameCallack(haxeOnFrame) return;
haxeOnFrame callback is triggered, but not "below"
9800 -- haxeOnFrame --- // Top of stack will be below bottom of stack.
inForce = false-
gc_set_top_of_stack(void * inTopOfStack,bool inForce);
hxcpp_mainand reattach each callback - Android solution because render callbacks happen on different threads -
gc_set_top_of_stack(&base,true);- attach -
hxcppwill check for calls from unattached threads.
hxcppcan log conservative ranges. With a native debugger you can check the address of your local variables and ensure they are included.
hxcppwill scan native objects on the stack, but will not follow non-Haxe pointers to other objects, so additional GC roots may be required.