153 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			153 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| 
								 | 
							
								Threads And Stacks
							 | 
						||
| 
								 | 
							
								-------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### Conservative, co-operation
							 | 
						||
| 
								 | 
							
								Hxcpp uses conservative stop-the-world GC, where the threads need to co-operate.
							 | 
						||
| 
								 | 
							
								 - Threads must not change GC pointers in the collection phase
							 | 
						||
| 
								 | 
							
								 - The thread stacks/registers must be scanned for GC pointers
							 | 
						||
| 
								 | 
							
								 - Threads must not block without letting the GC system know not to wait for them, otherwise GC blocks until end of block 
							 | 
						||
| 
								 | 
							
								   + call hx::GCEnterBlocking() / gc_enter_blocking() / (cpp.vm.Gc.enterGCFreeZone() from Haxe) before potentially blocking system call (fs, network, etc)
							 | 
						||
| 
								 | 
							
								   + call hx::GCExitBlocking() / gc_exit_blocking() / (cpp.vm.Gc.exitGCFreeZone() from Haxe) before making more GC calls
							 | 
						||
| 
								 | 
							
								   + Might need to pre-allocate buffers
							 | 
						||
| 
								 | 
							
								   + Don't forget the exit blocking on error condition
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### Foreign Threads
							 | 
						||
| 
								 | 
							
								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.
							 | 
						||
| 
								 | 
							
								  - Foreign threads must be attached-detached
							 | 
						||
| 
								 | 
							
								     - SetTopOfStack(int * inTop,bool inPush)
							 | 
						||
| 
								 | 
							
								     - *inTop* = pointer to top of stack to attach, or '0' to remove stack
							 | 
						||
| 
								 | 
							
								     - *inPush* = usually true.  recursive attachment/detachment
							 | 
						||
| 
								 | 
							
								  - Must not change things when the world is stopped
							 | 
						||
| 
								 | 
							
								  - Must define their stack range for scanning
							 | 
						||
| 
								 | 
							
								  - If you are attached, you may need to enter/exit gc free zone
							 | 
						||
| 
								 | 
							
								  - Must release context when done, if no more calls are going to be made
							 | 
						||
| 
								 | 
							
								  - Make sure local variables are covered in stack
							 | 
						||
| 
								 | 
							
								    - compiler may reorder, so be careful
							 | 
						||
| 
								 | 
							
								  - Read documentation because some things, eg audio callbacks, happen on other threads
							 | 
						||
| 
								 | 
							
								  - You can use other techniques, eg
							 | 
						||
| 
								 | 
							
								     - create a haxe thread, which blocks waiting for signal
							 | 
						||
| 
								 | 
							
								     - foreign thread generates request and signals haxe thread
							 | 
						||
| 
								 | 
							
								     - haxe thread performs job and generates data then signals foreign thread
							 | 
						||
| 
								 | 
							
								     - foreign picks up data and carries on
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### Top of Stack
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								 - To understand how to handle threads, you need a mental picture of the c++ stack
							 | 
						||
| 
								 | 
							
								 - The stack usually goes "down".  That is, if the first stack location is 10000, the next one will be 9999 etc.
							 | 
						||
| 
								 | 
							
								 - Historical, but consistent.  Except for emscripten which 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 it 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 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 does a collection, the gc-free thread can be scanned from 8088 to 9976, regardless of any stuff happening lower dowsn the stack.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### Not Called From Main 
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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;
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Later, the haxeOnFrame callback is trigger, but not "below" `__hxcpp_main`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								 9800  -- haxeOnFrame ---
							 | 
						||
| 
								 | 
							
								     // Top of stack will be below bottom of stack.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Solutions:
							 | 
						||
| 
								 | 
							
								  - Make sure you get in at top of main
							 | 
						||
| 
								 | 
							
								    + may scan too much?
							 | 
						||
| 
								 | 
							
								  - Ratchet up top-of-stack in callbacks, inForce = false
							 | 
						||
| 
								 | 
							
								    + gc_set_top_of_stack(void * inTopOfStack,bool inForce);
							 | 
						||
| 
								 | 
							
								  - Detach main thread after hxcpp_main and reattach each callback
							 | 
						||
| 
								 | 
							
								    + android solution because render callbacks happen on different threads
							 | 
						||
| 
								 | 
							
								    + gc_set_top_of_stack(&base,true); // attach
							 | 
						||
| 
								 | 
							
								    + gc_set_top_of_stack(0,true); // detach
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### Debugging.
							 | 
						||
| 
								 | 
							
								  - in debug mode, hxcpp will check for calls from unattached threads
							 | 
						||
| 
								 | 
							
								  - hxcpp can log conservative ranges.  With a native debugger you can check the address of
							 | 
						||
| 
								 | 
							
								     your local variables and ensure they are included.
							 | 
						||
| 
								 | 
							
								  - hxcpp will scan native objects on the stack, but will not follow non-haxe pointers to other objects, so additional GC roots may be required.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 |