Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
36 changes: 35 additions & 1 deletion lib/web_ui/flutter_js/src/entrypoint_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,13 @@ export class FlutterEntrypointLoader {
if (this._ttPolicy != null) {
jsSupportRuntimeUri = this._ttPolicy.createScriptURL(jsSupportRuntimeUri);
}
const dartModulePromise = WebAssembly.compileStreaming(fetch(moduleUri));

const stringBuiltinSupported = _detectWasmStringBuiltin();

const dartModulePromise = WebAssembly.compileStreaming(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may consider in the future to make the .mjs file export 3 things:

  • (new) Compile API wrapping WebAssembly.compile*
  • Instantiate API wrapping WebAssembly.instantiate* (we already provide this)
  • Invoke main API (we already provide this)

That would encapsulate this logic here in the dart2wasm compiler instead of making a user of the compiler aware of what builtins the compiled code requires.

There's an aspect to think about: The current way allows flutter JS code to be loaded in parallel to the mjs file, then we'd have a dependency from former to the latter - which may harm startup latency. This may especially be noticeable if we use imported strings (which may make the mjs file much bigger). So we'd have to think whether it would make sense to do that or not.

Anyways, that's not needed right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you want separate compilation and instantiation steps? Currently we don't do anything with the compiled module other than instantiating it. We could combine both steps in a single export.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also offer this in the mjs api:

  • (new) Compile&Instantiate API wrapping WebAssembly.compile* and WebAssembly.instantiate*

The setup should be optimized for startup time / initial page load. So we should try to do as much in parallel we can. So one advantage of separating the two, is that it allows the browser to download the wasm file & do some work in the streaming compile step in parallel to downloading/parsing/running the mjs file (which may become bigger once we have imported strings).

I don't know if Chrome/V8 does a lot of work in the compile step or most in instantiation step. May also differ between browsers.

This would need to be measured.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So one advantage of separating the two, is that it allows the browser to download the wasm file & do some work in the streaming compile step in parallel to downloading/parsing/running the mjs file (which may become bigger once we have imported strings).

If the compile function is provided by the mjs file so you can't compile the Wasm and download mjs file with lots of strings in parallel, right?

Maybe strings can be in a separate file that we download in parallel.

fetch(moduleUri),
stringBuiltinSupported ? {builtins: 'js-string'} : {}
);

const jsSupportRuntime = await import(jsSupportRuntimeUri);

Expand All @@ -171,6 +177,23 @@ export class FlutterEntrypointLoader {
} else {
imports = {};
}

if (!stringBuiltinSupported) {
imports['wasm:js-string'] = {
"charCodeAt": (s, i) => s.charCodeAt(i),
"compare": (s1, s2) => {
if (s1 < s2) return -1;
if (s1 > s2) return 1;
return 0;
},
"concat": (s1, s2) => s1 + s2,
"equals": (s1, s2) => s1 === s2,
"fromCharCode": (i) => String.fromCharCode(i),
"length": (s) => s.length,
"substring": (s, a, b) => s.substring(a, b),
};
}

const moduleInstance = await jsSupportRuntime.instantiate(dartModulePromise, imports);
await jsSupportRuntime.invoke(moduleInstance);
}
Expand All @@ -195,4 +218,15 @@ export class FlutterEntrypointLoader {
scriptTag.src = trustedUrl;
return scriptTag;
}

/// Returns whether the `js-string` Wasm built-in is supported.
_detectWasmStringBuiltin() {
let bytes = [
0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0,
0, 2, 23, 1, 14, 119, 97, 115, 109, 58, 106, 115, 45,
115, 116, 114, 105, 110, 103, 4, 99, 97, 115, 116, 0, 0
];
return !WebAssembly.validate(
new Uint8Array(bytes), {builtins: ['js-string']});
}
}