systhrd.inc 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027
  1. {%MainUnit system.pp}
  2. {
  3. This file is part of the Free Pascal run time library.
  4. Copyright (c) 2022 by Nikolay Nikolov,
  5. member of the Free Pascal development team.
  6. WASI threading support implementation
  7. See the file COPYING.FPC, included in this distribution,
  8. for details about the copyright.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  12. **********************************************************************}
  13. {$ifndef FPC_WASM_THREADS}
  14. {$fatal This file shouldn't be included if thread support is disabled!}
  15. {$endif FPC_WASM_THREADS}
  16. {$DEFINE FPC_WASM_MAIN_THREAD_CAN_WAIT}
  17. {$DEFINE FPC_WASM_WORKER_THREADS_CAN_WAIT}
  18. {//$DEFINE DEBUGWASMTHREADS}
  19. Const
  20. MaxThreadSignal = high(uint32); // maximum threads to signal
  21. Type
  22. TThreadState = (tsNone,tsInit,tsRunning,tsCanceling,tsExit);
  23. TOSTime = __wasi_timestamp_t;
  24. // Forwards used in mutex
  25. Function GetClockTime: TOSTime; forward;
  26. Function IsWaitAllowed : Boolean; forward;
  27. Function GetSelfThread : TThreadID; forward;
  28. Function GetThreadState(aThread : TThreadID) : TThreadState; forward;
  29. {$i wasmmem.inc}
  30. {$i wasmmutex.inc}
  31. Type
  32. PWasmRTLEvent = ^TWasmRTLEvent;
  33. TWasmRTLEvent = record
  34. Signal : Longint;
  35. Destroying : Boolean;
  36. end;
  37. PWasmThread = ^TWasmThread;
  38. TWasmThread = Record
  39. InitialStackPointer : Pointer;
  40. InitTLSBase : Pointer;
  41. ThreadHasFinished : Boolean;
  42. ID : LongInt; // Allocated by host
  43. ThreadFunction : TThreadFunc;
  44. ThreadFunctionArg : Pointer;
  45. State : TThreadState;
  46. DoneEvent : PWasmRTLEvent;
  47. Running : TWasmMutex;
  48. ExitCode : Cardinal;
  49. StackBlock : Pointer;
  50. TLSBlock : Pointer;
  51. StackSize : PtrUInt;
  52. ThreadName : Array of byte; // UTF8 name
  53. end;
  54. { EWasmThreadTerminate }
  55. EWasmThreadTerminate = class(TObject)
  56. strict private
  57. FExitCode : DWord;
  58. public
  59. constructor Create(AExitCode: DWord);
  60. property ExitCode: DWord read FExitCode;
  61. end;
  62. Var
  63. MainThread : TWasmThread;
  64. WasiThreadManager : TThreadManager;
  65. GlobalIsWorkerThread : Longint; section 'WebAssembly.Global';
  66. GlobalIsMainThread : Longint; section 'WebAssembly.Global';
  67. GlobalIsThreadBlockable : Longint; section 'WebAssembly.Global';
  68. GlobalCurrentThread : PWasmThread; section 'WebAssembly.Global';
  69. { EWasmThreadTerminate }
  70. constructor EWasmThreadTerminate.Create(AExitCode: DWord);
  71. begin
  72. FExitCode:=AExitCode;
  73. end;
  74. // Forward functions
  75. Function IsWaitAllowed : Boolean;
  76. begin
  77. IsWaitAllowed:=GlobalIsThreadBlockable<>0;
  78. end;
  79. Function GetClockTime: TOSTime;
  80. var
  81. NanoSecsPast: TOSTime;
  82. begin
  83. if __wasi_clock_time_get(__WASI_CLOCKID_REALTIME,1000000,@NanoSecsPast)=__WASI_ERRNO_SUCCESS then
  84. GetClockTime:=NanoSecsPast
  85. else
  86. GetClockTime:=0;
  87. end;
  88. Function GetSelfThread : TThreadID;
  89. begin
  90. GetSelfThread:=GlobalCurrentThread;
  91. end;
  92. Function GetMainThread : TThreadID;
  93. begin
  94. Result:=PWasmThread(@MainThread);
  95. end;
  96. Function GetThreadState(aThread : TThreadID) : TThreadState;
  97. begin
  98. GetThreadState:=PWasmThread(aThread)^.State
  99. end;
  100. function WasiInitManager: Boolean;
  101. begin
  102. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Initializing manager');{$ENDIF}
  103. FillChar(MainThread,SizeOf(MainThread),0);
  104. MainThread.State:=tsRunning;
  105. GlobalIsMainThread:=1;
  106. GlobalIsWorkerThread:=0;
  107. GlobalCurrentThread:=@MainThread;
  108. ThreadId:=@MainThread;
  109. {$IFDEF FPC_WASM_MAIN_THREAD_CAN_WAIT}
  110. GlobalIsThreadBlockable:=1;
  111. {$ELSE FPC_WASM_MAIN_THREAD_CAN_WAIT}
  112. GlobalIsThreadBlockable:=0;
  113. {$ENDIF FPC_WASM_MAIN_THREAD_CAN_WAIT}
  114. InitMutex(TWasmMutex(InitialHeapCriticalSection));
  115. InitialHeapCriticalSectionInitialized:=true;
  116. if TLSInfoBlock=Nil then
  117. TLSInfoBlock:=AllocateOSInfoBlock;
  118. {$IFDEF DEBUGWASMTHREADS}
  119. if TLSInfoBlock = Nil then
  120. DebugWriteln('Initializing manager done: failed');
  121. {$ENDIF}
  122. WasiInitManager:=True;
  123. end;
  124. function WasiDoneManager: Boolean;
  125. begin
  126. WasiDoneManager:=True;
  127. end;
  128. { ----------------------------------------------------------------------
  129. Critical section (mutex)
  130. ----------------------------------------------------------------------}
  131. procedure WasiInitCriticalSection(var cs);
  132. begin
  133. InitMutex(TWasmMutex(CS));
  134. end;
  135. procedure WasiDoneCriticalSection(var cs);
  136. begin
  137. DoneMutex(TWasmMutex(CS));
  138. end;
  139. procedure WasiEnterCriticalSection(var cs);
  140. begin
  141. LockMutex(TWasmMutex(CS));
  142. end;
  143. function WasiCriticalSectionTryEnter(var cs):longint;
  144. begin
  145. WasiCriticalSectionTryEnter:=Ord(TryLockMutex(TWasmMutex(CS)))
  146. end;
  147. procedure WasiLeaveCriticalSection(var cs);
  148. begin
  149. UnLockMutex(TWasmMutex(CS));
  150. end;
  151. { ----------------------------------------------------------------------
  152. RTL event
  153. ----------------------------------------------------------------------}
  154. function WasiRTLCreateEvent:PRTLEvent;
  155. Var
  156. P : PWasmRTLEvent;
  157. begin
  158. New(P);
  159. fpc_wasm32_i32_atomic_store(@P^.Signal,0);
  160. fpc_wasm32_i32_atomic_store8(@P^.Destroying,0);
  161. Result:=P;
  162. end;
  163. procedure WasiRTLEventSetEvent(AEvent:PRTLEvent);
  164. Var
  165. P : PWasmRTLEvent absolute aEvent;
  166. a : longint;
  167. begin
  168. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventSetEvent : setting signal=1');{$ENDIF}
  169. if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,0,1)=0 then
  170. begin
  171. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventSetEvent : notifying 1 waiting thread');{$ENDIF}
  172. a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),1);
  173. end
  174. else
  175. begin
  176. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventSetEvent : signal was already 1, nothing to do');{$ENDIF}
  177. end;
  178. end;
  179. procedure WasiRTLEventDestroy(AEvent:PRTLEvent);
  180. Var
  181. P : PWasmRTLEvent absolute aEvent;
  182. a : LongInt;
  183. begin
  184. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventDestroy : setting destroying to true');{$ENDIF}
  185. fpc_wasm32_i32_atomic_store8(@P^.Destroying,1);
  186. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventDestroy : setting event to notify others');{$ENDIF}
  187. fpc_wasm32_i32_atomic_store(@P^.Signal,1);
  188. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventDestroy : notifying waiting threads');{$ENDIF}
  189. a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),MaxThreadSignal);
  190. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventDestroy : freeing memory');{$ENDIF}
  191. Dispose(P);
  192. end;
  193. procedure WasiRTLEventResetEvent(AEvent:PRTLEvent);
  194. Var
  195. P : PWasmRTLEvent absolute aEvent;
  196. begin
  197. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventResetEvent : setting signal=0');{$ENDIF}
  198. fpc_wasm32_i32_atomic_store(@P^.Signal,0);
  199. end;
  200. procedure WasiRTLEventWaitFor_WaitAllowed(P:PWasmRTLEvent; aTimeoutNs : Int64);
  201. Var
  202. a : Longint;
  203. EndTime: TOSTime;
  204. RemainingTime: Int64;
  205. begin
  206. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  207. exit; // abandoned
  208. if aTimeOutNS>=0 then
  209. EndTime:=GetClockTime+aTimeOutNS
  210. else
  211. begin
  212. EndTime:=0;
  213. RemainingTime:=-1;
  214. end;
  215. repeat
  216. if aTimeOutNS>=0 then
  217. begin
  218. RemainingTime:=EndTime-GetClockTime;
  219. if RemainingTime<0 then
  220. exit; // timeout
  221. end;
  222. case fpc_wasm32_memory_atomic_wait32(@P^.Signal,0,RemainingTime) of
  223. 0, { "ok" }
  224. 1: { "not-equal" }
  225. begin
  226. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  227. exit // abandoned
  228. else if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,1,0)=1 then
  229. exit // signaled
  230. else
  231. ; { try waiting again (loop continues) }
  232. end;
  233. 2: { "timed-out" }
  234. exit; // timeout or abandoned
  235. else { invalid result from wait }
  236. exit; // error
  237. end;
  238. until false;
  239. end;
  240. procedure WasiRTLEventWaitFor_WaitNotAllowed(P:PWasmRTLEvent; aTimeoutNs : Int64);
  241. Var
  242. EndTime: TOSTime;
  243. RemainingTime: Int64;
  244. begin
  245. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  246. exit; // abandoned
  247. if aTimeOutNS>=0 then
  248. EndTime:=GetClockTime+aTimeOutNS
  249. else
  250. begin
  251. EndTime:=0;
  252. RemainingTime:=-1;
  253. end;
  254. repeat
  255. if aTimeOutNS>=0 then
  256. begin
  257. RemainingTime:=EndTime-GetClockTime;
  258. if RemainingTime<0 then
  259. exit; // timeout
  260. end;
  261. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  262. exit // abandoned
  263. else if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,1,0)=1 then
  264. exit; // signaled
  265. until false;
  266. end;
  267. procedure WasiRTLEventWaitFor(AEvent:PRTLEvent);
  268. Var
  269. P : PWasmRTLEvent absolute aEvent;
  270. begin
  271. if IsWaitAllowed then
  272. WasiRTLEventWaitFor_WaitAllowed(P,-1)
  273. else
  274. WasiRTLEventWaitFor_WaitNotAllowed(P,-1);
  275. end;
  276. procedure WasiRTLEventWaitForTimeout(AEvent:PRTLEvent;timeout : longint);
  277. Var
  278. P : PWasmRTLEvent absolute aEvent;
  279. TimeoutNs: Int64;
  280. begin
  281. if timeout=-1 then
  282. TimeoutNs:=-1
  283. else
  284. TimeoutNs:=Int64(timeout)*1000000;
  285. if IsWaitAllowed then
  286. WasiRTLEventWaitFor_WaitAllowed(P,TimeoutNs)
  287. else
  288. WasiRTLEventWaitFor_WaitNotAllowed(P,TimeoutNs);
  289. end;
  290. { ----------------------------------------------------------------------
  291. Thread
  292. ----------------------------------------------------------------------}
  293. //procedure FPCWasmThreadSetStackPointer(Address: Pointer); [public, alias: 'FPC_WASM_THREAD_SET_STACK_POINTER'];
  294. //begin
  295. // fpc_wasm32_set_base_pointer(Address);
  296. //end;
  297. // Javascript definition: TThreadInitInstanceFunction = Function(IsWorkerThread : Longint; IsMainThread : Integer; CanBlock : Integer) : Integer;
  298. //Function FPCWasmThreadInit(IsWorkerThread : Longint; IsMainThread : Longint; CanBlock : Longint) : Longint; [public, alias: 'FPC_WASM_THREAD_INIT'];
  299. //
  300. //begin
  301. // {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadInit('+IntToStr(IsWorkerThread)+','+IntToStr(IsMainThread)+','+IntToStr(CanBlock)+')');{$ENDIF}
  302. // GlobalIsWorkerThread:=IsWorkerThread;
  303. // GlobalIsMainThread:=IsMainThread;
  304. // GlobalIsThreadBlockable:=CanBlock;
  305. // Result:=0;
  306. //end;
  307. procedure WasiAllocateThreadVars; forward;
  308. // Javascript definition: TThreadEntryFunction = Function(ThreadId : Longint; RunFunction : Longint; Args : LongInt) : Longint;
  309. //Function FPCWasmThreadEntry(ThreadID : PWasmThread; RunFunction : Pointer; Args : Pointer) : Longint; [public, alias: 'FPC_WASM_THREAD_ENTRY'];
  310. //begin
  311. // {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadEntry('+IntToStr(PtrUint(ThreadID))+','+IntToStr(PtrUint(RunFunction))+','+IntToStr(PtrUint(Args))+')');{$ENDIF}
  312. // GlobalCurrentThread:=ThreadID;
  313. // {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadEntry: allocating threadvars (thread function: '+intToStr(PtrUint(RunFunction))+')');{$ENDIF}
  314. // WasiAllocateThreadVars;
  315. // {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadEntry: calling initthread (thread function: '+intToStr(PtrUint(RunFunction))+')');{$ENDIF}
  316. // InitThread;
  317. // {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadEntry: calling thread function '+intToStr(PtrUint(RunFunction)));{$ENDIF}
  318. // Result:=tthreadfunc(RunFunction)(args);
  319. //end;
  320. {$push}{$S-} // no stack checking for this procedure
  321. procedure FPCWasmThreadStartPascal(tid: longint; start_arg: PWasmThread);
  322. begin
  323. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadStartPascal('+IntToStr(tid)+','+IntToStr(ptrint(start_arg))+')');{$ENDIF}
  324. start_arg^.ID:=tid;
  325. GlobalCurrentThread:=start_arg;
  326. GlobalIsMainThread:=0;
  327. GlobalIsWorkerThread:=1;
  328. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Check : TID='+IntToStr(tid)+', start_arg_id='+IntToStr(start_arg^.ID)+', currentthread= '+IntTostr(ptrint(GetCurrentThreadID))+')');{$ENDIF}
  329. {$IFDEF FPC_WASM_WORKER_THREADS_CAN_WAIT}
  330. GlobalIsThreadBlockable:=1;
  331. {$ELSE FPC_WASM_WORKER_THREADS_CAN_WAIT}
  332. GlobalIsThreadBlockable:=0;
  333. {$ENDIF FPC_WASM_WORKER_THREADS_CAN_WAIT}
  334. start_arg^.State:=tsRunning;
  335. InitThread(start_arg^.StackSize);
  336. StackBottom:=start_arg^.StackBlock;
  337. try
  338. start_arg^.ExitCode:=Cardinal(start_arg^.ThreadFunction(start_arg^.ThreadFunctionArg));
  339. except
  340. on e: EWasmThreadTerminate do
  341. begin
  342. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadStartPascal: Caught EWasmThreadTerminate with ExitCode='+IntToStr(e.ExitCode));{$ENDIF}
  343. start_arg^.ExitCode:=e.ExitCode;
  344. end;
  345. else
  346. begin
  347. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadStartPascal: Uncaught exception');{$ENDIF}
  348. { TODO: what should we return here? }
  349. start_arg^.ExitCode:=High(Cardinal);
  350. end;
  351. end;
  352. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadStartPascal: Signaling end of thread');{$ENDIF}
  353. WasiRTLEventSetEvent(start_arg^.DoneEvent);
  354. end;
  355. {$pop}
  356. procedure wasi_thread_start(tid: longint; start_arg: PWasmThread); assembler; nostackframe;
  357. asm
  358. local.get 1 ;; start_arg
  359. i32.load ;; load InitialStackPointer
  360. global.set $__stack_pointer
  361. ;; call fpc_wasm32_init_tls from within assembly code, because in branchful
  362. ;; exceptions mode, Free Pascal generates threadvar access after every
  363. ;; function call. Therefore, we want threadvars to be initialized, before we
  364. ;; call any sort of Pascal code.
  365. local.get 1 ;; start_arg
  366. i32.const 4 ;; offset to InitTLSBase
  367. i32.add
  368. i32.load
  369. call $fpc_wasm32_init_tls
  370. local.get 0 ;; tid
  371. local.get 1 ;; start_arg
  372. call $FPCWasmThreadStartPascal
  373. ;; Set start_arg^.ThreadHasFinished to true.
  374. ;; This is done from within inline asm, after the pascal code has finished
  375. ;; executing, because it indicates that the thread no longer needs its TLS
  376. ;; block and linear stack block, so this means it's safe to free them.
  377. local.get 1 ;; start_arg
  378. i32.const 8 ;; offset to ThreadHasFinished
  379. i32.add
  380. i32.const 1 ;; true
  381. i32.atomic.store8
  382. end;
  383. exports wasi_thread_start, GetSelfThread, GetMainThread;
  384. Function wasi_thread_spawn(start_arg: PWasmThread) : LongInt; external 'wasi' name 'thread-spawn';
  385. { Just because we set the original pointer to nil, using InterlockedExchange
  386. to avoid race conditions leading to double free, doesn't mean this function is
  387. meant to be called more than once, or from multiple threads. This just adds
  388. some extra layer of protection. }
  389. procedure FreeStackAndTlsBlock(T : PWasmThread);
  390. var
  391. P: Pointer;
  392. begin
  393. P:=InterlockedExchange(T^.StackBlock,nil);
  394. if Assigned(P) then
  395. FreeMem(P);
  396. P:=InterlockedExchange(T^.TLSBlock,nil);
  397. if Assigned(P) then
  398. FreeMem(P);
  399. end;
  400. function WasiBeginThread(sa : Pointer;stacksize : PtrUInt; ThreadFunction : tthreadfunc;p : pointer;creationFlags : dword; var ThreadId : TThreadID) : TThreadID;
  401. Const
  402. HeapAlignment=16;
  403. Var
  404. T : PWasmThread;
  405. begin
  406. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread(sa: '+IntToStr(PtrUint(Sa))+',ss: '+IntToStr(PtrUint(StackSize))+',TF: '+IntToStr(PtrUint(ThreadFunction))+',Arg: '+IntToStr(PtrUint(P))+',fl: '+IntToStr(PtrUint(CreationFlags))+',ID: '+IntToStr(PtrUint(ThreadID))+')');{$ENDIF}
  407. IsMultiThread:=true;
  408. New(T);
  409. fpc_wasm32_i32_atomic_store8(@T^.ThreadHasFinished,0);
  410. T^.StackBlock:=nil;
  411. T^.TLSBlock:=nil;
  412. ThreadID:=T;
  413. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread thread ID : '+IntToStr(PtrUint(ThreadID)));{$ENDIF}
  414. T^.ThreadFunction:=ThreadFunction;
  415. T^.ThreadFunctionArg:=p;
  416. if stacksize<=0 then
  417. stacksize:=StkLen;
  418. T^.StackSize:=stacksize;
  419. T^.StackBlock:=GetMem(stacksize);
  420. T^.InitialStackPointer:=Pointer(PtrUInt(PtrUInt(T^.StackBlock)+stacksize) and $FFFFFFF0);
  421. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: InitialStackPointer='+IntToStr(PtrUint(T^.InitialStackPointer)));{$ENDIF}
  422. T^.TLSBlock:=AllocMem(fpc_wasm32_tls_size+fpc_wasm32_tls_align-1);
  423. T^.InitTLSBase:=Align(T^.TLSBlock,fpc_wasm32_tls_align);
  424. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: InitTLSBase='+IntToStr(PtrUint(T^.InitTLSBase)));{$ENDIF}
  425. InitMutex(T^.Running,mkNormal);
  426. T^.DoneEvent:=WasiRTLCreateEvent;
  427. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: Locked mutex');{$ENDIF}
  428. if wasi_thread_spawn(T)>0 then
  429. begin
  430. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread OK, setting result');{$ENDIF}
  431. WasiBeginThread:=T;
  432. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread OK, done setting result');{$ENDIF}
  433. end
  434. else
  435. begin
  436. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread failed');{$ENDIF}
  437. WasiRTLEventDestroy(T^.DoneEvent);
  438. DoneMutex(T^.Running);
  439. FreeStackAndTlsBlock(T);
  440. Dispose(T);
  441. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread failed, freeing thread struct');{$ENDIF}
  442. WasiBeginThread:=TThreadID(0);
  443. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread failed, returning 0');{$ENDIF}
  444. end
  445. end;
  446. procedure WasiEndThread(ExitCode : DWord);
  447. Var
  448. T : PWasmThread;
  449. begin
  450. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('EndThread('+IntToStr(ExitCode)+')');{$ENDIF}
  451. raise EWasmThreadTerminate.Create(ExitCode);
  452. end;
  453. function WasiSuspendThread(threadHandle : TThreadID) : dword;
  454. begin
  455. WasiSuspendThread:=DWord(-1);
  456. end;
  457. function WasiResumeThread(threadHandle : TThreadID) : dword;
  458. begin
  459. WasiResumeThread:=DWord(-1);
  460. end;
  461. function WasiKillThread(threadHandle : TThreadID) : dword;
  462. begin
  463. WasiKillThread:=DWord(-1);
  464. end;
  465. function WasiCloseThread(threadHandle : TThreadID) : dword;
  466. begin
  467. Result:=0;
  468. end;
  469. procedure WasiThreadSwitch;
  470. begin
  471. // Normally a yield, but this does not (yet) exist in webassembly.
  472. end;
  473. function WasiWaitForThreadTerminate(threadHandle : TThreadID; TimeoutMs : longint) : dword;
  474. Var
  475. Res : LongInt;
  476. TH : PWasmThread absolute ThreadHandle;
  477. TimeoutNs : Int64;
  478. begin
  479. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+','+IntToStr(TimeoutMs)+')');{$ENDIF}
  480. if TimeoutMs>=0 then
  481. TimeoutNs:=TimeoutMs*1000000
  482. else
  483. TimeoutNs:=-1;
  484. WasiRTLEventWaitFor(TH^.DoneEvent);
  485. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : Event set, waiting for lock');{$ENDIF}
  486. Case LockMuTexTimeout(PWasmThread(ThreadHandle)^.Running,TimeoutNs) of
  487. lmrOK : Res:=LongInt(TH^.ExitCode);
  488. lmrError : Res:=-2;
  489. else
  490. Res:=-1;
  491. end;
  492. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : Got Lock');{$ENDIF}
  493. UnLockMuTex(PWasmThread(ThreadHandle)^.Running);
  494. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : Lock released');{$ENDIF}
  495. WasiWaitForThreadTerminate:=DWord(Res);
  496. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : Waiting until ThreadHasFinished becomes true');{$ENDIF}
  497. repeat
  498. until fpc_wasm32_i32_atomic_load8_u(@TH^.ThreadHasFinished)<>0;
  499. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : FreeStackAndTlsBlock');{$ENDIF}
  500. FreeStackAndTlsBlock(TH);
  501. end;
  502. function WasiThreadSetPriority(threadHandle : TThreadID; Prio: longint): boolean;
  503. begin
  504. Result:=False;
  505. end;
  506. function WasiThreadGetPriority(threadHandle : TThreadID): longint;
  507. begin
  508. Result:=0;
  509. end;
  510. function WasiGetCurrentThreadId : TThreadID;
  511. begin
  512. Result:=GetSelfThread;
  513. end;
  514. procedure WasiThreadSetThreadDebugNameA(threadHandle: TThreadID; const ThreadName: AnsiString);
  515. Var
  516. P : PWasmThread absolute ThreadHandle;
  517. Len : Integer;
  518. begin
  519. Len:=Length(ThreadName);
  520. SetLength(P^.ThreadName,Len);
  521. if Len>0 then
  522. Move(ThreadName[1],P^.ThreadName[0],Len);
  523. end;
  524. {$ifdef FPC_HAS_FEATURE_UNICODESTRINGS}
  525. procedure WasiThreadSetThreadDebugNameU(threadHandle: TThreadID; const ThreadName: UnicodeString);
  526. Var
  527. P : PWasmThread absolute ThreadHandle;
  528. LThreadName : RawBytestring;
  529. Len : Integer;
  530. begin
  531. Len:=Length(LThreadName);
  532. LThreadName:=Utf8Encode(ThreadName);
  533. SetLength(P^.ThreadName,Len*SizeOf(UnicodeChar));
  534. if Len>0 then
  535. Move(LThreadName[1],P^.ThreadName[0],Len*SizeOf(UnicodeChar));
  536. end;
  537. {$endif FPC_HAS_FEATURE_UNICODESTRINGS}
  538. { ----------------------------------------------------------------------
  539. Threadvars
  540. ----------------------------------------------------------------------}
  541. Var
  542. threadvarblocksize : PtrUint;
  543. procedure WasiInitThreadVar(var offset : dword;size : dword);
  544. begin
  545. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiInitThreadVar('+IntToStr(offset)+','+IntToStr(size)+')');{$ENDIF}
  546. threadvarblocksize:=align(threadvarblocksize, fpc_wasm32_tls_align);
  547. offset:=threadvarblocksize;
  548. inc(threadvarblocksize,size);
  549. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Done WasiInitThreadVar. Total size: '+IntToStr(threadvarblocksize));{$ENDIF}
  550. end;
  551. procedure WasiAllocateThreadVars;
  552. var
  553. tlsMemBlock : pointer;
  554. tlsBlockSize : Integer;
  555. P : POSMemBlock;
  556. begin
  557. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiAllocateThreadVars');{$ENDIF}
  558. tlsBlockSize:=fpc_wasm32_tls_size;
  559. if threadvarblocksize<>tlsBlocksize then
  560. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Warning : block sizes differ: (linker) '+IntToStr(tlsBlocksize)+'<>'+IntToStr(threadvarblocksize)+' (calculated) !');{$ENDIF}
  561. P:=GetFreeOSBlock;
  562. FillChar((P^.Data)^.TLSMemory,tlsBlockSize,0);
  563. fpc_wasm32_init_tls(@((P^.Data)^.TLSMemory));
  564. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Done WasiAllocateThreadVars');{$ENDIF}
  565. end;
  566. Function GetTLSMemory : Pointer;
  567. begin
  568. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('GetTLSMemory Enter');{$ENDIF}
  569. GetTLSMemory:=fpc_wasm32_tls_base();
  570. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('GetTLSMemory exit: '+InttoStr(PtrUint(fpc_wasm32_tls_base())));{$ENDIF}
  571. end;
  572. procedure WasiReleaseThreadVars;
  573. Var
  574. PTLS : PTLSMem;
  575. begin
  576. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiReleaseThreadVars');{$ENDIF}
  577. PTLS:=GetTLSMemory-Sizeof(Pointer);
  578. ReleaseOSBlock(PTLS^.OSMemBlock);
  579. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiReleaseThreadVars done');{$ENDIF}
  580. end;
  581. procedure HookThread;
  582. { Set up externally created thread }
  583. begin
  584. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('HookThread');{$ENDIF}
  585. WasiAllocateThreadVars;
  586. InitThread(1000000000);
  587. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('HookThread done');{$ENDIF}
  588. end;
  589. function WasiRelocateThreadVar(offset : dword) : pointer;
  590. var
  591. P : Pointer;
  592. begin
  593. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRelocateThreadVar ('+IntToStr(offset)+')');{$ENDIF}
  594. P:=GetTLSMemory;
  595. if (P=Nil) then
  596. begin
  597. HookThread;
  598. P:=GetTLSMemory;
  599. end;
  600. WasiRelocateThreadvar:=P+Offset;
  601. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRelocateThreadVar done. Result: '+IntToStr(PtrUint(P+Offset)));{$ENDIF}
  602. end;
  603. { ----------------------------------------------------------------------
  604. Basic event
  605. ----------------------------------------------------------------------}
  606. const
  607. wrSignaled = 0;
  608. wrTimeout = 1;
  609. wrAbandoned = 2;
  610. wrError = 3;
  611. type
  612. PWasmBasicEventState = ^TWasmBasicEventState;
  613. TWasmBasicEventState = record
  614. Signal : Longint;
  615. ManualReset : Boolean;
  616. Destroying : Boolean;
  617. end;
  618. function WasiBasicEventCreate(EventAttributes :Pointer; AManualReset,InitialState : Boolean;const Name:ansistring):pEventState;
  619. var
  620. P: PWasmBasicEventState;
  621. begin
  622. New(P);
  623. fpc_wasm32_i32_atomic_store(@P^.Signal,Ord(InitialState));
  624. fpc_wasm32_i32_atomic_store8(@P^.ManualReset,Ord(AManualReset));
  625. fpc_wasm32_i32_atomic_store8(@P^.Destroying,0);
  626. Result:=P;
  627. end;
  628. procedure WasiBasicEventDestroy(state:peventstate);
  629. var
  630. P: PWasmBasicEventState absolute state;
  631. a: longword;
  632. begin
  633. fpc_wasm32_i32_atomic_store8(@P^.Destroying,1);
  634. fpc_wasm32_i32_atomic_store(@P^.Signal,1);
  635. a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),MaxThreadSignal);
  636. Dispose(P);
  637. end;
  638. procedure WasiBasicEventResetEvent(state:peventstate);
  639. var
  640. P: PWasmBasicEventState absolute state;
  641. begin
  642. fpc_wasm32_i32_atomic_store(@P^.Signal,0);
  643. end;
  644. procedure WasiBasicEventSetEvent(state:peventstate);
  645. var
  646. P: PWasmBasicEventState absolute state;
  647. a: longword;
  648. begin
  649. if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,0,1)=0 then
  650. begin
  651. if fpc_wasm32_i32_atomic_load8_u(@P^.ManualReset)<>0 then
  652. a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),MaxThreadSignal)
  653. else
  654. a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),1);
  655. end;
  656. end;
  657. function WasiBasicEventWaitFor_WaitAllowed(aTimeOutNS:Int64;P:PWasmBasicEventState):longint;
  658. var
  659. EndTime: TOSTime;
  660. RemainingTime: Int64;
  661. begin
  662. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  663. begin
  664. result:=wrAbandoned;
  665. exit;
  666. end;
  667. if fpc_wasm32_i32_atomic_load8_u(@P^.ManualReset)<>0 then
  668. begin
  669. { manual reset event }
  670. case fpc_wasm32_memory_atomic_wait32(@P^.Signal,0,aTimeOutNS) of
  671. 0, 1:
  672. result:=wrSignaled;
  673. 2:
  674. result:=wrTimeout;
  675. else
  676. result:=wrError;
  677. end;
  678. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  679. result:=wrAbandoned;
  680. end
  681. else
  682. begin
  683. { auto reset event }
  684. if aTimeOutNS>=0 then
  685. EndTime:=GetClockTime+aTimeOutNS
  686. else
  687. begin
  688. EndTime:=0;
  689. RemainingTime:=-1;
  690. end;
  691. repeat
  692. if aTimeOutNS>=0 then
  693. begin
  694. RemainingTime:=EndTime-GetClockTime;
  695. if RemainingTime<0 then
  696. begin
  697. result:=wrTimeout;
  698. exit;
  699. end;
  700. end;
  701. case fpc_wasm32_memory_atomic_wait32(@P^.Signal,0,RemainingTime) of
  702. 0, { "ok" }
  703. 1: { "not-equal" }
  704. begin
  705. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  706. begin
  707. result:=wrAbandoned;
  708. exit;
  709. end
  710. else if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,1,0)=1 then
  711. begin
  712. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  713. begin
  714. fpc_wasm32_i32_atomic_store(@P^.Signal,1);
  715. result:=wrAbandoned;
  716. exit;
  717. end
  718. else
  719. begin
  720. result:=wrSignaled;
  721. exit;
  722. end;
  723. end
  724. else
  725. ; { try waiting again (loop continues) }
  726. end;
  727. 2: { "timed-out" }
  728. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  729. begin
  730. result:=wrAbandoned;
  731. exit;
  732. end
  733. else
  734. begin
  735. result:=wrTimeout;
  736. exit;
  737. end;
  738. else { invalid result from wait }
  739. begin
  740. result:=wrError;
  741. exit;
  742. end;
  743. end;
  744. until false;
  745. end;
  746. end;
  747. function WasiBasicEventWaitFor_WaitNotAllowed(aTimeOutNS:Int64;P:PWasmBasicEventState):longint;
  748. var
  749. EndTime: TOSTime;
  750. RemainingTime: Int64;
  751. begin
  752. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  753. begin
  754. result:=wrAbandoned;
  755. exit;
  756. end;
  757. if aTimeOutNS>=0 then
  758. EndTime:=GetClockTime+aTimeOutNS
  759. else
  760. begin
  761. EndTime:=0;
  762. RemainingTime:=-1;
  763. end;
  764. if fpc_wasm32_i32_atomic_load8_u(@P^.ManualReset)<>0 then
  765. begin
  766. { manual reset event }
  767. repeat
  768. if aTimeOutNS>=0 then
  769. begin
  770. RemainingTime:=EndTime-GetClockTime;
  771. if RemainingTime<0 then
  772. begin
  773. result:=wrTimeout;
  774. exit;
  775. end;
  776. end;
  777. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  778. begin
  779. result:=wrAbandoned;
  780. exit;
  781. end
  782. else if fpc_wasm32_i32_atomic_load(@P^.Signal)<>0 then
  783. begin
  784. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  785. begin
  786. result:=wrAbandoned;
  787. exit;
  788. end
  789. else
  790. begin
  791. result:=wrSignaled;
  792. exit;
  793. end;
  794. end;
  795. until false;
  796. end
  797. else
  798. begin
  799. { auto reset event }
  800. repeat
  801. if aTimeOutNS>=0 then
  802. begin
  803. RemainingTime:=EndTime-GetClockTime;
  804. if RemainingTime<0 then
  805. begin
  806. result:=wrTimeout;
  807. exit;
  808. end;
  809. end;
  810. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  811. begin
  812. result:=wrAbandoned;
  813. exit;
  814. end
  815. else if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,1,0)=1 then
  816. begin
  817. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  818. begin
  819. fpc_wasm32_i32_atomic_store(@P^.Signal,1);
  820. result:=wrAbandoned;
  821. exit;
  822. end
  823. else
  824. begin
  825. result:=wrSignaled;
  826. exit;
  827. end;
  828. end;
  829. until false;
  830. end;
  831. end;
  832. function WasiBasicEventWaitFor(timeout:cardinal;state:peventstate;FUseComWait : Boolean=False):longint;
  833. var
  834. timeoutNS: Int64;
  835. begin
  836. if timeout<>$FFFFFFFF then
  837. timeoutNS:=timeout*1000000
  838. else
  839. timeoutNS:=-1;
  840. if isWaitAllowed then
  841. Result:=WasiBasicEventWaitFor_WaitAllowed(timeoutNS,PWasmBasicEventState(state))
  842. else
  843. Result:=WasiBasicEventWaitFor_WaitNotAllowed(timeoutNS,PWasmBasicEventState(state));
  844. end;
  845. procedure InitSystemThreads;public name '_FPC_InitSystemThreads';
  846. begin
  847. with WasiThreadManager do
  848. begin
  849. InitManager := @WasiInitManager;
  850. DoneManager := @WasiDoneManager;
  851. BeginThread := @WasiBeginThread;
  852. EndThread := @WasiEndThread;
  853. SuspendThread := @WasiSuspendThread;
  854. ResumeThread := @WasiResumeThread;
  855. KillThread := @WasiKillThread;
  856. CloseThread := @WasiCloseThread;
  857. ThreadSwitch := @WasiThreadSwitch;
  858. WaitForThreadTerminate := @WasiWaitForThreadTerminate;
  859. ThreadSetPriority := @WasiThreadSetPriority;
  860. ThreadGetPriority := @WasiThreadGetPriority;
  861. GetCurrentThreadId := @WasiGetCurrentThreadId;
  862. SetThreadDebugNameA := @WasiThreadSetThreadDebugNameA;
  863. {$ifdef FPC_HAS_FEATURE_UNICODESTRINGS}
  864. SetThreadDebugNameU := @WasiThreadSetThreadDebugNameU;
  865. {$endif FPC_HAS_FEATURE_UNICODESTRINGS}
  866. InitCriticalSection := @WasiInitCriticalSection;
  867. DoneCriticalSection := @WasiDoneCriticalSection;
  868. EnterCriticalSection := @WasiEnterCriticalSection;
  869. TryEnterCriticalSection:= @WasiCriticalSectionTryEnter;
  870. LeaveCriticalSection := @WasiLeaveCriticalSection;
  871. InitThreadVar := @WasiInitThreadVar;
  872. RelocateThreadVar := @WasiRelocateThreadVar;
  873. AllocateThreadVars := @WasiAllocateThreadVars;
  874. ReleaseThreadVars := @WasiReleaseThreadVars;
  875. BasicEventCreate := @WasiBasicEventCreate;
  876. BasicEventDestroy := @WasiBasicEventDestroy;
  877. BasicEventResetEvent := @WasiBasicEventResetEvent;
  878. BasicEventSetEvent := @WasiBasicEventSetEvent;
  879. BasiceventWaitFOr := @WasiBasicEventWaitFor;
  880. RTLEventCreate := @WasiRTLCreateEvent;
  881. RTLEventDestroy := @WasiRTLEventDestroy;
  882. RTLEventSetEvent := @WasiRTLEventSetEvent;
  883. RTLEventResetEvent := @WasiRTLEventResetEvent;
  884. RTLEventWaitFor := @WasiRTLEventWaitFor;
  885. RTLEventWaitForTimeout := @WasiRTLEventWaitForTimeout;
  886. end;
  887. SetThreadManager(WasiThreadManager);
  888. end;