[vm/ffi] Support FFI in AOT (excluding callbacks).

Move generation of Function objects for native trampolines to the Precompiler, so they can be generated during AOT and tree-shaken if possible.

Issue dartbug.com/35765

Change-Id: I0e69b7e0b22db73e3a40f2fe445660e57ddb6fa9
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-bare-linux-release-simarm64-try, vm-kernel-precomp-bare-linux-release-x64-try, vm-kernel-precomp-linux-debug-x64-try, vm-dartkb-linux-debug-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107407
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Samir Jindel 2019-07-05 21:59:27 +00:00 committed by commit-bot@chromium.org
parent f0d6f119de
commit fcc72ad83f
29 changed files with 407 additions and 163 deletions

View file

@ -76,20 +76,21 @@ class Command {
}
static Command adbPrecompiled(
String precompiledRunner,
String buildPath,
String processTest,
String testDirectory,
List<String> arguments,
bool useBlobs,
bool useElf) {
return AdbPrecompilationCommand._(precompiledRunner, processTest,
testDirectory, arguments, useBlobs, useElf);
bool useElf,
List<String> extraLibs) {
return AdbPrecompilationCommand._(buildPath, processTest, testDirectory,
arguments, useBlobs, useElf, extraLibs);
}
static Command adbDartk(String precompiledRunner, String processTest,
String script, List<String> arguments, List<String> extraLibraries) {
static Command adbDartk(String buildPath, String processTest, String script,
List<String> arguments, List<String> extraLibraries) {
return AdbDartkCommand._(
precompiledRunner, processTest, script, arguments, extraLibraries);
buildPath, processTest, script, arguments, extraLibraries);
}
static Command jsCommandLine(
@ -587,54 +588,72 @@ class VmBatchCommand extends ProcessCommand implements VmCommand {
}
}
class AdbPrecompilationCommand extends Command {
final String precompiledRunnerFilename;
abstract class AdbCommand {
String get buildPath;
List<String> get extraLibraries;
}
class AdbPrecompilationCommand extends Command implements AdbCommand {
final String buildPath; // Path to the output directory of the build.
final String processTestFilename;
final String precompiledTestDirectory;
final List<String> arguments;
final bool useBlobs;
final bool useElf;
final List<String> extraLibraries;
AdbPrecompilationCommand._(
this.precompiledRunnerFilename,
this.buildPath,
this.processTestFilename,
this.precompiledTestDirectory,
this.arguments,
this.useBlobs,
this.useElf,
this.extraLibraries,
{int index = 0})
: super._("adb_precompilation", index: index);
AdbPrecompilationCommand indexedCopy(int index) => AdbPrecompilationCommand._(
precompiledRunnerFilename,
buildPath,
processTestFilename,
precompiledTestDirectory,
arguments,
useBlobs,
useElf,
extraLibraries,
index: index);
_buildHashCode(HashCodeBuilder builder) {
super._buildHashCode(builder);
builder.add(precompiledRunnerFilename);
builder.add(buildPath);
builder.add(precompiledTestDirectory);
builder.add(arguments);
builder.add(useBlobs);
builder.add(useElf);
extraLibraries.forEach(builder.add);
}
static bool _listEquals(List<String> x, List<String> y) {
if (x.length != y.length) return false;
for (int i = 0; i < x.length; ++i) {
if (x[i] != y[i]) return false;
}
return true;
}
bool _equal(AdbPrecompilationCommand other) =>
super._equal(other) &&
precompiledRunnerFilename == other.precompiledRunnerFilename &&
buildPath == other.buildPath &&
useBlobs == other.useBlobs &&
useElf == other.useElf &&
arguments == other.arguments &&
precompiledTestDirectory == other.precompiledTestDirectory;
precompiledTestDirectory == other.precompiledTestDirectory &&
_listEquals(extraLibraries, other.extraLibraries);
String toString() => 'Steps to push precompiled runner and precompiled code '
'to an attached device. Uses (and requires) adb.';
}
class AdbDartkCommand extends Command {
class AdbDartkCommand extends Command implements AdbCommand {
final String buildPath;
final String processTestFilename;
final String kernelFile;

View file

@ -629,9 +629,23 @@ class CommandExecutorImpl implements CommandExecutor {
}
}
List<_StepFunction> _pushLibraries(AdbCommand command, AdbDevice device,
String deviceDir, String deviceTestDir) {
final List<_StepFunction> steps = [];
for (var lib in command.extraLibraries) {
var libName = "lib${lib}.so";
steps.add(() => device.runAdbCommand([
'push',
'${command.buildPath}/$libName',
'$deviceTestDir/$libName'
]));
}
return steps;
}
Future<CommandOutput> _runAdbPrecompilationCommand(
AdbDevice device, AdbPrecompilationCommand command, int timeout) async {
var runner = command.precompiledRunnerFilename;
final String buildPath = command.buildPath;
var processTest = command.processTestFilename;
var testdir = command.precompiledTestDirectory;
var arguments = command.arguments;
@ -652,8 +666,8 @@ class CommandExecutorImpl implements CommandExecutor {
steps.add(() => device.runAdbShellCommand(['rm', '-Rf', deviceTestDir]));
steps.add(() => device.runAdbShellCommand(['mkdir', '-p', deviceTestDir]));
steps.add(() =>
device.pushCachedData(runner, '$devicedir/dart_precompiled_runtime'));
steps.add(() => device.pushCachedData('$buildPath/dart_precompiled_runtime',
'$devicedir/dart_precompiled_runtime'));
steps.add(
() => device.pushCachedData(processTest, '$devicedir/process_test'));
steps.add(() => device.runAdbShellCommand([
@ -662,6 +676,8 @@ class CommandExecutorImpl implements CommandExecutor {
'$devicedir/dart_precompiled_runtime $devicedir/process_test'
]));
steps.addAll(_pushLibraries(command, device, devicedir, deviceTestDir));
for (var file in files) {
steps.add(() => device
.runAdbCommand(['push', '$testdir/$file', '$deviceTestDir/$file']));
@ -669,7 +685,8 @@ class CommandExecutorImpl implements CommandExecutor {
steps.add(() => device.runAdbShellCommand(
[
'$devicedir/dart_precompiled_runtime',
'export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:$deviceTestDir;'
'$devicedir/dart_precompiled_runtime',
]..addAll(arguments),
timeout: timeoutDuration));
@ -723,11 +740,7 @@ class CommandExecutorImpl implements CommandExecutor {
steps.add(() => device
.runAdbCommand(['push', hostKernelFile, '$deviceTestDir/out.dill']));
for (var lib in command.extraLibraries) {
var libName = "lib${lib}.so";
steps.add(() => device.runAdbCommand(
['push', '${buildPath}/$libName', '$deviceTestDir/$libName']));
}
steps.addAll(_pushLibraries(command, device, devicedir, deviceTestDir));
steps.add(() => device.runAdbShellCommand(
[

View file

@ -369,11 +369,10 @@ class DartPrecompiledAdbRuntimeConfiguration
throw "dart_precompiled cannot run files of type '$type'.";
}
String precompiledRunner = dartPrecompiledBinaryFileName;
String processTest = processTestBinaryFileName;
return [
Command.adbPrecompiled(
precompiledRunner, processTest, script, arguments, useBlobs, useElf)
buildDir, processTest, script, arguments, useBlobs, useElf, extraLibs)
];
}
}

View file

@ -90,6 +90,7 @@ const List<int> nativeTypeSizes = [
/// _FfiUseSiteTransformer and _FfiDefinitionTransformer.
class FfiTransformer extends Transformer {
final TypeEnvironment env;
final CoreTypes coreTypes;
final LibraryIndex index;
final ClassHierarchy hierarchy;
final DiagnosticReporter diagnosticReporter;
@ -107,16 +108,18 @@ class FfiTransformer extends Transformer {
final Procedure storeMethod;
final Procedure offsetByMethod;
final Procedure asFunctionMethod;
final Procedure asFunctionInternal;
final Procedure lookupFunctionMethod;
final Procedure fromFunctionMethod;
final Field addressOfField;
final Constructor structFromPointer;
final Procedure libraryLookupMethod;
/// Classes corresponding to [NativeType], indexed by [NativeType].
final List<Class> nativeTypesClasses;
FfiTransformer(
this.index, CoreTypes coreTypes, this.hierarchy, this.diagnosticReporter)
this.index, this.coreTypes, this.hierarchy, this.diagnosticReporter)
: env = new TypeEnvironment(coreTypes, hierarchy),
intClass = coreTypes.intClass,
doubleClass = coreTypes.doubleClass,
@ -133,10 +136,14 @@ class FfiTransformer extends Transformer {
structFromPointer =
index.getMember('dart:ffi', 'Struct', 'fromPointer'),
asFunctionMethod = index.getMember('dart:ffi', 'Pointer', 'asFunction'),
asFunctionInternal =
index.getTopLevelMember('dart:ffi', '_asFunctionInternal'),
lookupFunctionMethod =
index.getMember('dart:ffi', 'DynamicLibrary', 'lookupFunction'),
fromFunctionMethod =
index.getMember('dart:ffi', 'Pointer', 'fromFunction'),
libraryLookupMethod =
index.getMember('dart:ffi', 'DynamicLibrary', 'lookup'),
nativeTypesClasses = nativeTypeClassNames
.map((name) => index.getClass('dart:ffi', name))
.toList();

View file

@ -144,12 +144,48 @@ class _FfiUseSiteTransformer extends FfiTransformer {
return node;
}
// We need to replace calls to 'DynamicLibrary.lookupFunction' with explicit
// Kernel, because we cannot have a generic call to 'asFunction' in its body.
//
// Below, in 'visitMethodInvocation', we ensure that the type arguments to
// 'lookupFunction' are constants, so by inlining the call to 'asFunction' at
// the call-site, we ensure that there are no generic calls to 'asFunction'.
//
// We will not detect dynamic invocations of 'asFunction' -- these are handled
// by the stub in 'dynamic_library_patch.dart'. Dynamic invocations of
// 'lookupFunction' (and 'asFunction') are not legal and throw a runtime
// exception.
Expression _replaceLookupFunction(MethodInvocation node) {
// The generated code looks like:
//
// _asFunctionInternal<DS, NS>(lookup<NativeFunction<NS>>(symbolName))
final DartType nativeSignature = node.arguments.types[0];
final DartType dartSignature = node.arguments.types[1];
final Arguments args = Arguments([
node.arguments.positional.single
], types: [
InterfaceType(nativeFunctionClass, [nativeSignature])
]);
final Expression lookupResult = MethodInvocation(
node.receiver, Name("lookup"), args, libraryLookupMethod);
return StaticInvocation(asFunctionInternal,
Arguments([lookupResult], types: [dartSignature, nativeSignature]));
}
@override
visitMethodInvocation(MethodInvocation node) {
super.visitMethodInvocation(node);
Member target = node.interfaceTarget;
try {
// We will not detect dynamic invocations of 'asFunction' and
// 'lookupFunction' -- these are handled by the 'asFunctionInternal' stub
// in 'dynamic_library_patch.dart'. Dynamic invocations of 'asFunction'
// and 'lookupFunction' are not legal and throw a runtime exception.
if (target == lookupFunctionMethod) {
DartType nativeType =
InterfaceType(nativeFunctionClass, [node.arguments.types[0]]);
@ -157,14 +193,8 @@ class _FfiUseSiteTransformer extends FfiTransformer {
_ensureNativeTypeValid(nativeType, node);
_ensureNativeTypeToDartType(nativeType, dartType, node);
return _replaceLookupFunction(node);
} else if (target == asFunctionMethod) {
if (isFfiLibrary) {
// Library code of dart:ffi uses asFunction to implement
// lookupFunction. Since we treat lookupFunction as well, this call
// can be generic and still support AOT.
return node;
}
DartType dartType = node.arguments.types[0];
DartType pointerType = node.receiver.getStaticType(env);
DartType nativeType = _pointerTypeGetTypeArg(pointerType);
@ -172,6 +202,11 @@ class _FfiUseSiteTransformer extends FfiTransformer {
_ensureNativeTypeValid(pointerType, node);
_ensureNativeTypeValid(nativeType, node);
_ensureNativeTypeToDartType(nativeType, dartType, node);
final DartType nativeSignature =
(nativeType as InterfaceType).typeArguments[0];
return StaticInvocation(asFunctionInternal,
Arguments([node.receiver], types: [dartType, nativeSignature]));
} else if (target == loadMethod) {
// TODO(dacoharkes): should load and store be generic?
// https://github.com/dart-lang/sdk/issues/35902

View file

@ -13,6 +13,7 @@
#include "vm/compiler/ffi.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/exceptions.h"
#include "vm/flags.h"
#include "vm/log.h"
#include "vm/native_arguments.h"
#include "vm/native_entry.h"
@ -31,11 +32,6 @@ static bool IsPointerType(const AbstractType& type) {
return RawObject::IsFfiPointerClassId(type.type_class_id());
}
static bool IsNativeFunction(const AbstractType& type_arg) {
classid_t type_cid = type_arg.type_class_id();
return RawObject::IsFfiTypeNativeFunctionClassId(type_cid);
}
static void CheckSized(const AbstractType& type_arg) {
const classid_t type_cid = type_arg.type_class_id();
if (RawObject::IsFfiNativeTypeTypeClassId(type_cid) ||
@ -52,7 +48,7 @@ static void CheckSized(const AbstractType& type_arg) {
}
}
enum class FfiVariance { kInvariant = 0, kCovariant = 1, kContravariant = 2 };
enum class FfiVariance { kCovariant = 0, kContravariant = 1 };
// Checks that a dart type correspond to a [NativeType].
// Because this is checked already in a kernel transformation, it does not throw
@ -86,9 +82,7 @@ static bool DartAndCTypeCorrespond(const AbstractType& native_type,
Heap::kNew);
}
if (RawObject::IsFfiPointerClassId(native_type_cid)) {
return (variance == FfiVariance::kInvariant &&
dart_type.Equals(native_type)) ||
(variance == FfiVariance::kCovariant &&
return (variance == FfiVariance::kCovariant &&
dart_type.IsSubtypeOf(native_type, Heap::kNew)) ||
(variance == FfiVariance::kContravariant &&
native_type.IsSubtypeOf(dart_type, Heap::kNew)) ||
@ -105,7 +99,8 @@ static bool DartAndCTypeCorrespond(const AbstractType& native_type,
if (!nativefunction_type_arg.IsFunctionType()) {
return false;
}
Function& dart_function = Function::Handle(((Type&)dart_type).signature());
Function& dart_function =
Function::Handle((Type::Cast(dart_type)).signature());
if (dart_function.NumTypeParameters() != 0 ||
dart_function.HasOptionalPositionalParameters() ||
dart_function.HasOptionalNamedParameters()) {
@ -434,91 +429,12 @@ DEFINE_NATIVE_ENTRY(Ffi_sizeOf, 1, 0) {
return Integer::New(SizeOf(type_arg));
}
// TODO(dacoharkes): Cache the trampolines.
// We can possibly address simultaniously with 'precaching' in AOT.
static RawFunction* TrampolineFunction(const Function& dart_signature,
const Function& c_signature) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
String& name =
String::ZoneHandle(Symbols::New(Thread::Current(), "FfiTrampoline"));
const Library& lib = Library::Handle(Library::FfiLibrary());
const Class& owner_class = Class::Handle(lib.toplevel_class());
Function& function =
Function::Handle(zone, Function::New(name, RawFunction::kFfiTrampoline,
/*is_static=*/true,
/*is_const=*/false,
/*is_abstract=*/false,
/*is_external=*/false,
/*is_native=*/false, owner_class,
TokenPosition::kMinSource));
function.set_is_debuggable(false);
function.set_num_fixed_parameters(dart_signature.num_fixed_parameters());
function.set_result_type(AbstractType::Handle(dart_signature.result_type()));
function.set_parameter_types(Array::Handle(dart_signature.parameter_types()));
// The signature function won't have any names for the parameters. We need to
// assign unique names for scope building and error messages.
const intptr_t num_params = dart_signature.num_fixed_parameters();
const Array& parameter_names = Array::Handle(Array::New(num_params));
for (intptr_t i = 0; i < num_params; ++i) {
if (i == 0) {
name = Symbols::ClosureParameter().raw();
} else {
name = Symbols::NewFormatted(thread, ":ffiParam%" Pd, i);
}
parameter_names.SetAt(i, name);
}
function.set_parameter_names(parameter_names);
function.SetFfiCSignature(c_signature);
return function.raw();
}
DEFINE_NATIVE_ENTRY(Ffi_asFunction, 1, 1) {
GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0));
AbstractType& pointer_type_arg =
AbstractType::Handle(pointer.type_argument());
ASSERT(IsNativeFunction(pointer_type_arg));
GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0));
CheckDartAndCTypeCorrespond(pointer_type_arg, type_arg,
FfiVariance::kInvariant);
Function& dart_signature = Function::Handle(Type::Cast(type_arg).signature());
TypeArguments& nativefunction_type_args =
TypeArguments::Handle(pointer_type_arg.arguments());
AbstractType& nativefunction_type_arg =
AbstractType::Handle(nativefunction_type_args.TypeAt(0));
Function& c_signature =
Function::Handle(Type::Cast(nativefunction_type_arg).signature());
Function& function =
Function::Handle(TrampolineFunction(dart_signature, c_signature));
// Set the c function pointer in the context of the closure rather than in
// the function so that we can reuse the function for each c function with
// the same signature.
Context& context = Context::Handle(Context::New(1));
context.SetAt(0,
Integer::Handle(zone, Integer::New(pointer.NativeAddress())));
RawClosure* raw_closure =
Closure::New(Object::null_type_arguments(), Object::null_type_arguments(),
function, context, Heap::kOld);
return raw_closure;
}
#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_PRECOMPILER) && \
!defined(TARGET_ARCH_DBC)
// Generates assembly to trampoline from native code into Dart.
#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_PRECOMPILER)
static uword CompileNativeCallback(const Function& c_signature,
const Function& dart_target,
const Instance& exceptional_return) {
#if defined(TARGET_ARCH_DBC)
// https://github.com/dart-lang/sdk/issues/35774
// FFI is supported, but callbacks are not.
Exceptions::ThrowUnsupportedError(
"FFI callbacks are not yet supported on DBC.");
#else
Thread* const thread = Thread::Current();
const int32_t callback_id = thread->AllocateFfiCallbackId();
@ -526,10 +442,9 @@ static uword CompileNativeCallback(const Function& c_signature,
// library. Note that these functions will never be invoked by Dart, so it
// doesn't matter that they all have the same name.
Zone* const Z = thread->zone();
const String& name =
String::ZoneHandle(Symbols::New(Thread::Current(), "FfiCallback"));
const Library& lib = Library::Handle(Library::FfiLibrary());
const Class& owner_class = Class::Handle(lib.toplevel_class());
const String& name = String::Handle(Symbols::New(thread, "FfiCallback"));
const Library& lib = Library::Handle(Z, Library::FfiLibrary());
const Class& owner_class = Class::Handle(Z, lib.toplevel_class());
const Function& function =
Function::Handle(Z, Function::New(name, RawFunction::kFfiTrampoline,
/*is_static=*/true,
@ -587,13 +502,49 @@ static uword CompileNativeCallback(const Function& c_signature,
thread->SetFfiCallbackCode(callback_id, code);
return code.EntryPoint();
#endif // defined(TARGET_ARCH_DBC)
}
#endif // !defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_PRECOMPILER)
#endif
DEFINE_NATIVE_ENTRY(Ffi_fromFunction, 1, 2) {
// Static invocations to this method are translated directly in streaming FGB
// and bytecode FGB. However, we can still reach this entrypoint in the bytecode
// interpreter.
DEFINE_NATIVE_ENTRY(Ffi_asFunctionInternal, 2, 1) {
#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
UNREACHABLE();
#else
ASSERT(FLAG_enable_interpreter);
GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0));
GET_NATIVE_TYPE_ARGUMENT(dart_type, arguments->NativeTypeArgAt(0));
GET_NATIVE_TYPE_ARGUMENT(native_type, arguments->NativeTypeArgAt(1));
const Function& dart_signature =
Function::Handle(zone, Type::Cast(dart_type).signature());
const Function& native_signature =
Function::Handle(zone, Type::Cast(native_type).signature());
const Function& function = Function::Handle(
compiler::ffi::TrampolineFunction(dart_signature, native_signature));
// Set the c function pointer in the context of the closure rather than in
// the function so that we can reuse the function for each c function with
// the same signature.
const Context& context = Context::Handle(Context::New(1));
context.SetAt(0,
Integer::Handle(zone, Integer::New(pointer.NativeAddress())));
return Closure::New(Object::null_type_arguments(),
Object::null_type_arguments(), function, context,
Heap::kOld);
#endif
}
DEFINE_NATIVE_ENTRY(Ffi_fromFunction, 1, 2) {
#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER) || \
defined(TARGET_ARCH_DBC)
// https://github.com/dart-lang/sdk/issues/37295
// FFI is supported, but callbacks are not.
Exceptions::ThrowUnsupportedError(
"FFI callbacks are not yet supported in AOT or on DBC.");
#else
GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Closure, closure, arguments->NativeArgAt(0));

View file

@ -18,6 +18,15 @@ class DynamicLibrary {
Pointer<T> lookup<T extends NativeType>(String symbolName)
native "Ffi_dl_lookup";
// The real implementation of this function lives in FfiUseSitesTransformer
// for interface calls. Only dynamic calls (which are illegal) reach this
// implementation.
@patch
F lookupFunction<T extends Function, F extends Function>(String symbolName) {
throw UnsupportedError(
"Dynamic invocation of lookupFunction is not supported.");
}
// TODO(dacoharkes): Expose this to users, or extend Pointer?
// https://github.com/dart-lang/sdk/issues/35881
int getHandle() native "Ffi_dl_getHandle";

View file

@ -11,6 +11,12 @@ Pointer<T> _allocate<T extends NativeType>(int count) native "Ffi_allocate";
Pointer<T> _fromAddress<T extends NativeType>(int ptr) native "Ffi_fromAddress";
// The real implementation of this function (for interface calls) lives in
// BuildFfiAsFunctionCall in the Kernel frontend. No calls can actually reach
// this function.
DS _asFunctionInternal<DS extends Function, NS extends Function>(
Pointer<NativeFunction<NS>> ptr) native "Ffi_asFunctionInternal";
@patch
@pragma("vm:entry-point")
class Pointer<T extends NativeType> {
@ -55,7 +61,9 @@ class Pointer<T extends NativeType> {
Pointer<U> cast<U extends NativeType>() native "Ffi_cast";
@patch
R asFunction<R extends Function>() native "Ffi_asFunction";
R asFunction<R extends Function>() {
throw UnsupportedError("Pointer.asFunction cannot be called dynamically.");
}
@patch
void free() native "Ffi_free";

View file

@ -383,7 +383,7 @@ namespace dart {
V(Ffi_offsetBy, 2) \
V(Ffi_cast, 1) \
V(Ffi_sizeOf, 0) \
V(Ffi_asFunction, 1) \
V(Ffi_asFunctionInternal, 1) \
V(Ffi_fromFunction, 2) \
V(Ffi_dl_open, 1) \
V(Ffi_dl_lookup, 2) \

View file

@ -5109,9 +5109,6 @@ class AllocateObjectInstr : public TemplateAllocation<0, NoThrow> {
DISALLOW_COPY_AND_ASSIGN(AllocateObjectInstr);
};
// TODO(vegorov) the name of the instruction is confusing. At some point
// it used to allocate uninitialized storage, but this is no longer true.
// These days it allocates null initialized storage.
class AllocateUninitializedContextInstr
: public TemplateAllocation<0, NoThrow> {
public:

View file

@ -10,7 +10,9 @@
#include "vm/compiler/backend/locations.h"
#include "vm/compiler/runtime_api.h"
#include "vm/growable_array.h"
#include "vm/object_store.h"
#include "vm/stack_frame.h"
#include "vm/symbols.h"
namespace dart {
@ -427,6 +429,45 @@ Location ResultLocation(Representation result_rep) {
#endif
}
// TODO(36607): Cache the trampolines.
RawFunction* TrampolineFunction(const Function& dart_signature,
const Function& c_signature) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
String& name = String::Handle(zone, Symbols::New(thread, "FfiTrampoline"));
const Library& lib = Library::Handle(zone, Library::FfiLibrary());
const Class& owner_class = Class::Handle(zone, lib.toplevel_class());
Function& function =
Function::Handle(zone, Function::New(name, RawFunction::kFfiTrampoline,
/*is_static=*/true,
/*is_const=*/false,
/*is_abstract=*/false,
/*is_external=*/false,
/*is_native=*/false, owner_class,
TokenPosition::kMinSource));
function.set_is_debuggable(false);
function.set_num_fixed_parameters(dart_signature.num_fixed_parameters());
function.set_result_type(AbstractType::Handle(dart_signature.result_type()));
function.set_parameter_types(Array::Handle(dart_signature.parameter_types()));
// The signature function won't have any names for the parameters. We need to
// assign unique names for scope building and error messages.
const intptr_t num_params = dart_signature.num_fixed_parameters();
const Array& parameter_names = Array::Handle(Array::New(num_params));
for (intptr_t i = 0; i < num_params; ++i) {
if (i == 0) {
name = Symbols::ClosureParameter().raw();
} else {
name = Symbols::NewFormatted(thread, ":ffi_param%" Pd, i);
}
parameter_names.SetAt(i, name);
}
function.set_parameter_names(parameter_names);
function.SetFfiCSignature(c_signature);
return function.raw();
}
// Accounts for alignment, where some stack slots are used as padding.
template <class Location>
intptr_t TemplateNumStackSlots(const ZoneGrowableArray<Location>& locations) {
@ -532,6 +573,27 @@ Representation FfiSignatureDescriptor::ResultRepresentation() const {
#endif // defined(TARGET_ARCH_DBC)
bool IsAsFunctionInternal(Zone* zone, Isolate* isolate, const Function& func) {
Object& asFunctionInternal =
Object::Handle(zone, isolate->object_store()->ffi_as_function_internal());
if (asFunctionInternal.raw() == Object::null()) {
// Cache the reference.
Library& ffi =
Library::Handle(zone, isolate->object_store()->ffi_library());
asFunctionInternal =
ffi.LookupFunctionAllowPrivate(Symbols::AsFunctionInternal());
// Cannot assert that 'asFunctionInternal' is found because it may have been
// tree-shaken.
if (asFunctionInternal.IsNull()) {
// Set the entry in the object store to a sentinel so we don't try to look
// it up again.
asFunctionInternal = Object::sentinel().raw();
}
isolate->object_store()->set_ffi_as_function_internal(asFunctionInternal);
}
return func.raw() == asFunctionInternal.raw();
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
} // namespace ffi

View file

@ -41,6 +41,9 @@ bool NativeTypeIsVoid(const AbstractType& result_type);
// Location for the result of a C signature function.
Location ResultLocation(Representation result_rep);
RawFunction* TrampolineFunction(const Function& dart_signature,
const Function& c_signature);
#if !defined(TARGET_ARCH_DBC)
// Unboxed representations of the arguments to a C signature function.
@ -133,6 +136,8 @@ class CallbackArgumentTranslator : public ValueObject {
intptr_t argument_slots_required_ = 0;
};
bool IsAsFunctionInternal(Zone* zone, Isolate* isolate, const Function& func);
} // namespace ffi
} // namespace compiler

View file

@ -601,9 +601,14 @@ void BaseFlowGraphBuilder::Push(Definition* definition) {
Value::AddToList(new (Z) Value(definition), &stack_);
}
Definition* BaseFlowGraphBuilder::Peek() {
ASSERT(stack_ != NULL);
return stack_->definition();
Definition* BaseFlowGraphBuilder::Peek(intptr_t depth) {
Value* head = stack_;
for (intptr_t i = 0; i < depth; ++i) {
ASSERT(head != nullptr);
head = head->next_use();
}
ASSERT(head != nullptr);
return head->definition();
}
Value* BaseFlowGraphBuilder::Pop() {
@ -813,6 +818,62 @@ Fragment BaseFlowGraphBuilder::LoadClassId() {
return Fragment(load);
}
Fragment BaseFlowGraphBuilder::AllocateObject(TokenPosition position,
const Class& klass,
intptr_t argument_count) {
ArgumentArray arguments = GetArguments(argument_count);
AllocateObjectInstr* allocate =
new (Z) AllocateObjectInstr(position, klass, arguments);
Push(allocate);
return Fragment(allocate);
}
Fragment BaseFlowGraphBuilder::BuildFfiAsFunctionInternalCall(
const TypeArguments& signatures) {
ASSERT(signatures.IsInstantiated() && signatures.Length() == 2);
const AbstractType& dart_type = AbstractType::Handle(signatures.TypeAt(0));
const AbstractType& native_type = AbstractType::Handle(signatures.TypeAt(1));
ASSERT(dart_type.IsFunctionType() && native_type.IsFunctionType());
const Function& target =
Function::ZoneHandle(compiler::ffi::TrampolineFunction(
Function::Handle(Z, Type::Cast(dart_type).signature()),
Function::Handle(Z, Type::Cast(native_type).signature())));
Fragment code;
code += LoadNativeField(Slot::Pointer_c_memory_address());
LocalVariable* address = MakeTemporary();
auto& context_variables = CompilerState::Current().GetDummyContextVariables(
/*context_id=*/0, /*num_variables=*/1);
code += AllocateContext(context_variables);
LocalVariable* context = MakeTemporary();
code += LoadLocal(context);
code += LoadLocal(address);
code += StoreInstanceField(
TokenPosition::kNoSource,
Slot::GetContextVariableSlotFor(thread_, *context_variables[0]));
code += AllocateClosure(TokenPosition::kNoSource, target);
LocalVariable* closure = MakeTemporary();
code += LoadLocal(closure);
code += LoadLocal(context);
code += StoreInstanceField(TokenPosition::kNoSource, Slot::Closure_context());
code += LoadLocal(closure);
code += Constant(target);
code +=
StoreInstanceField(TokenPosition::kNoSource, Slot::Closure_function());
// Drop address and context.
code += DropTempsPreserveTop(2);
return code;
}
} // namespace kernel
} // namespace dart

View file

@ -169,7 +169,7 @@ class BaseFlowGraphBuilder {
Fragment StoreIndexed(intptr_t class_id);
void Push(Definition* definition);
Definition* Peek();
Definition* Peek(intptr_t depth = 0);
Value* Pop();
Fragment Drop();
// Drop given number of temps from the stack but preserve top of the stack.
@ -292,6 +292,15 @@ class BaseFlowGraphBuilder {
return stack_ == nullptr ? 0 : stack_->definition()->temp_index() + 1;
}
// Builds the graph for an invocation of '_asFunctionInternal'.
//
// 'signatures' contains the pair [<dart signature>, <native signature>].
Fragment BuildFfiAsFunctionInternalCall(const TypeArguments& signatures);
Fragment AllocateObject(TokenPosition position,
const Class& klass,
intptr_t argument_count);
protected:
intptr_t AllocateBlockId() { return ++last_used_block_id_; }

View file

@ -5,6 +5,7 @@
#include "vm/compiler/frontend/bytecode_flow_graph_builder.h"
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/ffi.h"
#include "vm/compiler/frontend/bytecode_reader.h"
#include "vm/compiler/frontend/prologue_builder.h"
#include "vm/compiler/jit/compiler.h"
@ -776,6 +777,11 @@ void BytecodeFlowGraphBuilder::BuildDirectCall() {
const Function& target = Function::Cast(ConstantAt(DecodeOperandD()).value());
const intptr_t argc = DecodeOperandF().value();
if (compiler::ffi::IsAsFunctionInternal(Z, Isolate::Current(), target)) {
BuildFfiAsFunction();
return;
}
// Recognize identical() call.
// Note: similar optimization is performed in AST flow graph builder - see
// StreamingFlowGraphBuilder::BuildStaticInvocation, special_case_identical.
@ -1560,6 +1566,22 @@ void BytecodeFlowGraphBuilder::BuildAllocateClosure() {
code_ += B->AllocateClosure(position_, target);
}
// Builds graph for a call to 'dart:ffi::_asFunctionInternal'. The stack must
// look like:
//
// <receiver> => pointer argument
// <type arguments vector> => signatures
// ...
void BytecodeFlowGraphBuilder::BuildFfiAsFunction() {
// The bytecode FGB doesn't eagerly insert PushArguments, so the type
// arguments won't be wrapped in a PushArgumentsInstr.
const TypeArguments& type_args =
TypeArguments::Cast(B->Peek(/*depth=*/1)->AsConstant()->value());
// Drop type arguments, preserving pointer.
code_ += B->DropTempsPreserveTop(1);
code_ += B->BuildFfiAsFunctionInternalCall(type_args);
}
static bool IsICDataEntry(const ObjectPool& object_pool, intptr_t index) {
if (object_pool.TypeAt(index) != ObjectPool::EntryType::kTaggedObject) {
return false;

View file

@ -150,6 +150,7 @@ class BytecodeFlowGraphBuilder {
void BuildInterfaceCallCommon(bool is_unchecked_call);
void BuildInstruction(KernelBytecode::Opcode opcode);
void BuildFfiAsFunction();
#define DECLARE_BUILD_METHOD(name, encoding, kind, op1, op2, op3) \
void Build##name();

View file

@ -4,6 +4,7 @@
#include "vm/compiler/frontend/kernel_binary_flowgraph.h"
#include "vm/compiler/ffi.h"
#include "vm/compiler/frontend/bytecode_flow_graph_builder.h"
#include "vm/compiler/frontend/bytecode_reader.h"
#include "vm/compiler/frontend/flow_graph_builder.h" // For dart::FlowGraphBuilder::SimpleInstanceOfType.
@ -3048,6 +3049,10 @@ Fragment StreamingFlowGraphBuilder::BuildStaticInvocation(bool is_const,
++argument_count;
}
if (compiler::ffi::IsAsFunctionInternal(Z, Isolate::Current(), target)) {
return BuildFfiAsFunctionInternal();
}
Fragment instructions;
LocalVariable* instance_variable = NULL;
@ -5000,6 +5005,25 @@ Fragment StreamingFlowGraphBuilder::BuildFunctionNode(
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildFfiAsFunctionInternal() {
const intptr_t argc = ReadUInt(); // read argument count.
ASSERT(argc == 1); // pointer
const intptr_t list_length = ReadListLength(); // read types list length.
ASSERT(list_length == 2); // dart signature, then native signature
const TypeArguments& type_arguments =
T.BuildTypeArguments(list_length); // read types.
Fragment code;
const intptr_t positional_count =
ReadListLength(); // read positional argument count
ASSERT(positional_count == 1);
code += BuildExpression(); // build first positional argument (pointer)
const intptr_t named_args_len =
ReadListLength(); // skip (empty) named arguments list
ASSERT(named_args_len == 0);
code += B->BuildFfiAsFunctionInternalCall(type_arguments);
return code;
}
} // namespace kernel
} // namespace dart

View file

@ -350,6 +350,10 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
Fragment BuildFunctionNode(TokenPosition parent_position,
StringIndex name_index);
// Build build FG for '_asFunctionInternal'. Reads an Arguments from the
// Kernel buffer and pushes the resulting closure.
Fragment BuildFfiAsFunctionInternal();
FlowGraphBuilder* flow_graph_builder_;
ActiveClass* const active_class_;
TypeTranslator type_translator_;

View file

@ -216,16 +216,6 @@ Fragment FlowGraphBuilder::TranslateInstantiatedTypeArguments(
return instructions;
}
Fragment FlowGraphBuilder::AllocateObject(TokenPosition position,
const Class& klass,
intptr_t argument_count) {
ArgumentArray arguments = GetArguments(argument_count);
AllocateObjectInstr* allocate =
new (Z) AllocateObjectInstr(position, klass, arguments);
Push(allocate);
return Fragment(allocate);
}
Fragment FlowGraphBuilder::CatchBlockEntry(const Array& handler_types,
intptr_t handler_index,
bool needs_stacktrace,

View file

@ -127,9 +127,6 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
Fragment TranslateInstantiatedTypeArguments(
const TypeArguments& type_arguments);
Fragment AllocateObject(TokenPosition position,
const Class& klass,
intptr_t argument_count);
Fragment CatchBlockEntry(const Array& handler_types,
intptr_t handler_index,
bool needs_stacktrace,

View file

@ -98,9 +98,6 @@ static void PrecompilationModeHandler(bool value) {
FLAG_background_compilation = false;
FLAG_enable_mirrors = false;
// TODO(dacoharkes): Ffi support in AOT
// https://github.com/dart-lang/sdk/issues/35765
FLAG_enable_ffi = false;
FLAG_fields_may_be_reset = true;
FLAG_interpret_irregexp = true;
FLAG_lazy_dispatchers = false;

View file

@ -17273,6 +17273,10 @@ bool AbstractType::IsDartClosureType() const {
return !IsFunctionType() && (type_class_id() == kClosureCid);
}
bool AbstractType::IsFfiPointerType() const {
return HasTypeClass() && type_class_id() == kFfiPointerCid;
}
bool AbstractType::IsSubtypeOf(const AbstractType& other,
Heap::Space space) const {
ASSERT(IsFinalized());

View file

@ -6842,6 +6842,9 @@ class AbstractType : public Instance {
// Check if this type represents the Dart '_Closure' type.
bool IsDartClosureType() const;
// Check if this type represents the 'Pointer' type from "dart:ffi".
bool IsFfiPointerType() const;
// Check the subtype relationship.
bool IsSubtypeOf(const AbstractType& other, Heap::Space space) const;

View file

@ -144,6 +144,7 @@ class ObjectPointerVisitor;
RW(Class, ffi_pointer_class) \
RW(Class, ffi_native_type_class) \
RW(Class, ffi_struct_class) \
RW(Object, ffi_as_function_internal) \
// Please remember the last entry must be referred in the 'to' function below.
// The object store is a per isolate instance which stores references to
@ -235,7 +236,9 @@ class ObjectStore {
OBJECT_STORE_FIELD_LIST(DECLARE_OBJECT_STORE_FIELD,
DECLARE_OBJECT_STORE_FIELD)
#undef DECLARE_OBJECT_STORE_FIELD
RawObject** to() { return reinterpret_cast<RawObject**>(&ffi_struct_class_); }
RawObject** to() {
return reinterpret_cast<RawObject**>(&ffi_as_function_internal_);
}
RawObject** to_snapshot(Snapshot::Kind kind) {
switch (kind) {
case Snapshot::kFull:

View file

@ -25,6 +25,7 @@ class ObjectPointerVisitor;
V(ApiError, "ApiError") \
V(ArgDescVar, ":arg_desc") \
V(ArgumentError, "ArgumentError") \
V(AsFunctionInternal, "_asFunctionInternal") \
V(AssertionError, "_AssertionError") \
V(AssignIndexToken, "[]=") \
V(AsyncCompleter, ":async_completer") \

View file

@ -20,9 +20,8 @@ class DynamicLibrary {
external Pointer<T> lookup<T extends NativeType>(String symbolName);
/// Helper that combines lookup and cast to a Dart function.
F lookupFunction<T extends Function, F extends Function>(String symbolName) {
return lookup<NativeFunction<T>>(symbolName)?.asFunction<F>();
}
external F lookupFunction<T extends Function, F extends Function>(
String symbolName);
/// Dynamic libraries are equal if they load the same library.
external bool operator ==(other);

View file

@ -46,6 +46,9 @@ class Pointer<T extends NativeType> extends NativeType {
/// The pointer returned will remain alive for the duration of the current
/// isolate's lifetime. After the isolate it was created in is terminated,
/// invoking it from native code will cause undefined behavior.
///
/// Does not accept dynamic invocations -- where the type of the receiver is
/// [dynamic].
external static Pointer<NativeFunction<T>> fromFunction<T extends Function>(
@DartRepresentationOf("T") Function f, Object exceptionalReturn);
@ -81,7 +84,8 @@ class Pointer<T extends NativeType> extends NativeType {
/// Convert to Dart function, automatically marshalling the arguments
/// and return value.
///
/// Can only be called on [Pointer]<[NativeFunction]>.
/// Can only be called on [Pointer]<[NativeFunction]>. Does not accept dynamic
/// invocations -- where the type of the receiver is [dynamic].
external R asFunction<@DartRepresentationOf("T") R extends Function>();
/// Free memory on the C heap pointed to by this pointer with free().

View file

@ -19,7 +19,7 @@ function_callbacks_test/02: Skip
function_callbacks_test/03: Skip
[ $runtime == dart_precompiled ]
*: Skip # AOT is not yet supported: dartbug.com/35765
function_callbacks_test: Skip # Issue dartbug.com/37295
[ $arch == arm && $system != android ]
*: Skip # "hardfp" calling convention is not yet supported (iOS is also supported but not tested): dartbug.com/36309

View file

@ -13,6 +13,8 @@ import "package:expect/expect.dart";
main() {
testWrongArity();
testWrongTypes();
testDynamicAsFunction();
testDynamicLookupFunction();
}
ffi.DynamicLibrary ffiTestFunctions =
@ -61,3 +63,21 @@ void testWrongTypes() {
Expect.throwsTypeError(() => pointerOp(0));
}
}
// Test that invoking 'Pointer.asFunction' with a dynamic receiver type throws
// an exception.
void testDynamicAsFunction() {
dynamic x = ffi.nullptr.cast<ffi.NativeFunction<ffi.Void Function()>>();
Expect.throwsUnsupportedError(() {
x.asFunction<void Function()>();
});
}
// Test that invoking 'DynamicLibrary.lookupFunction' with a dynamic receiver
// type throws an exception.
void testDynamicLookupFunction() {
dynamic lib = ffiTestFunctions;
Expect.throwsUnsupportedError(() {
lib.lookupFunction<ffi.Void Function(), void Function()>("_");
});
}