MIRROR: javascript for 🐜's, a tiny runtime with big ambitions
1#include <math.h>
2#include <stdint.h>
3#include <stdlib.h>
4#include <string.h>
5#include <uv.h>
6
7#include "arena.h"
8#include "errors.h"
9#include "internal.h"
10#include "modules/buffer.h"
11
12// serialize / deserialize
13//
14// wire format:
15// [0xAB 0xCD] — magic header (2 bytes)
16// <value> — recursively encoded
17//
18// value tags (1 byte):
19// 'U' undefined
20// 'N' null
21// 'T' true
22// 'F' false
23// 'Z' NaN
24// 'D' double (8 bytes little-endian)
25// 'S' string (uint32 length LE + UTF-8 bytes)
26// 'A' array (uint32 count LE + <value>…)
27// 'O' object (uint32 count LE + (uint32 klen + key bytes + <value>)…)
28
29#define SER_MAGIC_0 0xAB
30#define SER_MAGIC_1 0xCD
31
32typedef struct {
33 uint8_t *data;
34 size_t len;
35 size_t cap;
36} enc_t;
37
38static bool enc_grow(enc_t *e, size_t need) {
39 if (e->len + need <= e->cap) return true;
40 size_t nc = e->cap ? e->cap * 2 : 64;
41 while (nc < e->len + need) nc *= 2;
42 uint8_t *p = realloc(e->data, nc);
43 if (!p) return false;
44 e->data = p;
45 e->cap = nc;
46 return true;
47}
48
49static bool enc_u8(enc_t *e, uint8_t b) {
50 if (!enc_grow(e, 1)) return false;
51 e->data[e->len++] = b;
52 return true;
53}
54
55static bool enc_u32(enc_t *e, uint32_t v) {
56 if (!enc_grow(e, 4)) return false;
57 e->data[e->len++] = (uint8_t)(v);
58 e->data[e->len++] = (uint8_t)(v >> 8);
59 e->data[e->len++] = (uint8_t)(v >> 16);
60 e->data[e->len++] = (uint8_t)(v >> 24);
61 return true;
62}
63
64static bool enc_bytes(enc_t *e, const void *src, size_t n) {
65 if (!enc_grow(e, n)) return false;
66 memcpy(e->data + e->len, src, n);
67 e->len += n;
68 return true;
69}
70
71static bool ser_val(ant_t *js, enc_t *e, ant_value_t val, int depth) {
72 if (depth > 64) return false;
73 uint8_t t = vtype(val);
74
75 if (t == T_UNDEF) return enc_u8(e, 'U');
76 if (t == T_NULL) return enc_u8(e, 'N');
77 if (t == T_BOOL) return enc_u8(e, (val == js_true) ? 'T' : 'F');
78
79 if (t == T_NUM) {
80 double d = js_getnum(val);
81 if (isnan(d)) return enc_u8(e, 'Z');
82 if (!enc_u8(e, 'D')) return false;
83 return enc_bytes(e, &d, 8);
84 }
85
86 if (t == T_STR) {
87 size_t slen;
88 const char *s = js_getstr(js, val, &slen);
89 if (!s) { s = ""; slen = 0; }
90 if (!enc_u8(e, 'S')) return false;
91 if (!enc_u32(e, (uint32_t)slen)) return false;
92 return enc_bytes(e, s, slen);
93 }
94
95 if (t == T_ARR) {
96 ant_offset_t n = js_arr_len(js, val);
97 if (!enc_u8(e, 'A')) return false;
98 if (!enc_u32(e, (uint32_t)n)) return false;
99 for (ant_offset_t i = 0; i < n; i++) {
100 if (!ser_val(js, e, js_arr_get(js, val, i), depth + 1)) return false;
101 }
102 return true;
103 }
104
105 if (t == T_OBJ) {
106 uint32_t count = 0;
107 ant_iter_t it = js_prop_iter_begin(js, val);
108 const char *k; size_t klen; ant_value_t v;
109 while (js_prop_iter_next(&it, &k, &klen, &v)) count++;
110 js_prop_iter_end(&it);
111
112 if (!enc_u8(e, 'O')) return false;
113 if (!enc_u32(e, count)) return false;
114
115 it = js_prop_iter_begin(js, val);
116 while (js_prop_iter_next(&it, &k, &klen, &v)) {
117 if (!enc_u32(e, (uint32_t)klen) || !enc_bytes(e, k, klen)) {
118 js_prop_iter_end(&it);
119 return false;
120 }
121 if (!ser_val(js, e, v, depth + 1)) {
122 js_prop_iter_end(&it);
123 return false;
124 }
125 }
126 js_prop_iter_end(&it);
127 return true;
128 }
129
130 return enc_u8(e, 'U');
131}
132
133static ant_value_t v8_serialize(ant_t *js, ant_value_t *args, int nargs) {
134 if (nargs < 1) return js_mkerr(js, "serialize: value required");
135
136 enc_t e = {0};
137 if (!enc_u8(&e, SER_MAGIC_0) || !enc_u8(&e, SER_MAGIC_1)) goto oom;
138 if (!ser_val(js, &e, args[0], 0)) goto oom;
139
140 ArrayBufferData *ab = create_array_buffer_data(e.len);
141 if (!ab) goto oom;
142 memcpy(ab->data, e.data, e.len);
143 free(e.data);
144 return create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, e.len, "Buffer");
145
146oom:
147 free(e.data);
148 return js_mkerr(js, "serialize: out of memory");
149}
150
151typedef struct {
152 const uint8_t *data;
153 size_t len;
154 size_t pos;
155} dec_t;
156
157static bool dec_u8(dec_t *d, uint8_t *out) {
158 if (d->pos >= d->len) return false;
159 *out = d->data[d->pos++];
160 return true;
161}
162
163static bool dec_u32(dec_t *d, uint32_t *out) {
164 if (d->pos + 4 > d->len) return false;
165 *out = (uint32_t)d->data[d->pos]
166 | ((uint32_t)d->data[d->pos+1] << 8)
167 | ((uint32_t)d->data[d->pos+2] << 16)
168 | ((uint32_t)d->data[d->pos+3] << 24);
169 d->pos += 4;
170 return true;
171}
172
173static ant_value_t des_val(ant_t *js, dec_t *d, int depth) {
174 if (depth > 64) return js_mkerr(js, "deserialize: maximum depth exceeded");
175
176 uint8_t tag;
177 if (!dec_u8(d, &tag)) return js_mkundef();
178
179 switch (tag) {
180 case 'U': return js_mkundef();
181 case 'N': return js_mknull();
182 case 'T': return js_true;
183 case 'F': return js_false;
184 case 'Z': return js_mknum((double)NAN);
185 case 'D': {
186 if (d->pos + 8 > d->len) return js_mkundef();
187 double v;
188 memcpy(&v, d->data + d->pos, 8);
189 d->pos += 8;
190 return js_mknum(v);
191 }
192 case 'S': {
193 uint32_t n;
194 if (!dec_u32(d, &n) || d->pos + n > d->len) return js_mkundef();
195 ant_value_t s = js_mkstr(js, (const char *)(d->data + d->pos), n);
196 d->pos += n;
197 return s;
198 }
199 case 'A': {
200 uint32_t n;
201 if (!dec_u32(d, &n)) return js_mkundef();
202 ant_value_t arr = js_mkarr(js);
203 for (uint32_t i = 0; i < n; i++) {
204 ant_value_t elem = des_val(js, d, depth + 1);
205 if (is_err(elem)) return elem;
206 js_arr_push(js, arr, elem);
207 }
208 return arr;
209 }
210 case 'O': {
211 uint32_t n;
212 if (!dec_u32(d, &n)) return js_mkundef();
213 ant_value_t obj = js_mkobj(js);
214 char kbuf[4096];
215 for (uint32_t i = 0; i < n; i++) {
216 uint32_t klen;
217 if (!dec_u32(d, &klen) || klen >= sizeof(kbuf) || d->pos + klen > d->len)
218 break;
219 memcpy(kbuf, d->data + d->pos, klen);
220 kbuf[klen] = '\0';
221 d->pos += klen;
222 ant_value_t v = des_val(js, d, depth + 1);
223 if (is_err(v)) return v;
224 js_set(js, obj, kbuf, v);
225 }
226 return obj;
227 }
228 default:
229 return js_mkundef();
230 }
231}
232
233static ant_value_t v8_deserialize(ant_t *js, ant_value_t *args, int nargs) {
234 if (nargs < 1) return js_mkerr(js, "deserialize: Buffer required");
235
236 TypedArrayData *ta = buffer_get_typedarray_data(args[0]);
237 if (!ta || !ta->buffer) return js_mkerr(js, "deserialize: expected a Buffer");
238
239 const uint8_t *data = ta->buffer->data + ta->byte_offset;
240 size_t len = ta->byte_length;
241
242 if (len < 2 || data[0] != SER_MAGIC_0 || data[1] != SER_MAGIC_1)
243 return js_mkerr(js, "deserialize: invalid or incompatible buffer");
244
245 dec_t d = { .data = data, .len = len, .pos = 2 };
246 return des_val(js, &d, 0);
247}
248
249static ant_value_t v8_get_heap_statistics(ant_t *js, ant_value_t *args, int nargs) {
250 size_t rss = 0;
251 uv_resident_set_memory(&rss);
252
253 size_t arena_committed = js->obj_arena.committed;
254 size_t arena_reserved = js->obj_arena.reserved;
255 size_t arena_live_bytes = js->obj_arena.live_count * js->obj_arena.elem_size;
256
257 size_t closure_committed = js->closure_arena.committed;
258 size_t closure_reserved = js->closure_arena.reserved;
259 size_t closure_live = js->closure_arena.live_count * js->closure_arena.elem_size;
260
261 size_t pool_live = js->gc_pool_last_live;
262 size_t pool_alloc = js->gc_pool_alloc;
263 size_t pool_total = pool_live + pool_alloc;
264
265 size_t extra_alloc = js->alloc_bytes.closures + js->alloc_bytes.upvalues;
266
267 size_t used_heap = arena_live_bytes + closure_live + pool_total + extra_alloc;
268 size_t total_heap = arena_committed + closure_committed + pool_total + extra_alloc;
269 size_t heap_limit = arena_reserved + closure_reserved;
270
271 ant_value_t obj = js_mkobj(js);
272 js_set(js, obj, "total_heap_size", js_mknum((double)total_heap));
273 js_set(js, obj, "total_heap_size_executable", js_mknum(0));
274 js_set(js, obj, "total_physical_size", js_mknum((double)rss));
275 js_set(js, obj, "total_available_size", js_mknum((double)(heap_limit > used_heap ? heap_limit - used_heap : 0)));
276 js_set(js, obj, "total_global_handles_size", js_mknum(0));
277 js_set(js, obj, "used_global_handles_size", js_mknum(0));
278 js_set(js, obj, "used_heap_size", js_mknum((double)used_heap));
279 js_set(js, obj, "heap_size_limit", js_mknum((double)heap_limit));
280 js_set(js, obj, "malloced_memory", js_mknum((double)extra_alloc));
281 js_set(js, obj, "peak_malloced_memory", js_mknum((double)extra_alloc));
282 js_set(js, obj, "does_zap_garbage", js_mknum(0));
283 js_set(js, obj, "number_of_native_contexts", js_mknum(1));
284 js_set(js, obj, "number_of_detached_contexts", js_mknum(0));
285 js_set(js, obj, "total_heap_blinded_size", js_mknum(0));
286
287 return obj;
288}
289
290static ant_value_t v8_get_heap_space_statistics(ant_t *js, ant_value_t *args, int nargs) {
291 ant_value_t nursery = js_mkobj(js);
292 size_t arena_committed = js->obj_arena.committed;
293 size_t arena_live_bytes = js->obj_arena.live_count * js->obj_arena.elem_size;
294 double arena_available = (double)(arena_committed / 2 > arena_live_bytes / 2 ? arena_committed / 2 - arena_live_bytes / 2 : 0);
295
296 js_set(js, nursery, "space_name", js_mkstr(js, "new_space", 9));
297 js_set(js, nursery, "space_size", js_mknum((double)arena_committed / 2));
298 js_set(js, nursery, "space_used_size", js_mknum((double)arena_live_bytes / 2));
299 js_set(js, nursery, "space_available_size", js_mknum(arena_available));
300 js_set(js, nursery, "physical_space_size", js_mknum((double)arena_committed / 2));
301
302 ant_value_t oldspace = js_mkobj(js);
303 size_t old_live = js->old_live_count * js->obj_arena.elem_size;
304 double old_size = (double)(arena_committed / 2 > old_live ? arena_committed / 2 - old_live : 0);
305
306 js_set(js, oldspace, "space_name", js_mkstr(js, "old_space", 9));
307 js_set(js, oldspace, "space_size", js_mknum((double)arena_committed / 2));
308 js_set(js, oldspace, "space_used_size", js_mknum((double)old_live));
309 js_set(js, oldspace, "space_available_size", js_mknum(old_size));
310 js_set(js, oldspace, "physical_space_size", js_mknum((double)arena_committed / 2));
311
312 ant_value_t arr = js_mkarr(js);
313 js_arr_push(js, arr, nursery);
314 js_arr_push(js, arr, oldspace);
315
316 return arr;
317}
318
319static ant_value_t v8_noop(ant_t *js, ant_value_t *args, int nargs) {
320 return js_mkundef();
321}
322
323static ant_value_t v8_noop_false(ant_t *js, ant_value_t *args, int nargs) {
324 return js_false;
325}
326
327static ant_value_t v8_write_heap_snapshot(ant_t *js, ant_value_t *args, int nargs) {
328 return js_mkstr(js, "", 0);
329}
330
331static ant_value_t v8_get_heap_snapshot(ant_t *js, ant_value_t *args, int nargs) {
332 return js_mkundef();
333}
334
335static ant_value_t v8_get_heap_code_statistics(ant_t *js, ant_value_t *args, int nargs) {
336 size_t closure_structs = js->closure_arena.live_count * js->closure_arena.elem_size;
337 size_t bytecode_alloc = js->alloc_bytes.closures;
338
339 ant_pool_stats_t rope_stats = js_pool_stats(&js->pool.rope);
340 ant_value_t obj = js_mkobj(js);
341
342 js_set(js, obj, "code_and_metadata_size", js_mknum((double)(closure_structs + bytecode_alloc)));
343 js_set(js, obj, "bytecode_and_metadata_size", js_mknum((double)bytecode_alloc));
344 js_set(js, obj, "external_script_source_size", js_mknum((double)rope_stats.used));
345 js_set(js, obj, "cpu_profiler_metadata_size", js_mknum(0));
346
347 return obj;
348}
349
350static ant_value_t v8_cached_data_version_tag(ant_t *js, ant_value_t *args, int nargs) {
351 return js_mknum(0xA0A0A0);
352}
353
354ant_value_t v8_library(ant_t *js) {
355 ant_value_t lib = js_mkobj(js);
356
357 js_set(js, lib, "serialize", js_mkfun(v8_serialize));
358 js_set(js, lib, "deserialize", js_mkfun(v8_deserialize));
359 js_set(js, lib, "writeHeapSnapshot", js_mkfun(v8_write_heap_snapshot));
360 js_set(js, lib, "getHeapSnapshot", js_mkfun(v8_get_heap_snapshot));
361 js_set(js, lib, "getHeapStatistics", js_mkfun(v8_get_heap_statistics));
362 js_set(js, lib, "getHeapSpaceStatistics", js_mkfun(v8_get_heap_space_statistics));
363 js_set(js, lib, "getHeapCodeStatistics", js_mkfun(v8_get_heap_code_statistics));
364 js_set(js, lib, "cachedDataVersionTag", js_mkfun(v8_cached_data_version_tag));
365 js_set(js, lib, "setFlagsFromString", js_mkfun(v8_noop));
366 js_set(js, lib, "stopCoverage", js_mkfun(v8_noop));
367 js_set(js, lib, "takeCoverage", js_mkfun(v8_noop));
368 js_set(js, lib, "promiseEvents", js_mkfun(v8_noop));
369
370 ant_value_t snapshot = js_mkobj(js);
371 js_set(js, snapshot, "isBuildingSnapshot", js_mkfun(v8_noop_false));
372 js_set(js, snapshot, "addSerializeCallback", js_mkfun(v8_noop));
373 js_set(js, snapshot, "addDeserializeCallback", js_mkfun(v8_noop));
374
375 js_set(js, lib, "startupSnapshot", snapshot);
376 js_set(js, lib, "constants", js_mkobj(js));
377
378 return lib;
379}