Cache included ASTs in memory: a BOSL2 render goes 67ms to 7.5ms - #148
Merged
Merged
Conversation
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>
This was referenced Sep 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).
evaluate()with BOSL2What 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/Compilernow carry the run'sScopeTable, since a node no longer carries its own scope — the parser PR explains why it cannot.A
Closurealso captures its defining scope now. It used to read it back off itsFunctionLiteralnode, which works only while the node holds one, and a closure can be called at generate time with noEvalContextleft to look it up through.The bug worth knowing about
childCtx()andcallCtx()build a freshEvalContextfield by field. The version of this that forgot to carryscopeTablemade 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'smodule _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
🤖 Generated with Claude Code