Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions source/compiler/qsc/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,84 @@ pub mod qir {
})
}

/// Generates the RIR (raw and SSA forms) from an AST package, mirroring `get_qir_from_ast`.
pub fn get_rir_from_ast(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems unused (except in tests). Seems like you achieved the same purpose with get_rir in interpret.rs. I think you can delete this one (and the corresponding tests)

store: &mut PackageStore,
dependencies: &Dependencies,
ast_package: qsc_ast::ast::Package,
sources: SourceMap,
capabilities: TargetCapabilityFlags,
) -> Result<Vec<String>, Vec<Error>> {
if capabilities == TargetCapabilityFlags::all() {
return Err(vec![Error::UnsupportedRuntimeCapabilities]);
}

let (unit, errors) = crate::compile::compile_ast(
store,
dependencies,
ast_package,
sources,
PackageType::Exe,
capabilities,
);

// Ensure it compiles before trying to add it to the store.
if !errors.is_empty() {
return Err(errors.iter().map(|e| Error::Compile(e.clone())).collect());
}

let package_id = store.insert(unit);
let (fir_store, fir_package_id) = qsc_passes::lower_hir_to_fir(store, package_id);
let package = fir_store.get(fir_package_id);
let entry = ProgramEntry {
exec_graph: package.entry_exec_graph.clone(),
expr: (
fir_package_id,
package
.entry
.expect("package must have an entry expression"),
)
.into(),
};

let compute_properties = PassContext::run_fir_passes_on_fir(
&fir_store,
fir_package_id,
capabilities,
)
.map_err(|errors| {
let source_package = store.get(package_id).expect("package should be in store");
errors
.iter()
.map(|e| Error::Pass(WithSource::from_map(&source_package.sources, e.clone())))
.collect::<Vec<_>>()
})?;

let (raw, ssa) = fir_to_rir(
&fir_store,
capabilities,
Some(compute_properties),
&entry,
PartialEvalConfig {
generate_debug_metadata: false,
},
)
.map_err(|e| {
let source_package_id = match e.span() {
Some(span) => span.package,
None => package_id,
};
let source_package = store
.get(source_package_id)
.expect("package should be in store");
vec![Error::PartialEvaluation(WithSource::from_map(
&source_package.sources,
e,
))]
})?;
Ok(vec![raw.to_string(), ssa.to_string()])
}

pub fn get_rir(
sources: SourceMap,
language_features: LanguageFeatures,
Expand Down
55 changes: 55 additions & 0 deletions source/compiler/qsc/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,61 @@ impl Interpreter {
})
}

/// Performs RIR codegen using the given entry expression on a new instance of the environment
/// and simulator but using the current compilation. Returns the raw and SSA forms of the RIR
/// as strings.
pub fn get_rir(&mut self, expr: &str) -> std::result::Result<Vec<String>, Vec<Error>> {
if self.capabilities == TargetCapabilityFlags::all() {
return Err(vec![Error::UnsupportedRuntimeCapabilities]);
}

// Compile the expression. This operation will set the expression as
// the entry-point in the FIR store.
let (graph, compute_properties) = self.compile_entry_expr(expr)?;

let Some(compute_properties) = compute_properties else {
// This can only happen if capability analysis was not run. This would be a bug
// and we are in a bad state and can't proceed.
panic!("internal error: compute properties not set after lowering entry expression");
};
let package = self.fir_store.get(self.package);
let entry = ProgramEntry {
exec_graph: graph,
expr: (
self.package,
package
.entry
.expect("package must have an entry expression"),
)
.into(),
};
let (raw, ssa) = fir_to_rir(
&self.fir_store,
self.capabilities,
Some(compute_properties),
&entry,
PartialEvalConfig {
generate_debug_metadata: true,
},
)
.map_err(|e| {
let hir_package_id = match e.span() {
Some(span) => span.package,
None => map_fir_package_to_hir(self.package),
};
let source_package = self
.compiler
.package_store()
.get(hir_package_id)
.expect("package should exist in the package store");
vec![Error::PartialEvaluation(WithSource::from_map(
&source_package.sources,
e,
))]
})?;
Ok(vec![raw.to_string(), ssa.to_string()])
}

