Decouple kMaxUserCallDepth's guard from callStack_.size() - #65
Merged
Conversation
…2 ident() false trip) enterUserCall's own depth guard checked callStack_.size() directly -- but callStack_ also grows from cheap, zero-native-cost compiled module-call pushes (skipDepthGuard=true, Op::CallModule via pushBracketedModuleFrame), which cost no real native stack at all (serviced by driveVm's own heap-based loop). Those pushes still count toward the SAME threshold a later genuinely interpreted call (skipDepthGuard=false) is guarded by. Caught for real: Anklet.scad's own ambient callStack_ depth (mostly cheap compiled Op::CallModule pushes from its BOSL2 attachable() chain) reached the mid-30s, tripping kMaxUserCallDepth=30 for BOSL2's plain `ident()` function call -- even though the REAL native C++ nesting at that point was nowhere near it. This is the exact same class of bug driveVmNativeDepth_ already exists to avoid for the other guard site (bytecode_vm.cpp) -- this is that same fix, applied here: a new nativeUserCallDepth_ counter, incremented/decremented only for skipDepthGuard=false pushes, checked instead of callStack_.size(). New regression test (DeepCompiledModuleRecursionDoesNotFalselyTripThe InterpretedFunctionGuard) proves it: 100 levels of cheap compiled module recursion followed by one genuinely-interpreted function call (forced via a fast-continue breakpoint) no longer trips the guard -- confirmed to actually fail without the fix by temporarily reverting it. Bumps to 0.13.3, since Anklet.scad (fixed for its translate/multmatrix/ color chain in 0.13.2) needs this too to fully render. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2 tasks
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.
Summary
ERROR: Recursion too deep while calling function 'ident'.enterUserCall's own depth guard checkedcallStack_.size()directly — butcallStack_also grows from cheap, zero-native-cost compiled module-call pushes (skipDepthGuard=true,Op::CallModuleviapushBracketedModuleFrame), which cost no real native stack at all (serviced bydriveVm's own heap-based loop). Those pushes still counted toward the SAME threshold a later genuinely interpreted call (skipDepthGuard=false) is guarded by — the exact same class of bugdriveVmNativeDepth_already exists to avoid for the other guard site.nativeUserCallDepth_counter, incremented/decremented only forskipDepthGuard=falsepushes, checked instead ofcallStack_.size().Known follow-up still outstanding (not fixed here)
Anklet.scad now gets past
translate/multmatrix/color-wrapped recursion (0.13.2) and past theident()false trip (this PR), but still doesn't fully render:children()(BOSL2's own children-forwarding mechanism) is the next and last remaining native-reentry source. Unlike the constructsOp::PushBuiltinWrapcovers,children()forwards a runtime-varying set of children (resolved dynamically viactx.childrenNodes/childrenCallerCtxper caller, not statically known from the AST) — so it can't reuse the same "compile children inline" trick. Fixing it needs its own design pass extending the explicit-stack VM to handle a runtime-varying splice target.Test plan
oscad_eval_testssuite green (714/714), bothOSCAD_BYTECODE_VM=0and=1.🤖 Generated with Claude Code