mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:29:48 +00:00
29509e0124
The constants in [0] were not quite correct for riscv. [0] https://dart-review.googlesource.com/c/sdk/+/305900 TEST=ci Change-Id: I194cde2541293935ef89861f3d1607c1913068f9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/311743 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Liam Appelbe <liama@google.com>
297 lines
11 KiB
C++
297 lines
11 KiB
C++
// 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.
|
|
#ifndef RUNTIME_VM_FFI_CALLBACK_METADATA_H_
|
|
#define RUNTIME_VM_FFI_CALLBACK_METADATA_H_
|
|
|
|
#include "platform/growable_array.h"
|
|
#include "platform/utils.h"
|
|
#include "vm/hash_map.h"
|
|
#include "vm/lockers.h"
|
|
#include "vm/virtual_memory.h"
|
|
|
|
namespace dart {
|
|
|
|
// Stores metadata related to FFI callbacks (Dart functions that are assigned a
|
|
// function pointer that can be invoked by native code). This is essentially a
|
|
// map from trampoline pointer to Metadata, with some logic to assign and memory
|
|
// manage those trampolines.
|
|
//
|
|
// In the past, callbacks were primarily identified by an integer ID, but in
|
|
// this class we identify them by their trampoline pointer to solve a very
|
|
// specific issue. The trampolines are allocated in pages. On iOS in AOT mode,
|
|
// we can't create new executable memory, but we can duplicate existing memory.
|
|
// When we were using numeric IDs to identify the trampolines, each trampoline
|
|
// page was different, because the IDs were embedded in the machine code. So we
|
|
// couldn't use trampolines in AOT mode. But if we key the metadata table by the
|
|
// trampoline pointer, then the trampoline just has to look up the PC at the
|
|
// start of the trampoline function, so the machine code will always be the
|
|
// same. This means we can just duplicate the trampoline page, allowing us to
|
|
// unify the FFI callback implementation across JIT and AOT, even on iOS.
|
|
class FfiCallbackMetadata {
|
|
public:
|
|
class Metadata;
|
|
|
|
// The address of the allocated trampoline.
|
|
using Trampoline = uword;
|
|
|
|
enum class TrampolineType : uint8_t {
|
|
kSync = 0,
|
|
kAsync = 1,
|
|
#if defined(TARGET_ARCH_IA32)
|
|
kSyncStackDelta4 = 2,
|
|
#endif
|
|
};
|
|
|
|
enum RuntimeFunctions {
|
|
kGetFfiCallbackMetadata,
|
|
kExitTemporaryIsolate,
|
|
kNumRuntimeFunctions,
|
|
};
|
|
|
|
static void Init();
|
|
static void Cleanup();
|
|
|
|
// Returns the FfiCallbackMetadata singleton.
|
|
static FfiCallbackMetadata* Instance();
|
|
|
|
// Creates a sync callback trampoline for the given function.
|
|
Trampoline CreateSyncFfiCallback(Isolate* isolate,
|
|
Zone* zone,
|
|
const Function& function,
|
|
Metadata** list_head);
|
|
|
|
// Creates an async callback trampoline for the given function and associates
|
|
// it with the send_port.
|
|
Trampoline CreateAsyncFfiCallback(Isolate* isolate,
|
|
Zone* zone,
|
|
const Function& function,
|
|
Dart_Port send_port,
|
|
Metadata** list_head);
|
|
|
|
// Deletes a single trampoline.
|
|
void DeleteCallback(Trampoline trampoline, Metadata** list_head);
|
|
|
|
// Deletes all the trampolines in the list.
|
|
void DeleteAllCallbacks(Metadata** list_head);
|
|
|
|
// FFI callback metadata for any sync or async trampoline.
|
|
class Metadata {
|
|
Isolate* target_isolate_;
|
|
TrampolineType trampoline_type_;
|
|
|
|
union {
|
|
// IsLive()
|
|
struct {
|
|
// Note: This is a pointer into an an Instructions object. This is only
|
|
// safe because Instructions objects are never moved by the GC.
|
|
uword target_entry_point_;
|
|
|
|
Dart_Port send_port_;
|
|
|
|
// Links in the Isolate's list of callbacks.
|
|
Metadata* list_prev_;
|
|
Metadata* list_next_;
|
|
};
|
|
|
|
// !IsLive()
|
|
Metadata* free_list_next_;
|
|
};
|
|
|
|
Metadata(Isolate* target_isolate,
|
|
TrampolineType trampoline_type,
|
|
uword target_entry_point,
|
|
Dart_Port send_port,
|
|
Metadata* list_prev,
|
|
Metadata* list_next)
|
|
: target_isolate_(target_isolate),
|
|
trampoline_type_(trampoline_type),
|
|
target_entry_point_(target_entry_point),
|
|
send_port_(send_port),
|
|
list_prev_(list_prev),
|
|
list_next_(list_next) {}
|
|
|
|
public:
|
|
friend class FfiCallbackMetadata;
|
|
bool IsSameCallback(const Metadata& other) const {
|
|
// Not checking the list links, because they can change when other
|
|
// callbacks are deleted.
|
|
return target_isolate_ == other.target_isolate_ &&
|
|
trampoline_type_ == other.trampoline_type_ &&
|
|
target_entry_point_ == other.target_entry_point_ &&
|
|
send_port_ == other.send_port_;
|
|
}
|
|
|
|
// Whether the callback is still alive.
|
|
bool IsLive() const { return target_isolate_ != 0; }
|
|
|
|
// The target isolate. The isolate that owns the callback. Sync callbacks
|
|
// must be invoked on this isolate. Async callbacks will send a message to
|
|
// this isolate.
|
|
Isolate* target_isolate() const {
|
|
ASSERT(IsLive());
|
|
return target_isolate_;
|
|
}
|
|
|
|
// The Dart entrypoint for the callback, which the trampoline invokes.
|
|
uword target_entry_point() const {
|
|
ASSERT(IsLive());
|
|
return target_entry_point_;
|
|
}
|
|
|
|
// The send port that the async callback will send a message to.
|
|
Dart_Port send_port() const {
|
|
ASSERT(IsLive());
|
|
return send_port_;
|
|
}
|
|
|
|
// To efficiently delete all the callbacks for a isolate, they are stored in
|
|
// a linked list. Since we also need to delete async callbacks at arbitrary
|
|
// times, the list must be doubly linked.
|
|
Metadata* list_prev() {
|
|
ASSERT(IsLive());
|
|
return list_prev_;
|
|
}
|
|
Metadata* list_next() {
|
|
ASSERT(IsLive());
|
|
return list_next_;
|
|
}
|
|
|
|
// Tells FfiCallbackTrampolineStub how to call into the entry point. Mostly
|
|
// it's just a flag for whether this is a sync or async callback, but on
|
|
// IA32 it also encodes whether there's a stack delta of 4 to deal with.
|
|
TrampolineType trampoline_type() const {
|
|
return trampoline_type_;
|
|
}
|
|
};
|
|
|
|
// Returns the Metadata object for the given trampoline.
|
|
Metadata LookupMetadataForTrampoline(Trampoline trampoline) const;
|
|
|
|
// The mutex that guards creation and destruction of callbacks.
|
|
Mutex* lock() { return &lock_; }
|
|
|
|
// The number of trampolines that can be stored on a single page.
|
|
static constexpr intptr_t NumCallbackTrampolinesPerPage() {
|
|
return (kPageSize - kNativeCallbackSharedStubSize) /
|
|
kNativeCallbackTrampolineSize;
|
|
}
|
|
|
|
// Size of the trampoline page. Ideally we'd use VirtualMemory::PageSize(),
|
|
// but that varies across machines, and we need it to be consistent between
|
|
// host and target since it affects stub code generation. So kPageSize may be
|
|
// an overestimate of the target's VirtualMemory::PageSize(), but we try to
|
|
// get it as close as possible to avoid wasting memory.
|
|
#if defined(DART_TARGET_OS_LINUX) && defined(TARGET_ARCH_ARM64)
|
|
static constexpr intptr_t kPageSize = 64 * KB;
|
|
#elif defined(DART_TARGET_OS_MACOS) && defined(TARGET_ARCH_ARM64)
|
|
static constexpr intptr_t kPageSize = 16 * KB;
|
|
#elif defined(DART_TARGET_OS_FUCHSIA)
|
|
// Fuchsia only gets one page, so make it big.
|
|
// TODO(https://dartbug.com/52579): Remove.
|
|
static constexpr intptr_t kPageSize = 64 * KB;
|
|
#else
|
|
static constexpr intptr_t kPageSize = 4 * KB;
|
|
#endif
|
|
static constexpr intptr_t kPageMask = ~(kPageSize - 1);
|
|
|
|
// Each time we allocate new virtual memory for trampolines we allocate an
|
|
// [RX][RW] area:
|
|
//
|
|
// * [RX] 2 pages fully containing [StubCode::FfiCallbackTrampoline()]
|
|
// * [RW] pages sufficient to hold
|
|
// - `kNumRuntimeFunctions` x [uword] function pointers
|
|
// - `NumCallbackTrampolinesPerPage()` x [Metadata] objects
|
|
static constexpr intptr_t RXMappingSize() { return 2 * kPageSize; }
|
|
static constexpr intptr_t RWMappingSize() {
|
|
return Utils::RoundUp(
|
|
kNumRuntimeFunctions * compiler::target::kWordSize +
|
|
sizeof(Metadata) * NumCallbackTrampolinesPerPage(),
|
|
kPageSize);
|
|
}
|
|
static constexpr intptr_t MappingSize() {
|
|
return RXMappingSize() + RWMappingSize();
|
|
}
|
|
static constexpr intptr_t MappingAlignment() {
|
|
return Utils::RoundUpToPowerOfTwo(MappingSize());
|
|
}
|
|
static constexpr intptr_t MappingStart(uword address) {
|
|
const uword mask = MappingAlignment() - 1;
|
|
return address & ~mask;
|
|
}
|
|
static constexpr uword RuntimeFunctionOffset(uword function_index) {
|
|
return RXMappingSize() + function_index * compiler::target::kWordSize;
|
|
}
|
|
static constexpr intptr_t MetadataOffset() {
|
|
return RuntimeFunctionOffset(kNumRuntimeFunctions);
|
|
}
|
|
|
|
#if defined(TARGET_ARCH_X64)
|
|
static constexpr intptr_t kNativeCallbackTrampolineSize = 12;
|
|
static constexpr intptr_t kNativeCallbackSharedStubSize = 289;
|
|
static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 2;
|
|
#elif defined(TARGET_ARCH_IA32)
|
|
static constexpr intptr_t kNativeCallbackTrampolineSize = 10;
|
|
static constexpr intptr_t kNativeCallbackSharedStubSize = 146;
|
|
static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 4;
|
|
#elif defined(TARGET_ARCH_ARM)
|
|
static constexpr intptr_t kNativeCallbackTrampolineSize = 8;
|
|
static constexpr intptr_t kNativeCallbackSharedStubSize = 232;
|
|
static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 4;
|
|
#elif defined(TARGET_ARCH_ARM64)
|
|
static constexpr intptr_t kNativeCallbackTrampolineSize = 8;
|
|
static constexpr intptr_t kNativeCallbackSharedStubSize = 320;
|
|
static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 2;
|
|
#elif defined(TARGET_ARCH_RISCV32)
|
|
static constexpr intptr_t kNativeCallbackTrampolineSize = 8;
|
|
static constexpr intptr_t kNativeCallbackSharedStubSize = 284;
|
|
static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 2;
|
|
#elif defined(TARGET_ARCH_RISCV64)
|
|
static constexpr intptr_t kNativeCallbackTrampolineSize = 8;
|
|
static constexpr intptr_t kNativeCallbackSharedStubSize = 252;
|
|
static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 2;
|
|
#else
|
|
#error What architecture?
|
|
#endif
|
|
|
|
// Visible for testing.
|
|
Metadata* MetadataOfTrampoline(Trampoline trampoline) const;
|
|
Trampoline TrampolineOfMetadata(Metadata* metadata) const;
|
|
|
|
private:
|
|
FfiCallbackMetadata();
|
|
~FfiCallbackMetadata();
|
|
void EnsureStubPageLocked();
|
|
void AddToFreeListLocked(Metadata* entry);
|
|
void FillRuntimeFunction(VirtualMemory* page, uword index, void* function);
|
|
VirtualMemory* AllocateTrampolinePage();
|
|
void EnsureFreeListNotEmptyLocked();
|
|
Trampoline CreateMetadataEntry(Isolate* target_isolate,
|
|
TrampolineType trampoline_type,
|
|
uword target_entry_point,
|
|
Dart_Port send_port,
|
|
Metadata** list_head);
|
|
Trampoline TryAllocateFromFreeListLocked();
|
|
static uword GetEntryPoint(Zone* zone, const Function& function);
|
|
|
|
static FfiCallbackMetadata* singleton_;
|
|
|
|
mutable Mutex lock_;
|
|
VirtualMemory* stub_page_ = nullptr;
|
|
MallocGrowableArray<VirtualMemory*> trampoline_pages_;
|
|
uword offset_of_first_trampoline_in_page_ = 0;
|
|
Metadata* free_list_head_ = nullptr;
|
|
Metadata* free_list_tail_ = nullptr;
|
|
|
|
#if defined(DART_TARGET_OS_FUCHSIA)
|
|
// TODO(https://dartbug.com/52579): Remove.
|
|
VirtualMemory* fuchsia_metadata_page_ = nullptr;
|
|
#endif // defined(DART_TARGET_OS_FUCHSIA)
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(FfiCallbackMetadata);
|
|
};
|
|
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_VM_FFI_CALLBACK_METADATA_H_
|