Skip to main content

shape_jit/ffi/object/
object_ops.rs

1// Heap allocation audit (PR-9 V8 Gap Closure):
2//   Category A (NaN-boxed returns): 2 sites
3//     jit_box(HK_JIT_OBJECT, ...) — jit_new_object, jit_object_rest
4//   Category B (intermediate/consumed): 0 sites
5//   Category C (heap islands): 2 sites (jit_new_object, jit_object_rest)
6//!
7//! Object Creation and Manipulation Operations
8//!
9//! Functions for creating objects, setting properties, and object_rest.
10
11use std::collections::HashMap;
12
13use super::super::super::context::JITContext;
14use super::super::super::jit_array::JitArray;
15use super::super::super::nan_boxing::*;
16
17// ============================================================================
18// Object Creation and Manipulation
19// ============================================================================
20
21/// Create a new object from key-value pairs on stack
22#[inline(always)]
23pub extern "C" fn jit_new_object(ctx: *mut JITContext, field_count: usize) -> u64 {
24    unsafe {
25        if ctx.is_null() || field_count > 64 {
26            return TAG_NULL;
27        }
28
29        let ctx_ref = &mut *ctx;
30
31        // Check both bounds
32        if ctx_ref.stack_ptr < field_count * 2 || ctx_ref.stack_ptr > 512 {
33            return TAG_NULL;
34        }
35
36        // Pop field_count * 2 values (key, value pairs)
37        // AUDIT(C6): heap island — values inserted into this HashMap may themselves
38        // be JitAlloc pointers (strings, arrays, nested objects). These inner
39        // allocations escape into the HashMap without GC tracking.
40        // When GC feature enabled, route through gc_allocator.
41        let mut map = HashMap::new();
42        for _ in 0..field_count {
43            // Pop value, then key
44            ctx_ref.stack_ptr -= 1;
45            let value = ctx_ref.stack[ctx_ref.stack_ptr];
46            ctx_ref.stack_ptr -= 1;
47            let key_bits = ctx_ref.stack[ctx_ref.stack_ptr];
48
49            // Key should be a string
50            if is_heap_kind(key_bits, HK_STRING) {
51                let key = jit_unbox::<String>(key_bits).clone();
52                map.insert(key, value);
53            }
54        }
55
56        jit_box(HK_JIT_OBJECT, map)
57    }
58}
59
60/// Set property on object or array (returns the modified container)
61#[inline(always)]
62pub extern "C" fn jit_set_prop(obj_bits: u64, key_bits: u64, value_bits: u64) -> u64 {
63    unsafe {
64        match heap_kind(obj_bits) {
65            Some(HK_JIT_OBJECT) => {
66                // Object with string key
67                if !is_heap_kind(key_bits, HK_STRING) {
68                    return obj_bits;
69                }
70                let obj = jit_unbox_mut::<HashMap<String, u64>>(obj_bits);
71                let key = jit_unbox::<String>(key_bits).clone();
72                let old_bits = obj.get(&key).copied().unwrap_or(TAG_NULL);
73                super::super::gc::jit_write_barrier(old_bits, value_bits);
74                obj.insert(key, value_bits);
75                obj_bits
76            }
77            Some(HK_ARRAY) => {
78                let arr = jit_unbox_mut::<JitArray>(obj_bits);
79
80                if is_number(key_bits) {
81                    // Numeric index assignment
82                    let idx_f64 = unbox_number(key_bits);
83                    let len = arr.len() as i64;
84                    let idx = if idx_f64 < 0.0 {
85                        let neg_idx = idx_f64 as i64;
86                        let actual = len + neg_idx;
87                        if actual < 0 {
88                            return obj_bits;
89                        }
90                        actual as usize
91                    } else {
92                        idx_f64 as usize
93                    };
94                    if idx < arr.len() {
95                        super::super::gc::jit_write_barrier(arr[idx], value_bits);
96                        arr.set_boxed(idx, value_bits);
97                    }
98                    obj_bits
99                } else if is_heap_kind(key_bits, HK_RANGE) {
100                    // Range assignment: arr[start:end] = values
101                    use super::super::super::context::JITRange;
102                    let range = jit_unbox::<JITRange>(key_bits);
103                    let start_bits = range.start;
104                    let end_bits = range.end;
105
106                    let start_f64 = if is_number(start_bits) {
107                        unbox_number(start_bits)
108                    } else {
109                        0.0
110                    };
111                    let end_f64 = if is_number(end_bits) {
112                        unbox_number(end_bits)
113                    } else {
114                        arr.len() as f64
115                    };
116
117                    let len = arr.len() as i32;
118                    let mut actual_start = if start_f64 < 0.0 {
119                        len + start_f64 as i32
120                    } else {
121                        start_f64 as i32
122                    };
123                    let mut actual_end = if end_f64 < 0.0 {
124                        len + end_f64 as i32
125                    } else {
126                        end_f64 as i32
127                    };
128
129                    // Clamp bounds
130                    if actual_start < 0 {
131                        actual_start = 0;
132                    }
133                    if actual_end < 0 {
134                        actual_end = 0;
135                    }
136                    if actual_start > len {
137                        actual_start = len;
138                    }
139                    if actual_end > len {
140                        actual_end = len;
141                    }
142                    if actual_start > actual_end {
143                        actual_end = actual_start;
144                    }
145
146                    let start_idx = actual_start as usize;
147                    let end_idx = actual_end as usize;
148
149                    // Get values to insert
150                    if is_heap_kind(value_bits, HK_ARRAY) {
151                        let values = jit_unbox::<JitArray>(value_bits);
152                        // Splice via Vec since JitArray doesn't support splice
153                        let mut vec = arr.as_slice().to_vec();
154                        vec.splice(start_idx..end_idx, values.iter().copied());
155                        // Rebuild the JitArray in-place
156                        let arr_mut = jit_unbox_mut::<JitArray>(obj_bits);
157                        let new_arr = JitArray::from_vec(vec);
158                        // Splice replaces the entire array contents; barrier on the container write.
159                        super::super::gc::jit_write_barrier(obj_bits, obj_bits);
160                        std::ptr::write(arr_mut as *mut JitArray, new_arr);
161                    } else {
162                        // Single value - fill range with it
163                        for idx in start_idx..end_idx {
164                            if idx < arr.len() {
165                                super::super::gc::jit_write_barrier(arr[idx], value_bits);
166                                arr.set_boxed(idx, value_bits);
167                            }
168                        }
169                    }
170                    obj_bits
171                } else {
172                    obj_bits
173                }
174            }
175            _ => obj_bits,
176        }
177    }
178}
179
180/// ObjectRest: create a new object excluding specified keys
181/// Takes (obj_bits: u64, keys_bits: u64) and returns a new object with remaining keys
182#[inline(always)]
183pub extern "C" fn jit_object_rest(obj_bits: u64, keys_bits: u64) -> u64 {
184    unsafe {
185        // Get the source object
186        if !is_heap_kind(obj_bits, HK_JIT_OBJECT) {
187            return TAG_NULL;
188        }
189        let obj = jit_unbox::<HashMap<String, u64>>(obj_bits);
190
191        // Get the keys to exclude
192        if !is_heap_kind(keys_bits, HK_ARRAY) {
193            return TAG_NULL;
194        }
195        let keys = jit_unbox::<JitArray>(keys_bits);
196
197        // Build exclude set
198        let mut exclude = std::collections::HashSet::new();
199        for &key_bits in keys.iter() {
200            if is_heap_kind(key_bits, HK_STRING) {
201                let s = jit_unbox::<String>(key_bits);
202                exclude.insert(s.clone());
203            }
204        }
205
206        // AUDIT(C7): heap island — values copied from source object into the rest
207        // HashMap may be JitAlloc pointers. These inner allocations escape into
208        // the new HashMap without GC tracking.
209        // When GC feature enabled, route through gc_allocator.
210        let mut rest = HashMap::new();
211        for (key, &value) in obj.iter() {
212            if !exclude.contains(key) {
213                rest.insert(key.clone(), value);
214            }
215        }
216
217        // Box and return
218        jit_box(HK_JIT_OBJECT, rest)
219    }
220}