microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
fedimser/is-re

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/pip/tests/test_adaptive_pass.py

972lines · modecode

1# Copyright (c) Microsoft Corporation.
2# Licensed under the MIT License.
3
4"""Unit tests for AdaptiveProfilePass.
5
6Tests verify the Python QIR-to-bytecode translation pass by feeding
7LLVM IR strings through the pass and checking the resulting
8instruction dict encoding.
9"""
10
11from dataclasses import astuple, asdict
12import pyqir
13import pytest
14
15from qsharp._adaptive_pass import AdaptiveProfilePass, AdaptiveProgram
16from qsharp._adaptive_bytecode import *
17
18
19# ---------------------------------------------------------------------------
20# Helpers
21# ---------------------------------------------------------------------------
22
23
24def _run_pass(ir: str, name: str = "test.ll") -> AdaptiveProgram:
25 """Parse an LLVM IR string and run through AdaptiveProfilePass."""
26 mod = pyqir.Module.from_ir(pyqir.Context(), ir, name)
27 return AdaptiveProfilePass().run(mod)
28
29
30def _primary(opcode_word: int) -> int:
31 """Extract primary opcode from opcode word."""
32 return opcode_word & 0xFF
33
34
35def _sub(opcode_word: int) -> int:
36 """Extract sub-opcode from opcode word."""
37 return (opcode_word >> 8) & 0xFF
38
39
40# ---------------------------------------------------------------------------
41# Test: Simple linear (H, CNOT, MResetZ on static qubits, no branching)
42# ---------------------------------------------------------------------------
43
44LINEAR_QIR = """\
45%Result = type opaque
46%Qubit = type opaque
47
48define void @ENTRYPOINT__main() #0 {
49entry:
50 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
51 call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*))
52 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
53 call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
54 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
55 ret void
56}
57
58declare void @__quantum__qis__h__body(%Qubit*)
59declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*)
60declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*)
61declare void @__quantum__rt__tuple_record_output(i64, i8*)
62declare void @__quantum__rt__result_record_output(%Result*, i8*)
63
64attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="1" }
65"""
66
67
68def test_linear_structure():
69 """Linear QIR with no branching produces correct blocks and ops."""
70 r = _run_pass(LINEAR_QIR)
71 assert r.num_qubits == 2
72 assert r.num_results == 1
73 assert r.entry_block == 0
74 assert len(r.blocks) == 1
75 assert len(r.phi_entries) == 0
76 assert len(r.functions) == 0
77 assert len(r.switch_cases) == 0
78
79
80def test_linear_quantum_ops():
81 """Linear QIR emits the correct quantum op IDs."""
82 r = _run_pass(LINEAR_QIR)
83 op_ids = [q.op_id for q in r.quantum_ops]
84 assert 5 in op_ids, "Missing H gate (OpID=5)"
85 assert 15 in op_ids, "Missing CNOT gate (OpID=15)"
86 assert 22 in op_ids, "Missing MResetZ gate (OpID=22)"
87
88
89def test_linear_instruction_opcodes():
90 """Linear QIR instructions have expected primary opcodes."""
91 r = _run_pass(LINEAR_QIR)
92 primaries = [_primary(inst.opcode) for inst in r.instructions]
93 assert OP_QUANTUM_GATE in primaries, "Missing OP_QUANTUM_GATE"
94 assert OP_MEASURE in primaries, "Missing OP_MEASURE"
95 assert OP_RECORD_OUTPUT in primaries, "Missing OP_RECORD_OUTPUT"
96 # Should have a RET at the end
97 assert _primary(r.instructions[-1].opcode) == OP_RET
98
99
100def test_linear_block_offset_consistency():
101 """Instruction offsets and counts in blocks must cover all instructions."""
102 r = _run_pass(LINEAR_QIR)
103 total = 0
104 for b in r.blocks:
105 offset, count = b.instr_offset, b.instr_count
106 assert offset == total
107 total += count
108 assert total == len(r.instructions)
109
110
111def test_linear_static_qubit_sentinel():
112 """Static qubit IDs use DYN_QUBIT_SENTINEL in aux1/aux2 (no dynamic override)."""
113 r = _run_pass(LINEAR_QIR)
114 for inst in r.instructions:
115 if _primary(inst.opcode) == OP_QUANTUM_GATE:
116 # aux1 and aux2 should be DYN_QUBIT_SENTINEL for static qubits
117 assert inst.opcode & FLAG_AUX1_IMM, "Expected FLAG_AUX1_IMM in opcode"
118 assert inst.opcode & FLAG_AUX2_IMM, "Expected FLAG_AUX2_IMM in opcode"
119 return
120 pytest.fail("No OP_QUANTUM_GATE found")
121
122
123# ---------------------------------------------------------------------------
124# Test: Measure-and-correct (conditional branch on measurement result)
125# ---------------------------------------------------------------------------
126
127MEASURE_CORRECT_QIR = """\
128%Result = type opaque
129%Qubit = type opaque
130
131define void @ENTRYPOINT__main() #0 {
132entry:
133 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
134 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
135 %r = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
136 br i1 %r, label %then, label %end
137
138then:
139 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
140 br label %end
141
142end:
143 call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
144 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
145 ret void
146}
147
148declare void @__quantum__qis__h__body(%Qubit*)
149declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*)
150declare i1 @__quantum__qis__read_result__body(%Result*)
151declare void @__quantum__qis__x__body(%Qubit*)
152declare void @__quantum__rt__tuple_record_output(i64, i8*)
153declare void @__quantum__rt__result_record_output(%Result*, i8*)
154
155attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" }
156"""
157
158
159def test_measure_correct_structure():
160 """Measure-and-correct has 3 blocks (entry, then, end)."""
161 r = _run_pass(MEASURE_CORRECT_QIR)
162 assert r.num_qubits == 1
163 assert r.num_results == 1
164 assert r.entry_block == 0
165 assert len(r.blocks) == 3
166 assert len(r.phi_entries) == 0
167
168
169def test_measure_correct_branch():
170 """Entry block ends with a conditional branch."""
171 r = _run_pass(MEASURE_CORRECT_QIR)
172 entry_block = r.blocks[0]
173 offset, count = entry_block.instr_offset, entry_block.instr_count
174 last_instr = r.instructions[offset + count - 1]
175 assert _primary(last_instr.opcode) == OP_BRANCH
176
177
178def test_measure_correct_read_result():
179 """Read result instruction is emitted."""
180 r = _run_pass(MEASURE_CORRECT_QIR)
181 primaries = [_primary(inst.opcode) for inst in r.instructions]
182 assert OP_READ_RESULT in primaries
183
184
185def test_measure_correct_quantum_ops():
186 """H, MResetZ, and X gates are emitted."""
187 r = _run_pass(MEASURE_CORRECT_QIR)
188 op_ids = [q.op_id for q in r.quantum_ops]
189 assert 5 in op_ids, "Missing H"
190 assert 22 in op_ids, "Missing MResetZ"
191 assert 2 in op_ids, "Missing X (OpID=2)"
192
193
194def test_measure_correct_read_result_bool_type():
195 """read_result destination register has REG_TYPE_BOOL."""
196 r = _run_pass(MEASURE_CORRECT_QIR)
197 for inst in r.instructions:
198 if _primary(inst.opcode) == OP_READ_RESULT:
199 dst_reg = inst.dst
200 assert r.register_types[dst_reg] == REG_TYPE_BOOL, (
201 f"read_result dst reg type is {r.register_types[dst_reg]}, "
202 f"expected REG_TYPE_BOOL={REG_TYPE_BOOL}"
203 )
204 return
205 pytest.fail("No OP_READ_RESULT found")
206
207
208def test_measure_correct_unconditional_jump():
209 """'then' block ends with OP_JUMP (unconditional branch to 'end')."""
210 r = _run_pass(MEASURE_CORRECT_QIR)
211 then_block = r.blocks[1] # block 1 = then
212 offset, count = then_block.instr_offset, then_block.instr_count
213 last_instr = r.instructions[offset + count - 1]
214 assert (
215 _primary(last_instr.opcode) == OP_JUMP
216 ), f"Expected OP_JUMP at end of 'then' block, got {_primary(last_instr.opcode):#x}"
217
218
219# ---------------------------------------------------------------------------
220# Test: Loop with phi node
221# ---------------------------------------------------------------------------
222
223LOOP_PHI_QIR = """\
224%Result = type opaque
225%Qubit = type opaque
226
227define void @ENTRYPOINT__main() #0 {
228entry:
229 br label %loop
230
231loop:
232 %i = phi i64 [ 0, %entry ], [ %next, %loop ]
233 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
234 %next = add i64 %i, 1
235 %cond = icmp ult i64 %next, 4
236 br i1 %cond, label %loop, label %exit
237
238exit:
239 call void @__quantum__rt__tuple_record_output(i64 0, i8* null)
240 ret void
241}
242
243declare void @__quantum__qis__h__body(%Qubit*)
244declare void @__quantum__rt__tuple_record_output(i64, i8*)
245
246attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="0" }
247"""
248
249
250def test_loop_phi_structure():
251 """Loop with phi produces 3 blocks (entry, loop, exit)."""
252 r = _run_pass(LOOP_PHI_QIR)
253 assert len(r.blocks) == 3
254 assert r.entry_block == 0
255
256
257def test_loop_phi_entries():
258 """Phi node generates 2 phi_entries (from entry and back-edge)."""
259 r = _run_pass(LOOP_PHI_QIR)
260 assert len(r.phi_entries) == 2
261 # Each phi entry is (block_id, val_reg)
262 block_ids = {pe.block_id for pe in r.phi_entries}
263 # Should come from entry (block 0) and loop (block 1, back-edge)
264 assert 0 in block_ids, "Missing phi entry from entry block"
265 assert 1 in block_ids, "Missing phi entry from loop back-edge"
266
267
268def test_loop_phi_register_types():
269 """Phi destination register should have i64 type tag."""
270 r = _run_pass(LOOP_PHI_QIR)
271 # Find the PHI instruction
272 for inst in r.instructions:
273 if _primary(inst.opcode) == OP_PHI:
274 dst_reg = inst.dst
275 assert r.register_types[dst_reg] == REG_TYPE_I64
276 return
277 pytest.fail("No PHI instruction found")
278
279
280def test_loop_phi_forward_ref_reuse():
281 """Forward-referenced phi incoming value shares register with its definition."""
282 r = _run_pass(LOOP_PHI_QIR)
283 # The phi incoming from the loop back-edge references %next (add result)
284 # Find the phi entry from the loop block (block 1)
285 back_edge_entry = next(pe for pe in r.phi_entries if pe.block_id == 1)
286 next_reg = back_edge_entry.val_reg
287
288 # Find the ADD instruction (for %next = add %i, 1)
289 for inst in r.instructions:
290 if _primary(inst.opcode) == OP_ADD:
291 add_dst = inst.dst
292 assert (
293 add_dst == next_reg
294 ), f"Forward ref register {next_reg} != ADD dst {add_dst}"
295 return
296 pytest.fail("No ADD instruction found")
297
298
299def test_loop_icmp_and_branch():
300 """Loop body ends with icmp + conditional branch."""
301 r = _run_pass(LOOP_PHI_QIR)
302 loop_block = r.blocks[1] # block 1 = loop
303 offset, count = loop_block.instr_offset, loop_block.instr_count
304 instrs = r.instructions[offset : offset + count]
305 primaries = [_primary(inst.opcode) for inst in instrs]
306 assert OP_ICMP in primaries, "Missing ICMP in loop block"
307 assert primaries[-1] == OP_BRANCH, "Loop block should end with BRANCH"
308
309
310def test_loop_icmp_ult_sub_opcode():
311 """The icmp ult instruction encodes ICMP_ULT in the sub-opcode field."""
312 r = _run_pass(LOOP_PHI_QIR)
313 for inst in r.instructions:
314 if _primary(inst.opcode) == OP_ICMP:
315 assert (
316 _sub(inst.opcode) == ICMP_ULT
317 ), f"Expected ICMP_ULT={ICMP_ULT} sub-opcode, got {_sub(inst.opcode)}"
318 return
319 pytest.fail("No OP_ICMP found")
320
321
322def test_loop_entry_unconditional_jump():
323 """Entry block ends with OP_JUMP (unconditional branch to loop header)."""
324 r = _run_pass(LOOP_PHI_QIR)
325 entry_block = r.blocks[0] # block 0 = entry
326 offset, count = entry_block.instr_offset, entry_block.instr_count
327 last_instr = r.instructions[offset + count - 1]
328 assert (
329 _primary(last_instr.opcode) == OP_JUMP
330 ), f"Expected OP_JUMP at end of entry block, got {_primary(last_instr.opcode):#x}"
331
332
333# ---------------------------------------------------------------------------
334# Test: Classical boolean (AND of two read_results, conditional branch)
335# ---------------------------------------------------------------------------
336
337CLASSICAL_BOOLEAN_QIR = """\
338%Result = type opaque
339%Qubit = type opaque
340
341define void @ENTRYPOINT__main() #0 {
342entry:
343 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
344 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*))
345 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
346 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
347 %r0 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
348 %r1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
349 %both = and i1 %r0, %r1
350 br i1 %both, label %then, label %end
351
352then:
353 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
354 br label %end
355
356end:
357 call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
358 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
359 ret void
360}
361
362declare void @__quantum__qis__h__body(%Qubit*)
363declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*)
364declare i1 @__quantum__qis__read_result__body(%Result*)
365declare void @__quantum__qis__x__body(%Qubit*)
366declare void @__quantum__rt__tuple_record_output(i64, i8*)
367declare void @__quantum__rt__result_record_output(%Result*, i8*)
368
369attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="2" }
370"""
371
372
373def test_classical_boolean_and():
374 """AND of two read_results emits an OP_AND instruction."""
375 r = _run_pass(CLASSICAL_BOOLEAN_QIR)
376 primaries = [_primary(inst.opcode) for inst in r.instructions]
377 assert OP_AND in primaries, "Missing OP_AND for boolean AND"
378
379
380def test_classical_boolean_structure():
381 """Classical boolean program has 3 blocks and correct qubits/results."""
382 r = _run_pass(CLASSICAL_BOOLEAN_QIR)
383 assert r.num_qubits == 2
384 assert r.num_results == 2
385 assert len(r.blocks) == 3
386
387
388def test_classical_boolean_read_results():
389 """Two read_result instructions are emitted."""
390 r = _run_pass(CLASSICAL_BOOLEAN_QIR)
391 read_count = sum(
392 1 for inst in r.instructions if _primary(inst.opcode) == OP_READ_RESULT
393 )
394 assert read_count == 2
395
396
397# ---------------------------------------------------------------------------
398# Test: Select instruction
399# ---------------------------------------------------------------------------
400
401SELECT_QIR = """\
402%Result = type opaque
403%Qubit = type opaque
404
405define void @ENTRYPOINT__main() #0 {
406entry:
407 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
408 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
409 %r = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
410 %val = select i1 %r, i32 42, i32 7
411 call void @__quantum__rt__int_record_output(i64 0, i8* null)
412 ret void
413}
414
415declare void @__quantum__qis__h__body(%Qubit*)
416declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*)
417declare i1 @__quantum__qis__read_result__body(%Result*)
418declare void @__quantum__rt__int_record_output(i64, i8*)
419
420attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" }
421"""
422
423
424def test_select_instruction():
425 """Select instruction emits OP_SELECT."""
426 r = _run_pass(SELECT_QIR)
427 primaries = [_primary(inst.opcode) for inst in r.instructions]
428 assert OP_SELECT in primaries, "Missing OP_SELECT"
429
430
431def test_select_i32_type():
432 """Select with i32 result type assigns REG_TYPE_I32."""
433 r = _run_pass(SELECT_QIR)
434 for inst in r.instructions:
435 if _primary(inst.opcode) == OP_SELECT:
436 dst_reg = inst.dst
437 assert r.register_types[dst_reg] == REG_TYPE_I32, (
438 f"select dst type is {r.register_types[dst_reg]}, "
439 f"expected REG_TYPE_I32={REG_TYPE_I32}"
440 )
441 return
442 pytest.fail("No OP_SELECT found")
443
444
445def test_select_const_operands():
446 """Select true/false values (i32 42 and 7)."""
447 r = _run_pass(SELECT_QIR)
448 for inst in r.instructions:
449 if _primary(inst.opcode) == OP_SELECT:
450 assert inst.opcode & FLAG_AUX0_IMM, "aux0 should be an immediate"
451 assert inst.opcode & FLAG_AUX1_IMM, "aux1 should be an immediate"
452 assert inst.aux0 == 42, "aux0 operand should be const 42"
453 assert inst.aux1 == 7, "aux1 operand should be const 7"
454 return
455 pytest.fail("No OP_SELECT found")
456
457
458# ---------------------------------------------------------------------------
459# Test: Reset gate
460# ---------------------------------------------------------------------------
461
462RESET_QIR = """\
463%Result = type opaque
464%Qubit = type opaque
465
466define void @ENTRYPOINT__main() #0 {
467entry:
468 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
469 call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
470 call void @__quantum__rt__tuple_record_output(i64 0, i8* null)
471 ret void
472}
473
474declare void @__quantum__qis__h__body(%Qubit*)
475declare void @__quantum__qis__reset__body(%Qubit*)
476declare void @__quantum__rt__tuple_record_output(i64, i8*)
477
478attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="0" }
479"""
480
481
482def test_reset_instruction():
483 """Reset gate emits OP_RESET."""
484 r = _run_pass(RESET_QIR)
485 quantum_instrs = [
486 inst for inst in r.instructions if _primary(inst.opcode) == OP_RESET
487 ]
488 assert len(quantum_instrs) == 1
489 reset = quantum_instrs[0]
490 assert reset.opcode & FLAG_AUX1_IMM, "Qubit argument should be static"
491 assert reset.aux1 == 0
492
493
494# ---------------------------------------------------------------------------
495# Test: Dynamic qubit (inttoptr with non-constant → OP_MOV, no sentinel)
496# ---------------------------------------------------------------------------
497
498BELL_LOOP_QIR = """\
499%Result = type opaque
500%Qubit = type opaque
501
502define i64 @ENTRYPOINT__main() #0 {
503block_0:
504 br label %loop_cond
505loop_cond: ; preds = %loop_body, %block_0
506 %i = phi i64 [ 0, %block_0 ], [ %i_next, %loop_body ]
507 %cond = icmp ult i64 %i, 8
508 br i1 %cond, label %loop_body, label %loop_cond2
509loop_body: ; preds = %loop_cond
510 %q0 = inttoptr i64 %i to %Qubit*
511 %i1 = add i64 %i, 1
512 %q1 = inttoptr i64 %i1 to %Qubit*
513 call void @__quantum__qis__h__body(%Qubit* %q0)
514 call void @__quantum__qis__cx__body(%Qubit* %q0, %Qubit* %q1)
515 %i_next = add i64 %i, 2
516 br label %loop_cond
517loop_cond2: ; preds = %loop_cond
518 %i3 = phi i64 [ 0, %loop_cond ], [ %i_next2, %loop_body2 ]
519 %cond2 = icmp ult i64 %i3, 16
520 br i1 %cond2, label %loop_body2, label %end
521loop_body2: ; preds = %loop_cond2
522 %q2 = inttoptr i64 %i3 to %Qubit*
523 %r = inttoptr i64 %i3 to %Result*
524 call void @__quantum__qis__mresetz__body(%Qubit* %q2, %Result* %r)
525 %i_next2 = add i64 %i3, 1
526 br label %loop_cond2
527end: ; preds = %loop_cond2
528 call void @__quantum__rt__array_record_output(i64 8, i8* null)
529 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
530 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
531 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
532 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
533 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null)
534 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null)
535 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
536 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 4 to %Result*), i8* null)
537 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 5 to %Result*), i8* null)
538 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
539 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 6 to %Result*), i8* null)
540 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 7 to %Result*), i8* null)
541 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
542 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 8 to %Result*), i8* null)
543 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 9 to %Result*), i8* null)
544 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
545 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 10 to %Result*), i8* null)
546 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 11 to %Result*), i8* null)
547 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
548 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 12 to %Result*), i8* null)
549 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 13 to %Result*), i8* null)
550 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
551 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 14 to %Result*), i8* null)
552 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 15 to %Result*), i8* null)
553 ret i64 0
554}
555
556declare void @__quantum__qis__x__body(%Qubit*)
557
558declare void @__quantum__qis__h__body(%Qubit*)
559
560declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*)
561
562declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1
563
564declare i1 @__quantum__rt__read_loss(%Result*)
565
566declare i1 @__quantum__qis__read_result__body(%Result*)
567
568declare void @__quantum__qis__z__body(%Qubit*)
569
570declare void @__quantum__rt__array_record_output(i64, i8*)
571
572declare void @__quantum__rt__tuple_record_output(i64, i8*)
573
574declare void @__quantum__rt__result_record_output(%Result*, i8*)
575
576declare void @__quantum__rt__bool_record_output(i1, i8*)
577declare void @__quantum__rt__int_record_output(i64, i8*)
578
579attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="16" "required_num_results"="16" }
580attributes #1 = { "irreversible" }
581
582; module flags
583
584!llvm.module.flags = !{!0, !1, !2, !3, !4}
585
586!0 = !{i32 1, !"qir_major_version", i32 1}
587!1 = !{i32 7, !"qir_minor_version", i32 0}
588!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
589!3 = !{i32 1, !"dynamic_result_management", i1 false}
590!4 = !{i32 5, !"int_computations", !{!"i64"}}
591"""
592
593
594def test_bell_loop_dynamic_qubit():
595 """Dynamic qubits from inttoptr use register index (not DYN_QUBIT_SENTINEL) in aux1."""
596 r = _run_pass(BELL_LOOP_QIR)
597 # bell_loop has dynamic qubits via inttoptr of loop variable
598 # At least one OP_QUANTUM_GATE should have aux1 != DYN_QUBIT_SENTINEL
599 has_dynamic = False
600 for inst in r.instructions:
601 if _primary(inst.opcode) == OP_QUANTUM_GATE and not (
602 OP_QUANTUM_GATE & FLAG_AUX1_IMM
603 ):
604 has_dynamic = True
605 # The dynamic reg must be a valid register index
606 assert (
607 0 <= inst.aux1 < r.num_registers
608 ), f"Invalid dynamic qubit register: {inst.aux1}"
609 assert has_dynamic, "Expected at least one dynamic qubit gate in bell_loop"
610
611
612def test_bell_loop_inttoptr_emits_mov():
613 """inttoptr instructions in bell_loop emit OP_MOV (aliasing int to ptr register)."""
614 r = _run_pass(BELL_LOOP_QIR)
615 primaries = [_primary(inst.opcode) for inst in r.instructions]
616 assert OP_MOV in primaries, "Missing OP_MOV for inttoptr"
617
618
619# ---------------------------------------------------------------------------
620# Test: Bell loop with IR-defined function calls
621# ---------------------------------------------------------------------------
622
623BELL_LOOP_FUNCS_QIR = """\
624%Result = type opaque
625%Qubit = type opaque
626
627define i64 @ENTRYPOINT__main() #0 {
628block_0:
629 br label %loop_cond
630loop_cond: ; preds = %loop_body, %block_0
631 %i = phi i64 [ 0, %block_0 ], [ %i_next, %loop_body ]
632 %cond = icmp ult i64 %i, 8
633 br i1 %cond, label %loop_body, label %loop_cond2
634loop_body: ; preds = %loop_cond
635 %q0 = inttoptr i64 %i to %Qubit*
636 %i1 = add i64 %i, 1
637 %q1 = inttoptr i64 %i1 to %Qubit*
638 call void @make_bell(%Qubit* %q0, %Qubit* %q1)
639 %i_next = add i64 %i, 2
640 br label %loop_cond
641loop_cond2: ; preds = %loop_cond
642 %i3 = phi i64 [ 0, %loop_cond ], [ %i_next2, %loop_body2 ]
643 %cond2 = icmp ult i64 %i3, 16
644 br i1 %cond2, label %loop_body2, label %end
645loop_body2: ; preds = %loop_cond2
646 %q2 = inttoptr i64 %i3 to %Qubit*
647 %r = inttoptr i64 %i3 to %Result*
648 call void @__quantum__qis__mresetz__body(%Qubit* %q2, %Result* %r)
649 %i_next2 = add i64 %i3, 1
650 br label %loop_cond2
651end: ; preds = %loop_cond2
652 call void @__quantum__rt__array_record_output(i64 8, i8* null)
653 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
654 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
655 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
656 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
657 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null)
658 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null)
659 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
660 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 4 to %Result*), i8* null)
661 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 5 to %Result*), i8* null)
662 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
663 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 6 to %Result*), i8* null)
664 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 7 to %Result*), i8* null)
665 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
666 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 8 to %Result*), i8* null)
667 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 9 to %Result*), i8* null)
668 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
669 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 10 to %Result*), i8* null)
670 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 11 to %Result*), i8* null)
671 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
672 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 12 to %Result*), i8* null)
673 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 13 to %Result*), i8* null)
674 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
675 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 14 to %Result*), i8* null)
676 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 15 to %Result*), i8* null)
677 ret i64 0
678}
679
680define void @make_bell(%Qubit* %q0, %Qubit* %q1) {
681 call void @__quantum__qis__h__body(%Qubit* %q0)
682 call void @__quantum__qis__cx__body(%Qubit* %q0, %Qubit* %q1)
683 ret void
684}
685
686declare void @__quantum__qis__x__body(%Qubit*)
687
688declare void @__quantum__qis__h__body(%Qubit*)
689
690declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*)
691
692declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1
693
694declare i1 @__quantum__rt__read_loss(%Result*)
695
696declare i1 @__quantum__qis__read_result__body(%Result*)
697
698declare void @__quantum__qis__z__body(%Qubit*)
699
700declare void @__quantum__rt__array_record_output(i64, i8*)
701
702declare void @__quantum__rt__tuple_record_output(i64, i8*)
703
704declare void @__quantum__rt__result_record_output(%Result*, i8*)
705
706declare void @__quantum__rt__bool_record_output(i1, i8*)
707declare void @__quantum__rt__int_record_output(i64, i8*)
708
709attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="16" "required_num_results"="16" }
710attributes #1 = { "irreversible" }
711
712; module flags
713
714!llvm.module.flags = !{!0, !1, !2, !3, !4}
715
716!0 = !{i32 1, !"qir_major_version", i32 1}
717!1 = !{i32 7, !"qir_minor_version", i32 0}
718!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
719!3 = !{i32 1, !"dynamic_result_management", i1 false}
720!4 = !{i32 5, !"int_computations", !{!"i64"}}
721"""
722
723
724def test_bell_loop_funcs_structure():
725 """Bell loop with make_bell function: 7 blocks (6 main + 1 make_bell), 1 function."""
726 r = _run_pass(BELL_LOOP_FUNCS_QIR)
727 assert r.num_qubits == 16
728 assert r.num_results == 16
729 assert r.entry_block == 0
730 assert len(r.blocks) == 7 # 6 in main + 1 in make_bell
731 assert len(r.functions) == 1 # make_bell
732
733
734def test_bell_loop_funcs_function_entry():
735 """The make_bell function table entry has correct param count and entry block."""
736 r = _run_pass(BELL_LOOP_FUNCS_QIR)
737 func = r.functions[0] # (entry_block, num_params, param_base, reserved)
738 entry_block, num_params, param_base = astuple(func)
739 assert num_params == 2, "make_bell takes 2 params (%Qubit*, %Qubit*)"
740 # entry_block should be a valid block ID
741 valid_block_ids = {b.block_id for b in r.blocks}
742 assert (
743 entry_block in valid_block_ids
744 ), f"Function entry block {entry_block} not found"
745
746
747def test_bell_loop_funcs_call_instruction():
748 """An OP_CALL instruction is emitted for the make_bell call."""
749 r = _run_pass(BELL_LOOP_FUNCS_QIR)
750 call_instrs = [inst for inst in r.instructions if _primary(inst.opcode) == OP_CALL]
751 assert len(call_instrs) >= 1, "Expected at least one OP_CALL for make_bell"
752 # The call should reference function 0 (make_bell) via aux0
753 call = call_instrs[0]
754 assert call.aux0 == 0, f"OP_CALL aux0 (func_id) should be 0, got {call.aux0}"
755 # aux1 = num_args (2 qubit pointers)
756 assert call.aux1 == 2, f"OP_CALL aux1 (num_args) should be 2, got {call.aux1}"
757
758
759def test_bell_loop_funcs_call_args():
760 """call_args contains resolved registers for the make_bell call arguments."""
761 r = _run_pass(BELL_LOOP_FUNCS_QIR)
762 assert (
763 len(r.call_args) >= 2
764 ), "Expected at least 2 call args for make_bell(%q0, %q1)"
765 call_instrs = [inst for inst in r.instructions if _primary(inst.opcode) == OP_CALL]
766 call = call_instrs[0]
767 num_args = call.aux1 # aux1 = num_args
768 arg_offset = call.aux2 # aux2 = arg_offset into call_args
769 args = r.call_args[arg_offset : arg_offset + num_args]
770 assert len(args) == 2, f"Expected 2 call args, got {len(args)}"
771 # Both args should be valid register indices
772 for a in args:
773 assert 0 <= a < r.num_registers, f"Invalid call arg register: {a}"
774
775
776def test_bell_loop_funcs_call_return():
777 """The make_bell function body ends with OP_CALL_RETURN."""
778 r = _run_pass(BELL_LOOP_FUNCS_QIR)
779 # Find the make_bell function's entry block
780 func_entry_block = r.functions[0].func_entry_block
781 # Find the block tuple for that block
782 func_block = next(b for b in r.blocks if b.block_id == func_entry_block)
783 offset, count = func_block.instr_offset, func_block.instr_count
784 last_instr = r.instructions[offset + count - 1]
785 assert (
786 _primary(last_instr.opcode) == OP_CALL_RETURN
787 ), f"make_bell should end with OP_CALL_RETURN, got {_primary(last_instr.opcode):#x}"
788
789
790def test_bell_loop_funcs_quantum_ops():
791 """H and CX are emitted from make_bell; MResetZ from main."""
792 r = _run_pass(BELL_LOOP_FUNCS_QIR)
793 op_ids = [q.op_id for q in r.quantum_ops]
794 assert 5 in op_ids, "Missing H gate"
795 assert 15 in op_ids, "Missing CNOT gate"
796 assert 22 in op_ids, "Missing MResetZ gate"
797
798
799def test_bell_loop_funcs_param_registers():
800 """make_bell params get allocated registers with PTR type tag."""
801 r = _run_pass(BELL_LOOP_FUNCS_QIR)
802 func = r.functions[0]
803 _, num_params, param_base = astuple(func)
804 for i in range(num_params):
805 reg = param_base + i
806 assert (
807 r.register_types[reg] == REG_TYPE_PTR
808 ), f"Param register {reg} type is {r.register_types[reg]}, expected REG_TYPE_PTR"
809
810
811# ---------------------------------------------------------------------------
812# Test: Bell loop from file (integration test with real QIR)
813# ---------------------------------------------------------------------------
814
815
816def test_bell_loop_structure():
817 """Bell loop has 6 blocks, 16 qubits, 16 results, 2 phi nodes."""
818 r = _run_pass(BELL_LOOP_QIR)
819 assert r.num_qubits == 16
820 assert r.num_results == 16
821 assert r.entry_block == 0
822 assert len(r.blocks) == 6
823 assert len(r.phi_entries) == 4 # 2 phi nodes * 2 incoming each
824
825
826def test_bell_loop_phi_block_refs():
827 """Phi entries reference valid block IDs within the program."""
828 r = _run_pass(BELL_LOOP_QIR)
829 valid_block_ids = {b.block_id for b in r.blocks}
830 for pe in r.phi_entries:
831 block_id, val_reg = pe.block_id, pe.val_reg
832 assert block_id in valid_block_ids, f"Invalid phi block_id: {block_id}"
833 assert 0 <= val_reg < r.num_registers, f"Invalid phi val_reg: {val_reg}"
834
835
836def test_bell_loop_phi_i64_types():
837 """Both phi destination registers have i64 type tag."""
838 r = _run_pass(BELL_LOOP_QIR)
839 phi_dst_regs = []
840 for inst in r.instructions:
841 if _primary(inst.opcode) == OP_PHI:
842 phi_dst_regs.append(inst.dst)
843 assert len(phi_dst_regs) == 2
844 for reg in phi_dst_regs:
845 assert (
846 r.register_types[reg] == REG_TYPE_I64
847 ), f"Phi dst reg {reg} type is {r.register_types[reg]}, expected REG_TYPE_I64={REG_TYPE_I64}"
848
849
850def test_bell_loop_quantum_ops():
851 """Bell loop emits H, CNOT, and MResetZ quantum operations."""
852 r = _run_pass(BELL_LOOP_QIR)
853 op_ids = [q.op_id for q in r.quantum_ops]
854 assert 5 in op_ids, "Missing H gate"
855 assert 15 in op_ids, "Missing CNOT gate"
856 assert 22 in op_ids, "Missing MResetZ gate"
857
858
859def test_bell_loop_forward_ref_consistency():
860 """Forward-referenced phi values share registers with their definitions."""
861 r = _run_pass(BELL_LOOP_QIR)
862 # Find all ADD instructions and their dst registers
863 add_dsts = set()
864 for inst in r.instructions:
865 if _primary(inst.opcode) == OP_ADD:
866 add_dsts.add(inst.dst)
867
868 # The phi entries from back-edge blocks should reference ADD dst registers
869 # Block 2 = loop_body (back-edge for phi %i), block 4 = loop_body2 (back-edge for phi %i3)
870 back_edge_regs = set()
871 for pe in r.phi_entries:
872 if pe.block_id in (2, 4): # back-edge blocks
873 back_edge_regs.add(pe.val_reg)
874
875 assert back_edge_regs.issubset(
876 add_dsts
877 ), f"Phi back-edge registers {back_edge_regs} not in ADD dsts {add_dsts}"
878
879
880def test_bell_loop_block_offset_consistency():
881 """Block instruction offsets are contiguous and cover all instructions."""
882 r = _run_pass(BELL_LOOP_QIR)
883 total = 0
884 for b in r.blocks:
885 offset, count = b.instr_offset, b.instr_count
886 assert offset == total, f"Block offset {offset} != expected {total}"
887 total += count
888 assert total == len(r.instructions)
889
890
891# ---------------------------------------------------------------------------
892# Test: Output dict schema completeness
893# ---------------------------------------------------------------------------
894
895
896def test_output_schema_keys():
897 """Output dict contains all expected keys."""
898 r = _run_pass(LINEAR_QIR)
899 expected_keys = {
900 "num_qubits",
901 "num_results",
902 "num_registers",
903 "entry_block",
904 "blocks",
905 "instructions",
906 "quantum_ops",
907 "functions",
908 "phi_entries",
909 "switch_cases",
910 "call_args",
911 "labels",
912 "register_types",
913 }
914 assert set(r.as_dict().keys()) == expected_keys
915
916
917def test_instruction_tuple_length():
918 """All instructions are 8-tuples."""
919 r = _run_pass(LINEAR_QIR)
920 for i, inst in enumerate(r.instructions):
921 assert (
922 len(astuple(inst)) == 8
923 ), f"Instruction {i} has {len(astuple(inst))} fields, expected 8"
924
925
926def test_quantum_op_tuple_length():
927 """All quantum ops are 5-tuples."""
928 r = _run_pass(LINEAR_QIR)
929 for i, qop in enumerate(r.quantum_ops):
930 assert (
931 len(astuple(qop)) == 5
932 ), f"Quantum op {i} has {len(astuple(qop))} fields, expected 5"
933
934
935ADAPTIVE_RIFLA_QIR = r"""
936%Result = type opaque
937%Qubit = type opaque
938
939@0 = internal constant [4 x i8] c"0_t\00"
940
941define i64 @ENTRYPOINT__main() #0 {
942block_0:
943 call void @__quantum__rt__initialize(i8* null)
944 call void @__quantum__rt__tuple_record_output(i64 0, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i64 0, i64 0))
945 ret i64 0
946}
947
948declare void @__quantum__rt__initialize(i8*)
949declare void @__quantum__rt__tuple_record_output(i64, i8*)
950
951attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="0" "required_num_results"="0" }
952attributes #1 = { "irreversible" }
953
954; module flags
955
956!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7}
957
958!0 = !{i32 1, !"qir_major_version", i32 1}
959!1 = !{i32 7, !"qir_minor_version", i32 0}
960!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
961!3 = !{i32 1, !"dynamic_result_management", i1 false}
962!4 = !{i32 5, !"int_computations", !{!"i64"}}
963!5 = !{i32 5, !"float_computations", !{!"double"}}
964!6 = !{i32 7, !"backwards_branching", i2 3}
965!7 = !{i32 1, !"arrays", i1 true}
966"""
967
968
969def test_arrays_capability_is_rejected():
970 """Only Adaptive_RIFL is supported at the moment, no arrays."""
971 with pytest.raises(ValueError, match="QIR arrays are not currently supported"):
972 _run_pass(ADAPTIVE_RIFLA_QIR)
973