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
35 changes: 28 additions & 7 deletions source/qdk_package/qdk/_adaptive_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,21 @@ def _is_output_recording_label(value: pyqir.Constant):
and ty.element.width == 8
)

@staticmethod
def _is_zero_array(value: pyqir.Value) -> bool:
"""Whether `value` is an all-zero array constant (`zeroinitializer`).

LLVM folds arrays whose elements are all zero (including
`[N x ptr] [ptr inttoptr (i64 0 to ptr), ...]`, since that is `null`)
to `zeroinitializer`, which pyqir exposes as a `Constant` rather
than an `ArrayConstant`.
"""
return (
isinstance(value, pyqir.Constant)
and isinstance(value.type, pyqir.ArrayType)
and value.is_null
)

@staticmethod
def _is_global_array(global_variable: pyqir.GlobalVariable):
init = global_variable.initializer
Expand All @@ -620,7 +635,9 @@ def _is_global_array(global_variable: pyqir.GlobalVariable):
# If the global variable has an initializer, but the initializer value
# is not an array, return False. E.g.: This can happens when initializing
# a global integer constant.
if not isinstance(init, pyqir.ArrayConstant):
if not isinstance(
init, pyqir.ArrayConstant
) and not AdaptiveProfilePass._is_zero_array(init):
return False

# ``[N x i8]`` globals are excluded on purpose: in Adaptive Profile QIR
Expand Down Expand Up @@ -658,12 +675,12 @@ def _scan_global_arrays(self, mod: pyqir.Module) -> None:
# encoding elements. This ensures that forward references between
# globals (e.g. @matrix declared before @row0/@row1) are resolved
# correctly in Pass 2.
supported_globals: list[tuple[pyqir.GlobalVariable, pyqir.ArrayConstant]] = []
supported_globals: list[tuple[pyqir.GlobalVariable, pyqir.Constant]] = []
base = len(self.constant_data)
for gv in mod.global_variables:
if not self._is_global_array(gv):
continue
init = cast(pyqir.ArrayConstant, gv.initializer)
init = cast(pyqir.Constant, gv.initializer)
self._global_to_address[gv.name] = base
base += self._size_in_words(init.type)
supported_globals.append((gv, init))
Expand All @@ -675,21 +692,25 @@ def _scan_global_arrays(self, mod: pyqir.Module) -> None:
self._alloca_ptr = len(self.constant_data)
self._memory_size = self._alloca_ptr

def _encode_array_elements(self, arr: pyqir.ArrayConstant, gv_name: str) -> None:
def _encode_array_elements(self, arr: pyqir.Constant, gv_name: str) -> None:
"""Recursively encode ArrayConstant elements into constant_data.

Nested ``ArrayConstant`` elements (e.g. ``[2 x [2 x i32]]``) are
flattened in row-major order.
flattened in row-major order. All-zero arrays (``zeroinitializer``)
have no elements and are encoded as zero words.
"""
if self._is_zero_array(arr):
self.constant_data.extend([0] * self._size_in_words(arr.type))
return
mask = (1 << self._int_bits) - 1
for elem in arr.elements:
for elem in cast(pyqir.ArrayConstant, arr).elements:
if isinstance(elem, pyqir.IntConstant):
self.constant_data.append(elem.value & mask)
elif isinstance(elem, pyqir.FloatConstant):
self.constant_data.append(
encode_float_as_bits(elem.value, self._bytecode_kind)
)
elif isinstance(elem, pyqir.ArrayConstant):
elif isinstance(elem, pyqir.ArrayConstant) or self._is_zero_array(elem):
# Nested array — flatten recursively.
self._encode_array_elements(elem, gv_name)
elif isinstance(elem, pyqir.GlobalVariable):
Expand Down
92 changes: 92 additions & 0 deletions source/qdk_package/tests/test_adaptive_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,98 @@ def test_byte_string_global_used_as_data_raises():
assert "record_output" in msg


# ---------------------------------------------------------------------------
# Test: all-zero global arrays (folded by LLVM to `zeroinitializer`)
# ---------------------------------------------------------------------------

