MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

use a stack-based coro runtime

+275 -84
+1 -5
README.txt
··· 27 27 - Async/await and Promises 28 28 - ES6+ syntax (arrow functions, classes, template literals) 29 29 - Signal handlers (SIGINT, SIGTERM, etc.) 30 - - 64MB default memory pool 31 - - Embedded garbage collector 32 - 33 - DEPENDENCIES: 34 - libsodium, curl, mongoose, yyjson, argtable3, uuidv7 30 + - Embedded garbage collector
+4 -2
meson.build
··· 25 25 uuidv7_dep = subproject('uuidv7').get_variable('uuidv7_dep') 26 26 mongoose_dep = subproject('mongoose').get_variable('mongoose_dep') 27 27 argtable3_dep = subproject('argtable3').get_variable('argtable3_dep') 28 + minicoro_dep = subproject('minicoro').get_variable('minicoro_dep') 28 29 29 30 git = find_program('git', required: false) 30 31 if git.found() ··· 41 42 build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() 42 43 43 44 version_conf = configuration_data() 44 - version_conf.set('ANT_VERSION', '0.0.6.7') 45 + version_conf.set('ANT_VERSION', '0.0.6.8') 45 46 version_conf.set('ANT_GIT_HASH', git_hash) 46 47 version_conf.set('ANT_BUILD_DATE', build_date) 47 48 ··· 64 65 uuidv7_dep, 65 66 mongoose_dep, 66 67 argtable3_dep, 67 - libsodium_dep 68 + libsodium_dep, 69 + minicoro_dep 68 70 ] 69 71 )
+208 -77
src/ant.c
··· 19 19 #include "config.h" 20 20 #include "modules/timer.h" 21 21 22 + #define MINICORO_IMPL 23 + #include "minicoro.h" 24 + 22 25 typedef uint32_t jsoff_t; 23 26 24 27 typedef struct { ··· 62 65 jsoff_t resume_point; 63 66 jsval_t yield_value; 64 67 struct coroutine *next; 68 + mco_coro* mco; 69 + bool mco_started; 70 + bool is_ready; 65 71 } coroutine_t; 66 72 67 73 typedef struct { 68 74 coroutine_t *head; 69 75 coroutine_t *tail; 70 76 } coroutine_queue_t; 77 + 78 + typedef struct { 79 + struct js *js; 80 + const char *code; 81 + size_t code_len; 82 + jsval_t closure_scope; 83 + jsval_t result; 84 + jsval_t promise; 85 + bool has_error; 86 + coroutine_t *coro; 87 + } async_exec_context_t; 71 88 72 89 static this_stack_t global_this_stack = {NULL, 0, 0}; 73 90 static call_stack_t global_call_stack = {NULL, 0, 0}; ··· 261 278 static jsval_t builtin_Date(struct js *js, jsval_t *args, int nargs); 262 279 static jsval_t builtin_Date_now(struct js *js, jsval_t *args, int nargs); 263 280 281 + static jsval_t call_js(struct js *js, const char *fn, jsoff_t fnlen, jsval_t closure_scope); 282 + static jsval_t start_async_in_coroutine(struct js *js, const char *code, size_t code_len, jsval_t closure_scope); 283 + 284 + static void free_coroutine(coroutine_t *coro); 285 + static bool has_ready_coroutines(void); 286 + 287 + static void mco_async_entry(mco_coro* mco) { 288 + async_exec_context_t *ctx = (async_exec_context_t *)mco_get_user_data(mco); 289 + 290 + struct js *js = ctx->js; 291 + jsval_t result = call_js(js, ctx->code, (jsoff_t)ctx->code_len, ctx->closure_scope); 292 + 293 + ctx->result = result; 294 + ctx->has_error = is_err(result); 295 + 296 + if (ctx->has_error) { 297 + js_reject_promise(js, ctx->promise, result); 298 + } else { 299 + js_resolve_promise(js, ctx->promise, result); 300 + } 301 + 302 + } 303 + 304 + /* unused for now 264 305 static coroutine_t *create_coroutine(struct js *js, jsval_t promise, jsval_t async_func) { 265 306 coroutine_t *coro = (coroutine_t *)malloc(sizeof(coroutine_t)); 266 307 if (!coro) return NULL; ··· 281 322 coro->yield_value = js_mkundef(); 282 323 coro->next = NULL; 283 324 325 + coro->mco = NULL; 326 + coro->mco_started = false; 327 + coro->is_ready = false; 328 + 284 329 return coro; 285 330 } 286 331 287 - /* unused for now 288 332 static coroutine_t *create_generator(struct js *js, jsval_t gen_func, jsval_t *args, int nargs, bool is_async) { 289 333 coroutine_t *coro = (coroutine_t *)malloc(sizeof(coroutine_t)); 290 334 if (!coro) return NULL; ··· 346 390 return pending_coroutines.head != NULL; 347 391 } 348 392 393 + static bool has_ready_coroutines(void) { 394 + coroutine_t *temp = pending_coroutines.head; 395 + while (temp) { 396 + if (temp->is_ready) return true; 397 + temp = temp->next; 398 + } 399 + return false; 400 + } 401 + 349 402 void js_run_event_loop(struct js *js) { 350 - while (has_pending_microtasks() || has_pending_timers()) { 351 - process_microtasks(js); 352 - 403 + while (has_pending_microtasks() || has_pending_timers() || has_pending_coroutines()) { 353 404 if (has_pending_timers()) { 354 405 int64_t next_timeout_ms = get_next_timer_timeout(); 406 + if (next_timeout_ms <= 0) process_timers(js); 407 + } 408 + 409 + process_microtasks(js); 410 + 411 + coroutine_t *temp = pending_coroutines.head; 412 + coroutine_t *prev = NULL; 413 + 414 + while (temp) { 415 + coroutine_t *next = temp->next; 355 416 356 - if (next_timeout_ms <= 0) { 357 - process_timers(js); 358 - continue; 417 + if (temp->is_ready && temp->mco && mco_status(temp->mco) == MCO_SUSPENDED) { 418 + if (prev) { 419 + prev->next = next; 420 + } else { 421 + pending_coroutines.head = next; 422 + } 423 + if (pending_coroutines.tail == temp) { 424 + pending_coroutines.tail = prev; 425 + } 426 + 427 + mco_result res = mco_resume(temp->mco); 428 + 429 + if (res == MCO_SUCCESS && mco_status(temp->mco) == MCO_DEAD) { 430 + free_coroutine(temp); 431 + } else if (res == MCO_SUCCESS) { 432 + temp->is_ready = false; 433 + if (pending_coroutines.tail) { 434 + pending_coroutines.tail->next = temp; 435 + pending_coroutines.tail = temp; 436 + } else { 437 + pending_coroutines.head = pending_coroutines.tail = temp; 438 + } 439 + temp->next = NULL; 440 + prev = temp; 441 + } else { 442 + free_coroutine(temp); 443 + } 444 + 445 + temp = next; 359 446 } else { 360 - usleep(next_timeout_ms > 1000000 ? 1000000 : next_timeout_ms * 1000); 447 + prev = temp; 448 + temp = next; 361 449 } 362 450 } 451 + 452 + if (!has_pending_microtasks() && has_pending_timers() && !has_ready_coroutines()) { 453 + int64_t next_timeout_ms = get_next_timer_timeout(); 454 + if (next_timeout_ms > 0) usleep(next_timeout_ms > 1000000 ? 1000000 : next_timeout_ms * 1000); 455 + } 456 + 457 + if (!has_pending_microtasks() && !has_pending_timers() && !has_pending_coroutines()) break; 363 458 } 364 459 } 365 460 461 + static jsval_t start_async_in_coroutine(struct js *js, const char *code, size_t code_len, jsval_t closure_scope) { 462 + jsval_t promise = js_mkpromise(js); 463 + async_exec_context_t *ctx = (async_exec_context_t *)malloc(sizeof(async_exec_context_t)); 464 + if (!ctx) return js_mkerr(js, "out of memory for async context"); 465 + 466 + ctx->js = js; 467 + ctx->code = code; 468 + ctx->code_len = code_len; 469 + ctx->closure_scope = closure_scope; 470 + ctx->result = js_mkundef(); 471 + ctx->promise = promise; 472 + ctx->has_error = false; 473 + ctx->coro = NULL; 474 + 475 + mco_desc desc = mco_desc_init(mco_async_entry, 0); 476 + desc.user_data = ctx; 477 + 478 + mco_coro* mco = NULL; 479 + mco_result res = mco_create(&mco, &desc); 480 + if (res != MCO_SUCCESS) { 481 + free(ctx); 482 + return js_mkerr(js, "failed to create minicoro coroutine"); 483 + } 484 + 485 + coroutine_t *coro = (coroutine_t *)malloc(sizeof(coroutine_t)); 486 + if (!coro) { 487 + mco_destroy(mco); 488 + free(ctx); 489 + return js_mkerr(js, "out of memory for coroutine"); 490 + } 491 + 492 + coro->js = js; 493 + coro->type = CORO_ASYNC_AWAIT; 494 + coro->scope = closure_scope; 495 + coro->this_val = js->this_val; 496 + coro->awaited_promise = js_mkundef(); 497 + coro->result = js_mkundef(); 498 + coro->async_func = js->current_func; 499 + coro->args = NULL; 500 + coro->nargs = 0; 501 + coro->is_settled = false; 502 + coro->is_error = false; 503 + coro->is_done = false; 504 + coro->resume_point = 0; 505 + coro->yield_value = js_mkundef(); 506 + coro->next = NULL; 507 + coro->mco = mco; 508 + coro->mco_started = false; 509 + coro->is_ready = true; 510 + 511 + ctx->coro = coro; 512 + enqueue_coroutine(coro); 513 + 514 + res = mco_resume(mco); 515 + if (res != MCO_SUCCESS && mco_status(mco) != MCO_DEAD) { 516 + dequeue_coroutine(); 517 + free_coroutine(coro); 518 + free(ctx); 519 + return js_mkerr(js, "failed to start coroutine"); 520 + } 521 + 522 + coro->mco_started = true; 523 + if (mco_status(mco) == MCO_DEAD) free(ctx); 524 + 525 + return promise; 526 + } 527 + 366 528 static void free_coroutine(coroutine_t *coro) { 367 529 if (coro) { 530 + if (coro->mco) { 531 + mco_destroy(coro->mco); 532 + coro->mco = NULL; 533 + } 368 534 if (coro->args) free(coro->args); 369 535 free(coro); 370 536 } ··· 1793 1959 jsval_t saved_func = js->current_func; 1794 1960 js->current_func = func; 1795 1961 js->nogc = (jsoff_t) (fnoff - sizeof(jsoff_t)); 1796 - res = call_js(js, code_str, fnlen, closure_scope); 1797 - js->current_func = saved_func; 1798 1962 1799 - pop_call_frame(); 1800 - if (is_async && !is_err(res)) { 1801 - jsval_t promise_args[] = { res }; 1802 - res = builtin_Promise_resolve(js, promise_args, 1); 1963 + if (is_async) { 1964 + res = start_async_in_coroutine(js, code_str, fnlen, closure_scope); 1965 + pop_call_frame(); 1966 + } else { 1967 + res = call_js(js, code_str, fnlen, closure_scope); 1968 + pop_call_frame(); 1803 1969 } 1970 + 1971 + js->current_func = saved_func; 1804 1972 } 1805 1973 } 1806 1974 } else { ··· 2838 3006 } 2839 3007 } 2840 3008 2841 - coroutine_t *coro = create_coroutine(js, resolved, js->current_func); 2842 - if (!coro) return js_mkerr(js, "failed to create coroutine"); 3009 + mco_coro* current_mco = mco_running(); 3010 + if (!current_mco) return js_mkerr(js, "await can only be used inside async functions"); 3011 + 3012 + async_exec_context_t *ctx = (async_exec_context_t *)mco_get_user_data(current_mco); 3013 + if (!ctx || !ctx->coro) return js_mkerr(js, "invalid async context"); 3014 + 3015 + coroutine_t *coro = ctx->coro; 3016 + coro->awaited_promise = resolved; 3017 + coro->is_settled = false; 3018 + coro->is_ready = false; 2843 3019 2844 3020 jsval_t resume_obj = mkobj(js, 0); 2845 3021 setprop(js, resume_obj, js_mkstr(js, "__native_func", 13), js_mkfun(resume_coroutine_wrapper)); ··· 2864 3040 uint8_t saved_tok = js->tok; 2865 3041 uint8_t saved_consumed = js->consumed; 2866 3042 2867 - while (!coro->is_settled) { 2868 - if (has_pending_timers()) { 2869 - int64_t next_timeout = get_next_timer_timeout(); 2870 - if (next_timeout <= 0) { 2871 - process_timers(js); 2872 - js->flags = saved_flags; 2873 - js->code = saved_code; 2874 - js->clen = saved_clen; 2875 - js->pos = saved_pos; 2876 - js->tok = saved_tok; 2877 - js->consumed = saved_consumed; 2878 - } 2879 - } 2880 - 2881 - process_microtasks(js); 2882 - js->flags = saved_flags; 2883 - js->code = saved_code; 2884 - js->clen = saved_clen; 2885 - js->pos = saved_pos; 2886 - js->tok = saved_tok; 2887 - js->consumed = saved_consumed; 2888 - 2889 - int coroutines_checked = 0; 2890 - int total_coroutines = 0; 2891 - 2892 - coroutine_t *temp = pending_coroutines.head; 2893 - while (temp) { 2894 - total_coroutines++; 2895 - temp = temp->next; 2896 - } 2897 - 2898 - while (has_pending_coroutines() && coroutines_checked < total_coroutines) { 2899 - coroutine_t *resumed = dequeue_coroutine(); 2900 - coroutines_checked++; 2901 - 2902 - if (resumed == coro) { 2903 - jsval_t result = resumed->result; 2904 - bool is_error = resumed->is_error; 2905 - free_coroutine(resumed); 2906 - 2907 - if (is_error) return js_throw(js, result); 2908 - return result; 2909 - } 2910 - enqueue_coroutine(resumed); 2911 - } 2912 - 2913 - if (!has_pending_microtasks() && !has_pending_coroutines() && !has_pending_timers()) { 2914 - free_coroutine(coro); 2915 - return js_mkerr(js, "await: promise never settled"); 2916 - } 2917 - 2918 - if (has_pending_timers() && !has_pending_microtasks() && !has_pending_coroutines()) { 2919 - int64_t next_timeout = get_next_timer_timeout(); 2920 - if (next_timeout > 0) { 2921 - int64_t sleep_ms = next_timeout < 1 ? 1 : (next_timeout > 1 ? 1 : next_timeout); 2922 - usleep(sleep_ms * 1000); 2923 - } 2924 - } 3043 + mco_result mco_res = mco_yield(current_mco); 3044 + js->flags = saved_flags; 3045 + js->code = saved_code; 3046 + js->clen = saved_clen; 3047 + js->pos = saved_pos; 3048 + js->tok = saved_tok; 3049 + js->consumed = saved_consumed; 3050 + 3051 + if (mco_res != MCO_SUCCESS) { 3052 + return js_mkerr(js, "failed to yield coroutine"); 2925 3053 } 2926 3054 2927 3055 jsval_t result = coro->result; 2928 3056 bool is_error = coro->is_error; 2929 - free_coroutine(coro); 3057 + 3058 + coro->is_settled = false; 3059 + coro->awaited_promise = js_mkundef(); 2930 3060 2931 3061 if (is_error) { 2932 3062 return js_throw(js, result); ··· 6225 6355 coroutine_t *coro = (coroutine_t *)(uintptr_t)tod(coro_val); 6226 6356 if (!coro) return js_mkundef(); 6227 6357 6358 + 6228 6359 coro->result = nargs > 0 ? args[0] : js_mkundef(); 6229 6360 coro->is_settled = true; 6230 6361 coro->is_error = false; 6362 + coro->is_ready = true; 6231 6363 6232 - enqueue_coroutine(coro); 6233 6364 return js_mkundef(); 6234 6365 } 6235 6366 ··· 6245 6376 coro->result = nargs > 0 ? args[0] : js_mkundef(); 6246 6377 coro->is_settled = true; 6247 6378 coro->is_error = true; 6379 + coro->is_ready = true; 6248 6380 6249 - enqueue_coroutine(coro); 6250 6381 return js_mkundef(); 6251 6382 } 6252 6383
+8
subprojects/minicoro.wrap
··· 1 + [wrap-git] 2 + url = https://github.com/edubart/minicoro.git 3 + revision = head 4 + patch_directory = minicoro 5 + depth = 1 6 + 7 + [provide] 8 + minicoro = minicoro_dep
+4
subprojects/packagefiles/minicoro/meson.build
··· 1 + project('minicoro', 'c') 2 + 3 + minicoro_inc = include_directories('.') 4 + minicoro_dep = declare_dependency(include_directories: minicoro_inc)
+6
tests/dynamic-import.js
··· 1 + async function main() { 2 + const module = await import('./export-test.js'); 3 + module.hello('ant'); 4 + } 5 + 6 + void main();
+3
tests/export-test.js
··· 1 + export function hello(name) { 2 + console.log('hello', name); 3 + }
+29
tests/test_minicoro_concurrent.cjs
··· 1 + // Test that multiple async functions run concurrently 2 + async function delay(ms, name) { 3 + const start = Date.now(); 4 + await new Promise(resolve => setTimeout(resolve, ms)); 5 + const elapsed = Date.now() - start; 6 + console.log(`${name}: ${elapsed}ms`); 7 + return elapsed; 8 + } 9 + 10 + console.log("Testing concurrent async/await with minicoro"); 11 + const globalStart = Date.now(); 12 + 13 + // Start both async functions - they should run concurrently 14 + delay(100, "Task A (100ms)"); 15 + delay(50, "Task B (50ms)"); 16 + 17 + // Wait for both to complete 18 + setTimeout(() => { 19 + const total = Date.now() - globalStart; 20 + console.log(`\nTotal time: ${total}ms`); 21 + 22 + // If concurrent: ~105ms (max of the two) 23 + // If sequential: ~155ms (sum of the two) 24 + if (total < 130) { 25 + console.log("โœ“ PASS: Tasks ran concurrently!"); 26 + } else { 27 + console.log("โœ— FAIL: Tasks ran sequentially"); 28 + } 29 + }, 120);
+12
tests/test_promise_resolve.cjs
··· 1 + async function test() { 2 + await new Promise(resolve => setTimeout(resolve, 50)); 3 + return "result"; 4 + } 5 + 6 + console.log("Calling async function"); 7 + const p = test(); 8 + console.log("Promise returned:", typeof p); 9 + 10 + p.then(r => console.log("Then callback:", r)); 11 + 12 + setTimeout(() => console.log("Done"), 100);