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 <string.h>
6#include <uthash.h>
7
8#ifdef __APPLE__
9#include <sys/sysctl.h>
10#elif defined(__linux__)
11#include <sys/sysinfo.h>
12#endif
13
14#include "ant.h"
15#include "errors.h"
16#include "runtime.h"
17#include "internal.h"
18#include "silver/engine.h"
19#include "modules/navigator.h"
20#include "modules/symbol.h"
21#include "gc/modules.h"
22
23typedef enum {
24 LOCK_MODE_EXCLUSIVE,
25 LOCK_MODE_SHARED
26} lock_mode_t;
27
28typedef struct lock_entry {
29 char *name;
30 lock_mode_t mode;
31 int shared_count;
32 UT_hash_handle hh;
33} lock_entry_t;
34
35typedef struct lock_request {
36 ant_t *js;
37 char *name;
38 lock_mode_t mode;
39 ant_value_t callback;
40 ant_value_t promise;
41 struct lock_request *next;
42} lock_request_t;
43
44static lock_entry_t *locks = NULL;
45static lock_request_t *pending_requests = NULL;
46
47static int get_hardware_concurrency(void) {
48#ifdef __APPLE__
49 int count;
50 size_t size = sizeof(count);
51 if (sysctlbyname("hw.ncpu", &count, &size, NULL, 0) == 0) {
52 return count;
53 }
54 return 1;
55#elif defined(__linux__)
56 int count = (int)sysconf(_SC_NPROCESSORS_ONLN);
57 return count > 0 ? count : 1;
58#elif defined(_WIN32) || defined(_WIN64)
59 SYSTEM_INFO sysinfo;
60 GetSystemInfo(&sysinfo);
61 return (int)sysinfo.dwNumberOfProcessors;
62#else
63 return 1;
64#endif
65}
66
67static const char *get_platform_string(void) {
68#if defined(__APPLE__)
69 return "MacIntel";
70#elif defined(__linux__)
71 return "Linux x86_64";
72#elif defined(_WIN32) || defined(_WIN64)
73 return "Win32";
74#elif defined(__FreeBSD__)
75 return "FreeBSD";
76#else
77 return "Unknown";
78#endif
79}
80
81static lock_entry_t *find_lock(const char *name) {
82 lock_entry_t *entry = NULL;
83 HASH_FIND_STR(locks, name, entry);
84 return entry;
85}
86
87static lock_entry_t *create_lock(const char *name, lock_mode_t mode) {
88 lock_entry_t *entry = calloc(1, sizeof(lock_entry_t));
89 if (!entry) return NULL;
90
91 entry->name = strdup(name);
92 entry->mode = mode;
93 entry->shared_count = (mode == LOCK_MODE_SHARED) ? 1 : 0;
94
95 HASH_ADD_STR(locks, name, entry);
96 return entry;
97}
98
99static bool can_acquire_lock(const char *name, lock_mode_t mode) {
100 lock_entry_t *entry = find_lock(name);
101 if (!entry) return true;
102 if (mode == LOCK_MODE_SHARED && entry->mode == LOCK_MODE_SHARED) return true;
103 return false;
104}
105
106static void release_lock(const char *name) {
107 lock_entry_t *entry = find_lock(name);
108 if (!entry) return;
109
110 if (entry->mode == LOCK_MODE_SHARED) {
111 entry->shared_count--;
112 if (entry->shared_count > 0) return;
113 }
114
115 HASH_DEL(locks, entry);
116 free(entry->name);
117 free(entry);
118}
119
120static void process_pending_requests(ant_t *js);
121
122static ant_value_t make_lock_handler(ant_t *js, ant_value_t cfunc, ant_value_t lock_name, ant_value_t outer_promise) {
123 ant_value_t fn_obj = js_mkobj(js);
124
125 ant_value_t data_obj = js_mkobj(js);
126 js_set(js, data_obj, "lockName", lock_name);
127 js_set(js, data_obj, "outerPromise", outer_promise);
128 js_set_slot(fn_obj, SLOT_DATA, data_obj);
129 js_set_slot(fn_obj, SLOT_CFUNC, cfunc);
130
131 return js_obj_to_func(fn_obj);
132}
133
134static ant_value_t lock_then_handler(ant_t *js, ant_value_t *args, int nargs) {
135 ant_value_t current_func = js_getcurrentfunc(js);
136 ant_value_t data_obj = js_get_slot(current_func, SLOT_DATA);
137
138 ant_value_t lock_name_val = js_get(js, data_obj, "lockName");
139 ant_value_t outer_promise = js_get(js, data_obj, "outerPromise");
140 ant_value_t result_val = (nargs > 0) ? args[0] : js_mkundef();
141
142 size_t name_len;
143 char *name = js_getstr(js, lock_name_val, &name_len);
144
145 if (name) release_lock(name);
146 js_resolve_promise(js, outer_promise, result_val);
147 process_pending_requests(js);
148
149 return js_mkundef();
150}
151
152static ant_value_t lock_catch_handler(ant_t *js, ant_value_t *args, int nargs) {
153 ant_value_t current_func = js_getcurrentfunc(js);
154 ant_value_t data_obj = js_get_slot(current_func, SLOT_DATA);
155
156 ant_value_t lock_name_val = js_get(js, data_obj, "lockName");
157 ant_value_t outer_promise = js_get(js, data_obj, "outerPromise");
158 ant_value_t error_val = (nargs > 0) ? args[0] : js_mkundef();
159
160 size_t name_len;
161 char *name = js_getstr(js, lock_name_val, &name_len);
162
163 if (name) release_lock(name);
164
165 js_reject_promise(js, outer_promise, error_val);
166 process_pending_requests(js);
167
168 return js_mkundef();
169}
170
171static void execute_lock_callback(ant_t *js, const char *name, lock_mode_t mode, ant_value_t callback, ant_value_t outer_promise) {
172 ant_value_t lock_obj = js_mkobj(js);
173 js_set(js, lock_obj, "name", js_mkstr(js, name, strlen(name)));
174 js_set(js, lock_obj, "mode", js_mkstr(js, mode == LOCK_MODE_EXCLUSIVE ? "exclusive" : "shared", mode == LOCK_MODE_EXCLUSIVE ? 9 : 6));
175 js_set_sym(js, lock_obj, get_toStringTag_sym(), js_mkstr(js, "Lock", 4));
176
177 ant_value_t result = sv_vm_call(js->vm, js, callback, js_mkundef(), &lock_obj, 1, NULL, false);
178
179 if (vtype(result) == T_ERR) {
180 release_lock(name);
181 js_reject_promise(js, outer_promise, result);
182 process_pending_requests(js);
183 return;
184 }
185
186 if (vtype(result) == T_PROMISE) {
187 ant_value_t name_str = js_mkstr(js, name, strlen(name));
188 ant_value_t on_resolve = make_lock_handler(js, js_mkfun(lock_then_handler), name_str, outer_promise);
189 ant_value_t on_reject = make_lock_handler(js, js_mkfun(lock_catch_handler), name_str, outer_promise);
190 js_promise_then(js, result, on_resolve, on_reject);
191 return;
192 }
193
194 release_lock(name);
195 js_resolve_promise(js, outer_promise, result);
196 process_pending_requests(js);
197}
198
199static void process_pending_requests(ant_t *js) {
200 lock_request_t *prev = NULL;
201 lock_request_t *req = pending_requests;
202
203 while (req) {
204 if (can_acquire_lock(req->name, req->mode)) {
205 lock_entry_t *entry = find_lock(req->name);
206
207 if (entry && entry->mode == LOCK_MODE_SHARED && req->mode == LOCK_MODE_SHARED) {
208 entry->shared_count++;
209 } else {
210 create_lock(req->name, req->mode);
211 }
212
213 lock_request_t *to_process = req;
214
215 if (prev) {
216 prev->next = req->next;
217 } else {
218 pending_requests = req->next;
219 }
220 req = req->next;
221
222 execute_lock_callback(js, to_process->name, to_process->mode, to_process->callback, to_process->promise);
223 free(to_process->name);
224 free(to_process);
225 return;
226 } else {
227 prev = req;
228 req = req->next;
229 }
230 }
231}
232
233static ant_value_t locks_request(ant_t *js, ant_value_t *args, int nargs) {
234 if (nargs < 2) {
235 return js_mkerr_typed(js, JS_ERR_TYPE, "locks.request requires at least 2 arguments");
236 }
237
238 size_t name_len;
239 char *name = js_getstr(js, args[0], &name_len);
240 if (!name) {
241 return js_mkerr_typed(js, JS_ERR_TYPE, "First argument must be a string");
242 }
243
244 ant_value_t options = js_mkundef();
245 ant_value_t callback;
246 lock_mode_t mode = LOCK_MODE_EXCLUSIVE;
247 bool if_available = false;
248
249 if (nargs == 2) {
250 callback = args[1];
251 } else {
252 options = args[1];
253 callback = args[2];
254
255 if (is_special_object(options)) {
256 ant_value_t mode_val = js_get(js, options, "mode");
257 if (vtype(mode_val) == T_STR) {
258 size_t mode_len;
259 char *mode_str = js_getstr(js, mode_val, &mode_len);
260 if (mode_str && strcmp(mode_str, "shared") == 0) mode = LOCK_MODE_SHARED;
261 }
262
263 if (js_get(js, options, "ifAvailable") == js_true) if_available = true;
264 }
265 }
266
267 if (vtype(callback) != T_FUNC) {
268 return js_mkerr_typed(js, JS_ERR_TYPE, "Callback must be a function");
269 }
270
271 ant_value_t promise = js_mkpromise(js);
272
273 if (if_available && !can_acquire_lock(name, mode)) {
274 ant_value_t null_val = js_mknull();
275 ant_value_t result = sv_vm_call(js->vm, js, callback, js_mkundef(), &null_val, 1, NULL, false);
276
277 if (vtype(result) == T_PROMISE) {
278 ant_value_t on_resolve = make_lock_handler(
279 js, js_mkfun(lock_then_handler),
280 js_mkstr(js, "", 0), promise
281 );
282 js_promise_then(js, result, on_resolve, js_mkundef());
283 return promise;
284 }
285 js_resolve_promise(js, promise, result);
286 return promise;
287 }
288
289 if (can_acquire_lock(name, mode)) {
290 lock_entry_t *entry = find_lock(name);
291
292 if (entry && entry->mode == LOCK_MODE_SHARED && mode == LOCK_MODE_SHARED) {
293 entry->shared_count++;
294 } else {
295 create_lock(name, mode);
296 }
297
298 execute_lock_callback(js, name, mode, callback, promise);
299 } else {
300 lock_request_t *req = calloc(1, sizeof(lock_request_t));
301 req->js = js;
302 req->name = strdup(name);
303 req->mode = mode;
304 req->callback = callback;
305 req->promise = promise;
306 req->next = NULL;
307
308 lock_request_t *tail = pending_requests;
309 if (!tail) {
310 pending_requests = req;
311 } else {
312 while (tail->next) tail = tail->next;
313 tail->next = req;
314 }
315 }
316
317 return promise;
318}
319
320static ant_value_t locks_query(ant_t *js, ant_value_t *args, int nargs) {
321 (void)args;
322 (void)nargs;
323
324 ant_value_t result = js_mkobj(js);
325 ant_value_t held_arr = js_mkarr(js);
326 ant_value_t pending_arr = js_mkarr(js);
327
328 lock_entry_t *entry, *tmp;
329 HASH_ITER(hh, locks, entry, tmp) {
330 ant_value_t lock_info = js_mkobj(js);
331 js_set(js, lock_info, "name", js_mkstr(js, entry->name, strlen(entry->name)));
332 js_set(js, lock_info, "mode", js_mkstr(js, entry->mode == LOCK_MODE_EXCLUSIVE ? "exclusive" : "shared", entry->mode == LOCK_MODE_EXCLUSIVE ? 9 : 6));
333 js_arr_push(js, held_arr, lock_info);
334 }
335
336 lock_request_t *req = pending_requests;
337 while (req) {
338 ant_value_t req_info = js_mkobj(js);
339 js_set(js, req_info, "name", js_mkstr(js, req->name, strlen(req->name)));
340 js_set(js, req_info, "mode", js_mkstr(js, req->mode == LOCK_MODE_EXCLUSIVE ? "exclusive" : "shared", req->mode == LOCK_MODE_EXCLUSIVE ? 9 : 6));
341 js_arr_push(js, pending_arr, req_info);
342 req = req->next;
343 }
344
345 js_set(js, result, "held", held_arr);
346 js_set(js, result, "pending", pending_arr);
347
348 ant_value_t promise = js_mkpromise(js);
349 js_resolve_promise(js, promise, result);
350
351 return promise;
352}
353
354void init_navigator_module(void) {
355 ant_t *js = rt->js;
356
357 ant_value_t navigator_obj = js_mkobj(js);
358
359 js_set(js, navigator_obj, "hardwareConcurrency", js_mknum((double)get_hardware_concurrency()));
360 js_set(js, navigator_obj, "language", js_mkstr(js, "en-US", 5));
361
362 ant_value_t languages_arr = js_mkarr(js);
363 js_arr_push(js, languages_arr, js_mkstr(js, "en-US", 5));
364 js_set(js, navigator_obj, "languages", languages_arr);
365
366 const char *platform = get_platform_string();
367 js_set(js, navigator_obj, "platform", js_mkstr(js, platform, strlen(platform)));
368
369 char user_agent[64];
370 snprintf(user_agent, sizeof(user_agent), "Ant/%s", ANT_VERSION);
371 js_set(js, navigator_obj, "userAgent", js_mkstr(js, user_agent, strlen(user_agent)));
372
373 ant_value_t locks_obj = js_mkobj(js);
374 js_set(js, locks_obj, "request", js_mkfun(locks_request));
375 js_set(js, locks_obj, "query", js_mkfun(locks_query));
376 js_set_sym(js, locks_obj, get_toStringTag_sym(), js_mkstr(js, "LockManager", 11));
377 js_set(js, navigator_obj, "locks", locks_obj);
378
379 js_set_sym(js, navigator_obj, get_toStringTag_sym(), js_mkstr(js, "Navigator", 9));
380 js_set(js, js_glob(js), "navigator", navigator_obj);
381}
382
383void gc_mark_navigator(ant_t *js, gc_mark_fn mark) {
384 for (lock_request_t *req = pending_requests; req; req = req->next) {
385 mark(js, req->callback);
386 mark(js, req->promise);
387 }
388}