Skip to content

Commit 5207d89

Browse files
QIR Profiles Selection via Project Field or EntryPoint argument (#2591)
This removes the workspace setting for the QIR profile and adds a setting for this for Q# project files instead, i.e. `"targetProfile": "base"`. Single-file Q# files are supported by adding an argument to the @entrypoint attribute where a user will specify what profile the file is meant to compile with, i.e. `@EntryPoint(Adaptive_RI)`, or uses `Unrestricted` as a default.
1 parent 03b8e5b commit 5207d89

File tree

40 files changed

+644
-525
lines changed

40 files changed

+644
-525
lines changed

source/compiler/qsc/src/compile.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Licensed under the MIT License.
33

44
use miette::{Diagnostic, Report};
5-
use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
5+
use qsc_data_structures::{
6+
language_features::LanguageFeatures, span::Span, target::TargetCapabilityFlags,
7+
};
68
pub use qsc_frontend::compile::Dependencies;
79
use qsc_frontend::{
810
compile::{CompileUnit, PackageStore, SourceMap},
@@ -45,6 +47,11 @@ pub enum ErrorKind {
4547
/// `OpenQASM` compilation errors.
4648
#[diagnostic(transparent)]
4749
OpenQasm(#[from] crate::qasm::error::Error),
50+
51+
#[error(
52+
"The @EntryPoint attribute with a profile argument is not allowed in a Q# project (with qsharp.json). Please specify the profile in qsharp.json instead."
53+
)]
54+
EntryPointProfileInProject(#[label] Span),
4855
}
4956

5057
/// Compiles a package from its AST representation.

source/compiler/qsc/src/interpret.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ pub enum Error {
9999
NoEntryPoint,
100100
#[error("unsupported runtime capabilities for code generation")]
101101
#[diagnostic(code("Qsc.Interpret.UnsupportedRuntimeCapabilities"))]
102+
#[diagnostic(help(
103+
"@EntryPoint attribute argument should be 'Base', 'Adaptive_RI' or 'Adaptive_RIF'"
104+
))]
102105
UnsupportedRuntimeCapabilities,
103106
#[error("expression does not evaluate to an operation")]
104107
#[diagnostic(code("Qsc.Interpret.NotAnOperation"))]

source/compiler/qsc/src/interpret/package_tests.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ fn import_and_call_reexport() {
1919
indoc! {"
2020
import Foo.DependencyA.Foo;
2121
function Main() : Unit {
22-
Foo([1, 2]);
22+
Foo([1, 2]);
2323
Foo.DependencyA.MagicFunction();
2424
}"}
2525
.into(),
@@ -50,6 +50,7 @@ fn import_and_call_reexport() {
5050
)]
5151
.into_iter()
5252
.collect(),
53+
has_manifest: true,
5354
};
5455

5556
// This builds all the dependencies
@@ -129,6 +130,7 @@ fn directly_call_reexport() {
129130
)]
130131
.into_iter()
131132
.collect(),
133+
has_manifest: true,
132134
};
133135

134136
// This builds all the dependencies

source/compiler/qsc/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ pub mod incremental;
88
pub mod interpret;
99
pub mod location;
1010
pub mod packages;
11-
pub mod target;
1211

1312
pub use qsc_formatter::formatter;
1413

@@ -80,4 +79,8 @@ pub mod partial_eval {
8079
pub use qsc_partial_eval::Error;
8180
}
8281

82+
pub mod target {
83+
pub use qsc_data_structures::target::Profile;
84+
}
85+
8386
pub mod qasm;

source/compiler/qsc/src/packages.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
};
99
use qsc_circuit::circuit_to_qsharp::circuits_to_qsharp;
1010
use qsc_data_structures::language_features::LanguageFeatures;
11-
use qsc_frontend::compile::SourceMap;
11+
use qsc_frontend::compile::{SourceMap, check_for_entry_profile};
1212
use qsc_passes::PackageType;
1313
use qsc_project::PackageGraphSources;
1414
use rustc_hash::FxHashMap;
@@ -24,6 +24,7 @@ pub struct BuildableProgram {
2424
pub user_code: qsc_project::PackageInfo,
2525
pub user_code_dependencies: Vec<(PackageId, Option<Arc<str>>)>,
2626
pub dependency_errors: Vec<compile::Error>,
27+
pub capabilities: TargetCapabilityFlags,
2728
}
2829

