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}