| // 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. |
| |
| #include "vm/ffi_callback_metadata.h" |
| |
| #include "vm/compiler/assembler/disassembler.h" |
| #include "vm/dart_api_state.h" |
| #include "vm/flag_list.h" |
| #include "vm/object.h" |
| #include "vm/runtime_entry.h" |
| #include "vm/stub_code.h" |
| |
| namespace dart { |
| |
| FfiCallbackMetadata::FfiCallbackMetadata() {} |
| |
| void FfiCallbackMetadata::EnsureStubPageLocked() { |
| ASSERT(lock_.IsOwnedByCurrentThread()); |
| if (stub_page_ != nullptr) { |
| return; |
| } |
| |
| ASSERT_LESS_OR_EQUAL(VirtualMemory::PageSize(), kPageSize); |
| |
| const Code& trampoline_code = StubCode::FfiCallbackTrampoline(); |
| const uword code_start = trampoline_code.EntryPoint(); |
| const uword code_end = code_start + trampoline_code.Size(); |
| const uword page_start = code_start & ~(VirtualMemory::PageSize() - 1); |
| |
| ASSERT_LESS_OR_EQUAL((code_start - page_start) + trampoline_code.Size(), |
| RXMappingSize()); |
| |
| // Stub page uses a tight (unaligned) bound for the end of the code area. |
| // Otherwise we can read past the end of the code area when doing DuplicateRX. |
| stub_page_ = VirtualMemory::ForImagePage(reinterpret_cast<void*>(page_start), |
| code_end - page_start); |
| |
| offset_of_first_trampoline_in_page_ = code_start - page_start; |
| |
| #if defined(DART_TARGET_OS_FUCHSIA) |
| // On Fuchsia we can't currently duplicate pages, so use the first page of |
| // trampolines. Store the stub page's metadata in a separately allocated RW |
| // page. |
| // TODO(https://dartbug.com/52579): Remove. |
| fuchsia_metadata_page_ = VirtualMemory::AllocateAligned( |
| MappingSize(), MappingAlignment(), /*is_executable=*/false, |
| /*is_compressed=*/false, "FfiCallbackMetadata::TrampolinePage"); |
| Metadata* metadata = reinterpret_cast<Metadata*>( |
| fuchsia_metadata_page_->start() + MetadataOffset()); |
| for (intptr_t i = 0; i < NumCallbackTrampolinesPerPage(); ++i) { |
| AddToFreeListLocked(&metadata[i]); |
| } |
| #endif // defined(DART_TARGET_OS_FUCHSIA) |
| } |
| |
| FfiCallbackMetadata::~FfiCallbackMetadata() { |
| // Unmap all the trampoline pages. 'VirtualMemory's are new-allocated. |
| delete stub_page_; |
| for (intptr_t i = 0; i < trampoline_pages_.length(); ++i) { |
| delete trampoline_pages_[i]; |
| } |
| |
| #if defined(DART_TARGET_OS_FUCHSIA) |
| // TODO(https://dartbug.com/52579): Remove. |
| delete fuchsia_metadata_page_; |
| #endif // defined(DART_TARGET_OS_FUCHSIA) |
| } |
| |
| void FfiCallbackMetadata::Init() { |
| ASSERT(singleton_ == nullptr); |
| singleton_ = new FfiCallbackMetadata(); |
| } |
| |
| void FfiCallbackMetadata::Cleanup() { |
| ASSERT(singleton_ != nullptr); |
| delete singleton_; |
| singleton_ = nullptr; |
| } |
| |
| FfiCallbackMetadata* FfiCallbackMetadata::Instance() { |
| ASSERT(singleton_ != nullptr); |
| return singleton_; |
| } |
| |
| void FfiCallbackMetadata::FillRuntimeFunction(VirtualMemory* page, |
| uword index, |
| void* function) { |
| void** slot = |
| reinterpret_cast<void**>(page->start() + RuntimeFunctionOffset(index)); |
| *slot = function; |
| } |
| |
| VirtualMemory* FfiCallbackMetadata::AllocateTrampolinePage() { |
| #if defined(DART_TARGET_OS_FUCHSIA) |
| // TODO(https://dartbug.com/52579): Remove. |
| UNREACHABLE(); |
| return nullptr; |
| #else |
| |
| #if defined(DART_HOST_OS_MACOS) && !defined(DART_PRECOMPILED_RUNTIME) |
| // If we are not going to use vm_remap then we need to pass |
| // is_executable=true so that pages get allocated with MAP_JIT flag if |
| // necessary. Otherwise OS will kill us with a codesigning violation if |
| // hardened runtime is enabled. |
| const bool is_executable = true; |
| #else |
| const bool is_executable = false; |
| #endif |
| |
| VirtualMemory* new_page = VirtualMemory::AllocateAligned( |
| MappingSize(), MappingAlignment(), is_executable, |
| /*is_compressed=*/false, "FfiCallbackMetadata::TrampolinePage"); |
| if (new_page == nullptr) { |
| return nullptr; |
| } |
| |
| if (!stub_page_->DuplicateRX(new_page)) { |
| delete new_page; |
| return nullptr; |
| } |
| |
| #if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) |
| if (FLAG_support_disassembler && FLAG_disassemble_stubs) { |
| DisassembleToStdout formatter; |
| THR_Print("Code for duplicated stub 'FfiCallbackTrampoline' {\n"); |
| const uword code_start = |
| new_page->start() + offset_of_first_trampoline_in_page_; |
| Disassembler::Disassemble(code_start, code_start + kPageSize, &formatter, |
| /*comments=*/nullptr); |
| THR_Print("}\n"); |
| } |
| #endif |
| |
| return new_page; |
| #endif // defined(DART_TARGET_OS_FUCHSIA) |
| } |
| |
| void FfiCallbackMetadata::EnsureFreeListNotEmptyLocked() { |
| ASSERT(lock_.IsOwnedByCurrentThread()); |
| EnsureStubPageLocked(); |
| |
| if (free_list_head_ != nullptr) { |
| return; |
| } |
| |
| VirtualMemory* new_page = AllocateTrampolinePage(); |
| if (new_page == nullptr) { |
| Exceptions::ThrowOOM(); |
| } |
| trampoline_pages_.Add(new_page); |
| |
| // Fill in the runtime functions. |
| FillRuntimeFunction(new_page, kGetFfiCallbackMetadata, |
| reinterpret_cast<void*>(DLRT_GetFfiCallbackMetadata)); |
| FillRuntimeFunction(new_page, kExitTemporaryIsolate, |
| reinterpret_cast<void*>(DLRT_ExitTemporaryIsolate)); |
| |
| // Add all the trampolines to the free list. |
| const intptr_t trampolines_per_page = NumCallbackTrampolinesPerPage(); |
| Metadata* metadata = |
| reinterpret_cast<Metadata*>(new_page->start() + MetadataOffset()); |
| for (intptr_t i = 0; i < trampolines_per_page; ++i) { |
| AddToFreeListLocked(&metadata[i]); |
| } |
| } |
| |
| FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateMetadataEntry( |
| Isolate* target_isolate, |
| TrampolineType trampoline_type, |
| uword target_entry_point, |
| uint64_t context, |
| Metadata** list_head) { |
| MutexLocker locker(&lock_); |
| EnsureFreeListNotEmptyLocked(); |
| ASSERT(free_list_head_ != nullptr); |
| Metadata* entry = free_list_head_; |
| free_list_head_ = entry->free_list_next_; |
| if (free_list_head_ == nullptr) { |
| ASSERT(free_list_tail_ == entry); |
| free_list_tail_ = nullptr; |
| } |
| Metadata* next_entry = *list_head; |
| if (next_entry != nullptr) { |
| ASSERT(next_entry->list_prev_ == nullptr); |
| next_entry->list_prev_ = entry; |
| } |
| *entry = Metadata(target_isolate, trampoline_type, target_entry_point, |
| context, nullptr, next_entry); |
| *list_head = entry; |
| return TrampolineOfMetadata(entry); |
| } |
| |
| void FfiCallbackMetadata::AddToFreeListLocked(Metadata* entry) { |
| ASSERT(lock_.IsOwnedByCurrentThread()); |
| if (free_list_tail_ == nullptr) { |
| ASSERT(free_list_head_ == nullptr); |
| free_list_head_ = free_list_tail_ = entry; |
| } else { |
| ASSERT(free_list_head_ != nullptr && free_list_tail_ != nullptr); |
| ASSERT(!free_list_tail_->IsLive()); |
| free_list_tail_->free_list_next_ = entry; |
| free_list_tail_ = entry; |
| } |
| entry->context_ = 0; |
| entry->target_isolate_ = nullptr; |
| entry->free_list_next_ = nullptr; |
| } |
| |
| void FfiCallbackMetadata::DeleteCallbackLocked(Metadata* entry) { |
| ASSERT(lock_.IsOwnedByCurrentThread()); |
| if (entry->trampoline_type_ != TrampolineType::kAsync && |
| entry->context_ != 0) { |
| ASSERT(entry->target_isolate_ != nullptr); |
| auto* api_state = entry->target_isolate_->group()->api_state(); |
| ASSERT(api_state != nullptr); |
| api_state->FreePersistentHandle(entry->closure_handle()); |
| } |
| AddToFreeListLocked(entry); |
| } |
| |
| void FfiCallbackMetadata::DeleteAllCallbacks(Metadata** list_head) { |
| MutexLocker locker(&lock_); |
| for (Metadata* entry = *list_head; entry != nullptr;) { |
| Metadata* next = entry->list_next(); |
| DeleteCallbackLocked(entry); |
| entry = next; |
| } |
| *list_head = nullptr; |
| } |
| |
| void FfiCallbackMetadata::DeleteCallback(Trampoline trampoline, |
| Metadata** list_head) { |
| MutexLocker locker(&lock_); |
| auto* entry = MetadataOfTrampoline(trampoline); |
| ASSERT(entry->IsLive()); |
| auto* prev = entry->list_prev_; |
| auto* next = entry->list_next_; |
| if (prev != nullptr) { |
| prev->list_next_ = next; |
| } else { |
| ASSERT(*list_head == entry); |
| *list_head = next; |
| } |
| if (next != nullptr) { |
| next->list_prev_ = prev; |
| } |
| DeleteCallbackLocked(entry); |
| } |
| |
| uword FfiCallbackMetadata::GetEntryPoint(Zone* zone, const Function& function) { |
| const auto& code = |
| Code::Handle(zone, FLAG_precompiled_mode ? function.CurrentCode() |
| : function.EnsureHasCode()); |
| ASSERT(!code.IsNull()); |
| return code.EntryPoint(); |
| } |
| |
| PersistentHandle* FfiCallbackMetadata::CreatePersistentHandle( |
| Isolate* isolate, |
| const Closure& closure) { |
| auto* api_state = isolate->group()->api_state(); |
| ASSERT(api_state != nullptr); |
| auto* handle = api_state->AllocatePersistentHandle(); |
| handle->set_ptr(closure); |
| return handle; |
| } |
| |
| FfiCallbackMetadata::Trampoline |
| FfiCallbackMetadata::CreateIsolateLocalFfiCallback(Isolate* isolate, |
| Zone* zone, |
| const Function& function, |
| const Closure& closure, |
| Metadata** list_head) { |
| if (closure.IsNull()) { |
| // If the closure is null, it means the target is a static function, so is |
| // baked into the trampoline and is an ordinary sync callback. |
| ASSERT(function.GetFfiCallbackKind() == |
| FfiCallbackKind::kIsolateLocalStaticCallback); |
| return CreateSyncFfiCallbackImpl(isolate, zone, function, nullptr, |
| list_head); |
| } else { |
| ASSERT(function.GetFfiCallbackKind() == |
| FfiCallbackKind::kIsolateLocalClosureCallback); |
| return CreateSyncFfiCallbackImpl(isolate, zone, function, |
| CreatePersistentHandle(isolate, closure), |
| list_head); |
| } |
| } |
| |
| FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateSyncFfiCallbackImpl( |
| Isolate* isolate, |
| Zone* zone, |
| const Function& function, |
| PersistentHandle* closure, |
| Metadata** list_head) { |
| TrampolineType trampoline_type = TrampolineType::kSync; |
| |
| #if defined(TARGET_ARCH_IA32) |
| // On ia32, store the stack delta that we need to use when returning. |
| const intptr_t stack_return_delta = |
| function.FfiCSignatureReturnsStruct() && CallingConventions::kUsesRet4 |
| ? compiler::target::kWordSize |
| : 0; |
| if (stack_return_delta != 0) { |
| ASSERT(stack_return_delta == 4); |
| trampoline_type = TrampolineType::kSyncStackDelta4; |
| } |
| #endif |
| |
| return CreateMetadataEntry(isolate, trampoline_type, |
| GetEntryPoint(zone, function), |
| reinterpret_cast<uint64_t>(closure), list_head); |
| } |
| |
| FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateAsyncFfiCallback( |
| Isolate* isolate, |
| Zone* zone, |
| const Function& send_function, |
| Dart_Port send_port, |
| Metadata** list_head) { |
| ASSERT(send_function.GetFfiCallbackKind() == FfiCallbackKind::kAsyncCallback); |
| return CreateMetadataEntry(isolate, TrampolineType::kAsync, |
| GetEntryPoint(zone, send_function), |
| static_cast<uint64_t>(send_port), list_head); |
| } |
| |
| FfiCallbackMetadata::Trampoline FfiCallbackMetadata::TrampolineOfMetadata( |
| Metadata* metadata) const { |
| const uword start = MappingStart(reinterpret_cast<uword>(metadata)); |
| Metadata* metadatas = reinterpret_cast<Metadata*>(start + MetadataOffset()); |
| const uword index = metadata - metadatas; |
| #if defined(DART_TARGET_OS_FUCHSIA) |
| return StubCode::FfiCallbackTrampoline().EntryPoint() + |
| index * kNativeCallbackTrampolineSize; |
| #else |
| return start + offset_of_first_trampoline_in_page_ + |
| index * kNativeCallbackTrampolineSize; |
| #endif |
| } |
| |
| FfiCallbackMetadata::Metadata* FfiCallbackMetadata::MetadataOfTrampoline( |
| Trampoline trampoline) const { |
| #if defined(DART_TARGET_OS_FUCHSIA) |
| // On Fuchsia the metadata page is separate to the trampoline page. |
| // TODO(https://dartbug.com/52579): Remove. |
| const uword page_start = |
| Utils::RoundDown(trampoline - offset_of_first_trampoline_in_page_, |
| VirtualMemory::PageSize()); |
| const uword index = |
| (trampoline - offset_of_first_trampoline_in_page_ - page_start) / |
| kNativeCallbackTrampolineSize; |
| ASSERT(index < NumCallbackTrampolinesPerPage()); |
| Metadata* metadata_table = reinterpret_cast<Metadata*>( |
| fuchsia_metadata_page_->start() + MetadataOffset()); |
| return metadata_table + index; |
| #else |
| const uword start = MappingStart(trampoline); |
| Metadata* metadatas = reinterpret_cast<Metadata*>(start + MetadataOffset()); |
| const uword index = |
| (trampoline - start - offset_of_first_trampoline_in_page_) / |
| kNativeCallbackTrampolineSize; |
| return &metadatas[index]; |
| #endif |
| } |
| |
| FfiCallbackMetadata::Metadata FfiCallbackMetadata::LookupMetadataForTrampoline( |
| Trampoline trampoline) const { |
| return *MetadataOfTrampoline(trampoline); |
| } |
| |
| FfiCallbackMetadata* FfiCallbackMetadata::singleton_ = nullptr; |
| |
| } // namespace dart |