2930
impl BuildableProgram {
@@ -78,16 +79,40 @@ fn convert_circuit_sources(
7879
/// Given a program config, prepare the package store by compiling all dependencies in the correct order and inserting them.
7980
#[must_use]
8081
pub fn prepare_package_store(
81-
capabilities: TargetCapabilityFlags,
82+
mut capabilities: TargetCapabilityFlags,
8283
package_graph_sources: PackageGraphSources,
8384
) -> BuildableProgram {
85+
let mut dependency_errors = Vec::new();
86+
87+
// Convert circuit files in user code to generated Q# before entry profile check
88+
let mut sources = package_graph_sources.root.sources.clone();
89+
sources = convert_circuit_sources(sources, &mut dependency_errors);
90+
91+
// Check if the entry profile is set in the source code.
92+
let entry_profile = check_for_entry_profile(
93+
&SourceMap::new(sources.clone(), None),
94+
package_graph_sources.root.language_features,
95+
);
96+
97+
// If the entry profile is set, we need to ensure that the user code is compiled with it.
98+
if let Some((profile, span)) = entry_profile {
99+
// If this is a project (qsharp.json present), emit error with span
100+
if package_graph_sources.has_manifest {
101+
dependency_errors.push(Error::from_map(
102+
&SourceMap::new(sources, None),
103+
ErrorKind::EntryPointProfileInProject(span),
104+
));
105+
} else {
106+
capabilities = profile.into();
107+
}
108+
}
109+
84110
let (std_id, mut package_store) = package_store_with_stdlib(capabilities);
85111

86112
let mut canonical_package_identifier_to_package_id_mapping = FxHashMap::default();
87113

88114
let (ordered_packages, user_code) = package_graph_sources.compilation_order();
89115

90-
let mut dependency_errors = Vec::new();
91116
let ordered_packages = if let Ok(o) = ordered_packages {
92117
o
93118
} else {
@@ -163,5 +188,6 @@ pub fn prepare_package_store(
163188
dependency_errors,
164189
user_code,
165190
user_code_dependencies,
191+
capabilities,
166192
}
167193
}

source/compiler/qsc/src/packages/tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@ fn mock_program() -> Project {
3535
package_type: Some(qsc_project::PackageType::Lib),
3636
},
3737
)]),
38+
has_manifest: true,
3839
};
3940
Project {
4041
lints: vec![],
4142
errors: vec![],
4243
path: "project/qsharp.json".into(),
4344
name: "project".into(),
4445
project_type: qsc_project::ProjectType::QSharp(package_graph_sources),
46+
target_profile: qsc_data_structures::target::Profile::Unrestricted,
4547
}
4648
}
4749

source/compiler/qsc/src/target.rs

Lines changed: 0 additions & 56 deletions
This file was deleted.

