blob: 05f7fe237657882a719871abc1cd79e33cbf9ab3 [file] [edit]
// 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';
}
}
}