Skip to content

Commit 43f2347

Browse files
natebiggsCommit Queue
authored andcommitted
[dart2wasm] Fix JS callback wrappers invoked in deferred modules.
The generated callback wrappers were referring to exported functions via 'dartInstance.exports' which is always the main module. But in order to avoid bloating the main module we put some of these JS exports into submodules. So the wrapper must be fetched from the correct module that defines the wrapped function. In order to facilitate this we give each module a self-reference that it can then pass up to the JS wrapper as an externref. Change-Id: Id2514de2c13d38a0cee1f6c935e45a9ef826f805 Fixes: #62094 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/464820 Reviewed-by: Martin Kustermann <[email protected]> Commit-Queue: Nate Biggs <[email protected]>
1 parent 0caeb97 commit 43f2347

File tree

8 files changed

+92
-13
lines changed

8 files changed

+92
-13
lines changed

pkg/dart2wasm/lib/intrinsics.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,10 @@ class Intrinsifier {
401401
op == '_le_u' ||
402402
op == '_lt_u';
403403

404+
final Map<w.ModuleBuilder, w.ImportedFunction> _thisModuleGlobals = {};
405+
late final w.FunctionType _thisModuleType = translator.typesBuilder
406+
.defineFunction(const [], const [w.RefType.extern(nullable: false)]);
407+
404408
Intrinsifier(this.codeGen);
405409

406410
/// Generate inline code for an [InstanceGet] if the member is an inlined
@@ -835,6 +839,18 @@ class Intrinsifier {
835839
}
836840
}
837841

842+
if (target.enclosingLibrary.name == 'dart._js_helper') {
843+
if (target.name.text == 'thisModule') {
844+
final resultType = w.RefType.extern(nullable: false);
845+
final func = _thisModuleGlobals.putIfAbsent(b.moduleBuilder, () {
846+
return b.moduleBuilder.functions
847+
.import("\$moduleHelpers", "this", _thisModuleType);
848+
});
849+
b.call(func);
850+
return resultType;
851+
}
852+
}
853+
838854
return null;
839855
}
840856

