diff --git a/source/compiler/qsc/src/codegen.rs b/source/compiler/qsc/src/codegen.rs index 5538d07f39..007eb7fa2d 100644 --- a/source/compiler/qsc/src/codegen.rs +++ b/source/compiler/qsc/src/codegen.rs @@ -88,6 +88,84 @@ pub mod qir { }) } + /// Generates the RIR (raw and SSA forms) from an AST package, mirroring `get_qir_from_ast`. + pub fn get_rir_from_ast( + store: &mut PackageStore, + dependencies: &Dependencies, + ast_package: qsc_ast::ast::Package, + sources: SourceMap, + capabilities: TargetCapabilityFlags, + ) -> Result, Vec> { + if capabilities == TargetCapabilityFlags::all() { + return Err(vec![Error::UnsupportedRuntimeCapabilities]); + } + + let (unit, errors) = crate::compile::compile_ast( + store, + dependencies, + ast_package, + sources, + PackageType::Exe, + capabilities, + ); + + // Ensure it compiles before trying to add it to the store. + if !errors.is_empty() { + return Err(errors.iter().map(|e| Error::Compile(e.clone())).collect()); + } + + let package_id = store.insert(unit); + 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 compute_properties = PassContext::run_fir_passes_on_fir( + &fir_store, + fir_package_id, + capabilities, + ) + .map_err(|errors| { + let source_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::>() + })?; + + let (raw, ssa) = fir_to_rir( + &fir_store, + capabilities, + Some(compute_properties), + &entry, + PartialEvalConfig { + generate_debug_metadata: false, + }, + ) + .map_err(|e| { + let source_package_id = match e.span() { + Some(span) => span.package, + None => package_id, + }; + let source_package = store + .get(source_package_id) + .expect("package should be in store"); + vec![Error::PartialEvaluation(WithSource::from_map( + &source_package.sources, + e, + ))] + })?; + Ok(vec![raw.to_string(), ssa.to_string()]) + } + pub fn get_rir( sources: SourceMap, language_features: LanguageFeatures, diff --git a/source/compiler/qsc/src/interpret.rs b/source/compiler/qsc/src/interpret.rs index cfab2097a5..4695bbe8da 100644 --- a/source/compiler/qsc/src/interpret.rs +++ b/source/compiler/qsc/src/interpret.rs @@ -997,6 +997,61 @@ impl Interpreter { }) } + /// Performs RIR codegen using the given entry expression on a new instance of the environment + /// and simulator but using the current compilation. Returns the raw and SSA forms of the RIR + /// as strings. + pub fn get_rir(&mut self, expr: &str) -> std::result::Result, Vec> { + if self.capabilities == TargetCapabilityFlags::all() { + return Err(vec![Error::UnsupportedRuntimeCapabilities]); + } + + // Compile the expression. This operation will set the expression as + // the entry-point in the FIR store. + let (graph, compute_properties) = self.compile_entry_expr(expr)?; + + let Some(compute_properties) = compute_properties else { + // This can only happen if capability analysis was not run. This would be a bug + // and we are in a bad state and can't proceed. + panic!("internal error: compute properties not set after lowering entry expression"); + }; + let package = self.fir_store.get(self.package); + let entry = ProgramEntry { + exec_graph: graph, + expr: ( + self.package, + package + .entry + .expect("package must have an entry expression"), + ) + .into(), + }; + let (raw, ssa) = fir_to_rir( + &self.fir_store, + self.capabilities, + Some(compute_properties), + &entry, + PartialEvalConfig { + generate_debug_metadata: true, + }, + ) + .map_err(|e| { + let hir_package_id = match e.span() { + Some(span) => span.package, + None => map_fir_package_to_hir(self.package), + }; + let source_package = self + .compiler + .package_store() + .get(hir_package_id) + .expect("package should exist in the package store"); + vec![Error::PartialEvaluation(WithSource::from_map( + &source_package.sources, + e, + ))] + })?; + Ok(vec![raw.to_string(), ssa.to_string()]) + } + /// Performs QIR codegen using the given callable with the given arguments on a new instance of the environment /// and simulator but using the current compilation. pub fn qirgen_from_callable( diff --git a/source/compiler/qsc/src/interpret/tests.rs b/source/compiler/qsc/src/interpret/tests.rs index 09bd204840..e851673184 100644 --- a/source/compiler/qsc/src/interpret/tests.rs +++ b/source/compiler/qsc/src/interpret/tests.rs @@ -1098,6 +1098,73 @@ mod given_interpreter { .assert_eq(&res); } + #[test] + fn base_get_rir() { + let mut interpreter = get_interpreter_with_capabilities(TargetCapabilityFlags::empty()); + let (result, output) = line( + &mut interpreter, + indoc! {"operation Foo() : Result { use q = Qubit(); let r = M(q); Reset(q); return r; } "}, + ); + is_only_value(&result, &output, &Value::unit()); + let res = interpreter.get_rir("Foo()").expect("expected success"); + // get_rir returns the raw RIR and the SSA-transformed RIR. The full + // dump embeds source offsets in its debug metadata, so assert on the + // stable structure rather than snapshotting the whole program. + assert_eq!(res.len(), 2); + let ssa = &res[1]; + assert!(ssa.contains("Program:"), "{ssa}"); + assert!(ssa.contains("capabilities: Base"), "{ssa}"); + assert!(ssa.contains("num_results: 1"), "{ssa}"); + assert!(ssa.contains("call_type: Measurement"), "{ssa}"); + } + + #[test] + fn adaptive_get_rir() { + let mut interpreter = get_interpreter_with_capabilities( + TargetCapabilityFlags::Adaptive | TargetCapabilityFlags::IntegerComputations, + ); + let (result, output) = line( + &mut interpreter, + indoc! {r#" + namespace Test { + import Std.Math.*; + open QIR.Intrinsic; + @EntryPoint() + operation Main() : Result { + use q = Qubit(); + let pi_over_2 = 4.0 / 2.0; + __quantum__qis__rz__body(pi_over_2, q); + __quantum__qis__mresetz__body(q) + } + }"# + }, + ); + is_only_value(&result, &output, &Value::unit()); + let res = interpreter + .get_rir("Test.Main()") + .expect("expected success"); + assert_eq!(res.len(), 2); + let ssa = &res[1]; + assert!(ssa.contains("Program:"), "{ssa}"); + assert!(ssa.contains("Adaptive"), "{ssa}"); + assert!(ssa.contains("num_results: 1"), "{ssa}"); + assert!(ssa.contains("call_type: Measurement"), "{ssa}"); + } + + #[test] + fn get_rir_fails_for_unrestricted_profile() { + let mut interpreter = get_interpreter_with_capabilities(TargetCapabilityFlags::all()); + let (result, output) = line( + &mut interpreter, + indoc! {"operation Foo() : Result { use q = Qubit(); let r = M(q); Reset(q); return r; } "}, + ); + is_only_value(&result, &output, &Value::unit()); + let res = interpreter + .get_rir("Foo()") + .expect_err("expected get_rir to fail for the unrestricted profile"); + expect!["[UnsupportedRuntimeCapabilities]"].assert_eq(&format!("{res:?}")); + } + #[test] fn adaptive_qirgen_nested_output_types() { let mut interpreter = diff --git a/source/compiler/qsc_openqasm_compiler/src/tests.rs b/source/compiler/qsc_openqasm_compiler/src/tests.rs index e9b08bfb98..d2f2457f22 100644 --- a/source/compiler/qsc_openqasm_compiler/src/tests.rs +++ b/source/compiler/qsc_openqasm_compiler/src/tests.rs @@ -76,6 +76,25 @@ pub(crate) fn generate_qir_from_ast( ) } +/// Generates RIR (raw and SSA forms) from an AST package. +/// This function is used for testing purposes only, mirroring `generate_qir_from_ast`. +pub(crate) fn generate_rir_from_ast( + ast_package: Package, + source_map: SourceMap, + profile: Profile, +) -> Result, Vec> { + let capabilities = profile.into(); + let (stdid, mut store) = package_store_with_stdlib(capabilities); + let dependencies = vec![(PackageId::CORE, None), (stdid, None)]; + qsc::codegen::qir::get_rir_from_ast( + &mut store, + &dependencies, + ast_package, + source_map, + capabilities, + ) +} + fn compile>>(source: S) -> miette::Result> { let config = CompilerConfig::new( QubitSemantics::Qiskit, @@ -197,6 +216,19 @@ fn compile_qasm_to_qir(source: &str) -> Result> { Ok(qir) } +fn compile_qasm_to_rir(source: &str) -> Result, Vec> { + let unit = compile(source)?; + fail_on_compilation_errors(&unit); + let package = unit.package; + let rir = generate_rir_from_ast(package, unit.source_map, unit.profile).map_err(|errors| { + errors + .iter() + .map(|e| Report::new(e.clone())) + .collect::>() + })?; + Ok(rir) +} + /// used to do full compilation with best effort of the input. /// This is useful for fuzz testing. fn compile_qasm_best_effort(source: &str) { @@ -356,6 +388,23 @@ pub fn check_qasm_to_qir(source: &str, expect: &Expect) { } } +pub fn check_qasm_to_rir(source: &str, expect: &Expect) { + match compile_qasm_to_rir(source) { + // get_rir returns the raw and SSA-transformed RIR; check the final (SSA) form. + Ok(rir) => { + expect.assert_eq(&rir[1]); + } + Err(errors) => { + let buffer = errors + .iter() + .map(|e| format!("{e:?}")) + .collect::>() + .join("\n"); + expect.assert_eq(&buffer); + } + } +} + pub fn compile_qasm_to_qsharp_with_semantics>>( source: S, qubit_semantics: QubitSemantics, diff --git a/source/compiler/qsc_openqasm_compiler/src/tests/statement/pragma/profile.rs b/source/compiler/qsc_openqasm_compiler/src/tests/statement/pragma/profile.rs index 8435606464..f052c11210 100644 --- a/source/compiler/qsc_openqasm_compiler/src/tests/statement/pragma/profile.rs +++ b/source/compiler/qsc_openqasm_compiler/src/tests/statement/pragma/profile.rs @@ -1,10 +1,103 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::tests::{check_qasm_to_qsharp, compile_qasm_to_qir}; +use crate::tests::{check_qasm_to_qsharp, check_qasm_to_rir, compile_qasm_to_qir}; use expect_test::expect; use miette::Report; +#[test] +fn profile_pragma_generates_rir_with_adaptive_ri() { + let source = r#" + include "stdgates.inc"; + #pragma qdk.qir.profile Adaptive_RI + qubit[2] q; + output bit[2] c; + h q[0]; + cx q[0], q[1]; + c = measure q; + "#; + + check_qasm_to_rir( + source, + &expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: __quantum__rt__initialize + call_type: Regular + input_type: + [0]: Pointer + output_type: + body: + Callable 2: Callable: + name: __quantum__qis__h__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: + Callable 3: Callable: + name: __quantum__qis__cx__body + call_type: Regular + input_type: + [0]: Qubit + [1]: Qubit + output_type: + body: + Callable 4: Callable: + name: __quantum__qis__m__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: + Callable 5: Callable: + name: __quantum__rt__array_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 6: Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Pointer, ) + Call id(2), args( Qubit(0), ) + Call id(3), args( Qubit(0), Qubit(1), ) + Call id(4), args( Qubit(0), Result(0), ) + Call id(4), args( Qubit(1), Result(1), ) + Call id(5), args( Integer(2), Tag(0, 3), ) + Call id(6), args( Result(1), Tag(1, 5), ) + Call id(6), args( Result(0), Tag(2, 5), ) + Return + config: Config: + capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations) + num_qubits: 2 + num_results: 2 + tags: + [0]: 0_a + [1]: 1_a0r + [2]: 2_a1r + "#]], + ); +} + #[test] fn profile_pragma_compiles_with_adaptive_ri() -> miette::Result<(), Vec> { let source = r#" diff --git a/source/npm/qsharp/src/compiler/compiler.ts b/source/npm/qsharp/src/compiler/compiler.ts index c2be4ef82c..d9fd554389 100644 --- a/source/npm/qsharp/src/compiler/compiler.ts +++ b/source/npm/qsharp/src/compiler/compiler.ts @@ -37,9 +37,9 @@ type Wasm = typeof import("../../lib/web/qsc_wasm.js"); export interface ICompiler { checkCode(code: string): Promise; - getAst(code: string, languageFeatures: string[]): Promise; + getAst(program: ProgramConfig): Promise; - getHir(code: string, languageFeatures: string[]): Promise; + getHir(program: ProgramConfig): Promise; getRir(program: ProgramConfig): Promise; @@ -148,12 +148,14 @@ export class Compiler implements ICompiler { return diags; } - async getAst(code: string, languageFeatures: string[]): Promise { - return this.wasm.get_ast(code, languageFeatures); + async getAst(program: ProgramConfig): Promise { + const config = toWasmProgramConfig(program, "unrestricted"); + return callAndTransformExceptions(async () => this.wasm.get_ast(config)); } - async getHir(code: string, languageFeatures: string[]): Promise { - return this.wasm.get_hir(code, languageFeatures); + async getHir(program: ProgramConfig): Promise { + const config = toWasmProgramConfig(program, "unrestricted"); + return callAndTransformExceptions(async () => this.wasm.get_hir(config)); } async getRir(program: ProgramConfig): Promise { diff --git a/source/npm/qsharp/test/basics.js b/source/npm/qsharp/test/basics.js index 21127dcf21..bee0620d7e 100644 --- a/source/npm/qsharp/test/basics.js +++ b/source/npm/qsharp/test/basics.js @@ -480,9 +480,11 @@ test("cancel worker", () => { .catch((err) => { cancelledArray.push(err); }); - compiler.getHir(code, []).catch((err) => { - cancelledArray.push(err); - }); + compiler + .getHir({ sources: [["test.qs", code]], languageFeatures: [] }) + .catch((err) => { + cancelledArray.push(err); + }); // Ensure those tasks are running/queued before terminating. setTimeout(async () => { @@ -491,7 +493,10 @@ test("cancel worker", () => { // Start a new compiler and ensure that works fine const compiler2 = getCompilerWorker(compilerWorkerPath); - const result = await compiler2.getHir(code, []); + const result = await compiler2.getHir({ + sources: [["test.qs", code]], + languageFeatures: [], + }); compiler2.terminate(); // getHir should have worked diff --git a/source/playground/src/editor.tsx b/source/playground/src/editor.tsx index 28ef7323b1..0c2b04eef4 100644 --- a/source/playground/src/editor.tsx +++ b/source/playground/src/editor.tsx @@ -86,7 +86,13 @@ export function Editor(props: { setQir: (qir: string) => void; activeTab: ActiveTab; languageService: ILanguageServiceWorker; + language?: "qsharp" | "openqasm"; }) { + // The editor is remounted (keyed on language in the parent) whenever the + // language changes, so this is stable for the lifetime of the component. + const language = props.language ?? "qsharp"; + const fileName = language === "openqasm" ? "main.qasm" : "main.qs"; + const editor = useRef(null); const errMarks = useRef({ checkDiags: [], shotDiags: [] }); const editorDiv = useRef(null); @@ -115,7 +121,7 @@ export function Editor(props: { monaco.editor.setModelMarkers(model, "qsharp", markers); const errList = markers.map((err) => ({ - location: `main.qs@(${err.startLineNumber},${err.startColumn})`, + location: `${fileName}@(${err.startLineNumber},${err.startColumn})`, severity: err.severity, msg: err.message.split("\n\n"), })); @@ -130,18 +136,26 @@ export function Editor(props: { sources: [["code", code]] as [string, string][], languageFeatures: [], profile: - (await getTargetProfileFromEntryPoint("main.qs", code)) || + (language === "openqasm" + ? undefined + : await getTargetProfileFromEntryPoint("main.qs", code)) || "adaptive_rif", // Default to adaptive_rif for qir and rir generation + projectType: language, }; if (props.activeTab === "ast-tab") { - props.setAst(await props.compiler.getAst(code, config.languageFeatures)); + props.setAst(await props.compiler.getAst(config)); + return; } if (props.activeTab === "hir-tab") { - props.setHir(await props.compiler.getHir(code, config.languageFeatures)); + props.setHir(await props.compiler.getHir(config)); + return; } - const codeGenTimeout = 1000; // ms + if (props.activeTab === "qir-tab" || props.activeTab === "rir-tab") { + // OpenQASM codegen runs through the interpreter and is slower than Q#'s + // direct path, so allow more time before terminating the worker. + const codeGenTimeout = language === "openqasm" ? 10000 : 1000; // ms let timedOut = false; const compiler = props.compiler_worker_factory(); const compilerTimeout = setTimeout(() => { @@ -186,7 +200,12 @@ export function Editor(props: { const config = { sources: [["code", code]], languageFeatures: [], - profile: await getTargetProfileFromEntryPoint("main.qs", code), + // The entry-point profile annotation is Q#-specific; skip it for OpenQASM. + profile: + language === "openqasm" + ? undefined + : await getTargetProfileFromEntryPoint("main.qs", code), + projectType: language, } as ProgramConfig; try { @@ -237,12 +256,12 @@ export function Editor(props: { editor.current = newEditor; const srcModel = monaco.editor.getModel( - monaco.Uri.parse(props.kataSection?.id ?? "main.qs"), + monaco.Uri.parse(props.kataSection?.id ?? fileName), ) ?? monaco.editor.createModel( "", - "qsharp", - monaco.Uri.parse(props.kataSection?.id ?? "main.qs"), + language, + monaco.Uri.parse(props.kataSection?.id ?? fileName), ); srcModel.setValue(props.code); newEditor.setModel(srcModel); @@ -263,6 +282,7 @@ export function Editor(props: { srcModel.uri.toString(), srcModel.getVersionId(), srcModel.getValue(), + language, ); const measure = performance.measure( "update-document", @@ -280,7 +300,7 @@ export function Editor(props: { return () => { log.info("Disposing a monaco editor"); window.removeEventListener("resize", onResize); - props.languageService.closeDocument(srcModel.uri.toString()); + props.languageService.closeDocument(srcModel.uri.toString(), language); newEditor.dispose(); }; }, []); @@ -382,7 +402,7 @@ export function Editor(props: { return (
-
main.qs
+
{fileName}
elem.title); const sampleTitles = samples.map((sample) => sample.title); + const openqasmSampleTitles = openqasm_samples.map((sample) => sample.title); const [documentation, setDocumentation] = useState< Map | undefined @@ -134,12 +137,22 @@ function App(props: { katas: Kata[]; linkedCode?: string }) { setDocumentation(processDocumentFiles(docFiles)); } - const sampleCode = - samples.find((sample) => "sample-" + sample.title === currentNavItem) - ?.code || props.linkedCode; + // OpenQASM samples are namespaced with their own nav prefix because some + // sample titles (e.g. "Random Number Generator") exist in both languages. + const isOpenQasmSample = currentNavItem.startsWith("openqasm-sample-"); + const currentLanguage: "qsharp" | "openqasm" = isOpenQasmSample + ? "openqasm" + : "qsharp"; - const defaultShots = - samples.find((sample) => sample.title === currentNavItem)?.shots || 100; + const selectedSample = isOpenQasmSample + ? openqasm_samples.find( + (sample) => "openqasm-sample-" + sample.title === currentNavItem, + ) + : samples.find((sample) => "sample-" + sample.title === currentNavItem); + + const sampleCode = selectedSample?.code || props.linkedCode; + + const defaultShots = selectedSample?.shots || 100; const activeKata = kataTitles.includes(currentNavItem) ? props.katas.find((kata) => kata.title === currentNavItem) @@ -165,11 +178,14 @@ function App(props: { katas: Kata[]; linkedCode?: string }) { navSelected={onNavItemSelected} katas={kataTitles} samples={sampleTitles} + openqasmSamples={openqasmSampleTitles} namespaces={getNamespaces(documentation)} > {sampleCode ? ( <> , document.body); } +// Languages that share the language-service-backed providers below. OpenQASM +// supports completions, go-to-definition, find-references and rename in the +// language service, matching the set registered by the VS Code extension. +const qdkLanguages = ["qsharp", "openqasm"]; + function registerMonacoLanguageServiceProviders( languageService: ILanguageService, ) { - monaco.languages.registerCompletionItemProvider("qsharp", { + registerOpenQasmLanguage(); + + monaco.languages.registerCompletionItemProvider(qdkLanguages, { // @ts-expect-error - Monaco's types expect range to be defined, // but it's actually optional and the default behavior is better provideCompletionItems: async ( @@ -341,7 +364,7 @@ function registerMonacoLanguageServiceProviders( }, }); - monaco.languages.registerDefinitionProvider("qsharp", { + monaco.languages.registerDefinitionProvider(qdkLanguages, { provideDefinition: async ( model: monaco.editor.ITextModel, position: monaco.Position, @@ -360,7 +383,7 @@ function registerMonacoLanguageServiceProviders( }, }); - monaco.languages.registerReferenceProvider("qsharp", { + monaco.languages.registerReferenceProvider(qdkLanguages, { provideReferences: async ( model: monaco.editor.ITextModel, position: monaco.Position, @@ -424,7 +447,7 @@ function registerMonacoLanguageServiceProviders( }, }); - monaco.languages.registerRenameProvider("qsharp", { + monaco.languages.registerRenameProvider(qdkLanguages, { provideRenameEdits: async ( model: monaco.editor.ITextModel, position: monaco.Position, diff --git a/source/playground/src/nav.tsx b/source/playground/src/nav.tsx index 0ef118bdf6..ca99776843 100644 --- a/source/playground/src/nav.tsx +++ b/source/playground/src/nav.tsx @@ -6,6 +6,7 @@ export function Nav(props: { navSelected: (name: string) => void; katas: string[]; samples: string[]; + openqasmSamples: string[]; namespaces: string[]; }) { function onSelected(name: string) { @@ -14,7 +15,7 @@ export function Nav(props: { return ( + {props.samples.map((name) => (
))} + + + {props.openqasmSamples.map((name) => ( +
onSelected("openqasm-sample-" + name)} + > + {name} +
+ ))} + {props.katas.map((name) => (
", + ">=", + "+", + "++", + "+=", + "-", + "-=", + "*", + "**", + "*=", + "/", + "/=", + "%", + "^", + "|", + "||", + "&", + "&&", + "!", + "~", + "<<", + ">>", + "->", + ], + symbols: /[=> Option { } #[wasm_bindgen] -pub fn get_ast(code: &str, language_features: Vec) -> Result { - let language_features = LanguageFeatures::from_iter(language_features); - let sources = SourceMap::new([("code".into(), code.into())], None); - let profile = Profile::Unrestricted; - let package = STORE_CORE_STD.with(|(store, std)| { +pub fn get_ast(program: ProgramConfig) -> Result { + if is_openqasm_program(&program) { + let (sources, _capabilities) = into_openqasm_arg(program); + let (store, package_id) = compile_openqasm_to_store(&sources)?; + let unit = store + .get(package_id) + .expect("compiled OpenQASM package should be in the store"); + Ok(format!("{}", unit.ast.package)) + } else { + let (source_map, _capabilities, language_features, store, deps) = + into_qsc_args(program, None, false).map_err(compile_errors_into_qsharp_errors_json)?; let (unit, _) = compile::compile( - store, - &[(*std, None)], - sources, + &store, + &deps[..], + source_map, PackageType::Exe, - profile.into(), + Profile::Unrestricted.into(), language_features, ); - unit.ast.package - }); - Ok(format!("{package}")) + Ok(format!("{}", unit.ast.package)) + } } #[wasm_bindgen] -pub fn get_hir(code: &str, language_features: Vec) -> Result { - let language_features = LanguageFeatures::from_iter(language_features); - let sources = SourceMap::new([("code".into(), code.into())], None); - let profile = Profile::Unrestricted; - let package = STORE_CORE_STD.with(|(store, std)| { +pub fn get_hir(program: ProgramConfig) -> Result { + if is_openqasm_program(&program) { + let (sources, _capabilities) = into_openqasm_arg(program); + let (store, package_id) = compile_openqasm_to_store(&sources)?; + let unit = store + .get(package_id) + .expect("compiled OpenQASM package should be in the store"); + Ok(unit.package.to_string()) + } else { + let (source_map, _capabilities, language_features, store, deps) = + into_qsc_args(program, None, false).map_err(compile_errors_into_qsharp_errors_json)?; let (unit, _) = compile::compile( - store, - &[(*std, None)], - sources, + &store, + &deps[..], + source_map, PackageType::Exe, - profile.into(), + Profile::Unrestricted.into(), language_features, ); - unit.package - }); - Ok(package.to_string()) + Ok(unit.package.to_string()) + } } #[wasm_bindgen] pub fn get_rir(program: ProgramConfig) -> Result, String> { - let (source_map, capabilities, language_features, store, deps) = - into_qsc_args(program, None, false).map_err(compile_errors_into_qsharp_errors_json)?; + if is_openqasm_program(&program) { + let (sources, capabilities) = into_openqasm_arg(program); + get_rir_from_openqasm(&sources, capabilities) + } else { + let (source_map, capabilities, language_features, store, deps) = + into_qsc_args(program, None, false).map_err(compile_errors_into_qsharp_errors_json)?; - qsc::codegen::qir::get_rir( - source_map, - language_features, - capabilities, - store, - &deps[..], - ) - .map_err(interpret_errors_into_qsharp_errors_json) + qsc::codegen::qir::get_rir( + source_map, + language_features, + capabilities, + store, + &deps[..], + ) + .map_err(interpret_errors_into_qsharp_errors_json) + } +} + +/// Compiles raw `OpenQASM` sources into a Q# package store, returning the store and the id of the +/// compiled package. Used by the AST and HIR views, which read the compiled package directly. +fn compile_openqasm_to_store( + sources: &[(Arc, Arc)], +) -> Result<(PackageStore, PackageId), String> { + let (file, source) = sources + .iter() + .next() + .expect("there should be at least one source"); + let mut resolver = sources.iter().cloned().collect::(); + let CompileRawQasmResult(store, source_package_id, _dependencies, _sig, errors) = + qsc::openqasm::parse_and_compile_raw_qasm( + source.clone(), + file.clone(), + Some(&mut resolver), + PackageType::Exe, + ); + if !errors.is_empty() { + return Err(interpret_errors_into_qsharp_errors_json( + errors + .iter() + .map(|e| qsc::interpret::Error::Compile(e.clone())) + .collect(), + )); + } + Ok((store, source_package_id)) +} + +pub(crate) fn get_rir_from_openqasm( + sources: &[(Arc, Arc)], + capabilities: TargetCapabilityFlags, +) -> Result, String> { + let (entry_expr, mut interpreter) = get_interpreter_from_openqasm(sources, capabilities) + .map_err(interpret_errors_into_qsharp_errors_json)?; + interpreter + .get_rir(&entry_expr) + .map_err(interpret_errors_into_qsharp_errors_json) } #[wasm_bindgen]