From 2d78883f270a76cde314675810542d2c3d73f79a Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Thu, 11 Jun 2026 12:24:16 -0700 Subject: [PATCH] [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.` 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 Reviewed-by: Srujan Gaddam --- pkg/dart2wasm/lib/functions.dart | 3 + pkg/dart2wasm/lib/intrinsics.dart | 17 +--- .../lib/js/callback_specializer.dart | 90 +++++++++++-------- pkg/dart2wasm/lib/js/runtime_blob.dart | 2 - pkg/dart2wasm/lib/js/runtime_generator.dart | 8 +- pkg/dart2wasm/lib/js/util.dart | 44 +++++---- pkg/dart2wasm/lib/namer.dart | 37 +------- pkg/dart2wasm/lib/translator.dart | 35 -------- .../ir_tests/deferred.type_checks_module1.wat | 32 +++---- .../lib/src/builder/function.dart | 1 + pkg/wasm_builder/lib/src/ir/function.dart | 16 +++- pkg/wasm_builder/lib/src/ir/module.dart | 5 ++ .../lib/src/serialize/sections.dart | 50 +++++++++++ .../_internal/wasm/js_common/js_helper.dart | 3 - sdk/lib/_wasm/wasm_types.dart | 1 + 15 files changed, 174 insertions(+), 170 deletions(-) diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart index cb85cf473ed..56c0ac4dda4 100644 --- a/pkg/dart2wasm/lib/functions.dart +++ b/pkg/dart2wasm/lib/functions.dart @@ -171,6 +171,9 @@ class FunctionCollector { final function = module.functions.define(ftype, getFunctionName(target)) ..isPure = hasPureAnnotation && !target.isCheckedEntryReference ..inlineHint = inlineHint; + if (util.hasPragma(translator.coreTypes, member, 'wasm:js-trampoline')) { + function.isJSCalled = true; + } if (exportName != null) { // Add weak exports to the module as we now know they're used. Strong // exports have already been added. diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index 5212239734e..c48c06daa26 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -10,7 +10,6 @@ import 'abi.dart' show kWasmAbiEnumIndex; import 'class_info.dart'; import 'code_generator.dart'; import 'dynamic_dispatchers.dart'; -import 'js/util.dart'; import 'translator.dart'; import 'types.dart'; import 'util.dart'; @@ -1136,15 +1135,6 @@ class Intrinsifier { 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; } @@ -1540,12 +1530,7 @@ class Intrinsifier { final constant = argument.constant; if (constant is! StaticTearOffConstant) throw error; final target = constant.target; - if (!hasWasmWeakExportPragma(codeGen.translator.coreTypes, target) && - !(JsInteropMemberData.fromMember( - target, - codeGen.translator.coreTypes, - )?.isWeakExport ?? - true)) { + if (!hasWasmWeakExportPragma(codeGen.translator.coreTypes, target)) { throw error; } diff --git a/pkg/dart2wasm/lib/js/callback_specializer.dart b/pkg/dart2wasm/lib/js/callback_specializer.dart index fac118527c5..eaadfc839f3 100644 --- a/pkg/dart2wasm/lib/js/callback_specializer.dart +++ b/pkg/dart2wasm/lib/js/callback_specializer.dart @@ -17,7 +17,7 @@ class CallbackSpecializer { CallbackSpecializer(this._staticTypeContext, this._util); Statement _generateDispatchCase( - FunctionType function, + FunctionType instantiatedFunctionType, Variable callbackVariable, List positionalParameters, int requiredParameterCount, { @@ -25,7 +25,8 @@ class CallbackSpecializer { }) { List callbackArguments = []; for (int i = 0; i < requiredParameterCount; i++) { - DartType callbackParameterType = function.positionalParameters[i]; + DartType callbackParameterType = + instantiatedFunctionType.positionalParameters[i]; Expression expression; VariableGet v = VariableGet(positionalParameters[i]); if (_util.isJSValueType(callbackParameterType) && boxExternRef) { @@ -50,10 +51,7 @@ class CallbackSpecializer { FunctionAccessKind.FunctionType, VariableGet(callbackVariable), Arguments(callbackArguments), - // Instantiate any type parameters to bounds as they would otherwise - // be free type variables in this context. - functionType: - const _InstantiateToBounds().substituteType(function) as FunctionType, + functionType: instantiatedFunctionType, ); final temp = Variable( @@ -100,7 +98,7 @@ class CallbackSpecializer { // needed. final callbackVariable = Variable( 'callback', - type: _util.nonNullableObjectType, + type: _util.nonNullableWasmExternRefType, isSynthesized: true, ); 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))); if (castClosureArguments.isNotEmpty) { @@ -185,8 +205,8 @@ class CallbackSpecializer { IntConstant(positionalParametersLength), ), _generateDispatchCase( - function, - callbackVariable, + instantiatedFunctionType, + callbackFunctionVar, positionalParameters, positionalParametersLength, boxExternRef: boxExternRef, @@ -204,8 +224,8 @@ class CallbackSpecializer { IfStatement( _util.variableCheckConstant(argumentsLength, IntConstant(i)), _generateDispatchCase( - function, - callbackVariable, + instantiatedFunctionType, + callbackFunctionVar, positionalParameters, i, boxExternRef: boxExternRef, @@ -306,8 +326,8 @@ class CallbackSpecializer { null, positionalParameters: [ Variable( - 'thisModule', - type: _util.nonNullableWasmExternRefType, + 'wasmFunction', + type: _util.nonNullableWasmFuncRefType, isSynthesized: true, ), Variable( @@ -331,7 +351,6 @@ class CallbackSpecializer { numJsParameters: jsParametersLength, captureThis: captureThis, needsCastClosure: needsCastClosure, - trampoline: functionTrampoline, ).applyToMember(dartProcedure, _util.coreTypes); return (dartProcedure, functionTrampoline); @@ -415,35 +434,30 @@ class CallbackSpecializer { captureThis: captureThis, ); return _createJSValue( - BlockExpression( - Block([ - // This ensures TFA will retain the function which the - // JS code will call. The backend in return will export - // the function due to `@pragma('wasm:weak-export', ...)` - ExpressionStatement( - StaticInvocation( - _util.exportWasmFunctionTarget, - Arguments([ - ConstantExpression(StaticTearOffConstant(exportedFunction)), - ]), + StaticInvocation( + jsWrapperFunction, + Arguments([ + StaticInvocation( + _util.wasmFunctionFromFunction, + Arguments( + [ConstantExpression(StaticTearOffConstant(exportedFunction))], + types: [ + exportedFunction.function.computeFunctionType( + Nullability.nonNullable, + ), + ], ), ), - ]), - StaticInvocation( - jsWrapperFunction, - Arguments([ - StaticGet(_util.thisModuleGetter), + StaticInvocation( + _util.jsObjectFromDartObjectTarget, + Arguments([argument]), + ), + if (castClosure != null) StaticInvocation( _util.jsObjectFromDartObjectTarget, - Arguments([argument]), + Arguments([castClosure]), ), - if (castClosure != null) - StaticInvocation( - _util.jsObjectFromDartObjectTarget, - Arguments([castClosure]), - ), - ]), - ), + ]), ), ); } diff --git a/pkg/dart2wasm/lib/js/runtime_blob.dart b/pkg/dart2wasm/lib/js/runtime_blob.dart index f7ba5da12cb..47e3d5c063c 100644 --- a/pkg/dart2wasm/lib/js/runtime_blob.dart +++ b/pkg/dart2wasm/lib/js/runtime_blob.dart @@ -104,7 +104,6 @@ class CompiledApp { <> <> }); - dartInstance.exports.<>(dartInstance); return new InstantiatedApp(this, dartInstance); } @@ -181,7 +180,6 @@ final moduleLoadingHelperTemplate = Template(r''' <> "<>": dartInstance.exports, }); - moduleInstance.exports.<>(moduleInstance); } const moduleLoadingHelper = { "loadDeferredModules": async (moduleNames) => { diff --git a/pkg/dart2wasm/lib/js/runtime_generator.dart b/pkg/dart2wasm/lib/js/runtime_generator.dart index f659e9ccf05..5a494224ea0 100644 --- a/pkg/dart2wasm/lib/js/runtime_generator.dart +++ b/pkg/dart2wasm/lib/js/runtime_generator.dart @@ -68,13 +68,11 @@ class RuntimeFinalizer { case JsCodeData(:final jsCode): final importName = _interopMemberNamer.getImportName(p)!.itemName; usedJSMethods.add((importName: importName, jsCode: jsCode)); - case JsTrampolineWrapperData(:final trampoline): + case JsTrampolineWrapperData(): final importName = _interopMemberNamer.getImportName(p)!.itemName; usedJSMethods.add(( importName: importName, - jsCode: annotationInfo.jsCode( - _interopMemberNamer.getExportName(trampoline)!, - ), + jsCode: annotationInfo.jsCode(), )); case JsTrampolineData(): // do nothing @@ -161,7 +159,6 @@ class RuntimeFinalizer { ? moduleLoadingHelperTemplate.instantiate({ ...jsStringBuiltinPolyfillImportVars, 'MAIN_MODULE_NAME': mainModuleName, - 'THIS_MODULE_SETTER_NAME': _interopMemberNamer.thisModuleSetterName, }) : ''; @@ -170,7 +167,6 @@ class RuntimeFinalizer { ...moduleLoadingImportVars, 'BUILTINS_MAP_BODY': builtins.join(', '), 'JS_METHODS': jsMethods, - 'THIS_MODULE_SETTER_NAME': _interopMemberNamer.thisModuleSetterName, 'INTERNAL_IMPORTS_MODULE_NAME': _interopMemberNamer.interopHelperModuleName, 'IMPORTED_JS_STRINGS_IN_MJS': internalizedStrings, diff --git a/pkg/dart2wasm/lib/js/util.dart b/pkg/dart2wasm/lib/js/util.dart index d50c8759c40..24d65ddc3cd 100644 --- a/pkg/dart2wasm/lib/js/util.dart +++ b/pkg/dart2wasm/lib/js/util.dart @@ -26,7 +26,6 @@ sealed class JsInteropMemberData { String get pragmaName; Constant? get toPragmaValue; bool get isImport; - bool get isWeakExport => !isImport; void applyToMember(Member member, CoreTypes coreTypes) { addPragma(member, pragmaName, coreTypes, value: toPragmaValue); } @@ -112,7 +111,6 @@ class JsTrampolineData extends JsInteropMemberData { class JsTrampolineWrapperData extends JsInteropMemberData { static const String _pragmaName = 'wasm:js-trampoline-wrapper'; final int numJsParameters; - final Procedure trampoline; final bool captureThis; final bool needsCastClosure; @@ -124,19 +122,16 @@ class JsTrampolineWrapperData extends JsInteropMemberData { JsTrampolineWrapperData({ required this.numJsParameters, - required this.trampoline, required this.captureThis, required this.needsCastClosure, }); factory JsTrampolineWrapperData.fromPragmaValue(ListConstant constant) { - final trampoline = (constant.entries[0] as StaticTearOffConstant).target; - final numJsParameters = (constant.entries[1] as IntConstant).value; - final captureThis = (constant.entries[2] as BoolConstant).value; - final needsCastClosure = (constant.entries[3] as BoolConstant).value; + final numJsParameters = (constant.entries[0] as IntConstant).value; + final captureThis = (constant.entries[1] as BoolConstant).value; + final needsCastClosure = (constant.entries[2] as BoolConstant).value; return JsTrampolineWrapperData( numJsParameters: numJsParameters, - trampoline: trampoline, captureThis: captureThis, needsCastClosure: needsCastClosure, ); @@ -144,13 +139,12 @@ class JsTrampolineWrapperData extends JsInteropMemberData { @override ListConstant get toPragmaValue => ListConstant(DynamicType(), [ - StaticTearOffConstant(trampoline), IntConstant(numJsParameters), BoolConstant(captureThis), BoolConstant(needsCastClosure), ]); - String jsCode(String trampolineExportName) { + String jsCode() { final jsParameters = []; for (int i = 0; i < numJsParameters; i++) { jsParameters.add('x$i'); @@ -163,10 +157,10 @@ class JsTrampolineWrapperData extends JsInteropMemberData { ? 'arguments.length + 1' : 'arguments.length'; String dartArguments = 'f,$argumentsLength'; - String jsMethodParams = '(module,f)'; + String jsMethodParams = '(wasmFunction,f)'; if (needsCastClosure) { dartArguments = '$dartArguments,castClosure'; - jsMethodParams = '(module,f,castClosure)'; + jsMethodParams = '(wasmFunction,f,castClosure)'; } if (captureThis) dartArguments = '$dartArguments,this'; 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 // cases because we need access to `arguments`. return "$jsMethodParams => finalizeWrapper(f, function($jsWrapperParams) {" - " return module.exports.$trampolineExportName($dartArguments) })"; + " return wasmFunction($dartArguments) })"; } } @@ -221,6 +215,8 @@ class CoreTypesUtil { final Class wasmArrayRefClass; final Procedure wrapDartFunctionTarget; final Procedure exportWasmFunctionTarget; + final Procedure wasmInternalizeNonNullable; + final Procedure unsafeCastOpaqueTarget; final Member wasmExternRefNullRef; final Class wasmI32Class; final Procedure wasmI32ToIntSigned; @@ -258,7 +254,8 @@ class CoreTypesUtil { final Procedure jsifyJSArrayBufferImpl; // JS ByteBuffer final Procedure jsArrayBufferFromDartByteBuffer; // Wasm ByteBuffer final Procedure jsifyFunction; - final Procedure thisModuleGetter; + final Class wasmFuncRefClass; + final Procedure wasmFunctionFromFunction; // Classes used in type tests for the converters. final Class jsInt8ArrayImplClass; @@ -503,6 +500,14 @@ class CoreTypesUtil { 'get:nullRef', ), 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'), wasmArrayRefClass = coreTypes.index.getClass( 'dart:_wasm', @@ -643,9 +648,11 @@ class CoreTypesUtil { 'dart:_js_helper', 'jsifyFunction', ), - thisModuleGetter = coreTypes.index.getTopLevelProcedure( - 'dart:_js_helper', - 'get:thisModule', + wasmFuncRefClass = coreTypes.index.getClass('dart:_wasm', 'WasmFuncRef'), + wasmFunctionFromFunction = coreTypes.index.getProcedure( + 'dart:_wasm', + 'WasmFunction', + 'fromFunction', ), jsInt8ArrayImplClass = coreTypes.index.getClass( 'dart:_js_types', @@ -735,6 +742,9 @@ class CoreTypesUtil { DartType get nonNullableWasmExternRefType => wasmExternRefClass.getThisType(coreTypes, Nullability.nonNullable); + DartType get nonNullableWasmFuncRefType => + wasmFuncRefClass.getThisType(coreTypes, Nullability.nonNullable); + DartType get nullableJSValueType => InterfaceType(jsValueClass, Nullability.nullable); diff --git a/pkg/dart2wasm/lib/namer.dart b/pkg/dart2wasm/lib/namer.dart index 90a47f20349..3764beaa4fe 100644 --- a/pkg/dart2wasm/lib/namer.dart +++ b/pkg/dart2wasm/lib/namer.dart @@ -104,23 +104,14 @@ class _ExternalMemberNamer { /// Their names can therefore be minified. class _InteropHelperMemberNamer { final Namer _interopHelperNamer; - final Namer _exportNamer; final CoreTypes coreTypes; final String interopModuleName; - final String thisModuleSetterName; final Map interopMemberNames = {}; - _InteropHelperMemberNamer( - this._exportNamer, - this.coreTypes, - TranslatorOptions options, - ) : interopModuleName = options.minify ? '_' : 'dart2wasm', + _InteropHelperMemberNamer(this.coreTypes, TranslatorOptions options) + : interopModuleName = options.minify ? '_' : 'dart2wasm', _interopHelperNamer = Namer( minify: options.minify || options.minifyInteropNames, - ), - thisModuleSetterName = _exportNamer.getName( - '\$setThisModule', - jsSafeName: true, ); ImportName? getImportName(Member member) { @@ -137,18 +128,6 @@ class _InteropHelperMemberNamer { } 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. @@ -165,16 +144,10 @@ class InteropMemberNamer { Namer exportNamer, TranslatorOptions options, ) : _externalMemberNamer = _ExternalMemberNamer(coreTypes, exportNamer), - _interopHelperMemberNamer = _InteropHelperMemberNamer( - exportNamer, - coreTypes, - options, - ); + _interopHelperMemberNamer = _InteropHelperMemberNamer(coreTypes, options); String get interopHelperModuleName => _interopHelperMemberNamer.interopModuleName; - String get thisModuleSetterName => - _interopHelperMemberNamer.thisModuleSetterName; /// 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 /// interop helper exports. String? getExportName(Member member) { - final externalName = _externalMemberNamer.getExportName(member); - if (externalName != null) return externalName; - return _interopHelperMemberNamer.getExportName(member); + return _externalMemberNamer.getExportName(member); } /// Registers the export name for the given member with the [Namer]. diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index b8a1c399bcd..d0c08a63245 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -414,7 +414,6 @@ class Translator with KernelNodes { final Map _builderToOutput = {}; final Map moduleToBuilder = {}; bool get hasMultipleModules => _moduleOutputData.hasMultipleModules; - final Map _thisModuleGlobals = {}; w.ModuleBuilder moduleForReference(Reference 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() { while (!compilationQueue.isEmpty) { 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 // runtime code will then be pruned to call out to embedder instead of // consulting the load mapping bundled in the app. diff --git a/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat index d4b97bd1e1b..2638b952546 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat @@ -7,14 +7,14 @@ (type $_Environment <...>) (type $_InterfaceType <...>) (type $_Type <...>) - (global $"\")\"_9" (import "$" "0") (ref $JSExternWrapper)) - (global $_InterfaceType (import "$" ".") (ref $_InterfaceType)) - (table $$.% (import "$" "%") 765 funcref) - (table $$.& (import "$" "&") 20 funcref) + (global $"\")\"_9" (import "$" "/") (ref $JSExternWrapper)) + (global $_InterfaceType (import "$" "-") (ref $_InterfaceType)) + (table $$.$ (import "$" "$") 765 funcref) + (table $$.% (import "$" "%") 20 funcref) (global $"\">.takeT(\"" (ref $JSExternWrapper) <...>) (global $"\"Foo<\"" (ref $JSExternWrapper) <...>) - (elem $$.& <...>) (elem $$.% <...>) + (elem $$.$ <...>) (@binaryen.inline 0) (func $"Foo.takeT (body)" (param $var0 (ref $Foo)) (param $var1 (ref $#Top)) (local $var2 (ref $_InterfaceType)) @@ -26,9 +26,9 @@ global.get $"\")\"_9" array.new_fixed $Array 5 i32.const 14 - call_indirect $$.& (param (ref $Array)) (result (ref $JSExternWrapper)) + call_indirect $$.% (param (ref $Array)) (result (ref $JSExternWrapper)) i32.const 18 - call_indirect $$.& (param (ref null $#Top)) + call_indirect $$.% (param (ref null $#Top)) global.get $_InterfaceType local.set $var2 block $label0 (result i32) @@ -45,14 +45,14 @@ struct.get $Foo $field0 i32.const 344 i32.add - call_indirect $$.% (param (ref $#Top)) (result (ref $Array<_Type>)) + call_indirect $$.$ (param (ref $#Top)) (result (ref $Array<_Type>)) i32.const 0 array.get $Array<_Type> ref.null none local.get $var2 ref.null none 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.ne br_if $label0 @@ -62,12 +62,12 @@ i32.eqz if i32.const 2 - call_indirect $$.& + call_indirect $$.% unreachable end local.get $var0 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)) (local $var2 i32) @@ -110,7 +110,7 @@ ref.as_non_null local.get $var1 i32.const 4 - call_indirect $$.& (param (ref $_Type) (ref $#Top)) (result i32) + call_indirect $$.% (param (ref $_Type) (ref $#Top)) (result i32) br $label0 end br $label1 @@ -127,7 +127,7 @@ ref.as_non_null local.get $var1 i32.const 5 - call_indirect $$.& (param (ref $_Type) (ref $#Top)) (result i32) + call_indirect $$.% (param (ref $_Type) (ref $#Top)) (result i32) br $label0 end br $label1 @@ -140,7 +140,7 @@ ref.as_non_null local.get $var1 i32.const 6 - call_indirect $$.& (param (ref $_Type) (ref $#Top)) (result i32) + call_indirect $$.% (param (ref $_Type) (ref $#Top)) (result i32) br $label0 end end $label1 @@ -152,12 +152,12 @@ struct.get $_Type $field0 i32.const 467 i32.add - call_indirect $$.% (param (ref $_Type) (ref $#Top)) (result i32) + call_indirect $$.$ (param (ref $_Type) (ref $#Top)) (result i32) end $label0 i32.eqz if i32.const 2 - call_indirect $$.& + call_indirect $$.% unreachable end local.get $var0 diff --git a/pkg/wasm_builder/lib/src/builder/function.dart b/pkg/wasm_builder/lib/src/builder/function.dart index b476008241a..420f1330cbc 100644 --- a/pkg/wasm_builder/lib/src/builder/function.dart +++ b/pkg/wasm_builder/lib/src/builder/function.dart @@ -41,6 +41,7 @@ class FunctionBuilder extends ir.BaseFunction functionName, ) ..isPure = isPure + ..isJSCalled = isJSCalled ..inlineHint = inlineHint; @override diff --git a/pkg/wasm_builder/lib/src/ir/function.dart b/pkg/wasm_builder/lib/src/ir/function.dart index 6d9b7347e48..f7cc3a48f46 100644 --- a/pkg/wasm_builder/lib/src/ir/function.dart +++ b/pkg/wasm_builder/lib/src/ir/function.dart @@ -40,10 +40,16 @@ abstract class BaseFunction with Indexable, Exportable { /// 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. 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. /// /// If set, we'll emit metadata in the `binaryen.inline` custom section. @@ -122,6 +128,9 @@ class DefinedFunction extends BaseFunction implements Serializable { if (isPure) { p.writeln('(@binaryen.removable.if.unused)'); } + if (isJSCalled) { + p.writeln('(@binaryen.js.called)'); + } if (inlineHint != null) { p.writeln('(@binaryen.inline $inlineHint)'); } @@ -195,12 +204,11 @@ class ImportedFunction extends BaseFunction implements Import { } void printTo(IrPrinter p) { + assert(!isJSCalled); + assert(inlineHint == null); if (isPure) { p.writeln('(@binaryen.removable.if.unused)'); } - if (inlineHint != null) { - p.writeln('(@binaryen.inline $inlineHint)'); - } p.write('(func '); p.writeFunctionReference(this); p.write(' '); diff --git a/pkg/wasm_builder/lib/src/ir/module.dart b/pkg/wasm_builder/lib/src/ir/module.dart index f974082c228..e1d3ed1a034 100644 --- a/pkg/wasm_builder/lib/src/ir/module.dart +++ b/pkg/wasm_builder/lib/src/ir/module.dart @@ -134,6 +134,7 @@ class Module implements Serializable { ).serialize(s); BinaryenRemovableIfUnusedSection(functions).serialize(s); BinaryenInlineHintSection(functions).serialize(s); + BinaryenJSCalledSection(functions).serialize(s); SourceMapSection(sourceMapUrl).serialize(s); for (final customSection in _extraCustomSections) { customSection.serialize(s); @@ -300,6 +301,10 @@ class Module implements Serializable { ?.single, functions, ); + BinaryenJSCalledSection.deserialize( + customSections.remove(BinaryenJSCalledSection.customSectionName)?.single, + functions, + ); final sourceMapUrl = SourceMapSection.deserialize( customSections.remove(SourceMapSection.customSectionName)?.single, ); diff --git a/pkg/wasm_builder/lib/src/serialize/sections.dart b/pkg/wasm_builder/lib/src/serialize/sections.dart index 02dfcad7df4..3183647a6b3 100644 --- a/pkg/wasm_builder/lib/src/serialize/sections.dart +++ b/pkg/wasm_builder/lib/src/serialize/sections.dart @@ -14,6 +14,7 @@ const Set _reservedCustomSectionNames = { SourceMapSection.customSectionName, BinaryenRemovableIfUnusedSection.customSectionName, BinaryenInlineHintSection.customSectionName, + BinaryenJSCalledSection.customSectionName, }; 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 { final String name; final Uint8List bytes; diff --git a/sdk/lib/_internal/wasm/js_common/js_helper.dart b/sdk/lib/_internal/wasm/js_common/js_helper.dart index b8052609a1c..b2ebf4900e1 100644 --- a/sdk/lib/_internal/wasm/js_common/js_helper.dart +++ b/sdk/lib/_internal/wasm/js_common/js_helper.dart @@ -766,9 +766,6 @@ external T JS( arg19, ]); -@pragma("wasm:intrinsic") -external WasmExternRef get thisModule; - /// 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 diff --git a/sdk/lib/_wasm/wasm_types.dart b/sdk/lib/_wasm/wasm_types.dart index 490487fd330..fb7729f0e74 100644 --- a/sdk/lib/_wasm/wasm_types.dart +++ b/sdk/lib/_wasm/wasm_types.dart @@ -696,6 +696,7 @@ class WasmFunction extends WasmFuncRef { /// The argument must directly name a static function with no optional /// parameters and no type parameters. @pragma("wasm:intrinsic") + @pragma("wasm:entry-point") external factory WasmFunction.fromFunction(F f); /// Downcast `funcref` to a typed function reference.