You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Introducing a ComptimeInterpretor to replace the escaping-comptime-writes approach
The earlier 706/escaping-comptime-writes branch established valuable regression coverage and a provenance model for mutable references, but we stopped extending its architecture.
Why we abandoned that approach
That branch evolved two separate mechanisms:
A concrete comptime evaluator that decided values on known paths.
A provenance/effect traversal that tried to find possible escaping writes on unknown paths.
The problem was structural, not merely that the implementation was large.
Both mechanisms had to independently understand every child-bearing AST owner: expressions, statements, topology operands, aggregate fields, index expressions, calls, closures, lowered closure environments, and so on. If either traversal missed a child, the compiler could silently fold away a write. The topology-index bug was a concrete example: one traversal visited the relevant child while the other did not.
This created several long-term problems:
Every new syntax form required synchronized changes in two semantic walkers.
The walkers could disagree about evaluation order, reachability, control flow, and whether an operation was supported.
Provenance-only analysis could not faithfully reproduce behavior that depends on concrete evaluation, such as function targets, aliases, closure environments, reassignment, and branch-local values.
Fixes tended to add another special case or another conservative fallback. That risked either missing a write or rejecting valid comptime programs.
The design made review difficult: proving that a new owner was safe required auditing two independently evolving implementations.
The earlier branch is still useful as behavioral evidence: its fixtures and provenance cases were retained. What was abandoned was the two-walker architecture, not the regressions or safety requirements.
Current design
The current design uses one ComptimeInterpreter as the source of truth for comptime folding.
It combines:
concrete evaluation on known paths;
abstract traversal on unknown paths;
lexical value/provenance facts;
escaping-write detection;
control-flow tracking; and
supported/refused evaluation status.
A comptime block folds only when the interpreter proves that its result is concrete, supported, and free of possible escaping writes. Unsupported or insufficiently modelled constructs fail closed rather than being assumed pure.
The old fold-specific evaluator, name-based escaping-write scan, transition comparator, and temporary parity allowances have been removed. Ordinary eval_expr remains for assertion checking during type checking, but is not a fallback for comptime folding.
Why this is a better foundation
The main benefit is that effects and values now follow the same expression ownership and child-evaluation rules.
A known path can produce a concrete value. An unknown path visits the same owners conservatively and merges possible provenance/effects. This prevents the previous class of bugs where the evaluator and effect analysis disagreed about what a construct evaluates.
The interpreter dispatch is intentionally exhaustive over Expr, Statement, and child-bearing Topology variants. Adding a new AST variant requires an explicit interpreter decision at compile time; there is no wildcard arm that can accidentally treat new syntax as harmless.
This gives us a maintainable extension rule:
Add an explicit interpreter arm for the new syntax.
Define its child evaluation order and control-flow behavior.
Choose either concrete comptime semantics or an explicit live-refusal policy.
Add direct escaping-write, unknown-control-flow, and local-only regressions where applicable.
Keep the new behavior covered by the normal compiler test suite.
If a future feature needs a new representable comptime result, the value model can be extended deliberately. If it has no sound compile-time semantics yet, it remains explicitly refused until such semantics exist.
Inspiration
This is not a direct port of Rust CTFE or Miri, but it follows a similar architectural principle: use one interpreter as the semantic source of truth instead of trying to keep a separate effect analysis synchronized with evaluation.
Rust CTFE evaluates a supported subset of Rust during compilation. Miri interprets Rust’s compiler IR and uses execution semantics to detect invalid behavior. Vx’s comptime interpreter is much narrower: it does not model Rust’s memory model or undefined behavior. But, like an interpreter-based design, it evaluates supported operations directly and refuses cases it cannot justify.
It also uses abstract-interpretation ideas for unknown control flow: abstract values retain provenance, both possible branches are traversed conservatively, and their facts are merged. That combination lets the same component answer both “what value does this block produce?” and “could evaluating it write outside the block?”
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Introducing a
ComptimeInterpretorto replace the escaping-comptime-writes approachThe earlier
706/escaping-comptime-writesbranch established valuable regression coverage and a provenance model for mutable references, but we stopped extending its architecture.Why we abandoned that approach
That branch evolved two separate mechanisms:
The problem was structural, not merely that the implementation was large.
Both mechanisms had to independently understand every child-bearing AST owner: expressions, statements, topology operands, aggregate fields, index expressions, calls, closures, lowered closure environments, and so on. If either traversal missed a child, the compiler could silently fold away a write. The topology-index bug was a concrete example: one traversal visited the relevant child while the other did not.
This created several long-term problems:
The earlier branch is still useful as behavioral evidence: its fixtures and provenance cases were retained. What was abandoned was the two-walker architecture, not the regressions or safety requirements.
Current design
The current design uses one
ComptimeInterpreteras the source of truth for comptime folding.It combines:
A comptime block folds only when the interpreter proves that its result is concrete, supported, and free of possible escaping writes. Unsupported or insufficiently modelled constructs fail closed rather than being assumed pure.
The old fold-specific evaluator, name-based escaping-write scan, transition comparator, and temporary parity allowances have been removed. Ordinary
eval_exprremains for assertion checking during type checking, but is not a fallback for comptime folding.Why this is a better foundation
The main benefit is that effects and values now follow the same expression ownership and child-evaluation rules.
A known path can produce a concrete value. An unknown path visits the same owners conservatively and merges possible provenance/effects. This prevents the previous class of bugs where the evaluator and effect analysis disagreed about what a construct evaluates.
The interpreter dispatch is intentionally exhaustive over
Expr,Statement, and child-bearingTopologyvariants. Adding a new AST variant requires an explicit interpreter decision at compile time; there is no wildcard arm that can accidentally treat new syntax as harmless.This gives us a maintainable extension rule:
If a future feature needs a new representable comptime result, the value model can be extended deliberately. If it has no sound compile-time semantics yet, it remains explicitly refused until such semantics exist.
Inspiration
This is not a direct port of Rust CTFE or Miri, but it follows a similar architectural principle: use one interpreter as the semantic source of truth instead of trying to keep a separate effect analysis synchronized with evaluation.
Rust CTFE evaluates a supported subset of Rust during compilation. Miri interprets Rust’s compiler IR and uses execution semantics to detect invalid behavior. Vx’s comptime interpreter is much narrower: it does not model Rust’s memory model or undefined behavior. But, like an interpreter-based design, it evaluates supported operations directly and refuses cases it cannot justify.
It also uses abstract-interpretation ideas for unknown control flow: abstract values retain provenance, both possible branches are traversed conservatively, and their facts are merged. That combination lets the same component answer both “what value does this block produce?” and “could evaluating it write outside the block?”
All reactions