frostney / frostney/GocciaScript
Tree-walker-created functions never run as bytecode, so shim and Date.prototype methods tree-walk forever
Nobody has claimed this yet.
- Dominant language
- Pascal
- Stars
- 20
- Forks
- 3
- Avg merge
- 3d 4h
- Merged PRs (30d)
- 45
Description
Summary
GocciaScript has no function-level compile-on-call: a function object created by the tree-walk interpreter keeps an AST body and tree-walks it on every invocation — even in --mode=bytecode and even when the caller is bytecode-compiled. Only functions compiled as part of a program through TGocciaCompiler.Compile ever become VM closures.
Add a path that promotes tree-walker-created functions to bytecode so hot calls run on the VM. The motivating consumer is the boot shims, especially Date.prototype.*: as of #747 / PR #790 the Date shim is lazily materialized, but once materialized its methods still tree-walk on every call, which is the residual interpreter-dispatch cost behind the Date-perf tail in #829.
Shims are the prime beneficiary, but the limitation is general — it also covers Goccia.Shims.pas functions and anything built via InjectGlobalsFromModule.
Why
Date.prototype.*tree-walks forever.Goccia.Shims.pasevaluates shim modules viaLoadShimValue→ the interpreter (EvaluateStatement). The resultingTGocciaFunctionValues carryFBodyStatements(AST), soCall → ExecuteBody(Goccia.Values.FunctionValue.pas) tree-walks them on every call. Date is a WeakMap-slot JS shim whose methods are called heavily in date-processing workloads; this dispatch is the un-optimized hot path under #819 / #829.- Conformance timeouts, not raw-throughput vanity. VISION.md deliberately ranks correctness/embeddability/reduced-attack-surface above raw throughput, so this is not about chasing V8. It is justified by the conformance-track objective: the dst-offset DST shards in #829 time out at 20s and the residual is interpreter dispatch/alloc, not the DST-offset code. Promoting hot shim methods to bytecode is a targeted lever for those timeouts.
- The bytecode path is otherwise complete. Per Bytecode VM, the executor abstraction already compiles and runs programs; the one residual interpreter coupling is direct
eval, and shim evaluation rides that same interpreter path. Closing this gap removes a whole class of "runs as bytecode except when it doesn't."
Current behavior
LoadShimValue(AInterpreter, AShim)parses the shim module and runs each top-level statement withEvaluateStatement— always the tree-walker, regardless of the configured executor. Functions defined in the shim (Date constructor,Date.prototype.toISOString, etc.) become AST-backedTGocciaFunctionValues.- When such a function is later called from bytecode-mode user code,
TGocciaFunctionValue.Call → ExecuteBodytree-walks the AST body. There is no compile-on-first-call and no promotion.
Expected behavior
- A correctness-preserving mechanism by which a tree-walker-created function's body executes as bytecode on the VM when the engine is in bytecode mode — either by routing shim evaluation through the executor's
CompileModule/RunCompiledModuleInScope/EvaluateModuleBody(the same API the loader/REPL uses for new input, so nested shim functions becomeTGocciaFunctionTemplateclosures), or by a general per-function compile-on-call promotion. - Interpreter and bytecode modes stay semantically identical; promotion changes only how a body runs, never what it computes.
Scope notes
- Known blockers (from investigation): (1) bootstrap ordering —
ExecuteShimsruns atGoccia.Engine.pas~line 670, butFExecutor.Initialize(which setsFVM.Realm/GlobalScope) runs after it, so the bytecode VM is not wired up at shim time; (2)LoadShimValueis hardwired to the interpreter. A deliberate code comment documents the current interpreter-only choice ("consistent with howInjectGlobalsFromModuleworks in both engine modes"). - Direction-dependent trade-off: compiling a shim's one-shot top-level costs more at materialization (no amortization → slower boot, which works against #747), but the resulting methods become VM-backed (a win only for hot shims like
Date; a net loss for rarely-called ones likebtoa/parseInt). The design must account for this asymmetry (e.g. promote per-function on a call-count threshold, or compile only selected shims). - Hard acceptance gate (benchmark-gated): land only on a measured net win — the Date/shim hot path must get faster and empty-script boot must not regress from the #747 baseline (~75M instructions retired through the loader profile,
--mode=bytecode, aarch64 dev). If top-level compile cost outweighs the per-call speedup, do not land. Record before/after for both the hot-path benchmark and empty-script boot in the PR. Reproduce CI-representative timing per the project's OrbStack guidance when judging #829 shard timeouts. - Not lazy materialization. This is orthogonal to #747 / PR #790: laziness controls whether/when a built-in is built; this controls how its functions dispatch once built.
- Related: #819 (performance micro-optimizations), #829 (dst-offset DST tests time out at 20s — the primary motivator), #747 / PR #790 (lazy built-ins; lazy Date), and the bytecode-VM executor architecture in docs/bytecode-vm.md.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with LoadShimValue in Goccia.Shims.pas, TGocciaFunctionValue.Call and ExecuteBody in Goccia.Values.FunctionValue.pas, and the executor initialization order in Goccia.Engine.pas. Read docs/bytecode-vm.md, then measure the Date/shim hot path and empty-script bytecode boot before and after. Done means semantically identical interpreter and bytecode modes, with promotion improving the hot path without regressing the boot baseline.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- compilers, performance
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100