Files
sdk/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
T
Martin Kustermann bf4280ff75 [vm/concurrency] Add Dart_CreateLightweightIsolate/Dart_RunLoopAsync APIs
Dart_CreateLightweightIsolate API:

  This API can be used by embedders to create a lightweight isolate
  (inside an existing isolate group).

  It is the analogous to Dart_CreateIsolateGroup - though taking a
  parent isolate as parameter (inside whose IG we create a new isolate)
  instead of taking kernel/snapshot data.

  Right now this API works on AOT and returns an error in JIT, both cases
  are covered by the test.

Dart_RunLoopAsync:

  The API can be used by embedders to transfer ownership of an isolate to
  the VM, which will take care of running the message handling loop and
  shuts the isolate down once the last receive port has been closed.

  It does allow listening to error/exit events generated by the message
  loop implementation as well as allows setting errors-are-fatal,
  effectively the same event-loop related parameters from the
  `Isolate.spawn()` API, just in Dart.

  It's the embedders responsibility to first launch initial dart code
  which will take care of responding to events (i.e. directly/indirectly
  open a receive port) - otherwise the isolate cannot be talked to and
  would immediately shut down.

Since our vm/cc tests do support running in AOT, we use a hybrid
approach to test the functionality: We let a normal Dart test call a
small C wrapper using FFI, to create a lightweight isolate, run it on a
new thread and join that thread.

TEST=vm/dart{,_2}/isolates/dart_api_create_lightweight_isolate_test

Issue https://github.com/dart-lang/sdk/issues/36097
Closes https://github.com/dart-lang/sdk/issues/44088

Change-Id: Id77ba928793fdb517f6cb7e8130df98a0366ddd6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/170983
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
2020-11-12 10:22:16 +00:00

1039 lines
32 KiB
C++

// 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(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);
}
#if !defined(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(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(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);
}
#endif // defined(TARGET_OS_LINUX)
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)
void SleepOnAnyOS(intptr_t seconds) {
#if defined(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_);
}
} // namespace dart