diff --git a/src/preamble.js b/src/preamble.js index 50c14df482bc3..1cdac8b447ec2 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -649,7 +649,7 @@ function getBinarySync(file) { #endif } -function getBinaryPromise(binaryFile) { +async function getWasmBinary(binaryFile) { #if !SINGLE_FILE // If we don't have the binary yet, load it asynchronously using readAsync. if (!wasmBinary @@ -658,16 +658,18 @@ function getBinaryPromise(binaryFile) { #endif ) { // Fetch the binary using readAsync - return readAsync(binaryFile).then( - (response) => new Uint8Array(/** @type{!ArrayBuffer} */(response)), - // Fall back to getBinarySync if readAsync fails - () => getBinarySync(binaryFile) - ); + try { + /** @type{!ArrayBuffer} */ + var response = await readAsync(binaryFile); + return new Uint8Array(response); + } catch { + // Fall back to getBinarySync below; + } } #endif // Otherwise, getBinarySync should be able to get it synchronously - return Promise.resolve(getBinarySync(binaryFile)); + return getBinarySync(binaryFile); } #if LOAD_SOURCE_MAP @@ -775,56 +777,47 @@ function resetPrototype(constructor, attrs) { #endif #if WASM_ASYNC_COMPILATION -function instantiateArrayBuffer(binaryFile, imports) { +async function instantiateArrayBuffer(binaryFile, imports) { + try { + var binary = await getWasmBinary(binaryFile); + var instance = await WebAssembly.instantiate(binary, imports); #if USE_OFFSET_CONVERTER - var savedBinary; + // wasmOffsetConverter needs to be assigned before calling resolve. + // See comments below in instantiateAsync. + wasmOffsetConverter = new WasmOffsetConverter(binary, instance.module); #endif - return new Promise((resolve, reject) => { - getBinaryPromise(binaryFile).then((binary) => { -#if USE_OFFSET_CONVERTER - savedBinary = binary; -#endif - return WebAssembly.instantiate(binary, imports); -#if USE_OFFSET_CONVERTER - }).then((instance) => { - // wasmOffsetConverter needs to be assigned before calling resolve. - // See comments below in instantiateAsync. - wasmOffsetConverter = new WasmOffsetConverter(savedBinary, instance.module); - return instance; -#endif - }).then(resolve, (reason) => { - err(`failed to asynchronously prepare wasm: ${reason}`); - + return instance; + } catch (reason) { + err(`failed to asynchronously prepare wasm: ${reason}`); #if WASM == 2 #if ENVIRONMENT_MAY_BE_NODE || ENVIRONMENT_MAY_BE_SHELL - if (typeof location != 'undefined') { -#endif - // WebAssembly compilation failed, try running the JS fallback instead. - var search = location.search; - if (search.indexOf('_rwasm=0') < 0) { - location.href += (search ? search + '&' : '?') + '_rwasm=0'; - // Return here to avoid calling abort() below. The application - // still has a chance to start successfully do we don't want to - // trigger onAbort or onExit handlers. - return; - } -#if ENVIRONMENT_MAY_BE_NODE || ENVIRONMENT_MAY_BE_SHELL + if (typeof location != 'undefined') { +#endif + // WebAssembly compilation failed, try running the JS fallback instead. + var search = location.search; + if (search.indexOf('_rwasm=0') < 0) { + location.href += (search ? search + '&' : '?') + '_rwasm=0'; + // Return here to avoid calling abort() below. The application + // still has a chance to start successfully do we don't want to + // trigger onAbort or onExit handlers. + return; } +#if ENVIRONMENT_MAY_BE_NODE || ENVIRONMENT_MAY_BE_SHELL + } #endif #endif // WASM == 2 #if ASSERTIONS - // Warn on some common problems. - if (isFileURI(wasmBinaryFile)) { - err(`warning: Loading from a file URI (${wasmBinaryFile}) is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing`); - } + // Warn on some common problems. + if (isFileURI(wasmBinaryFile)) { + err(`warning: Loading from a file URI (${wasmBinaryFile}) is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing`); + } #endif - abort(reason); - }); - }); + abort(reason); + } } -function instantiateAsync(binary, binaryFile, imports) { +async function instantiateAsync(binary, binaryFile, imports) { #if !SINGLE_FILE if (!binary && typeof WebAssembly.instantiateStreaming == 'function' && @@ -843,56 +836,44 @@ function instantiateAsync(binary, binaryFile, imports) { !ENVIRONMENT_IS_NODE && #endif typeof fetch == 'function') { - return new Promise((resolve) => { - fetch(binaryFile, {{{ makeModuleReceiveExpr('fetchSettings', "{ credentials: 'same-origin' }") }}}).then((response) => { - // Suppress closure warning here since the upstream definition for - // instantiateStreaming only allows Promise rather than - // an actual Response. - // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure is fixed. - /** @suppress {checkTypes} */ - var result = WebAssembly.instantiateStreaming(response, imports); - + try { + var response = fetch(binaryFile, {{{ makeModuleReceiveExpr('fetchSettings', "{ credentials: 'same-origin' }") }}}); #if USE_OFFSET_CONVERTER - // We need the wasm binary for the offset converter. Clone the response - // in order to get its arrayBuffer (cloning should be more efficient - // than doing another entire request). - // (We must clone the response now in order to use it later, as if we - // try to clone it asynchronously lower down then we will get a - // "response was already consumed" error.) - var clonedResponsePromise = response.clone().arrayBuffer(); -#endif - - result.then( + // We need the wasm binary for the offset converter. Clone the response + // in order to get its arrayBuffer (cloning should be more efficient + // than doing another entire request). + // (We must clone the response now in order to use it later, as if we + // try to clone it asynchronously lower down then we will get a + // "response was already consumed" error.) + var clonedResponse = (await response).clone(); +#endif + var result = WebAssembly.instantiateStreaming(response, imports); #if USE_OFFSET_CONVERTER - (instantiationResult) => { - // When using the offset converter, we must interpose here. First, - // the instantiation result must arrive (if it fails, the error - // handling later down will handle it). Once it arrives, we can - // initialize the offset converter. And only then is it valid to - // call receiveInstantiationResult, as that function will use the - // offset converter (in the case of pthreads, it will create the - // pthreads and send them the offsets along with the wasm instance). - - clonedResponsePromise.then((arrayBufferResult) => { - wasmOffsetConverter = new WasmOffsetConverter(new Uint8Array(arrayBufferResult), instantiationResult.module); - resolve(instantiationResult); - }, - (reason) => err(`failed to initialize offset-converter: ${reason}`) - ); - }, + // When using the offset converter, we must interpose here. First, + // the instantiation result must arrive (if it fails, the error + // handling later down will handle it). Once it arrives, we can + // initialize the offset converter. And only then is it valid to + // call receiveInstantiationResult, as that function will use the + // offset converter (in the case of pthreads, it will create the + // pthreads and send them the offsets along with the wasm instance). + var instantiationResult = await result; + var arrayBufferResult = await clonedResponse.arrayBuffer(); + try { + wasmOffsetConverter = new WasmOffsetConverter(new Uint8Array(arrayBufferResult), instantiationResult.module); + } catch (reason) { + err(`failed to initialize offset-converter: ${reason}`); + } + return instantiationResult; #else - resolve, -#endif - (reason) => { - // We expect the most common failure cause to be a bad MIME type for the binary, - // in which case falling back to ArrayBuffer instantiation should work. - err(`wasm streaming compile failed: ${reason}`); - err('falling back to ArrayBuffer instantiation'); - return resolve(instantiateArrayBuffer(binaryFile, imports)); - } - ); - }); - }); + return await result; +#endif + } catch (reason) { + // We expect the most common failure cause to be a bad MIME type for the binary, + // in which case falling back to ArrayBuffer instantiation should work. + err(`wasm streaming compile failed: ${reason}`); + err('falling back to ArrayBuffer instantiation'); + // fall back of instantiateArrayBuffer below + }; } #endif return instantiateArrayBuffer(binaryFile, imports); @@ -938,7 +919,13 @@ function getWasmImports() { // Create the wasm instance. // Receives the wasm imports, returns the exports. +#if WASM_ASYNC_COMPILATION +// Funnily enough in JS the `async` keyword has to be on the same line as the +// function keyword. +async function createWasm() { +#else function createWasm() { +#endif // Load the wasm module and create an instance of using native support in the JS engine. // handle a generated wasm instance, receiving its exports and // performing other necessary setup @@ -1106,17 +1093,23 @@ function createWasm() { #if RUNTIME_DEBUG dbg('asynchronously preparing wasm'); #endif - instantiateAsync(wasmBinary, wasmBinaryFile, info).then(receiveInstantiationResult) #if MODULARIZE - // If instantiation fails, reject the module ready promise. - .catch(readyPromiseReject) + try { #endif - ; + var result = await instantiateAsync(wasmBinary, wasmBinaryFile, info); + receiveInstantiationResult(result); #if LOAD_SOURCE_MAP - getSourceMapPromise().then(receiveSourceMapJSON); + receiveSourceMapJSON(await getSourceMapPromise()); #endif - return {}; // no exports yet; we'll fill them in later -#else + return result; +#if MODULARIZE + } catch (e) { + // If instantiation fails, reject the module ready promise. + readyPromiseReject(e); + throw e; + } +#endif +#else // WASM_ASYNC_COMPILATION var result = instantiateSync(wasmBinaryFile, info); #if PTHREADS || MAIN_MODULE return receiveInstance(result[0], result[1]); @@ -1126,7 +1119,7 @@ function createWasm() { // When the regression is fixed, we can remove this if/else. return receiveInstance(result[0]); #endif -#endif +#endif // WASM_ASYNC_COMPILATION } #if !WASM_BIGINT diff --git a/test/test_browser.py b/test/test_browser.py index b5a6b97d93611..3e54d60157349 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5519,7 +5519,7 @@ def test(args, expect_fail): if expect_fail: js = read_file('a.out.js') create_file('a.out.js', 'let origFetch = fetch; fetch = undefined;\n' + js) - return self.run_browser('a.out.html', '/report_result?exception:fetch is not a function') + return self.run_browser('a.out.html', '/report_result?abort:both async and sync fetching of the wasm failed') else: return self.run_browser('a.out.html', '/report_result?exit:42') diff --git a/test/test_other.py b/test/test_other.py index 7e0e11ffb7a17..7ce2a5734dd3b 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -2955,20 +2955,25 @@ def test_m_mm(self): self.assertNotContained('error', proc.stderr) @uses_canonical_tmp - def test_emcc_debug_files(self): - for opts in (0, 1, 2, 3): - for debug in (None, '1', '2'): - print(opts, debug) - if os.path.exists(self.canonical_temp_dir): - shutil.rmtree(self.canonical_temp_dir) - - with env_modify({'EMCC_DEBUG': debug}): - self.run_process([EMCC, test_file('hello_world.c'), '-O' + str(opts)], stderr=PIPE) - if debug is None: - self.assertFalse(os.path.exists(self.canonical_temp_dir)) - else: - print(sorted(os.listdir(self.canonical_temp_dir))) - self.assertExists(os.path.join(self.canonical_temp_dir, 'emcc-03-original.js')) + @parameterized({ + 'O0': ('-O0',), + 'O1': ('-O1',), + 'O2': ('-O2',), + 'O3': ('-O3',), + }) + def test_emcc_debug_files(self, opt): + for debug in (None, '1', '2'): + print('debug =', debug) + if os.path.exists(self.canonical_temp_dir): + shutil.rmtree(self.canonical_temp_dir) + + with env_modify({'EMCC_DEBUG': debug}): + self.run_process([EMCC, test_file('hello_world.c'), opt], stderr=PIPE) + if debug is None: + self.assertFalse(os.path.exists(self.canonical_temp_dir)) + else: + print(sorted(os.listdir(self.canonical_temp_dir))) + self.assertExists(os.path.join(self.canonical_temp_dir, 'emcc-03-original.js')) def test_debuginfo_line_tables_only(self): def test(do_compile): diff --git a/tools/emscripten.py b/tools/emscripten.py index 65584d17e0144..c2d36561d7776 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -989,7 +989,10 @@ def create_module(receiving, metadata, global_exports, library_symbols): module.append('var wasmImports = %s;\n' % sending) if not settings.MINIMAL_RUNTIME: - module.append("var wasmExports = createWasm();\n") + if settings.WASM_ASYNC_COMPILATION: + module.append("var wasmExports;\ncreateWasm();\n") + else: + module.append("var wasmExports = createWasm();\n") module.append(receiving) if settings.SUPPORT_LONGJMP == 'emscripten' or not settings.DISABLE_EXCEPTION_CATCHING: