systhrd.inc 30 KB

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