// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #[cfg(test)] mod instruction_tests; #[cfg(test)] mod tests; use qsc_data_structures::{attrs::Attributes, target::TargetCapabilityFlags}; use qsc_rir::{ rir::{self, ConditionCode, FcmpConditionCode}, utils::get_all_block_successors, }; use std::fmt::Write; use super::name::llvm_global_name; /// A trait for converting a type into QIR of type `T`. /// This can be used to generate QIR strings or other representations. pub trait ToQir { fn to_qir(&self, program: &rir::Program) -> T; } impl ToQir for rir::Literal { fn to_qir(&self, _program: &rir::Program) -> String { match self { rir::Literal::Bool(b) => format!("i1 {b}"), rir::Literal::Double(d) => { if (d.floor() - d.ceil()).abs() < f64::EPSILON { // The value is a whole number, which requires at least one decimal point // to differentiate it from an integer value. format!("double {d:.1}") } else { format!("double {d}") } } rir::Literal::Integer(i) => format!("i64 {i}"), rir::Literal::NullPointer => "i8* null".to_string(), rir::Literal::Qubit(q) => format!("%Qubit* inttoptr (i64 {q} to %Qubit*)"), rir::Literal::Result(r) => format!("%Result* inttoptr (i64 {r} to %Result*)"), rir::Literal::Tag(idx, len) => { let len = len + 1; // +1 for the null terminator format!( "i8* getelementptr inbounds ([{len} x i8], [{len} x i8]* @{idx}, i64 0, i64 0)" ) } rir::Literal::Array(_) => { panic!("array literals are not supported in QIR v1 generation") } } } } impl ToQir for rir::Ty { fn to_qir(&self, program: &rir::Program) -> String { match self { rir::Ty::Prim(prim) => ToQir::::to_qir(prim, program), rir::Ty::Array(..) => { unimplemented!("array types are not supported in QIR v1 generation") } } } } impl ToQir for rir::Prim { fn to_qir(&self, _program: &rir::Program) -> String { get_prim_ty(*self).to_owned() } } impl ToQir for Option { fn to_qir(&self, program: &rir::Program) -> String { match self { Some(ty) => ToQir::::to_qir(ty, program), None => "void".to_string(), } } } impl ToQir for rir::VariableId { fn to_qir(&self, _program: &rir::Program) -> String { format!("%var_{}", self.0) } } impl ToQir for rir::Variable { fn to_qir(&self, program: &rir::Program) -> String { format!( "{} {}", ToQir::::to_qir(&self.ty, program), ToQir::::to_qir(&self.variable_id, program) ) } } impl ToQir for rir::Operand { fn to_qir(&self, program: &rir::Program) -> String { match self { rir::Operand::Literal(lit) => ToQir::::to_qir(lit, program), rir::Operand::Variable(var) => ToQir::::to_qir(var, program), } } } impl ToQir for rir::FcmpConditionCode { fn to_qir(&self, _program: &rir::Program) -> String { match self { rir::FcmpConditionCode::False => "false".to_string(), rir::FcmpConditionCode::OrderedAndEqual => "oeq".to_string(), rir::FcmpConditionCode::OrderedAndGreaterThan => "ogt".to_string(), rir::FcmpConditionCode::OrderedAndGreaterThanOrEqual => "oge".to_string(), rir::FcmpConditionCode::OrderedAndLessThan => "olt".to_string(), rir::FcmpConditionCode::OrderedAndLessThanOrEqual => "ole".to_string(), rir::FcmpConditionCode::OrderedAndNotEqual => "one".to_string(), rir::FcmpConditionCode::Ordered => "ord".to_string(), rir::FcmpConditionCode::UnorderedOrEqual => "ueq".to_string(), rir::FcmpConditionCode::UnorderedOrGreaterThan => "ugt".to_string(), rir::FcmpConditionCode::UnorderedOrGreaterThanOrEqual => "uge".to_string(), rir::FcmpConditionCode::UnorderedOrLessThan => "ult".to_string(), rir::FcmpConditionCode::UnorderedOrLessThanOrEqual => "ule".to_string(), rir::FcmpConditionCode::UnorderedOrNotEqual => "une".to_string(), rir::FcmpConditionCode::Unordered => "uno".to_string(), rir::FcmpConditionCode::True => "true".to_string(), } } } impl ToQir for rir::ConditionCode { fn to_qir(&self, _program: &rir::Program) -> String { match self { rir::ConditionCode::Eq => "eq".to_string(), rir::ConditionCode::Ne => "ne".to_string(), rir::ConditionCode::Sgt => "sgt".to_string(), rir::ConditionCode::Sge => "sge".to_string(), rir::ConditionCode::Slt => "slt".to_string(), rir::ConditionCode::Sle => "sle".to_string(), } } } impl ToQir for rir::Instruction { fn to_qir(&self, program: &rir::Program) -> String { match self { rir::Instruction::Add(lhs, rhs, variable) => { binop_to_qir("add", lhs, rhs, *variable, program) } rir::Instruction::Ashr(lhs, rhs, variable) => { binop_to_qir("ashr", lhs, rhs, *variable, program) } rir::Instruction::BitwiseAnd(lhs, rhs, variable) => { simple_bitwise_to_qir("and", lhs, rhs, *variable, program) } rir::Instruction::BitwiseNot(value, variable) => { bitwise_not_to_qir(value, *variable, program) } rir::Instruction::BitwiseOr(lhs, rhs, variable) => { simple_bitwise_to_qir("or", lhs, rhs, *variable, program) } rir::Instruction::BitwiseXor(lhs, rhs, variable) => { simple_bitwise_to_qir("xor", lhs, rhs, *variable, program) } rir::Instruction::Branch(cond, true_id, false_id, _) => { format!( " br {}, label %{}, label %{}", ToQir::::to_qir(cond, program), ToQir::::to_qir(true_id, program), ToQir::::to_qir(false_id, program) ) } rir::Instruction::Call(call_id, args, output, _) => { call_to_qir(args, *call_id, *output, program) } rir::Instruction::Convert(operand, variable) => { convert_to_qir(operand, *variable, program) } rir::Instruction::Fadd(lhs, rhs, variable) => { fbinop_to_qir("fadd", lhs, rhs, *variable, program) } rir::Instruction::Fdiv(lhs, rhs, variable) => { fbinop_to_qir("fdiv", lhs, rhs, *variable, program) } rir::Instruction::Frem(lhs, rhs, variable) => { fbinop_to_qir("frem", lhs, rhs, *variable, program) } rir::Instruction::Fmul(lhs, rhs, variable) => { fbinop_to_qir("fmul", lhs, rhs, *variable, program) } rir::Instruction::Fsub(lhs, rhs, variable) => { fbinop_to_qir("fsub", lhs, rhs, *variable, program) } rir::Instruction::LogicalAnd(lhs, rhs, variable) => { logical_binop_to_qir("and", lhs, rhs, *variable, program) } rir::Instruction::LogicalNot(value, variable) => { logical_not_to_qir(value, *variable, program) } rir::Instruction::LogicalOr(lhs, rhs, variable) => { logical_binop_to_qir("or", lhs, rhs, *variable, program) } rir::Instruction::Mul(lhs, rhs, variable) => { binop_to_qir("mul", lhs, rhs, *variable, program) } rir::Instruction::Fcmp(op, lhs, rhs, variable) => { fcmp_to_qir(*op, lhs, rhs, *variable, program) } rir::Instruction::Icmp(op, lhs, rhs, variable) => { icmp_to_qir(*op, lhs, rhs, *variable, program) } rir::Instruction::Jump(block_id) => { format!(" br label %{}", ToQir::::to_qir(block_id, program)) } rir::Instruction::Phi(args, variable) => phi_to_qir(args, *variable, program), rir::Instruction::Return(_) => " ret i64 0".to_string(), rir::Instruction::Sdiv(lhs, rhs, variable) => { binop_to_qir("sdiv", lhs, rhs, *variable, program) } rir::Instruction::Shl(lhs, rhs, variable) => { binop_to_qir("shl", lhs, rhs, *variable, program) } rir::Instruction::Srem(lhs, rhs, variable) => { binop_to_qir("srem", lhs, rhs, *variable, program) } rir::Instruction::Store(_, _) => unimplemented!("store should be removed by pass"), rir::Instruction::Sub(lhs, rhs, variable) => { binop_to_qir("sub", lhs, rhs, *variable, program) } rir::Instruction::Alloca(..) | rir::Instruction::Load(..) | rir::Instruction::Index(..) => { unimplemented!("advanced instructions are not supported in QIR v1 generation") } } } } fn convert_to_qir( operand: &rir::Operand, variable: rir::Variable, program: &rir::Program, ) -> String { let operand_ty = get_value_ty(operand); let var_ty = get_variable_ty(variable); assert_ne!( operand_ty, var_ty, "input/output types ({operand_ty}, {var_ty}) should not match in convert" ); let convert_instr = match (operand_ty, var_ty) { ("i64", "double") => "sitofp i64", ("double", "i64") => "fptosi double", _ => panic!("unsupported conversion from {operand_ty} to {var_ty} in convert instruction"), }; format!( " {} = {convert_instr} {} to {var_ty}", ToQir::::to_qir(&variable.variable_id, program), get_value_as_str(operand, program), ) } fn logical_not_to_qir( value: &rir::Operand, variable: rir::Variable, program: &rir::Program, ) -> String { let value_ty = get_value_ty(value); let var_ty = get_variable_ty(variable); assert_eq!( value_ty, var_ty, "mismatched input/output types ({value_ty}, {var_ty}) for not" ); assert_eq!(var_ty, "i1", "unsupported type {var_ty} for not"); format!( " {} = xor i1 {}, true", ToQir::::to_qir(&variable.variable_id, program), get_value_as_str(value, program) ) } fn logical_binop_to_qir( op: &str, lhs: &rir::Operand, rhs: &rir::Operand, variable: rir::Variable, program: &rir::Program, ) -> String { let lhs_ty = get_value_ty(lhs); let rhs_ty = get_value_ty(rhs); let var_ty = get_variable_ty(variable); assert_eq!( lhs_ty, rhs_ty, "mismatched input types ({lhs_ty}, {rhs_ty}) for {op}" ); assert_eq!( lhs_ty, var_ty, "mismatched input/output types ({lhs_ty}, {var_ty}) for {op}" ); assert_eq!(var_ty, "i1", "unsupported type {var_ty} for {op}"); format!( " {} = {op} {var_ty} {}, {}", ToQir::::to_qir(&variable.variable_id, program), get_value_as_str(lhs, program), get_value_as_str(rhs, program) ) } fn bitwise_not_to_qir( value: &rir::Operand, variable: rir::Variable, program: &rir::Program, ) -> String { let value_ty = get_value_ty(value); let var_ty = get_variable_ty(variable); assert_eq!( value_ty, var_ty, "mismatched input/output types ({value_ty}, {var_ty}) for not" ); assert_eq!(var_ty, "i64", "unsupported type {var_ty} for not"); format!( " {} = xor {var_ty} {}, -1", ToQir::::to_qir(&variable.variable_id, program), get_value_as_str(value, program) ) } fn call_to_qir( args: &[rir::Operand], call_id: rir::CallableId, output: Option, program: &rir::Program, ) -> String { let args = args .iter() .map(|arg| ToQir::::to_qir(arg, program)) .collect::>() .join(", "); let callable = program.get_callable(call_id); let callable_name = llvm_global_name(&callable.name); if let Some(output) = output { format!( " {} = call {} {}({args})", ToQir::::to_qir(&output.variable_id, program), ToQir::::to_qir(&callable.output_type, program), callable_name ) } else { format!( " call {} {}({args})", ToQir::::to_qir(&callable.output_type, program), callable_name ) } } fn fcmp_to_qir( op: FcmpConditionCode, lhs: &rir::Operand, rhs: &rir::Operand, variable: rir::Variable, program: &rir::Program, ) -> String { let lhs_ty = get_value_ty(lhs); let rhs_ty = get_value_ty(rhs); let var_ty = get_variable_ty(variable); assert_eq!( lhs_ty, rhs_ty, "mismatched input types ({lhs_ty}, {rhs_ty}) for fcmp {op}" ); assert_eq!(var_ty, "i1", "unsupported output type {var_ty} for fcmp"); format!( " {} = fcmp {} {lhs_ty} {}, {}", ToQir::::to_qir(&variable.variable_id, program), ToQir::::to_qir(&op, program), get_value_as_str(lhs, program), get_value_as_str(rhs, program) ) } fn icmp_to_qir( op: ConditionCode, lhs: &rir::Operand, rhs: &rir::Operand, variable: rir::Variable, program: &rir::Program, ) -> String { let lhs_ty = get_value_ty(lhs); let rhs_ty = get_value_ty(rhs); let var_ty = get_variable_ty(variable); assert_eq!( lhs_ty, rhs_ty, "mismatched input types ({lhs_ty}, {rhs_ty}) for icmp {op}" ); assert_eq!(var_ty, "i1", "unsupported output type {var_ty} for icmp"); format!( " {} = icmp {} {lhs_ty} {}, {}", ToQir::::to_qir(&variable.variable_id, program), ToQir::::to_qir(&op, program), get_value_as_str(lhs, program), get_value_as_str(rhs, program) ) } fn binop_to_qir( op: &str, lhs: &rir::Operand, rhs: &rir::Operand, variable: rir::Variable, program: &rir::Program, ) -> String { let lhs_ty = get_value_ty(lhs); let rhs_ty = get_value_ty(rhs); let var_ty = get_variable_ty(variable); assert_eq!( lhs_ty, rhs_ty, "mismatched input types ({lhs_ty}, {rhs_ty}) for {op}" ); assert_eq!( lhs_ty, var_ty, "mismatched input/output types ({lhs_ty}, {var_ty}) for {op}" ); assert_eq!(var_ty, "i64", "unsupported type {var_ty} for {op}"); format!( " {} = {op} {var_ty} {}, {}", ToQir::::to_qir(&variable.variable_id, program), get_value_as_str(lhs, program), get_value_as_str(rhs, program) ) } fn fbinop_to_qir( op: &str, lhs: &rir::Operand, rhs: &rir::Operand, variable: rir::Variable, program: &rir::Program, ) -> String { let lhs_ty = get_value_ty(lhs); let rhs_ty = get_value_ty(rhs); let var_ty = get_variable_ty(variable); assert_eq!( lhs_ty, rhs_ty, "mismatched input types ({lhs_ty}, {rhs_ty}) for {op}" ); assert_eq!( lhs_ty, var_ty, "mismatched input/output types ({lhs_ty}, {var_ty}) for {op}" ); assert_eq!(var_ty, "double", "unsupported type {var_ty} for {op}"); format!( " {} = {op} {var_ty} {}, {}", ToQir::::to_qir(&variable.variable_id, program), get_value_as_str(lhs, program), get_value_as_str(rhs, program) ) } fn simple_bitwise_to_qir( op: &str, lhs: &rir::Operand, rhs: &rir::Operand, variable: rir::Variable, program: &rir::Program, ) -> String { let lhs_ty = get_value_ty(lhs); let rhs_ty = get_value_ty(rhs); let var_ty = get_variable_ty(variable); assert_eq!( lhs_ty, rhs_ty, "mismatched input types ({lhs_ty}, {rhs_ty}) for {op}" ); assert_eq!( lhs_ty, var_ty, "mismatched input/output types ({lhs_ty}, {var_ty}) for {op}" ); assert_eq!(var_ty, "i64", "unsupported type {var_ty} for {op}"); format!( " {} = {op} {var_ty} {}, {}", ToQir::::to_qir(&variable.variable_id, program), get_value_as_str(lhs, program), get_value_as_str(rhs, program) ) } fn phi_to_qir( args: &[(rir::Operand, rir::BlockId)], variable: rir::Variable, program: &rir::Program, ) -> String { assert!( !args.is_empty(), "phi instruction should have at least one argument" ); let var_ty = get_variable_ty(variable); let args = args .iter() .map(|(arg, block_id)| { let arg_ty = get_value_ty(arg); assert_eq!( arg_ty, var_ty, "mismatched types ({var_ty} [... {arg_ty}]) for phi" ); format!( "[{}, %{}]", get_value_as_str(arg, program), ToQir::::to_qir(block_id, program) ) }) .collect::>() .join(", "); format!( " {} = phi {var_ty} {args}", ToQir::::to_qir(&variable.variable_id, program) ) } fn get_value_as_str(value: &rir::Operand, program: &rir::Program) -> String { match value { rir::Operand::Literal(lit) => match lit { rir::Literal::Bool(b) => format!("{b}"), rir::Literal::Double(d) => { if (d.floor() - d.ceil()).abs() < f64::EPSILON { // The value is a whole number, which requires at least one decimal point // to differentiate it from an integer value. format!("{d:.1}") } else { format!("{d}") } } rir::Literal::Integer(i) => format!("{i}"), rir::Literal::NullPointer => "null".to_string(), rir::Literal::Qubit(q) => format!("{q}"), rir::Literal::Result(r) => format!("{r}"), rir::Literal::Tag(..) => panic!( "tag literals should not be used as string values outside of output recording" ), rir::Literal::Array(..) => { panic!("array literals are not supported in QIR v1 generation") } }, rir::Operand::Variable(var) => ToQir::::to_qir(&var.variable_id, program), } } fn get_value_ty(lhs: &rir::Operand) -> &str { match lhs { rir::Operand::Literal(lit) => match lit { rir::Literal::Integer(_) => "i64", rir::Literal::Bool(_) => "i1", rir::Literal::Double(_) => get_f64_ty(), rir::Literal::Qubit(_) => "%Qubit*", rir::Literal::Result(_) => "%Result*", rir::Literal::NullPointer | rir::Literal::Tag(..) => "i8*", rir::Literal::Array(_) => { panic!("array literals are not supported in QIR v1 generation") } }, rir::Operand::Variable(var) => get_variable_ty(*var), } } fn get_variable_ty(variable: rir::Variable) -> &'static str { match variable.ty { rir::Ty::Prim(prim) => get_prim_ty(prim), rir::Ty::Array(..) => unimplemented!("array types are not supported in QIR v1 generation"), } } fn get_prim_ty(prim: rir::Prim) -> &'static str { match prim { rir::Prim::Integer => "i64", rir::Prim::Boolean => "i1", rir::Prim::Double => get_f64_ty(), rir::Prim::Qubit => "%Qubit*", rir::Prim::Result => "%Result*", rir::Prim::Pointer => "i8*", } } /// phi only supports "Floating-Point Types" which are defined as: /// - `half` (`f16`) /// - `bfloat` /// - `float` (`f32`) /// - `double` (`f64`) /// - `fp128` /// /// We only support `f64`, so we break the pattern used for integers /// and have to use `double` here. /// /// This conflicts with the QIR spec which says f64. Need to follow up on this. fn get_f64_ty() -> &'static str { "double" } impl ToQir for rir::BlockId { fn to_qir(&self, _program: &rir::Program) -> String { format!("block_{}", self.0) } } impl ToQir for rir::Block { fn to_qir(&self, program: &rir::Program) -> String { self.0 .iter() .map(|instr| ToQir::::to_qir(instr, program)) .collect::>() .join("\n") } } impl ToQir for rir::Callable { fn to_qir(&self, program: &rir::Program) -> String { let input_type = self .input_type .iter() .map(|t| ToQir::::to_qir(t, program)) .collect::>() .join(", "); let output_type = ToQir::::to_qir(&self.output_type, program); let Some(entry_id) = self.body else { let callable_name = llvm_global_name(&self.name); return format!( "declare {output_type} {callable_name}({input_type}){}", match self.call_type { rir::CallableType::Measurement | rir::CallableType::Reset => { // These callables are a special case that need the irreversible attribute. " #1" } rir::CallableType::NoiseIntrinsic => " #2", _ => "", } ); }; let mut body = String::new(); let mut all_blocks = vec![entry_id]; all_blocks.extend(get_all_block_successors(entry_id, program)); for block_id in all_blocks { let block = program.get_block(block_id); write!( body, "{}:\n{}\n", ToQir::::to_qir(&block_id, program), ToQir::::to_qir(block, program) ) .expect("writing to string should succeed"); } assert!( input_type.is_empty(), "entry point should not have an input" ); format!("define {output_type} @ENTRYPOINT__main() #0 {{\n{body}}}") } } impl ToQir for rir::Program { fn to_qir(&self, _program: &rir::Program) -> String { let callables = self .callables .iter() .map(|(_, callable)| ToQir::::to_qir(callable, self)) .collect::>() .join("\n\n"); let profile = if self.config.is_base() { "base_profile" } else { "adaptive_profile" }; assert!( self.array_literals.is_empty(), "array literals are not supported in QIR v1 generation" ); let mut constants = String::default(); for (idx, tag) in self.tags.iter().enumerate() { // We need to add the tag as a global constant. writeln!( constants, "@{idx} = internal constant [{} x i8] c\"{tag}\\00\"", tag.len() + 1 ) .expect("writing to string should succeed"); } let body = format!( include_str!("./v1/template.ll"), constants, callables, profile, self.num_qubits, self.num_results, get_additional_module_attributes(self) ); let flags = get_module_metadata(self); body + "\n" + &flags } } fn get_additional_module_attributes(program: &rir::Program) -> String { let mut attrs = String::new(); if program.attrs.contains(Attributes::QdkNoise) { attrs.push_str("\nattributes #2 = { \"qdk_noise\" }"); } attrs } /// Create the module metadata for the given program. /// creating the `llvm.module.flags` and its associated values. fn get_module_metadata(program: &rir::Program) -> String { let mut flags = String::new(); // push the default attrs, we don't have any config values // for now that would change any of them. flags.push_str( r#" !0 = !{i32 1, !"qir_major_version", i32 1} !1 = !{i32 7, !"qir_minor_version", i32 0} !2 = !{i32 1, !"dynamic_qubit_management", i1 false} !3 = !{i32 1, !"dynamic_result_management", i1 false} "#, ); let mut index = 4; // If we are not in the base profile, we need to add the capabilities // associated with the adaptive profile. if !program.config.is_base() { // loop through the capabilities and add them to the metadata // for values that we can generate. for cap in program.config.capabilities.iter() { match cap { TargetCapabilityFlags::IntegerComputations => { // Use `5` as the flag to signify "Append" mode. See https://llvm.org/docs/LangRef.html#module-flags-metadata writeln!( flags, "!{index} = !{{i32 5, !\"int_computations\", !{{!\"i64\"}}}}", ) .expect("writing to string should succeed"); index += 1; } TargetCapabilityFlags::FloatingPointComputations => { // Use `5` as the flag to signify "Append" mode. See https://llvm.org/docs/LangRef.html#module-flags-metadata writeln!( flags, "!{index} = !{{i32 5, !\"float_computations\", !{{!\"double\"}}}}", ) .expect("writing to string should succeed"); index += 1; } _ => {} } } } let mut metadata_def = String::new(); metadata_def.push_str("!llvm.module.flags = !{"); for i in 0..index - 1 { write!(metadata_def, "!{i}, ").expect("writing to string should succeed"); } writeln!(metadata_def, "!{}}}", index - 1).expect("writing to string should succeed"); metadata_def + &flags }