1
+ // META: global=window,dedicatedworker,jsshell
2
+ // META: script=/wasm/jsapi/wasm-module-builder.js
3
+
4
+ // Test for invalid wrappers
5
+ test ( ( ) => {
6
+ assert_throws_js ( TypeError , ( ) => WebAssembly . promising ( { } ) ,
7
+ "Argument 0 must be a function" ) ;
8
+ assert_throws_js ( TypeError , ( ) => WebAssembly . promising ( ( ) => { } ) ,
9
+ "Argument 0 must be a WebAssembly exported function" ) ;
10
+ assert_throws_js ( TypeError , ( ) => WebAssembly . Suspending ( ( ) => { } ) ,
11
+ "WebAssembly.Suspending must be invoked with 'new'" ) ;
12
+ assert_throws_js ( TypeError , ( ) => new WebAssembly . Suspending ( { } ) ,
13
+ "Argument 0 must be a function" ) ;
14
+
15
+ function asmModule ( ) {
16
+ "use asm" ;
17
+
18
+ function x ( v ) {
19
+ v = v | 0 ;
20
+ }
21
+ return x ;
22
+ }
23
+ assert_throws_js ( TypeError , ( ) => WebAssembly . promising ( asmModule ( ) ) ,
24
+ "Argument 0 must be a WebAssembly exported function" ) ;
25
+ } , "Valid use of API" ) ;
26
+
27
+ test ( ( ) => {
28
+ let builder = new WasmModuleBuilder ( ) ;
29
+ builder . addGlobal ( kWasmI32 , true ) . exportAs ( 'g' ) ;
30
+ builder . addFunction ( "test" , kSig_i_v )
31
+ . addBody ( [
32
+ kExprI32Const , 42 ,
33
+ kExprGlobalSet , 0 ,
34
+ kExprI32Const , 0
35
+ ] ) . exportFunc ( ) ;
36
+ let instance = builder . instantiate ( ) ;
37
+ let wrapper = WebAssembly . promising ( instance . exports . test ) ;
38
+ wrapper ( ) ;
39
+ assert_equals ( 42 , instance . exports . g . value ) ;
40
+ } , "Promising function always entered" ) ;
41
+
42
+ promise_test ( async ( ) => {
43
+ let builder = new WasmModuleBuilder ( ) ;
44
+ let import_index = builder . addImport ( 'm' , 'import' , kSig_i_v ) ;
45
+ builder . addFunction ( "test" , kSig_i_i )
46
+ . addBody ( [
47
+ kExprCallFunction , import_index , // suspend
48
+ ] ) . exportFunc ( ) ;
49
+ let js_import = ( ) => 42 ;
50
+ let instance = builder . instantiate ( {
51
+ m : {
52
+ import : js_import
53
+ }
54
+ } ) ;
55
+ let wrapped_export = WebAssembly . promising ( instance . exports . test ) ;
56
+ let export_promise = wrapped_export ( ) ;
57
+ assert_true ( export_promise instanceof Promise ) ;
58
+ assert_equals ( await export_promise , 42 ) ;
59
+ } , "Always get a Promise" ) ;
60
+
61
+ promise_test ( async ( ) => {
62
+ let builder = new WasmModuleBuilder ( ) ;
63
+ let import_index = builder . addImport ( 'm' , 'import' , kSig_i_i ) ;
64
+ builder . addFunction ( "test" , kSig_i_i )
65
+ . addBody ( [
66
+ kExprLocalGet , 0 ,
67
+ kExprCallFunction , import_index , // suspend
68
+ ] ) . exportFunc ( ) ;
69
+ let js_import = new WebAssembly . Suspending ( ( ) => Promise . resolve ( 42 ) ) ;
70
+ let instance = builder . instantiate ( {
71
+ m : {
72
+ import : js_import
73
+ }
74
+ } ) ;
75
+ let wrapped_export = WebAssembly . promising ( instance . exports . test ) ;
76
+ let export_promise = wrapped_export ( ) ;
77
+ assert_true ( export_promise instanceof Promise ) ;
78
+ assert_equals ( await export_promise , 42 ) ;
79
+ } , "Suspend once" ) ;
80
+
81
+ promise_test ( async ( ) => {
82
+ let builder = new WasmModuleBuilder ( ) ;
83
+ builder . addGlobal ( kWasmI32 , true ) . exportAs ( 'g' ) ;
84
+ let import_index = builder . addImport ( 'm' , 'import' , kSig_i_v ) ;
85
+ // void test() {
86
+ // for (i = 0; i < 5; ++i) {
87
+ // g = g + await import();
88
+ // }
89
+ // }
90
+ builder . addFunction ( "test" , kSig_v_i )
91
+ . addLocals ( {
92
+ i32_count : 1
93
+ } )
94
+ . addBody ( [
95
+ kExprI32Const , 5 ,
96
+ kExprLocalSet , 1 ,
97
+ kExprLoop , kWasmStmt ,
98
+ kExprCallFunction , import_index , // suspend
99
+ kExprGlobalGet , 0 ,
100
+ kExprI32Add ,
101
+ kExprGlobalSet , 0 ,
102
+ kExprLocalGet , 1 ,
103
+ kExprI32Const , 1 ,
104
+ kExprI32Sub ,
105
+ kExprLocalTee , 1 ,
106
+ kExprBrIf , 0 ,
107
+ kExprEnd ,
108
+ ] ) . exportFunc ( ) ;
109
+ let i = 0 ;
110
+
111
+ function js_import ( ) {
112
+ return Promise . resolve ( ++ i ) ;
113
+ } ;
114
+ let wasm_js_import = new WebAssembly . Suspending ( js_import ) ;
115
+ let instance = builder . instantiate ( {
116
+ m : {
117
+ import : wasm_js_import
118
+ }
119
+ } ) ;
120
+ let wrapped_export = WebAssembly . promising ( instance . exports . test ) ;
121
+ let export_promise = wrapped_export ( ) ;
122
+ assert_equals ( instance . exports . g . value , 0 ) ;
123
+ assert_true ( export_promise instanceof Promise ) ;
124
+ await export_promise ;
125
+ assert_equals ( instance . exports . g . value , 15 ) ;
126
+ } , "Suspend/resume in a loop" ) ;
127
+
128
+ promise_test ( async ( ) => {
129
+ let builder = new WasmModuleBuilder ( ) ;
130
+ let import_index = builder . addImport ( 'm' , 'import' , kSig_i_v ) ;
131
+ builder . addFunction ( "test" , kSig_i_v )
132
+ . addBody ( [
133
+ kExprCallFunction , import_index , // suspend
134
+ ] ) . exportFunc ( ) ;
135
+ let js_import = new WebAssembly . Suspending ( ( ) => Promise . resolve ( 42 ) ) ;
136
+ let instance = builder . instantiate ( {
137
+ m : {
138
+ import : js_import
139
+ }
140
+ } ) ;
141
+ let wrapped_export = WebAssembly . promising ( instance . exports . test ) ;
142
+ assert_equals ( await wrapped_export ( ) , 42 ) ;
143
+
144
+ // Also try with a JS function with a mismatching arity.
145
+ js_import = new WebAssembly . Suspending ( ( unused ) => Promise . resolve ( 42 ) ) ;
146
+ instance = builder . instantiate ( {
147
+ m : {
148
+ import : js_import
149
+ }
150
+ } ) ;
151
+ wrapped_export = WebAssembly . promising ( instance . exports . test ) ;
152
+ assert_equals ( await wrapped_export ( ) , 42 ) ;
153
+
154
+ // Also try with a proxy.
155
+ js_import = new WebAssembly . Suspending ( new Proxy ( ( ) => Promise . resolve ( 42 ) , { } ) ) ;
156
+ instance = builder . instantiate ( {
157
+ m : {
158
+ import : js_import
159
+ }
160
+ } ) ;
161
+ wrapped_export = WebAssembly . promising ( instance . exports . test ) ;
162
+ assert_equals ( await wrapped_export ( ) , 42 ) ;
163
+ } , "Suspending with mismatched args and via Proxy" ) ;
164
+
165
+ function recordAbeforeB ( ) {
166
+ let AbeforeB = [ ] ;
167
+ let setA = ( ) => {
168
+ AbeforeB . push ( "A" )
169
+ }
170
+ let setB = ( ) => {
171
+ AbeforeB . push ( "B" )
172
+ }
173
+ let isAbeforeB = ( ) =>
174
+ AbeforeB [ 0 ] == "A" && AbeforeB [ 1 ] == "B" ;
175
+
176
+ let isBbeforeA = ( ) =>
177
+ AbeforeB [ 0 ] == "B" && AbeforeB [ 1 ] == "A" ;
178
+
179
+ return {
180
+ setA : setA ,
181
+ setB : setB ,
182
+ isAbeforeB : isAbeforeB ,
183
+ isBbeforeA : isBbeforeA ,
184
+ }
185
+ }
186
+
187
+ promise_test ( async ( ) => {
188
+ let builder = new WasmModuleBuilder ( ) ;
189
+ let AbeforeB = recordAbeforeB ( ) ;
190
+ let import42_index = builder . addImport ( 'm' , 'import42' , kSig_i_i ) ;
191
+ let importSetA_index = builder . addImport ( 'm' , 'setA' , kSig_v_v ) ;
192
+ builder . addFunction ( "test" , kSig_i_i )
193
+ . addBody ( [
194
+ kExprLocalGet , 0 ,
195
+ kExprCallFunction , import42_index , // suspend?
196
+ kExprCallFunction , importSetA_index
197
+ ] ) . exportFunc ( ) ;
198
+ let import42 = new WebAssembly . Suspending ( ( ) => Promise . resolve ( 42 ) ) ;
199
+ let instance = builder . instantiate ( {
200
+ m : {
201
+ import42 : import42 ,
202
+ setA : AbeforeB . setA
203
+ }
204
+ } ) ;
205
+
206
+ let wrapped_export = WebAssembly . promising ( instance . exports . test ) ;
207
+
208
+ let exported_promise = wrapped_export ( ) ;
209
+
210
+ AbeforeB . setB ( ) ;
211
+
212
+ assert_equals ( await exported_promise , 42 ) ;
213
+
214
+ assert_true ( AbeforeB . isBbeforeA ( ) ) ;
215
+ } , "Make sure we actually suspend" ) ;
216
+
217
+ promise_test ( async ( ) => {
218
+ let builder = new WasmModuleBuilder ( ) ;
219
+ let AbeforeB = recordAbeforeB ( ) ;
220
+ let import42_index = builder . addImport ( 'm' , 'import42' , kSig_i_i ) ;
221
+ let importSetA_index = builder . addImport ( 'm' , 'setA' , kSig_v_v ) ;
222
+ builder . addFunction ( "test" , kSig_i_i )
223
+ . addBody ( [
224
+ kExprLocalGet , 0 ,
225
+ kExprCallFunction , import42_index , // suspend?
226
+ kExprCallFunction , importSetA_index
227
+ ] ) . exportFunc ( ) ;
228
+ let import42 = new WebAssembly . Suspending ( ( ) => 42 ) ;
229
+ let instance = builder . instantiate ( {
230
+ m : {
231
+ import42 : import42 ,
232
+ setA : AbeforeB . setA
233
+ }
234
+ } ) ;
235
+
236
+ let wrapped_export = WebAssembly . promising ( instance . exports . test ) ;
237
+
238
+ let exported_promise = wrapped_export ( ) ;
239
+ AbeforeB . setB ( ) ;
240
+ assert_true ( AbeforeB . isAbeforeB ( ) ) ;
241
+
242
+ assert_equals ( await exported_promise , 42 ) ;
243
+ } , "Do not suspend if the import's return value is not a Promise" ) ;
244
+
245
+ promise_test ( async ( t ) => {
246
+ let tag = new WebAssembly . Tag ( {
247
+ parameters : [ 'i32' ]
248
+ } ) ;
249
+ let builder = new WasmModuleBuilder ( ) ;
250
+ let import_index = builder . addImport ( 'm' , 'import' , kSig_i_i ) ;
251
+ let tag_index = builder . addImportedTag ( 'm' , 'tag' , kSig_v_i ) ;
252
+ builder . addFunction ( "test" , kSig_i_i )
253
+ . addBody ( [
254
+ kExprTry , kWasmI32 ,
255
+ kExprLocalGet , 0 ,
256
+ kExprCallFunction , import_index ,
257
+ kExprCatch , tag_index ,
258
+ kExprEnd
259
+ ] ) . exportFunc ( ) ;
260
+
261
+ function js_import ( unused ) {
262
+ return Promise . reject ( new WebAssembly . Exception ( tag , [ 42 ] ) ) ;
263
+ } ;
264
+ let wasm_js_import = new WebAssembly . Suspending ( js_import ) ;
265
+
266
+ let instance = builder . instantiate ( {
267
+ m : {
268
+ import : wasm_js_import ,
269
+ tag : tag
270
+ }
271
+ } ) ;
272
+ let wrapped_export = WebAssembly . promising ( instance . exports . test ) ;
273
+ let export_promise = wrapped_export ( ) ;
274
+ assert_true ( export_promise instanceof Promise ) ;
275
+ assert_equals ( await export_promise , 42 ) ;
276
+ } , "Catch rejected promise" ) ;
277
+
278
+ async function TestSandwich ( suspend ) {
279
+ // Set up a 'sandwich' scenario. The call chain looks like:
280
+ // top (wasm) -> outer (js) -> bottom (wasm) -> inner (js)
281
+ // If 'suspend' is true, the inner JS function returns a Promise, which
282
+ // suspends the bottom wasm function, which returns a Promise, which suspends
283
+ // the top wasm function, which returns a Promise. The inner Promise
284
+ // resolves first, which resumes the bottom continuation. Then the outer
285
+ // promise resolves which resumes the top continuation.
286
+ // If 'suspend' is false, the bottom JS function returns a regular value and
287
+ // no computation is suspended.
288
+ let builder = new WasmModuleBuilder ( ) ;
289
+ let inner_index = builder . addImport ( 'm' , 'inner' , kSig_i_i ) ;
290
+ let outer_index = builder . addImport ( 'm' , 'outer' , kSig_i_i ) ;
291
+ builder . addFunction ( "top" , kSig_i_i )
292
+ . addBody ( [
293
+ kExprLocalGet , 0 ,
294
+ kExprCallFunction , outer_index
295
+ ] ) . exportFunc ( ) ;
296
+ builder . addFunction ( "bottom" , kSig_i_i )
297
+ . addBody ( [
298
+ kExprLocalGet , 0 ,
299
+ kExprCallFunction , inner_index
300
+ ] ) . exportFunc ( ) ;
301
+
302
+ let inner = new WebAssembly . Suspending ( ( ) => suspend ? Promise . resolve ( 42 ) : 43 ) ;
303
+
304
+ let export_inner ;
305
+ let outer = new WebAssembly . Suspending ( ( ) => export_inner ( ) ) ;
306
+
307
+ let instance = builder . instantiate ( {
308
+ m : {
309
+ inner,
310
+ outer
311
+ }
312
+ } ) ;
313
+ export_inner = WebAssembly . promising ( instance . exports . bottom ) ;
314
+ let export_top = WebAssembly . promising ( instance . exports . top ) ;
315
+ let result = export_top ( ) ;
316
+ assert_true ( result instanceof Promise ) ;
317
+ if ( suspend )
318
+ assert_equals ( await result , 42 ) ;
319
+ else
320
+ assert_equals ( await result , 43 ) ;
321
+ }
322
+
323
+ promise_test ( async ( ) => {
324
+ TestSandwich ( true ) ;
325
+ } , "Test sandwich with suspension" ) ;
326
+
327
+ promise_test ( async ( ) => {
328
+ TestSandwich ( false ) ;
329
+ } , "Test sandwich with no suspension" ) ;
330
+
331
+ test ( ( ) => {
332
+ // Check that a promising function with no return is allowed.
333
+ let builder = new WasmModuleBuilder ( ) ;
334
+ builder . addFunction ( "export" , kSig_v_v ) . addBody ( [ ] ) . exportFunc ( ) ;
335
+ let instance = builder . instantiate ( ) ;
336
+ let export_wrapper = WebAssembly . promising ( instance . exports . export ) ;
337
+ let export_sig = export_wrapper . type ( ) ;
338
+ assert_array_equals ( export_sig . parameters , [ ] ) ;
339
+ assert_array_equals ( export_sig . results , [ 'externref' ] ) ;
340
+ } , "Promising with no return" ) ;
341
+
342
+ promise_test ( async ( ) => {
343
+ let builder1 = new WasmModuleBuilder ( ) ;
344
+ let import_index = builder1 . addImport ( 'm' , 'import' , kSig_i_v ) ;
345
+ builder1 . addFunction ( "f" , kSig_i_v )
346
+ . addBody ( [
347
+ kExprCallFunction , import_index , // suspend
348
+ kExprI32Const , 1 ,
349
+ kExprI32Add ,
350
+ ] ) . exportFunc ( ) ;
351
+ let js_import = new WebAssembly . Suspending ( ( ) => Promise . resolve ( 1 ) ) ;
352
+ let instance1 = builder1 . instantiate ( {
353
+ m : {
354
+ import : js_import
355
+ }
356
+ } ) ;
357
+ let builder2 = new WasmModuleBuilder ( ) ;
358
+ import_index = builder2 . addImport ( 'm' , 'import' , kSig_i_v ) ;
359
+ builder2 . addFunction ( "main" , kSig_i_v )
360
+ . addBody ( [
361
+ kExprCallFunction , import_index ,
362
+ kExprI32Const , 1 ,
363
+ kExprI32Add ,
364
+ ] ) . exportFunc ( ) ;
365
+ let instance2 = builder2 . instantiate ( {
366
+ m : {
367
+ import : instance1 . exports . f
368
+ }
369
+ } ) ;
370
+ let wrapped_export = WebAssembly . promising ( instance2 . exports . main ) ;
371
+ assert_equals ( await wrapped_export ( ) , 3 ) ;
372
+ } , "Suspend two modules" ) ;
0 commit comments