Flutter Engine
The Flutter Engine
ffi_callback_metadata.h
Go to the documentation of this file.
1// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4#ifndef RUNTIME_VM_FFI_CALLBACK_METADATA_H_
5#define RUNTIME_VM_FFI_CALLBACK_METADATA_H_
6
8#include "platform/utils.h"
9#include "vm/hash_map.h"
10#include "vm/lockers.h"
11#include "vm/virtual_memory.h"
12
13namespace dart {
14
15class PersistentHandle;
16
17// Stores metadata related to FFI callbacks (Dart functions that are assigned a
18// function pointer that can be invoked by native code). This is essentially a
19// map from trampoline pointer to Metadata, with some logic to assign and memory
20// manage those trampolines.
21//
22// In the past, callbacks were primarily identified by an integer ID, but in
23// this class we identify them by their trampoline pointer to solve a very
24// specific issue. The trampolines are allocated in pages. On iOS in AOT mode,
25// we can't create new executable memory, but we can duplicate existing memory.
26// When we were using numeric IDs to identify the trampolines, each trampoline
27// page was different, because the IDs were embedded in the machine code. So we
28// couldn't use trampolines in AOT mode. But if we key the metadata table by the
29// trampoline pointer, then the trampoline just has to look up the PC at the
30// start of the trampoline function, so the machine code will always be the
31// same. This means we can just duplicate the trampoline page, allowing us to
32// unify the FFI callback implementation across JIT and AOT, even on iOS.
34 public:
35 class Metadata;
36
37 // The address of the allocated trampoline.
39
40 enum class TrampolineType : uint8_t {
41 kSync = 0,
42 kSyncStackDelta4 = 1, // Only used by TARGET_ARCH_IA32
43 kAsync = 2,
44 };
45
50 };
51
52 static void Init();
53 static void Cleanup();
54
55 // Returns the FfiCallbackMetadata singleton.
57
58 // Creates an async callback trampoline for the given function and associates
59 // it with the send_port.
61 Zone* zone,
62 const Function& function,
63 Dart_Port send_port,
64 Metadata** list_head);
65
66 // Creates an isolate local callback trampoline for the given function.
68 Zone* zone,
69 const Function& function,
70 const Closure& closure,
71 Metadata** list_head);
72
73 // Deletes a single trampoline.
74 void DeleteCallback(Trampoline trampoline, Metadata** list_head);
75
76 // Deletes all the trampolines in the list.
77 void DeleteAllCallbacks(Metadata** list_head);
78
79 // FFI callback metadata for any sync or async trampoline.
80 class Metadata {
81 Isolate* target_isolate_;
82 TrampolineType trampoline_type_;
83
84 union {
85 // IsLive()
86 struct {
87 // Note: This is a pointer into an an Instructions object. This is only
88 // safe because Instructions objects are never moved by the GC.
90
91 // For async callbacks, this is the send port. For sync callbacks this
92 // is a persistent handle to the callback's closure, or null.
93 uint64_t context_;
94
95 // Links in the Isolate's list of callbacks.
98 };
99
100 // !IsLive()
102 };
103
107 uint64_t context,
110 : target_isolate_(target_isolate),
111 trampoline_type_(trampoline_type),
116
117 public:
119 bool IsSameCallback(const Metadata& other) const {
120 // Not checking the list links, because they can change when other
121 // callbacks are deleted.
122 return target_isolate_ == other.target_isolate_ &&
123 trampoline_type_ == other.trampoline_type_ &&
125 context_ == other.context_;
126 }
127
128 // Whether the callback is still alive.
129 bool IsLive() const { return target_isolate_ != 0; }
130
131 // The target isolate. The isolate that owns the callback. Sync callbacks
132 // must be invoked on this isolate. Async callbacks will send a message to
133 // this isolate.
135 ASSERT(IsLive());
136 return target_isolate_;
137 }
138
139 // The Dart entrypoint for the callback, which the trampoline invokes.
141 ASSERT(IsLive());
142 return target_entry_point_;
143 }
144
145 // The persistent handle to the closure that the NativeCallable.isolateLocal
146 // is wrapping.
148 ASSERT(IsLive());
149 ASSERT(trampoline_type_ == TrampolineType::kSync ||
150 trampoline_type_ == TrampolineType::kSyncStackDelta4);
151 return reinterpret_cast<PersistentHandle*>(context_);
152 }
153
154 // For async callbacks, this is the send port. For sync callbacks this is a
155 // persistent handle to the callback's closure, or null.
156 uint64_t context() const {
157 ASSERT(IsLive());
158 return context_;
159 }
160
161 // The send port that the async callback will send a message to.
163 ASSERT(IsLive());
164 ASSERT(trampoline_type_ == TrampolineType::kAsync);
165 return static_cast<Dart_Port>(context_);
166 }
167
168 // To efficiently delete all the callbacks for a isolate, they are stored in
169 // a linked list. Since we also need to delete async callbacks at arbitrary
170 // times, the list must be doubly linked.
172 ASSERT(IsLive());
173 return list_prev_;
174 }
176 ASSERT(IsLive());
177 return list_next_;
178 }
179
180 // Tells FfiCallbackTrampolineStub how to call into the entry point. Mostly
181 // it's just a flag for whether this is a sync or async callback, but on
182 // IA32 it also encodes whether there's a stack delta of 4 to deal with.
183 TrampolineType trampoline_type() const { return trampoline_type_; }
184 };
185
186 // Returns the Metadata object for the given trampoline.
187 Metadata LookupMetadataForTrampoline(Trampoline trampoline) const;
188
189 // The mutex that guards creation and destruction of callbacks.
190 Mutex* lock() { return &lock_; }
191
192 // The number of trampolines that can be stored on a single page.
193 static constexpr intptr_t NumCallbackTrampolinesPerPage() {
194 return (kPageSize - kNativeCallbackSharedStubSize) /
195 kNativeCallbackTrampolineSize;
196 }
197
198 // Size of the trampoline page. Ideally we'd use VirtualMemory::PageSize(),
199 // but that varies across machines, and we need it to be consistent between
200 // host and target since it affects stub code generation. So kPageSize may be
201 // an overestimate of the target's VirtualMemory::PageSize(), but we try to
202 // get it as close as possible to avoid wasting memory.
203#if defined(DART_TARGET_OS_LINUX) && defined(TARGET_ARCH_ARM64)
204 static constexpr intptr_t kPageSize = 64 * KB;
205#elif defined(DART_TARGET_OS_ANDROID) && defined(TARGET_ARCH_IS_64_BIT)
206 static constexpr intptr_t kPageSize = 64 * KB;
207#elif defined(DART_TARGET_OS_MACOS) && defined(TARGET_ARCH_ARM64)
208 static constexpr intptr_t kPageSize = 16 * KB;
209#elif defined(DART_TARGET_OS_FUCHSIA)
210 // Fuchsia only gets one page, so make it big.
211 // TODO(https://dartbug.com/52579): Remove.
212 static constexpr intptr_t kPageSize = 64 * KB;
213#else
214 static constexpr intptr_t kPageSize = 4 * KB;
215#endif
216 static constexpr intptr_t kPageMask = ~(kPageSize - 1);
217
218 // Each time we allocate new virtual memory for trampolines we allocate an
219 // [RX][RW] area:
220 //
221 // * [RX] 2 pages fully containing [StubCode::FfiCallbackTrampoline()]
222 // * [RW] pages sufficient to hold
223 // - `kNumRuntimeFunctions` x [uword] function pointers
224 // - `NumCallbackTrampolinesPerPage()` x [Metadata] objects
225 static constexpr intptr_t RXMappingSize() { return 2 * kPageSize; }
226 static constexpr intptr_t RWMappingSize() {
227 return Utils::RoundUp(
230 kPageSize);
231 }
232 static constexpr intptr_t MappingSize() {
233 return RXMappingSize() + RWMappingSize();
234 }
235 static constexpr intptr_t MappingAlignment() {
237 }
238 static constexpr intptr_t MappingStart(uword address) {
239 const uword mask = MappingAlignment() - 1;
240 return address & ~mask;
241 }
242 static constexpr uword RuntimeFunctionOffset(uword function_index) {
243 return RXMappingSize() + function_index * compiler::target::kWordSize;
244 }
245 static constexpr intptr_t MetadataOffset() {
247 }
248
249#if defined(TARGET_ARCH_X64)
250 static constexpr intptr_t kNativeCallbackTrampolineSize = 12;
251 static constexpr intptr_t kNativeCallbackSharedStubSize = 289;
252 static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 2;
253#elif defined(TARGET_ARCH_IA32)
254 static constexpr intptr_t kNativeCallbackTrampolineSize = 10;
255 static constexpr intptr_t kNativeCallbackSharedStubSize = 146;
256 static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 4;
257#elif defined(TARGET_ARCH_ARM)
258 static constexpr intptr_t kNativeCallbackTrampolineSize = 8;
259 static constexpr intptr_t kNativeCallbackSharedStubSize = 232;
260 static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 4;
261#elif defined(TARGET_ARCH_ARM64)
262 static constexpr intptr_t kNativeCallbackTrampolineSize = 8;
263 static constexpr intptr_t kNativeCallbackSharedStubSize = 332;
264 static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 2;
265#elif defined(TARGET_ARCH_RISCV32)
266 static constexpr intptr_t kNativeCallbackTrampolineSize = 8;
267 static constexpr intptr_t kNativeCallbackSharedStubSize = 284;
268 static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 2;
269#elif defined(TARGET_ARCH_RISCV64)
270 static constexpr intptr_t kNativeCallbackTrampolineSize = 8;
271 static constexpr intptr_t kNativeCallbackSharedStubSize = 252;
272 static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 2;
273#else
274#error What architecture?
275#endif
276
277 // Visible for testing.
278 Metadata* MetadataOfTrampoline(Trampoline trampoline) const;
279 Trampoline TrampolineOfMetadata(Metadata* metadata) const;
280
281 private:
284 void EnsureStubPageLocked();
285 void AddToFreeListLocked(Metadata* entry);
286 void DeleteCallbackLocked(Metadata* entry);
287 void FillRuntimeFunction(VirtualMemory* page, uword index, void* function);
288 VirtualMemory* AllocateTrampolinePage();
289 void EnsureFreeListNotEmptyLocked();
290 Trampoline CreateMetadataEntry(Isolate* target_isolate,
291 TrampolineType trampoline_type,
292 uword target_entry_point,
293 uint64_t context,
294 Metadata** list_head);
295 Trampoline CreateSyncFfiCallbackImpl(Isolate* isolate,
296 Zone* zone,
297 const Function& function,
299 Metadata** list_head);
300 Trampoline TryAllocateFromFreeListLocked();
301 static uword GetEntryPoint(Zone* zone, const Function& function);
302 static PersistentHandle* CreatePersistentHandle(Isolate* isolate,
303 const Closure& closure);
304
305 static FfiCallbackMetadata* singleton_;
306
307 mutable Mutex lock_;
308 VirtualMemory* stub_page_ = nullptr;
309 MallocGrowableArray<VirtualMemory*> trampoline_pages_;
310 uword offset_of_first_trampoline_in_page_ = 0;
311 Metadata* free_list_head_ = nullptr;
312 Metadata* free_list_tail_ = nullptr;
313
314#if defined(DART_TARGET_OS_FUCHSIA)
315 // TODO(https://dartbug.com/52579): Remove.
316 VirtualMemory* fuchsia_metadata_page_ = nullptr;
317#endif // defined(DART_TARGET_OS_FUCHSIA)
318
320};
321
322} // namespace dart
323
324#endif // RUNTIME_VM_FFI_CALLBACK_METADATA_H_
PersistentHandle * closure_handle() const
bool IsSameCallback(const Metadata &other) const
Trampoline TrampolineOfMetadata(Metadata *metadata) const
Metadata LookupMetadataForTrampoline(Trampoline trampoline) const
static constexpr intptr_t MappingAlignment()
static constexpr intptr_t RXMappingSize()
Trampoline CreateIsolateLocalFfiCallback(Isolate *isolate, Zone *zone, const Function &function, const Closure &closure, Metadata **list_head)
static constexpr intptr_t NumCallbackTrampolinesPerPage()
static constexpr intptr_t kPageMask
void DeleteCallback(Trampoline trampoline, Metadata **list_head)
Metadata * MetadataOfTrampoline(Trampoline trampoline) const
static FfiCallbackMetadata * Instance()
Trampoline CreateAsyncFfiCallback(Isolate *isolate, Zone *zone, const Function &function, Dart_Port send_port, Metadata **list_head)
static constexpr uword RuntimeFunctionOffset(uword function_index)
static constexpr intptr_t MappingSize()
static constexpr intptr_t MappingStart(uword address)
static constexpr intptr_t RWMappingSize()
static constexpr intptr_t MetadataOffset()
static constexpr intptr_t kPageSize
void DeleteAllCallbacks(Metadata **list_head)
static constexpr uintptr_t RoundUpToPowerOfTwo(uintptr_t x)
Definition: utils.h:135
static constexpr T RoundUp(T x, uintptr_t alignment, uintptr_t offset=0)
Definition: utils.h:120
int64_t Dart_Port
Definition: dart_api.h:1525
#define ASSERT(E)
Dart_NativeFunction function
Definition: fuchsia.cc:51
static constexpr intptr_t kWordSize
Definition: runtime_api.h:274
Definition: dart_vm.cc:33
constexpr intptr_t KB
Definition: globals.h:528
uintptr_t uword
Definition: globals.h:501
std::function< void()> closure
Definition: closure.h:14
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition: globals.h:581