munich-quantum-toolkit / munich-quantum-toolkit/core

✨ Support quantum-register function arguments in QC and QCO

Open
#2,428 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feature MLIR
Dominant language
C++
Stars
133
Forks
73
Avg merge
17h 34m
Merged PRs (30d)
261

Description

🤖 AI text below 🤖

Problem Statement

QC and QCO already represent qubit registers, but their reusable-function paths do not share an ownership contract:

  • QCOProgramBuilder::createFunction tracks QTensor arguments, while call rejects every quantum-tensor operand.
  • QC→QCO accepts only directly allocated qubit memrefs and rejects function register arguments.
  • QCO→QC converts qubit tensors to memrefs but removes pass-through results only for scalar-qubit arguments.
  • Hand-written invalid IR can reorder same-typed register results unless a shared provenance check owns the invariant.

Frontend-specific workarounds would duplicate this logic and can reconnect the wrong linear wires.

Proposed Solution

Add one function-call contract for quantum arguments.

Types and ownership
  • QC register arguments are borrowed rank-one memref<Nx!qc.qubit> or memref<?x!qc.qubit> values. Require the default memory space and identity layout.
  • QCO register arguments are linear rank-one tensor<Nx!qco.qubit> or tensor<?x!qco.qubit> values.
  • Use ordinary func.call for every register-taking function. Such functions must not carry mqt.unitary and must not use qc.call or qco.call.
  • A QCO function returns ordinary results first. It then returns one pass-through quantum result for each scalar-qubit or qubit-register argument, in original argument order and with the matching type.
  • QC does not return borrowed quantum arguments. QCO→QC removes the matching suffix. Newly allocated quantum results before the suffix remain owned results.
Supported provenance
  • Accept whole registers produced by a direct allocation or received as an enclosing function argument.
  • Reject unranked registers, non-identity layouts, non-default memory spaces, views, subviews, casts, and other aliases whose identity is not proved.
  • Reject a repeated register and a call that passes both a whole register and a scalar qubit loaded from it.
  • Initially require direct calls to complete, non-recursive, single-outer-block definitions. Reject declarations and unresolved callees.

Implementation direction

  • Extend FunctionUtils with tensor-argument provenance beside traceQubitArgument. Reuse TensorIterator and cross nested func.call only through the positional contract. Detect cycles and fail closed.
  • Extend QCOProgramBuilder to consume and rebind tensor operands. Reinsert live extracted elements before a call and transfer each register identity to its matching result.
  • Extend QC→QCO register collection to seed function arguments with register identities, pass the current tensor state at calls, and append updated tensors at returns.
  • Extend QCO→QC validation to check and remove the complete scalar/register pass-through suffix.
  • Keep verifyLinearity as the owner of one-use checking. Do not add a second linearity framework or frontend-specific call mappings.

Likely implementation areas:

  • mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp
  • mlir/include/mlir/Dialect/QCO/Utils/FunctionUtils.h
  • mlir/lib/Dialect/QCO/Utils/FunctionUtils.cpp
  • mlir/lib/Conversion/QCToQCO/QCToQCO.cpp
  • mlir/lib/Conversion/QCOToQC/QCOToQC.cpp
  • Corresponding builder, utility, and conversion tests

Acceptance criteria

  • QC→QCO→QC succeeds for static and dynamic rank-one register arguments.
  • Tests cover two registers, mixed scalar-qubit/register arguments, nested calls, classical results, and a newly allocated register result.
  • Negative tests cover repeated or overlapping operands, missing or reordered pass-through results, declarations, recursion, multi-block definitions, unranked/view operands, and attributes on erased pass-through results.
  • Every successful QCO result passes qco::verifyLinearity.
  • Builder tests prove consumption and rebinding: an input tensor becomes invalid at the call and only its matching result remains live.
  • A non-inlined QCO helper round trips through QCO→jeff→serialization→jeff→QCO with both static and dynamic qureg signatures and calls.
  • Valid input never reaches an assertion or crash; unsupported input receives a targeted diagnostic.

Dependencies

Parent feature: #2427

This is the foundational pull request. The OpenQASM and QIR sub-issues depend on it.

Non-goals

Register modifiers; indirect calls; external register declarations; recursive call graphs; arbitrary memref layouts, memory spaces, slices, or aliases; target-specific lowering beyond the jeff regression.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with FunctionUtils.h/.cpp and the existing traceQubitArgument logic, then inspect QCOProgramBuilder.cpp, QCToQCO.cpp, and QCOToQC.cpp. Run the corresponding builder, utility, and conversion tests to understand current call and ownership behavior. Done means supported static and dynamic register arguments round-trip with linearity preserved, while unsupported provenance and call shapes receive targeted diagnostics without crashes.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.