ZERO_INITIALIZER_ARRAY_QIR = """\
%Result = type opaque
%Qubit = type opaque

@zeros = internal constant [3 x i64] [i64 0, i64 0, i64 0]
@after = internal constant [2 x i64] [i64 7, i64 8]

define void @ENTRYPOINT__main() #0 {
entry:
%ptr_z = getelementptr inbounds [3 x i64], [3 x i64]* @zeros, i64 0, i64 1
%ptr_a = getelementptr inbounds [2 x i64], [2 x i64]* @after, i64 0, i64 1
%vz = load i64, i64* %ptr_z, align 8
%va = load i64, i64* %ptr_a, align 8
call void @__quantum__rt__tuple_record_output(i64 0, i8* null)
ret void
}

declare void @__quantum__rt__tuple_record_output(i64, i8*)

attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="0" }
"""


def test_zero_initializer_array_constant_data():
"""All-zero arrays (parsed as `zeroinitializer`) are encoded as zeros.

Subsequent globals must still be placed after the zero-filled region.
"""
r = _run_pass(ZERO_INITIALIZER_ARRAY_QIR)
assert r.constant_data == [0, 0, 0, 7, 8]


NESTED_ZERO_INITIALIZER_ARRAY_QIR = """\
%Result = type opaque
%Qubit = type opaque

@matrix = internal constant [2 x [2 x i64]] [
[2 x i64] [i64 0, i64 0],
[2 x i64] [i64 3, i64 4]
]

define void @ENTRYPOINT__main() #0 {
entry:
%ptr = getelementptr inbounds [2 x [2 x i64]], [2 x [2 x i64]]* @matrix, i64 0, i64 1, i64 0
%val = load i64, i64* %ptr, align 8
call void @__quantum__rt__tuple_record_output(i64 0, i8* null)
ret void
}

declare void @__quantum__rt__tuple_record_output(i64, i8*)

attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="0" }
"""


def test_nested_zero_initializer_array_constant_data():
"""A zero sub-array inside a nested array is flattened as zeros."""
r = _run_pass(NESTED_ZERO_INITIALIZER_ARRAY_QIR)
assert r.constant_data == [0, 0, 3, 4]


EMPTY_LABEL_QIR = """\
%Result = type opaque
%Qubit = type opaque

@empty = internal constant [1 x i8] c"\\00"
@data = internal constant [2 x i64] [i64 42, i64 99]

define void @ENTRYPOINT__main() #0 {
entry:
%ptr = getelementptr inbounds [2 x i64], [2 x i64]* @data, i64 0, i64 0
%val = load i64, i64* %ptr, align 8
call void @__quantum__rt__tuple_record_output(i64 0, i8* null)
ret void
}

declare void @__quantum__rt__tuple_record_output(i64, i8*)

attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="0" }
"""


def test_empty_label_zero_initializer_skipped():
"""An empty `[1 x i8]` label (also `zeroinitializer`) is still skipped."""
r = _run_pass(EMPTY_LABEL_QIR)
assert r.constant_data == [42, 99]


# ---------------------------------------------------------------------------
# Test: mutable global array still works
# ---------------------------------------------------------------------------
Expand Down
19 changes: 19 additions & 0 deletions source/qdk_package/tests/test_clifford_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,25 @@
assert len(results) == 1


def test_program_indexing_single_zero_result_array_succeeds():
# Regression test for https://github.com/microsoft/qdk/issues/3777.
qsharp.init(target_profile=TargetProfile.Adaptive)
qsharp.eval("""
operation Main() : Result {
use q = Qubit();
for result in [M(q)] {
if result == One {
X(q);
}
}
MResetZ(q)
}
""")
ir = qsharp.compile("Main()")
results = run_qir_clifford(str(ir), 1, NoiseConfig())
Comment thread
orpuente-MS marked this conversation as resolved.
Dismissed
assert results == [Result.Zero]


def test_program_with_unconditional_branching_succeeds():
qir = """
%Result = type opaque
Expand Down
Loading