pkg/dart2wasm/lib/js/callback_specializer.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,10 @@ class CallbackSpecializer {
234234
String argumentsLength =
235235
captureThis ? 'arguments.length + 1' : 'arguments.length';
236236
String dartArguments = 'f,$argumentsLength';
237-
String jsMethodParams = 'f';
237+
String jsMethodParams = '(module,f)';
238238
if (needsCastClosure) {
239239
dartArguments = '$dartArguments,castClosure';
240-
jsMethodParams = '($jsMethodParams,castClosure)';
240+
jsMethodParams = '(module,f,castClosure)';
241241
}
242242
if (captureThis) dartArguments = '$dartArguments,this';
243243
if (jsParameters.isNotEmpty) {
@@ -251,6 +251,9 @@ class CallbackSpecializer {
251251
'dart2wasm.$jsMethodName',
252252
FunctionNode(null,
253253
positionalParameters: [
254+
VariableDeclaration('thisModule',
255+
type: _util.nonNullableWasmExternRefType,
256+
isSynthesized: true),
254257
VariableDeclaration('dartFunction',
255258
type: _util.nonNullableWasmExternRefType,
256259
isSynthesized: true),
@@ -271,7 +274,7 @@ class CallbackSpecializer {
271274
dartProcedure,
272275
jsMethodName,
273276
"$jsMethodParams => finalizeWrapper(f, function($jsWrapperParams) {"
274-
" return dartInstance.exports.${functionTrampoline.name.text}($dartArguments) "
277+
" return module.exports.${functionTrampoline.name.text}($dartArguments) "
275278
"})");
276279

277280
return (dartProcedure, functionTrampoline);
@@ -356,6 +359,7 @@ class CallbackSpecializer {
356359
StaticInvocation(
357360
jsWrapperFunction,
358361
Arguments([
362+
StaticGet(_util.thisModuleGetter),
359363
StaticInvocation(
360364
_util.jsObjectFromDartObjectTarget, Arguments([argument])),
361365
if (castClosure != null)

pkg/dart2wasm/lib/js/runtime_blob.dart

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ class CompiledApp {
119119
...additionalImports,
120120
<<MODULE_LOADING_IMPORT>>
121121
<<JS_POLYFILL_IMPORT>>
122+
"$moduleHelpers": {"this": () => dartInstance},
122123
});
123124
124125
return new InstantiatedApp(this, dartInstance);
@@ -196,11 +197,13 @@ final moduleLoadingHelperTemplate = Template(r'''
196197
const module = await ((source instanceof Response)
197198
? WebAssembly.compileStreaming(source, builtins)
198199
: WebAssembly.compile(source, builtins));
199-
await WebAssembly.instantiate(module, {
200+
let moduleInstance;
201+
moduleInstance = await WebAssembly.instantiate(module, {
200202
...baseImports,
201203
...additionalImports,
202204
<<JS_POLYFILL_IMPORT>>
203205
"<<MAIN_MODULE_NAME>>": dartInstance.exports,
206+
"$moduleHelpers": {"this": () => moduleInstance},
204207
});
205208
}
206209
const promises = [];
@@ -218,11 +221,13 @@ final moduleLoadingHelperTemplate = Template(r'''
218221
const module = await ((source instanceof Response)
219222
? WebAssembly.compileStreaming(source, this.builtins)
220223
: WebAssembly.compile(source, this.builtins));
221-
await WebAssembly.instantiate(module, {
224+
let moduleInstance;
225+
moduleInstance = await WebAssembly.instantiate(module, {
222226
...baseImports,
223227
...additionalImports,
224228
<<JS_POLYFILL_IMPORT>>
225229
"<<MAIN_MODULE_NAME>>": dartInstance.exports,
230+
"$moduleHelpers": {"this": () => moduleInstance},
226231
});
227232
}
228233
},
@@ -231,14 +236,16 @@ final moduleLoadingHelperTemplate = Template(r'''
231236
throw "No implementation of loadDynamicModule provided.";
232237
}
233238
const [source, jsModule] = await loadDynamicModule(wasmUri, jsUri);
239+
let moduleInstance;
234240
const module = await ((source instanceof Response)
235241
? WebAssembly.compileStreaming(source, this.builtins)
236242
: WebAssembly.compile(source, this.builtins));
237-
const loadedModule = await WebAssembly.instantiate(module, {
243+
moduleInstance = await WebAssembly.instantiate(module, {
238244
"<<MAIN_MODULE_NAME>>": dartInstance.exports,
245+
"$moduleHelpers": {"this": () => moduleInstance},
239246
...jsModule.imports(finalizeWrapper),
240247
});
241-
return loadedModule.exports.$invokeEntryPoint;
248+
return moduleInstance.exports.$invokeEntryPoint;
242249
},
243250
};
244251
''');

pkg/dart2wasm/lib/js/util.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class CoreTypesUtil {
6868
final Procedure jsifyJSArrayBufferImpl; // JS ByteBuffer
6969
final Procedure jsArrayBufferFromDartByteBuffer; // Wasm ByteBuffer
7070
final Procedure jsifyFunction;
71+
final Procedure thisModuleGetter;
7172

7273
// Classes used in type tests for the converters.
7374
final Class jsInt8ArrayImplClass;
@@ -268,6 +269,8 @@ class CoreTypesUtil {
268269
'dart:_js_helper', 'jsArrayBufferFromDartByteBuffer'),
269270
jsifyFunction = coreTypes.index
270271
.getTopLevelProcedure('dart:_js_helper', 'jsifyFunction'),
272+
thisModuleGetter = coreTypes.index
273+
.getTopLevelProcedure('dart:_js_helper', 'get:thisModule'),
271274
jsInt8ArrayImplClass =
272275
coreTypes.index.getClass('dart:_js_types', 'JSInt8ArrayImpl'),
273276
jsUint8ArrayImplClass =

pkg/dart2wasm/test/ir_tests/deferred.constant.wat

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
(field $field0 i32)
77
(field $field1 (mut i32))
88
(field $fun (ref $#Closure-0-1)))))
9-
(type $type234 <...>)
10-
(type $type237 <...>)
11-
(type $type240 <...>)
12-
(table $static1-0 (export "static1-0") 1 (ref null $type240))
13-
(table $static2-0 (export "static2-0") 1 (ref null $type234))
14-
(table $static3-0 (export "static3-0") 1 (ref null $type237))
9+
(type $type236 <...>)
10+
(type $type239 <...>)
11+
(type $type242 <...>)
12+
(table $static1-0 (export "static1-0") 1 (ref null $type242))
13+
(table $static2-0 (export "static2-0") 1 (ref null $type236))
14+
(table $static3-0 (export "static3-0") 1 (ref null $type239))
1515
(func $print (param $var0 (ref null $#Top)) (result (ref null $#Top)) <...>)
1616
(func $"modMainUseH0 <noInline>"
1717
i64.const 0

sdk/lib/_internal/wasm/lib/js_helper.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,3 +716,6 @@ external T JS<T>(
716716
arg18,
717717
arg19,
718718
]);
719+
720+
@pragma("wasm:intrinsic")
721+
external WasmExternRef get thisModule;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:js_interop';
6+
7+
int closureCalled = 0;
8+
JSFunction? func;
9+
void Function()? clos;
10+
11+
void deferredMain() {
12+
void closure() {
13+
closureCalled++;
14+
}
15+
16+
final jsClosure = closure.toJS;
17+
jsClosure.callAsFunction();
18+
clos = closure;
19+
func = jsClosure;
20+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// dart2wasmOptions=--enable-deferred-loading
6+
7+
import 'dart:js_interop';
8+
9+
import 'package:async_helper/async_helper.dart';
10+
import 'package:expect/expect.dart';
11+
12+
import 'deferred_loading_js_callback_helper.dart' deferred as D;
13+
14+
main() async {
15+
asyncStart();
16+
await D.loadLibrary();
17+
Expect.equals(D.closureCalled, 0);
18+
D.deferredMain();
19+
Expect.equals(D.closureCalled, 1);
20+
D.func!.callAsFunction();
21+
Expect.equals(D.closureCalled, 2);
22+
final jsClosure = D.clos!.toJS;
23+
jsClosure.callAsFunction();
24+
Expect.equals(D.closureCalled, 3);
25+
asyncEnd();
26+
}

0 commit comments

Comments
 (0)