MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include "gc.h"
2#include "utils.h"
3#include "shapes.h"
4#include "internal.h"
5#include "descriptors.h"
6#include "silver/engine.h"
7
8#include <assert.h>
9#include <string.h>
10
11descriptor_entry_t *desc_registry = NULL;
12
13static descriptor_entry_t arr_length_desc = {
14 .key = 0,
15 .obj_off = 0,
16 .prop_name = "length",
17 .prop_len = 6,
18 .writable = true,
19 .enumerable = false,
20 .configurable = false,
21 .has_getter = false,
22 .has_setter = false,
23 .getter = 0,
24 .setter = 0,
25};
26
27static inline bool is_canonical_desc_obj(ant_value_t obj) {
28 return vtype(obj) == T_OBJ;
29}
30
31static inline bool is_exotic_desc_obj(ant_value_t obj) {
32 ant_object_t *ptr = js_obj_ptr(obj);
33 return ptr && ptr->is_exotic;
34}
35
36static inline bool desc_registry_allowed(ant_value_t obj) {
37 if (is_exotic_desc_obj(obj)) return true;
38#ifndef NDEBUG
39 assert(false && "desc_registry is exotic-only; ordinary objects must use shape metadata");
40#endif
41 return false;
42}
43
44static bool ensure_added_shape_slot_storage(ant_object_t *ptr, uint32_t slot) {
45 if (!ptr || !ptr->shape) return false;
46 if (!js_obj_ensure_prop_capacity(ptr, ant_shape_count(ptr->shape))) return false;
47 if (slot >= ptr->prop_count && !js_obj_ensure_prop_capacity(ptr, slot + 1)) return false;
48
49 return true;
50}
51
52static inline uint8_t attrs_from_flags(int flags, bool include_writable) {
53 uint8_t attrs = 0;
54 if (include_writable && (flags & JS_DESC_W)) attrs |= ANT_PROP_ATTR_WRITABLE;
55 if (flags & JS_DESC_E) attrs |= ANT_PROP_ATTR_ENUMERABLE;
56 if (flags & JS_DESC_C) attrs |= ANT_PROP_ATTR_CONFIGURABLE;
57 return attrs;
58}
59
60uint64_t make_desc_key(ant_value_t obj, const char *key, size_t klen) {
61 assert(is_canonical_desc_obj(obj) && "descriptor APIs require canonical js_as_obj(...) value");
62 if (!is_canonical_desc_obj(obj)) return hash_key(key, klen);
63 uintptr_t obj_off = (uintptr_t)vdata(obj);
64 uint64_t key_hash = hash_key(key, klen);
65 uint64_t h = (uint64_t)obj_off ^ ((uint64_t)obj_off >> 33);
66 h ^= key_hash + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
67 return h;
68}
69
70uint64_t make_sym_desc_key(ant_value_t obj, ant_offset_t sym_off) {
71 assert(is_canonical_desc_obj(obj) && "descriptor APIs require canonical js_as_obj(...) value");
72 if (!is_canonical_desc_obj(obj)) return ((uint64_t)sym_off << 1) | 1u;
73 uintptr_t obj_off = (uintptr_t)vdata(obj);
74 uint64_t h = (uint64_t)obj_off ^ ((uint64_t)obj_off >> 33);
75 h ^= ((uint64_t)sym_off << 1) | 1u;
76 h += 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
77 return h;
78}
79
80static descriptor_entry_t *registry_lookup_desc(ant_value_t obj, const char *key, size_t klen) {
81 descriptor_entry_t *entry = NULL;
82 uint64_t desc_key = make_desc_key(obj, key, klen);
83 HASH_FIND(hh, desc_registry, &desc_key, sizeof(uint64_t), entry);
84 return entry;
85}
86
87static descriptor_entry_t *registry_lookup_sym_desc(ant_value_t obj, ant_offset_t sym_off) {
88 descriptor_entry_t *entry = NULL;
89 uint64_t k = make_sym_desc_key(obj, sym_off);
90 HASH_FIND(hh, desc_registry, &k, sizeof(uint64_t), entry);
91 return entry;
92}
93
94descriptor_entry_t *lookup_descriptor(ant_value_t obj, const char *key, size_t klen) {
95 assert(is_canonical_desc_obj(obj) && "lookup_descriptor expects js_as_obj(...)");
96 if (!is_canonical_desc_obj(obj)) return NULL;
97
98 ant_object_t *ptr = js_obj_ptr(obj);
99 if (klen == 6 && memcmp(key, "length", 6) == 0 && ptr && ptr->type_tag == T_ARR)
100 return &arr_length_desc;
101
102 if (!is_exotic_desc_obj(obj)) return NULL;
103 return registry_lookup_desc(obj, key, klen);
104}
105
106descriptor_entry_t *lookup_sym_descriptor(ant_value_t obj, ant_offset_t sym_off) {
107 assert(is_canonical_desc_obj(obj) && "lookup_sym_descriptor expects js_as_obj(...)");
108 if (!is_canonical_desc_obj(obj)) return NULL;
109
110 if (!is_exotic_desc_obj(obj)) return NULL;
111 return registry_lookup_sym_desc(obj, sym_off);
112}
113
114static descriptor_entry_t *get_or_create_desc(ant_t *js, ant_value_t obj, const char *key, size_t klen) {
115 if (!desc_registry_allowed(obj)) return NULL;
116 descriptor_entry_t *entry = registry_lookup_desc(obj, key, klen);
117 if (entry) return entry;
118
119 entry = (descriptor_entry_t *)ant_calloc(sizeof(descriptor_entry_t) + klen + 1);
120 if (!entry) return NULL;
121
122 entry->key = make_desc_key(obj, key, klen);
123 entry->obj_off = (uintptr_t)vdata(obj);
124 entry->prop_name = (char *)(entry + 1);
125 memcpy(entry->prop_name, key, klen);
126 entry->prop_name[klen] = '\0';
127 entry->prop_len = klen;
128 entry->writable = true;
129 entry->enumerable = true;
130 entry->configurable = true;
131 entry->has_getter = false;
132 entry->has_setter = false;
133 entry->getter = js_mkundef();
134 entry->setter = js_mkundef();
135
136 HASH_ADD(hh, desc_registry, key, sizeof(uint64_t), entry);
137 return entry;
138}
139
140static descriptor_entry_t *get_or_create_sym_desc(ant_value_t obj, ant_offset_t sym_off) {
141 if (!desc_registry_allowed(obj)) return NULL;
142 descriptor_entry_t *entry = registry_lookup_sym_desc(obj, sym_off);
143 if (entry) return entry;
144
145 entry = (descriptor_entry_t *)ant_calloc(sizeof(descriptor_entry_t));
146 if (!entry) return NULL;
147
148 entry->key = make_sym_desc_key(obj, sym_off);
149 entry->obj_off = (uintptr_t)vdata(obj);
150 entry->sym_off = sym_off;
151 entry->writable = true;
152 entry->enumerable = true;
153 entry->configurable = true;
154 entry->has_getter = false;
155 entry->has_setter = false;
156 entry->getter = js_mkundef();
157 entry->setter = js_mkundef();
158
159 HASH_ADD(hh, desc_registry, key, sizeof(uint64_t), entry);
160 return entry;
161}
162
163static bool ensure_string_shape_slot(ant_t *js, ant_value_t obj, const char *key, size_t klen, uint32_t *out_slot) {
164 ant_object_t *ptr = js_obj_ptr(obj);
165 if (!ptr || !ptr->shape) return false;
166
167 const char *interned = intern_string(key, klen);
168 if (!interned) return false;
169
170 int32_t slot = ant_shape_lookup_interned(ptr->shape, interned);
171 if (slot < 0) {
172 uint32_t added_slot = 0;
173 if (!ant_shape_add_interned_tr(&ptr->shape, interned, ANT_PROP_ATTR_DEFAULT, &added_slot)) return false;
174 if (!ensure_added_shape_slot_storage(ptr, added_slot)) return false;
175 slot = (int32_t)added_slot;
176 }
177
178 if (out_slot) *out_slot = (uint32_t)slot;
179 return true;
180}
181
182static bool ensure_symbol_shape_slot(ant_t *js, ant_value_t obj, ant_offset_t sym_off, uint32_t *out_slot) {
183 ant_object_t *ptr = js_obj_ptr(obj);
184 if (!ptr || !ptr->shape) return false;
185
186 int32_t slot = ant_shape_lookup_symbol(ptr->shape, sym_off);
187 if (slot < 0) {
188 ant_value_t sym = mkval(T_SYMBOL, sym_off);
189 if (is_err(mkprop(js, obj, sym, js_mkundef(), 0))) return false;
190 slot = ant_shape_lookup_symbol(ptr->shape, sym_off);
191 if (slot < 0) return false;
192 }
193
194 if (out_slot) *out_slot = (uint32_t)slot;
195 return true;
196}
197
198static ant_shape_prop_t *ensure_string_shape_prop(ant_t *js, ant_value_t obj, const char *key, size_t klen) {
199 uint32_t slot = 0;
200 if (!ensure_string_shape_slot(js, obj, key, klen, &slot)) return NULL;
201 ant_object_t *ptr = js_obj_ptr(obj);
202 if (!ptr || !ptr->shape) return NULL;
203 if (!js_obj_ensure_unique_shape(ptr)) return NULL;
204 return ant_shape_prop_mut_at(ptr->shape, slot);
205}
206
207static ant_shape_prop_t *ensure_symbol_shape_prop(ant_t *js, ant_value_t obj, ant_offset_t sym_off) {
208 uint32_t slot = 0;
209 if (!ensure_symbol_shape_slot(js, obj, sym_off, &slot)) return NULL;
210 ant_object_t *ptr = js_obj_ptr(obj);
211 if (!ptr || !ptr->shape) return NULL;
212 if (!js_obj_ensure_unique_shape(ptr)) return NULL;
213 return ant_shape_prop_mut_at(ptr->shape, slot);
214}
215
216static void apply_shape_desc_update(
217 ant_shape_prop_t *prop,
218 int flags,
219 bool include_writable,
220 bool set_getter_flag,
221 bool has_getter,
222 ant_value_t getter,
223 bool set_setter_flag,
224 bool has_setter,
225 ant_value_t setter
226) {
227 if (!prop) return;
228 prop->attrs = attrs_from_flags(flags, include_writable);
229 if (set_getter_flag) {
230 prop->has_getter = has_getter ? 1 : 0;
231 prop->getter = has_getter ? getter : js_mkundef();
232 }
233 if (set_setter_flag) {
234 prop->has_setter = has_setter ? 1 : 0;
235 prop->setter = has_setter ? setter : js_mkundef();
236 }
237}
238
239static void apply_registry_desc_update(
240 descriptor_entry_t *entry,
241 int flags,
242 bool include_writable,
243 bool set_getter_flag,
244 bool has_getter,
245 ant_value_t getter,
246 bool set_setter_flag,
247 bool has_setter,
248 ant_value_t setter
249) {
250 if (!entry) return;
251 if (include_writable) entry->writable = (flags & JS_DESC_W) != 0;
252 entry->enumerable = (flags & JS_DESC_E) != 0;
253 entry->configurable = (flags & JS_DESC_C) != 0;
254 if (set_getter_flag) {
255 entry->has_getter = has_getter;
256 entry->getter = has_getter ? getter : js_mkundef();
257 }
258 if (set_setter_flag) {
259 entry->has_setter = has_setter;
260 entry->setter = has_setter ? setter : js_mkundef();
261 }
262}
263
264void js_set_descriptor(ant_t *js, ant_value_t obj, const char *key, size_t klen, int flags) {
265 assert(is_canonical_desc_obj(obj) && "js_set_descriptor expects js_as_obj(...)");
266 if (!is_canonical_desc_obj(obj)) return;
267
268 ant_shape_prop_t *prop = ensure_string_shape_prop(js, obj, key, klen);
269 if (prop) {
270 apply_shape_desc_update(
271 prop, flags, true,
272 false, false, js_mkundef(),
273 false, false, js_mkundef()
274 );
275 ant_ic_epoch_bump();
276 return;
277 }
278
279 if (!desc_registry_allowed(obj)) return;
280 descriptor_entry_t *entry = get_or_create_desc(js, obj, key, klen);
281 apply_registry_desc_update(
282 entry, flags, true,
283 false, false, js_mkundef(),
284 false, false, js_mkundef()
285 );
286}
287
288void js_set_getter_desc(ant_t *js, ant_value_t obj, const char *key, size_t klen, ant_value_t getter, int flags) {
289 assert(is_canonical_desc_obj(obj) && "js_set_getter_desc expects js_as_obj(...)");
290 if (!is_canonical_desc_obj(obj)) return;
291
292 ant_shape_prop_t *prop = ensure_string_shape_prop(js, obj, key, klen);
293 if (prop) {
294 apply_shape_desc_update(
295 prop, flags, false,
296 true, true, getter,
297 false, false, js_mkundef()
298 );
299 ant_ic_epoch_bump();
300 gc_write_barrier(js, js_obj_ptr(js_as_obj(obj)), getter);
301 return;
302 }
303
304 if (!desc_registry_allowed(obj)) return;
305 descriptor_entry_t *entry = get_or_create_desc(js, obj, key, klen);
306 apply_registry_desc_update(
307 entry, flags, false,
308 true, true, getter,
309 false, false, js_mkundef()
310 );
311}
312
313void js_set_setter_desc(ant_t *js, ant_value_t obj, const char *key, size_t klen, ant_value_t setter, int flags) {
314 assert(is_canonical_desc_obj(obj) && "js_set_setter_desc expects js_as_obj(...)");
315 if (!is_canonical_desc_obj(obj)) return;
316
317 ant_shape_prop_t *prop = ensure_string_shape_prop(js, obj, key, klen);
318 if (prop) {
319 apply_shape_desc_update(
320 prop, flags, false,
321 false, false, js_mkundef(),
322 true, true, setter
323 );
324 ant_ic_epoch_bump();
325 gc_write_barrier(js, js_obj_ptr(js_as_obj(obj)), setter);
326 return;
327 }
328
329 if (!desc_registry_allowed(obj)) return;
330 descriptor_entry_t *entry = get_or_create_desc(js, obj, key, klen);
331 apply_registry_desc_update(
332 entry, flags, false,
333 false, false, js_mkundef(),
334 true, true, setter
335 );
336}
337
338void js_set_accessor_desc(ant_t *js, ant_value_t obj, const char *key, size_t klen, ant_value_t getter, ant_value_t setter, int flags) {
339 assert(is_canonical_desc_obj(obj) && "js_set_accessor_desc expects js_as_obj(...)");
340 if (!is_canonical_desc_obj(obj)) return;
341
342 ant_shape_prop_t *prop = ensure_string_shape_prop(js, obj, key, klen);
343 if (prop) {
344 apply_shape_desc_update(
345 prop, flags, false,
346 true, true, getter,
347 true, true, setter
348 );
349 ant_ic_epoch_bump();
350 gc_write_barrier(js, js_obj_ptr(js_as_obj(obj)), getter);
351 gc_write_barrier(js, js_obj_ptr(js_as_obj(obj)), setter);
352 return;
353 }
354
355 if (!desc_registry_allowed(obj)) return;
356 descriptor_entry_t *entry = get_or_create_desc(js, obj, key, klen);
357 apply_registry_desc_update(
358 entry, flags, false,
359 true, true, getter,
360 true, true, setter
361 );
362}
363
364void js_set_sym_getter_desc(ant_t *js, ant_value_t obj, ant_value_t sym, ant_value_t getter, int flags) {
365 assert(is_canonical_desc_obj(obj) && "js_set_sym_getter_desc expects js_as_obj(...)");
366 if (!is_canonical_desc_obj(obj)) return;
367 if (vtype(sym) != T_SYMBOL) return;
368 ant_offset_t sym_off = (ant_offset_t)vdata(sym);
369
370 ant_shape_prop_t *prop = ensure_symbol_shape_prop(js, obj, sym_off);
371 if (prop) {
372 apply_shape_desc_update(
373 prop, flags, false,
374 true, true, getter,
375 false, false, js_mkundef()
376 );
377 ant_ic_epoch_bump();
378 gc_write_barrier(js, js_obj_ptr(js_as_obj(obj)), getter);
379 return;
380 }
381
382 if (!desc_registry_allowed(obj)) return;
383 descriptor_entry_t *entry = get_or_create_sym_desc(obj, sym_off);
384 apply_registry_desc_update(
385 entry, flags, false,
386 true, true, getter,
387 false, false, js_mkundef()
388 );
389}
390
391void js_set_sym_setter_desc(ant_t *js, ant_value_t obj, ant_value_t sym, ant_value_t setter, int flags) {
392 assert(is_canonical_desc_obj(obj) && "js_set_sym_setter_desc expects js_as_obj(...)");
393 if (!is_canonical_desc_obj(obj)) return;
394 if (vtype(sym) != T_SYMBOL) return;
395 ant_offset_t sym_off = (ant_offset_t)vdata(sym);
396
397 ant_shape_prop_t *prop = ensure_symbol_shape_prop(js, obj, sym_off);
398 if (prop) {
399 apply_shape_desc_update(
400 prop, flags, false,
401 false, false, js_mkundef(),
402 true, true, setter
403 );
404 ant_ic_epoch_bump();
405 gc_write_barrier(js, js_obj_ptr(js_as_obj(obj)), setter);
406 return;
407 }
408
409 if (!desc_registry_allowed(obj)) return;
410 descriptor_entry_t *entry = get_or_create_sym_desc(obj, sym_off);
411 apply_registry_desc_update(
412 entry, flags, false,
413 false, false, js_mkundef(),
414 true, true, setter
415 );
416}