| // 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/modular/transformations/ffi/abi.dart' show Abi; |
| import 'package:vm/modular/transformations/ffi/common.dart' show NativeType; |
| import 'package:vm/modular/transformations/ffi/native.dart' |
| show FfiNativeTransformer; |
| |
| import 'abi.dart' show kWasmAbiEnumIndex; |
| |
| /// Transform `@Native`-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: |
| /// |
| /// @Native<Int8 Function(Int8, Int8)>(symbol: "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', |
| 'dart:isolate', |
| ]); |
| final transformer = WasmFfiNativeTransformer( |
| index, coreTypes, hierarchy, diagnosticReporter, referenceFromIndex); |
| libraries.forEach(transformer.visitLibrary); |
| } |
| |
| class WasmFfiNativeTransformer extends FfiNativeTransformer { |
| final Class wasmI32Class; |
| final Class wasmI64Class; |
| final Class wasmF32Class; |
| final Class wasmF64Class; |
| final Class wasmEqRefClass; |
| 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; |
| final Procedure pointerFromAddressI32; |
| final Field pointerAddressField; |
| |
| WasmFfiNativeTransformer(super.index, super.coreTypes, super.hierarchy, |
| super.diagnosticReporter, super.referenceFromIndex) |
| : 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'), |
| 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'), |
| pointerFromAddressI32 = |
| index.getProcedure('dart:ffi', 'Pointer', '_fromAddressI32'), |
| pointerAddressField = index.getField('dart:ffi', 'Pointer', '_address'); |
| |
| @override |
| visitProcedure(Procedure node) { |
| // Only transform functions that are external and have Native annotation: |
| // @Native<Double Function(Double)>(symbol: 'Math_sqrt') |
| // external double _square_root(double x); |
| final nativeAnnotation = tryGetNativeAnnotationOrWarnOnDuplicates(node); |
| if (nativeAnnotation == null) { |
| return node; |
| } |
| final annotationOffset = nativeAnnotation.fileOffset; |
| final ffiConstant = nativeAnnotation.constant as InstanceConstant; |
| final ffiFunctionType = ffiConstant.typeArguments.first as FunctionType; |
| final isLeafField = |
| ffiConstant.fieldValues[nativeIsLeafField.fieldReference]; |
| final isLeaf = (isLeafField as BoolConstant).value; |
| final assetField = ffiConstant.fieldValues[nativeAssetField.fieldReference]; |
| final assetName = (assetField is StringConstant) |
| ? assetField |
| : (currentAsset ?? StringConstant("ffi")); |
| final nameField = ffiConstant.fieldValues[nativeSymbolField.fieldReference]; |
| final functionName = (nameField is StringConstant) |
| ? nameField |
| : StringConstant(node.name.text); |
| final nativeFunctionName = |
| StringConstant("${assetName.value}.${functionName.value}"); |
| |
| // Original function should be external and without body |
| assert(node.isExternal == true); |
| assert(node.function.body == null); |
| |
| final dartFunctionType = |
| node.function.computeThisFunctionType(Nullability.nonNullable); |
| |
| final wrappedDartFunctionType = checkFfiType( |
| node, dartFunctionType, ffiFunctionType, isLeaf, annotationOffset); |
| |
| 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, |
| isSynthesized: true, |
| )); |
| } |
| } |
| |
| final retWasmType = _convertFfiTypeToWasmType(ffiFunctionType.returnType); |
| final retWasmType_ = retWasmType ?? VoidType(); |
| |
| final wasmImportProcedure = Procedure( |
| wasmImportName, |
| ProcedureKind.Method, |
| FunctionNode(null, |
| positionalParameters: wasmImportProcedureArgs, |
| returnType: retWasmType_), |
| fileUri: node.fileUri, |
| isExternal: true, |
| isStatic: true, |
| isSynthetic: true) |
| ..fileOffset = node.fileOffset; |
| 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(nativeAnnotation); |
| |
| // 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. |
| /// |
| /// Unboxes `Pointer` values as `i32`. |
| /// |
| /// Returns `null` for [Void] values. |
| Expression? _dartValueToFfiValue(DartType ffiType, Expression expr) { |
| final InterfaceType abiType_ = |
| _getFixedWidthIntegerFromAbiSpecificInteger(ffiType as InterfaceType); |
| final NativeType abiTypeNativeType = getType(abiType_.classNode)!; |
| |
| return switch (abiTypeNativeType) { |
| NativeType.kInt8 => |
| StaticInvocation(wasmI32Int8FromInt, Arguments([expr])), |
| NativeType.kUint8 => |
| StaticInvocation(wasmI32Uint8FromInt, Arguments([expr])), |
| NativeType.kInt16 => |
| StaticInvocation(wasmI32Int16FromInt, Arguments([expr])), |
| NativeType.kUint16 => |
| StaticInvocation(wasmI32Uint16FromInt, Arguments([expr])), |
| NativeType.kInt32 || |
| NativeType.kUint32 => |
| StaticInvocation(wasmI32FromInt, Arguments([expr])), |
| NativeType.kInt64 || |
| NativeType.kUint64 => |
| StaticInvocation(wasmI64FromInt, Arguments([expr])), |
| NativeType.kFloat => |
| StaticInvocation(wasmF32FromDouble, Arguments([expr])), |
| NativeType.kDouble => |
| StaticInvocation(wasmF64FromDouble, Arguments([expr])), |
| NativeType.kPointer => InstanceGet( |
| InstanceAccessKind.Instance, |
| expr, |
| pointerAddressField.name, |
| interfaceTarget: pointerAddressField, |
| resultType: InterfaceType(wasmI32Class, Nullability.nonNullable), |
| ), |
| NativeType.kBool => StaticInvocation(wasmI32FromBool, Arguments([expr])), |
| NativeType.kVoid => null, |
| _ => 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_ = |
| _getFixedWidthIntegerFromAbiSpecificInteger(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: |
| assert(ffiType_.classNode == pointerClass); |
| assert(ffiType_.typeArguments.length == 1); |
| return StaticInvocation(pointerFromAddressI32, |
| Arguments([expr], types: ffiType_.typeArguments)); |
| |
| case NativeType.kVoid: |
| 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: |
| case NativeType.kStruct: |
| throw '_ffiValueToDartValue: $nativeType cannot be converted'; |
| } |
| } |
| |
| InterfaceType _getFixedWidthIntegerFromAbiSpecificInteger( |
| InterfaceType ffiType) { |
| final MapConstant? abiIntegerMapping = |
| getAbiSpecificIntegerMappingAnnotation(ffiType.classNode); |
| if (abiIntegerMapping == null) { |
| // This isn't an ABI specific integer. Just return the type itself |
| return ffiType; |
| } |
| final Abi wasmAbi = Abi.values[kWasmAbiEnumIndex]; |
| final entry = abiIntegerMapping.entries |
| .firstWhere((e) => constantAbis[e.key] == wasmAbi); |
| return (entry.value as InstanceConstant) |
| .classNode |
| .getThisType(coreTypes, Nullability.nonNullable); |
| } |
| |
| /// 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'; |
| } |
| ffiType = _getFixedWidthIntegerFromAbiSpecificInteger(ffiType); |
| final NativeType nativeType_ = getType(ffiType.classNode)!; |
| |
| switch (nativeType_) { |
| case NativeType.kInt8: |
| case NativeType.kUint8: |
| case NativeType.kInt16: |
| case NativeType.kUint16: |
| case NativeType.kInt32: |
| case NativeType.kUint32: |
| case NativeType.kBool: |
| case NativeType.kPointer: |
| 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.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'; |
| } |
| } |
| } |