diff --git a/pkg/vm/lib/transformations/ffi/common.dart b/pkg/vm/lib/transformations/ffi/common.dart index 3e61a06013d..d805289e226 100644 --- a/pkg/vm/lib/transformations/ffi/common.dart +++ b/pkg/vm/lib/transformations/ffi/common.dart @@ -230,7 +230,7 @@ class FfiTransformer extends Transformer { final Procedure abiSpecificIntegerArrayElemAt; final Procedure abiSpecificIntegerArraySetElemAt; final Procedure asFunctionMethod; - final Procedure asFunctionInternal; + final Procedure ffiCallMethod; final Procedure sizeOfMethod; final Procedure lookupFunctionMethod; final Procedure fromFunctionMethod; @@ -283,6 +283,8 @@ class FfiTransformer extends Transformer { final Field nativeCallablePointerField; final Procedure nativeAddressOf; final Procedure nativePrivateAddressOf; + final Class ffiCallClass; + final Field ffiCallIsLeafField; late final InterfaceType nativeFieldWrapperClass1Type; late final InterfaceType voidType; @@ -463,8 +465,7 @@ class FfiTransformer extends Transformer { index.getProcedure('dart:ffi', 'AbiSpecificIntegerArray', '[]='), asFunctionMethod = index.getProcedure( 'dart:ffi', 'NativeFunctionPointer', 'asFunction'), - asFunctionInternal = - index.getTopLevelProcedure('dart:ffi', '_asFunctionInternal'), + ffiCallMethod = index.getTopLevelProcedure('dart:ffi', '_ffiCall'), sizeOfMethod = index.getTopLevelProcedure('dart:ffi', 'sizeOf'), lookupFunctionMethod = index.getProcedure( 'dart:ffi', 'DynamicLibraryExtension', 'lookupFunction'), @@ -571,7 +572,9 @@ class FfiTransformer extends Transformer { nativeAddressOf = index.getMember('dart:ffi', 'Native', 'addressOf') as Procedure, nativePrivateAddressOf = - index.getMember('dart:ffi', 'Native', '_addressOf') as Procedure { + index.getMember('dart:ffi', 'Native', '_addressOf') as Procedure, + ffiCallClass = index.getClass('dart:ffi', '_FfiCall'), + ffiCallIsLeafField = index.getField('dart:ffi', '_FfiCall', 'isLeaf') { nativeFieldWrapperClass1Type = nativeFieldWrapperClass1Class.getThisType( coreTypes, Nullability.nonNullable); voidType = nativeTypesClasses[NativeType.kVoid]! @@ -1199,37 +1202,6 @@ class FfiTransformer extends Transformer { ..fileOffset = nestedExpression.fileOffset; } - /// Creates an invocation to asFunctionInternal. - /// - /// Adds a native effect invoking a compound constructors if this is used - /// as return type. - Expression buildAsFunctionInternal({ - required Expression functionPointer, - required DartType nativeSignature, - required DartType dartSignature, - required bool isLeaf, - required int fileOffset, - }) { - final asFunctionInternalInvocation = StaticInvocation( - asFunctionInternal, - Arguments([ - functionPointer, - BoolLiteral(isLeaf), - ], types: [ - dartSignature, - nativeSignature, - ])) - ..fileOffset = fileOffset; - - final possibleCompoundReturn = findCompoundReturnType(dartSignature); - if (possibleCompoundReturn != null) { - return invokeCompoundConstructor( - asFunctionInternalInvocation, possibleCompoundReturn); - } - - return asFunctionInternalInvocation; - } - /// Returns the compound [Class] if a compound is returned, otherwise `null`. Class? findCompoundReturnType(DartType dartSignature) { if (dartSignature is! FunctionType) { diff --git a/pkg/vm/lib/transformations/ffi/use_sites.dart b/pkg/vm/lib/transformations/ffi/use_sites.dart index 3c9a18b5ebf..016c76d17cb 100644 --- a/pkg/vm/lib/transformations/ffi/use_sites.dart +++ b/pkg/vm/lib/transformations/ffi/use_sites.dart @@ -113,9 +113,14 @@ mixin _FfiUseSiteTransformer on FfiTransformer { // callback. int callbackCount = 0; + // Used to create private top-level trampoline methods with unique names + // for each call. + int callCount = 0; + @override TreeNode visitLibrary(Library node) { callbackCount = 0; + callCount = 0; return super.visitLibrary(node); } @@ -349,10 +354,12 @@ mixin _FfiUseSiteTransformer on FfiTransformer { ); final DartType nativeSignature = nativeType.typeArguments[0]; - return buildAsFunctionInternal( + return _replaceAsFunction( functionPointer: node.arguments.positional[0], + pointerType: InterfaceType( + pointerClass, Nullability.nonNullable, [nativeType]), nativeSignature: nativeSignature, - dartSignature: dartType, + dartSignature: dartType as FunctionType, isLeaf: isLeaf, fileOffset: node.fileOffset, ); @@ -428,6 +435,84 @@ mixin _FfiUseSiteTransformer on FfiTransformer { return node; } + /// Create Dart function which calls native code. + /// + /// Adds a native effect invoking a compound constructors if this is used + /// as return type. + Expression _replaceAsFunction({ + required Expression functionPointer, + required DartType pointerType, + required DartType nativeSignature, + required FunctionType dartSignature, + required bool isLeaf, + required int fileOffset, + }) { + assert(dartSignature.namedParameters.isEmpty); + final functionPointerVarName = '#ffiTarget$callCount'; + final closureName = '#ffiClosure$callCount'; + ++callCount; + + final pointerVar = VariableDeclaration(functionPointerVarName, + initializer: functionPointer, type: pointerType, isSynthesized: true); + + final positionalParameters = [ + for (int i = 0; i < dartSignature.positionalParameters.length; ++i) + VariableDeclaration( + 'arg${i + 1}', + type: dartSignature.positionalParameters[i], + ) + ]; + + final closure = FunctionDeclaration( + VariableDeclaration(closureName, + type: dartSignature, isSynthesized: true) + ..addAnnotation(ConstantExpression( + InstanceConstant(coreTypes.pragmaClass.reference, [], { + coreTypes.pragmaName.fieldReference: + StringConstant('vm:ffi:call-closure'), + coreTypes.pragmaOptions.fieldReference: InstanceConstant( + ffiCallClass.reference, + [nativeSignature], + { + ffiCallIsLeafField.fieldReference: BoolConstant(isLeaf), + }, + ), + }))), + FunctionNode( + Block([ + for (final param in positionalParameters) + ExpressionStatement(StaticInvocation( + nativeEffectMethod, Arguments([VariableGet(param)]))), + ReturnStatement(StaticInvocation( + ffiCallMethod, + Arguments([ + VariableGet(pointerVar), + ], types: [ + dartSignature.returnType, + ])) + ..fileOffset = fileOffset), + ]), + positionalParameters: positionalParameters, + requiredParameterCount: dartSignature.requiredParameterCount, + returnType: dartSignature.returnType) + ..fileOffset = fileOffset) + ..fileOffset = fileOffset; + + final result = BlockExpression( + Block([ + pointerVar, + closure, + ]), + VariableGet(closure.variable)); + + final possibleCompoundReturn = findCompoundReturnType(dartSignature); + if (possibleCompoundReturn != null) { + return invokeCompoundConstructor(result, possibleCompoundReturn); + } + + return result; + } + Expression invokeCompoundConstructors( Expression nestedExpression, List compoundClasses) => compoundClasses @@ -462,10 +547,6 @@ mixin _FfiUseSiteTransformer on FfiTransformer { // 'lookupFunction' are constants, so by inlining the call to 'asFunction' at // the call-site, we ensure that there are no generic calls to 'asFunction'. Expression _replaceLookupFunction(StaticInvocation node) { - // The generated code looks like: - // - // _asFunctionInternal(lookup>(symbolName), - // isLeaf) final DartType nativeSignature = node.arguments.types[0]; final DartType dartSignature = node.arguments.types[1]; @@ -478,21 +559,19 @@ mixin _FfiUseSiteTransformer on FfiTransformer { final FunctionType lookupFunctionType = libraryLookupMethod.getterType as FunctionType; - final Expression lookupResult = InstanceInvocation( - InstanceAccessKind.Instance, - node.arguments.positional[0], - libraryLookupMethod.name, - lookupArgs, + final lookupResult = InstanceInvocation(InstanceAccessKind.Instance, + node.arguments.positional[0], libraryLookupMethod.name, lookupArgs, interfaceTarget: libraryLookupMethod, functionType: FunctionTypeInstantiator.instantiate( lookupFunctionType, lookupTypeArgs)); final isLeaf = getIsLeafBoolean(node) ?? false; - return buildAsFunctionInternal( + return _replaceAsFunction( functionPointer: lookupResult, + pointerType: lookupResult.functionType.returnType, nativeSignature: nativeSignature, - dartSignature: dartSignature, + dartSignature: dartSignature as FunctionType, isLeaf: isLeaf, fileOffset: node.fileOffset, ); diff --git a/pkg/vm/testcases/transformations/ffi/as_function.dart b/pkg/vm/testcases/transformations/ffi/as_function.dart new file mode 100644 index 00000000000..8bf0dc0d49b --- /dev/null +++ b/pkg/vm/testcases/transformations/ffi/as_function.dart @@ -0,0 +1,37 @@ +// Copyright (c) 2023, 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. + +// Tests for NativeFunctionPointer.asFunction transformation. + +import 'dart:ffi'; + +testVoidNoArg() { + final pointer = + Pointer>.fromAddress(0xdeadbeef); + final function = pointer.asFunction(); + function(); +} + +testIntInt() { + final pointer = + Pointer>.fromAddress(0xdeadbeef); + final function = pointer.asFunction(); + return function(42); +} + +testLeaf5Args() { + final pointer = Pointer< + NativeFunction< + Int32 Function( + Int32, Int32, Int32, Int32, Int32)>>.fromAddress(0xdeadbeef); + final function = + pointer.asFunction(isLeaf: true); + return function(1, 2, 3, 4, 5); +} + +void main() { + testVoidNoArg(); + testIntInt(); + testLeaf5Args(); +} diff --git a/pkg/vm/testcases/transformations/ffi/as_function.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/as_function.dart.aot.expect new file mode 100644 index 00000000000..233545a76b8 --- /dev/null +++ b/pkg/vm/testcases/transformations/ffi/as_function.dart.aot.expect @@ -0,0 +1,63 @@ +library #lib; +import self as self; +import "dart:ffi" as ffi; +import "dart:core" as core; +import "dart:_internal" as _in; + +import "dart:ffi"; + +static method testVoidNoArg() → dynamic { + final ffi::Pointer> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress>(3735928559); + final () → void function = block { + [@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer> #ffiTarget0 = pointer; + @#C4 + function #ffiClosure0() → void { + return ffi::_ffiCall(#ffiTarget0); + } + } =>#ffiClosure0; + function(){() → void}; +} +[@vm.unboxing-info.metadata=()->i]static method testIntInt() → dynamic { + final ffi::Pointer> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress>(3735928559); + final (core::int) → core::int function = block { + [@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer> #ffiTarget1 = pointer; + @#C6 + function #ffiClosure1(core::int arg1) → core::int { + _in::_nativeEffect(arg1); + return ffi::_ffiCall(#ffiTarget1); + } + } =>#ffiClosure1; + return function(42){(core::int) → core::int}; +} +[@vm.unboxing-info.metadata=()->i]static method testLeaf5Args() → dynamic { + final ffi::Pointer> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress>(3735928559); + final (core::int, core::int, core::int, core::int, core::int) → core::int function = block { + [@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer> #ffiTarget2 = pointer; + @#C9 + function #ffiClosure2(core::int arg1, core::int arg2, core::int arg3, core::int arg4, core::int arg5) → core::int { + _in::_nativeEffect(arg1); + _in::_nativeEffect(arg2); + _in::_nativeEffect(arg3); + _in::_nativeEffect(arg4); + _in::_nativeEffect(arg5); + return ffi::_ffiCall(#ffiTarget2); + } + } =>#ffiClosure2; + return function(1, 2, 3, 4, 5){(core::int, core::int, core::int, core::int, core::int) → core::int}; +} +static method main() → void { + self::testVoidNoArg(); + self::testIntInt(); + self::testLeaf5Args(); +} +constants { + #C1 = "vm:ffi:call-closure" + #C2 = false + #C3 = ffi::_FfiCall<() → ffi::Void> {isLeaf:#C2} + #C4 = core::pragma {name:#C1, options:#C3} + #C5 = ffi::_FfiCall<(ffi::Int64) → ffi::Int32> {isLeaf:#C2} + #C6 = core::pragma {name:#C1, options:#C5} + #C7 = true + #C8 = ffi::_FfiCall<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32> {isLeaf:#C7} + #C9 = core::pragma {name:#C1, options:#C8} +} diff --git a/pkg/vm/testcases/transformations/ffi/as_function.dart.expect b/pkg/vm/testcases/transformations/ffi/as_function.dart.expect new file mode 100644 index 00000000000..4bd77d2d8fa --- /dev/null +++ b/pkg/vm/testcases/transformations/ffi/as_function.dart.expect @@ -0,0 +1,63 @@ +library #lib; +import self as self; +import "dart:ffi" as ffi; +import "dart:core" as core; +import "dart:_internal" as _in; + +import "dart:ffi"; + +static method testVoidNoArg() → dynamic { + final ffi::Pointer> pointer = ffi::Pointer::fromAddress>(3735928559); + final () → void function = block { + synthesized ffi::Pointer> #ffiTarget0 = pointer; + @#C4 + function #ffiClosure0() → void { + return ffi::_ffiCall(#ffiTarget0); + } + } =>#ffiClosure0; + function(){() → void}; +} +static method testIntInt() → dynamic { + final ffi::Pointer> pointer = ffi::Pointer::fromAddress>(3735928559); + final (core::int) → core::int function = block { + synthesized ffi::Pointer> #ffiTarget1 = pointer; + @#C6 + function #ffiClosure1(core::int arg1) → core::int { + _in::_nativeEffect(arg1); + return ffi::_ffiCall(#ffiTarget1); + } + } =>#ffiClosure1; + return function(42){(core::int) → core::int}; +} +static method testLeaf5Args() → dynamic { + final ffi::Pointer> pointer = ffi::Pointer::fromAddress>(3735928559); + final (core::int, core::int, core::int, core::int, core::int) → core::int function = block { + synthesized ffi::Pointer> #ffiTarget2 = pointer; + @#C9 + function #ffiClosure2(core::int arg1, core::int arg2, core::int arg3, core::int arg4, core::int arg5) → core::int { + _in::_nativeEffect(arg1); + _in::_nativeEffect(arg2); + _in::_nativeEffect(arg3); + _in::_nativeEffect(arg4); + _in::_nativeEffect(arg5); + return ffi::_ffiCall(#ffiTarget2); + } + } =>#ffiClosure2; + return function(1, 2, 3, 4, 5){(core::int, core::int, core::int, core::int, core::int) → core::int}; +} +static method main() → void { + self::testVoidNoArg(); + self::testIntInt(); + self::testLeaf5Args(); +} +constants { + #C1 = "vm:ffi:call-closure" + #C2 = false + #C3 = ffi::_FfiCall<() → ffi::Void> {isLeaf:#C2} + #C4 = core::pragma {name:#C1, options:#C3} + #C5 = ffi::_FfiCall<(ffi::Int64) → ffi::Int32> {isLeaf:#C2} + #C6 = core::pragma {name:#C1, options:#C5} + #C7 = true + #C8 = ffi::_FfiCall<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32> {isLeaf:#C7} + #C9 = core::pragma {name:#C1, options:#C8} +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/ffi_struct_constructors.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/ffi_struct_constructors.dart.expect index f6c28c47cb4..2e9b3d2fdbe 100644 --- a/pkg/vm/testcases/transformations/type_flow/transformer/ffi_struct_constructors.dart.expect +++ b/pkg/vm/testcases/transformations/type_flow/transformer/ffi_struct_constructors.dart.expect @@ -67,7 +67,13 @@ static method testLookupFunctionReturn() → void { final ffi::DynamicLibrary dylib = [@vm.inferred-type.metadata=dart.ffi::DynamicLibrary] ffi::DynamicLibrary::executable(); final () → self::Struct1 function1 = block { _in::_nativeEffect(new self::Struct1::#fromTypedDataBase([@vm.inferred-type.metadata=dart.typed_data::_Uint8List] typ::Uint8List::•(#C18))); - } =>ffi::_asFunctionInternal<() → self::Struct1, () → self::Struct1>([@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup] [@vm.inferred-type.metadata=dart.ffi::Pointer (skip check)] dylib.{ffi::DynamicLibrary::lookup}>("function1"){(core::String) → ffi::Pointer>}, false); + } => block { + [@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer> #ffiTarget0 = [@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup] [@vm.inferred-type.metadata=dart.ffi::Pointer (skip check)] dylib.{ffi::DynamicLibrary::lookup}>("function1"){(core::String) → ffi::Pointer>}; + @#C22 + function #ffiClosure0() → self::Struct1 { + return ffi::_ffiCall(#ffiTarget0); + } + } =>#ffiClosure0; final self::Struct1 struct1 = function1(){() → self::Struct1}; core::print(struct1); } @@ -75,7 +81,13 @@ static method testAsFunctionReturn() → void { final ffi::Pointer> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress>(3735928559); final () → self::Struct2 function2 = block { _in::_nativeEffect(new self::Struct2::#fromTypedDataBase([@vm.inferred-type.metadata=dart.typed_data::_Uint8List] typ::Uint8List::•(#C18))); - } =>ffi::_asFunctionInternal<() → self::Struct2, () → self::Struct2>(pointer, false); + } => block { + [@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer> #ffiTarget1 = pointer; + @#C24 + function #ffiClosure1() → self::Struct2 { + return ffi::_ffiCall(#ffiTarget1); + } + } =>#ffiClosure1; final self::Struct2 struct2 = function2(){() → self::Struct2}; core::print(struct2); } @@ -90,12 +102,26 @@ static method testFromFunctionArgument() → void { } static method testLookupFunctionArgument() → void { final ffi::DynamicLibrary dylib = [@vm.inferred-type.metadata=dart.ffi::DynamicLibrary] ffi::DynamicLibrary::executable(); - final (self::Struct5) → void function5 = [@vm.inferred-type.metadata=dart.core::_Closure] ffi::_asFunctionInternal<(self::Struct5) → void, (self::Struct5) → ffi::Void>([@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup] [@vm.inferred-type.metadata=dart.ffi::Pointer (skip check)] dylib.{ffi::DynamicLibrary::lookup}>("function5"){(core::String) → ffi::Pointer>}, false); + final (self::Struct5) → void function5 = block { + [@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer> #ffiTarget2 = [@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup] [@vm.inferred-type.metadata=dart.ffi::Pointer (skip check)] dylib.{ffi::DynamicLibrary::lookup}>("function5"){(core::String) → ffi::Pointer>}; + @#C26 + function #ffiClosure2(self::Struct5 arg1) → void { + _in::_nativeEffect(arg1); + return ffi::_ffiCall(#ffiTarget2); + } + } =>#ffiClosure2; core::print(function5); } static method testAsFunctionArgument() → void { final ffi::Pointer> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress>(3735928559); - final (self::Struct6) → void function6 = [@vm.inferred-type.metadata=dart.core::_Closure] ffi::_asFunctionInternal<(self::Struct6) → void, (self::Struct6) → ffi::Void>(pointer, false); + final (self::Struct6) → void function6 = block { + [@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer> #ffiTarget3 = pointer; + @#C28 + function #ffiClosure3(self::Struct6 arg1) → void { + _in::_nativeEffect(arg1); + return ffi::_ffiCall(#ffiTarget3); + } + } =>#ffiClosure3; core::print(function6); } static method returnStruct7() → self::Struct7 { @@ -135,4 +161,14 @@ constants { #C16 = static-tearoff self::useStruct3 #C17 = static-tearoff self::returnStruct7 #C18 = 1 + #C19 = "vm:ffi:call-closure" + #C20 = false + #C21 = ffi::_FfiCall<() → self::Struct1> {isLeaf:#C20} + #C22 = core::pragma {name:#C19, options:#C21} + #C23 = ffi::_FfiCall<() → self::Struct2> {isLeaf:#C20} + #C24 = core::pragma {name:#C19, options:#C23} + #C25 = ffi::_FfiCall<(self::Struct5) → ffi::Void> {isLeaf:#C20} + #C26 = core::pragma {name:#C19, options:#C25} + #C27 = ffi::_FfiCall<(self::Struct6) → ffi::Void> {isLeaf:#C20} + #C28 = core::pragma {name:#C19, options:#C27} } diff --git a/runtime/docs/compiler/ffi_pragmas.md b/runtime/docs/compiler/ffi_pragmas.md index 545e8edd76a..6240f558e8d 100644 --- a/runtime/docs/compiler/ffi_pragmas.md +++ b/runtime/docs/compiler/ffi_pragmas.md @@ -45,3 +45,11 @@ Related files: * [runtime/vm/kernel_loader.cc](../../../runtime/vm/kernel_loader.cc) * [runtime/vm/object.cc](../../../runtime/vm/object.cc) +## FFI Calls + +This pragma is used to mark Dart closures which perform FFI calls: + +``` + @pragma('vm:ffi:call-closure', _FfiCall(isLeaf: false)) + int #ffiCall0(int arg1) => _ffiCall(target); +``` diff --git a/runtime/docs/pragmas.md b/runtime/docs/pragmas.md index 120db16fa13..4e274b4dabf 100644 --- a/runtime/docs/pragmas.md +++ b/runtime/docs/pragmas.md @@ -47,6 +47,7 @@ These pragma's are only used on AST nodes synthesized by us, so users defining t | Pragma | Meaning | | --- | --- | +| `vm:ffi:call-closure`| [Closure performing FFI calls](compiler/ffi_pragmas.md) | | `vm:ffi:native-assets` | [Passing a native assets mapping to the VM](compiler/ffi_pragmas.md) | | `vm:ffi:native`| [Passing a native arguments to the VM](compiler/ffi_pragmas.md) | diff --git a/runtime/lib/ffi.cc b/runtime/lib/ffi.cc index 799dd7882ad..74640319da4 100644 --- a/runtime/lib/ffi.cc +++ b/runtime/lib/ffi.cc @@ -28,11 +28,6 @@ namespace dart { -// Static invocations to this method are translated directly in streaming FGB. -DEFINE_NATIVE_ENTRY(Ffi_asFunctionInternal, 2, 2) { - UNREACHABLE(); -} - DEFINE_NATIVE_ENTRY(Ffi_createNativeCallableListener, 1, 2) { const auto& send_function = Function::CheckedHandle(zone, arguments->NativeArg0()); diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h index b9e42cefdf0..11be9beb2a0 100644 --- a/runtime/vm/bootstrap_natives.h +++ b/runtime/vm/bootstrap_natives.h @@ -321,7 +321,6 @@ namespace dart { V(VMService_DecodeAssets, 1) \ V(VMService_AddUserTagsToStreamableSampleList, 1) \ V(VMService_RemoveUserTagsFromStreamableSampleList, 1) \ - V(Ffi_asFunctionInternal, 2) \ V(Ffi_createNativeCallableListener, 2) \ V(Ffi_createNativeCallableIsolateLocal, 3) \ V(Ffi_deleteNativeCallable, 1) \ diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc index 59c2d6835ef..5e57d1cf199 100644 --- a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc +++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc @@ -1025,65 +1025,6 @@ Fragment BaseFlowGraphBuilder::Box(Representation from) { return Fragment(box); } -Fragment BaseFlowGraphBuilder::BuildFfiAsFunctionInternalCall( - const TypeArguments& signatures, - bool is_leaf) { - ASSERT(signatures.Length() == 2); - const auto& sig0 = AbstractType::Handle(signatures.TypeAt(0)); - const auto& sig1 = AbstractType::Handle(signatures.TypeAt(1)); - - if (!signatures.IsInstantiated() || !sig0.IsFunctionType() || - !sig1.IsFunctionType()) { - const auto& msg = String::Handle(String::NewFormatted( - "Invalid type arguments passed to dart:ffi _asFunctionInternal: %s", - String::Handle(signatures.UserVisibleName()).ToCString())); - const auto& language_error = - Error::Handle(LanguageError::New(msg, Report::kError, Heap::kOld)); - Report::LongJump(language_error); - } - - const auto& dart_type = FunctionType::Cast(sig0); - const auto& native_type = FunctionType::Cast(sig1); - - // AbiSpecificTypes can have an incomplete mapping. - const char* error = nullptr; - compiler::ffi::NativeFunctionTypeFromFunctionType(zone_, native_type, &error); - if (error != nullptr) { - const auto& language_error = Error::Handle( - LanguageError::New(String::Handle(String::New(error, Heap::kOld)), - Report::kError, Heap::kOld)); - Report::LongJump(language_error); - } - - const auto& name = - String::Handle(parsed_function_->function().UserVisibleName()); - const Function& target = Function::ZoneHandle( - compiler::ffi::TrampolineFunction(dart_type, native_type, is_leaf, name)); - - Fragment code; - // Store the pointer in the context, we cannot load the untagged address - // here as these can be unoptimized call sites. - LocalVariable* pointer = MakeTemporary(); - - code += Constant(target); - - auto& context_slots = CompilerState::Current().GetDummyContextSlots( - /*context_id=*/0, /*num_variables=*/1); - code += AllocateContext(context_slots); - LocalVariable* context = MakeTemporary(); - - code += LoadLocal(context); - code += LoadLocal(pointer); - code += StoreNativeField(*context_slots[0]); - - code += AllocateClosure(); - - // Drop address. - code += DropTempsPreserveTop(1); - - return code; -} - Fragment BaseFlowGraphBuilder::DebugStepCheck(TokenPosition position) { #ifdef PRODUCT return Fragment(); diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.h b/runtime/vm/compiler/frontend/base_flow_graph_builder.h index ab8c02658f7..e3df988aa38 100644 --- a/runtime/vm/compiler/frontend/base_flow_graph_builder.h +++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.h @@ -405,12 +405,6 @@ class BaseFlowGraphBuilder { return stack_ == nullptr ? 0 : stack_->definition()->temp_index() + 1; } - // Builds the graph for an invocation of '_asFunctionInternal'. - // - // 'signatures' contains the pair [, ]. - Fragment BuildFfiAsFunctionInternalCall(const TypeArguments& signatures, - bool is_leaf); - Fragment AllocateObject(TokenPosition position, const Class& klass, intptr_t argument_count); diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index 78984ab1503..5fb0834b0b7 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -3348,8 +3348,8 @@ Fragment StreamingFlowGraphBuilder::BuildStaticInvocation(TokenPosition* p) { return BuildNativeEffect(); case MethodRecognizer::kReachabilityFence: return BuildReachabilityFence(); - case MethodRecognizer::kFfiAsFunctionInternal: - return BuildFfiAsFunctionInternal(); + case MethodRecognizer::kFfiCall: + return BuildFfiCall(); case MethodRecognizer::kFfiNativeCallbackFunction: return BuildFfiNativeCallbackFunction( FfiFunctionKind::kIsolateLocalStaticCallback); @@ -6221,34 +6221,46 @@ Fragment StreamingFlowGraphBuilder::BuildStoreAbiSpecificInt(bool at_index) { return code; } -Fragment StreamingFlowGraphBuilder::BuildFfiAsFunctionInternal() { +Fragment StreamingFlowGraphBuilder::BuildFfiCall() { const intptr_t argc = ReadUInt(); // Read argument count. - ASSERT(argc == 2); // Pointer, isLeaf. + ASSERT(argc == 1); // Target pointer. const intptr_t list_length = ReadListLength(); // Read types list length. - ASSERT(list_length == 2); // Dart signature, then native signature - // Read types. - const TypeArguments& type_arguments = T.BuildTypeArguments(list_length); - Fragment code; + T.BuildTypeArguments(list_length); // Read types. // Read positional argument count. const intptr_t positional_count = ReadListLength(); - ASSERT(positional_count == 2); - code += BuildExpression(); // Build first positional argument (pointer). + ASSERT(positional_count == argc); - // The second argument, `isLeaf`, is only used internally and dictates whether - // we can do a lightweight leaf function call. - bool is_leaf = false; - Fragment frag = BuildExpression(); - ASSERT(frag.entry->IsConstant()); - if (frag.entry->AsConstant()->value().ptr() == Object::bool_true().ptr()) { - is_leaf = true; - } - Pop(); + Fragment code; + // Push the target function pointer passed as Pointer object. + code += BuildExpression(); + // This can only be Pointer, so it is always safe to LoadUntagged. + code += B->LoadUntagged(compiler::target::PointerBase::data_offset()); + code += B->ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr); // Skip (empty) named arguments list. const intptr_t named_args_len = ReadListLength(); ASSERT(named_args_len == 0); - code += B->BuildFfiAsFunctionInternalCall(type_arguments, is_leaf); + const auto& native_type = FunctionType::ZoneHandle( + Z, parsed_function()->function().FfiCSignature()); + + // AbiSpecificTypes can have an incomplete mapping. + const char* error = nullptr; + compiler::ffi::NativeFunctionTypeFromFunctionType(Z, native_type, &error); + if (error != nullptr) { + const auto& language_error = Error::Handle( + LanguageError::New(String::Handle(String::New(error, Heap::kOld)), + Report::kError, Heap::kOld)); + Report::LongJump(language_error); + } + + code += B->FfiCallFunctionBody(parsed_function()->function(), native_type, + /*first_argument_parameter_offset=*/1); + + ASSERT(code.is_closed()); + + NullConstant(); // Maintain stack balance. + return code; } diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h index e05f6e39a42..75811a20ffb 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h @@ -387,9 +387,8 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper { Fragment BuildLoadAbiSpecificInt(bool at_index); Fragment BuildStoreAbiSpecificInt(bool at_index); - // Build FG for '_asFunctionInternal'. Reads an Arguments from the - // Kernel buffer and pushes the resulting closure. - Fragment BuildFfiAsFunctionInternal(); + // Build FG for FFI call. + Fragment BuildFfiCall(); // Build FG for '_nativeCallbackFunction'. Reads an Arguments from the // Kernel buffer and pushes the resulting Function object. diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc index 29a43a0a592..add84380d65 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.cc +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc @@ -400,11 +400,12 @@ Fragment FlowGraphBuilder::InstanceCall( } Fragment FlowGraphBuilder::FfiCall( - const compiler::ffi::CallMarshaller& marshaller) { + const compiler::ffi::CallMarshaller& marshaller, + bool is_leaf) { Fragment body; - FfiCallInstr* const call = new (Z) FfiCallInstr( - GetNextDeoptId(), marshaller, parsed_function_->function().FfiIsLeaf()); + FfiCallInstr* const call = + new (Z) FfiCallInstr(GetNextDeoptId(), marshaller, is_leaf); for (intptr_t i = call->InputCount() - 1; i >= 0; --i) { call->SetInputAt(i, Pop()); @@ -5037,7 +5038,8 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline( case FfiFunctionKind::kAsyncCallback: return BuildGraphOfAsyncFfiCallback(function); case FfiFunctionKind::kCall: - return BuildGraphOfFfiCall(function); + UNREACHABLE(); + return nullptr; } UNREACHABLE(); return nullptr; @@ -5122,26 +5124,6 @@ Fragment FlowGraphBuilder::FfiNativeLookupAddress(const Function& function) { return FfiNativeLookupAddress(native_instance); } -Fragment FlowGraphBuilder::FfiCallLookupAddress(const Function& function) { - ASSERT(function.IsFfiTrampoline()); - const intptr_t kClosureParameterOffset = 0; - Fragment body; - // Push the function pointer, which is stored (as Pointer object) in the - // first slot of the context. - body += - LoadLocal(parsed_function_->ParameterVariable(kClosureParameterOffset)); - body += LoadNativeField(Slot::Closure_context()); - body += LoadNativeField(Slot::GetContextVariableSlotFor( - thread_, *MakeImplicitClosureScope( - Z, Class::Handle(IG->object_store()->ffi_pointer_class())) - ->context_variables()[0])); - - // This can only be Pointer, so it is always safe to LoadUntagged. - body += LoadUntagged(compiler::target::PointerBase::data_offset()); - body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr); - return body; -} - Fragment FlowGraphBuilder::FfiNativeFunctionBody(const Function& function) { ASSERT(function.is_ffi_native()); ASSERT(!IsRecognizedMethodForFlowGraph(function)); @@ -5151,18 +5133,16 @@ Fragment FlowGraphBuilder::FfiNativeFunctionBody(const Function& function) { Fragment body; body += FfiNativeLookupAddress(function); - body += FfiCallFunctionBody(function, c_signature); + body += FfiCallFunctionBody(function, c_signature, + /*first_argument_parameter_offset=*/0); return body; } Fragment FlowGraphBuilder::FfiCallFunctionBody( const Function& function, - const FunctionType& c_signature) { - ASSERT(function.is_ffi_native() || function.IsFfiTrampoline()); - const bool is_ffi_native = function.is_ffi_native(); - const intptr_t kClosureParameterOffset = 0; - const intptr_t first_argument_parameter_offset = - is_ffi_native ? 0 : kClosureParameterOffset + 1; + const FunctionType& c_signature, + intptr_t first_argument_parameter_offset) { + ASSERT(function.is_ffi_native() || function.IsFfiCallClosure()); LocalVariable* address = MakeTemporary("address"); @@ -5252,7 +5232,7 @@ Fragment FlowGraphBuilder::FfiCallFunctionBody( body += LoadLocal(return_compound_typed_data); } - body += FfiCall(marshaller); + body += FfiCall(marshaller, function.FfiIsLeaf()); for (intptr_t i = 0; i < marshaller.num_args(); i++) { if (marshaller.IsPointer(i)) { @@ -5315,31 +5295,6 @@ Fragment FlowGraphBuilder::FfiCallFunctionBody( return body; } -FlowGraph* FlowGraphBuilder::BuildGraphOfFfiCall(const Function& function) { - graph_entry_ = - new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId); - - auto normal_entry = BuildFunctionEntry(graph_entry_); - graph_entry_->set_normal_entry(normal_entry); - - PrologueInfo prologue_info(-1, -1); - - BlockEntryInstr* instruction_cursor = - BuildPrologue(normal_entry, &prologue_info); - - Fragment function_body(instruction_cursor); - function_body += CheckStackOverflowInPrologue(function.token_pos()); - - const auto& c_signature = - FunctionType::ZoneHandle(Z, function.FfiCSignature()); - - function_body += FfiCallLookupAddress(function); - function_body += FfiCallFunctionBody(function, c_signature); - - return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, - prologue_info); -} - Fragment FlowGraphBuilder::LoadNativeArg( const compiler::ffi::CallbackMarshaller& marshaller, intptr_t arg_index) { diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h index 88fa2857c98..c112a32f8fc 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.h +++ b/runtime/vm/compiler/frontend/kernel_to_il.h @@ -137,8 +137,6 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder { FlowGraph* BuildGraphOfFfiTrampoline(const Function& function); FlowGraph* BuildGraphOfSyncFfiCallback(const Function& function); FlowGraph* BuildGraphOfAsyncFfiCallback(const Function& function); - FlowGraph* BuildGraphOfFfiCall(const Function& function); - Fragment FfiCallLookupAddress(const Function& function); // Resolves the address of a native symbol from the constant data of a // vm:ffi:native pragma. @@ -150,7 +148,8 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder { Fragment FfiNativeLookupAddress(const Function& function); // Expects target address on stack. Fragment FfiCallFunctionBody(const Function& function, - const FunctionType& c_signature); + const FunctionType& c_signature, + intptr_t first_argument_parameter_offset); Fragment FfiNativeFunctionBody(const Function& function); Fragment NativeFunctionBody(const Function& function, LocalVariable* first_parameter); @@ -204,7 +203,8 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder { bool receiver_is_not_smi = false, bool is_call_on_this = false); - Fragment FfiCall(const compiler::ffi::CallMarshaller& marshaller); + Fragment FfiCall(const compiler::ffi::CallMarshaller& marshaller, + bool is_leaf); Fragment CCall( const compiler::ffi::NativeCallingConvention& native_calling_convention); diff --git a/runtime/vm/compiler/frontend/scope_builder.cc b/runtime/vm/compiler/frontend/scope_builder.cc index 328eae0ddd1..fa148b56a43 100644 --- a/runtime/vm/compiler/frontend/scope_builder.cc +++ b/runtime/vm/compiler/frontend/scope_builder.cc @@ -154,8 +154,7 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() { FunctionNodeHelper::kPositionalParameters); // NOTE: FunctionNode is read further below the if. - intptr_t pos = 0; - if (function.is_ffi_native()) { + if (function.is_ffi_native() || function.IsFfiCallClosure()) { needs_expr_temp_ = true; // Calls with handles need try/catch variables. if (function.FfiCSignatureContainsHandles()) { @@ -167,7 +166,9 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() { FinalizeCatchVariables(); --depth_.catch_; } - } else if (function.IsClosureFunction()) { + } + intptr_t pos = 0; + if (function.IsClosureFunction()) { LocalVariable* closure_parameter = MakeVariable( TokenPosition::kNoSource, TokenPosition::kNoSource, Symbols::ClosureParameter(), AbstractType::dynamic_type()); diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h index 3f0dcc4ac03..8b47dc30117 100644 --- a/runtime/vm/compiler/recognized_methods_list.h +++ b/runtime/vm/compiler/recognized_methods_list.h @@ -272,7 +272,7 @@ namespace dart { V(_WeakReference, get:target, WeakReference_getTarget, 0xc98185aa) \ V(_WeakReference, set:_target, WeakReference_setTarget, 0xc71add9a) \ V(::, _abi, FfiAbi, 0x7c3c2b95) \ - V(::, _asFunctionInternal, FfiAsFunctionInternal, 0x630c8491) \ + V(::, _ffiCall, FfiCall, 0x6118e962) \ V(::, _nativeCallbackFunction, FfiNativeCallbackFunction, 0x3fe722bc) \ V(::, _nativeAsyncCallbackFunction, FfiNativeAsyncCallbackFunction, \ 0xbec4b7b9) \ diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 319f7f8f9bf..f4ed6f29639 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -8384,10 +8384,16 @@ FunctionTypePtr Function::FfiCSignature() const { ASSERT(!obj.IsNull()); return FfiTrampolineData::Cast(obj).c_signature(); } - ASSERT(is_ffi_native()); - auto const& native_instance = Instance::Handle(GetNativeAnnotation()); + auto& pragma_value = Instance::Handle(zone); + if (is_ffi_native()) { + pragma_value = GetNativeAnnotation(); + } else if (IsFfiCallClosure()) { + pragma_value = GetFfiCallClosurePragmaValue(); + } else { + UNREACHABLE(); + } const auto& type_args = - TypeArguments::Handle(zone, native_instance.GetTypeArguments()); + TypeArguments::Handle(zone, pragma_value.GetTypeArguments()); ASSERT(type_args.Length() == 1); const auto& native_type = FunctionType::Cast(AbstractType::ZoneHandle(zone, type_args.TypeAt(0))); @@ -8465,22 +8471,29 @@ void Function::AssignFfiCallbackId(int32_t callback_id) const { } bool Function::FfiIsLeaf() const { + Zone* zone = Thread::Current()->zone(); if (IsFfiTrampoline()) { const Object& obj = Object::Handle(untag()->data()); ASSERT(!obj.IsNull()); return FfiTrampolineData::Cast(obj).is_leaf(); } - ASSERT(is_ffi_native()); - Zone* zone = Thread::Current()->zone(); - auto const& native_instance = Instance::Handle(GetNativeAnnotation()); - const auto& native_class = Class::Handle(zone, native_instance.clazz()); - const auto& native_class_fields = Array::Handle(zone, native_class.fields()); - ASSERT(native_class_fields.Length() == 4); - const auto& is_leaf_field = - Field::Handle(zone, Field::RawCast(native_class_fields.At(3))); - ASSERT(!is_leaf_field.is_static()); - return Bool::Handle(zone, - Bool::RawCast(native_instance.GetField(is_leaf_field))) + auto& pragma_value = Instance::Handle(zone); + if (is_ffi_native()) { + pragma_value = GetNativeAnnotation(); + } else if (IsFfiCallClosure()) { + pragma_value = GetFfiCallClosurePragmaValue(); + } else { + UNREACHABLE(); + } + const auto& pragma_value_class = Class::Handle(zone, pragma_value.clazz()); + const auto& pragma_value_fields = + Array::Handle(zone, pragma_value_class.fields()); + ASSERT(pragma_value_fields.Length() >= 1); + const auto& is_leaf_field = Field::Handle( + zone, + Field::RawCast(pragma_value_fields.At(pragma_value_fields.Length() - 1))); + ASSERT(is_leaf_field.name() == Symbols::isLeaf().ptr()); + return Bool::Handle(zone, Bool::RawCast(pragma_value.GetField(is_leaf_field))) .value(); } @@ -9132,8 +9145,9 @@ static bool InVmTests(const Function& function) { } bool Function::ForceOptimize() const { - if (RecognizedKindForceOptimize() || IsFfiTrampoline() || is_ffi_native() || - IsTypedDataViewFactory() || IsUnmodifiableTypedDataViewFactory()) { + if (RecognizedKindForceOptimize() || IsFfiCallClosure() || + IsFfiTrampoline() || is_ffi_native() || IsTypedDataViewFactory() || + IsUnmodifiableTypedDataViewFactory()) { return true; } @@ -9173,6 +9187,25 @@ bool Function::IsCachableIdempotent() const { return InVmTests(*this); } +bool Function::IsFfiCallClosure() const { + if (!IsNonImplicitClosureFunction()) return false; + if (!has_pragma()) return false; + return Library::FindPragma(Thread::Current(), /*only_core=*/false, *this, + Symbols::vm_ffi_call_closure()); +} + +InstancePtr Function::GetFfiCallClosurePragmaValue() const { + ASSERT(IsFfiCallClosure()); + Thread* thread = Thread::Current(); + Zone* zone = thread->zone(); + auto& pragma_value = Object::Handle(zone); + Library::FindPragma(thread, /*only_core=*/false, *this, + Symbols::vm_ffi_call_closure(), + /*multiple=*/false, &pragma_value); + ASSERT(!pragma_value.IsNull()); + return Instance::Cast(pragma_value).ptr(); +} + bool Function::RecognizedKindForceOptimize() const { switch (recognized_kind()) { // Uses unboxed/untagged data not supported in unoptimized. @@ -9247,7 +9280,7 @@ bool Function::RecognizedKindForceOptimize() const { #if !defined(DART_PRECOMPILED_RUNTIME) bool Function::CanBeInlined() const { if (ForceOptimize()) { - if (IsFfiTrampoline() || is_ffi_native()) { + if (IsFfiCallClosure() || IsFfiTrampoline() || is_ffi_native()) { // We currently don't support inlining FFI trampolines. Some of them // are naturally non-inlinable because they contain a try/catch block, // but this condition is broader than strictly necessary. diff --git a/runtime/vm/object.h b/runtime/vm/object.h index 80ba52770f5..a97f89fe266 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -3911,6 +3911,13 @@ class Function : public Object { UntaggedFunction::kFfiTrampoline; } + // Returns true if this function is a closure function + // used to represent ffi call. + bool IsFfiCallClosure() const; + + // Returns value of vm:ffi:call-closure pragma. + InstancePtr GetFfiCallClosurePragmaValue() const; + // Returns true for functions which execution can be suspended // using Suspend/Resume stubs. Such functions have an artificial // :suspend_state local variable at the fixed location of the frame. diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index 846d71723f6..c937627c092 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -501,6 +501,7 @@ class ObjectPointerVisitor; V(from, "from") \ V(get, "get") \ V(index_temp, ":index_temp") \ + V(isLeaf, "isLeaf") \ V(isPaused, "isPaused") \ V(match_end_index, ":match_end_index") \ V(match_start_index, ":match_start_index") \ @@ -529,6 +530,7 @@ class ObjectPointerVisitor; V(vm_exact_result_type, "vm:exact-result-type") \ V(vm_external_name, "vm:external-name") \ V(vm_ffi_abi_specific_mapping, "vm:ffi:abi-specific-mapping") \ + V(vm_ffi_call_closure, "vm:ffi:call-closure") \ V(vm_ffi_native, "vm:ffi:native") \ V(vm_ffi_native_assets, "vm:ffi:native-assets") \ V(vm_ffi_struct_fields, "vm:ffi:struct-fields") \ diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart index ecfdfb9e825..d7181571769 100644 --- a/sdk/lib/_internal/vm/lib/ffi_patch.dart +++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart @@ -82,13 +82,21 @@ int sizeOf() { @pragma("vm:idempotent") external Pointer _fromAddress(int ptr); -// The real implementation of this function (for interface calls) lives in -// BuildFfiAsFunctionInternal in the Kernel frontend. No calls can actually -// reach this function. +/// Argument for vm:ffi:call-closure pragma describing FFI call. +final class _FfiCall { + // Implementation note: VM hardcodes the layout of this class (number and + // order of its fields), so adding/removing/changing fields requires + // updating the VM code (see Function::GetFfiCallClosurePragmaValue()). + final bool isLeaf; + const _FfiCall({this.isLeaf = false}); +} + +// Helper function to perform FFI call. +// Inserted by FFI kernel transformation into the FFI call closures. +// Implemented in BuildFfiCall +// in runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc. @pragma("vm:recognized", "other") -@pragma("vm:external-name", "Ffi_asFunctionInternal") -external DS _asFunctionInternal( - Pointer> ptr, bool isLeaf); +external ReturnType _ffiCall(Pointer target); @pragma("vm:recognized", "other") @pragma("vm:idempotent") diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart index 7707cfa90a7..6851cdd4d5f 100644 --- a/sdk/lib/ffi/ffi.dart +++ b/sdk/lib/ffi/ffi.dart @@ -69,7 +69,7 @@ final class Pointer extends NativeType { /// On 32-bit systems, the upper 32-bits of the result are 0. external int get address; - /// Cast Pointer to a Pointer. + /// Cast Pointer to a Pointer. external Pointer cast(); /// Equality for Pointers only depends on their address. @@ -1157,6 +1157,10 @@ abstract final class NativeApi { /// NOTE: This is an experimental feature and may change in the future. @Since('2.19') final class Native { + // Implementation note: VM hardcodes the layout of this class (number and + // order of its fields), so adding/removing/changing fields requires + // updating the VM code (see Function::GetNativeAnnotation()). + /// The native symbol to be resolved, if not using the default. /// /// If not specified, the default symbol used for native function lookup diff --git a/tests/ffi/abi_specific_int_incomplete_jit_test.dart b/tests/ffi/abi_specific_int_incomplete_jit_test.dart index 8a0dd413f50..b58539ba347 100644 --- a/tests/ffi/abi_specific_int_incomplete_jit_test.dart +++ b/tests/ffi/abi_specific_int_incomplete_jit_test.dart @@ -116,21 +116,27 @@ void testAsFunction() { Expect.throws(() { nullptr .cast>() - .asFunction(); + .asFunction() + .call(42); }); Expect.throws(() { nullptr .cast>() - .asFunction(); + .asFunction() + .call(42); }); + final p = calloc(100).cast(); Expect.throws(() { nullptr .cast>() - .asFunction(); + .asFunction() + .call(p.ref); }); + calloc.free(p); Expect.throws(() { nullptr .cast>() - .asFunction(); + .asFunction() + .call(); }); } diff --git a/tests/ffi/function_test.dart b/tests/ffi/function_test.dart index 6d4c77e9c71..1e7d5aa62d6 100644 --- a/tests/ffi/function_test.dart +++ b/tests/ffi/function_test.dart @@ -459,6 +459,12 @@ void testNoArgs() { Expect.approxEquals(1337.0, result); } +// Returns a possibly ofuscated 'arg2' identifier. +String get arg2ObfuscatedName { + final str = (arg2: 0).toString(); + return str.substring('('.length, str.length - ': 0)'.length); +} + void testNativeFunctionNullableInt() { final sumPlus42 = ffiTestFunctions.lookupFunction< Int32 Function(Int32, Int32), int Function(int, int?)>("SumPlus42"); @@ -467,7 +473,7 @@ void testNativeFunctionNullableInt() { sumPlus42(3, null); } catch (e) { // TODO(http://dartbug.com/47098): Save param names to dwarf. - Expect.isTrue(e.toString().contains('ffi_param2') || + Expect.isTrue(e.toString().contains(arg2ObfuscatedName) || e.toString().contains('')); } }