From fd2e9b9f1af9e2ced5d263956f82e4ef3b583c8a Mon Sep 17 00:00:00 2001 From: Alexander Markov Date: Tue, 12 Dec 2023 18:34:39 +0000 Subject: [PATCH] [vm/ffi] Express FFI call closures explicitly in AST Instead of implicitly creating closures for FFI asFunction/lookupFunction APIs in the VM, now they are explicitly expressed in the kernel AST. That makes it possible to analyze them in TFA. FFI calls from Dart code into native are now performed in the following way: ``` block { Pointer #ffiTarget0 = target; @pragma('vm:ffi:call-closure', _FfiCall(isLeaf: false)) #ffiClosure0(int arg1) { _nativeEffect(arg1); return _ffiCall(#ffiTarget0); } } =>#ffiClosure0; ``` _ffiCall method is recognized by the VM and its call is replaced directly with FFI calling sequence. _ffiCall uses closure parameters implicitly. No extra trampolines are generated for FFI calls. TEST=existing Fixes https://github.com/dart-lang/sdk/issues/54172 Issue https://github.com/dart-lang/sdk/issues/39692 CoreLibraryReviewExempt: Implementation change only. Change-Id: I92b3ff7391470686151ad0807e2cdbbf1a69d256 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/339662 Commit-Queue: Alexander Markov Reviewed-by: Daco Harkes --- pkg/vm/lib/transformations/ffi/common.dart | 42 ++----- pkg/vm/lib/transformations/ffi/use_sites.dart | 105 +++++++++++++++--- .../transformations/ffi/as_function.dart | 37 ++++++ .../ffi/as_function.dart.aot.expect | 63 +++++++++++ .../ffi/as_function.dart.expect | 63 +++++++++++ .../ffi_struct_constructors.dart.expect | 44 +++++++- runtime/docs/compiler/ffi_pragmas.md | 8 ++ runtime/docs/pragmas.md | 1 + runtime/lib/ffi.cc | 5 - runtime/vm/bootstrap_natives.h | 1 - .../frontend/base_flow_graph_builder.cc | 59 ---------- .../frontend/base_flow_graph_builder.h | 6 - .../frontend/kernel_binary_flowgraph.cc | 52 +++++---- .../frontend/kernel_binary_flowgraph.h | 5 +- runtime/vm/compiler/frontend/kernel_to_il.cc | 69 ++---------- runtime/vm/compiler/frontend/kernel_to_il.h | 8 +- runtime/vm/compiler/frontend/scope_builder.cc | 7 +- runtime/vm/compiler/recognized_methods_list.h | 2 +- runtime/vm/object.cc | 67 ++++++++--- runtime/vm/object.h | 7 ++ runtime/vm/symbols.h | 2 + sdk/lib/_internal/vm/lib/ffi_patch.dart | 20 +++- sdk/lib/ffi/ffi.dart | 6 +- .../abi_specific_int_incomplete_jit_test.dart | 14 ++- tests/ffi/function_test.dart | 8 +- 25 files changed, 461 insertions(+), 240 deletions(-) create mode 100644 pkg/vm/testcases/transformations/ffi/as_function.dart create mode 100644 pkg/vm/testcases/transformations/ffi/as_function.dart.aot.expect create mode 100644 pkg/vm/testcases/transformations/ffi/as_function.dart.expect 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('')); } }