Skip to content
155 changes: 155 additions & 0 deletions src/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <map>
#include <mutex>
#include <thread>

#include "cppgc/allocation.h"
Expand Down Expand Up @@ -3010,6 +3012,144 @@ v8::StartupData v8__SnapshotCreator__CreateBlob(
return self->CreateBlob(function_code_handling);
}

// Rust-side callbacks for trait-based CustomPlatform (PlatformImpl trait).
// Each callback corresponds to a C++ virtual method on TaskRunner or Platform.
// `context` is a pointer to the Rust Box<dyn PlatformImpl>.
extern "C" {
void v8__Platform__CustomPlatform__BASE__PostTask(void* context, void* isolate);
void v8__Platform__CustomPlatform__BASE__PostNonNestableTask(void* context,
void* isolate);
void v8__Platform__CustomPlatform__BASE__PostDelayedTask(
void* context, void* isolate, double delay_in_seconds);
void v8__Platform__CustomPlatform__BASE__PostNonNestableDelayedTask(
void* context, void* isolate, double delay_in_seconds);
void v8__Platform__CustomPlatform__BASE__PostIdleTask(void* context,
void* isolate);
void v8__Platform__CustomPlatform__BASE__NotifyIsolateShutdown(void* context,
void* isolate);
void v8__Platform__CustomPlatform__BASE__DROP(void* context);
}

// TaskRunner wrapper that intercepts all PostTask* virtual methods, forwards
// tasks to the default platform's queue, and notifies Rust via the
// corresponding PlatformImpl trait method.
class CustomTaskRunner final : public v8::TaskRunner {
public:
CustomTaskRunner(std::shared_ptr<v8::TaskRunner> wrapped, void* context,
v8::Isolate* isolate)
: wrapped_(std::move(wrapped)), context_(context), isolate_(isolate) {}

bool IdleTasksEnabled() override { return wrapped_->IdleTasksEnabled(); }
bool NonNestableTasksEnabled() const override {
return wrapped_->NonNestableTasksEnabled();
}
bool NonNestableDelayedTasksEnabled() const override {
return wrapped_->NonNestableDelayedTasksEnabled();
}

protected:
void PostTaskImpl(std::unique_ptr<v8::Task> task,
const v8::SourceLocation& location) override {
wrapped_->PostTask(std::move(task), location);
v8__Platform__CustomPlatform__BASE__PostTask(context_,
static_cast<void*>(isolate_));
}
void PostNonNestableTaskImpl(std::unique_ptr<v8::Task> task,
const v8::SourceLocation& location) override {
wrapped_->PostNonNestableTask(std::move(task), location);
v8__Platform__CustomPlatform__BASE__PostNonNestableTask(
context_, static_cast<void*>(isolate_));
}
void PostDelayedTaskImpl(std::unique_ptr<v8::Task> task,
double delay_in_seconds,
const v8::SourceLocation& location) override {
wrapped_->PostDelayedTask(std::move(task), delay_in_seconds, location);
v8__Platform__CustomPlatform__BASE__PostDelayedTask(
context_, static_cast<void*>(isolate_),
delay_in_seconds > 0 ? delay_in_seconds : 0.0);
}
void PostNonNestableDelayedTaskImpl(
std::unique_ptr<v8::Task> task, double delay_in_seconds,
const v8::SourceLocation& location) override {
wrapped_->PostNonNestableDelayedTask(std::move(task), delay_in_seconds,
location);
v8__Platform__CustomPlatform__BASE__PostNonNestableDelayedTask(
context_, static_cast<void*>(isolate_),
delay_in_seconds > 0 ? delay_in_seconds : 0.0);
}
void PostIdleTaskImpl(std::unique_ptr<v8::IdleTask> task,
const v8::SourceLocation& location) override {
wrapped_->PostIdleTask(std::move(task), location);
v8__Platform__CustomPlatform__BASE__PostIdleTask(
context_, static_cast<void*>(isolate_));
}

private:
std::shared_ptr<v8::TaskRunner> wrapped_;
void* context_;
v8::Isolate* isolate_;
};

