Skip to content

Commit f040303

Browse files
authored
Support Base Profile RIR to QIR (#1305)
This adds support for converting the subset of RIR that corresponds to base profile to QIR. Initial unit tests are included, along with test infrastructure for creating simple RIR programs.
1 parent 368b0b6 commit f040303

File tree

8 files changed

+571
-5
lines changed

8 files changed

+571
-5
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compiler/qsc_codegen/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ qsc_data_structures = { path = "../qsc_data_structures" }
1717
qsc_frontend = { path = "../qsc_frontend" }
1818
qsc_fir = { path = "../qsc_fir" }
1919
qsc_hir = { path = "../qsc_hir" }
20+
qsc_rir = { path = "../qsc_rir" }
2021

2122
[dev-dependencies]
2223
expect-test = { workspace = true }

compiler/qsc_codegen/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
pub mod qir;
45
pub mod qir_base;
56
pub mod remapper;

compiler/qsc_codegen/src/qir.rs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
#[cfg(test)]
5+
mod rir_builder;
6+
#[cfg(test)]
7+
mod tests;
8+
9+
use qsc_rir::rir;
10+
11+
/// A trait for converting a type into QIR of type `T`.
12+
/// This can be used to generate QIR strings or other representations.
13+
trait ToQir<T> {
14+
fn to_qir(&self, program: &rir::Program) -> T;
15+
}
16+
17+
impl ToQir<String> for rir::Literal {
18+
fn to_qir(&self, _program: &rir::Program) -> String {
19+
match self {
20+
rir::Literal::Bool(b) => format!("i1 {b}"),
21+
rir::Literal::Double(d) => {
22+
if (d.floor() - d.ceil()).abs() < f64::EPSILON {
23+
// The value is a whole number, which requires at least one decimal point
24+
// to differentiate it from an integer value.
25+
format!("double {d:.1}")
26+
} else {
27+
format!("double {d}")
28+
}
29+
}
30+
rir::Literal::Integer(i) => format!("i64 {i}"),
31+
rir::Literal::Pointer => "i8* null".to_string(),
32+
rir::Literal::Qubit(q) => format!("%Qubit* inttoptr (i64 {q} to %Qubit*)"),
33+
rir::Literal::Result(r) => format!("%Result* inttoptr (i64 {r} to %Result*)"),
34+
}
35+
}
36+
}
37+
38+
impl ToQir<String> for rir::Ty {
39+
fn to_qir(&self, _program: &rir::Program) -> String {
40+
match self {
41+
rir::Ty::Boolean => "i1".to_string(),
42+
rir::Ty::Double => "double".to_string(),
43+
rir::Ty::Integer => "i64".to_string(),
44+
rir::Ty::Pointer => "i8*".to_string(),
45+
rir::Ty::Qubit => "%Qubit*".to_string(),
46+
rir::Ty::Result => "%Result*".to_string(),
47+
}
48+
}
49+
}
50+
51+
impl ToQir<String> for Option<rir::Ty> {
52+
fn to_qir(&self, program: &rir::Program) -> String {
53+
match self {
54+
Some(ty) => ToQir::<String>::to_qir(ty, program),
55+
None => "void".to_string(),
56+
}
57+
}
58+
}
59+
60+
impl ToQir<String> for rir::VariableId {
61+
fn to_qir(&self, _program: &rir::Program) -> String {
62+
format!("%var_{}", self.0)
63+
}
64+
}
65+
66+
impl ToQir<String> for rir::Variable {
67+
fn to_qir(&self, program: &rir::Program) -> String {
68+
format!(
69+
"{} {}",
70+
ToQir::<String>::to_qir(&self.ty, program),
71+
ToQir::<String>::to_qir(&self.variable_id, program)
72+
)
73+
}
74+
}
75+
76+
impl ToQir<String> for rir::Value {
77+
fn to_qir(&self, program: &rir::Program) -> String {
78+
match self {
79+
rir::Value::Literal(lit) => ToQir::<String>::to_qir(lit, program),
80+
rir::Value::Variable(var) => ToQir::<String>::to_qir(var, program),
81+
}
82+
}
83+
}
84+
85+
impl ToQir<String> for rir::Instruction {
86+
fn to_qir(&self, program: &rir::Program) -> String {
87+
match self {
88+
rir::Instruction::Store(_, _) => unimplemented!("store should be removed by pass"),
89+
rir::Instruction::Call(call_id, args) => {
90+
let args = args
91+
.iter()
92+
.map(|arg| ToQir::<String>::to_qir(arg, program))
93+
.collect::<Vec<_>>()
94+
.join(", ");
95+
let callable = program.get_callable(*call_id);
96+
format!(
97+
" call {} @{}({})",
98+
ToQir::<String>::to_qir(&callable.output_type, program),
99+
callable.name,
100+
args
101+
)
102+
}
103+
rir::Instruction::Jump(_) => todo!(),
104+
rir::Instruction::Branch(_, _, _) => todo!(),
105+
rir::Instruction::Add(_, _, _) => todo!(),
106+
rir::Instruction::Sub(_, _, _) => todo!(),
107+
rir::Instruction::Mul(_, _, _) => todo!(),
108+
rir::Instruction::Div(_, _, _) => todo!(),
109+
rir::Instruction::LogicalNot(_, _) => todo!(),
110+
rir::Instruction::LogicalAnd(_, _, _) => todo!(),
111+
rir::Instruction::LogicalOr(_, _, _) => todo!(),
112+
rir::Instruction::BitwiseNot(_, _) => todo!(),
113+
rir::Instruction::BitwiseAnd(_, _, _) => todo!(),
114+
rir::Instruction::BitwiseOr(_, _, _) => todo!(),
115+
rir::Instruction::BitwiseXor(_, _, _) => todo!(),
116+
rir::Instruction::Return => " ret void".to_string(),
117+
}
118+
}
119+
}
120+
121+
impl ToQir<String> for rir::Callable {
122+
fn to_qir(&self, program: &rir::Program) -> String {
123+
let input_type = self
124+
.input_type
125+
.iter()
126+
.map(|t| ToQir::<String>::to_qir(t, program))
127+
.collect::<Vec<_>>()
128+
.join(", ");
129+
let output_type = ToQir::<String>::to_qir(&self.output_type, program);
130+
let signature = format!("{} @{}({})", output_type, self.name, input_type);
131+
let Some(entry_id) = self.body else {
132+
return format!(
133+
"declare {signature}{}",
134+
if self.name == "__quantum__qis__mz__body" {
135+
// The mz callable is a special case that needs the irreversable attribute.
136+
" #1"
137+
} else {
138+
""
139+
}
140+
);
141+
};
142+
// For now, assume a single block.
143+
let block = program.get_block(entry_id);
144+
let body = block
145+
.0
146+
.iter()
147+
.map(|instr| ToQir::<String>::to_qir(instr, program))
148+
.collect::<Vec<_>>()
149+
.join("\n");
150+
format!(
151+
"define {signature} #0 {{\nblock_{}:\n{body}\n}}",
152+
entry_id.0
153+
)
154+
}
155+
}
156+
157+
impl ToQir<String> for rir::Program {
158+
fn to_qir(&self, _program: &rir::Program) -> String {
159+
let callables = self
160+
.callables
161+
.iter()
162+
.map(|(_, callable)| ToQir::<String>::to_qir(callable, self))
163+
.collect::<Vec<_>>()
164+
.join("\n\n");
165+
let profile = if self.config.is_base() {
166+
"base_profile"
167+
} else {
168+
"adaptive_profile"
169+
};
170+
format!(
171+
include_str!("./qir/template.ll"),
172+
callables, profile, self.num_qubits, self.num_results
173+
)
174+
}
175+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use qsc_rir::rir;
5+
6+
pub fn x_decl() -> rir::Callable {
7+
rir::Callable {
8+
name: "__quantum__qis__x__body".to_string(),
9+
input_type: vec![rir::Ty::Qubit],
10+
output_type: None,
11+
body: None,
12+
}
13+
}
14+
15+
pub fn h_decl() -> rir::Callable {
16+
rir::Callable {
17+
name: "__quantum__qis__h__body".to_string(),
18+
input_type: vec![rir::Ty::Qubit],
19+
output_type: None,
20+
body: None,
21+
}
22+
}
23+
24+
pub fn cx_decl() -> rir::Callable {
25+
rir::Callable {
26+
name: "__quantum__qis__cx__body".to_string(),
27+
input_type: vec![rir::Ty::Qubit, rir::Ty::Qubit],
28+
output_type: None,
29+
body: None,
30+
}
31+
}
32+
33+
pub fn rx_decl() -> rir::Callable {
34+
rir::Callable {
35+
name: "__quantum__qis__rx__body".to_string(),
36+
input_type: vec![rir::Ty::Double, rir::Ty::Qubit],
37+
output_type: None,
38+
body: None,
39+
}
40+
}
41+
42+
pub fn mz_decl() -> rir::Callable {
43+
rir::Callable {
44+
name: "__quantum__qis__mz__body".to_string(),
45+
input_type: vec![rir::Ty::Qubit, rir::Ty::Result],
46+
output_type: None,
47+
body: None,
48+
}
49+
}
50+
51+
pub fn read_result_decl() -> rir::Callable {
52+
rir::Callable {
53+
name: "__quantum__rt__read_result".to_string(),
54+
input_type: vec![rir::Ty::Result],
55+
output_type: Some(rir::Ty::Boolean),
56+
body: None,
57+
}
58+
}
59+
60+
pub fn result_record_decl() -> rir::Callable {
61+
rir::Callable {
62+
name: "__quantum__rt__result_record_output".to_string(),
63+
input_type: vec![rir::Ty::Result, rir::Ty::Pointer],
64+
output_type: None,
65+
body: None,
66+
}
67+
}
68+
69+
pub fn array_record_decl() -> rir::Callable {
70+
rir::Callable {
71+
name: "__quantum__rt__array_record_output".to_string(),
72+
input_type: vec![rir::Ty::Integer, rir::Ty::Pointer],
73+
output_type: None,
74+
body: None,
75+
}
76+
}
77+
78+
pub fn bell_program() -> rir::Program {
79+
let mut program = rir::Program::default();
80+
program.callables.insert(rir::CallableId(0), h_decl());
81+
program.callables.insert(rir::CallableId(1), cx_decl());
82+
program.callables.insert(rir::CallableId(2), mz_decl());
83+
program
84+
.callables
85+
.insert(rir::CallableId(3), array_record_decl());
86+
program
87+
.callables
88+
.insert(rir::CallableId(4), result_record_decl());
89+
program.callables.insert(
90+
rir::CallableId(5),
91+
rir::Callable {
92+
name: "main".to_string(),
93+
input_type: vec![],
94+
output_type: None,
95+
body: Some(rir::BlockId(0)),
96+
},
97+
);
98+
program.blocks.insert(
99+
rir::BlockId(0),
100+
rir::Block(vec![
101+
rir::Instruction::Call(
102+
rir::CallableId(0),
103+
vec![rir::Value::Literal(rir::Literal::Qubit(0))],
104+
),
105+
rir::Instruction::Call(
106+
rir::CallableId(1),
107+
vec![
108+
rir::Value::Literal(rir::Literal::Qubit(0)),
109+
rir::Value::Literal(rir::Literal::Qubit(1)),
110+
],
111+
),
112+
rir::Instruction::Call(
113+
rir::CallableId(2),
114+
vec![
115+
rir::Value::Literal(rir::Literal::Qubit(0)),
116+
rir::Value::Literal(rir::Literal::Result(0)),
117+
],
118+
),
119+
rir::Instruction::Call(
120+
rir::CallableId(2),
121+
vec![
122+
rir::Value::Literal(rir::Literal::Qubit(1)),
123+
rir::Value::Literal(rir::Literal::Result(1)),
124+
],
125+
),
126+
rir::Instruction::Call(
127+
rir::CallableId(3),
128+
vec![
129+
rir::Value::Literal(rir::Literal::Integer(2)),
130+
rir::Value::Literal(rir::Literal::Pointer),
131+
],
132+
),
133+
rir::Instruction::Call(
134+
rir::CallableId(4),
135+
vec![
136+
rir::Value::Literal(rir::Literal::Result(0)),
137+
rir::Value::Literal(rir::Literal::Pointer),
138+
],
139+
),
140+
rir::Instruction::Call(
141+
rir::CallableId(4),
142+
vec![
143+
rir::Value::Literal(rir::Literal::Result(1)),
144+
rir::Value::Literal(rir::Literal::Pointer),
145+
],
146+
),
147+
rir::Instruction::Return,
148+
]),
149+
);
150+
program.num_qubits = 2;
151+
program.num_results = 2;
152+
program.config.defer_measurements = true;
153+
program.config.remap_qubits_on_reuse = true;
154+
program
155+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
%Result = type opaque
2+
%Qubit = type opaque
3+
4+
{}
5+
6+
attributes #0 = {{ "entry_point" "output_labeling_schema" "qir_profiles"="{}" "required_num_qubits"="{}" "required_num_results"="{}" }}
7+
attributes #1 = {{ "irreversible" }}
8+
9+
; module flags
10+
11+
!llvm.module.flags = !{{!0, !1, !2, !3}}
12+
13+
!0 = !{{i32 1, !"qir_major_version", i32 1}}
14+
!1 = !{{i32 7, !"qir_minor_version", i32 0}}
15+
!2 = !{{i32 1, !"dynamic_qubit_management", i1 false}}
16+
!3 = !{{i32 1, !"dynamic_result_management", i1 false}}

0 commit comments

Comments
 (0)