js-promise-integration.any.js (11754B)
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 241 assert_equals(await exported_promise, 42); 242 assert_true(AbeforeB.isBbeforeA()); 243 }, "Do suspend even if the import's return value is not a Promise by wrapping it with Promise.resolve"); 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 assert_true(export_wrapper instanceof Function); 338 },"Promising with no return"); 339 340 promise_test(async () => { 341 let builder1 = new WasmModuleBuilder(); 342 let import_index = builder1.addImport('m', 'import', kSig_i_v); 343 builder1.addFunction("f", kSig_i_v) 344 .addBody([ 345 kExprCallFunction, import_index, // suspend 346 kExprI32Const, 1, 347 kExprI32Add, 348 ]).exportFunc(); 349 let js_import = new WebAssembly.Suspending(() => Promise.resolve(1)); 350 let instance1 = builder1.instantiate({ 351 m: { 352 import: js_import 353 } 354 }); 355 let builder2 = new WasmModuleBuilder(); 356 import_index = builder2.addImport('m', 'import', kSig_i_v); 357 builder2.addFunction("main", kSig_i_v) 358 .addBody([ 359 kExprCallFunction, import_index, 360 kExprI32Const, 1, 361 kExprI32Add, 362 ]).exportFunc(); 363 let instance2 = builder2.instantiate({ 364 m: { 365 import: instance1.exports.f 366 } 367 }); 368 let wrapped_export = WebAssembly.promising(instance2.exports.main); 369 assert_equals(await wrapped_export(), 3); 370 },"Suspend two modules");