MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include <stdlib.h>
4#include <stdio.h>
5#include <signal.h>
6#include <string.h>
7#include <time.h>
8#include <errno.h>
9#include <crprintf.h>
10
11#ifdef __APPLE__
12#include <mach/mach.h>
13#elif defined(__linux__)
14#include <sys/resource.h>
15#endif
16
17#include "ant.h"
18#include "gc.h"
19#include "crash.h"
20#include "errors.h"
21#include "runtime.h"
22#include "internal.h"
23#include "highlight.h"
24#include "descriptors.h"
25
26#include "silver/engine.h"
27#include "modules/builtin.h"
28#include "modules/buffer.h"
29#include "modules/symbol.h"
30
31static struct {
32 ant_t *js;
33 ant_value_t handler;
34} signal_handlers[NSIG] = {0};
35
36static void general_signal_handler(int signum) {
37 if (signum < 0 || signum >= NSIG) return;
38 ant_t *js = signal_handlers[signum].js;
39 ant_value_t handler = signal_handlers[signum].handler;
40
41 if (js && vtype(handler) != T_UNDEF) {
42 ant_value_t args[] = {js_mknum(signum)};
43 sv_vm_call(js->vm, js, handler, js_mkundef(), args, 1, NULL, false);
44 }
45
46 exit(0);
47}
48
49// Ant.signal(signal, handler)
50static ant_value_t js_signal(ant_t *js, ant_value_t *args, int nargs) {
51 if (nargs < 2) return js_mkerr(js, "Ant.signal() requires 2 arguments");
52
53 int signum = (int)js_getnum(args[0]);
54 if (signum <= 0 || signum >= NSIG) {
55 return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid signal number: %d", signum);
56 }
57
58 signal_handlers[signum].js = js;
59 signal_handlers[signum].handler = args[1];
60 signal(signum, general_signal_handler);
61
62 return js_mkundef();
63}
64
65// Ant.raw.stack()
66static ant_value_t js_raw_stack(ant_t *js, ant_value_t *args, int nargs) {
67 return js_capture_raw_stack(js);
68}
69
70// Ant.raw.typeof(ant_value_t)
71static ant_value_t js_raw_typeof(ant_t *js, ant_value_t *args, int nargs) {
72 if (nargs < 1) return js_mkerr(js, "Ant.raw.typeof() requires 1 argument");
73 const uint8_t type = vtype(args[0]);
74 return js_mknum((double)type);
75}
76
77// Ant.raw.ctorPropFeedback(constructorFn)
78static ant_value_t js_raw_ctor_prop_feedback(ant_t *js, ant_value_t *args, int nargs) {
79 if (nargs < 1) return js_mkerr(js, "Ant.raw.ctorPropFeedback() requires 1 argument");
80 if (vtype(args[0]) != T_FUNC) return js_mkerr(js, "constructor must be a function");
81
82#ifndef ANT_JIT
83 return js_mkerr(js, "constructor property feedback requires ANT_JIT");
84#else
85 ant_value_t ctor = args[0];
86 ant_value_t ctor_obj = js_func_obj(ctor);
87 ant_value_t target_func = js_get_slot(ctor_obj, SLOT_TARGET_FUNC);
88 if (vtype(target_func) == T_FUNC) ctor = target_func;
89
90 sv_closure_t *closure = js_func_closure(ctor);
91 if (!closure || !closure->func) return js_mkundef();
92 sv_func_t *fn = closure->func;
93
94 ant_value_t out = js_newobj(js);
95 js_set(js, out, "samples", js_mknum((double)fn->ctor_prop_samples));
96 js_set(js, out, "overflowFrom", js_mknum((double)SV_TFB_CTOR_PROP_OVERFLOW_FROM));
97 js_set(js, out, "inobjLimit", js_mknum((double)sv_tfb_ctor_inobj_limit(ctor)));
98 js_set(js, out, "inobjLimitFrozen", js_bool(sv_tfb_ctor_inobj_limit_frozen(ctor)));
99 js_set(js, out, "slackRemaining", js_mknum((double)sv_tfb_ctor_inobj_slack_remaining(ctor)));
100
101 ant_value_t bins = js_mkarr(js);
102 for (uint32_t i = 0; i < SV_TFB_CTOR_PROP_BINS; i++) {
103 js_arr_push(js, bins, js_mknum((double)fn->ctor_prop_hist[i]));
104 }
105 js_set(js, out, "bins", bins);
106
107 if (fn->name) js_set(js, out, "name", js_mkstr(js, fn->name, strlen(fn->name)));
108 if (fn->filename) js_set(js, out, "filename", js_mkstr(js, fn->filename, strlen(fn->filename)));
109
110 return out;
111#endif
112}
113
114static ant_value_t js_raw_gc_mark_profile(ant_t *js, ant_value_t *args, int nargs) {
115 gc_func_mark_profile_t p = gc_func_mark_profile_get();
116 ant_value_t out = js_newobj(js);
117
118 js_set(js, out, "enabled", js_bool(p.enabled));
119 js_set(js, out, "collections", js_mknum((double)p.collections));
120 js_set(js, out, "funcVisits", js_mknum((double)p.func_visits));
121 js_set(js, out, "childEdges", js_mknum((double)p.child_edges));
122 js_set(js, out, "constSlots", js_mknum((double)p.const_slots));
123 js_set(js, out, "timeNs", js_mknum((double)p.time_ns));
124 js_set(js, out, "timeMs", js_mknum((double)p.time_ns / 1000000.0));
125
126 return out;
127}
128
129static ant_value_t js_raw_gc_mark_profile_enable(ant_t *js, ant_value_t *args, int nargs) {
130 bool enabled = true;
131 if (nargs > 0) enabled = js_truthy(js, args[0]);
132 gc_func_mark_profile_enable(enabled);
133 return js_bool(enabled);
134}
135
136static ant_value_t js_raw_gc_mark_profile_reset(ant_t *js, ant_value_t *args, int nargs) {
137 gc_func_mark_profile_reset();
138 return js_mkundef();
139}
140
141// Ant.sleep(seconds)
142static ant_value_t js_sleep(ant_t *js, ant_value_t *args, int nargs) {
143 if (nargs < 1) return js_mkerr(js, "Ant.sleep() requires 1 argument");
144 unsigned int seconds = (unsigned int)js_getnum(args[0]);
145 sleep(seconds);
146 return js_mkundef();
147}
148
149// Ant.msleep(milliseconds)
150static ant_value_t js_msleep(ant_t *js, ant_value_t *args, int nargs) {
151 if (nargs < 1) return js_mkerr(js, "Ant.msleep() requires 1 argument");
152 long ms = (long)js_getnum(args[0]);
153 struct timespec ts = { .tv_sec = ms / 1000, .tv_nsec = (ms % 1000) * 1000000 };
154 struct timespec rem;
155 while (nanosleep(&ts, &rem) == -1 && errno == EINTR) ts = rem;
156 return js_mkundef();
157}
158
159// Ant.usleep(microseconds)
160static ant_value_t js_usleep(ant_t *js, ant_value_t *args, int nargs) {
161 if (nargs < 1) return js_mkerr(js, "Ant.usleep() requires 1 argument");
162 useconds_t us = (useconds_t)js_getnum(args[0]);
163 usleep(us);
164 return js_mkundef();
165}
166
167// Ant.suppressReporting()
168static ant_value_t js_suppress_reporting(ant_t *js, ant_value_t *args, int nargs) {
169 ant_crash_suppress_reporting();
170 return js_mkundef();
171}
172
173// Ant.stats()
174static ant_value_t js_stats_fn(ant_t *js, ant_value_t *args, int nargs) {
175 ant_value_t result = js_newobj(js);
176
177 ant_pool_stats_t rope_s = js_pool_stats(&js->pool.rope);
178 ant_pool_stats_t sym_s = js_pool_stats(&js->pool.symbol);
179 ant_pool_stats_t bigint_s = js_class_pool_stats(&js->pool.bigint);
180 ant_string_pool_stats_t string_s = js_string_pool_stats(&js->pool.string);
181
182 ant_value_t pools = js_newobj(js);
183 ant_value_t rope_obj = js_newobj(js);
184 js_set(js, rope_obj, "used", js_mknum((double)rope_s.used));
185 js_set(js, rope_obj, "capacity", js_mknum((double)rope_s.capacity));
186 js_set(js, rope_obj, "blocks", js_mknum((double)rope_s.blocks));
187 js_set(js, pools, "rope", rope_obj);
188
189 ant_value_t sym_obj = js_newobj(js);
190 js_set(js, sym_obj, "used", js_mknum((double)sym_s.used));
191 js_set(js, sym_obj, "capacity", js_mknum((double)sym_s.capacity));
192 js_set(js, sym_obj, "blocks", js_mknum((double)sym_s.blocks));
193 js_set(js, pools, "symbol", sym_obj);
194
195 ant_value_t bigint_obj = js_newobj(js);
196 js_set(js, bigint_obj, "used", js_mknum((double)bigint_s.used));
197 js_set(js, bigint_obj, "capacity", js_mknum((double)bigint_s.capacity));
198 js_set(js, bigint_obj, "blocks", js_mknum((double)bigint_s.blocks));
199 js_set(js, pools, "bigint", bigint_obj);
200
201 ant_value_t string_obj = js_newobj(js);
202 js_set(js, string_obj, "used", js_mknum((double)string_s.total.used));
203 js_set(js, string_obj, "capacity", js_mknum((double)string_s.total.capacity));
204 js_set(js, string_obj, "blocks", js_mknum((double)string_s.total.blocks));
205
206 ant_value_t string_pooled_obj = js_newobj(js);
207 js_set(js, string_pooled_obj, "used", js_mknum((double)string_s.pooled.used));
208 js_set(js, string_pooled_obj, "capacity", js_mknum((double)string_s.pooled.capacity));
209 js_set(js, string_pooled_obj, "blocks", js_mknum((double)string_s.pooled.blocks));
210 js_set(js, string_obj, "pooled", string_pooled_obj);
211
212 ant_value_t string_large_live_obj = js_newobj(js);
213 js_set(js, string_large_live_obj, "used", js_mknum((double)string_s.large_live.used));
214 js_set(js, string_large_live_obj, "capacity", js_mknum((double)string_s.large_live.capacity));
215 js_set(js, string_large_live_obj, "blocks", js_mknum((double)string_s.large_live.blocks));
216 js_set(js, string_obj, "largeLive", string_large_live_obj);
217
218 ant_value_t string_large_reusable_obj = js_newobj(js);
219 js_set(js, string_large_reusable_obj, "used", js_mknum((double)string_s.large_reusable.used));
220 js_set(js, string_large_reusable_obj, "capacity", js_mknum((double)string_s.large_reusable.capacity));
221 js_set(js, string_large_reusable_obj, "blocks", js_mknum((double)string_s.large_reusable.blocks));
222 js_set(js, string_obj, "largeReusable", string_large_reusable_obj);
223
224 ant_value_t string_large_quarantine_obj = js_newobj(js);
225 js_set(js, string_large_quarantine_obj, "used", js_mknum((double)string_s.large_quarantine.used));
226 js_set(js, string_large_quarantine_obj, "capacity", js_mknum((double)string_s.large_quarantine.capacity));
227 js_set(js, string_large_quarantine_obj, "blocks", js_mknum((double)string_s.large_quarantine.blocks));
228 js_set(js, string_obj, "largeQuarantine", string_large_quarantine_obj);
229
230 size_t pool_used = rope_s.used + sym_s.used + bigint_s.used + string_s.total.used;
231 size_t pool_cap = rope_s.capacity + sym_s.capacity + bigint_s.capacity + string_s.total.capacity;
232
233 js_set(js, pools, "totalUsed", js_mknum((double)pool_used));
234 js_set(js, pools, "totalCapacity", js_mknum((double)pool_cap));
235 js_set(js, result, "pools", pools);
236 js_set(js, pools, "string", string_obj);
237
238 size_t obj_count = 0;
239 size_t obj_bytes = 0;
240 size_t overflow_bytes = 0;
241 size_t extra_bytes = 0;
242 size_t promise_bytes = 0;
243 size_t proxy_bytes = 0;
244 size_t exotic_bytes = 0;
245 size_t array_bytes = 0;
246
247 for (int pass = 0; pass < 3; pass++) {
248 ant_object_t *head = pass == 0 ? js->objects : pass == 1 ? js->objects_old : js->permanent_objects;
249 for (ant_object_t *obj = head; obj; obj = obj->next) {
250 obj_count++;
251 obj_bytes += sizeof(ant_object_t);
252
253 uint32_t inobj_limit = ant_object_inobj_limit(obj);
254 if (obj->overflow_prop && obj->prop_count > inobj_limit)
255 overflow_bytes += (obj->prop_count - inobj_limit) * sizeof(ant_value_t);
256 if (obj->extra_slots) extra_bytes += obj->extra_count * sizeof(ant_extra_slot_t);
257 if (obj->promise_state) promise_bytes += sizeof(ant_promise_state_t);
258 if (obj->proxy_state) proxy_bytes += sizeof(ant_proxy_state_t);
259 if (obj->exotic_ops) exotic_bytes += sizeof(ant_exotic_ops_t);
260 if (obj->type_tag == T_ARR && obj->u.array.data)
261 array_bytes += obj->u.array.cap * sizeof(ant_value_t);
262 }
263 }
264
265 ant_value_t alloc = js_newobj(js);
266 js_set(js, alloc, "objectCount", js_mknum((double)obj_count));
267 js_set(js, alloc, "objects", js_mknum((double)obj_bytes));
268 js_set(js, alloc, "overflow", js_mknum((double)overflow_bytes));
269 js_set(js, alloc, "extraSlots", js_mknum((double)extra_bytes));
270 js_set(js, alloc, "promises", js_mknum((double)promise_bytes));
271 js_set(js, alloc, "proxies", js_mknum((double)proxy_bytes));
272 js_set(js, alloc, "exotic", js_mknum((double)exotic_bytes));
273 js_set(js, alloc, "arrays", js_mknum((double)array_bytes));
274
275 size_t shape_bytes = ant_shape_total_bytes();
276 js_set(js, alloc, "shapes", js_mknum((double)shape_bytes));
277 js_set(js, alloc, "closures", js_mknum((double)js->alloc_bytes.closures));
278 js_set(js, alloc, "upvalues", js_mknum((double)js->alloc_bytes.upvalues));
279 js_set(js, alloc, "propRefs", js_mknum((double)(js->prop_refs_cap * sizeof(ant_prop_ref_t))));
280
281 size_t alloc_total = obj_bytes + overflow_bytes + extra_bytes
282 + promise_bytes + proxy_bytes + exotic_bytes + array_bytes
283 + shape_bytes + js->alloc_bytes.closures + js->alloc_bytes.upvalues
284 + js->prop_refs_cap * sizeof(ant_prop_ref_t);
285
286 js_set(js, alloc, "total", js_mknum((double)alloc_total));
287 js_set(js, result, "alloc", alloc);
288
289 size_t buffer_mem = buffer_get_external_memory();
290 size_t code_mem = code_arena_get_memory();
291 size_t external_total = buffer_mem + code_mem;
292
293 ant_value_t ext = js_newobj(js);
294 js_set(js, ext, "buffers", js_mknum((double)buffer_mem));
295 js_set(js, ext, "code", js_mknum((double)code_mem));
296 js_set(js, ext, "total", js_mknum((double)external_total));
297 js_set(js, result, "external", ext);
298
299 js_intern_stats_t intern_stats = js_intern_stats();
300 ant_value_t intern = js_newobj(js);
301
302 js_set(js, intern, "count", js_mknum((double)intern_stats.count));
303 js_set(js, intern, "bytes", js_mknum((double)intern_stats.bytes));
304 js_set(js, result, "intern", intern);
305
306 sv_vm_t *vm = sv_vm_get_active(js);
307 if (vm) {
308 ant_value_t vmobj = js_newobj(js);
309 js_set(js, vmobj, "stackSize", js_mknum((double)vm->stack_size));
310 js_set(js, vmobj, "stackUsed", js_mknum((double)vm->sp));
311 js_set(js, vmobj, "maxFrames", js_mknum((double)vm->max_frames));
312 js_set(js, vmobj, "framesUsed", js_mknum((double)(vm->fp + 1)));
313 js_set(js, result, "vm", vmobj);
314 }
315
316 if (js->cstk.base != NULL) {
317 volatile char marker;
318 uintptr_t base = (uintptr_t)js->cstk.base;
319 uintptr_t curr = (uintptr_t)▮
320 size_t used = (base > curr) ? (base - curr) : (curr - base);
321 ant_value_t cstk = js_newobj(js);
322 js_set(js, cstk, "used", js_mknum((double)used));
323 js_set(js, cstk, "limit", js_mknum((double)js->cstk.limit));
324 js_set(js, result, "cstack", cstk);
325 } else js_set(js, result, "cstack", js_mknum(0));
326
327#ifdef __APPLE__
328 struct mach_task_basic_info info;
329 mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
330 if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &count) == KERN_SUCCESS) {
331 js_set(js, result, "residentSize", js_mknum((double)info.resident_size));
332 js_set(js, result, "virtualSize", js_mknum((double)info.virtual_size));
333 }
334
335 task_vm_info_data_t vm_info;
336 mach_msg_type_number_t vm_count = TASK_VM_INFO_COUNT;
337 if (task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&vm_info, &vm_count) == KERN_SUCCESS) {
338 js_set(js, result, "rss", js_mknum((double)vm_info.phys_footprint));
339 js_set(js, result, "physFootprint", js_mknum((double)vm_info.phys_footprint));
340 } else if (vtype(js_get(js, result, "rss")) == T_UNDEF) {
341 ant_value_t resident = js_get(js, result, "residentSize");
342 if (vtype(resident) != T_UNDEF) js_set(js, result, "rss", resident);
343 }
344#elif defined(__linux__)
345 struct rusage usage;
346 if (getrusage(RUSAGE_SELF, &usage) == 0) {
347 js_set(js, result, "rss", js_mknum((double)usage.ru_maxrss * 1024));
348 }
349#endif
350
351 return result;
352}
353
354static inline ant_value_t match_resolve_arm(ant_t *js, ant_value_t arm, ant_value_t value) {
355 if (!is_callable(arm)) return arm;
356 return sv_vm_call(js->vm, js, arm, js_mkundef(), &value, 1, NULL, false);
357}
358
359static ant_value_t js_match(ant_t *js, ant_value_t *args, int nargs) {
360 if (nargs < 2) return js_mkerr(js, "Ant.match() requires 2 arguments");
361
362 ant_value_t value = args[0];
363 ant_value_t arms = args[1];
364
365 if (is_callable(arms)) {
366 arms = sv_vm_call(js->vm, js, arms, js_mkundef(), &value, 1, NULL, false);
367 if (is_err(arms)) return arms;
368 }
369
370 if (!is_object_type(arms)) return js_mkundef();
371 ant_value_t value_str = js_tostring_val(js, value);
372 const char *vs = js_getstr(js, value_str, NULL);
373
374 ant_iter_t iter = js_prop_iter_begin(js, arms);
375 ant_value_t guard_arm = js_mkundef();
376
377 ant_value_t key = js_mkundef();
378 ant_value_t arm = js_mkundef();
379
380 while (js_prop_iter_next_val(&iter, &key, &arm)) {
381 if (vtype(key) == T_SYMBOL) continue;
382
383 const char *ks = js_getstr(js, key, NULL);
384 if (!ks) continue;
385
386 if (strcmp(ks, vs) == 0) {
387 js_prop_iter_end(&iter);
388 return match_resolve_arm(js, arm, value);
389 }
390
391 if (
392 strcmp(ks, "true") == 0
393 && vtype(guard_arm) == T_UNDEF
394 ) guard_arm = arm;
395 }
396
397 js_prop_iter_end(&iter);
398 if (vtype(guard_arm) != T_UNDEF) return match_resolve_arm(js, guard_arm, value);
399
400 ant_value_t fallback = js_get_sym(js, arms, get_default_sym());
401 if (vtype(fallback) != T_UNDEF) return match_resolve_arm(js, fallback, value);
402
403 return js_mkundef();
404}
405
406static ant_value_t hl_get_tagged(ant_t *js, ant_value_t *args, int nargs) {
407 size_t input_len;
408 char *input = js_getstr(js, args[0], &input_len);
409
410 size_t out_size = input_len * 8 + 1;
411 char *out = malloc(out_size);
412 if (!out) return js_mkerr(js, "out of memory");
413
414 int len = ant_highlight(input, input_len, out, out_size);
415 ant_value_t result = js_mkstr(js, out, (size_t)len);
416 free(out);
417
418 return result;
419}
420
421static ant_value_t hl_render_tagged(ant_t *js, ant_value_t tagged) {
422 size_t tagged_len;
423 char *tagged_str = js_getstr(js, tagged, &tagged_len);
424
425 size_t ansi_size = tagged_len * 2 + 1;
426 char *ansi = malloc(ansi_size);
427 if (!ansi) return js_mkerr(js, "out of memory");
428
429 int len = crsprintf_stateful(ansi, ansi_size, NULL, tagged_str);
430 ant_value_t result = js_mkstr(js, ansi, len < 0 ? 0 : (size_t)len);
431 free(ansi);
432
433 return result;
434}
435
436static ant_value_t js_highlight(ant_t *js, ant_value_t *args, int nargs) {
437 if (nargs < 1) return js_mkerr(js, "Ant.highlight() requires 1 argument");
438 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Ant.highlight() argument must be a string");
439
440 ant_value_t tagged = hl_get_tagged(js, args, nargs);
441 if (is_err(tagged)) return tagged;
442 return hl_render_tagged(js, tagged);
443}
444
445static ant_value_t js_highlight_render(ant_t *js, ant_value_t *args, int nargs) {
446 if (nargs < 1) return js_mkerr(js, "Ant.highlight.render() requires 1 argument");
447 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Ant.highlight.render() argument must be a string");
448 return hl_render_tagged(js, args[0]);
449}
450
451static ant_value_t js_highlight_tags(ant_t *js, ant_value_t *args, int nargs) {
452 if (nargs < 1) return js_mkerr(js, "Ant.highlight.tags() requires 1 argument");
453 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Ant.highlight.tags() argument must be a string");
454 return hl_get_tagged(js, args, nargs);
455}
456
457void init_builtin_module() {
458 ant_t *js = rt->js;
459 ant_value_t ant_obj = rt->ant_obj;
460
461 js_set(js, ant_obj, "match", js_mkfun(js_match));
462 js_set(js, ant_obj, "stats", js_mkfun(js_stats_fn));
463 js_set(js, ant_obj, "signal", js_mkfun(js_signal));
464 js_set(js, ant_obj, "sleep", js_mkfun(js_sleep));
465 js_set(js, ant_obj, "msleep", js_mkfun(js_msleep));
466 js_set(js, ant_obj, "usleep", js_mkfun(js_usleep));
467 js_set(js, ant_obj, "suppressReporting", js_mkfun(js_suppress_reporting));
468
469 ant_value_t hl_obj = js_newobj(js);
470 ant_value_t hl_fn = js_obj_to_func(hl_obj);
471
472 js_set_slot(hl_obj, SLOT_CFUNC, js_mkfun(js_highlight));
473 js_set(js, hl_fn, "render", js_mkfun(js_highlight_render));
474 js_set(js, hl_fn, "tags", js_mkfun(js_highlight_tags));
475 js_set(js, ant_obj, "highlight", hl_fn);
476
477 ant_value_t raw_obj = js_newobj(js);
478 js_set_getter_desc(js, js_as_obj(raw_obj), "stack", 5, js_mkfun(js_raw_stack), JS_DESC_C);
479 js_set(js, raw_obj, "typeof", js_mkfun(js_raw_typeof));
480 js_set(js, raw_obj, "ctorPropFeedback", js_mkfun(js_raw_ctor_prop_feedback));
481 js_set(js, raw_obj, "gcMarkProfile", js_mkfun(js_raw_gc_mark_profile));
482 js_set(js, raw_obj, "gcMarkProfileEnable", js_mkfun(js_raw_gc_mark_profile_enable));
483 js_set(js, raw_obj, "gcMarkProfileReset", js_mkfun(js_raw_gc_mark_profile_reset));
484 js_set(js, ant_obj, "raw", raw_obj);
485}