Skip to content

Commit

Permalink
Use RCA based code generation for all scenarios (#1523)
Browse files Browse the repository at this point in the history
new QSC option:
```
  -p, --profile <PROFILE>
          Target QIR profile for code generation

          Possible values:
          - unrestricted: This is the default profile, which allows all operations
          - base:         This profile restricts the set of operations to those that are supported by the Base profile
          - adaptive-ri:  This profile restricts the set of operations to those that are supported by the AdaptiveRI profile
```
  • Loading branch information
idavis authored May 21, 2024
1 parent 5df4684 commit 308c578
Show file tree
Hide file tree
Showing 35 changed files with 448 additions and 2,847 deletions.
99 changes: 85 additions & 14 deletions compiler/qsc/src/bin/qsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ allocator::assign_global!();
use clap::{crate_version, ArgGroup, Parser, ValueEnum};
use log::info;
use miette::{Context, IntoDiagnostic, Report};
use qsc::compile::compile;
use qsc_codegen::qir_base;
use qsc::hir::PackageId;
use qsc::{compile::compile, PassContext};
use qsc_codegen::qir::fir_to_qir;
use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
use qsc_frontend::{
compile::{PackageStore, SourceContents, SourceMap, SourceName},
error::WithSource,
};
use qsc_hir::hir::{Package, PackageId};
use qsc_hir::hir::Package;
use qsc_partial_eval::ProgramEntry;
use qsc_passes::PackageType;
use qsc_project::{FileSystem, Manifest, StdFs};
use std::{
Expand All @@ -24,6 +26,28 @@ use std::{
string::String,
};

#[derive(clap::ValueEnum, Clone, Debug, Default, PartialEq)]
pub enum Profile {
/// This is the default profile, which allows all operations.
#[default]
Unrestricted,
/// This profile restricts the set of operations to those that are supported by the Base profile.
Base,
/// This profile restricts the set of operations to those that are supported by the AdaptiveRI profile.
AdaptiveRI,
}

// convert Profile into qsc::target::Profile
impl From<Profile> for qsc::target::Profile {
fn from(profile: Profile) -> Self {
match profile {
Profile::Unrestricted => qsc::target::Profile::Unrestricted,
Profile::Base => qsc::target::Profile::Base,
Profile::AdaptiveRI => qsc::target::Profile::AdaptiveRI,
}
}
}

#[derive(Debug, Parser)]
#[command(version = concat!(crate_version!(), " (", env!("QSHARP_GIT_HASH"), ")"), arg_required_else_help(false))]
#[clap(group(ArgGroup::new("input").args(["entry", "sources"]).required(false).multiple(true)))]
Expand All @@ -48,6 +72,10 @@ struct Cli {
#[arg(short, long)]
entry: Option<String>,

/// Target QIR profile for code generation
#[arg(short, long)]
profile: Option<Profile>,

/// Q# source files to compile, or `-` to read from stdin.
#[arg()]
sources: Vec<PathBuf>,
Expand All @@ -72,11 +100,12 @@ fn main() -> miette::Result<ExitCode> {
let cli = Cli::parse();
let mut store = PackageStore::new(qsc::compile::core());
let mut dependencies = Vec::new();

let (package_type, capabilities) = if cli.emit.contains(&Emit::Qir) {
(PackageType::Exe, TargetCapabilityFlags::empty())
let profile: qsc::target::Profile = cli.profile.unwrap_or_default().into();
let capabilities = profile.into();
let package_type = if cli.emit.contains(&Emit::Qir) {
PackageType::Exe
} else {
(PackageType::Lib, TargetCapabilityFlags::all())
PackageType::Lib
};

if !cli.nostdlib {
Expand Down Expand Up @@ -124,8 +153,21 @@ fn main() -> miette::Result<ExitCode> {
match emit {
Emit::Hir => emit_hir(&unit.package, out_dir)?,
Emit::Qir => {
if package_type != PackageType::Exe {
eprintln!("QIR generation is only supported for executable packages");
return Ok(ExitCode::FAILURE);
}
if capabilities == TargetCapabilityFlags::all() {
eprintln!("QIR generation is not supported for unrestricted profile");
return Ok(ExitCode::FAILURE);
}
if errors.is_empty() {
emit_qir(out_dir, &store, package_id)?;
if let Err(reports) = emit_qir(out_dir, &store, package_id, capabilities) {
for report in reports {
eprintln!("{report:?}");
}
return Ok(ExitCode::FAILURE);
}
}
}
}
Expand Down Expand Up @@ -172,22 +214,51 @@ fn emit_hir(package: &Package, dir: impl AsRef<Path>) -> miette::Result<()> {
.with_context(|| format!("could not emit HIR file `{}`", path.display()))
}

fn emit_qir(out_dir: &Path, store: &PackageStore, package_id: PackageId) -> Result<(), Report> {
let path = out_dir.join("qir.ll");
let result = qir_base::generate_qir(store, package_id);
match result {
fn emit_qir(
out_dir: &Path,
store: &PackageStore,
package_id: PackageId,
capabilities: TargetCapabilityFlags,
) -> Result<(), Vec<Report>> {
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 results = PassContext::run_fir_passes_on_fir(&fir_store, fir_package_id, capabilities);
if results.is_err() {
let errors = results.expect_err("should have errors");
let errors = errors.into_iter().map(Report::new).collect();
return Err(errors);
}
let compute_properties = results.expect("should have compute properties");

match fir_to_qir(&fir_store, capabilities, Some(compute_properties), &entry) {
Ok(qir) => {
let path = out_dir.join("qir.ll");
info!(
"Writing QIR output file to: {}",
path.to_str().unwrap_or_default()
);
fs::write(&path, qir)
.into_diagnostic()
.with_context(|| format!("could not emit QIR file `{}`", path.display()))
.map_err(|err| vec![err])
}
Err((error, _)) => {
Err(error) => {
let unit = store.get(package_id).expect("package should be in store");
Err(Report::new(WithSource::from_map(&unit.sources, error)))
Err(vec![Report::new(WithSource::from_map(
&unit.sources,
error,
))])
}
}
}
59 changes: 31 additions & 28 deletions compiler/qsc/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,23 @@ mod tests;

use qsc_codegen::qir::fir_to_qir;
use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
use qsc_frontend::compile::{PackageStore, SourceMap};
use qsc_frontend::{
compile::{PackageStore, SourceMap},
error::WithSource,
};
use qsc_partial_eval::ProgramEntry;
use qsc_passes::{PackageType, PassContext};
use qsc_rca::Analyzer;

use crate::compile;
use crate::{compile, interpret::Error};

pub fn get_qir(
sources: SourceMap,
language_features: LanguageFeatures,
capabilities: TargetCapabilityFlags,
) -> Result<String, String> {
) -> Result<String, Vec<Error>> {
if capabilities == TargetCapabilityFlags::all() {
return Err(vec![Error::UnsupportedRuntimeCapabilities]);
}
let core = compile::core();
let mut package_store = PackageStore::new(core);
let std = compile::std(&package_store, capabilities);
Expand All @@ -34,15 +39,7 @@ pub fn get_qir(

// Ensure it compiles before trying to add it to the store.
if !errors.is_empty() {
// This will happen when QIR generation is attempted on a program that has errors.
// This can happen in the playground.
let mut error_message =
String::from("Failed to generate QIR. Could not compile sources.:\n");
for error in errors {
error_message.push_str(&format!("{error}\n"));
}

return Err(error_message);
return Err(errors.iter().map(|e| Error::Compile(e.clone())).collect());
}

let package_id = package_store.insert(unit);
Expand All @@ -59,20 +56,26 @@ pub fn get_qir(
.into(),
};

let compute_properties = if capabilities == TargetCapabilityFlags::empty() {
// baseprofchk already handled compliance, run the analyzer to get the compute properties.
let analyzer = Analyzer::init(&fir_store);
Ok(analyzer.analyze_all())
} else {
PassContext::run_fir_passes_on_fir(&fir_store, fir_package_id, capabilities)
};

let Ok(compute_properties) = compute_properties else {
// This should never happen, as the program should be checked for errors before trying to
// generate code for it. But just in case, simply report the failure.
return Err("Failed to generate QIR. Could not generate compute properties.".to_string());
};
let compute_properties =
PassContext::run_fir_passes_on_fir(&fir_store, fir_package_id, capabilities).map_err(
|errors| {
let source_package = 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<_>>()
},
)?;

fir_to_qir(&fir_store, capabilities, Some(compute_properties), &entry)
.map_err(|e| e.to_string())
fir_to_qir(&fir_store, capabilities, Some(compute_properties), &entry).map_err(|e| {
let source_package = package_store
.get(package_id)
.expect("package should be in store");
vec![Error::PartialEvaluation(WithSource::from_map(
&source_package.sources,
e,
))]
})
}
32 changes: 31 additions & 1 deletion compiler/qsc/src/codegen/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,37 @@ fn code_with_errors_returns_errors() {

expect![[r#"
Err(
"Failed to generate QIR. Could not compile sources.:\nsyntax error\n",
[
Compile(
WithSource {
sources: [
Source {
name: "test.qs",
contents: "namespace Test {\n @EntryPoint()\n operation Main() : Unit {\n use q = Qubit()\n let pi_over_two = 4.0 / 2.0;\n }\n }",
offset: 0,
},
],
error: Frontend(
Error(
Parse(
Error(
Token(
Semi,
Keyword(
Let,
),
Span {
lo: 129,
hi: 132,
},
),
),
),
),
),
},
),
],
)
"#]]
.assert_debug_eq(&get_qir(sources, language_features, capabilities));
Expand Down
9 changes: 4 additions & 5 deletions compiler/qsc/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub fn compile_ast(
capabilities,
vec![],
);
process_compile_unit(store, package_type, capabilities, unit)
process_compile_unit(store, package_type, unit)
}

/// Compiles a package from its source representation.
Expand All @@ -72,15 +72,14 @@ pub fn compile(
capabilities,
language_features,
);
process_compile_unit(store, package_type, capabilities, unit)
process_compile_unit(store, package_type, unit)
}

#[must_use]
#[allow(clippy::module_name_repetitions)]
fn process_compile_unit(
store: &PackageStore,
package_type: PackageType,
capabilities: TargetCapabilityFlags,
mut unit: CompileUnit,
) -> (CompileUnit, Vec<Error>) {
let mut errors = Vec::new();
Expand All @@ -89,7 +88,7 @@ fn process_compile_unit(
}

if errors.is_empty() {
for error in run_default_passes(store.core(), &mut unit, package_type, capabilities) {
for error in run_default_passes(store.core(), &mut unit, package_type) {
errors.push(WithSource::from_map(&unit.sources, error.into()));
}
}
Expand Down Expand Up @@ -126,7 +125,7 @@ pub fn core() -> CompileUnit {
#[must_use]
pub fn std(store: &PackageStore, capabilities: TargetCapabilityFlags) -> CompileUnit {
let mut unit = qsc_frontend::compile::std(store, capabilities);
let pass_errors = run_default_passes(store.core(), &mut unit, PackageType::Lib, capabilities);
let pass_errors = run_default_passes(store.core(), &mut unit, PackageType::Lib);
if pass_errors.is_empty() {
unit
} else {
Expand Down
4 changes: 2 additions & 2 deletions compiler/qsc/src/incremental.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl Compiler {
store,
source_package_id,
frontend,
passes: PassContext::new(capabilities),
passes: PassContext::default(),
})
}

Expand All @@ -97,7 +97,7 @@ impl Compiler {
store,
source_package_id,
frontend,
passes: PassContext::new(capabilities),
passes: PassContext::default(),
})
}

Expand Down
Loading

0 comments on commit 308c578

Please sign in to comment.