Sign in Join
Wiki copytemporary

WebAssembly/Threads: what changed

Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11# Thread support
22
3-This page contains some collected informations on the features needed for thread support in WebAssembly (in the browser).
4-
5-Thread support consists of 4 parts:
3+Free Pascal implements the WASI threads proposal:
64
7-- Atomic instructions.
8-- Shared memory and passive segments.
9-- Thread Local Storage (threadvars)
10-- Actually starting a thread.
5+<https://github.com/WebAssembly/wasi-threads>
116
12-## Atomic instructions
7+To enable it, you must use the **wasip1threads** compiler target. A WASI host implementation that is known to work with threads is _iwasm_ from the [WebAssembly Micro Runtime](<https://github.com/bytecodealliance/wasm-micro-runtime>) project.
138
14-[The proposed specs](https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md) 
9+# Memory limits
1510
16-When the Free Pascal RTL is compiled with -CTwasmthreads, the following RTL functions will use the new atomic instructions and thus should be thread safe in a multithreaded environment:
11+WebAssembly thread support requires the program to specify the maximum amount of memory it is going to use. By default, FPC specifies 256 MiB as the maximum amount of memory for multithreaded applications and 8 MiB for the default stack size. Thеsе valueс can be changed via the _$M_ directive:
12+```
13+{$M stacksize,initialmem,maxmem}
14+```
1715
18-```text
19-InterlockedDecrement
20-InterlockedIncrement
21-InterlockedExchange
22-InterlockedCompareExchange
23-InterlockedExchangeAdd
16+For example, to specify 1 MiB stack size, 256 MiB initial memory, and 512 MiB max memory, use:
2417```
18+{$M 1048576,268435456,536870912}
19+```
2520
26-Note that these require proper alignment (4 bytes) of the target, otherwise they trap (i.e. terminate the program with a stack trace).
21+Setting too high value for max memory may result in too much wasted memory, especially on mobile browsers. It is also possible that some WebAssembly hosts (browsers) reject the module completely.
2722
28-In addition to that, there are many more atomic functions available in the WebAssembly unit:
23+Setting the value too low can result in out of memory errors in the Free Pascal program.
2924
30-``` pascal
31-const
32- { Special values for the TimeoutNanoseconds parameter of AtomicWait }
33- awtInfiniteTimeout = -1;
34- { AtomicWait result values }
35- awrOk = 0; { woken by another agent in the cluster }
36- awrNotEqual = 1; { the loaded value did not match the expected value }
37- awrTimedOut = 2; { not woken before timeout expired }
25+For programs that don't know in advance the maximum amount of memory that they need, as of now (Sep 2024) WebAssembly still offers no solution, see the issue [Wasm needs a better memory management story](<https://github.com/WebAssembly/design/issues/1397>).
3826
39-procedure AtomicFence; inline;
27+# Host implementations
4028
41-function AtomicLoad(constref Mem: Int8): Int8; inline;
42-function AtomicLoad(constref Mem: UInt8): UInt8; inline;
43-function AtomicLoad(constref Mem: Int16): Int16; inline;
44-function AtomicLoad(constref Mem: UInt16): UInt16; inline;
45-function AtomicLoad(constref Mem: Int32): Int32; inline;
46-function AtomicLoad(constref Mem: UInt32): UInt32; inline;
47-function AtomicLoad(constref Mem: Int64): Int64; inline;
48-function AtomicLoad(constref Mem: UInt64): UInt64; inline;
29+This section contains a list of known WebAssembly host implementations that support threads.
4930
50-procedure AtomicStore(out Mem: Int8; Data: Int8); inline;
51-procedure AtomicStore(out Mem: UInt8; Data: UInt8); inline;
52-procedure AtomicStore(out Mem: Int16; Data: Int16); inline;
53-procedure AtomicStore(out Mem: UInt16; Data: UInt16); inline;
54-procedure AtomicStore(out Mem: Int32; Data: Int32); inline;
55-procedure AtomicStore(out Mem: UInt32; Data: UInt32); inline;
56-procedure AtomicStore(out Mem: Int64; Data: Int64); inline;
57-procedure AtomicStore(out Mem: UInt64; Data: UInt64); inline;
31+## iwasm
5832
59-function AtomicAdd(var Mem: Int8; Data: Int8): Int8; inline;
60-function AtomicAdd(var Mem: UInt8; Data: UInt8): UInt8; inline;
61-function AtomicAdd(var Mem: Int16; Data: Int16): Int16; inline;
62-function AtomicAdd(var Mem: UInt16; Data: UInt16): UInt16; inline;
63-function AtomicAdd(var Mem: Int32; Data: Int32): Int32; inline;
64-function AtomicAdd(var Mem: UInt32; Data: UInt32): UInt32; inline;
65-function AtomicAdd(var Mem: Int64; Data: Int64): Int64; inline;
66-function AtomicAdd(var Mem: UInt64; Data: UInt64): UInt64; inline;
33+From the [WebAssembly Micro Runtime](<https://github.com/bytecodealliance/wasm-micro-runtime>) project.
6734
68-function AtomicSub(var Mem: Int8; Data: Int8): Int8; inline;
69-function AtomicSub(var Mem: UInt8; Data: UInt8): UInt8; inline;
70-function AtomicSub(var Mem: Int16; Data: Int16): Int16; inline;
71-function AtomicSub(var Mem: UInt16; Data: UInt16): UInt16; inline;
72-function AtomicSub(var Mem: Int32; Data: Int32): Int32; inline;
73-function AtomicSub(var Mem: UInt32; Data: UInt32): UInt32; inline;
74-function AtomicSub(var Mem: Int64; Data: Int64): Int64; inline;
75-function AtomicSub(var Mem: UInt64; Data: UInt64): UInt64; inline;
35+Works pretty well.
7636
77-function AtomicAnd(var Mem: Int8; Data: Int8): Int8; inline;
78-function AtomicAnd(var Mem: UInt8; Data: UInt8): UInt8; inline;
79-function AtomicAnd(var Mem: Int16; Data: Int16): Int16; inline;
80-function AtomicAnd(var Mem: UInt16; Data: UInt16): UInt16; inline;
81-function AtomicAnd(var Mem: Int32; Data: Int32): Int32; inline;
82-function AtomicAnd(var Mem: UInt32; Data: UInt32): UInt32; inline;
83-function AtomicAnd(var Mem: Int64; Data: Int64): Int64; inline;
84-function AtomicAnd(var Mem: UInt64; Data: UInt64): UInt64; inline;
37+## wasmtime
8538
86-function AtomicOr(var Mem: Int8; Data: Int8): Int8; inline;
87-function AtomicOr(var Mem: UInt8; Data: UInt8): UInt8; inline;
88-function AtomicOr(var Mem: Int16; Data: Int16): Int16; inline;
89-function AtomicOr(var Mem: UInt16; Data: UInt16): UInt16; inline;
90-function AtomicOr(var Mem: Int32; Data: Int32): Int32; inline;
91-function AtomicOr(var Mem: UInt32; Data: UInt32): UInt32; inline;
92-function AtomicOr(var Mem: Int64; Data: Int64): Int64; inline;
93-function AtomicOr(var Mem: UInt64; Data: UInt64): UInt64; inline;
39+[Project website](<https://wasmtime.dev/>)
9440
95-function AtomicXor(var Mem: Int8; Data: Int8): Int8; inline;
96-function AtomicXor(var Mem: UInt8; Data: UInt8): UInt8; inline;
97-function AtomicXor(var Mem: Int16; Data: Int16): Int16; inline;
98-function AtomicXor(var Mem: UInt16; Data: UInt16): UInt16; inline;
99-function AtomicXor(var Mem: Int32; Data: Int32): Int32; inline;
100-function AtomicXor(var Mem: UInt32; Data: UInt32): UInt32; inline;
101-function AtomicXor(var Mem: Int64; Data: Int64): Int64; inline;
102-function AtomicXor(var Mem: UInt64; Data: UInt64): UInt64; inline;
41+Written in Rust. Claims to support WASI threads since version 15, but testing showed it didn't work. Even testing with C code, compiled with the official WASI C/C++ SDK didn't work.
10342
104-function AtomicExchange(var Mem: Int8; Data: Int8): Int8; inline;
105-function AtomicExchange(var Mem: UInt8; Data: UInt8): UInt8; inline;
106-function AtomicExchange(var Mem: Int16; Data: Int16): Int16; inline;
107-function AtomicExchange(var Mem: UInt16; Data: UInt16): UInt16; inline;
108-function AtomicExchange(var Mem: Int32; Data: Int32): Int32; inline;
109-function AtomicExchange(var Mem: UInt32; Data: UInt32): UInt32; inline;
110-function AtomicExchange(var Mem: Int64; Data: Int64): Int64; inline;
111-function AtomicExchange(var Mem: UInt64; Data: UInt64): UInt64; inline;
43+For this reason, FPC's WASI thread support was developed and tested against iwasm. However, after FPC's internal linker was updated to support threads, surprisingly, it turned out that wasmtime also works with threads with FPC compiled programs with the internal linker. But it doesn't work when the program was linked with the external linker (which is LLVM's linker). The reason for that is not yet known.
11244
113-function AtomicCompareExchange(var Mem: Int8; Compare, Data: Int8): Int8; inline;
114-function AtomicCompareExchange(var Mem: UInt8; Compare, Data: UInt8): UInt8; inline;
115-function AtomicCompareExchange(var Mem: Int16; Compare, Data: Int16): Int16; inline;
116-function AtomicCompareExchange(var Mem: UInt16; Compare, Data: UInt16): UInt16; inline;
117-function AtomicCompareExchange(var Mem: Int32; Compare, Data: Int32): Int32; inline;
118-function AtomicCompareExchange(var Mem: UInt32; Compare, Data: UInt32): UInt32; inline;
119-function AtomicCompareExchange(var Mem: Int64; Compare, Data: Int64): Int64; inline;
120-function AtomicCompareExchange(var Mem: UInt64; Compare, Data: UInt64): UInt64; inline;
45+## Browsers
12146
122-function AtomicWait(constref Mem: Int32; Compare: Int32; TimeoutNanoseconds: Int64): Int32; inline;
123-function AtomicWait(constref Mem: UInt32; Compare: UInt32; TimeoutNanoseconds: Int64): Int32; inline;
124-function AtomicWait(constref Mem: Int64; Compare: Int64; TimeoutNanoseconds: Int64): Int32; inline;
125-function AtomicWait(constref Mem: UInt64; Compare: UInt64; TimeoutNanoseconds: Int64): Int32; inline;
47+TODO: add info about browser support. Setting up pas2js, etc.
12648
127-function AtomicNotify(constref Mem: Int32; Count: UInt32): UInt32; inline;
128-function AtomicNotify(constref Mem: UInt32; Count: UInt32): UInt32; inline;
129-function AtomicNotify(constref Mem: Int64; Count: UInt32): UInt32; inline;
130-function AtomicNotify(constref Mem: UInt64; Count: UInt32): UInt32; inline;
49+### Chrome
50+
51+Threads are supported since version 74.
52+
53+### Firefox
54+
55+Threads are supported since version 79.
56+
57+### Safari
58+
59+Threads are supported since version 14.1 for desktop Safari and version 14.5 for iOS Safari.
60+
61+# Technical details
62+
63+This page contains some collected informations on the features needed for thread support in WebAssembly (in the browser).
64+
65+Thread support consists of 5 parts:
66+
67+ * Atomic instructions.
68+ * Thread synchronization primitives.
69+ * Shared memory and passive segments.
70+ * Thread Local Storage (threadvars)
71+ * Actually starting a thread.
72+
73+
74+
75+## Atomic instructions
76+
77+[The proposed specs](<https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md>)
78+
79+When the Free Pascal RTL is compiled with -CTwasmthreads, the following RTL functions will use the new atomic instructions and thus should be thread safe in a multithreaded environment:
13180```
81+[InterlockedDecrement](<https://www.freepascal.org/docs-html/current/rtl/system/interlockeddecrement.html>)
82+ [InterlockedIncrement](<https://www.freepascal.org/docs-html/current/rtl/system/interlockedincrement.html>)
83+ [InterlockedExchange](<https://www.freepascal.org/docs-html/current/rtl/system/interlockedexchange.html>)
84+ [InterlockedCompareExchange](<https://www.freepascal.org/docs-html/current/rtl/system/interlockedcompareexchange.html>)
85+ [InterlockedExchangeAdd](<https://www.freepascal.org/docs-html/current/rtl/system/interlockedexchangeadd.html>)
86+```
13287
88+Note that these require proper alignment (4 bytes) of the target, otherwise they trap (i.e. terminate the program with a stack trace).
89+
90+In addition to that, there are many more atomic functions available in the WebAssembly unit:
91+```
92+const
93+ { Special values for the TimeoutNanoseconds parameter of AtomicWait }
94+ awtInfiniteTimeout = -1;
95+ { AtomicWait result values }
96+ awrOk = 0; { woken by another agent in the cluster }
97+ awrNotEqual = 1; { the loaded value did not match the expected value }
98+ awrTimedOut = 2; { not woken before timeout expired }
99+
100+ procedure AtomicFence; inline;
101+
102+ function AtomicLoad(constref Mem: Int8): Int8; inline;
103+ function AtomicLoad(constref Mem: UInt8): UInt8; inline;
104+ function AtomicLoad(constref Mem: Int16): Int16; inline;
105+ function AtomicLoad(constref Mem: UInt16): UInt16; inline;
106+ function AtomicLoad(constref Mem: Int32): Int32; inline;
107+ function AtomicLoad(constref Mem: UInt32): UInt32; inline;
108+ function AtomicLoad(constref Mem: Int64): Int64; inline;
109+ function AtomicLoad(constref Mem: UInt64): UInt64; inline;
110+
111+ procedure AtomicStore(out Mem: Int8; Data: Int8); inline;
112+ procedure AtomicStore(out Mem: UInt8; Data: UInt8); inline;
113+ procedure AtomicStore(out Mem: Int16; Data: Int16); inline;
114+ procedure AtomicStore(out Mem: UInt16; Data: UInt16); inline;
115+ procedure AtomicStore(out Mem: Int32; Data: Int32); inline;
116+ procedure AtomicStore(out Mem: UInt32; Data: UInt32); inline;
117+ procedure AtomicStore(out Mem: Int64; Data: Int64); inline;
118+ procedure AtomicStore(out Mem: UInt64; Data: UInt64); inline;
119+
120+ function AtomicAdd(var Mem: Int8; Data: Int8): Int8; inline;
121+ function AtomicAdd(var Mem: UInt8; Data: UInt8): UInt8; inline;
122+ function AtomicAdd(var Mem: Int16; Data: Int16): Int16; inline;
123+ function AtomicAdd(var Mem: UInt16; Data: UInt16): UInt16; inline;
124+ function AtomicAdd(var Mem: Int32; Data: Int32): Int32; inline;
125+ function AtomicAdd(var Mem: UInt32; Data: UInt32): UInt32; inline;
126+ function AtomicAdd(var Mem: Int64; Data: Int64): Int64; inline;
127+ function AtomicAdd(var Mem: UInt64; Data: UInt64): UInt64; inline;
128+
129+ function AtomicSub(var Mem: Int8; Data: Int8): Int8; inline;
130+ function AtomicSub(var Mem: UInt8; Data: UInt8): UInt8; inline;
131+ function AtomicSub(var Mem: Int16; Data: Int16): Int16; inline;
132+ function AtomicSub(var Mem: UInt16; Data: UInt16): UInt16; inline;
133+ function AtomicSub(var Mem: Int32; Data: Int32): Int32; inline;
134+ function AtomicSub(var Mem: UInt32; Data: UInt32): UInt32; inline;
135+ function AtomicSub(var Mem: Int64; Data: Int64): Int64; inline;
136+ function AtomicSub(var Mem: UInt64; Data: UInt64): UInt64; inline;
137+
138+ function AtomicAnd(var Mem: Int8; Data: Int8): Int8; inline;
139+ function AtomicAnd(var Mem: UInt8; Data: UInt8): UInt8; inline;
140+ function AtomicAnd(var Mem: Int16; Data: Int16): Int16; inline;
141+ function AtomicAnd(var Mem: UInt16; Data: UInt16): UInt16; inline;
142+ function AtomicAnd(var Mem: Int32; Data: Int32): Int32; inline;
143+ function AtomicAnd(var Mem: UInt32; Data: UInt32): UInt32; inline;
144+ function AtomicAnd(var Mem: Int64; Data: Int64): Int64; inline;
145+ function AtomicAnd(var Mem: UInt64; Data: UInt64): UInt64; inline;
146+
147+ function AtomicOr(var Mem: Int8; Data: Int8): Int8; inline;
148+ function AtomicOr(var Mem: UInt8; Data: UInt8): UInt8; inline;
149+ function AtomicOr(var Mem: Int16; Data: Int16): Int16; inline;
150+ function AtomicOr(var Mem: UInt16; Data: UInt16): UInt16; inline;
151+ function AtomicOr(var Mem: Int32; Data: Int32): Int32; inline;
152+ function AtomicOr(var Mem: UInt32; Data: UInt32): UInt32; inline;
153+ function AtomicOr(var Mem: Int64; Data: Int64): Int64; inline;
154+ function AtomicOr(var Mem: UInt64; Data: UInt64): UInt64; inline;
155+
156+ function AtomicXor(var Mem: Int8; Data: Int8): Int8; inline;
157+ function AtomicXor(var Mem: UInt8; Data: UInt8): UInt8; inline;
158+ function AtomicXor(var Mem: Int16; Data: Int16): Int16; inline;
159+ function AtomicXor(var Mem: UInt16; Data: UInt16): UInt16; inline;
160+ function AtomicXor(var Mem: Int32; Data: Int32): Int32; inline;
161+ function AtomicXor(var Mem: UInt32; Data: UInt32): UInt32; inline;
162+ function AtomicXor(var Mem: Int64; Data: Int64): Int64; inline;
163+ function AtomicXor(var Mem: UInt64; Data: UInt64): UInt64; inline;
164+
165+ function AtomicExchange(var Mem: Int8; Data: Int8): Int8; inline;
166+ function AtomicExchange(var Mem: UInt8; Data: UInt8): UInt8; inline;
167+ function AtomicExchange(var Mem: Int16; Data: Int16): Int16; inline;
168+ function AtomicExchange(var Mem: UInt16; Data: UInt16): UInt16; inline;
169+ function AtomicExchange(var Mem: Int32; Data: Int32): Int32; inline;
170+ function AtomicExchange(var Mem: UInt32; Data: UInt32): UInt32; inline;
171+ function AtomicExchange(var Mem: Int64; Data: Int64): Int64; inline;
172+ function AtomicExchange(var Mem: UInt64; Data: UInt64): UInt64; inline;
173+
174+ function AtomicCompareExchange(var Mem: Int8; Compare, Data: Int8): Int8; inline;
175+ function AtomicCompareExchange(var Mem: UInt8; Compare, Data: UInt8): UInt8; inline;
176+ function AtomicCompareExchange(var Mem: Int16; Compare, Data: Int16): Int16; inline;
177+ function AtomicCompareExchange(var Mem: UInt16; Compare, Data: UInt16): UInt16; inline;
178+ function AtomicCompareExchange(var Mem: Int32; Compare, Data: Int32): Int32; inline;
179+ function AtomicCompareExchange(var Mem: UInt32; Compare, Data: UInt32): UInt32; inline;
180+ function AtomicCompareExchange(var Mem: Int64; Compare, Data: Int64): Int64; inline;
181+ function AtomicCompareExchange(var Mem: UInt64; Compare, Data: UInt64): UInt64; inline;
182+
183+ function AtomicWait(constref Mem: Int32; Compare: Int32; TimeoutNanoseconds: Int64): Int32; inline;
184+ function AtomicWait(constref Mem: UInt32; Compare: UInt32; TimeoutNanoseconds: Int64): Int32; inline;
185+ function AtomicWait(constref Mem: Int64; Compare: Int64; TimeoutNanoseconds: Int64): Int32; inline;
186+ function AtomicWait(constref Mem: UInt64; Compare: UInt64; TimeoutNanoseconds: Int64): Int32; inline;
187+
188+ function AtomicNotify(constref Mem: Int32; Count: UInt32): UInt32; inline;
189+ function AtomicNotify(constref Mem: UInt32; Count: UInt32): UInt32; inline;
190+ function AtomicNotify(constref Mem: Int64; Count: UInt32): UInt32; inline;
191+ function AtomicNotify(constref Mem: UInt64; Count: UInt32): UInt32; inline;
192+```
193+
194+## Thread synchronization primitives
195+
196+The RTL provides (and internally uses) several thread synchronization primitives:
197+
198+ * critical sections
199+ * auto reset events
200+ * manual reset events
201+
202+These are all implemented using the atomic instructions, described in the previous section.
203+
133204## Shared memory and passive segments
134205
135-First, the memory needs to be declared shared.
206+First, the memory needs to be declared shared.
136207
137-Secondly, the data segments need to be declared passive segments and extra startup code should be generated by the compiler to initialize them only once. Without this, when the module is instantiated on a new WebWorker (in order to start a new thread), this will cause memory to be initialized again to the initial state, which is not what we want when starting a thread.
208+Secondly, the data segments need to be declared passive segments and extra startup code should be generated by the compiler to initialize them only once. Without this, when the module is instantiated on a new WebWorker (in order to start a new thread), this will cause memory to be initialized again to the initial state, which is not what we want when starting a thread.
138209
139-Some info: [Shared Memory and Passive Segments](https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#shared-memory-and-passive-segments)
210+Some info: [Shared Memory and Passive Segments](<https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#shared-memory-and-passive-segments>)
140211
141-Turns out, this is all done by the LLVM linker (including the initialization startup code), when you pass the appropriate command line options. The compiler now passes these options to the linker, when a program is compiled with -CTbfexceptions. As a side effect, such programs no longer work with "wasmtime run --enable-features threads", but that's because wasmtime's threads support is incomplete.
212+Turns out, this is all done by the LLVM linker (including the initialization startup code), when you pass the appropriate command line options. The compiler now passes these options to the linker, when a program is compiled with -CTbfexceptions. As a side effect, such programs no longer work with "wasmtime run --enable-features threads", but that's because wasmtime's threads support is incomplete.
142213
143214## Thread Local Storage (threadvars)
144215
145-Special consideration is needed to support threadvars.
146-
147-More info here: [Thread Local Storage](https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#thread-local-storage)
216+Special consideration is needed to support threadvars.
148217
149-Code generation for threadvar access is now implemented in FPC. It follows the ABI convention for TLS from Emscripten. However, it causes the LLVM 14 linker to crash. The LLVM 15 (release candidate) linker from Emscripten seems to work. The following intrinsics exist in the system unit, they should be used for things like setting up the TLS of the new thread, or for allocating memory for the TLS in the calling thread, before passing it to the JavaScript helper:
218+More info here: [Thread Local Storage](<https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#thread-local-storage>)
150219
151-``` pascal
220+Code generation for threadvar access is now implemented in FPC. It follows the ABI convention for TLS from Emscripten. However, it causes the LLVM 14 linker to crash. The LLVM 15 (release candidate) linker from Emscripten seems to work. The following intrinsics exist in the system unit, they should be used for things like setting up the TLS of the new thread, or for allocating memory for the TLS in the calling thread, before passing it to the JavaScript helper:
221+```
152222function fpc_wasm32_tls_size: SizeUInt; - returns the TLS size in bytes for the entire program
153-function fpc_wasm32_tls_align: SizeUInt; - returns the alignment requirements for the TLS block for the program
154-function fpc_wasm32_tls_base: Pointer; - the start of the TLS block for the current thread. Only becomes valid after calling __wasm_init_tls (which is a special function, generated by the linker).
155-procedure fpc_wasm32_init_tls(memory: Pointer);external name '__wasm_init_tls';
223+ function fpc_wasm32_tls_align: SizeUInt; - returns the alignment requirements for the TLS block for the program
224+ function fpc_wasm32_tls_base: Pointer; - the start of the TLS block for the current thread. Only becomes valid after calling __wasm_init_tls (which is a special function, generated by the linker).
225+ procedure fpc_wasm32_init_tls(memory: Pointer);external name '__wasm_init_tls';
156226```
157227
158-TODO: initialize threadvars for the main thread, during system unit initialization, before the first ever threadvar is accessed.
228+TLS for the main thread works on startup, thanks to linker magic. The linker allocates a static area for the thread vars and sets initial values for the global variables that point to them, so when a module is instantiated, the threadvars already point to this fixed location. Therefore, it is not necessary to call _fpc_wasm32_init_tls_ for the main thread. However, it is absolutely crucial that _fpc_wasm32_init_tls_ is called on new non-main threads, before the first access to a threadvar, otherwise they would access the threadvars of the main thread, causing all sorts of trouble and race conditions. That's why FPC implements the _wasi_thread_start_ routine in inline asm and calls _fpc_wasm32_init_tls_ before executing any Pascal code. Note that in the branchful exceptions mode (when using _-CTbfexceptions_), a threadvar is accessed every function call, called from Pascal code.
159229
160230## Actually starting a thread
161231
162-Webassembly relies on the hosting environment to actually start threads.
232+Webassembly relies on the hosting environment to actually start threads.
163233
164-Some extra info:
234+### Now
165235
166-- [something like specs](https://web.dev/webassembly-threads/)
167-- [A demo](https://rreverser.com/wasm-bindgen-rayon-demo/)
168-- [related](https://wicg.github.io/file-system-access/)
169-- [WASI Native Threads API proposal](https://github.com/WebAssembly/wasi-native-threads)
236+The [WASI threads](<https://github.com/WebAssembly/wasi-threads>) proposal has been updated to include enough details, and now FPC implements it. A thread is started, by calling the following new API function:
237+```
238+Function wasi_thread_spawn(start_arg: Pointer) : LongInt; external 'wasi' name 'thread-spawn';
239+```
170240
171-Unfortunately, the WASI Native Threads API proposal is very incomplete. Emscripten implements threads using a different API/ABI, but it's quite messy and poorly documented.
241+In turn, the host calls:
242+```
243+procedure wasi_thread_start(tid: longint; start_arg: Pointer);
244+```
172245
173-Starting a thread requires the following steps:
246+Which must be exported by the current module:
247+```
248+exports wasi_thread_start;
249+```
174250
175-- **WebAssembly**: allocating a block of memory for the stack and TLS (threadvar) block for the new thread. This needs to be done in a thread safe manner (can we use the heap?). TODO: How do we determine the stack size for the new thread? Do we use the same main stack size as specified by the {\$M *stacksize*} directive?
176-- **WebAssembly**: calling an external (JavaScript) function and passing at least the following data, that needs to be passed to the new thread by JavaScript code:
251+This is all done internally in the FPC RTL now. Users shouldn't call these functions directly, but instead use the standard FPC APIs for threads (e.g. the _TThread_ class, or calling _BeginThread_ from the system unit).
177252
178-``` pascal
179- - the start address of the new thread procedure
180- - the args that need to be passed to the new thread procedure
181- - the stack and TLS address
182- - TODO: how/where do we determine the thread ID?
183-```
253+### Historically
184254
185-What does Emscripten do?
255+This section is outdated and left here for historical purposes.
186256
187-``` pascal
188-int __pthread_create_js(struct pthread *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
257+Some extra info:
258+
259+ * [something like specs](<https://web.dev/webassembly-threads/>)
260+ * [A demo](<https://rreverser.com/wasm-bindgen-rayon-demo/>)
261+ * [related](<https://wicg.github.io/file-system-access/>)
262+ * [WASI Native Threads API proposal](<https://github.com/WebAssembly/wasi-native-threads>)
263+
264+Unfortunately, the WASI Native Threads API proposal is very incomplete. Emscripten implements threads using a different API/ABI, but it's quite messy and poorly documented.
265+
266+Starting a thread requires the following steps:
267+
268+ * **WebAssembly** : allocating a block of memory for the stack and TLS (threadvar) block for the new thread. This needs to be done in a thread safe manner (can we use the heap?). TODO: How do we determine the stack size for the new thread? Do we use the same main stack size as specified by the {$M _stacksize_} directive?
269+ * **WebAssembly** : calling an external (JavaScript) function and passing at least the following data, that needs to be passed to the new thread by JavaScript code:
270+
189271```
272+- the start address of the new thread procedure
273+ - the args that need to be passed to the new thread procedure
274+ - the stack and TLS address
275+ - TODO: how/where do we determine the thread ID?
276+```
190277
191-Basically, it exposes the pthread structure to the JavaScript code, which expects certain things (stack start, stack limit, TLS start, thread ID, etc.) to be at certain fixed offsets.
278+What does Emscripten do?
279+```
280+int __pthread_create_js(struct pthread *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
281+```
192282
193-What does the WASI Native Threads API proposal do?
283+Basically, it exposes the pthread structure to the JavaScript code, which expects certain things (stack start, stack limit, TLS start, thread ID, etc.) to be at certain fixed offsets.
194284
195-``` pascal
285+What does the WASI Native Threads API proposal do?
286+```
196287status thread_spawn(thread_id* thread_id, const thread_attributes* attrs, thread_start_func* function, thread_args* arg);
197288```
198289
199-It doesn't (yet) define what thread_id or thread_attributes are.
290+It doesn't (yet) define what thread_id or thread_attributes are.
200291
201-- **JavaScript**: TBD...delegate the new thread to a Web Worker, pass the arguments, etc. Decisions: Should the main thread be started in the main JavaScript GUI thread or inside a worker? Emscripten supports both (via PROXY_TO_PTHREAD). Should we support both as well? Other caveats: [1](https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread) and [2](https://emscripten.org/docs/porting/pthreads.html#special-considerations)
202-- **WebAssembly**: a function that sets up the new thread:
292+ * **JavaScript** : TBD...delegate the new thread to a Web Worker, pass the arguments, etc. Decisions: Should the main thread be started in the main JavaScript GUI thread or inside a worker? Emscripten supports both (via PROXY_TO_PTHREAD). Should we support both as well? Other caveats: [[1]](<https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread>) and [[2]](<https://emscripten.org/docs/porting/pthreads.html#special-considerations>)
293+ * **WebAssembly** : a function that sets up the new thread:
203294
204-``` pascal
205- - setting up the stack pointer in linear memory (might need inline asm, an external wasm module or some compiler magic helper code generation, because no local variables can be used, before this is set up)
206- - initialize global variables that hold the TLS (threadvar) block (calling __wasm_init_tls). Threadvars should not be used before this point.
207- - maybe initialize some threadvars, that contain information about the current thread (is it the main thread, is it run in the main browser thread or in a worker, can it use atomic wait, or should it busy-wait instead?)
208- - call the actual thread function and pass its parameters
209295```
210-
211-What does Emscripten do?
296+- setting up the stack pointer in linear memory (might need inline asm, an external wasm module or some compiler magic helper code generation, because no local variables can be used, before this is set up)
297+ - initialize global variables that hold the TLS (threadvar) block (calling __wasm_init_tls). Threadvars should not be used before this point.
298+ - maybe initialize some threadvars, that contain information about the current thread (is it the main thread, is it run in the main browser thread or in a worker, can it use atomic wait, or should it busy-wait instead?)
299+ - call the actual thread function and pass its parameters
300+```
212301
213-```text
302+What does Emscripten do?
303+```
214304void _emscripten_thread_init(pthread_t ptr, int is_main, int is_runtime, int can_block, int start_profiling);
215305```
216306
217-What does the WASI Native Threads API proposal do?
218-
219-``` pascal
220-- It only mentions a function, called _start_thread, but doesn't specify its parameters.
307+What does the WASI Native Threads API proposal do?
221308```
309+- It only mentions a function, called __start_thread_ , but doesn't specify its parameters.
310+```
222311
223312---
224-*From the Free Pascal wiki, page "WebAssembly/Threads", as it stood on 2022-07-15 after a change by Nickysn, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
225-Category: WebAssembly
313+*From the Free Pascal wiki, page "WebAssembly/Threads", as the Internet Archive kept it on 2025-03-14, last changed on 2025-02-28. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history