v8/platform.rs
1use crate::Isolate;
2use crate::isolate::RealIsolate;
3use crate::support::int;
4
5use crate::support::Opaque;
6use crate::support::Shared;
7use crate::support::SharedPtrBase;
8use crate::support::SharedRef;
9use crate::support::UniquePtr;
10use crate::support::UniqueRef;
11use crate::support::long;
12
13unsafe extern "C" {
14 fn v8__Platform__NewDefaultPlatform(
15 thread_pool_size: int,
16 idle_task_support: bool,
17 ) -> *mut Platform;
18 fn v8__Platform__NewUnprotectedDefaultPlatform(
19 thread_pool_size: int,
20 idle_task_support: bool,
21 ) -> *mut Platform;
22 fn v8__Platform__NewSingleThreadedDefaultPlatform(
23 idle_task_support: bool,
24 ) -> *mut Platform;
25 fn v8__Platform__NewCustomPlatform(
26 thread_pool_size: int,
27 idle_task_support: bool,
28 unprotected: bool,
29 context: *mut std::ffi::c_void,
30 ) -> *mut Platform;
31 fn v8__Platform__DELETE(this: *mut Platform);
32
33 fn v8__Platform__PumpMessageLoop(
34 platform: *mut Platform,
35 isolate: *mut RealIsolate,
36 wait_for_work: bool,
37 ) -> bool;
38
39 fn v8__Platform__RunIdleTasks(
40 platform: *mut Platform,
41 isolate: *mut RealIsolate,
42 idle_time_in_seconds: f64,
43 );
44
45 fn v8__Platform__NotifyIsolateShutdown(
46 platform: *mut Platform,
47 isolate: *mut RealIsolate,
48 );
49
50 fn std__shared_ptr__v8__Platform__CONVERT__std__unique_ptr(
51 unique_ptr: UniquePtr<Platform>,
52 ) -> SharedPtrBase<Platform>;
53 fn std__shared_ptr__v8__Platform__get(
54 ptr: *const SharedPtrBase<Platform>,
55 ) -> *mut Platform;
56 fn std__shared_ptr__v8__Platform__COPY(
57 ptr: *const SharedPtrBase<Platform>,
58 ) -> SharedPtrBase<Platform>;
59 fn std__shared_ptr__v8__Platform__reset(ptr: *mut SharedPtrBase<Platform>);
60 fn std__shared_ptr__v8__Platform__use_count(
61 ptr: *const SharedPtrBase<Platform>,
62 ) -> long;
63}
64
65#[repr(C)]
66#[derive(Debug)]
67pub struct Platform(Opaque);
68
69/// Trait for customizing platform behavior, following the same pattern as
70/// [`V8InspectorClientImpl`](crate::inspector::V8InspectorClientImpl).
71///
72/// Implement this trait to receive callbacks for overridden C++ virtual
73/// methods on the `DefaultPlatform` and its per-isolate `TaskRunner`.
74///
75/// The C++ `CustomPlatform` wraps each isolate's `TaskRunner` so that
76/// every `PostTask` / `PostDelayedTask` / etc. call is forwarded to the
77/// default implementation *and* notifies Rust through the corresponding
78/// trait method.
79///
80/// All methods have default no-op implementations; override only what
81/// you need.
82///
83/// Implementations must be `Send + Sync` as callbacks may fire from any
84/// thread.
85#[allow(unused_variables)]
86pub trait PlatformImpl: Send + Sync {
87 // ---- TaskRunner virtual methods ----
88
89 /// Called when `TaskRunner::PostTask` is invoked for the given isolate.
90 ///
91 /// The task itself has already been forwarded to the default platform's
92 /// queue and will be executed by `PumpMessageLoop`. This callback is a
93 /// notification that a new task is available.
94 ///
95 /// May be called from ANY thread (V8 background threads, etc.).
96 fn post_task(&self, isolate_ptr: *mut std::ffi::c_void) {}
97
98 /// Called when `TaskRunner::PostNonNestableTask` is invoked.
99 ///
100 /// Same semantics as [`post_task`](Self::post_task).
101 fn post_non_nestable_task(&self, isolate_ptr: *mut std::ffi::c_void) {}
102
103 /// Called when `TaskRunner::PostDelayedTask` is invoked.
104 ///
105 /// The task has been forwarded to the default runner's delayed queue.
106 /// `delay_in_seconds` is the delay before the task should execute.
107 /// Embedders should schedule a wake-up after this delay.
108 ///
109 /// May be called from ANY thread.
110 fn post_delayed_task(
111 &self,
112 isolate_ptr: *mut std::ffi::c_void,
113 delay_in_seconds: f64,
114 ) {
115 }
116
117 /// Called when `TaskRunner::PostNonNestableDelayedTask` is invoked.
118 ///
119 /// Same semantics as [`post_delayed_task`](Self::post_delayed_task).
120 fn post_non_nestable_delayed_task(
121 &self,
122 isolate_ptr: *mut std::ffi::c_void,
123 delay_in_seconds: f64,
124 ) {
125 }
126
127 /// Called when `TaskRunner::PostIdleTask` is invoked.
128 ///
129 /// Same semantics as [`post_task`](Self::post_task).
130 fn post_idle_task(&self, isolate_ptr: *mut std::ffi::c_void) {}
131}
132
133// FFI callbacks called from C++ CustomPlatform/CustomTaskRunner.
134// `context` is a raw pointer to a `Box<dyn PlatformImpl>`.
135
136#[unsafe(no_mangle)]
137unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostTask(
138 context: *mut std::ffi::c_void,
139 isolate: *mut std::ffi::c_void,
140) {
141 let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
142 imp.post_task(isolate);
143}
144
145#[unsafe(no_mangle)]
146unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostNonNestableTask(
147 context: *mut std::ffi::c_void,
148 isolate: *mut std::ffi::c_void,
149) {
150 let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
151 imp.post_non_nestable_task(isolate);
152}
153
154#[unsafe(no_mangle)]
155unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostDelayedTask(
156 context: *mut std::ffi::c_void,
157 isolate: *mut std::ffi::c_void,
158 delay_in_seconds: f64,
159) {
160 let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
161 imp.post_delayed_task(isolate, delay_in_seconds);
162}
163
164#[unsafe(no_mangle)]
165unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostNonNestableDelayedTask(
166 context: *mut std::ffi::c_void,
167 isolate: *mut std::ffi::c_void,
168 delay_in_seconds: f64,
169) {
170 let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
171 imp.post_non_nestable_delayed_task(isolate, delay_in_seconds);
172}
173
174#[unsafe(no_mangle)]
175unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostIdleTask(
176 context: *mut std::ffi::c_void,
177 isolate: *mut std::ffi::c_void,
178) {
179 let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
180 imp.post_idle_task(isolate);
181}
182
183#[unsafe(no_mangle)]
184unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__DROP(
185 context: *mut std::ffi::c_void,
186) {
187 unsafe {
188 let _ = Box::from_raw(context as *mut Box<dyn PlatformImpl>);
189 }
190}
191
192/// Returns a new instance of the default v8::Platform implementation.
193///
194/// |thread_pool_size| is the number of worker threads to allocate for
195/// background jobs. If a value of zero is passed, a suitable default
196/// based on the current number of processors online will be chosen.
197/// If |idle_task_support| is enabled then the platform will accept idle
198/// tasks (IdleTasksEnabled will return true) and will rely on the embedder
199/// calling v8::platform::RunIdleTasks to process the idle tasks.
200///
201/// The default platform for v8 may include restrictions and caveats on thread
202/// creation and initialization. This platform should only be used in cases
203/// where v8 can be reliably initialized on the application's main thread, or
204/// the parent thread to all threads in the system that will use v8.
205///
206/// One example of a restriction is the use of Memory Protection Keys (pkeys) on
207/// modern Linux systems using modern Intel/AMD processors. This particular
208/// technology requires that all threads using v8 are created as descendent
209/// threads of the thread that called `v8::Initialize`.
210#[inline(always)]
211pub fn new_default_platform(
212 thread_pool_size: u32,
213 idle_task_support: bool,
214) -> UniqueRef<Platform> {
215 Platform::new(thread_pool_size, idle_task_support)
216}
217
218/// Creates a platform that is identical to the default platform, but does not
219/// enforce thread-isolated allocations. This may reduce security in some cases,
220/// so this method should be used with caution in cases where the threading
221/// guarantees of `new_default_platform` cannot be upheld (generally for tests).
222#[inline(always)]
223pub fn new_unprotected_default_platform(
224 thread_pool_size: u32,
225 idle_task_support: bool,
226) -> UniqueRef<Platform> {
227 Platform::new_unprotected(thread_pool_size, idle_task_support)
228}
229
230/// The same as new_default_platform() but disables the worker thread pool.
231/// It must be used with the --single-threaded V8 flag.
232///
233/// If |idle_task_support| is enabled then the platform will accept idle
234/// tasks (IdleTasksEnabled will return true) and will rely on the embedder
235/// calling v8::platform::RunIdleTasks to process the idle tasks.
236#[inline(always)]
237pub fn new_single_threaded_default_platform(
238 idle_task_support: bool,
239) -> UniqueRef<Platform> {
240 Platform::new_single_threaded(idle_task_support)
241}
242
243/// Creates a custom platform backed by `DefaultPlatform` that delegates
244/// virtual method overrides to the provided [`PlatformImpl`] trait object.
245///
246/// This follows the same pattern as
247/// [`V8InspectorClient::new`](crate::inspector::V8InspectorClient::new).
248///
249/// When `unprotected` is true, thread-isolated allocations are disabled
250/// (same as `new_unprotected_default_platform`). This is required when
251/// isolates may be created on threads other than the one that called
252/// `V8::initialize`.
253#[inline(always)]
254pub fn new_custom_platform(
255 thread_pool_size: u32,
256 idle_task_support: bool,
257 unprotected: bool,
258 platform_impl: impl PlatformImpl + 'static,
259) -> UniqueRef<Platform> {
260 Platform::new_custom(
261 thread_pool_size,
262 idle_task_support,
263 unprotected,
264 platform_impl,
265 )
266}
267
268impl Platform {
269 /// Returns a new instance of the default v8::Platform implementation.
270 ///
271 /// |thread_pool_size| is the number of worker threads to allocate for
272 /// background jobs. If a value of zero is passed, a suitable default
273 /// based on the current number of processors online will be chosen.
274 /// If |idle_task_support| is enabled then the platform will accept idle
275 /// tasks (IdleTasksEnabled will return true) and will rely on the embedder
276 /// calling v8::platform::RunIdleTasks to process the idle tasks.
277 ///
278 /// The default platform for v8 may include restrictions and caveats on thread
279 /// creation and initialization. This platform should only be used in cases
280 /// where v8 can be reliably initialized on the application's main thread, or
281 /// the parent thread to all threads in the system that will use v8.
282 ///
283 /// One example of a restriction is the use of Memory Protection Keys (pkeys)
284 /// on modern Linux systems using modern Intel/AMD processors. This particular
285 /// technology requires that all threads using v8 are created as descendent
286 /// threads of the thread that called `v8::Initialize`.
287 #[inline(always)]
288 pub fn new(
289 thread_pool_size: u32,
290 idle_task_support: bool,
291 ) -> UniqueRef<Self> {
292 unsafe {
293 UniqueRef::from_raw(v8__Platform__NewDefaultPlatform(
294 thread_pool_size.min(16) as i32,
295 idle_task_support,
296 ))
297 }
298 }
299
300 /// Creates a platform that is identical to the default platform, but does not
301 /// enforce thread-isolated allocations. This may reduce security in some
302 /// cases, so this method should be used with caution in cases where the
303 /// threading guarantees of `new_default_platform` cannot be upheld (generally
304 /// for tests).
305 #[inline(always)]
306 pub fn new_unprotected(
307 thread_pool_size: u32,
308 idle_task_support: bool,
309 ) -> UniqueRef<Self> {
310 unsafe {
311 UniqueRef::from_raw(v8__Platform__NewUnprotectedDefaultPlatform(
312 thread_pool_size.min(16) as i32,
313 idle_task_support,
314 ))
315 }
316 }
317
318 /// The same as new() but disables the worker thread pool.
319 /// It must be used with the --single-threaded V8 flag.
320 ///
321 /// If |idle_task_support| is enabled then the platform will accept idle
322 /// tasks (IdleTasksEnabled will return true) and will rely on the embedder
323 /// calling v8::platform::RunIdleTasks to process the idle tasks.
324 #[inline(always)]
325 pub fn new_single_threaded(idle_task_support: bool) -> UniqueRef<Self> {
326 unsafe {
327 UniqueRef::from_raw(v8__Platform__NewSingleThreadedDefaultPlatform(
328 idle_task_support,
329 ))
330 }
331 }
332
333 /// Creates a custom platform backed by `DefaultPlatform` that delegates
334 /// virtual method overrides to the provided [`PlatformImpl`] trait object.
335 ///
336 /// The trait object is owned by the platform and will be dropped when the
337 /// platform is destroyed.
338 #[inline(always)]
339 pub fn new_custom(
340 thread_pool_size: u32,
341 idle_task_support: bool,
342 unprotected: bool,
343 platform_impl: impl PlatformImpl + 'static,
344 ) -> UniqueRef<Self> {
345 // Double-box: inner Box<dyn> is a fat pointer, outer Box gives us a
346 // thin pointer we can pass through C++ void*.
347 let boxed: Box<dyn PlatformImpl> = Box::new(platform_impl);
348 let context = Box::into_raw(Box::new(boxed)) as *mut std::ffi::c_void;
349 // thread_pool_size clamping (0 → hardware_concurrency, max 16) is
350 // handled on the C++ side in v8__Platform__NewCustomPlatform.
351 unsafe {
352 UniqueRef::from_raw(v8__Platform__NewCustomPlatform(
353 thread_pool_size as i32,
354 idle_task_support,
355 unprotected,
356 context,
357 ))
358 }
359 }
360}
361
362impl Platform {
363 /// Pumps the message loop for the given isolate.
364 ///
365 /// The caller has to make sure that this is called from the right thread.
366 /// Returns true if a task was executed, and false otherwise. If the call to
367 /// PumpMessageLoop is nested within another call to PumpMessageLoop, only
368 /// nestable tasks may run. Otherwise, any task may run. Unless requested through
369 /// the |wait_for_work| parameter, this call does not block if no task is pending.
370 #[inline(always)]
371 pub fn pump_message_loop(
372 platform: &SharedRef<Self>,
373 isolate: &Isolate,
374 wait_for_work: bool,
375 ) -> bool {
376 unsafe {
377 v8__Platform__PumpMessageLoop(
378 &**platform as *const Self as *mut _,
379 isolate.as_real_ptr(),
380 wait_for_work,
381 )
382 }
383 }
384
385 /// Runs pending idle tasks for at most |idle_time_in_seconds| seconds.
386 ///
387 /// The caller has to make sure that this is called from the right thread.
388 /// This call does not block if no task is pending.
389 #[inline(always)]
390 pub fn run_idle_tasks(
391 platform: &SharedRef<Self>,
392 isolate: &Isolate,
393 idle_time_in_seconds: f64,
394 ) {
395 unsafe {
396 v8__Platform__RunIdleTasks(
397 &**platform as *const Self as *mut _,
398 isolate.as_real_ptr(),
399 idle_time_in_seconds,
400 );
401 }
402 }
403
404 /// Notifies the given platform about the Isolate getting deleted soon. Has to
405 /// be called for all Isolates which are deleted - unless we're shutting down
406 /// the platform.
407 ///
408 /// The |platform| has to be created using |NewDefaultPlatform|.
409 #[inline(always)]
410 pub(crate) unsafe fn notify_isolate_shutdown(
411 platform: &SharedRef<Self>,
412 isolate: &Isolate,
413 ) {
414 unsafe {
415 v8__Platform__NotifyIsolateShutdown(
416 &**platform as *const Self as *mut _,
417 isolate.as_real_ptr(),
418 );
419 }
420 }
421}
422
423impl Shared for Platform {
424 fn from_unique_ptr(unique_ptr: UniquePtr<Self>) -> SharedPtrBase<Self> {
425 unsafe {
426 std__shared_ptr__v8__Platform__CONVERT__std__unique_ptr(unique_ptr)
427 }
428 }
429 fn get(ptr: &SharedPtrBase<Self>) -> *const Self {
430 unsafe { std__shared_ptr__v8__Platform__get(ptr) }
431 }
432 fn clone(ptr: &SharedPtrBase<Self>) -> SharedPtrBase<Self> {
433 unsafe { std__shared_ptr__v8__Platform__COPY(ptr) }
434 }
435 fn reset(ptr: &mut SharedPtrBase<Self>) {
436 unsafe { std__shared_ptr__v8__Platform__reset(ptr) }
437 }
438 fn use_count(ptr: &SharedPtrBase<Self>) -> long {
439 unsafe { std__shared_ptr__v8__Platform__use_count(ptr) }
440 }
441}
442
443impl Drop for Platform {
444 fn drop(&mut self) {
445 unsafe { v8__Platform__DELETE(self) };
446 }
447}