Skip to content

Cache included ASTs in memory: a BOSL2 render goes 67ms to 7.5ms - #148

Merged
revarbat merged 1 commit into
mainfrom
include-ast-cache-v2
Sep 6, 2026
Merged

revarbat merged 1 commit into
mainfrom
include-ast-cache-v2

Conversation

@revarbat

@revarbat revarbat commented Sep 6, 2026

Copy link
Copy Markdown
Member

Parsing include <BOSL2/std.scad> is ~55 ms, which was 82% of evaluating a small script against it — and it happened on every render, of every script, even though the library never changed. An editor re-rendering paid it each time; BOSL2's docs build paid it once per example, a thousand times over.

Files are now parsed once and shared (openscad_cpp_parser#7).

before after
evaluate() with BOSL2 67.2 ms 7.5 ms (9×)
BOSL2's 909-test suite 95.3 s 39.1 s (2.4×)

What changed here

The evaluator takes the borrowed statement list the cache produces, which needed a resolveUseScopes() overload for nodes it does not own. Evaluator/EvalContext/Compiler now carry the run's ScopeTable, since a node no longer carries its own scope — the parser PR explains why it cannot.

A Closure also captures its defining scope now. It used to read it back off its FunctionLiteral node, which works only while the node holds one, and a closure can be called at generate time with no EvalContext left to look it up through.

The bug worth knowing about

childCtx() and callCtx() build a fresh EvalContext field by field. The version of this that forgot to carry scopeTable made every node's scope read back as null. Name resolution then quietly fell back to the enclosing scope, so a module body calling a builtin of its own name found itself — BOSL2's module _cube(...) cube(...); wrapper recursed to the depth limit.

All 1149 tests passed with that bug. BOSL2 caught it. There is now a test that checks each derived context directly; removing the assignment makes it fail (verified).

Verified

  • 1153 C++ tests, 648 parser tests, 31 binding tests
  • BOSL2's 909 regression tests
  • Cache holds one entry per file and replaces on edit, so repeated saves don't accumulate ASTs — tested

🤖 Generated with Claude Code

Parsing `include <BOSL2/std.scad>` is ~55ms, which was 82% of evaluating a
small script against it -- and it happened on every render, of every script,
even though the library never changed. An editor re-rendering paid it each
time; BOSL2's docs build paid it once per example, a thousand times over.

Files are now parsed once and shared (openscad_cpp_parser#7). The evaluator
takes the borrowed statement list that produces, which needed a
resolveUseScopes() overload for nodes it does not own, and Evaluator/
EvalContext/Compiler now carry the run's ScopeTable, since a node no longer
carries its own scope -- see the parser PR for why it cannot.

    evaluate() with BOSL2      67.2ms -> 7.5ms   (9x)
    BOSL2's 909-test suite     95.3s  -> 39.1s   (2.4x)

A Closure also captures its defining scope now. It used to read it back off
its FunctionLiteral node, which works only while the node holds one, and a
closure can be called at generate time with no EvalContext left to look it
up through.

Watch out for one thing when adding an EvalContext constructor: childCtx()
and callCtx() build a fresh context field by field, and the version of this
that forgot to carry scopeTable made every node's scope read back as null.
Name resolution then quietly fell back to the enclosing scope, so a module
body calling a builtin of its own name found ITSELF -- BOSL2's
`module _cube(...) cube(...);` wrapper recursed to the depth limit. All 1149
tests passed with that bug; BOSL2 caught it. There is now a test that checks
each derived context directly, and it fails if the assignment is removed.

1153 C++ tests, 648 parser tests, 31 binding tests, BOSL2's 909.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@revarbat
revarbat merged commit c44eefe into main Sep 6, 2026
3 checks passed
@revarbat
revarbat deleted the include-ast-cache-v2 branch September 6, 2026 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant