[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))
..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.
+1 -16
View File
@@ -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;
}
+52 -38
View File
@@ -17,7 +17,7 @@ class CallbackSpecializer {
CallbackSpecializer(this._staticTypeContext, this._util);
Statement _generateDispatchCase(
FunctionType function,
FunctionType instantiatedFunctionType,
Variable callbackVariable,
List<Variable> positionalParameters,
int requiredParameterCount, {
@@ -25,7 +25,8 @@ class CallbackSpecializer {
}) {
List<Expression> 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]),
),
]),
),
]),
),
);
}
-2
View File
@@ -104,7 +104,6 @@ class CompiledApp {
<<MODULE_LOADING_IMPORT>>
<<JS_POLYFILL_IMPORT>>
});
dartInstance.exports.<<THIS_MODULE_SETTER_NAME>>(dartInstance);
return new InstantiatedApp(this, dartInstance);
}
@@ -181,7 +180,6 @@ final moduleLoadingHelperTemplate = Template(r'''
<<JS_POLYFILL_IMPORT>>
"<<MAIN_MODULE_NAME>>": dartInstance.exports,
});
moduleInstance.exports.<<THIS_MODULE_SETTER_NAME>>(moduleInstance);
}
const moduleLoadingHelper = {
"loadDeferredModules": async (moduleNames) => {
+2 -6
View File
@@ -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,
+27 -17
View File
@@ -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 = <String>[];
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);
+4 -33
View File
@@ -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<Member, String> 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].
-35
View File
@@ -414,7 +414,6 @@ class Translator with KernelNodes {
final Map<w.ModuleBuilder, ModuleMetadata> _builderToOutput = {};
final Map<w.Module, w.ModuleBuilder> moduleToBuilder = {};
bool get hasMultipleModules => _moduleOutputData.hasMultipleModules;
final Map<w.ModuleBuilder, w.Global> _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.
@@ -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<Object?> 5
i32.const 14
call_indirect $$.& (param (ref $Array<Object?>)) (result (ref $JSExternWrapper))
call_indirect $$.% (param (ref $Array<Object?>)) (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
@@ -41,6 +41,7 @@ class FunctionBuilder extends ir.BaseFunction
functionName,
)
..isPure = isPure
..isJSCalled = isJSCalled
..inlineHint = inlineHint;
@override
+12 -4
View File
@@ -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(' ');
+5
View File
@@ -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,
);
@@ -14,6 +14,7 @@ const Set<String> _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;
@@ -766,9 +766,6 @@ external T JS<T>(
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
+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
/// 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.