diff --git a/frontend/catalyst/compiler.py b/frontend/catalyst/compiler.py index 86c16c20c9..3176ff170e 100644 --- a/frontend/catalyst/compiler.py +++ b/frontend/catalyst/compiler.py @@ -21,6 +21,7 @@ import pathlib import platform import shutil +import signal import subprocess import sys import tempfile @@ -424,9 +425,9 @@ def run_from_ir(self, ir: str, module_name: str, workspace: Directory): workspace, Directory ), f"Compiler expects a Directory type, got {type(workspace)}." assert workspace.is_dir(), f"Compiler expects an existing directory, got {workspace}." - assert ( - self.options.lower_to_llvm - ), "lower_to_llvm must be set to True in order to compile to a shared object" + # assert ( + # self.options.lower_to_llvm + # ), "lower_to_llvm must be set to True in order to compile to a shared object" if self.options.verbose: print(f"[LIB] Running compiler driver in {workspace}", file=self.options.logfile) @@ -444,12 +445,47 @@ def run_from_ir(self, ir: str, module_name: str, workspace: Directory): try: if self.options.verbose: print(f"[SYSTEM] {' '.join(cmd)}", file=self.options.logfile) - result = subprocess.run(cmd, check=True, capture_output=True, text=True) + # result = subprocess.run(cmd, check=True, capture_output=True, text=True) + + if self.options.debug_compiler != True: + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + else: # self.options.debug_compiler == True: + with subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) as p: + # Ensure process creation succeeds + if p.returncode not in {0, None}: + raise subprocess.CalledProcessError(p.returncode, cmd) + if self.options.debug_compiler: + print(f"Compiler PID={p.pid}") + print( + f"""Ensure C++ debugger is attached and running before continuing with: + kill -s SIGCONT {p.pid}""" + ) + p.send_signal(signal.SIGSTOP) + res_stdout, res_stderr = p.communicate() + # Ensure process execution succeeds + if p.returncode not in {0, None}: + raise subprocess.CalledProcessError( + p.returncode, cmd, res_stdout, res_stderr + ) + if self.options.verbose or os.getenv("ENABLE_DIAGNOSTICS"): - if result.stdout: - print(result.stdout.strip(), file=self.options.logfile) - if result.stderr: - print(result.stderr.strip(), file=self.options.logfile) + # if result.stdout: + # print(result.stdout.strip(), file=self.options.logfile) + # if result.stderr: + # print(result.stderr.strip(), file=self.options.logfile) + if self.options.debug_compiler != True: + if result.stdout: + print(result.stdout.strip(), file=self.options.logfile) + if result.stderr: + print(result.stderr.strip(), file=self.options.logfile) + else: # self.options.debug_compiler == True: + if res_stdout: + print(res_stdout.strip(), file=self.options.logfile) + if res_stderr: + print(res_stderr.strip(), file=self.options.logfile) + except subprocess.CalledProcessError as e: # pragma: nocover raise CompileError(f"catalyst failed with error code {e.returncode}: {e.stderr}") from e diff --git a/frontend/catalyst/debug/__init__.py b/frontend/catalyst/debug/__init__.py index e70e96027f..0726fb542a 100644 --- a/frontend/catalyst/debug/__init__.py +++ b/frontend/catalyst/debug/__init__.py @@ -22,6 +22,7 @@ get_compilation_stages_groups, replace_ir, ) +from catalyst.debug.debugger import is_debugger_active from catalyst.debug.instruments import instrumentation from catalyst.debug.printing import print, print_memref # pylint: disable=redefined-builtin diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index d7bdc00375..8dfafdcb64 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -90,6 +90,7 @@ def qjit( circuit_transform_pipeline=None, pass_plugins=None, dialect_plugins=None, + debug_compiler=False, ): # pylint: disable=too-many-arguments,unused-argument """A just-in-time decorator for PennyLane and JAX programs using Catalyst. @@ -161,6 +162,8 @@ def qjit( If not specified, the default pass pipeline will be applied. pass_plugins (Optional[List[Path]]): List of paths to pass plugins. dialect_plugins (Optional[List[Path]]): List of paths to dialect plugins. + debug_compiler (Optional[bool]): Enable external debugger attachment to the compiler + driver when launching from an active Python debugging environment. Returns: QJIT object. diff --git a/frontend/catalyst/pipelines.py b/frontend/catalyst/pipelines.py index 328a4d6623..c8e45e4ce7 100644 --- a/frontend/catalyst/pipelines.py +++ b/frontend/catalyst/pipelines.py @@ -109,10 +109,12 @@ class CompileOptions: Default is None. pass_plugins (Optional[Iterable[Path]]): List of paths to pass plugins. dialect_plugins (Optional[Iterable[Path]]): List of paths to dialect plugins. + debug_compiler (Optional[bool]): Enable external debugger attachment to the compiler + driver when launching from an active Python debugging environment. """ verbose: Optional[bool] = False - logfile: Optional[TextIOWrapper] = sys.stderr + logfile: Optional[TextIOWrapper] = "test1.txt" # sys.stderr target: Optional[str] = "binary" keep_intermediate: Optional[Union[str, int, bool, KeepIntermediateLevel]] = False pipelines: Optional[List[Any]] = None @@ -122,13 +124,14 @@ class CompileOptions: static_argnums: Optional[Union[int, Iterable[int]]] = None static_argnames: Optional[Union[str, Iterable[str]]] = None abstracted_axes: Optional[Union[Iterable[Iterable[str]], Dict[int, str]]] = None - lower_to_llvm: Optional[bool] = True + lower_to_llvm: Optional[bool] = False # "True" checkpoint_stage: Optional[str] = "" - disable_assertions: Optional[bool] = False + disable_assertions: Optional[bool] = True seed: Optional[int] = None circuit_transform_pipeline: Optional[dict[str, dict[str, str]]] = None pass_plugins: Optional[Set[Path]] = None dialect_plugins: Optional[Set[Path]] = None + debug_compiler: Optional[bool] = False def __post_init__(self): # Convert keep_intermediate to Enum diff --git a/frontend/catalyst/utils/filesystem.py b/frontend/catalyst/utils/filesystem.py index 586e3c16d4..ced47eb018 100644 --- a/frontend/catalyst/utils/filesystem.py +++ b/frontend/catalyst/utils/filesystem.py @@ -121,12 +121,12 @@ def _get_or_create_directory(path, name): preferred_name = name.name # TODO: Maybe just look for the last one? - while curr_preferred_abspath.exists(): + while curr_preferred_abspath.exists() and False: curr_preferred_name_str = preferred_name + "_" + str(count) curr_preferred_name = pathlib.Path(curr_preferred_name_str) curr_preferred_abspath = path / curr_preferred_name count += 1 free_preferred_abspath = pathlib.Path(curr_preferred_abspath) - free_preferred_abspath.mkdir() + free_preferred_abspath.mkdir(exist_ok=True) return free_preferred_abspath diff --git a/mlir/cmake/modules/CMakeLists.txt b/mlir/cmake/modules/CMakeLists.txt index b262fb0b01..b1394ce1a2 100644 --- a/mlir/cmake/modules/CMakeLists.txt +++ b/mlir/cmake/modules/CMakeLists.txt @@ -26,7 +26,7 @@ set(llvm_cmake_builddir "${LLVM_BINARY_DIR}/${LLVM_INSTALL_PACKAGE_DIR}") # Targets with third party dependencies are not exported. get_property(MLIR_EXPORTS GLOBAL PROPERTY MLIR_EXPORTS) -set(TARGETS_TO_REMOVE nlohmann_json tomlplusplus_tomlplusplus ion-transforms CatalystCompilerDriver QECUtils QuantumCAPI qec-transforms) +set(TARGETS_TO_REMOVE nlohmann_json tomlplusplus_tomlplusplus ion-transforms CatalystCompilerDriver QECUtils QuantumCAPI qec-transforms quantum-transforms) list(REMOVE_ITEM MLIR_EXPORTS ${TARGETS_TO_REMOVE}) export(TARGETS ${MLIR_EXPORTS} ExternalStablehloLib FILE ${catalyst_cmake_builddir}/CatalystTargets.cmake) diff --git a/mlir/include/Quantum/Transforms/Passes.h b/mlir/include/Quantum/Transforms/Passes.h index 00f33d8fa4..d619aee3ca 100644 --- a/mlir/include/Quantum/Transforms/Passes.h +++ b/mlir/include/Quantum/Transforms/Passes.h @@ -34,5 +34,6 @@ std::unique_ptr createDisentangleCNOTPass(); std::unique_ptr createDisentangleSWAPPass(); std::unique_ptr createIonsDecompositionPass(); std::unique_ptr createLoopBoundaryOptimizationPass(); +std::unique_ptr createQuantumSpecsInfoPass(); } // namespace catalyst diff --git a/mlir/include/Quantum/Transforms/Passes.td b/mlir/include/Quantum/Transforms/Passes.td index f0a344190e..b9c57f288f 100644 --- a/mlir/include/Quantum/Transforms/Passes.td +++ b/mlir/include/Quantum/Transforms/Passes.td @@ -139,4 +139,11 @@ def LoopBoundaryOptimizationPass : Pass<"loop-boundary"> { } // ----- Quantum circuit transformation passes end ----- // + +def QuantumSpecsInfoPass : Pass<"quantum-specs-info"> { + let summary = "Informative pass to provide quantum specs, like gate counts."; + + let constructor = "catalyst::createQuantumSpecsInfoPass()"; +} + #endif // QUANTUM_PASSES diff --git a/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp b/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp index 0e7be8337b..f2d9b8770a 100644 --- a/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp +++ b/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp @@ -75,4 +75,5 @@ void catalyst::registerAllCatalystPasses() mlir::registerPass(catalyst::createSplitMultipleTapesPass); mlir::registerPass(catalyst::createTestPass); mlir::registerPass(catalyst::createTLayerReductionPass); + mlir::registerPass(catalyst::createQuantumSpecsInfoPass); } diff --git a/mlir/lib/QEC/Transforms/CountPPMSpecs.cpp b/mlir/lib/QEC/Transforms/CountPPMSpecs.cpp index 2531c2c367..5d2b6191f8 100644 --- a/mlir/lib/QEC/Transforms/CountPPMSpecs.cpp +++ b/mlir/lib/QEC/Transforms/CountPPMSpecs.cpp @@ -19,6 +19,9 @@ #include +// #include "llvm/Support/raw_ostream.h" +#include "llvm/Support/FileSystem.h" + #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Pass/Pass.h" @@ -26,6 +29,7 @@ #include "Catalyst/Utils/SCFUtils.h" #include "QEC/IR/QECDialect.h" #include "QEC/IR/QECOpInterfaces.h" +// #include "QEC/Utils/PauliStringWrapper.h" #include "QEC/Utils/QECLayer.h" #include "QEC/Utils/QECOpUtils.h" #include "Quantum/IR/QuantumOps.h" @@ -229,9 +233,18 @@ struct CountPPMSpecsPass : public impl::CountPPMSpecsPassBase countDepths(layers, PPMSpecs, stringAllocator); + std::error_code EC; + llvm::raw_fd_ostream fileOutputStream("test.txt", EC, llvm::sys::fs::OF_Append); + if (EC) { + llvm::errs() << "Error opening file: " << EC.message() << "\n"; + return failure(); // Handle error + } json PPMSpecsJson = PPMSpecs; llvm::outs() << PPMSpecsJson.dump(4) << "\n"; // dump(4) makes an indent with 4 spaces when printing JSON + fileOutputStream << PPMSpecsJson.dump(4) + << "\n"; // dump(4) makes an indent with 4 spaces when printing JSON + fileOutputStream.flush(); return success(); } diff --git a/mlir/lib/Quantum/Transforms/CMakeLists.txt b/mlir/lib/Quantum/Transforms/CMakeLists.txt index 3a244ac4d6..5998e84c6c 100644 --- a/mlir/lib/Quantum/Transforms/CMakeLists.txt +++ b/mlir/lib/Quantum/Transforms/CMakeLists.txt @@ -20,6 +20,7 @@ file(GLOB SRC IonsDecompositionPatterns.cpp loop_boundary_optimization.cpp LoopBoundaryOptimizationPatterns.cpp + QuantumSpecsInfo.cpp ) get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) @@ -36,6 +37,19 @@ set(DEPENDS add_mlir_library(${LIBRARY_NAME} STATIC ${SRC} LINK_LIBS PRIVATE ${LIBS} DEPENDS ${DEPENDS}) target_compile_features(${LIBRARY_NAME} PUBLIC cxx_std_20) + +target_link_libraries(${LIBRARY_NAME} + PRIVATE nlohmann_json::nlohmann_json + ) + +# Disable warning from json library +if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") +set_source_files_properties( + QuantumSpecsInfo.cpp + COMPILE_FLAGS "-Wno-covered-switch-default" +) +endif() + target_include_directories(${LIBRARY_NAME} PUBLIC . ${PROJECT_SOURCE_DIR}/include diff --git a/mlir/lib/Quantum/Transforms/QuantumSpecsInfo.cpp b/mlir/lib/Quantum/Transforms/QuantumSpecsInfo.cpp new file mode 100644 index 0000000000..ead6fb8862 --- /dev/null +++ b/mlir/lib/Quantum/Transforms/QuantumSpecsInfo.cpp @@ -0,0 +1,132 @@ +// Copyright 2025 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#define DEBUG_TYPE "quantum-specs-info" + +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Pass/Pass.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/FileSystem.h" +#include + +#include "QEC/IR/QECDialect.h" +#include "Quantum/IR/QuantumDialect.h" +#include "Quantum/IR/QuantumInterfaces.h" +#include "Quantum/IR/QuantumOps.h" + +using namespace llvm; +using namespace mlir; +using namespace catalyst; +using namespace catalyst::quantum; +using namespace catalyst::qec; +using json = nlohmann::json; + +namespace catalyst { +namespace quantum { + +#define GEN_PASS_DEF_QUANTUMSPECSINFOPASS +#define GEN_PASS_DECL_QUANTUMSPECSINFOPASS + +#include "Quantum/Transforms/Passes.h.inc" + +struct QuantumSpecsInfoPass : public impl::QuantumSpecsInfoPassBase { + using impl::QuantumSpecsInfoPassBase::QuantumSpecsInfoPassBase; + + LogicalResult printQuantumSpecs() + { + llvm::BumpPtrAllocator stringAllocator; + llvm::DenseMap> PPMSpecs; + + int numQuantumGates = 0; + int numMeasureOp = 0; + int numPPRotation = 0; + int numPPMeasurement = 0; + int numTotal = 0; + + // Walk over all operations in the IR (could be ModuleOp or FuncOp) + WalkResult wr = getOperation()->walk([&](Operation *op) { + if (isa(op)) { + auto parentFuncOp = op->getParentOfType(); + StringRef funcName = parentFuncOp.getName(); + // llvm::errs() << funcName; + PPMSpecs[funcName]["QuantumGate_Count"] = ++numQuantumGates; + PPMSpecs[funcName]["Total_Count"] = ++numTotal; + return WalkResult::advance(); + } + + if (isa(op)) { + auto parentFuncOp = op->getParentOfType(); + StringRef funcName = parentFuncOp.getName(); + // llvm::errs() << funcName; + PPMSpecs[funcName]["MeasureOp_Count"] = ++numMeasureOp; + PPMSpecs[funcName]["Total_Count"] = ++numTotal; + return WalkResult::advance(); + } + // Count PPMs + else if (isa(op)) { + auto parentFuncOp = op->getParentOfType(); + StringRef funcName = parentFuncOp.getName(); + PPMSpecs[funcName]["PPMeasurement_Count"] = ++numPPMeasurement; + PPMSpecs[funcName]["Total_Count"] = ++numTotal; + return WalkResult::advance(); + } + // Count PPRs + else if (isa(op)) { + auto parentFuncOp = op->getParentOfType(); + StringRef funcName = parentFuncOp.getName(); + PPMSpecs[funcName]["PPRotation_Count"] = ++numPPRotation; + PPMSpecs[funcName]["Total_Count"] = ++numTotal; + return WalkResult::advance(); + } + // Skip other ops + else { + return WalkResult::skip(); + } + }); + if (wr.wasInterrupted()) { + return failure(); + } + + std::error_code EC; + // llvm::raw_fd_ostream fileOutputStream("test.txt", EC, llvm::sys::fs::OF_None); + llvm::raw_fd_ostream fileOutputStream("test.txt", EC, llvm::sys::fs::OF_Append); + if (EC) { + llvm::errs() << "Error opening file: " << EC.message() << "\n"; + return failure(); // Handle error + } + json PPMSpecsJson = PPMSpecs; + llvm::outs() << PPMSpecsJson.dump(4) + << "\n"; // dump(4) makes an indent with 4 spaces when printing JSON + fileOutputStream << PPMSpecsJson.dump(4) + << "\n"; // dump(4) makes an indent with 4 spaces when printing JSON + fileOutputStream.flush(); + return success(); + } + + void runOnOperation() final + { + if (failed(printQuantumSpecs())) { + signalPassFailure(); + } + } +}; + +} // namespace quantum + +std::unique_ptr createQuantumSpecsInfoPass() +{ + return std::make_unique(); +} + +} // namespace catalyst \ No newline at end of file diff --git a/mlir/test/Quantum/QuantumSpecsInfoTest.mlir b/mlir/test/Quantum/QuantumSpecsInfoTest.mlir new file mode 100644 index 0000000000..b2793ee7cd --- /dev/null +++ b/mlir/test/Quantum/QuantumSpecsInfoTest.mlir @@ -0,0 +1,46 @@ +// Copyright 2025 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// RUN: quantum-opt --quantum-specs-info --split-input-file -verify-diagnostics %s | FileCheck %s + +//CHECK: { +//CHECK: "circuit_0": { +//CHECK: "PPMeasurement_Count": 1, +//CHECK: "PPRotation_Count": 6, +//CHECK: "QuantumGate_Count": 1, +//CHECK: "Total_Count": 8 +//CHECK: } +//CHECK: } +func.func public @circuit_0() -> tensor attributes {diff_method = "adjoint", llvm.linkage = #llvm.linkage, qnode} { + %cst = arith.constant 1.5707963267948966 : f64 + %c0_i64 = arith.constant 0 : i64 + quantum.device shots(%c0_i64) ["/home/zorawar.bassi/pyenv/versions/pennylaneB/lib/python3.13/site-packages/pennylane_lightning/liblightning_qubit_catalyst.so", "LightningSimulator", "{'mcmc': False, 'num_burnin': 0, 'kernel_name': None}"] + %0 = quantum.alloc( 2) : !quantum.reg + %1 = quantum.extract %0[ 1] : !quantum.reg -> !quantum.bit + %2 = qec.ppr ["Z"](4) %1 : !quantum.bit + %3 = qec.ppr ["X"](4) %2 : !quantum.bit + %4 = qec.ppr ["Z"](4) %3 : !quantum.bit + %5 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit + %out_qubits:2 = quantum.multirz(%cst) %5, %4 : !quantum.bit, !quantum.bit + %6 = qec.ppr ["Z"](4) %out_qubits#1 : !quantum.bit + %7 = qec.ppr ["X"](4) %6 : !quantum.bit + %8 = qec.ppr ["Z"](4) %7 : !quantum.bit + %mres, %out_qubits_0 = qec.ppm ["Z"] %8 : !quantum.bit + %from_elements = tensor.from_elements %mres : tensor + %9 = quantum.insert %0[ 1], %out_qubits_0 : !quantum.reg, !quantum.bit + %10 = quantum.insert %9[ 0], %out_qubits#0 : !quantum.reg, !quantum.bit + quantum.dealloc %10 : !quantum.reg + quantum.device_release + return %from_elements : tensor +}