Files
sdk/runtime/vm/compiler/ffi/callback.cc
T
Martin Kustermann 4be2981c2d [vm/ffi] Ensure there's a single Function object per ffi-callback + exceptional-return combination
Right each `Pointer.fromFunction()` invocation will lead to creation of
a new ffi trampoline function & it's following JITed code. In AOT we
have exactly one ffi trampoline per target/native-signature/exceptional-return
combination.
=> This CL ensures we have only one such function.

Furthermore each `Pointer.fromFunction()` will currently perform 2
runtime calls in JIT: One to create a `Function` object, the other to
JIT that function & register callback metadata.
=> This CL ensures we won't do a runtime call to get a function, instead
   do it at compile-time (as in AOT)

Furthermore we eagerly assign a callback-id to the unique/deduped ffi
trampoline callbacks. Only when the application requests a pointer, do
we populate metadata on the `Thread` object.

This CL doesn't (yet) change the fact that in JIT mode we have
isolate-specific jit trampolines (that will call now shared ffi trampoline
functions).

We also avoid baking in C++ runtime function pointers in generated
code. As a result we can now preserve ffi trampolines across AppJIT
serialization.

As a nice side-effect, we remove 100 lines of code.

TEST=ffi{,_2}/ffi_callback_unique_test

Issue https://github.com/dart-lang/sdk/issues/50611

Change-Id: I458831a47b041a088086f28f825de2a3849f6adc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/273420
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2022-12-07 20:47:13 +00:00

111 lines
4.3 KiB
C++

// Copyright (c) 2020, 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.
#include "vm/compiler/ffi/callback.h"
#include "vm/canonical_tables.h"
#include "vm/class_finalizer.h"
#include "vm/object_store.h"
#include "vm/symbols.h"
namespace dart {
namespace compiler {
namespace ffi {
FunctionPtr NativeCallbackFunction(const FunctionType& c_signature,
const Function& dart_target,
const Instance& exceptional_return) {
Thread* const thread = Thread::Current();
Zone* const zone = thread->zone();
Function& function = Function::Handle(zone);
ASSERT(c_signature.IsCanonical());
ASSERT(exceptional_return.IsSmi() || exceptional_return.IsCanonical());
// Create a new Function named '<target>_FfiCallback' and stick it in the
// 'dart:ffi' library. Note that these functions will never be invoked by
// Dart, so they may have duplicate names.
const auto& name = String::Handle(
zone, Symbols::FromConcat(thread, Symbols::FfiCallback(),
String::Handle(zone, dart_target.name())));
const Library& lib = Library::Handle(zone, Library::FfiLibrary());
const Class& owner_class = Class::Handle(zone, lib.toplevel_class());
auto& signature = FunctionType::Handle(zone, FunctionType::New());
function =
Function::New(signature, name, UntaggedFunction::kFfiTrampoline,
/*is_static=*/true,
/*is_const=*/false,
/*is_abstract=*/false,
/*is_external=*/false,
/*is_native=*/false, owner_class, TokenPosition::kNoSource);
function.set_is_debuggable(false);
// Set callback-specific fields which the flow-graph builder needs to generate
// the body.
function.SetFfiCSignature(c_signature);
function.SetFfiCallbackTarget(dart_target);
// We need to load the exceptional return value as a constant in the generated
// function. Even though the FE ensures that it is a constant, it could still
// be a literal allocated in new space. We need to copy it into old space in
// that case.
//
// Exceptional return values currently cannot be pointers because we don't
// have constant pointers.
ASSERT(exceptional_return.IsNull() || exceptional_return.IsNumber() ||
exceptional_return.IsBool());
if (!exceptional_return.IsSmi() && exceptional_return.IsNew()) {
function.SetFfiCallbackExceptionalReturn(Instance::Handle(
zone, exceptional_return.CopyShallowToOldSpace(thread)));
} else {
function.SetFfiCallbackExceptionalReturn(exceptional_return);
}
// The dart type of the FfiCallback has no arguments or type arguments and
// has a result type of dynamic, as the callback is never invoked via Dart,
// only via native calls that do not use this information. Having no Dart
// arguments ensures the scope builder does not add inappropriate parameter
// variables.
signature.set_result_type(Object::dynamic_type());
// Finalize (and thus canonicalize) the signature.
signature ^= ClassFinalizer::FinalizeType(signature);
function.SetSignature(signature);
{
// Ensure only one thread updates the cache of deduped ffi trampoline
// functions.
auto isolate_group = thread->isolate_group();
SafepointWriteRwLocker ml(thread, isolate_group->program_lock());
auto object_store = isolate_group->object_store();
if (object_store->ffi_callback_functions() == Array::null()) {
FfiCallbackFunctionSet set(
HashTables::New<FfiCallbackFunctionSet>(/*initial_capacity=*/4));
object_store->set_ffi_callback_functions(set.Release());
}
FfiCallbackFunctionSet set(object_store->ffi_callback_functions());
const intptr_t entry_count_before = set.NumOccupied();
function ^= set.InsertOrGet(function);
const intptr_t entry_count_after = set.NumOccupied();
object_store->set_ffi_callback_functions(set.Release());
if (entry_count_before != entry_count_after) {
function.AssignFfiCallbackId(entry_count_before);
} else {
ASSERT(function.FfiCallbackId() != -1);
}
}
return function.ptr();
}
} // namespace ffi
} // namespace compiler
} // namespace dart