blob: 524fcb317160c8abc7a229d240da901ce974518e [file] [log] [blame]
// Copyright (c) 2019, 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.
// This file contains test functions for the dart:ffi test cases.
#include <stddef.h>
#include <stdlib.h>
#include <sys/types.h>
#include <csignal>
#include "platform/globals.h"
#include "platform/memory_sanitizer.h"
#if defined(DART_HOST_OS_WINDOWS)
#include <psapi.h>
#include <windows.h>
#else
#include <unistd.h>
#endif
// Only OK to use here because this is test code.
#include <condition_variable> // NOLINT(build/c++11)
#include <functional> // NOLINT(build/c++11)
#include <mutex> // NOLINT(build/c++11)
#include <queue> // NOLINT(build/c++11)
#include <thread> // NOLINT(build/c++11)
#include <setjmp.h> // NOLINT
#include <signal.h> // NOLINT
#include <iostream>
#include <limits>
// TODO(dartbug.com/40579): This requires static linking to either link
// dart.exe or dart_precompiled_runtime.exe on Windows.
// The sample currently fails on Windows in AOT mode.
#include "include/dart_api.h"
#include "include/dart_native_api.h"
#include "include/dart_api_dl.h"
namespace dart {
#define CHECK(X) \
if (!(X)) { \
fprintf(stderr, "%s\n", "Check failed: " #X); \
return 1; \
}
#define CHECK_EQ(X, Y) CHECK((X) == (Y))
#define ENSURE(X) \
if (!(X)) { \
fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, "Check failed: " #X); \
exit(1); \
}
////////////////////////////////////////////////////////////////////////////////
// Functions for stress-testing.
DART_EXPORT int64_t MinInt64() {
Dart_ExecuteInternalCommand("gc-on-nth-allocation",
reinterpret_cast<void*>(1));
return 0x8000000000000000;
}
DART_EXPORT int64_t MinInt32() {
Dart_ExecuteInternalCommand("gc-on-nth-allocation",
reinterpret_cast<void*>(1));
return 0x80000000;
}
DART_EXPORT double SmallDouble() {
Dart_ExecuteInternalCommand("gc-on-nth-allocation",
reinterpret_cast<void*>(1));
return 0x80000000 * -1.0;
}
// Requires boxing on 32-bit and 64-bit systems, even if the top 32-bits are
// truncated.
DART_EXPORT void* LargePointer() {
Dart_ExecuteInternalCommand("gc-on-nth-allocation",
reinterpret_cast<void*>(1));
uint64_t origin = 0x8100000082000000;
return reinterpret_cast<void*>(origin);
}
DART_EXPORT void TriggerGC(uint64_t count) {
Dart_ExecuteInternalCommand("gc-now", nullptr);
}
DART_EXPORT void CollectOnNthAllocation(intptr_t num_allocations) {
Dart_ExecuteInternalCommand("gc-on-nth-allocation",
reinterpret_cast<void*>(num_allocations));
}
// Triggers GC. Has 11 dummy arguments as unboxed odd integers which should be
// ignored by GC.
DART_EXPORT void Regress37069(uint64_t a,
uint64_t b,
uint64_t c,
uint64_t d,
uint64_t e,
uint64_t f,
uint64_t g,
uint64_t h,
uint64_t i,
uint64_t j,
uint64_t k) {
Dart_ExecuteInternalCommand("gc-now", nullptr);
}
DART_EXPORT uint8_t IsThreadInGenerated() {
return Dart_ExecuteInternalCommand("is-thread-in-generated", nullptr) !=
nullptr
? 1
: 0;
}
#if !defined(DART_HOST_OS_WINDOWS)
DART_EXPORT void* UnprotectCodeOtherThread(void* isolate,
std::condition_variable* var,
std::mutex* mut) {
std::function<void()> callback = [&]() {
mut->lock();
var->notify_all();
mut->unlock();
// Wait for mutator thread to continue (and block) before leaving the
// safepoint.
while (Dart_ExecuteInternalCommand("is-mutator-in-native", isolate) !=
nullptr) {
usleep(10 * 1000 /*10 ms*/);
}
};
struct {
void* isolate;
std::function<void()>* callback;
} args = {.isolate = isolate, .callback = &callback};
Dart_ExecuteInternalCommand("run-in-safepoint-and-rw-code", &args);
return nullptr;
}
struct HelperThreadState {
std::mutex mutex;
std::condition_variable cvar;
std::unique_ptr<std::thread> helper;
};
DART_EXPORT void* TestUnprotectCode(void (*fn)(void*)) {
HelperThreadState* state = new HelperThreadState;
{
std::unique_lock<std::mutex> lock(state->mutex); // locks the mutex
state->helper.reset(new std::thread(UnprotectCodeOtherThread,
Dart_CurrentIsolate(), &state->cvar,
&state->mutex));
state->cvar.wait(lock);
}
if (fn != nullptr) {
fn(state);
return nullptr;
} else {
return state;
}
}
DART_EXPORT void WaitForHelper(HelperThreadState* helper) {
helper->helper->join();
delete helper;
}
#else
// Our version of VSC++ doesn't support std::thread yet.
DART_EXPORT void WaitForHelper(void* helper) {}
DART_EXPORT void* TestUnprotectCode(void (*fn)(void)) {
return nullptr;
}
#endif
// Defined in ffi_test_functions.S.
//
// Clobbers some registers with special meaning in Dart before re-entry, for
// stress-testing. Not used on 32-bit Windows due to complications with Windows
// "safeseh".
#if defined(DART_TARGET_OS_WINDOWS) && defined(HOST_ARCH_IA32)
void ClobberAndCall(void (*fn)()) {
fn();
}
#else
extern "C" void ClobberAndCall(void (*fn)());
#endif
DART_EXPORT intptr_t TestGC(void (*do_gc)()) {
ClobberAndCall(do_gc);
return 0;
}
struct CallbackTestData {
intptr_t success;
void (*callback)();
};
#if defined(DART_TARGET_OS_LINUX)
thread_local sigjmp_buf buf;
void CallbackTestSignalHandler(int) {
siglongjmp(buf, 1);
}
intptr_t ExpectAbort(void (*fn)()) {
fprintf(stderr, "**** EXPECT STACKTRACE TO FOLLOW. THIS IS OK. ****\n");
struct sigaction old_action = {};
intptr_t result = __sigsetjmp(buf, /*savesigs=*/1);
if (result == 0) {
// Install signal handler.
struct sigaction handler = {};
handler.sa_handler = CallbackTestSignalHandler;
sigemptyset(&handler.sa_mask);
handler.sa_flags = 0;
sigaction(SIGABRT, &handler, &old_action);
fn();
} else {
// Caught the setjmp.
sigaction(SIGABRT, &old_action, NULL);
exit(0);
}
fprintf(stderr, "Expected abort!!!\n");
exit(1);
}
void* TestCallbackOnThreadOutsideIsolate(void* parameter) {
CallbackTestData* data = reinterpret_cast<CallbackTestData*>(parameter);
data->success = ExpectAbort(data->callback);
return NULL;
}
intptr_t TestCallbackOtherThreadHelper(void* (*tester)(void*), void (*fn)()) {
CallbackTestData data = {1, fn};
pthread_attr_t attr;
intptr_t result = pthread_attr_init(&attr);
CHECK_EQ(result, 0);
pthread_t tid;
result = pthread_create(&tid, &attr, tester, &data);
CHECK_EQ(result, 0);
result = pthread_attr_destroy(&attr);
CHECK_EQ(result, 0);
void* retval;
result = pthread_join(tid, &retval);
// Doesn't actually return because the other thread will exit when the test is
// finished.
return 1;
}
// Run a callback on another thread and verify that it triggers SIGABRT.
DART_EXPORT intptr_t TestCallbackWrongThread(void (*fn)()) {
return TestCallbackOtherThreadHelper(&TestCallbackOnThreadOutsideIsolate, fn);
}
// Verify that we get SIGABRT when invoking a native callback outside an
// isolate.
DART_EXPORT intptr_t TestCallbackOutsideIsolate(void (*fn)()) {
Dart_Isolate current = Dart_CurrentIsolate();
Dart_ExitIsolate();
CallbackTestData data = {1, fn};
TestCallbackOnThreadOutsideIsolate(&data);
Dart_EnterIsolate(current);
return data.success;
}
DART_EXPORT intptr_t TestCallbackWrongIsolate(void (*fn)()) {
return ExpectAbort(fn);
}
DART_EXPORT intptr_t TestCallbackLeaf(void (*fn)()) {
#if defined(DEBUG)
// Calling a callback from a leaf call will crash on T->IsAtSafepoint().
return ExpectAbort(fn);
#else
// The above will only crash in debug as ASSERTS are disabled in all other
// build modes.
return 0;
#endif
}
void CallDebugName() {
Dart_DebugName();
}
DART_EXPORT intptr_t TestLeafCallApi(void (*fn)()) {
// This should be fine since it's a simple function that returns a const
// string. Though any API call should be considered unsafe from leaf calls.
Dart_VersionString();
#if defined(DEBUG)
// This will fail because it requires running in DARTSCOPE.
return ExpectAbort(&CallDebugName);
#else
// The above will only crash in debug as ASSERTS are disabled in all other
// build modes.
return 0;
#endif
}
#endif // defined(DART_TARGET_OS_LINUX)
// Restore default SIGPIPE handler, which is only needed on mac
// since that is the only platform we explicitly ignore it.
// See Platform::Initialize() in platform_macos.cc.
DART_EXPORT void RestoreSIGPIPEHandler() {
#if defined(DART_HOST_OS_MACOS)
signal(SIGPIPE, SIG_DFL);
#endif
}
DART_EXPORT void IGH_MsanUnpoison(void* start, intptr_t length) {
MSAN_UNPOISON(start, length);
}
DART_EXPORT Dart_Isolate IGH_CreateIsolate(const char* name, void* peer) {
struct Helper {
static void ShutdownCallback(void* ig_data, void* isolate_data) {
char* string = reinterpret_cast<char*>(isolate_data);
ENSURE(string[0] == 'a');
string[0] = 'x';
}
static void CleanupCallback(void* ig_data, void* isolate_data) {
char* string = reinterpret_cast<char*>(isolate_data);
ENSURE(string[2] == 'c');
string[2] = 'z';
}
};
Dart_Isolate parent = Dart_CurrentIsolate();
Dart_ExitIsolate();
char* error = nullptr;
Dart_Isolate child =
Dart_CreateIsolateInGroup(parent, name, &Helper::ShutdownCallback,
&Helper::CleanupCallback, peer, &error);
if (child == nullptr) {
Dart_EnterIsolate(parent);
Dart_Handle error_obj = Dart_NewStringFromCString(error);
free(error);
Dart_ThrowException(error_obj);
return nullptr;
}
Dart_ExitIsolate();
Dart_EnterIsolate(parent);
return child;
}
DART_EXPORT void IGH_StartIsolate(Dart_Isolate child_isolate,
int64_t main_isolate_port,
const char* library_uri,
const char* function_name,
bool errors_are_fatal,
Dart_Port on_error_port,
Dart_Port on_exit_port) {
Dart_Isolate parent = Dart_CurrentIsolate();
Dart_ExitIsolate();
Dart_EnterIsolate(child_isolate);
{
Dart_EnterScope();
Dart_Handle library_name = Dart_NewStringFromCString(library_uri);
ENSURE(!Dart_IsError(library_name));
Dart_Handle library = Dart_LookupLibrary(library_name);
ENSURE(!Dart_IsError(library));
Dart_Handle fun = Dart_NewStringFromCString(function_name);
ENSURE(!Dart_IsError(fun));
Dart_Handle port = Dart_NewInteger(main_isolate_port);
ENSURE(!Dart_IsError(port));
Dart_Handle args[] = {
port,
};
Dart_Handle result = Dart_Invoke(library, fun, 1, args);
if (Dart_IsError(result)) {
fprintf(stderr, "Failed to invoke %s/%s in child isolate: %s\n",
library_uri, function_name, Dart_GetError(result));
}
ENSURE(!Dart_IsError(result));
Dart_ExitScope();
}
char* error = nullptr;
ENSURE(
Dart_RunLoopAsync(errors_are_fatal, on_error_port, on_exit_port, &error));
Dart_EnterIsolate(parent);
}
////////////////////////////////////////////////////////////////////////////////
// Initialize `dart_api_dl.h`
DART_EXPORT intptr_t InitDartApiDL(void* data) {
return Dart_InitializeApiDL(data);
}
////////////////////////////////////////////////////////////////////////////////
// Functions for async callbacks example.
//
// sample_async_callback.dart
void Fatal(char const* file, int line, char const* error) {
printf("FATAL %s:%i\n", file, line);
printf("%s\n", error);
Dart_DumpNativeStackTrace(NULL);
Dart_PrepareToAbort();
abort();
}
#define FATAL(error) Fatal(__FILE__, __LINE__, error)
DART_EXPORT void SleepOnAnyOS(intptr_t seconds) {
#if defined(DART_HOST_OS_WINDOWS)
Sleep(1000 * seconds);
#else
sleep(seconds);
#endif
}
intptr_t (*my_callback_blocking_fp_)(intptr_t);
Dart_Port my_callback_blocking_send_port_;
void (*my_callback_non_blocking_fp_)(intptr_t);
Dart_Port my_callback_non_blocking_send_port_;
typedef std::function<void()> Work;
// Notify Dart through a port that the C lib has pending async callbacks.
//
// Expects heap allocated `work` so delete can be called on it.
//
// The `send_port` should be from the isolate which registered the callback.
void NotifyDart(Dart_Port send_port, const Work* work) {
const intptr_t work_addr = reinterpret_cast<intptr_t>(work);
printf("C : Posting message (port: %" Px64 ", work: %" Px ").\n",
send_port, work_addr);
Dart_CObject dart_object;
dart_object.type = Dart_CObject_kInt64;
dart_object.value.as_int64 = work_addr;
const bool result = Dart_PostCObject_DL(send_port, &dart_object);
if (!result) {
FATAL("C : Posting message to port failed.");
}
}
// Do a callback to Dart in a blocking way, being interested in the result.
//
// Dart returns `a + 3`.
intptr_t MyCallbackBlocking(intptr_t a) {
std::mutex mutex;
std::unique_lock<std::mutex> lock(mutex);
intptr_t result;
auto callback = my_callback_blocking_fp_; // Define storage duration.
std::condition_variable cv;
bool notified = false;
const Work work = [a, &result, callback, &cv, &notified]() {
result = callback(a);
printf("C Da: Notify result ready.\n");
notified = true;
cv.notify_one();
};
const Work* work_ptr = new Work(work); // Copy to heap.
NotifyDart(my_callback_blocking_send_port_, work_ptr);
printf("C : Waiting for result.\n");
while (!notified) {
cv.wait(lock);
}
printf("C : Received result.\n");
return result;
}
// Do a callback to Dart in a non-blocking way.
//
// Dart sums all numbers posted to it.
void MyCallbackNonBlocking(intptr_t a) {
auto callback = my_callback_non_blocking_fp_; // Define storage duration.
const Work work = [a, callback]() { callback(a); };
// Copy to heap to make it outlive the function scope.
const Work* work_ptr = new Work(work);
NotifyDart(my_callback_non_blocking_send_port_, work_ptr);
}
// Simulated work for Thread #1.
//
// Simulates heavy work with sleeps.
void Work1() {
printf("C T1: Work1 Start.\n");
SleepOnAnyOS(1);
const intptr_t val1 = 3;
printf("C T1: MyCallbackBlocking(%" Pd ").\n", val1);
const intptr_t val2 = MyCallbackBlocking(val1); // val2 = 6.
printf("C T1: MyCallbackBlocking returned %" Pd ".\n", val2);
SleepOnAnyOS(1);
const intptr_t val3 = val2 - 1; // val3 = 5.
printf("C T1: MyCallbackNonBlocking(%" Pd ").\n", val3);
MyCallbackNonBlocking(val3); // Post 5 to Dart.
printf("C T1: Work1 Done.\n");
}
// Simulated work for Thread #2.
//
// Simulates lighter work, no sleeps.
void Work2() {
printf("C T2: Work2 Start.\n");
const intptr_t val1 = 5;
printf("C T2: MyCallbackNonBlocking(%" Pd ").\n", val1);
MyCallbackNonBlocking(val1); // Post 5 to Dart.
const intptr_t val2 = 1;
printf("C T2: MyCallbackBlocking(%" Pd ").\n", val2);
const intptr_t val3 = MyCallbackBlocking(val2); // val3 = 4.
printf("C T2: MyCallbackBlocking returned %" Pd ".\n", val3);
printf("C T2: MyCallbackNonBlocking(%" Pd ").\n", val3);
MyCallbackNonBlocking(val3); // Post 4 to Dart.
printf("C T2: Work2 Done.\n");
}
// Simulator that simulates concurrent work with multiple threads.
class SimulateWork {
public:
static void StartWorkSimulator() {
running_work_simulator_ = new SimulateWork();
running_work_simulator_->Start();
}
static void StopWorkSimulator() {
running_work_simulator_->Stop();
delete running_work_simulator_;
running_work_simulator_ = nullptr;
}
private:
static SimulateWork* running_work_simulator_;
void Start() {
printf("C Da: Starting SimulateWork.\n");
printf("C Da: Starting worker threads.\n");
thread1 = new std::thread(Work1);
thread2 = new std::thread(Work2);
printf("C Da: Started SimulateWork.\n");
}
void Stop() {
printf("C Da: Stopping SimulateWork.\n");
printf("C Da: Waiting for worker threads to finish.\n");
thread1->join();
thread2->join();
delete thread1;
delete thread2;
printf("C Da: Stopped SimulateWork.\n");
}
std::thread* thread1;
std::thread* thread2;
};
SimulateWork* SimulateWork::running_work_simulator_ = 0;
DART_EXPORT void RegisterMyCallbackBlocking(Dart_Port send_port,
intptr_t (*callback1)(intptr_t)) {
my_callback_blocking_fp_ = callback1;
my_callback_blocking_send_port_ = send_port;
}
DART_EXPORT void RegisterMyCallbackNonBlocking(Dart_Port send_port,
void (*callback)(intptr_t)) {
my_callback_non_blocking_fp_ = callback;
my_callback_non_blocking_send_port_ = send_port;
}
DART_EXPORT void StartWorkSimulator() {
SimulateWork::StartWorkSimulator();
}
DART_EXPORT void StopWorkSimulator() {
SimulateWork::StopWorkSimulator();
}
DART_EXPORT void ExecuteCallback(Work* work_ptr) {
printf("C Da: ExecuteCallback(%" Pp ").\n",
reinterpret_cast<intptr_t>(work_ptr));
const Work work = *work_ptr;
work();
delete work_ptr;
printf("C Da: ExecuteCallback done.\n");
}
////////////////////////////////////////////////////////////////////////////////
// Functions for async callbacks example.
//
// sample_native_port_call.dart
Dart_Port send_port_;
static void FreeFinalizer(void*, void* value) {
free(value);
}
class PendingCall {
public:
PendingCall(void** buffer, size_t* length)
: response_buffer_(buffer), response_length_(length) {
receive_port_ =
Dart_NewNativePort_DL("cpp-response", &PendingCall::HandleResponse,
/*handle_concurrently=*/false);
}
~PendingCall() { Dart_CloseNativePort_DL(receive_port_); }
Dart_Port port() const { return receive_port_; }
void PostAndWait(Dart_Port port, Dart_CObject* object) {
std::unique_lock<std::mutex> lock(mutex);
const bool success = Dart_PostCObject_DL(send_port_, object);
if (!success) FATAL("Failed to send message, invalid port or isolate died");
printf("C : Waiting for result.\n");
while (!notified) {
cv.wait(lock);
}
}
static void HandleResponse(Dart_Port p, Dart_CObject* message) {
if (message->type != Dart_CObject_kArray) {
FATAL("C : Wrong Data: message->type != Dart_CObject_kArray.\n");
}
Dart_CObject** c_response_args = message->value.as_array.values;
Dart_CObject* c_pending_call = c_response_args[0];
Dart_CObject* c_message = c_response_args[1];
printf("C : HandleResponse (call: %" Px ", message: %" Px ").\n",
reinterpret_cast<intptr_t>(c_pending_call),
reinterpret_cast<intptr_t>(c_message));
auto pending_call = reinterpret_cast<PendingCall*>(
c_pending_call->type == Dart_CObject_kInt64
? c_pending_call->value.as_int64
: c_pending_call->value.as_int32);
pending_call->ResolveCall(c_message);
}
private:
static bool NonEmptyBuffer(void** value) { return *value != nullptr; }
void ResolveCall(Dart_CObject* bytes) {
assert(bytes->type == Dart_CObject_kTypedData);
if (bytes->type != Dart_CObject_kTypedData) {
FATAL("C : Wrong Data: bytes->type != Dart_CObject_kTypedData.\n");
}
const intptr_t response_length = bytes->value.as_typed_data.length;
const uint8_t* response_buffer = bytes->value.as_typed_data.values;
printf("C : ResolveCall(length: %" Pd ", buffer: %" Px ").\n",
response_length, reinterpret_cast<intptr_t>(response_buffer));
void* buffer = malloc(response_length);
memmove(buffer, response_buffer, response_length);
*response_buffer_ = buffer;
*response_length_ = response_length;
printf("C : Notify result ready.\n");
notified = true;
cv.notify_one();
}
std::mutex mutex;
std::condition_variable cv;
bool notified = false;
Dart_Port receive_port_;
void** response_buffer_;
size_t* response_length_;
};
// Do a callback to Dart in a blocking way, being interested in the result.
//
// Dart returns `a + 3`.
uint8_t MyCallback1(uint8_t a) {
const char* methodname = "myCallback1";
size_t request_length = sizeof(uint8_t) * 1;
void* request_buffer = malloc(request_length); // FreeFinalizer.
reinterpret_cast<uint8_t*>(request_buffer)[0] = a; // Populate buffer.
void* response_buffer = nullptr;
size_t response_length = 0;
PendingCall pending_call(&response_buffer, &response_length);
Dart_CObject c_send_port;
c_send_port.type = Dart_CObject_kSendPort;
c_send_port.value.as_send_port.id = pending_call.port();
c_send_port.value.as_send_port.origin_id = ILLEGAL_PORT;
Dart_CObject c_pending_call;
c_pending_call.type = Dart_CObject_kInt64;
c_pending_call.value.as_int64 = reinterpret_cast<int64_t>(&pending_call);
Dart_CObject c_method_name;
c_method_name.type = Dart_CObject_kString;
c_method_name.value.as_string = const_cast<char*>(methodname);
Dart_CObject c_request_data;
c_request_data.type = Dart_CObject_kExternalTypedData;
c_request_data.value.as_external_typed_data.type = Dart_TypedData_kUint8;
c_request_data.value.as_external_typed_data.length = request_length;
c_request_data.value.as_external_typed_data.data =
static_cast<uint8_t*>(request_buffer);
c_request_data.value.as_external_typed_data.peer = request_buffer;
c_request_data.value.as_external_typed_data.callback = FreeFinalizer;
Dart_CObject* c_request_arr[] = {&c_send_port, &c_pending_call,
&c_method_name, &c_request_data};
Dart_CObject c_request;
c_request.type = Dart_CObject_kArray;
c_request.value.as_array.values = c_request_arr;
c_request.value.as_array.length =
sizeof(c_request_arr) / sizeof(c_request_arr[0]);
printf("C : Dart_PostCObject_(request: %" Px ", call: %" Px ").\n",
reinterpret_cast<intptr_t>(&c_request),
reinterpret_cast<intptr_t>(&c_pending_call));
pending_call.PostAndWait(send_port_, &c_request);
printf("C : Received result.\n");
const intptr_t result = reinterpret_cast<uint8_t*>(response_buffer)[0];
free(response_buffer);
return result;
}
// Do a callback to Dart in a non-blocking way.
//
// Dart sums all numbers posted to it.
void MyCallback2(uint8_t a) {
const char* methodname = "myCallback2";
void* request_buffer = malloc(sizeof(uint8_t) * 1); // FreeFinalizer.
reinterpret_cast<uint8_t*>(request_buffer)[0] = a; // Populate buffer.
const size_t request_length = sizeof(uint8_t) * 1;
Dart_CObject c_send_port;
c_send_port.type = Dart_CObject_kNull;
Dart_CObject c_pending_call;
c_pending_call.type = Dart_CObject_kNull;
Dart_CObject c_method_name;
c_method_name.type = Dart_CObject_kString;
c_method_name.value.as_string = const_cast<char*>(methodname);
Dart_CObject c_request_data;
c_request_data.type = Dart_CObject_kExternalTypedData;
c_request_data.value.as_external_typed_data.type = Dart_TypedData_kUint8;
c_request_data.value.as_external_typed_data.length = request_length;
c_request_data.value.as_external_typed_data.data =
static_cast<uint8_t*>(request_buffer);
c_request_data.value.as_external_typed_data.peer = request_buffer;
c_request_data.value.as_external_typed_data.callback = FreeFinalizer;
Dart_CObject* c_request_arr[] = {&c_send_port, &c_pending_call,
&c_method_name, &c_request_data};
Dart_CObject c_request;
c_request.type = Dart_CObject_kArray;
c_request.value.as_array.values = c_request_arr;
c_request.value.as_array.length =
sizeof(c_request_arr) / sizeof(c_request_arr[0]);
printf("C : Dart_PostCObject_(request: %" Px ", call: %" Px ").\n",
reinterpret_cast<intptr_t>(&c_request),
reinterpret_cast<intptr_t>(&c_pending_call));
Dart_PostCObject_DL(send_port_, &c_request);
}
// Simulated work for Thread #1.
//
// Simulates heavy work with sleeps.
void Work1_2() {
printf("C T1: Work1 Start.\n");
SleepOnAnyOS(1);
const intptr_t val1 = 3;
printf("C T1: MyCallback1(%" Pd ").\n", val1);
const intptr_t val2 = MyCallback1(val1); // val2 = 6.
printf("C T1: MyCallback1 returned %" Pd ".\n", val2);
SleepOnAnyOS(1);
const intptr_t val3 = val2 - 1; // val3 = 5.
printf("C T1: MyCallback2(%" Pd ").\n", val3);
MyCallback2(val3); // Post 5 to Dart.
printf("C T1: Work1 Done.\n");
}
// Simulated work for Thread #2.
//
// Simulates lighter work, no sleeps.
void Work2_2() {
printf("C T2: Work2 Start.\n");
const intptr_t val1 = 5;
printf("C T2: MyCallback2(%" Pd ").\n", val1);
MyCallback2(val1); // Post 5 to Dart.
const intptr_t val2 = 1;
printf("C T2: MyCallback1(%" Pd ").\n", val2);
const intptr_t val3 = MyCallback1(val2); // val3 = 4.
printf("C T2: MyCallback1 returned %" Pd ".\n", val3);
printf("C T2: MyCallback2(%" Pd ").\n", val3);
MyCallback2(val3); // Post 4 to Dart.
printf("C T2: Work2 Done.\n");
}
// Simulator that simulates concurrent work with multiple threads.
class SimulateWork2 {
public:
static void StartWorkSimulator() {
running_work_simulator_ = new SimulateWork2();
running_work_simulator_->Start();
}
static void StopWorkSimulator() {
running_work_simulator_->Stop();
delete running_work_simulator_;
running_work_simulator_ = nullptr;
}
private:
static SimulateWork2* running_work_simulator_;
void Start() {
printf("C Da: Starting SimulateWork.\n");
printf("C Da: Starting worker threads.\n");
thread1 = new std::thread(Work1_2);
thread2 = new std::thread(Work2_2);
printf("C Da: Started SimulateWork.\n");
}
void Stop() {
printf("C Da: Stopping SimulateWork.\n");
printf("C Da: Waiting for worker threads to finish.\n");
thread1->join();
thread2->join();
delete thread1;
delete thread2;
printf("C Da: Stopped SimulateWork.\n");
}
std::thread* thread1;
std::thread* thread2;
};
SimulateWork2* SimulateWork2::running_work_simulator_ = 0;
DART_EXPORT void RegisterSendPort(Dart_Port send_port) {
send_port_ = send_port;
}
DART_EXPORT void StartWorkSimulator2() {
SimulateWork2::StartWorkSimulator();
}
DART_EXPORT void StopWorkSimulator2() {
SimulateWork2::StopWorkSimulator();
}
////////////////////////////////////////////////////////////////////////////////
// Helpers used for lightweight isolate tests.
////////////////////////////////////////////////////////////////////////////////
DART_EXPORT void ThreadPoolTest_BarrierSync(
Dart_Isolate (*dart_current_isolate)(),
void (*dart_enter_isolate)(Dart_Isolate),
void (*dart_exit_isolate)(),
intptr_t num_threads) {
// Guaranteed to be initialized exactly once (no race between multiple
// threads).
static std::mutex mutex;
static std::condition_variable cvar;
static intptr_t thread_count = 0;
const Dart_Isolate isolate = dart_current_isolate();
dart_exit_isolate();
{
std::unique_lock<std::mutex> lock(mutex);
++thread_count;
if (thread_count < num_threads) {
while (thread_count < num_threads) {
cvar.wait(lock);
}
} else {
if (thread_count != num_threads) FATAL("bug");
cvar.notify_all();
}
}
dart_enter_isolate(isolate);
}
////////////////////////////////////////////////////////////////////////////////
// Functions for handle tests.
//
// vmspecific_handle_test.dart (statically linked).
// vmspecific_handle_dynamically_linked_test.dart (dynamically linked).
static void RunFinalizer(void* isolate_callback_data, void* peer) {
printf("Running finalizer for weak handle.\n");
}
// Tests that passing handles through FFI calls works, and that the FFI call
// sets up the VM state etc. correctly so that the handle API calls work.
DART_EXPORT Dart_Handle PassObjectToC(Dart_Handle h) {
// Can use "h" until this function returns.
// A persistent handle which outlives this call. Lifetime managed in C.
auto persistent_handle = Dart_NewPersistentHandle(h);
Dart_Handle handle_2 = Dart_HandleFromPersistent(persistent_handle);
Dart_DeletePersistentHandle(persistent_handle);
if (Dart_IsError(handle_2)) {
Dart_PropagateError(handle_2);
}
Dart_Handle return_value;
if (!Dart_IsNull(h)) {
// A weak handle which outlives this call. Lifetime managed in C.
auto weak_handle = Dart_NewWeakPersistentHandle(
h, reinterpret_cast<void*>(0x1234), 64, RunFinalizer);
return_value = Dart_HandleFromWeakPersistent(weak_handle);
// Deleting a weak handle is not required, it deletes itself on
// finalization.
// Deleting a weak handle cancels the finalizer.
Dart_DeleteWeakPersistentHandle(weak_handle);
} else {
return_value = h;
}
return return_value;
}
DART_EXPORT void ClosureCallbackThroughHandle(void (*callback)(Dart_Handle),
Dart_Handle closureHandle) {
printf("ClosureCallbackThroughHandle %p %p\n", callback, closureHandle);
callback(closureHandle);
}
DART_EXPORT Dart_Handle ReturnHandleInCallback(Dart_Handle (*callback)()) {
printf("ReturnHandleInCallback %p\n", callback);
Dart_Handle handle = callback();
if (Dart_IsError(handle)) {
printf("callback() returned an error, propagating error\n");
// Do C/C++ resource cleanup if needed, before propagating error.
Dart_PropagateError(handle);
}
return handle;
}
// Recurses til `i` reaches 0. Throws some Dart_Invoke in there as well.
DART_EXPORT Dart_Handle HandleRecursion(Dart_Handle object,
Dart_Handle (*callback)(int64_t),
int64_t i) {
printf("HandleRecursion %" Pd64 "\n", i);
const bool do_invoke = i % 3 == 0;
const bool do_gc = i % 7 == 3;
if (do_gc) {
Dart_ExecuteInternalCommand("gc-now", nullptr);
}
Dart_Handle result;
if (do_invoke) {
Dart_Handle method_name = Dart_NewStringFromCString("a");
if (Dart_IsError(method_name)) {
Dart_PropagateError(method_name);
}
Dart_Handle arg = Dart_NewInteger(i - 1);
if (Dart_IsError(arg)) {
Dart_PropagateError(arg);
}
printf("Dart_Invoke\n");
result = Dart_Invoke(object, method_name, 1, &arg);
} else {
printf("callback\n");
result = callback(i - 1);
}
if (do_gc) {
Dart_ExecuteInternalCommand("gc-now", nullptr);
}
if (Dart_IsError(result)) {
// Do C/C++ resource cleanup if needed, before propagating error.
printf("Dart_PropagateError %" Pd64 "\n", i);
Dart_PropagateError(result);
}
printf("return %" Pd64 "\n", i);
return result;
}
DART_EXPORT int64_t HandleReadFieldValue(Dart_Handle handle) {
printf("HandleReadFieldValue\n");
Dart_Handle field_name = Dart_NewStringFromCString("a");
if (Dart_IsError(field_name)) {
printf("Dart_PropagateError(field_name)\n");
Dart_PropagateError(field_name);
}
Dart_Handle field_value = Dart_GetField(handle, field_name);
if (Dart_IsError(field_value)) {
printf("Dart_PropagateError(field_value)\n");
Dart_PropagateError(field_value);
}
int64_t value;
Dart_Handle err = Dart_IntegerToInt64(field_value, &value);
if (Dart_IsError(err)) {
Dart_PropagateError(err);
}
return value;
}
// Does not have a handle in it's own signature, so does not enter and exit
// scope in the trampoline.
DART_EXPORT int64_t PropagateErrorWithoutHandle(Dart_Handle (*callback)()) {
Dart_EnterScope();
Dart_Handle result = callback();
if (Dart_IsError(result)) {
Dart_PropagateError(result);
}
Dart_ExitScope();
return 0;
}
DART_EXPORT Dart_Handle TrueHandle() {
return Dart_True();
}
DART_EXPORT Dart_Handle PassObjectToCUseDynamicLinking(Dart_Handle h) {
auto persistent_handle = Dart_NewPersistentHandle_DL(h);
Dart_Handle handle_2 = Dart_HandleFromPersistent_DL(persistent_handle);
Dart_SetPersistentHandle_DL(persistent_handle, h);
Dart_DeletePersistentHandle_DL(persistent_handle);
auto weak_handle = Dart_NewWeakPersistentHandle_DL(
handle_2, reinterpret_cast<void*>(0x1234), 64, RunFinalizer);
Dart_Handle return_value = Dart_HandleFromWeakPersistent_DL(weak_handle);
Dart_DeleteWeakPersistentHandle_DL(weak_handle);
return return_value;
}
////////////////////////////////////////////////////////////////////////////////
// Example for doing closure callbacks with help of `dart_api.h`.
//
// sample_ffi_functions_callbacks_closures.dart
void (*callback_)(Dart_Handle);
Dart_PersistentHandle closure_to_callback_;
DART_EXPORT void RegisterClosureCallbackFP(void (*callback)(Dart_Handle)) {
callback_ = callback;
}
DART_EXPORT void RegisterClosureCallback(Dart_Handle h) {
closure_to_callback_ = Dart_NewPersistentHandle_DL(h);
}
DART_EXPORT void InvokeClosureCallback() {
Dart_Handle closure_handle =
Dart_HandleFromPersistent_DL(closure_to_callback_);
callback_(closure_handle);
}
DART_EXPORT void ReleaseClosureCallback() {
Dart_DeletePersistentHandle_DL(closure_to_callback_);
}
////////////////////////////////////////////////////////////////////////////////
// Functions for testing @FfiNative.
DART_EXPORT Dart_Handle GetRootLibraryUrl() {
Dart_Handle root_lib = Dart_RootLibrary();
Dart_Handle lib_url = Dart_LibraryUrl(root_lib);
ENSURE(!Dart_IsError(lib_url));
return lib_url;
}
intptr_t ReturnIntPtr(intptr_t x) {
return x;
}
intptr_t PassAsHandle(Dart_Handle handle) {
intptr_t result = 0;
ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(handle, 0, &result)));
return result;
}
intptr_t PassAsPointer(void* ptr) {
return reinterpret_cast<intptr_t>(ptr);
}
intptr_t PassAsPointerAndValue(void* ptr, intptr_t value) {
return reinterpret_cast<intptr_t>(value);
}
intptr_t PassAsValueAndPointer(intptr_t value, void* ptr) {
return reinterpret_cast<intptr_t>(value);
}
// We're using this to keep track of whether the finalizer has been called.
static intptr_t shared_resource = 0;
void DummyResourceFinalizer(void* isolate_peer, void* peer) {
shared_resource = 0;
}
void SetSharedResource(Dart_Handle handle, intptr_t value) {
Dart_NewFinalizableHandle(handle, nullptr, sizeof(Dart_FinalizableHandle),
DummyResourceFinalizer);
shared_resource = value;
}
intptr_t GetSharedResource() {
return shared_resource;
}
static void* FfiNativeResolver(const char* name, uintptr_t args_n) {
if (strcmp(name, "Dart_SetNativeInstanceField") == 0 && args_n == 3) {
return reinterpret_cast<void*>(Dart_SetNativeInstanceField);
}
if (strcmp(name, "IsThreadInGenerated") == 0 && args_n == 0) {
return reinterpret_cast<void*>(IsThreadInGenerated);
}
if (strcmp(name, "ReturnIntPtr") == 0 && args_n == 1) {
return reinterpret_cast<void*>(ReturnIntPtr);
}
if (strcmp(name, "PassAsHandle") == 0 && args_n == 1) {
return reinterpret_cast<void*>(PassAsHandle);
}
if (strcmp(name, "PassAsPointer") == 0 && args_n == 1) {
return reinterpret_cast<void*>(PassAsPointer);
}
if (strcmp(name, "PassAsPointerAndValue") == 0 && args_n == 2) {
return reinterpret_cast<void*>(PassAsPointerAndValue);
}
if (strcmp(name, "PassAsValueAndPointer") == 0 && args_n == 2) {
return reinterpret_cast<void*>(PassAsValueAndPointer);
}
if (strcmp(name, "SetSharedResource") == 0 && args_n == 2) {
return reinterpret_cast<void*>(SetSharedResource);
}
if (strcmp(name, "GetSharedResource") == 0 && args_n == 0) {
return reinterpret_cast<void*>(GetSharedResource);
}
// This should be unreachable in tests.
ENSURE(false);
}
DART_EXPORT void SetFfiNativeResolverForTest(Dart_Handle url) {
Dart_Handle library = Dart_LookupLibrary(url);
ENSURE(!Dart_IsError(library));
Dart_Handle result = Dart_SetFfiNativeResolver(library, &FfiNativeResolver);
ENSURE(!Dart_IsError(result));
}
} // namespace dart