Skip to main content

oxide_core/heap/
object.rs

1//! Object headers and the kind-specific payload arenas.
2
3use super::arena::{Arena, Record};
4use super::values::Range;
5use crate::value::{ShapeId, Value};
6
7/// Sentinel for an absent index in a `u32` payload field.
8pub const NONE: u32 = u32::MAX;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11#[repr(u8)]
12pub enum ObjectKind {
13    Ordinary,
14    Array,
15    Function,
16    /// A closure environment: `slots` holds the variables, `payload` the
17    /// parent scope (or [`NONE`]).
18    Scope,
19    GlobalObject,
20    /// An `arguments` object. Its elements hold the values of unmapped indices;
21    /// a mapped one's `payload` is a scope object whose slot 0 is the mapped
22    /// count and slots 1.. the parameters' cells, `Value::EMPTY` once an index
23    /// is unmapped. An unmapped one's payload is [`NONE`].
24    Arguments,
25    /// A regular expression: `payload` indexes the runtime's program store;
26    /// `lastIndex` is an own data property.
27    RegExp,
28    /// A `for-in` enumeration, never exposed to JavaScript: the elements are
29    /// the key snapshot, slot 0 the object, slot 1 the key count, `payload` the
30    /// cursor.
31    ForInIterator,
32    /// A plain kind no builtin allocates: a host function is a
33    /// [`ObjectKind::Function`] with a `native` index.
34    Native,
35    /// `new String(s)`: internal slot 0 the string. Its indices are exotic;
36    /// `length` is an own data property.
37    StringWrapper,
38    /// `new Number(n)`: internal slot 0 the number.
39    NumberWrapper,
40    /// `new Boolean(b)`: internal slot 0 the boolean.
41    BooleanWrapper,
42    /// `Object(symbol)`: internal slot 0 the symbol.
43    SymbolWrapper,
44    /// `Object(bigint)`: internal slot 0 the BigInt.
45    BigIntWrapper,
46    /// A `Date`: internal slot 0 the time value.
47    Date,
48    /// A bound function: internal slots the target, the bound `this`, then
49    /// the bound arguments.
50    BoundFunction,
51    /// A proxy: internal slot 0 the target, slot 1 the handler (`null` once
52    /// revoked).
53    Proxy,
54    /// A typed array: internal slot 0 the buffer, the data its view. Canonical
55    /// numeric keys are its elements, never properties.
56    TypedArray,
57    /// A module namespace: internal slot 0 the module, then per export (the
58    /// data's sorted keys) its cell or, for a namespace export, its module.
59    ModuleNamespace,
60    /// An object whose internal slots hold builtin state the natives read:
61    /// iterators, promises, collections and the like. `Internal::tag` says
62    /// which.
63    Builtin,
64    /// Poison written over a freed header.
65    Free,
66}
67
68impl ObjectKind {
69    /// Kinds whose `payload` indexes an [`Internal`] record.
70    #[inline]
71    pub const fn has_internal(self) -> bool {
72        matches!(
73            self,
74            ObjectKind::StringWrapper
75                | ObjectKind::NumberWrapper
76                | ObjectKind::BooleanWrapper
77                | ObjectKind::SymbolWrapper
78                | ObjectKind::BigIntWrapper
79                | ObjectKind::Date
80                | ObjectKind::BoundFunction
81                | ObjectKind::Proxy
82                | ObjectKind::TypedArray
83                | ObjectKind::ModuleNamespace
84                | ObjectKind::Builtin
85        )
86    }
87
88    /// Kinds whose index properties may live in the element range.
89    #[inline]
90    pub const fn has_elements(self) -> bool {
91        matches!(
92            self,
93            ObjectKind::Ordinary
94                | ObjectKind::Array
95                | ObjectKind::Function
96                | ObjectKind::Arguments
97                | ObjectKind::RegExp
98                | ObjectKind::NumberWrapper
99                | ObjectKind::BooleanWrapper
100                | ObjectKind::SymbolWrapper
101                | ObjectKind::BigIntWrapper
102                | ObjectKind::Date
103                | ObjectKind::BoundFunction
104                | ObjectKind::Builtin
105                | ObjectKind::GlobalObject
106        )
107    }
108
109    /// Kinds whose internal methods are not the ordinary ones for every key:
110    /// property lookups that meet one leave the heap-only fast paths for
111    /// `vm::property`'s dispatch.
112    #[inline]
113    pub const fn is_exotic(self) -> bool {
114        matches!(
115            self,
116            ObjectKind::StringWrapper
117                | ObjectKind::Proxy
118                | ObjectKind::TypedArray
119                | ObjectKind::ModuleNamespace
120        )
121    }
122}
123
124pub mod flags {
125    pub const EXTENSIBLE: u8 = 1 << 0;
126    pub const USED_AS_PROTOTYPE: u8 = 1 << 1;
127    pub const CALLABLE: u8 = 1 << 2;
128    pub const CONSTRUCTOR: u8 = 1 << 3;
129    pub const DICTIONARY: u8 = 1 << 4;
130    /// An error object (`new_error` sets it), so `Object.prototype.toString`
131    /// and the error checks need no prototype walk.
132    pub const ERROR: u8 = 1 << 5;
133    /// Index properties live as named properties only: the element range is
134    /// empty and stays so. Set when an element gets attributes other than
135    /// writable, enumerable, configurable, or the object stops being
136    /// extensible.
137    pub const SPARSE: u8 = 1 << 6;
138    /// An array whose `length` is not writable.
139    pub const LENGTH_READONLY: u8 = 1 << 7;
140}
141
142/// One fixed-size record per object (20 bytes).
143#[derive(Clone, Copy, Debug)]
144pub struct ObjectHeader {
145    pub shape: ShapeId,
146    pub slots_start: u32,
147    pub elements_start: u32,
148    /// Index into the kind's side arena, or an inline payload: the array
149    /// length, the parent scope.
150    pub payload: u32,
151    pub slots_log: u8,
152    pub elements_log: u8,
153    pub kind: ObjectKind,
154    pub flags: u8,
155}
156
157const _: () = assert!(size_of::<ObjectHeader>() <= 24);
158
159impl ObjectHeader {
160    pub fn new(
161        kind: ObjectKind,
162        shape: ShapeId,
163        slots: Range,
164        elements: Range,
165        payload: u32,
166        flags: u8,
167    ) -> Self {
168        ObjectHeader {
169            shape,
170            slots_start: slots.start,
171            elements_start: elements.start,
172            payload,
173            slots_log: slots.exponent(),
174            elements_log: elements.log(),
175            kind,
176            flags,
177        }
178    }
179
180    /// The named property slots: always a power of two
181    /// ([`ValueHeap::alloc_slots`](super::values::ValueHeap::alloc_slots)).
182    #[inline]
183    pub fn slots(&self) -> Range {
184        Range::from_exponent(self.slots_start, self.slots_log)
185    }
186
187    #[inline]
188    pub fn set_slots(&mut self, range: Range) {
189        debug_assert!(range.len == 0 || range.len.is_power_of_two());
190        self.slots_start = range.start;
191        self.slots_log = range.exponent();
192    }
193
194    #[inline]
195    pub fn elements(&self) -> Range {
196        Range::from_log(self.elements_start, self.elements_log)
197    }
198
199    #[inline]
200    pub fn set_elements(&mut self, range: Range) {
201        self.elements_start = range.start;
202        self.elements_log = range.log();
203    }
204
205    #[inline]
206    pub fn has(&self, flag: u8) -> bool {
207        self.flags & flag != 0
208    }
209}
210
211impl Record for ObjectHeader {
212    fn vacant() -> Self {
213        ObjectHeader::new(
214            ObjectKind::Free,
215            ShapeId(NONE),
216            Range::EMPTY,
217            Range::EMPTY,
218            NONE,
219            0,
220        )
221    }
222}
223
224/// The payload of a `Function` object.
225#[derive(Clone, Copy, Debug)]
226pub struct FunctionRecord {
227    /// The captured scope object, or [`NONE`].
228    pub scope: u32,
229    /// A [`FunctionBytecode`] index, or [`NONE`].
230    pub bytecode: u32,
231    /// A [`Native`](crate::vm::native::Native) index for a host function, or
232    /// [`NONE`].
233    pub native: u32,
234    /// The home object of a method that uses `super`, or [`NONE`].
235    pub home_object: u32,
236    /// A class constructor's instance-field initializer function, or
237    /// [`NONE`].
238    pub fields: u32,
239}
240
241impl Record for FunctionRecord {
242    fn vacant() -> Self {
243        FunctionRecord {
244            scope: NONE,
245            bytecode: NONE,
246            native: NONE,
247            home_object: NONE,
248            fields: NONE,
249        }
250    }
251}
252
253/// The heap side of one published code unit, shared by every closure over
254/// it: the constant table, and the records of the unit's children (as
255/// `Int32` record indices) so that a live function keeps every function it
256/// can create alive. Both are value-heap ranges, traced like everything else.
257#[derive(Clone, Copy, Debug)]
258pub struct FunctionBytecode {
259    /// The [`CodeUnitId`](crate::bytecode::CodeUnitId) index.
260    pub unit: u32,
261    pub constants: Range,
262    pub constant_count: u32,
263    pub children: Range,
264    pub child_count: u32,
265}
266
267impl Record for FunctionBytecode {
268    fn vacant() -> Self {
269        FunctionBytecode {
270            unit: NONE,
271            constants: Range::EMPTY,
272            constant_count: 0,
273            children: Range::EMPTY,
274            child_count: 0,
275        }
276    }
277}
278
279/// The internal state of an object of a kind with [`ObjectKind::has_internal`]:
280/// internal slots, traced like properties, and data that holds no values.
281#[derive(Debug)]
282pub struct Internal {
283    /// What the slots mean, for [`ObjectKind::Builtin`] objects.
284    pub tag: InternalTag,
285    pub slots: Vec<Value>,
286    pub data: InternalData,
287}
288
289/// Which builtin an [`ObjectKind::Builtin`] object belongs to.
290#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
291pub enum InternalTag {
292    None,
293    ArrayIterator,
294    StringIterator,
295    MapIterator,
296    SetIterator,
297    RegExpStringIterator,
298    IteratorHelper,
299    WrapForValidIterator,
300    AsyncFromSyncIterator,
301    Promise,
302    /// A `then` record: the capability's promise, resolve and reject, the two
303    /// handlers, and the async frame an `await` resumes.
304    PromiseReaction,
305    Map,
306    Set,
307    WeakMap,
308    WeakSet,
309    WeakRef,
310    FinalizationRegistry,
311    Generator,
312    AsyncGenerator,
313    AsyncFunction,
314    ArrayBuffer,
315    SharedArrayBuffer,
316    TypedArray,
317    DataView,
318    ModuleNamespace,
319    /// A closure environment for a native function (`FunctionRecord::scope`
320    /// of a native): its slots are the native's captured values.
321    NativeData,
322    /// Test262's `$262.IsHTMLDDA`: falsy, `typeof` "undefined", loosely equal
323    /// to `null` and `undefined`, and callable (returning `null`).
324    HtmlDda,
325    /// A `JSON.rawJSON` object (its brand).
326    RawJson,
327    /// A module record.
328    Module,
329    /// An object the `Iterator` constructor made for a subclass: QuickJS's
330    /// `JS_CLASS_ITERATOR`, which its printer names.
331    Iterator,
332    /// An embedder's host object: its datum is `InternalData::HostData`.
333    HostData,
334}
335
336/// Non-value state of an internal record.
337#[derive(Debug, Default)]
338pub enum InternalData {
339    #[default]
340    None,
341    Bytes(Vec<u8>),
342    /// A suspended generator or async frame; its registers are the record's
343    /// slots from [`FRAME_SLOTS`].
344    Frame(SavedFrame),
345    /// A collection's index; its entries are the record's slots.
346    Table(Box<super::table::Table>),
347    /// A collection iterator's position, moved by compactions.
348    Cursor(std::rc::Rc<std::cell::Cell<u32>>),
349    /// An `ArrayBuffer` or `SharedArrayBuffer`.
350    Buffer(Box<super::buffer::Buffer>),
351    /// A typed array's or `DataView`'s window; slot 0 is the buffer.
352    View(super::buffer::View),
353    /// A module record's state.
354    Module(Box<crate::vm::module::ModuleData>),
355    /// A module namespace's export names, sorted.
356    Exports(Box<[super::atom::PropertyKey]>),
357    /// A host function's body (`InternalTag::NativeData` of a host
358    /// closure); the record's slots are the values it captured.
359    HostFn(crate::vm::native::HostBody),
360    /// An embedder's value attached to a host object
361    /// (`InternalTag::HostData`).
362    HostData(std::rc::Rc<dyn std::any::Any>),
363}
364
365/// The internal slots of a suspended activation before its registers: the
366/// function, then the kind's own (an async function's promise, an async
367/// generator's request queue).
368pub const FRAME_SLOTS: usize = 2;
369
370/// The header of a suspended frame: everything `Frame` holds that is not a
371/// heap reference (the function is internal slot 0) or a position in the
372/// register file.
373#[derive(Clone, Copy, Debug)]
374pub struct SavedFrame {
375    pub unit: u32,
376    pub bytecode: u32,
377    /// Where it resumes: the instruction after its suspension point.
378    pub pc: u32,
379    pub argc: u32,
380    /// The activation's state (`vm::suspend`'s constants).
381    pub state: u8,
382}
383
384impl Record for Internal {
385    fn vacant() -> Self {
386        Internal {
387            tag: InternalTag::None,
388            slots: Vec::new(),
389            data: InternalData::None,
390        }
391    }
392}
393
394impl Internal {
395    /// Bytes owned outside the arena: charged to the trigger by the
396    /// allocating calls that grow them, not tracked by the arena (the slots
397    /// change size after allocation).
398    pub fn owned_bytes(&self) -> usize {
399        self.slots.capacity() * size_of::<Value>()
400            + match &self.data {
401                InternalData::None
402                | InternalData::Frame(_)
403                | InternalData::Cursor(_)
404                | InternalData::View(_)
405                | InternalData::Module(_)
406                | InternalData::HostFn(_)
407                | InternalData::HostData(_) => 0,
408                InternalData::Exports(keys) => keys.len() * 8,
409                InternalData::Buffer(buffer) => buffer.owned_bytes(),
410                InternalData::Bytes(bytes) => bytes.capacity(),
411                InternalData::Table(table) => table.owned_bytes(),
412            }
413    }
414}
415
416#[derive(Default)]
417pub struct PayloadArenas {
418    pub functions: Arena<FunctionRecord>,
419    pub bytecode: Arena<FunctionBytecode>,
420    pub internals: Arena<Internal>,
421}