Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion samples/testing/test_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pytest
expecttest==0.3.0
pyqir>=0.12.3,<0.13
pyqir>=0.12.5,<0.13
cirq==1.6.1; platform_system != 'Windows' or platform_machine == 'AMD64'
pandas>=2.1
2 changes: 1 addition & 1 deletion source/qdk_package/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description = "Quantum Development Kit Python Package"
readme = "README.md"
authors = [ { name = "Microsoft" } ]
requires-python = ">=3.10"
dependencies = ["pyqir>=0.12.3,<0.13", "qsharp==0.0.0"]
dependencies = ["pyqir>=0.12.5,<0.13", "qsharp==0.0.0"]
classifiers = [
"License :: OSI Approved :: MIT License",
"Development Status :: 5 - Production/Stable",
Expand Down
6 changes: 6 additions & 0 deletions source/qdk_package/qdk/_adaptive_bytecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
OP_MOV = 0x52
OP_CONST = 0x53

# ── Memory Operations ────────────────────────────────────────────────────────
OP_ALLOCA = 0x60
OP_LOAD = 0x61
OP_STORE = 0x62
OP_GEP = 0x63 # https://llvm.org/docs/LangRef.html#getelementptr-instruction

# ── ICmp condition codes (sub-opcode, placed in bits[15:8] via << 8) ─────────
# Reference: https://llvm.org/docs/LangRef.html#icmp-instruction
ICMP_EQ = 0
Expand Down
355 changes: 351 additions & 4 deletions source/qdk_package/qdk/_adaptive_pass.py

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions source/qdk_package/src/qir_simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -819,13 +819,16 @@ where

let mut call_args = extract_key::<Vec<Word>>(dict, "call_args")?;

let mut constant_data = extract_key::<Vec<Word>>(dict, "constant_data")?;

// WebGPU requires that arrays have at least one element,
// so, we push a dummy element on each of these arrays if they are empty.
push_default_if_empty(&mut block_table);
push_default_if_empty(&mut function_table);
push_default_if_empty(&mut phi_entries);
push_default_if_empty(&mut switch_cases);
push_default_if_empty(&mut call_args);
push_default_if_empty(&mut constant_data);

Ok(AdaptiveProgram {
instructions,
Expand All @@ -835,6 +838,7 @@ where
phi_entries,
switch_cases,
call_args,
constant_data,
num_qubits,
num_results,
num_registers,
Expand Down
2 changes: 1 addition & 1 deletion source/qdk_package/test_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pytest
expecttest==0.3.0
pyqir>=0.12.3,<0.13
pyqir>=0.12.5,<0.13
cirq==1.6.1; platform_system != 'Windows' or platform_machine == 'AMD64'
pandas>=2.1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pytest==9.0.3
qirrunner==0.9.0
pyqir>=0.12.3,<0.13
pyqir>=0.12.5,<0.13
qiskit-aer==0.17.2
qiskit_qasm3_import==0.6.0
expecttest==0.3.0
Expand Down
207 changes: 207 additions & 0 deletions source/qdk_package/tests/test_adaptive_cpu_bytecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""

from collections import Counter
import pyqir
import pytest
from qdk.simulation import run_qir, NoiseConfig
from qdk.simulation._simulation import Result
Expand Down Expand Up @@ -1693,6 +1694,212 @@ def test_sitofp_negative(sim_type):
check_arith_result(SITOFP_NEG_QIR, "1", sim_type=sim_type)


# #########################################################################
# Memory Operations (OP_ALLOCA, OP_LOAD, OP_STORE, OP_GEP)
# #########################################################################


# =========================================================================
# OP_ALLOCA + OP_STORE + OP_LOAD — scalar alloca/store/load
# =========================================================================

ALLOCA_STORE_LOAD_SCALAR_QIR = """
entry:
%ptr = alloca i64
store i64 1, i64* %ptr
%val = load i64, i64* %ptr
%flag = icmp eq i64 %val, 1
br i1 %flag, label %then, label %end
then:
call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
br label %end
end:
call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
"""


@pytest.mark.parametrize("sim_type", SIM_TYPES)
def test_alloca_store_load_scalar(sim_type):
"""Alloca an i64, store 1, load it back → value is 1 → X applied → measure 1."""
check_result(ALLOCA_STORE_LOAD_SCALAR_QIR, "1", sim_type=sim_type)


# =========================================================================
# OP_STORE — overwrite previous value
# =========================================================================

STORE_OVERWRITE_QIR = """
entry:
%ptr = alloca i64
store i64 0, i64* %ptr
store i64 1, i64* %ptr
%val = load i64, i64* %ptr
%flag = icmp eq i64 %val, 1
br i1 %flag, label %then, label %end
then:
call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
br label %end
end:
call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
"""


@pytest.mark.parametrize("sim_type", SIM_TYPES)
def test_store_overwrite(sim_type):
"""Store 0 then 1 to same address → load should get 1 → X → measure 1."""
check_result(STORE_OVERWRITE_QIR, "1", sim_type=sim_type)


# =========================================================================
# OP_GEP + OP_LOAD — static array with constant index
# =========================================================================

_OPAQUE_DECLS = """\
declare void @__quantum__qis__x__body(ptr)
declare void @__quantum__qis__h__body(ptr)
declare void @__quantum__qis__mresetz__body(ptr, ptr) #1
declare void @__quantum__qis__mz__body(ptr, ptr) #1
declare void @__quantum__qis__reset__body(ptr)
declare i1 @__quantum__qis__read_result__body(ptr)
declare void @__quantum__rt__tuple_record_output(i64, ptr)
declare void @__quantum__rt__result_record_output(ptr, ptr)
declare void @__quantum__rt__initialize(ptr)
"""

STATIC_ARRAY_CONST_INDEX_QIR = (
"""\
@arr = internal constant [3 x i64] [i64 0, i64 5, i64 0]

define i64 @ENTRYPOINT__main() #0 {
entry:
%ptr = getelementptr [3 x i64], ptr @arr, i64 0, i64 1
%val = load i64, ptr %ptr
%flag = icmp eq i64 %val, 5
br i1 %flag, label %then, label %end
then:
call void @__quantum__qis__x__body(ptr inttoptr (i64 0 to ptr))
br label %end
end:
call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr))
call void @__quantum__rt__tuple_record_output(i64 1, ptr null)
call void @__quantum__rt__result_record_output(ptr inttoptr (i64 0 to ptr), ptr null)
ret i64 0
}

"""
+ _OPAQUE_DECLS
+ """
attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" }
attributes #1 = { "irreversible" }
"""
)


@pytest.mark.parametrize("sim_type", SIM_TYPES)
def test_static_array_lookup_const_index(sim_type):
"""Global array [0, 1, 0], GEP index 1 → load 1 → X → measure 1."""
results = _run(STATIC_ARRAY_CONST_INDEX_QIR, sim_type=sim_type)
counts = Counter(results)
assert counts == {"1": SHOTS}


# =========================================================================
# OP_GEP — dynamic (register-based) index into static array
# =========================================================================

GEP_DYNAMIC_INDEX_QIR = (
"""\
@data = internal constant [2 x i64] [i64 0, i64 1]

define i64 @ENTRYPOINT__main() #0 {
entry:
%idx = add i64 0, 1
%ptr = getelementptr [2 x i64], ptr @data, i64 0, i64 %idx
%val = load i64, ptr %ptr
%flag = icmp eq i64 %val, 1
br i1 %flag, label %then, label %end
then:
call void @__quantum__qis__x__body(ptr inttoptr (i64 0 to ptr))
br label %end
end:
call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr))
call void @__quantum__rt__tuple_record_output(i64 1, ptr null)
call void @__quantum__rt__result_record_output(ptr inttoptr (i64 0 to ptr), ptr null)
ret i64 0
}

"""
+ _OPAQUE_DECLS
+ """
attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" }
attributes #1 = { "irreversible" }
"""
)


@pytest.mark.parametrize("sim_type", SIM_TYPES)
def test_gep_with_dynamic_index(sim_type):
"""GEP with a runtime-computed index into [0, 1] → load element 1 → X → measure 1."""
results = _run(GEP_DYNAMIC_INDEX_QIR, sim_type=sim_type)
counts = Counter(results)
assert counts == {"1": SHOTS}


# =========================================================================
# Multiple global arrays — access elements from distinct arrays
# =========================================================================

MULTIPLE_ARRAYS_QIR = (
"""\
@arr_a = internal constant [2 x i64] [i64 1, i64 0]
@arr_b = internal constant [2 x i64] [i64 0, i64 1]

define i64 @ENTRYPOINT__main() #0 {
entry:
; Load arr_a[0] → should be 1
%ptr_a = getelementptr [2 x i64], ptr @arr_a, i64 0, i64 0
%val_a = load i64, ptr %ptr_a
; Load arr_b[1] → should be 1
%ptr_b = getelementptr [2 x i64], ptr @arr_b, i64 0, i64 1
%val_b = load i64, ptr %ptr_b
; Both should be 1: apply X to q0 from arr_a, X to q1 from arr_b
%flag_a = icmp eq i64 %val_a, 1
br i1 %flag_a, label %apply_a, label %check_b
apply_a:
call void @__quantum__qis__x__body(ptr inttoptr (i64 0 to ptr))
br label %check_b
check_b:
%flag_b = icmp eq i64 %val_b, 1
br i1 %flag_b, label %apply_b, label %measure
apply_b:
call void @__quantum__qis__x__body(ptr inttoptr (i64 1 to ptr))
br label %measure
measure:
call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr))
call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 1 to ptr))
call void @__quantum__rt__tuple_record_output(i64 2, ptr null)
call void @__quantum__rt__result_record_output(ptr inttoptr (i64 0 to ptr), ptr null)
call void @__quantum__rt__result_record_output(ptr inttoptr (i64 1 to ptr), ptr null)
ret i64 0
}

"""
+ _OPAQUE_DECLS
+ """
attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="2" }
attributes #1 = { "irreversible" }
"""
)


@pytest.mark.parametrize("sim_type", SIM_TYPES)
def test_multiple_arrays(sim_type):
"""Two global arrays: arr_a[0]=1 flips q0, arr_b[1]=1 flips q1 → measure '11'."""
results = _run(MULTIPLE_ARRAYS_QIR, sim_type=sim_type)
counts = Counter(results)
assert counts == {"11": SHOTS}


# #########################################################################
# Dynamic register file sizing (programs exceeding 128 registers)
# #########################################################################
Expand Down
Loading
Loading