1#![deny(clippy::panic)]
2#![deny(arithmetic_overflow)] #![deny(clippy::arithmetic_side_effects)] #![deny(clippy::cast_possible_truncation)] #![deny(clippy::cast_possible_wrap)] #![deny(clippy::cast_precision_loss)] #![deny(clippy::cast_sign_loss)] #![deny(clippy::checked_conversions)] #![deny(clippy::integer_division)] #![deny(clippy::unchecked_time_subtraction)] #![deny(clippy::expect_used)] #![deny(clippy::option_env_unwrap)] #![deny(clippy::panicking_unwrap)] #![deny(clippy::unwrap_used)] #![deny(clippy::join_absolute_paths)] #![deny(clippy::serde_api_misuse)] #![deny(clippy::uninit_vec)] #![deny(clippy::transmute_ptr_to_ref)] #![deny(clippy::transmute_undefined_repr)] #![deny(unnecessary_transmutes)] #![deny(clippy::fallible_impl_from)]
37#![deny(clippy::fn_params_excessive_bools)]
38#![deny(clippy::must_use_candidate)]
39#![deny(clippy::unneeded_field_pattern)]
40#![deny(clippy::wildcard_enum_match_arm)]
41
42#[cfg(feature = "python")]
43use pyo3::prelude::*;
44#[cfg(feature = "python")]
45use pyo3_stub_gen::define_stub_info_gatherer;
46
47pub mod convert;
48mod decompose;
49pub mod opt;
50mod utils;
51
52mod aux {
53 use std::collections::{BTreeMap, HashMap};
54
55 use crate::{
56 convert::{
57 INIT_QARRAY_FN, LOAD_QUBIT_FN, add_print_call, build_result_global, convert_globals,
58 create_reset_call, get_index, get_or_create_function, get_required_num_qubits,
59 get_result_vars, get_string_label, handle_tuple_or_array_output, parse_gep,
60 record_classical_output, replace_rxy_call, replace_rz_call, replace_rzz_call,
61 },
62 utils::extract_operands,
63 };
64
65 use inkwell::{
66 AddressSpace,
67 attributes::AttributeLoc,
68 context::Context,
69 module::Module,
70 types::BasicTypeEnum,
71 values::{
72 AnyValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, CallSiteValue,
73 FunctionValue, PointerValue,
74 },
75 };
76
77 static ALLOWED_QIS_FNS: [&str; 22] = [
78 "__quantum__qis__rxy__body",
80 "__quantum__qis__rz__body",
81 "__quantum__qis__rzz__body",
82 "__quantum__qis__mz__body",
83 "__quantum__qis__reset__body",
84 "__quantum__qis__mresetz__body",
86 "__quantum__qis__u1q__body", "__quantum__qis__m__body", "__quantum__qis__h__body",
91 "__quantum__qis__x__body",
92 "__quantum__qis__y__body",
93 "__quantum__qis__z__body",
94 "__quantum__qis__s__body",
95 "__quantum__qis__s__adj",
96 "__quantum__qis__t__body",
97 "__quantum__qis__t__adj",
98 "__quantum__qis__rx__body",
99 "__quantum__qis__ry__body",
100 "__quantum__qis__cz__body",
101 "__quantum__qis__cx__body",
102 "__quantum__qis__cnot__body",
103 "__quantum__qis__ccx__body",
104 ];
106
107 static ALLOWED_RT_FNS: [&str; 8] = [
108 "__quantum__rt__read_result",
109 "__quantum__rt__initialize",
110 "__quantum__rt__result_record_output",
111 "__quantum__rt__array_record_output",
112 "__quantum__rt__tuple_record_output",
113 "__quantum__rt__bool_record_output",
114 "__quantum__rt__double_record_output",
115 "__quantum__rt__int_record_output",
116 ];
117
118 #[cfg(feature = "wasm")]
119 static ALLOWED_QTM_FNS: [&str; 8] = [
120 "___get_current_shot",
121 "___random_seed",
122 "___random_int",
123 "___random_float",
124 "___random_int_bounded",
125 "___random_advance",
126 "___get_wasm_context",
127 "___barrier",
128 ];
129
130 #[cfg(not(feature = "wasm"))]
131 static ALLOWED_QTM_FNS: [&str; 7] = [
132 "___get_current_shot",
133 "___random_seed",
134 "___random_int",
135 "___random_float",
136 "___random_int_bounded",
137 "___random_advance",
138 "___barrier",
139 ];
140
141 const REQ_FLAG_COUNT: usize = 4;
142
143 #[derive(Default)]
149 struct ModuleFlagState {
150 found: [bool; REQ_FLAG_COUNT],
151 wrong: [bool; REQ_FLAG_COUNT],
152 required: [(usize, &'static str, u64, &'static str); REQ_FLAG_COUNT],
153 }
154
155 impl ModuleFlagState {
156 const fn new() -> Self {
158 Self {
159 found: [false; REQ_FLAG_COUNT],
160 wrong: [false; REQ_FLAG_COUNT],
161 required: [
162 (0, "qir_major_version", 1, "1"),
164 (1, "qir_minor_version", 0, "0"),
165 (2, "dynamic_qubit_management", 0, "false"),
166 (3, "dynamic_result_management", 0, "false"),
167 ],
168 }
169 }
170
171 fn set_state(&mut self, index: usize, value: &BasicMetadataValueEnum, expected: u64) {
173 self.found[index] = true;
174 self.wrong[index] = !value.is_int_value()
175 || value.into_int_value().get_zero_extended_constant() != Some(expected);
176 }
177 }
178
179 pub fn validate_module_layout_and_triple(module: &Module) {
180 let datalayout = module.get_data_layout();
181 let triple = module.get_triple();
182
183 if !datalayout.as_str().is_empty() {
184 log::warn!("QIR module has a data layout: {:?}", datalayout.as_str());
185 }
186 if !triple.as_str().is_empty() {
187 log::warn!("QIR module has a target triple: {:?}", triple.as_str());
188 }
189 }
190
191 pub fn validate_functions(
192 module: &Module,
193 entry_fn: FunctionValue,
194 _wasm_fns: &BTreeMap<String, u64>,
195 errors: &mut Vec<String>,
196 ) {
197 let required_num_qubits = get_required_num_qubits(entry_fn);
199
200 for fun in module.get_functions() {
201 if fun == entry_fn {
202 continue;
204 }
205 let fn_name = fun.get_name().to_str().unwrap_or("");
206 if fn_name.starts_with("__quantum__qis__") {
207 let is_barrier = if fn_name.starts_with("__quantum__qis__barrier")
209 && fn_name.ends_with("__body")
210 {
211 parse_barrier_arity(fn_name).is_ok_and(|arity| {
212 if let Some(max_qubits) = required_num_qubits
214 && let Ok(arity_u32) = u32::try_from(arity)
215 && arity_u32 > max_qubits
216 {
217 errors.push(format!(
218 "Barrier arity {arity} exceeds module's required_num_qubits ({max_qubits})"
219 ));
220 }
221 true
222 })
223 } else {
224 false
225 };
226
227 if !is_barrier && !ALLOWED_QIS_FNS.contains(&fn_name) {
228 errors.push(format!("Unsupported QIR QIS function: {fn_name}"));
229 }
230 continue;
231 } else if fn_name.starts_with("__quantum__rt__") {
232 if !ALLOWED_RT_FNS.contains(&fn_name) {
233 errors.push(format!("Unsupported QIR RT function: {fn_name}"));
234 }
235 continue;
236 } else if fn_name.starts_with("___") {
237 if !ALLOWED_QTM_FNS.contains(&fn_name) {
238 errors.push(format!("Unsupported Qtm QIS function: {fn_name}"));
239 }
240 continue;
241 }
242
243 if fun.count_basic_blocks() > 0 {
244 if fn_name == "main" {
247 errors.push("IR defined function cannot be called `main`".to_string());
248 }
249 if fun
251 .get_type()
252 .get_return_type()
253 .is_some_and(BasicTypeEnum::is_pointer_type)
254 {
255 errors.push(format!("Function `{fn_name}` cannot return a pointer type"));
256 }
257 continue;
258 }
259
260 log::debug!(
261 "External function `{fn_name}` found, leaving as-is for downstream processing"
262 );
263 }
264 }
265
266 pub fn validate_module_flags(module: &Module, errors: &mut Vec<String>) {
267 let mut mflags = ModuleFlagState::new();
268
269 for md in module.get_global_metadata("llvm.module.flags") {
270 if let [_, key, value] = md.get_node_values().as_slice()
271 && let Some(key_str) = key
272 .into_metadata_value()
273 .get_string_value()
274 .and_then(|s| s.to_str().ok())
275 && let Some((idx, _, expected, _)) = mflags
276 .required
277 .iter()
278 .find(|(_, name, _, _)| *name == key_str)
279 {
280 mflags.set_state(*idx, value, *expected);
281 }
282 }
283
284 for (idx, name, _, expected_str) in mflags.required {
285 if !mflags.found[idx] {
286 errors.push(format!("Missing required module flag: `{name}`"));
287 } else if mflags.wrong[idx] {
288 errors.push(format!("Module flag `{name}` must be {expected_str}"));
289 }
290 }
291 }
292
293 #[allow(dead_code)]
294 struct ProcessCallArgs<'a, 'ctx> {
295 ctx: &'ctx Context,
296 module: &'a Module<'ctx>,
297 instr: &'a inkwell::values::InstructionValue<'ctx>,
298 fn_name: &'a str,
299 wasm_fns: &'a BTreeMap<String, u64>,
300 qubit_array: PointerValue<'ctx>,
301 global_mapping: &'a mut HashMap<String, inkwell::values::GlobalValue<'ctx>>,
302 result_ssa: &'a mut [Option<(BasicValueEnum<'ctx>, Option<BasicValueEnum<'ctx>>)>],
303 }
304
305 pub fn process_entry_function<'ctx>(
307 ctx: &'ctx Context,
308 module: &Module<'ctx>,
309 entry_fn: FunctionValue<'ctx>,
310 wasm_fns: &BTreeMap<String, u64>,
311 qubit_array: PointerValue<'ctx>,
312 ) -> Result<(), String> {
313 let mut global_mapping = convert_globals(ctx, module)?;
314
315 if global_mapping.is_empty() {
316 log::warn!("No globals found in QIR module");
317 }
318 let mut result_ssa = get_result_vars(entry_fn)?;
319
320 for bb in entry_fn.get_basic_blocks() {
321 for instr in bb.get_instructions() {
322 let Ok(call) = CallSiteValue::try_from(instr) else {
323 continue;
324 };
325 if let Some(fn_name) = call.get_called_fn_value().and_then(|f| {
326 f.as_global_value()
327 .get_name()
328 .to_str()
329 .ok()
330 .map(ToOwned::to_owned)
331 }) {
332 let mut args = ProcessCallArgs {
333 ctx,
334 module,
335 instr: &instr,
336 fn_name: &fn_name,
337 wasm_fns,
338 qubit_array,
339 global_mapping: &mut global_mapping,
340 result_ssa: &mut result_ssa,
341 };
342 process_call_instruction(&mut args)?;
343 }
344 }
345 }
346
347 Ok(())
348 }
349
350 fn process_call_instruction(args: &mut ProcessCallArgs) -> Result<(), String> {
351 let call = CallSiteValue::try_from(*args.instr)
352 .map_err(|()| "Instruction is not a call site".to_string())?;
353 match args.fn_name {
354 name if name.starts_with("__quantum__qis__") => handle_qis_call(args),
355 name if name.starts_with("__quantum__rt__") => handle_rt_call(args),
356 name if name.starts_with("___") => handle_qtm_call(args),
357 _ => {
358 if let Some(f) = call.get_called_fn_value() {
359 if f.count_basic_blocks() > 0 {
361 if args.fn_name != INIT_QARRAY_FN {
365 log::debug!("IR defined function `{}`: {}", args.fn_name, f.get_type());
366 }
367 if args.fn_name == "main" {
368 return Err("IR defined function cannot be called `main`".to_string());
369 }
370 return Ok(());
371 }
372
373 if f.get_string_attribute(AttributeLoc::Function, "cudaq-fnid")
375 .is_some()
376 {
377 log::debug!(
378 "GPU function `{}` found, leaving as-is for downstream processing",
379 args.fn_name
380 );
381 return Ok(());
382 }
383
384 if f.get_string_attribute(AttributeLoc::Function, "wasm")
386 .is_some()
387 {
388 log::debug!(
389 "WASM function `{}` found, leaving as-is for downstream processing",
390 args.fn_name
391 );
392 return Ok(());
393 }
394
395 log::error!("Unknown external function: {}", args.fn_name);
396 return Err(format!("Unsupported function: {}", args.fn_name));
397 }
398
399 log::error!("Unsupported function: {}", args.fn_name);
400 Err(format!("Unsupported function: {}", args.fn_name))
401 }
402 }
403 }
404
405 fn handle_qis_call(args: &mut ProcessCallArgs) -> Result<(), String> {
406 match args.fn_name {
407 "__quantum__qis__rxy__body" => {
408 replace_rxy_call(args.ctx, args.module, *args.instr)?;
409 }
410 "__quantum__qis__rz__body" => {
411 replace_rz_call(args.ctx, args.module, *args.instr)?;
412 }
413 "__quantum__qis__rzz__body" => {
414 replace_rzz_call(args.ctx, args.module, *args.instr)?;
415 }
416 "__quantum__qis__u1q__body" => {
417 log::info!(
418 "`__quantum__qis__u1q__body` used, synonym for `__quantum__qis__rxy__body`"
419 );
420 replace_rxy_call(args.ctx, args.module, *args.instr)?;
421 }
422 "__quantum__qis__mz__body"
423 | "__quantum__qis__m__body"
424 | "__quantum__qis__mresetz__body" => {
425 handle_mz_call(args)?;
426 }
427 "__quantum__qis__reset__body" => {
428 handle_reset_call(args)?;
429 }
430 name if name.starts_with("__quantum__qis__barrier") && name.ends_with("__body") => {
431 handle_barrier_call(args)?;
432 }
433 _ => return Err(format!("Unsupported QIR QIS function: {}", args.fn_name)),
434 }
435 Ok(())
436 }
437
438 fn handle_rt_call(args: &mut ProcessCallArgs) -> Result<(), String> {
439 match args.fn_name {
440 "__quantum__rt__initialize" => {
441 args.instr.erase_from_basic_block();
442 }
443 "__quantum__rt__read_result" | "__quantum__rt__result_record_output" => {
444 handle_read_result_call(args)?;
445 }
446 "__quantum__rt__tuple_record_output" | "__quantum__rt__array_record_output" => {
447 handle_tuple_or_array_output(
448 args.ctx,
449 args.module,
450 *args.instr,
451 args.global_mapping,
452 args.fn_name,
453 )?;
454 }
455 "__quantum__rt__bool_record_output"
456 | "__quantum__rt__int_record_output"
457 | "__quantum__rt__double_record_output" => {
458 handle_classical_record_output(args)?;
459 }
460 _ => return Err(format!("Unsupported QIR RT function: {}", args.fn_name)),
461 }
462 Ok(())
463 }
464
465 fn handle_qtm_call(args: &mut ProcessCallArgs) -> Result<(), String> {
466 match args.fn_name {
467 "___get_current_shot" => {
468 handle_get_current_shot(args)?;
469 }
470 "___random_seed" => {
471 handle_random_seed(args)?;
472 }
473 "___random_int" => {
474 handle_random_int(args)?;
475 }
476 "___random_float" => {
477 handle_random_float(args)?;
478 }
479 "___random_int_bounded" => {
480 handle_random_int_bounded(args)?;
481 }
482 "___random_advance" => {
483 handle_random_advance(args)?;
484 }
485 "___get_wasm_context" => {
486 log::debug!("___get_wasm_context found, leaving as-is for downstream processing");
488 }
489 _ => {
490 log::trace!("Ignoring Qtm QIS function: {}", args.fn_name);
492 }
493 }
494 Ok(())
495 }
496
497 fn handle_mz_call(args: &mut ProcessCallArgs) -> Result<(), String> {
498 let ProcessCallArgs {
499 ctx,
500 module,
501 instr,
502 fn_name,
503 qubit_array,
504 result_ssa,
505 ..
506 } = args;
507
508 if *fn_name == "__quantum__qis__m__body" {
509 log::warn!(
510 "`__quantum__qis__m__body` is from Q# QDK, synonym for `__quantum__qis__mz__body`"
511 );
512 }
513 let builder = ctx.create_builder();
514 builder.position_before(instr);
515
516 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
518 let qubit_ptr = call_args[0].into_pointer_value();
519 let result_ptr = call_args[1].into_pointer_value();
520
521 let q_handle = {
523 let i64_type = ctx.i64_type();
524 let index = get_index(qubit_ptr)?;
525 let index_val = i64_type.const_int(index, false);
526 let elem_ptr =
527 unsafe { builder.build_gep(*qubit_array, &[i64_type.const_zero(), index_val], "") }
528 .map_err(|e| format!("Failed to build GEP for qubit handle: {e}",))?;
529 builder
530 .build_load(elem_ptr, "qbit")
531 .map_err(|e| format!("Failed to build load for qubit handle: {e}",))?
532 };
533
534 let meas = {
536 let meas_func = get_or_create_function(
537 module,
538 "___lazy_measure",
539 ctx.i64_type().fn_type(&[ctx.i64_type().into()], false),
540 );
541
542 let call = builder.build_call(meas_func, &[q_handle.into()], "meas");
543 let call_result =
544 call.map_err(|e| format!("Failed to build call for lazy measure function: {e}",))?;
545 match call_result.try_as_basic_value() {
546 inkwell::values::ValueKind::Basic(bv) => bv,
547 inkwell::values::ValueKind::Instruction(_) => {
548 return Err("Failed to get basic value from lazy measure call".into());
549 }
550 }
551 };
552
553 let result_idx = get_index(result_ptr)?;
555 let result_idx_usize = usize::try_from(result_idx)
556 .map_err(|e| format!("Failed to convert result index to usize: {e}"))?;
557 result_ssa[result_idx_usize] = Some((meas, None));
558
559 if *fn_name == "__quantum__qis__mresetz__body" {
560 log::warn!("`__quantum__qis__mresetz__body` is from Q# QDK");
561 create_reset_call(ctx, module, &builder, q_handle);
563 }
564
565 instr.erase_from_basic_block();
567 Ok(())
568 }
569
570 fn handle_reset_call(args: &ProcessCallArgs) -> Result<(), String> {
571 let ProcessCallArgs {
572 ctx, module, instr, ..
573 } = args;
574 let builder = ctx.create_builder();
575 builder.position_before(instr);
576
577 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
579 let qubit_ptr = call_args[0].into_pointer_value();
580
581 let idx_fn = module
583 .get_function(LOAD_QUBIT_FN)
584 .ok_or_else(|| format!("{LOAD_QUBIT_FN} not found"))?;
585 let idx_call = builder
586 .build_call(idx_fn, &[qubit_ptr.into()], "qbit")
587 .map_err(|e| format!("Failed to build call to {LOAD_QUBIT_FN}: {e}",))?;
588 let q_handle = match idx_call.try_as_basic_value() {
589 inkwell::values::ValueKind::Basic(bv) => bv,
590 inkwell::values::ValueKind::Instruction(_) => {
591 return Err(format!(
592 "Failed to get basic value from {LOAD_QUBIT_FN} call"
593 ));
594 }
595 };
596
597 create_reset_call(ctx, module, &builder, q_handle);
599
600 instr.erase_from_basic_block();
601 Ok(())
602 }
603
604 fn parse_barrier_arity(fn_name: &str) -> Result<usize, String> {
605 fn_name
606 .strip_prefix("__quantum__qis__barrier")
607 .and_then(|s| s.strip_suffix("__body"))
608 .and_then(|s| s.parse::<usize>().ok())
609 .filter(|&n| n > 0)
610 .ok_or_else(|| format!("Invalid barrier function name: {fn_name}"))
611 }
612
613 fn handle_barrier_call(args: &ProcessCallArgs) -> Result<(), String> {
614 let ProcessCallArgs {
615 ctx,
616 module,
617 instr,
618 fn_name,
619 ..
620 } = args;
621
622 let builder = ctx.create_builder();
623 builder.position_before(instr);
624
625 let num_qubits = parse_barrier_arity(fn_name)?;
626
627 let all_operands: Vec<BasicValueEnum> = extract_operands(instr)?;
629 let num_operands = all_operands
630 .len()
631 .checked_sub(1)
632 .ok_or("Expected at least one operand")?;
633
634 if num_operands != num_qubits {
635 return Err(format!(
636 "Barrier function {fn_name} expects {num_qubits} arguments, got {num_operands}"
637 ));
638 }
639
640 let call_args = &all_operands[..num_operands];
641
642 let i64_type = ctx.i64_type();
644 let array_type = i64_type.array_type(
645 u32::try_from(num_qubits).map_err(|e| format!("Failed to convert num_qubits: {e}"))?,
646 );
647 let array_alloca = builder
648 .build_alloca(array_type, "barrier_qubits")
649 .map_err(|e| format!("Failed to allocate array for barrier qubits: {e}"))?;
650
651 let idx_fn = module
652 .get_function(LOAD_QUBIT_FN)
653 .ok_or_else(|| format!("{LOAD_QUBIT_FN} not found"))?;
654
655 for (i, arg) in call_args.iter().enumerate() {
656 let qubit_ptr = arg.into_pointer_value();
657 let idx_call = builder
658 .build_call(idx_fn, &[qubit_ptr.into()], "qbit")
659 .map_err(|e| format!("Failed to build call to {LOAD_QUBIT_FN}: {e}"))?;
660 let q_handle = match idx_call.try_as_basic_value() {
661 inkwell::values::ValueKind::Basic(bv) => bv,
662 inkwell::values::ValueKind::Instruction(_) => {
663 return Err(format!(
664 "Failed to get basic value from {LOAD_QUBIT_FN} call"
665 ));
666 }
667 };
668
669 let elem_ptr = unsafe {
670 builder.build_gep(
671 array_alloca,
672 &[
673 i64_type.const_zero(),
674 i64_type.const_int(
675 u64::try_from(i)
676 .map_err(|e| format!("Failed to convert index: {e}"))?,
677 false,
678 ),
679 ],
680 "",
681 )
682 }
683 .map_err(|e| format!("Failed to build GEP for barrier array: {e}"))?;
684 builder
685 .build_store(elem_ptr, q_handle)
686 .map_err(|e| format!("Failed to store qubit handle in array: {e}"))?;
687 }
688
689 let array_ptr = unsafe {
690 builder.build_gep(
691 array_alloca,
692 &[i64_type.const_zero(), i64_type.const_zero()],
693 "barrier_array_ptr",
694 )
695 }
696 .map_err(|e| format!("Failed to build GEP for barrier array pointer: {e}"))?;
697
698 let barrier_func = get_or_create_function(
700 module,
701 "___barrier",
702 ctx.void_type().fn_type(
703 &[
704 i64_type.ptr_type(AddressSpace::default()).into(),
705 i64_type.into(),
706 ],
707 false,
708 ),
709 );
710
711 builder
712 .build_call(
713 barrier_func,
714 &[
715 array_ptr.into(),
716 i64_type
717 .const_int(
718 u64::try_from(num_qubits)
719 .map_err(|e| format!("Failed to convert num_qubits: {e}"))?,
720 false,
721 )
722 .into(),
723 ],
724 "",
725 )
726 .map_err(|e| format!("Failed to build call to ___barrier: {e}"))?;
727
728 instr.erase_from_basic_block();
729 Ok(())
730 }
731
732 fn handle_read_result_call(args: &mut ProcessCallArgs) -> Result<(), String> {
733 let ProcessCallArgs {
734 ctx,
735 module,
736 instr,
737 fn_name,
738 global_mapping,
739 result_ssa,
740 ..
741 } = args;
742 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
743 let result_ptr = call_args[0].into_pointer_value();
744 let result_idx = get_index(result_ptr)?;
745 let meas_handle = result_ssa[usize::try_from(result_idx)
746 .map_err(|e| format!("Failed to convert result index to usize: {e}"))?]
747 .ok_or_else(|| "Expected measurement handle".to_string())?;
748
749 let builder = ctx.create_builder();
750 builder.position_before(instr);
751
752 let result_idx_usize = usize::try_from(result_idx)
754 .map_err(|e| format!("Failed to convert result index to usize: {e}"))?;
755 let bool_val = result_ssa[result_idx_usize]
756 .and_then(|v| v.1)
757 .and_then(|val| val.as_instruction_value())
758 .map_or_else(
759 || {
760 let read_func = get_or_create_function(
761 module,
762 "___read_future_bool",
763 ctx.bool_type().fn_type(&[ctx.i64_type().into()], false),
764 );
765 let bool_call = builder
766 .build_call(read_func, &[meas_handle.0.into()], "bool")
767 .map_err(|e| format!("Failed to build call for read_future_bool: {e}"))?;
768 let bool_val = match bool_call.try_as_basic_value() {
769 inkwell::values::ValueKind::Basic(bv) => bv,
770 inkwell::values::ValueKind::Instruction(_) => {
771 return Err(
772 "Failed to get basic value from read_future_bool call".into()
773 );
774 }
775 };
776
777 let dec_func = get_or_create_function(
779 module,
780 "___dec_future_refcount",
781 ctx.void_type().fn_type(&[ctx.i64_type().into()], false),
782 );
783 let _ = builder
784 .build_call(dec_func, &[meas_handle.0.into()], "")
785 .map_err(|e| {
786 format!("Failed to build call for dec_future_refcount: {e}")
787 })?;
788
789 result_ssa[result_idx_usize] = Some((meas_handle.0, Some(bool_val)));
791 Ok(bool_val)
792 },
793 |val| {
794 val.as_any_value_enum()
795 .try_into()
796 .map_err(|()| "Expected BasicValueEnum".to_string())
797 },
798 )?;
799
800 if *fn_name == "__quantum__rt__read_result" {
801 let instruction_val = bool_val
802 .as_instruction_value()
803 .ok_or("Failed to convert bool_val to instruction value")?;
804 instr.replace_all_uses_with(&instruction_val);
805 } else {
806 let gep = call_args[1];
808 let old_global = parse_gep(gep)?;
809 let new_global = global_mapping[old_global.as_str()];
810
811 let print_func = get_or_create_function(
812 module,
813 "print_bool",
814 ctx.void_type().fn_type(
815 &[
816 ctx.i8_type().ptr_type(AddressSpace::default()).into(), ctx.i64_type().into(), ctx.bool_type().into(), ],
820 false,
821 ),
822 );
823
824 add_print_call(ctx, &builder, new_global, print_func, bool_val)?;
825 }
826 instr.erase_from_basic_block();
827 Ok(())
828 }
829
830 fn handle_classical_record_output(args: &mut ProcessCallArgs) -> Result<(), String> {
831 let ProcessCallArgs {
832 ctx,
833 module,
834 instr,
835 fn_name,
836 global_mapping,
837 ..
838 } = args;
839 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
840 let (print_func_name, value, type_tag) = match *fn_name {
841 "__quantum__rt__bool_record_output" => (
842 "print_bool",
843 call_args[0].into_int_value().as_basic_value_enum(),
844 "BOOL",
845 ),
846 "__quantum__rt__int_record_output" => (
847 "print_int",
848 call_args[0].into_int_value().as_basic_value_enum(),
849 "INT",
850 ),
851 "__quantum__rt__double_record_output" => (
852 "print_float",
853 call_args[0].into_float_value().as_basic_value_enum(),
854 "FLOAT",
855 ),
856 _ => unreachable!(),
857 };
858
859 let ret_type = ctx.void_type();
861 let param_types = &[
862 ctx.i8_type().ptr_type(AddressSpace::default()).into(), ctx.i64_type().into(), match type_tag {
865 "BOOL" => ctx.bool_type().into(),
866 "INT" => ctx.i64_type().into(),
867 "FLOAT" => ctx.f64_type().into(),
868 _ => unreachable!(),
869 },
870 ];
871 let fn_type = ret_type.fn_type(param_types, false);
872
873 let print_func = get_or_create_function(module, print_func_name, fn_type);
874
875 let old_global = parse_gep(call_args[1])?;
876 let old_name = old_global.as_str();
877
878 let full_tag = get_string_label(global_mapping[old_name])?;
879 let old_label = full_tag
881 .rfind(':')
882 .and_then(|pos| pos.checked_add(1))
883 .map_or_else(|| full_tag.clone(), |pos| full_tag[pos..].to_string());
884
885 let (new_const, new_name) = build_result_global(ctx, &old_label, old_name, type_tag)?;
886
887 let new_global = module.add_global(new_const.get_type(), None, &new_name);
888 new_global.set_initializer(&new_const);
889 new_global.set_linkage(inkwell::module::Linkage::Private);
890 new_global.set_constant(true);
891 global_mapping.insert(old_name.to_string(), new_global);
892 record_classical_output(ctx, **instr, new_global, print_func, value)?;
893 Ok(())
894 }
895
896 fn handle_get_current_shot(args: &ProcessCallArgs) -> Result<(), String> {
897 let ProcessCallArgs {
898 ctx, module, instr, ..
899 } = args;
900 let builder = ctx.create_builder();
901 builder.position_before(instr);
902
903 let get_shot_func = get_or_create_function(
904 module,
905 "get_current_shot",
907 ctx.i64_type().fn_type(&[], false),
908 );
909
910 let shot_call = builder
911 .build_call(get_shot_func, &[], "current_shot")
912 .map_err(|e| format!("Failed to build call to get_current_shot: {e}"))?;
913
914 let shot_result = match shot_call.try_as_basic_value() {
915 inkwell::values::ValueKind::Basic(bv) => bv,
916 inkwell::values::ValueKind::Instruction(_) => {
917 return Err("Failed to get basic value from get_current_shot call".into());
918 }
919 };
920
921 if let Some(instr_val) = shot_result.as_instruction_value() {
922 instr.replace_all_uses_with(&instr_val);
923 }
924
925 instr.erase_from_basic_block();
926 Ok(())
927 }
928
929 fn handle_random_seed(args: &ProcessCallArgs) -> Result<(), String> {
930 let ProcessCallArgs {
931 ctx, module, instr, ..
932 } = args;
933 let builder = ctx.create_builder();
934 builder.position_before(instr);
935
936 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
937 let seed = call_args[0];
938
939 let random_seed_func = get_or_create_function(
940 module,
941 "random_seed",
943 ctx.void_type().fn_type(&[ctx.i64_type().into()], false),
944 );
945
946 let _ = builder
947 .build_call(random_seed_func, &[seed.into()], "")
948 .map_err(|e| format!("Failed to build call to random_seed: {e}"))?;
949
950 instr.erase_from_basic_block();
951 Ok(())
952 }
953
954 fn handle_random_int(args: &ProcessCallArgs) -> Result<(), String> {
955 let ProcessCallArgs {
956 ctx, module, instr, ..
957 } = args;
958 let builder = ctx.create_builder();
959 builder.position_before(instr);
960
961 let random_int_func = get_or_create_function(
962 module,
963 "random_int",
965 ctx.i32_type().fn_type(&[], false),
966 );
967
968 let random_call = builder
969 .build_call(random_int_func, &[], "rint")
970 .map_err(|e| format!("Failed to build call to random_int: {e}"))?;
971
972 let random_result = match random_call.try_as_basic_value() {
973 inkwell::values::ValueKind::Basic(bv) => bv,
974 inkwell::values::ValueKind::Instruction(_) => {
975 return Err("Failed to get basic value from random_int call".into());
976 }
977 };
978
979 if let Some(instr_val) = random_result.as_instruction_value() {
980 instr.replace_all_uses_with(&instr_val);
981 }
982
983 instr.erase_from_basic_block();
984 Ok(())
985 }
986
987 fn handle_random_float(args: &ProcessCallArgs) -> Result<(), String> {
988 let ProcessCallArgs {
989 ctx, module, instr, ..
990 } = args;
991 let builder = ctx.create_builder();
992 builder.position_before(instr);
993
994 let random_float_func = get_or_create_function(
995 module,
996 "random_float",
998 ctx.f64_type().fn_type(&[], false),
999 );
1000
1001 let random_call = builder
1002 .build_call(random_float_func, &[], "rfloat")
1003 .map_err(|e| format!("Failed to build call to random_float: {e}"))?;
1004
1005 let random_result = match random_call.try_as_basic_value() {
1006 inkwell::values::ValueKind::Basic(bv) => bv,
1007 inkwell::values::ValueKind::Instruction(_) => {
1008 return Err("Failed to get basic value from random_float call".into());
1009 }
1010 };
1011
1012 if let Some(instr_val) = random_result.as_instruction_value() {
1013 instr.replace_all_uses_with(&instr_val);
1014 }
1015
1016 instr.erase_from_basic_block();
1017 Ok(())
1018 }
1019
1020 fn handle_random_int_bounded(args: &ProcessCallArgs) -> Result<(), String> {
1021 let ProcessCallArgs {
1022 ctx, module, instr, ..
1023 } = args;
1024 let builder = ctx.create_builder();
1025 builder.position_before(instr);
1026
1027 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
1028 let bound = call_args[0];
1029
1030 let random_rng_func = get_or_create_function(
1031 module,
1032 "random_rng",
1034 ctx.i32_type().fn_type(&[ctx.i32_type().into()], false),
1035 );
1036
1037 let rng_call = builder
1038 .build_call(random_rng_func, &[bound.into()], "rintb")
1039 .map_err(|e| format!("Failed to build call to random_rng: {e}"))?;
1040
1041 let rng_result = match rng_call.try_as_basic_value() {
1042 inkwell::values::ValueKind::Basic(bv) => bv,
1043 inkwell::values::ValueKind::Instruction(_) => {
1044 return Err("Failed to get basic value from random_rng call".into());
1045 }
1046 };
1047
1048 if let Some(instr_val) = rng_result.as_instruction_value() {
1049 instr.replace_all_uses_with(&instr_val);
1050 }
1051
1052 instr.erase_from_basic_block();
1053 Ok(())
1054 }
1055
1056 fn handle_random_advance(args: &ProcessCallArgs) -> Result<(), String> {
1057 let ProcessCallArgs {
1058 ctx, module, instr, ..
1059 } = args;
1060 let builder = ctx.create_builder();
1061 builder.position_before(instr);
1062
1063 let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
1064 let delta = call_args[0];
1065
1066 let random_advance_func = get_or_create_function(
1067 module,
1068 "random_advance",
1070 ctx.void_type().fn_type(&[ctx.i64_type().into()], false),
1071 );
1072
1073 let _ = builder
1074 .build_call(random_advance_func, &[delta.into()], "")
1075 .map_err(|e| format!("Failed to build call to random_advance: {e}"))?;
1076
1077 instr.erase_from_basic_block();
1078 Ok(())
1079 }
1080}
1081
1082pub fn qir_to_qis(
1093 bc_bytes: &[u8],
1094 opt_level: u32,
1095 target: &str,
1096 _wasm_bytes: Option<&[u8]>,
1097) -> Result<Vec<u8>, String> {
1098 use crate::{
1099 aux::process_entry_function,
1100 convert::{
1101 add_qmain_wrapper, create_qubit_array, find_entry_function, free_all_qubits,
1102 get_string_attrs, process_ir_defined_q_fns,
1103 },
1104 decompose::add_decompositions,
1105 opt::optimize,
1106 utils::add_generator_metadata,
1107 };
1108 use inkwell::{attributes::AttributeLoc, context::Context, memory_buffer::MemoryBuffer};
1109 use std::{collections::BTreeMap, env};
1110
1111 let ctx = Context::create();
1112 let memory_buffer = MemoryBuffer::create_from_memory_range(bc_bytes, "bitcode");
1113 let module = ctx
1114 .create_module_from_ir(memory_buffer)
1115 .map_err(|e| format!("Failed to create module: {e}"))?;
1116
1117 let _ = add_decompositions(&ctx, &module);
1118
1119 let entry_fn = find_entry_function(&module)
1120 .map_err(|e| format!("Failed to find entry function in QIR module: {e}"))?;
1121
1122 let entry_fn_name = entry_fn
1123 .get_name()
1124 .to_str()
1125 .map_err(|e| format!("Invalid UTF-8 in entry function name: {e}"))?;
1126
1127 log::trace!("Entry function: {entry_fn_name}");
1128 let new_name = format!("___user_qir_{entry_fn_name}");
1129 entry_fn.as_global_value().set_name(&new_name);
1130 log::debug!("Renamed entry function to: {new_name}");
1131
1132 let qubit_array = create_qubit_array(&ctx, &module, entry_fn)?;
1133
1134 let wasm_fns: BTreeMap<String, u64> = BTreeMap::new();
1135
1136 process_entry_function(&ctx, &module, entry_fn, &wasm_fns, qubit_array)?;
1137
1138 process_ir_defined_q_fns(&ctx, &module, entry_fn)?;
1140
1141 free_all_qubits(&ctx, &module, entry_fn, qubit_array)?;
1142
1143 let _ = add_qmain_wrapper(&ctx, &module, entry_fn);
1145
1146 module
1147 .verify()
1148 .map_err(|e| format!("LLVM module verification failed: {e}"))?;
1149
1150 for attr in get_string_attrs(entry_fn) {
1152 let kind_id = attr
1153 .get_string_kind_id()
1154 .to_str()
1155 .map_err(|e| format!("Invalid UTF-8 in attribute kind ID: {e}"))?;
1156 entry_fn.remove_string_attribute(AttributeLoc::Function, kind_id);
1157 }
1158
1159 let md_string = ctx.metadata_string("mainlib");
1164 let md_node = ctx.metadata_node(&[md_string.into()]);
1165 module
1166 .add_global_metadata("name", &md_node)
1167 .map_err(|e| format!("Failed to add global metadata: {e}"))?;
1168 add_generator_metadata(&ctx, &module, "gen_name", env!("CARGO_PKG_NAME"))?;
1169 add_generator_metadata(&ctx, &module, "gen_version", env!("CARGO_PKG_VERSION"))?;
1170
1171 optimize(&module, opt_level, target)?;
1172
1173 Ok(module.write_bitcode_to_memory().as_slice().to_vec())
1174}
1175
1176#[cfg(feature = "wasm")]
1181pub fn get_wasm_functions(
1182 wasm_bytes: Option<&[u8]>,
1183) -> Result<std::collections::BTreeMap<String, u64>, String> {
1184 use crate::utils::parse_wasm_functions;
1185 use std::collections::BTreeMap;
1186
1187 let mut wasm_fns: BTreeMap<String, u64> = BTreeMap::new();
1188 if let Some(bytes) = wasm_bytes {
1189 wasm_fns = parse_wasm_functions(bytes)?;
1190 log::debug!("WASM function map: {wasm_fns:?}");
1191 }
1192 Ok(wasm_fns)
1193}
1194
1195#[cfg(not(feature = "wasm"))]
1196fn get_wasm_functions(
1197 _wasm_bytes: Option<&[u8]>,
1198) -> Result<std::collections::BTreeMap<String, u64>, String> {
1199 Ok(std::collections::BTreeMap::new())
1200}
1201
1202pub fn validate_qir(bc_bytes: &[u8], wasm_bytes: Option<&[u8]>) -> Result<(), String> {
1211 use crate::{
1212 aux::{validate_functions, validate_module_flags, validate_module_layout_and_triple},
1213 convert::find_entry_function,
1214 };
1215 use inkwell::{attributes::AttributeLoc, context::Context, memory_buffer::MemoryBuffer};
1216
1217 let ctx = Context::create();
1218 let memory_buffer = MemoryBuffer::create_from_memory_range(bc_bytes, "bitcode");
1219 let module = ctx
1220 .create_module_from_ir(memory_buffer)
1221 .map_err(|e| format!("Failed to parse bitcode: {e}"))?;
1222 let mut errors = Vec::new();
1223
1224 validate_module_layout_and_triple(&module);
1225
1226 let entry_fn = if let Ok(entry_fn) = find_entry_function(&module) {
1227 if entry_fn.get_basic_blocks().is_empty() {
1228 errors.push("Entry function has no basic blocks".to_string());
1229 }
1230
1231 let required_attrs = [
1233 "required_num_qubits",
1234 "required_num_results",
1235 "qir_profiles",
1236 "output_labeling_schema",
1237 ];
1238 for &attr in &required_attrs {
1239 let val = entry_fn.get_string_attribute(AttributeLoc::Function, attr);
1240 if val.is_none() {
1241 errors.push(format!("Missing required attribute: `{attr}`"));
1242 }
1243 }
1244
1245 for (attr, type_) in [
1247 ("required_num_qubits", "qubit"),
1248 ("required_num_results", "result"),
1249 ] {
1250 if entry_fn
1251 .get_string_attribute(AttributeLoc::Function, attr)
1252 .and_then(|a| a.get_string_value().to_str().ok()?.parse::<u32>().ok())
1253 == Some(0)
1254 {
1255 errors.push(format!("Entry function must have at least one {type_}"));
1256 }
1257 }
1258 entry_fn
1259 } else {
1260 errors.push("No entry function found in QIR module".to_string());
1261 return Err(errors.join("; "));
1262 };
1263
1264 let wasm_fns = get_wasm_functions(wasm_bytes)?;
1265
1266 validate_functions(&module, entry_fn, &wasm_fns, &mut errors);
1267
1268 validate_module_flags(&module, &mut errors);
1269
1270 if !errors.is_empty() {
1271 return Err(errors.join("; "));
1272 }
1273 log::info!("QIR validation passed");
1274 Ok(())
1275}
1276
1277pub fn qir_ll_to_bc(ll_text: &str) -> Result<Vec<u8>, String> {
1282 use inkwell::{context::Context, memory_buffer::MemoryBuffer};
1283
1284 let ctx = Context::create();
1285 let memory_buffer = MemoryBuffer::create_from_memory_range(ll_text.as_bytes(), "qir");
1286 let module = ctx
1287 .create_module_from_ir(memory_buffer)
1288 .map_err(|e| format!("Failed to create module from LLVM IR: {e}"))?;
1289
1290 Ok(module.write_bitcode_to_memory().as_slice().to_vec())
1291}
1292
1293pub fn get_entry_attributes(
1301 bc_bytes: &[u8],
1302) -> Result<std::collections::BTreeMap<String, Option<String>>, String> {
1303 use crate::convert::{find_entry_function, get_string_attrs};
1304 use inkwell::{context::Context, memory_buffer::MemoryBuffer};
1305 use std::collections::BTreeMap;
1306
1307 let ctx = Context::create();
1308 let memory_buffer = MemoryBuffer::create_from_memory_range(bc_bytes, "bitcode");
1309 let module = ctx
1310 .create_module_from_ir(memory_buffer)
1311 .map_err(|e| format!("Failed to create module from QIR bitcode: {e}"))?;
1312
1313 let mut metadata = BTreeMap::new();
1314 if let Ok(entry_fn) = find_entry_function(&module) {
1315 for attr in get_string_attrs(entry_fn) {
1316 let kind_id = if let Ok(kind_id) = attr.get_string_kind_id().to_str() {
1317 kind_id.to_owned()
1318 } else {
1319 log::warn!("Skipping invalid UTF-8 attribute kind ID");
1320 continue;
1321 };
1322 if let Ok(value) = attr.get_string_value().to_str() {
1323 metadata.insert(
1324 kind_id,
1325 if value.is_empty() {
1326 None
1327 } else {
1328 Some(value.to_owned())
1329 },
1330 );
1331 } else {
1332 log::warn!("Invalid UTF-8 value for attribute `{kind_id}`");
1333 metadata.insert(kind_id, None);
1334 }
1335 }
1336 }
1337 Ok(metadata)
1338}
1339
1340#[cfg(feature = "python")]
1341mod exceptions {
1342 use pyo3::exceptions::PyException;
1343 use pyo3_stub_gen::create_exception;
1344
1345 create_exception!(
1346 qir_qis,
1347 ValidationError,
1348 PyException,
1349 "QIR ValidationError.\n\nRaised when the QIR is invalid."
1350 );
1351 create_exception!(
1352 qir_qis,
1353 CompilerError,
1354 PyException,
1355 "QIR CompilerError.\n\nRaised when QIR to QIS compilation fails."
1356 );
1357}
1358
1359#[cfg(feature = "python")]
1360#[pymodule]
1361mod qir_qis {
1362 use std::borrow::Cow;
1363 use std::collections::BTreeMap;
1364
1365 use super::{PyErr, PyResult, pyfunction};
1366
1367 use pyo3_stub_gen::derive::gen_stub_pyfunction;
1368
1369 #[pymodule_export]
1370 use super::exceptions::CompilerError;
1371 #[pymodule_export]
1372 use super::exceptions::ValidationError;
1373
1374 #[gen_stub_pyfunction]
1386 #[pyfunction]
1387 #[allow(clippy::needless_pass_by_value)]
1388 #[pyo3(signature = (bc_bytes, *, wasm_bytes = None))]
1389 pub fn validate_qir(bc_bytes: Cow<[u8]>, wasm_bytes: Option<Cow<[u8]>>) -> PyResult<()> {
1390 crate::validate_qir(&bc_bytes, wasm_bytes.as_deref())
1391 .map_err(PyErr::new::<ValidationError, _>)
1392 }
1393
1394 #[gen_stub_pyfunction]
1405 #[pyfunction]
1406 #[allow(clippy::needless_pass_by_value)]
1407 #[allow(clippy::missing_errors_doc)]
1408 #[pyo3(signature = (bc_bytes, *, opt_level = 2, target = "aarch64", wasm_bytes = None))]
1409 pub fn qir_to_qis<'a>(
1410 bc_bytes: Cow<[u8]>,
1411 opt_level: u32,
1412 target: &'a str,
1413 wasm_bytes: Option<Cow<'a, [u8]>>,
1414 ) -> PyResult<Cow<'a, [u8]>> {
1415 let result = crate::qir_to_qis(&bc_bytes, opt_level, target, wasm_bytes.as_deref())
1416 .map_err(PyErr::new::<CompilerError, _>)?;
1417
1418 Ok(result.into())
1419 }
1420
1421 #[gen_stub_pyfunction]
1426 #[pyfunction]
1427 fn qir_ll_to_bc(ll_text: &str) -> PyResult<Cow<'_, [u8]>> {
1428 let result = crate::qir_ll_to_bc(ll_text).map_err(PyErr::new::<ValidationError, _>)?;
1429 Ok(result.into())
1430 }
1431
1432 #[gen_stub_pyfunction]
1440 #[pyfunction]
1441 #[allow(clippy::needless_pass_by_value)]
1442 fn get_entry_attributes(bc_bytes: Cow<[u8]>) -> PyResult<BTreeMap<String, Option<String>>> {
1443 crate::get_entry_attributes(&bc_bytes).map_err(PyErr::new::<ValidationError, _>)
1444 }
1445}
1446
1447#[cfg(feature = "python")]
1448define_stub_info_gatherer!(stub_info);
1449
1450#[cfg(test)]
1451mod test {
1452 #![allow(clippy::expect_used)]
1453 #![allow(clippy::unwrap_used)]
1454 use crate::{get_entry_attributes, qir_ll_to_bc};
1455
1456 #[test]
1457 fn test_get_entry_attributes() {
1458 let ll_text = std::fs::read_to_string("tests/data/base-attrs.ll")
1459 .expect("Failed to read base-attrs.ll");
1460 let bc_bytes = qir_ll_to_bc(&ll_text).unwrap();
1461 let attrs = get_entry_attributes(&bc_bytes).unwrap();
1462 assert!(matches!(attrs.get("entry_point"), Some(None)));
1463 assert_eq!(
1464 attrs.get("qir_profiles"),
1465 Some(&Some("base_profile".to_string()))
1466 );
1467 assert_eq!(
1468 attrs.get("output_labeling_schema"),
1469 Some(&Some("labeled".to_string()))
1470 );
1471 assert_eq!(
1472 attrs.get("required_num_qubits"),
1473 Some(&Some("2".to_string()))
1474 );
1475 assert_eq!(
1476 attrs.get("required_num_results"),
1477 Some(&Some("2".to_string()))
1478 );
1479 }
1480}