forked from LeenkxTeam/LNXSDK
		
	
		
			
	
	
		
			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. | ||
|  | 
 | ||
|  | 
 |