[dart2wasm] Add support for @pragma('wasm:weak-export', '<name>')

We explicitly & unconditionally export functions to JS via the
`@pragma('wasm:export', '<name>')` annotation.

This is mainly useful for external APIs that the JavaScript side can
invoke - for example `$invokeMain()`.

Though we currently use the same mechanism also in other places where a
Dart function `A` (if used) calls to JS which calls back into Dart via
calling exported Dart function `A*`.

The issue is that this mechanism doesn't work very well with tree
shaking: If the function `A` is not used it will be tree shaken. We will
then also not emit the JS code, but we still compile the exported
function `A*` as it's a root due to `@pragma('wasm:export', '<name')`. We then also have to compile everything reachable from `A*`.

This is the case for a few functions in the core libraries but even more
pronounced in code that the modular JS interop transformer generates for
callbacks: It generates `|_<num>` functions that call out to JS which
call back into a dart-exported `_<num>` function. The former may be
unused & tree shaken (as well as their JS code) but the ladder are
force-exported and therefore treated as entrypoints.

This CL solves problem by

  * Mark function `A*` as weakly exported via
    `@pragma('wasm:weak-export', '<name>')`

    => TFA will not consider such functions as entrypoints
    => TFA will only retain such functions if they are referenced by
       other functions that aren't tree shaken.
    => The backend will export such functions as `<name>` if they are
       referenced by any other code that's compiled.

  * Making the code that calls function `A` also reference (but
    not use) `A*`.

    => This will make TFA retain function `A*` if it retains `A`.
    => In core libraries we manually reference `A*` in code that uses
    `A` and mark `A*` as weakly exported
    => In JS interop transformer we emit similar code for callbacks
    => We refer `A*` by using `exportWasmFunction()` which is an opaque
       external function that prevents TFA and backend from optimizing
       it away - so the function will be generated & exported.

Overall this CL ensures we only keep the exported functions if we
actually need them (i.e. we call to JS and JS calls those exported
functions).

This shrinks stripped dart2wasm hello world file in -O4 from
28 KB to 12 KB.

It also enables tree shaking of callback using JS interop code.

Change-Id: Ie81eac49cbcb574d569ea95a90538e8f417e2a12
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/415220
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann
2025-03-14 01:31:10 -07:00
committed by Commit Queue
parent 5bad3ac1c0
commit d66befd347
10 changed files with 255 additions and 122 deletions
+12
View File
@@ -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);
+61 -46
View File
@@ -23,8 +23,6 @@ class FunctionCollector {
final Map<Reference, w.BaseFunction> _functions = {};
// Wasm function for each function expression and local function.
final Map<Lambda, w.BaseFunction> _lambdas = {};
// Names of exported functions
final Map<Reference, String> _exports = {};
// Selector IDs that are invoked via GDT.
final Set<int> _calledSelectors = {};
final Set<int> _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', '<module>:<name>')` 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', '<name>')` or
// * `@pragma('wasm:weak-export', '<name>')`
// we export it under the given `<name>`
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));
+23
View File
@@ -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(
+52 -29
View File
@@ -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<String> 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<type>(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]))
]))));
}
}
+7 -3
View File
@@ -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, [], {
+2
View File
@@ -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 =
+1 -1
View File
@@ -1,4 +1,4 @@
import 'dart:_internal' show _AsyncCompleter, patch;
import 'dart:_internal' show _AsyncCompleter, patch, exportWasmFunction;
import 'dart:_js_helper' show JS;
+5 -18
View File
@@ -89,6 +89,11 @@ external T unsafeCastOpaque<T>(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', '<name>')
@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;
@@ -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<WasmExternRef?>(
"""(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<WasmExternRef?>(
"""(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<WasmExternRef?>(
"""(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<WasmExternRef?>(
"""(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<WasmExternRef?>(
"""(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<WasmExternRef?>(
"""(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<WasmExternRef?>(
"""(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<WasmExternRef?>(
"""(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<WasmArray<WasmI8>>(
unsafeCast<WasmExternRef>(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<WasmArray<WasmI8>>(
unsafeCast<WasmExternRef>(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<WasmArray<WasmI32>>(
unsafeCast<WasmExternRef>(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<WasmArray<WasmI32>>(
unsafeCast<WasmExternRef>(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<WasmArray<WasmF32>>(
unsafeCast<WasmExternRef>(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<WasmArray<WasmF32>>(
unsafeCast<WasmExternRef>(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<WasmArray<WasmF64>>(
unsafeCast<WasmExternRef>(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<WasmArray<WasmF64>>(
unsafeCast<WasmExternRef>(ref).internalize(),
+50 -16
View File
@@ -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<double>(
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<double>(
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<void>(r"""(handle) => clearTimeout(handle)""", handle.toDouble());
/// Schedule a periodic callback from JS via `setInterval`.
static int _setInterval(double ms, dynamic Function() callback) =>
JS<double>(
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<double>(
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<void>(r"""(handle) => clearInterval(handle)""", handle.toDouble());
/// Schedule a callback from JS via `queueMicrotask`.
static void _queueMicrotask(dynamic Function() callback) => JS<void>(
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<void>(
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<double>('() => 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