1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! This is AnyTask except it gives you two pointers instead of one.
//! Generally, prefer jsc.Task instead of this.
use core::ffi::c_void;
use core::marker::PhantomData;
use core::ptr::NonNull;
pub struct AnyTaskWithExtraContext {
pub ctx: Option<NonNull<()>>,
pub callback: fn(*mut (), *mut ()),
/// Intrusive link for `UnboundedQueue(AnyTaskWithExtraContext, .next)` (MiniEventLoop).
pub next: bun_threading::Link<AnyTaskWithExtraContext>,
}
impl Default for AnyTaskWithExtraContext {
fn default() -> Self {
// Zig: ctx/callback default to `undefined`, next defaults to `null`.
Self {
ctx: None,
callback: |_, _| unreachable!("callback was undefined"),
next: bun_threading::Link::new(),
}
}
}
impl AnyTaskWithExtraContext {
/// Heap-allocates a wrapper around `ptr`, returns a pointer to the embedded
/// `AnyTaskWithExtraContext`. When `run` fires, it calls `callback(ptr, extra)`
/// and then frees the wrapper.
///
/// Zig signature: `fn fromCallbackAutoDeinit(ptr: anytype, comptime fieldName: [:0]const u8) *AnyTaskWithExtraContext`
/// where `fieldName` names a decl on `@TypeOf(ptr).*`. Rust cannot look up a
/// method by comptime string, so callers pass the function directly.
pub fn from_callback_auto_deinit<T>(
ptr: *mut T,
callback: fn(*mut T, *mut c_void),
) -> *mut AnyTaskWithExtraContext {
#[repr(C)]
struct Wrapper<T> {
any_task: AnyTaskWithExtraContext,
// TODO(port): LIFETIMES.tsv classifies this as BORROW_PARAM (&'a mut T),
// but Wrapper is Box'd and escapes the call frame, so a borrow lifetime
// cannot be expressed. Kept as raw; caller guarantees `ptr` outlives the task.
wrapped: *mut T,
// Extra field vs Zig: Zig monomorphized the callback into `Wrapper.function`
// via `comptime fieldName`. Stable Rust has no const fn-pointer generics,
// so we store it here instead.
callback: fn(*mut T, *mut c_void),
}
fn function<T>(this: *mut (), extra: *mut ()) {
// SAFETY: `this` is the `ctx` we set below, which is the Box'd `Wrapper<T>`
// pointer. `any_task` is the first field of a `#[repr(C)]` struct, so the
// address is also valid as `*mut Wrapper<T>`.
let that: Box<Wrapper<T>> = unsafe { bun_core::heap::take(this.cast::<Wrapper<T>>()) };
// `defer bun.default_allocator.destroy(that)` — Box drops at end of scope.
let ctx = that.wrapped;
(that.callback)(ctx, extra.cast::<c_void>());
}
let task = bun_core::heap::into_raw(Box::new(Wrapper::<T> {
any_task: AnyTaskWithExtraContext {
callback: function::<T>,
ctx: None, // patched below to point at the Box itself
next: bun_threading::Link::new(),
},
wrapped: ptr,
callback,
}));
// SAFETY: `task` was just produced by heap::alloc; valid and exclusive.
unsafe {
(*task).any_task.ctx = NonNull::new(task.cast::<()>());
core::ptr::addr_of_mut!((*task).any_task)
}
}
/// Zig signature: `fn from(this: *@This(), of: anytype, comptime field: []const u8) *@This()`
/// — initializes `this` in place to call `@TypeOf(of).field(of, extra)` with
/// `ContextType = void`.
// PORT NOTE: Zig used `@field(T, field)` comptime decl lookup; Rust callers
// pass the fn pointer directly.
// TODO(port): Zig passes `ContextType = void` (the unit type, NOT `anyopaque`);
// `*void` is zero-bit so the callee is effectively `fn(*T)` only. Mapped here
// to `*mut ()` — could be `fn(*mut T)` with the second arg dropped.
// PORT NOTE: name kept as `from` to match Zig; not the `From` trait.
pub fn from<T>(&mut self, of: *mut T, callback: fn(*mut T, *mut ())) -> *mut Self {
*self = New::<T, ()>::init(of, callback);
std::ptr::from_mut::<Self>(self)
}
pub fn run(&mut self, extra: *mut c_void) {
// Zig: @setRuntimeSafety(false) — no-op in Rust release; debug keeps the unwrap check.
let callback = self.callback;
let ctx = self.ctx;
// SAFETY: caller contract — `ctx` was set by `init`/`from*` to a live pointer.
callback(ctx.expect("ctx is non-null").as_ptr(), extra.cast::<()>());
}
}
/// Zig: `fn New(comptime Type: type, comptime ContextType: type, comptime Callback: anytype) type`
///
/// Stable Rust cannot take a fn value as a const generic, so `Callback` moves to
/// a runtime argument on `init` and is type-erased (ABI-identical: both forms
/// are thin fn pointers taking two thin data pointers).
// TODO(port): if a zero-storage comptime form is ever needed, switch to a
// `trait TaskCallback<C> { fn call(&mut self, extra: *mut C); }` bound on `T`.
pub struct New<T, C>(PhantomData<(*mut T, *mut C)>);
impl<T, C> New<T, C> {
pub fn init(ctx: *mut T, callback: fn(*mut T, *mut C)) -> AnyTaskWithExtraContext {
AnyTaskWithExtraContext {
// SAFETY: `fn(*mut T, *mut C)` and `fn(*mut (), *mut ())` have identical
// ABI (single code pointer, two pointer-sized args). This is the moral
// equivalent of Zig's `wrap` thunk that `@ptrCast`/`@alignCast`s the args.
callback: unsafe {
bun_ptr::cast_fn_ptr::<fn(*mut T, *mut C), fn(*mut (), *mut ())>(callback)
},
ctx: NonNull::new(ctx.cast::<()>()),
next: bun_threading::Link::new(),
}
}
// TODO(port): Zig's `New(...).wrap(this: ?*anyopaque, extra: ?*anyopaque)` was
// the type-erasing thunk stored in `.callback = wrap`. Because stable Rust
// can't take `Callback` as a const generic, `init` erases the typed fn
// pointer directly instead — so `wrap` is folded into that cast and
// intentionally omitted here. If this ever switches to a `TaskCallback<C>`
// trait bound on `T`, reintroduce `wrap` as the 2-arg stored thunk.
// PERF(port): Zig used `@call(bun.callmod_inline, Callback, ...)` — profile if hot.
}
// ported from: src/event_loop/AnyTaskWithExtraContext.zig