[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:
Aske Simon Christensen 2022-03-22 14:05:28 +00:00 committed by Commit Bot
parent 7b8b06d717
commit c2339ee782
22 changed files with 416 additions and 75 deletions

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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());
}
}

View file

@ -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) {

View file

@ -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);

View file

@ -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;

View file

@ -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"
},

View file

@ -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: