Skip to content

Commit

Permalink
Use async/await instead of promises for wasm loading. NFC
Browse files Browse the repository at this point in the history
These get lowered away by babel when targetting older engines.
  • Loading branch information
sbc100 committed Dec 9, 2024
1 parent 1e05a29 commit 60c299c
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 116 deletions.
193 changes: 93 additions & 100 deletions src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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' &&
Expand All @@ -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<Repsponse> 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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]);
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down
33 changes: 19 additions & 14 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
5 changes: 4 additions & 1 deletion tools/emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 60c299c

Please sign in to comment.