systhrd.inc 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024
  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(...)');{$ENDIF}
  324. start_arg^.ID:=tid;
  325. GlobalCurrentThread:=@start_arg;
  326. GlobalIsMainThread:=0;
  327. GlobalIsWorkerThread:=1;
  328. {$IFDEF FPC_WASM_WORKER_THREADS_CAN_WAIT}
  329. GlobalIsThreadBlockable:=1;
  330. {$ELSE FPC_WASM_WORKER_THREADS_CAN_WAIT}
  331. GlobalIsThreadBlockable:=0;
  332. {$ENDIF FPC_WASM_WORKER_THREADS_CAN_WAIT}
  333. start_arg^.State:=tsRunning;
  334. InitThread(start_arg^.StackSize);
  335. StackBottom:=start_arg^.StackBlock;
  336. try
  337. start_arg^.ExitCode:=Cardinal(start_arg^.ThreadFunction(start_arg^.ThreadFunctionArg));
  338. except
  339. on e: EWasmThreadTerminate do
  340. begin
  341. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadStartPascal: Caught EWasmThreadTerminate with ExitCode='+IntToStr(e.ExitCode));{$ENDIF}
  342. start_arg^.ExitCode:=e.ExitCode;
  343. end;
  344. else
  345. begin
  346. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadStartPascal: Uncaught exception');{$ENDIF}
  347. { TODO: what should we return here? }
  348. start_arg^.ExitCode:=High(Cardinal);
  349. end;
  350. end;
  351. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadStartPascal: Signaling end of thread');{$ENDIF}
  352. WasiRTLEventSetEvent(start_arg^.DoneEvent);
  353. end;
  354. {$pop}
  355. procedure wasi_thread_start(tid: longint; start_arg: PWasmThread); assembler; nostackframe;
  356. asm
  357. local.get 1 ;; start_arg
  358. i32.load ;; load InitialStackPointer
  359. global.set $__stack_pointer
  360. ;; call fpc_wasm32_init_tls from within assembly code, because in branchful
  361. ;; exceptions mode, Free Pascal generates threadvar access after every
  362. ;; function call. Therefore, we want threadvars to be initialized, before we
  363. ;; call any sort of Pascal code.
  364. local.get 1 ;; start_arg
  365. i32.const 4 ;; offset to InitTLSBase
  366. i32.add
  367. i32.load
  368. call $fpc_wasm32_init_tls
  369. local.get 0 ;; tid
  370. local.get 1 ;; start_arg
  371. call $FPCWasmThreadStartPascal
  372. ;; Set start_arg^.ThreadHasFinished to true.
  373. ;; This is done from within inline asm, after the pascal code has finished
  374. ;; executing, because it indicates that the thread no longer needs its TLS
  375. ;; block and linear stack block, so this means it's safe to free them.
  376. local.get 1 ;; start_arg
  377. i32.const 8 ;; offset to ThreadHasFinished
  378. i32.add
  379. i32.const 1 ;; true
  380. i32.atomic.store8
  381. end;
  382. exports wasi_thread_start, GetSelfThread, GetMainThread;
  383. Function wasi_thread_spawn(start_arg: PWasmThread) : LongInt; external 'wasi' name 'thread-spawn';
  384. { Just because we set the original pointer to nil, using InterlockedExchange
  385. to avoid race conditions leading to double free, doesn't mean this function is
  386. meant to be called more than once, or from multiple threads. This just adds
  387. some extra layer of protection. }
  388. procedure FreeStackAndTlsBlock(T : PWasmThread);
  389. var
  390. P: Pointer;
  391. begin
  392. P:=InterlockedExchange(T^.StackBlock,nil);
  393. if Assigned(P) then
  394. FreeMem(P);
  395. P:=InterlockedExchange(T^.TLSBlock,nil);
  396. if Assigned(P) then
  397. FreeMem(P);
  398. end;
  399. function WasiBeginThread(sa : Pointer;stacksize : PtrUInt; ThreadFunction : tthreadfunc;p : pointer;creationFlags : dword; var ThreadId : TThreadID) : TThreadID;
  400. Const
  401. HeapAlignment=16;
  402. Var
  403. T : PWasmThread;
  404. begin
  405. {$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}
  406. IsMultiThread:=true;
  407. New(T);
  408. fpc_wasm32_i32_atomic_store8(@T^.ThreadHasFinished,0);
  409. T^.StackBlock:=nil;
  410. T^.TLSBlock:=nil;
  411. ThreadID:=T;
  412. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread thread ID : '+IntToStr(PtrUint(ThreadID)));{$ENDIF}
  413. T^.ThreadFunction:=ThreadFunction;
  414. T^.ThreadFunctionArg:=p;
  415. if stacksize<=0 then
  416. stacksize:=StkLen;
  417. T^.StackSize:=stacksize;
  418. T^.StackBlock:=GetMem(stacksize);
  419. T^.InitialStackPointer:=Pointer(PtrUInt(PtrUInt(T^.StackBlock)+stacksize) and $FFFFFFF0);
  420. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: InitialStackPointer='+IntToStr(PtrUint(T^.InitialStackPointer)));{$ENDIF}
  421. T^.TLSBlock:=AllocMem(fpc_wasm32_tls_size+fpc_wasm32_tls_align-1);
  422. T^.InitTLSBase:=Align(T^.TLSBlock,fpc_wasm32_tls_align);
  423. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: InitTLSBase='+IntToStr(PtrUint(T^.InitTLSBase)));{$ENDIF}
  424. InitMutex(T^.Running,mkNormal);
  425. T^.DoneEvent:=WasiRTLCreateEvent;
  426. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: Locked mutex');{$ENDIF}
  427. if wasi_thread_spawn(T)>0 then
  428. begin
  429. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread OK, setting result');{$ENDIF}
  430. WasiBeginThread:=T;
  431. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread OK, done setting result');{$ENDIF}
  432. end
  433. else
  434. begin
  435. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread failed');{$ENDIF}
  436. WasiRTLEventDestroy(T^.DoneEvent);
  437. DoneMutex(T^.Running);
  438. FreeStackAndTlsBlock(T);
  439. Dispose(T);
  440. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread failed, freeing thread struct');{$ENDIF}
  441. WasiBeginThread:=TThreadID(0);
  442. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread failed, returning 0');{$ENDIF}
  443. end
  444. end;
  445. procedure WasiEndThread(ExitCode : DWord);
  446. Var
  447. T : PWasmThread;
  448. begin
  449. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('EndThread('+IntToStr(ExitCode)+')');{$ENDIF}
  450. raise EWasmThreadTerminate.Create(ExitCode);
  451. end;
  452. function WasiSuspendThread(threadHandle : TThreadID) : dword;
  453. begin
  454. WasiSuspendThread:=DWord(-1);
  455. end;
  456. function WasiResumeThread(threadHandle : TThreadID) : dword;
  457. begin
  458. WasiResumeThread:=DWord(-1);
  459. end;
  460. function WasiKillThread(threadHandle : TThreadID) : dword;
  461. begin
  462. WasiKillThread:=DWord(-1);
  463. end;
  464. function WasiCloseThread(threadHandle : TThreadID) : dword;
  465. begin
  466. Result:=0;
  467. end;
  468. procedure WasiThreadSwitch;
  469. begin
  470. // Normally a yield, but this does not (yet) exist in webassembly.
  471. end;
  472. function WasiWaitForThreadTerminate(threadHandle : TThreadID; TimeoutMs : longint) : dword;
  473. Var
  474. Res : LongInt;
  475. TH : PWasmThread absolute ThreadHandle;
  476. TimeoutNs : Int64;
  477. begin
  478. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+','+IntToStr(TimeoutMs)+')');{$ENDIF}
  479. if TimeoutMs>=0 then
  480. TimeoutNs:=TimeoutMs*1000000
  481. else
  482. TimeoutNs:=-1;
  483. WasiRTLEventWaitFor(TH^.DoneEvent);
  484. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : Event set, waiting for lock');{$ENDIF}
  485. Case LockMuTexTimeout(PWasmThread(ThreadHandle)^.Running,TimeoutNs) of
  486. lmrOK : Res:=LongInt(TH^.ExitCode);
  487. lmrError : Res:=-2;
  488. else
  489. Res:=-1;
  490. end;
  491. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : Got Lock');{$ENDIF}
  492. UnLockMuTex(PWasmThread(ThreadHandle)^.Running);
  493. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : Lock released');{$ENDIF}
  494. WasiWaitForThreadTerminate:=DWord(Res);
  495. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : Waiting until ThreadHasFinished becomes true');{$ENDIF}
  496. repeat
  497. until fpc_wasm32_i32_atomic_load8_u(@TH^.ThreadHasFinished)<>0;
  498. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : FreeStackAndTlsBlock');{$ENDIF}
  499. FreeStackAndTlsBlock(TH);
  500. end;
  501. function WasiThreadSetPriority(threadHandle : TThreadID; Prio: longint): boolean;
  502. begin
  503. Result:=False;
  504. end;
  505. function WasiThreadGetPriority(threadHandle : TThreadID): longint;
  506. begin
  507. Result:=0;
  508. end;
  509. function WasiGetCurrentThreadId : TThreadID;
  510. begin
  511. Result:=GetSelfThread;
  512. end;
  513. procedure WasiThreadSetThreadDebugNameA(threadHandle: TThreadID; const ThreadName: AnsiString);
  514. Var
  515. P : PWasmThread absolute ThreadHandle;
  516. Len : Integer;
  517. begin
  518. Len:=Length(ThreadName);
  519. SetLength(P^.ThreadName,Len);
  520. if Len>0 then
  521. Move(ThreadName[1],P^.ThreadName[0],Len);
  522. end;
  523. {$ifdef FPC_HAS_FEATURE_UNICODESTRINGS}
  524. procedure WasiThreadSetThreadDebugNameU(threadHandle: TThreadID; const ThreadName: UnicodeString);
  525. Var
  526. P : PWasmThread absolute ThreadHandle;
  527. LThreadName : RawBytestring;
  528. Len : Integer;
  529. begin
  530. Len:=Length(LThreadName);
  531. LThreadName:=Utf8Encode(ThreadName);
  532. SetLength(P^.ThreadName,Len*SizeOf(UnicodeChar));
  533. if Len>0 then
  534. Move(LThreadName[1],P^.ThreadName[0],Len*SizeOf(UnicodeChar));
  535. end;
  536. {$endif FPC_HAS_FEATURE_UNICODESTRINGS}
  537. { ----------------------------------------------------------------------
  538. Threadvars
  539. ----------------------------------------------------------------------}
  540. Var
  541. threadvarblocksize : PtrUint;
  542. procedure WasiInitThreadVar(var offset : dword;size : dword);
  543. begin
  544. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiInitThreadVar('+IntToStr(offset)+','+IntToStr(size)+')');{$ENDIF}
  545. threadvarblocksize:=align(threadvarblocksize, fpc_wasm32_tls_align);
  546. offset:=threadvarblocksize;
  547. inc(threadvarblocksize,size);
  548. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Done WasiInitThreadVar. Total size: '+IntToStr(threadvarblocksize));{$ENDIF}
  549. end;
  550. procedure WasiAllocateThreadVars;
  551. var
  552. tlsMemBlock : pointer;
  553. tlsBlockSize : Integer;
  554. P : POSMemBlock;
  555. begin
  556. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiAllocateThreadVars');{$ENDIF}
  557. tlsBlockSize:=fpc_wasm32_tls_size;
  558. if threadvarblocksize<>tlsBlocksize then
  559. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Warning : block sizes differ: (linker) '+IntToStr(tlsBlocksize)+'<>'+IntToStr(threadvarblocksize)+' (calculated) !');{$ENDIF}
  560. P:=GetFreeOSBlock;
  561. FillChar((P^.Data)^.TLSMemory,tlsBlockSize,0);
  562. fpc_wasm32_init_tls(@((P^.Data)^.TLSMemory));
  563. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Done WasiAllocateThreadVars');{$ENDIF}
  564. end;
  565. Function GetTLSMemory : Pointer;
  566. begin
  567. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('GetTLSMemory Enter');{$ENDIF}
  568. GetTLSMemory:=fpc_wasm32_tls_base();
  569. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('GetTLSMemory exit: '+InttoStr(PtrUint(fpc_wasm32_tls_base())));{$ENDIF}
  570. end;
  571. procedure WasiReleaseThreadVars;
  572. Var
  573. PTLS : PTLSMem;
  574. begin
  575. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiReleaseThreadVars');{$ENDIF}
  576. PTLS:=GetTLSMemory-Sizeof(Pointer);
  577. ReleaseOSBlock(PTLS^.OSMemBlock);
  578. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiReleaseThreadVars done');{$ENDIF}
  579. end;
  580. procedure HookThread;
  581. { Set up externally created thread }
  582. begin
  583. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('HookThread');{$ENDIF}
  584. WasiAllocateThreadVars;
  585. InitThread(1000000000);
  586. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('HookThread done');{$ENDIF}
  587. end;
  588. function WasiRelocateThreadVar(offset : dword) : pointer;
  589. var
  590. P : Pointer;
  591. begin
  592. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRelocateThreadVar ('+IntToStr(offset)+')');{$ENDIF}
  593. P:=GetTLSMemory;
  594. if (P=Nil) then
  595. begin
  596. HookThread;
  597. P:=GetTLSMemory;
  598. end;
  599. WasiRelocateThreadvar:=P+Offset;
  600. {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRelocateThreadVar done. Result: '+IntToStr(PtrUint(P+Offset)));{$ENDIF}
  601. end;
  602. { ----------------------------------------------------------------------
  603. Basic event
  604. ----------------------------------------------------------------------}
  605. const
  606. wrSignaled = 0;
  607. wrTimeout = 1;
  608. wrAbandoned = 2;
  609. wrError = 3;
  610. type
  611. PWasmBasicEventState = ^TWasmBasicEventState;
  612. TWasmBasicEventState = record
  613. Signal : Longint;
  614. ManualReset : Boolean;
  615. Destroying : Boolean;
  616. end;
  617. function WasiBasicEventCreate(EventAttributes :Pointer; AManualReset,InitialState : Boolean;const Name:ansistring):pEventState;
  618. var
  619. P: PWasmBasicEventState;
  620. begin
  621. New(P);
  622. fpc_wasm32_i32_atomic_store(@P^.Signal,Ord(InitialState));
  623. fpc_wasm32_i32_atomic_store8(@P^.ManualReset,Ord(AManualReset));
  624. fpc_wasm32_i32_atomic_store8(@P^.Destroying,0);
  625. Result:=P;
  626. end;
  627. procedure WasiBasicEventDestroy(state:peventstate);
  628. var
  629. P: PWasmBasicEventState absolute state;
  630. a: longword;
  631. begin
  632. fpc_wasm32_i32_atomic_store8(@P^.Destroying,1);
  633. fpc_wasm32_i32_atomic_store(@P^.Signal,1);
  634. a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),MaxThreadSignal);
  635. Dispose(P);
  636. end;
  637. procedure WasiBasicEventResetEvent(state:peventstate);
  638. var
  639. P: PWasmBasicEventState absolute state;
  640. begin
  641. fpc_wasm32_i32_atomic_store(@P^.Signal,0);
  642. end;
  643. procedure WasiBasicEventSetEvent(state:peventstate);
  644. var
  645. P: PWasmBasicEventState absolute state;
  646. a: longword;
  647. begin
  648. if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,0,1)=0 then
  649. begin
  650. if fpc_wasm32_i32_atomic_load8_u(@P^.ManualReset)<>0 then
  651. a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),MaxThreadSignal)
  652. else
  653. a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),1);
  654. end;
  655. end;
  656. function WasiBasicEventWaitFor_WaitAllowed(aTimeOutNS:Int64;P:PWasmBasicEventState):longint;
  657. var
  658. EndTime: TOSTime;
  659. RemainingTime: Int64;
  660. begin
  661. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  662. begin
  663. result:=wrAbandoned;
  664. exit;
  665. end;
  666. if fpc_wasm32_i32_atomic_load8_u(@P^.ManualReset)<>0 then
  667. begin
  668. { manual reset event }
  669. case fpc_wasm32_memory_atomic_wait32(@P^.Signal,0,aTimeOutNS) of
  670. 0, 1:
  671. result:=wrSignaled;
  672. 2:
  673. result:=wrTimeout;
  674. else
  675. result:=wrError;
  676. end;
  677. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  678. result:=wrAbandoned;
  679. end
  680. else
  681. begin
  682. { auto reset event }
  683. if aTimeOutNS>=0 then
  684. EndTime:=GetClockTime+aTimeOutNS
  685. else
  686. begin
  687. EndTime:=0;
  688. RemainingTime:=-1;
  689. end;
  690. repeat
  691. if aTimeOutNS>=0 then
  692. begin
  693. RemainingTime:=EndTime-GetClockTime;
  694. if RemainingTime<0 then
  695. begin
  696. result:=wrTimeout;
  697. exit;
  698. end;
  699. end;
  700. case fpc_wasm32_memory_atomic_wait32(@P^.Signal,0,RemainingTime) of
  701. 0, { "ok" }
  702. 1: { "not-equal" }
  703. begin
  704. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  705. begin
  706. result:=wrAbandoned;
  707. exit;
  708. end
  709. else if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,1,0)=1 then
  710. begin
  711. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  712. begin
  713. fpc_wasm32_i32_atomic_store(@P^.Signal,1);
  714. result:=wrAbandoned;
  715. exit;
  716. end
  717. else
  718. begin
  719. result:=wrSignaled;
  720. exit;
  721. end;
  722. end
  723. else
  724. ; { try waiting again (loop continues) }
  725. end;
  726. 2: { "timed-out" }
  727. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  728. begin
  729. result:=wrAbandoned;
  730. exit;
  731. end
  732. else
  733. begin
  734. result:=wrTimeout;
  735. exit;
  736. end;
  737. else { invalid result from wait }
  738. begin
  739. result:=wrError;
  740. exit;
  741. end;
  742. end;
  743. until false;
  744. end;
  745. end;
  746. function WasiBasicEventWaitFor_WaitNotAllowed(aTimeOutNS:Int64;P:PWasmBasicEventState):longint;
  747. var
  748. EndTime: TOSTime;
  749. RemainingTime: Int64;
  750. begin
  751. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  752. begin
  753. result:=wrAbandoned;
  754. exit;
  755. end;
  756. if aTimeOutNS>=0 then
  757. EndTime:=GetClockTime+aTimeOutNS
  758. else
  759. begin
  760. EndTime:=0;
  761. RemainingTime:=-1;
  762. end;
  763. if fpc_wasm32_i32_atomic_load8_u(@P^.ManualReset)<>0 then
  764. begin
  765. { manual reset event }
  766. repeat
  767. if aTimeOutNS>=0 then
  768. begin
  769. RemainingTime:=EndTime-GetClockTime;
  770. if RemainingTime<0 then
  771. begin
  772. result:=wrTimeout;
  773. exit;
  774. end;
  775. end;
  776. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  777. begin
  778. result:=wrAbandoned;
  779. exit;
  780. end
  781. else if fpc_wasm32_i32_atomic_load(@P^.Signal)<>0 then
  782. begin
  783. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  784. begin
  785. result:=wrAbandoned;
  786. exit;
  787. end
  788. else
  789. begin
  790. result:=wrSignaled;
  791. exit;
  792. end;
  793. end;
  794. until false;
  795. end
  796. else
  797. begin
  798. { auto reset event }
  799. repeat
  800. if aTimeOutNS>=0 then
  801. begin
  802. RemainingTime:=EndTime-GetClockTime;
  803. if RemainingTime<0 then
  804. begin
  805. result:=wrTimeout;
  806. exit;
  807. end;
  808. end;
  809. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  810. begin
  811. result:=wrAbandoned;
  812. exit;
  813. end
  814. else if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,1,0)=1 then
  815. begin
  816. if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
  817. begin
  818. fpc_wasm32_i32_atomic_store(@P^.Signal,1);
  819. result:=wrAbandoned;
  820. exit;
  821. end
  822. else
  823. begin
  824. result:=wrSignaled;
  825. exit;
  826. end;
  827. end;
  828. until false;
  829. end;
  830. end;
  831. function WasiBasicEventWaitFor(timeout:cardinal;state:peventstate;FUseComWait : Boolean=False):longint;
  832. var
  833. timeoutNS: Int64;
  834. begin
  835. if timeout<>$FFFFFFFF then
  836. timeoutNS:=timeout*1000000
  837. else
  838. timeoutNS:=-1;
  839. if isWaitAllowed then
  840. Result:=WasiBasicEventWaitFor_WaitAllowed(timeoutNS,PWasmBasicEventState(state))
  841. else
  842. Result:=WasiBasicEventWaitFor_WaitNotAllowed(timeoutNS,PWasmBasicEventState(state));
  843. end;
  844. procedure InitSystemThreads;public name '_FPC_InitSystemThreads';
  845. begin
  846. with WasiThreadManager do
  847. begin
  848. InitManager := @WasiInitManager;
  849. DoneManager := @WasiDoneManager;
  850. BeginThread := @WasiBeginThread;
  851. EndThread := @WasiEndThread;
  852. SuspendThread := @WasiSuspendThread;
  853. ResumeThread := @WasiResumeThread;
  854. KillThread := @WasiKillThread;
  855. CloseThread := @WasiCloseThread;
  856. ThreadSwitch := @WasiThreadSwitch;
  857. WaitForThreadTerminate := @WasiWaitForThreadTerminate;
  858. ThreadSetPriority := @WasiThreadSetPriority;
  859. ThreadGetPriority := @WasiThreadGetPriority;
  860. GetCurrentThreadId := @WasiGetCurrentThreadId;
  861. SetThreadDebugNameA := @WasiThreadSetThreadDebugNameA;
  862. {$ifdef FPC_HAS_FEATURE_UNICODESTRINGS}
  863. SetThreadDebugNameU := @WasiThreadSetThreadDebugNameU;
  864. {$endif FPC_HAS_FEATURE_UNICODESTRINGS}
  865. InitCriticalSection := @WasiInitCriticalSection;
  866. DoneCriticalSection := @WasiDoneCriticalSection;
  867. EnterCriticalSection := @WasiEnterCriticalSection;
  868. TryEnterCriticalSection:= @WasiCriticalSectionTryEnter;
  869. LeaveCriticalSection := @WasiLeaveCriticalSection;
  870. InitThreadVar := @WasiInitThreadVar;
  871. RelocateThreadVar := @WasiRelocateThreadVar;
  872. AllocateThreadVars := @WasiAllocateThreadVars;
  873. ReleaseThreadVars := @WasiReleaseThreadVars;
  874. BasicEventCreate := @WasiBasicEventCreate;
  875. BasicEventDestroy := @WasiBasicEventDestroy;
  876. BasicEventResetEvent := @WasiBasicEventResetEvent;
  877. BasicEventSetEvent := @WasiBasicEventSetEvent;
  878. BasiceventWaitFOr := @WasiBasicEventWaitFor;
  879. RTLEventCreate := @WasiRTLCreateEvent;
  880. RTLEventDestroy := @WasiRTLEventDestroy;
  881. RTLEventSetEvent := @WasiRTLEventSetEvent;
  882. RTLEventResetEvent := @WasiRTLEventResetEvent;
  883. RTLEventWaitFor := @WasiRTLEventWaitFor;
  884. RTLEventWaitForTimeout := @WasiRTLEventWaitForTimeout;
  885. end;
  886. SetThreadManager(WasiThreadManager);
  887. end;