Flutter Engine
The Flutter Engine
ffi_callback_metadata.cc
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
6
8#include "vm/dart_api_state.h"
9#include "vm/flag_list.h"
10#include "vm/object.h"
11#include "vm/runtime_entry.h"
12#include "vm/stub_code.h"
13
14namespace dart {
15
16FfiCallbackMetadata::FfiCallbackMetadata() {}
17
18void FfiCallbackMetadata::EnsureStubPageLocked() {
20 if (stub_page_ != nullptr) {
21 return;
22 }
23
25
26 const Code& trampoline_code = StubCode::FfiCallbackTrampoline();
27 const uword code_start = trampoline_code.EntryPoint();
28 const uword code_end = code_start + trampoline_code.Size();
29 const uword page_start = code_start & ~(VirtualMemory::PageSize() - 1);
30
31 ASSERT_LESS_OR_EQUAL((code_start - page_start) + trampoline_code.Size(),
33
34 // Stub page uses a tight (unaligned) bound for the end of the code area.
35 // Otherwise we can read past the end of the code area when doing DuplicateRX.
36 stub_page_ = VirtualMemory::ForImagePage(reinterpret_cast<void*>(page_start),
37 code_end - page_start);
38
39 offset_of_first_trampoline_in_page_ = code_start - page_start;
40
41#if defined(DART_TARGET_OS_FUCHSIA)
42 // On Fuchsia we can't currently duplicate pages, so use the first page of
43 // trampolines. Store the stub page's metadata in a separately allocated RW
44 // page.
45 // TODO(https://dartbug.com/52579): Remove.
46 fuchsia_metadata_page_ = VirtualMemory::AllocateAligned(
47 MappingSize(), MappingAlignment(), /*is_executable=*/false,
48 /*is_compressed=*/false, "FfiCallbackMetadata::TrampolinePage");
49 Metadata* metadata = reinterpret_cast<Metadata*>(
50 fuchsia_metadata_page_->start() + MetadataOffset());
51 for (intptr_t i = 0; i < NumCallbackTrampolinesPerPage(); ++i) {
52 AddToFreeListLocked(&metadata[i]);
53 }
54#endif // defined(DART_TARGET_OS_FUCHSIA)
55}
56
57FfiCallbackMetadata::~FfiCallbackMetadata() {
58 // Unmap all the trampoline pages. 'VirtualMemory's are new-allocated.
59 delete stub_page_;
60 for (intptr_t i = 0; i < trampoline_pages_.length(); ++i) {
61 delete trampoline_pages_[i];
62 }
63
64#if defined(DART_TARGET_OS_FUCHSIA)
65 // TODO(https://dartbug.com/52579): Remove.
66 delete fuchsia_metadata_page_;
67#endif // defined(DART_TARGET_OS_FUCHSIA)
68}
69
71 ASSERT(singleton_ == nullptr);
72 singleton_ = new FfiCallbackMetadata();
73}
74
76 ASSERT(singleton_ != nullptr);
77 delete singleton_;
78 singleton_ = nullptr;
79}
80
82 ASSERT(singleton_ != nullptr);
83 return singleton_;
84}
85
86void FfiCallbackMetadata::FillRuntimeFunction(VirtualMemory* page,
87 uword index,
88 void* function) {
89 void** slot =
90 reinterpret_cast<void**>(page->start() + RuntimeFunctionOffset(index));
91 *slot = function;
92}
93
94VirtualMemory* FfiCallbackMetadata::AllocateTrampolinePage() {
95#if defined(DART_TARGET_OS_FUCHSIA)
96 // TODO(https://dartbug.com/52579): Remove.
98 return nullptr;
99#else
100
101#if defined(DART_HOST_OS_MACOS) && !defined(DART_PRECOMPILED_RUNTIME)
102 // If we are not going to use vm_remap then we need to pass
103 // is_executable=true so that pages get allocated with MAP_JIT flag if
104 // necessary. Otherwise OS will kill us with a codesigning violation if
105 // hardened runtime is enabled.
106 const bool is_executable = true;
107#else
108 const bool is_executable = false;
109#endif
110
111 VirtualMemory* new_page = VirtualMemory::AllocateAligned(
112 MappingSize(), MappingAlignment(), is_executable,
113 /*is_compressed=*/false, "FfiCallbackMetadata::TrampolinePage");
114 if (new_page == nullptr) {
115 return nullptr;
116 }
117
118 if (!stub_page_->DuplicateRX(new_page)) {
119 delete new_page;
120 return nullptr;
121 }
122
123#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
124 if (FLAG_support_disassembler && FLAG_disassemble_stubs) {
125 DisassembleToStdout formatter;
126 THR_Print("Code for duplicated stub 'FfiCallbackTrampoline' {\n");
127 const uword code_start =
128 new_page->start() + offset_of_first_trampoline_in_page_;
129 Disassembler::Disassemble(code_start, code_start + kPageSize, &formatter,
130 /*comments=*/nullptr);
131 THR_Print("}\n");
132 }
133#endif
134
135 return new_page;
136#endif // defined(DART_TARGET_OS_FUCHSIA)
137}
138
139void FfiCallbackMetadata::EnsureFreeListNotEmptyLocked() {
141 EnsureStubPageLocked();
142
143 if (free_list_head_ != nullptr) {
144 return;
145 }
146
147 VirtualMemory* new_page = AllocateTrampolinePage();
148 if (new_page == nullptr) {
150 }
151 trampoline_pages_.Add(new_page);
152
153 // Fill in the runtime functions.
154 FillRuntimeFunction(new_page, kGetFfiCallbackMetadata,
155 reinterpret_cast<void*>(DLRT_GetFfiCallbackMetadata));
156 FillRuntimeFunction(new_page, kExitTemporaryIsolate,
157 reinterpret_cast<void*>(DLRT_ExitTemporaryIsolate));
158
159 // Add all the trampolines to the free list.
160 const intptr_t trampolines_per_page = NumCallbackTrampolinesPerPage();
161 Metadata* metadata =
162 reinterpret_cast<Metadata*>(new_page->start() + MetadataOffset());
163 for (intptr_t i = 0; i < trampolines_per_page; ++i) {
164 AddToFreeListLocked(&metadata[i]);
165 }
166}
167
168FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateMetadataEntry(
169 Isolate* target_isolate,
170 TrampolineType trampoline_type,
171 uword target_entry_point,
172 uint64_t context,
173 Metadata** list_head) {
174 MutexLocker locker(&lock_);
175 EnsureFreeListNotEmptyLocked();
176 ASSERT(free_list_head_ != nullptr);
177 Metadata* entry = free_list_head_;
178 free_list_head_ = entry->free_list_next_;
179 if (free_list_head_ == nullptr) {
180 ASSERT(free_list_tail_ == entry);
181 free_list_tail_ = nullptr;
182 }
183 Metadata* next_entry = *list_head;
184 if (next_entry != nullptr) {
185 ASSERT(next_entry->list_prev_ == nullptr);
186 next_entry->list_prev_ = entry;
187 }
188 *entry = Metadata(target_isolate, trampoline_type, target_entry_point,
189 context, nullptr, next_entry);
190 *list_head = entry;
191 return TrampolineOfMetadata(entry);
192}
193
194void FfiCallbackMetadata::AddToFreeListLocked(Metadata* entry) {
196 if (free_list_tail_ == nullptr) {
197 ASSERT(free_list_head_ == nullptr);
198 free_list_head_ = free_list_tail_ = entry;
199 } else {
200 ASSERT(free_list_head_ != nullptr && free_list_tail_ != nullptr);
201 ASSERT(!free_list_tail_->IsLive());
202 free_list_tail_->free_list_next_ = entry;
203 free_list_tail_ = entry;
204 }
205 entry->context_ = 0;
206 entry->target_isolate_ = nullptr;
207 entry->free_list_next_ = nullptr;
208}
209
210void FfiCallbackMetadata::DeleteCallbackLocked(Metadata* entry) {
212 if (entry->trampoline_type_ != TrampolineType::kAsync &&
213 entry->context_ != 0) {
214 ASSERT(entry->target_isolate_ != nullptr);
215 auto* api_state = entry->target_isolate_->group()->api_state();
216 ASSERT(api_state != nullptr);
217 api_state->FreePersistentHandle(entry->closure_handle());
218 }
219 AddToFreeListLocked(entry);
220}
221
223 MutexLocker locker(&lock_);
224 for (Metadata* entry = *list_head; entry != nullptr;) {
225 Metadata* next = entry->list_next();
226 DeleteCallbackLocked(entry);
227 entry = next;
228 }
229 *list_head = nullptr;
230}
231
233 Metadata** list_head) {
234 MutexLocker locker(&lock_);
235 auto* entry = MetadataOfTrampoline(trampoline);
236 ASSERT(entry->IsLive());
237 auto* prev = entry->list_prev_;
238 auto* next = entry->list_next_;
239 if (prev != nullptr) {
240 prev->list_next_ = next;
241 } else {
242 ASSERT(*list_head == entry);
243 *list_head = next;
244 }
245 if (next != nullptr) {
246 next->list_prev_ = prev;
247 }
248 DeleteCallbackLocked(entry);
249}
250
251uword FfiCallbackMetadata::GetEntryPoint(Zone* zone, const Function& function) {
252 const auto& code =
253 Code::Handle(zone, FLAG_precompiled_mode ? function.CurrentCode()
254 : function.EnsureHasCode());
255 ASSERT(!code.IsNull());
256 return code.EntryPoint();
257}
258
259PersistentHandle* FfiCallbackMetadata::CreatePersistentHandle(
260 Isolate* isolate,
261 const Closure& closure) {
262 auto* api_state = isolate->group()->api_state();
263 ASSERT(api_state != nullptr);
264 auto* handle = api_state->AllocatePersistentHandle();
265 handle->set_ptr(closure);
266 return handle;
267}
268
271 Zone* zone,
272 const Function& function,
273 const Closure& closure,
274 Metadata** list_head) {
275 if (closure.IsNull()) {
276 // If the closure is null, it means the target is a static function, so is
277 // baked into the trampoline and is an ordinary sync callback.
278 ASSERT(function.GetFfiCallbackKind() ==
280 return CreateSyncFfiCallbackImpl(isolate, zone, function, nullptr,
281 list_head);
282 } else {
283 ASSERT(function.GetFfiCallbackKind() ==
285 return CreateSyncFfiCallbackImpl(isolate, zone, function,
286 CreatePersistentHandle(isolate, closure),
287 list_head);
288 }
289}
290
291FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateSyncFfiCallbackImpl(
292 Isolate* isolate,
293 Zone* zone,
294 const Function& function,
296 Metadata** list_head) {
297 TrampolineType trampoline_type = TrampolineType::kSync;
298
299#if defined(TARGET_ARCH_IA32)
300 // On ia32, store the stack delta that we need to use when returning.
301 const intptr_t stack_return_delta =
302 function.FfiCSignatureReturnsStruct() && CallingConventions::kUsesRet4
304 : 0;
305 if (stack_return_delta != 0) {
306 ASSERT(stack_return_delta == 4);
307 trampoline_type = TrampolineType::kSyncStackDelta4;
308 }
309#endif
310
311 return CreateMetadataEntry(isolate, trampoline_type,
312 GetEntryPoint(zone, function),
313 reinterpret_cast<uint64_t>(closure), list_head);
314}
315
317 Isolate* isolate,
318 Zone* zone,
319 const Function& send_function,
320 Dart_Port send_port,
321 Metadata** list_head) {
323 return CreateMetadataEntry(isolate, TrampolineType::kAsync,
324 GetEntryPoint(zone, send_function),
325 static_cast<uint64_t>(send_port), list_head);
326}
327
329 Metadata* metadata) const {
330 const uword start = MappingStart(reinterpret_cast<uword>(metadata));
331 Metadata* metadatas = reinterpret_cast<Metadata*>(start + MetadataOffset());
332 const uword index = metadata - metadatas;
333#if defined(DART_TARGET_OS_FUCHSIA)
334 return StubCode::FfiCallbackTrampoline().EntryPoint() +
335 index * kNativeCallbackTrampolineSize;
336#else
337 return start + offset_of_first_trampoline_in_page_ +
338 index * kNativeCallbackTrampolineSize;
339#endif
340}
341
343 Trampoline trampoline) const {
344#if defined(DART_TARGET_OS_FUCHSIA)
345 // On Fuchsia the metadata page is separate to the trampoline page.
346 // TODO(https://dartbug.com/52579): Remove.
347 const uword page_start =
348 Utils::RoundDown(trampoline - offset_of_first_trampoline_in_page_,
350 const uword index =
351 (trampoline - offset_of_first_trampoline_in_page_ - page_start) /
352 kNativeCallbackTrampolineSize;
354 Metadata* metadata_table = reinterpret_cast<Metadata*>(
355 fuchsia_metadata_page_->start() + MetadataOffset());
356 return metadata_table + index;
357#else
358 const uword start = MappingStart(trampoline);
359 Metadata* metadatas = reinterpret_cast<Metadata*>(start + MetadataOffset());
360 const uword index =
361 (trampoline - start - offset_of_first_trampoline_in_page_) /
362 kNativeCallbackTrampolineSize;
363 return &metadatas[index];
364#endif
365}
366
368 Trampoline trampoline) const {
369 return *MetadataOfTrampoline(trampoline);
370}
371
372FfiCallbackMetadata* FfiCallbackMetadata::singleton_ = nullptr;
373
374} // namespace dart
static float next(float f)
static float prev(float f)
#define UNREACHABLE()
Definition: assert.h:248
#define ASSERT_LESS_OR_EQUAL(expected, actual)
Definition: assert.h:313
static constexpr bool kUsesRet4
static void Disassemble(uword start, uword end, DisassemblyFormatter *formatter, const Code &code, const CodeComments *comments=nullptr)
static DART_NORETURN void ThrowOOM()
Definition: exceptions.cc:1066
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()
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 MetadataOffset()
static constexpr intptr_t kPageSize
void DeleteAllCallbacks(Metadata **list_head)
FfiCallbackKind GetFfiCallbackKind() const
Definition: object.cc:8405
bool IsOwnedByCurrentThread() const
Definition: os_thread.h:402
static Object & Handle()
Definition: object.h:407
static constexpr T RoundDown(T x, intptr_t alignment)
Definition: utils.h:108
static VirtualMemory * AllocateAligned(intptr_t size, intptr_t alignment, bool is_executable, bool is_compressed, const char *name)
static intptr_t PageSize()
bool DuplicateRX(VirtualMemory *target)
static VirtualMemory * ForImagePage(void *pointer, uword size)
#define THR_Print(format,...)
Definition: log.h:20
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
Thread * DLRT_GetFfiCallbackMetadata(FfiCallbackMetadata::Trampoline trampoline, uword *out_entry_point, uword *out_trampoline_type)
uintptr_t uword
Definition: globals.h:501
void DLRT_ExitTemporaryIsolate()
std::function< void()> closure
Definition: closure.h:14