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}