Skip to content
This repository was archived by the owner on Apr 29, 2026. It is now read-only.

stash stacks#352

Merged
navicore merged 2 commits intomainfrom
rfc-350
Feb 7, 2026
Merged

stash stacks#352
navicore merged 2 commits intomainfrom
rfc-350

Conversation

@navicore
Copy link
Copy Markdown
Owner

@navicore navicore commented Feb 7, 2026

⏺ All CI checks pass clean:

#350

  • Code formatting ✓
  • Clippy lints ✓ (all function_casts_as_integer fixed)
  • 376 tests passed, 0 failed
  • Integration tests ✓
  • Seq lint ✓ (117 files)

The function_casts_as_integer lint was indeed new in a recent Rust stable release — it required adding an intermediate as *const () cast before casting
function items to integer types. Fixed across 7 files in seq-runtime: scheduler.rs, signal.rs, terminal.rs, cond.rs, list_ops.rs, quotations.rs, and
weave.rs.

⏺ All CI checks pass clean:

  - Code formatting ✓
  - Clippy lints ✓ (all function_casts_as_integer fixed)
  - 376 tests passed, 0 failed
  - Integration tests ✓
  - Seq lint ✓ (117 files)

  The function_casts_as_integer lint was indeed new in a recent Rust stable release — it required adding an intermediate as *const () cast before casting
  function items to integer types. Fixed across 7 files in seq-runtime: scheduler.rs, signal.rs, terminal.rs, cond.rs, list_ops.rs, quotations.rs, and
  weave.rs.
@claude
Copy link
Copy Markdown

claude Bot commented Feb 7, 2026

Code Review: PR #352 - Stash Stacks (Aux Stack Operations)

Overview

This PR implements auxiliary stack operations (>aux and aux>) to provide word-local temporary storage. The implementation is comprehensive and well-structured, addressing typechecking, codegen, resource linting, and testing.

✅ Strengths

1. Comprehensive Implementation

  • Complete integration across all compiler phases: AST, builtins, typechecker, codegen, and resource linting
  • Proper tracking of aux stack state through control flow (if/else, match, quotations)
  • Good separation of concerns between compile-time tracking and runtime allocation

2. Type Safety

  • Aux stack is properly tracked in the typechecker with balance verification at word boundaries (crates/compiler/src/typechecker.rs:393-402)
  • Correct handling of branch divergence - only non-divergent branches are required to have matching aux stacks (crates/compiler/src/typechecker.rs:484-500)
  • Quotations properly isolate their aux stack from the enclosing word (crates/compiler/src/typechecker.rs:508-528)

3. Code Generation

  • Smart allocation strategy: allocates exactly the max depth needed per word (crates/compiler/src/codegen/words.rs:186-194)
  • Proper virtual stack spilling before aux access to avoid register conflicts (crates/compiler/src/codegen/inline/dispatch.rs:63, 105)
  • Aux stack pointer correctly saved/restored across branches (crates/compiler/src/codegen/words.rs:203-216)

4. Testing

  • Good unit test coverage in typechecker (6 tests covering basic operations, type preservation, error cases, multiple values)
  • Integration test file with practical examples (tests/integration/src/test-aux.seq)
  • Tests verify both positive and negative cases

5. Documentation

🔍 Code Quality Observations

1. Potential Optimization Opportunity
In crates/compiler/src/codegen/inline/dispatch.rs:70-98, the >aux operation loads from stack, then stores to aux slot. Consider whether this could be optimized in cases where the value is already in a register (though spilling may be necessary for correctness).

2. Resource Linting - Conservative Join
In crates/compiler/src/resource_lint.rs:277-289, the aux stack join takes the longer of the two stacks. The comment says "conservative to avoid false negatives", but this could potentially hide balance errors in complex control flow. This is acceptable for linting, but worth noting.

3. Edge Case: Nested Control Flow
The implementation handles if/else and match statements independently. While the tests cover basic cases, deeply nested combinations (match inside if inside match) aren't explicitly tested. The recursive nature of infer_statements should handle this correctly, but explicit testing would increase confidence.

