[dart2wasm] Simplify handling of JS interop callbacks

Right now a JS interop callback works like this:

* Each wasm module that gets instantiated will be given it's module
  instance (JS calls Dart to set it) via `setThisModule`

* When Dart code calls JS and gives it a callback to invoke, it gave it
  this module instance. It will also make the callback wasm function
  weakly exported.

* The JS trampoline code, when invoked, would then call the weakly
  exported wasm function from the module instance.

We simplify this now by making the Dart code simply give the wasm
function reference to JS, then JS can later on invoke it. No need to
weakly export a function and call back via
`module.exports.<weaklyExportedCallback>`

To ensure binaryen is aware that the wasm function may be called from
JS, we annotate it via the `(@binaryen.js.called)` annotation.

Change-Id: I828dd0cf8d3b36db338792c4e277a4bb94c76faf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/511080
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
Martin Kustermann
2026-06-11 12:24:16 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 87ff0ea65d
commit 2d78883f27
15 changed files with 174 additions and 170 deletions
+3
View File
@@ -171,6 +171,9 @@ class FunctionCollector {
final function = module.functions.define(ftype, getFunctionName(target)) final function = module.functions.define(ftype, getFunctionName(target))
..isPure = hasPureAnnotation && !target.isCheckedEntryReference ..isPure = hasPureAnnotation && !target.isCheckedEntryReference
..inlineHint = inlineHint; ..inlineHint = inlineHint;
if (util.hasPragma(translator.coreTypes, member, 'wasm:js-trampoline')) {
function.isJSCalled = true;
}
if (exportName != null) { if (exportName != null) {
// Add weak exports to the module as we now know they're used. Strong // Add weak exports to the module as we now know they're used. Strong
// exports have already been added. // exports have already been added.
+1 -16
View File
@@ -10,7 +10,6 @@ import 'abi.dart' show kWasmAbiEnumIndex;
import 'class_info.dart'; import 'class_info.dart';
import 'code_generator.dart'; import 'code_generator.dart';
import 'dynamic_dispatchers.dart'; import 'dynamic_dispatchers.dart';
import 'js/util.dart';
import 'translator.dart'; import 'translator.dart';
import 'types.dart'; import 'types.dart';
import 'util.dart'; import 'util.dart';
@@ -1136,15 +1135,6 @@ class Intrinsifier {
return type; return type;
} }
} }
if (target.enclosingLibrary.name == 'dart._js_helper') {
if (target.name.text == 'thisModule') {
final global = translator.getThisModuleGlobal(b.moduleBuilder);
b.global_get(global);
return global.type.type;
}
}
return null; return null;
} }
@@ -1540,12 +1530,7 @@ class Intrinsifier {
final constant = argument.constant; final constant = argument.constant;
if (constant is! StaticTearOffConstant) throw error; if (constant is! StaticTearOffConstant) throw error;
final target = constant.target; final target = constant.target;
if (!hasWasmWeakExportPragma(codeGen.translator.coreTypes, target) && if (!hasWasmWeakExportPragma(codeGen.translator.coreTypes, target)) {
!(JsInteropMemberData.fromMember(
target,
codeGen.translator.coreTypes,
)?.isWeakExport ??
true)) {
throw error; throw error;
} }
+52 -38
View File
@@ -17,7 +17,7 @@ class CallbackSpecializer {
CallbackSpecializer(this._staticTypeContext, this._util); CallbackSpecializer(this._staticTypeContext, this._util);
Statement _generateDispatchCase( Statement _generateDispatchCase(
FunctionType function, FunctionType instantiatedFunctionType,
Variable callbackVariable, Variable callbackVariable,
List<Variable> positionalParameters, List<Variable> positionalParameters,
int requiredParameterCount, { int requiredParameterCount, {
@@ -25,7 +25,8 @@ class CallbackSpecializer {
}) { }) {
List<Expression> callbackArguments = []; List<Expression> callbackArguments = [];
for (int i = 0; i < requiredParameterCount; i++) { for (int i = 0; i < requiredParameterCount; i++) {
DartType callbackParameterType = function.positionalParameters[i]; DartType callbackParameterType =
instantiatedFunctionType.positionalParameters[i];
Expression expression; Expression expression;
VariableGet v = VariableGet(positionalParameters[i]); VariableGet v = VariableGet(positionalParameters[i]);
if (_util.isJSValueType(callbackParameterType) && boxExternRef) { if (_util.isJSValueType(callbackParameterType) && boxExternRef) {
@@ -50,10 +51,7 @@ class CallbackSpecializer {
FunctionAccessKind.FunctionType, FunctionAccessKind.FunctionType,
VariableGet(callbackVariable), VariableGet(callbackVariable),
Arguments(callbackArguments), Arguments(callbackArguments),
// Instantiate any type parameters to bounds as they would otherwise functionType: instantiatedFunctionType,
// be free type variables in this context.
functionType:
const _InstantiateToBounds().substituteType(function) as FunctionType,
); );
final temp = Variable( final temp = Variable(
@@ -100,7 +98,7 @@ class CallbackSpecializer {
// needed. // needed.
final callbackVariable = Variable( final callbackVariable = Variable(
'callback', 'callback',
type: _util.nonNullableObjectType, type: _util.nonNullableWasmExternRefType,
isSynthesized: true, isSynthesized: true,
); );
final argumentsLengthWasmI32 = Variable( final argumentsLengthWasmI32 = Variable(
@@ -151,6 +149,28 @@ class CallbackSpecializer {
), ),
); );
final instantiatedFunctionType =
const _InstantiateToBounds().substituteType(function) as FunctionType;
// Convert `WasmExternRef` argument to Dart Function
final callbackFunctionVar = Variable(
'callbackFunction',
type: instantiatedFunctionType,
initializer: StaticInvocation(
_util.unsafeCastOpaqueTarget,
Arguments(
[
StaticInvocation(
_util.wasmInternalizeNonNullable,
Arguments([VariableGet(callbackVariable)]),
),
],
types: [instantiatedFunctionType],
),
),
);
body.add(VariableStatement(VariableDeclaration(callbackFunctionVar)));
body.add(VariableStatement(VariableDeclaration(argumentsLength))); body.add(VariableStatement(VariableDeclaration(argumentsLength)));
if (castClosureArguments.isNotEmpty) { if (castClosureArguments.isNotEmpty) {
@@ -185,8 +205,8 @@ class CallbackSpecializer {
IntConstant(positionalParametersLength), IntConstant(positionalParametersLength),
), ),
_generateDispatchCase( _generateDispatchCase(
function, instantiatedFunctionType,
callbackVariable, callbackFunctionVar,
positionalParameters, positionalParameters,
positionalParametersLength, positionalParametersLength,
boxExternRef: boxExternRef, boxExternRef: boxExternRef,
@@ -204,8 +224,8 @@ class CallbackSpecializer {
IfStatement( IfStatement(
_util.variableCheckConstant(argumentsLength, IntConstant(i)), _util.variableCheckConstant(argumentsLength, IntConstant(i)),
_generateDispatchCase( _generateDispatchCase(
function, instantiatedFunctionType,
callbackVariable, callbackFunctionVar,
positionalParameters, positionalParameters,
i, i,
boxExternRef: boxExternRef, boxExternRef: boxExternRef,
@@ -306,8 +326,8 @@ class CallbackSpecializer {
null, null,
positionalParameters: [ positionalParameters: [
Variable( Variable(
'thisModule', 'wasmFunction',
type: _util.nonNullableWasmExternRefType, type: _util.nonNullableWasmFuncRefType,
isSynthesized: true, isSynthesized: true,
), ),
Variable( Variable(
@@ -331,7 +351,6 @@ class CallbackSpecializer {
numJsParameters: jsParametersLength, numJsParameters: jsParametersLength,
captureThis: captureThis, captureThis: captureThis,
needsCastClosure: needsCastClosure, needsCastClosure: needsCastClosure,
trampoline: functionTrampoline,
).applyToMember(dartProcedure, _util.coreTypes); ).applyToMember(dartProcedure, _util.coreTypes);
return (dartProcedure, functionTrampoline); return (dartProcedure, functionTrampoline);
@@ -415,35 +434,30 @@ class CallbackSpecializer {
captureThis: captureThis, captureThis: captureThis,
); );
return _createJSValue( return _createJSValue(
BlockExpression( StaticInvocation(
Block([ jsWrapperFunction,
// This ensures TFA will retain the function which the Arguments([
// JS code will call. The backend in return will export StaticInvocation(
// the function due to `@pragma('wasm:weak-export', ...)` _util.wasmFunctionFromFunction,
ExpressionStatement( Arguments(
StaticInvocation( [ConstantExpression(StaticTearOffConstant(exportedFunction))],
_util.exportWasmFunctionTarget, types: [
Arguments([ exportedFunction.function.computeFunctionType(
ConstantExpression(StaticTearOffConstant(exportedFunction)), Nullability.nonNullable,
]), ),
],
), ),
), ),
]), StaticInvocation(
StaticInvocation( _util.jsObjectFromDartObjectTarget,
jsWrapperFunction, Arguments([argument]),
Arguments([ ),
StaticGet(_util.thisModuleGetter), if (castClosure != null)
StaticInvocation( StaticInvocation(
_util.jsObjectFromDartObjectTarget, _util.jsObjectFromDartObjectTarget,
Arguments([argument]), Arguments([castClosure]),
), ),
if (castClosure != null) ]),
StaticInvocation(
_util.jsObjectFromDartObjectTarget,
Arguments([castClosure]),
),
]),
),
), ),
); );
} }
-2
View File
@@ -104,7 +104,6 @@ class CompiledApp {
<<MODULE_LOADING_IMPORT>> <<MODULE_LOADING_IMPORT>>
<<JS_POLYFILL_IMPORT>> <<JS_POLYFILL_IMPORT>>
}); });
dartInstance.exports.<<THIS_MODULE_SETTER_NAME>>(dartInstance);
return new InstantiatedApp(this, dartInstance); return new InstantiatedApp(this, dartInstance);
} }
@@ -181,7 +180,6 @@ final moduleLoadingHelperTemplate = Template(r'''
<<JS_POLYFILL_IMPORT>> <<JS_POLYFILL_IMPORT>>
"<<MAIN_MODULE_NAME>>": dartInstance.exports, "<<MAIN_MODULE_NAME>>": dartInstance.exports,
}); });
moduleInstance.exports.<<THIS_MODULE_SETTER_NAME>>(moduleInstance);
} }
const moduleLoadingHelper = { const moduleLoadingHelper = {
"loadDeferredModules": async (moduleNames) => { "loadDeferredModules": async (moduleNames) => {
+2 -6
View File
@@ -68,13 +68,11 @@ class RuntimeFinalizer {
case JsCodeData(:final jsCode): case JsCodeData(:final jsCode):
final importName = _interopMemberNamer.getImportName(p)!.itemName; final importName = _interopMemberNamer.getImportName(p)!.itemName;
usedJSMethods.add((importName: importName, jsCode: jsCode)); usedJSMethods.add((importName: importName, jsCode: jsCode));
case JsTrampolineWrapperData(:final trampoline): case JsTrampolineWrapperData():
final importName = _interopMemberNamer.getImportName(p)!.itemName; final importName = _interopMemberNamer.getImportName(p)!.itemName;
usedJSMethods.add(( usedJSMethods.add((
importName: importName, importName: importName,
jsCode: annotationInfo.jsCode( jsCode: annotationInfo.jsCode(),
_interopMemberNamer.getExportName(trampoline)!,
),
)); ));
case JsTrampolineData(): case JsTrampolineData():
// do nothing // do nothing
@@ -161,7 +159,6 @@ class RuntimeFinalizer {
? moduleLoadingHelperTemplate.instantiate({ ? moduleLoadingHelperTemplate.instantiate({
...jsStringBuiltinPolyfillImportVars, ...jsStringBuiltinPolyfillImportVars,
'MAIN_MODULE_NAME': mainModuleName, 'MAIN_MODULE_NAME': mainModuleName,
'THIS_MODULE_SETTER_NAME': _interopMemberNamer.thisModuleSetterName,
}) })
: ''; : '';
@@ -170,7 +167,6 @@ class RuntimeFinalizer {
...moduleLoadingImportVars, ...moduleLoadingImportVars,
'BUILTINS_MAP_BODY': builtins.join(', '), 'BUILTINS_MAP_BODY': builtins.join(', '),
'JS_METHODS': jsMethods, 'JS_METHODS': jsMethods,
'THIS_MODULE_SETTER_NAME': _interopMemberNamer.thisModuleSetterName,
'INTERNAL_IMPORTS_MODULE_NAME': 'INTERNAL_IMPORTS_MODULE_NAME':
_interopMemberNamer.interopHelperModuleName, _interopMemberNamer.interopHelperModuleName,
'IMPORTED_JS_STRINGS_IN_MJS': internalizedStrings, 'IMPORTED_JS_STRINGS_IN_MJS': internalizedStrings,
+27 -17
View File
@@ -26,7 +26,6 @@ sealed class JsInteropMemberData {
String get pragmaName; String get pragmaName;
Constant? get toPragmaValue; Constant? get toPragmaValue;
bool get isImport; bool get isImport;
bool get isWeakExport => !isImport;
void applyToMember(Member member, CoreTypes coreTypes) { void applyToMember(Member member, CoreTypes coreTypes) {
addPragma(member, pragmaName, coreTypes, value: toPragmaValue); addPragma(member, pragmaName, coreTypes, value: toPragmaValue);
} }
@@ -112,7 +111,6 @@ class JsTrampolineData extends JsInteropMemberData {
class JsTrampolineWrapperData extends JsInteropMemberData { class JsTrampolineWrapperData extends JsInteropMemberData {
static const String _pragmaName = 'wasm:js-trampoline-wrapper'; static const String _pragmaName = 'wasm:js-trampoline-wrapper';
final int numJsParameters; final int numJsParameters;
final Procedure trampoline;
final bool captureThis; final bool captureThis;
final bool needsCastClosure; final bool needsCastClosure;
@@ -124,19 +122,16 @@ class JsTrampolineWrapperData extends JsInteropMemberData {
JsTrampolineWrapperData({ JsTrampolineWrapperData({
required this.numJsParameters, required this.numJsParameters,
required this.trampoline,
required this.captureThis, required this.captureThis,
required this.needsCastClosure, required this.needsCastClosure,
}); });
factory JsTrampolineWrapperData.fromPragmaValue(ListConstant constant) { factory JsTrampolineWrapperData.fromPragmaValue(ListConstant constant) {
final trampoline = (constant.entries[0] as StaticTearOffConstant).target; final numJsParameters = (constant.entries[0] as IntConstant).value;
final numJsParameters = (constant.entries[1] as IntConstant).value; final captureThis = (constant.entries[1] as BoolConstant).value;
final captureThis = (constant.entries[2] as BoolConstant).value; final needsCastClosure = (constant.entries[2] as BoolConstant).value;
final needsCastClosure = (constant.entries[3] as BoolConstant).value;
return JsTrampolineWrapperData( return JsTrampolineWrapperData(
numJsParameters: numJsParameters, numJsParameters: numJsParameters,
trampoline: trampoline,
captureThis: captureThis, captureThis: captureThis,
needsCastClosure: needsCastClosure, needsCastClosure: needsCastClosure,
); );
@@ -144,13 +139,12 @@ class JsTrampolineWrapperData extends JsInteropMemberData {
@override @override
ListConstant get toPragmaValue => ListConstant(DynamicType(), [ ListConstant get toPragmaValue => ListConstant(DynamicType(), [
StaticTearOffConstant(trampoline),
IntConstant(numJsParameters), IntConstant(numJsParameters),
BoolConstant(captureThis), BoolConstant(captureThis),
BoolConstant(needsCastClosure), BoolConstant(needsCastClosure),
]); ]);
String jsCode(String trampolineExportName) { String jsCode() {
final jsParameters = <String>[]; final jsParameters = <String>[];
for (int i = 0; i < numJsParameters; i++) { for (int i = 0; i < numJsParameters; i++) {
jsParameters.add('x$i'); jsParameters.add('x$i');
@@ -163,10 +157,10 @@ class JsTrampolineWrapperData extends JsInteropMemberData {
? 'arguments.length + 1' ? 'arguments.length + 1'
: 'arguments.length'; : 'arguments.length';
String dartArguments = 'f,$argumentsLength'; String dartArguments = 'f,$argumentsLength';
String jsMethodParams = '(module,f)'; String jsMethodParams = '(wasmFunction,f)';
if (needsCastClosure) { if (needsCastClosure) {
dartArguments = '$dartArguments,castClosure'; dartArguments = '$dartArguments,castClosure';
jsMethodParams = '(module,f,castClosure)'; jsMethodParams = '(wasmFunction,f,castClosure)';
} }
if (captureThis) dartArguments = '$dartArguments,this'; if (captureThis) dartArguments = '$dartArguments,this';
if (jsParameters.isNotEmpty) { if (jsParameters.isNotEmpty) {
@@ -176,7 +170,7 @@ class JsTrampolineWrapperData extends JsInteropMemberData {
// Note: We have to use a regular function for the inner closure in some // Note: We have to use a regular function for the inner closure in some
// cases because we need access to `arguments`. // cases because we need access to `arguments`.
return "$jsMethodParams => finalizeWrapper(f, function($jsWrapperParams) {" return "$jsMethodParams => finalizeWrapper(f, function($jsWrapperParams) {"
" return module.exports.$trampolineExportName($dartArguments) })"; " return wasmFunction($dartArguments) })";
} }
} }
@@ -221,6 +215,8 @@ class CoreTypesUtil {
final Class wasmArrayRefClass; final Class wasmArrayRefClass;
final Procedure wrapDartFunctionTarget; final Procedure wrapDartFunctionTarget;
final Procedure exportWasmFunctionTarget; final Procedure exportWasmFunctionTarget;
final Procedure wasmInternalizeNonNullable;
final Procedure unsafeCastOpaqueTarget;
final Member wasmExternRefNullRef; final Member wasmExternRefNullRef;
final Class wasmI32Class; final Class wasmI32Class;
final Procedure wasmI32ToIntSigned; final Procedure wasmI32ToIntSigned;
@@ -258,7 +254,8 @@ class CoreTypesUtil {
final Procedure jsifyJSArrayBufferImpl; // JS ByteBuffer final Procedure jsifyJSArrayBufferImpl; // JS ByteBuffer
final Procedure jsArrayBufferFromDartByteBuffer; // Wasm ByteBuffer final Procedure jsArrayBufferFromDartByteBuffer; // Wasm ByteBuffer
final Procedure jsifyFunction; final Procedure jsifyFunction;
final Procedure thisModuleGetter; final Class wasmFuncRefClass;
final Procedure wasmFunctionFromFunction;
// Classes used in type tests for the converters. // Classes used in type tests for the converters.
final Class jsInt8ArrayImplClass; final Class jsInt8ArrayImplClass;
@@ -503,6 +500,14 @@ class CoreTypesUtil {
'get:nullRef', 'get:nullRef',
), ),
wasmVoidClass = coreTypes.index.getClass('dart:_wasm', 'WasmVoid'), wasmVoidClass = coreTypes.index.getClass('dart:_wasm', 'WasmVoid'),
wasmInternalizeNonNullable = coreTypes.index.getTopLevelProcedure(
'dart:_wasm',
'_internalizeNonNullable',
),
unsafeCastOpaqueTarget = coreTypes.index.getTopLevelProcedure(
'dart:_internal',
'unsafeCastOpaque',
),
wasmArrayClass = coreTypes.index.getClass('dart:_wasm', 'WasmArray'), wasmArrayClass = coreTypes.index.getClass('dart:_wasm', 'WasmArray'),
wasmArrayRefClass = coreTypes.index.getClass( wasmArrayRefClass = coreTypes.index.getClass(
'dart:_wasm', 'dart:_wasm',
@@ -643,9 +648,11 @@ class CoreTypesUtil {
'dart:_js_helper', 'dart:_js_helper',
'jsifyFunction', 'jsifyFunction',
), ),
thisModuleGetter = coreTypes.index.getTopLevelProcedure( wasmFuncRefClass = coreTypes.index.getClass('dart:_wasm', 'WasmFuncRef'),
'dart:_js_helper', wasmFunctionFromFunction = coreTypes.index.getProcedure(
'get:thisModule', 'dart:_wasm',
'WasmFunction',
'fromFunction',
), ),
jsInt8ArrayImplClass = coreTypes.index.getClass( jsInt8ArrayImplClass = coreTypes.index.getClass(
'dart:_js_types', 'dart:_js_types',
@@ -735,6 +742,9 @@ class CoreTypesUtil {
DartType get nonNullableWasmExternRefType => DartType get nonNullableWasmExternRefType =>
wasmExternRefClass.getThisType(coreTypes, Nullability.nonNullable); wasmExternRefClass.getThisType(coreTypes, Nullability.nonNullable);
DartType get nonNullableWasmFuncRefType =>
wasmFuncRefClass.getThisType(coreTypes, Nullability.nonNullable);
DartType get nullableJSValueType => DartType get nullableJSValueType =>
InterfaceType(jsValueClass, Nullability.nullable); InterfaceType(jsValueClass, Nullability.nullable);
+4 -33
View File
@@ -104,23 +104,14 @@ class _ExternalMemberNamer {
/// Their names can therefore be minified. /// Their names can therefore be minified.
class _InteropHelperMemberNamer { class _InteropHelperMemberNamer {
final Namer _interopHelperNamer; final Namer _interopHelperNamer;
final Namer _exportNamer;
final CoreTypes coreTypes; final CoreTypes coreTypes;
final String interopModuleName; final String interopModuleName;
final String thisModuleSetterName;
final Map<Member, String> interopMemberNames = {}; final Map<Member, String> interopMemberNames = {};
_InteropHelperMemberNamer( _InteropHelperMemberNamer(this.coreTypes, TranslatorOptions options)
this._exportNamer, : interopModuleName = options.minify ? '_' : 'dart2wasm',
this.coreTypes,
TranslatorOptions options,
) : interopModuleName = options.minify ? '_' : 'dart2wasm',
_interopHelperNamer = Namer( _interopHelperNamer = Namer(
minify: options.minify || options.minifyInteropNames, minify: options.minify || options.minifyInteropNames,
),
thisModuleSetterName = _exportNamer.getName(
'\$setThisModule',
jsSafeName: true,
); );
ImportName? getImportName(Member member) { ImportName? getImportName(Member member) {
@@ -137,18 +128,6 @@ class _InteropHelperMemberNamer {
} }
return null; return null;
} }
String? getExportName(Member member) {
final annotationInfo = JsInteropMemberData.fromMember(member, coreTypes);
if (annotationInfo == null) return null;
if (annotationInfo.isWeakExport) {
return interopMemberNames[member] ??= _exportNamer.getName(
member.name.text,
jsSafeName: true,
);
}
return null;
}
} }
/// Manages naming for [Member]s associated with JS interop. /// Manages naming for [Member]s associated with JS interop.
@@ -165,16 +144,10 @@ class InteropMemberNamer {
Namer exportNamer, Namer exportNamer,
TranslatorOptions options, TranslatorOptions options,
) : _externalMemberNamer = _ExternalMemberNamer(coreTypes, exportNamer), ) : _externalMemberNamer = _ExternalMemberNamer(coreTypes, exportNamer),
_interopHelperMemberNamer = _InteropHelperMemberNamer( _interopHelperMemberNamer = _InteropHelperMemberNamer(coreTypes, options);
exportNamer,
coreTypes,
options,
);
String get interopHelperModuleName => String get interopHelperModuleName =>
_interopHelperMemberNamer.interopModuleName; _interopHelperMemberNamer.interopModuleName;
String get thisModuleSetterName =>
_interopHelperMemberNamer.thisModuleSetterName;
/// Returns the import name for the given member. /// Returns the import name for the given member.
/// ///
@@ -190,9 +163,7 @@ class InteropMemberNamer {
/// Returns null if the member is not an export. Checks both external and /// Returns null if the member is not an export. Checks both external and
/// interop helper exports. /// interop helper exports.
String? getExportName(Member member) { String? getExportName(Member member) {
final externalName = _externalMemberNamer.getExportName(member); return _externalMemberNamer.getExportName(member);
if (externalName != null) return externalName;
return _interopHelperMemberNamer.getExportName(member);
} }
/// Registers the export name for the given member with the [Namer]. /// Registers the export name for the given member with the [Namer].
-35
View File
@@ -414,7 +414,6 @@ class Translator with KernelNodes {
final Map<w.ModuleBuilder, ModuleMetadata> _builderToOutput = {}; final Map<w.ModuleBuilder, ModuleMetadata> _builderToOutput = {};
final Map<w.Module, w.ModuleBuilder> moduleToBuilder = {}; final Map<w.Module, w.ModuleBuilder> moduleToBuilder = {};
bool get hasMultipleModules => _moduleOutputData.hasMultipleModules; bool get hasMultipleModules => _moduleOutputData.hasMultipleModules;
final Map<w.ModuleBuilder, w.Global> _thisModuleGlobals = {};
w.ModuleBuilder moduleForReference(Reference reference) { w.ModuleBuilder moduleForReference(Reference reference) {
final module = _moduleOutputData.moduleForReference(reference); final module = _moduleOutputData.moduleForReference(reference);
@@ -506,35 +505,6 @@ class Translator with KernelNodes {
} }
} }
w.Global getThisModuleGlobal(w.ModuleBuilder module) {
return _thisModuleGlobals.putIfAbsent(module, () {
final global = module.globals.define(
w.GlobalType(w.RefType.extern(nullable: true)),
'thisModule',
);
final gb = global.initializer;
gb.ref_null(w.HeapType.extern);
gb.end();
final thisModuleSetter = module.functions.define(
typesBuilder.defineFunction(const [
w.RefType.extern(nullable: false),
], const []),
"setThisModule",
);
module.exports.export(
interopMemberNamer.thisModuleSetterName,
thisModuleSetter,
);
final fb = thisModuleSetter.body;
fb.local_get(thisModuleSetter.locals[0]);
fb.global_set(global);
fb.end();
return global;
});
}
void drainCompletionQueue() { void drainCompletionQueue() {
while (!compilationQueue.isEmpty) { while (!compilationQueue.isEmpty) {
final task = compilationQueue.pop(); final task = compilationQueue.pop();
@@ -607,11 +577,6 @@ class Translator with KernelNodes {
); );
} }
// Ensure non-empty modules expose `$setThisModule` function.
for (final moduleBuilder in _outputToBuilder.values) {
getThisModuleGlobal(moduleBuilder);
}
// This getter will be null if we pass e.g. `--use-load-ids` as the // This getter will be null if we pass e.g. `--use-load-ids` as the
// runtime code will then be pruned to call out to embedder instead of // runtime code will then be pruned to call out to embedder instead of
// consulting the load mapping bundled in the app. // consulting the load mapping bundled in the app.
@@ -7,14 +7,14 @@
(type $_Environment <...>) (type $_Environment <...>)
(type $_InterfaceType <...>) (type $_InterfaceType <...>)
(type $_Type <...>) (type $_Type <...>)
(global $"\")\"_9" (import "$" "0") (ref $JSExternWrapper)) (global $"\")\"_9" (import "$" "/") (ref $JSExternWrapper))
(global $_InterfaceType (import "$" ".") (ref $_InterfaceType)) (global $_InterfaceType (import "$" "-") (ref $_InterfaceType))
(table $$.% (import "$" "%") 765 funcref) (table $$.$ (import "$" "$") 765 funcref)
(table $$.& (import "$" "&") 20 funcref) (table $$.% (import "$" "%") 20 funcref)
(global $"\">.takeT(\"" (ref $JSExternWrapper) <...>) (global $"\">.takeT(\"" (ref $JSExternWrapper) <...>)
(global $"\"Foo<\"" (ref $JSExternWrapper) <...>) (global $"\"Foo<\"" (ref $JSExternWrapper) <...>)
(elem $$.& <...>)
(elem $$.% <...>) (elem $$.% <...>)
(elem $$.$ <...>)
(@binaryen.inline 0) (@binaryen.inline 0)
(func $"Foo.takeT (body)" (param $var0 (ref $Foo)) (param $var1 (ref $#Top)) (func $"Foo.takeT (body)" (param $var0 (ref $Foo)) (param $var1 (ref $#Top))
(local $var2 (ref $_InterfaceType)) (local $var2 (ref $_InterfaceType))
@@ -26,9 +26,9 @@
global.get $"\")\"_9" global.get $"\")\"_9"
array.new_fixed $Array<Object?> 5 array.new_fixed $Array<Object?> 5
i32.const 14 i32.const 14
call_indirect $$.& (param (ref $Array<Object?>)) (result (ref $JSExternWrapper)) call_indirect $$.% (param (ref $Array<Object?>)) (result (ref $JSExternWrapper))
i32.const 18 i32.const 18
call_indirect $$.& (param (ref null $#Top)) call_indirect $$.% (param (ref null $#Top))
global.get $_InterfaceType global.get $_InterfaceType
local.set $var2 local.set $var2
block $label0 (result i32) block $label0 (result i32)
@@ -45,14 +45,14 @@
struct.get $Foo $field0 struct.get $Foo $field0
i32.const 344 i32.const 344
i32.add i32.add
call_indirect $$.% (param (ref $#Top)) (result (ref $Array<_Type>)) call_indirect $$.$ (param (ref $#Top)) (result (ref $Array<_Type>))
i32.const 0 i32.const 0
array.get $Array<_Type> array.get $Array<_Type>
ref.null none ref.null none
local.get $var2 local.get $var2
ref.null none ref.null none
i32.const 19 i32.const 19
call_indirect $$.& (param (ref $_Type) (ref null $_Environment) (ref $_Type) (ref null $_Environment)) (result i32) call_indirect $$.% (param (ref $_Type) (ref null $_Environment) (ref $_Type) (ref null $_Environment)) (result i32)
i32.const 1 i32.const 1
i32.ne i32.ne
br_if $label0 br_if $label0
@@ -62,12 +62,12 @@
i32.eqz i32.eqz
if if
i32.const 2 i32.const 2
call_indirect $$.& call_indirect $$.%
unreachable unreachable
end end
local.get $var0 local.get $var0
i32.const 18 i32.const 18
call_indirect $$.& (param (ref null $#Top)) call_indirect $$.% (param (ref null $#Top))
) )
(func $"Foo.takeT (checked entry)" (param $var0 (ref $Foo)) (param $var1 (ref $#Top)) (func $"Foo.takeT (checked entry)" (param $var0 (ref $Foo)) (param $var1 (ref $#Top))
(local $var2 i32) (local $var2 i32)
@@ -110,7 +110,7 @@
ref.as_non_null ref.as_non_null
local.get $var1 local.get $var1
i32.const 4 i32.const 4
call_indirect $$.& (param (ref $_Type) (ref $#Top)) (result i32) call_indirect $$.% (param (ref $_Type) (ref $#Top)) (result i32)
br $label0 br $label0
end end
br $label1 br $label1
@@ -127,7 +127,7 @@
ref.as_non_null ref.as_non_null
local.get $var1 local.get $var1
i32.const 5 i32.const 5
call_indirect $$.& (param (ref $_Type) (ref $#Top)) (result i32) call_indirect $$.% (param (ref $_Type) (ref $#Top)) (result i32)
br $label0 br $label0
end end
br $label1 br $label1
@@ -140,7 +140,7 @@
ref.as_non_null ref.as_non_null
local.get $var1 local.get $var1
i32.const 6 i32.const 6
call_indirect $$.& (param (ref $_Type) (ref $#Top)) (result i32) call_indirect $$.% (param (ref $_Type) (ref $#Top)) (result i32)
br $label0 br $label0
end end
end $label1 end $label1
@@ -152,12 +152,12 @@
struct.get $_Type $field0 struct.get $_Type $field0
i32.const 467 i32.const 467
i32.add i32.add
call_indirect $$.% (param (ref $_Type) (ref $#Top)) (result i32) call_indirect $$.$ (param (ref $_Type) (ref $#Top)) (result i32)
end $label0 end $label0
i32.eqz i32.eqz
if if
i32.const 2 i32.const 2
call_indirect $$.& call_indirect $$.%
unreachable unreachable
end end
local.get $var0 local.get $var0
@@ -41,6 +41,7 @@ class FunctionBuilder extends ir.BaseFunction
functionName, functionName,
) )
..isPure = isPure ..isPure = isPure
..isJSCalled = isJSCalled
..inlineHint = inlineHint; ..inlineHint = inlineHint;
@override @override
+12 -4
View File
@@ -40,10 +40,16 @@ abstract class BaseFunction with Indexable, Exportable {
/// Whether this function is pure and has no effect. /// Whether this function is pure and has no effect.
/// ///
/// If marked as spure, we'll emit metadata in the /// If marked as pure, we'll emit metadata in the
/// `binaryen.removable.if.unused` custom section. /// `binaryen.removable.if.unused` custom section.
bool isPure = false; bool isPure = false;
/// Whether this function is called from JS.
///
/// If marked as isJSCalled, we'll emit metadata in the
/// `binaryen.js.called` custom section.
bool isJSCalled = false;
/// Inline hint for this function. /// Inline hint for this function.
/// ///
/// If set, we'll emit metadata in the `binaryen.inline` custom section. /// If set, we'll emit metadata in the `binaryen.inline` custom section.
@@ -122,6 +128,9 @@ class DefinedFunction extends BaseFunction implements Serializable {
if (isPure) { if (isPure) {
p.writeln('(@binaryen.removable.if.unused)'); p.writeln('(@binaryen.removable.if.unused)');
} }
if (isJSCalled) {
p.writeln('(@binaryen.js.called)');
}
if (inlineHint != null) { if (inlineHint != null) {
p.writeln('(@binaryen.inline $inlineHint)'); p.writeln('(@binaryen.inline $inlineHint)');
} }
@@ -195,12 +204,11 @@ class ImportedFunction extends BaseFunction implements Import {
} }
void printTo(IrPrinter p) { void printTo(IrPrinter p) {
assert(!isJSCalled);
assert(inlineHint == null);
if (isPure) { if (isPure) {
p.writeln('(@binaryen.removable.if.unused)'); p.writeln('(@binaryen.removable.if.unused)');
} }
if (inlineHint != null) {
p.writeln('(@binaryen.inline $inlineHint)');
}
p.write('(func '); p.write('(func ');
p.writeFunctionReference(this); p.writeFunctionReference(this);
p.write(' '); p.write(' ');
+5
View File
@@ -134,6 +134,7 @@ class Module implements Serializable {
).serialize(s); ).serialize(s);
BinaryenRemovableIfUnusedSection(functions).serialize(s); BinaryenRemovableIfUnusedSection(functions).serialize(s);
BinaryenInlineHintSection(functions).serialize(s); BinaryenInlineHintSection(functions).serialize(s);
BinaryenJSCalledSection(functions).serialize(s);
SourceMapSection(sourceMapUrl).serialize(s); SourceMapSection(sourceMapUrl).serialize(s);
for (final customSection in _extraCustomSections) { for (final customSection in _extraCustomSections) {
customSection.serialize(s); customSection.serialize(s);
@@ -300,6 +301,10 @@ class Module implements Serializable {
?.single, ?.single,
functions, functions,
); );
BinaryenJSCalledSection.deserialize(
customSections.remove(BinaryenJSCalledSection.customSectionName)?.single,
functions,
);
final sourceMapUrl = SourceMapSection.deserialize( final sourceMapUrl = SourceMapSection.deserialize(
customSections.remove(SourceMapSection.customSectionName)?.single, customSections.remove(SourceMapSection.customSectionName)?.single,
); );
@@ -14,6 +14,7 @@ const Set<String> _reservedCustomSectionNames = {
SourceMapSection.customSectionName, SourceMapSection.customSectionName,
BinaryenRemovableIfUnusedSection.customSectionName, BinaryenRemovableIfUnusedSection.customSectionName,
BinaryenInlineHintSection.customSectionName, BinaryenInlineHintSection.customSectionName,
BinaryenJSCalledSection.customSectionName,
}; };
abstract class Section implements Serializable { abstract class Section implements Serializable {
@@ -1209,6 +1210,55 @@ class BinaryenInlineHintSection extends CustomSection {
} }
} }
class BinaryenJSCalledSection extends CustomSection {
static const String customSectionName = 'binaryen.js.called';
final ir.Functions functions;
BinaryenJSCalledSection(this.functions) : super([]);
@override
void serializeContents(Serializer s) {
final functionsToAnnotate = [
...functions.imported.where((f) => f.isJSCalled),
...functions.defined.where((f) => f.isJSCalled),
];
if (functionsToAnnotate.isNotEmpty) {
s.writeName(customSectionName);
s.writeUnsigned(functionsToAnnotate.length);
for (final function in functionsToAnnotate) {
s.writeUnsigned(function.index);
s.writeUnsigned(1); // Number of hints
s.writeUnsigned(0); // Offset (0 == function-level)
s.writeUnsigned(0); // hint length (always 0)
}
}
}
static void deserialize(Deserializer? d, ir.Functions functions) {
if (d == null) return;
final count = d.readUnsigned();
for (int i = 0; i < count; i++) {
final functionIndex = d.readUnsigned();
final numHints = d.readUnsigned();
for (int j = 0; j < numHints; j++) {
final offset = d.readUnsigned(); // Offset (0 == function-level)
if (offset != 0) {
throw UnsupportedError(
'Only function-level ($customSectionName) annotation supported.',
);
}
final data = d.readUnsigned(); // always 0
if (data != 0) {
throw StateError('Expected 0 but got $data');
}
functions[functionIndex].isJSCalled = true;
}
}
}
}
class ExtraCustomSection extends CustomSection { class ExtraCustomSection extends CustomSection {
final String name; final String name;
final Uint8List bytes; final Uint8List bytes;
@@ -766,9 +766,6 @@ external T JS<T>(
arg19, arg19,
]); ]);
@pragma("wasm:intrinsic")
external WasmExternRef get thisModule;
/// Represents a JS `null` or `undefined` thrown from JS and caught in Wasm. /// Represents a JS `null` or `undefined` thrown from JS and caught in Wasm.
/// ///
/// The class name is copied from the dart2js class for the same thing, for /// The class name is copied from the dart2js class for the same thing, for
+1
View File
@@ -696,6 +696,7 @@ class WasmFunction<F extends Function> extends WasmFuncRef {
/// The argument must directly name a static function with no optional /// The argument must directly name a static function with no optional
/// parameters and no type parameters. /// parameters and no type parameters.
@pragma("wasm:intrinsic") @pragma("wasm:intrinsic")
@pragma("wasm:entry-point")
external factory WasmFunction.fromFunction(F f); external factory WasmFunction.fromFunction(F f);
/// Downcast `funcref` to a typed function reference. /// Downcast `funcref` to a typed function reference.