Skip to main content

oxide_core/vm/
native.rs

1//! The native ABI: host functions that read their receiver and arguments from
2//! the register file by index and call back into JavaScript through the
3//! re-entrant [`call`](super::call) and [`construct`](super::construct) as
4//! straight-line Rust. A native roots what it holds across an allocation or a
5//! call on the root stack, which is cut back when it returns. The natives
6//! themselves are the realm's builtins (`crate::builtins`).
7
8use std::rc::Rc;
9
10use super::{Cx, Throw};
11use crate::builtins::Native;
12use crate::embed::NativeCx;
13use crate::heap::object::InternalData;
14use crate::value::Value;
15
16/// Where a native finds its receiver (register `this`) and its arguments
17/// (`argc` registers from `args`), whether `new` called it and with which
18/// `new.target`, and the function object called (whose record holds a
19/// native closure's data).
20#[derive(Clone, Copy, Debug)]
21pub(crate) struct CallSite {
22    pub this: usize,
23    pub args: usize,
24    pub argc: usize,
25    pub construct: bool,
26    pub function: Value,
27    pub new_target: Value,
28}
29
30impl CallSite {
31    #[inline]
32    pub fn this(&self, cx: &Cx<'_>) -> Value {
33        cx.heap.vm.registers[self.this]
34    }
35
36    #[inline]
37    pub fn arg(&self, cx: &Cx<'_>, index: usize) -> Value {
38        if index < self.argc {
39            cx.heap.vm.registers[self.args + index]
40        } else {
41            Value::UNDEFINED
42        }
43    }
44
45    /// The arguments as a slice of the register file.
46    #[inline]
47    pub fn args<'a>(&self, cx: &'a Cx<'_>) -> &'a [Value] {
48        &cx.heap.vm.registers[self.args..self.args + self.argc]
49    }
50
51    /// The arguments copied out (they stay rooted in their registers).
52    pub fn arg_list(&self, cx: &Cx<'_>) -> Vec<Value> {
53        self.args(cx).to_vec()
54    }
55
56    /// Internal slot `slot` of the called native closure's data object.
57    pub fn data(&self, cx: &Cx<'_>, slot: usize) -> Value {
58        let function = self.function.as_object().expect("a native closure");
59        let data = cx
60            .heap
61            .function_scope(function)
62            .expect("a native closure's data");
63        cx.heap.internal_slot(data.object(), slot)
64    }
65
66    /// Sets internal slot `slot` of the called native closure's data object.
67    pub fn set_data(&self, cx: &mut Cx<'_>, slot: usize, value: Value) {
68        let function = self.function.as_object().expect("a native closure");
69        let data = cx
70            .heap
71            .function_scope(function)
72            .expect("a native closure's data");
73        cx.heap.set_internal_slot(data.object(), slot, value);
74    }
75}
76
77/// Calls a native. A native may root what it holds across allocations on
78/// the root stack freely: the stack is cut back when it returns.
79#[inline(never)]
80pub(crate) fn call(cx: &mut Cx<'_>, native: Native, site: CallSite) -> Result<Value, Throw> {
81    let mark = cx.heap.roots().mark();
82    let result = (native.function())(cx, site);
83    cx.heap.roots().truncate(mark);
84    result
85}
86
87/// A host function's body: the embedder's Rust code, called with the
88/// engine's view and the call.
89pub type HostFn = dyn Fn(&mut NativeCx<'_>, NativeCall) -> Result<Value, Throw>;
90
91/// `Rc<HostFn>` with a `Debug` that names the kind and not the closure.
92#[derive(Clone)]
93pub struct HostBody(pub Rc<HostFn>);
94
95impl std::fmt::Debug for HostBody {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        f.write_str("HostBody")
98    }
99}
100
101/// Where a host function finds its receiver, arguments, `new.target` and
102/// captured values, read through a [`NativeCx`]. The arguments stay in the
103/// register file, rooted, while the body runs.
104#[derive(Clone, Copy, Debug)]
105pub struct NativeCall(CallSite);
106
107impl NativeCall {
108    pub fn this(&self, cx: &NativeCx<'_>) -> Value {
109        cx.heap.vm.registers[self.0.this]
110    }
111
112    /// Argument `index`; `undefined` past [`NativeCall::argc`].
113    pub fn arg(&self, cx: &NativeCx<'_>, index: usize) -> Value {
114        if index < self.0.argc {
115            cx.heap.vm.registers[self.0.args + index]
116        } else {
117            Value::UNDEFINED
118        }
119    }
120
121    pub fn argc(&self) -> usize {
122        self.0.argc
123    }
124
125    pub fn args<'a>(&self, cx: &'a NativeCx<'_>) -> &'a [Value] {
126        &cx.heap.vm.registers[self.0.args..self.0.args + self.0.argc]
127    }
128
129    /// `new.target`; `undefined` for a call.
130    pub fn new_target(&self) -> Value {
131        self.0.new_target
132    }
133
134    pub fn is_construct(&self) -> bool {
135        self.0.construct
136    }
137
138    /// The function object called.
139    pub fn callee(&self) -> Value {
140        self.0.function
141    }
142
143    /// Captured value `slot`.
144    pub fn data(&self, cx: &NativeCx<'_>, slot: usize) -> Value {
145        cx.heap.internal_slot(self.record(cx).object(), slot)
146    }
147
148    pub fn set_data(&self, cx: &mut NativeCx<'_>, slot: usize, value: Value) {
149        let record = self.record(cx);
150        cx.heap.set_internal_slot(record.object(), slot, value);
151    }
152
153    /// The number of captured values.
154    pub fn data_len(&self, cx: &NativeCx<'_>) -> usize {
155        cx.heap.internal(self.record(cx).object()).slots.len()
156    }
157
158    fn record(&self, cx: &NativeCx<'_>) -> crate::ScopeId {
159        let function = self.0.function.as_object().expect("a host function");
160        cx.heap
161            .function_scope(function)
162            .expect("a host closure's data")
163    }
164}
165
166/// The native of [`Native::HostFunction`] and [`Native::HostConstructor`]:
167/// calls the body in the function's data record through a clone of its
168/// `Rc`, so the body may re-enter the function.
169pub(crate) fn host_call(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
170    let function = site.function.as_object().expect("a host function");
171    let data = cx
172        .heap
173        .function_scope(function)
174        .expect("a host closure's data");
175    let InternalData::HostFn(body) = &cx.heap.internal(data.object()).data else {
176        unreachable!("a host function's data holds its body");
177    };
178    let body = Rc::clone(&body.0);
179    // The body may call back in (and so recurse through itself): it counts
180    // as a re-entry against the native-stack budget, as a C function's call
181    // does in QuickJS.
182    if !cx.heap.vm.enter_native() {
183        return Err(super::error::stack_overflow(cx));
184    }
185    let result = body(&mut NativeCx::from_cx(cx), NativeCall(site));
186    cx.heap.vm.leave_native();
187    result
188}