1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027 |
- {%MainUnit system.pp}
- {
- This file is part of the Free Pascal run time library.
- Copyright (c) 2022 by Nikolay Nikolov,
- member of the Free Pascal development team.
- WASI threading support implementation
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- **********************************************************************}
- {$ifndef FPC_WASM_THREADS}
- {$fatal This file shouldn't be included if thread support is disabled!}
- {$endif FPC_WASM_THREADS}
- {$DEFINE FPC_WASM_MAIN_THREAD_CAN_WAIT}
- {$DEFINE FPC_WASM_WORKER_THREADS_CAN_WAIT}
- {//$DEFINE DEBUGWASMTHREADS}
- Const
- MaxThreadSignal = high(uint32); // maximum threads to signal
- Type
- TThreadState = (tsNone,tsInit,tsRunning,tsCanceling,tsExit);
- TOSTime = __wasi_timestamp_t;
- // Forwards used in mutex
- Function GetClockTime: TOSTime; forward;
- Function IsWaitAllowed : Boolean; forward;
- Function GetSelfThread : TThreadID; forward;
- Function GetThreadState(aThread : TThreadID) : TThreadState; forward;
- {$i wasmmem.inc}
- {$i wasmmutex.inc}
- Type
- PWasmRTLEvent = ^TWasmRTLEvent;
- TWasmRTLEvent = record
- Signal : Longint;
- Destroying : Boolean;
- end;
- PWasmThread = ^TWasmThread;
- TWasmThread = Record
- InitialStackPointer : Pointer;
- InitTLSBase : Pointer;
- ThreadHasFinished : Boolean;
- ID : LongInt; // Allocated by host
- ThreadFunction : TThreadFunc;
- ThreadFunctionArg : Pointer;
- State : TThreadState;
- DoneEvent : PWasmRTLEvent;
- Running : TWasmMutex;
- ExitCode : Cardinal;
- StackBlock : Pointer;
- TLSBlock : Pointer;
- StackSize : PtrUInt;
- ThreadName : Array of byte; // UTF8 name
- end;
- { EWasmThreadTerminate }
- EWasmThreadTerminate = class(TObject)
- strict private
- FExitCode : DWord;
- public
- constructor Create(AExitCode: DWord);
- property ExitCode: DWord read FExitCode;
- end;
- Var
- MainThread : TWasmThread;
- WasiThreadManager : TThreadManager;
- GlobalIsWorkerThread : Longint; section 'WebAssembly.Global';
- GlobalIsMainThread : Longint; section 'WebAssembly.Global';
- GlobalIsThreadBlockable : Longint; section 'WebAssembly.Global';
- GlobalCurrentThread : PWasmThread; section 'WebAssembly.Global';
- { EWasmThreadTerminate }
- constructor EWasmThreadTerminate.Create(AExitCode: DWord);
- begin
- FExitCode:=AExitCode;
- end;
- // Forward functions
- Function IsWaitAllowed : Boolean;
- begin
- IsWaitAllowed:=GlobalIsThreadBlockable<>0;
- end;
- Function GetClockTime: TOSTime;
- var
- NanoSecsPast: TOSTime;
- begin
- if __wasi_clock_time_get(__WASI_CLOCKID_REALTIME,1000000,@NanoSecsPast)=__WASI_ERRNO_SUCCESS then
- GetClockTime:=NanoSecsPast
- else
- GetClockTime:=0;
- end;
- Function GetSelfThread : TThreadID;
- begin
- GetSelfThread:=GlobalCurrentThread;
- end;
- Function GetMainThread : TThreadID;
- begin
- Result:=PWasmThread(@MainThread);
- end;
- Function GetThreadState(aThread : TThreadID) : TThreadState;
- begin
- GetThreadState:=PWasmThread(aThread)^.State
- end;
- function WasiInitManager: Boolean;
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Initializing manager');{$ENDIF}
- FillChar(MainThread,SizeOf(MainThread),0);
- MainThread.State:=tsRunning;
- GlobalIsMainThread:=1;
- GlobalIsWorkerThread:=0;
- GlobalCurrentThread:=@MainThread;
- ThreadId:=@MainThread;
- {$IFDEF FPC_WASM_MAIN_THREAD_CAN_WAIT}
- GlobalIsThreadBlockable:=1;
- {$ELSE FPC_WASM_MAIN_THREAD_CAN_WAIT}
- GlobalIsThreadBlockable:=0;
- {$ENDIF FPC_WASM_MAIN_THREAD_CAN_WAIT}
- InitMutex(TWasmMutex(InitialHeapCriticalSection));
- InitialHeapCriticalSectionInitialized:=true;
- if TLSInfoBlock=Nil then
- TLSInfoBlock:=AllocateOSInfoBlock;
- {$IFDEF DEBUGWASMTHREADS}
- if TLSInfoBlock = Nil then
- DebugWriteln('Initializing manager done: failed');
- {$ENDIF}
- WasiInitManager:=True;
- end;
- function WasiDoneManager: Boolean;
- begin
- WasiDoneManager:=True;
- end;
- { ----------------------------------------------------------------------
- Critical section (mutex)
- ----------------------------------------------------------------------}
- procedure WasiInitCriticalSection(var cs);
- begin
- InitMutex(TWasmMutex(CS));
- end;
- procedure WasiDoneCriticalSection(var cs);
- begin
- DoneMutex(TWasmMutex(CS));
- end;
- procedure WasiEnterCriticalSection(var cs);
- begin
- LockMutex(TWasmMutex(CS));
- end;
- function WasiCriticalSectionTryEnter(var cs):longint;
- begin
- WasiCriticalSectionTryEnter:=Ord(TryLockMutex(TWasmMutex(CS)))
- end;
- procedure WasiLeaveCriticalSection(var cs);
- begin
- UnLockMutex(TWasmMutex(CS));
- end;
- { ----------------------------------------------------------------------
- RTL event
- ----------------------------------------------------------------------}
- function WasiRTLCreateEvent:PRTLEvent;
- Var
- P : PWasmRTLEvent;
- begin
- New(P);
- fpc_wasm32_i32_atomic_store(@P^.Signal,0);
- fpc_wasm32_i32_atomic_store8(@P^.Destroying,0);
- Result:=P;
- end;
- procedure WasiRTLEventSetEvent(AEvent:PRTLEvent);
- Var
- P : PWasmRTLEvent absolute aEvent;
- a : longint;
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventSetEvent : setting signal=1');{$ENDIF}
- if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,0,1)=0 then
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventSetEvent : notifying 1 waiting thread');{$ENDIF}
- a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),1);
- end
- else
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventSetEvent : signal was already 1, nothing to do');{$ENDIF}
- end;
- end;
- procedure WasiRTLEventDestroy(AEvent:PRTLEvent);
- Var
- P : PWasmRTLEvent absolute aEvent;
- a : LongInt;
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventDestroy : setting destroying to true');{$ENDIF}
- fpc_wasm32_i32_atomic_store8(@P^.Destroying,1);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventDestroy : setting event to notify others');{$ENDIF}
- fpc_wasm32_i32_atomic_store(@P^.Signal,1);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventDestroy : notifying waiting threads');{$ENDIF}
- a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),MaxThreadSignal);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventDestroy : freeing memory');{$ENDIF}
- Dispose(P);
- end;
- procedure WasiRTLEventResetEvent(AEvent:PRTLEvent);
- Var
- P : PWasmRTLEvent absolute aEvent;
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRTLEventResetEvent : setting signal=0');{$ENDIF}
- fpc_wasm32_i32_atomic_store(@P^.Signal,0);
- end;
- procedure WasiRTLEventWaitFor_WaitAllowed(P:PWasmRTLEvent; aTimeoutNs : Int64);
- Var
- a : Longint;
- EndTime: TOSTime;
- RemainingTime: Int64;
- begin
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- exit; // abandoned
- if aTimeOutNS>=0 then
- EndTime:=GetClockTime+aTimeOutNS
- else
- begin
- EndTime:=0;
- RemainingTime:=-1;
- end;
- repeat
- if aTimeOutNS>=0 then
- begin
- RemainingTime:=EndTime-GetClockTime;
- if RemainingTime<0 then
- exit; // timeout
- end;
- case fpc_wasm32_memory_atomic_wait32(@P^.Signal,0,RemainingTime) of
- 0, { "ok" }
- 1: { "not-equal" }
- begin
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- exit // abandoned
- else if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,1,0)=1 then
- exit // signaled
- else
- ; { try waiting again (loop continues) }
- end;
- 2: { "timed-out" }
- exit; // timeout or abandoned
- else { invalid result from wait }
- exit; // error
- end;
- until false;
- end;
- procedure WasiRTLEventWaitFor_WaitNotAllowed(P:PWasmRTLEvent; aTimeoutNs : Int64);
- Var
- EndTime: TOSTime;
- RemainingTime: Int64;
- begin
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- exit; // abandoned
- if aTimeOutNS>=0 then
- EndTime:=GetClockTime+aTimeOutNS
- else
- begin
- EndTime:=0;
- RemainingTime:=-1;
- end;
- repeat
- if aTimeOutNS>=0 then
- begin
- RemainingTime:=EndTime-GetClockTime;
- if RemainingTime<0 then
- exit; // timeout
- end;
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- exit // abandoned
- else if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,1,0)=1 then
- exit; // signaled
- until false;
- end;
- procedure WasiRTLEventWaitFor(AEvent:PRTLEvent);
- Var
- P : PWasmRTLEvent absolute aEvent;
- begin
- if IsWaitAllowed then
- WasiRTLEventWaitFor_WaitAllowed(P,-1)
- else
- WasiRTLEventWaitFor_WaitNotAllowed(P,-1);
- end;
- procedure WasiRTLEventWaitForTimeout(AEvent:PRTLEvent;timeout : longint);
- Var
- P : PWasmRTLEvent absolute aEvent;
- TimeoutNs: Int64;
- begin
- if timeout=-1 then
- TimeoutNs:=-1
- else
- TimeoutNs:=Int64(timeout)*1000000;
- if IsWaitAllowed then
- WasiRTLEventWaitFor_WaitAllowed(P,TimeoutNs)
- else
- WasiRTLEventWaitFor_WaitNotAllowed(P,TimeoutNs);
- end;
- { ----------------------------------------------------------------------
- Thread
- ----------------------------------------------------------------------}
- //procedure FPCWasmThreadSetStackPointer(Address: Pointer); [public, alias: 'FPC_WASM_THREAD_SET_STACK_POINTER'];
- //begin
- // fpc_wasm32_set_base_pointer(Address);
- //end;
- // Javascript definition: TThreadInitInstanceFunction = Function(IsWorkerThread : Longint; IsMainThread : Integer; CanBlock : Integer) : Integer;
- //Function FPCWasmThreadInit(IsWorkerThread : Longint; IsMainThread : Longint; CanBlock : Longint) : Longint; [public, alias: 'FPC_WASM_THREAD_INIT'];
- //
- //begin
- // {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadInit('+IntToStr(IsWorkerThread)+','+IntToStr(IsMainThread)+','+IntToStr(CanBlock)+')');{$ENDIF}
- // GlobalIsWorkerThread:=IsWorkerThread;
- // GlobalIsMainThread:=IsMainThread;
- // GlobalIsThreadBlockable:=CanBlock;
- // Result:=0;
- //end;
- procedure WasiAllocateThreadVars; forward;
- // Javascript definition: TThreadEntryFunction = Function(ThreadId : Longint; RunFunction : Longint; Args : LongInt) : Longint;
- //Function FPCWasmThreadEntry(ThreadID : PWasmThread; RunFunction : Pointer; Args : Pointer) : Longint; [public, alias: 'FPC_WASM_THREAD_ENTRY'];
- //begin
- // {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadEntry('+IntToStr(PtrUint(ThreadID))+','+IntToStr(PtrUint(RunFunction))+','+IntToStr(PtrUint(Args))+')');{$ENDIF}
- // GlobalCurrentThread:=ThreadID;
- // {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadEntry: allocating threadvars (thread function: '+intToStr(PtrUint(RunFunction))+')');{$ENDIF}
- // WasiAllocateThreadVars;
- // {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadEntry: calling initthread (thread function: '+intToStr(PtrUint(RunFunction))+')');{$ENDIF}
- // InitThread;
- // {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadEntry: calling thread function '+intToStr(PtrUint(RunFunction)));{$ENDIF}
- // Result:=tthreadfunc(RunFunction)(args);
- //end;
- {$push}{$S-} // no stack checking for this procedure
- procedure FPCWasmThreadStartPascal(tid: longint; start_arg: PWasmThread);
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadStartPascal('+IntToStr(tid)+','+IntToStr(ptrint(start_arg))+')');{$ENDIF}
- start_arg^.ID:=tid;
- GlobalCurrentThread:=start_arg;
- GlobalIsMainThread:=0;
- GlobalIsWorkerThread:=1;
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Check : TID='+IntToStr(tid)+', start_arg_id='+IntToStr(start_arg^.ID)+', currentthread= '+IntTostr(ptrint(GetCurrentThreadID))+')');{$ENDIF}
- {$IFDEF FPC_WASM_WORKER_THREADS_CAN_WAIT}
- GlobalIsThreadBlockable:=1;
- {$ELSE FPC_WASM_WORKER_THREADS_CAN_WAIT}
- GlobalIsThreadBlockable:=0;
- {$ENDIF FPC_WASM_WORKER_THREADS_CAN_WAIT}
- start_arg^.State:=tsRunning;
- InitThread(start_arg^.StackSize);
- StackBottom:=start_arg^.StackBlock;
- try
- start_arg^.ExitCode:=Cardinal(start_arg^.ThreadFunction(start_arg^.ThreadFunctionArg));
- except
- on e: EWasmThreadTerminate do
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadStartPascal: Caught EWasmThreadTerminate with ExitCode='+IntToStr(e.ExitCode));{$ENDIF}
- start_arg^.ExitCode:=e.ExitCode;
- end;
- else
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadStartPascal: Uncaught exception');{$ENDIF}
- { TODO: what should we return here? }
- start_arg^.ExitCode:=High(Cardinal);
- end;
- end;
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('FPCWasmThreadStartPascal: Signaling end of thread');{$ENDIF}
- WasiRTLEventSetEvent(start_arg^.DoneEvent);
- end;
- {$pop}
- procedure wasi_thread_start(tid: longint; start_arg: PWasmThread); assembler; nostackframe;
- asm
- local.get 1 ;; start_arg
- i32.load ;; load InitialStackPointer
- global.set $__stack_pointer
- ;; call fpc_wasm32_init_tls from within assembly code, because in branchful
- ;; exceptions mode, Free Pascal generates threadvar access after every
- ;; function call. Therefore, we want threadvars to be initialized, before we
- ;; call any sort of Pascal code.
- local.get 1 ;; start_arg
- i32.const 4 ;; offset to InitTLSBase
- i32.add
- i32.load
- call $fpc_wasm32_init_tls
- local.get 0 ;; tid
- local.get 1 ;; start_arg
- call $FPCWasmThreadStartPascal
- ;; Set start_arg^.ThreadHasFinished to true.
- ;; This is done from within inline asm, after the pascal code has finished
- ;; executing, because it indicates that the thread no longer needs its TLS
- ;; block and linear stack block, so this means it's safe to free them.
- local.get 1 ;; start_arg
- i32.const 8 ;; offset to ThreadHasFinished
- i32.add
- i32.const 1 ;; true
- i32.atomic.store8
- end;
- exports wasi_thread_start, GetSelfThread, GetMainThread;
- Function wasi_thread_spawn(start_arg: PWasmThread) : LongInt; external 'wasi' name 'thread-spawn';
- { Just because we set the original pointer to nil, using InterlockedExchange
- to avoid race conditions leading to double free, doesn't mean this function is
- meant to be called more than once, or from multiple threads. This just adds
- some extra layer of protection. }
- procedure FreeStackAndTlsBlock(T : PWasmThread);
- var
- P: Pointer;
- begin
- P:=InterlockedExchange(T^.StackBlock,nil);
- if Assigned(P) then
- FreeMem(P);
- P:=InterlockedExchange(T^.TLSBlock,nil);
- if Assigned(P) then
- FreeMem(P);
- end;
- function WasiBeginThread(sa : Pointer;stacksize : PtrUInt; ThreadFunction : tthreadfunc;p : pointer;creationFlags : dword; var ThreadId : TThreadID) : TThreadID;
- Const
- HeapAlignment=16;
- Var
- T : PWasmThread;
- begin
- {$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}
- IsMultiThread:=true;
- New(T);
- fpc_wasm32_i32_atomic_store8(@T^.ThreadHasFinished,0);
- T^.StackBlock:=nil;
- T^.TLSBlock:=nil;
- ThreadID:=T;
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread thread ID : '+IntToStr(PtrUint(ThreadID)));{$ENDIF}
- T^.ThreadFunction:=ThreadFunction;
- T^.ThreadFunctionArg:=p;
- if stacksize<=0 then
- stacksize:=StkLen;
- T^.StackSize:=stacksize;
- T^.StackBlock:=GetMem(stacksize);
- T^.InitialStackPointer:=Pointer(PtrUInt(PtrUInt(T^.StackBlock)+stacksize) and $FFFFFFF0);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: InitialStackPointer='+IntToStr(PtrUint(T^.InitialStackPointer)));{$ENDIF}
- T^.TLSBlock:=AllocMem(fpc_wasm32_tls_size+fpc_wasm32_tls_align-1);
- T^.InitTLSBase:=Align(T^.TLSBlock,fpc_wasm32_tls_align);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: InitTLSBase='+IntToStr(PtrUint(T^.InitTLSBase)));{$ENDIF}
- InitMutex(T^.Running,mkNormal);
- T^.DoneEvent:=WasiRTLCreateEvent;
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: Locked mutex');{$ENDIF}
- if wasi_thread_spawn(T)>0 then
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread OK, setting result');{$ENDIF}
- WasiBeginThread:=T;
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread OK, done setting result');{$ENDIF}
- end
- else
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread failed');{$ENDIF}
- WasiRTLEventDestroy(T^.DoneEvent);
- DoneMutex(T^.Running);
- FreeStackAndTlsBlock(T);
- Dispose(T);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread failed, freeing thread struct');{$ENDIF}
- WasiBeginThread:=TThreadID(0);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiBeginThread: spawn thread failed, returning 0');{$ENDIF}
- end
- end;
- procedure WasiEndThread(ExitCode : DWord);
- Var
- T : PWasmThread;
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('EndThread('+IntToStr(ExitCode)+')');{$ENDIF}
- raise EWasmThreadTerminate.Create(ExitCode);
- end;
- function WasiSuspendThread(threadHandle : TThreadID) : dword;
- begin
- WasiSuspendThread:=DWord(-1);
- end;
- function WasiResumeThread(threadHandle : TThreadID) : dword;
- begin
- WasiResumeThread:=DWord(-1);
- end;
- function WasiKillThread(threadHandle : TThreadID) : dword;
- begin
- WasiKillThread:=DWord(-1);
- end;
- function WasiCloseThread(threadHandle : TThreadID) : dword;
- begin
- Result:=0;
- end;
- procedure WasiThreadSwitch;
- begin
- // Normally a yield, but this does not (yet) exist in webassembly.
- end;
- function WasiWaitForThreadTerminate(threadHandle : TThreadID; TimeoutMs : longint) : dword;
- Var
- Res : LongInt;
- TH : PWasmThread absolute ThreadHandle;
- TimeoutNs : Int64;
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+','+IntToStr(TimeoutMs)+')');{$ENDIF}
- if TimeoutMs>=0 then
- TimeoutNs:=TimeoutMs*1000000
- else
- TimeoutNs:=-1;
- WasiRTLEventWaitFor(TH^.DoneEvent);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : Event set, waiting for lock');{$ENDIF}
- Case LockMuTexTimeout(PWasmThread(ThreadHandle)^.Running,TimeoutNs) of
- lmrOK : Res:=LongInt(TH^.ExitCode);
- lmrError : Res:=-2;
- else
- Res:=-1;
- end;
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : Got Lock');{$ENDIF}
- UnLockMuTex(PWasmThread(ThreadHandle)^.Running);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : Lock released');{$ENDIF}
- WasiWaitForThreadTerminate:=DWord(Res);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : Waiting until ThreadHasFinished becomes true');{$ENDIF}
- repeat
- until fpc_wasm32_i32_atomic_load8_u(@TH^.ThreadHasFinished)<>0;
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WaitForThreadTerminate('+IntToStr(PtrUINT(TH))+') : FreeStackAndTlsBlock');{$ENDIF}
- FreeStackAndTlsBlock(TH);
- end;
- function WasiThreadSetPriority(threadHandle : TThreadID; Prio: longint): boolean;
- begin
- Result:=False;
- end;
- function WasiThreadGetPriority(threadHandle : TThreadID): longint;
- begin
- Result:=0;
- end;
- function WasiGetCurrentThreadId : TThreadID;
- begin
- Result:=GetSelfThread;
- end;
- procedure WasiThreadSetThreadDebugNameA(threadHandle: TThreadID; const ThreadName: AnsiString);
- Var
- P : PWasmThread absolute ThreadHandle;
- Len : Integer;
- begin
- Len:=Length(ThreadName);
- SetLength(P^.ThreadName,Len);
- if Len>0 then
- Move(ThreadName[1],P^.ThreadName[0],Len);
- end;
- {$ifdef FPC_HAS_FEATURE_UNICODESTRINGS}
- procedure WasiThreadSetThreadDebugNameU(threadHandle: TThreadID; const ThreadName: UnicodeString);
- Var
- P : PWasmThread absolute ThreadHandle;
- LThreadName : RawBytestring;
- Len : Integer;
- begin
- Len:=Length(LThreadName);
- LThreadName:=Utf8Encode(ThreadName);
- SetLength(P^.ThreadName,Len*SizeOf(UnicodeChar));
- if Len>0 then
- Move(LThreadName[1],P^.ThreadName[0],Len*SizeOf(UnicodeChar));
- end;
- {$endif FPC_HAS_FEATURE_UNICODESTRINGS}
- { ----------------------------------------------------------------------
- Threadvars
- ----------------------------------------------------------------------}
- Var
- threadvarblocksize : PtrUint;
- procedure WasiInitThreadVar(var offset : dword;size : dword);
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiInitThreadVar('+IntToStr(offset)+','+IntToStr(size)+')');{$ENDIF}
- threadvarblocksize:=align(threadvarblocksize, fpc_wasm32_tls_align);
- offset:=threadvarblocksize;
- inc(threadvarblocksize,size);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Done WasiInitThreadVar. Total size: '+IntToStr(threadvarblocksize));{$ENDIF}
- end;
- procedure WasiAllocateThreadVars;
- var
- tlsMemBlock : pointer;
- tlsBlockSize : Integer;
- P : POSMemBlock;
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiAllocateThreadVars');{$ENDIF}
- tlsBlockSize:=fpc_wasm32_tls_size;
- if threadvarblocksize<>tlsBlocksize then
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Warning : block sizes differ: (linker) '+IntToStr(tlsBlocksize)+'<>'+IntToStr(threadvarblocksize)+' (calculated) !');{$ENDIF}
- P:=GetFreeOSBlock;
- FillChar((P^.Data)^.TLSMemory,tlsBlockSize,0);
- fpc_wasm32_init_tls(@((P^.Data)^.TLSMemory));
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('Done WasiAllocateThreadVars');{$ENDIF}
- end;
- Function GetTLSMemory : Pointer;
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('GetTLSMemory Enter');{$ENDIF}
- GetTLSMemory:=fpc_wasm32_tls_base();
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('GetTLSMemory exit: '+InttoStr(PtrUint(fpc_wasm32_tls_base())));{$ENDIF}
- end;
- procedure WasiReleaseThreadVars;
- Var
- PTLS : PTLSMem;
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiReleaseThreadVars');{$ENDIF}
- PTLS:=GetTLSMemory-Sizeof(Pointer);
- ReleaseOSBlock(PTLS^.OSMemBlock);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiReleaseThreadVars done');{$ENDIF}
- end;
- procedure HookThread;
- { Set up externally created thread }
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('HookThread');{$ENDIF}
- WasiAllocateThreadVars;
- InitThread(1000000000);
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('HookThread done');{$ENDIF}
- end;
- function WasiRelocateThreadVar(offset : dword) : pointer;
- var
- P : Pointer;
- begin
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRelocateThreadVar ('+IntToStr(offset)+')');{$ENDIF}
- P:=GetTLSMemory;
- if (P=Nil) then
- begin
- HookThread;
- P:=GetTLSMemory;
- end;
- WasiRelocateThreadvar:=P+Offset;
- {$IFDEF DEBUGWASMTHREADS}DebugWriteln('WasiRelocateThreadVar done. Result: '+IntToStr(PtrUint(P+Offset)));{$ENDIF}
- end;
- { ----------------------------------------------------------------------
- Basic event
- ----------------------------------------------------------------------}
- const
- wrSignaled = 0;
- wrTimeout = 1;
- wrAbandoned = 2;
- wrError = 3;
- type
- PWasmBasicEventState = ^TWasmBasicEventState;
- TWasmBasicEventState = record
- Signal : Longint;
- ManualReset : Boolean;
- Destroying : Boolean;
- end;
- function WasiBasicEventCreate(EventAttributes :Pointer; AManualReset,InitialState : Boolean;const Name:ansistring):pEventState;
- var
- P: PWasmBasicEventState;
- begin
- New(P);
- fpc_wasm32_i32_atomic_store(@P^.Signal,Ord(InitialState));
- fpc_wasm32_i32_atomic_store8(@P^.ManualReset,Ord(AManualReset));
- fpc_wasm32_i32_atomic_store8(@P^.Destroying,0);
- Result:=P;
- end;
- procedure WasiBasicEventDestroy(state:peventstate);
- var
- P: PWasmBasicEventState absolute state;
- a: longword;
- begin
- fpc_wasm32_i32_atomic_store8(@P^.Destroying,1);
- fpc_wasm32_i32_atomic_store(@P^.Signal,1);
- a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),MaxThreadSignal);
- Dispose(P);
- end;
- procedure WasiBasicEventResetEvent(state:peventstate);
- var
- P: PWasmBasicEventState absolute state;
- begin
- fpc_wasm32_i32_atomic_store(@P^.Signal,0);
- end;
- procedure WasiBasicEventSetEvent(state:peventstate);
- var
- P: PWasmBasicEventState absolute state;
- a: longword;
- begin
- if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,0,1)=0 then
- begin
- if fpc_wasm32_i32_atomic_load8_u(@P^.ManualReset)<>0 then
- a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),MaxThreadSignal)
- else
- a:=fpc_wasm32_memory_atomic_notify(@(P^.Signal),1);
- end;
- end;
- function WasiBasicEventWaitFor_WaitAllowed(aTimeOutNS:Int64;P:PWasmBasicEventState):longint;
- var
- EndTime: TOSTime;
- RemainingTime: Int64;
- begin
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- begin
- result:=wrAbandoned;
- exit;
- end;
- if fpc_wasm32_i32_atomic_load8_u(@P^.ManualReset)<>0 then
- begin
- { manual reset event }
- case fpc_wasm32_memory_atomic_wait32(@P^.Signal,0,aTimeOutNS) of
- 0, 1:
- result:=wrSignaled;
- 2:
- result:=wrTimeout;
- else
- result:=wrError;
- end;
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- result:=wrAbandoned;
- end
- else
- begin
- { auto reset event }
- if aTimeOutNS>=0 then
- EndTime:=GetClockTime+aTimeOutNS
- else
- begin
- EndTime:=0;
- RemainingTime:=-1;
- end;
- repeat
- if aTimeOutNS>=0 then
- begin
- RemainingTime:=EndTime-GetClockTime;
- if RemainingTime<0 then
- begin
- result:=wrTimeout;
- exit;
- end;
- end;
- case fpc_wasm32_memory_atomic_wait32(@P^.Signal,0,RemainingTime) of
- 0, { "ok" }
- 1: { "not-equal" }
- begin
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- begin
- result:=wrAbandoned;
- exit;
- end
- else if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,1,0)=1 then
- begin
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- begin
- fpc_wasm32_i32_atomic_store(@P^.Signal,1);
- result:=wrAbandoned;
- exit;
- end
- else
- begin
- result:=wrSignaled;
- exit;
- end;
- end
- else
- ; { try waiting again (loop continues) }
- end;
- 2: { "timed-out" }
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- begin
- result:=wrAbandoned;
- exit;
- end
- else
- begin
- result:=wrTimeout;
- exit;
- end;
- else { invalid result from wait }
- begin
- result:=wrError;
- exit;
- end;
- end;
- until false;
- end;
- end;
- function WasiBasicEventWaitFor_WaitNotAllowed(aTimeOutNS:Int64;P:PWasmBasicEventState):longint;
- var
- EndTime: TOSTime;
- RemainingTime: Int64;
- begin
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- begin
- result:=wrAbandoned;
- exit;
- end;
- if aTimeOutNS>=0 then
- EndTime:=GetClockTime+aTimeOutNS
- else
- begin
- EndTime:=0;
- RemainingTime:=-1;
- end;
- if fpc_wasm32_i32_atomic_load8_u(@P^.ManualReset)<>0 then
- begin
- { manual reset event }
- repeat
- if aTimeOutNS>=0 then
- begin
- RemainingTime:=EndTime-GetClockTime;
- if RemainingTime<0 then
- begin
- result:=wrTimeout;
- exit;
- end;
- end;
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- begin
- result:=wrAbandoned;
- exit;
- end
- else if fpc_wasm32_i32_atomic_load(@P^.Signal)<>0 then
- begin
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- begin
- result:=wrAbandoned;
- exit;
- end
- else
- begin
- result:=wrSignaled;
- exit;
- end;
- end;
- until false;
- end
- else
- begin
- { auto reset event }
- repeat
- if aTimeOutNS>=0 then
- begin
- RemainingTime:=EndTime-GetClockTime;
- if RemainingTime<0 then
- begin
- result:=wrTimeout;
- exit;
- end;
- end;
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- begin
- result:=wrAbandoned;
- exit;
- end
- else if fpc_wasm32_i32_atomic_rmw_cmpxchg_u(@P^.Signal,1,0)=1 then
- begin
- if fpc_wasm32_i32_atomic_load8_u(@P^.Destroying)<>0 then
- begin
- fpc_wasm32_i32_atomic_store(@P^.Signal,1);
- result:=wrAbandoned;
- exit;
- end
- else
- begin
- result:=wrSignaled;
- exit;
- end;
- end;
- until false;
- end;
- end;
- function WasiBasicEventWaitFor(timeout:cardinal;state:peventstate;FUseComWait : Boolean=False):longint;
- var
- timeoutNS: Int64;
- begin
- if timeout<>$FFFFFFFF then
- timeoutNS:=timeout*1000000
- else
- timeoutNS:=-1;
- if isWaitAllowed then
- Result:=WasiBasicEventWaitFor_WaitAllowed(timeoutNS,PWasmBasicEventState(state))
- else
- Result:=WasiBasicEventWaitFor_WaitNotAllowed(timeoutNS,PWasmBasicEventState(state));
- end;
- procedure InitSystemThreads;public name '_FPC_InitSystemThreads';
- begin
- with WasiThreadManager do
- begin
- InitManager := @WasiInitManager;
- DoneManager := @WasiDoneManager;
- BeginThread := @WasiBeginThread;
- EndThread := @WasiEndThread;
- SuspendThread := @WasiSuspendThread;
- ResumeThread := @WasiResumeThread;
- KillThread := @WasiKillThread;
- CloseThread := @WasiCloseThread;
- ThreadSwitch := @WasiThreadSwitch;
- WaitForThreadTerminate := @WasiWaitForThreadTerminate;
- ThreadSetPriority := @WasiThreadSetPriority;
- ThreadGetPriority := @WasiThreadGetPriority;
- GetCurrentThreadId := @WasiGetCurrentThreadId;
- SetThreadDebugNameA := @WasiThreadSetThreadDebugNameA;
- {$ifdef FPC_HAS_FEATURE_UNICODESTRINGS}
- SetThreadDebugNameU := @WasiThreadSetThreadDebugNameU;
- {$endif FPC_HAS_FEATURE_UNICODESTRINGS}
- InitCriticalSection := @WasiInitCriticalSection;
- DoneCriticalSection := @WasiDoneCriticalSection;
- EnterCriticalSection := @WasiEnterCriticalSection;
- TryEnterCriticalSection:= @WasiCriticalSectionTryEnter;
- LeaveCriticalSection := @WasiLeaveCriticalSection;
- InitThreadVar := @WasiInitThreadVar;
- RelocateThreadVar := @WasiRelocateThreadVar;
- AllocateThreadVars := @WasiAllocateThreadVars;
- ReleaseThreadVars := @WasiReleaseThreadVars;
- BasicEventCreate := @WasiBasicEventCreate;
- BasicEventDestroy := @WasiBasicEventDestroy;
- BasicEventResetEvent := @WasiBasicEventResetEvent;
- BasicEventSetEvent := @WasiBasicEventSetEvent;
- BasiceventWaitFOr := @WasiBasicEventWaitFor;
- RTLEventCreate := @WasiRTLCreateEvent;
- RTLEventDestroy := @WasiRTLEventDestroy;
- RTLEventSetEvent := @WasiRTLEventSetEvent;
- RTLEventResetEvent := @WasiRTLEventResetEvent;
- RTLEventWaitFor := @WasiRTLEventWaitFor;
- RTLEventWaitForTimeout := @WasiRTLEventWaitForTimeout;
- end;
- SetThreadManager(WasiThreadManager);
- end;
|