/// Performs QIR codegen using the given callable with the given arguments on a new instance of the environment
/// and simulator but using the current compilation.
pub fn qirgen_from_callable(
Expand Down
67 changes: 67 additions & 0 deletions source/compiler/qsc/src/interpret/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,73 @@ mod given_interpreter {
.assert_eq(&res);
}

#[test]
fn base_get_rir() {
let mut interpreter = get_interpreter_with_capabilities(TargetCapabilityFlags::empty());
let (result, output) = line(
&mut interpreter,
indoc! {"operation Foo() : Result { use q = Qubit(); let r = M(q); Reset(q); return r; } "},
);
is_only_value(&result, &output, &Value::unit());
let res = interpreter.get_rir("Foo()").expect("expected success");
// get_rir returns the raw RIR and the SSA-transformed RIR. The full
// dump embeds source offsets in its debug metadata, so assert on the
// stable structure rather than snapshotting the whole program.
assert_eq!(res.len(), 2);
let ssa = &res[1];
assert!(ssa.contains("Program:"), "{ssa}");
assert!(ssa.contains("capabilities: Base"), "{ssa}");
assert!(ssa.contains("num_results: 1"), "{ssa}");
assert!(ssa.contains("call_type: Measurement"), "{ssa}");
}

#[test]
fn adaptive_get_rir() {
let mut interpreter = get_interpreter_with_capabilities(
TargetCapabilityFlags::Adaptive | TargetCapabilityFlags::IntegerComputations,
);
let (result, output) = line(
&mut interpreter,
indoc! {r#"
namespace Test {
import Std.Math.*;
open QIR.Intrinsic;
@EntryPoint()
operation Main() : Result {
use q = Qubit();
let pi_over_2 = 4.0 / 2.0;
__quantum__qis__rz__body(pi_over_2, q);
__quantum__qis__mresetz__body(q)
}
}"#
},
);
is_only_value(&result, &output, &Value::unit());
let res = interpreter
.get_rir("Test.Main()")
.expect("expected success");
assert_eq!(res.len(), 2);
let ssa = &res[1];
assert!(ssa.contains("Program:"), "{ssa}");
assert!(ssa.contains("Adaptive"), "{ssa}");
assert!(ssa.contains("num_results: 1"), "{ssa}");
assert!(ssa.contains("call_type: Measurement"), "{ssa}");
}

#[test]
fn get_rir_fails_for_unrestricted_profile() {
let mut interpreter = get_interpreter_with_capabilities(TargetCapabilityFlags::all());
let (result, output) = line(
&mut interpreter,
indoc! {"operation Foo() : Result { use q = Qubit(); let r = M(q); Reset(q); return r; } "},
);
is_only_value(&result, &output, &Value::unit());
let res = interpreter
.get_rir("Foo()")
.expect_err("expected get_rir to fail for the unrestricted profile");
expect!["[UnsupportedRuntimeCapabilities]"].assert_eq(&format!("{res:?}"));
}

#[test]
fn adaptive_qirgen_nested_output_types() {
let mut interpreter =
Expand Down
49 changes: 49 additions & 0 deletions source/compiler/qsc_openqasm_compiler/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,25 @@ pub(crate) fn generate_qir_from_ast(
)
}

/// Generates RIR (raw and SSA forms) from an AST package.
/// This function is used for testing purposes only, mirroring `generate_qir_from_ast`.
pub(crate) fn generate_rir_from_ast(
ast_package: Package,
source_map: SourceMap,
profile: Profile,
) -> Result<Vec<String>, Vec<Error>> {
let capabilities = profile.into();
let (stdid, mut store) = package_store_with_stdlib(capabilities);
let dependencies = vec![(PackageId::CORE, None), (stdid, None)];
qsc::codegen::qir::get_rir_from_ast(
&mut store,
&dependencies,
ast_package,
source_map,
capabilities,
)
}

fn compile<S: Into<Arc<str>>>(source: S) -> miette::Result<QasmCompileUnit, Vec<Report>> {
let config = CompilerConfig::new(
QubitSemantics::Qiskit,
Expand Down Expand Up @@ -197,6 +216,19 @@ fn compile_qasm_to_qir(source: &str) -> Result<String, Vec<Report>> {
Ok(qir)
}

fn compile_qasm_to_rir(source: &str) -> Result<Vec<String>, Vec<Report>> {
let unit = compile(source)?;
fail_on_compilation_errors(&unit);
let package = unit.package;
let rir = generate_rir_from_ast(package, unit.source_map, unit.profile).map_err(|errors| {
errors
.iter()
.map(|e| Report::new(e.clone()))
.collect::<Vec<_>>()
})?;
Ok(rir)
}

/// used to do full compilation with best effort of the input.
/// This is useful for fuzz testing.
fn compile_qasm_best_effort(source: &str) {
Expand Down Expand Up @@ -356,6 +388,23 @@ pub fn check_qasm_to_qir(source: &str, expect: &Expect) {
}
}

pub fn check_qasm_to_rir(source: &str, expect: &Expect) {
match compile_qasm_to_rir(source) {
// get_rir returns the raw and SSA-transformed RIR; check the final (SSA) form.
Ok(rir) => {
expect.assert_eq(&rir[1]);
}
Err(errors) => {
let buffer = errors
.iter()
.map(|e| format!("{e:?}"))
.collect::<Vec<_>>()
.join("\n");
expect.assert_eq(&buffer);
}
}
}

pub fn compile_qasm_to_qsharp_with_semantics<S: Into<Arc<str>>>(
source: S,
qubit_semantics: QubitSemantics,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,103 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::tests::{check_qasm_to_qsharp, compile_qasm_to_qir};
use crate::tests::{check_qasm_to_qsharp, check_qasm_to_rir, compile_qasm_to_qir};
use expect_test::expect;
use miette::Report;

#[test]
fn profile_pragma_generates_rir_with_adaptive_ri() {
let source = r#"
include "stdgates.inc";
#pragma qdk.qir.profile Adaptive_RI
qubit[2] q;
output bit[2] c;
h q[0];
cx q[0], q[1];
c = measure q;
"#;

check_qasm_to_rir(
source,
&expect![[r#"
Program:
entry: 0
callables:
Callable 0: Callable:
name: main
call_type: Regular
input_type: <VOID>
output_type: Integer
body: 0
Callable 1: Callable:
name: __quantum__rt__initialize
call_type: Regular
input_type:
[0]: Pointer
output_type: <VOID>
body: <NONE>
Callable 2: Callable:
name: __quantum__qis__h__body
call_type: Regular
input_type:
[0]: Qubit
output_type: <VOID>
body: <NONE>
Callable 3: Callable:
name: __quantum__qis__cx__body
call_type: Regular
input_type:
[0]: Qubit
[1]: Qubit
output_type: <VOID>
body: <NONE>
Callable 4: Callable:
name: __quantum__qis__m__body
call_type: Measurement
input_type:
[0]: Qubit
[1]: Result
output_type: <VOID>
body: <NONE>
Callable 5: Callable:
name: __quantum__rt__array_record_output
call_type: OutputRecording
input_type:
[0]: Integer
[1]: Pointer
output_type: <VOID>
body: <NONE>
Callable 6: Callable:
name: __quantum__rt__result_record_output
call_type: OutputRecording
input_type:
[0]: Result
[1]: Pointer
output_type: <VOID>
body: <NONE>
blocks:
Block 0: Block:
Call id(1), args( Pointer, )
Call id(2), args( Qubit(0), )
Call id(3), args( Qubit(0), Qubit(1), )
Call id(4), args( Qubit(0), Result(0), )
Call id(4), args( Qubit(1), Result(1), )
Call id(5), args( Integer(2), Tag(0, 3), )
Call id(6), args( Result(1), Tag(1, 5), )
Call id(6), args( Result(0), Tag(2, 5), )
Return
config: Config:
capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations)
num_qubits: 2
num_results: 2
tags:
[0]: 0_a
[1]: 1_a0r
[2]: 2_a1r
"#]],
);
}

#[test]
fn profile_pragma_compiles_with_adaptive_ri() -> miette::Result<(), Vec<Report>> {
let source = r#"
Expand Down
Loading
Loading