mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 15:01:30 +00:00
e8d7425c4e
Bug: https://github.com/dart-lang/sdk/issues/52689 Change-Id: I54be397cfbf8519fe5b5a51b793fe46d602124d9 Fixes: https://github.com/dart-lang/sdk/issues/52689 Bug: https://github.com/dart-lang/sdk/issues/53096 TEST=isolate_local_function_callbacks_test.dart, plus generated tests and additions to existing tests CoreLibraryReviewExempt: The isolate and FFI packages are VM-only Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/317060 Commit-Queue: Liam Appelbe <liama@google.com> Reviewed-by: Liam Appelbe <liama@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com>
625 lines
21 KiB
C++
625 lines
21 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.
|
|
|
|
#include "vm/ffi_callback_metadata.h"
|
|
|
|
#include <memory>
|
|
#include <thread> // NOLINT(build/c++11)
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#include "include/dart_api.h"
|
|
#include "platform/assert.h"
|
|
#include "vm/class_finalizer.h"
|
|
#include "vm/compiler/ffi/callback.h"
|
|
#include "vm/compiler/jit/compiler.h"
|
|
#include "vm/message_handler.h"
|
|
#include "vm/object.h"
|
|
#include "vm/port.h"
|
|
#include "vm/symbols.h"
|
|
#include "vm/unit_test.h"
|
|
|
|
namespace dart {
|
|
|
|
FunctionPtr CreateTestFunction(FfiFunctionKind kind) {
|
|
const auto& ffi_lib = Library::Handle(Library::FfiLibrary());
|
|
const auto& ffi_void = Class::Handle(ffi_lib.LookupClass(Symbols::FfiVoid()));
|
|
const auto& ffi_void_type =
|
|
Type::Handle(Type::NewNonParameterizedType(ffi_void));
|
|
|
|
auto* thread = Thread::Current();
|
|
const char* kScriptChars =
|
|
R"(
|
|
void testFunction() {
|
|
}
|
|
)";
|
|
Dart_Handle library;
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
library = TestCase::LoadTestScript(kScriptChars, nullptr);
|
|
EXPECT_VALID(library);
|
|
}
|
|
|
|
const auto& lib =
|
|
Library::Handle(Library::RawCast(Api::UnwrapHandle(library)));
|
|
EXPECT(ClassFinalizer::ProcessPendingClasses());
|
|
const auto& cls = Class::Handle(lib.toplevel_class());
|
|
EXPECT(!cls.IsNull());
|
|
const auto& error = cls.EnsureIsFinalized(thread);
|
|
EXPECT(error == Error::null());
|
|
|
|
auto& function_name = String::Handle(String::New("testFunction"));
|
|
const auto& func = Function::Handle(cls.LookupStaticFunction(function_name));
|
|
EXPECT(!func.IsNull());
|
|
|
|
FunctionType& signature = FunctionType::Handle(FunctionType::New());
|
|
signature.set_result_type(ffi_void_type);
|
|
signature.SetIsFinalized();
|
|
signature ^= signature.Canonicalize(thread);
|
|
|
|
const auto& callback = Function::Handle(compiler::ffi::NativeCallbackFunction(
|
|
signature, func, Instance::Handle(Instance::null()), kind));
|
|
|
|
const auto& result = Object::Handle(
|
|
thread->zone(), Compiler::CompileFunction(thread, callback));
|
|
EXPECT(!result.IsError());
|
|
|
|
return callback.ptr();
|
|
}
|
|
|
|
class FakeMessageHandler : public MessageHandler {
|
|
public:
|
|
MessageStatus HandleMessage(std::unique_ptr<Message> message) override {
|
|
return MessageHandler::kOK;
|
|
}
|
|
};
|
|
|
|
VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateSyncFfiCallback) {
|
|
auto* fcm = FfiCallbackMetadata::Instance();
|
|
FfiCallbackMetadata::Trampoline tramp1 = 0;
|
|
FfiCallbackMetadata::Trampoline tramp2 = 0;
|
|
|
|
{
|
|
TestIsolateScope isolate_scope;
|
|
Thread* thread = Thread::Current();
|
|
Isolate* isolate = thread->isolate();
|
|
ASSERT(isolate == isolate_scope.isolate());
|
|
TransitionNativeToVM transition(thread);
|
|
StackZone stack_zone(thread);
|
|
HandleScope handle_scope(thread);
|
|
|
|
auto* zone = thread->zone();
|
|
|
|
const auto& func = Function::Handle(
|
|
CreateTestFunction(FfiFunctionKind::kIsolateLocalStaticCallback));
|
|
const auto& code = Code::Handle(func.EnsureHasCode());
|
|
EXPECT(!code.IsNull());
|
|
|
|
tramp1 = isolate->CreateIsolateLocalFfiCallback(
|
|
zone, func, Closure::Handle(Closure::null()), false);
|
|
EXPECT_NE(tramp1, 0u);
|
|
|
|
{
|
|
FfiCallbackMetadata::Metadata m1 =
|
|
fcm->LookupMetadataForTrampoline(tramp1);
|
|
EXPECT(m1.IsLive());
|
|
EXPECT_EQ(m1.target_isolate(), isolate);
|
|
EXPECT_EQ(m1.target_entry_point(), code.EntryPoint());
|
|
EXPECT_EQ(m1.closure_handle(), nullptr);
|
|
EXPECT_EQ(static_cast<int>(m1.trampoline_type()),
|
|
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
|
|
|
|
// head -> tramp1
|
|
auto* e1 = fcm->MetadataOfTrampoline(tramp1);
|
|
EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
|
|
EXPECT_EQ(e1->list_prev(), nullptr);
|
|
EXPECT_EQ(e1->list_next(), nullptr);
|
|
}
|
|
|
|
tramp2 = isolate->CreateIsolateLocalFfiCallback(
|
|
zone, func, Closure::Handle(Closure::null()), false);
|
|
EXPECT_NE(tramp2, 0u);
|
|
EXPECT_NE(tramp2, tramp1);
|
|
|
|
{
|
|
FfiCallbackMetadata::Metadata m2 =
|
|
fcm->LookupMetadataForTrampoline(tramp2);
|
|
EXPECT(m2.IsLive());
|
|
EXPECT_EQ(m2.target_isolate(), isolate);
|
|
EXPECT_EQ(m2.target_entry_point(), code.EntryPoint());
|
|
EXPECT_EQ(m2.closure_handle(), nullptr);
|
|
EXPECT_EQ(static_cast<int>(m2.trampoline_type()),
|
|
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
|
|
}
|
|
|
|
{
|
|
// head -> tramp2 -> tramp1
|
|
auto* e1 = fcm->MetadataOfTrampoline(tramp1);
|
|
auto* e2 = fcm->MetadataOfTrampoline(tramp2);
|
|
EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
|
|
EXPECT_EQ(e2->list_prev(), nullptr);
|
|
EXPECT_EQ(e2->list_next(), e1);
|
|
EXPECT_EQ(e1->list_prev(), e2);
|
|
EXPECT_EQ(e1->list_next(), nullptr);
|
|
}
|
|
|
|
{
|
|
isolate->DeleteFfiCallback(tramp1);
|
|
FfiCallbackMetadata::Metadata m1 =
|
|
fcm->LookupMetadataForTrampoline(tramp1);
|
|
EXPECT(!m1.IsLive());
|
|
|
|
// head -> tramp2
|
|
auto* e2 = fcm->MetadataOfTrampoline(tramp2);
|
|
EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
|
|
EXPECT_EQ(e2->list_prev(), nullptr);
|
|
EXPECT_EQ(e2->list_next(), nullptr);
|
|
}
|
|
}
|
|
|
|
{
|
|
// Isolate has shut down, so all callbacks should be deleted.
|
|
FfiCallbackMetadata::Metadata m1 = fcm->LookupMetadataForTrampoline(tramp1);
|
|
EXPECT(!m1.IsLive());
|
|
|
|
FfiCallbackMetadata::Metadata m2 = fcm->LookupMetadataForTrampoline(tramp2);
|
|
EXPECT(!m2.IsLive());
|
|
}
|
|
}
|
|
|
|
VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateAsyncFfiCallback) {
|
|
auto* fcm = FfiCallbackMetadata::Instance();
|
|
FfiCallbackMetadata::Trampoline tramp1 = 0;
|
|
FfiCallbackMetadata::Trampoline tramp2 = 0;
|
|
|
|
{
|
|
TestIsolateScope isolate_scope;
|
|
Thread* thread = Thread::Current();
|
|
Isolate* isolate = thread->isolate();
|
|
ASSERT(thread->isolate() == isolate_scope.isolate());
|
|
TransitionNativeToVM transition(thread);
|
|
StackZone stack_zone(thread);
|
|
HandleScope handle_scope(thread);
|
|
|
|
auto* zone = thread->zone();
|
|
|
|
const Function& func =
|
|
Function::Handle(CreateTestFunction(FfiFunctionKind::kAsyncCallback));
|
|
const Code& code = Code::Handle(func.EnsureHasCode());
|
|
EXPECT(!code.IsNull());
|
|
|
|
EXPECT_EQ(isolate->ffi_callback_list_head(), nullptr);
|
|
|
|
auto port1 = PortMap::CreatePort(new FakeMessageHandler());
|
|
tramp1 = isolate->CreateAsyncFfiCallback(zone, func, port1);
|
|
EXPECT_NE(tramp1, 0u);
|
|
|
|
{
|
|
FfiCallbackMetadata::Metadata m1 =
|
|
fcm->LookupMetadataForTrampoline(tramp1);
|
|
EXPECT(m1.IsLive());
|
|
EXPECT_EQ(m1.target_isolate(), isolate);
|
|
EXPECT_EQ(m1.target_entry_point(), code.EntryPoint());
|
|
EXPECT_EQ(m1.send_port(), port1);
|
|
EXPECT_EQ(static_cast<int>(m1.trampoline_type()),
|
|
static_cast<int>(FfiCallbackMetadata::TrampolineType::kAsync));
|
|
|
|
// head -> tramp1
|
|
auto* e1 = fcm->MetadataOfTrampoline(tramp1);
|
|
EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
|
|
EXPECT_EQ(e1->list_prev(), nullptr);
|
|
EXPECT_EQ(e1->list_next(), nullptr);
|
|
}
|
|
|
|
auto port2 = PortMap::CreatePort(new FakeMessageHandler());
|
|
tramp2 = isolate->CreateAsyncFfiCallback(zone, func, port2);
|
|
EXPECT_NE(tramp2, 0u);
|
|
EXPECT_NE(tramp2, tramp1);
|
|
|
|
{
|
|
FfiCallbackMetadata::Metadata m2 =
|
|
fcm->LookupMetadataForTrampoline(tramp2);
|
|
EXPECT(m2.IsLive());
|
|
EXPECT_EQ(m2.target_isolate(), isolate);
|
|
EXPECT_EQ(m2.target_entry_point(), code.EntryPoint());
|
|
EXPECT_EQ(m2.send_port(), port2);
|
|
EXPECT_EQ(static_cast<int>(m2.trampoline_type()),
|
|
static_cast<int>(FfiCallbackMetadata::TrampolineType::kAsync));
|
|
}
|
|
|
|
{
|
|
// head -> tramp2 -> tramp1
|
|
auto* e1 = fcm->MetadataOfTrampoline(tramp1);
|
|
auto* e2 = fcm->MetadataOfTrampoline(tramp2);
|
|
EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
|
|
EXPECT_EQ(e2->list_prev(), nullptr);
|
|
EXPECT_EQ(e2->list_next(), e1);
|
|
EXPECT_EQ(e1->list_prev(), e2);
|
|
EXPECT_EQ(e1->list_next(), nullptr);
|
|
}
|
|
|
|
{
|
|
isolate->DeleteFfiCallback(tramp2);
|
|
FfiCallbackMetadata::Metadata m2 =
|
|
fcm->LookupMetadataForTrampoline(tramp2);
|
|
EXPECT(!m2.IsLive());
|
|
|
|
// head -> tramp1
|
|
auto* e1 = fcm->MetadataOfTrampoline(tramp1);
|
|
EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
|
|
EXPECT_EQ(e1->list_prev(), nullptr);
|
|
EXPECT_EQ(e1->list_next(), nullptr);
|
|
}
|
|
}
|
|
|
|
{
|
|
// Isolate has shut down, so all callbacks should be deleted.
|
|
FfiCallbackMetadata::Metadata m1 = fcm->LookupMetadataForTrampoline(tramp1);
|
|
EXPECT(!m1.IsLive());
|
|
|
|
FfiCallbackMetadata::Metadata m2 = fcm->LookupMetadataForTrampoline(tramp2);
|
|
EXPECT(!m2.IsLive());
|
|
}
|
|
}
|
|
|
|
VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateIsolateLocalFfiCallback) {
|
|
auto* fcm = FfiCallbackMetadata::Instance();
|
|
FfiCallbackMetadata::Trampoline tramp1 = 0;
|
|
FfiCallbackMetadata::Trampoline tramp2 = 0;
|
|
|
|
{
|
|
TestIsolateScope isolate_scope;
|
|
Thread* thread = Thread::Current();
|
|
Isolate* isolate = thread->isolate();
|
|
ASSERT(thread->isolate() == isolate_scope.isolate());
|
|
TransitionNativeToVM transition(thread);
|
|
StackZone stack_zone(thread);
|
|
HandleScope handle_scope(thread);
|
|
|
|
auto* zone = thread->zone();
|
|
|
|
const Function& func = Function::Handle(
|
|
CreateTestFunction(FfiFunctionKind::kIsolateLocalClosureCallback));
|
|
const Code& code = Code::Handle(func.EnsureHasCode());
|
|
EXPECT(!code.IsNull());
|
|
|
|
// Using a FfiFunctionKind::kSync function as a dummy closure.
|
|
const Function& closure_func = Function::Handle(
|
|
CreateTestFunction(FfiFunctionKind::kIsolateLocalStaticCallback));
|
|
const Context& context = Context::Handle(Context::null());
|
|
const Closure& closure1 = Closure::Handle(
|
|
Closure::New(Object::null_type_arguments(),
|
|
Object::null_type_arguments(), closure_func, context));
|
|
|
|
EXPECT_EQ(isolate->ffi_callback_list_head(), nullptr);
|
|
|
|
tramp1 = isolate->CreateIsolateLocalFfiCallback(zone, func, closure1, true);
|
|
EXPECT_NE(tramp1, 0u);
|
|
|
|
{
|
|
FfiCallbackMetadata::Metadata m1 =
|
|
fcm->LookupMetadataForTrampoline(tramp1);
|
|
EXPECT(m1.IsLive());
|
|
EXPECT_EQ(m1.target_isolate(), isolate);
|
|
EXPECT_EQ(m1.target_entry_point(), code.EntryPoint());
|
|
EXPECT_EQ(m1.closure_handle()->ptr(), closure1.ptr());
|
|
EXPECT_EQ(static_cast<int>(m1.trampoline_type()),
|
|
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
|
|
|
|
// head -> tramp1
|
|
auto* e1 = fcm->MetadataOfTrampoline(tramp1);
|
|
EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
|
|
EXPECT_EQ(e1->list_prev(), nullptr);
|
|
EXPECT_EQ(e1->list_next(), nullptr);
|
|
}
|
|
|
|
const Closure& closure2 = Closure::Handle(
|
|
Closure::New(Object::null_type_arguments(),
|
|
Object::null_type_arguments(), closure_func, context));
|
|
tramp2 = isolate->CreateIsolateLocalFfiCallback(zone, func, closure2, true);
|
|
EXPECT_NE(tramp2, 0u);
|
|
EXPECT_NE(tramp2, tramp1);
|
|
|
|
{
|
|
FfiCallbackMetadata::Metadata m2 =
|
|
fcm->LookupMetadataForTrampoline(tramp2);
|
|
EXPECT(m2.IsLive());
|
|
EXPECT_EQ(m2.target_isolate(), isolate);
|
|
EXPECT_EQ(m2.target_entry_point(), code.EntryPoint());
|
|
EXPECT_EQ(m2.closure_handle()->ptr(), closure2.ptr());
|
|
EXPECT_EQ(static_cast<int>(m2.trampoline_type()),
|
|
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
|
|
}
|
|
|
|
{
|
|
// head -> tramp2 -> tramp1
|
|
auto* e1 = fcm->MetadataOfTrampoline(tramp1);
|
|
auto* e2 = fcm->MetadataOfTrampoline(tramp2);
|
|
EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
|
|
EXPECT_EQ(e2->list_prev(), nullptr);
|
|
EXPECT_EQ(e2->list_next(), e1);
|
|
EXPECT_EQ(e1->list_prev(), e2);
|
|
EXPECT_EQ(e1->list_next(), nullptr);
|
|
}
|
|
|
|
{
|
|
isolate->DeleteFfiCallback(tramp2);
|
|
FfiCallbackMetadata::Metadata m2 =
|
|
fcm->LookupMetadataForTrampoline(tramp2);
|
|
EXPECT(!m2.IsLive());
|
|
|
|
// head -> tramp1
|
|
auto* e1 = fcm->MetadataOfTrampoline(tramp1);
|
|
EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
|
|
EXPECT_EQ(e1->list_prev(), nullptr);
|
|
EXPECT_EQ(e1->list_next(), nullptr);
|
|
}
|
|
}
|
|
|
|
{
|
|
// Isolate has shut down, so all callbacks should be deleted.
|
|
FfiCallbackMetadata::Metadata m1 = fcm->LookupMetadataForTrampoline(tramp1);
|
|
EXPECT(!m1.IsLive());
|
|
|
|
FfiCallbackMetadata::Metadata m2 = fcm->LookupMetadataForTrampoline(tramp2);
|
|
EXPECT(!m2.IsLive());
|
|
}
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(FfiCallbackMetadata_TrampolineRecycling) {
|
|
Isolate* isolate = thread->isolate();
|
|
auto* zone = thread->zone();
|
|
auto* fcm = FfiCallbackMetadata::Instance();
|
|
|
|
const Function& func =
|
|
Function::Handle(CreateTestFunction(FfiFunctionKind::kAsyncCallback));
|
|
const Code& code = Code::Handle(func.EnsureHasCode());
|
|
EXPECT(!code.IsNull());
|
|
|
|
auto port = PortMap::CreatePort(new FakeMessageHandler());
|
|
FfiCallbackMetadata::Metadata* list_head = nullptr;
|
|
|
|
// Allocate and free one callback at a time, and verify that we don't reuse
|
|
// them. Allocate enough that the whole page fills up with dead trampolines.
|
|
std::vector<FfiCallbackMetadata::Trampoline> allocation_order;
|
|
std::unordered_set<FfiCallbackMetadata::Trampoline> allocated;
|
|
const intptr_t trampolines_per_page =
|
|
FfiCallbackMetadata::NumCallbackTrampolinesPerPage();
|
|
for (intptr_t i = 0; i < trampolines_per_page; ++i) {
|
|
auto tramp =
|
|
fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head);
|
|
EXPECT_EQ(allocated.count(tramp), 0u);
|
|
allocation_order.push_back(tramp);
|
|
allocated.insert(tramp);
|
|
fcm->DeleteCallback(tramp, &list_head);
|
|
}
|
|
|
|
// Now as we continue allocating and freeing, we start reusing them, in the
|
|
// same allocation order as before.
|
|
for (intptr_t i = 0; i < trampolines_per_page; ++i) {
|
|
auto tramp =
|
|
fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head);
|
|
EXPECT_EQ(allocated.count(tramp), 1u);
|
|
EXPECT_EQ(allocation_order[i], tramp);
|
|
fcm->DeleteCallback(tramp, &list_head);
|
|
}
|
|
|
|
// Now allocate enough to fill the page without freeing them. Again they
|
|
// should come out in the same order.
|
|
for (intptr_t i = 0; i < trampolines_per_page; ++i) {
|
|
auto tramp =
|
|
fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head);
|
|
EXPECT_EQ(allocated.count(tramp), 1u);
|
|
EXPECT_EQ(allocation_order[i], tramp);
|
|
}
|
|
|
|
// Now that the page is full, we should allocate a new page and see new
|
|
// trampolines we haven't seen before.
|
|
for (intptr_t i = 0; i < 3 * trampolines_per_page; ++i) {
|
|
auto tramp =
|
|
fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head);
|
|
EXPECT_EQ(allocated.count(tramp), 0u);
|
|
}
|
|
}
|
|
|
|
VM_UNIT_TEST_CASE(FfiCallbackMetadata_DeleteTrampolines) {
|
|
static constexpr int kCreations = 1000;
|
|
static constexpr int kDeletions = 100;
|
|
|
|
TestIsolateScope isolate_scope;
|
|
Thread* thread = Thread::Current();
|
|
Isolate* isolate = thread->isolate();
|
|
ASSERT(isolate == isolate_scope.isolate());
|
|
TransitionNativeToVM transition(thread);
|
|
StackZone stack_zone(thread);
|
|
HandleScope handle_scope(thread);
|
|
|
|
auto* fcm = FfiCallbackMetadata::Instance();
|
|
std::unordered_set<FfiCallbackMetadata::Trampoline> tramps;
|
|
FfiCallbackMetadata::Metadata* list_head = nullptr;
|
|
|
|
const auto& sync_func = Function::Handle(
|
|
CreateTestFunction(FfiFunctionKind::kIsolateLocalStaticCallback));
|
|
const auto& sync_code = Code::Handle(sync_func.EnsureHasCode());
|
|
EXPECT(!sync_code.IsNull());
|
|
|
|
// Create some callbacks.
|
|
for (int itr = 0; itr < kCreations; ++itr) {
|
|
tramps.insert(fcm->CreateIsolateLocalFfiCallback(
|
|
isolate, thread->zone(), sync_func, Closure::Handle(Closure::null()),
|
|
&list_head));
|
|
}
|
|
|
|
// Delete some of the callbacks.
|
|
for (int itr = 0; itr < kDeletions; ++itr) {
|
|
auto tramp = *tramps.begin();
|
|
fcm->DeleteCallback(tramp, &list_head);
|
|
tramps.erase(tramp);
|
|
}
|
|
|
|
// Verify all the callbacks.
|
|
for (FfiCallbackMetadata::Trampoline tramp : tramps) {
|
|
auto metadata = fcm->LookupMetadataForTrampoline(tramp);
|
|
EXPECT(metadata.IsLive());
|
|
EXPECT_EQ(metadata.target_isolate(), isolate);
|
|
EXPECT_EQ(static_cast<int>(metadata.trampoline_type()),
|
|
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
|
|
EXPECT_EQ(metadata.target_entry_point(), sync_code.EntryPoint());
|
|
}
|
|
|
|
// Verify the list of callbacks.
|
|
uword list_length = 0;
|
|
for (FfiCallbackMetadata::Metadata* m = list_head; m != nullptr;) {
|
|
++list_length;
|
|
auto tramp = fcm->TrampolineOfMetadata(m);
|
|
EXPECT(m->IsLive());
|
|
EXPECT_EQ(m->target_isolate(), isolate);
|
|
EXPECT_EQ(tramps.count(tramp), 1u);
|
|
auto* next = m->list_next();
|
|
auto* prev = m->list_prev();
|
|
if (prev != nullptr) {
|
|
EXPECT_EQ(prev->list_next(), m);
|
|
} else {
|
|
EXPECT_EQ(list_head, m);
|
|
}
|
|
if (next != nullptr) {
|
|
EXPECT_EQ(next->list_prev(), m);
|
|
}
|
|
m = m->list_next();
|
|
}
|
|
EXPECT_EQ(list_length, tramps.size());
|
|
|
|
// Delete all callbacks and verify they're destroyed.
|
|
fcm->DeleteAllCallbacks(&list_head);
|
|
EXPECT_EQ(list_head, nullptr);
|
|
for (FfiCallbackMetadata::Trampoline tramp : tramps) {
|
|
EXPECT(!fcm->LookupMetadataForTrampoline(tramp).IsLive());
|
|
}
|
|
}
|
|
|
|
static void RunBigRandomMultithreadedTest(uint64_t seed) {
|
|
static constexpr int kIterations = 1000;
|
|
|
|
TestIsolateScope isolate_scope;
|
|
Thread* thread = Thread::Current();
|
|
Isolate* isolate = thread->isolate();
|
|
ASSERT(isolate == isolate_scope.isolate());
|
|
TransitionNativeToVM transition(thread);
|
|
StackZone stack_zone(thread);
|
|
HandleScope handle_scope(thread);
|
|
|
|
struct TrampolineWithPort {
|
|
FfiCallbackMetadata::Trampoline tramp;
|
|
Dart_Port port;
|
|
};
|
|
|
|
auto* fcm = FfiCallbackMetadata::Instance();
|
|
Random random(seed);
|
|
std::vector<TrampolineWithPort> tramps;
|
|
std::unordered_set<FfiCallbackMetadata::Trampoline> tramp_set;
|
|
FfiCallbackMetadata::Metadata* list_head = nullptr;
|
|
|
|
const Function& async_func =
|
|
Function::Handle(CreateTestFunction(FfiFunctionKind::kAsyncCallback));
|
|
const Code& async_code = Code::Handle(async_func.EnsureHasCode());
|
|
EXPECT(!async_code.IsNull());
|
|
const Function& sync_func = Function::Handle(
|
|
CreateTestFunction(FfiFunctionKind::kIsolateLocalStaticCallback));
|
|
const auto& sync_code = Code::Handle(sync_func.EnsureHasCode());
|
|
EXPECT(!sync_code.IsNull());
|
|
|
|
for (int itr = 0; itr < kIterations; ++itr) {
|
|
// Do a random action:
|
|
// - Allocate a sync callback
|
|
// - Allocate an async callback
|
|
// - Delete a callback
|
|
// - Delete all the sync callbacks for an isolate
|
|
|
|
if ((random.NextUInt32() % 100) == 0) {
|
|
// 1% chance of deleting all the callbacks on the thread.
|
|
fcm->DeleteAllCallbacks(&list_head);
|
|
|
|
// It would be nice to verify that all the trampolines have been deleted,
|
|
// but this is flaky because other threads can recycle these trampolines
|
|
// before we finish checking all of them.
|
|
tramps.clear();
|
|
tramp_set.clear();
|
|
EXPECT_EQ(list_head, nullptr);
|
|
} else if (tramps.size() > 0 && (random.NextUInt32() % 4) == 0) {
|
|
// 25% chance of deleting a callback.
|
|
uint32_t r = random.NextUInt32() % tramps.size();
|
|
auto tramp = tramps[r].tramp;
|
|
fcm->DeleteCallback(tramp, &list_head);
|
|
tramps[r] = tramps[tramps.size() - 1];
|
|
tramps.pop_back();
|
|
tramp_set.erase(tramp);
|
|
} else {
|
|
TrampolineWithPort tramp;
|
|
if ((random.NextUInt32() % 2) == 0) {
|
|
// 50% chance of creating a sync callback.
|
|
tramp.port = ILLEGAL_PORT;
|
|
tramp.tramp = fcm->CreateIsolateLocalFfiCallback(
|
|
isolate, thread->zone(), sync_func,
|
|
Closure::Handle(Closure::null()), &list_head);
|
|
} else {
|
|
// 50% chance of creating an async callback.
|
|
tramp.port = PortMap::CreatePort(new FakeMessageHandler());
|
|
tramp.tramp = fcm->CreateAsyncFfiCallback(
|
|
isolate, thread->zone(), async_func, tramp.port, &list_head);
|
|
}
|
|
tramps.push_back(tramp);
|
|
tramp_set.insert(tramp.tramp);
|
|
}
|
|
|
|
// Verify all the callbacks.
|
|
for (const auto& tramp : tramps) {
|
|
auto metadata = fcm->LookupMetadataForTrampoline(tramp.tramp);
|
|
EXPECT(metadata.IsLive());
|
|
EXPECT_EQ(metadata.target_isolate(), isolate);
|
|
if (metadata.trampoline_type() ==
|
|
FfiCallbackMetadata::TrampolineType::kSync) {
|
|
EXPECT_EQ(metadata.closure_handle(), nullptr);
|
|
EXPECT_EQ(metadata.target_entry_point(), sync_code.EntryPoint());
|
|
} else {
|
|
EXPECT_EQ(metadata.send_port(), tramp.port);
|
|
EXPECT_EQ(metadata.target_entry_point(), async_code.EntryPoint());
|
|
}
|
|
}
|
|
|
|
// Verify the isolate's list of callbacks.
|
|
uword list_length = 0;
|
|
for (FfiCallbackMetadata::Metadata* m = list_head; m != nullptr;) {
|
|
++list_length;
|
|
auto tramp = fcm->TrampolineOfMetadata(m);
|
|
EXPECT(m->IsLive());
|
|
EXPECT_EQ(m->target_isolate(), isolate);
|
|
EXPECT_EQ(tramp_set.count(tramp), 1u);
|
|
m = m->list_next();
|
|
}
|
|
EXPECT_EQ(list_length, tramps.size());
|
|
EXPECT_EQ(list_length, tramp_set.size());
|
|
}
|
|
|
|
// Delete all remaining callbacks.
|
|
fcm->DeleteAllCallbacks(&list_head);
|
|
EXPECT_EQ(list_head, nullptr);
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(FfiCallbackMetadata_BigRandomMultithreadedTest) {
|
|
static constexpr int kThreads = 5;
|
|
|
|
std::vector<std::thread> threads;
|
|
|
|
Random random;
|
|
for (int i = 0; i < kThreads; ++i) {
|
|
threads.push_back(
|
|
std::thread(RunBigRandomMultithreadedTest, random.NextUInt64()));
|
|
}
|
|
|
|
for (auto& thread : threads) {
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
} // namespace dart
|