Skip to content

Commit 4f818d4

Browse files
natebiggsCommit Queue
authored andcommitted
[dart2wasm] Generate additional mjs module with JS support values.
This updates the dynamic module compile to emit an extra mjs file containing JS-defined functions and values (i.e. Strings). The runtime then loads this JS module along with the wasm module and links the two. Change-Id: Iedddf378822f3abd957f0ca9016a9f7883854973 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422341 Reviewed-by: Ömer Ağacan <[email protected]> Commit-Queue: Nate Biggs <[email protected]>
1 parent 62e7377 commit 4f818d4

File tree

9 files changed

+136
-111
lines changed

9 files changed

+136
-111
lines changed

pkg/_js_interop_checks/lib/src/js_interop.dart

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,10 @@ List<String> stringAnnotationValues(Expression node) {
255255
return values;
256256
}
257257

258-
/// Returns the [Library] within [component] matching the specified
258+
/// Returns the [Library] within [libraries] matching the specified
259259
/// [interopUri] or [null].
260-
Library? _findJsInteropLibrary(Component component, Uri interopUri) {
261-
for (Library lib in component.libraries) {
260+
Library? _findJsInteropLibrary(List<Library> libraries, Uri interopUri) {
261+
for (Library lib in libraries) {
262262
for (LibraryDependency dependency in lib.dependencies) {
263263
Library targetLibrary = dependency.targetLibrary;
264264
if (targetLibrary.importUri == interopUri) {
@@ -276,19 +276,17 @@ Library? _findJsInteropLibrary(Component component, Uri interopUri) {
276276
/// `calculateTransitiveImportsOfDartFfiIfUsed` in
277277
/// pkg/vm/lib/transformations/ffi/common.dart.
278278
Set<Library> calculateTransitiveImportsOfJsInteropIfUsed(
279-
Component component,
279+
List<Library> libraries,
280280
Uri interopUri,
281281
) {
282282
// Check for the presence of [jsInteropLibrary] as a dependency of any of the
283283
// libraries in [component]. We use this to bypass the expensive
284284
// [calculateTransitiveDependenciesOf] call for cases where js interop is
285285
// not used, otherwise we could just use the index of the library instead.
286-
Library? jsInteropLibrary = _findJsInteropLibrary(component, interopUri);
286+
Library? jsInteropLibrary = _findJsInteropLibrary(libraries, interopUri);
287287
if (jsInteropLibrary == null) return const <Library>{};
288288

289-
kernel_graph.LibraryGraph graph = kernel_graph.LibraryGraph(
290-
component.libraries,
291-
);
289+
kernel_graph.LibraryGraph graph = kernel_graph.LibraryGraph(libraries);
292290
Set<Library> result = kernel_graph.calculateTransitiveDependenciesOf(graph, {
293291
jsInteropLibrary,
294292
});

pkg/dart2wasm/bin/run_wasm.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -404,21 +404,22 @@ const main = async () => {
404404
importObject.ffi = ffiInstance.exports;
405405
}
406406

407+
// Instantiate the Dart module, importing from the global scope.
408+
const wasmFilename = args[wasmArg];
409+
const wasmDirectory = wasmFilename.slice(0, wasmFilename.lastIndexOf('/'));
410+
407411
globalThis.loadData = async (relativeToWasmFileUri) => {
408-
var path = wasmFilename.slice(0, wasmFilename.lastIndexOf('/'));
409-
return await readBytes(`${path}/${relativeToWasmFileUri}`);
412+
return await readBytes(`${wasmDirectory}/${relativeToWasmFileUri}`);
410413
};
411414

412-
// Instantiate the Dart module, importing from the global scope.
413-
const wasmFilename = args[wasmArg];
414415
const compiledApp = await dart2wasm.compile(readBytes(wasmFilename));
415416
const appInstance = await compiledApp.instantiate(importObject, {
416417
loadDeferredWasm: async (moduleName) => {
417418
let filename = wasmFilename.replace('.wasm', `_${moduleName}.wasm`);
418419
return readBytes(filename);
419420
},
420-
loadDynamicModule: async (uri) => {
421-
return readBytes(uri);
421+
loadDynamicModule: async (wasmUri, mjsUri) => {
422+
return [await readBytes(wasmUri), await import(`${wasmDirectory}/${mjsUri}`)];
422423
}
423424
});
424425

pkg/dart2wasm/lib/compile.dart

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ sealed class CompilationResult {}
5454

5555
class CompilationSuccess extends CompilationResult {
5656
final Map<String, ({Uint8List moduleBytes, String? sourceMap})> wasmModules;
57-
final String? jsRuntime;
57+
final String jsRuntime;
5858
final String supportJs;
5959

6060
CompilationSuccess(this.wasmModules, this.jsRuntime, this.supportJs);
@@ -234,16 +234,16 @@ Future<CompilationResult> compileToModule(
234234
}
235235

236236
final librariesToTransform = isDynamicModule
237-
? component.getMainModuleLibraries(coreTypes)
237+
? component.getDynamicModuleLibraries(coreTypes)
238238
: component.libraries;
239239
ConstantEvaluator constantEvaluator = ConstantEvaluator(
240240
options, target, component, coreTypes, classHierarchy, libraryIndex);
241241
unreachable_code_elimination.transformLibraries(target, librariesToTransform,
242242
constantEvaluator, options.translatorOptions.enableAsserts);
243243

244-
js.RuntimeFinalizer? jsRuntimeFinalizer = isDynamicModule
245-
? null
246-
: js.createRuntimeFinalizer(component, coreTypes, classHierarchy);
244+
js.RuntimeFinalizer? jsRuntimeFinalizer = js.RuntimeFinalizer(
245+
js.performJSInteropTransformations(
246+
librariesToTransform, coreTypes, classHierarchy));
247247

248248
final Map<RecordShape, Class> recordClasses = generateRecordClasses(
249249
component, coreTypes, isDynamicMainModule || isDynamicModule);
@@ -328,14 +328,17 @@ Future<CompilationResult> compileToModule(
328328
(moduleBytes: wasmModuleSerialized, sourceMap: sourceMap);
329329
});
330330

331-
final jsRuntime = jsRuntimeFinalizer?.generate(
332-
translator.functions.translatedProcedures,
333-
translator.internalizedStringsForJSRuntime,
334-
translator.options.requireJsStringBuiltin,
335-
translator.options.enableDeferredLoading ||
336-
translator.options.enableMultiModuleStressTestMode ||
337-
translator.dynamicModuleSupportEnabled,
338-
mode);
331+
final jsRuntime = translator.isDynamicModule
332+
? jsRuntimeFinalizer.generateDynamicModule(
333+
translator.functions.translatedProcedures,
334+
translator.internalizedStringsForJSRuntime)
335+
: jsRuntimeFinalizer.generate(
336+
translator.functions.translatedProcedures,
337+
translator.internalizedStringsForJSRuntime,
338+
translator.options.requireJsStringBuiltin,
339+
translator.options.enableDeferredLoading ||
340+
translator.options.enableMultiModuleStressTestMode ||
341+
translator.dynamicModuleSupportEnabled);
339342

340343
final supportJs = _generateSupportJs(options.translatorOptions);
341344
if (isDynamicMainModule) {

pkg/dart2wasm/lib/dynamic_modules.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ extension DynamicModuleComponent on Component {
4040
Expando<Procedure>();
4141

4242
Procedure? get dynamicModuleEntryPoint => _dynamicModuleEntryPoint[this];
43-
List<Library> getMainModuleLibraries(CoreTypes coreTypes) =>
44-
[...libraries.where((l) => l.isFromMainModule(coreTypes))];
43+
List<Library> getDynamicModuleLibraries(CoreTypes coreTypes) =>
44+
[...libraries.where((l) => !l.isFromMainModule(coreTypes))];
4545
}
4646

4747
extension DynamicModuleLibrary on Library {

pkg/dart2wasm/lib/generate_wasm.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,7 @@ Future<int> generateWasm(WasmCompilerOptions options,
115115

116116
final jsFile = path.setExtension(options.outputFile, '.mjs');
117117
final jsRuntime = result.jsRuntime;
118-
if (jsRuntime != null) {
119-
await File(jsFile).writeAsString(jsRuntime);
120-
}
118+
await File(jsFile).writeAsString(jsRuntime);
121119

122120
final supportJsFile = path.setExtension(options.outputFile, '.support.js');
123121
await File(supportJsFile).writeAsString(result.supportJs);

pkg/dart2wasm/lib/js/runtime_blob.dart

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ class CompiledApp {
5151
// wasm file produced by the dart2wasm compiler and returns the bytes to
5252
// load the module. These bytes can be in either a format supported by
5353
// `WebAssembly.compile` or `WebAssembly.compileStreaming`.
54+
// `loadDynamicModule` is a JS function that takes two string names matching,
55+
// in order, a wasm file produced by the dart2wasm compiler during dynamic
56+
// module compilation and a corresponding js file produced by the same
57+
// compilation. It should return a JS Array containing 2 elements. The first
58+
// should be the bytes for the wasm module in a format supported by
59+
// `WebAssembly.compile` or `WebAssembly.compileStreaming`. The second
60+
// should be the result of using the JS 'import' API on the js file path.
5461
async instantiate(additionalImports, {loadDeferredWasm, loadDynamicModule} = {}) {
5562
let dartInstance;
5663
@@ -170,18 +177,12 @@ const jsStringPolyfill = {
170177
''';
171178

172179
final moduleLoadingHelperTemplate = Template(r'''
173-
const loadModuleFromBytes = async (bytes) => {
174-
const module = await WebAssembly.compile(bytes, this.builtins);
175-
return await WebAssembly.instantiate(module, {
176-
...baseImports,
177-
...additionalImports,
178-
<<JS_POLYFILL_IMPORT>>
179-
"module0": dartInstance.exports,
180-
});
181-
}
182-
183-
const loadModule = async (loader, loaderArgument) => {
184-
const source = await Promise.resolve(loader(loaderArgument));
180+
const moduleLoadingHelper = {
181+
"loadModule": async (moduleName) => {
182+
if (!loadDeferredWasm) {
183+
throw "No implementation of loadDeferredWasm provided.";
184+
}
185+
const source = await Promise.resolve(loadDeferredWasm(moduleName));
185186
const module = await ((source instanceof Response)
186187
? WebAssembly.compileStreaming(source, this.builtins)
187188
: WebAssembly.compile(source, this.builtins));
@@ -191,29 +192,37 @@ const loadModuleFromBytes = async (bytes) => {
191192
<<JS_POLYFILL_IMPORT>>
192193
"module0": dartInstance.exports,
193194
});
194-
}
195-
196-
const moduleLoadingHelper = {
197-
"loadModule": async (moduleName) => {
198-
if (!loadDeferredWasm) {
199-
throw "No implementation of loadDeferredWasm provided.";
200-
}
201-
return await loadModule(loadDeferredWasm, moduleName);
202195
},
203-
"loadDynamicModuleFromUri": async (uri) => {
196+
"loadDynamicModuleFromUri": async (wasmUri, jsUri) => {
204197
if (!loadDynamicModule) {
205198
throw "No implementation of loadDynamicModule provided.";
206199
}
207-
const loadedModule = await loadModule(loadDynamicModule, uri);
208-
return loadedModule.exports.$invokeEntryPoint;
209-
},
210-
"loadDynamicModuleFromBytes": async (bytes) => {
211-
const loadedModule = await loadModuleFromBytes(loadDynamicModule, uri);
200+
const [source, jsModule] = await loadDynamicModule(wasmUri, jsUri);
201+
const module = await ((source instanceof Response)
202+
? WebAssembly.compileStreaming(source, this.builtins)
203+
: WebAssembly.compile(source, this.builtins));
204+
const loadedModule = await WebAssembly.instantiate(module, {
205+
"module0": dartInstance.exports,
206+
...jsModule.imports(finalizeWrapper),
207+
});
212208
return loadedModule.exports.$invokeEntryPoint;
213209
},
214210
};
215211
''');
216212

213+
final dynamicModuleJsImportTemplate = Template(r'''
214+
export function imports(finalizeWrapper) {
215+
const dart2wasm = {
216+
<<JS_METHODS>>
217+
};
218+
219+
return {
220+
dart2wasm: dart2wasm,
221+
<<IMPORTED_JS_STRINGS_IN_MJS>>
222+
};
223+
}
224+
''');
225+
217226
class Template {
218227
static final _templateVariableRegExp = RegExp(r'<<(?<varname>[A-Z_]+)>>');
219228
final List<_TemplatePart> _parts = [];

pkg/dart2wasm/lib/js/runtime_generator.dart

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,12 @@ import 'package:kernel/ast.dart';
1212
import 'package:kernel/class_hierarchy.dart';
1313
import 'package:kernel/core_types.dart';
1414

15-
import '../target.dart' as wasm_target;
1615
import 'interop_transformer.dart';
1716
import 'method_collector.dart';
1817
import 'runtime_blob.dart';
1918

20-
JSMethods _performJSInteropTransformations(
21-
Component component,
22-
CoreTypes coreTypes,
23-
ClassHierarchy classHierarchy,
24-
Set<Library> interopDependentLibraries) {
19+
JSMethods _performJSInteropTransformations(CoreTypes coreTypes,
20+
ClassHierarchy classHierarchy, Set<Library> interopDependentLibraries) {
2521
// Transform kernel and generate JS methods.
2622
final transformer = InteropTransformer(coreTypes, classHierarchy);
2723
for (final library in interopDependentLibraries) {
@@ -50,18 +46,13 @@ JSMethods _performJSInteropTransformations(
5046
}
5147

5248
class RuntimeFinalizer {
49+
static String escape(String s) => json.encode(s);
50+
5351
final Map<Procedure, ({String importName, String jsCode})> allJSMethods;
5452

5553
RuntimeFinalizer(this.allJSMethods);
5654

57-
String generate(
58-
Iterable<Procedure> translatedProcedures,
59-
List<String> constantStrings,
60-
bool requireJsBuiltin,
61-
bool supportsAdditionalModuleLoading,
62-
wasm_target.Mode mode) {
63-
String escape(String s) => json.encode(s);
64-
55+
String generateJsMethods(Iterable<Procedure> translatedProcedures) {
6556
Set<Procedure> usedProcedures = {};
6657
final usedJSMethods = <({String importName, String jsCode})>[];
6758
for (Procedure p in translatedProcedures) {
@@ -91,19 +82,31 @@ class RuntimeFinalizer {
9182
}
9283
}
9384

94-
final builtins = [
95-
'builtins: [\'js-string\']',
96-
if (requireJsBuiltin) 'importedStringConstants: \'S\'',
97-
];
85+
return jsMethods.toString();
86+
}
9887

99-
String internalizedStrings = '';
100-
if (constantStrings.isNotEmpty) {
101-
internalizedStrings = '''
88+
String _generateInternalizedStrings(List<String> constantStrings) {
89+
if (constantStrings.isEmpty) return '';
90+
return '''
10291
s: [
10392
${constantStrings.map(escape).join(',\n')}
10493
],
10594
''';
106-
}
95+
}
96+
97+
String generate(
98+
Iterable<Procedure> translatedProcedures,
99+
List<String> constantStrings,
100+
bool requireJsBuiltin,
101+
bool supportsAdditionalModuleLoading) {
102+
final jsMethods = generateJsMethods(translatedProcedures);
103+
104+
final builtins = [
105+
'builtins: [\'js-string\']',
106+
if (requireJsBuiltin) 'importedStringConstants: \'S\'',
107+
];
108+
109+
String internalizedStrings = _generateInternalizedStrings(constantStrings);
107110

108111
final jsStringBuiltinPolyfillImportVars = {
109112
'JS_POLYFILL_IMPORT':
@@ -125,29 +128,40 @@ class RuntimeFinalizer {
125128
...jsStringBuiltinPolyfillImportVars,
126129
...moduleLoadingImportVars,
127130
'BUILTINS_MAP_BODY': builtins.join(', '),
128-
'JS_METHODS': jsMethods.toString(),
131+
'JS_METHODS': jsMethods,
129132
'IMPORTED_JS_STRINGS_IN_MJS': internalizedStrings,
130133
'JS_STRING_POLYFILL_METHODS': requireJsBuiltin ? '' : jsPolyFillMethods,
131134
'DEFERRED_LIBRARY_HELPER_METHODS': moduleLoadingHelperMethods,
132135
});
133136
}
137+
138+
String generateDynamicModule(
139+
Iterable<Procedure> translatedProcedures, List<String> constantStrings) {
140+
final jsMethods = generateJsMethods(translatedProcedures);
141+
142+
return dynamicModuleJsImportTemplate.instantiate({
143+
'JS_METHODS': jsMethods,
144+
'IMPORTED_JS_STRINGS_IN_MJS':
145+
_generateInternalizedStrings(constantStrings),
146+
});
147+
}
134148
}
135149

136-
RuntimeFinalizer createRuntimeFinalizer(
137-
Component component, CoreTypes coreTypes, ClassHierarchy classHierarchy) {
150+
JSMethods performJSInteropTransformations(List<Library> libraries,
151+
CoreTypes coreTypes, ClassHierarchy classHierarchy) {
138152
Set<Library> transitiveImportingJSInterop = {
139153
...calculateTransitiveImportsOfJsInteropIfUsed(
140-
component, Uri.parse("package:js/js.dart")),
154+
libraries, Uri.parse("package:js/js.dart")),
141155
...calculateTransitiveImportsOfJsInteropIfUsed(
142-
component, Uri.parse("dart:_js_annotations")),
156+
libraries, Uri.parse("dart:_js_annotations")),
143157
...calculateTransitiveImportsOfJsInteropIfUsed(
144-
component, Uri.parse("dart:_js_helper")),
158+
libraries, Uri.parse("dart:_js_helper")),
145159
...calculateTransitiveImportsOfJsInteropIfUsed(
146-
component, Uri.parse("dart:js_interop")),
160+
libraries, Uri.parse("dart:js_interop")),
147161
};
148-
final jsInteropMethods = _performJSInteropTransformations(
149-
component, coreTypes, classHierarchy, transitiveImportingJSInterop);
150-
return RuntimeFinalizer(jsInteropMethods);
162+
163+
return _performJSInteropTransformations(
164+
coreTypes, classHierarchy, transitiveImportingJSInterop);
151165
}
152166

153167
// Removes indentation common among all lines of [block] (except for first one)

pkg/dart2wasm/lib/target.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -281,15 +281,15 @@ class WasmTarget extends Target {
281281

282282
Set<Library> transitiveImportingJSInterop = {
283283
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
284-
component, Uri.parse("package:js/js.dart")),
284+
component.libraries, Uri.parse("package:js/js.dart")),
285285
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
286-
component, Uri.parse("dart:_js_annotations")),
286+
component.libraries, Uri.parse("dart:_js_annotations")),
287287
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
288-
component, Uri.parse("dart:js_interop")),
288+
component.libraries, Uri.parse("dart:js_interop")),
289289
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
290-
component, Uri.parse("dart:convert")),
290+
component.libraries, Uri.parse("dart:convert")),
291291
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
292-
component, Uri.parse("dart:_string")),
292+
component.libraries, Uri.parse("dart:_string")),
293293
};
294294
if (transitiveImportingJSInterop.isEmpty) {
295295
logger?.call("Skipped JS interop transformations");

0 commit comments

Comments
 (0)