mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 21:30:17 +00:00
[dart2wasm] Basic FFI support
Implements memory access through the `Pointer`, `Struct` and `Union` FFI types. `Pointer` supports `fromAddress`, `address`, `elementAt`, `cast`, `value`, `ref` (only getter), `[]`, `[]=` (not for structs and unions), `==` and `hashCode`. Structs and unions support getters and setters for their members. `Pointer`, `Struct` and `Union` are all represented as `i32`, both internally and in imports and exports. They currently don't have any boxed representation, which means they can't be nullable (a `Pointer` can still contain `nullptr` - the C null) and can't be assigned to supertypes (i.e. `Object`, `dynamic`, type variables), stored in containers nor passed as arguments or return values of local functions or function expressions. To pass an FFI value in these situations, box it manually by storing it in a field. For the FFI integer types, only the explicitly sized versions (e.g. `Int8`, `Uint32`) are supported. Whenever the feature is used, the module will import a memory by the name "ffi.memory", which will be accessed by the operations. This also adds an optional commandline argument to `run_wasm.js` to specify the module containing the FFI code. When such a module is specified, it will be instantiated first, and its exports will appear as imports to the Dart module under the "ffi" module name. Change-Id: Ie55b072056f972b42db6b75e0c676944bbe88c1a Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-debug-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/237686 Reviewed-by: Joshua Litt <joshualitt@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com> Commit-Queue: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
parent
7b8b06d717
commit
c2339ee782
|
@ -2,16 +2,21 @@
|
|||
// 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.
|
||||
//
|
||||
// Runner V8 script for testing dart2wasm, takes ".wasm" file as argument.
|
||||
// Runner V8 script for testing dart2wasm, takes ".wasm" files as arguments.
|
||||
//
|
||||
// Run as follows:
|
||||
//
|
||||
// $> d8 --experimental-wasm-gc --wasm-gc-js-interop run_wasm.js -- <file_name>.wasm
|
||||
// $> d8 --experimental-wasm-gc --wasm-gc-js-interop run_wasm.js -- <dart_module>.wasm [<ffi_module>.wasm]
|
||||
//
|
||||
// If an FFI module is specified, it will be instantiated first, and its
|
||||
// exports will be supplied as imports to the Dart module under the 'ffi'
|
||||
// module name.
|
||||
|
||||
function stringFromDartString(string) {
|
||||
var length = inst.exports.$stringLength(string);
|
||||
var length = dartInstance.exports.$stringLength(string);
|
||||
var array = new Array(length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
array[i] = inst.exports.$stringRead(string, i);
|
||||
array[i] = dartInstance.exports.$stringRead(string, i);
|
||||
}
|
||||
return String.fromCharCode(...array);
|
||||
}
|
||||
|
@ -23,15 +28,15 @@ function stringToDartString(string) {
|
|||
range |= string.codePointAt(i);
|
||||
}
|
||||
if (range < 256) {
|
||||
var dartString = inst.exports.$stringAllocate1(length);
|
||||
var dartString = dartInstance.exports.$stringAllocate1(length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
inst.exports.$stringWrite1(dartString, i, string.codePointAt(i));
|
||||
dartInstance.exports.$stringWrite1(dartString, i, string.codePointAt(i));
|
||||
}
|
||||
return dartString;
|
||||
} else {
|
||||
var dartString = inst.exports.$stringAllocate2(length);
|
||||
var dartString = dartInstance.exports.$stringAllocate2(length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
inst.exports.$stringWrite2(dartString, i, string.codePointAt(i));
|
||||
dartInstance.exports.$stringWrite2(dartString, i, string.codePointAt(i));
|
||||
}
|
||||
return dartString;
|
||||
}
|
||||
|
@ -44,7 +49,7 @@ var dart2wasm = {
|
|||
},
|
||||
scheduleCallback: function(milliseconds, closure) {
|
||||
setTimeout(function() {
|
||||
inst.exports.$call0(closure);
|
||||
dartInstance.exports.$call0(closure);
|
||||
}, milliseconds);
|
||||
},
|
||||
getCurrentStackTrace: function() {
|
||||
|
@ -62,15 +67,28 @@ var dart2wasm = {
|
|||
}
|
||||
};
|
||||
|
||||
// Create a Wasm module from the binary wasm file.
|
||||
var bytes = readbuffer(arguments[0]);
|
||||
var module = new WebAssembly.Module(bytes);
|
||||
function instantiate(filename, imports) {
|
||||
// Create a Wasm module from the binary wasm file.
|
||||
var bytes = readbuffer(filename);
|
||||
var module = new WebAssembly.Module(bytes);
|
||||
return new WebAssembly.Instance(module, imports);
|
||||
}
|
||||
|
||||
// Instantiate the Wasm module, importing from the global scope.
|
||||
// Import from the global scope.
|
||||
var importObject = (typeof window !== 'undefined')
|
||||
? window
|
||||
: Realm.global(Realm.current());
|
||||
var inst = new WebAssembly.Instance(module, importObject);
|
||||
|
||||
var result = inst.exports.main();
|
||||
// Is an FFI module specified?
|
||||
if (arguments.length > 1) {
|
||||
// instantiate FFI module
|
||||
var ffiInstance = instantiate(arguments[1], {});
|
||||
// Make its exports available as imports under the 'ffi' module name
|
||||
importObject.ffi = ffiInstance.exports;
|
||||
}
|
||||
|
||||
// Instantiate the Dart module, importing from the global scope.
|
||||
var dartInstance = instantiate(arguments[0], importObject);
|
||||
|
||||
var result = dartInstance.exports.main();
|
||||
if (result) console.log(result);
|
||||
|
|
|
@ -236,7 +236,9 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
ClassInfo info = translator.classInfo[cls]!;
|
||||
thisLocal = paramLocals[0];
|
||||
w.RefType thisType = info.nonNullableType;
|
||||
if (translator.needsConversion(paramLocals[0].type, thisType)) {
|
||||
if (translator.needsConversion(paramLocals[0].type, thisType) &&
|
||||
!(cls == translator.ffiPointerClass ||
|
||||
translator.isFfiCompound(cls))) {
|
||||
preciseThisLocal = addLocal(thisType);
|
||||
b.local_get(paramLocals[0]);
|
||||
translator.ref_cast(b, info);
|
||||
|
@ -988,6 +990,10 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
@override
|
||||
w.ValueType visitConstructorInvocation(
|
||||
ConstructorInvocation node, w.ValueType expectedType) {
|
||||
w.ValueType? intrinsicResult =
|
||||
intrinsifier.generateConstructorIntrinsic(node);
|
||||
if (intrinsicResult != null) return intrinsicResult;
|
||||
|
||||
ClassInfo info = translator.classInfo[node.target.enclosingClass]!;
|
||||
translator.functions.allocateClass(info.classId);
|
||||
w.Local temp = addLocal(info.nonNullableType);
|
||||
|
@ -1014,6 +1020,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
StaticInvocation node, w.ValueType expectedType) {
|
||||
w.ValueType? intrinsicResult = intrinsifier.generateStaticIntrinsic(node);
|
||||
if (intrinsicResult != null) return intrinsicResult;
|
||||
|
||||
_visitArguments(node.arguments, node.targetReference, 0);
|
||||
return _call(node.targetReference);
|
||||
}
|
||||
|
@ -1042,6 +1049,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
InstanceInvocation node, w.ValueType expectedType) {
|
||||
w.ValueType? intrinsicResult = intrinsifier.generateInstanceIntrinsic(node);
|
||||
if (intrinsicResult != null) return intrinsicResult;
|
||||
|
||||
Procedure target = node.interfaceTarget;
|
||||
if (node.kind == InstanceAccessKind.Object) {
|
||||
switch (target.name.text) {
|
||||
|
@ -1096,6 +1104,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
w.ValueType visitEqualsCall(EqualsCall node, w.ValueType expectedType) {
|
||||
w.ValueType? intrinsicResult = intrinsifier.generateEqualsIntrinsic(node);
|
||||
if (intrinsicResult != null) return intrinsicResult;
|
||||
|
||||
Member? singleTarget = translator.singleTarget(node);
|
||||
if (singleTarget == translator.coreTypes.objectEquals) {
|
||||
// Plain reference comparison
|
||||
|
@ -1352,6 +1361,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
w.ValueType? intrinsicResult =
|
||||
intrinsifier.generateStaticGetterIntrinsic(node);
|
||||
if (intrinsicResult != null) return intrinsicResult;
|
||||
|
||||
Member target = node.target;
|
||||
if (target is Field) {
|
||||
return translator.globals.readGlobal(b, target);
|
||||
|
@ -1493,6 +1503,9 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
|
||||
w.ValueType _directGet(
|
||||
Member target, Expression receiver, w.ValueType? Function() intrinsify) {
|
||||
w.ValueType? intrinsicResult = intrinsify();
|
||||
if (intrinsicResult != null) return intrinsicResult;
|
||||
|
||||
if (target is Field) {
|
||||
ClassInfo info = translator.classInfo[target.enclosingClass]!;
|
||||
int fieldIndex = translator.fieldIndex[target]!;
|
||||
|
@ -1504,8 +1517,6 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
} else {
|
||||
// Instance call of getter
|
||||
assert(target is Procedure && target.isGetter);
|
||||
w.ValueType? intrinsicResult = intrinsify();
|
||||
if (intrinsicResult != null) return intrinsicResult;
|
||||
w.BaseFunction targetFunction =
|
||||
translator.functions.getFunction(target.reference);
|
||||
wrap(receiver, targetFunction.type.inputs.single);
|
||||
|
|
|
@ -125,8 +125,8 @@ class SelectorInfo {
|
|||
List<w.ValueType> inputs = List.generate(
|
||||
inputSets.length,
|
||||
(i) => translator.typeForInfo(
|
||||
upperBound(inputSets[i]), inputNullable[i]) as w.ValueType);
|
||||
inputs[0] = translator.ensureBoxed(inputs[0]);
|
||||
upperBound(inputSets[i]), inputNullable[i], ensureBoxed: i == 0)
|
||||
as w.ValueType);
|
||||
if (name == '==') {
|
||||
// == can't be called with null
|
||||
inputs[1] = inputs[1].withNullability(false);
|
||||
|
|
|
@ -16,6 +16,11 @@ import 'package:wasm_builder/wasm_builder.dart' as w;
|
|||
/// member in [generateMemberIntrinsic].
|
||||
class Intrinsifier {
|
||||
final CodeGenerator codeGen;
|
||||
|
||||
// The ABI type sizes are the same for 32-bit Wasm as for 32-bit ARM, so we
|
||||
// can just use an ABI enum index corresponding to a 32-bit ARM platform.
|
||||
static const int abiEnumIndex = 0; // androidArm
|
||||
|
||||
static const w.ValueType boolType = w.NumType.i32;
|
||||
static const w.ValueType intType = w.NumType.i64;
|
||||
static const w.ValueType doubleType = w.NumType.f64;
|
||||
|
@ -145,6 +150,7 @@ class Intrinsifier {
|
|||
return w.NumType.i64;
|
||||
}
|
||||
|
||||
// _HashAbstractImmutableBase._indexNullable
|
||||
if (node.interfaceTarget == translator.immutableMapIndexNullable) {
|
||||
ClassInfo info = translator.classInfo[translator.hashFieldBaseClass]!;
|
||||
codeGen.wrap(node.receiver, info.nullableType);
|
||||
|
@ -152,6 +158,25 @@ class Intrinsifier {
|
|||
return info.struct.fields[FieldIndex.hashBaseIndex].type.unpacked;
|
||||
}
|
||||
|
||||
// _Compound._typedDataBase
|
||||
if (node.interfaceTarget.enclosingClass == translator.ffiCompoundClass &&
|
||||
name == '_typedDataBase') {
|
||||
// A compound (subclass of Struct or Union) is represented by its i32
|
||||
// address. The _typedDataBase field contains a Pointer pointing to the
|
||||
// compound, whose representation is the same.
|
||||
codeGen.wrap(node.receiver, w.NumType.i32);
|
||||
return w.NumType.i32;
|
||||
}
|
||||
|
||||
// Pointer.address
|
||||
if (node.interfaceTarget.enclosingClass == translator.ffiPointerClass &&
|
||||
name == 'address') {
|
||||
// A Pointer is represented by its i32 address.
|
||||
codeGen.wrap(node.receiver, w.NumType.i32);
|
||||
b.i64_extend_i32_u();
|
||||
return w.NumType.i64;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -400,6 +425,31 @@ class Intrinsifier {
|
|||
if (receiver is ConstantExpression &&
|
||||
receiver.constant is ListConstant &&
|
||||
name == '[]') {
|
||||
Expression arg = node.arguments.positional.single;
|
||||
|
||||
// If the list is indexed by a constant, or the ABI index, just pick
|
||||
// the element at that constant index.
|
||||
int? constIndex = null;
|
||||
if (arg is IntLiteral) {
|
||||
constIndex = arg.value;
|
||||
} else if (arg is ConstantExpression) {
|
||||
Constant argConst = arg.constant;
|
||||
if (argConst is IntConstant) {
|
||||
constIndex = argConst.value;
|
||||
}
|
||||
} else if (arg is StaticInvocation) {
|
||||
if (arg.target.enclosingLibrary.name == "dart.ffi" &&
|
||||
arg.name.text == "_abi") {
|
||||
constIndex = abiEnumIndex;
|
||||
}
|
||||
}
|
||||
if (constIndex != null) {
|
||||
ListConstant list = receiver.constant as ListConstant;
|
||||
Expression element = ConstantExpression(list.entries[constIndex]);
|
||||
return codeGen.wrap(element, typeOfExp(element));
|
||||
}
|
||||
|
||||
// Access the underlying array directly.
|
||||
ClassInfo info = translator.classInfo[translator.listBaseClass]!;
|
||||
w.RefType listType = info.nullableType;
|
||||
Field arrayField = translator.listBaseClass.fields
|
||||
|
@ -410,7 +460,7 @@ class Intrinsifier {
|
|||
as w.ArrayType;
|
||||
codeGen.wrap(receiver, listType);
|
||||
b.struct_get(info.struct, arrayFieldIndex);
|
||||
codeGen.wrap(node.arguments.positional.single, w.NumType.i64);
|
||||
codeGen.wrap(arg, w.NumType.i64);
|
||||
b.i32_wrap_i64();
|
||||
b.array_get(arrayType);
|
||||
return translator.topInfo.nullableType;
|
||||
|
@ -451,6 +501,7 @@ class Intrinsifier {
|
|||
w.ValueType leftType = typeOfExp(node.left);
|
||||
w.ValueType rightType = typeOfExp(node.right);
|
||||
|
||||
// Compare bool or Pointer
|
||||
if (leftType == boolType && rightType == boolType) {
|
||||
codeGen.wrap(node.left, w.NumType.i32);
|
||||
codeGen.wrap(node.right, w.NumType.i32);
|
||||
|
@ -458,6 +509,7 @@ class Intrinsifier {
|
|||
return w.NumType.i32;
|
||||
}
|
||||
|
||||
// Compare int
|
||||
if (leftType == intType && rightType == intType) {
|
||||
codeGen.wrap(node.left, w.NumType.i64);
|
||||
codeGen.wrap(node.right, w.NumType.i64);
|
||||
|
@ -465,6 +517,7 @@ class Intrinsifier {
|
|||
return w.NumType.i32;
|
||||
}
|
||||
|
||||
// Compare double
|
||||
if (leftType == doubleType && rightType == doubleType) {
|
||||
codeGen.wrap(node.left, w.NumType.f64);
|
||||
codeGen.wrap(node.right, w.NumType.f64);
|
||||
|
@ -491,6 +544,14 @@ class Intrinsifier {
|
|||
return w.NumType.i64;
|
||||
}
|
||||
|
||||
// nullptr
|
||||
if (target.enclosingLibrary.name == "dart.ffi" &&
|
||||
target.name.text == "nullptr") {
|
||||
// A Pointer is represented by its i32 address.
|
||||
b.i32_const(0);
|
||||
return w.NumType.i32;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -552,6 +613,9 @@ class Intrinsifier {
|
|||
translator.translateType(node.arguments.types.single);
|
||||
Expression operand = node.arguments.positional.single;
|
||||
return codeGen.wrap(operand, targetType);
|
||||
case "_nativeEffect":
|
||||
// Ignore argument
|
||||
return translator.voidMarker;
|
||||
case "allocateOneByteString":
|
||||
ClassInfo info = translator.classInfo[translator.oneByteStringClass]!;
|
||||
translator.functions.allocateClass(info.classId);
|
||||
|
@ -644,6 +708,123 @@ class Intrinsifier {
|
|||
}
|
||||
}
|
||||
|
||||
// dart:ffi static functions
|
||||
if (node.target.enclosingLibrary.name == "dart.ffi") {
|
||||
// Pointer.fromAddress
|
||||
if (name == "fromAddress") {
|
||||
// A Pointer is represented by its i32 address.
|
||||
codeGen.wrap(node.arguments.positional.single, w.NumType.i64);
|
||||
b.i32_wrap_i64();
|
||||
return w.NumType.i32;
|
||||
}
|
||||
|
||||
// Accesses to Pointer.value, Pointer.value=, Pointer.[], Pointer.[]= and
|
||||
// the members of structs and unions are desugared by the FFI kernel
|
||||
// transformations into calls to memory load and store functions.
|
||||
RegExp loadStoreFunctionNames = RegExp("^_(load|store)"
|
||||
"((Int|Uint)(8|16|32|64)|(Float|Double)(Unaligned)?|Pointer)\$");
|
||||
if (loadStoreFunctionNames.hasMatch(name)) {
|
||||
Expression pointerArg = node.arguments.positional[0];
|
||||
Expression offsetArg = node.arguments.positional[1];
|
||||
codeGen.wrap(pointerArg, w.NumType.i32);
|
||||
int offset;
|
||||
if (offsetArg is IntLiteral) {
|
||||
offset = offsetArg.value;
|
||||
} else if (offsetArg is ConstantExpression &&
|
||||
offsetArg.constant is IntConstant) {
|
||||
offset = (offsetArg.constant as IntConstant).value;
|
||||
} else {
|
||||
codeGen.wrap(offsetArg, w.NumType.i64);
|
||||
b.i32_wrap_i64();
|
||||
b.i32_add();
|
||||
offset = 0;
|
||||
}
|
||||
switch (name) {
|
||||
case "_loadInt8":
|
||||
b.i64_load8_s(translator.ffiMemory, offset);
|
||||
return w.NumType.i64;
|
||||
case "_loadUint8":
|
||||
b.i64_load8_u(translator.ffiMemory, offset);
|
||||
return w.NumType.i64;
|
||||
case "_loadInt16":
|
||||
b.i64_load16_s(translator.ffiMemory, offset);
|
||||
return w.NumType.i64;
|
||||
case "_loadUint16":
|
||||
b.i64_load16_u(translator.ffiMemory, offset);
|
||||
return w.NumType.i64;
|
||||
case "_loadInt32":
|
||||
b.i64_load32_s(translator.ffiMemory, offset);
|
||||
return w.NumType.i64;
|
||||
case "_loadUint32":
|
||||
b.i64_load32_u(translator.ffiMemory, offset);
|
||||
return w.NumType.i64;
|
||||
case "_loadInt64":
|
||||
case "_loadUint64":
|
||||
b.i64_load(translator.ffiMemory, offset);
|
||||
return w.NumType.i64;
|
||||
case "_loadFloat":
|
||||
b.f32_load(translator.ffiMemory, offset);
|
||||
b.f64_promote_f32();
|
||||
return w.NumType.f64;
|
||||
case "_loadFloatUnaligned":
|
||||
b.f32_load(translator.ffiMemory, offset, 0);
|
||||
b.f64_promote_f32();
|
||||
return w.NumType.f64;
|
||||
case "_loadDouble":
|
||||
b.f64_load(translator.ffiMemory, offset);
|
||||
return w.NumType.f64;
|
||||
case "_loadDoubleUnaligned":
|
||||
b.f64_load(translator.ffiMemory, offset, 0);
|
||||
return w.NumType.f64;
|
||||
case "_loadPointer":
|
||||
b.i32_load(translator.ffiMemory, offset);
|
||||
return w.NumType.i32;
|
||||
case "_storeInt8":
|
||||
case "_storeUint8":
|
||||
codeGen.wrap(node.arguments.positional[2], w.NumType.i64);
|
||||
b.i64_store8(translator.ffiMemory, offset);
|
||||
return translator.voidMarker;
|
||||
case "_storeInt16":
|
||||
case "_storeUint16":
|
||||
codeGen.wrap(node.arguments.positional[2], w.NumType.i64);
|
||||
b.i64_store16(translator.ffiMemory, offset);
|
||||
return translator.voidMarker;
|
||||
case "_storeInt32":
|
||||
case "_storeUint32":
|
||||
codeGen.wrap(node.arguments.positional[2], w.NumType.i64);
|
||||
b.i64_store32(translator.ffiMemory, offset);
|
||||
return translator.voidMarker;
|
||||
case "_storeInt64":
|
||||
case "_storeUint64":
|
||||
codeGen.wrap(node.arguments.positional[2], w.NumType.i64);
|
||||
b.i64_store(translator.ffiMemory, offset);
|
||||
return translator.voidMarker;
|
||||
case "_storeFloat":
|
||||
codeGen.wrap(node.arguments.positional[2], w.NumType.f64);
|
||||
b.f32_demote_f64();
|
||||
b.f32_store(translator.ffiMemory, offset);
|
||||
return translator.voidMarker;
|
||||
case "_storeFloatUnaligned":
|
||||
codeGen.wrap(node.arguments.positional[2], w.NumType.f64);
|
||||
b.f32_demote_f64();
|
||||
b.f32_store(translator.ffiMemory, offset, 0);
|
||||
return translator.voidMarker;
|
||||
case "_storeDouble":
|
||||
codeGen.wrap(node.arguments.positional[2], w.NumType.f64);
|
||||
b.f64_store(translator.ffiMemory, offset);
|
||||
return translator.voidMarker;
|
||||
case "_storeDoubleUnaligned":
|
||||
codeGen.wrap(node.arguments.positional[2], w.NumType.f64);
|
||||
b.f64_store(translator.ffiMemory, offset, 0);
|
||||
return translator.voidMarker;
|
||||
case "_storePointer":
|
||||
codeGen.wrap(node.arguments.positional[2], w.NumType.i32);
|
||||
b.i32_store(translator.ffiMemory, offset);
|
||||
return translator.voidMarker;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wasm(Int|Float|Object)Array constructors
|
||||
if (node.target.enclosingClass?.superclass ==
|
||||
translator.wasmArrayBaseClass) {
|
||||
|
@ -683,6 +864,21 @@ class Intrinsifier {
|
|||
return null;
|
||||
}
|
||||
|
||||
w.ValueType? generateConstructorIntrinsic(ConstructorInvocation node) {
|
||||
String name = node.name.text;
|
||||
|
||||
// _Compound.#fromTypedDataBase
|
||||
if (name == "#fromTypedDataBase") {
|
||||
// A compound (subclass of Struct or Union) is represented by its i32
|
||||
// address. The argument to the #fromTypedDataBase constructor is a
|
||||
// Pointer, whose representation is the same.
|
||||
codeGen.wrap(node.arguments.positional.single, w.NumType.i32);
|
||||
return w.NumType.i32;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
bool generateMemberIntrinsic(Reference target, w.DefinedFunction function,
|
||||
List<w.Local> paramLocals, w.Label? returnLabel) {
|
||||
Member member = target.asMember;
|
||||
|
|
|
@ -11,6 +11,12 @@ import 'package:kernel/target/changed_structure_notifier.dart';
|
|||
import 'package:kernel/target/targets.dart';
|
||||
import 'package:vm/transformations/mixin_full_resolution.dart'
|
||||
as transformMixins show transformLibraries;
|
||||
import 'package:vm/transformations/ffi/common.dart' as ffiHelper
|
||||
show calculateTransitiveImportsOfDartFfiIfUsed;
|
||||
import 'package:vm/transformations/ffi/definitions.dart'
|
||||
as transformFfiDefinitions show transformLibraries;
|
||||
import 'package:vm/transformations/ffi/use_sites.dart' as transformFfiUseSites
|
||||
show transformLibraries;
|
||||
|
||||
import 'package:dart2wasm/constants_backend.dart';
|
||||
import 'package:dart2wasm/transformers.dart' as wasmTrans;
|
||||
|
@ -34,6 +40,14 @@ class WasmTarget extends Target {
|
|||
@override
|
||||
TargetFlags get flags => TargetFlags(enableNullSafety: true);
|
||||
|
||||
@override
|
||||
List<String> get extraRequiredLibraries => const <String>[
|
||||
'dart:ffi',
|
||||
'dart:_internal',
|
||||
'dart:typed_data',
|
||||
'dart:nativewrappers',
|
||||
];
|
||||
|
||||
@override
|
||||
List<String> get extraIndexedLibraries => const <String>[
|
||||
"dart:collection",
|
||||
|
@ -82,6 +96,24 @@ class WasmTarget extends Target {
|
|||
this, coreTypes, hierarchy, libraries, referenceFromIndex);
|
||||
logger?.call("Transformed mixin applications");
|
||||
|
||||
List<Library>? transitiveImportingDartFfi = ffiHelper
|
||||
.calculateTransitiveImportsOfDartFfiIfUsed(component, libraries);
|
||||
if (transitiveImportingDartFfi == null) {
|
||||
logger?.call("Skipped ffi transformation");
|
||||
} else {
|
||||
transformFfiDefinitions.transformLibraries(
|
||||
component,
|
||||
coreTypes,
|
||||
hierarchy,
|
||||
transitiveImportingDartFfi,
|
||||
diagnosticReporter,
|
||||
referenceFromIndex,
|
||||
changedStructureNotifier);
|
||||
transformFfiUseSites.transformLibraries(component, coreTypes, hierarchy,
|
||||
transitiveImportingDartFfi, diagnosticReporter, referenceFromIndex);
|
||||
logger?.call("Transformed ffi annotations");
|
||||
}
|
||||
|
||||
wasmTrans.transformLibraries(libraries, coreTypes, hierarchy);
|
||||
}
|
||||
|
||||
|
|
|
@ -83,11 +83,13 @@ class Translator {
|
|||
late final Class oneByteStringClass;
|
||||
late final Class twoByteStringClass;
|
||||
late final Class typeClass;
|
||||
late final Class stackTraceClass;
|
||||
late final Class ffiCompoundClass;
|
||||
late final Class ffiPointerClass;
|
||||
late final Class typedListBaseClass;
|
||||
late final Class typedListClass;
|
||||
late final Class typedListViewClass;
|
||||
late final Class byteDataViewClass;
|
||||
late final Class stackTraceClass;
|
||||
late final Procedure stackTraceCurrent;
|
||||
late final Procedure stringEquals;
|
||||
late final Procedure stringInterpolate;
|
||||
|
@ -115,7 +117,10 @@ class Translator {
|
|||
late final w.Module m;
|
||||
late final w.DefinedFunction initFunction;
|
||||
late final w.ValueType voidMarker;
|
||||
// Lazily create exception tag if used.
|
||||
late final w.Tag exceptionTag = createExceptionTag();
|
||||
// Lazily import FFI memory if used.
|
||||
late final w.Memory ffiMemory = m.importMemory("ffi", "memory", 0);
|
||||
|
||||
// Caches for when identical source constructs need a common representation.
|
||||
final Map<w.StorageType, w.ArrayType> arrayTypeCache = {};
|
||||
|
@ -138,29 +143,17 @@ class Translator {
|
|||
dispatchTable = DispatchTable(this);
|
||||
functions = FunctionCollector(this);
|
||||
|
||||
Library coreLibrary =
|
||||
component.libraries.firstWhere((l) => l.name == "dart.core");
|
||||
Class lookupCore(String name) {
|
||||
return coreLibrary.classes.firstWhere((c) => c.name == name);
|
||||
Class Function(String) makeLookup(String libraryName) {
|
||||
Library library =
|
||||
component.libraries.firstWhere((l) => l.name == libraryName);
|
||||
return (name) => library.classes.firstWhere((c) => c.name == name);
|
||||
}
|
||||
|
||||
Library collectionLibrary =
|
||||
component.libraries.firstWhere((l) => l.name == "dart.collection");
|
||||
Class lookupCollection(String name) {
|
||||
return collectionLibrary.classes.firstWhere((c) => c.name == name);
|
||||
}
|
||||
|
||||
Library typedDataLibrary =
|
||||
component.libraries.firstWhere((l) => l.name == "dart.typed_data");
|
||||
Class lookupTypedData(String name) {
|
||||
return typedDataLibrary.classes.firstWhere((c) => c.name == name);
|
||||
}
|
||||
|
||||
Library wasmLibrary =
|
||||
component.libraries.firstWhere((l) => l.name == "dart.wasm");
|
||||
Class lookupWasm(String name) {
|
||||
return wasmLibrary.classes.firstWhere((c) => c.name == name);
|
||||
}
|
||||
Class Function(String) lookupCore = makeLookup("dart.core");
|
||||
Class Function(String) lookupCollection = makeLookup("dart.collection");
|
||||
Class Function(String) lookupFfi = makeLookup("dart.ffi");
|
||||
Class Function(String) lookupTypedData = makeLookup("dart.typed_data");
|
||||
Class Function(String) lookupWasm = makeLookup("dart.wasm");
|
||||
|
||||
wasmTypesBaseClass = lookupWasm("_WasmBase");
|
||||
wasmArrayBaseClass = lookupWasm("_WasmArray");
|
||||
|
@ -182,6 +175,8 @@ class Translator {
|
|||
twoByteStringClass = lookupCore("_TwoByteString");
|
||||
typeClass = lookupCore("_Type");
|
||||
stackTraceClass = lookupCore("StackTrace");
|
||||
ffiCompoundClass = lookupFfi("_Compound");
|
||||
ffiPointerClass = lookupFfi("Pointer");
|
||||
typedListBaseClass = lookupTypedData("_TypedListBase");
|
||||
typedListClass = lookupTypedData("_TypedList");
|
||||
typedListViewClass = lookupTypedData("_TypedListView");
|
||||
|
@ -217,6 +212,7 @@ class Translator {
|
|||
lookupWasm("WasmI64"): w.NumType.i64,
|
||||
lookupWasm("WasmF32"): w.NumType.f32,
|
||||
lookupWasm("WasmF64"): w.NumType.f64,
|
||||
ffiPointerClass: w.NumType.i32,
|
||||
};
|
||||
boxedClasses = {
|
||||
w.NumType.i32: boxedBoolClass,
|
||||
|
@ -371,28 +367,39 @@ class Translator {
|
|||
throw "Packed types are only allowed in arrays and fields";
|
||||
}
|
||||
|
||||
bool isWasmType(Class cls) {
|
||||
bool _hasSuperclass(Class cls, Class superclass) {
|
||||
while (cls.superclass != null) {
|
||||
cls = cls.superclass!;
|
||||
if (cls == wasmTypesBaseClass) return true;
|
||||
if (cls == superclass) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
w.StorageType typeForInfo(ClassInfo info, bool nullable) {
|
||||
bool isWasmType(Class cls) => _hasSuperclass(cls, wasmTypesBaseClass);
|
||||
|
||||
bool isFfiCompound(Class cls) => _hasSuperclass(cls, ffiCompoundClass);
|
||||
|
||||
w.StorageType typeForInfo(ClassInfo info, bool nullable,
|
||||
{bool ensureBoxed = false}) {
|
||||
Class? cls = info.cls;
|
||||
if (cls != null) {
|
||||
w.StorageType? builtin = builtinTypes[cls];
|
||||
if (builtin != null) {
|
||||
if (!nullable) return builtin;
|
||||
if (!nullable && (!ensureBoxed || cls == ffiPointerClass)) {
|
||||
return builtin;
|
||||
}
|
||||
if (isWasmType(cls)) {
|
||||
if (builtin.isPrimitive) throw "Wasm numeric types can't be nullable";
|
||||
return (builtin as w.RefType).withNullability(true);
|
||||
}
|
||||
if (cls == ffiPointerClass) throw "FFI types can't be nullable";
|
||||
Class? boxedClass = boxedClasses[builtin];
|
||||
if (boxedClass != null) {
|
||||
info = classInfo[boxedClass]!;
|
||||
}
|
||||
} else if (isFfiCompound(cls)) {
|
||||
if (nullable) throw "FFI types can't be nullable";
|
||||
return w.NumType.i32;
|
||||
}
|
||||
}
|
||||
return w.RefType.def(info.repr.struct,
|
||||
|
@ -529,13 +536,6 @@ class Translator {
|
|||
});
|
||||
}
|
||||
|
||||
w.ValueType ensureBoxed(w.ValueType type) {
|
||||
// Box receiver if it's primitive
|
||||
if (type is w.RefType) return type;
|
||||
return w.RefType.def(classInfo[boxedClasses[type]!]!.struct,
|
||||
nullable: false);
|
||||
}
|
||||
|
||||
w.ValueType typeForLocal(w.ValueType type) {
|
||||
return options.localNullability ? type : type.withNullability(true);
|
||||
}
|
||||
|
|
|
@ -25,5 +25,5 @@ constants {
|
|||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///ffi_struct_inline_array.dart:
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:140:9)
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:141:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
|
||||
|
|
|
@ -52,5 +52,5 @@ constants {
|
|||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///ffi_struct_inline_array.dart:
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:140:9)
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:141:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
|
||||
|
|
|
@ -25,5 +25,5 @@ constants {
|
|||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///ffi_struct_inline_array.dart:
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:140:9)
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:141:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
|
||||
|
|
|
@ -25,5 +25,5 @@ constants {
|
|||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///ffi_struct_inline_array.dart:
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:140:9)
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:141:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
|
||||
|
|
|
@ -52,5 +52,5 @@ constants {
|
|||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///ffi_struct_inline_array.dart:
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:140:9)
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:141:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
|
||||
|
|
|
@ -33,5 +33,5 @@ constants {
|
|||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:140:9)
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:141:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
|
||||
|
|
|
@ -84,5 +84,5 @@ Extra constant evaluation: evaluated: 110, effectively constant: 2
|
|||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:140:9)
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:141:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
|
||||
|
|
|
@ -33,5 +33,5 @@ constants {
|
|||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:140:9)
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:141:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
|
||||
|
|
|
@ -33,5 +33,5 @@ constants {
|
|||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:140:9)
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:141:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
|
||||
|
|
|
@ -84,5 +84,5 @@ Extra constant evaluation: evaluated: 110, effectively constant: 2
|
|||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:140:9)
|
||||
- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:141:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
|
||||
|
|
|
@ -27,6 +27,7 @@ class Module with SerializerMixin {
|
|||
BaseFunction? startFunction = null;
|
||||
|
||||
bool anyFunctionsDefined = false;
|
||||
bool anyMemoriesDefined = false;
|
||||
bool anyGlobalsDefined = false;
|
||||
bool dataReferencedFromGlobalInitializer = false;
|
||||
|
||||
|
@ -45,13 +46,23 @@ class Module with SerializerMixin {
|
|||
}
|
||||
|
||||
/// All module imports (functions and globals).
|
||||
Iterable<Import> get imports =>
|
||||
functions.whereType<Import>().followedBy(globals.whereType<Import>());
|
||||
Iterable<Import> get imports => functions
|
||||
.whereType<Import>()
|
||||
.followedBy(memories.whereType<Import>())
|
||||
.followedBy(globals.whereType<Import>());
|
||||
|
||||
/// All functions defined in the module.
|
||||
Iterable<DefinedFunction> get definedFunctions =>
|
||||
functions.whereType<DefinedFunction>();
|
||||
|
||||
/// All memories defined in the module.
|
||||
Iterable<DefinedMemory> get definedMemories =>
|
||||
memories.whereType<DefinedMemory>();
|
||||
|
||||
/// All globals defined in the module.
|
||||
Iterable<DefinedGlobal> get definedGlobals =>
|
||||
globals.whereType<DefinedGlobal>();
|
||||
|
||||
/// Add a new function type to the module.
|
||||
///
|
||||
/// All function types are canonicalized, such that identical types become
|
||||
|
@ -119,8 +130,9 @@ class Module with SerializerMixin {
|
|||
}
|
||||
|
||||
/// Add a new memory to the module.
|
||||
Memory addMemory(int minSize, [int? maxSize]) {
|
||||
final memory = Memory(memories.length, minSize, maxSize);
|
||||
DefinedMemory addMemory(int minSize, [int? maxSize]) {
|
||||
anyMemoriesDefined = true;
|
||||
final memory = DefinedMemory(memories.length, minSize, maxSize);
|
||||
memories.add(memory);
|
||||
return memory;
|
||||
}
|
||||
|
@ -179,6 +191,21 @@ class Module with SerializerMixin {
|
|||
return function;
|
||||
}
|
||||
|
||||
/// Import a memory into the module.
|
||||
///
|
||||
/// All imported memories must be specified before any memories are declared
|
||||
/// using [Module.addMemory].
|
||||
ImportedMemory importMemory(String module, String name, int minSize,
|
||||
[int? maxSize]) {
|
||||
if (anyMemoriesDefined) {
|
||||
throw "All memory imports must be specified before any definitions.";
|
||||
}
|
||||
final memory =
|
||||
ImportedMemory(module, name, memories.length, minSize, maxSize);
|
||||
memories.add(memory);
|
||||
return memory;
|
||||
}
|
||||
|
||||
/// Import a global variable into the module.
|
||||
///
|
||||
/// All imported globals must be specified before any globals are declared
|
||||
|
@ -381,15 +408,14 @@ class Table implements Serializable {
|
|||
}
|
||||
|
||||
/// A memory in a module.
|
||||
class Memory implements Serializable {
|
||||
class Memory {
|
||||
final int index;
|
||||
final int minSize;
|
||||
final int? maxSize;
|
||||
|
||||
Memory(this.index, this.minSize, [this.maxSize]);
|
||||
|
||||
@override
|
||||
void serialize(Serializer s) {
|
||||
void _serializeLimits(Serializer s) {
|
||||
if (maxSize == null) {
|
||||
s.writeByte(0x00);
|
||||
s.writeUnsigned(minSize);
|
||||
|
@ -401,6 +427,14 @@ class Memory implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
class DefinedMemory extends Memory implements Serializable {
|
||||
DefinedMemory(int index, int minSize, int? maxSize)
|
||||
: super(index, minSize, maxSize);
|
||||
|
||||
@override
|
||||
void serialize(Serializer s) => _serializeLimits(s);
|
||||
}
|
||||
|
||||
/// A tag in a module.
|
||||
class Tag implements Serializable {
|
||||
final int index;
|
||||
|
@ -517,6 +551,23 @@ class ImportedFunction extends BaseFunction implements Import {
|
|||
String toString() => "$module.$name";
|
||||
}
|
||||
|
||||
/// An imported memory.
|
||||
class ImportedMemory extends Memory implements Import {
|
||||
final String module;
|
||||
final String name;
|
||||
|
||||
ImportedMemory(this.module, this.name, int index, int minSize, int? maxSize)
|
||||
: super(index, minSize, maxSize);
|
||||
|
||||
@override
|
||||
void serialize(Serializer s) {
|
||||
s.writeName(module);
|
||||
s.writeName(name);
|
||||
s.writeByte(0x02);
|
||||
_serializeLimits(s);
|
||||
}
|
||||
}
|
||||
|
||||
/// An imported global variable.
|
||||
class ImportedGlobal extends Global implements Import {
|
||||
final String module;
|
||||
|
@ -660,11 +711,11 @@ class MemorySection extends Section {
|
|||
int get id => 5;
|
||||
|
||||
@override
|
||||
bool get isNotEmpty => module.memories.isNotEmpty;
|
||||
bool get isNotEmpty => module.definedMemories.isNotEmpty;
|
||||
|
||||
@override
|
||||
void serializeContents() {
|
||||
writeList(module.memories);
|
||||
writeList(module.definedMemories.toList());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -690,11 +741,11 @@ class GlobalSection extends Section {
|
|||
int get id => 6;
|
||||
|
||||
@override
|
||||
bool get isNotEmpty => module.globals.whereType<DefinedGlobal>().isNotEmpty;
|
||||
bool get isNotEmpty => module.definedGlobals.isNotEmpty;
|
||||
|
||||
@override
|
||||
void serializeContents() {
|
||||
writeList(module.globals.whereType<DefinedGlobal>().toList());
|
||||
writeList(module.definedGlobals.toList());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -84,6 +84,12 @@ class Lists {
|
|||
// Important: this is unsafe and must be used with care.
|
||||
external T unsafeCast<T>(Object? v);
|
||||
|
||||
// This function can be used to keep an object alive till that point.
|
||||
void reachabilityFence(Object? object) {}
|
||||
|
||||
// This function can be used to encode native side effects.
|
||||
external void _nativeEffect(Object object);
|
||||
|
||||
// Thomas Wang 64-bit mix.
|
||||
// https://gist.github.com/badboy/6267743
|
||||
int mix64(int n) {
|
||||
|
|
|
@ -39,6 +39,7 @@ final Pointer<Never> nullptr = Pointer.fromAddress(0);
|
|||
|
||||
/// Represents a pointer into the native C memory. Cannot be extended.
|
||||
@pragma('vm:entry-point')
|
||||
@pragma("wasm:entry-point")
|
||||
class Pointer<T extends NativeType> extends NativeType {
|
||||
/// Construction from raw integer.
|
||||
external factory Pointer.fromAddress(int ptr);
|
||||
|
|
|
@ -8,6 +8,7 @@ part of dart.ffi;
|
|||
///
|
||||
/// FFI struct types should extend [Struct]. For more information see the
|
||||
/// documentation on this class.
|
||||
@pragma("wasm:entry-point")
|
||||
abstract class _Compound extends NativeType {
|
||||
@pragma("vm:entry-point")
|
||||
final Object _typedDataBase;
|
||||
|
|
|
@ -215,6 +215,20 @@
|
|||
"_internal/wasm/lib/developer.dart"
|
||||
]
|
||||
},
|
||||
"ffi": {
|
||||
"uri": "ffi/ffi.dart",
|
||||
"patches": [
|
||||
"_internal/vm/lib/ffi_patch.dart",
|
||||
"_internal/vm/lib/ffi_allocation_patch.dart",
|
||||
"_internal/vm/lib/ffi_dynamic_library_patch.dart",
|
||||
"_internal/vm/lib/ffi_native_finalizer_patch.dart",
|
||||
"_internal/vm/lib/ffi_native_type_patch.dart",
|
||||
"_internal/vm/lib/ffi_struct_patch.dart"
|
||||
]
|
||||
},
|
||||
"nativewrappers": {
|
||||
"uri": "html/dartium/nativewrappers.dart"
|
||||
},
|
||||
"isolate": {
|
||||
"uri": "isolate/isolate.dart"
|
||||
},
|
||||
|
|
|
@ -203,6 +203,17 @@ wasm:
|
|||
uri: developer/developer.dart
|
||||
patches:
|
||||
- _internal/wasm/lib/developer.dart
|
||||
ffi:
|
||||
uri: "ffi/ffi.dart"
|
||||
patches:
|
||||
- "_internal/vm/lib/ffi_patch.dart"
|
||||
- "_internal/vm/lib/ffi_allocation_patch.dart"
|
||||
- "_internal/vm/lib/ffi_dynamic_library_patch.dart"
|
||||
- "_internal/vm/lib/ffi_native_finalizer_patch.dart"
|
||||
- "_internal/vm/lib/ffi_native_type_patch.dart"
|
||||
- "_internal/vm/lib/ffi_struct_patch.dart"
|
||||
nativewrappers:
|
||||
uri: "html/dartium/nativewrappers.dart"
|
||||
isolate:
|
||||
uri: isolate/isolate.dart
|
||||
math:
|
||||
|
|
Loading…
Reference in a new issue