Skip to content

Commit c2339ee

Browse files
askeksaCommit Bot
authored and
Commit Bot
committed
[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 <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Aske Simon Christensen <[email protected]>
1 parent 7b8b06d commit c2339ee

22 files changed

+416
-75
lines changed

pkg/dart2wasm/bin/run_wasm.js

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44
//
5-
// Runner V8 script for testing dart2wasm, takes ".wasm" file as argument.
5+
// Runner V8 script for testing dart2wasm, takes ".wasm" files as arguments.
6+
//
67
// Run as follows:
78
//
8-
// $> d8 --experimental-wasm-gc --wasm-gc-js-interop run_wasm.js -- <file_name>.wasm
9+
// $> d8 --experimental-wasm-gc --wasm-gc-js-interop run_wasm.js -- <dart_module>.wasm [<ffi_module>.wasm]
10+
//
11+
// If an FFI module is specified, it will be instantiated first, and its
12+
// exports will be supplied as imports to the Dart module under the 'ffi'
13+
// module name.
914

1015
function stringFromDartString(string) {
11-
var length = inst.exports.$stringLength(string);
16+
var length = dartInstance.exports.$stringLength(string);
1217
var array = new Array(length);
1318
for (var i = 0; i < length; i++) {
14-
array[i] = inst.exports.$stringRead(string, i);
19+
array[i] = dartInstance.exports.$stringRead(string, i);
1520
}
1621
return String.fromCharCode(...array);
1722
}
@@ -23,15 +28,15 @@ function stringToDartString(string) {
2328
range |= string.codePointAt(i);
2429
}
2530
if (range < 256) {
26-
var dartString = inst.exports.$stringAllocate1(length);
31+
var dartString = dartInstance.exports.$stringAllocate1(length);
2732
for (var i = 0; i < length; i++) {
28-
inst.exports.$stringWrite1(dartString, i, string.codePointAt(i));
33+
dartInstance.exports.$stringWrite1(dartString, i, string.codePointAt(i));
2934
}
3035
return dartString;
3136
} else {
32-
var dartString = inst.exports.$stringAllocate2(length);
37+
var dartString = dartInstance.exports.$stringAllocate2(length);
3338
for (var i = 0; i < length; i++) {
34-
inst.exports.$stringWrite2(dartString, i, string.codePointAt(i));
39+
dartInstance.exports.$stringWrite2(dartString, i, string.codePointAt(i));
3540
}
3641
return dartString;
3742
}
@@ -44,7 +49,7 @@ var dart2wasm = {
4449
},
4550
scheduleCallback: function(milliseconds, closure) {
4651
setTimeout(function() {
47-
inst.exports.$call0(closure);
52+
dartInstance.exports.$call0(closure);
4853
}, milliseconds);
4954
},
5055
getCurrentStackTrace: function() {
@@ -62,15 +67,28 @@ var dart2wasm = {
6267
}
6368
};
6469

65-
// Create a Wasm module from the binary wasm file.
66-
var bytes = readbuffer(arguments[0]);
67-
var module = new WebAssembly.Module(bytes);
70+
function instantiate(filename, imports) {
71+
// Create a Wasm module from the binary wasm file.
72+
var bytes = readbuffer(filename);
73+
var module = new WebAssembly.Module(bytes);
74+
return new WebAssembly.Instance(module, imports);
75+
}
6876

69-
// Instantiate the Wasm module, importing from the global scope.
77+
// Import from the global scope.
7078
var importObject = (typeof window !== 'undefined')
7179
? window
7280
: Realm.global(Realm.current());
73-
var inst = new WebAssembly.Instance(module, importObject);
7481

75-
var result = inst.exports.main();
82+
// Is an FFI module specified?
83+
if (arguments.length > 1) {
84+
// instantiate FFI module
85+
var ffiInstance = instantiate(arguments[1], {});
86+
// Make its exports available as imports under the 'ffi' module name
87+
importObject.ffi = ffiInstance.exports;
88+
}
89+
90+
// Instantiate the Dart module, importing from the global scope.
91+
var dartInstance = instantiate(arguments[0], importObject);
92+
93+
var result = dartInstance.exports.main();
7694
if (result) console.log(result);

pkg/dart2wasm/lib/code_generator.dart

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,9 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
236236
ClassInfo info = translator.classInfo[cls]!;
237237
thisLocal = paramLocals[0];
238238
w.RefType thisType = info.nonNullableType;
239-
if (translator.needsConversion(paramLocals[0].type, thisType)) {
239+
if (translator.needsConversion(paramLocals[0].type, thisType) &&
240+
!(cls == translator.ffiPointerClass ||
241+
translator.isFfiCompound(cls))) {
240242
preciseThisLocal = addLocal(thisType);
241243
b.local_get(paramLocals[0]);
242244
translator.ref_cast(b, info);
@@ -988,6 +990,10 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
988990
@override
989991
w.ValueType visitConstructorInvocation(
990992
ConstructorInvocation node, w.ValueType expectedType) {
993+
w.ValueType? intrinsicResult =
994+
intrinsifier.generateConstructorIntrinsic(node);
995+
if (intrinsicResult != null) return intrinsicResult;
996+
991997
ClassInfo info = translator.classInfo[node.target.enclosingClass]!;
992998
translator.functions.allocateClass(info.classId);
993999
w.Local temp = addLocal(info.nonNullableType);
@@ -1014,6 +1020,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
10141020
StaticInvocation node, w.ValueType expectedType) {
10151021
w.ValueType? intrinsicResult = intrinsifier.generateStaticIntrinsic(node);
10161022
if (intrinsicResult != null) return intrinsicResult;
1023+
10171024
_visitArguments(node.arguments, node.targetReference, 0);
10181025
return _call(node.targetReference);
10191026
}
@@ -1042,6 +1049,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
10421049
InstanceInvocation node, w.ValueType expectedType) {
10431050
w.ValueType? intrinsicResult = intrinsifier.generateInstanceIntrinsic(node);
10441051
if (intrinsicResult != null) return intrinsicResult;
1052+
10451053
Procedure target = node.interfaceTarget;
10461054
if (node.kind == InstanceAccessKind.Object) {
10471055
switch (target.name.text) {
@@ -1096,6 +1104,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
10961104
w.ValueType visitEqualsCall(EqualsCall node, w.ValueType expectedType) {
10971105
w.ValueType? intrinsicResult = intrinsifier.generateEqualsIntrinsic(node);
10981106
if (intrinsicResult != null) return intrinsicResult;
1107+
10991108
Member? singleTarget = translator.singleTarget(node);
11001109
if (singleTarget == translator.coreTypes.objectEquals) {
11011110
// Plain reference comparison
@@ -1352,6 +1361,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
13521361
w.ValueType? intrinsicResult =
13531362
intrinsifier.generateStaticGetterIntrinsic(node);
13541363
if (intrinsicResult != null) return intrinsicResult;
1364+
13551365
Member target = node.target;
13561366
if (target is Field) {
13571367
return translator.globals.readGlobal(b, target);
@@ -1493,6 +1503,9 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
14931503

14941504
w.ValueType _directGet(
14951505
Member target, Expression receiver, w.ValueType? Function() intrinsify) {
1506+
w.ValueType? intrinsicResult = intrinsify();
1507+
if (intrinsicResult != null) return intrinsicResult;
1508+
14961509
if (target is Field) {
14971510
ClassInfo info = translator.classInfo[target.enclosingClass]!;
14981511
int fieldIndex = translator.fieldIndex[target]!;
@@ -1504,8 +1517,6 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
15041517
} else {
15051518
// Instance call of getter
15061519
assert(target is Procedure && target.isGetter);
1507-
w.ValueType? intrinsicResult = intrinsify();
1508-
if (intrinsicResult != null) return intrinsicResult;
15091520
w.BaseFunction targetFunction =
15101521
translator.functions.getFunction(target.reference);
15111522
wrap(receiver, targetFunction.type.inputs.single);

pkg/dart2wasm/lib/dispatch_table.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ class SelectorInfo {
125125
List<w.ValueType> inputs = List.generate(
126126
inputSets.length,
127127
(i) => translator.typeForInfo(
128-
upperBound(inputSets[i]), inputNullable[i]) as w.ValueType);
129-
inputs[0] = translator.ensureBoxed(inputs[0]);
128+
upperBound(inputSets[i]), inputNullable[i], ensureBoxed: i == 0)
129+
as w.ValueType);
130130
if (name == '==') {
131131
// == can't be called with null
132132
inputs[1] = inputs[1].withNullability(false);

0 commit comments

Comments
 (0)