microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/add-link-to-qsharp-application

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/pip/tests/test_adaptive_cpu_bytecode.py

1760lines · modecode

1# Copyright (c) Microsoft Corporation.
2# Licensed under the MIT License.
3
4"""Per-opcode tests for the adaptive CPU bytecode interpreter.
5
6Each test targets one (or a small family of) bytecode instruction(s) by
7supplying hand-written Adaptive Profile QIR that exercises the instruction
8and encodes the expected result into a measurement outcome.
9
10Tests are ordered to match the opcode definitions in ``_adaptive_opcodes.py``
11so that coverage can be verified by reading both files side by side.
12
13This is a CPU counterpart to ``test_adaptive_gpu_bytecode.py``.
14"""
15
16from collections import Counter
17import pytest
18from qsharp._simulation import run_qir, NoiseConfig, Result
19import qsharp.openqasm
20from typing import Literal
21
22
23# ---------------------------------------------------------------------------
24# Helpers
25# ---------------------------------------------------------------------------
26
27# Deterministic programs need a single shot but we run multiple shots
28# to verify that multiple shots yield the same result.
29SHOTS = 100
30SIM_TYPES = ["cpu", "clifford"]
31
32
33def map_result_list_to_str(results):
34 results_str = ""
35 if isinstance(results, (list, tuple)):
36 for r in results:
37 results_str += map_result_list_to_str(r)
38 else:
39 match results:
40 case Result.Zero:
41 results_str += "0"
42 case Result.One:
43 results_str += "1"
44 case Result.Loss:
45 results_str += "L"
46 return results_str
47
48
49def _run(
50 qir: str,
51 shots: int = SHOTS,
52 seed: int = 42,
53 sim_type: Literal["clifford", "cpu"] = "cpu",
54):
55 """Run *qir* on the given simulator and return shot results as a list of strings."""
56 results = run_qir(qir, shots, seed=seed, type=sim_type)
57 return [map_result_list_to_str(r) for r in results]
58
59
60def check_result(
61 qir_fragment: str,
62 expected: str,
63 *,
64 extra_decls: str = "",
65 num_qubits: int = 1,
66 num_results: int = 1,
67 record=None,
68 sim_type: Literal["clifford", "cpu"] = "cpu",
69):
70 """Assert every shot produces *expected*."""
71 qir = format_qir(
72 qir_fragment,
73 extra_decls=extra_decls,
74 num_qubits=num_qubits,
75 num_results=num_results,
76 record=record,
77 )
78 results = _run(qir, SHOTS, sim_type=sim_type)
79 counts = Counter(results)
80 assert counts == {
81 expected: SHOTS
82 }, f"Expected all {SHOTS} shots to be '{expected}', got {counts}"
83
84
85def check_arith_result(
86 qir_fragment: str, expected: str, sim_type: Literal["clifford", "cpu"] = "cpu"
87):
88 body = build_arith_body(qir_fragment)
89 check_result(body, expected, sim_type=sim_type)
90
91
92_DECLS = """\
93declare void @__quantum__qis__x__body(%Qubit*)
94declare void @__quantum__qis__h__body(%Qubit*)
95declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*)
96declare void @__quantum__qis__mz__body(%Qubit*, %Result*) #1
97declare void @__quantum__qis__reset__body(%Qubit*)
98declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*)
99declare void @__quantum__qis__z__body(%Qubit*)
100declare void @__quantum__qis__s__body(%Qubit*)
101declare void @__quantum__qis__t__body(%Qubit*)
102declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*)
103declare void @__quantum__qis__rz__body(double, %Qubit*)
104declare i1 @__quantum__qis__read_result__body(%Result*)
105declare void @__quantum__rt__tuple_record_output(i64, i8*)
106declare void @__quantum__rt__result_record_output(%Result*, i8*)
107declare void @__quantum__rt__initialize(i8*)
108"""
109
110
111def format_qir(
112 body: str,
113 *,
114 extra_decls: str = "",
115 num_qubits: int = 1,
116 num_results: int = 1,
117 record=None,
118):
119 if record is None:
120 record = range(num_results)
121 output_recording = (
122 f" call void @__quantum__rt__tuple_record_output(i64 {len(record)}, i8* null)"
123 )
124 for result_id in record:
125 output_recording += f"\n call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 {result_id} to %Result*), i8* null)"
126
127 return f"""\
128%Result = type opaque
129%Qubit = type opaque
130
131define i64 @ENTRYPOINT__main() #0 {{
132{body}
133{output_recording}
134 ret i64 0
135}}
136
137{_DECLS}
138{extra_decls}
139attributes #0 = {{ "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="{num_qubits}" "required_num_results"="{num_results}" }}
140attributes #1 = {{ "irreversible" }}
141"""
142
143
144def build_arith_body(
145 arith_fragment: str,
146):
147 """Builds the body for a QIR module that does classical work and
148 then conditionally applies X to qubit 0 before measuring into result 0.
149
150 *arith_fragment* should produce ``%flag`` (i1) which, when true, causes X.
151 The measurement of qubit 0 into result 0 is the observable.
152 """
153 return f"""\
154entry:
155{arith_fragment}
156 br i1 %flag, label %then, label %end
157then:
158 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
159 br label %end
160end:
161 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
162"""
163
164
165# #########################################################################
166# Control Flow
167# #########################################################################
168
169
170# =========================================================================
171# OP_NOP — no-op
172# =========================================================================
173
174NOP_SMOKE_QIR = """
175entry:
176 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
177"""
178
179
180@pytest.mark.parametrize("sim_type", SIM_TYPES)
181def test_nop_smoke(sim_type):
182 """Minimal program: just measure |0⟩ → always 0."""
183 check_result(NOP_SMOKE_QIR, "0", sim_type=sim_type)
184
185
186# =========================================================================
187# OP_RET — return / program termination
188# =========================================================================
189
190RET_QIR = """
191entry:
192"""
193
194
195@pytest.mark.parametrize("sim_type", SIM_TYPES)
196def test_ret(sim_type):
197 check_result(RET_QIR, "", sim_type=sim_type, num_qubits=0, num_results=0)
198
199
200# =========================================================================
201# OP_JUMP — unconditional jump
202# =========================================================================
203
204JUMP_QIR = """
205entry:
206 br label %target
207 ret i64 0 ; early return - unreachable
208target:
209 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
210 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
211"""
212
213
214@pytest.mark.parametrize("sim_type", SIM_TYPES)
215def test_jump(sim_type):
216 """Unconditional jump lands at target block, X applied → measure 1."""
217 check_result(JUMP_QIR, "1", sim_type=sim_type)
218
219
220# =========================================================================
221# OP_BRANCH — conditional branch
222# =========================================================================
223
224BRANCH_TRUE_QIR = """
225entry:
226 %c = icmp eq i64 1, 1
227 br i1 %c, label %yes, label %no
228 ret i64 0 ; early return - unreachable
229yes:
230 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
231 br label %measure
232no:
233 br label %measure
234measure:
235 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
236"""
237
238BRANCH_FALSE_QIR = """
239entry:
240 %c = icmp eq i64 1, 2
241 br i1 %c, label %yes, label %no
242 ret i64 0 ; early return - unreachable
243yes:
244 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
245 br label %measure
246no:
247 br label %measure
248measure:
249 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
250"""
251
252
253@pytest.mark.parametrize("sim_type", SIM_TYPES)
254def test_branch_true(sim_type):
255 check_result(BRANCH_TRUE_QIR, "1", sim_type=sim_type)
256
257
258@pytest.mark.parametrize("sim_type", SIM_TYPES)
259def test_branch_false(sim_type):
260 check_result(BRANCH_FALSE_QIR, "0", sim_type=sim_type)
261
262
263# =========================================================================
264# OP_SWITCH — switch dispatch
265# =========================================================================
266
267SWITCH_CASE1_QIR = """
268entry:
269 %val = add i64 0, 1
270 switch i64 %val, label %default [
271 i64 0, label %case0
272 i64 1, label %case1
273 i64 2, label %case2
274 ]
275case0:
276 br label %measure
277case1:
278 ; This is the expected path for val==1
279 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
280 br label %measure
281case2:
282 br label %measure
283default:
284 br label %measure
285measure:
286 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
287"""
288
289SWITCH_DEFAULT_QIR = """
290entry:
291 %val = add i64 0, 99
292 switch i64 %val, label %default [
293 i64 0, label %case0
294 i64 1, label %case1
295 ]
296case0:
297 br label %measure
298case1:
299 br label %measure
300default:
301 ; val=99 takes default path → X applied
302 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
303 br label %measure
304measure:
305 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
306"""
307
308
309@pytest.mark.parametrize("sim_type", SIM_TYPES)
310def test_switch_case(sim_type):
311 check_result(SWITCH_CASE1_QIR, "1", sim_type=sim_type)
312
313
314@pytest.mark.parametrize("sim_type", SIM_TYPES)
315def test_switch_default(sim_type):
316 check_result(SWITCH_DEFAULT_QIR, "1", sim_type=sim_type)
317
318
319# =========================================================================
320# OP_CALL / OP_CALL_RETURN — function calls
321# =========================================================================
322
323CALL_QIR = """
324entry:
325 call void @apply_x(%Qubit* inttoptr (i64 0 to %Qubit*))
326 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
327"""
328
329CALL_QIR_FN = """
330define void @apply_x(%Qubit* %q) {
331entry:
332 call void @__quantum__qis__x__body(%Qubit* %q)
333 ret void
334}
335"""
336
337
338@pytest.mark.parametrize("sim_type", SIM_TYPES)
339def test_call_and_return(sim_type):
340 """Call a helper function that applies X, then measure."""
341 check_result(CALL_QIR, "1", extra_decls=CALL_QIR_FN, sim_type=sim_type)
342
343
344# #########################################################################
345# Quantum
346# #########################################################################
347
348
349# =========================================================================
350# OP_QUANTUM_GATE — single and two-qubit gates
351# =========================================================================
352
353GATE_X_QIR = """
354entry:
355 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
356 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
357"""
358
359GATE_CNOT_QIR = """
360entry:
361 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
362 call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*))
363 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
364"""
365
366
367@pytest.mark.parametrize("sim_type", SIM_TYPES)
368def test_gate_x(sim_type):
369 check_result(GATE_X_QIR, "1", sim_type=sim_type)
370
371
372@pytest.mark.parametrize("sim_type", SIM_TYPES)
373def test_gate_cnot(sim_type):
374 check_result(GATE_CNOT_QIR, "1", num_qubits=2, sim_type=sim_type)
375
376
377# =========================================================================
378# OP_MEASURE — measurement (also see OP_READ_RESULT below)
379# =========================================================================
380
381MZ_THEN_RESET_QIR = """
382entry:
383 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
384 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
385 ; After mz, qubit should still be |1⟩
386 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
387 call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
388 ; After reset, qubit should be |0⟩
389 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 2 to %Result*))
390"""
391
392
393@pytest.mark.parametrize("sim_type", SIM_TYPES)
394def test_mz_then_reset(sim_type):
395 "X → MZ → MZ → reset should give 110."
396 check_result(MZ_THEN_RESET_QIR, "110", num_results=3, sim_type=sim_type)
397
398
399# =========================================================================
400# OP_RESET — qubit reset
401# =========================================================================
402
403RESET_QIR = """
404entry:
405 ; Put qubit 0 in |1⟩
406 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
407 ; Reset it back to |0⟩
408 call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
409 ; Measure — should be 0
410 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
411"""
412
413
414@pytest.mark.parametrize("sim_type", SIM_TYPES)
415def test_reset(sim_type):
416 """X → reset → measure should give 0."""
417 check_result(RESET_QIR, "0", sim_type=sim_type)
418
419
420# =========================================================================
421# OP_READ_RESULT + OP_MEASURE — read measurement results
422# =========================================================================
423
424READ_RESULT_QIR = """
425entry:
426 ; Prepare |1⟩ on qubit 0 via X
427 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
428 ; Measure qubit 0 → should always be 1
429 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
430 ; Read back the result
431 %r = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
432 ; If result was 1, apply X again so qubit is back in |1⟩ for second measurement
433 br i1 %r, label %then, label %end
434
435then:
436 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
437 br label %end
438
439end:
440 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
441"""
442
443
444@pytest.mark.parametrize("sim_type", SIM_TYPES)
445def test_read_result(sim_type):
446 """X → MResetZ → read_result → if 1: X again → MResetZ.
447 First result is always 1, read_result sees it, applies X, second result is also 1.
448 """
449 check_result(READ_RESULT_QIR, "11", num_results=2, sim_type=sim_type)
450
451
452# =========================================================================
453# OP_RECORD_OUTPUT — output recording
454# =========================================================================
455
456RECORD_OUTPUT_QIR = """
457entry:
458 ; q0 = |1⟩, q1 = |0⟩
459 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
460 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
461 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
462"""
463
464
465@pytest.mark.parametrize("sim_type", SIM_TYPES)
466def test_record_output_ordering(sim_type):
467 """Two results recorded: result0=1, result1=0 → '10'."""
468 check_result(
469 RECORD_OUTPUT_QIR, "10", num_qubits=2, num_results=2, sim_type=sim_type
470 )
471
472
473# =========================================================================
474# OP_READ_LOSS — read whether a measurement observed qubit loss
475# =========================================================================
476
477READ_LOSS_QIR = """
478entry:
479 ; Apply s to qubit 0 purely for its noise side effect. With
480 ; ``noise.s.loss = 1.0`` the simulator faults qubit 0 as lost on every
481 ; shot, so the next mz on qubit 0 records ``MeasurementResult::Loss``
482 ; into result 0. Qubit 1 is left untouched (no noise on x), so the
483 ; conditional X below cleanly flips it to |1⟩.
484 call void @__quantum__qis__s__body(%Qubit* inttoptr (i64 0 to %Qubit*))
485 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
486 ; Read the loss bit for result 0 — should be 1 because the qubit was lost.
487 %lost = call i1 @__quantum__rt__read_loss(%Result* inttoptr (i64 0 to %Result*))
488 br i1 %lost, label %then, label %end
489
490then:
491 ; Witness: if read_loss reported true, flip qubit 1 to |1⟩.
492 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*))
493 br label %end
494
495end:
496 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
497"""
498
499READ_LOSS_DECLS = """
500declare i1 @__quantum__rt__read_loss(%Result*)
501"""
502
503
504@pytest.mark.parametrize("sim_type", SIM_TYPES)
505def test_read_loss(sim_type):
506 """rz (with 100% loss) → mz → read_loss → branch on loss → mz witness.
507
508 Record both results: result 0 should always be ``Loss`` ('L'), and
509 result 1 should always be ``One`` ('1') because ``read_loss`` saw the
510 loss and the conditional X was applied to qubit 1.
511 """
512 qir = format_qir(
513 READ_LOSS_QIR,
514 extra_decls=READ_LOSS_DECLS,
515 num_qubits=2,
516 num_results=2,
517 )
518 noise = NoiseConfig()
519 noise.s.loss = 1.0
520 results = run_qir(qir, SHOTS, noise, seed=42, type=sim_type)
521 counts = Counter(map_result_list_to_str(r) for r in results)
522 assert counts == {
523 "L1": SHOTS
524 }, f"Expected all {SHOTS} shots to be 'L1', got {counts}"
525
526
527# =========================================================================
528# move (OpID 28) — qubit move with associated noise
529# =========================================================================
530
531MOVE_QIR = """
532entry:
533 ; ``move`` is a no-op on the simulator state, but the simulator applies
534 ; the configured ``noise.mov`` faults to the moved qubit. With
535 ; ``noise.mov.x = 1.0`` every move flips the qubit, so q0 ends in |1⟩.
536 call void @__quantum__qis__move__body(%Qubit* inttoptr (i64 0 to %Qubit*), i64 0, i64 0)
537 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
538"""
539
540MOVE_DECLS = """\
541declare void @__quantum__qis__move__body(%Qubit*, i64, i64)
542"""
543
544
545@pytest.mark.parametrize("sim_type", SIM_TYPES)
546def test_move_applies_noise(sim_type):
547 """move (with 100% X noise) → mz → always 1."""
548 qir = format_qir(MOVE_QIR, extra_decls=MOVE_DECLS, num_qubits=1, num_results=1)
549 noise = NoiseConfig()
550 noise.mov.x = 1.0
551 results = run_qir(qir, SHOTS, noise, seed=42, type=sim_type)
552 counts = Counter(map_result_list_to_str(r) for r in results)
553 assert counts == {"1": SHOTS}, f"Expected all {SHOTS} shots to be '1', got {counts}"
554
555
556@pytest.mark.parametrize("sim_type", SIM_TYPES)
557def test_move_noiseless_is_noop(sim_type):
558 """move without noise is a pure no-op → q0 stays in |0⟩ → measure 0."""
559 check_result(
560 MOVE_QIR,
561 "0",
562 extra_decls=MOVE_DECLS,
563 sim_type=sim_type,
564 )
565
566
567# #########################################################################
568# Integer Arithmetic
569# #########################################################################
570
571INT_ARITH_PARAMS = [
572 # Int
573 ("add", 3, 4, 7),
574 ("sub", 10, 3, 7),
575 ("sub", 3, 10, -7),
576 ("mul", 6, 7, 42),
577 ("udiv", 42, 7, 6),
578 ("sdiv", -42, 7, -6),
579 ("urem", 10, 3, 1),
580 ("srem", -10, 3, -1),
581 # Bitwise
582 ("and", 255, 15, 15),
583 ("or", 240, 15, 255),
584 ("xor", 255, 15, 240),
585 ("shl", 1, 3, 8),
586 ("lshr", 32, 2, 8),
587 ("ashr", -16, 2, -4),
588]
589
590
591@pytest.mark.parametrize("sim_type", SIM_TYPES)
592@pytest.mark.parametrize(
593 "bin_op,lhs,rhs,expected",
594 INT_ARITH_PARAMS,
595)
596def test_int_arith_imm_imm(sim_type, bin_op, lhs, rhs, expected):
597 check_arith_result(
598 f"""
599 %a = {bin_op} i64 {lhs}, {rhs}
600 %flag = icmp eq i64 %a, {expected}""",
601 "1",
602 sim_type=sim_type,
603 )
604
605
606@pytest.mark.parametrize("sim_type", SIM_TYPES)
607@pytest.mark.parametrize(
608 "bin_op,lhs,rhs,expected",
609 INT_ARITH_PARAMS,
610)
611def test_int_arith_imm_reg(sim_type, bin_op, lhs, rhs, expected):
612 check_arith_result(
613 f"""
614 %rhs = add i64 {rhs}, 0
615 %a = {bin_op} i64 {lhs}, %rhs
616 %flag = icmp eq i64 %a, {expected}""",
617 "1",
618 sim_type=sim_type,
619 )
620
621
622@pytest.mark.parametrize("sim_type", SIM_TYPES)
623@pytest.mark.parametrize(
624 "bin_op,lhs,rhs,expected",
625 INT_ARITH_PARAMS,
626)
627def test_int_arith_reg_imm(sim_type, bin_op, lhs, rhs, expected):
628 check_arith_result(
629 f"""
630 %lhs = add i64 {lhs}, 0
631 %a = {bin_op} i64 %lhs, {rhs}
632 %flag = icmp eq i64 %a, {expected}""",
633 "1",
634 sim_type=sim_type,
635 )
636
637
638@pytest.mark.parametrize("sim_type", SIM_TYPES)
639@pytest.mark.parametrize(
640 "bin_op,lhs,rhs,expected",
641 INT_ARITH_PARAMS,
642)
643def test_int_arith_reg_reg(sim_type, bin_op, lhs, rhs, expected):
644 check_arith_result(
645 f"""
646 %lhs = add i64 {lhs}, 0
647 %rhs = add i64 {rhs}, 0
648 %a = {bin_op} i64 %lhs, %rhs
649 %flag = icmp eq i64 %a, {expected}""",
650 "1",
651 sim_type=sim_type,
652 )
653
654
655@pytest.mark.parametrize("sim_type", SIM_TYPES)
656@pytest.mark.parametrize(
657 "bin_op,lhs,rhs,expected",
658 INT_ARITH_PARAMS,
659)
660def test_int_arith_negative_test(sim_type, bin_op, lhs, rhs, expected):
661 """Checks that the tests fail if the result is different from the expected value."""
662 expected = 12345
663 check_arith_result(
664 f"""
665 %a = {bin_op} i64 {lhs}, {rhs}
666 %flag = icmp eq i64 %a, {expected}""",
667 "0",
668 sim_type=sim_type,
669 )
670
671
672# #########################################################################
673# Comparison (OP_ICMP, OP_FCMP)
674# #########################################################################
675
676
677# =========================================================================
678# OP_ICMP — integer comparison (all condition codes)
679# =========================================================================
680
681
682@pytest.mark.parametrize("sim_type", SIM_TYPES)
683@pytest.mark.parametrize(
684 "pred,lhs,rhs,expected",
685 [
686 ("eq", 2, 2, "1"),
687 ("eq", 2, 3, "0"),
688 ("ne", 2, 3, "1"),
689 ("ne", 2, 2, "0"),
690 ("slt", 2, 3, "1"),
691 ("slt", 2, 2, "0"),
692 ("sle", 2, 2, "1"),
693 ("sle", 3, 2, "0"),
694 ("sgt", 3, 2, "1"),
695 ("sgt", 2, 3, "0"),
696 ("sge", 3, 3, "1"),
697 ("sge", 2, 3, "0"),
698 ("ult", 2, 3, "1"),
699 ("ult", 3, 2, "0"),
700 ("ule", 3, 3, "1"),
701 ("ule", 3, 2, "0"),
702 ("ugt", 3, 2, "1"),
703 ("ugt", 2, 3, "0"),
704 ("uge", 3, 3, "1"),
705 ("uge", 2, 3, "0"),
706 ],
707)
708def test_icmp(sim_type, pred, lhs, rhs, expected):
709 check_arith_result(
710 f"%flag = icmp {pred} i64 {lhs}, {rhs}",
711 expected,
712 sim_type=sim_type,
713 )
714
715
716# =========================================================================
717# OP_ICMP — signed vs unsigned edge case (negative as unsigned)
718# =========================================================================
719
720ICMP_SIGNED_VS_UNSIGNED_QIR = """
721 ; -1 in two's complement is 0xFFFFFFFFFFFFFFFF, which is the max u64
722 ; signed: -1 < 0 → true
723 %neg1 = sub i64 0, 1
724 %flag = icmp slt i64 %neg1, 0
725"""
726
727ICMP_UNSIGNED_WRAP_QIR = """
728 ; unsigned: -1 wraps to max u64, so -1 > 0 → true (unsigned)
729 %neg1 = sub i64 0, 1
730 %flag = icmp ugt i64 %neg1, 0
731"""
732
733
734@pytest.mark.parametrize("sim_type", SIM_TYPES)
735def test_icmp_signed_negative(sim_type):
736 check_arith_result(ICMP_SIGNED_VS_UNSIGNED_QIR, "1", sim_type=sim_type)
737
738
739@pytest.mark.parametrize("sim_type", SIM_TYPES)
740def test_icmp_unsigned_wrap(sim_type):
741 check_arith_result(ICMP_UNSIGNED_WRAP_QIR, "1", sim_type=sim_type)
742
743
744# =========================================================================
745# OP_FCMP — float comparison
746# =========================================================================
747
748
749@pytest.mark.parametrize("sim_type", SIM_TYPES)
750@pytest.mark.parametrize(
751 "pred,lhs,rhs,expected",
752 [
753 ("oeq", "3.0", "3.0", "1"),
754 ("oeq", "3.0", "4.0", "0"),
755 ("one", "3.0", "4.0", "1"),
756 ("one", "3.0", "3.0", "0"),
757 ("olt", "2.0", "3.0", "1"),
758 ("olt", "3.0", "2.0", "0"),
759 ("ole", "3.0", "3.0", "1"),
760 ("ole", "4.0", "3.0", "0"),
761 ("ogt", "4.0", "3.0", "1"),
762 ("ogt", "3.0", "4.0", "0"),
763 ("oge", "3.0", "3.0", "1"),
764 ("oge", "2.0", "3.0", "0"),
765 ],
766)
767def test_fcmp(sim_type, pred, lhs, rhs, expected):
768 check_arith_result(
769 f"%flag = fcmp {pred} double {lhs}, {rhs}",
770 expected,
771 sim_type=sim_type,
772 )
773
774
775# #########################################################################
776# Float Arithmetic (OP_FADD → OP_FDIV)
777# #########################################################################
778
779FLOAT_ARITH_PARAMS = [
780 ("fadd", 1.5, 2.5, 4.0),
781 ("fsub", 10.0, 3.0, 7.0),
782 ("fsub", 3.0, 10.0, -7.0),
783 ("fmul", 6.0, 7.0, 42.0),
784 ("fdiv", 8.0, 2.0, 4.0),
785]
786
787
788@pytest.mark.parametrize("sim_type", SIM_TYPES)
789@pytest.mark.parametrize(
790 "bin_op,lhs,rhs,expected",
791 FLOAT_ARITH_PARAMS,
792)
793def test_float_arith_imm_imm(sim_type, bin_op, lhs, rhs, expected):
794 check_arith_result(
795 f"""
796 %a = {bin_op} double {lhs}, {rhs}
797 %flag = fcmp oeq double %a, {expected}""",
798 "1",
799 sim_type=sim_type,
800 )
801
802
803@pytest.mark.parametrize("sim_type", SIM_TYPES)
804@pytest.mark.parametrize(
805 "bin_op,lhs,rhs,expected",
806 FLOAT_ARITH_PARAMS,
807)
808def test_float_arith_imm_reg(sim_type, bin_op, lhs, rhs, expected):
809 check_arith_result(
810 f"""
811 %rhs = fadd double {rhs}, 0.0
812 %a = {bin_op} double {lhs}, %rhs
813 %flag = fcmp oeq double %a, {expected}""",
814 "1",
815 sim_type=sim_type,
816 )
817
818
819@pytest.mark.parametrize("sim_type", SIM_TYPES)
820@pytest.mark.parametrize(
821 "bin_op,lhs,rhs,expected",
822 FLOAT_ARITH_PARAMS,
823)
824def test_float_arith_reg_imm(sim_type, bin_op, lhs, rhs, expected):
825 check_arith_result(
826 f"""
827 %lhs = fadd double {lhs}, 0.0
828 %a = {bin_op} double %lhs, {rhs}
829 %flag = fcmp oeq double %a, {expected}""",
830 "1",
831 sim_type=sim_type,
832 )
833
834
835@pytest.mark.parametrize("sim_type", SIM_TYPES)
836@pytest.mark.parametrize(
837 "bin_op,lhs,rhs,expected",
838 FLOAT_ARITH_PARAMS,
839)
840def test_float_arith_reg_reg(sim_type, bin_op, lhs, rhs, expected):
841 check_arith_result(
842 f"""
843 %lhs = fadd double {lhs}, 0.0
844 %rhs = fadd double {rhs}, 0.0
845 %a = {bin_op} double %lhs, %rhs
846 %flag = fcmp oeq double %a, {expected}""",
847 "1",
848 sim_type=sim_type,
849 )
850
851
852@pytest.mark.parametrize("sim_type", SIM_TYPES)
853@pytest.mark.parametrize(
854 "bin_op,lhs,rhs,expected",
855 FLOAT_ARITH_PARAMS,
856)
857def test_float_arith_negative_test(sim_type, bin_op, lhs, rhs, expected):
858 """Checks that the tests fail if the result is different from the expected value."""
859 expected = 12345.0
860 check_arith_result(
861 f"""
862 %a = {bin_op} double {lhs}, {rhs}
863 %flag = fcmp oeq double %a, {expected}""",
864 "0",
865 sim_type=sim_type,
866 )
867
868
869# #########################################################################
870# Type Conversion (OP_ZEXT → OP_SITOFP)
871# #########################################################################
872
873
874# =========================================================================
875# OP_ZEXT — zero extension
876# =========================================================================
877
878ZEXT_QIR = """
879 ; zext i1 true to i64 → 1, check 1 == 1 → true
880 %z = zext i1 true to i64
881 %flag = icmp eq i64 %z, 1
882"""
883
884
885@pytest.mark.parametrize("sim_type", SIM_TYPES)
886def test_zext(sim_type):
887 check_arith_result(ZEXT_QIR, "1", sim_type=sim_type)
888
889
890# =========================================================================
891# OP_SEXT — sign extension
892# =========================================================================
893
894SEXT_QIR = """
895 ; sext i1 true to i64 → -1 (all ones), check -1 < 0 → true
896 %s = sext i1 true to i64
897 %flag = icmp eq i64 %s, -1
898"""
899
900
901@pytest.mark.parametrize("sim_type", SIM_TYPES)
902def test_sext(sim_type):
903 check_arith_result(SEXT_QIR, "1", sim_type=sim_type)
904
905
906# =========================================================================
907# OP_TRUNC — truncation
908# =========================================================================
909
910TRUNC_QIR = """
911 ; trunc i64 257 to i32 → 257 (fits), check 257 == 257 → true
912 %t = trunc i64 257 to i32
913 %z = zext i32 %t to i64
914 %flag = icmp eq i64 %z, 257
915"""
916
917
918@pytest.mark.parametrize("sim_type", SIM_TYPES)
919def test_trunc(sim_type):
920 check_arith_result(TRUNC_QIR, "1", sim_type=sim_type)
921
922
923# =========================================================================
924# OP_FPEXT / OP_FPTRUNC — float extension/truncation
925# =========================================================================
926
927FPEXT_QIR = """
928 ; fpext float 3.0 to double, then check == 3
929 %f32 = fadd float 1.0, 2.0
930 %f64 = fpext float %f32 to double
931 %i = fptosi double %f64 to i64
932 %flag = icmp eq i64 %i, 3
933"""
934
935
936@pytest.mark.parametrize("sim_type", SIM_TYPES)
937def test_fpext(sim_type):
938 check_arith_result(FPEXT_QIR, "1", sim_type=sim_type)
939
940
941# =========================================================================
942# OP_INTTOPTR / OP_MOV — dynamic qubit addressing
943# =========================================================================
944
945INTTOPTR_QIR = """
946entry:
947 ; Compute qubit ID 0 from arithmetic
948 %q_id = sub i64 1, 1
949 %q = inttoptr i64 %q_id to %Qubit*
950 call void @__quantum__qis__x__body(%Qubit* %q)
951 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
952"""
953
954
955@pytest.mark.parametrize("sim_type", SIM_TYPES)
956def test_inttoptr_dynamic_qubit(sim_type):
957 check_result(INTTOPTR_QIR, "1", sim_type=sim_type)
958
959
960# =========================================================================
961# OP_FPTOSI — float to signed int
962# =========================================================================
963
964FPTOSI_QIR = """
965 ; fptosi -3.7 → -3 (truncation toward zero), check -3 < 0 → true
966 %neg = fsub double 0.0, 3.7
967 %i = fptosi double %neg to i64
968 %flag = icmp slt i64 %i, 0
969"""
970
971
972@pytest.mark.parametrize("sim_type", SIM_TYPES)
973def test_fptosi(sim_type):
974 check_arith_result(FPTOSI_QIR, "1", sim_type=sim_type)
975
976
977# =========================================================================
978# OP_SITOFP — signed int to float
979# =========================================================================
980
981SITOFP_QIR = """
982 ; sitofp -5 → -5.0, then -5.0 < 0.0 → true
983 %neg5 = sub i64 0, 5
984 %f = sitofp i64 %neg5 to double
985 %zero = sitofp i64 0 to double
986 %flag = fcmp olt double %f, %zero
987"""
988
989
990@pytest.mark.parametrize("sim_type", SIM_TYPES)
991def test_sitofp(sim_type):
992 check_arith_result(SITOFP_QIR, "1", sim_type=sim_type)
993
994
995# #########################################################################
996# SSA / Data Movement (OP_PHI → OP_CONST)
997# #########################################################################
998
999
1000# =========================================================================
1001# OP_PHI — phi node
1002# =========================================================================
1003
1004PHI_LOOP_QIR = """
1005entry:
1006 br label %loop
1007
1008loop:
1009 %i = phi i64 [ 0, %entry ], [ %next, %loop ]
1010 %next = add i64 %i, 1
1011 %cond = icmp slt i64 %next, 5
1012 br i1 %cond, label %loop, label %done
1013
1014done:
1015 ; %next should be 5 here
1016 %flag = icmp eq i64 %next, 5
1017 br i1 %flag, label %apply_x, label %measure
1018
1019apply_x:
1020 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1021 br label %measure
1022
1023measure:
1024 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
1025"""
1026
1027
1028@pytest.mark.parametrize("sim_type", SIM_TYPES)
1029def test_phi_loop_counter(sim_type):
1030 check_result(PHI_LOOP_QIR, "1", sim_type=sim_type)
1031
1032
1033# =========================================================================
1034# OP_SELECT
1035# =========================================================================
1036
1037SELECT_TRUE_QIR = """
1038 ; select i1 true, i64 1, i64 0 → 1, then icmp eq 1, 1 → true
1039 %s = select i1 true, i64 1, i64 0
1040 %flag = icmp eq i64 %s, 1
1041"""
1042
1043SELECT_FALSE_QIR = """
1044 ; select i1 false, i64 1, i64 0 → 0, then icmp eq 0, 0 → true
1045 %s = select i1 false, i64 1, i64 0
1046 %flag = icmp eq i64 %s, 0
1047"""
1048
1049
1050@pytest.mark.parametrize("sim_type", SIM_TYPES)
1051def test_select_true(sim_type):
1052 check_arith_result(SELECT_TRUE_QIR, "1", sim_type=sim_type)
1053
1054
1055@pytest.mark.parametrize("sim_type", SIM_TYPES)
1056def test_select_false(sim_type):
1057 check_arith_result(SELECT_FALSE_QIR, "1", sim_type=sim_type)
1058
1059
1060# =========================================================================
1061# OP_CONST — constant materialization
1062# =========================================================================
1063
1064CONST_QIR = """
1065 ; Use a specific constant 12345, check add identity
1066 %a = add i64 12345, 0
1067 %flag = icmp eq i64 %a, 12345
1068"""
1069
1070
1071@pytest.mark.parametrize("sim_type", SIM_TYPES)
1072def test_const(sim_type):
1073 check_arith_result(CONST_QIR, "1", sim_type=sim_type)
1074
1075
1076# #########################################################################
1077# Boolean (i1) variants of bitwise ops
1078# #########################################################################
1079
1080
1081# =========================================================================
1082# OP_AND with i1 (boolean AND) — used in classical boolean logic
1083# =========================================================================
1084
1085AND_I1_QIR = """
1086entry:
1087 ; Prepare both qubits in |1⟩ deterministically
1088 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1089 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*))
1090 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
1091 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
1092 %r0 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
1093 %r1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
1094 %both = and i1 %r0, %r1
1095 ; both should be true (1 AND 1 = 1), apply X → measure 1
1096 br i1 %both, label %then, label %measure
1097
1098then:
1099 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1100 br label %measure
1101
1102measure:
1103 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 2 to %Result*))
1104"""
1105
1106
1107@pytest.mark.parametrize("sim_type", SIM_TYPES)
1108def test_and_i1_boolean(sim_type):
1109 """Deterministic boolean AND: both qubits |1⟩ → and i1 true, true → X → 1."""
1110 check_result(
1111 AND_I1_QIR, "1", num_qubits=2, num_results=3, record=[2], sim_type=sim_type
1112 )
1113
1114
1115# =========================================================================
1116# OP_OR with i1 (boolean OR)
1117# =========================================================================
1118
1119OR_I1_QIR = """
1120entry:
1121 ; q0 = |1⟩, q1 = |0⟩
1122 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1123 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
1124 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
1125 %r0 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
1126 %r1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
1127 %either = or i1 %r0, %r1
1128 ; true OR false = true → X → measure 1
1129 br i1 %either, label %then, label %measure
1130then:
1131 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1132 br label %measure
1133measure:
1134 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 2 to %Result*))
1135"""
1136
1137
1138@pytest.mark.parametrize("sim_type", SIM_TYPES)
1139def test_or_i1_boolean(sim_type):
1140 """Deterministic boolean OR: q0=1, q1=0 → or i1 true, false → true → X → 1."""
1141 check_result(
1142 OR_I1_QIR, "1", num_qubits=2, num_results=3, record=[2], sim_type=sim_type
1143 )
1144
1145
1146# =========================================================================
1147# OP_XOR with i1 (boolean XOR / NOT)
1148# =========================================================================
1149
1150XOR_NOT_QIR = """
1151entry:
1152 ; q0 = |0⟩ → measure 0
1153 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
1154 %r0 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
1155 ; XOR with true is NOT: false XOR true = true
1156 %not_r0 = xor i1 %r0, true
1157 br i1 %not_r0, label %then, label %measure
1158
1159then:
1160 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1161 br label %measure
1162
1163measure:
1164 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
1165"""
1166
1167
1168@pytest.mark.parametrize("sim_type", SIM_TYPES)
1169def test_xor_i1_not(sim_type):
1170 """XOR i1 used as NOT: measure 0 → XOR true → true → X → 1."""
1171 check_result(
1172 XOR_NOT_QIR, "1", num_qubits=1, num_results=2, record=[1], sim_type=sim_type
1173 )
1174
1175
1176# #########################################################################
1177# Compound / Integration Tests
1178# #########################################################################
1179
1180
1181# =========================================================================
1182# Chained arithmetic — complex expression
1183# =========================================================================
1184
1185CHAINED_ARITH_QIR = """
1186 ; (3 + 4) * 2 - 1 = 13, check 13 == 13 → true
1187 %a = add i64 3, 4
1188 %b = mul i64 %a, 2
1189 %c = sub i64 %b, 1
1190 %flag = icmp eq i64 %c, 13
1191"""
1192
1193
1194@pytest.mark.parametrize("sim_type", SIM_TYPES)
1195def test_chained_arithmetic(sim_type):
1196 check_arith_result(CHAINED_ARITH_QIR, "1", sim_type=sim_type)
1197
1198
1199# =========================================================================
1200# OP_PHI with multiple predecessors (diamond CFG)
1201# =========================================================================
1202
1203PHI_DIAMOND_QIR = """
1204entry:
1205 %c = icmp eq i64 1, 1
1206 br i1 %c, label %left, label %right
1207left:
1208 br label %merge
1209right:
1210 br label %merge
1211merge:
1212 ; From left: 42, from right: 0. Since condition is true, we go left → 42.
1213 %v = phi i64 [ 42, %left ], [ 0, %right ]
1214 %flag = icmp eq i64 %v, 42
1215 br i1 %flag, label %apply_x, label %measure
1216apply_x:
1217 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1218 br label %measure
1219measure:
1220 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
1221"""
1222
1223
1224@pytest.mark.parametrize("sim_type", SIM_TYPES)
1225def test_phi_diamond(sim_type):
1226 """Diamond CFG with phi: true branch → phi resolves to 42 → X → 1."""
1227 check_result(PHI_DIAMOND_QIR, "1", sim_type=sim_type)
1228
1229
1230# =========================================================================
1231# OP_SELECT with computed condition
1232# =========================================================================
1233
1234SELECT_COMPUTED_QIR = """
1235 ; 5 > 3 is true → select returns 10, check 10 == 10 → true
1236 %cmp = icmp sgt i64 5, 3
1237 %s = select i1 %cmp, i64 10, i64 20
1238 %flag = icmp eq i64 %s, 10
1239"""
1240
1241
1242@pytest.mark.parametrize("sim_type", SIM_TYPES)
1243def test_select_computed(sim_type):
1244 check_arith_result(SELECT_COMPUTED_QIR, "1", sim_type=sim_type)
1245
1246
1247# =========================================================================
1248# Nested loop — OP_PHI + OP_BRANCH + OP_ADD + OP_ICMP combined
1249# =========================================================================
1250
1251NESTED_LOOP_SUM_QIR = """
1252entry:
1253 br label %loop
1254loop:
1255 %i = phi i64 [ 1, %entry ], [ %next_i, %loop ]
1256 %sum = phi i64 [ 0, %entry ], [ %next_sum, %loop ]
1257 %next_sum = add i64 %sum, %i
1258 %next_i = add i64 %i, 1
1259 %cond = icmp sle i64 %next_i, 5
1260 br i1 %cond, label %loop, label %done
1261done:
1262 ; %next_sum should be 15
1263 %flag = icmp eq i64 %next_sum, 15
1264 br i1 %flag, label %apply_x, label %measure
1265apply_x:
1266 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1267 br label %measure
1268measure:
1269 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
1270"""
1271
1272
1273@pytest.mark.parametrize("sim_type", SIM_TYPES)
1274def test_nested_loop_sum(sim_type):
1275 """Sum 1..5 using phi loop, check total == 15."""
1276 check_result(NESTED_LOOP_SUM_QIR, "1", sim_type=sim_type)
1277
1278
1279# =========================================================================
1280# OP_QUANTUM_GATE — dynamic qubit addressing in a loop (GHZ-like)
1281# =========================================================================
1282
1283DYNAMIC_QUBIT_LOOP_QIR = """
1284entry:
1285 ; Create |+⟩ on q0
1286 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1287 br label %loop
1288loop:
1289 %i = phi i64 [ 1, %entry ], [ %next_i, %loop ]
1290 %qi = inttoptr i64 %i to %Qubit*
1291 call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* %qi)
1292 %next_i = add i64 %i, 1
1293 %cond = icmp sle i64 %next_i, 2
1294 br i1 %cond, label %loop, label %measure
1295measure:
1296 ; Measure all 3 qubits — GHZ state means all agree
1297 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
1298 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
1299 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*))
1300"""
1301
1302
1303@pytest.mark.parametrize("sim_type", SIM_TYPES)
1304def test_dynamic_qubit_loop(sim_type):
1305 """3-qubit GHZ via dynamic qubit loop — only '000' and '111' should appear."""
1306 qir = format_qir(DYNAMIC_QUBIT_LOOP_QIR, num_qubits=3, num_results=3)
1307 results = _run(qir, shots=5000, seed=42, sim_type=sim_type)
1308 counts = Counter(results)
1309 assert set(counts.keys()) <= {"000", "111"}, f"Unexpected GHZ outcomes: {counts}"
1310 assert counts.get("000", 0) > 1500
1311 assert counts.get("111", 0) > 1500
1312
1313
1314# =========================================================================
1315# OP_SHL + OP_OR combined — bit packing
1316# =========================================================================
1317
1318BIT_PACK_QIR = """
1319 ; Pack bits: (1 << 2) | 1 = 5, check 5 == 5 → true
1320 %shifted = shl i64 1, 2
1321 %packed = or i64 %shifted, 1
1322 %flag = icmp eq i64 %packed, 5
1323"""
1324
1325
1326@pytest.mark.parametrize("sim_type", SIM_TYPES)
1327def test_bit_packing(sim_type):
1328 check_arith_result(BIT_PACK_QIR, "1", sim_type=sim_type)
1329
1330
1331# =========================================================================
1332# Combined test: all shift and bitwise ops in sequence
1333# =========================================================================
1334
1335SHIFT_BITWISE_CHAIN_QIR = """
1336 ; Start with 0b1010 = 10
1337 ; SHL by 1 → 0b10100 = 20
1338 ; OR with 0b00011 = 3 → 0b10111 = 23
1339 ; AND with 0b11110 = 30 → 0b10110 = 22
1340 ; XOR with 0b00010 = 2 → 0b10100 = 20
1341 ; LSHR by 2 → 0b00101 = 5
1342 %step1 = shl i64 10, 1
1343 %step2 = or i64 %step1, 3
1344 %step3 = and i64 %step2, 30
1345 %step4 = xor i64 %step3, 2
1346 %step5 = lshr i64 %step4, 2
1347 %flag = icmp eq i64 %step5, 5
1348"""
1349
1350
1351@pytest.mark.parametrize("sim_type", SIM_TYPES)
1352def test_shift_bitwise_chain(sim_type):
1353 check_arith_result(SHIFT_BITWISE_CHAIN_QIR, "1", sim_type=sim_type)
1354
1355
1356# #########################################################################
1357# Structured Output Recording
1358# #########################################################################
1359
1360
1361NESTED_OUTPUT_QIR = """\
1362%Result = type opaque
1363%Qubit = type opaque
1364
1365define i64 @ENTRYPOINT__main() #0 {
1366 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*))
1367 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 3 to %Qubit*))
1368 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
1369 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
1370 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*))
1371 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 3 to %Result*))
1372 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
1373 call void @__quantum__rt__array_record_output(i64 2, i8* null)
1374 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
1375 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
1376 call void @__quantum__rt__array_record_output(i64 2, i8* null)
1377 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null)
1378 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null)
1379 ret i64 0
1380}
1381
1382declare void @__quantum__qis__x__body(%Qubit*)
1383declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*)
1384declare void @__quantum__rt__tuple_record_output(i64, i8*)
1385declare void @__quantum__rt__array_record_output(i64, i8*)
1386declare void @__quantum__rt__result_record_output(%Result*, i8*)
1387
1388attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="4" "required_num_results"="4" }
1389"""
1390
1391
1392@pytest.mark.parametrize("sim_type", SIM_TYPES)
1393def test_nested_output_structure(sim_type):
1394 """Verify that adaptive results preserve nested tuple/array structure.
1395
1396 The QIR records output as a tuple of two arrays: ([r0, r1], [r2, r3]).
1397 Before the fix, run_adaptive flattened this into [r0, r1, r2, r3].
1398 """
1399 results = run_qir(NESTED_OUTPUT_QIR, shots=10, seed=42, type=sim_type)
1400 for shot in results:
1401 assert isinstance(shot, tuple), f"Expected tuple, got {type(shot)}: {shot}"
1402 assert len(shot) == 2, f"Expected 2-element tuple, got {len(shot)}: {shot}"
1403 assert isinstance(
1404 shot[0], list
1405 ), f"Expected list, got {type(shot[0])}: {shot[0]}"
1406 assert isinstance(
1407 shot[1], list
1408 ), f"Expected list, got {type(shot[1])}: {shot[1]}"
1409 assert shot == ([Result.Zero, Result.One], [Result.Zero, Result.One])
1410
1411
1412# =========================================================================
1413# OP_SWITCH with computed value from arithmetic
1414# =========================================================================
1415
1416SWITCH_ARITH_QIR = """
1417entry:
1418 ; Compute 2 * 3 - 4 = 2
1419 %a = mul i64 2, 3
1420 %val = sub i64 %a, 4
1421 switch i64 %val, label %default [
1422 i64 0, label %case0
1423 i64 1, label %case1
1424 i64 2, label %case2
1425 i64 3, label %case3
1426 ]
1427case0:
1428 br label %measure
1429case1:
1430 br label %measure
1431case2:
1432 ; Expected path
1433 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1434 br label %measure
1435case3:
1436 br label %measure
1437default:
1438 br label %measure
1439measure:
1440 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
1441"""
1442
1443
1444@pytest.mark.parametrize("sim_type", SIM_TYPES)
1445def test_switch_from_arithmetic(sim_type):
1446 """Switch on computed value 2*3-4=2 → case2 → X → 1."""
1447 check_result(SWITCH_ARITH_QIR, "1", sim_type=sim_type)
1448
1449
1450# =========================================================================
1451# Float: sitofp → fadd → fptosi round-trip
1452# =========================================================================
1453
1454FLOAT_ROUNDTRIP_QIR = """
1455 ; sitofp 7 → 7.0, fadd 7.0 + 3.0 → 10.0, fptosi → 10, check == 10
1456 %f = sitofp i64 7 to double
1457 %three = fadd double 0.0, 3.0
1458 %sum = fadd double %f, %three
1459 %i = fptosi double %sum to i64
1460 %flag = icmp eq i64 %i, 10
1461"""
1462
1463
1464@pytest.mark.parametrize("sim_type", SIM_TYPES)
1465def test_float_roundtrip(sim_type):
1466 check_arith_result(FLOAT_ROUNDTRIP_QIR, "1", sim_type=sim_type)
1467
1468
1469# =========================================================================
1470# OP_CALL with return value
1471# =========================================================================
1472
1473CALL_WITH_RETVAL_QIR = """
1474entry:
1475 %result = call i64 @add_numbers(i64 3, i64 4)
1476 %flag = icmp eq i64 %result, 7
1477 br i1 %flag, label %then, label %measure
1478then:
1479 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1480 br label %measure
1481measure:
1482 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
1483"""
1484
1485CALL_WITH_RETVAL_QIR_FN = """
1486define i64 @add_numbers(i64 %a, i64 %b) {
1487entry:
1488 %sum = add i64 %a, %b
1489 ret i64 %sum
1490}
1491"""
1492
1493
1494@pytest.mark.parametrize("sim_type", SIM_TYPES)
1495def test_call_with_return_value(sim_type):
1496 """Call a function returning i64, use result in comparison."""
1497 check_result(
1498 CALL_WITH_RETVAL_QIR,
1499 "1",
1500 extra_decls=CALL_WITH_RETVAL_QIR_FN,
1501 sim_type=sim_type,
1502 )
1503
1504
1505# =========================================================================
1506# OP_MUL + OP_UDIV + OP_UREM combined
1507# =========================================================================
1508
1509MUL_DIV_REM_QIR = """
1510 ; 17 / 5 = 3 (udiv), 17 % 5 = 2 (urem), 3 * 5 + 2 = 17
1511 %q = udiv i64 17, 5
1512 %r = urem i64 17, 5
1513 %product = mul i64 %q, 5
1514 %reconstructed = add i64 %product, %r
1515 %flag = icmp eq i64 %reconstructed, 17
1516"""
1517
1518
1519@pytest.mark.parametrize("sim_type", SIM_TYPES)
1520def test_mul_div_rem_identity(sim_type):
1521 """Division identity: (a/b)*b + (a%b) == a."""
1522 check_arith_result(MUL_DIV_REM_QIR, "1", sim_type=sim_type)
1523
1524
1525# =========================================================================
1526# OP_MEASURE with mid-circuit branch (measure-and-correct pattern)
1527# =========================================================================
1528
1529MEASURE_BRANCH_QIR = """
1530entry:
1531 ; Deterministically put qubit in |1⟩
1532 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1533 ; Measure (should be 1) and reset to |0⟩
1534 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
1535 %r = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
1536 ; Since r=1, branch to 'correct' which applies X to restore |1⟩
1537 br i1 %r, label %correct, label %measure
1538
1539correct:
1540 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
1541 br label %measure
1542
1543measure:
1544 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
1545"""
1546
1547
1548@pytest.mark.parametrize("sim_type", SIM_TYPES)
1549def test_measure_and_branch(sim_type):
1550 """Deterministic measure-and-correct: X→MResetZ→read_result→X→MResetZ → always 1."""
1551 check_result(MEASURE_BRANCH_QIR, "1", num_results=2, record=[1], sim_type=sim_type)
1552
1553
1554# =========================================================================
1555# OP_ADD with register-register (no immediates)
1556# =========================================================================
1557
1558ADD_REG_REG_QIR = """
1559 ; Use computed values in registers, not just immediates
1560 %a = add i64 2, 1
1561 %b = add i64 3, 1
1562 %c = add i64 %a, %b
1563 ; 3 + 4 = 7
1564 %flag = icmp eq i64 %c, 7
1565"""
1566
1567
1568@pytest.mark.parametrize("sim_type", SIM_TYPES)
1569def test_add_register_register(sim_type):
1570 check_arith_result(ADD_REG_REG_QIR, "1", sim_type=sim_type)
1571
1572
1573# #########################################################################
1574# Regression tests — exercising specific edge-cases that previously failed
1575# #########################################################################
1576
1577
1578# =========================================================================
1579# SREM with negative dividend
1580# =========================================================================
1581
1582SREM_NEG_DIVIDEND_QIR = """
1583 ; -7 % 2 = -1, verify result < 0
1584 %neg7 = sub i64 0, 7
1585 %a = srem i64 %neg7, 2
1586 %flag = icmp slt i64 %a, 0
1587"""
1588
1589
1590@pytest.mark.parametrize("sim_type", SIM_TYPES)
1591def test_srem_negative_dividend(sim_type):
1592 """srem must preserve the sign of the dividend."""
1593 check_arith_result(SREM_NEG_DIVIDEND_QIR, "1", sim_type=sim_type)
1594
1595
1596SREM_NEG_BOTH_QIR = """
1597 ; -10 % -3 = -1 (sign follows dividend)
1598 %neg10 = sub i64 0, 10
1599 %neg3 = sub i64 0, 3
1600 %a = srem i64 %neg10, %neg3
1601 %neg1 = sub i64 0, 1
1602 %flag = icmp eq i64 %a, %neg1
1603"""
1604
1605
1606@pytest.mark.parametrize("sim_type", SIM_TYPES)
1607def test_srem_negative_both(sim_type):
1608 """srem with both operands negative."""
1609 check_arith_result(SREM_NEG_BOTH_QIR, "1", sim_type=sim_type)
1610
1611
1612# =========================================================================
1613# SEXT from i1 (sign-extension must convert 1 → -1)
1614# =========================================================================
1615
1616SEXT_I1_FALSE_QIR = """
1617 ; sext i1 false to i64 → 0, check 0 == 0 → true
1618 %s = sext i1 false to i64
1619 %flag = icmp eq i64 %s, 0
1620"""
1621
1622
1623@pytest.mark.parametrize("sim_type", SIM_TYPES)
1624def test_sext_i1_false(sim_type):
1625 """sext of false (i1 0) must be 0."""
1626 check_arith_result(SEXT_I1_FALSE_QIR, "1", sim_type=sim_type)
1627
1628
1629SEXT_I1_RUNTIME_QIR = """
1630 ; compute i1 true at runtime, sext → -1, check < 0
1631 %one = add i64 1, 0
1632 %b = icmp eq i64 %one, 1
1633 %s = sext i1 %b to i64
1634 %flag = icmp slt i64 %s, 0
1635"""
1636
1637
1638@pytest.mark.parametrize("sim_type", SIM_TYPES)
1639def test_sext_i1_runtime(sim_type):
1640 """sext of a runtime i1 true value must also sign-extend to -1."""
1641 check_arith_result(SEXT_I1_RUNTIME_QIR, "1", sim_type=sim_type)
1642
1643
1644# =========================================================================
1645# Call to IR-defined function with inttoptr constant argument
1646# =========================================================================
1647
1648CALL_INTTOPTR_ARG_QIR = """
1649entry:
1650 call void @apply_h_then_z_then_h(%Qubit* inttoptr (i64 0 to %Qubit*))
1651 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
1652"""
1653
1654CALL_INTTOPTR_ARG_QIR_FN = """
1655define void @apply_h_then_z_then_h(%Qubit* %q) {
1656entry:
1657 call void @__quantum__qis__h__body(%Qubit* %q)
1658 call void @__quantum__qis__z__body(%Qubit* %q)
1659 call void @__quantum__qis__h__body(%Qubit* %q)
1660 ret void
1661}
1662"""
1663
1664
1665@pytest.mark.parametrize("sim_type", SIM_TYPES)
1666def test_call_inttoptr_arg(sim_type):
1667 """Call a helper with an inttoptr constant expression argument."""
1668 check_result(
1669 CALL_INTTOPTR_ARG_QIR,
1670 "1",
1671 extra_decls=CALL_INTTOPTR_ARG_QIR_FN,
1672 sim_type=sim_type,
1673 )
1674
1675
1676# =========================================================================
1677# SITOFP with negative value (signed int → float)
1678# =========================================================================
1679
1680SITOFP_NEG_QIR = """
1681 ; sitofp -3 → -3.0, then -3.0 < 0.0 → true
1682 %neg3 = sub i64 0, 3
1683 %f = sitofp i64 %neg3 to double
1684 %zero = sitofp i64 0 to double
1685 %flag = fcmp olt double %f, %zero
1686"""
1687
1688
1689@pytest.mark.parametrize("sim_type", SIM_TYPES)
1690def test_sitofp_negative(sim_type):
1691 """sitofp must correctly convert a negative integer."""
1692 check_arith_result(SITOFP_NEG_QIR, "1", sim_type=sim_type)
1693
1694
1695# #########################################################################
1696# Dynamic register file sizing (programs exceeding 128 registers)
1697# #########################################################################
1698
1699
1700def _run_openqasm(
1701 qasm_src: str,
1702 shots: int = SHOTS,
1703 seed: int = 42,
1704 sim_type: Literal["clifford", "cpu"] = "cpu",
1705):
1706 """Compile OpenQASM source via the adaptive pass and run on the given simulator."""
1707 qir = qsharp.openqasm.compile(
1708 qasm_src,
1709 output_semantics=qsharp.openqasm.OutputSemantics.OpenQasm,
1710 target_profile=qsharp.TargetProfile.Adaptive_RIF,
1711 )
1712 results = run_qir(qir, shots, seed=seed, type=sim_type)
1713 return [map_result_list_to_str(r) for r in results]
1714
1715
1716# =========================================================================
1717# Complex RUS loop — requires >128 registers after loop unrolling
1718# =========================================================================
1719
1720
1721@pytest.mark.parametrize("sim_type", SIM_TYPES)
1722def test_complex_rus_exceeds_128_registers(sim_type):
1723 """A complex repeat-until-success pattern with 50 iterations.
1724
1725 The Q# compiler fully unrolls the loop for the Adaptive_RIF profile,
1726 producing ~301 registers — well above the old fixed limit of 128.
1727 This validates that dynamic register file sizing works correctly.
1728 """
1729 qasm_src = """\
1730OPENQASM 3.0;
1731include "stdgates.inc";
1732qubit[4] q;
1733bit c;
1734int total = 0;
1735int i = 0;
1736while (i < 50) {
1737 h q[0];
1738 cx q[0], q[1];
1739 c = measure q[0];
1740 if (c) {
1741 x q[1];
1742 reset q[0];
1743 total = total + 1;
1744 }
1745 h q[2];
1746 cx q[2], q[3];
1747 c = measure q[2];
1748 if (c) {
1749 x q[3];
1750 reset q[2];
1751 total = total + 1;
1752 }
1753 i = i + 1;
1754}
1755bit[4] result = measure q;
1756"""
1757 results = _run_openqasm(qasm_src, shots=100, sim_type=sim_type)
1758 assert all(
1759 len(r) >= 4 and all(c in "01" for c in r) for r in results
1760 ), f"Unexpected result format: {results[:5]}"
1761