@@ -5,8 +5,21 @@ const fixtures = require('../common/fixtures');
5
5
const tmpdir = require ( '../common/tmpdir' ) ;
6
6
const fs = require ( 'fs' ) ;
7
7
const path = require ( 'path' ) ;
8
+ const { parseArgs } = require ( 'util' ) ;
8
9
const common = require ( '../common' ) ;
9
10
const { WASI } = require ( 'wasi' ) ;
11
+ const { Worker, isMainThread, parentPort, workerData } = require ( 'worker_threads' ) ;
12
+
13
+ const args = parseArgs ( {
14
+ allowPositionals : true ,
15
+ options : {
16
+ target : {
17
+ type : 'string' ,
18
+ default : 'wasm32-wasip1' ,
19
+ } ,
20
+ } ,
21
+ strict : false ,
22
+ } ) ;
10
23
11
24
function returnOnExitEnvToValue ( env ) {
12
25
const envValue = env . RETURN_ON_EXIT ;
@@ -36,13 +49,182 @@ const wasiPreview1 = new WASI({
36
49
// Validate the getImportObject helper
37
50
assert . strictEqual ( wasiPreview1 . wasiImport ,
38
51
wasiPreview1 . getImportObject ( ) . wasi_snapshot_preview1 ) ;
39
- const modulePathPreview1 = path . join ( wasmDir , `${ process . argv [ 2 ] } .wasm` ) ;
40
- const bufferPreview1 = fs . readFileSync ( modulePathPreview1 ) ;
41
52
42
53
( async ( ) => {
43
- const { instance : instancePreview1 } =
44
- await WebAssembly . instantiate ( bufferPreview1 ,
45
- wasiPreview1 . getImportObject ( ) ) ;
54
+ const importObject = { ...wasiPreview1 . getImportObject ( ) } ;
55
+ if ( args . values . target === 'wasm32-wasip1-threads' ) {
56
+ let nextTid = 43 ;
57
+ const workers = [ ] ;
58
+ const terminateAllThreads = ( ) => {
59
+ workers . forEach ( ( w ) => w . terminate ( ) ) ;
60
+ } ;
61
+ const proc_exit = importObject . wasi_snapshot_preview1 . proc_exit ;
62
+ importObject . wasi_snapshot_preview1 . proc_exit = function ( code ) {
63
+ terminateAllThreads ( ) ;
64
+ return proc_exit . call ( this , code ) ;
65
+ } ;
66
+ const spawn = ( startArg , threadId ) => {
67
+ const tid = nextTid ++ ;
68
+ const name = `pthread-${ tid } ` ;
69
+ const sab = new SharedArrayBuffer ( 8 + 8192 ) ;
70
+ const result = new Int32Array ( sab ) ;
71
+
72
+ const workerData = {
73
+ name,
74
+ startArg,
75
+ tid,
76
+ wasmModule,
77
+ memory : importObject . env . memory ,
78
+ result,
79
+ } ;
80
+
81
+ const worker = new Worker ( __filename , {
82
+ name,
83
+ argv : process . argv . slice ( 2 ) ,
84
+ execArgv : [
85
+ '--experimental-wasi-unstable-preview1' ,
86
+ ] ,
87
+ workerData,
88
+ } ) ;
89
+ workers [ tid ] = worker ;
90
+
91
+ worker . on ( 'message' , ( { cmd, startArg, threadId, tid } ) => {
92
+ if ( cmd === 'loaded' ) {
93
+ worker . unref ( ) ;
94
+ } else if ( cmd === 'thread-spawn' ) {
95
+ spawn ( startArg , threadId ) ;
96
+ } else if ( cmd === 'cleanup-thread' ) {
97
+ workers [ tid ] . terminate ( ) ;
98
+ delete workers [ tid ] ;
99
+ } else if ( cmd === 'terminate-all-threads' ) {
100
+ terminateAllThreads ( ) ;
101
+ }
102
+ } ) ;
46
103
47
- wasiPreview1 . start ( instancePreview1 ) ;
104
+ worker . on ( 'error' , ( e ) => {
105
+ terminateAllThreads ( ) ;
106
+ throw new Error ( e ) ;
107
+ } ) ;
108
+
109
+ const r = Atomics . wait ( result , 0 , 0 , 1000 ) ;
110
+ if ( r === 'timed-out' ) {
111
+ workers [ tid ] . terminate ( ) ;
112
+ delete workers [ tid ] ;
113
+ if ( threadId ) {
114
+ Atomics . store ( threadId , 0 , - 6 ) ;
115
+ Atomics . notify ( threadId , 0 ) ;
116
+ }
117
+ return - 6 ;
118
+ }
119
+ if ( Atomics . load ( result , 0 ) !== 0 ) {
120
+ const decoder = new TextDecoder ( ) ;
121
+ const nameLength = Atomics . load ( result , 1 ) ;
122
+ const messageLength = Atomics . load ( result , 2 ) ;
123
+ const stackLength = Atomics . load ( result , 3 ) ;
124
+ const name = decoder . decode ( sab . slice ( 16 , 16 + nameLength ) ) ;
125
+ const message = decoder . decode ( sab . slice ( 16 + nameLength , 16 + nameLength + messageLength ) ) ;
126
+ const stack = decoder . decode (
127
+ sab . slice ( 16 + nameLength + messageLength ,
128
+ 16 + nameLength + messageLength + stackLength ) ) ;
129
+ const ErrorConstructor = globalThis [ name ] ?? (
130
+ name === 'RuntimeError' ? ( WebAssembly . RuntimeError ?? Error ) : Error ) ;
131
+ const error = new ErrorConstructor ( message ) ;
132
+ Object . defineProperty ( error , 'stack' , {
133
+ value : stack ,
134
+ writable : true ,
135
+ enumerable : false ,
136
+ configurable : true ,
137
+ } ) ;
138
+ Object . defineProperty ( error , 'name' , {
139
+ value : name ,
140
+ writable : true ,
141
+ enumerable : false ,
142
+ configurable : true ,
143
+ } ) ;
144
+ throw error ;
145
+ }
146
+ if ( threadId ) {
147
+ Atomics . store ( threadId , 0 , tid ) ;
148
+ Atomics . notify ( threadId , 0 ) ;
149
+ }
150
+ return tid ;
151
+ } ;
152
+ const memory = isMainThread ? new WebAssembly . Memory ( {
153
+ initial : 16777216 / 65536 ,
154
+ maximum : 2147483648 / 65536 ,
155
+ shared : true ,
156
+ } ) : workerData . memory ;
157
+ importObject . env ??= { } ;
158
+ importObject . env . memory = memory ;
159
+ importObject . wasi = {
160
+ 'thread-spawn' : ( startArg ) => {
161
+ if ( isMainThread ) {
162
+ return spawn ( startArg ) ;
163
+ }
164
+ const threadIdBuffer = new SharedArrayBuffer ( 4 ) ;
165
+ const id = new Int32Array ( threadIdBuffer ) ;
166
+ Atomics . store ( id , 0 , - 1 ) ;
167
+ parentPort . postMessage ( {
168
+ cmd : 'thread-spawn' ,
169
+ startArg,
170
+ threadId : id ,
171
+ } ) ;
172
+ Atomics . wait ( id , 0 , - 1 ) ;
173
+ const tid = Atomics . load ( id , 0 ) ;
174
+ return tid ;
175
+ } ,
176
+ } ;
177
+ }
178
+ let wasmModule ;
179
+ let instancePreview1 ;
180
+ try {
181
+ if ( isMainThread ) {
182
+ const modulePathPreview1 = path . join ( wasmDir , `${ args . positionals [ 0 ] } .wasm` ) ;
183
+ const bufferPreview1 = fs . readFileSync ( modulePathPreview1 ) ;
184
+ wasmModule = await WebAssembly . compile ( bufferPreview1 ) ;
185
+ } else {
186
+ wasmModule = workerData . wasmModule ;
187
+ }
188
+ instancePreview1 = await WebAssembly . instantiate ( wasmModule , importObject ) ;
189
+
190
+ if ( isMainThread ) {
191
+ wasiPreview1 . start ( instancePreview1 ) ;
192
+ } else {
193
+ wasiPreview1 . finalizeBindings ( instancePreview1 , {
194
+ memory : importObject . env . memory ,
195
+ } ) ;
196
+ parentPort . postMessage ( { cmd : 'loaded' } ) ;
197
+ Atomics . store ( workerData . result , 0 , 0 ) ;
198
+ Atomics . notify ( workerData . result , 0 ) ;
199
+ }
200
+ } catch ( e ) {
201
+ if ( ! isMainThread ) {
202
+ const encoder = new TextEncoder ( ) ;
203
+ const nameBuffer = encoder . encode ( e . name ) ;
204
+ const messageBuffer = encoder . encode ( e . message ) ;
205
+ const stackBuffer = encoder . encode ( e . stack ) ;
206
+ Atomics . store ( workerData . result , 0 , 1 ) ;
207
+ Atomics . store ( workerData . result , 1 , nameBuffer . length ) ;
208
+ Atomics . store ( workerData . result , 2 , messageBuffer . length ) ;
209
+ Atomics . store ( workerData . result , 3 , stackBuffer . length ) ;
210
+ const u8arr = new Uint8Array ( workerData . result . buffer ) ;
211
+ u8arr . set ( nameBuffer , 16 ) ;
212
+ u8arr . set ( messageBuffer , 16 + nameBuffer . length ) ;
213
+ u8arr . set ( stackBuffer , 16 + nameBuffer . length + messageBuffer . length ) ;
214
+ Atomics . notify ( workerData . result , 0 ) ;
215
+ }
216
+ throw e ;
217
+ }
218
+ if ( ! isMainThread ) {
219
+ try {
220
+ instancePreview1 . exports . wasi_thread_start ( workerData . tid , workerData . startArg ) ;
221
+ } catch ( err ) {
222
+ if ( err instanceof WebAssembly . RuntimeError ) {
223
+ parentPort . postMessage ( { cmd : 'terminate-all-threads' } ) ;
224
+ }
225
+ throw err
226
+ }
227
+
228
+ parentPort . postMessage ( { cmd : 'cleanup-thread' , tid : workerData . tid } ) ;
229
+ }
48
230
} ) ( ) . then ( common . mustCall ( ) ) ;
0 commit comments