-
-
Notifications
You must be signed in to change notification settings - Fork 42
✨ Add MQTRef-QIR conversions #1091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
✨ Add MQTRef-QIR conversions #1091
Conversation
@burgholzer sorry for pinging you once again. I incorporated some feedback already to make the conversion more compliant to the base profile of QIR instead of the reference files but I am still working on it. The main changes for now where the following:
This transforms the following program:
To this:
And finally once converted to .ll :
As you can see the mainly the conversion is not entirely correct, mainly because of the awkward measure conversion. I am not exactly sure what the best way to convert it is. For the gate operations I still have to flip the order of arguments as I saw that the control qubits come first and then the target qubits. But is the order within the control qubits still the same? So
would be
Lastly, the base profile requires that some module flags with metadata should be set. |
No worries. Feel free to ping me anytime. You are correct, that the base profile mandates the use of As for the controls and targets: yes, most languages put controls first and targets afterwards. The order of the controls does not make a difference (it's a set conceptually). So you might as well use the same ordering as in the MLIR input. As for the metadata annotations: that seems unfortunate. Just to be sure: this only affects the way going from QIR to MLIR, right? Can we work around that somehow? Maybe by using some concept that is not mandated by QIR, but that would allow us to perform the back-transformation? What are these flags again? Can you point me towards the respective places in the spec? |
https://github.com/qir-alliance/qir-spec/blob/main/specification/under_development/profiles/Base_Profile.md#module-flags-metadata Here is the link to the module flag section. From what I have seen, MLIR does not support llvm.module flags until LLVM 22. For now I think the best way is to add this information to the attributes of the entry function. So would you recommend to convert a So something like this
Should look like this once it is converted:
where both the %Qubit* and %Result* type are just of llvm.ptr type. |
yeah. this seems to have landed in llvm/llvm-project#130679 only in March 2025, which is pretty recent.
Yeah. that seems like a reasonable assumption. |
I think I addressed most of the previous issues for now. Also, there were some questions that I havent answered yet, mainly because I was unsure of the answer. The main question is of course if this conversion produces valid QIR code.
I think it would good if during the lowering process from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @li-mingbao 👋🏼
Thanks again for yet another iteration on the code! Really nice.
I went through the conversions in very much detail again and left further inline comments.
I'll let @ystade do the next review here. Until then, it would be great if you could explicitly answer on the comments themselves that are open from the last review and this review. Much appreciated 🙏🏼
// create name and signature of the new function | ||
const StringRef fnName = "__quantum__rt__qubit_allocate_array"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Truly a micro-optimization, but this string could be turned into a static member of the pattern struct so that it is not part of every matchAndRewrite
call.
Same goes for the signature below and other similar functions throughout this file.
// create the new callOp | ||
const auto elemPtr = | ||
rewriter | ||
.create<LLVM::CallOp>(op.getLoc(), fnDecl, | ||
ValueRange{adaptor.getInQreg(), index}) | ||
.getResult(); | ||
|
||
// replace the old operation with a loadOp | ||
rewriter.replaceOpWithNewOp<LLVM::LoadOp>( | ||
op, LLVM::LLVMPointerType::get(ctx), elemPtr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am still not 100% sure if this load
is necessary. I'll leave that for @ystade to comment on when he is back from vacation.
attributes.emplace_back( | ||
builder.getStrArrayAttr({"dynamic_result_management", "false"})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be using the flag from the state
shouldn't it?
// replace the int to ptr operation with static qubit | ||
const auto newOp = rewriter.replaceOpWithNewOp<ref::QubitOp>( | ||
op, ref::QubitType::get(rewriter.getContext()), value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems wasteful. It will insert an operation for every single static qubit access.
In the ref dialect, we should be good if we simply define a certain static qubit once and then use that value onwards. Quite similarly to how you cache the pointer in the other conversion (if I understood that correctly).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isnt there only one intToPtr
operation in the beginning with a given index and this gets converted exactly once to get a static qubit? The result of this single static qubit conversion is then used for every access.
// get the new operands from the operandMap | ||
// workaround to avoid unrealized conversion | ||
SmallVector<Value> newOperands; | ||
newOperands.reserve(operands.size()); | ||
for (auto const& val : operands) { | ||
if (getState().operandMap.contains(val)) { | ||
newOperands.emplace_back(getState().operandMap.at(val)); | ||
} else { | ||
newOperands.emplace_back(val); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you maybe explain why exactly that workaround is required and what it is actually working around?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This solves the issue that the TypeConverter
did not work for the conversion as !llvm.ptr
type needs to be converted to either !mqtref.Qubit
or !mqtref.QubitRegister
depending on the operation. Since the type typeConverter does not have any information regarding operation but only the types, I had to track which !llvm.ptr
is converted to which operation in the map.
} | ||
|
||
// remove the prefix and the suffix of the gate name | ||
auto gateName(fnName->substr(16).drop_back(6)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we replace these magic numbers by constants with a usefull name?
Co-authored-by: Lukas Burgholzer <burgholzer@me.com> Signed-off-by: li-mingbao <74404929+li-mingbao@users.noreply.github.com>
Description
This PR introduces conversion passes between the
mqtdyn
dialect and theLLVM IR
that adheres to the QIR specifications for dynamic qubit allocations. Currently it mainly focuses on the conversion within MLIR between themqtdyn
dialect and thellvmlir
dialect and vice versa. Once the IR consists only of llvm dialect operations it can be convert to LLVM IR with the built-in passes from themlir-translate
module and back.Checklist: