|  | // 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 |