mirror of
https://github.com/dart-lang/sdk
synced 2024-10-01 19:14:49 +00:00
[dart2wasm] Implement Wasm FfiNative support
This CL introduces a new kernel-to-kernel pass in Wasm backend to compile `FfiNative`-annotated external functions to external functions with `wasm:import` pragmas. The new pass `WasmFfiNativeTransformer` extends `FfiNativeTransformer`. Some `FfiNativeTransformer` methods made public to allow reuse in the pass. The conversion works like this: when we see a member like: @FfiNative<Int8 Function(Int8, Int8)>("ffi.addInt8") external int addInt8(int a, int b); We generate a Wasm import using Wasm ABI types according to emscripten calling conventions: @pragma('wasm:import', 'ffi.addInt8') external static WasmI32 addInt8_$import(WasmI32 a, WasmI32 b); and convert the original member (`addInt8()`) to a wrapper function that calls the Wasm import, converting the arguments and return values to Dart types: static int addInt8(int a, int b) { return WasmI32.toIntSigned( addInt8_$import( WasmI32::int8FromInt(a), WasmI32::int8FromInt(b), )); } Build, test, and CI changes: - Test runner now uses `// SharedObjects=...` lines in dart2wasm tests to pass extra Wasm modules to d8 command used to run the tests. Support for `// SharedObjects=...` lines were already used in native/AOT FFI tests, not added in this CL. We just start to use those lines in dart2wasm tests. - dart2wasm gn file updated with a target "test_wasm_modules" that builds FFI test Wasm modules to Wasm using emcc (emscripten C compiler). - CI dart2wasm_hostasserts configuration updated to copy the Wasm files generated by the "test_wasm_modules" target to the shards. TEST=tests/web/wasm/ffi_native_test Change-Id: I6527fe4e2ca2b582e16d84fee244e9cbe6dee307 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/252822 Reviewed-by: Alexander Thomas <athom@google.com> Commit-Queue: Ömer Ağacan <omersa@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com> Reviewed-by: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
parent
d43aa20afa
commit
affc3392a0
1
BUILD.gn
1
BUILD.gn
|
@ -109,6 +109,7 @@ group("dart2wasm") {
|
|||
"utils/dart2wasm:compile_dart2wasm_platform",
|
||||
"utils/dart2wasm:dart2wasm_asserts_snapshot",
|
||||
"utils/dart2wasm:dart2wasm_snapshot",
|
||||
"utils/dart2wasm:test_wasm_modules",
|
||||
]
|
||||
}
|
||||
|
||||
|
|
395
pkg/dart2wasm/lib/ffi_native_transformer.dart
Normal file
395
pkg/dart2wasm/lib/ffi_native_transformer.dart
Normal file
|
@ -0,0 +1,395 @@
|
|||
// Copyright (c) 2022, 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 'package:kernel/ast.dart';
|
||||
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
|
||||
import 'package:kernel/core_types.dart';
|
||||
import 'package:kernel/library_index.dart' show LibraryIndex;
|
||||
import 'package:kernel/reference_from_index.dart' show ReferenceFromIndex;
|
||||
import 'package:kernel/target/targets.dart' show DiagnosticReporter;
|
||||
import 'package:vm/transformations/ffi/common.dart' show NativeType;
|
||||
import 'package:vm/transformations/ffi/native.dart' show FfiNativeTransformer;
|
||||
|
||||
/// Transform `@FfiNative`-annotated functions to convert Dart arguments to
|
||||
/// Wasm arguments expected by the FFI functions, and convert the Wasm function
|
||||
/// return value to the Dart value.
|
||||
///
|
||||
/// Add a new `external` procedure for the Wasm import.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// @FfiNative<Int8 Function(Int8, Int8)>("ffi.addInt8")
|
||||
/// external int addInt8(int a, int b);
|
||||
///
|
||||
/// Converted to:
|
||||
///
|
||||
/// external static wasm::WasmI32 addInt8_$import(wasm::WasmI32 a, wasm::WasmI32 b);
|
||||
///
|
||||
/// static int addInt8(int a, int b) =>
|
||||
/// addInt8_$import(WasmI32::int8FromInt(a), WasmI32::int8FromInt(b)).toIntSigned();
|
||||
///
|
||||
void transformLibraries(
|
||||
Component component,
|
||||
CoreTypes coreTypes,
|
||||
ClassHierarchy hierarchy,
|
||||
List<Library> libraries,
|
||||
DiagnosticReporter diagnosticReporter,
|
||||
ReferenceFromIndex? referenceFromIndex) {
|
||||
final index = LibraryIndex(component, [
|
||||
'dart:core',
|
||||
'dart:ffi',
|
||||
'dart:_internal',
|
||||
'dart:typed_data',
|
||||
'dart:nativewrappers',
|
||||
'dart:wasm'
|
||||
]);
|
||||
final transformer = WasmFfiNativeTransformer(
|
||||
index, coreTypes, hierarchy, diagnosticReporter, referenceFromIndex);
|
||||
libraries.forEach(transformer.visitLibrary);
|
||||
}
|
||||
|
||||
class WasmFfiNativeTransformer extends FfiNativeTransformer {
|
||||
final Class ffiNativeClass;
|
||||
final Class nativeFunctionClass;
|
||||
final Class wasmI32Class;
|
||||
final Class wasmI64Class;
|
||||
final Class wasmF32Class;
|
||||
final Class wasmF64Class;
|
||||
final Class wasmEqRefClass;
|
||||
final Field ffiNativeNameField;
|
||||
final Field ffiNativeIsLeafField;
|
||||
final Procedure wasmI32FromInt;
|
||||
final Procedure wasmI32Int8FromInt;
|
||||
final Procedure wasmI32Uint8FromInt;
|
||||
final Procedure wasmI32Int16FromInt;
|
||||
final Procedure wasmI32Uint16FromInt;
|
||||
final Procedure wasmI32FromBool;
|
||||
final Procedure wasmI32ToIntSigned;
|
||||
final Procedure wasmI32ToIntUnsigned;
|
||||
final Procedure wasmI32ToBool;
|
||||
final Procedure wasmI64FromInt;
|
||||
final Procedure wasmI64ToInt;
|
||||
final Procedure wasmF32FromDouble;
|
||||
final Procedure wasmF32ToDouble;
|
||||
final Procedure wasmF64FromDouble;
|
||||
final Procedure wasmF64ToDouble;
|
||||
|
||||
WasmFfiNativeTransformer(
|
||||
LibraryIndex index,
|
||||
CoreTypes coreTypes,
|
||||
ClassHierarchy hierarchy,
|
||||
DiagnosticReporter diagnosticReporter,
|
||||
ReferenceFromIndex? referenceFromIndex)
|
||||
: ffiNativeClass = index.getClass('dart:ffi', 'FfiNative'),
|
||||
nativeFunctionClass = index.getClass('dart:ffi', 'NativeFunction'),
|
||||
wasmI32Class = index.getClass('dart:wasm', 'WasmI32'),
|
||||
wasmI64Class = index.getClass('dart:wasm', 'WasmI64'),
|
||||
wasmF32Class = index.getClass('dart:wasm', 'WasmF32'),
|
||||
wasmF64Class = index.getClass('dart:wasm', 'WasmF64'),
|
||||
wasmEqRefClass = index.getClass('dart:wasm', 'WasmEqRef'),
|
||||
ffiNativeNameField =
|
||||
index.getField('dart:ffi', 'FfiNative', 'nativeName'),
|
||||
ffiNativeIsLeafField =
|
||||
index.getField('dart:ffi', 'FfiNative', 'isLeaf'),
|
||||
wasmI32FromInt = index.getProcedure('dart:wasm', 'WasmI32', 'fromInt'),
|
||||
wasmI32Int8FromInt =
|
||||
index.getProcedure('dart:wasm', 'WasmI32', 'int8FromInt'),
|
||||
wasmI32Uint8FromInt =
|
||||
index.getProcedure('dart:wasm', 'WasmI32', 'uint8FromInt'),
|
||||
wasmI32Int16FromInt =
|
||||
index.getProcedure('dart:wasm', 'WasmI32', 'int16FromInt'),
|
||||
wasmI32Uint16FromInt =
|
||||
index.getProcedure('dart:wasm', 'WasmI32', 'uint16FromInt'),
|
||||
wasmI32FromBool =
|
||||
index.getProcedure('dart:wasm', 'WasmI32', 'fromBool'),
|
||||
wasmI32ToIntSigned =
|
||||
index.getProcedure('dart:wasm', 'WasmI32', 'toIntSigned'),
|
||||
wasmI32ToIntUnsigned =
|
||||
index.getProcedure('dart:wasm', 'WasmI32', 'toIntUnsigned'),
|
||||
wasmI32ToBool = index.getProcedure('dart:wasm', 'WasmI32', 'toBool'),
|
||||
wasmI64ToInt = index.getProcedure('dart:wasm', 'WasmI64', 'toInt'),
|
||||
wasmI64FromInt = index.getProcedure('dart:wasm', 'WasmI64', 'fromInt'),
|
||||
wasmF32FromDouble =
|
||||
index.getProcedure('dart:wasm', 'WasmF32', 'fromDouble'),
|
||||
wasmF32ToDouble =
|
||||
index.getProcedure('dart:wasm', 'WasmF32', 'toDouble'),
|
||||
wasmF64FromDouble =
|
||||
index.getProcedure('dart:wasm', 'WasmF64', 'fromDouble'),
|
||||
wasmF64ToDouble =
|
||||
index.getProcedure('dart:wasm', 'WasmF64', 'toDouble'),
|
||||
super(index, coreTypes, hierarchy, diagnosticReporter,
|
||||
referenceFromIndex);
|
||||
|
||||
@override
|
||||
visitProcedure(Procedure node) {
|
||||
// Only transform functions that are external and have FfiNative annotation:
|
||||
// @FfiNative<Double Function(Double)>('Math_sqrt')
|
||||
// external double _square_root(double x);
|
||||
final ffiNativeAnnotation = tryGetFfiNativeAnnotation(node);
|
||||
if (ffiNativeAnnotation == null) {
|
||||
return node;
|
||||
}
|
||||
|
||||
// Original function should be external and without body
|
||||
assert(node.isExternal == true);
|
||||
assert(node.function.body == null);
|
||||
|
||||
final ffiConstant = ffiNativeAnnotation.constant as InstanceConstant;
|
||||
final ffiFunctionType = ffiConstant.typeArguments[0] as FunctionType;
|
||||
final nativeFunctionName = ffiConstant
|
||||
.fieldValues[ffiNativeNameField.fieldReference] as StringConstant;
|
||||
final isLeaf = (ffiConstant.fieldValues[ffiNativeIsLeafField.fieldReference]
|
||||
as BoolConstant)
|
||||
.value;
|
||||
|
||||
final dartFunctionType =
|
||||
node.function.computeThisFunctionType(Nullability.nonNullable);
|
||||
|
||||
final wrappedDartFunctionType = checkFfiType(node, dartFunctionType,
|
||||
ffiFunctionType, isLeaf, ffiNativeAnnotation.fileOffset);
|
||||
|
||||
if (wrappedDartFunctionType == null) {
|
||||
// It's OK to continue because the diagnostics issued will cause
|
||||
// compilation to fail. By continuing, we can report more diagnostics
|
||||
// before compilation ends.
|
||||
return node;
|
||||
}
|
||||
|
||||
// Create a new extern static procedure for the import. The original
|
||||
// function will be calling this one with arguments converted to right Wasm
|
||||
// types, and it will convert the return value to the right Dart type.
|
||||
final wasmImportName = Name('${node.name.text}_\$import', currentLibrary);
|
||||
final wasmImportPragma =
|
||||
ConstantExpression(InstanceConstant(pragmaClass.reference, [], {
|
||||
pragmaName.fieldReference: StringConstant("wasm:import"),
|
||||
pragmaOptions.fieldReference: nativeFunctionName,
|
||||
}));
|
||||
|
||||
// For the imported function arguments, use names in the Dart function but
|
||||
// types in the FFI declaration
|
||||
final List<VariableDeclaration> wasmImportProcedureArgs = [];
|
||||
for (int i = 0; i < ffiFunctionType.positionalParameters.length; i += 1) {
|
||||
final argWasmType =
|
||||
_convertFfiTypeToWasmType(ffiFunctionType.positionalParameters[i]);
|
||||
if (argWasmType != null) {
|
||||
wasmImportProcedureArgs.add(VariableDeclaration(
|
||||
node.function.positionalParameters[i].name!,
|
||||
type: argWasmType,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
final retWasmType = _convertFfiTypeToWasmType(ffiFunctionType.returnType);
|
||||
final retWasmType_ = retWasmType ?? VoidType();
|
||||
|
||||
final wasmImportProcedure = Procedure(
|
||||
wasmImportName,
|
||||
ProcedureKind.Method,
|
||||
FunctionNode(null,
|
||||
positionalParameters: wasmImportProcedureArgs,
|
||||
returnType: retWasmType_),
|
||||
fileUri: currentLibrary.fileUri,
|
||||
isExternal: true,
|
||||
isStatic: true,
|
||||
isSynthetic: true)
|
||||
..fileOffset = node.fileOffset
|
||||
..isNonNullableByDefault = true;
|
||||
wasmImportProcedure.addAnnotation(wasmImportPragma);
|
||||
currentLibrary.addProcedure(wasmImportProcedure);
|
||||
|
||||
// Update the original procedure to call the Wasm import, converting
|
||||
// arguments and return value
|
||||
node.isExternal = false;
|
||||
node.annotations.remove(ffiNativeAnnotation);
|
||||
|
||||
// Convert arguments
|
||||
assert(ffiFunctionType.positionalParameters.length ==
|
||||
node.function.positionalParameters.length);
|
||||
|
||||
final ffiCallArgs = <Expression>[];
|
||||
|
||||
for (int i = 0; i < node.function.positionalParameters.length; i += 1) {
|
||||
final ffiArgumentType = ffiFunctionType.positionalParameters[i];
|
||||
|
||||
final ffiValue = _dartValueToFfiValue(
|
||||
ffiArgumentType, VariableGet(node.function.positionalParameters[i]));
|
||||
|
||||
if (ffiValue != null) {
|
||||
ffiCallArgs.add(ffiValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert return value
|
||||
node.function.body = ReturnStatement(_ffiValueToDartValue(
|
||||
ffiFunctionType.returnType,
|
||||
StaticInvocation(wasmImportProcedure, Arguments(ffiCallArgs))));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/// Converts a Dart value to the corresponding Wasm FFI value according to
|
||||
/// emscripten ABI.
|
||||
///
|
||||
/// For example, converts a Dart `int` for an `Uint8` native type to Wasm I32
|
||||
/// and masks high bits.
|
||||
///
|
||||
/// Returns `null` for [Void] values.
|
||||
Expression? _dartValueToFfiValue(DartType ffiType, Expression expr) {
|
||||
final InterfaceType abiType_ = ffiType as InterfaceType;
|
||||
final NativeType abiTypeNativeType = getType(abiType_.classNode)!;
|
||||
|
||||
switch (abiTypeNativeType) {
|
||||
case NativeType.kInt8:
|
||||
return StaticInvocation(wasmI32Int8FromInt, Arguments([expr]));
|
||||
|
||||
case NativeType.kUint8:
|
||||
return StaticInvocation(wasmI32Uint8FromInt, Arguments([expr]));
|
||||
|
||||
case NativeType.kInt16:
|
||||
return StaticInvocation(wasmI32Int16FromInt, Arguments([expr]));
|
||||
|
||||
case NativeType.kUint16:
|
||||
return StaticInvocation(wasmI32Uint16FromInt, Arguments([expr]));
|
||||
|
||||
case NativeType.kInt32:
|
||||
case NativeType.kUint32:
|
||||
return StaticInvocation(wasmI32FromInt, Arguments([expr]));
|
||||
|
||||
case NativeType.kInt64:
|
||||
case NativeType.kUint64:
|
||||
return StaticInvocation(wasmI64FromInt, Arguments([expr]));
|
||||
|
||||
case NativeType.kFloat:
|
||||
return StaticInvocation(wasmF32FromDouble, Arguments([expr]));
|
||||
|
||||
case NativeType.kDouble:
|
||||
return StaticInvocation(wasmF64FromDouble, Arguments([expr]));
|
||||
|
||||
case NativeType.kPointer:
|
||||
case NativeType.kStruct:
|
||||
return expr;
|
||||
|
||||
case NativeType.kBool:
|
||||
return StaticInvocation(wasmI32FromBool, Arguments([expr]));
|
||||
|
||||
case NativeType.kVoid:
|
||||
return null;
|
||||
|
||||
case NativeType.kHandle:
|
||||
case NativeType.kNativeDouble:
|
||||
case NativeType.kNativeFunction:
|
||||
case NativeType.kNativeInteger:
|
||||
case NativeType.kNativeType:
|
||||
case NativeType.kOpaque:
|
||||
throw '_dartValueToFfiValue: $abiTypeNativeType cannot be converted';
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a Wasm FFI value to the corresponding Dart value according to
|
||||
/// emscripten ABI.
|
||||
///
|
||||
/// For example, converts an `Bool` native type to Dart bool by checking the
|
||||
/// Wasm I32 value for the bool: 0 means `false`, non-0 means `true`.
|
||||
Expression _ffiValueToDartValue(DartType ffiType, Expression expr) {
|
||||
final InterfaceType ffiType_ = ffiType as InterfaceType;
|
||||
final NativeType nativeType = getType(ffiType_.classNode)!;
|
||||
|
||||
Expression instanceInvocation(Procedure converter, Expression receiver) =>
|
||||
InstanceInvocation(
|
||||
InstanceAccessKind.Instance,
|
||||
receiver,
|
||||
converter.name,
|
||||
Arguments([]),
|
||||
interfaceTarget: converter,
|
||||
functionType: converter.getterType as FunctionType,
|
||||
);
|
||||
|
||||
switch (nativeType) {
|
||||
case NativeType.kInt8:
|
||||
case NativeType.kInt16:
|
||||
case NativeType.kInt32:
|
||||
return instanceInvocation(wasmI32ToIntSigned, expr);
|
||||
|
||||
case NativeType.kUint8:
|
||||
case NativeType.kUint16:
|
||||
case NativeType.kUint32:
|
||||
return instanceInvocation(wasmI32ToIntUnsigned, expr);
|
||||
|
||||
case NativeType.kPointer:
|
||||
case NativeType.kVoid:
|
||||
case NativeType.kStruct:
|
||||
return expr;
|
||||
|
||||
case NativeType.kUint64:
|
||||
case NativeType.kInt64:
|
||||
return instanceInvocation(wasmI64ToInt, expr);
|
||||
|
||||
case NativeType.kFloat:
|
||||
return instanceInvocation(wasmF32ToDouble, expr);
|
||||
|
||||
case NativeType.kDouble:
|
||||
return instanceInvocation(wasmF64ToDouble, expr);
|
||||
|
||||
case NativeType.kBool:
|
||||
return instanceInvocation(wasmI32ToBool, expr);
|
||||
|
||||
case NativeType.kHandle:
|
||||
case NativeType.kNativeDouble:
|
||||
case NativeType.kNativeFunction:
|
||||
case NativeType.kNativeInteger:
|
||||
case NativeType.kNativeType:
|
||||
case NativeType.kOpaque:
|
||||
throw '_ffiValueToDartValue: $nativeType cannot be converted';
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an FFI type like `InterfaceType(Int8)` to the corresponding Wasm
|
||||
/// type (`InterfaceType(WasmI32)`) according to emscripten Wasm ABI.
|
||||
///
|
||||
/// Returns `null` for [Void]. Other types are converted to their
|
||||
/// [InterfaceType]s.
|
||||
DartType? _convertFfiTypeToWasmType(DartType ffiType) {
|
||||
if (ffiType is! InterfaceType) {
|
||||
throw 'Native type is not an interface type: $ffiType';
|
||||
}
|
||||
final Class nativeClass = ffiType.classNode;
|
||||
final NativeType nativeType_ = getType(nativeClass)!;
|
||||
|
||||
switch (nativeType_) {
|
||||
case NativeType.kInt8:
|
||||
case NativeType.kUint8:
|
||||
case NativeType.kInt16:
|
||||
case NativeType.kUint16:
|
||||
case NativeType.kInt32:
|
||||
case NativeType.kUint32:
|
||||
case NativeType.kBool:
|
||||
return InterfaceType(wasmI32Class, Nullability.nonNullable);
|
||||
|
||||
case NativeType.kInt64:
|
||||
case NativeType.kUint64:
|
||||
return InterfaceType(wasmI64Class, Nullability.nonNullable);
|
||||
|
||||
case NativeType.kFloat:
|
||||
return InterfaceType(wasmF32Class, Nullability.nonNullable);
|
||||
|
||||
case NativeType.kDouble:
|
||||
return InterfaceType(wasmF64Class, Nullability.nonNullable);
|
||||
|
||||
case NativeType.kPointer:
|
||||
case NativeType.kStruct:
|
||||
return ffiType;
|
||||
|
||||
case NativeType.kVoid:
|
||||
return null;
|
||||
|
||||
case NativeType.kHandle:
|
||||
case NativeType.kNativeDouble:
|
||||
case NativeType.kNativeFunction:
|
||||
case NativeType.kNativeInteger:
|
||||
case NativeType.kNativeType:
|
||||
case NativeType.kOpaque:
|
||||
throw '_convertFfiTypeToWasmType: $nativeType_ cannot be converted';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -431,17 +431,21 @@ class Intrinsifier {
|
|||
w.StorageType receiverType = translator.builtinTypes[cls]!;
|
||||
switch (receiverType) {
|
||||
case w.NumType.i32:
|
||||
assert(name == "toIntSigned" || name == "toIntUnsigned");
|
||||
codeGen.wrap(receiver, w.NumType.i32);
|
||||
switch (name) {
|
||||
case "toIntSigned":
|
||||
b.i64_extend_i32_s();
|
||||
break;
|
||||
return w.NumType.i64;
|
||||
case "toIntUnsigned":
|
||||
b.i64_extend_i32_u();
|
||||
break;
|
||||
}
|
||||
return w.NumType.i64;
|
||||
case "toBool":
|
||||
b.i32_const(0);
|
||||
b.i32_ne();
|
||||
return w.NumType.i32;
|
||||
default:
|
||||
throw 'Unknown i32 conversion to $receiverType';
|
||||
}
|
||||
case w.NumType.i64:
|
||||
assert(name == "toInt");
|
||||
codeGen.wrap(receiver, w.NumType.i64);
|
||||
|
@ -991,9 +995,40 @@ class Intrinsifier {
|
|||
w.StorageType targetType = translator.builtinTypes[cls]!;
|
||||
switch (targetType) {
|
||||
case w.NumType.i32:
|
||||
switch (name) {
|
||||
case "fromInt":
|
||||
codeGen.wrap(value, w.NumType.i64);
|
||||
b.i32_wrap_i64();
|
||||
return w.NumType.i32;
|
||||
case "int8FromInt":
|
||||
codeGen.wrap(value, w.NumType.i64);
|
||||
b.i32_wrap_i64();
|
||||
b.i32_extend8_s();
|
||||
return w.NumType.i32;
|
||||
case "uint8FromInt":
|
||||
codeGen.wrap(value, w.NumType.i64);
|
||||
b.i32_wrap_i64();
|
||||
b.i32_const(0xFF);
|
||||
b.i32_and();
|
||||
return w.NumType.i32;
|
||||
case "int16FromInt":
|
||||
codeGen.wrap(value, w.NumType.i64);
|
||||
b.i32_wrap_i64();
|
||||
b.i32_extend16_s();
|
||||
return w.NumType.i32;
|
||||
case "uint16FromInt":
|
||||
codeGen.wrap(value, w.NumType.i64);
|
||||
b.i32_wrap_i64();
|
||||
b.i32_const(0xFFFF);
|
||||
b.i32_and();
|
||||
return w.NumType.i32;
|
||||
case "fromBool":
|
||||
codeGen.wrap(value, w.NumType.i32);
|
||||
return w.NumType.i32;
|
||||
default:
|
||||
throw 'Unhandled WasmI32 factory: $name';
|
||||
}
|
||||
|
||||
case w.NumType.i64:
|
||||
codeGen.wrap(value, w.NumType.i64);
|
||||
return w.NumType.i64;
|
||||
|
|
|
@ -26,6 +26,7 @@ import 'package:vm/transformations/ffi/definitions.dart'
|
|||
import 'package:vm/transformations/ffi/use_sites.dart' as transformFfiUseSites
|
||||
show transformLibraries;
|
||||
|
||||
import 'package:dart2wasm/ffi_native_transformer.dart' as wasmFfiNativeTrans;
|
||||
import 'package:dart2wasm/transformers.dart' as wasmTrans;
|
||||
|
||||
class WasmTarget extends Target {
|
||||
|
@ -171,6 +172,8 @@ class WasmTarget extends Target {
|
|||
if (transitiveImportingDartFfi == null) {
|
||||
logger?.call("Skipped ffi transformation");
|
||||
} else {
|
||||
wasmFfiNativeTrans.transformLibraries(component, coreTypes, hierarchy,
|
||||
transitiveImportingDartFfi, diagnosticReporter, referenceFromIndex);
|
||||
transformFfiDefinitions.transformLibraries(
|
||||
component,
|
||||
coreTypes,
|
||||
|
|
|
@ -579,6 +579,8 @@ class Dart2WasmCompilerConfiguration extends CompilerConfiguration {
|
|||
'pkg/dart2wasm/bin/run_wasm.js',
|
||||
'--',
|
||||
artifact!.filename,
|
||||
...testFile.sharedObjects
|
||||
.map((obj) => '${_configuration.buildDirectory}/wasm/$obj.wasm'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ class FfiNativeTransformer extends FfiTransformer {
|
|||
super(index, coreTypes, hierarchy, diagnosticReporter,
|
||||
referenceFromIndex);
|
||||
|
||||
ConstantExpression? _tryGetFfiNativeAnnotation(Member node) {
|
||||
ConstantExpression? tryGetFfiNativeAnnotation(Member node) {
|
||||
for (final Expression annotation in node.annotations) {
|
||||
if (annotation is! ConstantExpression) {
|
||||
continue;
|
||||
|
@ -443,26 +443,13 @@ class FfiNativeTransformer extends FfiTransformer {
|
|||
List<Expression> argumentList, {
|
||||
required bool checkReceiverForNullptr,
|
||||
}) {
|
||||
if (!_verifySignatures(
|
||||
node, dartFunctionType, ffiFunctionType, annotationOffset)) {
|
||||
return node;
|
||||
}
|
||||
final wrappedDartFunctionType = checkFfiType(
|
||||
node, dartFunctionType, ffiFunctionType, isLeaf, annotationOffset);
|
||||
|
||||
// int Function(Pointer<Void>)
|
||||
final wrappedDartFunctionType =
|
||||
_wrapFunctionType(dartFunctionType, ffiFunctionType);
|
||||
|
||||
final nativeType = InterfaceType(
|
||||
nativeFunctionClass, Nullability.legacy, [ffiFunctionType]);
|
||||
try {
|
||||
ensureNativeTypeValid(nativeType, node);
|
||||
ensureNativeTypeToDartType(nativeType, wrappedDartFunctionType, node,
|
||||
allowHandle: true);
|
||||
ensureLeafCallDoesNotUseHandles(nativeType, isLeaf, node);
|
||||
} on FfiStaticTypeError {
|
||||
// It's OK to swallow the exception because the diagnostics issued will
|
||||
// cause compilation to fail. By continuing, we can report more
|
||||
// diagnostics before compilation ends.
|
||||
if (wrappedDartFunctionType == null) {
|
||||
// It's OK to continue because the diagnostics issued will cause
|
||||
// compilation to fail. By continuing, we can report more diagnostics
|
||||
// before compilation ends.
|
||||
return node;
|
||||
}
|
||||
|
||||
|
@ -609,7 +596,7 @@ class FfiNativeTransformer extends FfiTransformer {
|
|||
// Only transform functions that are external and have FfiNative annotation:
|
||||
// @FfiNative<Double Function(Double)>('Math_sqrt')
|
||||
// external double _square_root(double x);
|
||||
final ffiNativeAnnotation = _tryGetFfiNativeAnnotation(node);
|
||||
final ffiNativeAnnotation = tryGetFfiNativeAnnotation(node);
|
||||
if (ffiNativeAnnotation == null) {
|
||||
return node;
|
||||
}
|
||||
|
@ -639,4 +626,38 @@ class FfiNativeTransformer extends FfiTransformer {
|
|||
return _transformStaticFunction(node, ffiFunctionType, nativeFunctionName,
|
||||
isLeaf, ffiNativeAnnotation.fileOffset);
|
||||
}
|
||||
|
||||
/// Checks whether the FFI function type is valid and reports any errors.
|
||||
/// Returns the Dart function type for the FFI function if the type is valid.
|
||||
///
|
||||
/// For example, for FFI function type `Int8 Function(Double)`, this returns
|
||||
/// `int Function(double)`.
|
||||
FunctionType? checkFfiType(Procedure node, FunctionType dartFunctionType,
|
||||
FunctionType ffiFunctionType, bool isLeaf, int annotationOffset) {
|
||||
if (!_verifySignatures(
|
||||
node, dartFunctionType, ffiFunctionType, annotationOffset)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// int Function(Pointer<Void>)
|
||||
final wrappedDartFunctionType =
|
||||
_wrapFunctionType(dartFunctionType, ffiFunctionType);
|
||||
|
||||
final nativeType = InterfaceType(
|
||||
nativeFunctionClass, Nullability.legacy, [ffiFunctionType]);
|
||||
|
||||
try {
|
||||
ensureNativeTypeValid(nativeType, node);
|
||||
ensureNativeTypeToDartType(nativeType, wrappedDartFunctionType, node,
|
||||
allowHandle: true);
|
||||
ensureLeafCallDoesNotUseHandles(nativeType, isLeaf, node);
|
||||
} on FfiStaticTypeError {
|
||||
// It's OK to swallow the exception because the diagnostics issued will
|
||||
// cause compilation to fail. By continuing, we can report more
|
||||
// diagnostics before compilation ends.
|
||||
return null;
|
||||
}
|
||||
|
||||
return wrappedDartFunctionType;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,3 +158,10 @@ class _DuplicatedFieldInitializerError extends Error {
|
|||
|
||||
toString() => "Error: field '$_name' is already initialized.";
|
||||
}
|
||||
|
||||
@patch
|
||||
class StateError {
|
||||
static _throwNew(String msg) {
|
||||
throw new StateError(msg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,8 +97,14 @@ class WasmI16 extends _WasmInt {}
|
|||
@pragma("wasm:entry-point")
|
||||
class WasmI32 extends _WasmInt {
|
||||
external factory WasmI32.fromInt(int value);
|
||||
external factory WasmI32.int8FromInt(int value);
|
||||
external factory WasmI32.uint8FromInt(int value);
|
||||
external factory WasmI32.int16FromInt(int value);
|
||||
external factory WasmI32.uint16FromInt(int value);
|
||||
external factory WasmI32.fromBool(bool value);
|
||||
external int toIntSigned();
|
||||
external int toIntUnsigned();
|
||||
external bool toBool();
|
||||
}
|
||||
|
||||
/// The Wasm `i64` type.
|
||||
|
|
107
tests/web/wasm/ffi_native_test.dart
Normal file
107
tests/web/wasm/ffi_native_test.dart
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Copyright (c) 2022, 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.
|
||||
|
||||
// SharedObjects=ffi_native_test_module
|
||||
|
||||
import 'dart:ffi';
|
||||
import 'dart:nativewrappers';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
@FfiNative<Void Function()>("ffi.empty")
|
||||
external void empty();
|
||||
|
||||
@FfiNative<Int8 Function(Int8, Int8)>("ffi.addInt8")
|
||||
external int addInt8(int a, int b);
|
||||
|
||||
@FfiNative<Int8 Function(Uint8, Uint8)>("ffi.addUint8")
|
||||
external int addUint8(int a, int b);
|
||||
|
||||
@FfiNative<Int16 Function(Int16, Int16)>("ffi.addInt16")
|
||||
external int addInt16(int a, int b);
|
||||
|
||||
@FfiNative<Uint16 Function(Uint16, Uint16)>("ffi.addUint16")
|
||||
external int addUint16(int a, int b);
|
||||
|
||||
@FfiNative<Int32 Function(Int32, Int32)>("ffi.addInt32")
|
||||
external int addInt32(int a, int b);
|
||||
|
||||
@FfiNative<Uint32 Function(Uint32, Uint32)>("ffi.addUint32")
|
||||
external int addUint32(int a, int b);
|
||||
|
||||
@FfiNative<Int64 Function(Int64, Int64)>("ffi.addInt64")
|
||||
external int addInt64(int a, int b);
|
||||
|
||||
@FfiNative<Uint64 Function(Uint64, Uint64)>("ffi.addUint64")
|
||||
external int addUint64(int a, int b);
|
||||
|
||||
@FfiNative<Bool Function(Bool)>("ffi.negateBool")
|
||||
external bool negateBool(bool b);
|
||||
|
||||
@FfiNative<Bool Function(Int32)>("ffi.boolReturn")
|
||||
external bool boolReturn(int b);
|
||||
|
||||
@FfiNative<Void Function()>("ffi.toggleBool")
|
||||
external void toggleBool();
|
||||
|
||||
@FfiNative<Double Function(Double)>("ffi.sqrt")
|
||||
external double sqrt(double d);
|
||||
|
||||
class MyStruct extends Struct implements NativeFieldWrapperClass1 {
|
||||
@Double()
|
||||
external double x;
|
||||
|
||||
@Int16()
|
||||
external int y;
|
||||
}
|
||||
|
||||
@FfiNative<Pointer<MyStruct> Function()>("ffi.getStruct")
|
||||
external Pointer<MyStruct> getStruct();
|
||||
|
||||
@FfiNative<Void Function(Pointer<MyStruct>)>("ffi.clearStruct")
|
||||
external void clearStruct(Pointer<MyStruct> struct);
|
||||
|
||||
void main() {
|
||||
empty();
|
||||
|
||||
Expect.equals(addInt8(1, 2), 3);
|
||||
Expect.equals(addInt8(127, 10), -119);
|
||||
|
||||
Expect.equals(addUint8(1, 2), 3);
|
||||
Expect.equals(addUint8(255, 10), 9);
|
||||
|
||||
Expect.equals(addInt16(1, 2), 3);
|
||||
Expect.equals(addInt16(32767, 10), -32759);
|
||||
|
||||
Expect.equals(addUint16(1, 2), 3);
|
||||
Expect.equals(addUint16(65535, 10), 9);
|
||||
|
||||
Expect.equals(addInt32(1, 2), 3);
|
||||
Expect.equals(addInt32(2147483647, 10), -2147483639);
|
||||
|
||||
Expect.equals(addUint32(1, 2), 3);
|
||||
Expect.equals(addUint32(4294967295, 10), 9);
|
||||
|
||||
Expect.equals(addInt64(1, 2), 3);
|
||||
Expect.equals(addInt64(9223372036854775807, 10), -9223372036854775799);
|
||||
|
||||
Expect.equals(addUint64(1, 2), 3);
|
||||
Expect.equals(addUint64(9223372036854775807, 10), -9223372036854775799);
|
||||
|
||||
Expect.equals(negateBool(true), false);
|
||||
Expect.equals(negateBool(false), true);
|
||||
|
||||
Expect.equals(boolReturn(123), true);
|
||||
Expect.equals(boolReturn(456), false);
|
||||
Expect.equals(boolReturn(789), true);
|
||||
toggleBool();
|
||||
Expect.equals(boolReturn(789), false);
|
||||
|
||||
final struct_ = getStruct();
|
||||
Expect.equals(struct_.ref.x, 1.0);
|
||||
Expect.equals(struct_.ref.y, 2);
|
||||
clearStruct(struct_);
|
||||
Expect.equals(struct_.ref.x, 0.0);
|
||||
Expect.equals(struct_.ref.y, 0);
|
||||
}
|
90
tests/web/wasm/ffi_native_test_module.c
Normal file
90
tests/web/wasm/ffi_native_test_module.c
Normal file
|
@ -0,0 +1,90 @@
|
|||
// Copyright (c) 2022, 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.
|
||||
|
||||
#include <emscripten.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct MyStruct {
|
||||
double x;
|
||||
int16_t y;
|
||||
} s = { 1.0, 2 }, a[10];
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
struct MyStruct* getStruct() {
|
||||
return &s;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void clearStruct(struct MyStruct* s) {
|
||||
s->x = 0.0;
|
||||
s->y = 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void empty() {}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int8_t addInt8(int8_t a, int8_t b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
uint8_t addUint8(uint8_t a, uint8_t b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int16_t addInt16(int16_t a, int16_t b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
uint16_t addUint16(uint16_t a, uint16_t b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int32_t addInt32(int32_t a, int32_t b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
uint32_t addUint32(uint32_t a, uint32_t b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int64_t addInt64(int64_t a, int64_t b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
uint64_t addUint64(uint64_t a, uint64_t b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
bool negateBool(bool a) {
|
||||
return !a;
|
||||
}
|
||||
|
||||
static bool b = true;
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void toggleBool() {
|
||||
b = !b;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
bool boolReturn(int a) {
|
||||
if (a == 123) {
|
||||
return true;
|
||||
} else if (a == 456) {
|
||||
return false;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
|
@ -181,6 +181,7 @@
|
|||
"out/ReleaseX64/dart2wasm_asserts.snapshot",
|
||||
"out/ReleaseX64/dart2wasm_outline.dill",
|
||||
"out/ReleaseX64/dart2wasm_platform.dill",
|
||||
"out/ReleaseX64/wasm/",
|
||||
"pkg/",
|
||||
"runtime/tests/",
|
||||
"samples-dev/",
|
||||
|
@ -206,8 +207,7 @@
|
|||
"third_party/d8/",
|
||||
"third_party/pkg/",
|
||||
"third_party/requirejs/",
|
||||
"tools/",
|
||||
"xcodebuild/ReleaseX64/dart"
|
||||
"tools/"
|
||||
],
|
||||
"front-end": [
|
||||
".dart_tool/package_config.json",
|
||||
|
|
|
@ -7,6 +7,22 @@ import("../compile_platform.gni")
|
|||
|
||||
sdk_root = "../../sdk"
|
||||
|
||||
template("wasm_module") {
|
||||
action(target_name) {
|
||||
script = rebase_path("//build/gn_run_binary.py")
|
||||
args = [
|
||||
"compiled_action",
|
||||
rebase_path("//third_party/emsdk/upstream/emscripten/emcc"),
|
||||
"--no-entry",
|
||||
rebase_path("//tests/web/wasm/${invoker.module_name}.c"),
|
||||
"-o",
|
||||
rebase_path("$root_out_dir/wasm/${invoker.module_name}.wasm"),
|
||||
"-O",
|
||||
]
|
||||
outputs = [ "$root_out_dir/wasm/${invoker.module_name}.wasm" ]
|
||||
}
|
||||
}
|
||||
|
||||
aot_snapshot("dart2wasm_snapshot") {
|
||||
main_dart = "../../pkg/dart2wasm/bin/dart2wasm.dart"
|
||||
name = "dart2wasm"
|
||||
|
@ -36,3 +52,11 @@ compile_platform("compile_dart2wasm_platform") {
|
|||
"--nnbd-strong",
|
||||
]
|
||||
}
|
||||
|
||||
wasm_module("ffi_native_test_wasm_module") {
|
||||
module_name = "ffi_native_test_module"
|
||||
}
|
||||
|
||||
group("test_wasm_modules") {
|
||||
deps = [ ":ffi_native_test_wasm_module" ]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue