microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.27.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/pip/tests/test_adaptive_gpu_bytecode.py

1666lines · modecode

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