Hi Robert — first off, thank you for ShenScript. The async-transparent design has held up remarkably well, and I've been building on it heavily.
I've been maintaining a fork at pyrex41/ShenScript and wanted to surface the work in case any of it is useful upstream. Summary of what's there:
Modernization
- Migrated to ES modules; runs on Node 20+, Bun, and Deno 2 (CI runs the kernel suite on all three)
- Targets ShenOSKernel 41.2 (vendored from Shen-Language/shen-sources with checksums/provenance)
Performance — the kernel certification suite went from 19.0s (Node) / 50.1s (Bun) to ~10s / ~19.5s on the same machine, via three changes that preserve semantics (all 134 kernel tests + extensions pass on all three runtimes, REPL/trap-error/Prolog/eval-redefinition verified):
- Arity-specialized function wrappers (
7841d8b) — funSync/funAsync emit fixed-parameter wrappers for arities 0–4 instead of variadic (...args) wrappers; rest/spread on every call was a dominant cost on JavaScriptCore and a measurable one on V8.
- No double promise layer (
0bc94ed) — async wrappers became plain functions returning the callee's promise directly, with an async: true own-property marker replacing instanceof AsyncFunction checks where wrappers are observed.
- Compile sync where provable (
ef36617) — call sites in async contexts compile to a conditional await ((w$ = $.t(f(...))) instanceof Promise ? await w$ : w$), any compiled lambda whose body contains no await is emitted as a plain function (66% of kernel lambdas), and let flattens to scoped assignments instead of awaited async IIFEs (2330 → 41 in the rendered kernel). Because callers test the result dynamically, runtime redefinition via eval — including redefining a sync-compiled function to an async one — keeps working with no restrictions.
While profiling this I also filed oven-sh/bun#32208; Bun's team confirmed the remaining Bun-vs-Node gap is JSC engine-level async-frame/promise-allocation throughput, not anything in ShenScript's design.
New capability — the fork is also a stage-2 target for Ratatoskr (a tree-shaker for Shen descended from Mark Tarver's Yggdrasil): a shaken, eval-stripped Shen program compiles ahead of time into one self-contained ~140 KB ES module with ~50 ms startup on Bun/Deno.
I realize this repo has been quiet for a while and the fork has diverged a fair bit (ESM + kernel bump underneath everything), so rather than open a large unsolicited PR I wanted to ask: is there interest in any of this landing upstream? The three performance changes are the most self-contained pieces — I'd be happy to discuss backporting them to the current codebase, or whatever form is most useful to you. And of course feel free to cherry-pick anything directly; it's all under the same BSD-3 license with your copyright intact.
Hi Robert — first off, thank you for ShenScript. The async-transparent design has held up remarkably well, and I've been building on it heavily.
I've been maintaining a fork at pyrex41/ShenScript and wanted to surface the work in case any of it is useful upstream. Summary of what's there:
Modernization
Performance — the kernel certification suite went from 19.0s (Node) / 50.1s (Bun) to ~10s / ~19.5s on the same machine, via three changes that preserve semantics (all 134 kernel tests + extensions pass on all three runtimes, REPL/trap-error/Prolog/eval-redefinition verified):
7841d8b) —funSync/funAsyncemit fixed-parameter wrappers for arities 0–4 instead of variadic(...args)wrappers; rest/spread on every call was a dominant cost on JavaScriptCore and a measurable one on V8.0bc94ed) — async wrappers became plain functions returning the callee's promise directly, with anasync: trueown-property marker replacinginstanceof AsyncFunctionchecks where wrappers are observed.ef36617) — call sites in async contexts compile to a conditional await ((w$ = $.t(f(...))) instanceof Promise ? await w$ : w$), any compiled lambda whose body contains no await is emitted as a plain function (66% of kernel lambdas), andletflattens to scoped assignments instead of awaited async IIFEs (2330 → 41 in the rendered kernel). Because callers test the result dynamically, runtime redefinition via eval — including redefining a sync-compiled function to an async one — keeps working with no restrictions.While profiling this I also filed oven-sh/bun#32208; Bun's team confirmed the remaining Bun-vs-Node gap is JSC engine-level async-frame/promise-allocation throughput, not anything in ShenScript's design.
New capability — the fork is also a stage-2 target for Ratatoskr (a tree-shaker for Shen descended from Mark Tarver's Yggdrasil): a shaken, eval-stripped Shen program compiles ahead of time into one self-contained ~140 KB ES module with ~50 ms startup on Bun/Deno.
I realize this repo has been quiet for a while and the fork has diverged a fair bit (ESM + kernel bump underneath everything), so rather than open a large unsolicited PR I wanted to ask: is there interest in any of this landing upstream? The three performance changes are the most self-contained pieces — I'd be happy to discuss backporting them to the current codebase, or whatever form is most useful to you. And of course feel free to cherry-pick anything directly; it's all under the same BSD-3 license with your copyright intact.