// Platform subclass that overrides GetForegroundTaskRunner to wrap each
// isolate's runner with a CustomTaskRunner, and intercepts
// NotifyIsolateShutdown. Follows the inspector API pattern.
class CustomPlatform : public v8::platform::DefaultPlatform {
using IdleTaskSupport = v8::platform::IdleTaskSupport;

public:
CustomPlatform(int thread_pool_size, IdleTaskSupport idle_task_support,
void* context)
: DefaultPlatform(thread_pool_size, idle_task_support),
context_(context) {}

~CustomPlatform() override {
v8__Platform__CustomPlatform__BASE__DROP(context_);
}

std::shared_ptr<v8::TaskRunner> GetForegroundTaskRunner(
v8::Isolate* isolate, v8::TaskPriority priority) override {
auto original = DefaultPlatform::GetForegroundTaskRunner(isolate, priority);
std::lock_guard<std::mutex> lock(mutex_);
auto key = std::make_pair(isolate, priority);
auto it = runners_.find(key);
if (it != runners_.end()) {
auto runner = it->second.lock();
if (runner) return runner;
}
auto custom =
std::make_shared<CustomTaskRunner>(original, context_, isolate);
runners_[key] = custom;
return custom;
}

void NotifyIsolateShutdown(v8::Isolate* isolate) {
{
std::lock_guard<std::mutex> lock(mutex_);
for (auto it = runners_.begin(); it != runners_.end();) {
if (it->first.first == isolate) {
it = runners_.erase(it);
} else {
++it;
}
}
}
v8__Platform__CustomPlatform__BASE__NotifyIsolateShutdown(
context_, static_cast<void*>(isolate));
DefaultPlatform::NotifyIsolateShutdown(isolate);
}

v8::ThreadIsolatedAllocator* GetThreadIsolatedAllocator() override {
return nullptr;
}

private:
void* context_;
std::mutex mutex_;
std::map<std::pair<v8::Isolate*, v8::TaskPriority>,
std::weak_ptr<CustomTaskRunner>>
runners_;
};

