2d78883f27
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>
237 lines
5.7 KiB
Dart
237 lines
5.7 KiB
Dart
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
|
// 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 '../serialize/printer.dart';
|
|
import '../serialize/serialize.dart';
|
|
import 'ir.dart';
|
|
|
|
/// A local variable defined in a function.
|
|
class Local {
|
|
final int index;
|
|
final ValueType type;
|
|
|
|
Local(this.index, this.type);
|
|
|
|
void printTo(
|
|
IrPrinter p,
|
|
Map<int, String> localNames, {
|
|
bool isParam = false,
|
|
}) {
|
|
p.write(isParam ? 'param' : 'local');
|
|
p.write(' ');
|
|
p.writeLocalIndexReference(index);
|
|
p.write(' ');
|
|
p.writeValueType(type);
|
|
}
|
|
|
|
@override
|
|
String toString() => "$index";
|
|
}
|
|
|
|
/// An (imported or defined) function.
|
|
abstract class BaseFunction with Indexable, Exportable {
|
|
@override
|
|
final FinalizableIndex finalizableIndex;
|
|
final FunctionType type;
|
|
String? functionName;
|
|
@override
|
|
final Module enclosingModule;
|
|
|
|
/// Whether this function is pure and has no effect.
|
|
///
|
|
/// 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.
|
|
/// Value must be in range [0..127].
|
|
int? inlineHint;
|
|
|
|
BaseFunction(
|
|
this.enclosingModule,
|
|
this.finalizableIndex,
|
|
this.type, [
|
|
this.functionName,
|
|
]);
|
|
|
|
@override
|
|
String get name => functionName ?? super.name;
|
|
|
|
/// Creates an export of this function in this module.
|
|
@override
|
|
Export buildExport(String name) {
|
|
return FunctionExport(name, this);
|
|
}
|
|
}
|
|
|
|
/// A function defined in a module.
|
|
class DefinedFunction extends BaseFunction implements Serializable {
|
|
late final Instructions body;
|
|
|
|
/// All local variables defined in the function, including its inputs.
|
|
List<Local> get locals => body.locals;
|
|
|
|
Map<int, String> get localNames => body.localNames;
|
|
|
|
DefinedFunction(
|
|
super.enclosingModule,
|
|
this.body,
|
|
super.finalizableIndex,
|
|
super.type, [
|
|
super.functionName,
|
|
]);
|
|
|
|
DefinedFunction.withoutBody(
|
|
super.enclosingModule,
|
|
super.finalizableIndex,
|
|
super.type, [
|
|
super.functionName,
|
|
]);
|
|
|
|
@override
|
|
void serialize(Serializer s) {
|
|
// Serialize locals internally first in order to compute the total size of
|
|
// the serialized data.
|
|
final localS = Serializer();
|
|
int paramCount = type.inputs.length;
|
|
int entries = 0;
|
|
for (int i = paramCount + 1; i <= locals.length; i++) {
|
|
if (i == locals.length || locals[i - 1].type != locals[i].type) entries++;
|
|
}
|
|
localS.writeUnsigned(entries);
|
|
int start = paramCount;
|
|
for (int i = paramCount + 1; i <= locals.length; i++) {
|
|
if (i == locals.length || locals[i - 1].type != locals[i].type) {
|
|
localS.writeUnsigned(i - start);
|
|
localS.write(locals[i - 1].type);
|
|
start = i;
|
|
}
|
|
}
|
|
|
|
// Bundle locals and body
|
|
localS.write(body);
|
|
s.writeUnsigned(localS.data.length);
|
|
s.sourceMapSerializer.copyMappings(localS.sourceMapSerializer, s.offset);
|
|
s.writeData(localS);
|
|
}
|
|
|
|
void printTo(IrPrinter p) {
|
|
if (isPure) {
|
|
p.writeln('(@binaryen.removable.if.unused)');
|
|
}
|
|
if (isJSCalled) {
|
|
p.writeln('(@binaryen.js.called)');
|
|
}
|
|
if (inlineHint != null) {
|
|
p.writeln('(@binaryen.inline $inlineHint)');
|
|
}
|
|
p.write('(func ');
|
|
p.writeFunctionReference(this);
|
|
String? exportName;
|
|
for (final f in enclosingModule.exports.exported) {
|
|
if (f is FunctionExport && f.function == this) {
|
|
exportName = f.name;
|
|
break;
|
|
}
|
|
}
|
|
if (exportName != null) {
|
|
p.write(' ');
|
|
p.writeExport(exportName);
|
|
}
|
|
|
|
p.withLocalNames(localNames, () {
|
|
if (type.inputs.isNotEmpty || type.outputs.isNotEmpty) {
|
|
p.write(' ');
|
|
type.printSignatureWithNamesTo(p, oneLine: true);
|
|
}
|
|
p.writeln('');
|
|
p.withIndent(() {
|
|
for (int i = type.inputs.length; i < locals.length; ++i) {
|
|
p.write('(');
|
|
locals[i].printTo(p, localNames);
|
|
p.writeln(')');
|
|
}
|
|
body.printTo(p);
|
|
});
|
|
});
|
|
p.write(')');
|
|
}
|
|
|
|
void printDeclarationTo(IrPrinter p) {
|
|
p.write('(func \$$functionName ');
|
|
p.withLocalNames(localNames, () {
|
|
type.printSignatureWithNamesTo(p, oneLine: true);
|
|
});
|
|
p.write(' <...>');
|
|
p.writeln(')');
|
|
}
|
|
|
|
@override
|
|
String toString() => functionName ?? "#$finalizableIndex";
|
|
}
|
|
|
|
/// An imported function.
|
|
class ImportedFunction extends BaseFunction implements Import {
|
|
@override
|
|
final String module;
|
|
@override
|
|
final String name;
|
|
|
|
ImportedFunction(
|
|
super.enclosingModule,
|
|
this.module,
|
|
this.name,
|
|
super.finalizableIndex,
|
|
super.type, [
|
|
super.functionName,
|
|
]);
|
|
|
|
@override
|
|
void serialize(Serializer s) {
|
|
s.writeName(module);
|
|
s.writeName(name);
|
|
s.writeByte(0x00);
|
|
s.writeUnsigned(type.index);
|
|
}
|
|
|
|
void printTo(IrPrinter p) {
|
|
assert(!isJSCalled);
|
|
assert(inlineHint == null);
|
|
if (isPure) {
|
|
p.writeln('(@binaryen.removable.if.unused)');
|
|
}
|
|
p.write('(func ');
|
|
p.writeFunctionReference(this);
|
|
p.write(' ');
|
|
p.writeImport(module, name);
|
|
p.write(' ');
|
|
type.printOneLineSignatureTo(p);
|
|
p.write(')');
|
|
}
|
|
|
|
@override
|
|
String toString() => "$module.$name";
|
|
}
|
|
|
|
class FunctionExport extends Export {
|
|
final BaseFunction function;
|
|
|
|
FunctionExport(super.name, this.function);
|
|
|
|
@override
|
|
void serialize(Serializer s) {
|
|
s.writeName(name);
|
|
s.writeByte(0x00);
|
|
s.writeUnsigned(function.index);
|
|
}
|
|
}
|