blob: b1eafdc8ac11cd75328c37f4afb78cc7d072b409 [file] [log] [blame]
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
final jsRuntimeBlobTemplate = Template(r'''
// Compiles a dart2wasm-generated main module from `source` which can then
// instantiatable via the `instantiate` method.
//
// `source` needs to be a `Response` object (or promise thereof) e.g. created
// via the `fetch()` JS API.
export async function compileStreaming(source) {
const builtins = {<<BUILTINS_MAP_BODY>>};
return new CompiledApp(
await WebAssembly.compileStreaming(source, builtins), builtins);
}
// Compiles a dart2wasm-generated wasm modules from `bytes` which is then
// instantiatable via the `instantiate` method.
export async function compile(bytes) {
const builtins = {<<BUILTINS_MAP_BODY>>};
return new CompiledApp(await WebAssembly.compile(bytes, builtins), builtins);
}
// DEPRECATED: Please use `compile` or `compileStreaming` to get a compiled app,
// use `instantiate` method to get an instantiated app and then call
// `invokeMain` to invoke the main function.
export async function instantiate(modulePromise, importObjectPromise) {
var moduleOrCompiledApp = await modulePromise;
if (!(moduleOrCompiledApp instanceof CompiledApp)) {
moduleOrCompiledApp = new CompiledApp(moduleOrCompiledApp);
}
const instantiatedApp = await moduleOrCompiledApp.instantiate(await importObjectPromise);
return instantiatedApp.instantiatedModule;
}
// DEPRECATED: Please use `compile` or `compileStreaming` to get a compiled app,
// use `instantiate` method to get an instantiated app and then call
// `invokeMain` to invoke the main function.
export const invoke = (moduleInstance, ...args) => {
moduleInstance.exports.$invokeMain(args);
}
class CompiledApp {
constructor(module, builtins) {
this.module = module;
this.builtins = builtins;
}
// The second argument is an options object containing:
// `loadDeferredWasm` is a JS function that takes a module name matching a
// wasm file produced by the dart2wasm compiler and returns the bytes to
// load the module. These bytes can be in either a format supported by
// `WebAssembly.compile` or `WebAssembly.compileStreaming`.
// `loadDynamicModule` is a JS function that takes two string names matching,
// in order, a wasm file produced by the dart2wasm compiler during dynamic
// module compilation and a corresponding js file produced by the same
// compilation. It should return a JS Array containing 2 elements. The first
// should be the bytes for the wasm module in a format supported by
// `WebAssembly.compile` or `WebAssembly.compileStreaming`. The second
// should be the result of using the JS 'import' API on the js file path.
async instantiate(additionalImports, {loadDeferredWasm, loadDynamicModule} = {}) {
let dartInstance;
// Prints to the console
function printToConsole(value) {
if (typeof dartPrint == "function") {
dartPrint(value);
return;
}
if (typeof console == "object" && typeof console.log != "undefined") {
console.log(value);
return;
}
if (typeof print == "function") {
print(value);
return;
}
throw "Unable to print message: " + value;
}
// A special symbol attached to functions that wrap Dart functions.
const jsWrappedDartFunctionSymbol = Symbol("JSWrappedDartFunction");
function finalizeWrapper(dartFunction, wrapped) {
wrapped.dartFunction = dartFunction;
wrapped[jsWrappedDartFunctionSymbol] = true;
return wrapped;
}
// Imports
const dart2wasm = {
<<JS_METHODS>>
};
const baseImports = {
dart2wasm: dart2wasm,
Math: Math,
Date: Date,
Object: Object,
Array: Array,
Reflect: Reflect,
<<IMPORTED_JS_STRINGS_IN_MJS>>
};
<<JS_STRING_POLYFILL_METHODS>>
<<DEFERRED_LIBRARY_HELPER_METHODS>>
dartInstance = await WebAssembly.instantiate(this.module, {
...baseImports,
...additionalImports,
<<MODULE_LOADING_IMPORT>>
<<JS_POLYFILL_IMPORT>>
});
return new InstantiatedApp(this, dartInstance);
}
}
class InstantiatedApp {
constructor(compiledApp, instantiatedModule) {
this.compiledApp = compiledApp;
this.instantiatedModule = instantiatedModule;
}
// Call the main function with the given arguments.
invokeMain(...args) {
this.instantiatedModule.exports.$invokeMain(args);
}
}
''');
const String jsPolyFillMethods = r'''
const jsStringPolyfill = {
"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),
"fromCharCodeArray": (a, start, end) => {
if (end <= start) return '';
const read = dartInstance.exports.$wasmI16ArrayGet;
let result = '';
let index = start;
const chunkLength = Math.min(end - index, 500);
let array = new Array(chunkLength);
while (index < end) {
const newChunkLength = Math.min(end - index, 500);
for (let i = 0; i < newChunkLength; i++) {
array[i] = read(a, index++);
}
if (newChunkLength < chunkLength) {
array = array.slice(0, newChunkLength);
}
result += String.fromCharCode(...array);
}
return result;
},
"intoCharCodeArray": (s, a, start) => {
if (s === '') return 0;
const write = dartInstance.exports.$wasmI16ArraySet;
for (var i = 0; i < s.length; ++i) {
write(a, start++, s.charCodeAt(i));
}
return s.length;
},
"test": (s) => typeof s == "string",
};
''';
final moduleLoadingHelperTemplate = Template(r'''
const moduleLoadingHelper = {
"loadModule": async (moduleName) => {
if (!loadDeferredWasm) {
throw "No implementation of loadDeferredWasm provided.";
}
const source = await Promise.resolve(loadDeferredWasm(moduleName));
const module = await ((source instanceof Response)
? WebAssembly.compileStreaming(source, this.builtins)
: WebAssembly.compile(source, this.builtins));
return await WebAssembly.instantiate(module, {
...baseImports,
...additionalImports,
<<JS_POLYFILL_IMPORT>>
"module0": dartInstance.exports,
});
},
"loadDynamicModuleFromUri": async (wasmUri, jsUri) => {
if (!loadDynamicModule) {
throw "No implementation of loadDynamicModule provided.";
}
const [source, jsModule] = await loadDynamicModule(wasmUri, jsUri);
const module = await ((source instanceof Response)
? WebAssembly.compileStreaming(source, this.builtins)
: WebAssembly.compile(source, this.builtins));
const loadedModule = await WebAssembly.instantiate(module, {
"module0": dartInstance.exports,
...jsModule.imports(finalizeWrapper),
});
return loadedModule.exports.$invokeEntryPoint;
},
};
''');
final dynamicSubmoduleJsImportTemplate = Template(r'''
export function imports(finalizeWrapper) {
const dart2wasm = {
<<JS_METHODS>>
};
return {
dart2wasm: dart2wasm,
<<IMPORTED_JS_STRINGS_IN_MJS>>
};
}
''');
class Template {
static final _templateVariableRegExp = RegExp(r'<<(?<varname>[A-Z_]+)>>');
final List<_TemplatePart> _parts = [];
Template(String stringTemplate) {
int offset = 0;
for (final match in _templateVariableRegExp.allMatches(stringTemplate)) {
_parts.add(
_TemplateStringPart(stringTemplate.substring(offset, match.start)));
_parts.add(_TemplateVariablePart(match.namedGroup('varname')!));
offset = match.end;
}
_parts.add(_TemplateStringPart(
stringTemplate.substring(offset, stringTemplate.length)));
}
String instantiate(Map<String, String> variableValues) {
final sb = StringBuffer();
for (final part in _parts) {
sb.write(part.instantiate(variableValues));
}
return sb.toString();
}
}
abstract class _TemplatePart {
String instantiate(Map<String, String> variableValues);
}
class _TemplateStringPart extends _TemplatePart {
final String string;
_TemplateStringPart(this.string);
@override
String instantiate(Map<String, String> variableValues) => string;
}
class _TemplateVariablePart extends _TemplatePart {
final String variable;
_TemplateVariablePart(this.variable);
@override
String instantiate(Map<String, String> variableValues) {
final value = variableValues[variable];
if (value != null) return value;
throw 'Template contains no value for variable $variable';
}
}