1#[cfg(feature = "python")]
2use pyo3::prelude::*;
3#[cfg(feature = "python")]
4use pyo3_stub_gen::define_stub_info_gatherer;
5
6pub mod convert;
7mod decompose;
8mod llvm_verify;
9pub mod opt;
10mod utils;
11
12#[cfg(windows)]
13pub const DEFAULT_OPT_LEVEL: u32 = 0;
14#[cfg(not(windows))]
15pub const DEFAULT_OPT_LEVEL: u32 = 2;
16
17#[cfg(windows)]
18pub const DEFAULT_TARGET: &str = "native";
19#[cfg(not(windows))]
20pub const DEFAULT_TARGET: &str = "aarch64";
21
22pub const MAX_STATIC_QUBITS: u32 = 65_535;
27
28pub const MAX_STATIC_RESULTS: u32 = 65_535;
33
34mod aux {
35 #![allow(
36 clippy::expect_used,
37 reason = "LLVM builder calls here encode internal lowering invariants; user-facing validation errors are propagated separately"
38 )]
39 #![allow(
40 clippy::indexing_slicing,
41 reason = "LLVM operand and metadata layouts are validated before fixed-position access"
42 )]
43
44 use std::collections::{BTreeMap, BTreeSet, HashMap};
45
46 use crate::{
47 convert::{
48 INIT_QARRAY_FN, add_print_call, build_result_global, checked_qubit_index,
49 convert_globals, create_reset_call, get_index, get_or_create_function,
50 get_required_num_qubits, get_required_num_qubits_strict, get_required_num_results,
51 get_result_vars, get_string_label, handle_tuple_or_array_output,
52 is_reserved_passthrough_name, parse_gep, record_classical_output, replace_rxy_call,
53 replace_rz_call, replace_rzz_call,
54 },
55 decode_llvm_bytes,
56 utils::extract_operands,
57 };
58
59 use inkwell::{
60 AddressSpace,
61 attributes::AttributeLoc,
62 basic_block::BasicBlock,
63 context::Context,
64 module::{Linkage, Module},
65 types::{ArrayType, BasicMetadataTypeEnum, BasicTypeEnum, FunctionType},
66 values::{
67 AnyValue, AsValueRef, BasicMetadataValueEnum, BasicValue, BasicValueEnum,
68 CallSiteValue, FunctionValue, InstructionOpcode, PointerValue,
69 },
70 };
71
72 static ALLOWED_QIS_FNS: [&str; 23] = [
73 "__quantum__qis__rxy__body",
75 "__quantum__qis__rz__body",
76 "__quantum__qis__rzz__body",
77 "__quantum__qis__mz__body",
78 "__quantum__qis__mz_leaked__body",
79 "__quantum__qis__reset__body",
80 "__quantum__qis__mresetz__body",
82 "__quantum__qis__u1q__body", "__quantum__qis__m__body", "__quantum__qis__h__body",
87 "__quantum__qis__x__body",
88 "__quantum__qis__y__body",
89 "__quantum__qis__z__body",
90 "__quantum__qis__s__body",
91 "__quantum__qis__s__adj",
92 "__quantum__qis__t__body",
93 "__quantum__qis__t__adj",
94 "__quantum__qis__rx__body",
95 "__quantum__qis__ry__body",
96 "__quantum__qis__cz__body",
97 "__quantum__qis__cx__body",
98 "__quantum__qis__cnot__body",
99 "__quantum__qis__ccx__body",
100 ];
102
103 static BASE_ALLOWED_RT_FNS: [&str; 8] = [
104 "__quantum__rt__read_result",
105 "__quantum__rt__initialize",
106 "__quantum__rt__result_record_output",
107 "__quantum__rt__array_record_output",
108 "__quantum__rt__tuple_record_output",
109 "__quantum__rt__bool_record_output",
110 "__quantum__rt__double_record_output",
111 "__quantum__rt__int_record_output",
112 ];
113
114 #[derive(Clone, Copy, Debug, Default)]
115 pub struct CapabilityFlags {
116 pub dynamic_qubit_management: bool,
117 pub dynamic_result_management: bool,
118 pub arrays: bool,
119 }
120
121 fn is_capability_gated_rt_function(fn_name: &str) -> bool {
122 matches!(
123 fn_name,
124 "__quantum__rt__qubit_allocate"
125 | "__quantum__rt__qubit_release"
126 | "__quantum__rt__result_allocate"
127 | "__quantum__rt__result_release"
128 | "__quantum__rt__qubit_array_allocate"
129 | "__quantum__rt__qubit_array_release"
130 | "__quantum__rt__result_array_allocate"
131 | "__quantum__rt__result_array_release"
132 | "__quantum__rt__result_array_record_output"
133 )
134 }
135
136 fn is_i64_type(type_: BasicMetadataTypeEnum<'_>) -> bool {
137 type_.is_int_type() && type_.into_int_type().get_bit_width() == 64
138 }
139
140 fn is_ptr_type(type_: BasicMetadataTypeEnum<'_>) -> bool {
141 type_.is_pointer_type()
142 }
143
144 fn is_void_return(fn_type: FunctionType<'_>) -> bool {
145 fn_type.get_return_type().is_none()
146 }
147
148 fn is_ptr_return(fn_type: FunctionType<'_>) -> bool {
149 fn_type
150 .get_return_type()
151 .is_some_and(BasicTypeEnum::is_pointer_type)
152 }
153
154 fn extract_label_from_tag(full_tag: &str) -> &str {
158 full_tag
159 .rsplit_once(':')
160 .map_or(full_tag, |(_, label)| label)
161 }
162
163 fn validate_dynamic_rt_signature(
164 fn_name: &str,
165 fn_type: FunctionType<'_>,
166 ) -> Result<(), String> {
167 let params = fn_type.get_param_types();
168 let valid = match fn_name {
169 "__quantum__rt__qubit_allocate" | "__quantum__rt__result_allocate" => {
170 is_ptr_return(fn_type)
171 && matches!(params.as_slice(), [param] if is_ptr_type(*param))
172 }
173 "__quantum__rt__qubit_release" | "__quantum__rt__result_release" => {
174 is_void_return(fn_type)
175 && matches!(params.as_slice(), [param] if is_ptr_type(*param))
176 }
177 "__quantum__rt__qubit_array_allocate"
178 | "__quantum__rt__result_array_allocate"
179 | "__quantum__rt__result_array_record_output" => {
180 is_void_return(fn_type)
181 && matches!(
182 params.as_slice(),
183 [len, first_ptr, second_ptr]
184 if is_i64_type(*len)
185 && is_ptr_type(*first_ptr)
186 && is_ptr_type(*second_ptr)
187 )
188 }
189 "__quantum__rt__qubit_array_release" | "__quantum__rt__result_array_release" => {
190 is_void_return(fn_type)
191 && matches!(params.as_slice(), [len, ptr] if is_i64_type(*len) && is_ptr_type(*ptr))
192 }
193 _ => true,
194 };
195
196 if valid {
197 Ok(())
198 } else {
199 Err(format!("Malformed QIR RT function declaration: {fn_name}"))
200 }
201 }
202
203 pub fn get_capability_flags(module: &Module) -> CapabilityFlags {
204 let module_flags = collect_module_flags(module);
205 CapabilityFlags {
206 dynamic_qubit_management: module_flag_is_enabled(
207 &module_flags,
208 "dynamic_qubit_management",
209 ),
210 dynamic_result_management: module_flag_is_enabled(
211 &module_flags,
212 "dynamic_result_management",
213 ),
214 arrays: module_flag_is_enabled(&module_flags, "arrays"),
215 }
216 }
217
218 #[cfg(feature = "wasm")]
219 static ALLOWED_QTM_FNS: [&str; 7] = [
220 "___get_current_shot",
221 "___random_seed",
222 "___random_int",
223 "___random_float",
224 "___random_int_bounded",
225 "___random_advance",
226 "___get_wasm_context",
227 ];
228
229 #[cfg(not(feature = "wasm"))]
230 static ALLOWED_QTM_FNS: [&str; 6] = [
231 "___get_current_shot",
232 "___random_seed",
233 "___random_int",
234 "___random_float",
235 "___random_int_bounded",
236 "___random_advance",
237 ];
238
239 #[cfg(not(windows))]
240 pub fn validate_module_layout_and_triple(module: &Module) {
241 let datalayout = module.get_data_layout();
242 let triple = module.get_triple();
243
244 if !datalayout.as_str().is_empty() {
245 log::warn!("QIR module has a data layout: {:?}", datalayout.as_str());
246 }
247 if !triple.as_str().is_empty() {
248 log::warn!("QIR module has a target triple: {:?}", triple.as_str());
249 }
250 }
251
252 #[cfg(windows)]
253 pub const fn validate_module_layout_and_triple(_module: &Module) {
254 }
258
259 fn direct_qubit_operand_positions(fn_name: &str, arg_count: usize) -> Vec<usize> {
260 match fn_name {
261 "__quantum__qis__rxy__body" | "__quantum__qis__u1q__body" => vec![2],
262 "__quantum__qis__rz__body"
263 | "__quantum__qis__rx__body"
264 | "__quantum__qis__ry__body" => {
265 vec![1]
266 }
267 "__quantum__qis__rzz__body" => vec![1, 2],
268 "__quantum__qis__cz__body"
269 | "__quantum__qis__cx__body"
270 | "__quantum__qis__cnot__body" => vec![0, 1],
271 "__quantum__qis__ccx__body" => vec![0, 1, 2],
272 "__quantum__qis__h__body"
273 | "__quantum__qis__x__body"
274 | "__quantum__qis__y__body"
275 | "__quantum__qis__z__body"
276 | "__quantum__qis__s__body"
277 | "__quantum__qis__s__adj"
278 | "__quantum__qis__t__body"
279 | "__quantum__qis__t__adj"
280 | "__quantum__qis__mz__body"
281 | "__quantum__qis__m__body"
282 | "__quantum__qis__mz_leaked__body"
283 | "__quantum__qis__reset__body"
284 | "__quantum__qis__mresetz__body" => vec![0],
285 name if name.starts_with("__quantum__qis__barrier") && name.ends_with("__body") => {
286 (0..arg_count).collect()
287 }
288 _ => Vec::new(),
289 }
290 }
291
292 fn is_user_ir_defined_helper_name(name: &str) -> bool {
293 !name.starts_with("__quantum__qis__")
294 && !name.starts_with("__quantum__rt__")
295 && !name.starts_with("qir_qis.")
296 && !name.starts_with("___")
297 && !name.starts_with("print_")
298 && name != "panic"
299 }
300
301 fn find_pointer_param_index(
302 function: FunctionValue<'_>,
303 ptr: PointerValue<'_>,
304 ) -> Option<usize> {
305 function
306 .get_param_iter()
307 .enumerate()
308 .find_map(|(idx, param)| {
309 let BasicValueEnum::PointerValue(param_ptr) = param else {
310 return None;
311 };
312 (param_ptr.as_value_ref() == ptr.as_value_ref()).then_some(idx)
313 })
314 }
315
316 fn infer_ir_defined_helper_qubit_params(
317 module: &Module,
318 errors: &mut Vec<String>,
319 ) -> HashMap<String, BTreeSet<usize>> {
320 let mut helper_qubit_params: HashMap<String, BTreeSet<usize>> = module
321 .get_functions()
322 .filter(|function| function.count_basic_blocks() > 0)
323 .filter_map(|function| {
324 function
325 .get_name()
326 .to_str()
327 .ok()
328 .filter(|name| is_user_ir_defined_helper_name(name))
329 .map(|name| (name.to_string(), BTreeSet::new()))
330 })
331 .collect();
332
333 loop {
334 let mut changed = false;
335 for function in module
336 .get_functions()
337 .filter(|function| function.count_basic_blocks() > 0)
338 {
339 let Ok(function_name) = function.get_name().to_str() else {
340 continue;
341 };
342 if !is_user_ir_defined_helper_name(function_name) {
343 continue;
344 }
345 let mut discovered = helper_qubit_params
346 .get(function_name)
347 .cloned()
348 .unwrap_or_default();
349
350 for bb in function.get_basic_blocks() {
351 for instr in bb.get_instructions() {
352 let Ok(call) = CallSiteValue::try_from(instr) else {
353 continue;
354 };
355 let Some(callee_name) = call.get_called_fn_value().and_then(|f| {
356 f.as_global_value()
357 .get_name()
358 .to_str()
359 .ok()
360 .map(ToOwned::to_owned)
361 }) else {
362 continue;
363 };
364
365 let call_args = match extract_operands(&instr) {
366 Ok(args) => args,
367 Err(err) => {
368 errors.push(format!(
369 "Failed to inspect `{callee_name}` call in `{function_name}`: {err}"
370 ));
371 continue;
372 }
373 };
374
375 let qubit_positions = {
376 let direct_positions =
377 direct_qubit_operand_positions(&callee_name, call_args.len());
378 if direct_positions.is_empty() {
379 helper_qubit_params
380 .get(&callee_name)
381 .map(|positions| positions.iter().copied().collect())
382 .unwrap_or_default()
383 } else {
384 direct_positions
385 }
386 };
387
388 for pos in qubit_positions {
389 let Some(BasicValueEnum::PointerValue(ptr)) =
390 call_args.get(pos).copied()
391 else {
392 continue;
393 };
394 if let Some(param_idx) = find_pointer_param_index(function, ptr) {
395 discovered.insert(param_idx);
396 }
397 }
398 }
399 }
400
401 if helper_qubit_params.get(function_name) != Some(&discovered) {
402 helper_qubit_params.insert(function_name.to_string(), discovered);
403 changed = true;
404 }
405 }
406
407 if !changed {
408 return helper_qubit_params;
409 }
410 }
411 }
412
413 fn validate_static_qubit_call_operands(
414 function: FunctionValue<'_>,
415 callee_name: &str,
416 qubit_positions: impl IntoIterator<Item = usize>,
417 call_args: &[BasicValueEnum<'_>],
418 required_num_qubits: u32,
419 report_shape_errors: bool,
420 errors: &mut Vec<String>,
421 ) {
422 for pos in qubit_positions {
423 let Some(arg) = call_args.get(pos).copied() else {
424 if report_shape_errors {
425 errors.push(format!(
426 "Call to `{callee_name}` is missing qubit operand {pos}"
427 ));
428 }
429 continue;
430 };
431 let BasicValueEnum::PointerValue(qubit_ptr) = arg else {
432 if report_shape_errors {
433 errors.push(format!(
434 "Call to `{callee_name}` has a non-pointer qubit operand"
435 ));
436 }
437 continue;
438 };
439 if find_pointer_param_index(function, qubit_ptr).is_some() {
440 continue;
441 }
442
443 let Ok(qubit_idx) = get_index(qubit_ptr) else {
444 continue;
445 };
446 if let Err(err) = checked_qubit_index(qubit_idx, required_num_qubits) {
447 errors.push(format!(
448 "Invalid static qubit handle passed to `{callee_name}`: {err}"
449 ));
450 }
451 }
452 }
453
454 pub fn validate_static_qubit_helper_usage(
455 module: &Module,
456 entry_fn: FunctionValue,
457 errors: &mut Vec<String>,
458 ) {
459 if get_capability_flags(module).dynamic_qubit_management {
460 return;
461 }
462 let required_num_qubits = match get_required_num_qubits_strict(entry_fn) {
463 Ok(required_num_qubits) => required_num_qubits,
464 Err(err) => {
465 errors.push(err);
466 return;
467 }
468 };
469
470 let previous_error_count = errors.len();
471 let helper_qubit_params = infer_ir_defined_helper_qubit_params(module, errors);
472 if errors.len() > previous_error_count {
473 return;
474 }
475
476 for function in module.get_functions() {
477 for bb in function.get_basic_blocks() {
478 for instr in bb.get_instructions() {
479 let Ok(call) = CallSiteValue::try_from(instr) else {
480 continue;
481 };
482 let Some(callee_name) = call.get_called_fn_value().and_then(|f| {
483 f.as_global_value()
484 .get_name()
485 .to_str()
486 .ok()
487 .map(ToOwned::to_owned)
488 }) else {
489 continue;
490 };
491 let call_args = match extract_operands(&instr) {
492 Ok(args) => args,
493 Err(err) => {
494 errors.push(format!("Failed to inspect `{callee_name}` call: {err}"));
495 continue;
496 }
497 };
498
499 let direct_positions =
500 direct_qubit_operand_positions(&callee_name, call_args.len());
501 if !direct_positions.is_empty() {
502 validate_static_qubit_call_operands(
503 function,
504 &callee_name,
505 direct_positions,
506 &call_args,
507 required_num_qubits,
508 false,
509 errors,
510 );
511 }
512
513 if let Some(qubit_positions) = helper_qubit_params.get(&callee_name)
514 && !qubit_positions.is_empty()
515 {
516 validate_static_qubit_call_operands(
517 function,
518 &callee_name,
519 qubit_positions.iter().copied(),
520 &call_args,
521 required_num_qubits,
522 true,
523 errors,
524 );
525 }
526 }
527 }
528 }
529 }
530
531 #[derive(Default)]
532 struct ModuleFlagErrors {
533 entries: Vec<ModuleFlagErrorEntry>,
534 }
535
536 enum ModuleFlagErrorEntry {
537 MissingRequired(String),
538 MissingOrUnsupported(String),
539 Message(String),
540 }
541
542 impl ModuleFlagErrors {
543 fn push_message(&mut self, message: String) {
544 self.entries.push(ModuleFlagErrorEntry::Message(message));
545 }
546
547 fn push_missing_required(&mut self, flag_name: &str) {
548 self.entries
549 .push(ModuleFlagErrorEntry::MissingRequired(flag_name.to_string()));
550 }
551
552 fn push_missing_or_unsupported(&mut self, flag_name: &str) {
553 self.entries
554 .push(ModuleFlagErrorEntry::MissingOrUnsupported(
555 flag_name.to_string(),
556 ));
557 }
558
559 fn into_messages(self) -> Vec<String> {
560 let mut messages = Vec::new();
561 let mut entries = self.entries.into_iter().peekable();
562 while let Some(entry) = entries.next() {
563 match entry {
564 ModuleFlagErrorEntry::Message(message) => messages.push(message),
565 ModuleFlagErrorEntry::MissingRequired(flag_name) => {
566 let mut flag_names = vec![flag_name];
567 while let Some(ModuleFlagErrorEntry::MissingRequired(next_flag_name)) =
568 entries.peek()
569 {
570 push_unique_flag_name(&mut flag_names, next_flag_name);
571 let _ = entries.next();
572 }
573 if let Some(message) = format_grouped_module_flag_error(
574 "Missing required module flag",
575 "Missing required module flags",
576 &flag_names,
577 ) {
578 messages.push(message);
579 }
580 }
581 ModuleFlagErrorEntry::MissingOrUnsupported(flag_name) => {
582 let mut flag_names = vec![flag_name];
583 while let Some(ModuleFlagErrorEntry::MissingOrUnsupported(next_flag_name)) =
584 entries.peek()
585 {
586 push_unique_flag_name(&mut flag_names, next_flag_name);
587 let _ = entries.next();
588 }
589 if let Some(message) = format_grouped_module_flag_error(
590 "Missing or unsupported module flag",
591 "Missing or unsupported module flags",
592 &flag_names,
593 ) {
594 messages.push(message);
595 }
596 }
597 }
598 }
599
600 messages
601 }
602 }
603
604 fn push_unique_flag_name(flag_names: &mut Vec<String>, flag_name: &str) {
605 if flag_names.iter().all(|existing| existing != flag_name) {
606 flag_names.push(flag_name.to_string());
607 }
608 }
609
610 fn format_grouped_module_flag_error(
611 singular_prefix: &str,
612 plural_prefix: &str,
613 flag_names: &[String],
614 ) -> Option<String> {
615 match flag_names {
616 [] => None,
617 [flag_name] => Some(format!("{singular_prefix}: {flag_name}")),
618 _ => Some(format!("{plural_prefix}: {}", flag_names.join(", "))),
619 }
620 }
621
622 pub fn validate_module_flags(module: &Module, errors: &mut Vec<String>) {
623 let module_flags = collect_module_flags(module);
624 let mut module_flag_errors = ModuleFlagErrors::default();
625 if module_flags.has_malformed_name() {
626 module_flag_errors.push_message(
627 "Malformed llvm.module.flags entry: expected metadata string name".to_string(),
628 );
629 }
630 validate_qir_version_flags(&module_flags, &mut module_flag_errors);
631 validate_exact_module_flag(
632 &module_flags,
633 "dynamic_qubit_management",
634 &["i1 false", "i1 true"],
635 &mut module_flag_errors,
636 );
637 validate_exact_module_flag(
638 &module_flags,
639 "dynamic_result_management",
640 &["i1 false", "i1 true"],
641 &mut module_flag_errors,
642 );
643 validate_optional_module_flag(
644 &module_flags,
645 "arrays",
646 &["i1 false", "i1 true"],
647 &mut module_flag_errors,
648 );
649 errors.extend(module_flag_errors.into_messages());
650 }
651
652 fn validate_qir_version_flags(module_flags: &ModuleFlags, errors: &mut ModuleFlagErrors) {
653 let major_values = required_module_flag_values(module_flags, "qir_major_version", errors);
654 let minor_values = required_module_flag_values(module_flags, "qir_minor_version", errors);
655 let (Some(major_values), Some(minor_values)) = (major_values, minor_values) else {
656 return;
657 };
658
659 for major_value in major_values {
660 for minor_value in minor_values {
661 if matches!(
662 (major_value.as_str(), minor_value.as_str()),
663 ("i32 1", "i32 0") | ("i32 2", "i32 0" | "i32 1")
664 ) {
665 return;
666 }
667 }
668 }
669
670 if !major_values
671 .iter()
672 .any(|major| matches!(major.as_str(), "i32 1" | "i32 2"))
673 {
674 errors.push_message(
675 "Unsupported qir_major_version: expected one of i32 1, i32 2".to_string(),
676 );
677 return;
678 }
679
680 errors.push_message(
681 "Unsupported qir_minor_version: expected i32 0 for QIR 1 or one of i32 0, i32 1 for QIR 2"
682 .to_string(),
683 );
684 }
685
686 fn required_module_flag_values<'a>(
687 module_flags: &'a ModuleFlags,
688 flag_name: &str,
689 errors: &mut ModuleFlagErrors,
690 ) -> Option<&'a [String]> {
691 match module_flags.get(flag_name) {
692 Some(values) => Some(values),
693 None if module_flags.is_malformed(flag_name) => {
694 errors.push_missing_or_unsupported(flag_name);
695 None
696 }
697 None => {
698 errors.push_missing_required(flag_name);
699 None
700 }
701 }
702 }
703
704 pub struct ModuleFlags {
705 values: BTreeMap<String, Vec<String>>,
706 has_malformed_name: bool,
707 malformed: BTreeSet<String>,
708 }
709
710 impl ModuleFlags {
711 pub fn get(&self, flag_name: &str) -> Option<&[String]> {
712 self.values.get(flag_name).map(Vec::as_slice)
713 }
714
715 const fn has_malformed_name(&self) -> bool {
716 self.has_malformed_name
717 }
718
719 fn is_malformed(&self, flag_name: &str) -> bool {
720 self.malformed.contains(flag_name)
721 }
722 }
723
724 pub fn collect_module_flags(module: &Module) -> ModuleFlags {
725 let mut values = BTreeMap::<String, Vec<String>>::new();
726 let mut has_malformed_name = false;
727 let mut malformed = BTreeSet::new();
728
729 for entry in module.get_global_metadata("llvm.module.flags") {
730 let Some(node_values) = entry.get_node_values() else {
731 continue;
732 };
733 let flag_name = extract_module_flag_name(&node_values);
734 if flag_name.is_none() {
735 has_malformed_name = true;
736 }
737
738 if node_values.len() != 3 {
739 if let Some(flag_name) = flag_name {
740 malformed.insert(flag_name);
741 }
742 continue;
743 }
744
745 let Some(flag_name) = flag_name else {
746 continue;
747 };
748
749 let Some(flag_value) = node_values
750 .get(2)
751 .copied()
752 .and_then(format_module_flag_value)
753 else {
754 malformed.insert(flag_name);
755 continue;
756 };
757
758 values.entry(flag_name).or_default().push(flag_value);
759 }
760
761 ModuleFlags {
762 values,
763 has_malformed_name,
764 malformed,
765 }
766 }
767
768 fn extract_module_flag_name(values: &[BasicMetadataValueEnum]) -> Option<String> {
769 match values.get(1).copied()? {
770 BasicMetadataValueEnum::MetadataValue(value) => value
771 .get_string_value()
772 .and_then(decode_llvm_bytes)
773 .map(str::to_owned),
774 BasicMetadataValueEnum::ArrayValue(_)
775 | BasicMetadataValueEnum::IntValue(_)
776 | BasicMetadataValueEnum::FloatValue(_)
777 | BasicMetadataValueEnum::PointerValue(_)
778 | BasicMetadataValueEnum::StructValue(_)
779 | BasicMetadataValueEnum::VectorValue(_)
780 | BasicMetadataValueEnum::ScalableVectorValue(_) => None,
781 }
782 }
783
784 fn format_module_flag_value(value: BasicMetadataValueEnum) -> Option<String> {
785 match value {
786 BasicMetadataValueEnum::IntValue(value) => {
787 let bit_width = value.get_type().get_bit_width();
788 let raw_value = value.get_zero_extended_constant()?;
789 if bit_width == 1 {
790 Some(format!(
791 "i1 {}",
792 if raw_value == 0 { "false" } else { "true" }
793 ))
794 } else {
795 Some(format!("i{bit_width} {raw_value}"))
796 }
797 }
798 BasicMetadataValueEnum::MetadataValue(value) => value
799 .get_string_value()
800 .and_then(decode_llvm_bytes)
801 .map(|string| format!("!\"{string}\"")),
802 BasicMetadataValueEnum::ArrayValue(_)
803 | BasicMetadataValueEnum::FloatValue(_)
804 | BasicMetadataValueEnum::PointerValue(_)
805 | BasicMetadataValueEnum::StructValue(_)
806 | BasicMetadataValueEnum::VectorValue(_)
807 | BasicMetadataValueEnum::ScalableVectorValue(_) => None,
808 }
809 }
810
811 fn module_flag_is_enabled(module_flags: &ModuleFlags, flag_name: &str) -> bool {
812 module_flags
813 .get(flag_name)
814 .is_some_and(|values| values.iter().any(|value| value == "i1 true"))
815 }
816
817 fn validate_optional_module_flag(
818 module_flags: &ModuleFlags,
819 flag_name: &str,
820 expected_values: &[&str],
821 errors: &mut ModuleFlagErrors,
822 ) {
823 let Some(actual_values) = module_flags.get(flag_name) else {
824 if module_flags.is_malformed(flag_name) {
825 errors.push_missing_or_unsupported(flag_name);
826 }
827 return;
828 };
829
830 if actual_values
831 .iter()
832 .any(|actual| expected_values.contains(&actual.as_str()))
833 {
834 return;
835 }
836
837 let expected = if expected_values.len() == 1 {
838 expected_values.first().unwrap_or(&"").to_string()
839 } else {
840 format!("one of {}", expected_values.join(", "))
841 };
842 errors.push_message(format!("Unsupported {flag_name}: expected {expected}"));
843 }
844 fn validate_exact_module_flag(
845 module_flags: &ModuleFlags,
846 flag_name: &str,
847 expected_values: &[&str],
848 errors: &mut ModuleFlagErrors,
849 ) {
850 let Some(actual_values) = module_flags.get(flag_name) else {
851 if module_flags.is_malformed(flag_name) {
852 errors.push_missing_or_unsupported(flag_name);
853 return;
854 }
855 errors.push_missing_required(flag_name);
856 return;
857 };
858
859 if actual_values
860 .iter()
861 .any(|actual| expected_values.contains(&actual.as_str()))
862 {
863 return;
864 }
865
866 let expected = format!("one of {}", expected_values.join(", "));
867 errors.push_message(format!("Unsupported {flag_name}: expected {expected}"));
868 }
869
870 fn get_fixed_pointer_array_len(
871 array_ptr: PointerValue<'_>,
872 opname: &str,
873 ) -> Result<u64, String> {
874 let array_backing_error =
875 || format!("{opname} requires a fixed-size backing array allocated as [N x ptr]");
876 let Some(instr) = array_ptr.as_instruction_value() else {
877 return Err(array_backing_error());
878 };
879 let opcode = instr.get_opcode();
880
881 if opcode == InstructionOpcode::Alloca {
882 let allocated_type = instr
883 .get_allocated_type()
884 .map_err(|_err| array_backing_error())?;
885 let BasicTypeEnum::ArrayType(array_type) = allocated_type else {
886 return Err(array_backing_error());
887 };
888 if !matches!(array_type.get_element_type(), BasicTypeEnum::PointerType(_)) {
889 return Err(array_backing_error());
890 }
891 return Ok(u64::from(array_type.len()));
892 }
893
894 if opcode == InstructionOpcode::BitCast || opcode == InstructionOpcode::AddrSpaceCast {
895 return instr
896 .get_operand(0)
897 .and_then(inkwell::values::Operand::value)
898 .map(BasicValueEnum::into_pointer_value)
899 .ok_or_else(array_backing_error)
900 .and_then(|backing_ptr| get_fixed_pointer_array_len(backing_ptr, opname));
901 }
902
903 if opcode == InstructionOpcode::GetElementPtr {
904 for operand_idx in 1..instr.get_num_operands() {
905 let Some(operand) = instr.get_operand(operand_idx) else {
906 return Err(array_backing_error());
907 };
908 let inkwell::values::Operand::Value(value) = operand else {
909 return Err(array_backing_error());
910 };
911 let idx = value
912 .into_int_value()
913 .get_zero_extended_constant()
914 .ok_or_else(array_backing_error)?;
915 if idx != 0 {
916 return Err(array_backing_error());
917 }
918 }
919
920 return instr
921 .get_operand(0)
922 .and_then(inkwell::values::Operand::value)
923 .map(BasicValueEnum::into_pointer_value)
924 .ok_or_else(array_backing_error)
925 .and_then(|backing_ptr| get_fixed_pointer_array_len(backing_ptr, opname));
926 }
927
928 Err(array_backing_error())
929 }
930
931 #[cfg(test)]
935 pub fn validate_dynamic_array_allocation_backing(module: &Module, errors: &mut Vec<String>) {
936 for fun in module.get_functions() {
937 for bb in fun.get_basic_blocks() {
938 for instr in bb.get_instructions() {
939 let Ok(call) = CallSiteValue::try_from(instr) else {
940 continue;
941 };
942 let Some(callee) = call.get_called_fn_value() else {
943 continue;
944 };
945 let callee_global = callee.as_global_value();
946 let callee_name = callee_global.get_name();
947 let Some(fn_name) = callee_name.to_str().ok() else {
948 continue;
949 };
950 if !matches!(
951 fn_name,
952 "__quantum__rt__qubit_array_allocate"
953 | "__quantum__rt__qubit_array_release"
954 | "__quantum__rt__result_array_allocate"
955 | "__quantum__rt__result_array_release"
956 | "__quantum__rt__result_array_record_output"
957 ) {
958 continue;
959 }
960
961 let mut call_args: Vec<BasicValueEnum> = match extract_operands(&instr) {
962 Ok(args) => args,
963 Err(err) => {
964 errors.push(format!("Failed to inspect {fn_name} operands: {err}"));
965 continue;
966 }
967 };
968 call_args.truncate(call.count_arguments() as usize);
969 if call_args.len() < 2 {
970 errors.push(format!(
971 "{fn_name} requires a constant array length and backing array pointer"
972 ));
973 continue;
974 }
975 let BasicValueEnum::IntValue(_) = call_args[0] else {
976 errors.push(format!(
977 "{fn_name} requires a constant array length and backing array pointer"
978 ));
979 continue;
980 };
981 let requested_len = match extract_const_len(call_args[0], fn_name) {
982 Ok(len) => len,
983 Err(err) => {
984 errors.push(err);
985 continue;
986 }
987 };
988 let BasicValueEnum::PointerValue(backing_ptr) = call_args[1] else {
989 errors.push(format!(
990 "{fn_name} requires a fixed-size backing array allocated as [N x ptr]"
991 ));
992 continue;
993 };
994 let backing_len = match get_fixed_pointer_array_len(backing_ptr, fn_name) {
995 Ok(len) => len,
996 Err(err) => {
997 errors.push(err);
998 continue;
999 }
1000 };
1001 if requested_len != backing_len {
1002 errors.push(format!(
1003 "{fn_name} requires a fixed-size backing array whose requested length {requested_len} does not match backing array length {backing_len}"
1004 ));
1005 }
1006 if fn_name == "__quantum__rt__result_array_record_output"
1007 && requested_len > i32::MAX as u64
1008 {
1009 errors.push(format!(
1010 "{fn_name} requires an array length that fits in i32 for RESULT_ARRAY output"
1011 ));
1012 }
1013 }
1014 }
1015 }
1016 }
1017
1018 #[allow(
1033 clippy::too_many_lines,
1034 reason = "single consolidated validation pass mirrors five previously-separate functions"
1035 )]
1036 pub fn validate_qir_call_sites(
1037 module: &Module,
1038 entry_fn: FunctionValue,
1039 _wasm_fns: &BTreeMap<String, u64>,
1040 capability_flags: CapabilityFlags,
1041 errors: &mut Vec<String>,
1042 ) -> Vec<String> {
1043 let mut functions_errors = Vec::new();
1047 let mut result_slot_errors = Vec::new();
1048 let mut static_qubit_errors = Vec::new();
1049 let mut dynamic_result_placement_errors = Vec::new();
1050 let mut dynamic_array_backing_errors = Vec::new();
1051 let mut capability_errors = Vec::new();
1052
1053 let required_num_qubits_for_barrier = get_required_num_qubits(entry_fn);
1056
1057 let required_num_results = if entry_fn
1058 .get_string_attribute(AttributeLoc::Function, "required_num_results")
1059 .is_some()
1060 {
1061 match get_required_num_results(entry_fn) {
1062 Ok(required) => Some(required),
1063 Err(err) => {
1064 result_slot_errors.push(err);
1065 None
1066 }
1067 }
1068 } else {
1069 None
1070 };
1071
1072 let static_qubit_ctx = if capability_flags.dynamic_qubit_management {
1073 None
1074 } else {
1075 match get_required_num_qubits_strict(entry_fn) {
1076 Ok(required_num_qubits) => {
1077 let mut infer_errors = Vec::new();
1078 let helper_qubit_params =
1079 infer_ir_defined_helper_qubit_params(module, &mut infer_errors);
1080 if infer_errors.is_empty() {
1081 Some((required_num_qubits, helper_qubit_params))
1082 } else {
1083 static_qubit_errors.extend(infer_errors);
1084 None
1085 }
1086 }
1087 Err(err) => {
1088 static_qubit_errors.push(err);
1089 None
1090 }
1091 }
1092 };
1093
1094 for fun in module.get_functions() {
1095 if fun != entry_fn {
1097 let fn_name = fun.get_name().to_str().unwrap_or("");
1098 if fn_name.starts_with("qir_qis.") {
1099 functions_errors.push(format!(
1100 "Input QIR must not define internal helper function: {fn_name}"
1101 ));
1102 } else if fn_name.starts_with("__quantum__qis__") {
1103 let is_barrier = parse_barrier_arity(fn_name).is_ok_and(|arity| {
1104 if let Some(max_qubits) = required_num_qubits_for_barrier
1105 && let Ok(arity_u32) = u32::try_from(arity)
1106 && arity_u32 > max_qubits
1107 {
1108 functions_errors.push(format!(
1109 "Barrier arity {arity} exceeds module's required_num_qubits ({max_qubits})"
1110 ));
1111 }
1112 true
1113 });
1114
1115 if !is_barrier && !ALLOWED_QIS_FNS.contains(&fn_name) {
1116 functions_errors.push(format!("Unsupported QIR QIS function: {fn_name}"));
1117 }
1118 } else if fn_name.starts_with("__quantum__rt__") {
1119 if !BASE_ALLOWED_RT_FNS.contains(&fn_name)
1120 && !is_capability_gated_rt_function(fn_name)
1121 {
1122 functions_errors.push(format!("Unsupported QIR RT function: {fn_name}"));
1123 } else if is_capability_gated_rt_function(fn_name)
1124 && let Err(err) = validate_dynamic_rt_signature(fn_name, fun.get_type())
1125 {
1126 functions_errors.push(err);
1127 }
1128 } else if fn_name.starts_with("___") {
1129 if !ALLOWED_QTM_FNS.contains(&fn_name) {
1130 functions_errors.push(format!("Unsupported Qtm QIS function: {fn_name}"));
1131 }
1132 } else if fun.count_basic_blocks() > 0 {
1133 if fn_name == "main" {
1135 functions_errors
1136 .push("IR defined function cannot be called `main`".to_string());
1137 }
1138 if fun
1139 .get_type()
1140 .get_return_type()
1141 .is_some_and(BasicTypeEnum::is_pointer_type)
1142 {
1143 functions_errors
1144 .push(format!("Function `{fn_name}` cannot return a pointer type"));
1145 }
1146 } else {
1147 log::debug!(
1148 "External function `{fn_name}` found, leaving as-is for downstream processing"
1149 );
1150 }
1151 }
1152
1153 let allowed_result_alloc_block = if fun == entry_fn {
1154 fun.get_first_basic_block()
1155 } else {
1156 None
1157 };
1158
1159 for bb in fun.get_basic_blocks() {
1160 for instr in bb.get_instructions() {
1161 let Ok(call) = CallSiteValue::try_from(instr) else {
1162 continue;
1163 };
1164 let Some(callee_name) = call.get_called_fn_value().and_then(|f| {
1165 f.as_global_value()
1166 .get_name()
1167 .to_str()
1168 .ok()
1169 .map(ToOwned::to_owned)
1170 }) else {
1171 continue;
1172 };
1173
1174 if matches!(
1176 callee_name.as_str(),
1177 "__quantum__rt__result_allocate" | "__quantum__rt__result_array_allocate"
1178 ) && Some(bb) != allowed_result_alloc_block
1179 {
1180 dynamic_result_placement_errors.push(format!(
1181 "{callee_name} is only supported in the entry block because dynamic result slots are lowered to stack storage"
1182 ));
1183 }
1184
1185 match callee_name.as_str() {
1187 "__quantum__rt__qubit_array_allocate"
1188 | "__quantum__rt__qubit_array_release"
1189 if !capability_flags.arrays
1190 || !capability_flags.dynamic_qubit_management =>
1191 {
1192 capability_errors.push(format!(
1193 "{callee_name} requires both `arrays=true` and `dynamic_qubit_management=true`"
1194 ));
1195 }
1196 "__quantum__rt__result_array_allocate"
1197 | "__quantum__rt__result_array_release"
1198 | "__quantum__rt__result_array_record_output"
1199 if !capability_flags.arrays
1200 || !capability_flags.dynamic_result_management =>
1201 {
1202 capability_errors.push(format!(
1203 "{callee_name} requires both `arrays=true` and `dynamic_result_management=true`"
1204 ));
1205 }
1206 "__quantum__rt__qubit_allocate" | "__quantum__rt__qubit_release"
1207 if !capability_flags.dynamic_qubit_management =>
1208 {
1209 capability_errors.push(format!(
1210 "{callee_name} requires `dynamic_qubit_management=true`"
1211 ));
1212 }
1213 "__quantum__rt__result_allocate" | "__quantum__rt__result_release"
1214 if !capability_flags.dynamic_result_management =>
1215 {
1216 capability_errors.push(format!(
1217 "{callee_name} requires `dynamic_result_management=true`"
1218 ));
1219 }
1220 _ => {}
1221 }
1222
1223 let result_operand_index = match callee_name.as_str() {
1226 "__quantum__qis__mz__body"
1227 | "__quantum__qis__m__body"
1228 | "__quantum__qis__mresetz__body" => Some(1),
1229 "__quantum__rt__read_result" | "__quantum__rt__result_record_output" => {
1230 Some(0)
1231 }
1232 _ => None,
1233 };
1234 let result_slot_relevant = matches!(
1235 (required_num_results, result_operand_index),
1236 (Some(_), Some(_))
1237 );
1238 let direct_qubit_positions = static_qubit_ctx.as_ref().map(|_| {
1239 direct_qubit_operand_positions(
1240 &callee_name,
1241 call.count_arguments() as usize,
1242 )
1243 });
1244 let helper_qubit_positions = static_qubit_ctx
1245 .as_ref()
1246 .and_then(|(_, helper_qubit_params)| helper_qubit_params.get(&callee_name));
1247 let static_qubit_operand_inspection_relevant = static_qubit_ctx.is_some();
1248 let array_backing_relevant = matches!(
1249 callee_name.as_str(),
1250 "__quantum__rt__qubit_array_allocate"
1251 | "__quantum__rt__qubit_array_release"
1252 | "__quantum__rt__result_array_allocate"
1253 | "__quantum__rt__result_array_release"
1254 | "__quantum__rt__result_array_record_output"
1255 );
1256
1257 if matches!(
1258 (
1259 result_slot_relevant,
1260 static_qubit_ctx.as_ref(),
1261 array_backing_relevant
1262 ),
1263 (false, None, false)
1264 ) {
1265 continue;
1266 }
1267
1268 let mut call_args = match extract_operands(&instr) {
1269 Ok(args) => args,
1270 Err(err) => {
1271 if result_slot_relevant {
1272 result_slot_errors
1273 .push(format!("Failed to inspect `{callee_name}` call: {err}"));
1274 }
1275 if static_qubit_operand_inspection_relevant {
1276 static_qubit_errors
1277 .push(format!("Failed to inspect `{callee_name}` call: {err}"));
1278 }
1279 if array_backing_relevant {
1280 dynamic_array_backing_errors.push(format!(
1281 "Failed to inspect {callee_name} operands: {err}"
1282 ));
1283 }
1284 continue;
1285 }
1286 };
1287 call_args.truncate(call.count_arguments() as usize);
1288
1289 if let (Some(required_num_results), Some(result_operand_index)) =
1291 (required_num_results, result_operand_index)
1292 {
1293 match call_args.get(result_operand_index).copied() {
1294 None => result_slot_errors.push(format!(
1295 "Call to `{callee_name}` is missing a result operand"
1296 )),
1297 Some(BasicValueEnum::PointerValue(result_ptr)) => {
1298 match get_index(result_ptr) {
1299 Ok(result_idx) => {
1300 if let Err(err) =
1301 checked_result_index(result_idx, required_num_results)
1302 {
1303 result_slot_errors.push(err);
1304 }
1305 }
1306 Err(err) => result_slot_errors.push(format!(
1307 "Failed to inspect result operand for `{callee_name}`: {err}"
1308 )),
1309 }
1310 }
1311 Some(_) => result_slot_errors.push(format!(
1312 "Call to `{callee_name}` has a non-pointer result operand"
1313 )),
1314 }
1315 }
1316
1317 if let Some((required_num_qubits, _)) = static_qubit_ctx.as_ref() {
1319 if let Some(direct_positions) = direct_qubit_positions
1320 && !direct_positions.is_empty()
1321 {
1322 validate_static_qubit_call_operands(
1323 fun,
1324 &callee_name,
1325 direct_positions,
1326 &call_args,
1327 *required_num_qubits,
1328 false,
1329 &mut static_qubit_errors,
1330 );
1331 }
1332 if let Some(qubit_positions) = helper_qubit_positions
1333 && !qubit_positions.is_empty()
1334 {
1335 validate_static_qubit_call_operands(
1336 fun,
1337 &callee_name,
1338 qubit_positions.iter().copied(),
1339 &call_args,
1340 *required_num_qubits,
1341 true,
1342 &mut static_qubit_errors,
1343 );
1344 }
1345 }
1346
1347 if array_backing_relevant {
1349 let Some((length_operand, backing_operand)) =
1350 call_args.first().copied().zip(call_args.get(1).copied())
1351 else {
1352 dynamic_array_backing_errors.push(format!(
1353 "{callee_name} requires a constant array length and backing array pointer"
1354 ));
1355 continue;
1356 };
1357 if matches!(length_operand, BasicValueEnum::IntValue(_)) {
1358 match extract_const_len(length_operand, &callee_name) {
1359 Ok(requested_len) => {
1360 let BasicValueEnum::PointerValue(backing_ptr) = backing_operand
1361 else {
1362 dynamic_array_backing_errors.push(format!(
1363 "{callee_name} requires a fixed-size backing array allocated as [N x ptr]"
1364 ));
1365 continue;
1366 };
1367 match get_fixed_pointer_array_len(backing_ptr, &callee_name) {
1368 Ok(backing_len) => {
1369 if requested_len != backing_len {
1370 dynamic_array_backing_errors.push(format!(
1371 "{callee_name} requires a fixed-size backing array whose requested length {requested_len} does not match backing array length {backing_len}"
1372 ));
1373 }
1374 if callee_name
1375 == "__quantum__rt__result_array_record_output"
1376 && requested_len > i32::MAX as u64
1377 {
1378 dynamic_array_backing_errors.push(format!(
1379 "{callee_name} requires an array length that fits in i32 for RESULT_ARRAY output"
1380 ));
1381 }
1382 }
1383 Err(err) => dynamic_array_backing_errors.push(err),
1384 }
1385 }
1386 Err(err) => dynamic_array_backing_errors.push(err),
1387 }
1388 } else {
1389 dynamic_array_backing_errors.push(format!(
1390 "{callee_name} requires a constant array length and backing array pointer"
1391 ));
1392 }
1393 }
1394 }
1395 }
1396 }
1397
1398 errors.extend(functions_errors);
1399 errors.extend(result_slot_errors);
1400 errors.extend(static_qubit_errors);
1401 errors.extend(dynamic_result_placement_errors);
1402 errors.extend(dynamic_array_backing_errors);
1403 capability_errors
1404 }
1405
1406 struct ProcessCallArgs<'ctx> {
1411 ctx: &'ctx Context,
1412 module: *const Module<'ctx>,
1413 instr: inkwell::values::InstructionValue<'ctx>,
1414 fn_name: String,
1415 #[allow(dead_code, reason = "reserved field keeps WASM API shape stable")]
1416 wasm_fns: *const BTreeMap<String, u64>,
1417 passthrough_calls: *const BTreeSet<String>,
1418 qubit_array: Option<PointerValue<'ctx>>,
1419 qubit_array_type: Option<ArrayType<'ctx>>,
1420 capability_flags: CapabilityFlags,
1421 global_mapping: *mut HashMap<String, inkwell::values::GlobalValue<'ctx>>,
1422 result_ssa: *mut Vec<Option<(BasicValueEnum<'ctx>, Option<BasicValueEnum<'ctx>>)>>,
1423 }
1424
1425 pub fn process_entry_function<'ctx>(
1427 ctx: &'ctx Context,
1428 module: &Module<'ctx>,
1429 entry_fn: FunctionValue<'ctx>,
1430 wasm_fns: &BTreeMap<String, u64>,
1431 passthrough_calls: &BTreeSet<String>,
1432 qubit_array: Option<PointerValue<'ctx>>,
1433 capability_flags: CapabilityFlags,
1434 ) -> Result<(), String> {
1435 let mut global_mapping = convert_globals(ctx, module)?;
1436
1437 if global_mapping.is_empty() {
1438 log::warn!("No globals found in QIR module");
1439 }
1440 let mut result_ssa = if capability_flags.dynamic_result_management {
1441 Vec::new()
1442 } else {
1443 get_result_vars(entry_fn)?
1444 };
1445 let qubit_array_type = if capability_flags.dynamic_qubit_management {
1446 None
1447 } else {
1448 Some(
1449 ctx.i64_type()
1450 .array_type(get_required_num_qubits_strict(entry_fn)?),
1451 )
1452 };
1453
1454 for bb in entry_fn.get_basic_blocks() {
1455 let instructions: Vec<_> = bb.get_instructions().collect();
1458 for instr in instructions {
1459 let Ok(call) = CallSiteValue::try_from(instr) else {
1460 continue;
1461 };
1462 if let Some(fn_name) = call.get_called_fn_value().and_then(|f| {
1463 f.as_global_value()
1464 .get_name()
1465 .to_str()
1466 .ok()
1467 .map(ToOwned::to_owned)
1468 }) {
1469 let args = ProcessCallArgs {
1470 ctx,
1471 module,
1472 instr,
1473 fn_name,
1474 wasm_fns: std::ptr::from_ref(wasm_fns),
1475 passthrough_calls: std::ptr::from_ref(passthrough_calls),
1476 qubit_array,
1477 qubit_array_type,
1478 capability_flags,
1479 global_mapping: &raw mut global_mapping,
1480 result_ssa: &raw mut result_ssa,
1481 };
1482 process_call_instruction(args)?;
1483 }
1484 }
1485 }
1486
1487 Ok(())
1488 }
1489
1490 fn process_call_instruction(mut args: ProcessCallArgs<'_>) -> Result<(), String> {
1491 let call = CallSiteValue::try_from(args.instr)
1492 .map_err(|()| "Instruction is not a call site".to_string())?;
1493 if let Some(f) = call.get_called_fn_value() {
1494 let passthrough_calls = passthrough_calls_ref(&args);
1495 let is_passthrough = passthrough_calls.contains(args.fn_name.as_str());
1496 let is_ir_defined = f.count_basic_blocks() > 0;
1497 match args.fn_name.as_str() {
1498 name if name.starts_with("__quantum__qis__") => {
1499 if matches!(try_handle_qis_call(&args)?, BuiltinCallHandling::Handled) {
1500 return Ok(());
1501 }
1502 if is_ir_defined {
1503 return Ok(());
1507 }
1508 if is_passthrough {
1509 return passthrough_external_call(&args);
1510 }
1511 if check_downstream_attribute(f, args.fn_name.as_str()) {
1512 return Ok(());
1513 }
1514 return Err(format!("Unsupported QIR QIS function: {}", args.fn_name));
1515 }
1516 name if name.starts_with("__quantum__rt__") => {
1517 if matches!(try_handle_rt_call(&mut args)?, BuiltinCallHandling::Handled) {
1518 return Ok(());
1519 }
1520 if is_passthrough && !is_ir_defined {
1521 return passthrough_external_call(&args);
1522 }
1523 if check_downstream_attribute(f, args.fn_name.as_str()) {
1524 return Ok(());
1525 }
1526 return Err(format!("Unsupported QIR RT function: {}", args.fn_name));
1527 }
1528 name if name.starts_with("___") => return handle_qtm_call(&args),
1529 _ => {}
1530 }
1531
1532 if is_ir_defined {
1534 if args.fn_name != INIT_QARRAY_FN {
1538 log::debug!("IR defined function `{}`: {}", args.fn_name, f.get_type());
1539 }
1540 if args.fn_name == "main" {
1541 return Err("IR defined function cannot be called `main`".to_string());
1542 }
1543 return Ok(());
1544 }
1545
1546 if is_passthrough {
1547 return passthrough_external_call(&args);
1548 }
1549
1550 if check_downstream_attribute(f, args.fn_name.as_str()) {
1551 return Ok(());
1552 }
1553
1554 log::error!("Unknown external function: {}", args.fn_name);
1555 return Err(format!("Unsupported function: {}", args.fn_name));
1556 }
1557
1558 match args.fn_name.as_str() {
1559 name if name.starts_with("__quantum__qis__") => {
1560 if matches!(try_handle_qis_call(&args)?, BuiltinCallHandling::Handled) {
1561 return Ok(());
1562 }
1563 Err(format!("Unsupported QIR QIS function: {}", args.fn_name))
1564 }
1565 name if name.starts_with("__quantum__rt__") => {
1566 if matches!(try_handle_rt_call(&mut args)?, BuiltinCallHandling::Handled) {
1567 return Ok(());
1568 }
1569 Err(format!("Unsupported QIR RT function: {}", args.fn_name))
1570 }
1571 name if name.starts_with("___") => handle_qtm_call(&args),
1572 _ => {
1573 log::error!("Unsupported function: {}", args.fn_name);
1574 Err(format!("Unsupported function: {}", args.fn_name))
1575 }
1576 }
1577 }
1578
1579 fn check_downstream_attribute(f: FunctionValue<'_>, fn_name: &str) -> bool {
1582 if f.get_string_attribute(AttributeLoc::Function, "cudaq-fnid")
1583 .is_some()
1584 {
1585 log::debug!("GPU function `{fn_name}` found, leaving as-is for downstream processing");
1586 return true;
1587 }
1588 if f.get_string_attribute(AttributeLoc::Function, "wasm")
1589 .is_some()
1590 {
1591 log::debug!("WASM function `{fn_name}` found, leaving as-is for downstream processing");
1592 return true;
1593 }
1594 false
1595 }
1596
1597 fn passthrough_external_call(args: &ProcessCallArgs<'_>) -> Result<(), String> {
1598 if is_reserved_passthrough_name(args.fn_name.as_str()) {
1599 return Err(format!(
1600 "Pass-through function `{}` uses a reserved converter output name",
1601 args.fn_name
1602 ));
1603 }
1604 log::debug!(
1605 "Pass-through function `{}` found, leaving as-is for downstream processing",
1606 args.fn_name
1607 );
1608 Ok(())
1609 }
1610
1611 enum BuiltinCallHandling {
1612 Handled,
1613 Unsupported,
1614 }
1615
1616 fn try_handle_qis_call(args: &ProcessCallArgs<'_>) -> Result<BuiltinCallHandling, String> {
1617 let required_num_qubits = args
1618 .qubit_array_type
1619 .map_or(0, inkwell::types::ArrayType::len);
1620 match args.fn_name.as_str() {
1621 "__quantum__qis__rxy__body" => {
1622 replace_rxy_call(
1623 args.ctx,
1624 module_ref(args),
1625 args.instr,
1626 args.capability_flags.dynamic_qubit_management,
1627 required_num_qubits,
1628 )?;
1629 Ok(BuiltinCallHandling::Handled)
1630 }
1631 "__quantum__qis__rz__body" => {
1632 replace_rz_call(
1633 args.ctx,
1634 module_ref(args),
1635 args.instr,
1636 args.capability_flags.dynamic_qubit_management,
1637 required_num_qubits,
1638 )?;
1639 Ok(BuiltinCallHandling::Handled)
1640 }
1641 "__quantum__qis__rzz__body" => {
1642 replace_rzz_call(
1643 args.ctx,
1644 module_ref(args),
1645 args.instr,
1646 args.capability_flags.dynamic_qubit_management,
1647 required_num_qubits,
1648 )?;
1649 Ok(BuiltinCallHandling::Handled)
1650 }
1651 "__quantum__qis__u1q__body" => {
1652 log::info!(
1653 "`__quantum__qis__u1q__body` used, synonym for `__quantum__qis__rxy__body`"
1654 );
1655 replace_rxy_call(
1656 args.ctx,
1657 module_ref(args),
1658 args.instr,
1659 args.capability_flags.dynamic_qubit_management,
1660 required_num_qubits,
1661 )?;
1662 Ok(BuiltinCallHandling::Handled)
1663 }
1664 "__quantum__qis__mz__body"
1665 | "__quantum__qis__m__body"
1666 | "__quantum__qis__mresetz__body" => {
1667 handle_mz_call(
1668 args.ctx,
1669 args.module.cast::<()>(),
1670 &args.instr,
1671 args.fn_name.as_str(),
1672 args.capability_flags,
1673 args.qubit_array,
1674 args.qubit_array_type,
1675 args.result_ssa.cast::<()>(),
1676 )?;
1677 Ok(BuiltinCallHandling::Handled)
1678 }
1679 "__quantum__qis__mz_leaked__body" => {
1680 handle_mz_leaked_call(args)?;
1681 Ok(BuiltinCallHandling::Handled)
1682 }
1683 "__quantum__qis__reset__body" => {
1684 handle_reset_call(args)?;
1685 Ok(BuiltinCallHandling::Handled)
1686 }
1687 name if name.starts_with("__quantum__qis__barrier") && name.ends_with("__body") => {
1688 handle_barrier_call(args)?;
1689 Ok(BuiltinCallHandling::Handled)
1690 }
1691 _ => Ok(BuiltinCallHandling::Unsupported),
1692 }
1693 }
1694
1695 fn try_handle_rt_call(args: &mut ProcessCallArgs<'_>) -> Result<BuiltinCallHandling, String> {
1696 match args.fn_name.as_str() {
1697 "__quantum__rt__initialize" => {
1698 args.instr.erase_from_basic_block();
1699 Ok(BuiltinCallHandling::Handled)
1700 }
1701 "__quantum__rt__qubit_allocate" => {
1702 lower_dynamic_qubit_allocate(args)?;
1703 Ok(BuiltinCallHandling::Handled)
1704 }
1705 "__quantum__rt__qubit_release" => {
1706 lower_dynamic_qubit_release(args)?;
1707 Ok(BuiltinCallHandling::Handled)
1708 }
1709 "__quantum__rt__qubit_array_allocate" => {
1710 lower_dynamic_qubit_array_allocate(args)?;
1711 Ok(BuiltinCallHandling::Handled)
1712 }
1713 "__quantum__rt__qubit_array_release" => {
1714 lower_dynamic_qubit_array_release(args)?;
1715 Ok(BuiltinCallHandling::Handled)
1716 }
1717 "__quantum__rt__result_allocate" => {
1718 lower_dynamic_result_allocate(args)?;
1719 Ok(BuiltinCallHandling::Handled)
1720 }
1721 "__quantum__rt__result_release" => {
1722 lower_dynamic_result_release(args)?;
1723 Ok(BuiltinCallHandling::Handled)
1724 }
1725 "__quantum__rt__result_array_allocate" => {
1726 lower_dynamic_result_array_allocate(args)?;
1727 Ok(BuiltinCallHandling::Handled)
1728 }
1729 "__quantum__rt__result_array_release" => {
1730 lower_dynamic_result_array_release(args)?;
1731 Ok(BuiltinCallHandling::Handled)
1732 }
1733 "__quantum__rt__result_array_record_output" => {
1734 lower_dynamic_result_array_record_output(args)?;
1735 Ok(BuiltinCallHandling::Handled)
1736 }
1737 "__quantum__rt__read_result" | "__quantum__rt__result_record_output" => {
1738 handle_read_result_call(
1739 args.ctx,
1740 args.module.cast::<()>(),
1741 &args.instr,
1742 args.fn_name.as_str(),
1743 args.capability_flags,
1744 args.global_mapping.cast::<()>(),
1745 args.result_ssa.cast::<()>(),
1746 )?;
1747 Ok(BuiltinCallHandling::Handled)
1748 }
1749 "__quantum__rt__tuple_record_output" | "__quantum__rt__array_record_output" => {
1750 let fn_name = args.fn_name.clone();
1751 handle_tuple_or_array_output(
1752 args.ctx,
1753 module_ref(args),
1754 args.instr,
1755 unsafe { &mut *args.global_mapping },
1756 fn_name.as_str(),
1757 )?;
1758 Ok(BuiltinCallHandling::Handled)
1759 }
1760 "__quantum__rt__bool_record_output"
1761 | "__quantum__rt__int_record_output"
1762 | "__quantum__rt__double_record_output" => {
1763 handle_classical_record_output(args)?;
1764 Ok(BuiltinCallHandling::Handled)
1765 }
1766 _ => Ok(BuiltinCallHandling::Unsupported),
1767 }
1768 }
1769
1770 fn handle_qtm_call(args: &ProcessCallArgs<'_>) -> Result<(), String> {
1771 match args.fn_name.as_str() {
1772 "___get_current_shot" => {
1773 handle_get_current_shot(args)?;
1774 }
1775 "___random_seed" => {
1776 handle_random_seed(args)?;
1777 }
1778 "___random_int" => {
1779 handle_random_int(args)?;
1780 }
1781 "___random_float" => {
1782 handle_random_float(args)?;
1783 }
1784 "___random_int_bounded" => {
1785 handle_random_int_bounded(args)?;
1786 }
1787 "___random_advance" => {
1788 handle_random_advance(args)?;
1789 }
1790 "___get_wasm_context" => {
1791 log::debug!("___get_wasm_context found, leaving as-is for downstream processing");
1793 }
1794 "___barrier" => {
1795 return Err("Unsupported Qtm QIS function: ___barrier".to_string());
1796 }
1797 _ => {
1798 log::trace!("Ignoring Qtm QIS function: {}", args.fn_name);
1800 }
1801 }
1802 Ok(())
1803 }
1804
1805 fn get_qubit_handle<'ctx>(
1806 ctx: &'ctx Context,
1807 capability_flags: CapabilityFlags,
1808 qubit_array: Option<PointerValue<'ctx>>,
1809 qubit_array_type: Option<ArrayType<'ctx>>,
1810 builder: &inkwell::builder::Builder<'ctx>,
1811 qubit_ptr: PointerValue<'ctx>,
1812 ) -> Result<BasicValueEnum<'ctx>, String> {
1813 if capability_flags.dynamic_qubit_management {
1814 return builder
1815 .build_ptr_to_int(qubit_ptr, ctx.i64_type(), "qbit")
1816 .map(BasicValueEnum::from)
1817 .map_err(|e| format!("Failed to convert qubit pointer to handle: {e}"));
1818 }
1819
1820 let qubit_array = qubit_array.ok_or("Missing static qubit array for qubit lookup")?;
1821 let qubit_array_type =
1822 qubit_array_type.ok_or("Missing static qubit array type for qubit lookup")?;
1823 let i64_type = ctx.i64_type();
1824 let index = get_index(qubit_ptr)?;
1825 let index = checked_qubit_index(index, qubit_array_type.len())?;
1826 let index_val = i64_type.const_int(index, false);
1827 let elem_ptr = unsafe {
1828 builder.build_gep(
1829 qubit_array_type,
1830 qubit_array,
1831 &[i64_type.const_zero(), index_val],
1832 "",
1833 )
1834 }
1835 .map_err(|e| format!("Failed to build GEP for qubit handle: {e}"))?;
1836 builder
1837 .build_load(i64_type, elem_ptr, "qbit")
1838 .map_err(|e| format!("Failed to build load for qubit handle: {e}"))
1839 }
1840
1841 const fn module_ref<'ctx>(args: &ProcessCallArgs<'ctx>) -> &'ctx Module<'ctx> {
1842 unsafe { &*args.module }
1845 }
1846
1847 const fn passthrough_calls_ref<'a>(args: &'a ProcessCallArgs<'_>) -> &'a BTreeSet<String> {
1848 unsafe { &*args.passthrough_calls }
1851 }
1852
1853 fn get_or_create_qalloc_fail_global<'ctx>(
1854 ctx: &'ctx Context,
1855 module: &Module<'ctx>,
1856 ) -> inkwell::values::GlobalValue<'ctx> {
1857 let panic_msg = ctx.const_string(b".EXIT:INT:No more qubits available to allocate.", false);
1858 let panic_arr_ty = panic_msg.get_type();
1859 module.get_global("e_qalloc_fail").unwrap_or_else(|| {
1860 let global = module.add_global(panic_arr_ty, None, "e_qalloc_fail");
1861 global.set_initializer(&panic_msg);
1862 global.set_linkage(Linkage::Private);
1863 global.set_constant(true);
1864 global
1865 })
1866 }
1867
1868 struct DynamicQubitAllocatePaths<'ctx> {
1869 fail: BasicBlock<'ctx>,
1870 fail_store: BasicBlock<'ctx>,
1871 fail_panic: BasicBlock<'ctx>,
1872 success: BasicBlock<'ctx>,
1873 store_ok: BasicBlock<'ctx>,
1874 ret_ok: BasicBlock<'ctx>,
1875 }
1876
1877 fn build_dynamic_qubit_allocate_fail_path<'ctx>(
1878 ctx: &'ctx Context,
1879 module: &Module<'ctx>,
1880 builder: &inkwell::builder::Builder<'ctx>,
1881 ptr_type: inkwell::types::PointerType<'ctx>,
1882 out_err: PointerValue<'ctx>,
1883 paths: &DynamicQubitAllocatePaths<'ctx>,
1884 ) {
1885 builder.position_at_end(paths.fail);
1886 let out_err_is_null =
1887 build_ptr_is_null(ctx, builder, out_err, "out_err_int").expect("out_err null check");
1888 builder
1889 .build_conditional_branch(out_err_is_null, paths.fail_panic, paths.fail_store)
1890 .expect("branch fail handler");
1891
1892 builder.position_at_end(paths.fail_store);
1893 let _ = builder.build_store(out_err, ctx.bool_type().const_all_ones());
1894 builder
1895 .build_return(Some(&ptr_type.const_zero()))
1896 .expect("return null qubit");
1897
1898 builder.position_at_end(paths.fail_panic);
1899 let err_global = get_or_create_qalloc_fail_global(ctx, module);
1900 let err_ty = err_global
1901 .get_initializer()
1902 .expect("panic global initializer")
1903 .into_array_value()
1904 .get_type();
1905 let err_gep = unsafe {
1906 builder.build_gep(
1907 err_ty,
1908 err_global.as_pointer_value(),
1909 &[ctx.i64_type().const_zero(), ctx.i64_type().const_zero()],
1910 "err_gep",
1911 )
1912 }
1913 .expect("panic msg gep");
1914 let panic_fn = get_or_create_function(
1915 module,
1916 "panic",
1917 ctx.void_type()
1918 .fn_type(&[ctx.i32_type().into(), ptr_type.into()], false),
1919 );
1920 let _ = builder.build_call(
1921 panic_fn,
1922 &[ctx.i32_type().const_int(1001, false).into(), err_gep.into()],
1923 "",
1924 );
1925 builder.build_unreachable().expect("unreachable");
1926 }
1927
1928 fn build_dynamic_qubit_allocate_success_path<'ctx>(
1929 ctx: &'ctx Context,
1930 builder: &inkwell::builder::Builder<'ctx>,
1931 ptr_type: inkwell::types::PointerType<'ctx>,
1932 out_err: PointerValue<'ctx>,
1933 qid: inkwell::values::IntValue<'ctx>,
1934 paths: &DynamicQubitAllocatePaths<'ctx>,
1935 ) {
1936 builder.position_at_end(paths.success);
1937 let out_err_is_null =
1938 build_ptr_is_null(ctx, builder, out_err, "out_err_int_ok").expect("out_err null check");
1939 builder
1940 .build_conditional_branch(out_err_is_null, paths.ret_ok, paths.store_ok)
1941 .expect("branch success handler");
1942
1943 builder.position_at_end(paths.store_ok);
1944 let _ = builder.build_store(out_err, ctx.bool_type().const_zero());
1945 builder
1946 .build_unconditional_branch(paths.ret_ok)
1947 .expect("jump ret");
1948
1949 builder.position_at_end(paths.ret_ok);
1950 let ptr_val = builder
1951 .build_int_to_ptr(qid, ptr_type, "qubit_ptr")
1952 .expect("int to ptr");
1953 builder
1954 .build_return(Some(&ptr_val))
1955 .expect("return qubit ptr");
1956 }
1957
1958 struct DynamicQubitArrayAllocateBlocks<'ctx> {
1959 loop_header: BasicBlock<'ctx>,
1960 loop_body: BasicBlock<'ctx>,
1961 loop_exit: BasicBlock<'ctx>,
1962 continue_alloc: BasicBlock<'ctx>,
1963 maybe_rollback: BasicBlock<'ctx>,
1964 rollback: BasicBlock<'ctx>,
1965 rollback_done: BasicBlock<'ctx>,
1966 }
1967
1968 struct PointerArrayReleaseHelperArgs<'ctx> {
1969 function: FunctionValue<'ctx>,
1970 len: inkwell::values::IntValue<'ctx>,
1971 array_ptr: PointerValue<'ctx>,
1972 release_fn: FunctionValue<'ctx>,
1973 ptr_elem_type: inkwell::types::PointerType<'ctx>,
1974 gep_error: &'static str,
1975 load_error: &'static str,
1976 release_error: &'static str,
1977 }
1978
1979 fn create_dynamic_qubit_array_allocate_blocks<'ctx>(
1980 ctx: &'ctx Context,
1981 function: FunctionValue<'ctx>,
1982 ) -> DynamicQubitArrayAllocateBlocks<'ctx> {
1983 DynamicQubitArrayAllocateBlocks {
1984 loop_header: ctx.append_basic_block(function, "loop_header"),
1985 loop_body: ctx.append_basic_block(function, "loop_body"),
1986 loop_exit: ctx.append_basic_block(function, "loop_exit"),
1987 continue_alloc: ctx.append_basic_block(function, "continue_alloc"),
1988 maybe_rollback: ctx.append_basic_block(function, "maybe_rollback"),
1989 rollback: ctx.append_basic_block(function, "rollback"),
1990 rollback_done: ctx.append_basic_block(function, "rollback_done"),
1991 }
1992 }
1993
1994 fn build_dynamic_qubit_array_allocate_rollback<'ctx>(
1995 ctx: &'ctx Context,
1996 builder: &inkwell::builder::Builder<'ctx>,
1997 idx: inkwell::values::IntValue<'ctx>,
1998 array_ptr: PointerValue<'ctx>,
1999 release_array_fn: FunctionValue<'ctx>,
2000 rollback: BasicBlock<'ctx>,
2001 rollback_done: BasicBlock<'ctx>,
2002 ) {
2003 builder.position_at_end(rollback);
2004 let rollback_len = builder
2005 .build_int_add(idx, ctx.i64_type().const_int(1, false), "rollback_len")
2006 .expect("rollback len");
2007 let _ = builder
2008 .build_call(
2009 release_array_fn,
2010 &[rollback_len.into(), array_ptr.into()],
2011 "",
2012 )
2013 .expect("rollback release");
2014 builder
2015 .build_unconditional_branch(rollback_done)
2016 .expect("jump rollback_done");
2017 }
2018
2019 fn get_bool_cl_array_type(ctx: &Context) -> inkwell::types::StructType<'_> {
2020 ctx.struct_type(
2021 &[
2022 ctx.i32_type().into(),
2023 ctx.i32_type().into(),
2024 ctx.ptr_type(AddressSpace::default()).into(),
2025 ctx.ptr_type(AddressSpace::default()).into(),
2026 ],
2027 true,
2028 )
2029 }
2030
2031 fn build_dynamic_result_array_values<'ctx>(
2032 ctx: &'ctx Context,
2033 function: FunctionValue<'ctx>,
2034 builder: &inkwell::builder::Builder<'ctx>,
2035 len: inkwell::values::IntValue<'ctx>,
2036 array_ptr: PointerValue<'ctx>,
2037 read_fn: FunctionValue<'ctx>,
2038 ) -> Result<PointerValue<'ctx>, String> {
2039 let ptr_type = ctx.ptr_type(AddressSpace::default());
2040 let bool_arr = builder
2041 .build_array_alloca(ctx.bool_type(), len, "result_arr_data")
2042 .map_err(|e| format!("Failed to allocate result array data: {e}"))?;
2043 build_dynamic_array_loop(ctx, function, builder, len, |builder, idx| {
2044 let elem_ptr = unsafe { builder.build_gep(ptr_type, array_ptr, &[idx], "elem_ptr") }
2045 .map_err(|e| format!("Failed to build result record array GEP: {e}"))?;
2046 let result_ptr = builder
2047 .build_load(ptr_type, elem_ptr, "result_ptr")
2048 .map_err(|e| format!("Failed to load result pointer: {e}"))?;
2049 let bool_call = builder
2050 .build_call(read_fn, &[result_ptr.into()], "result_bool")
2051 .map_err(|e| format!("Failed to read result array element: {e}"))?;
2052 let bool_val = match bool_call.try_as_basic_value() {
2053 inkwell::values::ValueKind::Basic(bv) => bv,
2054 inkwell::values::ValueKind::Instruction(_) => {
2055 return Err("Dynamic result read helper did not return a bool".to_string());
2056 }
2057 };
2058 let out_ptr =
2059 unsafe { builder.build_gep(ctx.bool_type(), bool_arr, &[idx], "result_bool_ptr") }
2060 .map_err(|e| format!("Failed to index result bool array: {e}"))?;
2061 let _ = builder
2062 .build_store(out_ptr, bool_val)
2063 .map_err(|e| format!("Failed to store result bool array element: {e}"))?;
2064 Ok(())
2065 })?;
2066 Ok(bool_arr)
2067 }
2068
2069 fn build_dynamic_result_array_descriptor<'ctx>(
2070 ctx: &'ctx Context,
2071 builder: &inkwell::builder::Builder<'ctx>,
2072 len: inkwell::values::IntValue<'ctx>,
2073 bool_arr: PointerValue<'ctx>,
2074 ) -> Result<PointerValue<'ctx>, String> {
2075 let ptr_type = ctx.ptr_type(AddressSpace::default());
2076 let array_desc_type = get_bool_cl_array_type(ctx);
2077 let array_desc = builder
2078 .build_alloca(array_desc_type, "result_arr_desc")
2079 .map_err(|e| format!("Failed to allocate result array descriptor: {e}"))?;
2080 let x_ptr = builder
2081 .build_struct_gep(array_desc_type, array_desc, 0, "result_arr_x")
2082 .map_err(|e| format!("Failed to build result array length GEP: {e}"))?;
2083 let y_ptr = builder
2084 .build_struct_gep(array_desc_type, array_desc, 1, "result_arr_y")
2085 .map_err(|e| format!("Failed to build result array rank GEP: {e}"))?;
2086 let data_ptr = builder
2087 .build_struct_gep(array_desc_type, array_desc, 2, "result_arr_data_ptr")
2088 .map_err(|e| format!("Failed to build result array data GEP: {e}"))?;
2089 let mask_ptr = builder
2090 .build_struct_gep(array_desc_type, array_desc, 3, "result_arr_mask_ptr")
2091 .map_err(|e| format!("Failed to build result array mask GEP: {e}"))?;
2092 let mask_zero = builder
2093 .build_alloca(ctx.i32_type(), "result_arr_mask")
2094 .map_err(|e| format!("Failed to allocate result array mask: {e}"))?;
2095 let _ = builder
2096 .build_store(mask_zero, ctx.i32_type().const_zero())
2097 .map_err(|e| format!("Failed to initialize result array mask: {e}"))?;
2098 let len_i32 = builder
2099 .build_int_truncate(len, ctx.i32_type(), "result_arr_len")
2100 .map_err(|e| format!("Failed to truncate result array length: {e}"))?;
2101 let _ = builder
2102 .build_store(x_ptr, len_i32)
2103 .map_err(|e| format!("Failed to store result array length: {e}"))?;
2104 let _ = builder
2105 .build_store(y_ptr, ctx.i32_type().const_int(1, false))
2106 .map_err(|e| format!("Failed to store result array rank: {e}"))?;
2107 let data_as_ptr = builder
2108 .build_bit_cast(bool_arr, ptr_type, "result_arr_data_cast")
2109 .map_err(|e| format!("Failed to cast result array data pointer: {e}"))?;
2110 let _ = builder
2111 .build_store(data_ptr, data_as_ptr)
2112 .map_err(|e| format!("Failed to store result array data pointer: {e}"))?;
2113 let mask_as_ptr = builder
2114 .build_bit_cast(mask_zero, ptr_type, "result_arr_mask_cast")
2115 .map_err(|e| format!("Failed to cast result array mask pointer: {e}"))?;
2116 let _ = builder
2117 .build_store(mask_ptr, mask_as_ptr)
2118 .map_err(|e| format!("Failed to store result array mask pointer: {e}"))?;
2119 Ok(array_desc)
2120 }
2121
2122 struct DynamicResultSlotPtrs<'ctx> {
2123 state: PointerValue<'ctx>,
2124 cached: PointerValue<'ctx>,
2125 future: PointerValue<'ctx>,
2126 }
2127
2128 fn get_dynamic_result_slot_ptrs<'ctx>(
2129 builder: &inkwell::builder::Builder<'ctx>,
2130 slot_type: inkwell::types::StructType<'ctx>,
2131 result_ptr: PointerValue<'ctx>,
2132 ) -> DynamicResultSlotPtrs<'ctx> {
2133 DynamicResultSlotPtrs {
2134 state: builder
2135 .build_struct_gep(slot_type, result_ptr, 0, "state")
2136 .expect("state gep"),
2137 cached: builder
2138 .build_struct_gep(slot_type, result_ptr, 1, "cached")
2139 .expect("cached gep"),
2140 future: builder
2141 .build_struct_gep(slot_type, result_ptr, 2, "future")
2142 .expect("future gep"),
2143 }
2144 }
2145
2146 #[allow(
2147 clippy::unreachable,
2148 reason = "a runtime helper returning no basic value indicates a broken lowering invariant"
2149 )]
2150 fn build_dynamic_result_pending_return<'ctx>(
2151 ctx: &'ctx Context,
2152 module: &Module<'ctx>,
2153 builder: &inkwell::builder::Builder<'ctx>,
2154 future_ptr: PointerValue<'ctx>,
2155 cached_ptr: PointerValue<'ctx>,
2156 state_ptr: PointerValue<'ctx>,
2157 ) {
2158 let future = builder
2159 .build_load(ctx.i64_type(), future_ptr, "future")
2160 .expect("load future");
2161 let read_fn = get_or_create_function(
2162 module,
2163 "___read_future_bool",
2164 ctx.bool_type().fn_type(&[ctx.i64_type().into()], false),
2165 );
2166 let dec_fn = get_or_create_function(
2167 module,
2168 "___dec_future_refcount",
2169 ctx.void_type().fn_type(&[ctx.i64_type().into()], false),
2170 );
2171 let bool_call = builder
2172 .build_call(read_fn, &[future.into()], "bool")
2173 .expect("read future");
2174 let bool_val = match bool_call.try_as_basic_value() {
2175 inkwell::values::ValueKind::Basic(bv) => bv,
2176 inkwell::values::ValueKind::Instruction(_) => unreachable!(),
2177 };
2178 let _ = builder.build_call(dec_fn, &[future.into()], "");
2179 let _ = builder.build_store(cached_ptr, bool_val);
2180 let _ = builder.build_store(state_ptr, ctx.i8_type().const_int(2, false));
2181 builder
2182 .build_return(Some(&bool_val.into_int_value()))
2183 .expect("ret pending");
2184 }
2185
2186 fn call_basic_value<'ctx>(
2187 builder: &inkwell::builder::Builder<'ctx>,
2188 callee: FunctionValue<'ctx>,
2189 args: &[BasicMetadataValueEnum<'ctx>],
2190 name: &str,
2191 build_error: &str,
2192 value_error: &str,
2193 ) -> Result<BasicValueEnum<'ctx>, String> {
2194 let call = match builder.build_call(callee, args, name) {
2195 Ok(call) => call,
2196 Err(err) => return Err(format!("{build_error}: {err}")),
2197 };
2198 match call.try_as_basic_value() {
2199 inkwell::values::ValueKind::Basic(bv) => Ok(bv),
2200 inkwell::values::ValueKind::Instruction(_) => Err(value_error.to_string()),
2201 }
2202 }
2203
2204 fn clear_dynamic_result_slot<'ctx>(
2205 ctx: &'ctx Context,
2206 builder: &inkwell::builder::Builder<'ctx>,
2207 slot_ptrs: &DynamicResultSlotPtrs<'ctx>,
2208 ) {
2209 let _ = builder.build_store(slot_ptrs.state, ctx.i8_type().const_zero());
2210 let _ = builder.build_store(slot_ptrs.cached, ctx.bool_type().const_zero());
2211 let _ = builder.build_store(slot_ptrs.future, ctx.i64_type().const_zero());
2212 }
2213
2214 fn set_dynamic_result_pending<'ctx>(
2215 ctx: &'ctx Context,
2216 builder: &inkwell::builder::Builder<'ctx>,
2217 slot_ptrs: &DynamicResultSlotPtrs<'ctx>,
2218 future: inkwell::values::IntValue<'ctx>,
2219 ) {
2220 let _ = builder.build_store(slot_ptrs.state, ctx.i8_type().const_int(1, false));
2221 let _ = builder.build_store(slot_ptrs.future, future);
2222 }
2223
2224 fn read_result_bool<'ctx>(
2225 ctx: &'ctx Context,
2226 module: &Module<'ctx>,
2227 builder: &inkwell::builder::Builder<'ctx>,
2228 result_ptr: PointerValue<'ctx>,
2229 capability_flags: CapabilityFlags,
2230 result_ssa: &mut [Option<(BasicValueEnum<'ctx>, Option<BasicValueEnum<'ctx>>)>],
2231 ) -> Result<BasicValueEnum<'ctx>, String> {
2232 if capability_flags.dynamic_result_management {
2233 let read_func = ensure_dynamic_result_read(ctx, module);
2234 return call_basic_value(
2235 builder,
2236 read_func,
2237 &[result_ptr.into()],
2238 "bool",
2239 "Failed to build call for dynamic result read",
2240 "Failed to get basic value from dynamic result read call",
2241 );
2242 }
2243
2244 let result_idx = get_index(result_ptr)?;
2245 let result_idx_usize = usize::try_from(result_idx)
2246 .map_err(|e| format!("Failed to convert result index to usize: {e}"))?;
2247 let meas_handle = result_ssa[result_idx_usize]
2248 .ok_or_else(|| "Expected measurement handle".to_string())?;
2249 result_ssa[result_idx_usize]
2250 .and_then(|v| v.1)
2251 .and_then(|val: BasicValueEnum<'_>| val.as_instruction_value())
2252 .map_or_else(
2253 || {
2254 let read_func = get_or_create_function(
2255 module,
2256 "___read_future_bool",
2257 ctx.bool_type().fn_type(&[ctx.i64_type().into()], false),
2258 );
2259 let bool_val = call_basic_value(
2260 builder,
2261 read_func,
2262 &[meas_handle.0.into()],
2263 "bool",
2264 "Failed to build call for read_future_bool",
2265 "Failed to get basic value from read_future_bool call",
2266 )?;
2267 let dec_func = get_or_create_function(
2268 module,
2269 "___dec_future_refcount",
2270 ctx.void_type().fn_type(&[ctx.i64_type().into()], false),
2271 );
2272 let _ = builder
2273 .build_call(dec_func, &[meas_handle.0.into()], "")
2274 .map_err(|e| {
2275 format!("Failed to build call for dec_future_refcount: {e}")
2276 })?;
2277 result_ssa[result_idx_usize] = Some((meas_handle.0, Some(bool_val)));
2278 Ok(bool_val)
2279 },
2280 |val: inkwell::values::InstructionValue<'_>| {
2281 val.as_any_value_enum()
2282 .try_into()
2283 .map_err(|()| "Expected BasicValueEnum".to_string())
2284 },
2285 )
2286 }
2287
2288 fn get_or_create_result_output_global<'ctx>(
2289 ctx: &'ctx Context,
2290 module: &Module<'ctx>,
2291 global_mapping: &mut HashMap<String, inkwell::values::GlobalValue<'ctx>>,
2292 capability_flags: CapabilityFlags,
2293 result_ptr: PointerValue<'ctx>,
2294 gep: BasicValueEnum<'ctx>,
2295 ) -> Result<inkwell::values::GlobalValue<'ctx>, String> {
2296 if let Ok(old_global) = parse_gep(gep) {
2297 return global_mapping
2298 .get(old_global.as_str())
2299 .copied()
2300 .ok_or_else(|| format!("Output global `{old_global}` not found in mapping"));
2301 }
2302
2303 let fallback_label = if capability_flags.dynamic_result_management {
2304 "result_dynamic".to_string()
2305 } else {
2306 format!("result_{}", get_index(result_ptr)?)
2307 };
2308 let (new_const, new_name) =
2309 build_result_global(ctx, &fallback_label, &fallback_label, "RESULT", None)?;
2310 let new_global = module.add_global(new_const.get_type(), None, &new_name);
2311 new_global.set_initializer(&new_const);
2312 new_global.set_linkage(inkwell::module::Linkage::Private);
2313 new_global.set_constant(true);
2314 global_mapping.insert(fallback_label, new_global);
2315 Ok(new_global)
2316 }
2317
2318 fn get_dynamic_result_slot_type(ctx: &Context) -> inkwell::types::StructType<'_> {
2319 ctx.struct_type(
2320 &[
2321 ctx.i8_type().into(), ctx.bool_type().into(), ctx.i64_type().into(), ],
2325 false,
2326 )
2327 }
2328
2329 fn build_ptr_is_null<'ctx>(
2330 ctx: &'ctx Context,
2331 builder: &inkwell::builder::Builder<'ctx>,
2332 ptr: PointerValue<'ctx>,
2333 name: &str,
2334 ) -> Result<inkwell::values::IntValue<'ctx>, String> {
2335 let ptr_as_int = builder
2336 .build_ptr_to_int(ptr, ctx.i64_type(), name)
2337 .map_err(|e| format!("Failed to convert pointer to int: {e}"))?;
2338 builder
2339 .build_int_compare(
2340 inkwell::IntPredicate::EQ,
2341 ptr_as_int,
2342 ctx.i64_type().const_zero(),
2343 "is_null",
2344 )
2345 .map_err(|e| format!("Failed to compare pointer with null: {e}"))
2346 }
2347
2348 #[allow(
2349 clippy::unreachable,
2350 reason = "the internal qalloc helper must produce a basic value or lowering is inconsistent"
2351 )]
2352 fn ensure_dynamic_qubit_allocate<'ctx>(
2353 ctx: &'ctx Context,
2354 module: &Module<'ctx>,
2355 ) -> FunctionValue<'ctx> {
2356 if let Some(existing) = module.get_function("qir_qis.qubit_allocate") {
2357 return existing;
2358 }
2359
2360 let ptr_type = ctx.ptr_type(AddressSpace::default());
2361 let fn_type = ptr_type.fn_type(&[ptr_type.into()], false);
2362 let function =
2363 module.add_function("qir_qis.qubit_allocate", fn_type, Some(Linkage::Private));
2364 let builder = ctx.create_builder();
2365 let entry = ctx.append_basic_block(function, "entry");
2366 let paths = DynamicQubitAllocatePaths {
2367 fail: ctx.append_basic_block(function, "fail"),
2368 fail_store: ctx.append_basic_block(function, "fail_store"),
2369 fail_panic: ctx.append_basic_block(function, "fail_panic"),
2370 success: ctx.append_basic_block(function, "success"),
2371 store_ok: ctx.append_basic_block(function, "store_ok"),
2372 ret_ok: ctx.append_basic_block(function, "ret_ok"),
2373 };
2374 builder.position_at_end(entry);
2375
2376 let out_err = function
2377 .get_first_param()
2378 .expect("qubit allocate helper has out_err")
2379 .into_pointer_value();
2380 let qalloc_fn =
2381 get_or_create_function(module, "___qalloc", ctx.i64_type().fn_type(&[], false));
2382 let call_result = builder
2383 .build_call(qalloc_fn, &[], "qalloc")
2384 .expect("qalloc call");
2385 let qid = match call_result.try_as_basic_value() {
2386 inkwell::values::ValueKind::Basic(bv) => bv.into_int_value(),
2387 inkwell::values::ValueKind::Instruction(_) => unreachable!(),
2388 };
2389 let is_fail = builder
2390 .build_int_compare(
2391 inkwell::IntPredicate::EQ,
2392 qid,
2393 ctx.i64_type().const_int(u64::MAX, false),
2394 "is_fail",
2395 )
2396 .expect("compare fail");
2397 builder
2398 .build_conditional_branch(is_fail, paths.fail, paths.success)
2399 .expect("branch fail");
2400 build_dynamic_qubit_allocate_fail_path(ctx, module, &builder, ptr_type, out_err, &paths);
2401 build_dynamic_qubit_allocate_success_path(ctx, &builder, ptr_type, out_err, qid, &paths);
2402 function
2403 }
2404
2405 fn ensure_dynamic_qubit_release<'ctx>(
2406 ctx: &'ctx Context,
2407 module: &Module<'ctx>,
2408 ) -> FunctionValue<'ctx> {
2409 if let Some(existing) = module.get_function("qir_qis.qubit_release") {
2410 return existing;
2411 }
2412 let ptr_type = ctx.ptr_type(AddressSpace::default());
2413 let fn_type = ctx.void_type().fn_type(&[ptr_type.into()], false);
2414 let function =
2415 module.add_function("qir_qis.qubit_release", fn_type, Some(Linkage::Private));
2416 let builder = ctx.create_builder();
2417 let entry = ctx.append_basic_block(function, "entry");
2418 let ret = ctx.append_basic_block(function, "ret");
2419 let body = ctx.append_basic_block(function, "body");
2420 builder.position_at_end(entry);
2421 let qubit_ptr = function
2422 .get_first_param()
2423 .expect("qubit release param")
2424 .into_pointer_value();
2425 let is_null = build_ptr_is_null(ctx, &builder, qubit_ptr, "qubit_int").expect("null check");
2426 builder
2427 .build_conditional_branch(is_null, ret, body)
2428 .expect("branch");
2429 builder.position_at_end(body);
2430 let q_handle = builder
2431 .build_ptr_to_int(qubit_ptr, ctx.i64_type(), "qbit")
2432 .expect("ptr to int");
2433 let qfree_fn = get_or_create_function(
2434 module,
2435 "___qfree",
2436 ctx.void_type().fn_type(&[ctx.i64_type().into()], false),
2437 );
2438 let _ = builder.build_call(qfree_fn, &[q_handle.into()], "");
2439 builder.build_unconditional_branch(ret).expect("jump ret");
2440 builder.position_at_end(ret);
2441 builder.build_return(None).expect("return");
2442 function
2443 }
2444
2445 fn build_dynamic_array_loop<'ctx, F>(
2446 ctx: &'ctx Context,
2447 function: FunctionValue<'ctx>,
2448 builder: &inkwell::builder::Builder<'ctx>,
2449 trip_count: inkwell::values::IntValue<'ctx>,
2450 mut body_builder: F,
2451 ) -> Result<(), String>
2452 where
2453 F: FnMut(
2454 &inkwell::builder::Builder<'ctx>,
2455 inkwell::values::IntValue<'ctx>,
2456 ) -> Result<(), String>,
2457 {
2458 let entry_block = builder
2459 .get_insert_block()
2460 .ok_or("Missing loop entry block")?;
2461 let loop_header = ctx.append_basic_block(function, "loop_header");
2462 let loop_body = ctx.append_basic_block(function, "loop_body");
2463 let loop_exit = ctx.append_basic_block(function, "loop_exit");
2464 builder
2465 .build_unconditional_branch(loop_header)
2466 .map_err(|e| format!("Failed to branch to loop header: {e}"))?;
2467 builder.position_at_end(loop_header);
2468 let idx_phi = builder
2469 .build_phi(ctx.i64_type(), "idx")
2470 .map_err(|e| format!("Failed to create loop phi: {e}"))?;
2471 idx_phi.add_incoming(&[(&ctx.i64_type().const_zero(), entry_block)]);
2472 let idx = idx_phi.as_basic_value().into_int_value();
2473 let cond = builder
2474 .build_int_compare(inkwell::IntPredicate::ULT, idx, trip_count, "loop_cond")
2475 .map_err(|e| format!("Failed to build loop condition: {e}"))?;
2476 builder
2477 .build_conditional_branch(cond, loop_body, loop_exit)
2478 .map_err(|e| format!("Failed to build loop branch: {e}"))?;
2479 builder.position_at_end(loop_body);
2480 body_builder(builder, idx)?;
2481 let next = builder
2482 .build_int_add(idx, ctx.i64_type().const_int(1, false), "next_idx")
2483 .map_err(|e| format!("Failed to increment loop index: {e}"))?;
2484 builder
2485 .build_unconditional_branch(loop_header)
2486 .map_err(|e| format!("Failed to jump to loop header: {e}"))?;
2487 idx_phi.add_incoming(&[(&next, loop_body)]);
2488 builder.position_at_end(loop_exit);
2489 Ok(())
2490 }
2491
2492 fn build_pointer_array_release_helper<'ctx>(
2493 ctx: &'ctx Context,
2494 builder: &inkwell::builder::Builder<'ctx>,
2495 args: &PointerArrayReleaseHelperArgs<'ctx>,
2496 ) -> Result<(), String> {
2497 build_dynamic_array_loop(ctx, args.function, builder, args.len, |builder, idx| {
2498 let elem_ptr = unsafe {
2499 builder.build_gep(args.ptr_elem_type, args.array_ptr, &[idx], "elem_ptr")
2500 }
2501 .map_err(|e| format!("{}: {e}", args.gep_error))?;
2502 let value_ptr = builder
2503 .build_load(args.ptr_elem_type, elem_ptr, "value_ptr")
2504 .map_err(|e| format!("{}: {e}", args.load_error))?;
2505 let _ = builder
2506 .build_call(args.release_fn, &[value_ptr.into()], "")
2507 .map_err(|e| format!("{}: {e}", args.release_error))?;
2508 Ok(())
2509 })
2510 }
2511
2512 #[allow(
2513 clippy::unreachable,
2514 reason = "the dynamic qubit allocation helper must return a pointer or lowering is inconsistent"
2515 )]
2516 fn ensure_dynamic_qubit_array_allocate<'ctx>(
2517 ctx: &'ctx Context,
2518 module: &Module<'ctx>,
2519 ) -> FunctionValue<'ctx> {
2520 if let Some(existing) = module.get_function("qir_qis.qubit_array_allocate") {
2521 return existing;
2522 }
2523 let ptr_type = ctx.ptr_type(AddressSpace::default());
2524 let function = module.add_function(
2525 "qir_qis.qubit_array_allocate",
2526 ctx.void_type().fn_type(
2527 &[ctx.i64_type().into(), ptr_type.into(), ptr_type.into()],
2528 false,
2529 ),
2530 Some(Linkage::Private),
2531 );
2532 let builder = ctx.create_builder();
2533 let entry = ctx.append_basic_block(function, "entry");
2534 builder.position_at_end(entry);
2535 let len = function.get_nth_param(0).expect("len").into_int_value();
2536 let array_ptr = function
2537 .get_nth_param(1)
2538 .expect("array")
2539 .into_pointer_value();
2540 let out_err = function
2541 .get_nth_param(2)
2542 .expect("out_err")
2543 .into_pointer_value();
2544 let alloc_fn = ensure_dynamic_qubit_allocate(ctx, module);
2545 let out_err_success_fn = ensure_out_err_success(ctx, module);
2546 let _ = builder
2547 .build_call(out_err_success_fn, &[out_err.into()], "")
2548 .expect("initialize out_err");
2549 let release_array_fn = ensure_dynamic_qubit_array_release(ctx, module);
2550 let entry_block = entry;
2551 let blocks = create_dynamic_qubit_array_allocate_blocks(ctx, function);
2552
2553 builder
2554 .build_unconditional_branch(blocks.loop_header)
2555 .expect("jump loop_header");
2556 builder.position_at_end(blocks.loop_header);
2557 let idx_phi = builder.build_phi(ctx.i64_type(), "idx").expect("idx phi");
2558 idx_phi.add_incoming(&[(&ctx.i64_type().const_zero(), entry_block)]);
2559 let idx = idx_phi.as_basic_value().into_int_value();
2560 let cond = builder
2561 .build_int_compare(inkwell::IntPredicate::ULT, idx, len, "loop_cond")
2562 .expect("loop cond");
2563 builder
2564 .build_conditional_branch(cond, blocks.loop_body, blocks.loop_exit)
2565 .expect("loop branch");
2566
2567 builder.position_at_end(blocks.loop_body);
2568 let elem_ptr = unsafe { builder.build_gep(ptr_type, array_ptr, &[idx], "elem_ptr") }
2569 .expect("elem gep");
2570 let slot = builder
2571 .build_call(alloc_fn, &[out_err.into()], "qubit_slot")
2572 .expect("alloc qubit");
2573 let slot = match slot.try_as_basic_value() {
2574 inkwell::values::ValueKind::Basic(bv) => bv,
2575 inkwell::values::ValueKind::Instruction(_) => unreachable!(),
2576 };
2577 let _ = builder.build_store(elem_ptr, slot).expect("store qubit");
2578 let out_err_is_null =
2579 build_ptr_is_null(ctx, &builder, out_err, "out_err_int").expect("out_err null check");
2580 builder
2581 .build_conditional_branch(
2582 out_err_is_null,
2583 blocks.continue_alloc,
2584 blocks.maybe_rollback,
2585 )
2586 .expect("branch maybe_rollback");
2587
2588 builder.position_at_end(blocks.maybe_rollback);
2589 let failed = builder
2590 .build_load(ctx.bool_type(), out_err, "alloc_failed")
2591 .expect("load out_err")
2592 .into_int_value();
2593 builder
2594 .build_conditional_branch(failed, blocks.rollback, blocks.continue_alloc)
2595 .expect("branch rollback");
2596 build_dynamic_qubit_array_allocate_rollback(
2597 ctx,
2598 &builder,
2599 idx,
2600 array_ptr,
2601 release_array_fn,
2602 blocks.rollback,
2603 blocks.rollback_done,
2604 );
2605
2606 builder.position_at_end(blocks.continue_alloc);
2607 let next = builder
2608 .build_int_add(idx, ctx.i64_type().const_int(1, false), "next_idx")
2609 .expect("next idx");
2610 builder
2611 .build_unconditional_branch(blocks.loop_header)
2612 .expect("jump loop_header");
2613 idx_phi.add_incoming(&[(&next, blocks.continue_alloc)]);
2614
2615 builder.position_at_end(blocks.loop_exit);
2616 builder.build_return(None).expect("return");
2617
2618 builder.position_at_end(blocks.rollback_done);
2619 builder.build_return(None).expect("return");
2620 function
2621 }
2622
2623 fn ensure_dynamic_qubit_array_release<'ctx>(
2624 ctx: &'ctx Context,
2625 module: &Module<'ctx>,
2626 ) -> FunctionValue<'ctx> {
2627 if let Some(existing) = module.get_function("qir_qis.qubit_array_release") {
2628 return existing;
2629 }
2630 let ptr_type = ctx.ptr_type(AddressSpace::default());
2631 let function = module.add_function(
2632 "qir_qis.qubit_array_release",
2633 ctx.void_type()
2634 .fn_type(&[ctx.i64_type().into(), ptr_type.into()], false),
2635 Some(Linkage::Private),
2636 );
2637 let builder = ctx.create_builder();
2638 let entry = ctx.append_basic_block(function, "entry");
2639 builder.position_at_end(entry);
2640 let len = function.get_nth_param(0).expect("len").into_int_value();
2641 let array_ptr = function
2642 .get_nth_param(1)
2643 .expect("array")
2644 .into_pointer_value();
2645 let release_fn = ensure_dynamic_qubit_release(ctx, module);
2646 build_pointer_array_release_helper(
2647 ctx,
2648 &builder,
2649 &PointerArrayReleaseHelperArgs {
2650 function,
2651 len,
2652 array_ptr,
2653 release_fn,
2654 ptr_elem_type: ptr_type,
2655 gep_error: "Failed to build qubit array GEP",
2656 load_error: "Failed to load dynamic qubit pointer",
2657 release_error: "Failed to release dynamic qubit",
2658 },
2659 )
2660 .expect("build dynamic qubit release loop");
2661 builder.build_return(None).expect("return");
2662 function
2663 }
2664
2665 fn replace_call_with_value<'ctx>(
2666 instr: inkwell::values::InstructionValue<'ctx>,
2667 value: BasicValueEnum<'ctx>,
2668 ) -> Result<(), String> {
2669 let instruction_val = value
2670 .as_instruction_value()
2671 .ok_or("Expected replacement value to be instruction-backed")?;
2672 instr.replace_all_uses_with(&instruction_val);
2673 instr.erase_from_basic_block();
2674 Ok(())
2675 }
2676
2677 fn initialize_dynamic_result_slot<'ctx>(
2678 ctx: &'ctx Context,
2679 builder: &inkwell::builder::Builder<'ctx>,
2680 slot_ptr: PointerValue<'ctx>,
2681 ) {
2682 let slot_type = get_dynamic_result_slot_type(ctx);
2683 let slot_ptrs = get_dynamic_result_slot_ptrs(builder, slot_type, slot_ptr);
2684 clear_dynamic_result_slot(ctx, builder, &slot_ptrs);
2685 }
2686
2687 fn ensure_out_err_success<'ctx>(
2688 ctx: &'ctx Context,
2689 module: &Module<'ctx>,
2690 ) -> FunctionValue<'ctx> {
2691 if let Some(existing) = module.get_function("qir_qis.out_err_success") {
2692 return existing;
2693 }
2694 let ptr_type = ctx.ptr_type(AddressSpace::default());
2695 let function = module.add_function(
2696 "qir_qis.out_err_success",
2697 ctx.void_type().fn_type(&[ptr_type.into()], false),
2698 Some(Linkage::Private),
2699 );
2700 let builder = ctx.create_builder();
2701 let entry = ctx.append_basic_block(function, "entry");
2702 let ret = ctx.append_basic_block(function, "ret");
2703 let set_ok = ctx.append_basic_block(function, "set_ok");
2704 builder.position_at_end(entry);
2705 let out_err = function
2706 .get_first_param()
2707 .expect("out_err")
2708 .into_pointer_value();
2709 let is_null = build_ptr_is_null(ctx, &builder, out_err, "out_err_int").expect("null");
2710 builder
2711 .build_conditional_branch(is_null, ret, set_ok)
2712 .expect("branch");
2713 builder.position_at_end(set_ok);
2714 let _ = builder.build_store(out_err, ctx.bool_type().const_zero());
2715 builder.build_unconditional_branch(ret).expect("jump");
2716 builder.position_at_end(ret);
2717 builder.build_return(None).expect("ret");
2718 function
2719 }
2720
2721 fn extract_const_len(value: BasicValueEnum<'_>, opname: &str) -> Result<u64, String> {
2722 let Some(len) = value.into_int_value().get_zero_extended_constant() else {
2723 return Err(format!(
2724 "{opname} currently requires a constant array length"
2725 ));
2726 };
2727 Ok(len)
2728 }
2729
2730 fn lower_void_helper_call<'ctx>(
2731 ctx: &'ctx Context,
2732 instr: inkwell::values::InstructionValue<'ctx>,
2733 helper: FunctionValue<'ctx>,
2734 call_args: &[BasicValueEnum<'ctx>],
2735 error_context: &str,
2736 ) -> Result<(), String> {
2737 let builder = ctx.create_builder();
2738 builder.position_before(&instr);
2739 let metadata_args: Vec<BasicMetadataValueEnum<'ctx>> =
2740 call_args.iter().copied().map(Into::into).collect();
2741 if let Err(err) = builder.build_call(helper, &metadata_args, "") {
2742 return Err(format!("{error_context}: {err}"));
2743 }
2744 instr.erase_from_basic_block();
2745 Ok(())
2746 }
2747
2748 fn lower_dynamic_qubit_allocate(args: &ProcessCallArgs<'_>) -> Result<(), String> {
2749 let builder = args.ctx.create_builder();
2750 builder.position_before(&args.instr);
2751 let call_args: Vec<BasicValueEnum> = extract_operands(&args.instr)?;
2752 let helper = ensure_dynamic_qubit_allocate(args.ctx, module_ref(args));
2753 let value = call_basic_value(
2754 &builder,
2755 helper,
2756 &[call_args[0].into()],
2757 "dyn_q",
2758 "Failed to lower dynamic qubit allocation",
2759 "Dynamic qubit allocate helper did not return a pointer",
2760 )?;
2761 replace_call_with_value(args.instr, value)
2762 }
2763
2764 fn lower_dynamic_qubit_release(args: &ProcessCallArgs<'_>) -> Result<(), String> {
2765 let call_args: Vec<BasicValueEnum> = extract_operands(&args.instr)?;
2766 let helper = ensure_dynamic_qubit_release(args.ctx, module_ref(args));
2767 lower_void_helper_call(
2768 args.ctx,
2769 args.instr,
2770 helper,
2771 &call_args[..1],
2772 "Failed to lower dynamic qubit release",
2773 )
2774 }
2775
2776 fn lower_dynamic_qubit_array_allocate(args: &ProcessCallArgs<'_>) -> Result<(), String> {
2777 let call_args: Vec<BasicValueEnum> = extract_operands(&args.instr)?;
2778 let helper = ensure_dynamic_qubit_array_allocate(args.ctx, module_ref(args));
2779 lower_void_helper_call(
2780 args.ctx,
2781 args.instr,
2782 helper,
2783 &call_args[..3],
2784 "Failed to lower dynamic qubit array allocation",
2785 )
2786 }
2787
2788 fn lower_dynamic_qubit_array_release(args: &ProcessCallArgs<'_>) -> Result<(), String> {
2789 let call_args: Vec<BasicValueEnum> = extract_operands(&args.instr)?;
2790 let helper = ensure_dynamic_qubit_array_release(args.ctx, module_ref(args));
2791 lower_void_helper_call(
2792 args.ctx,
2793 args.instr,
2794 helper,
2795 &call_args[..2],
2796 "Failed to lower dynamic qubit array release",
2797 )
2798 }
2799
2800 fn ensure_dynamic_result_setter<'ctx>(
2801 ctx: &'ctx Context,
2802 module: &Module<'ctx>,
2803 ) -> FunctionValue<'ctx> {
2804 if let Some(existing) = module.get_function("qir_qis.result_set_pending") {
2805 return existing;
2806 }
2807 let ptr_type = ctx.ptr_type(AddressSpace::default());
2808 let slot_type = get_dynamic_result_slot_type(ctx);
2809 let function = module.add_function(
2810 "qir_qis.result_set_pending",
2811 ctx.void_type()
2812 .fn_type(&[ptr_type.into(), ctx.i64_type().into()], false),
2813 Some(Linkage::Private),
2814 );
2815 let builder = ctx.create_builder();
2816 let entry = ctx.append_basic_block(function, "entry");
2817 builder.position_at_end(entry);
2818 let result_ptr = function
2819 .get_nth_param(0)
2820 .expect("result")
2821 .into_pointer_value();
2822 let future = function.get_nth_param(1).expect("future").into_int_value();
2823 let slot_ptrs = get_dynamic_result_slot_ptrs(&builder, slot_type, result_ptr);
2824 set_dynamic_result_pending(ctx, &builder, &slot_ptrs, future);
2825 builder.build_return(None).expect("ret");
2826 function
2827 }
2828
2829 fn ensure_dynamic_result_read<'ctx>(
2830 ctx: &'ctx Context,
2831 module: &Module<'ctx>,
2832 ) -> FunctionValue<'ctx> {
2833 if let Some(existing) = module.get_function("qir_qis.result_read") {
2834 return existing;
2835 }
2836 let ptr_type = ctx.ptr_type(AddressSpace::default());
2837 let slot_type = get_dynamic_result_slot_type(ctx);
2838 let function = module.add_function(
2839 "qir_qis.result_read",
2840 ctx.bool_type().fn_type(&[ptr_type.into()], false),
2841 Some(Linkage::Private),
2842 );
2843 let builder = ctx.create_builder();
2844 let entry = ctx.append_basic_block(function, "entry");
2845 let ret_false = ctx.append_basic_block(function, "ret_false");
2846 let pending = ctx.append_basic_block(function, "pending");
2847 let cached = ctx.append_basic_block(function, "cached");
2848 builder.position_at_end(entry);
2849 let result_ptr = function
2850 .get_first_param()
2851 .expect("result")
2852 .into_pointer_value();
2853 let is_null =
2854 build_ptr_is_null(ctx, &builder, result_ptr, "result_int").expect("null check");
2855 let read_state = ctx.append_basic_block(function, "read_state");
2856 builder
2857 .build_conditional_branch(is_null, ret_false, read_state)
2858 .expect("branch");
2859
2860 builder.position_at_end(read_state);
2861 let slot_ptrs = get_dynamic_result_slot_ptrs(&builder, slot_type, result_ptr);
2862 let state = builder
2863 .build_load(ctx.i8_type(), slot_ptrs.state, "state")
2864 .expect("load state")
2865 .into_int_value();
2866 let is_pending = builder
2867 .build_int_compare(
2868 inkwell::IntPredicate::EQ,
2869 state,
2870 ctx.i8_type().const_int(1, false),
2871 "is_pending",
2872 )
2873 .expect("cmp");
2874 let is_cached = builder
2875 .build_int_compare(
2876 inkwell::IntPredicate::EQ,
2877 state,
2878 ctx.i8_type().const_int(2, false),
2879 "is_cached",
2880 )
2881 .expect("cmp");
2882 let check_cached = ctx.append_basic_block(function, "check_cached");
2883 builder
2884 .build_conditional_branch(is_pending, pending, check_cached)
2885 .expect("branch");
2886
2887 builder.position_at_end(check_cached);
2888 builder
2889 .build_conditional_branch(is_cached, cached, ret_false)
2890 .expect("branch");
2891
2892 builder.position_at_end(pending);
2893 build_dynamic_result_pending_return(
2894 ctx,
2895 module,
2896 &builder,
2897 slot_ptrs.future,
2898 slot_ptrs.cached,
2899 slot_ptrs.state,
2900 );
2901
2902 builder.position_at_end(cached);
2903 let cached_val = builder
2904 .build_load(ctx.bool_type(), slot_ptrs.cached, "cached")
2905 .expect("cached load");
2906 builder
2907 .build_return(Some(&cached_val.into_int_value()))
2908 .expect("ret cached");
2909
2910 builder.position_at_end(ret_false);
2911 builder
2912 .build_return(Some(&ctx.bool_type().const_zero()))
2913 .expect("ret false");
2914 function
2915 }
2916
2917 fn ensure_dynamic_result_release<'ctx>(
2918 ctx: &'ctx Context,
2919 module: &Module<'ctx>,
2920 ) -> FunctionValue<'ctx> {
2921 if let Some(existing) = module.get_function("qir_qis.result_release") {
2922 return existing;
2923 }
2924 let ptr_type = ctx.ptr_type(AddressSpace::default());
2925 let slot_type = get_dynamic_result_slot_type(ctx);
2926 let function = module.add_function(
2927 "qir_qis.result_release",
2928 ctx.void_type().fn_type(&[ptr_type.into()], false),
2929 Some(Linkage::Private),
2930 );
2931 let builder = ctx.create_builder();
2932 let entry = ctx.append_basic_block(function, "entry");
2933 let ret = ctx.append_basic_block(function, "ret");
2934 let body = ctx.append_basic_block(function, "body");
2935 builder.position_at_end(entry);
2936 let result_ptr = function
2937 .get_first_param()
2938 .expect("result")
2939 .into_pointer_value();
2940 let is_null =
2941 build_ptr_is_null(ctx, &builder, result_ptr, "result_int").expect("null check");
2942 builder
2943 .build_conditional_branch(is_null, ret, body)
2944 .expect("branch");
2945 builder.position_at_end(body);
2946 let slot_ptrs = get_dynamic_result_slot_ptrs(&builder, slot_type, result_ptr);
2947 let state = builder
2948 .build_load(ctx.i8_type(), slot_ptrs.state, "state")
2949 .expect("load state")
2950 .into_int_value();
2951 let is_pending = builder
2952 .build_int_compare(
2953 inkwell::IntPredicate::EQ,
2954 state,
2955 ctx.i8_type().const_int(1, false),
2956 "is_pending",
2957 )
2958 .expect("cmp");
2959 let dec_block = ctx.append_basic_block(function, "dec_future");
2960 let clear_block = ctx.append_basic_block(function, "clear");
2961 builder
2962 .build_conditional_branch(is_pending, dec_block, clear_block)
2963 .expect("branch");
2964 builder.position_at_end(dec_block);
2965 let future = builder
2966 .build_load(ctx.i64_type(), slot_ptrs.future, "future")
2967 .expect("load future");
2968 let dec_fn = get_or_create_function(
2969 module,
2970 "___dec_future_refcount",
2971 ctx.void_type().fn_type(&[ctx.i64_type().into()], false),
2972 );
2973 let _ = builder.build_call(dec_fn, &[future.into()], "");
2974 builder
2975 .build_unconditional_branch(clear_block)
2976 .expect("jump");
2977 builder.position_at_end(clear_block);
2978 clear_dynamic_result_slot(ctx, &builder, &slot_ptrs);
2979 builder.build_unconditional_branch(ret).expect("jump");
2980 builder.position_at_end(ret);
2981 builder.build_return(None).expect("ret");
2982 function
2983 }
2984
2985 fn ensure_dynamic_result_array_release<'ctx>(
2986 ctx: &'ctx Context,
2987 module: &Module<'ctx>,
2988 ) -> FunctionValue<'ctx> {
2989 if let Some(existing) = module.get_function("qir_qis.result_array_release") {
2990 return existing;
2991 }
2992 let ptr_type = ctx.ptr_type(AddressSpace::default());
2993 let function = module.add_function(
2994 "qir_qis.result_array_release",
2995 ctx.void_type()
2996 .fn_type(&[ctx.i64_type().into(), ptr_type.into()], false),
2997 Some(Linkage::Private),
2998 );
2999 let builder = ctx.create_builder();
3000 let entry = ctx.append_basic_block(function, "entry");
3001 builder.position_at_end(entry);
3002 let len = function.get_nth_param(0).expect("len").into_int_value();
3003 let array_ptr = function
3004 .get_nth_param(1)
3005 .expect("array")
3006 .into_pointer_value();
3007 let release_fn = ensure_dynamic_result_release(ctx, module);
3008 let ptr_elem_type = ctx.ptr_type(AddressSpace::default());
3009 build_pointer_array_release_helper(
3010 ctx,
3011 &builder,
3012 &PointerArrayReleaseHelperArgs {
3013 function,
3014 len,
3015 array_ptr,
3016 release_fn,
3017 ptr_elem_type,
3018 gep_error: "Failed to build result array GEP",
3019 load_error: "Failed to load dynamic result pointer",
3020 release_error: "Failed to release dynamic result",
3021 },
3022 )
3023 .expect("build dynamic result release loop");
3024 builder.build_return(None).expect("ret");
3025 function
3026 }
3027
3028 fn ensure_dynamic_result_array_record_output<'ctx>(
3029 ctx: &'ctx Context,
3030 module: &Module<'ctx>,
3031 ) -> Result<FunctionValue<'ctx>, String> {
3032 if let Some(existing) = module.get_function("qir_qis.result_array_record_output") {
3033 return Ok(existing);
3034 }
3035 let ptr_type = ctx.ptr_type(AddressSpace::default());
3036 let function = module.add_function(
3037 "qir_qis.result_array_record_output",
3038 ctx.void_type().fn_type(
3039 &[
3040 ctx.i64_type().into(),
3041 ptr_type.into(),
3042 ptr_type.into(),
3043 ctx.i64_type().into(),
3044 ],
3045 false,
3046 ),
3047 Some(Linkage::Private),
3048 );
3049 let builder = ctx.create_builder();
3050 let entry = ctx.append_basic_block(function, "entry");
3051 builder.position_at_end(entry);
3052 let len = function.get_nth_param(0).expect("len").into_int_value();
3053 let array_ptr = function
3054 .get_nth_param(1)
3055 .expect("array")
3056 .into_pointer_value();
3057 let tag_ptr = function.get_nth_param(2).expect("tag").into_pointer_value();
3058 let tag_len = function.get_nth_param(3).expect("tag_len").into_int_value();
3059 let print_bool_arr = get_or_create_function(
3060 module,
3061 "print_bool_arr",
3062 ctx.void_type().fn_type(
3063 &[ptr_type.into(), ctx.i64_type().into(), ptr_type.into()],
3064 false,
3065 ),
3066 );
3067 let read_fn = ensure_dynamic_result_read(ctx, module);
3068 let bool_arr =
3069 build_dynamic_result_array_values(ctx, function, &builder, len, array_ptr, read_fn)?;
3070 let array_desc = build_dynamic_result_array_descriptor(ctx, &builder, len, bool_arr)?;
3071 let _ = builder
3072 .build_call(
3073 print_bool_arr,
3074 &[tag_ptr.into(), tag_len.into(), array_desc.into()],
3075 "",
3076 )
3077 .map_err(|e| format!("Failed to print result array: {e}"))?;
3078 builder.build_return(None).expect("ret");
3079 Ok(function)
3080 }
3081
3082 fn lower_dynamic_result_allocate(args: &ProcessCallArgs<'_>) -> Result<(), String> {
3083 let builder = args.ctx.create_builder();
3084 builder.position_before(&args.instr);
3085 let call_args: Vec<BasicValueEnum> = extract_operands(&args.instr)?;
3086 let slot_ptr = builder
3087 .build_alloca(get_dynamic_result_slot_type(args.ctx), "dyn_result")
3088 .map_err(|e| format!("Failed to allocate dynamic result slot: {e}"))?;
3089 initialize_dynamic_result_slot(args.ctx, &builder, slot_ptr);
3090 let helper = ensure_out_err_success(args.ctx, module_ref(args));
3091 let _ = builder
3092 .build_call(helper, &[call_args[0].into()], "")
3093 .map_err(|e| format!("Failed to store dynamic result out_err success flag: {e}"))?;
3094 replace_call_with_value(args.instr, slot_ptr.as_basic_value_enum())
3095 }
3096
3097 fn lower_dynamic_result_release(args: &ProcessCallArgs<'_>) -> Result<(), String> {
3098 let call_args: Vec<BasicValueEnum> = extract_operands(&args.instr)?;
3099 let helper = ensure_dynamic_result_release(args.ctx, module_ref(args));
3100 lower_void_helper_call(
3101 args.ctx,
3102 args.instr,
3103 helper,
3104 &call_args[..1],
3105 "Failed to lower dynamic result release",
3106 )
3107 }
3108
3109 fn lower_dynamic_result_array_allocate(args: &mut ProcessCallArgs<'_>) -> Result<(), String> {
3110 let builder = args.ctx.create_builder();
3111 builder.position_before(&args.instr);
3112 let call_args: Vec<BasicValueEnum> = extract_operands(&args.instr)?;
3113 let len = extract_const_len(call_args[0], "__quantum__rt__result_array_allocate")?;
3114 let array_ptr = call_args[1].into_pointer_value();
3115 let out_err = call_args[2].into_pointer_value();
3116 let ptr_type = args.ctx.ptr_type(AddressSpace::default());
3117 for idx in 0..len {
3118 let elem_ptr = unsafe {
3119 builder.build_gep(
3120 ptr_type,
3121 array_ptr,
3122 &[args.ctx.i64_type().const_int(idx, false)],
3123 "result_elem_ptr",
3124 )
3125 }
3126 .map_err(|e| format!("Failed to build result array element GEP: {e}"))?;
3127 let slot_ptr = builder
3128 .build_alloca(get_dynamic_result_slot_type(args.ctx), "dyn_result")
3129 .map_err(|e| format!("Failed to allocate dynamic result slot: {e}"))?;
3130 initialize_dynamic_result_slot(args.ctx, &builder, slot_ptr);
3131 let _ = builder
3132 .build_store(elem_ptr, slot_ptr)
3133 .map_err(|e| format!("Failed to store dynamic result slot in array: {e}"))?;
3134 }
3135 let helper = ensure_out_err_success(args.ctx, module_ref(args));
3136 let _ = builder
3137 .build_call(helper, &[out_err.into()], "")
3138 .map_err(|e| {
3139 format!("Failed to store dynamic result array out_err success flag: {e}")
3140 })?;
3141 args.instr.erase_from_basic_block();
3142 Ok(())
3143 }
3144
3145 fn lower_dynamic_result_array_release(args: &ProcessCallArgs<'_>) -> Result<(), String> {
3146 let call_args: Vec<BasicValueEnum> = extract_operands(&args.instr)?;
3147 let helper = ensure_dynamic_result_array_release(args.ctx, module_ref(args));
3148 lower_void_helper_call(
3149 args.ctx,
3150 args.instr,
3151 helper,
3152 &call_args[..2],
3153 "Failed to lower dynamic result array release",
3154 )
3155 }
3156
3157 fn lower_dynamic_result_array_record_output(
3158 args: &mut ProcessCallArgs<'_>,
3159 ) -> Result<(), String> {
3160 let builder = args.ctx.create_builder();
3161 builder.position_before(&args.instr);
3162 let call_args: Vec<BasicValueEnum> = extract_operands(&args.instr)?;
3163 let old_name = parse_gep(call_args[2])?;
3164 let full_tag =
3165 if let Some(global) = unsafe { &mut *args.global_mapping }.get(old_name.as_str()) {
3166 get_string_label(*global)?
3167 } else {
3168 return Err(format!("Output global `{old_name}` not found in mapping"));
3169 };
3170 let old_label = extract_label_from_tag(&full_tag);
3171 let (new_const, new_name) =
3172 build_result_global(args.ctx, old_label, &old_name, "RESULT_ARRAY", None)?;
3173 let new_global = module_ref(args).add_global(new_const.get_type(), None, &new_name);
3174 new_global.set_initializer(&new_const);
3175 new_global.set_linkage(Linkage::Private);
3176 new_global.set_constant(true);
3177 unsafe { &mut *args.global_mapping }.insert(old_name, new_global);
3178 let helper = ensure_dynamic_result_array_record_output(args.ctx, module_ref(args))?;
3179 let tag_len = args.ctx.i64_type().const_int(
3180 u64::from(new_const.get_type().len()).saturating_sub(1),
3181 false,
3182 );
3183 let tag_ptr = unsafe {
3184 builder.build_gep(
3185 new_const.get_type(),
3186 new_global.as_pointer_value(),
3187 &[
3188 args.ctx.i64_type().const_zero(),
3189 args.ctx.i64_type().const_zero(),
3190 ],
3191 "tag_gep",
3192 )
3193 }
3194 .map_err(|e| format!("Failed to build result array tag GEP: {e}"))?;
3195 let _ = builder
3196 .build_call(
3197 helper,
3198 &[
3199 call_args[0].into(),
3200 call_args[1].into(),
3201 tag_ptr.into(),
3202 tag_len.into(),
3203 ],
3204 "",
3205 )
3206 .map_err(|e| format!("Failed to lower result array record output: {e}"))?;
3207 args.instr.erase_from_basic_block();
3208 Ok(())
3209 }
3210
3211 #[allow(
3212 clippy::too_many_arguments,
3213 reason = "measurement lowering needs a fixed set of builder inputs"
3214 )]
3215 fn handle_mz_call<'ctx>(
3216 ctx: &'ctx Context,
3217 module: *const (),
3218 instr: &'ctx inkwell::values::InstructionValue<'ctx>,
3219 fn_name: &str,
3220 capability_flags: CapabilityFlags,
3221 qubit_array: Option<PointerValue<'ctx>>,
3222 qubit_array_type: Option<ArrayType<'ctx>>,
3223 result_ssa: *mut (),
3224 ) -> Result<(), String> {
3225 let module = unsafe { &*module.cast::<Module<'ctx>>() };
3226 if fn_name == "__quantum__qis__m__body" {
3227 log::warn!(
3228 "`__quantum__qis__m__body` is from Q# QDK, synonym for `__quantum__qis__mz__body`"
3229 );
3230 }
3231 let builder = ctx.create_builder();
3232 builder.position_before(instr);
3233
3234 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
3236 let qubit_ptr = call_args[0].into_pointer_value();
3237 let result_ptr = call_args[1].into_pointer_value();
3238
3239 let q_handle = get_qubit_handle(
3240 ctx,
3241 capability_flags,
3242 qubit_array,
3243 qubit_array_type,
3244 &builder,
3245 qubit_ptr,
3246 )?;
3247
3248 let meas = {
3250 let meas_func = get_or_create_function(
3251 module,
3252 "___lazy_measure",
3253 ctx.i64_type().fn_type(&[ctx.i64_type().into()], false),
3254 );
3255
3256 let call = builder.build_call(meas_func, &[q_handle.into()], "meas");
3257 let call_result =
3258 call.map_err(|e| format!("Failed to build call for lazy measure function: {e}"))?;
3259 match call_result.try_as_basic_value() {
3260 inkwell::values::ValueKind::Basic(bv) => bv,
3261 inkwell::values::ValueKind::Instruction(_) => {
3262 return Err("Failed to get basic value from lazy measure call".into());
3263 }
3264 }
3265 };
3266
3267 if capability_flags.dynamic_result_management {
3269 let set_result_fn = ensure_dynamic_result_setter(ctx, module);
3270 let _ = builder
3271 .build_call(set_result_fn, &[result_ptr.into(), meas.into()], "")
3272 .map_err(|e| format!("Failed to update dynamic result state: {e}"))?;
3273 } else {
3274 let result_ssa = unsafe {
3275 &mut *result_ssa
3276 .cast::<Vec<Option<(BasicValueEnum<'ctx>, Option<BasicValueEnum<'ctx>>)>>>()
3277 };
3278 let result_idx = get_index(result_ptr)?;
3279 let result_idx_usize = checked_result_index(result_idx, result_ssa.len())?;
3280 result_ssa[result_idx_usize] = Some((meas, None));
3281 }
3282
3283 if fn_name == "__quantum__qis__mresetz__body" {
3284 log::warn!("`__quantum__qis__mresetz__body` is from Q# QDK");
3285 create_reset_call(ctx, module, &builder, q_handle);
3287 }
3288
3289 instr.erase_from_basic_block();
3291 Ok(())
3292 }
3293
3294 fn handle_mz_leaked_call(args: &ProcessCallArgs) -> Result<(), String> {
3295 let ProcessCallArgs { ctx, instr, .. } = args;
3296 let module = module_ref(args);
3297 let builder = ctx.create_builder();
3298 builder.position_before(instr);
3299 let call = CallSiteValue::try_from(args.instr)
3300 .map_err(|()| "Malformed mz_leaked call: instruction is not a call site".to_string())?;
3301 let called_fn = call
3302 .get_called_fn_value()
3303 .ok_or_else(|| "Malformed mz_leaked call: missing callee".to_string())?;
3304 let fn_type = called_fn.get_type();
3305 let param_types = fn_type.get_param_types();
3306 let has_expected_signature = fn_type
3307 .get_return_type()
3308 .is_some_and(|ty| ty.is_int_type() && ty.into_int_type().get_bit_width() == 64)
3309 && param_types.len() == 1
3310 && param_types[0].is_pointer_type();
3311 if !has_expected_signature {
3312 return Err("Malformed mz_leaked call: expected signature i64 (ptr)".to_string());
3313 }
3314
3315 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
3316 let qubit_ptr = mz_leaked_qubit_operand(&call_args)?;
3317
3318 let q_handle = get_qubit_handle(
3319 ctx,
3320 args.capability_flags,
3321 args.qubit_array,
3322 args.qubit_array_type,
3323 &builder,
3324 qubit_ptr,
3325 )?;
3326
3327 let meas_handle = {
3328 let meas_func = get_or_create_function(
3329 module,
3330 "___lazy_measure_leaked",
3331 ctx.i64_type().fn_type(&[ctx.i64_type().into()], false),
3332 );
3333
3334 let call = builder.build_call(meas_func, &[q_handle.into()], "meas_leaked");
3335 let call_result = call.map_err(|e| {
3336 format!("Failed to build call for lazy leaked measure function: {e}")
3337 })?;
3338 match call_result.try_as_basic_value() {
3339 inkwell::values::ValueKind::Basic(bv) => bv,
3340 inkwell::values::ValueKind::Instruction(_) => {
3341 return Err("Failed to get basic value from lazy leaked measure call".into());
3342 }
3343 }
3344 };
3345
3346 let meas_value = {
3347 let read_func = get_or_create_function(
3348 module,
3349 "___read_future_uint",
3350 ctx.i64_type().fn_type(&[ctx.i64_type().into()], false),
3351 );
3352 let call = builder.build_call(read_func, &[meas_handle.into()], "meas_leaked_value");
3353 let call_result =
3354 call.map_err(|e| format!("Failed to build call for read_future_uint: {e}"))?;
3355 match call_result.try_as_basic_value() {
3356 inkwell::values::ValueKind::Basic(bv) => bv,
3357 inkwell::values::ValueKind::Instruction(_) => {
3358 return Err("Failed to get basic value from read_future_uint call".into());
3359 }
3360 }
3361 };
3362
3363 let dec_func = get_or_create_function(
3364 module,
3365 "___dec_future_refcount",
3366 ctx.void_type().fn_type(&[ctx.i64_type().into()], false),
3367 );
3368 let _ = builder
3369 .build_call(dec_func, &[meas_handle.into()], "")
3370 .map_err(|e| format!("Failed to build call for dec_future_refcount: {e}"))?;
3371
3372 let instruction_val = meas_value
3373 .as_instruction_value()
3374 .ok_or("Failed to convert leaked measurement value to instruction value")?;
3375 instr.replace_all_uses_with(&instruction_val);
3376 instr.erase_from_basic_block();
3377 Ok(())
3378 }
3379
3380 pub fn mz_leaked_qubit_operand<'ctx>(
3381 call_args: &[BasicValueEnum<'ctx>],
3382 ) -> Result<PointerValue<'ctx>, String> {
3383 match call_args {
3384 [BasicValueEnum::PointerValue(ptr), _] => Ok(*ptr),
3385 [_, _] => {
3386 Err("Malformed mz_leaked call: expected first argument to be a pointer".into())
3387 }
3388 _ => Err(format!(
3389 "Malformed mz_leaked call: expected 1 argument plus callee, got {} operands",
3390 call_args.len()
3391 )),
3392 }
3393 }
3394
3395 fn handle_reset_call(args: &ProcessCallArgs<'_>) -> Result<(), String> {
3396 let ProcessCallArgs { ctx, instr, .. } = args;
3397 let module = module_ref(args);
3398 let builder = ctx.create_builder();
3399 builder.position_before(instr);
3400
3401 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
3403 let qubit_ptr = call_args[0].into_pointer_value();
3404
3405 let q_handle = get_qubit_handle(
3406 ctx,
3407 args.capability_flags,
3408 args.qubit_array,
3409 args.qubit_array_type,
3410 &builder,
3411 qubit_ptr,
3412 )?;
3413
3414 create_reset_call(ctx, module, &builder, q_handle);
3416
3417 instr.erase_from_basic_block();
3418 Ok(())
3419 }
3420
3421 fn parse_barrier_arity(fn_name: &str) -> Result<usize, String> {
3422 fn_name
3423 .strip_prefix("__quantum__qis__barrier")
3424 .and_then(|s| s.strip_suffix("__body"))
3425 .and_then(|s| s.parse::<usize>().ok())
3426 .filter(|&n| n > 0)
3427 .ok_or_else(|| format!("Invalid barrier function name: {fn_name}"))
3428 }
3429
3430 #[allow(
3431 clippy::too_many_lines,
3432 reason = "barrier lowering stays linear to keep validation and rewrite steps aligned"
3433 )]
3434 fn handle_barrier_call(args: &ProcessCallArgs<'_>) -> Result<(), String> {
3435 let ProcessCallArgs {
3436 ctx,
3437 instr,
3438 fn_name,
3439 ..
3440 } = args;
3441 let module = module_ref(args);
3442 let builder = ctx.create_builder();
3443 builder.position_before(instr);
3444
3445 let num_qubits = parse_barrier_arity(fn_name)?;
3446
3447 let all_operands: Vec<BasicValueEnum> = extract_operands(instr)?;
3449 let num_operands = all_operands
3450 .len()
3451 .checked_sub(1)
3452 .ok_or("Expected at least one operand")?;
3453
3454 if num_operands != num_qubits {
3455 return Err(format!(
3456 "Barrier function {fn_name} expects {num_qubits} arguments, got {num_operands}"
3457 ));
3458 }
3459
3460 let call_args = &all_operands[..num_operands];
3461
3462 let i64_type = ctx.i64_type();
3464 let array_type = i64_type.array_type(
3465 u32::try_from(num_qubits).map_err(|e| format!("Failed to convert num_qubits: {e}"))?,
3466 );
3467 let array_alloca = builder
3468 .build_alloca(array_type, "barrier_qubits")
3469 .map_err(|e| format!("Failed to allocate array for barrier qubits: {e}"))?;
3470
3471 for (i, arg) in call_args.iter().enumerate() {
3472 let qubit_ptr = arg.into_pointer_value();
3473 let q_handle = get_qubit_handle(
3474 ctx,
3475 args.capability_flags,
3476 args.qubit_array,
3477 args.qubit_array_type,
3478 &builder,
3479 qubit_ptr,
3480 )?;
3481
3482 let elem_ptr = unsafe {
3483 builder.build_gep(
3484 array_type,
3485 array_alloca,
3486 &[
3487 i64_type.const_zero(),
3488 i64_type.const_int(
3489 u64::try_from(i)
3490 .map_err(|e| format!("Failed to convert index: {e}"))?,
3491 false,
3492 ),
3493 ],
3494 "",
3495 )
3496 }
3497 .map_err(|e| format!("Failed to build GEP for barrier array: {e}"))?;
3498 builder
3499 .build_store(elem_ptr, q_handle)
3500 .map_err(|e| format!("Failed to store qubit handle in array: {e}"))?;
3501 }
3502
3503 let array_ptr = unsafe {
3504 builder.build_gep(
3505 array_type,
3506 array_alloca,
3507 &[i64_type.const_zero(), i64_type.const_zero()],
3508 "barrier_array_ptr",
3509 )
3510 }
3511 .map_err(|e| format!("Failed to build GEP for barrier array pointer: {e}"))?;
3512
3513 let barrier_func = get_or_create_function(
3515 module,
3516 "___barrier",
3517 ctx.void_type().fn_type(
3518 &[
3519 ctx.ptr_type(AddressSpace::default()).into(),
3520 i64_type.into(),
3521 ],
3522 false,
3523 ),
3524 );
3525
3526 builder
3527 .build_call(
3528 barrier_func,
3529 &[
3530 array_ptr.into(),
3531 i64_type
3532 .const_int(
3533 u64::try_from(num_qubits)
3534 .map_err(|e| format!("Failed to convert num_qubits: {e}"))?,
3535 false,
3536 )
3537 .into(),
3538 ],
3539 "",
3540 )
3541 .map_err(|e| format!("Failed to build call to ___barrier: {e}"))?;
3542
3543 instr.erase_from_basic_block();
3544 Ok(())
3545 }
3546
3547 fn handle_read_result_call<'ctx>(
3548 ctx: &'ctx Context,
3549 module: *const (),
3550 instr: &'ctx inkwell::values::InstructionValue<'ctx>,
3551 fn_name: &str,
3552 capability_flags: CapabilityFlags,
3553 global_mapping: *mut (),
3554 result_ssa: *mut (),
3555 ) -> Result<(), String> {
3556 let module = unsafe { &*module.cast::<Module<'ctx>>() };
3557 let global_mapping = unsafe {
3558 &mut *global_mapping.cast::<HashMap<String, inkwell::values::GlobalValue<'ctx>>>()
3559 };
3560 let result_ssa = unsafe {
3561 &mut *result_ssa
3562 .cast::<Vec<Option<(BasicValueEnum<'ctx>, Option<BasicValueEnum<'ctx>>)>>>()
3563 };
3564 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
3565 let result_ptr = call_args[0].into_pointer_value();
3566
3567 let builder = ctx.create_builder();
3568 builder.position_before(instr);
3569
3570 let bool_val = read_result_bool(
3571 ctx,
3572 module,
3573 &builder,
3574 result_ptr,
3575 capability_flags,
3576 result_ssa,
3577 )?;
3578
3579 if fn_name == "__quantum__rt__read_result" {
3580 let instruction_val = bool_val
3581 .as_instruction_value()
3582 .ok_or("Failed to convert bool_val to instruction value")?;
3583 instr.replace_all_uses_with(&instruction_val);
3584 } else {
3585 let new_global = get_or_create_result_output_global(
3586 ctx,
3587 module,
3588 global_mapping,
3589 capability_flags,
3590 result_ptr,
3591 call_args[1],
3592 )?;
3593
3594 let print_func = get_or_create_function(
3595 module,
3596 "print_bool",
3597 ctx.void_type().fn_type(
3598 &[
3599 ctx.ptr_type(AddressSpace::default()).into(), ctx.i64_type().into(), ctx.bool_type().into(), ],
3603 false,
3604 ),
3605 );
3606
3607 add_print_call(ctx, &builder, new_global, print_func, bool_val)?;
3608 }
3609 instr.erase_from_basic_block();
3610 Ok(())
3611 }
3612
3613 pub fn checked_result_index(result_idx: u64, result_ssa_len: usize) -> Result<usize, String> {
3614 let result_idx_usize = usize::try_from(result_idx)
3615 .map_err(|e| format!("Failed to convert result index to usize: {e}"))?;
3616 if result_idx_usize >= result_ssa_len {
3617 return Err(format!(
3618 "Result index {result_idx} exceeds required_num_results ({result_ssa_len})"
3619 ));
3620 }
3621 Ok(result_idx_usize)
3622 }
3623
3624 fn handle_classical_record_output(args: &mut ProcessCallArgs<'_>) -> Result<(), String> {
3625 let ProcessCallArgs {
3626 ctx,
3627 instr,
3628 fn_name,
3629 ..
3630 } = args;
3631 let module = unsafe { &*args.module };
3632 let global_mapping = unsafe { &mut *args.global_mapping };
3633 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
3634 let (print_func_name, value, type_tag) = match fn_name.as_str() {
3635 "__quantum__rt__bool_record_output" => (
3636 "print_bool",
3637 call_args[0].into_int_value().as_basic_value_enum(),
3638 "BOOL",
3639 ),
3640 "__quantum__rt__int_record_output" => (
3641 "print_int",
3642 call_args[0].into_int_value().as_basic_value_enum(),
3643 "INT",
3644 ),
3645 "__quantum__rt__double_record_output" => (
3646 "print_float",
3647 call_args[0].into_float_value().as_basic_value_enum(),
3648 "FLOAT",
3649 ),
3650 _ => return Err(format!("Unsupported print helper for `{fn_name}`")),
3651 };
3652
3653 let ret_type = ctx.void_type();
3655 let param_types = &[
3656 ctx.ptr_type(AddressSpace::default()).into(), ctx.i64_type().into(), match type_tag {
3659 "BOOL" => ctx.bool_type().into(),
3660 "INT" => ctx.i64_type().into(),
3661 "FLOAT" => ctx.f64_type().into(),
3662 _ => return Err(format!("Unsupported type tag `{type_tag}` for `{fn_name}`")),
3663 },
3664 ];
3665 let fn_type = ret_type.fn_type(param_types, false);
3666
3667 let print_func = get_or_create_function(module, print_func_name, fn_type);
3668
3669 let parsed_name = parse_gep(call_args[1]);
3670 let old_name = parsed_name.clone().unwrap_or_else(|_| {
3671 format!(
3672 "anon_classical_{}",
3673 match type_tag {
3674 "BOOL" => "bool",
3675 "INT" => "int",
3676 "FLOAT" => "float",
3677 _ => "value",
3678 }
3679 )
3680 });
3681
3682 let full_tag = if let Some(existing) = global_mapping.get(old_name.as_str()) {
3683 get_string_label(*existing)?
3684 } else if parsed_name.is_ok() {
3685 return Err(format!("Output global `{old_name}` not found in mapping"));
3686 } else {
3687 old_name.clone()
3688 };
3689 let old_label = extract_label_from_tag(&full_tag);
3691
3692 let (new_const, new_name) = build_result_global(ctx, old_label, &old_name, type_tag, None)?;
3693
3694 let new_global = module.add_global(new_const.get_type(), None, &new_name);
3695 new_global.set_initializer(&new_const);
3696 new_global.set_linkage(inkwell::module::Linkage::Private);
3697 new_global.set_constant(true);
3698 global_mapping.insert(old_name, new_global);
3699 record_classical_output(ctx, *instr, new_global, print_func, value)?;
3700 Ok(())
3701 }
3702
3703 fn handle_get_current_shot(args: &ProcessCallArgs<'_>) -> Result<(), String> {
3704 let ProcessCallArgs { ctx, instr, .. } = args;
3705 let module = module_ref(args);
3706 let get_shot_func = get_or_create_function(
3707 module,
3708 "get_current_shot",
3709 ctx.i64_type().fn_type(&[], false),
3710 );
3711 handle_runtime_value_call(
3712 ctx,
3713 *instr,
3714 get_shot_func,
3715 &[],
3716 "current_shot",
3717 "Failed to build call to get_current_shot",
3718 "Failed to get basic value from get_current_shot call",
3719 )
3720 }
3721
3722 fn handle_runtime_value_call<'ctx>(
3723 ctx: &'ctx Context,
3724 instr: inkwell::values::InstructionValue<'ctx>,
3725 callee: FunctionValue<'ctx>,
3726 call_args: &[BasicMetadataValueEnum<'ctx>],
3727 name: &str,
3728 build_error: &str,
3729 value_error: &str,
3730 ) -> Result<(), String> {
3731 let builder = ctx.create_builder();
3732 builder.position_before(&instr);
3733 let value = call_basic_value(&builder, callee, call_args, name, build_error, value_error)?;
3734 if let Some(instr_val) = value.as_instruction_value() {
3735 instr.replace_all_uses_with(&instr_val);
3736 }
3737 instr.erase_from_basic_block();
3738 Ok(())
3739 }
3740
3741 fn handle_runtime_void_call<'ctx>(
3742 ctx: &'ctx Context,
3743 instr: inkwell::values::InstructionValue<'ctx>,
3744 callee: FunctionValue<'ctx>,
3745 call_args: &[BasicMetadataValueEnum<'ctx>],
3746 build_error: &str,
3747 ) -> Result<(), String> {
3748 let builder = ctx.create_builder();
3749 builder.position_before(&instr);
3750 if let Err(err) = builder.build_call(callee, call_args, "") {
3751 return Err(format!("{build_error}: {err}"));
3752 }
3753 instr.erase_from_basic_block();
3754 Ok(())
3755 }
3756
3757 fn handle_random_seed(args: &ProcessCallArgs<'_>) -> Result<(), String> {
3758 let ProcessCallArgs { ctx, instr, .. } = args;
3759 let module = module_ref(args);
3760 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
3761 let random_seed_func = get_or_create_function(
3762 module,
3763 "random_seed",
3764 ctx.void_type().fn_type(&[ctx.i64_type().into()], false),
3765 );
3766 handle_runtime_void_call(
3767 ctx,
3768 *instr,
3769 random_seed_func,
3770 &[call_args[0].into()],
3771 "Failed to build call to random_seed",
3772 )
3773 }
3774
3775 fn handle_random_int(args: &ProcessCallArgs<'_>) -> Result<(), String> {
3776 let ProcessCallArgs { ctx, instr, .. } = args;
3777 let module = module_ref(args);
3778 let random_int_func =
3779 get_or_create_function(module, "random_int", ctx.i32_type().fn_type(&[], false));
3780 handle_runtime_value_call(
3781 ctx,
3782 *instr,
3783 random_int_func,
3784 &[],
3785 "rint",
3786 "Failed to build call to random_int",
3787 "Failed to get basic value from random_int call",
3788 )
3789 }
3790
3791 fn handle_random_float(args: &ProcessCallArgs<'_>) -> Result<(), String> {
3792 let ProcessCallArgs { ctx, instr, .. } = args;
3793 let module = module_ref(args);
3794 let random_float_func =
3795 get_or_create_function(module, "random_float", ctx.f64_type().fn_type(&[], false));
3796 handle_runtime_value_call(
3797 ctx,
3798 *instr,
3799 random_float_func,
3800 &[],
3801 "rfloat",
3802 "Failed to build call to random_float",
3803 "Failed to get basic value from random_float call",
3804 )
3805 }
3806
3807 fn handle_random_int_bounded(args: &ProcessCallArgs<'_>) -> Result<(), String> {
3808 let ProcessCallArgs { ctx, instr, .. } = args;
3809 let module = module_ref(args);
3810 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
3811 let random_rng_func = get_or_create_function(
3812 module,
3813 "random_rng",
3814 ctx.i32_type().fn_type(&[ctx.i32_type().into()], false),
3815 );
3816 handle_runtime_value_call(
3817 ctx,
3818 *instr,
3819 random_rng_func,
3820 &[call_args[0].into()],
3821 "rintb",
3822 "Failed to build call to random_rng",
3823 "Failed to get basic value from random_rng call",
3824 )
3825 }
3826
3827 fn handle_random_advance(args: &ProcessCallArgs<'_>) -> Result<(), String> {
3828 let ProcessCallArgs { ctx, instr, .. } = args;
3829 let module = module_ref(args);
3830 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
3831 let random_advance_func = get_or_create_function(
3832 module,
3833 "random_advance",
3834 ctx.void_type().fn_type(&[ctx.i64_type().into()], false),
3835 );
3836 handle_runtime_void_call(
3837 ctx,
3838 *instr,
3839 random_advance_func,
3840 &[call_args[0].into()],
3841 "Failed to build call to random_advance",
3842 )
3843 }
3844
3845 #[cfg(test)]
3846 mod tests {
3847 use super::format_grouped_module_flag_error;
3848
3849 #[test]
3850 fn test_format_grouped_module_flag_error_returns_none_for_empty_flag_names() {
3851 let empty: [String; 0] = [];
3852 assert_eq!(
3853 format_grouped_module_flag_error(
3854 "Missing required module flag",
3855 "Missing required module flags",
3856 &empty,
3857 ),
3858 None,
3859 );
3860 }
3861 }
3862}
3863
3864pub(crate) fn decode_llvm_bytes(value: &[u8]) -> Option<&str> {
3865 std::str::from_utf8(value).ok()
3866}
3867
3868pub(crate) fn decode_llvm_c_string(value: &std::ffi::CStr) -> Option<&str> {
3869 value.to_str().ok()
3870}
3871
3872pub fn create_memory_buffer_from_bytes(
3884 bytes: &[u8],
3885 name: &str,
3886) -> Result<inkwell::memory_buffer::MemoryBuffer<'static>, String> {
3887 use llvm_sys::core::LLVMCreateMemoryBufferWithMemoryRangeCopy;
3888
3889 let name = std::ffi::CString::new(name)
3890 .map_err(|_err| "Memory buffer name contains interior NUL byte".to_string())?;
3891 let memory_buffer = unsafe {
3892 LLVMCreateMemoryBufferWithMemoryRangeCopy(bytes.as_ptr().cast(), bytes.len(), name.as_ptr())
3893 };
3894 if memory_buffer.is_null() {
3895 return Err(
3896 "LLVM failed to create memory buffer from bytes: received null memory buffer pointer"
3897 .to_string(),
3898 );
3899 }
3900
3901 unsafe { Ok(inkwell::memory_buffer::MemoryBuffer::new(memory_buffer)) }
3902}
3903
3904pub(crate) fn create_module_from_ir_text<'ctx>(
3905 ctx: &'ctx inkwell::context::Context,
3906 ll_text: &str,
3907 name: &str,
3908) -> Result<inkwell::module::Module<'ctx>, String> {
3909 let memory_buffer = create_memory_buffer_from_bytes(ll_text.as_bytes(), name)?;
3910 ctx.create_module_from_ir(memory_buffer)
3911 .map_err(|e| format!("Failed to create module from LLVM IR: {e}"))
3912}
3913
3914#[must_use]
3922pub fn memory_buffer_to_owned_bytes(
3923 memory_buffer: &inkwell::memory_buffer::MemoryBuffer<'_>,
3924) -> Vec<u8> {
3925 memory_buffer.as_slice().to_vec()
3926}
3927
3928pub fn parse_bitcode_module<'ctx>(
3941 ctx: &'ctx inkwell::context::Context,
3942 bitcode: &[u8],
3943 name: &str,
3944) -> Result<inkwell::module::Module<'ctx>, String> {
3945 let memory_buffer = create_memory_buffer_from_bytes(bitcode, name)?;
3946 inkwell::module::Module::parse_bitcode_from_buffer(&memory_buffer, ctx)
3947 .map_err(|e| format!("Failed to parse bitcode: {e}"))
3948}
3949
3950pub fn qir_to_qis(
3964 bc_bytes: &[u8],
3965 opt_level: u32,
3966 target: &str,
3967 wasm_bytes: Option<&[u8]>,
3968) -> Result<Vec<u8>, String> {
3969 qir_to_qis_with_passthrough_calls(bc_bytes, opt_level, target, wasm_bytes, &[])
3970}
3971
3972pub fn qir_to_qis_with_passthrough_calls(
3993 bc_bytes: &[u8],
3994 opt_level: u32,
3995 target: &str,
3996 wasm_bytes: Option<&[u8]>,
3997 passthrough_calls: &[&str],
3998) -> Result<Vec<u8>, String> {
3999 use crate::{
4000 aux::{get_capability_flags, process_entry_function, validate_static_qubit_helper_usage},
4001 convert::{
4002 add_qmain_wrapper, create_qubit_array, find_entry_function, free_all_qubits,
4003 get_string_attrs, process_ir_defined_q_fns, prune_unused_ir_qis_helpers,
4004 validate_declared_resource_limits,
4005 },
4006 decompose::add_decompositions,
4007 opt::optimize,
4008 utils::add_generator_metadata,
4009 };
4010 use inkwell::{attributes::AttributeLoc, context::Context};
4011 use std::{collections::BTreeSet, env};
4012
4013 let ctx = Context::create();
4014 let module = parse_bitcode_module(&ctx, bc_bytes, "bitcode")?;
4015 crate::llvm_verify::verify_module(&module, "LLVM module verification failed after parse")?;
4016 let passthrough_calls: BTreeSet<String> = passthrough_calls
4017 .iter()
4018 .copied()
4019 .map(ToOwned::to_owned)
4020 .collect();
4021 for name in &passthrough_calls {
4022 if module
4023 .get_function(name)
4024 .is_some_and(|function| function.count_basic_blocks() > 0)
4025 {
4026 return Err(format!(
4027 "Pass-through function `{name}` must be an external declaration"
4028 ));
4029 }
4030 }
4031
4032 add_decompositions(&ctx, &module)
4033 .map_err(|e| format!("Failed to add QIR decompositions: {e}"))?;
4034 let entry_fn = find_entry_function(&module)
4035 .map_err(|e| format!("Failed to find entry function in QIR module: {e}"))?;
4036 validate_declared_resource_limits(entry_fn)?;
4037
4038 let entry_fn_name = entry_fn
4039 .get_name()
4040 .to_str()
4041 .map_err(|e| format!("Invalid UTF-8 in entry function name: {e}"))?;
4042 let capability_flags = get_capability_flags(&module);
4043 let mut validation_errors = Vec::new();
4044 validate_static_qubit_helper_usage(&module, entry_fn, &mut validation_errors);
4045 if !validation_errors.is_empty() {
4046 return Err(validation_errors.join("; "));
4047 }
4048
4049 log::trace!("Entry function: {entry_fn_name}");
4050 let new_name = format!("___user_qir_{entry_fn_name}");
4051 entry_fn.as_global_value().set_name(&new_name);
4052 log::debug!("Renamed entry function to: {new_name}");
4053 let qubit_array = if capability_flags.dynamic_qubit_management {
4054 None
4055 } else {
4056 Some(create_qubit_array(&ctx, &module, entry_fn)?)
4057 };
4058
4059 let wasm_fns = get_wasm_functions(wasm_bytes)?;
4060 process_entry_function(
4061 &ctx,
4062 &module,
4063 entry_fn,
4064 &wasm_fns,
4065 &passthrough_calls,
4066 qubit_array,
4067 capability_flags,
4068 )?;
4069
4070 process_ir_defined_q_fns(
4072 &ctx,
4073 &module,
4074 entry_fn,
4075 capability_flags.dynamic_qubit_management,
4076 &passthrough_calls,
4077 )?;
4078
4079 if let Some(qubit_array) = qubit_array {
4080 free_all_qubits(&ctx, &module, entry_fn, qubit_array)?;
4081 }
4082
4083 let _ = add_qmain_wrapper(&ctx, &module, entry_fn);
4085
4086 crate::llvm_verify::verify_module(&module, "LLVM module verification failed")?;
4087
4088 for attr in get_string_attrs(entry_fn) {
4090 let kind = decode_string_attribute_kind(attr)?;
4091 entry_fn.remove_string_attribute(AttributeLoc::Function, &kind);
4092 }
4093
4094 let md_string = ctx.metadata_string("mainlib");
4099 let md_node = ctx.metadata_node(&[md_string.into()]);
4100 module
4101 .add_global_metadata("name", &md_node)
4102 .map_err(|e| format!("Failed to add global metadata: {e}"))?;
4103 add_generator_metadata(&ctx, &module, "gen_name", env!("CARGO_PKG_NAME"))?;
4104 add_generator_metadata(&ctx, &module, "gen_version", env!("CARGO_PKG_VERSION"))?;
4105
4106 optimize(&module, opt_level, target)?;
4107 prune_unused_ir_qis_helpers(&module);
4108
4109 Ok(memory_buffer_to_owned_bytes(
4110 &module.write_bitcode_to_memory(),
4111 ))
4112}
4113
4114#[cfg(feature = "wasm")]
4119pub fn get_wasm_functions(
4120 wasm_bytes: Option<&[u8]>,
4121) -> Result<std::collections::BTreeMap<String, u64>, String> {
4122 use crate::utils::parse_wasm_functions;
4123 use std::collections::BTreeMap;
4124
4125 let mut wasm_fns: BTreeMap<String, u64> = BTreeMap::new();
4126 if let Some(bytes) = wasm_bytes {
4127 wasm_fns = parse_wasm_functions(bytes)?;
4128 log::debug!("WASM function map: {wasm_fns:?}");
4129 }
4130 Ok(wasm_fns)
4131}
4132
4133#[cfg(not(feature = "wasm"))]
4134fn get_wasm_functions(
4135 _wasm_bytes: Option<&[u8]>,
4136) -> Result<std::collections::BTreeMap<String, u64>, String> {
4137 Ok(std::collections::BTreeMap::new())
4138}
4139
4140pub fn validate_qir(bc_bytes: &[u8], wasm_bytes: Option<&[u8]>) -> Result<(), String> {
4149 use crate::{
4150 aux::{
4151 get_capability_flags, validate_module_flags, validate_module_layout_and_triple,
4152 validate_qir_call_sites,
4153 },
4154 convert::{
4155 ENTRY_ATTRIBUTE_KEYS, find_entry_function, validate_declared_qubit_limit,
4156 validate_declared_result_limit,
4157 },
4158 };
4159 use inkwell::{attributes::AttributeLoc, context::Context};
4160
4161 let ctx = Context::create();
4162 let module = parse_bitcode_module(&ctx, bc_bytes, "bitcode")?;
4163 let mut errors = Vec::new();
4164
4165 let capability_flags = get_capability_flags(&module);
4166 validate_module_layout_and_triple(&module);
4167 let entry_fn = if let Ok(entry_fn) = find_entry_function(&module) {
4168 if entry_fn.get_basic_blocks().is_empty() {
4169 errors.push("Entry function has no basic blocks".to_string());
4170 }
4171
4172 for attr in ENTRY_ATTRIBUTE_KEYS.iter().copied().filter(|attr| {
4174 *attr != "entry_point"
4175 && !(*attr == "required_num_qubits" && capability_flags.dynamic_qubit_management)
4176 && !(*attr == "required_num_results" && capability_flags.dynamic_result_management)
4177 }) {
4178 let val = entry_fn.get_string_attribute(AttributeLoc::Function, attr);
4179 if val.is_none() {
4180 errors.push(format!("Missing required attribute: `{attr}`"));
4181 }
4182 }
4183
4184 for (attr, type_) in [("required_num_qubits", "qubit")] {
4188 if capability_flags.dynamic_qubit_management {
4189 continue;
4190 }
4191 if entry_fn
4192 .get_string_attribute(AttributeLoc::Function, attr)
4193 .and_then(|a| {
4194 decode_llvm_c_string(a.get_string_value())?
4195 .parse::<u32>()
4196 .ok()
4197 })
4198 == Some(0)
4199 {
4200 errors.push(format!("Entry function must have at least one {type_}"));
4201 }
4202 }
4203 if capability_flags.dynamic_qubit_management
4204 && let Err(err) = validate_declared_qubit_limit(entry_fn)
4205 {
4206 errors.push(err);
4207 }
4208 if capability_flags.dynamic_result_management
4209 && let Err(err) = validate_declared_result_limit(entry_fn)
4210 {
4211 errors.push(err);
4212 }
4213 entry_fn
4214 } else {
4215 errors.push("No entry function found in QIR module".to_string());
4216 return Err(errors.join("; "));
4217 };
4218
4219 let wasm_fns = get_wasm_functions(wasm_bytes)?;
4220
4221 let capability_errors =
4222 validate_qir_call_sites(&module, entry_fn, &wasm_fns, capability_flags, &mut errors);
4223
4224 validate_module_flags(&module, &mut errors);
4225 errors.extend(capability_errors);
4226
4227 if !errors.is_empty() {
4228 return Err(errors.join("; "));
4229 }
4230 log::info!("QIR validation passed");
4231 Ok(())
4232}
4233
4234pub fn qir_ll_to_bc(ll_text: &str) -> Result<Vec<u8>, String> {
4239 use inkwell::context::Context;
4240
4241 let ctx = Context::create();
4242 let module = create_module_from_ir_text(&ctx, ll_text, "qir")?;
4243
4244 Ok(memory_buffer_to_owned_bytes(
4245 &module.write_bitcode_to_memory(),
4246 ))
4247}
4248
4249fn decode_string_attribute_kind(attr: inkwell::attributes::Attribute) -> Result<String, String> {
4250 use llvm_sys::core::LLVMGetStringAttributeKind;
4251 use std::slice;
4252
4253 let mut kind_len = 0_u32;
4254 let kind_ptr = unsafe { LLVMGetStringAttributeKind(attr.as_mut_ptr(), &raw mut kind_len) };
4255 if kind_ptr.is_null() {
4256 return Err("LLVM returned a null attribute kind pointer".to_string());
4257 }
4258 let kind_len = usize::try_from(kind_len)
4259 .map_err(|_err| "Attribute kind length does not fit into usize".to_string())?;
4260 let kind_bytes = unsafe { slice::from_raw_parts(kind_ptr.cast::<u8>(), kind_len) };
4261 std::str::from_utf8(kind_bytes)
4262 .map_err(|e| format!("Invalid UTF-8 in attribute kind: {e}"))
4263 .map(str::to_owned)
4264}
4265
4266fn decode_string_attribute_value(
4267 attr: inkwell::attributes::Attribute,
4268 kind: &str,
4269) -> Result<Option<String>, String> {
4270 use llvm_sys::core::LLVMGetStringAttributeValue;
4271 use std::slice;
4272
4273 let mut value_len = 0_u32;
4274 let value_ptr = unsafe { LLVMGetStringAttributeValue(attr.as_mut_ptr(), &raw mut value_len) };
4275 if value_len == 0 {
4276 return Ok(None);
4277 }
4278 if value_ptr.is_null() {
4279 return Err(format!(
4280 "LLVM returned a null attribute value pointer for `{kind}`"
4281 ));
4282 }
4283 let value_len = usize::try_from(value_len)
4284 .map_err(|_err| format!("Attribute `{kind}` value length does not fit into usize"))?;
4285 let value_bytes = unsafe { slice::from_raw_parts(value_ptr.cast::<u8>(), value_len) };
4286 let value = std::str::from_utf8(value_bytes)
4287 .map_err(|e| format!("Invalid UTF-8 in attribute `{kind}` value: {e}"))?
4288 .to_owned();
4289 Ok(Some(value))
4290}
4291
4292pub fn get_entry_attributes(
4300 bc_bytes: &[u8],
4301) -> Result<std::collections::BTreeMap<String, Option<String>>, String> {
4302 use crate::convert::{find_entry_function, get_string_attrs};
4303 use inkwell::context::Context;
4304 use std::collections::BTreeMap;
4305
4306 let ctx = Context::create();
4307 let module = parse_bitcode_module(&ctx, bc_bytes, "bitcode")?;
4308
4309 let mut metadata = BTreeMap::new();
4310 if let Ok(entry_fn) = find_entry_function(&module) {
4311 for attr in get_string_attrs(entry_fn) {
4312 let kind_id = match decode_string_attribute_kind(attr) {
4313 Ok(kind_id) => kind_id,
4314 Err(err) => {
4315 log::warn!("Skipping attribute with invalid kind: {err}");
4316 continue;
4317 }
4318 };
4319 match decode_string_attribute_value(attr, &kind_id) {
4320 Ok(value) => {
4321 metadata.insert(kind_id, value);
4322 }
4323 Err(err) => {
4324 log::warn!("{err}");
4325 metadata.insert(kind_id, None);
4326 }
4327 }
4328 }
4329 }
4330 Ok(metadata)
4331}
4332
4333#[cfg(feature = "python")]
4334mod exceptions {
4335 use pyo3::exceptions::PyException;
4336 use pyo3_stub_gen::create_exception;
4337
4338 create_exception!(
4339 qir_qis,
4340 ValidationError,
4341 PyException,
4342 "QIR ValidationError.\n\nRaised when the QIR is invalid."
4343 );
4344 create_exception!(
4345 qir_qis,
4346 CompilerError,
4347 PyException,
4348 "QIR CompilerError.\n\nRaised when QIR to QIS compilation fails."
4349 );
4350}
4351
4352#[cfg(feature = "python")]
4353#[pymodule]
4354mod qir_qis {
4355 use std::borrow::Cow;
4356 use std::collections::BTreeMap;
4357
4358 use super::{PyErr, PyResult, pyfunction};
4359
4360 use pyo3_stub_gen::derive::gen_stub_pyfunction;
4361
4362 #[pymodule_export]
4363 use super::exceptions::CompilerError;
4364 #[pymodule_export]
4365 use super::exceptions::ValidationError;
4366
4367 #[gen_stub_pyfunction]
4379 #[pyfunction]
4380 #[allow(
4381 clippy::needless_pass_by_value,
4382 reason = "PyO3 entrypoint accepts owned bytes from Python callers"
4383 )]
4384 #[pyo3(signature = (bc_bytes, *, wasm_bytes = None))]
4385 pub fn validate_qir(bc_bytes: Cow<[u8]>, wasm_bytes: Option<Cow<[u8]>>) -> PyResult<()> {
4386 crate::validate_qir(&bc_bytes, wasm_bytes.as_deref())
4387 .map_err(PyErr::new::<ValidationError, _>)
4388 }
4389
4390 #[gen_stub_pyfunction]
4403 #[pyfunction]
4404 #[allow(
4405 clippy::needless_pass_by_value,
4406 reason = "PyO3 entrypoint accepts owned bytes from Python callers"
4407 )]
4408 #[allow(
4409 clippy::missing_errors_doc,
4410 reason = "PyO3 signature and Python exception type already document failures"
4411 )]
4412 #[cfg_attr(
4413 windows,
4414 pyo3(signature = (bc_bytes, *, opt_level = 0, target = "native", wasm_bytes = None))
4415 )]
4416 #[cfg_attr(
4417 not(windows),
4418 pyo3(signature = (bc_bytes, *, opt_level = 2, target = "aarch64", wasm_bytes = None))
4419 )]
4420 pub fn qir_to_qis<'a>(
4421 bc_bytes: Cow<[u8]>,
4422 opt_level: u32,
4423 target: &'a str,
4424 wasm_bytes: Option<Cow<'a, [u8]>>,
4425 ) -> PyResult<Cow<'a, [u8]>> {
4426 let result = crate::qir_to_qis(&bc_bytes, opt_level, target, wasm_bytes.as_deref())
4427 .map_err(PyErr::new::<CompilerError, _>)?;
4428
4429 Ok(result.into())
4430 }
4431
4432 #[gen_stub_pyfunction]
4437 #[pyfunction]
4438 pub fn qir_ll_to_bc(ll_text: &str) -> PyResult<Cow<'_, [u8]>> {
4439 let result = crate::qir_ll_to_bc(ll_text).map_err(PyErr::new::<ValidationError, _>)?;
4440 Ok(result.into())
4441 }
4442
4443 #[gen_stub_pyfunction]
4451 #[pyfunction]
4452 #[allow(
4453 clippy::needless_pass_by_value,
4454 reason = "PyO3 entrypoint accepts owned bytes from Python callers"
4455 )]
4456 fn get_entry_attributes(bc_bytes: Cow<[u8]>) -> PyResult<BTreeMap<String, Option<String>>> {
4457 crate::get_entry_attributes(&bc_bytes).map_err(PyErr::new::<ValidationError, _>)
4458 }
4459}
4460
4461#[cfg(feature = "python")]
4462define_stub_info_gatherer!(stub_info);
4463
4464#[cfg(test)]
4465mod test {
4466 #![allow(
4467 clippy::expect_used,
4468 reason = "tests use expect for direct fixture failure messages"
4469 )]
4470 #![allow(
4471 clippy::unwrap_used,
4472 reason = "tests unwrap fixed fixture and parser outputs"
4473 )]
4474 #![allow(
4475 clippy::indexing_slicing,
4476 reason = "tests inspect fixed positions in small generated arrays"
4477 )]
4478 use crate::{
4479 convert::get_string_label, create_memory_buffer_from_bytes, create_module_from_ir_text,
4480 get_entry_attributes, memory_buffer_to_owned_bytes, parse_bitcode_module, qir_ll_to_bc,
4481 qir_to_qis, qir_to_qis_with_passthrough_calls, validate_qir,
4482 };
4483 use inkwell::{
4484 context::Context,
4485 memory_buffer::MemoryBuffer,
4486 module::Module,
4487 values::{CallSiteValue, FunctionValue},
4488 };
4489 use proptest::prelude::*;
4490 use rstest::rstest;
4491 use std::{collections::BTreeMap, path::Path, sync::LazyLock};
4492 #[cfg(feature = "wasm")]
4493 use wasm_encoder::{ExportKind, ExportSection, Module as WasmModule};
4494
4495 const PROPERTY_FIXTURES: &[&str] = &[
4496 "tests/data/base.ll",
4497 "tests/data/base_array.ll",
4498 "tests/data/adaptive.ll",
4499 "tests/data/qir2_base.ll",
4500 "tests/data/qir2_adaptive.ll",
4501 "tests/data/mz_leaked.ll",
4502 ];
4503 const DYNAMIC_FEATURE_FIXTURES: &[&str] = &[
4504 "tests/data/dynamic_qubit_alloc.ll",
4505 "tests/data/dynamic_qubit_alloc_checked.ll",
4506 "tests/data/dynamic_qubit_array_checked.ll",
4507 "tests/data/dynamic_qubit_array_ssa.ll",
4508 "tests/data/dynamic_result_alloc.ll",
4509 "tests/data/dynamic_result_mixed_array_output.ll",
4510 ];
4511 static PROPERTY_FIXTURE_BITCODE: LazyLock<BTreeMap<&'static str, Vec<u8>>> =
4512 LazyLock::new(|| {
4513 PROPERTY_FIXTURES
4514 .iter()
4515 .map(|path| {
4516 let ll_text =
4517 std::fs::read_to_string(path).expect("Failed to read LLVM IR fixture");
4518 let bitcode = qir_ll_to_bc(&ll_text)
4519 .expect("Failed to convert LLVM IR fixture to bitcode");
4520 (*path, bitcode)
4521 })
4522 .collect()
4523 });
4524
4525 fn conservative_translation_settings() -> (u32, &'static str) {
4526 (0, "native")
4527 }
4528
4529 fn load_fixture_bitcode(path: &str) -> Vec<u8> {
4530 PROPERTY_FIXTURE_BITCODE
4531 .get(path)
4532 .cloned()
4533 .expect("Fixture bitcode should be precompiled")
4534 }
4535
4536 fn verify_bitcode_module(bitcode: &[u8], name: &str) -> Result<(), String> {
4537 let ctx = Context::create();
4538 let module = parse_bitcode_module(&ctx, bitcode, name)?;
4539 crate::llvm_verify::verify_module(&module, "LLVM verifier rejected translated module")
4540 }
4541
4542 fn parse_bitcode_as_file(bitcode: &[u8], name: &str) -> Result<(), String> {
4543 let mut temp_file = tempfile::Builder::new()
4544 .prefix(name)
4545 .suffix(".bc")
4546 .tempfile()
4547 .map_err(|e| format!("Failed to create temp bitcode file: {e}"))?;
4548 std::io::Write::write_all(&mut temp_file, bitcode)
4549 .map_err(|e| format!("Failed to write temp bitcode: {e}"))?;
4550
4551 let ctx = Context::create();
4552 let memory_buffer = MemoryBuffer::create_from_file(temp_file.path())
4553 .map_err(|e| format!("Failed to read temp bitcode: {e}"))?;
4554 Module::parse_bitcode_from_buffer(&memory_buffer, &ctx)
4555 .map(|_| ())
4556 .map_err(|e| format!("Failed to parse bitcode: {e}"))
4557 }
4558
4559 fn validate_dynamic_array_backing_errors(ll_text: &str) -> Vec<String> {
4560 let ctx = Context::create();
4561 let module = create_module_from_ir_text(&ctx, ll_text, "qir")
4562 .expect("inline IR should parse for dynamic array backing validation");
4563 let mut errors = Vec::new();
4564 crate::aux::validate_dynamic_array_allocation_backing(&module, &mut errors);
4565 errors
4566 }
4567
4568 fn assert_public_bitcode_round_trips_from_file(bitcode: &[u8], name: &str) {
4569 let ctx = Context::create();
4570 let module = parse_bitcode_module(&ctx, bitcode, name)
4571 .expect("Bitcode should reparse through qir-qis helpers");
4572 let raw_buffer = module.write_bitcode_to_memory();
4573 assert_eq!(
4574 raw_buffer.as_slice().len(),
4575 bitcode.len(),
4576 "Public bitcode bytes should match LLVM's in-memory buffer length exactly"
4577 );
4578 parse_bitcode_as_file(bitcode, name)
4579 .expect("Public bitcode should parse when consumed from a file");
4580 }
4581
4582 #[cfg(feature = "wasm")]
4583 fn build_wasm_exports(exports: &[(String, u32)]) -> Vec<u8> {
4584 let mut module = WasmModule::new();
4585 let mut export_section = ExportSection::new();
4586 for (name, index) in exports {
4587 export_section.export(name, ExportKind::Func, *index);
4588 }
4589 module.section(&export_section);
4590 module.finish()
4591 }
4592
4593 fn minimal_qir_with_body(
4594 required_num_qubits: &str,
4595 required_num_results: &str,
4596 qir_major_flag: &str,
4597 extra_decl: &str,
4598 body: &str,
4599 ) -> String {
4600 format!(
4601 r#"%Qubit = type opaque
4602%Result = type opaque
4603
4604{extra_decl}
4605
4606define i64 @Entry_Point_Name() #0 {{
4607entry:
4608{body}
4609 ret i64 0
4610}}
4611
4612attributes #0 = {{ "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="{required_num_qubits}" "required_num_results"="{required_num_results}" }}
4613
4614!llvm.module.flags = !{{!0, !1, !2, !3}}
4615!0 = !{{i32 1, !"qir_major_version", i32 {qir_major_flag}}}
4616!1 = !{{i32 7, !"qir_minor_version", i32 0}}
4617!2 = !{{i32 1, !"dynamic_qubit_management", i1 false}}
4618!3 = !{{i32 1, !"dynamic_result_management", i1 false}}
4619"#
4620 )
4621 }
4622
4623 fn minimal_qir_missing_attr(missing_attr: &str) -> String {
4624 let attrs = [
4625 ("entry_point", None),
4626 ("qir_profiles", Some("base_profile")),
4627 ("output_labeling_schema", Some("schema_id")),
4628 ("required_num_qubits", Some("1")),
4629 ("required_num_results", Some("1")),
4630 ];
4631 let rendered_attrs = attrs
4632 .into_iter()
4633 .filter(|(name, _)| *name != missing_attr)
4634 .map(|(name, value)| {
4635 value.map_or_else(
4636 || format!(r#""{name}""#),
4637 |value| format!(r#""{name}"="{value}""#),
4638 )
4639 })
4640 .collect::<Vec<_>>()
4641 .join(" ");
4642
4643 format!(
4644 r#"
4645define i64 @Entry_Point_Name() #0 {{
4646entry:
4647 ret i64 0
4648}}
4649
4650attributes #0 = {{ {rendered_attrs} }}
4651
4652!llvm.module.flags = !{{!0, !1, !2, !3}}
4653!0 = !{{i32 1, !"qir_major_version", i32 1}}
4654!1 = !{{i32 7, !"qir_minor_version", i32 0}}
4655!2 = !{{i32 1, !"dynamic_qubit_management", i1 false}}
4656!3 = !{{i32 1, !"dynamic_result_management", i1 false}}
4657"#
4658 )
4659 }
4660
4661 fn minimal_dynamic_rt_declaration_qir(declaration: &str) -> String {
4662 format!(
4663 r#"
4664define i64 @Entry_Point_Name() #0 {{
4665entry:
4666 ret i64 0
4667}}
4668
4669{declaration}
4670
4671attributes #0 = {{ "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }}
4672
4673!llvm.module.flags = !{{!0, !1, !2, !3, !4}}
4674!0 = !{{i32 1, !"qir_major_version", i32 2}}
4675!1 = !{{i32 7, !"qir_minor_version", i32 0}}
4676!2 = !{{i32 1, !"dynamic_qubit_management", i1 true}}
4677!3 = !{{i32 1, !"dynamic_result_management", i1 true}}
4678!4 = !{{i32 1, !"arrays", i1 true}}
4679"#
4680 )
4681 }
4682
4683 fn assert_malformed_dynamic_rt_declaration(case_name: &str, declaration: &str, fn_name: &str) {
4684 let ll_text = minimal_dynamic_rt_declaration_qir(declaration);
4685 let bc_bytes = qir_ll_to_bc(&ll_text).unwrap();
4686 let err = validate_qir(&bc_bytes, None)
4687 .expect_err(&format!("{case_name} should fail validation"));
4688 assert!(
4689 err.contains(&format!("Malformed QIR RT function declaration: {fn_name}")),
4690 "{case_name} reported unexpected error: {err}"
4691 );
4692 }
4693
4694 fn minimal_qir_with_duplicate_major_flags(first_major: &str, second_major: &str) -> String {
4695 format!(
4696 r#"
4697define i64 @Entry_Point_Name() #0 {{
4698entry:
4699 ret i64 0
4700}}
4701
4702attributes #0 = {{ "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }}
4703
4704!llvm.module.flags = !{{!0, !1, !2, !3, !4}}
4705!0 = !{{i32 1, !"qir_major_version", i32 {first_major}}}
4706!1 = !{{i32 1, !"qir_major_version", i32 {second_major}}}
4707!2 = !{{i32 7, !"qir_minor_version", i32 0}}
4708!3 = !{{i32 1, !"dynamic_qubit_management", i1 false}}
4709!4 = !{{i32 1, !"dynamic_result_management", i1 false}}
4710"#
4711 )
4712 }
4713
4714 fn minimal_qir_with_duplicate_dynamic_flags(first_flag: &str, second_flag: &str) -> String {
4715 format!(
4716 r#"
4717define i64 @Entry_Point_Name() #0 {{
4718entry:
4719 ret i64 0
4720}}
4721
4722attributes #0 = {{ "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }}
4723
4724!llvm.module.flags = !{{!0, !1, !2, !3, !4}}
4725!0 = !{{i32 1, !"qir_major_version", i32 1}}
4726!1 = !{{i32 7, !"qir_minor_version", i32 0}}
4727!2 = !{{i32 1, !"dynamic_qubit_management", i1 {first_flag}}}
4728!3 = !{{i32 1, !"dynamic_qubit_management", i1 {second_flag}}}
4729!4 = !{{i32 1, !"dynamic_result_management", i1 false}}
4730"#
4731 )
4732 }
4733
4734 fn collect_called_function_names(helper: FunctionValue<'_>) -> Vec<String> {
4735 let mut calls = Vec::new();
4736 for bb in helper.get_basic_blocks() {
4737 for instr in bb.get_instructions() {
4738 let Ok(call) = CallSiteValue::try_from(instr) else {
4739 continue;
4740 };
4741 let Some(callee) = call.get_called_fn_value() else {
4742 continue;
4743 };
4744 let Ok(name) = callee.get_name().to_str() else {
4745 continue;
4746 };
4747 calls.push(name.to_string());
4748 }
4749 }
4750 calls
4751 }
4752
4753 fn get_qir_bytes(ll_path: &Path) -> Vec<u8> {
4754 let ll_text = std::fs::read_to_string(ll_path).expect("Failed to read test fixture");
4755 qir_ll_to_bc(&ll_text).expect("Failed to convert test fixture to bitcode")
4756 }
4757
4758 #[test]
4759 fn test_get_entry_attributes() {
4760 let ll_text = std::fs::read_to_string("tests/data/base-attrs.ll")
4761 .expect("Failed to read base-attrs.ll");
4762 let bc_bytes = qir_ll_to_bc(&ll_text).unwrap();
4763 let attrs = get_entry_attributes(&bc_bytes).unwrap();
4764 assert!(matches!(attrs.get("entry_point"), Some(None)));
4765 assert_eq!(
4766 attrs.get("qir_profiles"),
4767 Some(&Some("base_profile".to_string()))
4768 );
4769 assert_eq!(
4770 attrs.get("output_labeling_schema"),
4771 Some(&Some("labeled".to_string()))
4772 );
4773 assert_eq!(
4774 attrs.get("required_num_qubits"),
4775 Some(&Some("2".to_string()))
4776 );
4777 assert_eq!(
4778 attrs.get("required_num_results"),
4779 Some(&Some("2".to_string()))
4780 );
4781 }
4782
4783 #[test]
4784 fn test_entry_attributes_includes_optional_custom_attr() {
4785 let ll_text = r#"
4786define i64 @Entry_Point_Name() #0 {
4787entry:
4788 ret i64 0
4789}
4790
4791attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="labeled" "required_num_qubits"="2" "required_num_results"="2" "custom_attr"="custom_value" }
4792
4793!llvm.module.flags = !{!0, !1, !2, !3}
4794
4795!0 = !{i32 1, !"qir_major_version", i32 1}
4796!1 = !{i32 7, !"qir_minor_version", i32 0}
4797!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
4798!3 = !{i32 1, !"dynamic_result_management", i1 false}
4799"#;
4800 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
4801 let attrs = get_entry_attributes(&bc_bytes).unwrap();
4802 assert_eq!(
4803 attrs.get("custom_attr"),
4804 Some(&Some("custom_value".to_string()))
4805 );
4806 }
4807
4808 #[test]
4809 fn test_get_entry_attributes_is_order_independent() {
4810 let ll_text = r#"
4811define i64 @Entry_Point_Name() #0 {
4812entry:
4813 ret i64 0
4814}
4815
4816attributes #0 = { "custom_attr"="custom_value" "required_num_results"="2" "output_labeling_schema"="labeled" "entry_point" "required_num_qubits"="2" "qir_profiles"="base_profile" }
4817
4818!llvm.module.flags = !{!0, !1, !2, !3}
4819!0 = !{i32 1, !"qir_major_version", i32 1}
4820!1 = !{i32 7, !"qir_minor_version", i32 0}
4821!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
4822!3 = !{i32 1, !"dynamic_result_management", i1 false}
4823"#;
4824 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
4825 let attrs = get_entry_attributes(&bc_bytes).expect("entry attributes should parse");
4826 assert!(matches!(attrs.get("entry_point"), Some(None)));
4827 assert_eq!(
4828 attrs.get("qir_profiles"),
4829 Some(&Some("base_profile".to_string()))
4830 );
4831 assert_eq!(
4832 attrs.get("custom_attr"),
4833 Some(&Some("custom_value".to_string()))
4834 );
4835 }
4836
4837 #[test]
4838 fn test_qir_to_qis_strips_custom_entry_attrs() {
4839 let ll_text = r#"
4840define i64 @Entry_Point_Name() #0 {
4841entry:
4842 ret i64 0
4843}
4844
4845attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="labeled" "required_num_qubits"="2" "required_num_results"="2" "custom_attr"="custom_value" }
4846
4847!llvm.module.flags = !{!0, !1, !2, !3}
4848
4849!0 = !{i32 1, !"qir_major_version", i32 1}
4850!1 = !{i32 7, !"qir_minor_version", i32 0}
4851!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
4852!3 = !{i32 1, !"dynamic_result_management", i1 false}
4853"#;
4854 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
4855 let (opt_level, target) = if cfg!(windows) {
4856 (0, "native")
4857 } else {
4858 (2, "aarch64")
4859 };
4860 let qis_bytes = qir_to_qis(&bc_bytes, opt_level, target, None).unwrap();
4861
4862 let ctx = Context::create();
4863 let module = parse_bitcode_module(&ctx, &qis_bytes, "qis").unwrap();
4864 let entry_fn = module.get_function("___user_qir_Entry_Point_Name").unwrap();
4865
4866 assert!(
4867 entry_fn
4868 .get_string_attribute(inkwell::attributes::AttributeLoc::Function, "custom_attr")
4869 .is_none()
4870 );
4871 }
4872
4873 fn passthrough_qir_fixture(
4874 declarations: &str,
4875 entry_body: &str,
4876 required_num_qubits: u32,
4877 required_num_results: u32,
4878 ) -> Vec<u8> {
4879 let ll_text = format!(
4880 r#"
4881{declarations}
4882
4883define i64 @Entry_Point_Name() #0 {{
4884entry:
4885{entry_body}
4886}}
4887
4888attributes #0 = {{ "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="labeled" "required_num_qubits"="{required_num_qubits}" "required_num_results"="{required_num_results}" }}
4889
4890!llvm.module.flags = !{{!0, !1, !2, !3}}
4891!0 = !{{i32 1, !"qir_major_version", i32 2}}
4892!1 = !{{i32 7, !"qir_minor_version", i32 0}}
4893!2 = !{{i32 1, !"dynamic_qubit_management", i1 false}}
4894!3 = !{{i32 1, !"dynamic_result_management", i1 false}}
4895"#
4896 );
4897 qir_ll_to_bc(ll_text.as_str()).expect("Failed to convert pass-through fixture to bitcode")
4898 }
4899
4900 fn passthrough_fixture() -> Vec<u8> {
4901 passthrough_qir_fixture(
4902 "declare i64 @external_counter()",
4903 " %value = call i64 @external_counter()\n ret i64 %value",
4904 1,
4905 0,
4906 )
4907 }
4908
4909 #[test]
4910 fn test_qir_to_qis_rejects_unknown_external_call_by_default() {
4911 let bc_bytes = passthrough_fixture();
4912
4913 let err = qir_to_qis(&bc_bytes, 0, "native", None)
4914 .expect_err("default API should reject unknown external call");
4915
4916 assert!(err.contains("Unsupported function: external_counter"));
4917 }
4918
4919 #[test]
4920 fn test_qir_to_qis_with_passthrough_calls_preserves_listed_external_call() {
4921 let bc_bytes = passthrough_fixture();
4922
4923 let qis_bytes =
4924 qir_to_qis_with_passthrough_calls(&bc_bytes, 0, "native", None, &["external_counter"])
4925 .expect("listed external call should pass through");
4926
4927 let ctx = Context::create();
4928 let module = parse_bitcode_module(&ctx, &qis_bytes, "qis").unwrap();
4929 let external_counter = module.get_function("external_counter").unwrap();
4930 assert!(module_contains_direct_call(&module, external_counter));
4931 }
4932
4933 #[test]
4934 fn test_qir_to_qis_with_passthrough_calls_rejects_unlisted_external_call() {
4935 let bc_bytes = passthrough_fixture();
4936
4937 let err =
4938 qir_to_qis_with_passthrough_calls(&bc_bytes, 0, "native", None, &["other_external"])
4939 .expect_err("unlisted external call should fail");
4940
4941 assert!(err.contains("Unsupported function: external_counter"));
4942 }
4943
4944 #[test]
4945 fn test_qir_to_qis_with_passthrough_calls_rejects_ir_defined_passthrough_name() {
4946 let bc_bytes = passthrough_qir_fixture(
4947 r"define i64 @external_counter() {
4948entry:
4949 ret i64 7
4950}",
4951 " %value = call i64 @external_counter()\n ret i64 %value",
4952 1,
4953 0,
4954 );
4955
4956 let err =
4957 qir_to_qis_with_passthrough_calls(&bc_bytes, 0, "native", None, &["external_counter"])
4958 .expect_err("IR-defined pass-through names should fail");
4959
4960 assert!(
4961 err.contains(
4962 "Pass-through function `external_counter` must be an external declaration"
4963 )
4964 );
4965 }
4966
4967 #[test]
4968 fn test_qir_to_qis_with_passthrough_calls_preserves_listed_prefixed_external_call() {
4969 let bc_bytes = passthrough_qir_fixture(
4970 "declare void @__quantum__qis__vendor__body()",
4971 " call void @__quantum__qis__vendor__body()\n ret i64 0",
4972 1,
4973 0,
4974 );
4975
4976 let qis_bytes = qir_to_qis_with_passthrough_calls(
4977 &bc_bytes,
4978 0,
4979 "native",
4980 None,
4981 &["__quantum__qis__vendor__body"],
4982 )
4983 .expect("listed prefixed external call should pass through");
4984
4985 let ctx = Context::create();
4986 let module = parse_bitcode_module(&ctx, &qis_bytes, "qis").unwrap();
4987 let vendor = module.get_function("__quantum__qis__vendor__body").unwrap();
4988 assert!(module_contains_direct_call(&module, vendor));
4989 }
4990
4991 #[test]
4992 fn test_qir_to_qis_with_passthrough_calls_rejects_ir_defined_prefixed_call() {
4993 let bc_bytes = passthrough_qir_fixture(
4994 r"define void @__quantum__qis__vendor__body() {
4995entry:
4996 ret void
4997}",
4998 " call void @__quantum__qis__vendor__body()\n ret i64 0",
4999 1,
5000 0,
5001 );
5002
5003 let err = qir_to_qis_with_passthrough_calls(
5004 &bc_bytes,
5005 0,
5006 "native",
5007 None,
5008 &["__quantum__qis__vendor__body"],
5009 )
5010 .expect_err("IR-defined prefixed pass-through name should be rejected");
5011
5012 assert!(err.contains(
5013 "Pass-through function `__quantum__qis__vendor__body` must be an external declaration"
5014 ));
5015 }
5016
5017 #[test]
5018 fn test_qir_to_qis_rejects_unlisted_prefixed_external_rt_call() {
5019 let bc_bytes = passthrough_qir_fixture(
5020 "declare void @__quantum__rt__vendor__body()",
5021 " call void @__quantum__rt__vendor__body()\n ret i64 0",
5022 1,
5023 0,
5024 );
5025
5026 let err = qir_to_qis(&bc_bytes, 0, "native", None)
5027 .expect_err("unlisted prefixed runtime call should fail");
5028
5029 assert!(err.contains("Unsupported QIR RT function: __quantum__rt__vendor__body"));
5030 }
5031
5032 #[test]
5033 fn test_qir_to_qis_with_passthrough_calls_preserves_listed_prefixed_external_rt_call() {
5034 let bc_bytes = passthrough_qir_fixture(
5035 "declare void @__quantum__rt__vendor__body()",
5036 " call void @__quantum__rt__vendor__body()\n ret i64 0",
5037 1,
5038 0,
5039 );
5040
5041 let qis_bytes = qir_to_qis_with_passthrough_calls(
5042 &bc_bytes,
5043 0,
5044 "native",
5045 None,
5046 &["__quantum__rt__vendor__body"],
5047 )
5048 .expect("listed prefixed runtime call should pass through");
5049
5050 let ctx = Context::create();
5051 let module = parse_bitcode_module(&ctx, &qis_bytes, "qis").unwrap();
5052 let vendor = module.get_function("__quantum__rt__vendor__body").unwrap();
5053 assert!(module_contains_direct_call(&module, vendor));
5054 }
5055
5056 #[test]
5057 fn test_qir_to_qis_with_passthrough_calls_lowers_listed_builtin_qis_call() {
5058 let bc_bytes = passthrough_qir_fixture(
5059 r"%Qubit = type opaque
5060
5061declare void @__quantum__qis__h__body(%Qubit*)",
5062 " %q0 = inttoptr i64 0 to %Qubit*\n call void @__quantum__qis__h__body(%Qubit* %q0)\n ret i64 0",
5063 1,
5064 0,
5065 );
5066
5067 let default_qis_bytes =
5068 qir_to_qis(&bc_bytes, 0, "native", None).expect("built-in QIS call should lower");
5069 let passthrough_qis_bytes = qir_to_qis_with_passthrough_calls(
5070 &bc_bytes,
5071 0,
5072 "native",
5073 None,
5074 &["__quantum__qis__h__body"],
5075 )
5076 .expect("listed built-in QIS call should still lower");
5077
5078 assert_eq!(passthrough_qis_bytes, default_qis_bytes);
5079 }
5080
5081 #[test]
5082 fn test_qir_to_qis_with_passthrough_calls_rejects_reserved_output_names() {
5083 for reserved_name in crate::convert::RESERVED_PASSTHROUGH_EXACT_NAMES
5084 .iter()
5085 .copied()
5086 .chain(["qir_qis.helper", "res_result_slot"])
5087 {
5088 let bc_bytes = passthrough_qir_fixture(
5089 format!("declare void @{reserved_name}()").as_str(),
5090 format!(" call void @{reserved_name}()\n ret i64 0").as_str(),
5091 1,
5092 0,
5093 );
5094
5095 let err =
5096 qir_to_qis_with_passthrough_calls(&bc_bytes, 0, "native", None, &[reserved_name])
5097 .expect_err("reserved pass-through names should fail");
5098
5099 assert!(err.contains(&format!(
5100 "Pass-through function `{reserved_name}` uses a reserved converter output name"
5101 )));
5102 }
5103 }
5104
5105 #[cfg(feature = "wasm")]
5106 #[test]
5107 fn test_qir_to_qis_with_passthrough_calls_rejects_invalid_wasm_bytes() {
5108 let bc_bytes = passthrough_qir_fixture("", " ret i64 0", 0, 0);
5109 let err = qir_to_qis_with_passthrough_calls(&bc_bytes, 0, "native", Some(&[0x00]), &[])
5110 .expect_err("invalid wasm bytes should be rejected");
5111
5112 assert!(!err.is_empty());
5113 }
5114
5115 fn module_contains_direct_call(module: &Module<'_>, callee: FunctionValue<'_>) -> bool {
5116 module.get_functions().any(|function| {
5117 function.get_basic_blocks().iter().any(|bb| {
5118 bb.get_instructions()
5119 .any(|instr| match CallSiteValue::try_from(instr) {
5120 Ok(call) => call.get_called_fn_value() == Some(callee),
5121 Err(()) => false,
5122 })
5123 })
5124 })
5125 }
5126
5127 #[test]
5128 fn test_platform_default_conversion_settings_match_expectations() {
5129 if cfg!(windows) {
5130 assert_eq!(crate::DEFAULT_OPT_LEVEL, 0);
5131 assert_eq!(crate::DEFAULT_TARGET, "native");
5132 } else {
5133 assert_eq!(crate::DEFAULT_OPT_LEVEL, 2);
5134 assert_eq!(crate::DEFAULT_TARGET, "aarch64");
5135 }
5136 }
5137
5138 #[cfg(windows)]
5139 #[test]
5140 fn test_windows_optimized_conversion_returns_actionable_error() {
5141 let ll_text = r#"
5142define i64 @Entry_Point_Name() #0 {
5143entry:
5144 ret i64 0
5145}
5146
5147attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="labeled" "required_num_qubits"="1" "required_num_results"="1" }
5148
5149!llvm.module.flags = !{!0, !1, !2, !3}
5150!0 = !{i32 1, !"qir_major_version", i32 1}
5151!1 = !{i32 7, !"qir_minor_version", i32 0}
5152!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5153!3 = !{i32 1, !"dynamic_result_management", i1 false}
5154"#;
5155 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
5156 let err = qir_to_qis(&bc_bytes, 1, "native", None)
5157 .expect_err("optimized native conversion should fail fast on Windows");
5158 assert!(err.contains("supported only for `target=\"x86-64\"`"));
5159 assert!(err.contains("opt_level=0"));
5160 }
5161
5162 #[cfg(windows)]
5163 #[test]
5164 fn test_windows_optimized_x86_64_conversion_smoke() {
5165 let bc_bytes = load_fixture_bitcode("tests/data/base.ll");
5166 let output_bc = qir_to_qis(&bc_bytes, 1, "x86-64", None)
5167 .expect("optimized x86-64 conversion should succeed on Windows");
5168 let ctx = Context::create();
5169 let module = parse_bitcode_module(&ctx, &output_bc, "optimized_windows_qis")
5170 .expect("optimized Windows output should parse");
5171 assert!(module.get_function("qmain").is_some());
5172 }
5173
5174 #[test]
5175 fn test_invalid_target_reports_invalid_architecture_even_when_optimized_requested() {
5176 let bc_bytes = load_fixture_bitcode("tests/data/base.ll");
5177 let err = qir_to_qis(&bc_bytes, 1, "invalid-target", None)
5178 .expect_err("invalid targets should fail before platform-specific optimized guards");
5179 assert!(err.contains("Invalid target architecture: invalid-target"));
5180 }
5181
5182 #[test]
5183 fn test_qir_ll_to_bc_accepts_legacy_typed_pointers() {
5184 let ll_text =
5185 std::fs::read_to_string("tests/data/base.ll").expect("Failed to read base.ll");
5186 let bc_bytes = qir_ll_to_bc(&ll_text).unwrap();
5187 assert!(!bc_bytes.is_empty());
5188 }
5189
5190 #[test]
5191 fn test_qir_ll_to_bc_output_parses_when_read_from_file() {
5192 let ll_text =
5193 std::fs::read_to_string("tests/data/base.ll").expect("Failed to read base.ll");
5194 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert base.ll to bitcode");
5195
5196 assert_public_bitcode_round_trips_from_file(&bc_bytes, "public_qir_output");
5197 }
5198
5199 #[test]
5200 fn test_public_bitcode_helpers_parse_and_extract_public_bytes() {
5201 let ll_text =
5202 std::fs::read_to_string("tests/data/base.ll").expect("Failed to read base.ll");
5203 let public_bc = qir_ll_to_bc(&ll_text).expect("Failed to convert base.ll to bitcode");
5204
5205 let ctx = Context::create();
5206 let module = parse_bitcode_module(&ctx, &public_bc, "public_bitcode")
5207 .expect("public parser helper should parse generated bitcode");
5208 let raw_buffer = module.write_bitcode_to_memory();
5209
5210 let extracted = memory_buffer_to_owned_bytes(&raw_buffer);
5211 assert_eq!(
5212 extracted.len(),
5213 raw_buffer.as_slice().len(),
5214 "extracted bytes should preserve the full LLVM buffer payload"
5215 );
5216 assert_eq!(extracted, raw_buffer.as_slice());
5217
5218 let copied_buffer = create_memory_buffer_from_bytes(&extracted, "copied_public_bitcode")
5219 .expect("public memory-buffer helper should copy public bitcode");
5220 Module::parse_bitcode_from_buffer(&copied_buffer, &ctx)
5221 .expect("copied public bitcode should parse through inkwell");
5222 }
5223
5224 #[test]
5232 fn test_memory_buffer_to_owned_bytes_preserves_full_bitcode_payload() {
5233 let ll_text =
5234 std::fs::read_to_string("tests/data/base.ll").expect("Failed to read base.ll");
5235 let bitcode = qir_ll_to_bc(&ll_text).expect("Failed to convert base.ll to bitcode");
5236
5237 assert!(
5238 !bitcode.is_empty(),
5239 "bitcode writer should produce a non-empty payload"
5240 );
5241 assert_eq!(
5242 bitcode.len() % 4,
5243 0,
5244 "LLVM bitcode payloads are four-byte aligned; a truncated trailing byte breaks parsing"
5245 );
5246 assert_eq!(
5247 bitcode.first_chunk::<4>(),
5248 Some(&[0x42, 0x43, 0xc0, 0xde]),
5249 "bitcode should retain the LLVM 'BC\\u{{c0}}\\u{{de}}' magic"
5250 );
5251
5252 let ctx = Context::create();
5253 let module = parse_bitcode_module(&ctx, &bitcode, "round_trip")
5254 .expect("bitcode should parse without any terminator trimming");
5255 assert_eq!(
5256 memory_buffer_to_owned_bytes(&module.write_bitcode_to_memory()),
5257 bitcode,
5258 "round-tripping through the memory-buffer helper should be byte-identical"
5259 );
5260 }
5261
5262 #[cfg(feature = "python")]
5270 #[track_caller]
5271 #[expect(
5272 clippy::ok_expect,
5273 reason = "expecting the Result directly formats PyErr with Debug, which needs an \
5274 initialized interpreter and aborts the test binary"
5275 )]
5276 fn expect_py_ok<T>(result: crate::PyResult<T>, msg: &str) -> T {
5277 result.ok().expect(msg)
5278 }
5279
5280 #[cfg(feature = "python")]
5287 #[test]
5288 fn test_python_wrappers_match_rust_api_on_success() {
5289 let ll_text =
5290 std::fs::read_to_string("tests/data/base.ll").expect("Failed to read base.ll");
5291
5292 let rust_bc = qir_ll_to_bc(&ll_text).expect("Rust qir_ll_to_bc should succeed");
5293 let py_bc = expect_py_ok(
5294 crate::qir_qis::qir_ll_to_bc(&ll_text),
5295 "PyO3 qir_ll_to_bc should succeed for a valid fixture",
5296 );
5297 assert_eq!(
5298 py_bc.as_ref(),
5299 rust_bc.as_slice(),
5300 "PyO3 qir_ll_to_bc should return the same bytes as the Rust API"
5301 );
5302
5303 expect_py_ok(
5304 crate::qir_qis::validate_qir(rust_bc.clone().into(), None),
5305 "PyO3 validate_qir should accept a valid fixture",
5306 );
5307
5308 let rust_qis =
5311 qir_to_qis(&rust_bc, 0, "native", None).expect("Rust qir_to_qis should succeed");
5312 let py_qis = expect_py_ok(
5313 crate::qir_qis::qir_to_qis(rust_bc.into(), 0, "native", None),
5314 "PyO3 qir_to_qis should succeed for a valid fixture",
5315 );
5316 assert_eq!(
5317 py_qis.as_ref(),
5318 rust_qis.as_slice(),
5319 "PyO3 qir_to_qis should return the same bytes as the Rust API"
5320 );
5321 }
5322
5323 #[test]
5324 fn test_parse_bitcode_module_reports_llvm_parse_errors_for_malformed_bytes() {
5325 let ctx = Context::create();
5326 let err = parse_bitcode_module(&ctx, b"not llvm bitcode", "malformed")
5327 .expect_err("malformed bitcode should fail in LLVM parsing");
5328
5329 assert!(err.starts_with("Failed to parse bitcode:"));
5330 }
5331
5332 #[test]
5333 fn test_qir2_base_fixture_validate_and_compile() {
5334 let ll_text = std::fs::read_to_string("tests/data/qir2_base.ll")
5335 .expect("Failed to read qir2_base.ll");
5336 let input_bc = qir_ll_to_bc(&ll_text).expect("Failed to convert qir2_base.ll to bitcode");
5337
5338 validate_qir(&input_bc, None).expect("QIR 2.1 base fixture should validate");
5339 let output_bc =
5340 qir_to_qis(&input_bc, 0, "native", None).expect("QIR 2.1 base fixture should compile");
5341
5342 let ctx = Context::create();
5343 let module = parse_bitcode_module(&ctx, &output_bc, "qis_module")
5344 .expect("Compiled QIS bitcode should parse");
5345 assert!(module.get_function("qmain").is_some());
5346 assert!(module.get_function("qir_qis.load_qubit").is_some());
5347 }
5348
5349 #[test]
5350 fn test_qir2_adaptive_fixture_validate_and_compile() {
5351 let ll_text = std::fs::read_to_string("tests/data/qir2_adaptive.ll")
5352 .expect("Failed to read qir2_adaptive.ll");
5353 let input_bc =
5354 qir_ll_to_bc(&ll_text).expect("Failed to convert qir2_adaptive.ll to bitcode");
5355
5356 validate_qir(&input_bc, None).expect("QIR 2.1 adaptive fixture should validate");
5357 let output_bc = qir_to_qis(&input_bc, 0, "native", None)
5358 .expect("QIR 2.1 adaptive fixture should compile");
5359
5360 let ctx = Context::create();
5361 let module = parse_bitcode_module(&ctx, &output_bc, "qis_module")
5362 .expect("Compiled QIS bitcode should parse");
5363 assert!(module.get_function("qmain").is_some());
5364 assert!(module.get_function("___lazy_measure").is_some());
5365 }
5366
5367 #[test]
5368 fn test_qir_to_qis_output_parses_with_raw_llvm_buffer() {
5369 let ll_text =
5370 std::fs::read_to_string("tests/data/base.ll").expect("Failed to read base.ll");
5371 let input_bc = qir_ll_to_bc(&ll_text).expect("Failed to convert base.ll to bitcode");
5372 let output_bc =
5373 qir_to_qis(&input_bc, 0, "native", None).expect("base fixture should compile");
5374
5375 assert_public_bitcode_round_trips_from_file(&output_bc, "selene_qis_output");
5376 }
5377
5378 #[test]
5379 fn test_validate_module_flags_are_checked_cross_platform() {
5380 let ll_text = r#"
5381define i64 @Entry_Point_Name() #0 {
5382entry:
5383 ret i64 0
5384}
5385
5386attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5387
5388!llvm.module.flags = !{!0, !1, !2, !3}
5389!0 = !{i32 1, !"qir_major_version", i32 2}
5390!1 = !{i32 7, !"qir_minor_version", i32 0}
5391!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5392!3 = !{i32 1, !"dynamic_result_management", i1 false}
5393"#;
5394 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5395 validate_qir(&bc_bytes, None).expect("Module flags should validate on every platform");
5396 }
5397
5398 #[test]
5399 fn test_module_flag_parser_reads_existing_flags() {
5400 use crate::aux::collect_module_flags;
5401 use inkwell::context::Context;
5402
5403 let ll_text = r#"
5404define i64 @Entry_Point_Name() #0 {
5405entry:
5406 ret i64 0
5407}
5408
5409attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5410
5411!llvm.module.flags = !{!0, !1, !2, !3}
5412!0 = !{i32 1, !"qir_major_version", i32 2}
5413!1 = !{i32 7, !"qir_minor_version", i32 0}
5414!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5415!3 = !{i32 1, !"dynamic_result_management", i1 false}
5416"#;
5417
5418 let ctx = Context::create();
5419 let module = create_module_from_ir_text(&ctx, ll_text, "qir")
5420 .expect("Failed to create module from inline IR");
5421
5422 let flags = collect_module_flags(&module);
5423 assert_eq!(
5424 flags.get("qir_major_version").map(<[String]>::to_vec),
5425 Some(vec!["i32 2".to_string()])
5426 );
5427 assert_eq!(
5428 flags.get("qir_minor_version").map(<[String]>::to_vec),
5429 Some(vec!["i32 0".to_string()])
5430 );
5431 assert_eq!(
5432 flags
5433 .get("dynamic_qubit_management")
5434 .map(<[String]>::to_vec),
5435 Some(vec!["i1 false".to_string()])
5436 );
5437 assert_eq!(
5438 flags
5439 .get("dynamic_result_management")
5440 .map(<[String]>::to_vec),
5441 Some(vec!["i1 false".to_string()])
5442 );
5443 }
5444
5445 #[test]
5446 fn test_validate_module_flags_accept_duplicate_entries_if_one_matches() {
5447 let ll_text = r#"
5448define i64 @Entry_Point_Name() #0 {
5449entry:
5450 ret i64 0
5451}
5452
5453attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5454
5455!llvm.module.flags = !{!0, !1, !2, !3, !4}
5456!0 = !{i32 1, !"qir_major_version", i32 99}
5457!1 = !{i32 1, !"qir_major_version", i32 2}
5458!2 = !{i32 7, !"qir_minor_version", i32 0}
5459!3 = !{i32 1, !"dynamic_qubit_management", i1 false}
5460!4 = !{i32 1, !"dynamic_result_management", i1 false}
5461"#;
5462
5463 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5464 validate_qir(&bc_bytes, None)
5465 .expect("Module flags should validate when any duplicate entry matches");
5466 }
5467
5468 #[test]
5469 fn test_validate_module_flags_reports_malformed_required_flag() {
5470 let ll_text = r#"
5471define i64 @Entry_Point_Name() #0 {
5472entry:
5473 ret i64 0
5474}
5475
5476attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5477
5478!llvm.module.flags = !{!0, !1, !2, !3}
5479!0 = !{i32 1, !"qir_major_version", !4}
5480!1 = !{i32 7, !"qir_minor_version", i32 0}
5481!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5482!3 = !{i32 1, !"dynamic_result_management", i1 false}
5483!4 = !{i32 99}
5484"#;
5485
5486 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5487 let err = validate_qir(&bc_bytes, None).expect_err("Malformed module flag should fail");
5488 assert!(err.contains("Missing or unsupported module flag: qir_major_version"));
5489 }
5490
5491 #[test]
5492 fn test_validate_module_flags_with_non_metadata_name_operand_does_not_panic() {
5493 let ll_text = r#"
5494define i64 @Entry_Point_Name() #0 {
5495entry:
5496 ret i64 0
5497}
5498
5499attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5500
5501!llvm.module.flags = !{!0, !1, !2, !3}
5502!0 = !{i32 1, i32 123, i32 2}
5503!1 = !{i32 7, !"qir_minor_version", i32 0}
5504!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5505!3 = !{i32 1, !"dynamic_result_management", i1 false}
5506"#;
5507
5508 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5509 let err = validate_qir(&bc_bytes, None)
5510 .expect_err("Non-metadata module flag name operand should fail validation");
5511 assert!(err.contains("Missing required module flag: qir_major_version"));
5512 }
5513
5514 #[test]
5515 fn test_validate_module_flags_rejects_malformed_name_even_when_required_flags_exist() {
5516 let ll_text = r#"
5517define i64 @Entry_Point_Name() #0 {
5518entry:
5519 ret i64 0
5520}
5521
5522attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5523
5524!llvm.module.flags = !{!0, !1, !2, !3, !4}
5525!0 = !{i32 1, i32 123, i32 2}
5526!1 = !{i32 1, !"qir_major_version", i32 1}
5527!2 = !{i32 7, !"qir_minor_version", i32 0}
5528!3 = !{i32 1, !"dynamic_qubit_management", i1 false}
5529!4 = !{i32 1, !"dynamic_result_management", i1 false}
5530"#;
5531
5532 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5533 let err = validate_qir(&bc_bytes, None).expect_err(
5534 "Malformed module flag names should fail even with complete required flags",
5535 );
5536 assert!(err.contains("Malformed llvm.module.flags entry: expected metadata string name"));
5537 }
5538
5539 #[test]
5540 fn test_validate_qir_reports_exact_single_expected_module_flag_value() {
5541 let ll_text = r#"
5542define i64 @Entry_Point_Name() #0 {
5543entry:
5544 ret i64 0
5545}
5546
5547attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5548
5549!llvm.module.flags = !{!0, !1, !2, !3}
5550!0 = !{i32 1, !"qir_major_version", i32 1}
5551!1 = !{i32 7, !"qir_minor_version", i32 99}
5552!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5553!3 = !{i32 1, !"dynamic_result_management", i1 false}
5554"#;
5555
5556 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5557 let err = validate_qir(&bc_bytes, None)
5558 .expect_err("unsupported single-valued module flag should fail");
5559 assert!(err.contains("Unsupported qir_minor_version"));
5560 }
5561
5562 #[test]
5563 fn test_validate_qir_rejects_qir_1_1_version_pair() {
5564 let ll_text = r#"
5565define i64 @Entry_Point_Name() #0 {
5566entry:
5567 ret i64 0
5568}
5569
5570attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5571
5572!llvm.module.flags = !{!0, !1, !2, !3}
5573!0 = !{i32 1, !"qir_major_version", i32 1}
5574!1 = !{i32 7, !"qir_minor_version", i32 1}
5575!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5576!3 = !{i32 1, !"dynamic_result_management", i1 false}
5577"#;
5578
5579 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5580 let err = validate_qir(&bc_bytes, None).expect_err("QIR 1.1 should not validate");
5581 assert!(err.contains("Unsupported qir_minor_version"));
5582 }
5583
5584 #[test]
5585 fn test_validate_qir_accepts_qir_2_1_version_pair() {
5586 let ll_text = r#"
5587define i64 @Entry_Point_Name() #0 {
5588entry:
5589 ret i64 0
5590}
5591
5592attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5593
5594!llvm.module.flags = !{!0, !1, !2, !3}
5595!0 = !{i32 1, !"qir_major_version", i32 2}
5596!1 = !{i32 7, !"qir_minor_version", i32 1}
5597!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5598!3 = !{i32 1, !"dynamic_result_management", i1 false}
5599"#;
5600
5601 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5602 validate_qir(&bc_bytes, None).expect("QIR 2.1 should validate");
5603 }
5604
5605 #[test]
5606 fn test_validate_qir_reports_unsupported_optional_arrays_module_flag_value() {
5607 let ll_text = r#"
5608define i64 @Entry_Point_Name() #0 {
5609entry:
5610 ret i64 0
5611}
5612
5613attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5614
5615!llvm.module.flags = !{!0, !1, !2, !3, !4}
5616!0 = !{i32 1, !"qir_major_version", i32 2}
5617!1 = !{i32 7, !"qir_minor_version", i32 0}
5618!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5619!3 = !{i32 1, !"dynamic_result_management", i1 false}
5620!4 = !{i32 1, !"arrays", i32 7}
5621"#;
5622
5623 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5624 let err = validate_qir(&bc_bytes, None)
5625 .expect_err("unsupported optional arrays flag should fail validation");
5626 assert!(err.contains("Unsupported arrays: expected one of i1 false, i1 true"));
5627 }
5628
5629 #[test]
5630 fn test_validate_qir_reports_exact_expected_dynamic_module_flag_value() {
5631 let ll_text = r#"
5632define i64 @Entry_Point_Name() #0 {
5633entry:
5634 ret i64 0
5635}
5636
5637attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5638
5639!llvm.module.flags = !{!0, !1, !2, !3}
5640!0 = !{i32 1, !"qir_major_version", i32 1}
5641!1 = !{i32 7, !"qir_minor_version", i32 0}
5642!2 = !{i32 1, !"dynamic_qubit_management", i32 7}
5643!3 = !{i32 1, !"dynamic_result_management", i1 false}
5644"#;
5645
5646 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5647 let err = validate_qir(&bc_bytes, None).expect_err("unsupported dynamic flag should fail");
5648 assert!(
5649 err.contains("Unsupported dynamic_qubit_management: expected one of i1 false, i1 true")
5650 );
5651 }
5652
5653 #[test]
5654 fn test_validate_qir_reports_malformed_optional_arrays_module_flag() {
5655 let ll_text = r#"
5656define i64 @Entry_Point_Name() #0 {
5657entry:
5658 ret i64 0
5659}
5660
5661attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5662
5663!llvm.module.flags = !{!0, !1, !2, !3, !4}
5664!0 = !{i32 1, !"qir_major_version", i32 2}
5665!1 = !{i32 7, !"qir_minor_version", i32 0}
5666!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5667!3 = !{i32 1, !"dynamic_result_management", i1 false}
5668!4 = !{i32 1, !"arrays", !5}
5669!5 = !{i32 99}
5670"#;
5671
5672 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5673 let err =
5674 validate_qir(&bc_bytes, None).expect_err("malformed optional arrays flag should fail");
5675 assert!(err.contains("Missing or unsupported module flag: arrays"));
5676 }
5677
5678 #[test]
5679 fn test_validate_qir_missing_required_module_flag_reports_exact_message() {
5680 let ll_text = r#"
5681define i64 @Entry_Point_Name() #0 {
5682entry:
5683 ret i64 0
5684}
5685
5686attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5687
5688!llvm.module.flags = !{!0, !1, !2}
5689!0 = !{i32 7, !"qir_minor_version", i32 0}
5690!1 = !{i32 1, !"dynamic_qubit_management", i1 false}
5691!2 = !{i32 1, !"dynamic_result_management", i1 false}
5692"#;
5693
5694 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5695 let err = validate_qir(&bc_bytes, None).expect_err("Missing flag should fail");
5696 assert!(err.contains("Missing required module flag: qir_major_version"));
5697 }
5698
5699 #[test]
5700 fn test_validate_qir_consolidates_missing_required_module_flags() {
5701 let ll_text = r#"
5702define i64 @Entry_Point_Name() #0 {
5703entry:
5704 ret i64 0
5705}
5706
5707attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5708"#;
5709
5710 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5711 let err = validate_qir(&bc_bytes, None).expect_err("Missing flags should fail");
5712 assert_eq!(
5713 err,
5714 "Missing required module flags: qir_major_version, qir_minor_version, dynamic_qubit_management, dynamic_result_management"
5715 );
5716 }
5717
5718 #[test]
5719 fn test_validate_qir_consolidates_missing_or_unsupported_module_flags() {
5720 let ll_text = r#"
5721define i64 @Entry_Point_Name() #0 {
5722entry:
5723 ret i64 0
5724}
5725
5726attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5727
5728!llvm.module.flags = !{!0, !1, !2, !3, !4}
5729!0 = !{i32 1, !"qir_major_version", !5}
5730!1 = !{i32 7, !"qir_minor_version", i32 0}
5731!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5732!3 = !{i32 1, !"dynamic_result_management", i1 false}
5733!4 = !{i32 1, !"arrays", !5}
5734!5 = !{i32 99}
5735"#;
5736
5737 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5738 let err = validate_qir(&bc_bytes, None)
5739 .expect_err("Malformed module flags should report unsupported flags");
5740 assert_eq!(
5741 err,
5742 "Missing or unsupported module flags: qir_major_version, arrays"
5743 );
5744 }
5745
5746 #[test]
5747 fn test_validate_qir_preserves_error_ordering_across_grouped_module_flags() {
5748 let ll_text = r#"
5749define i64 @Entry_Point_Name() #0 {
5750entry:
5751 ret i64 0
5752}
5753
5754attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5755
5756!llvm.module.flags = !{!0, !1, !2, !3, !4}
5757!0 = !{i32 1, !"qir_major_version", !5}
5758!1 = !{i32 7, !"qir_minor_version", i32 0}
5759!2 = !{i32 1, !"dynamic_qubit_management", i32 7}
5760!3 = !{i32 1, !"dynamic_result_management", i1 false}
5761!4 = !{i32 1, !"arrays", !5}
5762!5 = !{i32 99}
5763"#;
5764
5765 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5766 let err =
5767 validate_qir(&bc_bytes, None).expect_err("Malformed and unsupported flags should fail");
5768 assert_eq!(
5769 err,
5770 "Missing or unsupported module flag: qir_major_version; Unsupported dynamic_qubit_management: expected one of i1 false, i1 true; Missing or unsupported module flag: arrays"
5771 );
5772 }
5773
5774 #[test]
5775 fn test_validate_qir_preserves_function_before_invalid_required_num_results_error_ordering() {
5776 let ll_text = minimal_qir_with_body(
5777 "1",
5778 "abc",
5779 "1",
5780 "declare void @__quantum__qis__bogus__body(%Qubit*)",
5781 r" %q0 = inttoptr i64 0 to %Qubit*
5782 call void @__quantum__qis__bogus__body(%Qubit* %q0)",
5783 );
5784
5785 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert inline QIR to bitcode");
5786 let err = validate_qir(&bc_bytes, None)
5787 .expect_err("invalid required_num_results and unsupported QIS call should fail");
5788
5789 let unsupported_fn_idx = err
5790 .find("Unsupported QIR QIS function: __quantum__qis__bogus__body")
5791 .expect("unsupported function error should be present");
5792 let invalid_required_num_results_idx = err
5793 .find("Invalid required_num_results attribute value: abc")
5794 .expect("invalid required_num_results error should be present");
5795
5796 assert!(
5797 unsupported_fn_idx < invalid_required_num_results_idx,
5798 "unsupported function error should be reported before required_num_results parse error: {err}"
5799 );
5800 }
5801
5802 #[test]
5803 fn test_validate_qir_preserves_module_flag_before_capability_error_ordering() {
5804 let ll_text = r#"
5805%Result = type opaque
5806
5807declare %Result* @__quantum__rt__result_allocate(ptr)
5808
5809define i64 @Entry_Point_Name() #0 {
5810entry:
5811 %tmp = alloca i64
5812 %res = call %Result* @__quantum__rt__result_allocate(ptr %tmp)
5813 ret i64 0
5814}
5815
5816attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5817
5818!llvm.module.flags = !{!0, !1, !2, !3}
5819!0 = !{i32 1, !"qir_major_version", !4}
5820!1 = !{i32 7, !"qir_minor_version", i32 0}
5821!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5822!3 = !{i32 1, !"dynamic_result_management", i1 false}
5823!4 = !{i32 99}
5824"#;
5825
5826 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5827 let err = validate_qir(&bc_bytes, None)
5828 .expect_err("malformed module flags and capability misuse should fail");
5829
5830 let module_flag_idx = err
5831 .find("Missing or unsupported module flag: qir_major_version")
5832 .expect("module flag error should be present");
5833 let capability_idx = err
5834 .find("__quantum__rt__result_allocate requires `dynamic_result_management=true`")
5835 .expect("capability error should be present");
5836
5837 assert!(
5838 module_flag_idx < capability_idx,
5839 "module flag errors should be reported before capability errors: {err}"
5840 );
5841 }
5842
5843 #[test]
5844 fn test_qir_to_qis_bool_output_uses_bool_tag_and_print_bool() {
5845 let ll_text = r#"
5846%Result = type opaque
5847
5848@bool_out = private constant [2 x i8] c"b\00"
5849
5850declare void @__quantum__rt__bool_record_output(i1, ptr)
5851
5852define i64 @Entry_Point_Name() #0 {
5853entry:
5854 call void @__quantum__rt__bool_record_output(i1 true, ptr getelementptr inbounds ([2 x i8], ptr @bool_out, i64 0, i64 0))
5855 ret i64 0
5856}
5857
5858attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5859
5860!llvm.module.flags = !{!0, !1, !2, !3}
5861!0 = !{i32 1, !"qir_major_version", i32 1}
5862!1 = !{i32 7, !"qir_minor_version", i32 0}
5863!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5864!3 = !{i32 1, !"dynamic_result_management", i1 false}
5865"#;
5866
5867 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5868 let output_bc =
5869 qir_to_qis(&bc_bytes, 0, "native", None).expect("bool output should compile");
5870
5871 let ctx = Context::create();
5872 let module = parse_bitcode_module(&ctx, &output_bc, "qis_module")
5873 .expect("Compiled QIS bitcode should parse");
5874 assert!(module.get_function("print_bool").is_some());
5875
5876 #[cfg(not(windows))]
5877 {
5878 let text = module.to_string();
5879 assert!(text.contains("USER:BOOL:b"));
5880 }
5881
5882 #[cfg(windows)]
5883 {
5884 let labels = module
5885 .get_globals()
5886 .filter_map(|global| crate::convert::get_string_label(global).ok())
5887 .collect::<Vec<_>>();
5888 assert!(labels.iter().any(|label| label.contains("USER:BOOL:b")));
5889 }
5890 }
5891
5892 #[test]
5893 fn test_validate_qir_rejects_malformed_barrier_suffix() {
5894 let ll_text = r#"
5895%Qubit = type opaque
5896
5897declare void @__quantum__qis__barrier2__adj(%Qubit*, %Qubit*)
5898
5899define i64 @Entry_Point_Name() #0 {
5900entry:
5901 %q0 = inttoptr i64 0 to %Qubit*
5902 %q1 = inttoptr i64 1 to %Qubit*
5903 call void @__quantum__qis__barrier2__adj(%Qubit* %q0, %Qubit* %q1)
5904 ret i64 0
5905}
5906
5907attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="2" "required_num_results"="1" }
5908
5909!llvm.module.flags = !{!0, !1, !2, !3}
5910!0 = !{i32 1, !"qir_major_version", i32 1}
5911!1 = !{i32 7, !"qir_minor_version", i32 0}
5912!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5913!3 = !{i32 1, !"dynamic_result_management", i1 false}
5914"#;
5915
5916 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5917 let err = validate_qir(&bc_bytes, None).expect_err("malformed barrier suffix should fail");
5918 assert!(err.contains("Unsupported QIR QIS function: __quantum__qis__barrier2__adj"));
5919 }
5920
5921 #[test]
5922 fn test_validate_qir_accepts_barrier_matching_required_qubits() {
5923 let ll_text = r#"
5924%Qubit = type opaque
5925
5926declare void @__quantum__qis__barrier2__body(%Qubit*, %Qubit*)
5927
5928define i64 @Entry_Point_Name() #0 {
5929entry:
5930 %q0 = inttoptr i64 0 to %Qubit*
5931 %q1 = inttoptr i64 1 to %Qubit*
5932 call void @__quantum__qis__barrier2__body(%Qubit* %q0, %Qubit* %q1)
5933 ret i64 0
5934}
5935
5936attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="2" "required_num_results"="1" }
5937
5938!llvm.module.flags = !{!0, !1, !2, !3}
5939!0 = !{i32 1, !"qir_major_version", i32 1}
5940!1 = !{i32 7, !"qir_minor_version", i32 0}
5941!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5942!3 = !{i32 1, !"dynamic_result_management", i1 false}
5943"#;
5944
5945 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5946 validate_qir(&bc_bytes, None)
5947 .expect("barrier arity matching required_num_qubits should validate");
5948 }
5949
5950 #[test]
5951 fn test_validate_qir_rejects_zero_arity_barrier() {
5952 let ll_text = r#"
5953%Qubit = type opaque
5954
5955declare void @__quantum__qis__barrier0__body()
5956
5957define i64 @Entry_Point_Name() #0 {
5958entry:
5959 call void @__quantum__qis__barrier0__body()
5960 ret i64 0
5961}
5962
5963attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5964
5965!llvm.module.flags = !{!0, !1, !2, !3}
5966!0 = !{i32 1, !"qir_major_version", i32 1}
5967!1 = !{i32 7, !"qir_minor_version", i32 0}
5968!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5969!3 = !{i32 1, !"dynamic_result_management", i1 false}
5970"#;
5971
5972 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5973 let err = validate_qir(&bc_bytes, None).expect_err("barrier0 should be rejected");
5974 assert!(err.contains("Unsupported QIR QIS function: __quantum__qis__barrier0__body"));
5975 }
5976
5977 #[test]
5978 fn test_validate_qir_rejects_unsupported_qtm_function() {
5979 let ll_text = r#"
5980declare void @___unknown_qtm()
5981
5982define i64 @Entry_Point_Name() #0 {
5983entry:
5984 call void @___unknown_qtm()
5985 ret i64 0
5986}
5987
5988attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
5989
5990!llvm.module.flags = !{!0, !1, !2, !3}
5991!0 = !{i32 1, !"qir_major_version", i32 1}
5992!1 = !{i32 7, !"qir_minor_version", i32 0}
5993!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
5994!3 = !{i32 1, !"dynamic_result_management", i1 false}
5995"#;
5996
5997 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
5998 let err = validate_qir(&bc_bytes, None)
5999 .expect_err("unsupported QTM declarations should fail validation");
6000 assert!(err.contains("Unsupported Qtm QIS function: ___unknown_qtm"));
6001 }
6002
6003 #[test]
6004 fn test_validate_qir_rejects_raw_barrier_runtime_function() {
6005 let ll_text = r#"
6006declare void @___barrier(ptr, i64)
6007
6008define i64 @Entry_Point_Name() #0 {
6009entry:
6010 call void @___barrier(ptr null, i64 1)
6011 ret i64 0
6012}
6013
6014attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6015
6016!llvm.module.flags = !{!0, !1, !2, !3}
6017!0 = !{i32 1, !"qir_major_version", i32 1}
6018!1 = !{i32 7, !"qir_minor_version", i32 0}
6019!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6020!3 = !{i32 1, !"dynamic_result_management", i1 false}
6021"#;
6022
6023 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6024 let err = validate_qir(&bc_bytes, None)
6025 .expect_err("raw ___barrier declarations should fail validation");
6026 assert!(err.contains("Unsupported Qtm QIS function: ___barrier"));
6027 }
6028
6029 #[test]
6030 fn test_qir_to_qis_rejects_raw_barrier_runtime_function() {
6031 let ll_text = r#"
6032declare void @___barrier(ptr, i64)
6033
6034define i64 @Entry_Point_Name() #0 {
6035entry:
6036 call void @___barrier(ptr null, i64 1)
6037 ret i64 0
6038}
6039
6040attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6041
6042!llvm.module.flags = !{!0, !1, !2, !3}
6043!0 = !{i32 1, !"qir_major_version", i32 1}
6044!1 = !{i32 7, !"qir_minor_version", i32 0}
6045!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6046!3 = !{i32 1, !"dynamic_result_management", i1 false}
6047"#;
6048
6049 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6050 let err = qir_to_qis(&bc_bytes, 0, "native", None)
6051 .expect_err("raw ___barrier declarations should fail translation");
6052 assert!(err.contains("Unsupported Qtm QIS function: ___barrier"));
6053 }
6054
6055 #[test]
6056 fn test_validate_qir_allows_ir_defined_non_main_helper() {
6057 let ll_text = r#"
6058%Qubit = type opaque
6059
6060define void @helper(%Qubit* %qubit) {
6061entry:
6062 call void @__quantum__qis__h__body(%Qubit* %qubit)
6063 ret void
6064}
6065
6066define i64 @Entry_Point_Name() #0 {
6067entry:
6068 %q0 = inttoptr i64 0 to %Qubit*
6069 call void @helper(%Qubit* %q0)
6070 ret i64 0
6071}
6072
6073declare void @__quantum__qis__h__body(%Qubit*)
6074
6075attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6076
6077!llvm.module.flags = !{!0, !1, !2, !3}
6078!0 = !{i32 1, !"qir_major_version", i32 1}
6079!1 = !{i32 7, !"qir_minor_version", i32 0}
6080!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6081!3 = !{i32 1, !"dynamic_result_management", i1 false}
6082"#;
6083
6084 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6085 validate_qir(&bc_bytes, None)
6086 .expect("IR-defined helper functions with non-main names should be allowed");
6087 }
6088
6089 #[test]
6090 fn test_validate_qir_allows_external_pointer_returning_declarations() {
6091 let ll_text = r#"
6092%Qubit = type opaque
6093
6094declare ptr @external_helper()
6095
6096define i64 @Entry_Point_Name() #0 {
6097entry:
6098 ret i64 0
6099}
6100
6101attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6102
6103!llvm.module.flags = !{!0, !1, !2, !3}
6104!0 = !{i32 1, !"qir_major_version", i32 1}
6105!1 = !{i32 7, !"qir_minor_version", i32 0}
6106!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6107!3 = !{i32 1, !"dynamic_result_management", i1 false}
6108"#;
6109
6110 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6111 validate_qir(&bc_bytes, None)
6112 .expect("external declarations without bodies should not be treated as IR-defined");
6113 }
6114
6115 #[test]
6116 fn test_validate_qir_rejects_ir_defined_pointer_returning_function() {
6117 let ll_text = r#"
6118define ptr @helper() {
6119entry:
6120 ret ptr null
6121}
6122
6123define i64 @Entry_Point_Name() #0 {
6124entry:
6125 ret i64 0
6126}
6127
6128attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6129
6130!llvm.module.flags = !{!0, !1, !2, !3}
6131!0 = !{i32 1, !"qir_major_version", i32 1}
6132!1 = !{i32 7, !"qir_minor_version", i32 0}
6133!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6134!3 = !{i32 1, !"dynamic_result_management", i1 false}
6135"#;
6136
6137 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6138 let err = validate_qir(&bc_bytes, None)
6139 .expect_err("IR-defined pointer-returning helper should fail validation");
6140 assert!(err.contains("Function `helper` cannot return a pointer type"));
6141 }
6142
6143 #[test]
6144 fn test_qir_to_qis_rejects_unknown_declared_qis_function() {
6145 let ll_text = r#"
6146%Qubit = type opaque
6147
6148declare void @__quantum__qis__mystery__body(%Qubit*)
6149
6150define i64 @Entry_Point_Name() #0 {
6151entry:
6152 %q0 = inttoptr i64 0 to %Qubit*
6153 call void @__quantum__qis__mystery__body(%Qubit* %q0)
6154 ret i64 0
6155}
6156
6157attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6158
6159!llvm.module.flags = !{!0, !1, !2, !3}
6160!0 = !{i32 1, !"qir_major_version", i32 1}
6161!1 = !{i32 7, !"qir_minor_version", i32 0}
6162!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6163!3 = !{i32 1, !"dynamic_result_management", i1 false}
6164"#;
6165
6166 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6167 let err = qir_to_qis(&bc_bytes, 0, "native", None)
6168 .expect_err("unknown declared QIS function should fail");
6169 assert!(err.contains("Unsupported QIR QIS function: __quantum__qis__mystery__body"));
6170 }
6171
6172 #[test]
6173 fn test_qir_to_qis_u1q_synonym_lowers_to_rxy() {
6174 let ll_text = r#"
6175%Qubit = type opaque
6176
6177declare void @__quantum__qis__u1q__body(double, double, %Qubit*)
6178
6179define i64 @Entry_Point_Name() #0 {
6180entry:
6181 %q0 = inttoptr i64 0 to %Qubit*
6182 call void @__quantum__qis__u1q__body(double 1.0, double 0.5, %Qubit* %q0)
6183 ret i64 0
6184}
6185
6186attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6187
6188!llvm.module.flags = !{!0, !1, !2, !3}
6189!0 = !{i32 1, !"qir_major_version", i32 1}
6190!1 = !{i32 7, !"qir_minor_version", i32 0}
6191!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6192!3 = !{i32 1, !"dynamic_result_management", i1 false}
6193"#;
6194
6195 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6196 let output_bc =
6197 qir_to_qis(&bc_bytes, 0, "native", None).expect("u1q synonym should compile");
6198
6199 let ctx = Context::create();
6200 let module = parse_bitcode_module(&ctx, &output_bc, "qis_module")
6201 .expect("Compiled QIS bitcode should parse");
6202 assert!(module.get_function("___rxy").is_some());
6203
6204 #[cfg(not(windows))]
6205 {
6206 let text = module.to_string();
6207 assert!(text.contains("___rxy"));
6208 }
6209 }
6210
6211 #[test]
6212 fn test_checked_result_index_rejects_out_of_bounds_values() {
6213 let err = crate::aux::checked_result_index(5, 1)
6214 .expect_err("out-of-bounds result indices should fail cleanly");
6215 assert_eq!(err, "Result index 5 exceeds required_num_results (1)");
6216 }
6217
6218 #[test]
6219 fn test_checked_qubit_index_accepts_zero_based_static_ids() {
6220 assert_eq!(
6221 crate::convert::checked_qubit_index(0, 2)
6222 .expect("zero-based first qubit id should map to slot 0"),
6223 0
6224 );
6225 assert_eq!(
6226 crate::convert::checked_qubit_index(1, 2)
6227 .expect("zero-based second qubit id should map to slot 1"),
6228 1
6229 );
6230 }
6231
6232 #[test]
6233 fn test_checked_qubit_index_rejects_out_of_bounds_values() {
6234 let err = crate::convert::checked_qubit_index(2, 2)
6235 .expect_err("out-of-bounds zero-based qubit ids should fail cleanly");
6236 assert_eq!(err, "Qubit index 2 exceeds required_num_qubits (2)");
6237 }
6238
6239 #[test]
6240 fn test_qir_to_qis_rejects_nonzero_single_qubit_rz_handle() {
6241 let ll_text = r#"
6242%Qubit = type opaque
6243
6244declare void @__quantum__qis__rz__body(double, %Qubit*)
6245
6246define i64 @Entry_Point_Name() #0 {
6247entry:
6248 %q0 = inttoptr i64 1 to %Qubit*
6249 call void @__quantum__qis__rz__body(double 5.000000e-1, %Qubit* %q0)
6250 ret i64 0
6251}
6252
6253attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6254
6255!llvm.module.flags = !{!0, !1, !2, !3}
6256!0 = !{i32 1, !"qir_major_version", i32 1}
6257!1 = !{i32 7, !"qir_minor_version", i32 0}
6258!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6259!3 = !{i32 1, !"dynamic_result_management", i1 false}
6260"#;
6261
6262 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6263 let err = qir_to_qis(&bc_bytes, 0, "native", None).expect_err(
6264 "static qubit id 1 should be rejected in a single-qubit zero-based program",
6265 );
6266 assert!(err.contains("Invalid static qubit handle passed to `__quantum__qis__rz__body`"));
6267 assert!(err.contains("Qubit index 1 exceeds required_num_qubits (1)"));
6268 }
6269
6270 #[test]
6271 fn test_validate_qir_rejects_out_of_range_handles_for_direct_qubit_gate_shapes() {
6272 let ll_text = r#"
6273%Qubit = type opaque
6274
6275declare void @__quantum__qis__rxy__body(double, double, %Qubit*)
6276declare void @__quantum__qis__u1q__body(double, double, %Qubit*)
6277declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*)
6278declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*)
6279declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*)
6280
6281define i64 @Entry_Point_Name() #0 {
6282entry:
6283 %q0 = inttoptr i64 0 to %Qubit*
6284 %q1 = inttoptr i64 1 to %Qubit*
6285 %q2 = inttoptr i64 2 to %Qubit*
6286 call void @__quantum__qis__rxy__body(double 5.000000e-1, double 2.500000e-1, %Qubit* %q1)
6287 call void @__quantum__qis__u1q__body(double 5.000000e-1, double 2.500000e-1, %Qubit* %q1)
6288 call void @__quantum__qis__rzz__body(double 5.000000e-1, %Qubit* %q0, %Qubit* %q1)
6289 call void @__quantum__qis__cx__body(%Qubit* %q0, %Qubit* %q1)
6290 call void @__quantum__qis__ccx__body(%Qubit* %q0, %Qubit* %q1, %Qubit* %q2)
6291 ret i64 0
6292}
6293
6294attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6295
6296!llvm.module.flags = !{!0, !1, !2, !3}
6297!0 = !{i32 1, !"qir_major_version", i32 1}
6298!1 = !{i32 7, !"qir_minor_version", i32 0}
6299!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6300!3 = !{i32 1, !"dynamic_result_management", i1 false}
6301"#;
6302
6303 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6304 let err = validate_qir(&bc_bytes, None)
6305 .expect_err("direct gate qubit positions should reject out-of-range static handles");
6306 for callee in [
6307 "__quantum__qis__rxy__body",
6308 "__quantum__qis__u1q__body",
6309 "__quantum__qis__rzz__body",
6310 "__quantum__qis__cx__body",
6311 "__quantum__qis__ccx__body",
6312 ] {
6313 assert!(
6314 err.contains(&format!("Invalid static qubit handle passed to `{callee}`")),
6315 "missing static handle validation for {callee}: {err}"
6316 );
6317 }
6318 assert!(err.contains("Qubit index 1 exceeds required_num_qubits (1)"));
6319 assert!(err.contains("Qubit index 2 exceeds required_num_qubits (1)"));
6320 }
6321
6322 #[test]
6323 fn test_validate_qir_rejects_nonzero_single_qubit_decomposed_handle() {
6324 let ll_text = r#"
6325%Qubit = type opaque
6326
6327declare void @__quantum__qis__h__body(%Qubit*)
6328
6329define i64 @Entry_Point_Name() #0 {
6330entry:
6331 %q0 = inttoptr i64 1 to %Qubit*
6332 call void @__quantum__qis__h__body(%Qubit* %q0)
6333 ret i64 0
6334}
6335
6336attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6337
6338!llvm.module.flags = !{!0, !1, !2, !3}
6339!0 = !{i32 1, !"qir_major_version", i32 1}
6340!1 = !{i32 7, !"qir_minor_version", i32 0}
6341!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6342!3 = !{i32 1, !"dynamic_result_management", i1 false}
6343"#;
6344
6345 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6346 let err = validate_qir(&bc_bytes, None)
6347 .expect_err("decomposed QIS gates should reject out-of-range static qubit handles");
6348 assert!(err.contains("Invalid static qubit handle passed to `__quantum__qis__h__body`"));
6349 assert!(err.contains("Qubit index 1 exceeds required_num_qubits (1)"));
6350 }
6351
6352 #[test]
6353 fn test_validate_qir_rejects_out_of_range_barrier_static_handles() {
6354 let ll_text = r#"
6355%Qubit = type opaque
6356
6357declare void @__quantum__qis__barrier2__body(%Qubit*, %Qubit*)
6358
6359define i64 @Entry_Point_Name() #0 {
6360entry:
6361 %q0 = inttoptr i64 0 to %Qubit*
6362 %q1 = inttoptr i64 2 to %Qubit*
6363 call void @__quantum__qis__barrier2__body(%Qubit* %q0, %Qubit* %q1)
6364 ret i64 0
6365}
6366
6367attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="2" "required_num_results"="1" }
6368
6369!llvm.module.flags = !{!0, !1, !2, !3}
6370!0 = !{i32 1, !"qir_major_version", i32 1}
6371!1 = !{i32 7, !"qir_minor_version", i32 0}
6372!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6373!3 = !{i32 1, !"dynamic_result_management", i1 false}
6374"#;
6375
6376 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6377 let err = validate_qir(&bc_bytes, None)
6378 .expect_err("barrier calls should reject out-of-range static qubit handles");
6379 assert!(
6380 err.contains("Invalid static qubit handle passed to `__quantum__qis__barrier2__body`")
6381 );
6382 assert!(err.contains("Qubit index 2 exceeds required_num_qubits (2)"));
6383 }
6384
6385 #[test]
6386 fn test_validate_qir_reports_static_handle_errors_with_preexisting_errors() {
6387 let ll_text = r#"
6388%Qubit = type opaque
6389
6390declare void @__quantum__qis__h__body(%Qubit*)
6391
6392define i64 @Entry_Point_Name() #0 {
6393entry:
6394 %q0 = inttoptr i64 1 to %Qubit*
6395 call void @__quantum__qis__h__body(%Qubit* %q0)
6396 ret i64 0
6397}
6398
6399attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" }
6400
6401!llvm.module.flags = !{!0, !1, !2, !3}
6402!0 = !{i32 1, !"qir_major_version", i32 1}
6403!1 = !{i32 7, !"qir_minor_version", i32 0}
6404!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6405!3 = !{i32 1, !"dynamic_result_management", i1 false}
6406"#;
6407
6408 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6409 let err = validate_qir(&bc_bytes, None)
6410 .expect_err("validation should collect unrelated and static qubit errors");
6411 assert!(err.contains("Missing required attribute: `required_num_results`"));
6412 assert!(err.contains("Invalid static qubit handle passed to `__quantum__qis__h__body`"));
6413 assert!(err.contains("Qubit index 1 exceeds required_num_qubits (1)"));
6414 }
6415
6416 #[test]
6417 fn test_validate_qir_ignores_reserved_helper_name_prefixes_for_qubit_inference() {
6418 for helper_name in [
6419 "__quantum__qis__custom_helper",
6420 "__quantum__rt__custom_helper",
6421 "qir_qis.custom_helper",
6422 "___custom_helper",
6423 "print_custom_helper",
6424 ] {
6425 let ll_text = format!(
6426 r#"
6427%Qubit = type opaque
6428
6429define internal void @{helper_name}(%Qubit* %qubit) {{
6430entry:
6431 call void @__quantum__qis__h__body(%Qubit* %qubit)
6432 ret void
6433}}
6434
6435define i64 @Entry_Point_Name() #0 {{
6436entry:
6437 %q1 = inttoptr i64 1 to %Qubit*
6438 call void @{helper_name}(%Qubit* %q1)
6439 ret i64 0
6440}}
6441
6442declare void @__quantum__qis__h__body(%Qubit*)
6443
6444attributes #0 = {{ "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }}
6445
6446!llvm.module.flags = !{{!0, !1, !2, !3}}
6447!0 = !{{i32 1, !"qir_major_version", i32 1}}
6448!1 = !{{i32 7, !"qir_minor_version", i32 0}}
6449!2 = !{{i32 1, !"dynamic_qubit_management", i1 false}}
6450!3 = !{{i32 1, !"dynamic_result_management", i1 false}}
6451"#
6452 );
6453
6454 let ctx = Context::create();
6455 let module = create_module_from_ir_text(&ctx, &ll_text, "qir")
6456 .expect("inline IR should parse for reserved helper-name coverage");
6457 let entry_fn = crate::convert::find_entry_function(&module)
6458 .expect("inline IR should include an entry function");
6459 let mut errors = Vec::new();
6460
6461 crate::aux::validate_static_qubit_helper_usage(&module, entry_fn, &mut errors);
6462
6463 assert!(
6464 errors.is_empty(),
6465 "reserved helper name `{helper_name}` should not trigger helper qubit inference: {errors:?}"
6466 );
6467 }
6468 }
6469
6470 #[test]
6471 fn test_validate_qir_does_not_treat_non_barrier_helpers_ending_in_body_as_barriers() {
6472 let ll_text = r#"
6473%Qubit = type opaque
6474
6475define internal void @helper__body(%Qubit* %qubit) {
6476entry:
6477 ret void
6478}
6479
6480define i64 @Entry_Point_Name() #0 {
6481entry:
6482 %q1 = inttoptr i64 1 to %Qubit*
6483 call void @helper__body(%Qubit* %q1)
6484 ret i64 0
6485}
6486
6487attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6488
6489!llvm.module.flags = !{!0, !1, !2, !3}
6490!0 = !{i32 1, !"qir_major_version", i32 1}
6491!1 = !{i32 7, !"qir_minor_version", i32 0}
6492!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6493!3 = !{i32 1, !"dynamic_result_management", i1 false}
6494"#;
6495
6496 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6497 let result = validate_qir(&bc_bytes, None);
6498 assert!(
6499 result.is_ok(),
6500 "non-barrier helpers ending in `__body` should not be treated as barriers: {result:?}"
6501 );
6502 }
6503
6504 #[test]
6505 fn test_qir_to_qis_loads_runtime_handles_for_static_native_calls() {
6506 let ll_path = Path::new("tests/data/base_native_only.ll");
6507 let qir_bytes = get_qir_bytes(ll_path);
6508 let (opt_level, target) = if cfg!(windows) {
6509 (0, "native")
6510 } else {
6511 (2, "aarch64")
6512 };
6513 let output_bc = qir_to_qis(&qir_bytes, opt_level, target, None)
6514 .expect("base_native_only should translate successfully");
6515
6516 let context = Context::create();
6517 let qis_text = crate::parse_bitcode_module(&context, &output_bc, "qis_module")
6518 .expect("translated QIS bitcode should parse")
6519 .to_string();
6520
6521 assert!(
6522 qis_text.contains("@qir_qis.load_qubit"),
6523 "expected translated output to keep the static-handle loader: {qis_text}"
6524 );
6525 assert!(
6526 !qis_text.contains("@___rzz(i64 0, i64 1,"),
6527 "native gates should use loaded runtime handles rather than slot indices: {qis_text}"
6528 );
6529 assert!(
6530 !qis_text.contains("@___rxy(i64 0,"),
6531 "single-qubit native gates should use loaded runtime handles rather than slot indices: {qis_text}"
6532 );
6533 assert!(
6534 !qis_text.contains("@___rz(i64 1,"),
6535 "single-qubit native gates should use loaded runtime handles rather than slot indices: {qis_text}"
6536 );
6537 }
6538
6539 #[test]
6540 fn test_validate_qir_allows_zero_required_num_results_for_mz_leaked() {
6541 let ll_text = minimal_qir_with_body(
6542 "1",
6543 "0",
6544 "1",
6545 r#"
6546declare i64 @__quantum__qis__mz_leaked__body(%Qubit*)
6547declare void @__quantum__rt__int_record_output(i64, i8*)
6548
6549@0 = private constant [7 x i8] c"leaked\00"
6550"#,
6551 r" %q0 = inttoptr i64 0 to %Qubit*
6552 %0 = call i64 @__quantum__qis__mz_leaked__body(%Qubit* %q0)
6553 call void @__quantum__rt__int_record_output(i64 %0, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @0, i64 0, i64 0))",
6554 );
6555
6556 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert inline QIR to bitcode");
6557 validate_qir(&bc_bytes, None)
6558 .expect("mz_leaked-only programs should validate with zero result slots");
6559 }
6560
6561 #[test]
6562 fn test_validate_qir_reports_invalid_required_num_results_value() {
6563 let ll_text = minimal_qir_with_body("1", "abc", "1", "", "");
6564
6565 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert inline QIR to bitcode");
6566 let err = validate_qir(&bc_bytes, None)
6567 .expect_err("invalid required_num_results should fail validation");
6568 assert!(err.contains("Invalid required_num_results attribute value: abc"));
6569 }
6570
6571 #[test]
6572 fn test_validate_qir_reports_missing_required_num_results_once() {
6573 let ll_text = r#"
6574%Qubit = type opaque
6575
6576define i64 @Entry_Point_Name() #0 {
6577entry:
6578 ret i64 0
6579}
6580
6581attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" }
6582
6583!llvm.module.flags = !{!0, !1, !2, !3}
6584!0 = !{i32 1, !"qir_major_version", i32 1}
6585!1 = !{i32 7, !"qir_minor_version", i32 0}
6586!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6587!3 = !{i32 1, !"dynamic_result_management", i1 false}
6588"#;
6589
6590 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6591 let err =
6592 validate_qir(&bc_bytes, None).expect_err("missing required_num_results should fail");
6593 assert_eq!(err, "Missing required attribute: `required_num_results`");
6594 }
6595
6596 #[test]
6597 fn test_validate_qir_rejects_result_usage_in_ir_defined_helper_with_zero_slots() {
6598 let ll_text = r#"
6599%Qubit = type opaque
6600%Result = type opaque
6601
6602declare i1 @__quantum__rt__read_result(%Result*)
6603
6604define internal void @helper() {
6605entry:
6606 %0 = call i1 @__quantum__rt__read_result(%Result* null)
6607 ret void
6608}
6609
6610define i64 @Entry_Point_Name() #0 {
6611entry:
6612 call void @helper()
6613 ret i64 0
6614}
6615
6616attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="0" }
6617
6618!llvm.module.flags = !{!0, !1, !2, !3}
6619!0 = !{i32 1, !"qir_major_version", i32 1}
6620!1 = !{i32 7, !"qir_minor_version", i32 0}
6621!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6622!3 = !{i32 1, !"dynamic_result_management", i1 false}
6623"#;
6624
6625 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6626 let err = validate_qir(&bc_bytes, None).expect_err(
6627 "result usage in IR-defined helpers should still respect required_num_results",
6628 );
6629 assert!(err.contains("Result index 0 exceeds required_num_results (0)"));
6630 }
6631
6632 #[test]
6633 fn test_validate_qir_rejects_out_of_range_static_handle_passed_to_ir_defined_helper() {
6634 let ll_text = r#"
6635%Qubit = type opaque
6636
6637define internal void @helper(%Qubit* %qubit) {
6638entry:
6639 call void @__quantum__qis__h__body(%Qubit* %qubit)
6640 ret void
6641}
6642
6643define i64 @Entry_Point_Name() #0 {
6644entry:
6645 %q1 = inttoptr i64 1 to %Qubit*
6646 call void @helper(%Qubit* %q1)
6647 ret i64 0
6648}
6649
6650declare void @__quantum__qis__h__body(%Qubit*)
6651
6652attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6653
6654!llvm.module.flags = !{!0, !1, !2, !3}
6655!0 = !{i32 1, !"qir_major_version", i32 1}
6656!1 = !{i32 7, !"qir_minor_version", i32 0}
6657!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6658!3 = !{i32 1, !"dynamic_result_management", i1 false}
6659"#;
6660
6661 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6662 let err = validate_qir(&bc_bytes, None)
6663 .expect_err("helper calls with out-of-range static handles should fail validation");
6664 assert!(err.contains("Invalid static qubit handle passed to `helper`"));
6665 assert!(err.contains("Qubit index 1 exceeds required_num_qubits (1)"));
6666 }
6667
6668 #[test]
6669 fn test_validate_qir_rejects_zero_required_num_results_for_result_measurement() {
6670 let ll_text = minimal_qir_with_body(
6671 "1",
6672 "0",
6673 "1",
6674 "declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly)",
6675 r" call void @__quantum__qis__mz__body(%Qubit* null, %Result* writeonly null)",
6676 );
6677
6678 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert inline QIR to bitcode");
6679 let err = validate_qir(&bc_bytes, None)
6680 .expect_err("result-backed measurements should fail validation without result slots");
6681 assert!(err.contains("Result index 0 exceeds required_num_results (0)"));
6682 }
6683
6684 #[test]
6685 fn test_validate_qir_rejects_zero_required_num_results_for_read_result() {
6686 let ll_text = minimal_qir_with_body(
6687 "1",
6688 "0",
6689 "1",
6690 "declare i1 @__quantum__rt__read_result(%Result*)",
6691 r" %0 = call i1 @__quantum__rt__read_result(%Result* null)",
6692 );
6693
6694 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert inline QIR to bitcode");
6695 let err = validate_qir(&bc_bytes, None)
6696 .expect_err("result reads should fail validation without result slots");
6697 assert!(err.contains("Result index 0 exceeds required_num_results (0)"));
6698 }
6699
6700 #[test]
6701 fn test_validate_qir_rejects_zero_required_num_results_for_result_record_output() {
6702 let ll_text = minimal_qir_with_body(
6703 "1",
6704 "0",
6705 "1",
6706 r#"
6707declare void @__quantum__rt__result_record_output(%Result*, i8*)
6708
6709@0 = private constant [4 x i8] c"res\00"
6710"#,
6711 r" call void @__quantum__rt__result_record_output(%Result* null, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i64 0, i64 0))",
6712 );
6713
6714 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert inline QIR to bitcode");
6715 let err = validate_qir(&bc_bytes, None)
6716 .expect_err("result output should fail validation without result slots");
6717 assert!(err.contains("Result index 0 exceeds required_num_results (0)"));
6718 }
6719
6720 #[test]
6721 fn test_validate_qir_rejects_out_of_bounds_result_measurement_index() {
6722 let ll_text = minimal_qir_with_body(
6723 "1",
6724 "1",
6725 "1",
6726 "declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly)",
6727 r" call void @__quantum__qis__mz__body(%Qubit* null, %Result* writeonly inttoptr (i64 5 to %Result*))",
6728 );
6729
6730 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert inline QIR to bitcode");
6731 let err = validate_qir(&bc_bytes, None)
6732 .expect_err("out-of-bounds result indices should fail during validation");
6733 assert!(err.contains("Result index 5 exceeds required_num_results (1)"));
6734 }
6735
6736 #[test]
6737 fn test_qir_to_qis_rejects_malformed_mz_leaked_call() {
6738 let ll_text = minimal_qir_with_body(
6739 "1",
6740 "0",
6741 "1",
6742 "declare i64 @__quantum__qis__mz_leaked__body()",
6743 r" %0 = call i64 @__quantum__qis__mz_leaked__body()",
6744 );
6745
6746 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert inline QIR to bitcode");
6747 let err = qir_to_qis(&bc_bytes, 0, "native", None)
6748 .expect_err("malformed mz_leaked calls should fail cleanly");
6749 assert!(err.contains("Malformed mz_leaked call"));
6750 }
6751
6752 #[test]
6753 fn test_qir_to_qis_rejects_mz_leaked_with_wrong_return_type() {
6754 let ll_text = minimal_qir_with_body(
6755 "1",
6756 "0",
6757 "1",
6758 "declare void @__quantum__qis__mz_leaked__body(%Qubit*)",
6759 r" call void @__quantum__qis__mz_leaked__body(%Qubit* null)",
6760 );
6761
6762 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert inline QIR to bitcode");
6763 let err = qir_to_qis(&bc_bytes, 0, "native", None)
6764 .expect_err("mz_leaked with the wrong signature should fail cleanly");
6765 assert!(err.contains("Malformed mz_leaked call: expected signature i64 (ptr)"));
6766 }
6767
6768 #[test]
6769 fn test_qir_to_qis_rejects_mz_leaked_with_wrong_return_width() {
6770 let ll_text = minimal_qir_with_body(
6771 "1",
6772 "0",
6773 "1",
6774 "declare i1 @__quantum__qis__mz_leaked__body(%Qubit*)",
6775 r" %0 = call i1 @__quantum__qis__mz_leaked__body(%Qubit* null)",
6776 );
6777
6778 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert inline QIR to bitcode");
6779 let err = qir_to_qis(&bc_bytes, 0, "native", None)
6780 .expect_err("mz_leaked with the wrong return width should fail cleanly");
6781 assert_eq!(
6782 err,
6783 "Malformed mz_leaked call: expected signature i64 (ptr)"
6784 );
6785 }
6786
6787 #[test]
6788 fn test_qir_to_qis_rejects_mz_leaked_with_non_pointer_parameter() {
6789 let ll_text = minimal_qir_with_body(
6790 "1",
6791 "0",
6792 "1",
6793 "declare i64 @__quantum__qis__mz_leaked__body(i64)",
6794 r" %0 = call i64 @__quantum__qis__mz_leaked__body(i64 0)",
6795 );
6796
6797 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert inline QIR to bitcode");
6798 let err = qir_to_qis(&bc_bytes, 0, "native", None)
6799 .expect_err("mz_leaked with a non-pointer parameter should fail cleanly");
6800 assert_eq!(
6801 err,
6802 "Malformed mz_leaked call: expected signature i64 (ptr)"
6803 );
6804 }
6805
6806 #[test]
6807 fn test_qir_to_qis_rejects_nonzero_single_qubit_mz_leaked_handle() {
6808 let ll_text = minimal_qir_with_body(
6809 "1",
6810 "0",
6811 "1",
6812 "declare i64 @__quantum__qis__mz_leaked__body(%Qubit*)",
6813 r" %q0 = inttoptr i64 1 to %Qubit*
6814 %0 = call i64 @__quantum__qis__mz_leaked__body(%Qubit* %q0)",
6815 );
6816
6817 let bc_bytes = qir_ll_to_bc(&ll_text).expect("Failed to convert inline QIR to bitcode");
6818 let err = qir_to_qis(&bc_bytes, 0, "native", None).expect_err(
6819 "static mz_leaked qubit id 1 should be rejected in a single-qubit zero-based program",
6820 );
6821 assert!(
6822 err.contains("Invalid static qubit handle passed to `__quantum__qis__mz_leaked__body`")
6823 );
6824 assert!(err.contains("Qubit index 1 exceeds required_num_qubits (1)"));
6825 }
6826
6827 #[test]
6828 fn test_qir_to_qis_rejects_out_of_range_static_handle_passed_to_ir_defined_helper() {
6829 let ll_text = r#"
6830%Qubit = type opaque
6831
6832define internal void @helper(%Qubit* %qubit) {
6833entry:
6834 call void @__quantum__qis__h__body(%Qubit* %qubit)
6835 ret void
6836}
6837
6838define i64 @Entry_Point_Name() #0 {
6839entry:
6840 %q1 = inttoptr i64 1 to %Qubit*
6841 call void @helper(%Qubit* %q1)
6842 ret i64 0
6843}
6844
6845declare void @__quantum__qis__h__body(%Qubit*)
6846
6847attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
6848
6849!llvm.module.flags = !{!0, !1, !2, !3}
6850!0 = !{i32 1, !"qir_major_version", i32 1}
6851!1 = !{i32 7, !"qir_minor_version", i32 0}
6852!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
6853!3 = !{i32 1, !"dynamic_result_management", i1 false}
6854"#;
6855
6856 let bc_bytes = qir_ll_to_bc(ll_text).expect("Failed to convert inline QIR to bitcode");
6857 let err = qir_to_qis(&bc_bytes, 0, "native", None).expect_err(
6858 "helper calls with out-of-range static handles should fail before runtime helper generation",
6859 );
6860 assert!(err.contains("Invalid static qubit handle passed to `helper`"));
6861 assert!(err.contains("Qubit index 1 exceeds required_num_qubits (1)"));
6862 }
6863
6864 #[test]
6865 fn test_mz_leaked_operand_check_rejects_non_pointer_first_operand() {
6866 let ctx = Context::create();
6867 let value = ctx.i64_type().const_zero().into();
6868 let err = crate::aux::mz_leaked_qubit_operand(&[value, value])
6869 .expect_err("non-pointer mz_leaked operands should fail cleanly");
6870 assert_eq!(
6871 err,
6872 "Malformed mz_leaked call: expected first argument to be a pointer"
6873 );
6874 }
6875
6876 #[cfg(not(windows))]
6877 #[test]
6878 fn test_qir_to_qis_mz_leaked_lowers_via_uint_future_runtime() {
6879 let bc_bytes = load_fixture_bitcode("tests/data/mz_leaked.ll");
6880 let output_bc = qir_to_qis(&bc_bytes, 0, "native", None)
6881 .expect("mz_leaked fixture should compile successfully");
6882
6883 verify_bitcode_module(&output_bc, "mz_leaked_qis")
6884 .expect("translated leaked-measure module should remain LLVM-verifiable");
6885
6886 let ctx = Context::create();
6887 let module = parse_bitcode_module(&ctx, &output_bc, "mz_leaked_qis")
6888 .expect("Compiled QIS bitcode should parse");
6889 let text = module.to_string();
6890 assert!(text.contains("___lazy_measure_leaked"));
6891 assert!(text.contains("___read_future_uint"));
6892 assert!(text.contains("___dec_future_refcount"));
6893 assert!(!text.contains("___read_future_bool"));
6894 }
6895
6896 #[cfg(windows)]
6897 #[test]
6898 fn test_qir_to_qis_mz_leaked_windows_smoke() {
6899 let bc_bytes = load_fixture_bitcode("tests/data/mz_leaked.ll");
6900 let output_bc = qir_to_qis(&bc_bytes, 0, "native", None)
6901 .expect("mz_leaked fixture should compile successfully on Windows");
6902
6903 let ctx = Context::create();
6904 let module = parse_bitcode_module(&ctx, &output_bc, "mz_leaked_qis")
6905 .expect("Compiled QIS bitcode should parse on Windows");
6906 assert!(module.get_function("qmain").is_some());
6907 }
6908
6909 #[cfg(feature = "wasm")]
6910 proptest! {
6911 #[test]
6912 fn prop_get_wasm_functions_round_trips_exact_exports(
6913 exports in proptest::collection::btree_map("[A-Za-z_][A-Za-z0-9_]{0,8}", 0u32..32u32, 0..8)
6914 ) {
6915 let exports_vec = exports
6916 .iter()
6917 .map(|(name, index)| (name.clone(), *index))
6918 .collect::<Vec<_>>();
6919 let wasm = build_wasm_exports(&exports_vec);
6920 let parsed = crate::get_wasm_functions(Some(&wasm))
6921 .map_err(|err| TestCaseError::fail(format!("get_wasm_functions failed unexpectedly: {err}")))?;
6922
6923 prop_assert_eq!(parsed.len(), exports.len());
6924 for (name, index) in exports {
6925 prop_assert_eq!(parsed.get(&name), Some(&u64::from(index)));
6926 }
6927 }
6928 }
6929
6930 proptest! {
6931 #[cfg(not(windows))]
6932 #[test]
6933 fn prop_qir_ll_to_bc_rejects_malformed_ir(suffix in "\\PC{0,128}") {
6934 let ll_text = format!("this is not valid llvm ir\n{suffix}");
6935 prop_assert!(qir_ll_to_bc(&ll_text).is_err());
6936 }
6937
6938 #[cfg(not(windows))]
6939 #[test]
6940 fn prop_validate_qir_rejects_malformed_bitcode(tail in proptest::collection::vec(any::<u8>(), 0..256)) {
6941 let mut bytes = b"NOTQIR".to_vec();
6942 bytes.extend(tail);
6943 prop_assert!(validate_qir(&bytes, None).is_err());
6944 }
6945
6946 #[cfg(not(windows))]
6947 #[test]
6948 fn prop_qir_to_qis_rejects_malformed_bitcode(tail in proptest::collection::vec(any::<u8>(), 0..256)) {
6949 let mut bytes = b"NOTQIR".to_vec();
6950 bytes.extend(tail);
6951 let (opt_level, target) = conservative_translation_settings();
6952 prop_assert!(qir_to_qis(&bytes, opt_level, target, None).is_err());
6953 }
6954
6955 #[test]
6956 fn prop_valid_fixtures_translate_to_verifiable_qis(fixture in proptest::sample::select(PROPERTY_FIXTURES)) {
6957 let input_bc = load_fixture_bitcode(fixture);
6958 prop_assert!(validate_qir(&input_bc, None).is_ok());
6959
6960 let (opt_level, target) = conservative_translation_settings();
6961 let output_bc = qir_to_qis(&input_bc, opt_level, target, None)
6962 .map_err(|err| TestCaseError::fail(format!("translation failed for {fixture}: {err}")))?;
6963
6964 verify_bitcode_module(&output_bc, "property_qis_module")
6965 .map_err(|err| TestCaseError::fail(format!("verification failed for {fixture}: {err}")))?;
6966 }
6967
6968 #[test]
6969 fn prop_invalid_targets_fail_fast(target in "[a-z0-9_-]{1,12}") {
6970 prop_assume!(target != "native");
6971 prop_assume!(target != "aarch64");
6972 prop_assume!(target != "x86-64");
6973
6974 let input_bc = load_fixture_bitcode("tests/data/base.ll");
6975 prop_assert!(qir_to_qis(&input_bc, 0, &target, None).is_err());
6976 }
6977
6978 #[test]
6979 fn prop_missing_required_attrs_fail_validation(
6980 missing_idx in 0usize..4usize
6981 ) {
6982 let missing_attr = *[
6983 "qir_profiles",
6984 "output_labeling_schema",
6985 "required_num_qubits",
6986 "required_num_results",
6987 ]
6988 .get(missing_idx)
6989 .expect("missing_idx is generated within bounds");
6990 let ll_text = minimal_qir_missing_attr(missing_attr);
6991 let bc = qir_ll_to_bc(&ll_text)
6992 .map_err(|err| TestCaseError::fail(format!("inline IR should parse: {err}")))?;
6993 let err = validate_qir(&bc, None)
6994 .expect_err("validation should reject missing required attributes");
6995 let expected = format!("Missing required attribute: `{missing_attr}`");
6996 prop_assert!(err.contains(&expected));
6997 }
6998
6999 #[test]
7000 fn prop_qir_major_versions_accept_only_one_or_two(major in 0u32..5u32) {
7001 let ll_text = minimal_qir_with_body("1", "1", &major.to_string(), "", "");
7002 let bc = qir_ll_to_bc(&ll_text)
7003 .map_err(|err| TestCaseError::fail(format!("inline IR should parse: {err}")))?;
7004 let result = validate_qir(&bc, None);
7005 if matches!(major, 1 | 2) {
7006 prop_assert!(result.is_ok());
7007 } else {
7008 let err = result.expect_err("invalid major versions must fail");
7009 prop_assert!(err.contains("Unsupported qir_major_version"));
7010 }
7011 }
7012
7013 #[test]
7014 fn prop_duplicate_qir_major_flags_pass_if_any_match(
7015 valid_first in any::<bool>(),
7016 valid_second in any::<bool>(),
7017 ) {
7018 let first_major = if valid_first { "1" } else { "99" };
7019 let second_major = if valid_second { "2" } else { "100" };
7020 let ll_text = minimal_qir_with_duplicate_major_flags(first_major, second_major);
7021 let bc = qir_ll_to_bc(&ll_text)
7022 .map_err(|err| TestCaseError::fail(format!("inline IR should parse: {err}")))?;
7023 let result = validate_qir(&bc, None);
7024 if valid_first || valid_second {
7025 prop_assert!(result.is_ok());
7026 } else {
7027 let err = result.expect_err("all-invalid duplicate major flags must fail");
7028 prop_assert!(err.contains("Unsupported qir_major_version"));
7029 }
7030 }
7031
7032 #[test]
7033 fn prop_duplicate_dynamic_qubit_flags_accept_true_and_false_values(
7034 first_is_true in any::<bool>(),
7035 second_is_true in any::<bool>(),
7036 ) {
7037 let first_flag = if first_is_true { "true" } else { "false" };
7038 let second_flag = if second_is_true { "true" } else { "false" };
7039 let ll_text = minimal_qir_with_duplicate_dynamic_flags(first_flag, second_flag);
7040 let bc = qir_ll_to_bc(&ll_text)
7041 .map_err(|err| TestCaseError::fail(format!("inline IR should parse: {err}")))?;
7042 prop_assert!(validate_qir(&bc, None).is_ok());
7043 }
7044
7045 #[test]
7046 fn prop_barrier_validation_tracks_required_qubits(
7047 required_num_qubits in 1u32..5u32,
7048 barrier_arity in 1u32..5u32,
7049 ) {
7050 let barrier_name = format!("__quantum__qis__barrier{barrier_arity}__body");
7051 let barrier_args = (0..barrier_arity)
7052 .map(|idx| format!("%Qubit* %q{idx}"))
7053 .collect::<Vec<_>>()
7054 .join(", ");
7055 let extra_decl = format!(
7056 "declare void @{barrier_name}({})",
7057 std::iter::repeat_n("%Qubit*", usize::try_from(barrier_arity).unwrap_or(0))
7058 .collect::<Vec<_>>()
7059 .join(", ")
7060 );
7061 let body = (0..barrier_arity)
7062 .map(|idx| format!(" %q{idx} = inttoptr i64 {idx} to %Qubit*"))
7063 .chain(std::iter::once(format!(" call void @{barrier_name}({barrier_args})")))
7064 .collect::<Vec<_>>()
7065 .join("\n");
7066 let ll_text = minimal_qir_with_body(
7067 &required_num_qubits.to_string(),
7068 "1",
7069 "1",
7070 &extra_decl,
7071 &body,
7072 );
7073 let bc = qir_ll_to_bc(&ll_text)
7074 .map_err(|err| TestCaseError::fail(format!("inline IR should parse: {err}")))?;
7075 let result = validate_qir(&bc, None);
7076 if barrier_arity <= required_num_qubits {
7077 prop_assert!(result.is_ok());
7078 } else {
7079 let err = result.expect_err("oversized barrier arity must fail");
7080 prop_assert!(err.contains("Barrier arity"));
7081 }
7082 }
7083
7084 #[cfg(not(windows))]
7085 #[test]
7086 fn prop_get_entry_attributes_rejects_malformed_bitcode(
7087 tail in proptest::collection::vec(any::<u8>(), 0..256)
7088 ) {
7089 let mut bytes = b"NOTQIR".to_vec();
7090 bytes.extend(tail);
7091 prop_assert!(get_entry_attributes(&bytes).is_err());
7092 }
7093 }
7094
7095 #[test]
7096 fn test_zero_qubits_fail_validation() {
7097 let ll_text = minimal_qir_with_body("0", "1", "1", "", "");
7098 let bc = qir_ll_to_bc(&ll_text).expect("inline IR should parse");
7099 let err = validate_qir(&bc, None).expect_err("validation should reject zero qubits");
7100 assert!(err.contains("Entry function must have at least one qubit"));
7101 }
7102
7103 #[rstest]
7104 #[case("65535", "1")]
7105 #[case("1", "65535")]
7106 fn test_validate_static_resource_limit_accepts_maximum(
7107 #[case] required_num_qubits: &str,
7108 #[case] required_num_results: &str,
7109 ) {
7110 let ll_text = minimal_qir_with_body(required_num_qubits, required_num_results, "1", "", "");
7111 let bc = qir_ll_to_bc(&ll_text).expect("inline IR should parse");
7112 validate_qir(&bc, None).expect("maximum static resource count should validate");
7113 qir_to_qis(&bc, 0, "native", None).expect("maximum static resource count should translate");
7114 }
7115
7116 #[rstest]
7117 #[case("65536", "1", "required_num_qubits")]
7118 #[case("1", "65536", "required_num_results")]
7119 fn test_static_resource_limit_rejects_first_oversized_value(
7120 #[case] required_num_qubits: &str,
7121 #[case] required_num_results: &str,
7122 #[case] resource_name: &str,
7123 ) {
7124 let ll_text = minimal_qir_with_body(required_num_qubits, required_num_results, "1", "", "");
7125 let bc = qir_ll_to_bc(&ll_text).expect("inline IR should parse");
7126 let expected_error = format!("{resource_name} value 65536 exceeds compiler limit 65535");
7127
7128 let validation_error =
7129 validate_qir(&bc, None).expect_err("oversized static resources should not validate");
7130 assert!(validation_error.contains(&expected_error));
7131
7132 let translation_error = qir_to_qis(&bc, 0, "native", None)
7133 .expect_err("oversized static resources should not be allocated during translation");
7134 assert!(translation_error.contains(&expected_error));
7135 }
7136
7137 #[rstest]
7138 #[case("65536", "1", "required_num_qubits")]
7139 #[case("1", "65536", "required_num_results")]
7140 fn test_dynamic_management_rejects_oversized_optional_static_resource_count(
7141 #[case] required_num_qubits: &str,
7142 #[case] required_num_results: &str,
7143 #[case] resource_name: &str,
7144 ) {
7145 let ll_text = minimal_qir_with_body(required_num_qubits, required_num_results, "2", "", "")
7146 .replace(
7147 "dynamic_qubit_management\", i1 false",
7148 "dynamic_qubit_management\", i1 true",
7149 )
7150 .replace(
7151 "dynamic_result_management\", i1 false",
7152 "dynamic_result_management\", i1 true",
7153 );
7154 let bc = qir_ll_to_bc(&ll_text).expect("inline adaptive IR should parse");
7155 let expected_error = format!("{resource_name} value 65536 exceeds compiler limit 65535");
7156
7157 let validation_error = validate_qir(&bc, None)
7158 .expect_err("oversized optional static resources should not validate");
7159 assert!(validation_error.contains(&expected_error));
7160
7161 let translation_error = qir_to_qis(&bc, 0, "native", None)
7162 .expect_err("oversized optional static resources should not translate");
7163 assert!(translation_error.contains(&expected_error));
7164 }
7165
7166 #[test]
7167 fn test_validate_dynamic_qubits_without_required_num_qubits() {
7168 let ll_text = r#"
7169define i64 @Entry_Point_Name() #0 {
7170entry:
7171 %err = alloca i1, align 1
7172 %q = call ptr @__quantum__rt__qubit_allocate(ptr %err)
7173 call void @__quantum__qis__h__body(ptr %q)
7174 call void @__quantum__rt__qubit_release(ptr %q)
7175 ret i64 0
7176}
7177
7178declare ptr @__quantum__rt__qubit_allocate(ptr)
7179declare void @__quantum__rt__qubit_release(ptr)
7180declare void @__quantum__qis__h__body(ptr)
7181
7182attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_results"="1" }
7183
7184!llvm.module.flags = !{!0, !1, !2, !3, !4}
7185!0 = !{i32 1, !"qir_major_version", i32 2}
7186!1 = !{i32 7, !"qir_minor_version", i32 0}
7187!2 = !{i32 1, !"dynamic_qubit_management", i1 true}
7188!3 = !{i32 1, !"dynamic_result_management", i1 false}
7189!4 = !{i32 1, !"arrays", i1 false}
7190"#;
7191 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7192 validate_qir(&bc_bytes, None).expect("dynamic qubit fixture should validate");
7193 let qis_bytes =
7194 qir_to_qis(&bc_bytes, 0, "native", None).expect("dynamic qubit fixture should compile");
7195 let ctx = Context::create();
7196 let module = parse_bitcode_module(&ctx, &qis_bytes, "qis_module").unwrap();
7197 assert!(module.get_function("qir_qis.qubit_allocate").is_some());
7198 assert!(module.get_function("qir_qis.qubit_release").is_some());
7199 }
7200
7201 #[test]
7202 fn test_validate_capability_usage_ignores_unused_rt_declarations() {
7203 let ll_text = r#"
7204define i64 @Entry_Point_Name() #0 {
7205entry:
7206 ret i64 0
7207}
7208
7209declare ptr @__quantum__rt__result_allocate(ptr)
7210declare void @__quantum__rt__result_release(ptr)
7211
7212attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
7213
7214!llvm.module.flags = !{!0, !1, !2, !3, !4}
7215!0 = !{i32 1, !"qir_major_version", i32 2}
7216!1 = !{i32 7, !"qir_minor_version", i32 0}
7217!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7218!3 = !{i32 1, !"dynamic_result_management", i1 false}
7219!4 = !{i32 1, !"arrays", i1 false}
7220"#;
7221 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7222 validate_qir(&bc_bytes, None)
7223 .expect("unused dynamic result declarations should not fail validation");
7224 }
7225
7226 #[test]
7227 fn test_validate_capability_usage_reports_called_rt_function_without_flag() {
7228 let ll_text = r#"
7229define i64 @Entry_Point_Name() #0 {
7230entry:
7231 %err = alloca i1, align 1
7232 %r = call ptr @__quantum__rt__result_allocate(ptr %err)
7233 call void @__quantum__rt__result_release(ptr %r)
7234 ret i64 0
7235}
7236
7237declare ptr @__quantum__rt__result_allocate(ptr)
7238declare void @__quantum__rt__result_release(ptr)
7239
7240attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
7241
7242!llvm.module.flags = !{!0, !1, !2, !3, !4}
7243!0 = !{i32 1, !"qir_major_version", i32 2}
7244!1 = !{i32 7, !"qir_minor_version", i32 0}
7245!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7246!3 = !{i32 1, !"dynamic_result_management", i1 false}
7247!4 = !{i32 1, !"arrays", i1 false}
7248"#;
7249 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7250 let err = validate_qir(&bc_bytes, None)
7251 .expect_err("called dynamic result functions should fail validation");
7252 assert!(
7253 err.contains(
7254 "__quantum__rt__result_allocate requires `dynamic_result_management=true`"
7255 )
7256 );
7257 }
7258
7259 #[test]
7260 fn test_validate_capability_usage_reports_called_dynamic_qubit_rt_function_without_flag() {
7261 let ll_text = r#"
7262define i64 @Entry_Point_Name() #0 {
7263entry:
7264 %err = alloca i1, align 1
7265 %q = call ptr @__quantum__rt__qubit_allocate(ptr %err)
7266 call void @__quantum__rt__qubit_release(ptr %q)
7267 ret i64 0
7268}
7269
7270declare ptr @__quantum__rt__qubit_allocate(ptr)
7271declare void @__quantum__rt__qubit_release(ptr)
7272
7273attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_results"="1" }
7274
7275!llvm.module.flags = !{!0, !1, !2, !3, !4}
7276!0 = !{i32 1, !"qir_major_version", i32 2}
7277!1 = !{i32 7, !"qir_minor_version", i32 0}
7278!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7279!3 = !{i32 1, !"dynamic_result_management", i1 false}
7280!4 = !{i32 1, !"arrays", i1 false}
7281"#;
7282
7283 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7284 let err = validate_qir(&bc_bytes, None)
7285 .expect_err("called dynamic qubit functions should fail validation");
7286 assert!(
7287 err.contains("__quantum__rt__qubit_allocate requires `dynamic_qubit_management=true`")
7288 );
7289 }
7290
7291 #[test]
7292 fn test_validate_qir_rejects_malformed_dynamic_qubit_allocate_signature() {
7293 let ll_text = r#"
7294define i64 @Entry_Point_Name() #0 {
7295entry:
7296 %q = call ptr @__quantum__rt__qubit_allocate()
7297 call void @__quantum__rt__qubit_release(ptr %q)
7298 ret i64 0
7299}
7300
7301declare ptr @__quantum__rt__qubit_allocate()
7302declare void @__quantum__rt__qubit_release(ptr)
7303
7304attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_results"="1" }
7305
7306!llvm.module.flags = !{!0, !1, !2, !3, !4}
7307!0 = !{i32 1, !"qir_major_version", i32 2}
7308!1 = !{i32 7, !"qir_minor_version", i32 0}
7309!2 = !{i32 1, !"dynamic_qubit_management", i1 true}
7310!3 = !{i32 1, !"dynamic_result_management", i1 false}
7311!4 = !{i32 1, !"arrays", i1 false}
7312"#;
7313
7314 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7315 let err = validate_qir(&bc_bytes, None)
7316 .expect_err("malformed dynamic runtime declaration should fail validation");
7317 assert!(
7318 err.contains("Malformed QIR RT function declaration: __quantum__rt__qubit_allocate")
7319 );
7320 }
7321
7322 #[test]
7323 fn test_validate_qir_rejects_malformed_dynamic_allocate_return_signature() {
7324 let cases = [
7325 (
7326 "declare void @__quantum__rt__qubit_allocate(ptr)",
7327 "__quantum__rt__qubit_allocate",
7328 ),
7329 (
7330 "declare void @__quantum__rt__result_allocate(ptr)",
7331 "__quantum__rt__result_allocate",
7332 ),
7333 ];
7334
7335 for (declaration, fn_name) in cases {
7336 assert_malformed_dynamic_rt_declaration(fn_name, declaration, fn_name);
7337 }
7338 }
7339
7340 #[test]
7341 fn test_validate_qir_rejects_malformed_dynamic_release_signatures() {
7342 let cases = [
7343 (
7344 "wrong return",
7345 "declare ptr @__quantum__rt__qubit_release(ptr)",
7346 "__quantum__rt__qubit_release",
7347 ),
7348 (
7349 "wrong arity",
7350 "declare void @__quantum__rt__qubit_release()",
7351 "__quantum__rt__qubit_release",
7352 ),
7353 (
7354 "wrong param type",
7355 "declare void @__quantum__rt__result_release(i64)",
7356 "__quantum__rt__result_release",
7357 ),
7358 ];
7359
7360 for (case_name, declaration, fn_name) in cases {
7361 assert_malformed_dynamic_rt_declaration(case_name, declaration, fn_name);
7362 }
7363 }
7364
7365 #[test]
7366 fn test_validate_qir_rejects_malformed_dynamic_array_three_arg_signatures() {
7367 let cases = [
7368 (
7369 "wrong return",
7370 "declare ptr @__quantum__rt__qubit_array_allocate(i64, ptr, ptr)",
7371 "__quantum__rt__qubit_array_allocate",
7372 ),
7373 (
7374 "wrong arity",
7375 "declare void @__quantum__rt__qubit_array_allocate(i64, ptr)",
7376 "__quantum__rt__qubit_array_allocate",
7377 ),
7378 (
7379 "wrong length type",
7380 "declare void @__quantum__rt__result_array_allocate(i32, ptr, ptr)",
7381 "__quantum__rt__result_array_allocate",
7382 ),
7383 (
7384 "wrong backing param type",
7385 "declare void @__quantum__rt__result_array_allocate(i64, i64, ptr)",
7386 "__quantum__rt__result_array_allocate",
7387 ),
7388 (
7389 "wrong output param type",
7390 "declare void @__quantum__rt__result_array_record_output(i64, ptr, i64)",
7391 "__quantum__rt__result_array_record_output",
7392 ),
7393 ];
7394
7395 for (case_name, declaration, fn_name) in cases {
7396 assert_malformed_dynamic_rt_declaration(case_name, declaration, fn_name);
7397 }
7398 }
7399
7400 #[test]
7401 fn test_validate_qir_rejects_malformed_dynamic_array_release_signatures() {
7402 let cases = [
7403 (
7404 "wrong return",
7405 "declare ptr @__quantum__rt__qubit_array_release(i64, ptr)",
7406 "__quantum__rt__qubit_array_release",
7407 ),
7408 (
7409 "wrong arity",
7410 "declare void @__quantum__rt__qubit_array_release(i64)",
7411 "__quantum__rt__qubit_array_release",
7412 ),
7413 (
7414 "wrong length type",
7415 "declare void @__quantum__rt__result_array_release(i32, ptr)",
7416 "__quantum__rt__result_array_release",
7417 ),
7418 (
7419 "wrong backing param type",
7420 "declare void @__quantum__rt__result_array_release(i64, i64)",
7421 "__quantum__rt__result_array_release",
7422 ),
7423 ];
7424
7425 for (case_name, declaration, fn_name) in cases {
7426 assert_malformed_dynamic_rt_declaration(case_name, declaration, fn_name);
7427 }
7428 }
7429
7430 #[test]
7431 fn test_validate_qir_rejects_unsupported_rt_function_declaration() {
7432 let ll_text = r#"
7433declare void @__quantum__rt__mystery()
7434
7435define i64 @Entry_Point_Name() #0 {
7436entry:
7437 ret i64 0
7438}
7439
7440attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
7441
7442!llvm.module.flags = !{!0, !1, !2, !3, !4}
7443!0 = !{i32 1, !"qir_major_version", i32 2}
7444!1 = !{i32 7, !"qir_minor_version", i32 0}
7445!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7446!3 = !{i32 1, !"dynamic_result_management", i1 false}
7447!4 = !{i32 1, !"arrays", i1 false}
7448"#;
7449
7450 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7451 let err =
7452 validate_qir(&bc_bytes, None).expect_err("unsupported RT declarations should fail");
7453 assert!(err.contains("Unsupported QIR RT function: __quantum__rt__mystery"));
7454 }
7455
7456 #[test]
7457 fn test_validate_dynamic_results_without_required_num_results() {
7458 let ll_text = r#"
7459@0 = internal constant [3 x i8] c"r0\00"
7460
7461define i64 @Entry_Point_Name() #0 {
7462entry:
7463 %r = call ptr @__quantum__rt__result_allocate(ptr null)
7464 call void @__quantum__qis__mz__body(ptr null, ptr %r)
7465 call void @__quantum__rt__result_record_output(ptr %r, ptr @0)
7466 call void @__quantum__rt__result_release(ptr %r)
7467 ret i64 0
7468}
7469
7470declare ptr @__quantum__rt__result_allocate(ptr)
7471declare void @__quantum__rt__result_release(ptr)
7472declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1
7473declare void @__quantum__rt__result_record_output(ptr, ptr)
7474
7475attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" }
7476attributes #1 = { "irreversible" }
7477
7478!llvm.module.flags = !{!0, !1, !2, !3, !4}
7479!0 = !{i32 1, !"qir_major_version", i32 2}
7480!1 = !{i32 7, !"qir_minor_version", i32 0}
7481!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7482!3 = !{i32 1, !"dynamic_result_management", i1 true}
7483!4 = !{i32 1, !"arrays", i1 false}
7484"#;
7485 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7486 validate_qir(&bc_bytes, None).expect("dynamic result fixture should validate");
7487 let qis_bytes = qir_to_qis(&bc_bytes, 0, "native", None)
7488 .expect("dynamic result fixture should compile");
7489 let ctx = Context::create();
7490 let module = parse_bitcode_module(&ctx, &qis_bytes, "qis_module").unwrap();
7491 assert!(module.get_function("qir_qis.result_read").is_some());
7492 assert!(module.get_function("qir_qis.out_err_success").is_some());
7493 }
7494
7495 #[test]
7496 fn test_validate_dynamic_result_array_record_output() {
7497 let ll_text = r#"
7498@0 = internal constant [3 x i8] c"a0\00"
7499
7500define i64 @Entry_Point_Name() #0 {
7501entry:
7502 %results = alloca [2 x ptr], align 8
7503 call void @__quantum__rt__result_array_allocate(i64 2, ptr %results, ptr null)
7504 %r0_ptr = getelementptr inbounds [2 x ptr], ptr %results, i64 0, i64 0
7505 %r0 = load ptr, ptr %r0_ptr, align 8
7506 %r1_ptr = getelementptr inbounds [2 x ptr], ptr %results, i64 0, i64 1
7507 %r1 = load ptr, ptr %r1_ptr, align 8
7508 call void @__quantum__qis__mz__body(ptr null, ptr %r0)
7509 call void @__quantum__qis__mz__body(ptr inttoptr (i64 1 to ptr), ptr %r1)
7510 call void @__quantum__rt__result_array_record_output(i64 2, ptr %results, ptr @0)
7511 call void @__quantum__rt__result_array_release(i64 2, ptr %results)
7512 ret i64 0
7513}
7514
7515declare void @__quantum__rt__result_array_allocate(i64, ptr, ptr)
7516declare void @__quantum__rt__result_array_release(i64, ptr)
7517declare void @__quantum__rt__result_array_record_output(i64, ptr, ptr)
7518declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1
7519
7520attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="2" }
7521attributes #1 = { "irreversible" }
7522
7523!llvm.module.flags = !{!0, !1, !2, !3, !4}
7524!0 = !{i32 1, !"qir_major_version", i32 2}
7525!1 = !{i32 7, !"qir_minor_version", i32 0}
7526!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7527!3 = !{i32 1, !"dynamic_result_management", i1 true}
7528!4 = !{i32 1, !"arrays", i1 true}
7529"#;
7530 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7531 validate_qir(&bc_bytes, None).expect("dynamic result array fixture should validate");
7532 let qis_bytes = qir_to_qis(&bc_bytes, 0, "native", None)
7533 .expect("dynamic result array fixture should compile");
7534 let ctx = Context::create();
7535 let module = parse_bitcode_module(&ctx, &qis_bytes, "qis_module").unwrap();
7536 assert!(
7537 module
7538 .get_function("qir_qis.result_array_record_output")
7539 .is_some()
7540 );
7541 assert!(module.get_function("qir_qis.out_err_success").is_some());
7542
7543 #[cfg(not(windows))]
7544 {
7545 let module_text = module.to_string();
7546 assert!(
7547 module_text.contains("USER:RESULT_ARRAY:a0"),
7548 "expected RESULT_ARRAY output tag, got module:\n{module_text}"
7549 );
7550 let print_bool_arr_calls = module_text.matches("@print_bool_arr").count();
7551 assert!(
7552 print_bool_arr_calls >= 2,
7553 "expected print_bool_arr declaration and use, got module:\n{module_text}"
7554 );
7555 assert!(
7556 !module_text.contains("@print_bool("),
7557 "expected array output lowering without scalar print_bool fallback, got module:\n{module_text}"
7558 );
7559 }
7560
7561 #[cfg(windows)]
7562 {
7563 let labels = module
7564 .get_globals()
7565 .filter_map(|global| crate::convert::get_string_label(global).ok())
7566 .collect::<Vec<_>>();
7567 assert!(
7568 labels
7569 .iter()
7570 .any(|label| label.contains("USER:RESULT_ARRAY:a0")),
7571 "expected RESULT_ARRAY output label in globals, got labels: {labels:?}"
7572 );
7573 assert!(module.get_function("print_bool_arr").is_some());
7574 assert!(module.get_function("print_bool").is_none());
7575 }
7576 }
7577
7578 #[test]
7579 fn test_validate_dynamic_result_array_record_output_length_mismatch_fails() {
7580 let ll_text = r#"
7581@0 = internal constant [3 x i8] c"a0\00"
7582
7583define i64 @Entry_Point_Name() #0 {
7584entry:
7585 %results = alloca [1 x ptr], align 8
7586 call void @__quantum__rt__result_array_record_output(i64 2, ptr %results, ptr @0)
7587 ret i64 0
7588}
7589
7590declare void @__quantum__rt__result_array_record_output(i64, ptr, ptr)
7591
7592attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" }
7593
7594!llvm.module.flags = !{!0, !1, !2, !3, !4}
7595!0 = !{i32 1, !"qir_major_version", i32 2}
7596!1 = !{i32 7, !"qir_minor_version", i32 0}
7597!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7598!3 = !{i32 1, !"dynamic_result_management", i1 true}
7599!4 = !{i32 1, !"arrays", i1 true}
7600"#;
7601 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7602 let err = validate_qir(&bc_bytes, None)
7603 .expect_err("mismatched result array output backing should fail validation");
7604 assert!(err.contains(
7605 "__quantum__rt__result_array_record_output requires a fixed-size backing array"
7606 ));
7607 assert!(err.contains("requested length 2 does not match backing array length 1"));
7608 }
7609
7610 #[test]
7611 fn test_validate_dynamic_result_array_record_output_large_length_fails() {
7612 let ll_text = r#"
7613@0 = internal constant [3 x i8] c"a0\00"
7614
7615define i64 @Entry_Point_Name() #0 {
7616entry:
7617 %results = alloca [2147483648 x ptr], align 8
7618 call void @__quantum__rt__result_array_record_output(i64 2147483648, ptr %results, ptr @0)
7619 ret i64 0
7620}
7621
7622declare void @__quantum__rt__result_array_record_output(i64, ptr, ptr)
7623
7624attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" }
7625
7626!llvm.module.flags = !{!0, !1, !2, !3, !4}
7627!0 = !{i32 1, !"qir_major_version", i32 2}
7628!1 = !{i32 7, !"qir_minor_version", i32 0}
7629!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7630!3 = !{i32 1, !"dynamic_result_management", i1 true}
7631!4 = !{i32 1, !"arrays", i1 true}
7632"#;
7633 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7634 let err = validate_qir(&bc_bytes, None)
7635 .expect_err("oversized result array output length should fail validation");
7636 assert!(err.contains(
7637 "__quantum__rt__result_array_record_output requires an array length that fits in i32 for RESULT_ARRAY output"
7638 ));
7639 }
7640
7641 #[test]
7642 fn test_validate_dynamic_result_array_record_output_i32_max_length_succeeds() {
7643 let ll_text = r#"
7644@0 = internal constant [3 x i8] c"a0\00"
7645
7646define i64 @Entry_Point_Name() #0 {
7647entry:
7648 %results = alloca [2147483647 x ptr], align 8
7649 call void @__quantum__rt__result_array_record_output(i64 2147483647, ptr %results, ptr @0)
7650 ret i64 0
7651}
7652
7653declare void @__quantum__rt__result_array_record_output(i64, ptr, ptr)
7654
7655attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" }
7656
7657!llvm.module.flags = !{!0, !1, !2, !3, !4}
7658!0 = !{i32 1, !"qir_major_version", i32 2}
7659!1 = !{i32 7, !"qir_minor_version", i32 0}
7660!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7661!3 = !{i32 1, !"dynamic_result_management", i1 true}
7662!4 = !{i32 1, !"arrays", i1 true}
7663"#;
7664 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7665 validate_qir(&bc_bytes, None).expect("i32::MAX result array output should validate");
7666 }
7667
7668 #[test]
7669 fn test_validate_dynamic_result_allocate_outside_entry_block_fails() {
7670 let ll_text = r#"
7671define i64 @Entry_Point_Name() #0 {
7672entry:
7673 br label %body
7674
7675body:
7676 %r = call ptr @__quantum__rt__result_allocate(ptr null)
7677 call void @__quantum__rt__result_release(ptr %r)
7678 ret i64 0
7679}
7680
7681declare ptr @__quantum__rt__result_allocate(ptr)
7682declare void @__quantum__rt__result_release(ptr)
7683
7684attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" }
7685
7686!llvm.module.flags = !{!0, !1, !2, !3, !4}
7687!0 = !{i32 1, !"qir_major_version", i32 2}
7688!1 = !{i32 7, !"qir_minor_version", i32 0}
7689!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7690!3 = !{i32 1, !"dynamic_result_management", i1 true}
7691!4 = !{i32 1, !"arrays", i1 false}
7692"#;
7693 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7694 let err = validate_qir(&bc_bytes, None).expect_err("fixture should fail validation");
7695 assert!(
7696 err.contains("__quantum__rt__result_allocate is only supported in the entry block")
7697 );
7698 }
7699
7700 #[test]
7701 fn test_validate_dynamic_result_allocate_in_helper_fails() {
7702 let ll_text = r#"
7703define void @helper() {
7704entry:
7705 %r = call ptr @__quantum__rt__result_allocate(ptr null)
7706 call void @__quantum__rt__result_release(ptr %r)
7707 ret void
7708}
7709
7710define i64 @Entry_Point_Name() #0 {
7711entry:
7712 call void @helper()
7713 ret i64 0
7714}
7715
7716declare ptr @__quantum__rt__result_allocate(ptr)
7717declare void @__quantum__rt__result_release(ptr)
7718
7719attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" }
7720
7721!llvm.module.flags = !{!0, !1, !2, !3, !4}
7722!0 = !{i32 1, !"qir_major_version", i32 2}
7723!1 = !{i32 7, !"qir_minor_version", i32 0}
7724!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7725!3 = !{i32 1, !"dynamic_result_management", i1 true}
7726!4 = !{i32 1, !"arrays", i1 false}
7727"#;
7728 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7729 let err = validate_qir(&bc_bytes, None).expect_err("fixture should fail validation");
7730 assert!(
7731 err.contains("__quantum__rt__result_allocate is only supported in the entry block")
7732 );
7733 }
7734
7735 #[test]
7736 fn test_validate_input_defined_qir_qis_helper_fails() {
7737 let ll_text = r#"
7738define ptr @qir_qis.qubit_allocate(ptr %out_err) {
7739entry:
7740 ret ptr null
7741}
7742
7743define i64 @Entry_Point_Name() #0 {
7744entry:
7745 ret i64 0
7746}
7747
7748attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }
7749
7750!llvm.module.flags = !{!0, !1, !2, !3, !4}
7751!0 = !{i32 1, !"qir_major_version", i32 2}
7752!1 = !{i32 7, !"qir_minor_version", i32 0}
7753!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7754!3 = !{i32 1, !"dynamic_result_management", i1 false}
7755!4 = !{i32 1, !"arrays", i1 false}
7756"#;
7757 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7758 let err = validate_qir(&bc_bytes, None).expect_err("fixture should fail validation");
7759 assert!(err.contains("Input QIR must not define internal helper function"));
7760 assert!(err.contains("qir_qis.qubit_allocate"));
7761 }
7762
7763 #[test]
7764 fn test_validate_dynamic_qubit_array_allocate_length_mismatch_fails() {
7765 let ll_text = r#"
7766define i64 @Entry_Point_Name() #0 {
7767entry:
7768 %qubits = alloca [1 x ptr], align 8
7769 call void @__quantum__rt__qubit_array_allocate(i64 2, ptr %qubits, ptr null)
7770 ret i64 0
7771}
7772
7773declare void @__quantum__rt__qubit_array_allocate(i64, ptr, ptr)
7774
7775attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_results"="1" }
7776
7777!llvm.module.flags = !{!0, !1, !2, !3, !4}
7778!0 = !{i32 1, !"qir_major_version", i32 2}
7779!1 = !{i32 7, !"qir_minor_version", i32 0}
7780!2 = !{i32 1, !"dynamic_qubit_management", i1 true}
7781!3 = !{i32 1, !"dynamic_result_management", i1 false}
7782!4 = !{i32 1, !"arrays", i1 true}
7783"#;
7784 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7785 let err = validate_qir(&bc_bytes, None).expect_err("fixture should fail validation");
7786 assert!(
7787 err.contains("__quantum__rt__qubit_array_allocate requires a fixed-size backing array")
7788 );
7789 assert!(err.contains("requested length 2 does not match backing array length 1"));
7790 }
7791
7792 #[test]
7793 fn test_validate_dynamic_qubit_array_allocate_malformed_signature_fails_without_panic() {
7794 let ll_text = r#"
7795define i64 @Entry_Point_Name() #0 {
7796entry:
7797 call void @__quantum__rt__qubit_array_allocate(i64 2)
7798 ret i64 0
7799}
7800
7801declare void @__quantum__rt__qubit_array_allocate(i64)
7802
7803attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_results"="1" }
7804
7805!llvm.module.flags = !{!0, !1, !2, !3, !4}
7806!0 = !{i32 1, !"qir_major_version", i32 2}
7807!1 = !{i32 7, !"qir_minor_version", i32 0}
7808!2 = !{i32 1, !"dynamic_qubit_management", i1 true}
7809!3 = !{i32 1, !"dynamic_result_management", i1 false}
7810!4 = !{i32 1, !"arrays", i1 true}
7811"#;
7812 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7813 let err = validate_qir(&bc_bytes, None).expect_err("fixture should fail validation");
7814 assert!(err.contains(
7815 "Malformed QIR RT function declaration: __quantum__rt__qubit_array_allocate"
7816 ));
7817 assert!(err.contains(
7818 "__quantum__rt__qubit_array_allocate requires a constant array length and backing array pointer"
7819 ));
7820 }
7821
7822 #[test]
7823 fn test_validate_dynamic_result_array_allocate_pointer_length_fails_without_panic() {
7824 let ll_text = r#"
7825define i64 @Entry_Point_Name() #0 {
7826entry:
7827 %len = alloca ptr, align 8
7828 %results = alloca [2 x ptr], align 8
7829 call void @__quantum__rt__result_array_allocate(ptr %len, ptr %results, ptr null)
7830 ret i64 0
7831}
7832
7833declare void @__quantum__rt__result_array_allocate(ptr, ptr, ptr)
7834
7835attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" }
7836
7837!llvm.module.flags = !{!0, !1, !2, !3, !4}
7838!0 = !{i32 1, !"qir_major_version", i32 2}
7839!1 = !{i32 7, !"qir_minor_version", i32 0}
7840!2 = !{i32 1, !"dynamic_qubit_management", i1 true}
7841!3 = !{i32 1, !"dynamic_result_management", i1 true}
7842!4 = !{i32 1, !"arrays", i1 true}
7843"#;
7844 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7845 let err = validate_qir(&bc_bytes, None).expect_err("fixture should fail validation");
7846 assert!(err.contains(
7847 "__quantum__rt__result_array_allocate requires a constant array length and backing array pointer"
7848 ));
7849 }
7850
7851 #[test]
7852 fn test_validate_dynamic_result_array_allocate_length_mismatch_fails() {
7853 let ll_text = r#"
7854define i64 @Entry_Point_Name() #0 {
7855entry:
7856 %results = alloca [1 x ptr], align 8
7857 call void @__quantum__rt__result_array_allocate(i64 2, ptr %results, ptr null)
7858 ret i64 0
7859}
7860
7861declare void @__quantum__rt__result_array_allocate(i64, ptr, ptr)
7862
7863attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" }
7864
7865!llvm.module.flags = !{!0, !1, !2, !3, !4}
7866!0 = !{i32 1, !"qir_major_version", i32 2}
7867!1 = !{i32 7, !"qir_minor_version", i32 0}
7868!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7869!3 = !{i32 1, !"dynamic_result_management", i1 true}
7870!4 = !{i32 1, !"arrays", i1 true}
7871"#;
7872 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7873 let err = validate_qir(&bc_bytes, None).expect_err("fixture should fail validation");
7874 assert!(
7875 err.contains(
7876 "__quantum__rt__result_array_allocate requires a fixed-size backing array"
7877 )
7878 );
7879 assert!(err.contains("requested length 2 does not match backing array length 1"));
7880 }
7881
7882 #[test]
7883 fn test_validate_dynamic_qubit_array_release_length_mismatch_fails() {
7884 let ll_text = r#"
7885define i64 @Entry_Point_Name() #0 {
7886entry:
7887 %qubits = alloca [1 x ptr], align 8
7888 call void @__quantum__rt__qubit_array_release(i64 2, ptr %qubits)
7889 ret i64 0
7890}
7891
7892declare void @__quantum__rt__qubit_array_release(i64, ptr)
7893
7894attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_results"="1" }
7895
7896!llvm.module.flags = !{!0, !1, !2, !3, !4}
7897!0 = !{i32 1, !"qir_major_version", i32 2}
7898!1 = !{i32 7, !"qir_minor_version", i32 0}
7899!2 = !{i32 1, !"dynamic_qubit_management", i1 true}
7900!3 = !{i32 1, !"dynamic_result_management", i1 false}
7901!4 = !{i32 1, !"arrays", i1 true}
7902"#;
7903 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7904 let err = validate_qir(&bc_bytes, None).expect_err("fixture should fail validation");
7905 assert!(
7906 err.contains("__quantum__rt__qubit_array_release requires a fixed-size backing array")
7907 );
7908 assert!(err.contains("requested length 2 does not match backing array length 1"));
7909 }
7910
7911 #[test]
7912 fn test_validate_dynamic_result_array_release_length_mismatch_fails() {
7913 let ll_text = r#"
7914define i64 @Entry_Point_Name() #0 {
7915entry:
7916 %results = alloca [1 x ptr], align 8
7917 call void @__quantum__rt__result_array_release(i64 2, ptr %results)
7918 ret i64 0
7919}
7920
7921declare void @__quantum__rt__result_array_release(i64, ptr)
7922
7923attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" }
7924
7925!llvm.module.flags = !{!0, !1, !2, !3, !4}
7926!0 = !{i32 1, !"qir_major_version", i32 2}
7927!1 = !{i32 7, !"qir_minor_version", i32 0}
7928!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
7929!3 = !{i32 1, !"dynamic_result_management", i1 true}
7930!4 = !{i32 1, !"arrays", i1 true}
7931"#;
7932 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
7933 let err = validate_qir(&bc_bytes, None).expect_err("fixture should fail validation");
7934 assert!(
7935 err.contains("__quantum__rt__result_array_release requires a fixed-size backing array")
7936 );
7937 assert!(err.contains("requested length 2 does not match backing array length 1"));
7938 }
7939
7940 #[test]
7941 fn test_validate_dynamic_qubit_array_release_matching_backing_succeeds() {
7942 let ll_text = r"
7943define i64 @Entry_Point_Name() {
7944entry:
7945 %qubits = alloca [2 x ptr], align 8
7946 call void @__quantum__rt__qubit_array_release(i64 2, ptr %qubits)
7947 ret i64 0
7948}
7949
7950declare void @__quantum__rt__qubit_array_release(i64, ptr)
7951";
7952
7953 let errors = validate_dynamic_array_backing_errors(ll_text);
7954 assert!(
7955 errors.is_empty(),
7956 "matching two-argument array release should not report backing errors: {errors:?}"
7957 );
7958 }
7959
7960 #[test]
7961 fn test_validate_dynamic_qubit_array_release_missing_backing_fails_without_panic() {
7962 let ll_text = r"
7963define i64 @Entry_Point_Name() {
7964entry:
7965 call void @__quantum__rt__qubit_array_release(i64 2)
7966 ret i64 0
7967}
7968
7969declare void @__quantum__rt__qubit_array_release(i64)
7970";
7971
7972 let errors = validate_dynamic_array_backing_errors(ll_text);
7973 assert_eq!(
7974 errors,
7975 vec![
7976 "__quantum__rt__qubit_array_release requires a constant array length and backing array pointer"
7977 .to_string()
7978 ]
7979 );
7980 }
7981
7982 #[test]
7983 fn test_validate_dynamic_qubit_array_allocate_bitcast_backing_succeeds() {
7984 let ll_text = r#"
7985define i64 @Entry_Point_Name() #0 {
7986entry:
7987 %qubits = alloca [2 x ptr], align 8
7988 %backing = bitcast ptr %qubits to ptr
7989 call void @__quantum__rt__qubit_array_allocate(i64 2, ptr %backing, ptr null)
7990 ret i64 0
7991}
7992
7993declare void @__quantum__rt__qubit_array_allocate(i64, ptr, ptr)
7994
7995attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_results"="1" }
7996
7997!llvm.module.flags = !{!0, !1, !2, !3, !4}
7998!0 = !{i32 1, !"qir_major_version", i32 2}
7999!1 = !{i32 7, !"qir_minor_version", i32 0}
8000!2 = !{i32 1, !"dynamic_qubit_management", i1 true}
8001!3 = !{i32 1, !"dynamic_result_management", i1 false}
8002!4 = !{i32 1, !"arrays", i1 true}
8003"#;
8004 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
8005 validate_qir(&bc_bytes, None).expect("bitcast-backed qubit array should validate");
8006 }
8007
8008 #[test]
8009 fn test_validate_dynamic_qubit_array_allocate_zero_gep_backing_succeeds() {
8010 let ll_text = r#"
8011define i64 @Entry_Point_Name() #0 {
8012entry:
8013 %qubits = alloca [2 x ptr], align 8
8014 %backing = getelementptr inbounds [2 x ptr], ptr %qubits, i64 0, i64 0
8015 call void @__quantum__rt__qubit_array_allocate(i64 2, ptr %backing, ptr null)
8016 ret i64 0
8017}
8018
8019declare void @__quantum__rt__qubit_array_allocate(i64, ptr, ptr)
8020
8021attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_results"="1" }
8022
8023!llvm.module.flags = !{!0, !1, !2, !3, !4}
8024!0 = !{i32 1, !"qir_major_version", i32 2}
8025!1 = !{i32 7, !"qir_minor_version", i32 0}
8026!2 = !{i32 1, !"dynamic_qubit_management", i1 true}
8027!3 = !{i32 1, !"dynamic_result_management", i1 false}
8028!4 = !{i32 1, !"arrays", i1 true}
8029"#;
8030 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
8031 validate_qir(&bc_bytes, None).expect("zero-index GEP backing should validate");
8032 }
8033
8034 #[test]
8035 fn test_validate_dynamic_qubit_array_allocate_nonzero_gep_backing_fails() {
8036 let ll_text = r#"
8037define i64 @Entry_Point_Name() #0 {
8038entry:
8039 %qubits = alloca [2 x ptr], align 8
8040 %backing = getelementptr inbounds [2 x ptr], ptr %qubits, i64 0, i64 1
8041 call void @__quantum__rt__qubit_array_allocate(i64 2, ptr %backing, ptr null)
8042 ret i64 0
8043}
8044
8045declare void @__quantum__rt__qubit_array_allocate(i64, ptr, ptr)
8046
8047attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_results"="1" }
8048
8049!llvm.module.flags = !{!0, !1, !2, !3, !4}
8050!0 = !{i32 1, !"qir_major_version", i32 2}
8051!1 = !{i32 7, !"qir_minor_version", i32 0}
8052!2 = !{i32 1, !"dynamic_qubit_management", i1 true}
8053!3 = !{i32 1, !"dynamic_result_management", i1 false}
8054!4 = !{i32 1, !"arrays", i1 true}
8055"#;
8056 let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
8057 let err = validate_qir(&bc_bytes, None).expect_err(
8058 "non-zero GEP-backed pointer should not count as a fixed-size array backing",
8059 );
8060 assert!(err.contains(
8061 "__quantum__rt__qubit_array_allocate requires a fixed-size backing array allocated as [N x ptr]"
8062 ));
8063 }
8064
8065 #[test]
8066 fn test_dynamic_qubit_array_allocate_rolls_back_on_failure() {
8067 let ll_text = std::fs::read_to_string("tests/data/dynamic_qubit_array_checked.ll")
8068 .expect("Failed to read dynamic_qubit_array_checked.ll");
8069 let input_bc =
8070 qir_ll_to_bc(&ll_text).expect("Failed to convert dynamic_qubit_array_checked.ll");
8071 let output_bc = qir_to_qis(&input_bc, 0, "native", None)
8072 .expect("dynamic qubit array checked fixture should compile");
8073
8074 let ctx = Context::create();
8075 let module = parse_bitcode_module(&ctx, &output_bc, "qis_module")
8076 .expect("Compiled QIS bitcode should parse");
8077 let helper = module
8078 .get_function("qir_qis.qubit_array_allocate")
8079 .expect("qubit array allocate helper should exist");
8080 let called_functions = collect_called_function_names(helper);
8081 assert!(
8082 called_functions
8083 .iter()
8084 .any(|name| name == "qir_qis.qubit_array_release"),
8085 "expected rollback release path in helper, got calls: {called_functions:?}"
8086 );
8087 }
8088
8089 #[test]
8090 fn test_dynamic_qubit_array_allocate_initializes_out_err() {
8091 let ll_text = std::fs::read_to_string("tests/data/dynamic_qubit_array_checked.ll")
8092 .expect("Failed to read dynamic_qubit_array_checked.ll");
8093 let input_bc =
8094 qir_ll_to_bc(&ll_text).expect("Failed to convert dynamic_qubit_array_checked.ll");
8095 let output_bc = qir_to_qis(&input_bc, 0, "native", None)
8096 .expect("dynamic qubit array checked fixture should compile");
8097
8098 let ctx = Context::create();
8099 let module = parse_bitcode_module(&ctx, &output_bc, "qis_module")
8100 .expect("Compiled QIS bitcode should parse");
8101 let helper = module
8102 .get_function("qir_qis.qubit_array_allocate")
8103 .expect("qubit array allocate helper should exist");
8104 let called_functions = collect_called_function_names(helper);
8105 assert!(
8106 called_functions
8107 .iter()
8108 .any(|name| name == "qir_qis.out_err_success"),
8109 "expected helper to initialize out_err, got calls: {called_functions:?}"
8110 );
8111 }
8112
8113 #[test]
8114 fn test_dynamic_feature_fixtures_translate_to_verifiable_qis() {
8115 let (opt_level, target) = conservative_translation_settings();
8116 for fixture in DYNAMIC_FEATURE_FIXTURES {
8117 let ll_text = std::fs::read_to_string(fixture).expect("Failed to read fixture");
8118 let input_bc = qir_ll_to_bc(&ll_text).expect("Failed to convert fixture to bitcode");
8119 validate_qir(&input_bc, None).expect("Dynamic fixture should validate");
8120 let output_bc = qir_to_qis(&input_bc, opt_level, target, None)
8121 .expect("Dynamic fixture should translate");
8122 verify_bitcode_module(&output_bc, fixture)
8123 .expect("Dynamic fixture should remain LLVM-verifiable");
8124 }
8125 }
8126
8127 proptest! {
8128 #[test]
8129 fn prop_dynamic_result_array_record_output_preserves_label(
8130 label in "[A-Za-z0-9_]{1,8}",
8131 ) {
8132 let label_len = label
8133 .len()
8134 .checked_add(1)
8135 .expect("label length bound should leave room for null terminator");
8136 let ll_text = format!(
8137 r#"
8138@0 = internal constant [{label_len} x i8] c"{label}\00"
8139
8140define i64 @Entry_Point_Name() #0 {{
8141entry:
8142 %results = alloca [2 x ptr], align 8
8143 call void @__quantum__rt__result_array_allocate(i64 2, ptr %results, ptr null)
8144 %r0_ptr = getelementptr inbounds [2 x ptr], ptr %results, i64 0, i64 0
8145 %r0 = load ptr, ptr %r0_ptr, align 8
8146 %r1_ptr = getelementptr inbounds [2 x ptr], ptr %results, i64 0, i64 1
8147 %r1 = load ptr, ptr %r1_ptr, align 8
8148 call void @__quantum__qis__mz__body(ptr null, ptr %r0)
8149 call void @__quantum__qis__mz__body(ptr inttoptr (i64 1 to ptr), ptr %r1)
8150 call void @__quantum__rt__result_array_record_output(i64 2, ptr %results, ptr @0)
8151 call void @__quantum__rt__result_array_release(i64 2, ptr %results)
8152 ret i64 0
8153}}
8154
8155declare void @__quantum__rt__result_array_allocate(i64, ptr, ptr)
8156declare void @__quantum__rt__result_array_release(i64, ptr)
8157declare void @__quantum__rt__result_array_record_output(i64, ptr, ptr)
8158declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1
8159
8160attributes #0 = {{ "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="2" }}
8161attributes #1 = {{ "irreversible" }}
8162
8163!llvm.module.flags = !{{!0, !1, !2, !3, !4}}
8164!0 = !{{i32 1, !"qir_major_version", i32 2}}
8165!1 = !{{i32 7, !"qir_minor_version", i32 0}}
8166!2 = !{{i32 1, !"dynamic_qubit_management", i1 false}}
8167!3 = !{{i32 1, !"dynamic_result_management", i1 true}}
8168!4 = !{{i32 1, !"arrays", i1 true}}
8169"#
8170 );
8171
8172 let bc_bytes = qir_ll_to_bc(&ll_text)
8173 .map_err(|err| TestCaseError::fail(format!("Failed to lower inline IR: {err}")))?;
8174 validate_qir(&bc_bytes, None)
8175 .map_err(|err| TestCaseError::fail(format!("Fixture should validate: {err}")))?;
8176 let qis_bytes = qir_to_qis(&bc_bytes, 0, "native", None)
8177 .map_err(|err| TestCaseError::fail(format!("Fixture should compile: {err}")))?;
8178 let ctx = Context::create();
8179 let module = parse_bitcode_module(&ctx, &qis_bytes, "qis_module")
8180 .map_err(|err| TestCaseError::fail(format!("Compiled module should parse: {err}")))?;
8181 let expected_label = format!("USER:RESULT_ARRAY:{label}");
8182 let labels = module
8183 .get_globals()
8184 .filter_map(|global| get_string_label(global).ok())
8185 .collect::<Vec<_>>();
8186
8187 prop_assert!(
8188 labels.iter().any(|item| item.contains(&expected_label)),
8189 "expected label payload {expected_label}, got globals: {labels:?}"
8190 );
8191 }
8192
8193 #[test]
8194 fn prop_dynamic_array_allocate_requires_matching_fixed_backing(
8195 backing_len in 1u32..4u32,
8196 requested_len in 1u32..4u32,
8197 is_result_array in any::<bool>(),
8198 ) {
8199 let (rt_decl, call, attrs, flags) = if is_result_array {
8200 (
8201 "declare void @__quantum__rt__result_array_allocate(i64, ptr, ptr)",
8202 format!(
8203 " %results = alloca [{backing_len} x ptr], align 8\n call void @__quantum__rt__result_array_allocate(i64 {requested_len}, ptr %results, ptr null)"
8204 ),
8205 r#""entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1""#,
8206 "!2 = !{i32 1, !\"dynamic_qubit_management\", i1 false}\n!3 = !{i32 1, !\"dynamic_result_management\", i1 true}\n!4 = !{i32 1, !\"arrays\", i1 true}",
8207 )
8208 } else {
8209 (
8210 "declare void @__quantum__rt__qubit_array_allocate(i64, ptr, ptr)",
8211 format!(
8212 " %qubits = alloca [{backing_len} x ptr], align 8\n call void @__quantum__rt__qubit_array_allocate(i64 {requested_len}, ptr %qubits, ptr null)"
8213 ),
8214 r#""entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_results"="1""#,
8215 "!2 = !{i32 1, !\"dynamic_qubit_management\", i1 true}\n!3 = !{i32 1, !\"dynamic_result_management\", i1 false}\n!4 = !{i32 1, !\"arrays\", i1 true}",
8216 )
8217 };
8218
8219 let ll_text = format!(
8220 r#"
8221define i64 @Entry_Point_Name() #0 {{
8222entry:
8223{call}
8224 ret i64 0
8225}}
8226
8227{rt_decl}
8228
8229attributes #0 = {{ {attrs} }}
8230
8231!llvm.module.flags = !{{!0, !1, !2, !3, !4}}
8232!0 = !{{i32 1, !"qir_major_version", i32 2}}
8233!1 = !{{i32 7, !"qir_minor_version", i32 0}}
8234{flags}
8235"#
8236 );
8237 let bc = qir_ll_to_bc(&ll_text)
8238 .map_err(|err| TestCaseError::fail(format!("inline IR should parse: {err}")))?;
8239 let result = validate_qir(&bc, None);
8240 if backing_len == requested_len {
8241 prop_assert!(result.is_ok());
8242 } else {
8243 let err = result.expect_err("mismatched fixed-size backing should fail");
8244 prop_assert!(err.contains("requires a fixed-size backing array"));
8245 prop_assert!(err.contains("requested length"));
8246 }
8247 }
8248
8249 #[test]
8250 fn prop_dynamic_array_runtime_calls_require_both_arrays_and_matching_dynamic_flag(
8251 arrays_enabled in any::<bool>(),
8252 dynamic_enabled in any::<bool>(),
8253 is_result_array in any::<bool>(),
8254 ) {
8255 let (call, rt_decl, attrs, flags, expected_error) = if is_result_array {
8256 (
8257 " %results = alloca [2 x ptr], align 8\n call void @__quantum__rt__result_array_allocate(i64 2, ptr %results, ptr null)",
8258 "declare void @__quantum__rt__result_array_allocate(i64, ptr, ptr)",
8259 r#""entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1""#.to_string(),
8260 format!(
8261 "!2 = !{{i32 1, !\"dynamic_qubit_management\", i1 false}}\n!3 = !{{i32 1, !\"dynamic_result_management\", i1 {dynamic_enabled}}}\n!4 = !{{i32 1, !\"arrays\", i1 {arrays_enabled}}}"
8262 ),
8263 "__quantum__rt__result_array_allocate requires both `arrays=true` and `dynamic_result_management=true`",
8264 )
8265 } else {
8266 (
8267 " %qubits = alloca [2 x ptr], align 8\n call void @__quantum__rt__qubit_array_allocate(i64 2, ptr %qubits, ptr null)",
8268 "declare void @__quantum__rt__qubit_array_allocate(i64, ptr, ptr)",
8269 r#""entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_results"="1""#.to_string(),
8270 format!(
8271 "!2 = !{{i32 1, !\"dynamic_qubit_management\", i1 {dynamic_enabled}}}\n!3 = !{{i32 1, !\"dynamic_result_management\", i1 false}}\n!4 = !{{i32 1, !\"arrays\", i1 {arrays_enabled}}}"
8272 ),
8273 "__quantum__rt__qubit_array_allocate requires both `arrays=true` and `dynamic_qubit_management=true`",
8274 )
8275 };
8276
8277 let ll_text = format!(
8278 r#"
8279define i64 @Entry_Point_Name() #0 {{
8280entry:
8281{call}
8282 ret i64 0
8283}}
8284
8285{rt_decl}
8286
8287attributes #0 = {{ {attrs} }}
8288
8289!llvm.module.flags = !{{!0, !1, !2, !3, !4}}
8290!0 = !{{i32 1, !"qir_major_version", i32 2}}
8291!1 = !{{i32 7, !"qir_minor_version", i32 0}}
8292{flags}
8293"#
8294 );
8295 let bc = qir_ll_to_bc(&ll_text)
8296 .map_err(|err| TestCaseError::fail(format!("inline IR should parse: {err}")))?;
8297 let result = validate_qir(&bc, None);
8298 if arrays_enabled && dynamic_enabled {
8299 prop_assert!(result.is_ok());
8300 } else {
8301 let err = result.expect_err("missing capability flag combination should fail");
8302 prop_assert!(err.contains(expected_error));
8303 }
8304 }
8305 }
8306}