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.

introduce sentinel type for internal optimizations

+156 -8
+4 -3
include/internal.h
··· 112 112 enum { 113 113 T_OBJ, T_PROP, T_STR, T_UNDEF, T_NULL, T_NUM, T_BOOL, T_FUNC, 114 114 T_CODEREF, T_CFUNC, T_ERR, T_ARR, T_PROMISE, T_TYPEDARRAY, 115 - T_BIGINT, T_PROPREF, T_SYMBOL, T_GENERATOR, T_FFI, T_NTARG 115 + T_BIGINT, T_PROPREF, T_SYMBOL, T_GENERATOR, T_FFI, T_NTARG, 116 + T_SENTINEL = 31 116 117 }; 117 118 118 119 #define JS_HASH_SIZE 512 ··· 137 138 #define NANBOX_DATA_MASK 0x0000FFFFFFFFFFFFULL 138 139 139 140 #define TYPE_FLAG(t) (1u << (t)) 140 - #define T_EMPTY (NANBOX_PREFIX | ((jsval_t)T_FFI << NANBOX_TYPE_SHIFT) | 0xDEADULL) 141 - #define T_TAILCALL (NANBOX_PREFIX | ((jsval_t)T_FFI << NANBOX_TYPE_SHIFT) | 0x7A1CULL) 141 + #define T_EMPTY (NANBOX_PREFIX | ((jsval_t)T_SENTINEL << NANBOX_TYPE_SHIFT) | 0xDEADULL) 142 + #define T_TAILCALL (NANBOX_PREFIX | ((jsval_t)T_SENTINEL << NANBOX_TYPE_SHIFT) | 0x7A1CULL) 142 143 143 144 #define T_SPECIAL_OBJECT_MASK (TYPE_FLAG(T_OBJ) | TYPE_FLAG(T_ARR)) 144 145 #define T_NEEDS_PROTO_FALLBACK (TYPE_FLAG(T_FUNC) | TYPE_FLAG(T_ARR) | TYPE_FLAG(T_PROMISE))
+1 -1
meson/ant.version
··· 1 - 0.6.4 1 + 0.7.0
+14 -4
src/ant.c
··· 364 364 const char *names[] = { 365 365 "object", "prop", "string", "undefined", "null", "number", 366 366 "boolean", "function", "coderef", "cfunc", "err", "array", 367 - "promise", "typedarray", "bigint", "propref", "symbol", "generator", "ffi", "ntarg" 367 + "promise", "typedarray", "bigint", "propref", "symbol", 368 + "generator", "ffi", "ntarg" 368 369 }; 369 370 370 371 return (t < sizeof(names) / sizeof(names[0])) ? names[t] : "??"; ··· 8417 8418 int argc = (int)utarray_len(call_args); 8418 8419 res = start_async_in_coroutine(js, code_str, fnlen, closure_scope, argv, argc); 8419 8420 utarray_free(call_args); 8420 - } else if (is_tail && !is_arrow && !is_bound && vtype(js->new_target) == T_UNDEF) { 8421 + } else if (is_tail && vtype(js->new_target) == T_UNDEF) { 8421 8422 UT_array *tc_args_arr; 8422 8423 utarray_new(tc_args_arr, &jsval_icd); 8423 8424 if (bound_args) { ··· 10313 10314 } 10314 10315 js_call_dot_loop: 10315 10316 bool opt_chain_skip = false; 10317 + bool saved_tail_for_chain = js->tail_ctx; 10316 10318 uint8_t cd_tok; 10317 10319 while ( 10318 10320 cd_tok = next(js), ··· 10403 10405 } else func_this = js->this_val; 10404 10406 } 10405 10407 push_this(func_this); 10408 + js->tail_ctx = false; 10406 10409 jsval_t params = js_call_params(js); 10407 10410 if (is_err(params)) { 10408 10411 pop_this(); 10409 10412 return params; 10410 10413 } 10414 + uint8_t la = lookahead(js); 10415 + if (la != TOK_LPAREN && la != TOK_DOT && la != TOK_LBRACKET 10416 + && la != TOK_OPTIONAL_CHAIN && la != TOK_TEMPLATE) { 10417 + js->tail_ctx = saved_tail_for_chain; 10418 + } 10411 10419 res = do_op(js, TOK_CALL, res, params); 10412 10420 pop_this(); 10421 + if (res == (jsval_t)T_TAILCALL) return res; 10413 10422 if (is_super_call && !is_err(res) && is_object_type(res)) { 10414 10423 jsoff_t proto_off = lkp_interned(js, mkval(T_OBJ, vdata(js->current_func)), INTERN_PROTOTYPE, 9); 10415 10424 if (proto_off) set_proto(js, res, resolveprop(js, mkval(T_PROP, proto_off))); ··· 13171 13180 static bool is_tail_call_expr(struct js *js) { 13172 13181 const char *code = js->code; 13173 13182 jsoff_t clen = js->clen; 13174 - jsoff_t start = skiptonext(code, clen, js->pos, NULL); 13183 + jsoff_t start = skiptonext(code, clen, js->toff, NULL); 13175 13184 return scan_tail_span(code, clen, start, clen) == TAIL_OK; 13176 13185 } 13177 13186 ··· 13185 13194 if (nxt != TOK_SEMICOLON && nxt != TOK_RBRACE && nxt != TOK_EOF && !js->had_newline) { 13186 13195 bool prev_tail = js->tail_ctx; 13187 13196 if (exe && in_func && js->has_tail_calls && is_tail_call_expr(js)) js->tail_ctx = true; 13188 - res = resolveprop(js, js_expr_comma(js)); 13197 + res = js_expr_comma(js); 13189 13198 js->tail_ctx = prev_tail; 13199 + if (res != (jsval_t)T_TAILCALL) res = resolveprop(js, res); 13190 13200 } 13191 13201 13192 13202 if (exe && !in_func) return js_mkundef();
+115
tests/test_tco_curried.js
··· 1 + // Test tail call optimization for curried/chained calls 2 + 3 + // 1. Basic curried tail call: f(a)(b) 4 + function makeStepper(target) { 5 + return function step(n) { 6 + if (n >= target) return n; 7 + return makeStepper(target)(n + 1); 8 + }; 9 + } 10 + console.log('curried step(0โ†’100000):', makeStepper(100000)(0)); // 100000 11 + 12 + // 2. Arrow function returning curried call 13 + const curry2 = f => a => b => f(a, b); 14 + const add = curry2((a, b) => a + b); 15 + console.log('curry2 add(3)(4):', add(3)(4)); // 7 16 + 17 + // 3. Deep curried recursion (bouncer pattern from newt bootstrap) 18 + const bouncer = (f, ini) => { 19 + let obj = ini; 20 + while (obj.tag) obj = f(obj); 21 + return obj.h0; 22 + }; 23 + 24 + function trampCountDown(n) { 25 + if (n <= 0) return { tag: 0, h0: 'done' }; 26 + return { tag: 1, h0: null, fn: () => trampCountDown(n - 1) }; 27 + } 28 + // Not a direct curried TCO test, but validates the pattern works 29 + const res1 = bouncer(obj => obj.fn(), { tag: 1, h0: null, fn: () => trampCountDown(100000) }); 30 + console.log('bouncer countDown:', res1); // done 31 + 32 + // 4. Curried tail call with intermediate result used as function 33 + function makeCounter(acc) { 34 + return function (n) { 35 + if (n <= 0) return acc; 36 + return makeCounter(acc + 1)(n - 1); 37 + }; 38 + } 39 + console.log('curried counter(100000):', makeCounter(0)(100000)); // 100000 40 + 41 + // 5. Triple-curried tail call: f(a)(b)(c) 42 + function tripleStep(target) { 43 + return function (inc) { 44 + return function (n) { 45 + if (n >= target) return n; 46 + return tripleStep(target)(inc)(n + inc); 47 + }; 48 + }; 49 + } 50 + console.log('triple curried(50000):', tripleStep(50000)(1)(0)); // 50000 51 + 52 + // 6. Method-style chained call should NOT get TCO for intermediate 53 + // (a.b() returns obj, then .c() is the tail call) 54 + const obj = { 55 + makeNext(n) { 56 + if (n <= 0) return { val: () => 'done' }; 57 + return { val: () => obj.makeNext(n - 1).val() }; 58 + } 59 + }; 60 + console.log('method chain:', obj.makeNext(100).val()); // done 61 + 62 + // 7. Curried arrow function tail call 63 + const curriedDown = n => { 64 + if (n <= 0) return 'finished'; 65 + return (x => curriedDown(x))(n - 1); 66 + }; 67 + console.log('curried arrow(100000):', curriedDown(100000)); // finished 68 + 69 + // 8. Ensure intermediate calls are NOT tail-optimized 70 + // f(a) must complete normally because its result is called 71 + let callCount = 0; 72 + function makeAdder(x) { 73 + callCount++; 74 + return function (y) { 75 + return x + y; 76 + }; 77 + } 78 + const result = makeAdder(10)(20); 79 + console.log('intermediate not TCO:', result, 'calls:', callCount); // 30 calls: 1 80 + 81 + // 9. Mutual curried recursion 82 + function pingFactory(n) { 83 + return function () { 84 + if (n <= 0) return 'pong'; 85 + return pongFactory(n - 1)(); 86 + }; 87 + } 88 + function pongFactory(n) { 89 + return function () { 90 + if (n <= 0) return 'ping'; 91 + return pingFactory(n - 1)(); 92 + }; 93 + } 94 + console.log('mutual curried(100000):', pingFactory(100000)()); // pong 95 + 96 + // 10. Monad-style bind chain (simplified newt pattern) 97 + // Note: m.h1(tc)(eta) is NOT in tail position (bound to const sc), 98 + // so this tests correctness of curried calls, not deep TCO. 99 + const MkM = h1 => ({ tag: 0, h1: h1 }); 100 + const bind = (m, f) => 101 + MkM(tc => eta => { 102 + const sc = m.h1(tc)(eta); 103 + if (sc.tag === 1) return f(sc.h2.h3).h1(sc.h2.h2)(eta); 104 + return sc; 105 + }); 106 + const pure = v => MkM(tc => eta => ({ tag: 1, h2: { h2: tc, h3: v } })); 107 + 108 + let chain = pure(0); 109 + for (let i = 0; i < 200; i++) { 110 + chain = bind(chain, v => pure(v + 1)); 111 + } 112 + const monadResult = chain.h1({})(0); 113 + console.log('monad chain result:', monadResult.h2.h3); // 1000 114 + 115 + console.log('\nAll curried TCO tests passed!');
+5
tests/test_tco_curried_debug.js
··· 1 + const curriedDown = (n) => { 2 + if (n <= 0) return 'finished'; 3 + return ((x) => curriedDown(x))(n - 1); 4 + }; 5 + console.log(curriedDown(10));
+17
tests/test_tco_monad.js
··· 1 + // Monad-style bind chain (simplified newt pattern) 2 + const MkM = (h1) => ({ tag: 0, h1: h1 }); 3 + 4 + const bind = (m, f) => MkM((tc) => (eta) => { 5 + const sc = m.h1(tc)(eta); 6 + if (sc.tag === 1) return f(sc.h2.h3).h1(sc.h2.h2)(eta); 7 + return sc; 8 + }); 9 + 10 + const pure = (v) => MkM((tc) => (eta) => ({ tag: 1, h2: { h2: tc, h3: v } })); 11 + 12 + let chain = pure(0); 13 + for (let i = 0; i < 100; i++) { 14 + chain = bind(chain, (v) => pure(v + 1)); 15 + } 16 + const result = chain.h1({})(0); 17 + console.log('monad chain result:', result.h2.h3); // 100