4. Error Messages
Error messages are clear and helpful (e.g., crates/compiler/src/typechecker.rs:397-401 shows remaining aux stack). Consider adding the word name to quotation balance errors for easier debugging in files with many quotations.

🐛 Potential Issues

1. CRITICAL - Aux Stack Scope in Codegen Branches
In crates/compiler/src/codegen/words.rs:203-216, the aux_sp is saved and restored around branch emission. However, this assumes branches are emitted sequentially. If there's any code path where branches could be emitted in a different order or where the aux_sp state could leak between unrelated branches, this could cause mismatched aux slot access.

Recommendation: Add assertions or comments documenting the assumption that branches are always emitted in a controlled manner with proper state restoration.

2. Quotation Aux Stack Isolation
The typechecker properly isolates quotation aux stacks (crates/compiler/src/typechecker.rs:508-528), but I don't see corresponding isolation in the codegen. Each word gets its own aux slots, but since quotations are inlined into their enclosing word during compilation, they share the same aux slots. This could work correctly if quotation bodies are type-checked independently, but verify that:

  • Quotations that use aux don't accidentally interfere with the enclosing word's aux usage
  • The aux_max_depths tracking accounts for quotation bodies

3. Integer Underflow Risk
In crates/compiler/src/codegen/inline/dispatch.rs:108, self.current_aux_sp -= 1 could underflow if called incorrectly. While the typechecker should prevent this, consider adding a debug assertion or comment noting that typechecker guarantees prevent underflow.

🔒 Security Considerations

1. Stack Bounds
The aux stack is implemented as fixed-size allocas in LLVM. While the typechecker calculates max depth, there's no runtime bounds checking. If there's any bug in the max depth calculation or if future compiler optimizations bypass type checking, this could lead to buffer overflows.

Recommendation: Consider adding debug assertions in unsafe builds or documenting that the typechecker provides the safety guarantee.

2. Memory Safety
The implementation uses raw pointer arithmetic in LLVM IR generation. The code looks correct, but ensure that:

  • Negative offsets (crates/compiler/src/codegen/inline/dispatch.rs:74, 96) are always within bounds
  • The spilling of virtual stack before aux access (lines 63, 105) correctly handles all register allocation scenarios

⚡ Performance Considerations

1. Alloca Cost
Each word that uses aux allocates slots upfront (crates/compiler/src/codegen/words.rs:186-194), even if the aux operations are in a rarely-taken branch. This is correct for simplicity, but could be optimized in hot paths by lazy allocation.

Assessment: Acceptable tradeoff - allocas are cheap, and the simplicity is worth it.

2. Spill on Every Aux Operation
Both >aux and aux> spill the virtual stack (crates/compiler/src/codegen/inline/dispatch.rs:63, 105). This is necessary for correctness but could be expensive in tight loops.

Assessment: Correct choice - safety over premature optimization. Future optimization could be selective spilling if profiling shows this as a bottleneck.

📋 Test Coverage Gaps

  1. No test for aux in match expressions - The typechecker has match-specific aux handling (crates/compiler/src/typechecker.rs:411-453), but I don't see a test covering this
  2. No test for aux in recursive functions - Does aux work correctly in tail-recursive scenarios?
  3. No test for maximum depth tracking accuracy - Only test_aux_max_depths_tracked verifies depth=1; test with deeper nesting
  4. Integration test doesn't verify resource linting - The resource_lint.rs has aux support but no test file

📝 Minor Code Style Issues

1. Consistent Formatting
The code follows the project's style guidelines well. No major issues.

