diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs index 5225b2fe74..0c51fc5033 100644 --- a/compiler/qsc/src/interpret.rs +++ b/compiler/qsc/src/interpret.rs @@ -210,19 +210,7 @@ impl Interpreter { fir_store, lowerer: qsc_lowerer::Lowerer::new().with_debug(dbg), env: Env::default(), - sim: BackendChain::new( - SparseSim::new(), - CircuitBuilder::new(CircuitConfig { - // When using in conjunction with the simulator, - // the circuit builder should *not* perform base profile - // decompositions, in order to match the simulator's behavior. - // - // Note that conditional compilation (e.g. @Config(Base) attributes) - // will still respect the selected profile. This also - // matches the behavior of the simulator. - base_profile: false, - }), - ), + sim: sim_circuit_backend(), quantum_seed: None, classical_seed: None, package: map_hir_package_to_fir(package_id), @@ -412,16 +400,16 @@ impl Interpreter { /// /// An operation can be specified by its name or a lambda expression that only takes qubits. /// e.g. `Sample.Main` , `qs => H(qs[0])` + /// + /// If `simulate` is specified, the program is simulated and the resulting + /// circuit is returned (a.k.a. trace mode). Otherwise, the circuit is generated without + /// simulation. In this case circuit generation may fail if the program contains dynamic + /// behavior (quantum operations that are dependent on measurement results). pub fn circuit( &mut self, entry: CircuitEntryPoint, + simulate: bool, ) -> std::result::Result> { - let mut sink = std::io::sink(); - let mut out = GenericReceiver::new(&mut sink); - let mut sim = CircuitBuilder::new(CircuitConfig { - base_profile: self.capabilities.is_empty(), - }); - let entry_expr = match entry { CircuitEntryPoint::Operation(operation_expr) => { let (item, functor_app) = self.eval_to_operation(&operation_expr)?; @@ -433,13 +421,23 @@ impl Interpreter { CircuitEntryPoint::EntryPoint => None, }; - if let Some(entry_expr) = entry_expr { - self.run_with_sim(&mut sim, &mut out, &entry_expr)? + let circuit = if simulate { + let mut sim = sim_circuit_backend(); + + self.run_with_sim_no_output(entry_expr, &mut sim)?; + + sim.chained.finish() } else { - self.eval_entry_with_sim(&mut sim, &mut out) - }?; + let mut sim = CircuitBuilder::new(CircuitConfig { + base_profile: self.capabilities.is_empty(), + }); + + self.run_with_sim_no_output(entry_expr, &mut sim)?; + + sim.finish() + }; - Ok(sim.finish()) + Ok(circuit) } /// Runs the given entry expression on the given simulator with a new instance of the environment @@ -468,6 +466,38 @@ impl Interpreter { )) } + fn run_with_sim_no_output( + &mut self, + entry_expr: Option, + sim: &mut impl Backend>, + ) -> InterpretResult { + let mut sink = std::io::sink(); + let mut out = GenericReceiver::new(&mut sink); + + let (package_id, graph) = if let Some(entry_expr) = entry_expr { + // entry expression is provided + (self.package, self.compile_entry_expr(&entry_expr)?.0.into()) + } else { + // no entry expression, use the entrypoint in the package + (self.source_package, self.get_entry_exec_graph()?) + }; + + if self.quantum_seed.is_some() { + sim.set_seed(self.quantum_seed); + } + + eval( + package_id, + self.classical_seed, + graph, + self.compiler.package_store(), + &self.fir_store, + &mut Env::default(), + sim, + &mut out, + ) + } + fn compile_entry_expr( &mut self, expr: &str, @@ -581,6 +611,22 @@ impl Interpreter { } } +fn sim_circuit_backend() -> BackendChain { + BackendChain::new( + SparseSim::new(), + CircuitBuilder::new(CircuitConfig { + // When using in conjunction with the simulator, + // the circuit builder should *not* perform base profile + // decompositions, in order to match the simulator's behavior. + // + // Note that conditional compilation (e.g. @Config(Base) attributes) + // will still respect the selected profile. This also + // matches the behavior of the simulator. + base_profile: false, + }), + ) +} + /// Describes the entry point for circuit generation. pub enum CircuitEntryPoint { /// An operation. This must be a callable name or a lambda diff --git a/compiler/qsc/src/interpret/circuit_tests.rs b/compiler/qsc/src/interpret/circuit_tests.rs index 4b2255e8fa..6b9a4fa77a 100644 --- a/compiler/qsc/src/interpret/circuit_tests.rs +++ b/compiler/qsc/src/interpret/circuit_tests.rs @@ -39,7 +39,7 @@ fn empty() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![].assert_eq(&circ.to_string()); @@ -61,7 +61,7 @@ fn one_gate() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -86,7 +86,7 @@ fn rotation_gate() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); // The wire isn't visible here since the gate label is longer @@ -115,7 +115,7 @@ fn classical_for_loop() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -142,7 +142,7 @@ fn m_base_profile() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -171,7 +171,7 @@ fn m_unrestricted_profile() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -199,7 +199,7 @@ fn mresetz_unrestricted_profile() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -227,7 +227,7 @@ fn mresetz_base_profile() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -265,7 +265,7 @@ fn unrestricted_profile_result_comparison() { interpreter.set_quantum_seed(Some(2)); let circuit_err = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect_err("circuit should return error") .pop() .expect("error should exist"); @@ -283,9 +283,23 @@ fn unrestricted_profile_result_comparison() { let mut out = std::io::sink(); let mut r = GenericReceiver::new(&mut out); - // Counterintuitive but expected: result comparisons - // are okay if calling get_circuit() after incremental - // evaluation, because we're using the current simulator + // Result comparisons are okay when tracing + // circuit with the simulator. + let circ = interpreter + .circuit(CircuitEntryPoint::EntryPoint, true) + .expect("circuit generation should succeed"); + + expect![[r" + q_0 ── H ──── M ──── X ─── |0〉 ─ + ╘═════════════════ + q_1 ── H ──── M ─── |0〉 ──────── + ╘═════════════════ + "]] + .assert_eq(&circ.to_string()); + + // Result comparisons are also okay if calling + // get_circuit() after incremental evaluation, + // because we're using the current simulator // state. interpreter .eval_fragments(&mut r, "Test.Main();") @@ -320,7 +334,7 @@ fn custom_intrinsic() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -349,7 +363,7 @@ fn custom_intrinsic_classical_arg() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); // A custom intrinsic that doesn't take qubits just doesn't @@ -380,7 +394,7 @@ fn custom_intrinsic_one_classical_arg() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); // A custom intrinsic that doesn't take qubits just doesn't @@ -418,7 +432,7 @@ fn custom_intrinsic_mixed_args() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); // This is one gate that spans ten target wires, even though the @@ -458,7 +472,7 @@ fn operation_with_qubits() { ); let circ = interpreter - .circuit(CircuitEntryPoint::Operation("Test.Test".into())) + .circuit(CircuitEntryPoint::Operation("Test.Test".into()), false) .expect("circuit generation should succeed"); expect![[r" @@ -488,7 +502,7 @@ fn operation_with_qubits_base_profile() { ); let circ = interpreter - .circuit(CircuitEntryPoint::Operation("Test.Test".into())) + .circuit(CircuitEntryPoint::Operation("Test.Test".into()), false) .expect("circuit generation should succeed"); expect![[r" @@ -535,7 +549,7 @@ fn operation_with_qubit_arrays() { ); let circ = interpreter - .circuit(CircuitEntryPoint::Operation("Test.Test".into())) + .circuit(CircuitEntryPoint::Operation("Test.Test".into()), false) .expect("circuit generation should succeed"); expect![[r" @@ -587,7 +601,10 @@ fn adjoint_operation() { ); let circ = interpreter - .circuit(CircuitEntryPoint::Operation("Adjoint Test.Foo".into())) + .circuit( + CircuitEntryPoint::Operation("Adjoint Test.Foo".into()), + false, + ) .expect("circuit generation should succeed"); expect![[r" @@ -608,7 +625,7 @@ fn lambda() { ); let circ = interpreter - .circuit(CircuitEntryPoint::Operation("q => H(q)".into())) + .circuit(CircuitEntryPoint::Operation("q => H(q)".into()), false) .expect("circuit generation should succeed"); expect![[r" @@ -649,7 +666,10 @@ fn controlled_operation() { ); let circ_err = interpreter - .circuit(CircuitEntryPoint::Operation("Controlled Test.SWAP".into())) + .circuit( + CircuitEntryPoint::Operation("Controlled Test.SWAP".into()), + false, + ) .expect_err("circuit generation should fail"); // Controlled operations are not supported at the moment. @@ -682,7 +702,7 @@ fn internal_operation() { ); let circ_err = interpreter - .circuit(CircuitEntryPoint::Operation("Test.Test".into())) + .circuit(CircuitEntryPoint::Operation("Test.Test".into()), false) .expect_err("circuit generation should fail"); expect![[r#" @@ -731,7 +751,7 @@ fn operation_with_non_qubit_args() { ); let circ_err = interpreter - .circuit(CircuitEntryPoint::Operation("Test.Test".into())) + .circuit(CircuitEntryPoint::Operation("Test.Test".into()), false) .expect_err("circuit generation should fail"); expect![[r" diff --git a/compiler/qsc_circuit/src/builder.rs b/compiler/qsc_circuit/src/builder.rs index 558282cd5d..b34b2796e9 100644 --- a/compiler/qsc_circuit/src/builder.rs +++ b/compiler/qsc_circuit/src/builder.rs @@ -206,7 +206,13 @@ impl Backend for Builder { Some(classical_args) }, )); - Some(Ok(Value::unit())) + + match name { + // Special case this known intrinsic to match the simulator + // behavior, so that our samples will work + "BeginEstimateCaching" => Some(Ok(Value::Bool(true))), + _ => Some(Ok(Value::unit())), + } } } diff --git a/npm/qsharp/src/compiler/compiler.ts b/npm/qsharp/src/compiler/compiler.ts index 76dd5d12e1..2fea46a352 100644 --- a/npm/qsharp/src/compiler/compiler.ts +++ b/npm/qsharp/src/compiler/compiler.ts @@ -81,6 +81,7 @@ export interface ICompiler { getCircuit( config: ProgramConfig, target: TargetProfile, + simulate: boolean, operation?: IOperationInfo, ): Promise; @@ -236,13 +237,15 @@ export class Compiler implements ICompiler { async getCircuit( config: ProgramConfig, target: TargetProfile, + simulate: boolean, operation?: IOperationInfo, ): Promise { return this.wasm.get_circuit( config.sources, target, - operation, config.languageFeatures || [], + simulate, + operation, ); } diff --git a/npm/qsharp/ux/circuit.tsx b/npm/qsharp/ux/circuit.tsx index 1a63141762..e968d3aa89 100644 --- a/npm/qsharp/ux/circuit.tsx +++ b/npm/qsharp/ux/circuit.tsx @@ -2,8 +2,9 @@ // Licensed under the MIT License. import * as qviz from "@microsoft/quantum-viz.js/lib"; -import { useEffect, useRef } from "preact/hooks"; +import { useEffect, useRef, useState } from "preact/hooks"; import { CircuitProps } from "./data.js"; +import { Spinner } from "./spinner.js"; // For perf reasons we set a limit on how many gates/qubits // we attempt to render. This is still a lot higher than a human would @@ -12,56 +13,157 @@ import { CircuitProps } from "./data.js"; const MAX_OPERATIONS = 10000; const MAX_QUBITS = 1000; -/* This component is shared by the Python widget and the VS Code panel */ +// This component is shared by the Python widget and the VS Code panel export function Circuit(props: { circuit: qviz.Circuit }) { + const circuit = props.circuit; + const unrenderable = + circuit.qubits.length === 0 || + circuit.operations.length > MAX_OPERATIONS || + circuit.qubits.length > MAX_QUBITS; + + return ( +
+ {unrenderable ? ( + + ) : ( + + )} +
+ ); +} + +function ZoomableCircuit(props: { circuit: qviz.Circuit }) { const circuitDiv = useRef(null); + const [zoomLevel, setZoomLevel] = useState(undefined); + const [rendering, setRendering] = useState(true); + + useEffect(() => { + // Enable "rendering" text while the circuit is being drawn + setRendering(true); + const container = circuitDiv.current!; + container.innerHTML = ""; + }, [props.circuit]); + + useEffect(() => { + if (rendering) { + const container = circuitDiv.current!; + // Draw the circuit - may take a while for large circuits + const svg = renderCircuit(props.circuit, container); + // Calculate the initial zoom level based on the container width + const initialZoom = calculateZoomToFit(container, svg as SVGElement); + // Set the initial zoom level + setZoomLevel(initialZoom); + // Resize the SVG to fit + updateWidth(); + // Disable "rendering" text + setRendering(false); + } + }, [rendering]); + + useEffect(() => { + updateWidth(); + }, [zoomLevel]); + return ( +
+
+ +
+
+ {rendering + ? `Rendering diagram with ${props.circuit.operations.length} gates...` + : ""} +
+
+
+ ); + + function updateWidth() { + const svg = circuitDiv.current?.getElementsByClassName("qviz")[0]; + if (svg) { + const width = svg.getAttribute("width")!; + svg.setAttribute( + "style", + `max-width: ${width}; width: ${(parseInt(width) * (zoomLevel || 100)) / 100}; height: auto`, + ); + } + } + + function renderCircuit(circuit: qviz.Circuit, container: HTMLDivElement) { + qviz.draw(circuit, container); + + // quantum-viz hardcodes the styles in the SVG. + // Remove the style elements -- we'll define the styles in our own CSS. + const styleElements = container.querySelectorAll("style"); + styleElements?.forEach((tag) => tag.remove()); + + return container.getElementsByClassName("qviz")[0]!; + } + + function calculateZoomToFit(container: HTMLDivElement, svg: SVGElement) { + const containerWidth = container.clientWidth; + // width and height the true dimensions generated by qviz + const width = parseInt(svg.getAttribute("width")!); + const height = svg.getAttribute("height")!; + + svg.setAttribute("viewBox", `0 0 ${width} ${height}`); + const zoom = Math.min(Math.ceil((containerWidth / width) * 100), 100); + return zoom; + } +} + +function Unrenderable(props: { qubits: number; operations: number }) { const errorDiv = - props.circuit.qubits.length === 0 ? ( + props.qubits === 0 ? (

No circuit to display. No qubits have been allocated.

- ) : props.circuit.operations.length > MAX_OPERATIONS ? ( + ) : props.operations > MAX_OPERATIONS ? (

- This circuit has too many gates to display. It has{" "} - {props.circuit.operations.length} gates, but the maximum supported is{" "} - {MAX_OPERATIONS}. + This circuit has too many gates to display. It has {props.operations}{" "} + gates, but the maximum supported is {MAX_OPERATIONS}.

- ) : props.circuit.qubits.length > MAX_QUBITS ? ( + ) : props.qubits > MAX_QUBITS ? (

- This circuit has too many qubits to display. It has{" "} - {props.circuit.qubits.length} qubits, but the maximum supported is{" "} - {MAX_QUBITS}. + This circuit has too many qubits to display. It has {props.qubits}{" "} + qubits, but the maximum supported is {MAX_QUBITS}.

) : undefined; - useEffect(() => { - if (errorDiv !== undefined) { - circuitDiv.current!.innerHTML = ""; - return; - } - - qviz.draw(props.circuit, circuitDiv.current!); - - // quantum-viz hardcodes the styles in the SVG. - // Remove the style elements -- we'll define the styles in our own CSS. - const styleElements = circuitDiv.current?.querySelectorAll("style"); - styleElements?.forEach((tag) => tag.remove()); - }, [props.circuit]); + return
{errorDiv}
; +} - return ( -
-
{errorDiv}
-
-
- ); +function ZoomControl(props: { + zoom?: number; + onChange: (zoom: number) => void; +}) { + return props.zoom !== undefined ? ( +

+ + + props.onChange(parseInt((e.target as HTMLInputElement).value) || 0) + } + /> + % +

+ ) : null; } -/* This component is exclusive to the VS Code panel */ +// This component is exclusive to the VS Code panel export function CircuitPanel(props: CircuitProps) { const error = props.errorHtml ? (
@@ -78,41 +180,29 @@ export function CircuitPanel(props: CircuitProps) { return (
-

{props.title}

+

+ {props.title} {props.simulated ? "(Trace)" : ""} +

- {props.circuit ? : null}
{error}

{props.targetProfile}

- {props.simulating ? ( -

- This circuit diagram was generated while running the program in the - simulator. -
-
- If your program contains behavior that is conditional on a qubit - measurement result, note that this circuit only shows the outcome that - was encountered during this simulation. Running the program again may - result in a different circuit being generated. -

- ) : null} - { - // show tip when the circuit is empty and we didn't run under the simulator (i.e. debugging) - !props.simulating && - !props.errorHtml && - props.circuit?.qubits.length === 0 ? ( -

- - Tip: you can generate a circuit diagram for any operation that - takes qubits or arrays of qubits as input. - -

- ) : null - }

- - Learn more - + { + props.simulated + ? "WARNING: This diagram shows the result of tracing a dynamic circuit, and may change from run to run." + : "\xa0" /* nbsp to keep line height consistent */ + }

+

+ Learn more at{" "} + https://aka.ms/qdk.circuits +

+ {props.calculating ? ( +
+ +
+ ) : null} + {props.circuit ? : null}
); } diff --git a/npm/qsharp/ux/data.ts b/npm/qsharp/ux/data.ts index b048a2eb46..72a3ab4be4 100644 --- a/npm/qsharp/ux/data.ts +++ b/npm/qsharp/ux/data.ts @@ -69,7 +69,10 @@ export type CircuitProps = { circuit?: CircuitData; errorHtml?: string; targetProfile: string; - simulating: boolean; + /** Circuit was generated by running the simulator */ + simulated: boolean; + /** Circuit is still being generated */ + calculating: boolean; }; export type CircuitData = import("@microsoft/quantum-viz.js/lib").Circuit; diff --git a/npm/qsharp/ux/estimatesPanel.tsx b/npm/qsharp/ux/estimatesPanel.tsx index 5ac0b98a5e..4f7093bc3e 100644 --- a/npm/qsharp/ux/estimatesPanel.tsx +++ b/npm/qsharp/ux/estimatesPanel.tsx @@ -6,6 +6,7 @@ import { type ReData, SingleEstimateResult } from "./data.js"; import { EstimatesOverview } from "./estimatesOverview.js"; import { ReTable } from "./reTable.js"; import { SpaceChart } from "./spaceChart.js"; +import { Spinner } from "./spinner.js"; export function EstimatesPanel(props: { estimatesData: ReData[]; @@ -52,16 +53,7 @@ export function EstimatesPanel(props: { {props.calculating ? ( - - - + ) : null}

Azure Quantum Resource Estimator

diff --git a/npm/qsharp/ux/spinner.tsx b/npm/qsharp/ux/spinner.tsx new file mode 100644 index 0000000000..70132318c3 --- /dev/null +++ b/npm/qsharp/ux/spinner.tsx @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export function Spinner(props: { style?: string }) { + return ( + + + + ); +} diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs index 17db39e947..bd606a0091 100644 --- a/pip/src/interpreter.rs +++ b/pip/src/interpreter.rs @@ -247,7 +247,7 @@ impl Interpreter { } }; - match self.interpreter.circuit(entrypoint) { + match self.interpreter.circuit(entrypoint, false) { Ok(circuit) => Ok(Circuit(circuit).into_py(py)), Err(errors) => Err(QSharpError::new_err(format_errors(errors))), } diff --git a/samples/notebooks/circuits.ipynb b/samples/notebooks/circuits.ipynb index df462406e0..dcc340678b 100644 --- a/samples/notebooks/circuits.ipynb +++ b/samples/notebooks/circuits.ipynb @@ -88,7 +88,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e8f7b9102d4d4a8b8b14c404af5a191e", + "model_id": "4a760784b95c464cbeeb20e6cb39493f", "version_major": 2, "version_minor": 0 }, @@ -111,7 +111,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You can syntheisize a circuit diagram for any program by calling `qsharp.circuit()` with an entry expression." + "You can synthesize a circuit diagram for any program by calling `qsharp.circuit()` with an entry expression." ] }, { @@ -149,7 +149,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d83baba969474c70940287978e39abe0", + "model_id": "739e08c87275454186529bcd76156a0d", "version_major": 2, "version_minor": 0 }, @@ -203,7 +203,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "90a8e20a35fa4591be278aa0a7982861", + "model_id": "17f2fea978c24ccb8ae137888ac4aa05", "version_major": 2, "version_minor": 0 }, @@ -262,7 +262,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "8790540b2821494f82beea3fc5496a23", + "model_id": "2dac021b8bf449b5a8df9fef0894f7b2", "version_major": 2, "version_minor": 0 }, @@ -340,7 +340,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "bc7c91299732407999d10edf4e316715", + "model_id": "df689aa58fa842ccb668294f5ce1e56b", "version_major": 2, "version_minor": 0 }, @@ -421,30 +421,45 @@ "name": "stdout", "output_type": "stream", "text": [ - "result was One, applying X gate\n", + "Simulating program...\n", "result was Zero\n", - "result was One, applying X gate\n" - ] - }, - { - "ename": "QSharpError", - "evalue": "Error: Result comparison is unsupported for this backend\nCall stack:\n at ResetIfOne in line_0\n\u001b[31mQsc.Eval.ResultComparisonUnsupported\u001b[0m\n\n \u001b[31m×\u001b[0m runtime error\n\u001b[31m ╰─▶ \u001b[0mResult comparison is unsupported for this backend\n ╭─[\u001b[36;1;4mline_0\u001b[0m:5:1]\n \u001b[2m5\u001b[0m │ let r = M(q);\n \u001b[2m6\u001b[0m │ if (r == One) {\n · \u001b[35;1m ─┬─\u001b[0m\n · \u001b[35;1m╰── \u001b[35;1mcannot compare to result\u001b[0m\u001b[0m\n \u001b[2m7\u001b[0m │ Message(\"result was One, applying X gate\");\n ╰────\n", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mQSharpError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[16], line 5\u001b[0m\n\u001b[0;32m 2\u001b[0m qsharp\u001b[38;5;241m.\u001b[39mrun(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mResetIfOne()\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;241m3\u001b[39m)\n\u001b[0;32m 4\u001b[0m \u001b[38;5;66;03m# The same program cannot be synthesized as a circuit because of the conditional X gate.\u001b[39;00m\n\u001b[1;32m----> 5\u001b[0m qsharp\u001b[38;5;241m.\u001b[39mcircuit(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mResetIfOne()\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", - "File \u001b[1;32mc:\\src\\qsharp\\.venv\\Lib\\site-packages\\qsharp\\_qsharp.py:259\u001b[0m, in \u001b[0;36mcircuit\u001b[1;34m(entry_expr, operation)\u001b[0m\n\u001b[0;32m 244\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mcircuit\u001b[39m(\n\u001b[0;32m 245\u001b[0m entry_expr: Optional[\u001b[38;5;28mstr\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m, \u001b[38;5;241m*\u001b[39m, operation: Optional[\u001b[38;5;28mstr\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m 246\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Circuit:\n\u001b[0;32m 247\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m 248\u001b[0m \u001b[38;5;124;03m Synthesizes a circuit for a Q# program. Either an entry\u001b[39;00m\n\u001b[0;32m 249\u001b[0m \u001b[38;5;124;03m expression or an operation must be provided.\u001b[39;00m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 257\u001b[0m \u001b[38;5;124;03m :raises QSharpError: If there is an error synthesizing the circuit.\u001b[39;00m\n\u001b[0;32m 258\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[1;32m--> 259\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m get_interpreter()\u001b[38;5;241m.\u001b[39mcircuit(entry_expr, operation)\n", - "\u001b[1;31mQSharpError\u001b[0m: Error: Result comparison is unsupported for this backend\nCall stack:\n at ResetIfOne in line_0\n\u001b[31mQsc.Eval.ResultComparisonUnsupported\u001b[0m\n\n \u001b[31m×\u001b[0m runtime error\n\u001b[31m ╰─▶ \u001b[0mResult comparison is unsupported for this backend\n ╭─[\u001b[36;1;4mline_0\u001b[0m:5:1]\n \u001b[2m5\u001b[0m │ let r = M(q);\n \u001b[2m6\u001b[0m │ if (r == One) {\n · \u001b[35;1m ─┬─\u001b[0m\n · \u001b[35;1m╰── \u001b[35;1mcannot compare to result\u001b[0m\u001b[0m\n \u001b[2m7\u001b[0m │ Message(\"result was One, applying X gate\");\n ╰────\n" + "result was Zero\n", + "result was Zero\n", + "\n", + "Synthesizing circuit for program (should raise error)...\n", + "Error: cannot compare measurement results\n", + "Call stack:\n", + " at ResetIfOne in line_0\n", + "\u001b[31mQsc.Eval.ResultComparisonUnsupported\u001b[0m\n", + "\n", + " \u001b[31m×\u001b[0m runtime error\n", + "\u001b[31m ╰─▶ \u001b[0mcannot compare measurement results\n", + " ╭─[\u001b[36;1;4mline_0\u001b[0m:5:1]\n", + " \u001b[2m5\u001b[0m │ let r = M(q);\n", + " \u001b[2m6\u001b[0m │ if (r == One) {\n", + " · \u001b[35;1m ─┬─\u001b[0m\n", + " · \u001b[35;1m╰── \u001b[35;1mcannot compare to result\u001b[0m\u001b[0m\n", + " \u001b[2m7\u001b[0m │ Message(\"result was One, applying X gate\");\n", + " ╰────\n", + "\u001b[36m help: \u001b[0mcomparing measurement results is not supported when performing\n", + " circuit synthesis or base profile QIR generation\n", + "\n" ] } ], "source": [ "# Program can be simulated. Differerent shots may produce different results.\n", + "print(\"Simulating program...\")\n", "qsharp.run(\"ResetIfOne()\", 3)\n", "\n", + "print()\n", + "\n", "# The same program cannot be synthesized as a circuit because of the conditional X gate.\n", - "qsharp.circuit(\"ResetIfOne()\")" + "print(\"Synthesizing circuit for program (should raise error)...\")\n", + "try:\n", + " qsharp.circuit(\"ResetIfOne()\")\n", + "except qsharp.QSharpError as e:\n", + " print(e)" ] }, { @@ -502,7 +517,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c36cf04725e0491aa72a621f0d44f933", + "model_id": "c4ef7327e1194d37991f5b46b35f3a9d", "version_major": 2, "version_minor": 0 }, @@ -536,7 +551,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.11.9" } }, "nbformat": 4, diff --git a/vscode/src/circuit.ts b/vscode/src/circuit.ts index 911c7e4c59..804829076e 100644 --- a/vscode/src/circuit.ts +++ b/vscode/src/circuit.ts @@ -4,8 +4,10 @@ import { type Circuit as CircuitData } from "@microsoft/quantum-viz.js/lib"; import { escapeHtml } from "markdown-it/lib/common/utils"; import { + ICompilerWorker, IOperationInfo, IRange, + TargetProfile, VSDiagnostic, getCompilerWorker, log, @@ -17,9 +19,41 @@ import { loadProject } from "./projectSystem"; import { EventType, UserFlowStatus, sendTelemetryEvent } from "./telemetry"; import { getRandomGuid } from "./utils"; import { sendMessageToPanel } from "./webviewPanel"; +import { ProgramConfig } from "../../npm/qsharp/dist/compiler/compiler"; const compilerRunTimeoutMs = 1000 * 60 * 5; // 5 minutes +/** + * Input parameters for generating a circuit. + */ +type CircuitParams = { + program: ProgramConfig; + targetProfile: TargetProfile; + operation?: IOperationInfo; +}; + +/** + * Result of a circuit generation attempt. + */ +type CircuitOrError = { + simulated: boolean; +} & ( + | { + result: "success"; + circuit: CircuitData; + } + | { + result: "error"; + errors: { + document: string; + diag: VSDiagnostic; + stack: string; + }[]; + hasResultComparisonError: boolean; + timeout: boolean; + } +); + export async function showCircuitCommand( extensionUri: Uri, operation: IOperationInfo | undefined, @@ -27,127 +61,265 @@ export async function showCircuitCommand( const associationId = getRandomGuid(); sendTelemetryEvent(EventType.TriggerCircuit, { associationId }, {}); - const compilerWorkerScriptPath = Uri.joinPath( - extensionUri, - "./out/compilerWorker.js", - ).toString(); - const editor = window.activeTextEditor; if (!editor || !isQsharpDocument(editor.document)) { throw new Error("The currently active window is not a Q# file"); } - sendMessageToPanel("circuit", true, undefined); - - let timeout = false; - - // Start the worker, run the code, and send the results to the webview - const worker = getCompilerWorker(compilerWorkerScriptPath); - const compilerTimeout = setTimeout(() => { - timeout = true; - sendTelemetryEvent(EventType.CircuitEnd, { - associationId, - reason: "timeout", - flowStatus: UserFlowStatus.Aborted, - }); - log.info("terminating circuit worker due to timeout"); - worker.terminate(); - }, compilerRunTimeoutMs); - const docUri = editor.document.uri; - const sources = await loadProject(docUri); + const program = await loadProject(docUri); const targetProfile = getTarget(); - try { - sendTelemetryEvent(EventType.CircuitStart, { associationId }, {}); - - const circuit = await worker.getCircuit(sources, targetProfile, operation); - clearTimeout(compilerTimeout); - - updateCircuitPanel( + sendTelemetryEvent( + EventType.CircuitStart, + { + associationId, targetProfile, - docUri.path, - true, // reveal - { circuit, operation }, - ); + isOperation: (!!operation).toString(), + }, + {}, + ); + + // Generate the circuit and update the panel. + // generateCircuits() takes care of handling timeouts and + // falling back to the simulator for dynamic circuits. + const result = await generateCircuit(extensionUri, docUri, { + program: program, + targetProfile, + operation, + }); + if (result.result === "success") { sendTelemetryEvent(EventType.CircuitEnd, { + simulated: result.simulated.toString(), associationId, flowStatus: UserFlowStatus.Succeeded, }); - } catch (e: any) { - log.error("Circuit error. ", e.toString()); - clearTimeout(compilerTimeout); - - const errors: [string, VSDiagnostic, string][] = - typeof e === "string" ? JSON.parse(e) : undefined; - let errorHtml = "There was an error generating the circuit."; - if (errors) { - errorHtml = errorsToHtml(errors, docUri); - } + } else { + if (result.timeout) { + sendTelemetryEvent(EventType.CircuitEnd, { + simulated: result.simulated.toString(), + associationId, + reason: "timeout", + flowStatus: UserFlowStatus.Aborted, + }); + } else { + const reason = + result.errors.length > 0 ? result.errors[0].diag.code : "unknown"; - if (!timeout) { sendTelemetryEvent(EventType.CircuitEnd, { + simulated: result.simulated.toString(), associationId, - reason: errors && errors[0] ? errors[0][1].code : undefined, + reason, flowStatus: UserFlowStatus.Failed, }); } + } +} + +/** + * Generate the circuit and update the panel with the results. + * We first attempt to generate a circuit without running the simulator, + * which should be fast. + * + * If that fails, specifically due to a result comparison error, + * that means this is a dynamic circuit. We fall back to using the + * simulator in this case ("trace" mode), which is slower. + */ +async function generateCircuit( + extensionUri: Uri, + docUri: Uri, + params: CircuitParams, +): Promise { + const programPath = docUri.path; + + // Before we start, reveal the panel with the "calculating" spinner + updateCircuitPanel( + params.targetProfile, + programPath, + true, // reveal + { operation: params.operation, calculating: true }, + ); + + // First, try without simulating + let result = await getCircuitOrErrorWithTimeout( + extensionUri, + params, + false, // simulate + ); + + if (result.result === "error" && result.hasResultComparisonError) { + // Retry with the simulator if circuit generation failed because + // there was a result comparison (i.e. if this is a dynamic circuit) updateCircuitPanel( - targetProfile, - docUri.path, + params.targetProfile, + programPath, + false, // reveal + { + operation: params.operation, + calculating: true, + simulated: true, + }, + ); + + // try again with the simulator + result = await getCircuitOrErrorWithTimeout( + extensionUri, + params, + true, // simulate + ); + } + + // Update the panel with the results + + if (result.result === "success") { + updateCircuitPanel( + params.targetProfile, + programPath, + false, // reveal + { + circuit: result.circuit, + operation: params.operation, + simulated: result.simulated, + }, + ); + } else { + log.error("Circuit error. ", result); + let errorHtml = "There was an error generating the circuit."; + if (result.errors.length > 0) { + errorHtml = errorsToHtml(result.errors); + } else if (result.timeout) { + errorHtml = `The circuit generation exceeded the timeout of ${compilerRunTimeoutMs}ms.`; + } + + updateCircuitPanel( + params.targetProfile, + programPath, false, // reveal - { errorHtml, operation }, + { + errorHtml, + operation: params.operation, + simulated: result.simulated, + }, ); - } finally { - log.info("terminating circuit worker"); + } + + return result; +} + +/** + * Wrapper around getCircuit() that enforces a timeout. + * Won't throw for known errors. + */ +async function getCircuitOrErrorWithTimeout( + extensionUri: Uri, + params: CircuitParams, + simulate: boolean, +): Promise { + let timeout = false; + + const compilerWorkerScriptPath = Uri.joinPath( + extensionUri, + "./out/compilerWorker.js", + ).toString(); + + const worker = getCompilerWorker(compilerWorkerScriptPath); + const compilerTimeout = setTimeout(() => { + timeout = true; + log.info("terminating circuit worker due to timeout"); worker.terminate(); + }, compilerRunTimeoutMs); + + const result = await getCircuitOrError(worker, params, simulate); + clearTimeout(compilerTimeout); + + if (result.result === "error") { + return { + ...result, + timeout, + }; + } else { + return result; } } +/** + * Wrapper around compiler getCircuit() that handles exceptions + * and converts to strongly typed error object. + * Won't throw for known errors. + */ +async function getCircuitOrError( + worker: ICompilerWorker, + params: CircuitParams, + simulate: boolean, +): Promise { + try { + const circuit = await worker.getCircuit( + params.program, + params.targetProfile, + simulate, + params.operation, + ); + return { result: "success", simulated: simulate, circuit }; + } catch (e: any) { + let errors: { document: string; diag: VSDiagnostic; stack: string }[] = []; + let resultCompError = false; + if (typeof e === "string") { + try { + const rawErrors: [string, VSDiagnostic, string][] = JSON.parse(e); + errors = rawErrors.map(([document, diag, stack]) => ({ + document, + diag, + stack, + })); + resultCompError = hasResultComparisonError(e); + } catch (e) { + // couldn't parse the error - would indicate a bug. + // will get reported up the stack as a generic error + } + } + return { + result: "error", + simulated: simulate, + errors, + hasResultComparisonError: resultCompError, + timeout: false, + }; + } +} + +function hasResultComparisonError(e: unknown) { + const errors: [string, VSDiagnostic, string][] = + typeof e === "string" ? JSON.parse(e) : undefined; + const hasResultComparisonError = + errors && + errors.findIndex( + ([, diag]) => diag.code === "Qsc.Eval.ResultComparisonUnsupported", + ) >= 0; + return hasResultComparisonError; +} + /** * Formats an array of compiler/runtime errors into HTML to be presented to the user. * - * @param errors - * The first string is the document URI or "" if the error isn't associated with a specific document. - * The VSDiagnostic is the error information. - * The last string is the stack trace. - * + * @param errors The list of errors to format. * @returns The HTML formatted errors, to be set as the inner contents of a container element. */ function errorsToHtml( - errors: [string, VSDiagnostic, string][], - programUri: Uri, + errors: { document: string; diag: VSDiagnostic; stack: string }[], ) { let errorHtml = ""; for (const error of errors) { - const [document, diag, rawStack] = error; - - if (diag.code === "Qsc.Eval.ResultComparisonUnsupported") { - const commandUri = Uri.parse( - `command:qsharp-vscode.runEditorContentsWithCircuit?${encodeURIComponent(JSON.stringify([programUri]))}`, - true, - ); - const messageHtml = - `

Synthesizing circuits is unsupported for programs that ` + - `contain behavior that is conditional on a qubit measurement result, ` + - `since the resulting circuit may depend on the outcome of the measurement.

` + - `

If you would like to generate a circuit for this program, you can ` + - `run the program in the simulator and show the resulting circuit, ` + - `or edit your code to avoid the result comparison indicated by the call stack below.

`; - - errorHtml += messageHtml; - } else { - const location = documentHtml(document, diag.range); + const { document, diag, stack: rawStack } = error; - const message = escapeHtml(`(${diag.code}) ${diag.message}`).replace( - "\n", - "

", - ); + const location = documentHtml(document, diag.range); + const message = escapeHtml(`(${diag.code}) ${diag.message}`).replace( + "\n", + "

", + ); - errorHtml += `

${location}: ${message}

`; - } + errorHtml += `

${location}: ${message}

`; if (rawStack) { const stack = rawStack @@ -172,18 +344,19 @@ function errorsToHtml( export function updateCircuitPanel( targetProfile: string, - docPath: string, + programPath: string, reveal: boolean, params: { circuit?: CircuitData; errorHtml?: string; - simulating?: boolean; + simulated?: boolean; operation?: IOperationInfo | undefined; + calculating?: boolean; }, ) { const title = params?.operation ? `${params.operation.operation} with ${params.operation.totalNumQubits} input qubits` - : basename(docPath) || "Circuit"; + : basename(programPath) || "Circuit"; // Trim the Q#: prefix from the target profile name - that's meant for the ui text in the status bar const target = `Target profile: ${getTargetFriendlyName(targetProfile).replace("Q#: ", "")} `; @@ -191,7 +364,8 @@ export function updateCircuitPanel( const props = { title, targetProfile: target, - simulating: params?.simulating || false, + simulated: params?.simulated || false, + calculating: params?.calculating || false, circuit: params?.circuit, errorHtml: params?.errorHtml, }; @@ -229,7 +403,7 @@ function documentHtml(maybeUri: string, range?: IRange) { ); const fsPath = escapeHtml(uri.fsPath); const lineColumn = range - ? escapeHtml(`:${range.start.line}:${range.start.character}`) + ? escapeHtml(`:${range.start.line + 1}:${range.start.character + 1}`) : ""; location = `${fsPath}${lineColumn}`; } catch (e) { diff --git a/vscode/src/debugger/activate.ts b/vscode/src/debugger/activate.ts index 840de76318..34b8c52111 100644 --- a/vscode/src/debugger/activate.ts +++ b/vscode/src/debugger/activate.ts @@ -74,6 +74,11 @@ function registerCommands(context: vscode.ExtensionContext) { config: { name: string; [key: string]: any }, options?: vscode.DebugSessionOptions, ) { + if (vscode.debug.activeDebugSession?.type === "qsharp") { + // Multiple debug sessions disallowed, to reduce confusion + return; + } + let targetResource = resource; if (!targetResource && vscode.window.activeTextEditor) { targetResource = vscode.window.activeTextEditor.document.uri; diff --git a/vscode/src/debugger/session.ts b/vscode/src/debugger/session.ts index 366f938973..bbeb369c00 100644 --- a/vscode/src/debugger/session.ts +++ b/vscode/src/debugger/session.ts @@ -42,6 +42,7 @@ import { getRandomGuid } from "../utils"; import { createDebugConsoleEventTarget } from "./output"; import { ILaunchRequestArguments } from "./types"; import { escapeHtml } from "markdown-it/lib/common/utils"; +import { isPanelOpen } from "../webviewPanel"; const ErrorProgramHasErrors = "program contains compile errors(s): cannot run. See debug console for more details."; @@ -316,9 +317,7 @@ export class QscDebugSession extends LoggingDebugSession { error = e; } - if (this.config.showCircuit) { - await this.showCircuit(error); - } + await this.updateCircuit(error); if (!result) { // Can be a runtime failure in the program @@ -389,17 +388,15 @@ export class QscDebugSession extends LoggingDebugSession { bps, this.eventTarget, ); - if (this.config.showCircuit) { - this.showCircuit(); - } + + await this.updateCircuit(); + if (result.id != StepResultId.Return) { await this.endSession(`execution didn't run to completion`, -1); return; } } catch (error) { - if (this.config.showCircuit) { - await this.showCircuit(error); - } + await this.updateCircuit(error); await this.endSession(`ending session due to error: ${error}`, 1); return; } @@ -819,13 +816,14 @@ export class QscDebugSession extends LoggingDebugSession { // This will get invoked when the "Quantum Circuit" scope is expanded // in the Variables view, but instead of showing any values in the variables // view, we can pop open the circuit diagram panel. - this.showCircuit(); - - // Keep updating the circuit for the rest of this session, even if - // the Variables scope gets collapsed by the user. If we don't do this, - // the diagram won't get updated with each step even though the circuit - // panel is still being shown, which is misleading. - this.config.showCircuit = true; + if (!this.config.showCircuit) { + // Keep updating the circuit for the rest of this session, even if + // the Variables scope gets collapsed by the user. If we don't do this, + // the diagram won't get updated with each step even though the circuit + // panel is still being shown, which is misleading. + this.config.showCircuit = true; + await this.updateCircuit(); + } response.body = { variables: [ { @@ -934,31 +932,34 @@ export class QscDebugSession extends LoggingDebugSession { } } - private async showCircuit(error?: any) { - // Error returned from the debugger has a message and a stack (which also includes the message). - // We would ideally retrieve the original runtime error, and format it to be consistent - // with the other runtime errors that can be shown in the circuit panel, but that will require - // a bit of refactoring. - const stack = - error && typeof error === "object" && typeof error.stack === "string" - ? escapeHtml(error.stack) - : undefined; - - const circuit = await this.debugService.getCircuit(); - - updateCircuitPanel( - this.targetProfile, - vscode.Uri.parse(this.sources[0][0]).path, - !this.revealedCircuit, - { - circuit, - errorHtml: stack ? `
${stack}
` : undefined, - simulating: true, - }, - ); + /* Updates the circuit panel if `showCircuit` is true or if panel is already open */ + private async updateCircuit(error?: any) { + if (this.config.showCircuit || isPanelOpen("circuit")) { + // Error returned from the debugger has a message and a stack (which also includes the message). + // We would ideally retrieve the original runtime error, and format it to be consistent + // with the other runtime errors that can be shown in the circuit panel, but that will require + // a bit of refactoring. + const stack = + error && typeof error === "object" && typeof error.stack === "string" + ? escapeHtml(error.stack) + : undefined; + + const circuit = await this.debugService.getCircuit(); + + updateCircuitPanel( + this.targetProfile, + vscode.Uri.parse(this.sources[0][0]).path, + !this.revealedCircuit, + { + circuit, + errorHtml: stack ? `
${stack}
` : undefined, + simulated: true, + }, + ); - // Only reveal the panel once per session, to keep it from - // moving around while stepping - this.revealedCircuit = true; + // Only reveal the panel once per session, to keep it from + // moving around while stepping + this.revealedCircuit = true; + } } } diff --git a/vscode/src/telemetry.ts b/vscode/src/telemetry.ts index ca28a6cf25..6caf4dfb99 100644 --- a/vscode/src/telemetry.ts +++ b/vscode/src/telemetry.ts @@ -225,15 +225,22 @@ type EventTypes = { measurements: Empty; }; [EventType.TriggerCircuit]: { - properties: { associationId: string }; + properties: { + associationId: string; + }; measurements: Empty; }; [EventType.CircuitStart]: { - properties: { associationId: string }; + properties: { + associationId: string; + isOperation: string; + targetProfile: string; + }; measurements: Empty; }; [EventType.CircuitEnd]: { properties: { + simulated: string; associationId: string; reason?: string; flowStatus: UserFlowStatus; @@ -292,6 +299,10 @@ export function sendTelemetryEvent( log.trace(`No telemetry reporter. Omitting telemetry event ${event}`); return; } + + // If you get a type error here, it's likely because you defined a + // non-string property or non-number measurement in `EventTypes`. + // For booleans, use `.toString()` to convert to string and store in `properties`. reporter.sendTelemetryEvent(event, properties, measurements); log.debug( `Sent telemetry: ${event} ${JSON.stringify(properties)} ${JSON.stringify( diff --git a/vscode/src/webviewPanel.ts b/vscode/src/webviewPanel.ts index e2dd6cb459..dca9460936 100644 --- a/vscode/src/webviewPanel.ts +++ b/vscode/src/webviewPanel.ts @@ -426,6 +426,10 @@ export function sendMessageToPanel( if (message) panelRecord.panel.sendMessage(message); } +export function isPanelOpen(panelType: PanelType) { + return panelTypeToPanel[panelType].panel !== undefined; +} + export class QSharpWebViewPanel { public static extensionUri: Uri; private _ready = false; diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 3ec0d7770f..cce0b941fd 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -146,8 +146,9 @@ pub fn get_estimates( pub fn get_circuit( sources: Vec, targetProfile: &str, - operation: Option, language_features: Vec, + simulate: bool, + operation: Option, ) -> Result { let sources = get_source_map(sources, &None); let target_profile = Profile::from_str(targetProfile).expect("invalid target profile"); @@ -161,14 +162,16 @@ pub fn get_circuit( ) .map_err(interpret_errors_into_vs_diagnostics_json)?; + let entry_point = match operation { + Some(p) => { + let o: language_service::OperationInfo = p.into(); + CircuitEntryPoint::Operation(o.operation) + } + None => CircuitEntryPoint::EntryPoint, + }; + let circuit = interpreter - .circuit(match operation { - Some(p) => { - let o: language_service::OperationInfo = p.into(); - CircuitEntryPoint::Operation(o.operation) - } - None => CircuitEntryPoint::EntryPoint, - }) + .circuit(entry_point, simulate) .map_err(interpret_errors_into_vs_diagnostics_json)?; serde_wasm_bindgen::to_value(&circuit).map_err(|e| e.to_string())