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