2. Issue References
Good consistent use of (Issue #350) comments throughout. All references are accurate.

3. Naming

  • current_aux_sp is clear (sp = stack pointer)
  • >aux and aux> follow stack language conventions
  • Consider documenting why "aux" vs "stash" (PR title says "stash stacks")

🎯 Recommendations

High Priority:

  1. Verify quotation aux stack isolation in codegen (see issue ✅ Runtime Functions Ready for Compiler #2 above)
  2. Add test for aux in match expressions
  3. Add test for aux in recursive functions
  4. Document the branch emission order assumption

Medium Priority:
5. Consider debug assertions for aux_sp underflow
6. Add integration test that triggers resource lint checks
7. Improve quotation error messages with word context

Low Priority:
8. Document the performance tradeoffs in inline comments
9. Add a test with deeply nested control flow (match in if in match)

✅ Final Assessment

This is a high-quality PR that implements a non-trivial feature correctly and comprehensively. The implementation demonstrates:

  • Strong understanding of the compiler architecture
  • Attention to type safety and correctness
  • Good testing practices
  • Clear documentation

The potential issues raised are mostly defensive suggestions rather than actual bugs. The critical item about quotation aux stack isolation should be verified, but the typechecker isolation likely provides the necessary guarantee.

Recommendation: Approve with minor suggestions. The CI passes confirm the implementation works correctly. Address the quotation codegen question and consider adding the suggested tests in a follow-up if desired.

Great work! 🎉

…ngs led to:

  Bug fixed — aux in quotations:
  - Quotations are compiled as separate LLVM functions, so they don't have access to the enclosing word's aux alloca slots. The typechecker was allowing
  >aux/aux> inside quotations, which would have generated invalid LLVM IR.
  - Fix: Reject aux ops in quotations at the typechecker level with a clear error message directing users to extract the body into a named word. Added
  in_quotation_scope flag.
  - Defense in depth: codegen_quotation() now saves/restores current_aux_slots and current_aux_sp so even if the typechecker check were bypassed, codegen
  wouldn't reference stale alloca names.

  Debug assertion added:
  - aux> codegen now has debug_assert!(self.current_aux_sp > 0) to catch underflow if the typechecker ever has a bug.

  New tests (3):
  - test_aux_in_match_balanced — aux used symmetrically across match arms ✓
  - test_aux_in_match_unbalanced_error — different aux states in match arms caught ✓
  - test_aux_in_quotation_rejected — clear error for aux inside quotations ✓
@claude
Copy link
Copy Markdown

claude Bot commented Feb 7, 2026

PR Review: Stash Stacks (Aux Stack Implementation)

Summary

This PR implements auxiliary stack operations (>aux and aux>) for word-local temporary storage, addressing issue #350. The implementation includes comprehensive type checking, codegen support, and integration tests. All 376 tests pass and CI checks are clean.

✅ Strengths

Excellent Type Safety

  • Balanced aux stack verification: The typechecker enforces that every >aux must be paired with a corresponding aux> before word return (typechecker.rs:445-454)
  • Branch consistency checking: Match arms and if/else branches are required to leave the aux stack in the same state (typechecker.rs:486-501, 536-552)
  • Quotation safety: Aux operations correctly rejected inside quotations since they compile as separate LLVM functions without aux slot allocas (typechecker.rs:615-620, 641-650)
  • Defense in depth: Added debug_assert! in codegen (dispatch.rs:109-112) to catch underflow even if typechecker has a bug

Well-Designed Architecture

  • Clean separation: Aux stack tracked separately from main stack during type checking
  • Efficient codegen: Pre-allocates exact number of aux slots per word based on typechecker analysis (words.rs:191-200)
  • Proper scope handling: Aux state correctly saved/restored across quotations (words.rs:208-229) and branches (words.rs:251-265)
  • Resource tracking integration: Aux stack properly integrated into resource lint analyzer (resource_lint.rs:307-373)

Comprehensive Testing

  • Unit tests: 9 new unit tests covering basic operations, type preservation, unbalanced stacks, empty stack errors, match arms, and quotations (typechecker.rs:677-1009)
  • Integration tests: Well-documented integration test with 6 test cases covering LIFO behavior, mixed types, if/else, nested calls, and interaction with stack ops (test-aux.seq)
  • All existing tests pass: No regressions in the 376-test suite

Code Quality

  • Clear error messages: User-friendly errors with actionable guidance (e.g., "Extract the quotation body into a named word if you need aux")
  • Consistent documentation: Issue RFC: Auxiliary stash stacks for complex stack juggling #350 referenced in comments throughout
  • Good naming: current_aux_stack, aux_max_depths, in_quotation_scope are all clear and descriptive

@claude
Copy link
Copy Markdown

claude Bot commented Feb 7, 2026

🔍 Observations & Minor Concerns

1. Clippy Lint Fixes (function_casts_as_integer)

The PR description mentions fixing the function_casts_as_integer lint across 7 runtime files. These changes look correct - adding intermediate as *const () cast before converting function pointers to integers is the proper fix for the new Rust lint.

Verification: Runtime files (scheduler.rs:1130-1133, signal.rs:1147, etc.) correctly use f as *const () as usize - all changes appear mechanical and correct ✓

2. Resource Lint Conservative Join

In resource_lint.rs:325-330, the aux stack join operation takes the longer aux stack to avoid false negatives. This seems overly conservative - if two branches have different aux stack depths at a join point, shouldnt that be an error? The typechecker already enforces balanced aux stacks across branches, so in practice they should always be the same length at join points.

Impact: Low - this is defensive programming that wont cause bugs, just might miss some edge case lints

3. Debug Assertion in Codegen

dispatch.rs:109-112 has a debug_assert! to catch aux stack underflow. In release builds, this becomes a no-op, so if the typechecker has a bug, we could generate invalid LLVM IR. Consider whether this should be a regular assert! or if we should return a codegen error instead.

Mitigation: The typechecker is thoroughly tested, so this is unlikely to occur in practice.

4. Test Coverage Gap

The integration tests (test-aux.seq) dont include test cases for the error conditions (unbalanced aux stack, empty stack, quotations, mismatched branches). These ARE tested in unit tests (typechecker.rs), so coverage exists, just not at the integration level.

5. Documentation

The builtin docs (builtins.rs:41-47) are concise and clear. Consider whether users might benefit from additional documentation about performance characteristics, when to use aux vs. alternatives, and the word-local scope limitation.

Impact: Low - the current docs are adequate for basic usage

@claude
Copy link
Copy Markdown

claude Bot commented Feb 7, 2026

🔒 Security Considerations

No security concerns identified:

  • Aux operations are purely stack-local with no heap allocation
  • Bounds checking handled by LLVM stack allocas
  • No unsafe code introduced beyond existing signal handler casts
  • Type safety prevents misuse via static analysis

⚡ Performance Considerations

Positive:

  • Aux slots are stack-allocated (%Value alloca), so no heap overhead
  • Slot count pre-computed by typechecker, so no runtime overhead for tracking
  • More efficient than alternatives like excessive dup/swap chains or named temporary words

Neutral:

  • Each word using aux pays a small cost for alloca instructions
  • Load/store instructions for aux access are cheap (L1 cache)

🧪 Test Coverage Assessment

Excellent overall coverage:

  • ✅ Type checking: 9 unit tests covering all error paths
  • ✅ Integration: 6 test cases covering real usage patterns
  • ✅ Regression: All 376 existing tests pass
  • ✅ CI: All checks passing (formatting, clippy, seq lint)

Could add (nice-to-have):

  • Performance benchmark comparing aux vs. stack manipulation
  • Stress test with deep aux stack (e.g., >20 values)

@claude
Copy link
Copy Markdown

claude Bot commented Feb 7, 2026

📋 Recommendations

Required: None

All critical concerns are already addressed. The implementation is solid.

Optional Improvements:

  1. Consider regular assert! instead of debug_assert! in dispatch.rs:109 for aux> underflow check, or return a codegen error
  2. Document the conservative join strategy in resource_lint.rs:325 with a comment explaining why we take the longer stack
  3. Add a comment in codegen explaining why aux slots are %Value allocas (to help future maintainers understand the memory model)

✨ Overall Assessment

Strong approval with minor suggestions

This is high-quality work that demonstrates:

  • Thorough understanding of the compiler architecture
  • Excellent test discipline
  • Careful attention to edge cases
  • Clear, maintainable code

The aux stack feature is well-designed, properly scoped (word-local), and integrates cleanly with existing systems (typechecker, codegen, resource lint). The "defense in depth" approach (typechecker + codegen assertions + saved/restored state) shows good engineering judgment.

Recommendation: Approve and merge after considering the optional improvements above.


Great work on this feature! The test coverage and error handling are particularly impressive. 🎉

@navicore navicore merged commit 29df57d into main Feb 7, 2026
2 checks passed
@navicore navicore deleted the rfc-350 branch February 7, 2026 18:00
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant