Skip to content

refactor(interpreter): invert frame composition ownership - #723

Open
zhenrongliew wants to merge 5 commits into
dl/generalize-bodyfrom
dl/interp-frame-composition
Open

zhenrongliew wants to merge 5 commits into
dl/generalize-bodyfrom
dl/interp-frame-composition

Conversation

@zhenrongliew

@zhenrongliew zhenrongliew commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #684. Make each interpreter engine's private stack enum own conversion from reusable member frames.

Problem:

Concrete member frames depended on the total enum that stores them. BlockFrame required F: FrameBuild<V, E> and re-wrapped itself with F::from_block(self), so the dependency ran:

BlockFrame -> FrameBuild -> total enum -> BlockFrame
The cause was in the frame protocol itself. Frame<I, F>'s three methods all returned FrameEffect<F, C> — over F, never over Self. A reusable frame was therefore obliged to construct the enclosing enum's variant for itself, which it could only do through a construction trait the enum had to implement. That forced a chain of scaffolding: a FrameBuild derive per engine family, a Build* construction trait per dialect frame.

Fix:

Split the parent from the child in FrameEffect:

pub enum FrameEffect<P, C, F = P> {
    Continue(P),                    // P = this frame's own next state
    Push { parent: P, child: F },   // F = configured representation of a child
    Done,
    Complete(C),
}

pub trait Frame<I: FrameEngine, F = Self> {
    fn step_into(self, interp: &mut I) -> Result<FrameEffect<Self, Self::Completion, F>, I::Error>;
    // ...
}

A member continuation now returns itself for Continue and as the suspended parent of Push. A private closed stack-item enum is the composition root: it owns the From<Member> conversions, dispatches exhaustively, and lifts member state into its own variant with the new FrameEffect::map_next.

Make each composition root own conversion of reusable member frames into its private stack-item enum.

Remove FrameBuild, StandardFrame, UnGraphEntry, the FrameBuild derive, and the concrete SCF injection traits. Replace member-side factory calls with narrow From<Member> conversions while keeping callable-body walker selection in CallBodyTraversal and DefaultCallBodyTraversal.

This reverses the dependency from member to factory to total enum to member, without changing concrete execution semantics.
…engines

Extend the concrete ownership rule to sparse-forward, sparse-backward, dense-backward, SCF, and the shared fixpoint adapters.

Each private stack-item enum now owns its From<Member> conversions and wraps member effects at the dispatch boundary. Reusable frames no longer name or construct the total enum that contains them.

The fixpoint and liveness changes are mechanical adaptations to the corrected Frame and FrameEffect contracts; their analysis equations are unchanged.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason as to why the snapshot tests are no longer needed for this macro?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, the deleted snapshot covered the old FrameBuild, AbstractFrameBuild, and DenseFrameBuild. I've not added new snapshots for the new frame.rs.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a snapshot covering member frame conversions, the new [derive(Frame)] macro. 6419a99

Comment thread example/toy-lang/src/interpreter/tests.rs

This branch has not been deployed

No deployments
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.

2 participants