source/compiler/qsc_data_structures/src/target.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,55 @@ impl Default for TargetCapabilityFlags {
3838
TargetCapabilityFlags::empty()
3939
}
4040
}
41+
42+
use std::str::FromStr;
43+
44+
#[derive(Clone, Copy, Debug, PartialEq)]
45+
pub enum Profile {
46+
Unrestricted,
47+
Base,
48+
AdaptiveRI,
49+
AdaptiveRIF,
50+
}
51+
52+
impl Profile {
53+
#[must_use]
54+
pub fn to_str(&self) -> &'static str {
55+
match self {
56+
Self::Unrestricted => "Unrestricted",
57+
Self::Base => "Base",
58+
Self::AdaptiveRI => "Adaptive_RI",
59+
Self::AdaptiveRIF => "Adaptive_RIF",
60+
}
61+
}
62+
}
63+
64+
impl From<Profile> for TargetCapabilityFlags {
65+
fn from(value: Profile) -> Self {
66+
match value {
67+
Profile::Unrestricted => Self::all(),
68+
Profile::Base => Self::empty(),
69+
Profile::AdaptiveRI => Self::Adaptive | Self::QubitReset | Self::IntegerComputations,
70+
Profile::AdaptiveRIF => {
71+
Self::Adaptive
72+
| Self::QubitReset
73+
| Self::IntegerComputations
74+
| Self::FloatingPointComputations
75+
}
76+
}
77+
}
78+
}
79+
80+
impl FromStr for Profile {
81+
type Err = ();
82+
83+
fn from_str(s: &str) -> Result<Self, Self::Err> {
84+
match s.to_lowercase().as_str() {
85+
"adaptive_ri" => Ok(Self::AdaptiveRI),
86+
"adaptive_rif" => Ok(Self::AdaptiveRIF),
87+
"base" => Ok(Self::Base),
88+
"unrestricted" => Ok(Self::Unrestricted),
89+
_ => Err(()),
90+
}
91+
}
92+
}

source/compiler/qsc_frontend/src/compile.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use qsc_data_structures::{
2626
index_map::{self, IndexMap},
2727
language_features::LanguageFeatures,
2828
span::Span,
29-
target::TargetCapabilityFlags,
29+
target::{Profile, TargetCapabilityFlags},
3030
};
3131
use qsc_hir::{
3232
assigner::Assigner as HirAssigner,
@@ -530,6 +530,22 @@ pub fn parse_all(
530530
(package, errors)
531531
}
532532

533+
#[must_use]
534+
pub fn check_for_entry_profile(
535+
sources: &SourceMap,
536+
language_features: LanguageFeatures,
537+
) -> Option<(Profile, Span)> {
538+
let (ast_package, parse_errors) = parse_all(sources, language_features);
539+
540+
if !parse_errors.is_empty() {
541+
return None;
542+
}
543+
544+
let mut check = preprocess::DetectEntryPointProfile::new();
545+
check.visit_package(&ast_package);
546+
check.profile
547+
}
548+
533549
pub(crate) struct ResolveResult {
534550
pub names: Names,
535551
pub locals: Locals,

source/compiler/qsc_frontend/src/compile/preprocess.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ use qsc_ast::{
88
TopLevelNode, UnOp,
99
},
1010
mut_visit::{MutVisitor, walk_stmt},
11+
visit::Visitor,
1112
};
12-
use qsc_data_structures::span::Span;
13+
use qsc_data_structures::{span::Span, target::Profile};
1314
use qsc_hir::hir;
1415
use std::rc::Rc;
1516

@@ -18,6 +19,34 @@ use super::{SourceMap, TargetCapabilityFlags};
1819
#[cfg(test)]
1920
mod tests;
2021

22+
/// Transformation to detect `@EntryPoint` attribute in the AST.
23+
#[derive(Default)]
24+
pub struct DetectEntryPointProfile {
25+
pub profile: Option<(Profile, Span)>,
26+
}
27+
28+
impl DetectEntryPointProfile {
29+
#[must_use]
30+
pub fn new() -> Self {
31+
Self { profile: None }
32+
}
33+
}
34+
35+
impl Visitor<'_> for DetectEntryPointProfile {
36+
fn visit_attr(&mut self, attr: &Attr) {
37+
if hir::Attr::from_str(attr.name.name.as_ref()) == Ok(hir::Attr::EntryPoint) {
38+
// Try to parse the argument as a profile name
39+
if let ExprKind::Paren(inner) = attr.arg.kind.as_ref() {
40+
if let ExprKind::Path(PathKind::Ok(path)) = inner.kind.as_ref() {
41+
if let Ok(profile) = Profile::from_str(path.name.name.as_ref()) {
42+
self.profile = Some((profile, path.span));
43+
}
44+
}
45+
}
46+
}
47+
}
48+
}
49+
2150
#[derive(PartialEq, Hash, Clone, Debug)]
2251
pub struct TrackedName {
2352
pub name: Rc<str>,

0 commit comments

Comments
 (0)