From 0f95081bf64d31ad1d7bc6b5dbd29e668af78102 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Mon, 15 Dec 2025 16:01:16 -0500 Subject: [PATCH 01/23] New OQD end-to-end pipeline is added to Catalyst --- frontend/catalyst/compiler.py | 40 ++++++-- frontend/catalyst/jit.py | 40 +++++++- frontend/catalyst/pipelines.py | 1 + .../catalyst/third_party/oqd/oqd_device.py | 97 +++++++++++++++---- mlir/include/Driver/CompilerDriver.h | 20 ++++ mlir/include/RTIO/Transforms/Passes.td | 15 ++- mlir/lib/Driver/CompilerDriver.cpp | 93 ++++++++++++++++-- mlir/lib/RTIO/Transforms/CMakeLists.txt | 1 + 8 files changed, 273 insertions(+), 34 deletions(-) diff --git a/frontend/catalyst/compiler.py b/frontend/catalyst/compiler.py index 71a58d20c4..bd2adf5f01 100644 --- a/frontend/catalyst/compiler.py +++ b/frontend/catalyst/compiler.py @@ -367,6 +367,19 @@ def _options_to_cli_flags(options): if options.async_qnodes: # pragma: nocover extra_args += ["--async-qnodes"] + # ARTIQ cross-compilation flags + if options.artiq_config: + extra_args += ["--artiq"] + kernel_ld = options.artiq_config.get("kernel_ld", "") + if kernel_ld: + extra_args += [("--artiq-kernel-ld", kernel_ld)] + llc_path = options.artiq_config.get("llc_path", "") + if llc_path: + extra_args += [("--artiq-llc-path", llc_path)] + lld_path = options.artiq_config.get("lld_path", "") + if lld_path: + extra_args += [("--artiq-lld-path", lld_path)] + return extra_args @@ -483,16 +496,27 @@ def run_from_ir(self, ir: str, module_name: str, workspace: Directory): else: out_IR = None - output = LinkerDriver.run(output_object_name, options=self.options) - output_object_name = str(pathlib.Path(output).absolute()) + if self.options.artiq_config: + output_elf_name = os.path.join(str(workspace), f"{module_name}.elf") + output = str(pathlib.Path(output_elf_name).absolute()) - # Clean up temporary files - if os.path.exists(tmp_infile_name): - os.remove(tmp_infile_name) - if os.path.exists(output_ir_name): - os.remove(output_ir_name) + if self.options.verbose: + print(f"[ARTIQ] Generated ELF: {output}", file=self.options.logfile) + + # Clean up temporary input file + if os.path.exists(tmp_infile_name): + os.remove(tmp_infile_name) + else: + output = LinkerDriver.run(output_object_name, options=self.options) + output = str(pathlib.Path(output).absolute()) + + # Clean up temporary files + if os.path.exists(tmp_infile_name): + os.remove(tmp_infile_name) + if os.path.exists(output_ir_name): + os.remove(output_ir_name) - return output_object_name, out_IR + return output, out_IR def has_xdsl_passes_in_transform_modules(self, mlir_module): """Check if the MLIR module contains xDSL passes in transform dialect. diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index f17051e41b..feb7d70076 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -530,6 +530,13 @@ def __init__(self, fn, compile_options): functools.update_wrapper(self, fn) self.original_function = fn self.compile_options = compile_options + + # Extract artiq_config from device + if compile_options.artiq_config is None and isinstance(fn, qml.QNode): + device_artiq_config = getattr(fn.device, "artiq_config", None) + if device_artiq_config is not None: + compile_options.artiq_config = device_artiq_config + self.compiler = Compiler(compile_options) self.fn_cache = CompilationCache( compile_options.static_argnums, compile_options.abstracted_axes @@ -546,7 +553,11 @@ def __init__(self, fn, compile_options): self.mlir_module = None self.out_type = None self.overwrite_ir = None - self.use_cwd_for_workspace = self.compile_options.keep_intermediate + # Use cwd for workspace if keep_intermediate is set or for ARTIQ targets + self.use_cwd_for_workspace = ( + self.compile_options.keep_intermediate or self.compile_options.artiq_config + ) + self._artiq_compiled = False # Track if ARTIQ compilation has been done self.user_sig = get_type_annotations(fn) self._validate_configuration() @@ -603,6 +614,10 @@ def __call__(self, *args, **kwargs): requires_promotion = self.jit_compile(args, **kwargs) + # For ARTIQ targets, compilation is complete, no execution needed + if self.compile_options.artiq_config: + return None + # If we receive tracers as input, dispatch to the JAX integration. if any(isinstance(arg, jax.core.Tracer) for arg in tree_flatten(args)[0]): if self.jaxed_function is None: @@ -631,6 +646,11 @@ def aot_compile(self): if self.compile_options.target in ("binary",): self.compiled_function, _ = self.compile() + + if self.compile_options.artiq_config: + self._artiq_compiled = True + return None + self.fn_cache.insert( self.compiled_function, self.user_sig, self.out_treedef, self.workspace ) @@ -656,6 +676,17 @@ def jit_compile(self, args, **kwargs): bool: whether the provided arguments will require promotion to be used with the compiled function """ + # For ARTIQ targets, compile only once (skip if already compiled via AOT) + if self.compile_options.artiq_config: + if self._artiq_compiled: + return False + self.workspace = self._get_workspace() + self.jaxpr, self.out_type, self.out_treedef, self.c_sig = self.capture(args, **kwargs) + self.mlir_module = self.generate_ir() + self.compiled_function, _ = self.compile() + self._artiq_compiled = True + return False + cached_fn, requires_promotion = self.fn_cache.lookup(args) if cached_fn is None: @@ -795,6 +826,7 @@ def compile(self): Returns: Tuple[CompiledFunction, str]: the compilation result and LLVMIR + For ARTIQ targets, returns (elf_path, llvm_ir) instead. """ # WARNING: assumption is that the first function is the entry point to the compiled program. entry_point_func = self.mlir_module.body.operations[0] @@ -820,6 +852,12 @@ def compile(self): else: shared_object, llvm_ir = self.compiler.run(self.mlir_module, self.workspace) + # We don't create a CompiledFunction for ARTIQ targets, just return the ELF path and LLVM IR + if self.compile_options.artiq_config: + print(f"[ARTIQ] Generated ELF: {shared_object}") + print(f"[ARTIQ] Object files saved in workspace: {self.workspace}") + return None, llvm_ir + compiled_fn = CompiledFunction( shared_object, func_name, restype, self.out_type, self.compile_options ) diff --git a/frontend/catalyst/pipelines.py b/frontend/catalyst/pipelines.py index 86dc177dab..f7987e5272 100644 --- a/frontend/catalyst/pipelines.py +++ b/frontend/catalyst/pipelines.py @@ -137,6 +137,7 @@ class CompileOptions: circuit_transform_pipeline: Optional[dict[str, dict[str, str]]] = None pass_plugins: Optional[Set[Path]] = None dialect_plugins: Optional[Set[Path]] = None + artiq_config: Optional[dict] = None def __post_init__(self): # Convert keep_intermediate to Enum diff --git a/frontend/catalyst/third_party/oqd/oqd_device.py b/frontend/catalyst/third_party/oqd/oqd_device.py index dc725afae8..1e9ca36d98 100644 --- a/frontend/catalyst/third_party/oqd/oqd_device.py +++ b/frontend/catalyst/third_party/oqd/oqd_device.py @@ -20,6 +20,7 @@ trapped-ion quantum computer device. """ from typing import Optional +import os import platform from pennylane.devices import Device, ExecutionConfig @@ -29,7 +30,17 @@ BACKENDS = ["default"] -def OQDDevicePipeline(device, qubit, gate): +def get_default_artiq_config(): + """Get default ARTIQ cross-compilation configuration""" + # Check environment variable + kernel_ld = os.environ.get("ARTIQ_KERNEL_LD") + if kernel_ld and os.path.exists(kernel_ld): + return {"kernel_ld": kernel_ld} + + return None + + +def OQDDevicePipeline(device, qubit, gate, device_db=None): """ Generate the compilation pipeline for an OQD device. @@ -37,12 +48,50 @@ def OQDDevicePipeline(device, qubit, gate): device (str): the path to the device toml file specifications. qubit (str): the path to the qubit toml file specifications. gate (str): the path to the gate toml file specifications. + device_db (str, optional): the path to the device_db.json file for ARTIQ. + If provided, generates ARTIQ-compatible output. + If None, uses convert-ion-to-llvm for legacy OQD pipeline. Returns: A list of tuples, with each tuple being a stage in the compilation pipeline. When using ``keep_intermediate=True`` from :func:`~.qjit`, the kept stages correspond to the tuples. """ + # Common gates-to-pulses pass + gates_to_pulses_pass = ( + "func.func(gates-to-pulses{" + + "device-toml-loc=" + + device + + " qubit-toml-loc=" + + qubit + + " gate-to-pulse-toml-loc=" + + gate + + "})" + ) + + # Build OQD pipeline based on whether device_db is provided + if device_db is not None: + oqd_passes = [ + "func.func(ions-decomposition)", + gates_to_pulses_pass, + "convert-ion-to-rtio{" + "device_db=" + device_db + "}", + "convert-rtio-event-to-artiq", + ] + llvm_lowering_passes = [ + "llvm-dialect-lowering-stage", + "emit-artiq-runtime", + ] + else: + # Standard LLVM lowering route (legacy OQD pipeline) + oqd_passes = [ + "func.func(ions-decomposition)", + gates_to_pulses_pass, + "convert-ion-to-llvm", + ] + llvm_lowering_passes = [ + "llvm-dialect-lowering-stage", + ] + return [ ( "device-agnostic-pipeline", @@ -55,30 +104,27 @@ def OQDDevicePipeline(device, qubit, gate): ), ( "oqd_pipeline", - [ - "func.func(ions-decomposition)", - "func.func(gates-to-pulses{" - + "device-toml-loc=" - + device - + " qubit-toml-loc=" - + qubit - + " gate-to-pulse-toml-loc=" - + gate - + "})", - "convert-ion-to-llvm", - ], + oqd_passes, ), ( "llvm-dialect-lowering-stage", - [ - "llvm-dialect-lowering-stage", - ], + llvm_lowering_passes, ), ] class OQDDevice(Device): - """The OQD device allows access to the hardware devices from OQD using Catalyst.""" + """The OQD device allows access to the hardware devices from OQD using Catalyst. + + Args: + wires: The number of wires/qubits. + backend: Backend name (default: "default"). + openapl_file_name: Output file name for OpenAPL. + artiq_config: ARTIQ cross-compilation configuration dict with keys: + - kernel_ld: Path to ARTIQ's kernel.ld linker script + - llc_path: Path to llc + - lld_path: Path to ld.lld + """ config_filepath = get_lib_path("oqd_runtime", "OQD_LIB_DIR") + "/backend" + "/oqd.toml" @@ -96,7 +142,12 @@ def get_c_interface(): return "oqd", lib_path def __init__( - self, wires, backend="default", openapl_file_name="__openapl__output.json", **kwargs + self, + wires, + backend="default", + openapl_file_name="__openapl__output.json", + artiq_config=None, + **kwargs, ): self._backend = backend self._openapl_file_name = openapl_file_name @@ -106,6 +157,16 @@ def __init__( "openapl_file_name": self._openapl_file_name, } + if artiq_config is not None: + self._artiq_config = artiq_config + else: + self._artiq_config = get_default_artiq_config() + + @property + def artiq_config(self): + """ARTIQ cross-compilation configuration.""" + return self._artiq_config + @property def openapl_file_name(self): """The OpenAPL output file name.""" diff --git a/mlir/include/Driver/CompilerDriver.h b/mlir/include/Driver/CompilerDriver.h index ff8743764d..dcd5a0f608 100644 --- a/mlir/include/Driver/CompilerDriver.h +++ b/mlir/include/Driver/CompilerDriver.h @@ -80,12 +80,32 @@ struct CompilerOptions { /// If true, the compiler will dump the pass pipeline that will be run. bool dumpPassPipeline; + /// ARTIQ cross-compilation settings + bool artiqEnabled = false; + std::string artiqKernelLd; // Path to ARTIQ kernel.ld linker script + std::string artiqLlcPath; // Path to llc + std::string artiqLldPath; // Path to ld.lld + /// Get the destination of the object file at the end of compilation. std::string getObjectFile() const { using path = std::filesystem::path; return path(workspace.str()) / path(moduleName.str() + ".o"); } + + /// Get the destination of the ELF file for ARTIQ. + std::string getElfFile() const + { + using path = std::filesystem::path; + return path(workspace.str()) / path(moduleName.str() + ".elf"); + } + + /// Get the destination of the LLVM IR file. + std::string getLLFile() const + { + using path = std::filesystem::path; + return path(workspace.str()) / path(moduleName.str() + ".ll"); + } }; struct CompilerOutput { diff --git a/mlir/include/RTIO/Transforms/Passes.td b/mlir/include/RTIO/Transforms/Passes.td index ea8ad1151b..bb1238c88f 100644 --- a/mlir/include/RTIO/Transforms/Passes.td +++ b/mlir/include/RTIO/Transforms/Passes.td @@ -39,6 +39,19 @@ def RTIOEventToARTIQPass : Pass<"convert-rtio-event-to-artiq", "mlir::ModuleOp"> ]; } -#endif // RTIO_PASSES +def EmitARTIQRuntimePass : Pass<"emit-artiq-runtime", "mlir::ModuleOp"> { + let summary = "Emit ARTIQ runtime wrapper (__modinit__) that calls the kernel function"; + let description = [{ + This pass creates the ARTIQ entry point structure: + - __modinit__ (entry point) -> calls kernel function + This pass injects the necessary ARTIQ runtime boilerplate directly into the MLIR module + to allow Catalyst-generated kernels can be loaded and executed by ARTIQ + }]; + let dependentDialects = [ + "mlir::LLVM::LLVMDialect", + "mlir::func::FuncDialect" + ]; +} +#endif // RTIO_PASSES diff --git a/mlir/lib/Driver/CompilerDriver.cpp b/mlir/lib/Driver/CompilerDriver.cpp index 6b53fec856..0db92b474f 100644 --- a/mlir/lib/Driver/CompilerDriver.cpp +++ b/mlir/lib/Driver/CompilerDriver.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include @@ -805,8 +806,6 @@ LogicalResult QuantumDriverMain(const CompilerOptions &options, CompilerOutput & if (runLLC && (inType == InputType::LLVMIR)) { TimingScope llcTiming = timing.nest("llc"); - // Set data layout before LLVM passes or the default one is used. - llvm::Triple targetTriple(llvm::sys::getDefaultTargetTriple()); llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargets(); @@ -814,11 +813,78 @@ LogicalResult QuantumDriverMain(const CompilerOptions &options, CompilerOutput & llvm::InitializeAllAsmParsers(); llvm::InitializeAllAsmPrinters(); - std::string err; - auto target = llvm::TargetRegistry::lookupTarget(targetTriple, err); - llvm::TargetOptions opt; + // Use external LLC with ARM support (catalyst's LLVM doesn't have corresponding ARM backend + // for now) + if (options.artiqEnabled) { + // Save LLVM IR to file for external LLC + std::string llFile = options.getLLFile(); + std::error_code errCode; + llvm::raw_fd_ostream llOut(llFile, errCode, llvm::sys::fs::OF_None); + if (errCode) { + CO_MSG(options, Verbosity::Urgent, + "Failed to open " << llFile << ": " << errCode.message() << "\n"); + return failure(); + } + llvmModule->print(llOut, nullptr); + llOut.close(); + + // Get LLC path + std::string llcPath = options.artiqLlcPath.empty() ? "llc" : options.artiqLlcPath; + std::string llcCmd = llcPath + + " -mtriple=armv7-unknown-linux-gnueabihf -mcpu=cortex-a9" + " -filetype=obj -relocation-model=pic -o " + + options.getObjectFile() + " " + llFile + " 2>&1"; + + CO_MSG(options, Verbosity::All, + "[ARTIQ] Compiling with external LLC: " << llcCmd << "\n"); + + int llcResult = std::system(llcCmd.c_str()); + if (llcResult != 0) { + CO_MSG(options, Verbosity::Urgent, + "External LLC failed with exit code: " << llcResult << "\n"); + return failure(); + } + + // Link to ELF + std::string lldPath = options.artiqLldPath.empty() ? "ld.lld" : options.artiqLldPath; + if (options.artiqKernelLd.empty()) { + CO_MSG(options, Verbosity::Urgent, "ARTIQ kernel.ld path not specified\n"); + return failure(); + } + + std::string lldCmd = lldPath + + " -shared --eh-frame-hdr -m armelf_linux_eabi " + "--target2=rel -T " + + options.artiqKernelLd + " " + options.getObjectFile() + " -o " + + options.getElfFile() + " 2>&1"; + + CO_MSG(options, Verbosity::All, "[ARTIQ] Linking ELF: " << lldCmd << "\n"); + + int lldResult = std::system(lldCmd.c_str()); + if (lldResult != 0) { + CO_MSG(options, Verbosity::Urgent, + "LLD linking failed with exit code: " << lldResult << "\n"); + return failure(); + } + + CO_MSG(options, Verbosity::All, + "[ARTIQ] Generated ELF: " << options.getElfFile() << "\n"); + llcTiming.stop(); + return success(); + } + + llvm::Triple targetTriple(llvm::sys::getDefaultTargetTriple()); const char *cpu = "generic"; const char *features = ""; + + std::string err; + auto target = llvm::TargetRegistry::lookupTarget(targetTriple.str(), err); + if (!target) { + CO_MSG(options, Verbosity::Urgent, "Failed to lookup target: " << err << "\n"); + return failure(); + } + + llvm::TargetOptions opt; auto targetMachine = target->createTargetMachine(targetTriple, cpu, features, opt, llvm::Reloc::Model::PIC_); targetMachine->setOptLevel(llvm::CodeGenOptLevel::None); @@ -1007,6 +1073,17 @@ int QuantumDriverMainFromCL(int argc, char **argv) cl::desc("Print the whole module in intermediate files"), cl::init(true), cl::cat(CatalystCat)); + // ARTIQ cross-compilation options + cl::opt ArtiqEnabled("artiq", cl::desc("Enable ARTIQ cross-compilation to ARM ELF"), + cl::init(false), cl::cat(CatalystCat)); + cl::opt ArtiqKernelLd("artiq-kernel-ld", + cl::desc("Path to ARTIQ kernel.ld linker script"), + cl::init(""), cl::cat(CatalystCat)); + cl::opt ArtiqLlcPath("artiq-llc-path", cl::desc("Path to llc for ARTIQ"), + cl::init(""), cl::cat(CatalystCat)); + cl::opt ArtiqLldPath("artiq-lld-path", cl::desc("Path to ld.lld for ARTIQ"), + cl::init(""), cl::cat(CatalystCat)); + // Create dialect registry DialectRegistry registry; mlir::registerAllPasses(); @@ -1058,7 +1135,11 @@ int QuantumDriverMainFromCL(int argc, char **argv) .pipelinesCfg = parsePipelines(CatalystPipeline), .checkpointStage = CheckpointStage, .loweringAction = LoweringAction, - .dumpPassPipeline = DumpPassPipeline}; + .dumpPassPipeline = DumpPassPipeline, + .artiqEnabled = ArtiqEnabled, + .artiqKernelLd = ArtiqKernelLd, + .artiqLlcPath = ArtiqLlcPath, + .artiqLldPath = ArtiqLldPath}; mlir::LogicalResult result = QuantumDriverMain(options, *output, registry); diff --git a/mlir/lib/RTIO/Transforms/CMakeLists.txt b/mlir/lib/RTIO/Transforms/CMakeLists.txt index e2ee789bcf..6b8da81d51 100644 --- a/mlir/lib/RTIO/Transforms/CMakeLists.txt +++ b/mlir/lib/RTIO/Transforms/CMakeLists.txt @@ -3,6 +3,7 @@ set(LIBRARY_NAME rtio-transforms) file(GLOB SRC RTIOEventToARTIQ.cpp RTIOEventToARTIQPatterns.cpp + EmitARTIQRuntime.cpp ) get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) From ac3512760d1054742abdcb3969ac3fa684fd316b Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Mon, 15 Dec 2025 16:17:31 -0500 Subject: [PATCH 02/23] formatting --- mlir/include/Driver/CompilerDriver.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mlir/include/Driver/CompilerDriver.h b/mlir/include/Driver/CompilerDriver.h index dcd5a0f608..e31cb5dfb9 100644 --- a/mlir/include/Driver/CompilerDriver.h +++ b/mlir/include/Driver/CompilerDriver.h @@ -82,9 +82,9 @@ struct CompilerOptions { /// ARTIQ cross-compilation settings bool artiqEnabled = false; - std::string artiqKernelLd; // Path to ARTIQ kernel.ld linker script - std::string artiqLlcPath; // Path to llc - std::string artiqLldPath; // Path to ld.lld + std::string artiqKernelLd; // Path to ARTIQ kernel.ld linker script + std::string artiqLlcPath; // Path to llc + std::string artiqLldPath; // Path to ld.lld /// Get the destination of the object file at the end of compilation. std::string getObjectFile() const From 8675505afb5951770c53a53ca48174793f788d17 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Tue, 16 Dec 2025 10:43:50 -0500 Subject: [PATCH 03/23] update comment --- frontend/catalyst/jit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index feb7d70076..87eae303c6 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -852,7 +852,7 @@ def compile(self): else: shared_object, llvm_ir = self.compiler.run(self.mlir_module, self.workspace) - # We don't create a CompiledFunction for ARTIQ targets, just return the ELF path and LLVM IR + # We don't create a CompiledFunction for ARTIQ targets, just return it with None if self.compile_options.artiq_config: print(f"[ARTIQ] Generated ELF: {shared_object}") print(f"[ARTIQ] Object files saved in workspace: {self.workspace}") From 1f5a13d29a73d616d65eac06e21466e4cc391cd5 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Tue, 16 Dec 2025 11:40:12 -0500 Subject: [PATCH 04/23] inconsistent return statements --- frontend/catalyst/jit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 87eae303c6..28908596f8 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -649,7 +649,7 @@ def aot_compile(self): if self.compile_options.artiq_config: self._artiq_compiled = True - return None + return self.fn_cache.insert( self.compiled_function, self.user_sig, self.out_treedef, self.workspace From cdd886e6ab183ca5a03f1295088b520da29fe792 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Tue, 16 Dec 2025 11:41:02 -0500 Subject: [PATCH 05/23] update comment --- frontend/catalyst/jit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 28908596f8..3b4f9f3401 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -826,7 +826,7 @@ def compile(self): Returns: Tuple[CompiledFunction, str]: the compilation result and LLVMIR - For ARTIQ targets, returns (elf_path, llvm_ir) instead. + For ARTIQ targets, returns (None, llvm_ir) instead. """ # WARNING: assumption is that the first function is the entry point to the compiled program. entry_point_func = self.mlir_module.body.operations[0] From 6710592db1f717096b30fce6a1808a2e180f5b94 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Mon, 5 Jan 2026 10:42:15 -0500 Subject: [PATCH 06/23] fix --- frontend/catalyst/compiler.py | 27 +++++++++++++++---------- mlir/include/RTIO/Transforms/Passes.td | 2 +- mlir/lib/RTIO/Transforms/CMakeLists.txt | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/frontend/catalyst/compiler.py b/frontend/catalyst/compiler.py index 3c28499e25..b2d7b61460 100644 --- a/frontend/catalyst/compiler.py +++ b/frontend/catalyst/compiler.py @@ -325,6 +325,21 @@ def canonicalize(*args, stdin=None, options: Optional[CompileOptions] = None): return _quantum_opt(*opts, *args, stdin=stdin) +def _artiq_config_to_cli_flags(artiq_config): + """Convert ARTIQ config dict to CLI flags.""" + flags = ["--artiq"] + flag_mapping = { + "kernel_ld": "--artiq-kernel-ld", + "llc_path": "--artiq-llc-path", + "lld_path": "--artiq-lld-path", + } + for key, flag in flag_mapping.items(): + value = artiq_config.get(key, "") + if value: + flags.append((flag, value)) + return flags + + def _options_to_cli_flags(options): """CompileOptions -> list[str|Tuple[str, str]]""" @@ -368,18 +383,8 @@ def _options_to_cli_flags(options): if options.async_qnodes: # pragma: nocover extra_args += ["--async-qnodes"] - # ARTIQ cross-compilation flags if options.artiq_config: - extra_args += ["--artiq"] - kernel_ld = options.artiq_config.get("kernel_ld", "") - if kernel_ld: - extra_args += [("--artiq-kernel-ld", kernel_ld)] - llc_path = options.artiq_config.get("llc_path", "") - if llc_path: - extra_args += [("--artiq-llc-path", llc_path)] - lld_path = options.artiq_config.get("lld_path", "") - if lld_path: - extra_args += [("--artiq-lld-path", lld_path)] + extra_args += _artiq_config_to_cli_flags(options.artiq_config) return extra_args diff --git a/mlir/include/RTIO/Transforms/Passes.td b/mlir/include/RTIO/Transforms/Passes.td index bb1238c88f..0fed01f7d4 100644 --- a/mlir/include/RTIO/Transforms/Passes.td +++ b/mlir/include/RTIO/Transforms/Passes.td @@ -39,7 +39,7 @@ def RTIOEventToARTIQPass : Pass<"convert-rtio-event-to-artiq", "mlir::ModuleOp"> ]; } -def EmitARTIQRuntimePass : Pass<"emit-artiq-runtime", "mlir::ModuleOp"> { +def RTIOEmitARTIQRuntimePass : Pass<"emit-artiq-runtime", "mlir::ModuleOp"> { let summary = "Emit ARTIQ runtime wrapper (__modinit__) that calls the kernel function"; let description = [{ This pass creates the ARTIQ entry point structure: diff --git a/mlir/lib/RTIO/Transforms/CMakeLists.txt b/mlir/lib/RTIO/Transforms/CMakeLists.txt index 6b8da81d51..4c806b5674 100644 --- a/mlir/lib/RTIO/Transforms/CMakeLists.txt +++ b/mlir/lib/RTIO/Transforms/CMakeLists.txt @@ -3,7 +3,7 @@ set(LIBRARY_NAME rtio-transforms) file(GLOB SRC RTIOEventToARTIQ.cpp RTIOEventToARTIQPatterns.cpp - EmitARTIQRuntime.cpp + RTIOEmitARTIQRuntime.cpp ) get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) From 4289603195675179529843a700a466958b7fb738 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Mon, 5 Jan 2026 11:21:20 -0500 Subject: [PATCH 07/23] add missing lib --- mlir/tools/catalyst-cli/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/tools/catalyst-cli/CMakeLists.txt b/mlir/tools/catalyst-cli/CMakeLists.txt index 39bd44ecf4..b83e628dca 100644 --- a/mlir/tools/catalyst-cli/CMakeLists.txt +++ b/mlir/tools/catalyst-cli/CMakeLists.txt @@ -43,6 +43,7 @@ set(LIBS MLIRIon ion-transforms MLIRRTIO + rtio-transforms MLIRCatalystTest ${ENZYME_LIB} CatalystCompilerDriver From ffa587565473fef3f7559654206825be0ce87889 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Thu, 15 Jan 2026 13:28:59 -0500 Subject: [PATCH 08/23] Update --- mlir/lib/Driver/CompilerDriver.cpp | 48 ++++- .../RTIO/Transforms/RTIOEmitARTIQRuntime.cpp | 164 ++++++++++++++++++ 2 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp diff --git a/mlir/lib/Driver/CompilerDriver.cpp b/mlir/lib/Driver/CompilerDriver.cpp index 294f317123..967c8f289f 100644 --- a/mlir/lib/Driver/CompilerDriver.cpp +++ b/mlir/lib/Driver/CompilerDriver.cpp @@ -33,6 +33,7 @@ #include "llvm/Passes/PassBuilder.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/InitLLVM.h" +#include "llvm/Support/Program.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/ToolOutputFile.h" @@ -840,10 +841,27 @@ LogicalResult QuantumDriverMain(const CompilerOptions &options, CompilerOutput & CO_MSG(options, Verbosity::All, "[ARTIQ] Compiling with external LLC: " << llcCmd << "\n"); - int llcResult = std::system(llcCmd.c_str()); + std::string objectFile = options.getObjectFile(); + llvm::SmallVector llcArgs; + llcArgs.push_back(llcPath); + llcArgs.push_back("-mtriple=armv7-unknown-linux-gnueabihf"); + llcArgs.push_back("-mcpu=cortex-a9"); + llcArgs.push_back("-filetype=obj"); + llcArgs.push_back("-relocation-model=pic"); + llcArgs.push_back("-o"); + llcArgs.push_back(objectFile); + llcArgs.push_back(llFile); + + std::string llcErrMsg; + int llcResult = + llvm::sys::ExecuteAndWait(llcPath, llcArgs, std::nullopt, {}, 0, 0, &llcErrMsg); if (llcResult != 0) { CO_MSG(options, Verbosity::Urgent, - "External LLC failed with exit code: " << llcResult << "\n"); + "External LLC failed with exit code: " << llcResult); + if (!llcErrMsg.empty()) { + CO_MSG(options, Verbosity::Urgent, ": " << llcErrMsg); + } + CO_MSG(options, Verbosity::Urgent, "\n"); return failure(); } @@ -862,10 +880,32 @@ LogicalResult QuantumDriverMain(const CompilerOptions &options, CompilerOutput & CO_MSG(options, Verbosity::All, "[ARTIQ] Linking ELF: " << lldCmd << "\n"); - int lldResult = std::system(lldCmd.c_str()); + std::string kernelLd = options.artiqKernelLd; + std::string objectFileLd = options.getObjectFile(); + std::string elfFile = options.getElfFile(); + llvm::SmallVector lldArgs; + lldArgs.push_back(lldPath); + lldArgs.push_back("-shared"); + lldArgs.push_back("--eh-frame-hdr"); + lldArgs.push_back("-m"); + lldArgs.push_back("armelf_linux_eabi"); + lldArgs.push_back("--target2=rel"); + lldArgs.push_back("-T"); + lldArgs.push_back(kernelLd); + lldArgs.push_back(objectFileLd); + lldArgs.push_back("-o"); + lldArgs.push_back(elfFile); + + std::string lldErrMsg; + int lldResult = + llvm::sys::ExecuteAndWait(lldPath, lldArgs, std::nullopt, {}, 0, 0, &lldErrMsg); if (lldResult != 0) { CO_MSG(options, Verbosity::Urgent, - "LLD linking failed with exit code: " << lldResult << "\n"); + "LLD linking failed with exit code: " << lldResult); + if (!lldErrMsg.empty()) { + CO_MSG(options, Verbosity::Urgent, ": " << lldErrMsg); + } + CO_MSG(options, Verbosity::Urgent, "\n"); return failure(); } diff --git a/mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp b/mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp new file mode 100644 index 0000000000..0c4f8f614d --- /dev/null +++ b/mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp @@ -0,0 +1,164 @@ +// 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. + +/// This pass creates the ARTIQ entry point structure that allows Catalyst-generated +/// kernels to be loaded and executed by ARTIQ +/// +/// The pass transforms: +/// @__kernel__(ptr, ptr, i64) +/// Into: +/// @__modinit__(ptr) -> calls @__kernel__(ptr, nullptr, 0) + +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/Pass/Pass.h" + +#include "ARTIQRuntimeBuilder.hpp" +#include "RTIO/Transforms/Passes.h" + +using namespace mlir; + +namespace catalyst { +namespace rtio { + +#define GEN_PASS_DEF_RTIOEMITARTIQRUNTIMEPASS +#include "RTIO/Transforms/Passes.h.inc" + +namespace { + +//===----------------------------------------------------------------------===// +// ARTIQ Runtime Constants +//===----------------------------------------------------------------------===// + +namespace ARTIQRuntime { +constexpr StringLiteral modinit = "__modinit__"; +constexpr StringLiteral artiqPersonality = "__artiq_personality"; +} // namespace ARTIQRuntime + +//===----------------------------------------------------------------------===// +// Pass Implementation +//===----------------------------------------------------------------------===// + +struct RTIOEmitARTIQRuntimePass + : public impl::RTIOEmitARTIQRuntimePassBase { + using RTIOEmitARTIQRuntimePassBase::RTIOEmitARTIQRuntimePassBase; + + void runOnOperation() override + { + ModuleOp module = getOperation(); + MLIRContext *ctx = &getContext(); + OpBuilder builder(ctx); + + // Check if __modinit__ already exists (the entry point of ARTIQ device) + if (module.lookupSymbol(ARTIQRuntime::modinit)) { + return; + } + + // Find the kernel function e(could be LLVM func or func.func) + LLVM::LLVMFuncOp llvmKernelFunc = + module.lookupSymbol(ARTIQFuncNames::kernel); + + if (!llvmKernelFunc) { + module.emitError("Cannot find kernel function"); + return signalPassFailure(); + } + + // Create ARTIQ runtime wrapper + if (failed(emitARTIQRuntimeForLLVMFunc(module, builder, llvmKernelFunc))) { + return signalPassFailure(); + } + } + + private: + /// Emit ARTIQ runtime wrapper for LLVM dialect kernel function + LogicalResult emitARTIQRuntimeForLLVMFunc(ModuleOp module, OpBuilder &builder, + LLVM::LLVMFuncOp kernelFunc) + { + MLIRContext *ctx = builder.getContext(); + Location loc = module.getLoc(); + + // Types + Type voidTy = LLVM::LLVMVoidType::get(ctx); + Type ptrTy = LLVM::LLVMPointerType::get(ctx); + Type i32Ty = IntegerType::get(ctx, 32); + Type i64Ty = IntegerType::get(ctx, 64); + + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(module.getBody()); + + // Declare __artiq_personality (exception handling) + declareARTIQPersonality(module, builder, loc); + + // Create entry function: void @__modinit__(ptr %self) + auto modinitTy = LLVM::LLVMFunctionType::get(voidTy, {ptrTy}); + auto modinitFunc = builder.create(loc, ARTIQRuntime::modinit, modinitTy); + modinitFunc.setLinkage(LLVM::Linkage::External); + + // Set personality function for exception handling + modinitFunc.setPersonalityAttr(FlatSymbolRefAttr::get(ctx, ARTIQRuntime::artiqPersonality)); + + // Create function body + Block *entry = modinitFunc.addEntryBlock(builder); + builder.setInsertionPointToStart(entry); + + // Get the actual kernel function type and create matching arguments + auto kernelFuncTy = kernelFunc.getFunctionType(); + SmallVector callArgs; + + for (Type argTy : kernelFuncTy.getParams()) { + // Create zero/null values for each argument type + if (isa(argTy)) { + callArgs.push_back(builder.create(loc, ptrTy)); + } + else if (argTy.isInteger(64)) { + callArgs.push_back( + builder.create(loc, i64Ty, builder.getI64IntegerAttr(0))); + } + else if (argTy.isInteger(32)) { + callArgs.push_back( + builder.create(loc, i32Ty, builder.getI32IntegerAttr(0))); + } + else { + // For other types, use null pointer as fallback + callArgs.push_back(builder.create(loc, ptrTy)); + } + } + + auto callOp = builder.create(loc, kernelFunc, callArgs); + callOp.setTailCallKind(LLVM::TailCallKind::Tail); + + builder.create(loc, ValueRange{}); + + return success(); + } + + /// Declare __artiq_personality function + void declareARTIQPersonality(ModuleOp module, OpBuilder &builder, Location loc) + { + if (module.lookupSymbol(ARTIQRuntime::artiqPersonality)) { + return; + } + + Type i32Ty = IntegerType::get(builder.getContext(), 32); + auto personalityTy = LLVM::LLVMFunctionType::get(i32Ty, {}, /*isVarArg=*/true); + builder.create(loc, ARTIQRuntime::artiqPersonality, personalityTy, + LLVM::Linkage::External); + } +}; + +} // namespace +} // namespace rtio +} // namespace catalyst From 22e3d6784817a80173545708027537228e368d61 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Thu, 15 Jan 2026 13:34:20 -0500 Subject: [PATCH 09/23] pylint --- frontend/catalyst/compiler.py | 1 + frontend/catalyst/jit.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/frontend/catalyst/compiler.py b/frontend/catalyst/compiler.py index b2d7b61460..d853c51456 100644 --- a/frontend/catalyst/compiler.py +++ b/frontend/catalyst/compiler.py @@ -448,6 +448,7 @@ def get_cli_command(self, tmp_infile_name, output_ir_name, module_name, workspac ) return cmd + # pylint: disable=too-many-branches @debug_logger def run_from_ir(self, ir: str, module_name: str, workspace: Directory): """Compile a shared object from a textual IR (MLIR or LLVM). diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 3b4f9f3401..3c442c1ad7 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -16,6 +16,8 @@ compilation of hybrid quantum-classical functions using Catalyst. """ +# pylint: disable=too-many-lines + import copy import functools import inspect From 14d18a577db8608248fd5c93d33aa53491c175af Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Thu, 15 Jan 2026 13:43:06 -0500 Subject: [PATCH 10/23] add tests for EmitArtiqRuntime --- mlir/test/RTIO/RTIOEmitARTIQRuntime.mlir | 49 ++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 mlir/test/RTIO/RTIOEmitARTIQRuntime.mlir diff --git a/mlir/test/RTIO/RTIOEmitARTIQRuntime.mlir b/mlir/test/RTIO/RTIOEmitARTIQRuntime.mlir new file mode 100644 index 0000000000..7b11472f81 --- /dev/null +++ b/mlir/test/RTIO/RTIOEmitARTIQRuntime.mlir @@ -0,0 +1,49 @@ +// 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 %s --emit-artiq-runtime --split-input-file | FileCheck %s + +// Test basic ARTIQ runtime wrapper generation +// CHECK-LABEL: module @test_basic +module @test_basic { + // CHECK: llvm.func @__artiq_personality(...) -> i32 + // CHECK: llvm.func @__modinit__(%arg0: !llvm.ptr) attributes {personality = @__artiq_personality} + // CHECK-SAME: { + // CHECK: llvm.call tail @__kernel__() : () -> () + // CHECK: llvm.return + // CHECK: } + llvm.func @__kernel__() { + llvm.return + } +} + +// ----- + +// Test with kernel function that has arguments +// CHECK-LABEL: module @test_kernel_with_args +module @test_kernel_with_args { + // CHECK: llvm.func @__artiq_personality(...) -> i32 + // CHECK: llvm.func @__modinit__(%arg0: !llvm.ptr) attributes {personality = @__artiq_personality} + // CHECK-SAME: { + // CHECK: %[[ZERO_PTR0:.*]] = llvm.mlir.zero : !llvm.ptr + // CHECK: %[[ZERO_PTR1:.*]] = llvm.mlir.zero : !llvm.ptr + // CHECK: %[[ZERO_I64:.*]] = llvm.mlir.constant(0 : i64) : i64 + // CHECK: llvm.call tail @__kernel__(%[[ZERO_PTR0]], %[[ZERO_PTR1]], %[[ZERO_I64]]) + // CHECK: llvm.return + // CHECK: } + llvm.func @__kernel__(%arg0: !llvm.ptr, %arg1: !llvm.ptr, %arg2: i64) { + llvm.return + } +} + From 3eeaf4ac8c82d58f3127b7997e6e76773120b725 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 10:50:27 -0500 Subject: [PATCH 11/23] move the artiq specific logic out catalyst --- frontend/catalyst/compiler.py | 46 ++--- frontend/catalyst/jit.py | 41 ++-- frontend/catalyst/pipelines.py | 1 - frontend/catalyst/third_party/oqd/__init__.py | 3 +- .../catalyst/third_party/oqd/oqd_compile.py | 191 ++++++++++++++++++ mlir/include/Driver/CompilerDriver.h | 13 -- mlir/lib/Driver/CompilerDriver.cpp | 116 +---------- 7 files changed, 218 insertions(+), 193 deletions(-) create mode 100644 frontend/catalyst/third_party/oqd/oqd_compile.py diff --git a/frontend/catalyst/compiler.py b/frontend/catalyst/compiler.py index d853c51456..02600f453a 100644 --- a/frontend/catalyst/compiler.py +++ b/frontend/catalyst/compiler.py @@ -325,21 +325,6 @@ def canonicalize(*args, stdin=None, options: Optional[CompileOptions] = None): return _quantum_opt(*opts, *args, stdin=stdin) -def _artiq_config_to_cli_flags(artiq_config): - """Convert ARTIQ config dict to CLI flags.""" - flags = ["--artiq"] - flag_mapping = { - "kernel_ld": "--artiq-kernel-ld", - "llc_path": "--artiq-llc-path", - "lld_path": "--artiq-lld-path", - } - for key, flag in flag_mapping.items(): - value = artiq_config.get(key, "") - if value: - flags.append((flag, value)) - return flags - - def _options_to_cli_flags(options): """CompileOptions -> list[str|Tuple[str, str]]""" @@ -383,9 +368,6 @@ def _options_to_cli_flags(options): if options.async_qnodes: # pragma: nocover extra_args += ["--async-qnodes"] - if options.artiq_config: - extra_args += _artiq_config_to_cli_flags(options.artiq_config) - return extra_args @@ -503,25 +485,21 @@ def run_from_ir(self, ir: str, module_name: str, workspace: Directory): else: out_IR = None - if self.options.artiq_config: - output_elf_name = os.path.join(str(workspace), f"{module_name}.elf") - output = str(pathlib.Path(output_elf_name).absolute()) - - if self.options.verbose: - print(f"[ARTIQ] Generated ELF: {output}", file=self.options.logfile) - - # Clean up temporary input file + # If target is llvm-ir, only return LLVM IR without linking + if self.options.target == "llvmir": + output = output_ir_name if os.path.exists(output_ir_name) else None if os.path.exists(tmp_infile_name): os.remove(tmp_infile_name) - else: - output = LinkerDriver.run(output_object_name, options=self.options) - output = str(pathlib.Path(output).absolute()) + return output, out_IR - # Clean up temporary files - if os.path.exists(tmp_infile_name): - os.remove(tmp_infile_name) - if os.path.exists(output_ir_name): - os.remove(output_ir_name) + output = LinkerDriver.run(output_object_name, options=self.options) + output = str(pathlib.Path(output).absolute()) + + # Clean up temporary files + if os.path.exists(tmp_infile_name): + os.remove(tmp_infile_name) + if os.path.exists(output_ir_name): + os.remove(output_ir_name) return output, out_IR diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 3c442c1ad7..a2ff6ec465 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -533,12 +533,6 @@ def __init__(self, fn, compile_options): self.original_function = fn self.compile_options = compile_options - # Extract artiq_config from device - if compile_options.artiq_config is None and isinstance(fn, qml.QNode): - device_artiq_config = getattr(fn.device, "artiq_config", None) - if device_artiq_config is not None: - compile_options.artiq_config = device_artiq_config - self.compiler = Compiler(compile_options) self.fn_cache = CompilationCache( compile_options.static_argnums, compile_options.abstracted_axes @@ -555,11 +549,8 @@ def __init__(self, fn, compile_options): self.mlir_module = None self.out_type = None self.overwrite_ir = None - # Use cwd for workspace if keep_intermediate is set or for ARTIQ targets - self.use_cwd_for_workspace = ( - self.compile_options.keep_intermediate or self.compile_options.artiq_config - ) - self._artiq_compiled = False # Track if ARTIQ compilation has been done + # Use cwd for workspace if keep_intermediate is set + self.use_cwd_for_workspace = self.compile_options.keep_intermediate self.user_sig = get_type_annotations(fn) self._validate_configuration() @@ -616,8 +607,8 @@ def __call__(self, *args, **kwargs): requires_promotion = self.jit_compile(args, **kwargs) - # For ARTIQ targets, compilation is complete, no execution needed - if self.compile_options.artiq_config: + # For llvm-ir target, compilation is complete, no execution needed + if self.compile_options.target == "llvmir": return None # If we receive tracers as input, dispatch to the JAX integration. @@ -638,21 +629,18 @@ def aot_compile(self): self.workspace = self._get_workspace() # TODO: awkward, refactor or redesign the target feature - if self.compile_options.target in ("jaxpr", "mlir", "binary"): + if self.compile_options.target in ("jaxpr", "mlir", "binary", "llvmir"): self.jaxpr, self.out_type, self.out_treedef, self.c_sig = self.capture( self.user_sig or () ) - if self.compile_options.target in ("mlir", "binary"): + if self.compile_options.target in ("mlir", "llvmir", "binary"): self.mlir_module = self.generate_ir() - if self.compile_options.target in ("binary",): + if self.compile_options.target in ("llvmir", "binary"): self.compiled_function, _ = self.compile() - if self.compile_options.artiq_config: - self._artiq_compiled = True - return - + if self.compile_options.target in ("binary",): self.fn_cache.insert( self.compiled_function, self.user_sig, self.out_treedef, self.workspace ) @@ -678,15 +666,13 @@ def jit_compile(self, args, **kwargs): bool: whether the provided arguments will require promotion to be used with the compiled function """ - # For ARTIQ targets, compile only once (skip if already compiled via AOT) - if self.compile_options.artiq_config: - if self._artiq_compiled: + if self.compile_options.target == "llvmir": + if self.mlir_module is not None: return False self.workspace = self._get_workspace() self.jaxpr, self.out_type, self.out_treedef, self.c_sig = self.capture(args, **kwargs) self.mlir_module = self.generate_ir() self.compiled_function, _ = self.compile() - self._artiq_compiled = True return False cached_fn, requires_promotion = self.fn_cache.lookup(args) @@ -828,7 +814,7 @@ def compile(self): Returns: Tuple[CompiledFunction, str]: the compilation result and LLVMIR - For ARTIQ targets, returns (None, llvm_ir) instead. + For targets that skip execution, returns (None, llvm_ir) instead. """ # WARNING: assumption is that the first function is the entry point to the compiled program. entry_point_func = self.mlir_module.body.operations[0] @@ -854,10 +840,7 @@ def compile(self): else: shared_object, llvm_ir = self.compiler.run(self.mlir_module, self.workspace) - # We don't create a CompiledFunction for ARTIQ targets, just return it with None - if self.compile_options.artiq_config: - print(f"[ARTIQ] Generated ELF: {shared_object}") - print(f"[ARTIQ] Object files saved in workspace: {self.workspace}") + if self.compile_options.target == "llvmir": return None, llvm_ir compiled_fn = CompiledFunction( diff --git a/frontend/catalyst/pipelines.py b/frontend/catalyst/pipelines.py index f7987e5272..86dc177dab 100644 --- a/frontend/catalyst/pipelines.py +++ b/frontend/catalyst/pipelines.py @@ -137,7 +137,6 @@ class CompileOptions: circuit_transform_pipeline: Optional[dict[str, dict[str, str]]] = None pass_plugins: Optional[Set[Path]] = None dialect_plugins: Optional[Set[Path]] = None - artiq_config: Optional[dict] = None def __post_init__(self): # Convert keep_intermediate to Enum diff --git a/frontend/catalyst/third_party/oqd/__init__.py b/frontend/catalyst/third_party/oqd/__init__.py index 7e7d2bb40b..d7174457e4 100644 --- a/frontend/catalyst/third_party/oqd/__init__.py +++ b/frontend/catalyst/third_party/oqd/__init__.py @@ -15,6 +15,7 @@ This submodule contains classes for the OQD device and its properties. """ +from .oqd_compile import compile_to_artiq from .oqd_device import OQDDevice, OQDDevicePipeline -__all__ = ["OQDDevice", "OQDDevicePipeline"] +__all__ = ["OQDDevice", "OQDDevicePipeline", "compile_to_artiq"] diff --git a/frontend/catalyst/third_party/oqd/oqd_compile.py b/frontend/catalyst/third_party/oqd/oqd_compile.py new file mode 100644 index 0000000000..cbdee458be --- /dev/null +++ b/frontend/catalyst/third_party/oqd/oqd_compile.py @@ -0,0 +1,191 @@ +# Copyright 2026 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. + +""" +OQD Compiler utilities for compiling and linking LLVM IR to ARTIQ's binary. +""" + + +# This module provides utilities to compile and link Catalyst-generated LLVM IR to ARTIQ's binary. +# This keeps OQD-specific logic out of Catalyst core. + +import os +import subprocess +from pathlib import Path +from typing import Optional + + +def compile_to_artiq(circuit, artiq_config, output_elf_name=None, verbose=True): + """Compile a qjit-compiled circuit to ARTIQ's binary. + + This function takes a circuit compiled with target="llvmir", writes the LLVM IR + to a file, and links it to an ARTIQ'se binary. + + Args: + circuit: A QJIT-compiled function (must be compiled with target="llvmir") + artiq_config: Dictionary containing ARTIQ configuration: + - kernel_ld: Path to ARTIQ kernel linker script + - llc_path: (optional) Path to llc compiler + - lld_path: (optional) Path to ld.lld linker + output_elf_name: Name of the output ELF file (default: None, uses circuit function name) + verbose: Whether to print verbose output (default: True) + + Returns: + str: Path to the generated binary file + """ + # Get LLVM IR text and write to file + llvm_ir_text = circuit.qir + circuit_name = getattr(circuit, "__name__", "circuit") + llvm_ir_path = os.path.join(str(circuit.workspace), f"{circuit_name}.ll") + with open(llvm_ir_path, "w", encoding="utf-8") as f: + f.write(llvm_ir_text) + print(f"LLVM IR file written to: {llvm_ir_path}") + + # Link to ARTIQ's binary + if output_elf_name is None: + output_elf_name = f"{circuit_name}.elf" + + # Output ELF file to current working directory if workspace is in /private (temp dir), + # otherwise use workspace directory + workspace_str = str(circuit.workspace) + if "/private" in workspace_str: + output_elf_path = os.path.join(os.getcwd(), output_elf_name) + else: + output_elf_path = os.path.join(workspace_str, output_elf_name) + + link_to_artiq_elf( + llvm_ir_path=llvm_ir_path, + output_elf_path=output_elf_path, + kernel_ld=artiq_config["kernel_ld"], + llc_path=artiq_config.get("llc_path"), + lld_path=artiq_config.get("lld_path"), + verbose=verbose, + ) + + return output_elf_path + + +def link_to_artiq_elf( + llvm_ir_path: str, + output_elf_path: str, + kernel_ld: str, + llc_path: Optional[str] = None, + lld_path: Optional[str] = None, + verbose: bool = False, +) -> str: + """Link LLVM IR to ARTIQ ELF format. + + Args: + llvm_ir_path: Path to the LLVM IR file (.ll) + output_elf_path: Path to output ELF file + kernel_ld: Path to ARTIQ's kernel.ld linker script + llc_path: Path to llc (LLVM compiler). If None, uses "llc" from PATH + lld_path: Path to ld.lld (LLVM linker). If None, uses "ld.lld" from PATH + verbose: If True, print compilation commands + + Returns: + Path to the generated ELF file + """ + llvm_ir_path = Path(llvm_ir_path) + output_elf_path = Path(output_elf_path) + kernel_ld = Path(kernel_ld) + + if not llvm_ir_path.exists(): + raise FileNotFoundError(f"LLVM IR file not found: {llvm_ir_path}") + + if not kernel_ld.exists(): + raise FileNotFoundError(f"ARTIQ kernel.ld not found: {kernel_ld}") + + # Use default paths if not provided + llc_cmd = llc_path if llc_path else "llc" + lld_cmd = lld_path if lld_path else "ld.lld" + + # Check if tools exist + if llc_path and not Path(llc_path).exists(): + raise FileNotFoundError(f"llc not found: {llc_path}") + if lld_path and not Path(lld_path).exists(): + raise FileNotFoundError(f"ld.lld not found: {lld_path}") + + # Compile LLVM IR to object file with llc + object_file = output_elf_path.with_suffix(".o") + llc_args = [ + llc_cmd, + "-mtriple=armv7-unknown-linux-gnueabihf", + "-mcpu=cortex-a9", + "-filetype=obj", + "-relocation-model=pic", + "-o", + str(object_file), + str(llvm_ir_path), + ] + + if verbose: + print(f"[ARTIQ] Compiling with external LLC: {' '.join(llc_args)}") + + try: + result = subprocess.run(llc_args, check=True, capture_output=True, text=True) + if verbose and result.stderr: + print(f"[ARTIQ] LLC stderr: {result.stderr}") + except subprocess.CalledProcessError as e: + error_msg = f"External LLC failed with exit code: {e.returncode}" + if e.stderr: + error_msg += f"\n{e.stderr}" + raise RuntimeError(error_msg) from e + except FileNotFoundError as exc: + raise FileNotFoundError( + "llc not found. Please install LLVM or provide path via llc_path argument." + ) from exc + + if not object_file.exists(): + raise RuntimeError(f"Object file was not created: {object_file}") + + # Link object file to ELF with ld.lld + lld_args = [ + lld_cmd, + "-shared", + "--eh-frame-hdr", + "-m", + "armelf_linux_eabi", + "--target2=rel", + "-T", + str(kernel_ld), + str(object_file), + "-o", + str(output_elf_path), + ] + + if verbose: + print(f"[ARTIQ] Linking ELF: {' '.join(lld_args)}") + + try: + result = subprocess.run(lld_args, check=True, capture_output=True, text=True) + if verbose and result.stderr: + print(f"[ARTIQ] LLD stderr: {result.stderr}") + except subprocess.CalledProcessError as e: + error_msg = f"LLD linking failed with exit code: {e.returncode}" + if e.stderr: + error_msg += f"\n{e.stderr}" + raise RuntimeError(error_msg) from e + except FileNotFoundError as exc: + raise FileNotFoundError( + "ld.lld not found. Please install LLVM LLD or provide path via lld_path argument." + ) from exc + + if not output_elf_path.exists(): + raise RuntimeError(f"ELF file was not created: {output_elf_path}") + + if verbose: + print(f"[ARTIQ] Generated ELF: {output_elf_path}") + + return str(output_elf_path) diff --git a/mlir/include/Driver/CompilerDriver.h b/mlir/include/Driver/CompilerDriver.h index e31cb5dfb9..8188a14c00 100644 --- a/mlir/include/Driver/CompilerDriver.h +++ b/mlir/include/Driver/CompilerDriver.h @@ -80,12 +80,6 @@ struct CompilerOptions { /// If true, the compiler will dump the pass pipeline that will be run. bool dumpPassPipeline; - /// ARTIQ cross-compilation settings - bool artiqEnabled = false; - std::string artiqKernelLd; // Path to ARTIQ kernel.ld linker script - std::string artiqLlcPath; // Path to llc - std::string artiqLldPath; // Path to ld.lld - /// Get the destination of the object file at the end of compilation. std::string getObjectFile() const { @@ -93,13 +87,6 @@ struct CompilerOptions { return path(workspace.str()) / path(moduleName.str() + ".o"); } - /// Get the destination of the ELF file for ARTIQ. - std::string getElfFile() const - { - using path = std::filesystem::path; - return path(workspace.str()) / path(moduleName.str() + ".elf"); - } - /// Get the destination of the LLVM IR file. std::string getLLFile() const { diff --git a/mlir/lib/Driver/CompilerDriver.cpp b/mlir/lib/Driver/CompilerDriver.cpp index 967c8f289f..9364887bd6 100644 --- a/mlir/lib/Driver/CompilerDriver.cpp +++ b/mlir/lib/Driver/CompilerDriver.cpp @@ -816,105 +816,6 @@ LogicalResult QuantumDriverMain(const CompilerOptions &options, CompilerOutput & llvm::InitializeAllAsmParsers(); llvm::InitializeAllAsmPrinters(); - // Use external LLC with ARM support (catalyst's LLVM doesn't have corresponding ARM backend - // for now) - if (options.artiqEnabled) { - // Save LLVM IR to file for external LLC - std::string llFile = options.getLLFile(); - std::error_code errCode; - llvm::raw_fd_ostream llOut(llFile, errCode, llvm::sys::fs::OF_None); - if (errCode) { - CO_MSG(options, Verbosity::Urgent, - "Failed to open " << llFile << ": " << errCode.message() << "\n"); - return failure(); - } - llvmModule->print(llOut, nullptr); - llOut.close(); - - // Get LLC path - std::string llcPath = options.artiqLlcPath.empty() ? "llc" : options.artiqLlcPath; - std::string llcCmd = llcPath + - " -mtriple=armv7-unknown-linux-gnueabihf -mcpu=cortex-a9" - " -filetype=obj -relocation-model=pic -o " + - options.getObjectFile() + " " + llFile + " 2>&1"; - - CO_MSG(options, Verbosity::All, - "[ARTIQ] Compiling with external LLC: " << llcCmd << "\n"); - - std::string objectFile = options.getObjectFile(); - llvm::SmallVector llcArgs; - llcArgs.push_back(llcPath); - llcArgs.push_back("-mtriple=armv7-unknown-linux-gnueabihf"); - llcArgs.push_back("-mcpu=cortex-a9"); - llcArgs.push_back("-filetype=obj"); - llcArgs.push_back("-relocation-model=pic"); - llcArgs.push_back("-o"); - llcArgs.push_back(objectFile); - llcArgs.push_back(llFile); - - std::string llcErrMsg; - int llcResult = - llvm::sys::ExecuteAndWait(llcPath, llcArgs, std::nullopt, {}, 0, 0, &llcErrMsg); - if (llcResult != 0) { - CO_MSG(options, Verbosity::Urgent, - "External LLC failed with exit code: " << llcResult); - if (!llcErrMsg.empty()) { - CO_MSG(options, Verbosity::Urgent, ": " << llcErrMsg); - } - CO_MSG(options, Verbosity::Urgent, "\n"); - return failure(); - } - - // Link to ELF - std::string lldPath = options.artiqLldPath.empty() ? "ld.lld" : options.artiqLldPath; - if (options.artiqKernelLd.empty()) { - CO_MSG(options, Verbosity::Urgent, "ARTIQ kernel.ld path not specified\n"); - return failure(); - } - - std::string lldCmd = lldPath + - " -shared --eh-frame-hdr -m armelf_linux_eabi " - "--target2=rel -T " + - options.artiqKernelLd + " " + options.getObjectFile() + " -o " + - options.getElfFile() + " 2>&1"; - - CO_MSG(options, Verbosity::All, "[ARTIQ] Linking ELF: " << lldCmd << "\n"); - - std::string kernelLd = options.artiqKernelLd; - std::string objectFileLd = options.getObjectFile(); - std::string elfFile = options.getElfFile(); - llvm::SmallVector lldArgs; - lldArgs.push_back(lldPath); - lldArgs.push_back("-shared"); - lldArgs.push_back("--eh-frame-hdr"); - lldArgs.push_back("-m"); - lldArgs.push_back("armelf_linux_eabi"); - lldArgs.push_back("--target2=rel"); - lldArgs.push_back("-T"); - lldArgs.push_back(kernelLd); - lldArgs.push_back(objectFileLd); - lldArgs.push_back("-o"); - lldArgs.push_back(elfFile); - - std::string lldErrMsg; - int lldResult = - llvm::sys::ExecuteAndWait(lldPath, lldArgs, std::nullopt, {}, 0, 0, &lldErrMsg); - if (lldResult != 0) { - CO_MSG(options, Verbosity::Urgent, - "LLD linking failed with exit code: " << lldResult); - if (!lldErrMsg.empty()) { - CO_MSG(options, Verbosity::Urgent, ": " << lldErrMsg); - } - CO_MSG(options, Verbosity::Urgent, "\n"); - return failure(); - } - - CO_MSG(options, Verbosity::All, - "[ARTIQ] Generated ELF: " << options.getElfFile() << "\n"); - llcTiming.stop(); - return success(); - } - llvm::Triple targetTriple(llvm::sys::getDefaultTargetTriple()); const char *cpu = "generic"; const char *features = ""; @@ -1115,17 +1016,6 @@ int QuantumDriverMainFromCL(int argc, char **argv) cl::desc("Print the whole module in intermediate files"), cl::init(true), cl::cat(CatalystCat)); - // ARTIQ cross-compilation options - cl::opt ArtiqEnabled("artiq", cl::desc("Enable ARTIQ cross-compilation to ARM ELF"), - cl::init(false), cl::cat(CatalystCat)); - cl::opt ArtiqKernelLd("artiq-kernel-ld", - cl::desc("Path to ARTIQ kernel.ld linker script"), - cl::init(""), cl::cat(CatalystCat)); - cl::opt ArtiqLlcPath("artiq-llc-path", cl::desc("Path to llc for ARTIQ"), - cl::init(""), cl::cat(CatalystCat)); - cl::opt ArtiqLldPath("artiq-lld-path", cl::desc("Path to ld.lld for ARTIQ"), - cl::init(""), cl::cat(CatalystCat)); - // Create dialect registry DialectRegistry registry; mlir::registerAllPasses(); @@ -1177,11 +1067,7 @@ int QuantumDriverMainFromCL(int argc, char **argv) .pipelinesCfg = parsePipelines(CatalystPipeline), .checkpointStage = CheckpointStage, .loweringAction = LoweringAction, - .dumpPassPipeline = DumpPassPipeline, - .artiqEnabled = ArtiqEnabled, - .artiqKernelLd = ArtiqKernelLd, - .artiqLlcPath = ArtiqLlcPath, - .artiqLldPath = ArtiqLldPath}; + .dumpPassPipeline = DumpPassPipeline}; mlir::LogicalResult result = QuantumDriverMain(options, *output, registry); From 12f03c5c65d73308f93599138f70fcf7ccb97d71 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 10:51:23 -0500 Subject: [PATCH 12/23] udpate --- frontend/catalyst/third_party/oqd/oqd_compile.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/catalyst/third_party/oqd/oqd_compile.py b/frontend/catalyst/third_party/oqd/oqd_compile.py index cbdee458be..22fc768371 100644 --- a/frontend/catalyst/third_party/oqd/oqd_compile.py +++ b/frontend/catalyst/third_party/oqd/oqd_compile.py @@ -16,10 +16,6 @@ OQD Compiler utilities for compiling and linking LLVM IR to ARTIQ's binary. """ - -# This module provides utilities to compile and link Catalyst-generated LLVM IR to ARTIQ's binary. -# This keeps OQD-specific logic out of Catalyst core. - import os import subprocess from pathlib import Path From 345ac019e5a859aa3ad45938caeec009354feadb Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 11:01:24 -0500 Subject: [PATCH 13/23] revert --- mlir/lib/Driver/CompilerDriver.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/mlir/lib/Driver/CompilerDriver.cpp b/mlir/lib/Driver/CompilerDriver.cpp index 9364887bd6..befb609c1a 100644 --- a/mlir/lib/Driver/CompilerDriver.cpp +++ b/mlir/lib/Driver/CompilerDriver.cpp @@ -13,7 +13,6 @@ // limitations under the License. #include -#include #include #include @@ -33,7 +32,6 @@ #include "llvm/Passes/PassBuilder.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/InitLLVM.h" -#include "llvm/Support/Program.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/ToolOutputFile.h" @@ -809,6 +807,8 @@ LogicalResult QuantumDriverMain(const CompilerOptions &options, CompilerOutput & if (runLLC && (inType == InputType::LLVMIR)) { TimingScope llcTiming = timing.nest("llc"); + // Set data layout before LLVM passes or the default one is used. + llvm::Triple targetTriple(llvm::sys::getDefaultTargetTriple()); llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargets(); @@ -816,18 +816,13 @@ LogicalResult QuantumDriverMain(const CompilerOptions &options, CompilerOutput & llvm::InitializeAllAsmParsers(); llvm::InitializeAllAsmPrinters(); - llvm::Triple targetTriple(llvm::sys::getDefaultTargetTriple()); + std::string err; + auto target = llvm::TargetRegistry::lookupTarget(targetTriple, err); + llvm::TargetOptions opt; + const char *cpu = "generic"; const char *features = ""; - std::string err; - auto target = llvm::TargetRegistry::lookupTarget(targetTriple.str(), err); - if (!target) { - CO_MSG(options, Verbosity::Urgent, "Failed to lookup target: " << err << "\n"); - return failure(); - } - - llvm::TargetOptions opt; auto targetMachine = target->createTargetMachine(targetTriple, cpu, features, opt, llvm::Reloc::Model::PIC_); targetMachine->setOptLevel(llvm::CodeGenOptLevel::None); From 8bae023af7ba3f772776b650297512df44f654b2 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 11:02:24 -0500 Subject: [PATCH 14/23] update --- frontend/catalyst/jit.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index a2ff6ec465..4cb31d4fd2 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -16,8 +16,6 @@ compilation of hybrid quantum-classical functions using Catalyst. """ -# pylint: disable=too-many-lines - import copy import functools import inspect @@ -532,7 +530,6 @@ def __init__(self, fn, compile_options): functools.update_wrapper(self, fn) self.original_function = fn self.compile_options = compile_options - self.compiler = Compiler(compile_options) self.fn_cache = CompilationCache( compile_options.static_argnums, compile_options.abstracted_axes From a579d2321d142047ec7e6598351eaea4acc7576c Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 11:03:55 -0500 Subject: [PATCH 15/23] revertT --- mlir/include/Driver/CompilerDriver.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/mlir/include/Driver/CompilerDriver.h b/mlir/include/Driver/CompilerDriver.h index 8188a14c00..ff8743764d 100644 --- a/mlir/include/Driver/CompilerDriver.h +++ b/mlir/include/Driver/CompilerDriver.h @@ -86,13 +86,6 @@ struct CompilerOptions { using path = std::filesystem::path; return path(workspace.str()) / path(moduleName.str() + ".o"); } - - /// Get the destination of the LLVM IR file. - std::string getLLFile() const - { - using path = std::filesystem::path; - return path(workspace.str()) / path(moduleName.str() + ".ll"); - } }; struct CompilerOutput { From 92b03af55506ce65eb7628433641dc3e9893ddf8 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 11:06:17 -0500 Subject: [PATCH 16/23] codecov --- .../catalyst/third_party/oqd/oqd_compile.py | 119 ++++++++++++------ 1 file changed, 80 insertions(+), 39 deletions(-) diff --git a/frontend/catalyst/third_party/oqd/oqd_compile.py b/frontend/catalyst/third_party/oqd/oqd_compile.py index 22fc768371..552d99f8b1 100644 --- a/frontend/catalyst/third_party/oqd/oqd_compile.py +++ b/frontend/catalyst/third_party/oqd/oqd_compile.py @@ -72,49 +72,39 @@ def compile_to_artiq(circuit, artiq_config, output_elf_name=None, verbose=True): return output_elf_path -def link_to_artiq_elf( - llvm_ir_path: str, - output_elf_path: str, - kernel_ld: str, - llc_path: Optional[str] = None, - lld_path: Optional[str] = None, - verbose: bool = False, -) -> str: - """Link LLVM IR to ARTIQ ELF format. - - Args: - llvm_ir_path: Path to the LLVM IR file (.ll) - output_elf_path: Path to output ELF file - kernel_ld: Path to ARTIQ's kernel.ld linker script - llc_path: Path to llc (LLVM compiler). If None, uses "llc" from PATH - lld_path: Path to ld.lld (LLVM linker). If None, uses "ld.lld" from PATH - verbose: If True, print compilation commands - - Returns: - Path to the generated ELF file - """ - llvm_ir_path = Path(llvm_ir_path) - output_elf_path = Path(output_elf_path) - kernel_ld = Path(kernel_ld) - +def _validate_paths(llvm_ir_path: Path, kernel_ld: Path) -> None: + """Validate that required input files exist.""" if not llvm_ir_path.exists(): raise FileNotFoundError(f"LLVM IR file not found: {llvm_ir_path}") - if not kernel_ld.exists(): raise FileNotFoundError(f"ARTIQ kernel.ld not found: {kernel_ld}") - # Use default paths if not provided - llc_cmd = llc_path if llc_path else "llc" - lld_cmd = lld_path if lld_path else "ld.lld" - # Check if tools exist - if llc_path and not Path(llc_path).exists(): - raise FileNotFoundError(f"llc not found: {llc_path}") - if lld_path and not Path(lld_path).exists(): - raise FileNotFoundError(f"ld.lld not found: {lld_path}") +def _get_tool_command(tool_path: Optional[str], default_name: str) -> str: + """Get tool command path, validating if custom path is provided.""" + if tool_path is None: + return default_name + tool_path_obj = Path(tool_path) + if not tool_path_obj.exists(): + raise FileNotFoundError(f"{default_name} not found: {tool_path}") + return tool_path - # Compile LLVM IR to object file with llc - object_file = output_elf_path.with_suffix(".o") + +def _compile_llvm_to_object( + llvm_ir_path: Path, object_file: Path, llc_cmd: str, verbose: bool +) -> None: + """Compile LLVM IR to object file with llc. + + Args: + llvm_ir_path: Path to LLVM IR file + object_file: Path to object file + llc_cmd: Command to use for llc compiler + verbose: Whether to print verbose output + + Raises: + RuntimeError: If compilation fails + FileNotFoundError: If llc is not found + """ llc_args = [ llc_cmd, "-mtriple=armv7-unknown-linux-gnueabihf", @@ -146,7 +136,23 @@ def link_to_artiq_elf( if not object_file.exists(): raise RuntimeError(f"Object file was not created: {object_file}") - # Link object file to ELF with ld.lld + +def _link_object_to_elf( + object_file: Path, output_elf_path: Path, kernel_ld: Path, lld_cmd: str, verbose: bool +) -> None: + """Link object file to ELF format with ld.lld. + + Args: + object_file: Path to object file + output_elf_path: Path to output ELF file + kernel_ld: Path to kernel linker script + lld_cmd: Command to use for ld.lld linker + verbose: Whether to print verbose output + + Raises: + RuntimeError: If linking fails + FileNotFoundError: If ld.lld is not found + """ lld_args = [ lld_cmd, "-shared", @@ -181,7 +187,42 @@ def link_to_artiq_elf( if not output_elf_path.exists(): raise RuntimeError(f"ELF file was not created: {output_elf_path}") + +def link_to_artiq_elf( + llvm_ir_path: str, + output_elf_path: str, + kernel_ld: str, + llc_path: Optional[str] = None, + lld_path: Optional[str] = None, + verbose: bool = False, +) -> str: + """Link LLVM IR to ARTIQ ELF format. + + Args: + llvm_ir_path: Path to the LLVM IR file (.ll) + output_elf_path: Path to output ELF file + kernel_ld: Path to ARTIQ's kernel.ld linker script + llc_path: Path to llc (LLVM compiler). If None, uses "llc" from PATH + lld_path: Path to ld.lld (LLVM linker). If None, uses "ld.lld" from PATH + verbose: If True, print compilation commands + + Returns: + Path to the generated ELF file + """ + llvm_ir_path_obj = Path(llvm_ir_path) + output_elf_path_obj = Path(output_elf_path) + kernel_ld_obj = Path(kernel_ld) + + _validate_paths(llvm_ir_path_obj, kernel_ld_obj) + + llc_cmd = _get_tool_command(llc_path, "llc") + lld_cmd = _get_tool_command(lld_path, "ld.lld") + + object_file = output_elf_path_obj.with_suffix(".o") + _compile_llvm_to_object(llvm_ir_path_obj, object_file, llc_cmd, verbose) + _link_object_to_elf(object_file, output_elf_path_obj, kernel_ld_obj, lld_cmd, verbose) + if verbose: - print(f"[ARTIQ] Generated ELF: {output_elf_path}") + print(f"[ARTIQ] Generated ELF: {output_elf_path_obj}") - return str(output_elf_path) + return str(output_elf_path_obj) From c5dd3743200ef42bca505fbfb880ae473b7a2e17 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 11:07:15 -0500 Subject: [PATCH 17/23] pylint --- frontend/catalyst/third_party/oqd/oqd_compile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/catalyst/third_party/oqd/oqd_compile.py b/frontend/catalyst/third_party/oqd/oqd_compile.py index 552d99f8b1..ce22264321 100644 --- a/frontend/catalyst/third_party/oqd/oqd_compile.py +++ b/frontend/catalyst/third_party/oqd/oqd_compile.py @@ -188,6 +188,7 @@ def _link_object_to_elf( raise RuntimeError(f"ELF file was not created: {output_elf_path}") +# pylint: disable=too-many-arguments,too-many-positional-arguments def link_to_artiq_elf( llvm_ir_path: str, output_elf_path: str, From c8523197c0c250f55bd9fc0ce001903c235377c9 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 11:13:32 -0500 Subject: [PATCH 18/23] date --- frontend/catalyst/compiler.py | 2 +- frontend/catalyst/jit.py | 2 +- frontend/catalyst/third_party/oqd/__init__.py | 2 +- frontend/catalyst/third_party/oqd/oqd_device.py | 2 +- mlir/include/RTIO/Transforms/Passes.td | 2 +- mlir/lib/Driver/CompilerDriver.cpp | 2 +- mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp | 2 +- mlir/test/RTIO/RTIOEmitARTIQRuntime.mlir | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/catalyst/compiler.py b/frontend/catalyst/compiler.py index 02600f453a..9289f6c985 100644 --- a/frontend/catalyst/compiler.py +++ b/frontend/catalyst/compiler.py @@ -1,4 +1,4 @@ -# Copyright 2022-2023 Xanadu Quantum Technologies Inc. +# Copyright 2022-2026 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. diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 4cb31d4fd2..438db44c2c 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Xanadu Quantum Technologies Inc. +# Copyright 2022-2026 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. diff --git a/frontend/catalyst/third_party/oqd/__init__.py b/frontend/catalyst/third_party/oqd/__init__.py index d7174457e4..c509cb991a 100644 --- a/frontend/catalyst/third_party/oqd/__init__.py +++ b/frontend/catalyst/third_party/oqd/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2024 Xanadu Quantum Technologies Inc. +# Copyright 2024-2026 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. diff --git a/frontend/catalyst/third_party/oqd/oqd_device.py b/frontend/catalyst/third_party/oqd/oqd_device.py index 2817238afa..544bda3734 100644 --- a/frontend/catalyst/third_party/oqd/oqd_device.py +++ b/frontend/catalyst/third_party/oqd/oqd_device.py @@ -1,4 +1,4 @@ -# Copyright 2024 Xanadu Quantum Technologies Inc. +# Copyright 2024-2026 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. diff --git a/mlir/include/RTIO/Transforms/Passes.td b/mlir/include/RTIO/Transforms/Passes.td index 0fed01f7d4..6597caf7a9 100644 --- a/mlir/include/RTIO/Transforms/Passes.td +++ b/mlir/include/RTIO/Transforms/Passes.td @@ -1,4 +1,4 @@ -// Copyright 2025 Xanadu Quantum Technologies Inc. +// Copyright 2025-2026 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. diff --git a/mlir/lib/Driver/CompilerDriver.cpp b/mlir/lib/Driver/CompilerDriver.cpp index befb609c1a..040c49db3e 100644 --- a/mlir/lib/Driver/CompilerDriver.cpp +++ b/mlir/lib/Driver/CompilerDriver.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Xanadu Quantum Technologies Inc. +// Copyright 2023-2026 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. diff --git a/mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp b/mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp index 0c4f8f614d..35205eb6c8 100644 --- a/mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp +++ b/mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp @@ -1,4 +1,4 @@ -// Copyright 2025 Xanadu Quantum Technologies Inc. +// Copyright 2026 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. diff --git a/mlir/test/RTIO/RTIOEmitARTIQRuntime.mlir b/mlir/test/RTIO/RTIOEmitARTIQRuntime.mlir index 7b11472f81..7d2e8dcca7 100644 --- a/mlir/test/RTIO/RTIOEmitARTIQRuntime.mlir +++ b/mlir/test/RTIO/RTIOEmitARTIQRuntime.mlir @@ -1,4 +1,4 @@ -// Copyright 2025 Xanadu Quantum Technologies Inc. +// Copyright 2026 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. From 9e99d37ffb9850f5debefd8c94280ffd726fd8a0 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 11:29:20 -0500 Subject: [PATCH 19/23] revert redundant change --- mlir/lib/Driver/CompilerDriver.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mlir/lib/Driver/CompilerDriver.cpp b/mlir/lib/Driver/CompilerDriver.cpp index 040c49db3e..9c264fbe67 100644 --- a/mlir/lib/Driver/CompilerDriver.cpp +++ b/mlir/lib/Driver/CompilerDriver.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2026 Xanadu Quantum Technologies Inc. +// Copyright 2023 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. @@ -819,10 +819,8 @@ LogicalResult QuantumDriverMain(const CompilerOptions &options, CompilerOutput & std::string err; auto target = llvm::TargetRegistry::lookupTarget(targetTriple, err); llvm::TargetOptions opt; - const char *cpu = "generic"; const char *features = ""; - auto targetMachine = target->createTargetMachine(targetTriple, cpu, features, opt, llvm::Reloc::Model::PIC_); targetMachine->setOptLevel(llvm::CodeGenOptLevel::None); From 38bee24d80a26974e2e0f626b18ec4b39dd1d17b Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 15:15:40 -0500 Subject: [PATCH 20/23] Update mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp Co-authored-by: Paul <79805239+paul0403@users.noreply.github.com> --- mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp b/mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp index 35205eb6c8..076b68f006 100644 --- a/mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp +++ b/mlir/lib/RTIO/Transforms/RTIOEmitARTIQRuntime.cpp @@ -67,7 +67,7 @@ struct RTIOEmitARTIQRuntimePass return; } - // Find the kernel function e(could be LLVM func or func.func) + // Find the kernel function (could be LLVM func or func.func) LLVM::LLVMFuncOp llvmKernelFunc = module.lookupSymbol(ARTIQFuncNames::kernel); From aba4efceaffbc9ec75169500d99ab535dbcf0b42 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 17:13:18 -0500 Subject: [PATCH 21/23] Update jit.py Co-authored-by: Mehrdad Malek <39844030+mehrdad2m@users.noreply.github.com> --- frontend/catalyst/jit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 438db44c2c..e1c84a9fe9 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -626,7 +626,7 @@ def aot_compile(self): self.workspace = self._get_workspace() # TODO: awkward, refactor or redesign the target feature - if self.compile_options.target in ("jaxpr", "mlir", "binary", "llvmir"): + if self.compile_options.target in ("jaxpr", "mlir", "llvmir", "binary"): self.jaxpr, self.out_type, self.out_treedef, self.c_sig = self.capture( self.user_sig or () ) From fc4b5eeed6bbc41f7df9d3e53d0d21a2d85a3089 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 17:13:24 -0500 Subject: [PATCH 22/23] Update jit.py Co-authored-by: Mehrdad Malek <39844030+mehrdad2m@users.noreply.github.com> --- frontend/catalyst/jit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index e1c84a9fe9..823047d6ad 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -546,7 +546,6 @@ def __init__(self, fn, compile_options): self.mlir_module = None self.out_type = None self.overwrite_ir = None - # Use cwd for workspace if keep_intermediate is set self.use_cwd_for_workspace = self.compile_options.keep_intermediate self.user_sig = get_type_annotations(fn) From abbeb60b7d9d3a1b32876298067025a7358721e4 Mon Sep 17 00:00:00 2001 From: Hong-Sheng Zheng Date: Fri, 16 Jan 2026 17:13:33 -0500 Subject: [PATCH 23/23] Update oqd_compile.py Co-authored-by: Mehrdad Malek <39844030+mehrdad2m@users.noreply.github.com> --- frontend/catalyst/third_party/oqd/oqd_compile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/catalyst/third_party/oqd/oqd_compile.py b/frontend/catalyst/third_party/oqd/oqd_compile.py index ce22264321..9d1dc3a861 100644 --- a/frontend/catalyst/third_party/oqd/oqd_compile.py +++ b/frontend/catalyst/third_party/oqd/oqd_compile.py @@ -26,7 +26,7 @@ def compile_to_artiq(circuit, artiq_config, output_elf_name=None, verbose=True): """Compile a qjit-compiled circuit to ARTIQ's binary. This function takes a circuit compiled with target="llvmir", writes the LLVM IR - to a file, and links it to an ARTIQ'se binary. + to a file, and links it to an ARTIQ's binary. Args: circuit: A QJIT-compiled function (must be compiled with target="llvmir")