Skip to main content

qir_qis/
lib.rs

1#![deny(clippy::panic)]
2// Following from https://corrode.dev/blog/pitfalls-of-safe-rust/#clippy-can-prevent-many-of-these-issues
3// and https://corrode.dev/blog/defensive-programming/#clippy-lints-for-defensive-programming
4// Arithmetic
5#![deny(arithmetic_overflow)] // Prevent operations that would cause integer overflow
6#![deny(clippy::arithmetic_side_effects)] // Detect arithmetic operations with potential side effects
7#![deny(clippy::cast_possible_truncation)] // Detect when casting might truncate a value
8#![deny(clippy::cast_possible_wrap)] // Detect when casting might cause value to wrap around
9#![deny(clippy::cast_precision_loss)] // Detect when casting might lose precision
10#![deny(clippy::cast_sign_loss)] // Detect when casting might lose sign information
11#![deny(clippy::checked_conversions)] // Suggest using checked conversions between numeric types
12#![deny(clippy::integer_division)] // Highlight potential bugs from integer division truncation
13#![deny(clippy::unchecked_time_subtraction)] // Ensure duration subtraction won't cause underflow
14
15// Unwraps
16#![deny(clippy::expect_used)] // Prevent using .expect() which can cause panics
17#![deny(clippy::option_env_unwrap)] // Prevent unwrapping environment variables which might be absent
18#![deny(clippy::panicking_unwrap)] // Prevent unwrap on values known to cause panics
19#![deny(clippy::unwrap_used)] // Prevent using .unwrap() which can cause panics
20
21// Path handling
22#![deny(clippy::join_absolute_paths)] // Prevent issues when joining paths with absolute paths
23
24// Serialization issues
25#![deny(clippy::serde_api_misuse)] // Prevent incorrect usage of Serde's serialization/deserialization API
26
27// Unbounded input
28#![deny(clippy::uninit_vec)] // Prevent creating uninitialized vectors which is unsafe
29
30// Unsafe code detection
31#![deny(clippy::transmute_ptr_to_ref)] // Prevent unsafe transmutation from pointers to references
32#![deny(clippy::transmute_undefined_repr)] // Detect transmutes with potentially undefined representations
33#![deny(unnecessary_transmutes)] // Prevent unsafe transmutation
34
35// Defensive programming
36#![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        // Native gates
79        "__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        // mz + reset
85        "__quantum__qis__mresetz__body",
86        // Synonyms for native gates
87        "__quantum__qis__u1q__body", // rxy
88        "__quantum__qis__m__body",   // mz
89        // Decomposed to native gates
90        "__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        // Note: barrier instructions with arbitrary arity are validated separately
105    ];
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    /// The module flags that we care about are:
144    /// 0: `qir_major_version`
145    /// 1: `qir_minor_version`
146    /// 2: `dynamic_qubit_management`
147    /// 3: `dynamic_result_management`
148    #[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        /// Create a new `ModuleFlagState` with the required flags.
157        const fn new() -> Self {
158            Self {
159                found: [false; REQ_FLAG_COUNT],
160                wrong: [false; REQ_FLAG_COUNT],
161                required: [
162                    // (index, name, expected_value, expected_str)
163                    (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        /// Set the flag state for a given module flag.
172        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        // Extract required_num_qubits for barrier validation
198        let required_num_qubits = get_required_num_qubits(entry_fn);
199
200        for fun in module.get_functions() {
201            if fun == entry_fn {
202                // Skip the entry function
203                continue;
204            }
205            let fn_name = fun.get_name().to_str().unwrap_or("");
206            if fn_name.starts_with("__quantum__qis__") {
207                // Check for barrier instructions with arbitrary arity (barrier1, barrier2, ...)
208                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                        // Validate barrier arity doesn't exceed module's required_num_qubits
213                        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                // IR defined functions
245                // TODO: allowed only if "ir_functions" is true
246                if fn_name == "main" {
247                    errors.push("IR defined function cannot be called `main`".to_string());
248                }
249                // See whether a function returns a pointer type
250                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    /// Primary translation loop over the entry function for translation to QIS.
306    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                    // IR defined function calls
360                    if f.count_basic_blocks() > 0 {
361                        // INIT_QARRAY_FN is a frequently invoked helper for array initialization;
362                        // skipping debug logs for it avoids excessive log noise while preserving
363                        // useful debug information for other IR-defined functions.
364                        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                    // Check if this is a GPU function (has cudaq-fnid attribute)
374                    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                    // Check if this is a WASM function (has wasm attribute)
385                    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                // External context calls are left as-is for downstream processing
487                log::debug!("___get_wasm_context found, leaving as-is for downstream processing");
488            }
489            _ => {
490                // Ignore already converted Qtm QIS functions
491                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        // Extract qubit and result indices
517        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        // Load qubit handle
522        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        // Create ___lazy_measure call
535        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        // Store measurement result
554        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
562            create_reset_call(ctx, module, &builder, q_handle);
563        }
564
565        // Remove original call
566        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        // Extract qubit index
578        let call_args: Vec<BasicValueEnum> = extract_operands(instr)?;
579        let qubit_ptr = call_args[0].into_pointer_value();
580
581        // Load qubit handle
582        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
598        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        // Extract qubit arguments (excluding the last operand which is the function pointer)
628        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        // Load qubit handles into an array
643        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        // void ___barrier(i64* %qbs, i64 %qbs_len)
699        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        // Compute or reuse the bool value for this meas_handle
753        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                    // Decrement refcount
778                    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                    // Store the result in SSA for reuse
790                    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            // "__quantum__rt__result_record_output"
807            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(), // i8*
817                        ctx.i64_type().into(),                                  // i64
818                        ctx.bool_type().into(),                                 // i1
819                    ],
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        // Get the print function type based on the value type
860        let ret_type = ctx.void_type();
861        let param_types = &[
862            ctx.i8_type().ptr_type(AddressSpace::default()).into(), // i8*
863            ctx.i64_type().into(),                                  // i64
864            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        // Parse the label from the global string (format: USER:RESULT:tag)
880        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            // fun get_current_shot() -> uint
906            "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            // void random_seed(uint64_t seq)
942            "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            // uint32_t random_int()
964            "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            // double random_float()
997            "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            // uint32_t random_rng(uint32_t bound)
1033            "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            // void random_advance(uint64_t delta)
1069            "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
1082/// Core QIR to QIS translation logic.
1083///
1084/// # Arguments
1085/// - `bc_bytes` - The QIR bytes to translate.
1086/// - `opt_level` - The optimization level to use (0-3).
1087/// - `target` - Target architecture ("aarch64", "x86-64", "native").
1088/// - `wasm_bytes` - Optional WASM bytes for Wasm codegen.
1089///
1090/// # Errors
1091/// Returns an error string if the translation fails.
1092pub 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    // Handle IR defined functions that take qubits
1139    process_ir_defined_q_fns(&ctx, &module, entry_fn)?;
1140
1141    free_all_qubits(&ctx, &module, entry_fn, qubit_array)?;
1142
1143    // Add qmain wrapper that calls setup, entry function, and teardown
1144    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    // Clean up the translated module
1151    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    // TODO: remove global module metadata
1160    // seems inkwell doesn't support this yet
1161
1162    // Add metadata to the module
1163    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/// Extract WASM function mapping from the given WASM bytes.
1177///
1178/// # Errors
1179/// Returns an error string if parsing fails.
1180#[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
1202/// Validate the given QIR bitcode.
1203///
1204/// # Arguments
1205/// - `bc_bytes` - The QIR bytes to validate.
1206/// - `wasm_bytes` - Optional WASM bytes to validate against.
1207///
1208/// # Errors
1209/// Returns an error string if validation fails.
1210pub 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        // Enforce required attributes
1232        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        // Check values for non-zero qubits/results
1246        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
1277/// Convert QIR LLVM IR text to QIR bitcode bytes.
1278///
1279/// # Errors
1280/// Returns an error string if the LLVM IR is invalid.
1281pub 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
1293/// Get QIR entry point function attributes.
1294///
1295/// These attributes are used to generate METADATA records in QIR output schemas.
1296/// This function assumes that QIR has been validated using `validate_qir`.
1297///
1298/// # Errors
1299/// Returns an error string if the input bitcode is invalid.
1300pub 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    /// Validate the given QIR.
1375    ///
1376    /// # Arguments
1377    /// - `bc_bytes` - The QIR bytes to validate.
1378    /// - `wasm_bytes` - Optional WASM bytes to validate against.
1379    ///
1380    /// # Errors
1381    /// Returns a `ValidationError`:
1382    /// - If the QIR is invalid.
1383    /// - If the WASM module is invalid.
1384    /// - If a QIR-referenced WASM function is missing from the WASM module.
1385    #[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    /// Translate QIR bitcode to Quantinuum QIS.
1395    ///
1396    /// # Arguments
1397    /// - `bc_bytes` - The QIR bytes to translate.
1398    /// - `opt_level` - The optimization level to use (0-3). Default is 2.
1399    /// - `target` - Target architecture (default: "aarch64"; options: "x86-64", "native").
1400    /// - `wasm_bytes` - Optional WASM bytes for Wasm codegen.
1401    ///
1402    /// # Errors
1403    /// Returns a `CompilerError` if the translation fails.
1404    #[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    /// Convert QIR LLVM IR to QIR bitcode.
1422    ///
1423    /// # Errors
1424    /// Returns a `ValidationError` if the LLVM IR is invalid.
1425    #[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    /// Get QIR entry point function attributes.
1433    ///
1434    /// These attributes are used to generate METADATA records in QIR output schemas.
1435    /// This function assumes that QIR has been validated using `validate_qir`.
1436    ///
1437    /// # Errors
1438    /// Returns a `ValidationError` if the input bitcode is invalid.
1439    #[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}