Skip to content
Merged
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
110 changes: 110 additions & 0 deletions source/compiler/qsc/src/codegen/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,61 @@ mod base_profile {
!3 = !{i32 1, !"dynamic_result_management", i1 false}
"#]].assert_eq(&qir);
}

#[test]
fn noise_intrinsic_generates_correct_qir() {
let source = "namespace Test {
operation Main() : Result {
use q = Qubit();
test_noise_intrinsic(q);
MResetZ(q)
}

@NoiseIntrinsic()
operation test_noise_intrinsic(target: Qubit) : Unit {
body intrinsic;
}
}";

let qir = compile_source_to_qir(source, *CAPABILITIES);
expect![[r#"
%Result = type opaque
%Qubit = type opaque

@empty_tag = internal constant [1 x i8] c"\00"
@0 = internal constant [4 x i8] c"0_r\00"

define i64 @ENTRYPOINT__main() #0 {
block_0:
call void @__quantum__rt__initialize(i8* null)
call void @test_noise_intrinsic(%Qubit* inttoptr (i64 0 to %Qubit*))
call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i64 0, i64 0))
ret i64 0
}

declare void @__quantum__rt__initialize(i8*)

declare void @test_noise_intrinsic(%Qubit*) #2

declare void @__quantum__rt__result_record_output(%Result*, i8*)

declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1

attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="1" "required_num_results"="1" }
attributes #1 = { "irreversible" }
attributes #2 = { "qdk_noise" }

; module flags

!llvm.module.flags = !{!0, !1, !2, !3}

!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}
"#]].assert_eq(&qir);
}
}

mod adaptive_profile {
Expand Down Expand Up @@ -611,6 +666,61 @@ mod adaptive_profile {
.assert_eq(&qir);
}

#[test]
fn noise_intrinsic_generates_correct_qir() {
let source = "namespace Test {
operation Main() : Result {
use q = Qubit();
test_noise_intrinsic(q);
MResetZ(q)
}

@NoiseIntrinsic()
operation test_noise_intrinsic(target: Qubit) : Unit {
body intrinsic;
}
}";

let qir = compile_source_to_qir(source, *CAPABILITIES);
expect![[r#"
%Result = type opaque
%Qubit = type opaque

@empty_tag = internal constant [1 x i8] c"\00"
@0 = internal constant [4 x i8] c"0_r\00"

define i64 @ENTRYPOINT__main() #0 {
block_0:
call void @__quantum__rt__initialize(i8* null)
call void @test_noise_intrinsic(%Qubit* inttoptr (i64 0 to %Qubit*))
call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i64 0, i64 0))
ret i64 0
}

declare void @__quantum__rt__initialize(i8*)

declare void @test_noise_intrinsic(%Qubit*) #2

declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1

declare void @__quantum__rt__result_record_output(%Result*, i8*)

attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" }
attributes #1 = { "irreversible" }
attributes #2 = { "qdk_noise" }

; module flags

!llvm.module.flags = !{!0, !1, !2, !3}

!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}
"#]].assert_eq(&qir);
}

