[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:
Ömer Sinan Ağacan 2022-09-23 09:41:22 +00:00 committed by Commit Bot
parent d43aa20afa
commit affc3392a0
12 changed files with 721 additions and 30 deletions

View file

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

View 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';
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View 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;
}
}

View file

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

View file

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