From affc3392a093fb6713e74995cc69947c58178cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Fri, 23 Sep 2022 09:41:22 +0000 Subject: [PATCH] [dart2wasm] Implement Wasm FfiNative support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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("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 Commit-Queue: Ömer Ağacan Reviewed-by: Ryan Macnak Reviewed-by: Aske Simon Christensen --- BUILD.gn | 1 + pkg/dart2wasm/lib/ffi_native_transformer.dart | 395 ++++++++++++++++++ pkg/dart2wasm/lib/intrinsics.dart | 49 ++- pkg/dart2wasm/lib/target.dart | 3 + .../lib/src/compiler_configuration.dart | 2 + pkg/vm/lib/transformations/ffi/native.dart | 63 ++- sdk/lib/_internal/wasm/lib/errors_patch.dart | 7 + sdk/lib/wasm/wasm_types.dart | 6 + tests/web/wasm/ffi_native_test.dart | 107 +++++ tests/web/wasm/ffi_native_test_module.c | 90 ++++ tools/bots/test_matrix.json | 4 +- utils/dart2wasm/BUILD.gn | 24 ++ 12 files changed, 721 insertions(+), 30 deletions(-) create mode 100644 pkg/dart2wasm/lib/ffi_native_transformer.dart create mode 100644 tests/web/wasm/ffi_native_test.dart create mode 100644 tests/web/wasm/ffi_native_test_module.c diff --git a/BUILD.gn b/BUILD.gn index 40bd3062436..1773913f298 100644 --- a/BUILD.gn +++ b/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", ] } diff --git a/pkg/dart2wasm/lib/ffi_native_transformer.dart b/pkg/dart2wasm/lib/ffi_native_transformer.dart new file mode 100644 index 00000000000..b2e02f49c2c --- /dev/null +++ b/pkg/dart2wasm/lib/ffi_native_transformer.dart @@ -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("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 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('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 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 = []; + + 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'; + } + } +} diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index bffa7428160..c09ad8de72a 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -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'; } - return w.NumType.i64; 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: - codeGen.wrap(value, w.NumType.i64); - b.i32_wrap_i64(); - return 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; diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart index 2c65c2d2cbb..e9d0627f4c8 100644 --- a/pkg/dart2wasm/lib/target.dart +++ b/pkg/dart2wasm/lib/target.dart @@ -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, diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart index ba098e5aacd..7780568bb45 100644 --- a/pkg/test_runner/lib/src/compiler_configuration.dart +++ b/pkg/test_runner/lib/src/compiler_configuration.dart @@ -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'), ]; } } diff --git a/pkg/vm/lib/transformations/ffi/native.dart b/pkg/vm/lib/transformations/ffi/native.dart index 6a3df4c2292..f06160879cb 100644 --- a/pkg/vm/lib/transformations/ffi/native.dart +++ b/pkg/vm/lib/transformations/ffi/native.dart @@ -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 argumentList, { required bool checkReceiverForNullptr, }) { - if (!_verifySignatures( - node, dartFunctionType, ffiFunctionType, annotationOffset)) { - return node; - } + final wrappedDartFunctionType = checkFfiType( + node, dartFunctionType, ffiFunctionType, isLeaf, annotationOffset); - // int Function(Pointer) - 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('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) + 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; + } } diff --git a/sdk/lib/_internal/wasm/lib/errors_patch.dart b/sdk/lib/_internal/wasm/lib/errors_patch.dart index f1542bd3ec1..10d875186fa 100644 --- a/sdk/lib/_internal/wasm/lib/errors_patch.dart +++ b/sdk/lib/_internal/wasm/lib/errors_patch.dart @@ -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); + } +} diff --git a/sdk/lib/wasm/wasm_types.dart b/sdk/lib/wasm/wasm_types.dart index b95e26d1185..142bc0753c1 100644 --- a/sdk/lib/wasm/wasm_types.dart +++ b/sdk/lib/wasm/wasm_types.dart @@ -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. diff --git a/tests/web/wasm/ffi_native_test.dart b/tests/web/wasm/ffi_native_test.dart new file mode 100644 index 00000000000..88c013d9238 --- /dev/null +++ b/tests/web/wasm/ffi_native_test.dart @@ -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("ffi.empty") +external void empty(); + +@FfiNative("ffi.addInt8") +external int addInt8(int a, int b); + +@FfiNative("ffi.addUint8") +external int addUint8(int a, int b); + +@FfiNative("ffi.addInt16") +external int addInt16(int a, int b); + +@FfiNative("ffi.addUint16") +external int addUint16(int a, int b); + +@FfiNative("ffi.addInt32") +external int addInt32(int a, int b); + +@FfiNative("ffi.addUint32") +external int addUint32(int a, int b); + +@FfiNative("ffi.addInt64") +external int addInt64(int a, int b); + +@FfiNative("ffi.addUint64") +external int addUint64(int a, int b); + +@FfiNative("ffi.negateBool") +external bool negateBool(bool b); + +@FfiNative("ffi.boolReturn") +external bool boolReturn(int b); + +@FfiNative("ffi.toggleBool") +external void toggleBool(); + +@FfiNative("ffi.sqrt") +external double sqrt(double d); + +class MyStruct extends Struct implements NativeFieldWrapperClass1 { + @Double() + external double x; + + @Int16() + external int y; +} + +@FfiNative Function()>("ffi.getStruct") +external Pointer getStruct(); + +@FfiNative)>("ffi.clearStruct") +external void clearStruct(Pointer 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); +} diff --git a/tests/web/wasm/ffi_native_test_module.c b/tests/web/wasm/ffi_native_test_module.c new file mode 100644 index 00000000000..e6964a0fd0b --- /dev/null +++ b/tests/web/wasm/ffi_native_test_module.c @@ -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 +#include +#include +#include + +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; + } +} diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json index 35b4365710b..411f2c17568 100644 --- a/tools/bots/test_matrix.json +++ b/tools/bots/test_matrix.json @@ -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", diff --git a/utils/dart2wasm/BUILD.gn b/utils/dart2wasm/BUILD.gn index 428922fc642..591b0263659 100644 --- a/utils/dart2wasm/BUILD.gn +++ b/utils/dart2wasm/BUILD.gn @@ -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" ] +}