microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
alex/pythontelem

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

compiler/qsc/src/bin/qsc.rs

325lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4allocator::assign_global!();
5
6use clap::{crate_version, ArgGroup, Parser, ValueEnum};
7use log::info;
8use miette::{Context, IntoDiagnostic, Report};
9use qsc::hir::PackageId;
10use qsc::packages::BuildableProgram;
11use qsc::{compile::compile, PassContext};
12use qsc_codegen::qir::fir_to_qir;
13use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
14use qsc_frontend::{
15 compile::{PackageStore, SourceContents, SourceMap, SourceName},
16 error::WithSource,
17};
18use qsc_hir::hir::Package;
19use qsc_partial_eval::ProgramEntry;
20use qsc_passes::PackageType;
21use qsc_project::{FileSystem, StdFs};
22use std::sync::Arc;
23use 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)]
32pub 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
43impl 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)))]
56struct 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)]
95enum Emit {
96 Hir,
97 Qir,
98}
99
100#[allow(clippy::too_many_lines)]
101fn 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
191fn 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
210fn 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
221fn 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)]
280fn 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}