diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index 3c5087d1470..bbb925c0955 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -1700,6 +1700,18 @@ abstract class AstCodeGenerator } Member? singleTarget = translator.singleTarget(node); + + // Custom devirtualization because TFA doesn't correctly devirtualize index + // accesses on constant lists (see https://dartbug.com/60313) + if (singleTarget == null && + target.kind == ProcedureKind.Operator && + target.name.text == '[]') { + final receiver = node.receiver; + if (receiver is ConstantExpression && receiver.constant is ListConstant) { + singleTarget = translator.listBaseIndexOperator; + } + } + if (singleTarget != null) { final target = translator.getFunctionEntry(singleTarget.reference, uncheckedEntry: useUncheckedEntry); diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart index bb643fc5991..5826201aa53 100644 --- a/pkg/dart2wasm/lib/functions.dart +++ b/pkg/dart2wasm/lib/functions.dart @@ -23,8 +23,6 @@ class FunctionCollector { final Map _functions = {}; // Wasm function for each function expression and local function. final Map _lambdas = {}; - // Names of exported functions - final Map _exports = {}; // Selector IDs that are invoked via GDT. final Set _calledSelectors = {}; final Set _calledUncheckedSelectors = {}; @@ -42,14 +40,13 @@ class FunctionCollector { void _collectImportsAndExports() { for (Library library in translator.libraries) { library.procedures.forEach(_importOrExport); - library.fields.forEach(_importOrExport); for (Class cls in library.classes) { cls.procedures.forEach(_importOrExport); } } } - void _importOrExport(Member member) { + void _importOrExport(Procedure member) { String? importName = translator.getPragma(member, "wasm:import", member.name.text); if (importName != null) { @@ -58,31 +55,34 @@ class FunctionCollector { assert(!member.isInstanceMember); String module = importName.substring(0, dot); String name = importName.substring(dot + 1); - if (member is Procedure) { - w.FunctionType ftype = _makeFunctionType( - translator, member.reference, null, - isImportOrExport: true); - _functions[member.reference] = translator - .moduleForReference(member.reference) - .functions - .import(module, name, ftype, "$importName (import)"); - } + final ftype = _makeFunctionType(translator, member.reference, null, + isImportOrExport: true); + _functions[member.reference] = translator + .moduleForReference(member.reference) + .functions + .import(module, name, ftype, "$importName (import)"); } } + + // Ensure any procedures marked as exported are enqueued. String? exportName = translator.getPragma(member, "wasm:export", member.name.text); if (exportName != null) { - if (member is Procedure) { - _makeFunctionType(translator, member.reference, null, - isImportOrExport: true); - } - _exports[member.reference] = exportName; + getFunction(member.reference); } } /// If the member with the reference [target] is exported, get the export /// name. - String? getExportName(Reference target) => _exports[target]; + String? getExportName(Reference target) { + final member = target.asMember; + if (member.reference == target) { + final text = member.name.text; + return translator.getPragma(member, "wasm:export", text) ?? + translator.getPragma(member, "wasm:weak-export", text); + } + return null; + } w.BaseFunction importFunctionToDynamicModule(w.BaseFunction fun) { assert(translator.isDynamicModule); @@ -110,31 +110,6 @@ class FunctionCollector { } } - // Add exports to the module and add exported functions to the - // compilationQueue. - for (var export in _exports.entries) { - Reference target = export.key; - Member node = target.asMember; - if (node is Procedure) { - assert(!node.isInstanceMember); - assert(!node.isGetter); - w.FunctionType ftype = - _makeFunctionType(translator, target, null, isImportOrExport: true); - final module = translator.moduleForReference(target); - w.FunctionBuilder function = module.functions.define(ftype, "$node"); - _functions[target] = function; - module.exports.export(export.value, function); - translator.compilationQueue.add(AstCompilationTask(function, - getMemberCodeGenerator(translator, function, target), target)); - } else if (node is Field) { - final module = translator.moduleForReference(target); - w.Table? table = translator.getTable(module, node); - if (table != null) { - module.exports.export(export.value, table); - } - } - } - // Value classes are always implicitly allocated. recordClassAllocation( translator.classInfo[translator.boxedBoolClass]!.classId); @@ -150,9 +125,49 @@ class FunctionCollector { w.BaseFunction getFunction(Reference target) { return _functions.putIfAbsent(target, () { + final member = target.asMember; + + // If this function is a `@pragma('wasm:import', ':')` we + // import the function and return it. + if (member.reference == target && member.annotations.isNotEmpty) { + final importName = + translator.getPragma(member, 'wasm:import', member.name.text); + if (importName != null) { + assert(!member.isInstanceMember); + int dot = importName.indexOf('.'); + if (dot != -1) { + final module = importName.substring(0, dot); + final name = importName.substring(dot + 1); + final ftype = _makeFunctionType(translator, member.reference, null, + isImportOrExport: true); + return _functions[member.reference] = translator + .moduleForReference(member.reference) + .functions + .import(module, name, ftype, "$importName (import)"); + } + } + } + + // If this function is exported via + // * `@pragma('wasm:export', '')` or + // * `@pragma('wasm:weak-export', '')` + // we export it under the given `` + String? exportName; + if (member.reference == target && member.annotations.isNotEmpty) { + exportName = translator.getPragma( + member, 'wasm:export', member.name.text) ?? + translator.getPragma(member, 'wasm:weak-export', member.name.text); + assert(exportName == null || member is Procedure && member.isStatic); + } + + final w.FunctionType ftype = exportName != null + ? _makeFunctionType(translator, target, null, isImportOrExport: true) + : translator.signatureForDirectCall(target); + final module = translator.moduleForReference(target); - final function = module.functions.define( - translator.signatureForDirectCall(target), getFunctionName(target)); + final function = module.functions.define(ftype, getFunctionName(target)); + if (exportName != null) module.exports.export(exportName, function); + translator.compilationQueue.add(AstCompilationTask(function, getMemberCodeGenerator(translator, function, target), target)); diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index ca9512cad70..d51273d4872 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -164,6 +164,7 @@ enum StaticIntrinsic { intBitsToFloat('dart:_internal', null, 'intBitsToFloat'), doubleToIntBits('dart:_internal', null, 'doubleToIntBits'), intBitsToDouble('dart:_internal', null, 'intBitsToDouble'), + exportWasmFunction('dart:_internal', null, 'exportWasmFunction'), getID('dart:_internal', 'ClassID', 'getID'), loadInt8('dart:ffi', null, '_loadInt8'), loadUint8('dart:ffi', null, '_loadUint8'), @@ -1162,6 +1163,28 @@ class Intrinsifier { node.arguments.positional.single, w.NumType.i64); b.f64_reinterpret_i64(); return w.NumType.f64; + case StaticIntrinsic.exportWasmFunction: + const error = + 'The `dart:_internal:exportWasmFunction` expects its argument ' + 'to be a tear-off of a `@pragma(\'wasm:weak-export\', ...)` ' + 'annotated function'; + + // Sanity check argument. + final argument = node.arguments.positional.single; + if (argument is! ConstantExpression) throw error; + final constant = argument.constant; + if (constant is! StaticTearOffConstant) throw error; + final target = constant.target; + if (translator.getPragma(target, 'wasm:weak-export', '') == null) { + throw error; + } + + // Ensure we compile the target function & export it. + translator.functions.getFunction(target.reference); + + final topType = translator.topInfo.nullableType; + codeGen.translateExpression(NullLiteral(), topType); + return topType; case StaticIntrinsic.getID: ClassInfo info = translator.topInfo; codeGen.translateExpression( diff --git a/pkg/dart2wasm/lib/js/callback_specializer.dart b/pkg/dart2wasm/lib/js/callback_specializer.dart index 838188fccb4..ca7f2263fc9 100644 --- a/pkg/dart2wasm/lib/js/callback_specializer.dart +++ b/pkg/dart2wasm/lib/js/callback_specializer.dart @@ -63,9 +63,8 @@ class CallbackSpecializer { /// returned value. [node] is the conversion function that was called to /// convert the callback. /// - /// Returns a [String] function name representing the name of the wrapping - /// function. - String _createFunctionTrampoline(Procedure node, FunctionType function, + /// Returns the created trampoline [Procedure]. + Procedure _createFunctionTrampoline(Procedure node, FunctionType function, {required bool boxExternRef}) { // Create arguments for each positional parameter in the function. These // arguments will be JS objects. The generated wrapper will cast each @@ -156,7 +155,7 @@ class CallbackSpecializer { // returned from the supplied callback will be converted with `jsifyRaw` to // a native JS value before being returned to JS. final functionTrampolineName = _methodCollector.generateMethodName(); - _methodCollector.addInteropProcedure( + return _methodCollector.addInteropProcedure( functionTrampolineName, functionTrampolineName, FunctionNode(functionTrampolineBody, @@ -169,9 +168,8 @@ class CallbackSpecializer { returnType: _util.nullableWasmExternRefType) ..fileOffset = node.fileOffset, node.fileUri, - AnnotationType.export, + AnnotationType.weakExport, isExternal: false); - return functionTrampolineName; } /// Create a [Procedure] that will wrap a Dart callback in a JS wrapper. @@ -189,12 +187,14 @@ class CallbackSpecializer { /// function's arguments' length, the cast closure if needed, and the JS /// function's arguments as arguments. /// - /// Returns the created [Procedure]. - Procedure _getJSWrapperFunction(Procedure node, FunctionType type, + /// Returns the created JS wrapper [Procedure] which will call out to JS + /// and the trampoline [Procedure] which will be invoked by the JS code. + (Procedure, Procedure) _getJSWrapperFunction( + Procedure node, FunctionType type, {required bool boxExternRef, required bool needsCastClosure, required bool captureThis}) { - final functionTrampolineName = + final functionTrampoline = _createFunctionTrampoline(node, type, boxExternRef: boxExternRef); List jsParameters = []; var jsParametersLength = type.positionalParameters.length; @@ -220,7 +220,7 @@ class CallbackSpecializer { } // Create Dart procedure stub. - final jsMethodName = functionTrampolineName; + final jsMethodName = functionTrampoline.name.text; Procedure dartProcedure = _methodCollector.addInteropProcedure( '|$jsMethodName', 'dart2wasm.$jsMethodName', @@ -246,10 +246,10 @@ class CallbackSpecializer { dartProcedure, jsMethodName, "$jsMethodParams => finalizeWrapper(f, function($jsWrapperParams) {" - " return dartInstance.exports.$functionTrampolineName($dartArguments) " + " return dartInstance.exports.${functionTrampoline.name.text}($dartArguments) " "})"); - return dartProcedure; + return (dartProcedure, functionTrampoline); } /// Lowers an invocation of `allowInterop(foo)` to: @@ -272,7 +272,7 @@ class CallbackSpecializer { Expression allowInterop(StaticInvocation staticInvocation) { final argument = staticInvocation.arguments.positional.single; final type = argument.getStaticType(_staticTypeContext) as FunctionType; - final jsWrapperFunction = _getJSWrapperFunction( + final (jsWrapperFunction, exportedFunction) = _getJSWrapperFunction( staticInvocation.target, type, boxExternRef: false, needsCastClosure: false, captureThis: false); final v = VariableDeclaration('#var', @@ -287,12 +287,24 @@ class CallbackSpecializer { _util.wrapDartFunctionTarget, Arguments([ VariableGet(v), - StaticInvocation( - jsWrapperFunction, - Arguments([ - StaticInvocation(_util.jsObjectFromDartObjectTarget, - Arguments([VariableGet(v)])) - ])), + 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.jsObjectFromDartObjectTarget, + Arguments([VariableGet(v)])) + ]))), ], types: [ type ])), @@ -359,19 +371,30 @@ class CallbackSpecializer { final argument = staticInvocation.arguments.positional.single; final type = argument.getStaticType(_staticTypeContext) as FunctionType; final castClosure = _createCastClosure(type); - final jsWrapperFunction = _getJSWrapperFunction( + final (jsWrapperFunction, exportedFunction) = _getJSWrapperFunction( staticInvocation.target, type, boxExternRef: true, needsCastClosure: castClosure != null, captureThis: captureThis); - return _createJSValue(StaticInvocation( - jsWrapperFunction, - Arguments([ - StaticInvocation( - _util.jsObjectFromDartObjectTarget, Arguments([argument])), - if (castClosure != null) - StaticInvocation( - _util.jsObjectFromDartObjectTarget, Arguments([castClosure])) - ]))); + 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.jsObjectFromDartObjectTarget, Arguments([argument])), + if (castClosure != null) + StaticInvocation(_util.jsObjectFromDartObjectTarget, + Arguments([castClosure])) + ])))); } } diff --git a/pkg/dart2wasm/lib/js/util.dart b/pkg/dart2wasm/lib/js/util.dart index d2008c11c29..5e1d4f56686 100644 --- a/pkg/dart2wasm/lib/js/util.dart +++ b/pkg/dart2wasm/lib/js/util.dart @@ -7,7 +7,7 @@ import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart' import 'package:kernel/ast.dart'; import 'package:kernel/core_types.dart'; -enum AnnotationType { import, export } +enum AnnotationType { import, export, weakExport } /// A utility wrapper for [CoreTypes]. class CoreTypesUtil { @@ -30,6 +30,7 @@ class CoreTypesUtil { final Class wasmArrayClass; final Class wasmArrayRefClass; final Procedure wrapDartFunctionTarget; + final Procedure exportWasmFunctionTarget; CoreTypesUtil(this.coreTypes, this._extensionIndex) : allowInteropTarget = coreTypes.index @@ -70,7 +71,9 @@ class CoreTypesUtil { wasmArrayRefClass = coreTypes.index.getClass('dart:_wasm', 'WasmArrayRef'), wrapDartFunctionTarget = coreTypes.index - .getTopLevelProcedure('dart:_js_helper', '_wrapDartFunction'); + .getTopLevelProcedure('dart:_js_helper', '_wrapDartFunction'), + exportWasmFunctionTarget = coreTypes.index + .getTopLevelProcedure('dart:_internal', 'exportWasmFunction'); DartType get nonNullableObjectType => coreTypes.objectRawType(Nullability.nonNullable); @@ -96,7 +99,8 @@ class CoreTypesUtil { Procedure procedure, String pragmaOptionString, AnnotationType type) { String pragmaNameType = switch (type) { AnnotationType.import => 'import', - AnnotationType.export => 'export' + AnnotationType.export => 'export', + AnnotationType.weakExport => 'weak-export', }; procedure.addAnnotation(ConstantExpression( InstanceConstant(coreTypes.pragmaClass.reference, [], { diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart index 72ced03892c..e87191812bf 100644 --- a/pkg/dart2wasm/lib/kernel_nodes.dart +++ b/pkg/dart2wasm/lib/kernel_nodes.dart @@ -39,6 +39,8 @@ mixin KernelNodes { index.getClass("dart:_boxed_int", "BoxedInt"); late final Class closureClass = index.getClass("dart:core", "_Closure"); late final Class listBaseClass = index.getClass("dart:_list", "WasmListBase"); + late final Procedure listBaseIndexOperator = + index.getProcedure("dart:_list", "WasmListBase", "[]"); late final Class fixedLengthListClass = index.getClass("dart:_list", "ModifiableFixedLengthList"); late final Class growableListClass = diff --git a/sdk/lib/_internal/wasm/lib/async_patch.dart b/sdk/lib/_internal/wasm/lib/async_patch.dart index c2d77ea9890..ef313408e45 100644 --- a/sdk/lib/_internal/wasm/lib/async_patch.dart +++ b/sdk/lib/_internal/wasm/lib/async_patch.dart @@ -1,4 +1,4 @@ -import 'dart:_internal' show _AsyncCompleter, patch; +import 'dart:_internal' show _AsyncCompleter, patch, exportWasmFunction; import 'dart:_js_helper' show JS; diff --git a/sdk/lib/_internal/wasm/lib/internal_patch.dart b/sdk/lib/_internal/wasm/lib/internal_patch.dart index 458e49675ba..adb2c9080d0 100644 --- a/sdk/lib/_internal/wasm/lib/internal_patch.dart +++ b/sdk/lib/_internal/wasm/lib/internal_patch.dart @@ -89,6 +89,11 @@ external T unsafeCastOpaque(Object? v); // This function can be used to keep an object alive till that point. void reachabilityFence(Object? object) {} +// Used for exporting wasm functions that are annotated via +// `@pragma('wasm:weak-export', '') +@pragma("wasm:intrinsic") +external void exportWasmFunction(Function object); + // This function can be used to encode native side effects. @pragma("wasm:intrinsic") external void _nativeEffect(Object object); @@ -115,24 +120,6 @@ external int doubleToIntBits(double value); @pragma("wasm:intrinsic") external double intBitsToDouble(int value); -/// Used to invoke a Dart closure from JS (for microtasks and other callbacks), -/// printing any exceptions that escape. -@pragma("wasm:export", "\$invokeCallback") -void _invokeCallback(void Function() callback) { - try { - callback(); - } catch (e, s) { - print(e); - print(s); - // FIXME: Chrome/V8 bug makes errors from `rethrow`s not being reported to - // `window.onerror`. Please change this back to `rethrow` once the chrome - // bug is fixed. - // - // https://g-issues.chromium.org/issues/327155548 - throw e; - } -} - // Will be patched in `pkg/dart2wasm/lib/compile.dart` right before TFA. external Function get mainTearOff; diff --git a/sdk/lib/_internal/wasm/lib/js_helper_patch.dart b/sdk/lib/_internal/wasm/lib/js_helper_patch.dart index 9952d52cb5b..bfd124e2ace 100644 --- a/sdk/lib/_internal/wasm/lib/js_helper_patch.dart +++ b/sdk/lib/_internal/wasm/lib/js_helper_patch.dart @@ -2,7 +2,8 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'dart:_internal' show patch, unsafeCast, unsafeCastOpaque; +import 'dart:_internal' + show patch, unsafeCast, unsafeCastOpaque, exportWasmFunction; import 'dart:_js_helper' show JS; import 'dart:_js_types' show JSArrayBase, JSDataViewImpl; import 'dart:js_interop'; @@ -31,6 +32,10 @@ void _copyFromWasmI8Array( int wasmOffset, int length, ) { + // This will make TFA retain [_wasmI8ArrayGet] which will then cause the + // backend to export it to JS (due to `@pragma('wasm:weak-export', ...)`) + exportWasmFunction(_wasmI8ArrayGet); + JS( """(jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => { const getValue = dartInstance.exports.\$wasmI8ArrayGet; @@ -54,6 +59,10 @@ void copyToWasmI8Array( int wasmOffset, int length, ) { + // This will make TFA retain [_wasmI8ArraySet] which will then cause the + // backend to export it to JS (due to `@pragma('wasm:weak-export', ...)`) + exportWasmFunction(_wasmI8ArraySet); + JS( """(jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => { const setValue = dartInstance.exports.\$wasmI8ArraySet; @@ -123,6 +132,10 @@ void _copyFromWasmI32Array( int wasmOffset, int length, ) { + // This will make TFA retain [_wasmI32ArrayGet] which will then cause the + // backend to export it to JS (due to `@pragma('wasm:weak-export', ...)`) + exportWasmFunction(_wasmI32ArrayGet); + JS( """(jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => { const getValue = dartInstance.exports.\$wasmI32ArrayGet; @@ -146,6 +159,10 @@ void copyToWasmI32Array( int wasmOffset, int length, ) { + // This will make TFA retain [_wasmI32ArraySet] which will then cause the + // backend to export it to JS (due to `@pragma('wasm:weak-export', ...)`) + exportWasmFunction(_wasmI32ArraySet); + JS( """(jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => { const setValue = dartInstance.exports.\$wasmI32ArraySet; @@ -169,6 +186,10 @@ void _copyFromWasmF32Array( int wasmOffset, int length, ) { + // This will make TFA retain [_wasmF32ArrayGet] which will then cause the + // backend to export it to JS (due to `@pragma('wasm:weak-export', ...)`) + exportWasmFunction(_wasmF32ArrayGet); + JS( """(jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => { const getValue = dartInstance.exports.\$wasmF32ArrayGet; @@ -192,6 +213,10 @@ void copyToWasmF32Array( int wasmOffset, int length, ) { + // This will make TFA retain [_wasmF32ArraySet] which will then cause the + // backend to export it to JS (due to `@pragma('wasm:weak-export', ...)`) + exportWasmFunction(_wasmF32ArraySet); + JS( """(jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => { const setValue = dartInstance.exports.\$wasmF32ArraySet; @@ -215,6 +240,10 @@ void _copyFromWasmF64Array( int wasmOffset, int length, ) { + // This will make TFA retain [_wasmF64ArrayGet] which will then cause the + // backend to export it to JS (due to `@pragma('wasm:weak-export', ...)`) + exportWasmFunction(_wasmF64ArrayGet); + JS( """(jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => { const getValue = dartInstance.exports.\$wasmF64ArrayGet; @@ -238,6 +267,10 @@ void copyToWasmF64Array( int wasmOffset, int length, ) { + // This will make TFA retain [_wasmF64ArraySet] which will then cause the + // backend to export it to JS (due to `@pragma('wasm:weak-export', ...)`) + exportWasmFunction(_wasmF64ArraySet); + JS( """(jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => { const setValue = dartInstance.exports.\$wasmF64ArraySet; @@ -421,7 +454,7 @@ WasmExternRef jsDataViewFromDartByteData(ByteData l, int length) { return (JSDataView(jsArrayBuffer, 0, length) as JSValue).toExternRef!; } -@pragma("wasm:export", "\$wasmI8ArrayGet") +@pragma("wasm:weak-export", "\$wasmI8ArrayGet") WasmI32 _wasmI8ArrayGet(WasmExternRef? ref, WasmI32 index) { final array = unsafeCastOpaque>( unsafeCast(ref).internalize(), @@ -429,7 +462,7 @@ WasmI32 _wasmI8ArrayGet(WasmExternRef? ref, WasmI32 index) { return array.readUnsigned(index.toIntUnsigned()).toWasmI32(); } -@pragma("wasm:export", "\$wasmI8ArraySet") +@pragma("wasm:weak-export", "\$wasmI8ArraySet") void _wasmI8ArraySet(WasmExternRef? ref, WasmI32 index, WasmI32 value) { final array = unsafeCastOpaque>( unsafeCast(ref).internalize(), @@ -453,7 +486,7 @@ void _wasmI16ArraySet(WasmExternRef? ref, WasmI32 index, WasmI32 value) { array.write(index.toIntUnsigned(), value.toIntUnsigned()); } -@pragma("wasm:export", "\$wasmI32ArrayGet") +@pragma("wasm:weak-export", "\$wasmI32ArrayGet") WasmI32 _wasmI32ArrayGet(WasmExternRef? ref, WasmI32 index) { final array = unsafeCastOpaque>( unsafeCast(ref).internalize(), @@ -461,7 +494,7 @@ WasmI32 _wasmI32ArrayGet(WasmExternRef? ref, WasmI32 index) { return array.readUnsigned(index.toIntUnsigned()).toWasmI32(); } -@pragma("wasm:export", "\$wasmI32ArraySet") +@pragma("wasm:weak-export", "\$wasmI32ArraySet") void _wasmI32ArraySet(WasmExternRef? ref, WasmI32 index, WasmI32 value) { final array = unsafeCastOpaque>( unsafeCast(ref).internalize(), @@ -469,7 +502,7 @@ void _wasmI32ArraySet(WasmExternRef? ref, WasmI32 index, WasmI32 value) { array.write(index.toIntUnsigned(), value.toIntUnsigned()); } -@pragma("wasm:export", "\$wasmF32ArrayGet") +@pragma("wasm:weak-export", "\$wasmF32ArrayGet") WasmF32 _wasmF32ArrayGet(WasmExternRef? ref, WasmI32 index) { final array = unsafeCastOpaque>( unsafeCast(ref).internalize(), @@ -477,7 +510,7 @@ WasmF32 _wasmF32ArrayGet(WasmExternRef? ref, WasmI32 index) { return array[index.toIntUnsigned()]; } -@pragma("wasm:export", "\$wasmF32ArraySet") +@pragma("wasm:weak-export", "\$wasmF32ArraySet") void _wasmF32ArraySet(WasmExternRef? ref, WasmI32 index, WasmF32 value) { final array = unsafeCastOpaque>( unsafeCast(ref).internalize(), @@ -485,7 +518,7 @@ void _wasmF32ArraySet(WasmExternRef? ref, WasmI32 index, WasmF32 value) { array[index.toIntUnsigned()] = value; } -@pragma("wasm:export", "\$wasmF64ArrayGet") +@pragma("wasm:weak-export", "\$wasmF64ArrayGet") WasmF64 _wasmF64ArrayGet(WasmExternRef? ref, WasmI32 index) { final array = unsafeCastOpaque>( unsafeCast(ref).internalize(), @@ -493,7 +526,7 @@ WasmF64 _wasmF64ArrayGet(WasmExternRef? ref, WasmI32 index) { return array[index.toIntUnsigned()]; } -@pragma("wasm:export", "\$wasmF64ArraySet") +@pragma("wasm:weak-export", "\$wasmF64ArraySet") void _wasmF64ArraySet(WasmExternRef? ref, WasmI32 index, WasmF64 value) { final array = unsafeCastOpaque>( unsafeCast(ref).internalize(), diff --git a/sdk/lib/_internal/wasm/lib/timer_patch.dart b/sdk/lib/_internal/wasm/lib/timer_patch.dart index b63dd9416b4..1f53d76901d 100644 --- a/sdk/lib/_internal/wasm/lib/timer_patch.dart +++ b/sdk/lib/_internal/wasm/lib/timer_patch.dart @@ -10,43 +10,77 @@ part of "async_patch.dart"; /// definitions to users. class _JSEventLoop { /// Schedule a callback from JS via `setTimeout`. - static int _setTimeout(double ms, dynamic Function() callback) => - JS( - r"""(ms, c) => + static int _setTimeout(double ms, dynamic Function() callback) { + // This will make TFA retain [_invokeCallback] which will then cause the + // backend to export it to JS (due to `@pragma('wasm:weak-export', ...)`) + exportWasmFunction(_invokeCallback); + + return JS( + r"""(ms, c) => setTimeout(() => dartInstance.exports.$invokeCallback(c),ms)""", - ms, - callback, - ).toInt(); + ms, + callback, + ).toInt(); + } /// Cancel a callback scheduled with `setTimeout`. static void _clearTimeout(int handle) => JS(r"""(handle) => clearTimeout(handle)""", handle.toDouble()); /// Schedule a periodic callback from JS via `setInterval`. - static int _setInterval(double ms, dynamic Function() callback) => - JS( - r"""(ms, c) => + static int _setInterval(double ms, dynamic Function() callback) { + // This will make TFA retain [_invokeCallback] which will then cause the + // backend to export it to JS (due to `@pragma('wasm:weak-export', ...)`) + exportWasmFunction(_invokeCallback); + + return JS( + r"""(ms, c) => setInterval(() => dartInstance.exports.$invokeCallback(c), ms)""", - ms, - callback, - ).toInt(); + ms, + callback, + ).toInt(); + } /// Cancel a callback scheduled with `setInterval`. static void _clearInterval(int handle) => JS(r"""(handle) => clearInterval(handle)""", handle.toDouble()); /// Schedule a callback from JS via `queueMicrotask`. - static void _queueMicrotask(dynamic Function() callback) => JS( - r"""(c) => + static void _queueMicrotask(dynamic Function() callback) { + // This will make TFA retain [_invokeCallback] which will then cause the + // backend to export it to JS (due to `@pragma('wasm:weak-export', ...)`) + exportWasmFunction(_invokeCallback); + + return JS( + r"""(c) => queueMicrotask(() => dartInstance.exports.$invokeCallback(c))""", - callback, - ); + callback, + ); + } /// JS `Date.now()`, returns the number of milliseconds elapsed since the /// epoch. static int _dateNow() => JS('() => Date.now()').toInt(); } +/// Used to invoke a Dart closure from JS (for microtasks and other callbacks), +/// printing any exceptions that escape. +@pragma("wasm:weak-export", "\$invokeCallback") +void _invokeCallback(void Function() callback) { + try { + callback(); + } catch (e, s) { + print(e); + print(s); + // FIXME: Chrome/V8 bug makes errors from `rethrow`s not being reported to + // `window.onerror`. Please change this back to `rethrow` once the chrome + // bug is fixed. + // + // https://g-issues.chromium.org/issues/327155548 + throw e; + } +} + @patch class Timer { @patch