Skip to content

Conversation

@johnzl-777
Copy link
Contributor

@johnzl-777 johnzl-777 commented Dec 4, 2025

This is my implementation of #540 , there is one thing that I've held off on after talking with @weinbe58 that's a bit tricky: handling measurements. My understanding is we don't need it for now but in the future it will be relevant.

While 95% of qasm2 maps very nicely to existing squin infrastructure (with the exception of some of the less common 2 and 3 qubit gates I had to skip - unless we want to do some implicit conversion for them) you run into problems trying to map the qasm2 measurement semantics.

Take the following examples:

  1. A user wants to measure out all the qubits to the entirety of the declared register. Your qasm2 looks something like this
qreg q[5]
creg c[5]
measure q -> c

You could potentially just have creg be an empty ilist and then when the measure happens produce the ilist of measurements - making sure that all subsequent references point to the new ilist. This seems acceptable but it gets tricky if a user wants to do something like

  1. a user only wants to measure a single qubit and save that to a slot in the classical register. This looks like
qreg q[5]
creg c[5]
measure q[0] -> c[0]

Now the classical register somehow has to already be get_item-able which is tricky considering there's no notion of a fixed-length list. Things get even trickier if a user wants to save at a different index:

measure q[1] -> c[3]

P.S. The sub kernel rewrite seems "off" to me, I'm sure there must be a better way to do it but I'm not aware of what the proper API for it is 😅

@johnzl-777 johnzl-777 linked an issue Dec 4, 2025 that may be closed by this pull request
@codecov
Copy link

codecov bot commented Dec 4, 2025

Codecov Report

❌ Patch coverage is 98.52217% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...c/bloqade/squin/passes/qasm2_gate_func_to_squin.py 86.95% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Contributor

github-actions bot commented Dec 4, 2025

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
10759 9494 88% 0% 🟢

New Files

File Coverage Status
src/bloqade/squin/passes/_init_.py 100% 🟢
src/bloqade/squin/passes/qasm2_gate_func_to_squin.py 87% 🟢
src/bloqade/squin/passes/qasm2_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/_init_.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/core_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/glob_parallel_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/id_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/noise_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/parametrized_uop_1q_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/uop_1q_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/uop_2q_to_squin.py 100% 🟢
TOTAL 99% 🟢

Modified Files

File Coverage Status
src/bloqade/qasm2/dialects/uop/stmts.py 100% 🟢
TOTAL 100% 🟢

updated for commit: 5f137de by action🐍

@johnzl-777 johnzl-777 marked this pull request as draft December 5, 2025 13:02
@johnzl-777 johnzl-777 marked this pull request as ready for review December 5, 2025 14:43
@johnzl-777
Copy link
Contributor Author

Used Callgraph pass after talking to Phil, I originally had a pretty funky rewrite rule to handle subkernels 😅

@johnzl-777
Copy link
Contributor Author

johnzl-777 commented Dec 5, 2025

Latest round of feedback (from Phil): use dictionary style mapping (faster than pattern matching) + can use pre-existing QASM2 to py expr rewriterule.

Should also try to avoid multi-layer matching logic, easier to reason through/test smaller rules.

Copy link
Collaborator

@david-pl david-pl left a comment

Choose a reason for hiding this comment

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

Overall looks good and the tests are quite extensive, which is great. I have some questions and small comments.

rewrite_result = (
IListDesugar(dialects=mt.dialects).unsafe_run(mt).join(rewrite_result)
)
TypeInfer(dialects=mt.dialects).unsafe_run(mt).join(rewrite_result)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure we should track the result of type inference. @weinbe58 didn't you recently fix a bug by removing a similar .join(result) in another pass?

else:
return RewriteResult()

new_stmt = func.Invoke(
Copy link
Collaborator

Choose a reason for hiding this comment

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

I feel like you could just do this within the if above, since the args have to match the node type anyway.


def rewrite_Statement(self, node: ir.Statement) -> RewriteResult:

if isinstance(node, glob.UGate):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Again, this seems a bit redundant: you might as well just assign squin_equivalent_stmt in each case here rather than using the map above.

Copy link
Contributor Author

@johnzl-777 johnzl-777 Dec 9, 2025

Choose a reason for hiding this comment

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

The dictionary was something that @weinbe58 recommended, I could be doing the pattern wrong here but in cases where I do see a dictionary used it only turns out nice if the attribute you're accessing exits across all the statements.

Like for arithmetic operation conversion, you'll always have an lhs and rhs attribute. Here the number and kinds of attributes change.

I actually realize if I wanted to be clever I could add some strings to the values in the dictionary and then __getattribute__ things which would resolve the clunkiness at the expense of making things a little uglier.

Wish I could still do pattern matching but I'm told the performance would take a hit

return RewriteResult()

squin_noise_stmt = NOISE_TO_SQUIN_MAP[type(node)]
invoke_stmt = func.Invoke(
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you know that you are going for the broadcast version, why not just rewrite to the statement directly instead of adding an invoke to the stdlib?

return RewriteResult()

new_stmt = func.Invoke(
callee=CORE_TO_SQUIN_MAP[type(node)],
Copy link
Collaborator

Choose a reason for hiding this comment

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

General question: Why do you rewrite to stdlib calls rather than statements?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In my head it felt nicer to default to the stdlib versions because the output to the user would be on par with what they would see if they constructed the squin kernel through the interface we provide. Figured it makes debugging a bit nicer.

That being said, I don't have a strong preference it has to be that way and if it ends up being the case we prefer the "unrolled"/flat form as the output I can easily do that (:

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see. It's just a bit of style preference, I suppose.

In my opinion, it's nice to fall back to the stdlib whenever you have stdlib functions but no matching statement in a dialect. For example, when rewriting from squin to native, there's a lot of statements in the more general squin dialect, but matching stdlib functions in native.

Here though we basically have matching statements in squin for all statements in qasm2. The only exceptions I see are probably U1 and U2.

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.

rewrite QASM2 dialect to SQUIN

3 participants