#[test]
fn custom_measurement_generates_correct_qir() {
let source = "namespace Test {
Expand Down
33 changes: 23 additions & 10 deletions source/compiler/qsc_codegen/src/qir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod instruction_tests;
#[cfg(test)]
mod tests;

use qsc_data_structures::target::TargetCapabilityFlags;
use qsc_data_structures::{attrs::Attributes, target::TargetCapabilityFlags};
use qsc_eval::val::Value;
use qsc_lowerer::map_hir_package_to_fir;
use qsc_partial_eval::{ProgramEntry, partially_evaluate, partially_evaluate_call};
Expand Down Expand Up @@ -658,14 +658,13 @@ impl ToQir<String> for rir::Callable {
return format!(
"declare {output_type} @{}({input_type}){}",
self.name,
if matches!(
self.call_type,
rir::CallableType::Measurement | rir::CallableType::Reset
) {
// These callables are a special case that need the irreversible attribute.
" #1"
} else {
""
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",
_ => "",
}
);
};
Expand Down Expand Up @@ -715,13 +714,27 @@ impl ToQir<String> for rir::Program {
}
let body = format!(
include_str!("./qir/template.ll"),
constants, callables, profile, self.num_qubits, self.num_results
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 {
Expand Down
2 changes: 1 addition & 1 deletion source/compiler/qsc_codegen/src/qir/template.ll
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
{}

attributes #0 = {{ "entry_point" "output_labeling_schema" "qir_profiles"="{}" "required_num_qubits"="{}" "required_num_results"="{}" }}
attributes #1 = {{ "irreversible" }}
attributes #1 = {{ "irreversible" }}{}

; module flags
20 changes: 20 additions & 0 deletions source/compiler/qsc_data_structures/src/attrs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use bitflags::bitflags;

bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// QIR attributes used during codegen.
pub struct Attributes: u32 {
const EntryPoint = 0b_0001;
const Irreversible = 0b_0010;
const QdkNoise = 0b_0100;
}
}

impl Default for Attributes {
fn default() -> Self {
Attributes::EntryPoint | Attributes::Irreversible
}
}
1 change: 1 addition & 0 deletions source/compiler/qsc_data_structures/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

pub mod attrs;
pub mod display;
pub mod error;
pub mod functors;
Expand Down
2 changes: 2 additions & 0 deletions source/compiler/qsc_fir/src/fir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1655,6 +1655,8 @@ pub enum Attr {
Measurement,
/// Indicates that a callable is a reset.
Reset,
/// Indicates that a callable is a noise intrinsic.
NoiseIntrinsic,
/// Indicates that a callable is used for unit testing.
Test,
}
Expand Down
16 changes: 15 additions & 1 deletion source/compiler/qsc_frontend/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ impl With<'_> {
ids
}

#[allow(clippy::too_many_lines)]
fn lower_attr(&mut self, attr: &ast::Attr) -> Option<hir::Attr> {
match hir::Attr::from_str(attr.name.name.as_ref()) {
Ok(hir::Attr::EntryPoint) => match &*attr.arg.kind {
Expand Down Expand Up @@ -425,6 +426,15 @@ impl With<'_> {
None
}
},
Ok(hir::Attr::NoiseIntrinsic) => match &*attr.arg.kind {
ast::ExprKind::Tuple(args) if args.is_empty() => Some(hir::Attr::NoiseIntrinsic),
_ => {
self.lowerer
.errors
.push(Error::InvalidAttrArgs("()".to_string(), attr.arg.span));
None
}
},
Ok(hir::Attr::Test) => {
// verify that no args are passed to the attribute
match &*attr.arg.kind {
Expand Down Expand Up @@ -545,7 +555,11 @@ impl With<'_> {
}

fn check_invalid_attrs_on_function(&mut self, attrs: &[hir::Attr], span: Span) {
const INVALID_ATTRS: [hir::Attr; 2] = [hir::Attr::Measurement, hir::Attr::Reset];
const INVALID_ATTRS: [hir::Attr; 3] = [
hir::Attr::Measurement,
hir::Attr::Reset,
hir::Attr::NoiseIntrinsic,
];

for invalid_attr in &INVALID_ATTRS {
if attrs.contains(invalid_attr) {
Expand Down
25 changes: 25 additions & 0 deletions source/compiler/qsc_frontend/src/lower/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2235,6 +2235,31 @@ fn test_measurement_attr_on_function_issues_error() {
);
}

#[test]
fn test_noise_intrinsic_attr_on_function_issues_error() {
check_errors(
indoc! {r#"
namespace Test {
@NoiseIntrinsic()
function Foo(q: Qubit) : Unit {
body intrinsic;
}
}
"#},
&expect![[r#"
[
InvalidAttrOnFunction(
"NoiseIntrinsic",
Span {
lo: 52,
hi: 55,
},
),
]
"#]],
);
}

#[test]
fn test_reset_attr_on_function_issues_error() {
check_errors(
Expand Down
5 changes: 5 additions & 0 deletions source/compiler/qsc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,9 @@ pub enum Attr {
/// Indicates that an intrinsic callable is a reset. This means that the operation will be marked as
/// "irreversible" in the generated QIR.
Reset,
/// Indicates that an intrinsic callable is a noise intrinsic. This means that the operation will be marked as
/// `"qdk_noise"` in the generated QIR.
NoiseIntrinsic,
/// Indicates that a callable is a test case.
Test,
}
Expand All @@ -1441,6 +1444,7 @@ The `not` operator is also supported to negate the attribute, e.g. `not Adaptive
Attr::SimulatableIntrinsic => "Indicates that an item should be treated as an intrinsic callable for QIR code generation and any implementation should only be used during simulation.",
Attr::Measurement => "Indicates that an intrinsic callable is a measurement. This means that the operation will be marked as \"irreversible\" in the generated QIR, and output Result types will be moved to the arguments.",
Attr::Reset => "Indicates that an intrinsic callable is a reset. This means that the operation will be marked as \"irreversible\" in the generated QIR.",
Attr::NoiseIntrinsic => "Indicates that an intrinsic callable is a noise intrinsic. This means that the operation will be marked as \"qdk_noise\" in the generated QIR.",
Attr::Test => "Indicates that a callable is a test case.",
}
}
Expand All @@ -1457,6 +1461,7 @@ impl FromStr for Attr {
"SimulatableIntrinsic" => Ok(Self::SimulatableIntrinsic),
"Measurement" => Ok(Self::Measurement),
"Reset" => Ok(Self::Reset),
"NoiseIntrinsic" => Ok(Self::NoiseIntrinsic),
"Test" => Ok(Self::Test),
_ => Err(()),
}
Expand Down
1 change: 1 addition & 0 deletions source/compiler/qsc_lowerer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,7 @@ fn lower_attrs(attrs: &[hir::Attr]) -> Vec<fir::Attr> {
hir::Attr::EntryPoint => Some(fir::Attr::EntryPoint),
hir::Attr::Measurement => Some(fir::Attr::Measurement),
hir::Attr::Reset => Some(fir::Attr::Reset),
hir::Attr::NoiseIntrinsic => Some(fir::Attr::NoiseIntrinsic),
hir::Attr::Test => Some(fir::Attr::Test),
hir::Attr::SimulatableIntrinsic | hir::Attr::Unimplemented | hir::Attr::Config => None,
})
Expand Down
Loading
Loading