microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.0.33

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

compiler/qsc_codegen/src/qir_base.rs

533lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use num_bigint::BigUint;
8use num_complex::Complex;
9use qsc_data_structures::index_map::IndexMap;
10use qsc_eval::{
11 backend::Backend,
12 debug::{map_hir_package_to_fir, Frame},
13 eval_expr,
14 output::GenericReceiver,
15 val::{GlobalId, Value},
16 Env, Error, Global, NodeLookup, State,
17};
18use qsc_fir::fir::{BlockId, ExprId, ItemKind, PackageId, PatId, StmtId};
19use qsc_frontend::compile::PackageStore;
20use qsc_hir::hir::{self};
21use std::fmt::{Display, Write};
22
23/// # Errors
24///
25/// This function will return an error if execution was unable to complete.
26/// # Panics
27///
28/// This function will panic if compiler state is invalid or in out-of-memory conditions.
29pub fn generate_qir(
30 store: &PackageStore,
31 package: hir::PackageId,
32) -> std::result::Result<String, (Error, Vec<Frame>)> {
33 let mut fir_lowerer = qsc_eval::lower::Lowerer::new();
34 let mut fir_store = IndexMap::new();
35 let package = map_hir_package_to_fir(package);
36 let mut sim = BaseProfSim::default();
37
38 for (id, unit) in store {
39 fir_store.insert(
40 map_hir_package_to_fir(id),
41 fir_lowerer.lower_package(&unit.package),
42 );
43 }
44
45 let unit = fir_store.get(package).expect("store should have package");
46 let entry_expr = unit.entry.expect("package should have entry");
47
48 let mut stdout = std::io::sink();
49 let mut out = GenericReceiver::new(&mut stdout);
50 let result = eval_expr(
51 &mut State::new(package),
52 entry_expr,
53 &Lookup {
54 fir_store: &fir_store,
55 },
56 &mut Env::with_empty_scope(),
57 &mut sim,
58 &mut out,
59 );
60 match result {
61 Ok(val) => Ok(sim.finish(&val)),
62 Err((err, stack)) => Err((err, stack)),
63 }
64}
65
66struct Lookup<'a> {
67 fir_store: &'a IndexMap<PackageId, qsc_fir::fir::Package>,
68}
69
70impl<'a> Lookup<'a> {
71 fn get_package(&self, package: PackageId) -> &qsc_fir::fir::Package {
72 self.fir_store
73 .get(package)
74 .expect("Package should be in FIR store")
75 }
76}
77
78impl<'a> NodeLookup for Lookup<'a> {
79 fn get(&self, id: GlobalId) -> Option<Global<'a>> {
80 get_global(self.fir_store, id)
81 }
82 fn get_block(&self, package: PackageId, id: BlockId) -> &qsc_fir::fir::Block {
83 self.get_package(package)
84 .blocks
85 .get(id)
86 .expect("BlockId should have been lowered")
87 }
88 fn get_expr(&self, package: PackageId, id: ExprId) -> &qsc_fir::fir::Expr {
89 self.get_package(package)
90 .exprs
91 .get(id)
92 .expect("ExprId should have been lowered")
93 }
94 fn get_pat(&self, package: PackageId, id: PatId) -> &qsc_fir::fir::Pat {
95 self.get_package(package)
96 .pats
97 .get(id)
98 .expect("PatId should have been lowered")
99 }
100 fn get_stmt(&self, package: PackageId, id: StmtId) -> &qsc_fir::fir::Stmt {
101 self.get_package(package)
102 .stmts
103 .get(id)
104 .expect("StmtId should have been lowered")
105 }
106}
107
108pub(super) fn get_global(
109 fir_store: &IndexMap<PackageId, qsc_fir::fir::Package>,
110 id: GlobalId,
111) -> Option<Global> {
112 fir_store
113 .get(id.package)
114 .and_then(|package| match &package.items.get(id.item)?.kind {
115 ItemKind::Callable(callable) => Some(Global::Callable(callable)),
116 ItemKind::Namespace(..) => None,
117 ItemKind::Ty(..) => Some(Global::Udt),
118 })
119}
120
121#[derive(Copy, Clone, Default)]
122struct HardwareId(usize);
123
124pub struct BaseProfSim {
125 next_meas_id: usize,
126 next_qubit_id: usize,
127 next_qubit_hardware_id: HardwareId,
128 qubit_map: IndexMap<usize, HardwareId>,
129 instrs: String,
130 measurements: String,
131}
132
133impl Default for BaseProfSim {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139impl BaseProfSim {
140 #[must_use]
141 pub fn new() -> Self {
142 let mut sim = BaseProfSim {
143 next_meas_id: 0,
144 next_qubit_id: 0,
145 next_qubit_hardware_id: HardwareId::default(),
146 qubit_map: IndexMap::new(),
147 instrs: String::new(),
148 measurements: String::new(),
149 };
150 sim.instrs.push_str(include_str!("./qir_base/prefix.ll"));
151 sim
152 }
153
154 #[must_use]
155 pub fn finish(mut self, val: &Value) -> String {
156 self.instrs.push_str(&self.measurements);
157 self.write_output_recording(val)
158 .expect("writing to string should succeed");
159
160 write!(
161 self.instrs,
162 include_str!("./qir_base/postfix.ll"),
163 self.next_qubit_hardware_id.0, self.next_meas_id
164 )
165 .expect("writing to string should succeed");
166
167 self.instrs
168 }
169
170 #[must_use]
171 fn get_meas_id(&mut self) -> usize {
172 let id = self.next_meas_id;
173 self.next_meas_id += 1;
174 id
175 }
176
177 fn map(&mut self, qubit: usize) -> HardwareId {
178 if let Some(mapped) = self.qubit_map.get(qubit) {
179 *mapped
180 } else {
181 let mapped = self.next_qubit_hardware_id;
182 self.next_qubit_hardware_id.0 += 1;
183 self.qubit_map.insert(qubit, mapped);
184 mapped
185 }
186 }
187
188 fn write_output_recording(&mut self, val: &Value) -> std::fmt::Result {
189 match val {
190 Value::Array(arr) => {
191 self.write_array_recording(arr.len())?;
192 for val in arr.iter() {
193 self.write_output_recording(val)?;
194 }
195 }
196 Value::Result(r) => {
197 self.write_result_recording(r.unwrap_id());
198 }
199 Value::Tuple(tup) => {
200 self.write_tuple_recording(tup.len())?;
201 for val in tup.iter() {
202 self.write_output_recording(val)?;
203 }
204 }
205 _ => panic!("unexpected value type: {val:?}"),
206 }
207 Ok(())
208 }
209
210 fn write_result_recording(&mut self, res: usize) {
211 writeln!(
212 self.instrs,
213 " call void @__quantum__rt__result_record_output({}, i8* null)",
214 Result(res),
215 )
216 .expect("writing to string should succeed");
217 }
218
219 fn write_tuple_recording(&mut self, size: usize) -> std::fmt::Result {
220 writeln!(
221 self.instrs,
222 " call void @__quantum__rt__tuple_record_output(i64 {size}, i8* null)"
223 )
224 }
225
226 fn write_array_recording(&mut self, size: usize) -> std::fmt::Result {
227 writeln!(
228 self.instrs,
229 " call void @__quantum__rt__array_record_output(i64 {size}, i8* null)"
230 )
231 }
232}
233
234impl Backend for BaseProfSim {
235 type ResultType = usize;
236
237 fn ccx(&mut self, ctl0: usize, ctl1: usize, q: usize) {
238 let ctl0 = self.map(ctl0);
239 let ctl1 = self.map(ctl1);
240 let q = self.map(q);
241 writeln!(
242 self.instrs,
243 " call void @__quantum__qis__ccx__body({}, {}, {})",
244 Qubit(ctl0),
245 Qubit(ctl1),
246 Qubit(q)
247 )
248 .expect("writing to string should succeed");
249 }
250
251 fn cx(&mut self, ctl: usize, q: usize) {
252 let ctl = self.map(ctl);
253 let q = self.map(q);
254 writeln!(
255 self.instrs,
256 " call void @__quantum__qis__cx__body({}, {})",
257 Qubit(ctl),
258 Qubit(q),
259 )
260 .expect("writing to string should succeed");
261 }
262
263 fn cy(&mut self, ctl: usize, q: usize) {
264 let ctl = self.map(ctl);
265 let q = self.map(q);
266 writeln!(
267 self.instrs,
268 " call void @__quantum__qis__cy__body({}, {})",
269 Qubit(ctl),
270 Qubit(q),
271 )
272 .expect("writing to string should succeed");
273 }
274
275 fn cz(&mut self, ctl: usize, q: usize) {
276 let ctl = self.map(ctl);
277 let q = self.map(q);
278 writeln!(
279 self.instrs,
280 " call void @__quantum__qis__cz__body({}, {})",
281 Qubit(ctl),
282 Qubit(q),
283 )
284 .expect("writing to string should succeed");
285 }
286
287 fn h(&mut self, q: usize) {
288 let q = self.map(q);
289 writeln!(
290 self.instrs,
291 " call void @__quantum__qis__h__body({})",
292 Qubit(q),
293 )
294 .expect("writing to string should succeed");
295 }
296
297 fn m(&mut self, q: usize) -> Self::ResultType {
298 let mapped_q = self.map(q);
299 let id = self.get_meas_id();
300 // Measurements are tracked separately from instructions, so that they can be
301 // deferred until the end of the program.
302 writeln!(
303 self.measurements,
304 " call void @__quantum__qis__mz__body({}, {}) #1",
305 Qubit(mapped_q),
306 Result(id),
307 )
308 .expect("writing to string should succeed");
309 self.reset(q);
310 id
311 }
312
313 fn mresetz(&mut self, q: usize) -> Self::ResultType {
314 self.m(q)
315 }
316
317 fn reset(&mut self, q: usize) {
318 // Reset is a no-op in Base Profile, but does force qubit remapping so that future
319 // operations on the given qubit id are performed on a fresh qubit. Clear the entry in the map
320 // so it is known to require remapping on next use.
321 self.qubit_map.remove(q);
322 }
323
324 fn rx(&mut self, theta: f64, q: usize) {
325 let q = self.map(q);
326 writeln!(
327 self.instrs,
328 " call void @__quantum__qis__rx__body({}, {})",
329 Double(theta),
330 Qubit(q),
331 )
332 .expect("writing to string should succeed");
333 }
334
335 fn rxx(&mut self, theta: f64, q0: usize, q1: usize) {
336 let q0 = self.map(q0);
337 let q1 = self.map(q1);
338 writeln!(
339 self.instrs,
340 " call void @__quantum__qis__rxx__body({}, {}, {})",
341 Double(theta),
342 Qubit(q0),
343 Qubit(q1),
344 )
345 .expect("writing to string should succeed");
346 }
347
348 fn ry(&mut self, theta: f64, q: usize) {
349 let q = self.map(q);
350 writeln!(
351 self.instrs,
352 " call void @__quantum__qis__ry__body({}, {})",
353 Double(theta),
354 Qubit(q),
355 )
356 .expect("writing to string should succeed");
357 }
358
359 fn ryy(&mut self, theta: f64, q0: usize, q1: usize) {
360 let q0 = self.map(q0);
361 let q1 = self.map(q1);
362 writeln!(
363 self.instrs,
364 " call void @__quantum__qis__ryy__body({}, {}, {})",
365 Double(theta),
366 Qubit(q0),
367 Qubit(q1),
368 )
369 .expect("writing to string should succeed");
370 }
371
372 fn rz(&mut self, theta: f64, q: usize) {
373 let q = self.map(q);
374 writeln!(
375 self.instrs,
376 " call void @__quantum__qis__rz__body({}, {})",
377 Double(theta),
378 Qubit(q),
379 )
380 .expect("writing to string should succeed");
381 }
382
383 fn rzz(&mut self, theta: f64, q0: usize, q1: usize) {
384 let q0 = self.map(q0);
385 let q1 = self.map(q1);
386 writeln!(
387 self.instrs,
388 " call void @__quantum__qis__rzz__body({}, {}, {})",
389 Double(theta),
390 Qubit(q0),
391 Qubit(q1),
392 )
393 .expect("writing to string should succeed");
394 }
395
396 fn sadj(&mut self, q: usize) {
397 let q = self.map(q);
398 writeln!(
399 self.instrs,
400 " call void @__quantum__qis__s__adj({})",
401 Qubit(q),
402 )
403 .expect("writing to string should succeed");
404 }
405
406 fn s(&mut self, q: usize) {
407 let q = self.map(q);
408 writeln!(
409 self.instrs,
410 " call void @__quantum__qis__s__body({})",
411 Qubit(q),
412 )
413 .expect("writing to string should succeed");
414 }
415
416 fn swap(&mut self, q0: usize, q1: usize) {
417 let q0 = self.map(q0);
418 let q1 = self.map(q1);
419 writeln!(
420 self.instrs,
421 " call void @__quantum__qis__swap__body({}, {})",
422 Qubit(q0),
423 Qubit(q1),
424 )
425 .expect("writing to string should succeed");
426 }
427
428 fn tadj(&mut self, q: usize) {
429 let q = self.map(q);
430 writeln!(
431 self.instrs,
432 " call void @__quantum__qis__t__adj({})",
433 Qubit(q),
434 )
435 .expect("writing to string should succeed");
436 }
437
438 fn t(&mut self, q: usize) {
439 let q = self.map(q);
440 writeln!(
441 self.instrs,
442 " call void @__quantum__qis__t__body({})",
443 Qubit(q),
444 )
445 .expect("writing to string should succeed");
446 }
447
448 fn x(&mut self, q: usize) {
449 let q = self.map(q);
450 writeln!(
451 self.instrs,
452 " call void @__quantum__qis__x__body({})",
453 Qubit(q),
454 )
455 .expect("writing to string should succeed");
456 }
457
458 fn y(&mut self, q: usize) {
459 let q = self.map(q);
460 writeln!(
461 self.instrs,
462 " call void @__quantum__qis__y__body({})",
463 Qubit(q),
464 )
465 .expect("writing to string should succeed");
466 }
467
468 fn z(&mut self, q: usize) {
469 let q = self.map(q);
470 writeln!(
471 self.instrs,
472 " call void @__quantum__qis__z__body({})",
473 Qubit(q),
474 )
475 .expect("writing to string should succeed");
476 }
477
478 fn qubit_allocate(&mut self) -> usize {
479 let id = self.next_qubit_id;
480 self.next_qubit_id += 1;
481 let _ = self.map(id);
482 id
483 }
484
485 fn qubit_release(&mut self, _q: usize) {
486 self.next_qubit_id -= 1;
487 }
488
489 fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
490 (Vec::new(), 0)
491 }
492
493 fn qubit_is_zero(&mut self, _q: usize) -> bool {
494 // Because `qubit_is_zero` is called on every qubit release, this must return
495 // true to avoid a panic.
496 true
497 }
498
499 fn reinit(&mut self) {
500 *self = Self::default();
501 }
502}
503
504struct Qubit(HardwareId);
505
506impl Display for Qubit {
507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
508 write!(f, "%Qubit* inttoptr (i64 {} to %Qubit*)", self.0 .0)
509 }
510}
511
512struct Result(usize);
513
514impl Display for Result {
515 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
516 write!(f, "%Result* inttoptr (i64 {} to %Result*)", self.0)
517 }
518}
519
520struct Double(f64);
521
522impl Display for Double {
523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524 let v = self.0;
525 if (v.floor() - v.ceil()).abs() < f64::EPSILON {
526 // The value is a whole number, which requires at least one decimal point
527 // to differentiate it from an integer value.
528 write!(f, "double {v:.1}")
529 } else {
530 write!(f, "double {v}")
531 }
532 }
533}
534