microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
compiler/qsc/src/bin/qsc.rs
325lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | allocator::assign_global!(); |
| 5 | |
| 6 | use clap::{crate_version, ArgGroup, Parser, ValueEnum}; |
| 7 | use log::info; |
| 8 | use miette::{Context, IntoDiagnostic, Report}; |
| 9 | use qsc::hir::PackageId; |
| 10 | use qsc::packages::BuildableProgram; |
| 11 | use qsc::{compile::compile, PassContext}; |
| 12 | use qsc_codegen::qir::fir_to_qir; |
| 13 | use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags}; |
| 14 | use qsc_frontend::{ |
| 15 | compile::{PackageStore, SourceContents, SourceMap, SourceName}, |
| 16 | error::WithSource, |
| 17 | }; |
| 18 | use qsc_hir::hir::Package; |
| 19 | use qsc_partial_eval::ProgramEntry; |
| 20 | use qsc_passes::PackageType; |
| 21 | use qsc_project::{FileSystem, StdFs}; |
| 22 | use std::sync::Arc; |
| 23 | use std::{ |
| 24 | concat, fs, |
| 25 | io::{self, Read}, |
| 26 | path::{Path, PathBuf}, |
| 27 | process::ExitCode, |
| 28 | string::String, |
| 29 | }; |
| 30 | |
| 31 | #[derive(clap::ValueEnum, Clone, Debug, Default, PartialEq)] |
| 32 | pub enum Profile { |
| 33 | /// This is the default profile, which allows all operations. |
| 34 | #[default] |
| 35 | Unrestricted, |
| 36 | /// This profile restricts the set of operations to those that are supported by the Base profile. |
| 37 | Base, |
| 38 | /// This profile restricts the set of operations to those that are supported by the `AdaptiveRI` profile. |
| 39 | AdaptiveRI, |
| 40 | } |
| 41 | |
| 42 | // convert Profile into qsc::target::Profile |
| 43 | impl From<Profile> for qsc::target::Profile { |
| 44 | fn from(profile: Profile) -> Self { |
| 45 | match profile { |
| 46 | Profile::Unrestricted => qsc::target::Profile::Unrestricted, |
| 47 | Profile::Base => qsc::target::Profile::Base, |
| 48 | Profile::AdaptiveRI => qsc::target::Profile::AdaptiveRI, |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | #[derive(Debug, Parser)] |
| 54 | #[command(version = concat!(crate_version!(), " (", env!("QSHARP_GIT_HASH"), ")"), arg_required_else_help(false))] |
| 55 | #[clap(group(ArgGroup::new("input").args(["entry", "sources"]).required(false).multiple(true)))] |
| 56 | struct Cli { |
| 57 | /// Disable automatic inclusion of the standard library. |
| 58 | #[arg(long)] |
| 59 | nostdlib: bool, |
| 60 | |
| 61 | /// Emit the compilation unit in the specified format. |
| 62 | #[arg(long, value_enum)] |
| 63 | emit: Vec<Emit>, |
| 64 | |
| 65 | /// Write output to compiler-chosen filename in <dir>. |
| 66 | #[arg(long = "outdir", value_name = "DIR")] |
| 67 | out_dir: Option<PathBuf>, |
| 68 | |
| 69 | /// Enable verbose output. |
| 70 | #[arg(short, long)] |
| 71 | verbose: bool, |
| 72 | |
| 73 | /// Entry expression to execute as the main operation. |
| 74 | #[arg(short, long)] |
| 75 | entry: Option<String>, |
| 76 | |
| 77 | /// Target QIR profile for code generation |
| 78 | #[arg(short, long)] |
| 79 | profile: Option<Profile>, |
| 80 | |
| 81 | /// Q# source files to compile, or `-` to read from stdin. |
| 82 | #[arg()] |
| 83 | sources: Vec<PathBuf>, |
| 84 | |
| 85 | /// Path to a Q# manifest for a project |
| 86 | #[arg(short, long)] |
| 87 | qsharp_json: Option<PathBuf>, |
| 88 | |
| 89 | /// Language features to compile with |
| 90 | #[arg(short, long)] |
| 91 | features: Vec<String>, |
| 92 | } |
| 93 | |
| 94 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] |
| 95 | enum Emit { |
| 96 | Hir, |
| 97 | Qir, |
| 98 | } |
| 99 | |
| 100 | #[allow(clippy::too_many_lines)] |
| 101 | fn main() -> miette::Result<ExitCode> { |
| 102 | env_logger::init(); |
| 103 | let cli = Cli::parse(); |
| 104 | let profile: qsc::target::Profile = cli.profile.unwrap_or_default().into(); |
| 105 | let capabilities = profile.into(); |
| 106 | let package_type = if cli.emit.contains(&Emit::Qir) { |
| 107 | PackageType::Exe |
| 108 | } else { |
| 109 | PackageType::Lib |
| 110 | }; |
| 111 | let mut features = LanguageFeatures::from_iter(cli.features); |
| 112 | |
| 113 | let (mut store, dependencies, source_map) = if let Some(qsharp_json) = cli.qsharp_json { |
| 114 | if let Some(dir) = qsharp_json.parent() { |
| 115 | match load_project(dir, &mut features) { |
| 116 | Ok(items) => items, |
| 117 | Err(exit_code) => return Ok(exit_code), |
| 118 | } |
| 119 | } else { |
| 120 | eprintln!("{} must have a parent directory", qsharp_json.display()); |
| 121 | return Ok(ExitCode::FAILURE); |
| 122 | } |
| 123 | } else { |
| 124 | let sources = cli |
| 125 | .sources |
| 126 | .iter() |
| 127 | .map(read_source) |
| 128 | .collect::<miette::Result<Vec<_>>>()?; |
| 129 | |
| 130 | let mut store = PackageStore::new(qsc::compile::core()); |
| 131 | let dependencies = if cli.nostdlib { |
| 132 | vec![] |
| 133 | } else { |
| 134 | let std_id = store.insert(qsc::compile::std(&store, TargetCapabilityFlags::all())); |
| 135 | vec![(std_id, None)] |
| 136 | }; |
| 137 | ( |
| 138 | store, |
| 139 | dependencies, |
| 140 | SourceMap::new(sources, cli.entry.clone().map(std::convert::Into::into)), |
| 141 | ) |
| 142 | }; |
| 143 | |
| 144 | let (unit, errors) = compile( |
| 145 | &store, |
| 146 | &dependencies, |
| 147 | source_map, |
| 148 | package_type, |
| 149 | capabilities, |
| 150 | features, |
| 151 | ); |
| 152 | let package_id = store.insert(unit); |
| 153 | let unit = store.get(package_id).expect("package should be in store"); |
| 154 | |
| 155 | let out_dir = cli.out_dir.as_ref().map_or(".".as_ref(), PathBuf::as_path); |
| 156 | for emit in &cli.emit { |
| 157 | match emit { |
| 158 | Emit::Hir => emit_hir(&unit.package, out_dir)?, |
| 159 | Emit::Qir => { |
| 160 | if package_type != PackageType::Exe { |
| 161 | eprintln!("QIR generation is only supported for executable packages"); |
| 162 | return Ok(ExitCode::FAILURE); |
| 163 | } |
| 164 | if capabilities == TargetCapabilityFlags::all() { |
| 165 | eprintln!("QIR generation is not supported for unrestricted profile"); |
| 166 | return Ok(ExitCode::FAILURE); |
| 167 | } |
| 168 | if errors.is_empty() { |
| 169 | if let Err(reports) = emit_qir(out_dir, &store, package_id, capabilities) { |
| 170 | for report in reports { |
| 171 | eprintln!("{report:?}"); |
| 172 | } |
| 173 | return Ok(ExitCode::FAILURE); |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | if errors.is_empty() { |
| 181 | Ok(ExitCode::SUCCESS) |
| 182 | } else { |
| 183 | for error in errors { |
| 184 | eprintln!("{:?}", Report::new(error)); |
| 185 | } |
| 186 | |
| 187 | Ok(ExitCode::FAILURE) |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | fn read_source(path: impl AsRef<Path>) -> miette::Result<(SourceName, SourceContents)> { |
| 192 | let path = path.as_ref(); |
| 193 | if path.as_os_str() == "-" { |
| 194 | let mut input = String::new(); |
| 195 | io::stdin() |
| 196 | .read_to_string(&mut input) |
| 197 | .into_diagnostic() |
| 198 | .context("could not read standard input")?; |
| 199 | |
| 200 | Ok(("<stdin>".into(), input.into())) |
| 201 | } else { |
| 202 | let contents = fs::read_to_string(path) |
| 203 | .into_diagnostic() |
| 204 | .with_context(|| format!("could not read source file `{}`", path.display()))?; |
| 205 | |
| 206 | Ok((path.to_string_lossy().into(), contents.into())) |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | fn emit_hir(package: &Package, dir: impl AsRef<Path>) -> miette::Result<()> { |
| 211 | let path = dir.as_ref().join("hir.txt"); |
| 212 | info!( |
| 213 | "Writing HIR output file to: {}", |
| 214 | path.to_str().unwrap_or_default() |
| 215 | ); |
| 216 | fs::write(&path, package.to_string()) |
| 217 | .into_diagnostic() |
| 218 | .with_context(|| format!("could not emit HIR file `{}`", path.display())) |
| 219 | } |
| 220 | |
| 221 | fn emit_qir( |
| 222 | out_dir: &Path, |
| 223 | store: &PackageStore, |
| 224 | package_id: PackageId, |
| 225 | capabilities: TargetCapabilityFlags, |
| 226 | ) -> Result<(), Vec<Report>> { |
| 227 | let (fir_store, fir_package_id) = qsc_passes::lower_hir_to_fir(store, package_id); |
| 228 | let package = fir_store.get(fir_package_id); |
| 229 | let entry = ProgramEntry { |
| 230 | exec_graph: package.entry_exec_graph.clone(), |
| 231 | expr: ( |
| 232 | fir_package_id, |
| 233 | package |
| 234 | .entry |
| 235 | .expect("package must have an entry expression"), |
| 236 | ) |
| 237 | .into(), |
| 238 | }; |
| 239 | |
| 240 | let results = PassContext::run_fir_passes_on_fir(&fir_store, fir_package_id, capabilities); |
| 241 | if results.is_err() { |
| 242 | let errors = results.expect_err("should have errors"); |
| 243 | let errors = errors.into_iter().map(Report::new).collect(); |
| 244 | return Err(errors); |
| 245 | } |
| 246 | let compute_properties = results.expect("should have compute properties"); |
| 247 | |
| 248 | match fir_to_qir(&fir_store, capabilities, Some(compute_properties), &entry) { |
| 249 | Ok(qir) => { |
| 250 | let path = out_dir.join("qir.ll"); |
| 251 | info!( |
| 252 | "Writing QIR output file to: {}", |
| 253 | path.to_str().unwrap_or_default() |
| 254 | ); |
| 255 | fs::write(&path, qir) |
| 256 | .into_diagnostic() |
| 257 | .with_context(|| format!("could not emit QIR file `{}`", path.display())) |
| 258 | .map_err(|err| vec![err]) |
| 259 | } |
| 260 | Err(error) => { |
| 261 | let source_package = match error.span() { |
| 262 | Some(span) => span.package, |
| 263 | None => package_id, |
| 264 | }; |
| 265 | let unit = store |
| 266 | .get(source_package) |
| 267 | .expect("package should be in store"); |
| 268 | Err(vec![Report::new(WithSource::from_map( |
| 269 | &unit.sources, |
| 270 | error, |
| 271 | ))]) |
| 272 | } |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | /// Loads a project from the given directory and returns the package store, the list of |
| 277 | /// dependencies, and the source map. |
| 278 | /// Pre-populates the package store with all of the compiled dependencies. |
| 279 | #[allow(clippy::type_complexity)] |
| 280 | fn load_project( |
| 281 | dir: impl AsRef<Path>, |
| 282 | features: &mut LanguageFeatures, |
| 283 | ) -> Result<(PackageStore, Vec<(PackageId, Option<Arc<str>>)>, SourceMap), ExitCode> { |
| 284 | let fs = StdFs; |
| 285 | let project = match fs.load_project(dir.as_ref(), None) { |
| 286 | Ok(project) => project, |
| 287 | Err(errs) => { |
| 288 | for e in errs { |
| 289 | eprintln!("{e:?}"); |
| 290 | } |
| 291 | return Err(ExitCode::FAILURE); |
| 292 | } |
| 293 | }; |
| 294 | |
| 295 | if !project.errors.is_empty() { |
| 296 | for e in project.errors { |
| 297 | eprintln!("{e:?}"); |
| 298 | } |
| 299 | return Err(ExitCode::FAILURE); |
| 300 | } |
| 301 | |
| 302 | // This builds all the dependencies |
| 303 | let buildable_program = |
| 304 | BuildableProgram::new(TargetCapabilityFlags::all(), project.package_graph_sources); |
| 305 | |
| 306 | if !buildable_program.dependency_errors.is_empty() { |
| 307 | for e in buildable_program.dependency_errors { |
| 308 | eprintln!("{e:?}"); |
| 309 | } |
| 310 | return Err(ExitCode::FAILURE); |
| 311 | } |
| 312 | |
| 313 | let BuildableProgram { |
| 314 | store, |
| 315 | user_code, |
| 316 | user_code_dependencies, |
| 317 | .. |
| 318 | } = buildable_program; |
| 319 | |
| 320 | let source_map = qsc::SourceMap::new(user_code.sources, None); |
| 321 | |
| 322 | features.merge(LanguageFeatures::from_iter(user_code.language_features)); |
| 323 | |
| 324 | Ok((store, user_code_dependencies, source_map)) |
| 325 | } |