Skip to content

analysis: reference analysis for squin qubits and jeff wires - #865

Merged
nathanieltornow merged 1 commit into
QuEraComputing:feat/jefffrom
nathanieltornow:feat/jeff-reference
Sep 28, 2026
Merged

nathanieltornow merged 1 commit into
QuEraComputing:feat/jefffrom
nathanieltornow:feat/jeff-reference

Conversation

@nathanieltornow

Copy link
Copy Markdown
Collaborator

This PR adds a forward analysis that states which allocation or parameter each value refers to.

Design

The analysis is generic and lives in bloqade/analysis/reference/, like the address analysis. A subclass implements: kind(type) says whether a type is one tracked item (Whole), a register of items (Register) or untracked, and register_length(root, call) gives the static length of a register. QubitReferenceAnalysis in bloqade/squin/analysis/reference.py tracks squin qubits, and WireReferenceAnalysis in bloqade/jeff/analysis/wire.py tracks jeff wires.

Example

@squin.kernel
def flip(q: Qubit) -> Qubit:
    squin.x(q)
    return q

@squin.kernel
def fresh() -> Qubit:
    return squin.qubit.new()

@squin.kernel
def main(data: ilist.IList[Qubit, Any], n: int, i: int):
    fixed = squin.qalloc(3)
    dynamic = squin.qalloc(n)
    a = fresh()
    b = fresh()
    c = flip(a)
    squin.cx(fixed[-1], dynamic[i])
    squin.cx(data[0], b)
    squin.h(c)

analysis = QubitReferenceAnalysis(main.dialects)
frame, _ = analysis.run(main)
main.print(analysis=frame.entries)
%data    --> Register(%data)
%fixed   = qalloc(%0)            # ---> Register(%fixed = qalloc()[0])
%dynamic = qalloc(%n)            # ---> Register(%dynamic = qalloc()[0])
%a       = fresh()               # ---> Whole(%a = fresh()[0])
%b       = fresh()               # ---> Whole(%b = fresh()[0])
%c       = flip(%a)              # ---> Whole(%a = fresh()[0])
%2       = getitem(%fixed, -1)   # ---> Slot(%fixed = qalloc()[0], 2)
%3       = getitem(%dynamic, %i) # ---> Slot(%dynamic = qalloc()[0], %i)
%6       = getitem(%data, 0)     # ---> Slot(%data, 0)

Limits

The result of a recursive call, a slice of a register, an index into a list at a runtime index, and a root that a callee allocates without returning it are Unknown.

Also in this PR

  • bloqade/constants.py::constant_int reads an integer out of any statement with kirin's ConstantLike trait. It replaces bloqade/jeff/constants.py::const_int.
  • jeff Call declares a StaticCall trait.

@codecov

codecov Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

@nathanieltornow
nathanieltornow force-pushed the feat/jeff-reference branch 5 times, most recently from aa84617 to 2243f6c Compare September 24, 2026 12:34

@david-pl david-pl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Neat PR, just some small issues with Bottom types and I'm not sure what happens when you nest return values. Otherwise looks good, so I'm approving to unblock merging, but it might make sense to address some things first.

Comment on lines +34 to +37
while node is not None:
if node is code:
return True
node = node.parent_stmt

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there some walking logic in kirin that can already do this?

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.

yes, replaced


keys = (KEY, "absint")
lattice = Ref
max_depth: int = field(default=64, kw_only=True)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What's the reason behind this lower default? InterpreterABC has default=800.

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.

it was because of the issue of the C-stack overflowing (QuEraComputing/kirin#743)

now, i removed the line and skip the tests that have recursive structure.

returned: dict[Root, Root] = {}
outputs = result.refs if isinstance(result, Positions) else (result,)
for position, ref in enumerate(outputs):
if isinstance(ref, (Whole, Register)) and _inside(ref.root, code):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What happens if there is a nested return, e.g. return ((q, q), 1)?

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.

We now explicitly reject nested nodes as returns


def kind(self, type_: types.TypeAttribute) -> type[Whole] | type[Register] | None:
"""Return `Whole` for a wire, `Register` for a register, else None."""
if type_.is_subseteq(QuregType):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Doesn't play nice when type_ is Bottom.

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.

fixed

Comment on lines +31 to +34
if type_.is_subseteq(ilist.IListType[QubitType, types.Any]):
return Register
if type_.is_subseteq(QubitType):
return Whole

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Another if where Bottom would lead to the wrong behavior.

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.

fixed

@nathanieltornow
nathanieltornow merged commit 6275ba3 into QuEraComputing:feat/jeff Sep 28, 2026
8 of 9 checks passed
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