class UnprotectedDefaultPlatform : public v8::platform::DefaultPlatform {
using IdleTaskSupport = v8::platform::IdleTaskSupport;
using InProcessStackDumping = v8::platform::InProcessStackDumping;
Expand Down Expand Up @@ -3063,6 +3203,21 @@ v8::Platform* v8__Platform__NewSingleThreadedDefaultPlatform(
.release();
}

v8::Platform* v8__Platform__NewCustomPlatform(int thread_pool_size,
bool idle_task_support,
void* context) {
if (thread_pool_size < 1) {
thread_pool_size = std::thread::hardware_concurrency();
}
thread_pool_size = std::max(std::min(thread_pool_size, 16), 1);
return std::make_unique<CustomPlatform>(
thread_pool_size,
idle_task_support ? v8::platform::IdleTaskSupport::kEnabled
: v8::platform::IdleTaskSupport::kDisabled,
context)
.release();
}

bool v8__Platform__PumpMessageLoop(v8::Platform* platform, v8::Isolate* isolate,
bool wait_for_work) {
return v8::platform::PumpMessageLoop(
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ pub use microtask::MicrotaskQueue;
pub use module::*;
pub use object::*;
pub use platform::Platform;
pub use platform::PlatformImpl;
pub use platform::new_custom_platform;
pub use platform::new_default_platform;
pub use platform::new_single_threaded_default_platform;
pub use platform::new_unprotected_default_platform;
Expand Down
186 changes: 186 additions & 0 deletions src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ unsafe extern "C" {
fn v8__Platform__NewSingleThreadedDefaultPlatform(
idle_task_support: bool,
) -> *mut Platform;
fn v8__Platform__NewCustomPlatform(
thread_pool_size: int,
idle_task_support: bool,
context: *mut std::ffi::c_void,
) -> *mut Platform;
fn v8__Platform__DELETE(this: *mut Platform);

fn v8__Platform__PumpMessageLoop(
Expand Down Expand Up @@ -60,6 +65,146 @@ unsafe extern "C" {
#[derive(Debug)]
pub struct Platform(Opaque);

/// Trait for customizing platform behavior, following the same pattern as
/// [`V8InspectorClientImpl`](crate::inspector::V8InspectorClientImpl).
///
/// Implement this trait to receive callbacks for overridden C++ virtual
/// methods on the `DefaultPlatform` and its per-isolate `TaskRunner`.
///
/// The C++ `CustomPlatform` wraps each isolate's `TaskRunner` so that
/// every `PostTask` / `PostDelayedTask` / etc. call is forwarded to the
/// default implementation *and* notifies Rust through the corresponding
/// trait method.
///
/// All methods have default no-op implementations; override only what
/// you need.
///
/// Implementations must be `Send + Sync` as callbacks may fire from any
/// thread.
#[allow(unused_variables)]
pub trait PlatformImpl: Send + Sync {
// ---- TaskRunner virtual methods ----

/// Called when `TaskRunner::PostTask` is invoked for the given isolate.
///
/// The task itself has already been forwarded to the default platform's
/// queue and will be executed by `PumpMessageLoop`. This callback is a
/// notification that a new task is available.
///
/// May be called from ANY thread (V8 background threads, etc.).
fn post_task(&self, isolate_ptr: *mut std::ffi::c_void) {}

/// Called when `TaskRunner::PostNonNestableTask` is invoked.
///
/// Same semantics as [`post_task`](Self::post_task).
fn post_non_nestable_task(&self, isolate_ptr: *mut std::ffi::c_void) {}

/// Called when `TaskRunner::PostDelayedTask` is invoked.
///
/// The task has been forwarded to the default runner's delayed queue.
/// `delay_in_seconds` is the delay before the task should execute.
/// Embedders should schedule a wake-up after this delay.
///
/// May be called from ANY thread.
fn post_delayed_task(
&self,
isolate_ptr: *mut std::ffi::c_void,
delay_in_seconds: f64,
) {
}

/// Called when `TaskRunner::PostNonNestableDelayedTask` is invoked.
///
/// Same semantics as [`post_delayed_task`](Self::post_delayed_task).
fn post_non_nestable_delayed_task(
&self,
isolate_ptr: *mut std::ffi::c_void,
delay_in_seconds: f64,
) {
}

/// Called when `TaskRunner::PostIdleTask` is invoked.
///
/// Same semantics as [`post_task`](Self::post_task).
fn post_idle_task(&self, isolate_ptr: *mut std::ffi::c_void) {}

// ---- Platform virtual methods ----

/// Called when `Platform::NotifyIsolateShutdown` is invoked.
///
/// The default `DefaultPlatform` cleanup runs after this callback
/// returns.
fn notify_isolate_shutdown(&self, isolate_ptr: *mut std::ffi::c_void) {}
}

// FFI callbacks called from C++ CustomPlatform/CustomTaskRunner.
// `context` is a raw pointer to a `Box<dyn PlatformImpl>`.

#[unsafe(no_mangle)]
unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostTask(
context: *mut std::ffi::c_void,
isolate: *mut std::ffi::c_void,
) {
let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
imp.post_task(isolate);
}

#[unsafe(no_mangle)]
unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostNonNestableTask(
context: *mut std::ffi::c_void,
isolate: *mut std::ffi::c_void,
) {
let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
imp.post_non_nestable_task(isolate);
}

#[unsafe(no_mangle)]
unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostDelayedTask(
context: *mut std::ffi::c_void,
isolate: *mut std::ffi::c_void,
delay_in_seconds: f64,
) {
let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
imp.post_delayed_task(isolate, delay_in_seconds);
}

#[unsafe(no_mangle)]
unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostNonNestableDelayedTask(
context: *mut std::ffi::c_void,
isolate: *mut std::ffi::c_void,
delay_in_seconds: f64,
) {
let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
imp.post_non_nestable_delayed_task(isolate, delay_in_seconds);
}

#[unsafe(no_mangle)]
unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostIdleTask(
context: *mut std::ffi::c_void,
isolate: *mut std::ffi::c_void,
) {
let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
imp.post_idle_task(isolate);
}

#[unsafe(no_mangle)]
unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__NotifyIsolateShutdown(
context: *mut std::ffi::c_void,
isolate: *mut std::ffi::c_void,
) {
let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
imp.notify_isolate_shutdown(isolate);
}

#[unsafe(no_mangle)]
unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__DROP(
context: *mut std::ffi::c_void,
) {
unsafe {
let _ = Box::from_raw(context as *mut Box<dyn PlatformImpl>);
}
}

/// Returns a new instance of the default v8::Platform implementation.
///
/// |thread_pool_size| is the number of worker threads to allocate for
Expand Down Expand Up @@ -111,6 +256,23 @@ pub fn new_single_threaded_default_platform(
Platform::new_single_threaded(idle_task_support)
}

/// Creates a custom platform backed by `DefaultPlatform` that delegates
/// virtual method overrides to the provided [`PlatformImpl`] trait object.
///
/// This follows the same pattern as
/// [`V8InspectorClient::new`](crate::inspector::V8InspectorClient::new).
///
/// Thread-isolated allocations are disabled (same as
/// `new_unprotected_default_platform`).
#[inline(always)]
pub fn new_custom_platform(
thread_pool_size: u32,
idle_task_support: bool,
platform_impl: impl PlatformImpl + 'static,
) -> UniqueRef<Platform> {
Platform::new_custom(thread_pool_size, idle_task_support, platform_impl)
}

impl Platform {
/// Returns a new instance of the default v8::Platform implementation.
///
Expand Down Expand Up @@ -175,6 +337,30 @@ impl Platform {
))
}
}

/// Creates a custom platform backed by `DefaultPlatform` that delegates
/// virtual method overrides to the provided [`PlatformImpl`] trait object.
///
/// The trait object is owned by the platform and will be dropped when the
/// platform is destroyed.
#[inline(always)]
pub fn new_custom(
thread_pool_size: u32,
idle_task_support: bool,
platform_impl: impl PlatformImpl + 'static,
) -> UniqueRef<Self> {
// Double-box: inner Box<dyn> is a fat pointer, outer Box gives us a
// thin pointer we can pass through C++ void*.
let boxed: Box<dyn PlatformImpl> = Box::new(platform_impl);
let context = Box::into_raw(Box::new(boxed)) as *mut std::ffi::c_void;
unsafe {
UniqueRef::from_raw(v8__Platform__NewCustomPlatform(
thread_pool_size.min(16) as i32,
idle_task_support,
context,
))
}
}
}

impl Platform {
Expand Down