diff --git a/hatch_build.py b/hatch_build.py index 9a0043db..ab4c968c 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -13,10 +13,6 @@ def __init__(self, hook: "BundleBuildHook") -> None: self.hook = hook self.metadata = self._get_metadata() - def run(self): - self.build_all() - self.extract_libs() - def _get_metadata(self): p = subprocess.Popen( [ @@ -37,34 +33,41 @@ def _get_metadata(self): sys.exit(1) return json.loads(stdout) - def build_all(self): - self.hook.app.display_mini_header("Building cargo workspace") + def _run_build(self, args: list[str], env: dict[str, str] | None = None): p = subprocess.Popen( - [ - "cargo", - "build", - "--workspace", - "--release", - "--locked", - ], + args, cwd=self.hook.root, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + env=env, ) + assert p.stdout is not None for line in p.stdout: - line = line.decode("utf-8").strip() - if "Finished" in line: - self.hook.app.display_info(line) - elif "error" in line: - self.hook.app.display_error(line) + line_str = line.decode("utf-8").strip() + if "finished" in line_str.lower(): + self.hook.app.display_info(line_str) + elif "error" in line_str.lower(): + self.hook.app.display_error(line_str) else: - self.hook.app.display_info(line) + self.hook.app.display_info(line_str) p.wait() if p.returncode != 0: self.hook.app.display_error( f"Cargo build failed with return code {p.returncode}" ) sys.exit(1) + + def build_all(self): + self.hook.app.display_mini_header("Building cargo workspace") + self._run_build( + [ + "cargo", + "build", + "--workspace", + "--release", + "--locked", + ] + ) self.hook.app.display_success("Cargo build completed successfully") def extract_libs(self): @@ -113,6 +116,146 @@ def extract_libs(self): shutil.copy(lib_path, destination) +class CargoAddonBuild: + def __init__(self, hook: "BundleBuildHook") -> None: + self.hook = hook + self.manifests = self._discover_manifests() + + def _discover_manifests(self) -> list[Path]: + manifests = [] + for manifest in Path(self.hook.root).glob("selene-ext/*/*/Cargo.toml"): + metadata = self._get_metadata(manifest) + package = metadata["packages"][0] + selene = (package.get("metadata") or {}).get("selene", {}) + if selene.get("build_after_helios_interface"): + manifests.append(manifest) + return manifests + + def _get_release_dir(self, manifest: Path) -> Path: + target = os.environ.get("CARGO_BUILD_TARGET") + target_dir = manifest.parent / "target" + if target: + return target_dir / target / "release" + return target_dir / "release" + + def _get_metadata(self, manifest: Path) -> dict: + p = subprocess.Popen( + [ + "cargo", + "metadata", + "--no-deps", + "--manifest-path", + str(manifest), + ], + cwd=self.hook.root, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + stdout, stderr = p.communicate() + if p.returncode != 0: + self.hook.app.display_error( + f"Cargo metadata command failed with return code {p.returncode}" + ) + self.hook.app.display_error(stderr.decode("utf-8")) + sys.exit(1) + return json.loads(stdout) + + def build_all(self): + if not self.manifests: + return + env = os.environ.copy() + env["SELENE_HELIOS_QIS_LIB_DIR"] = str( + Path(self.hook.root) + / "selene-ext/interfaces/helios_qis/python/selene_helios_qis_plugin/_dist/lib" + ) + for manifest in self.manifests: + self.hook.app.display_mini_header( + f"Building cargo addon {manifest.parent.name}" + ) + p = subprocess.Popen( + [ + "cargo", + "build", + "--manifest-path", + str(manifest), + "--release", + "--locked", + ], + cwd=self.hook.root, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env, + ) + assert p.stdout is not None + for line in p.stdout: + line_str = line.decode("utf-8").strip() + if "finished" in line_str.lower(): + self.hook.app.display_info(line_str) + elif "error" in line_str.lower(): + self.hook.app.display_error(line_str) + else: + self.hook.app.display_info(line_str) + p.wait() + if p.returncode != 0: + self.hook.app.display_error( + f"Cargo addon build failed with return code {p.returncode}" + ) + sys.exit(1) + self.hook.app.display_success( + f"Cargo addon build completed successfully: {manifest.parent.name}" + ) + + def extract_libs(self): + for manifest in self.manifests: + metadata = self._get_metadata(manifest) + release_dir = self._get_release_dir(manifest) + assert release_dir.exists() + for package in metadata["packages"]: + for target in package["targets"]: + if "cdylib" not in target["kind"]: + continue + self.hook.app.display_info( + f"Found addon cdylib target: {target['name']}" + ) + lib_name = target["name"] + lib_filenames = { + "darwin": [f"lib{lib_name}.dylib"], + "linux": [f"lib{lib_name}.so"], + "win32": [f"{lib_name}.dll", f"lib{lib_name}.dll.a"], + }[sys.platform] + assert all( + (release_dir / file).exists() for file in lib_filenames + ), ( + f"Compiled library for {lib_name} not found in {release_dir}. " + f"Expected to find {','.join(lib_filenames)}." + ) + + cargo_toml_path = Path(package["manifest_path"]) + python_path = cargo_toml_path.parent / "python" + subdirs = filter(lambda x: x.is_dir(), python_path.iterdir()) + subdirs = filter( + lambda x: x.name != "test" and x.name != "tests", subdirs + ) + subdirs = filter(lambda x: "pycache" not in x.name, subdirs) + subdirs = filter(lambda x: not x.name.endswith("egg-info"), subdirs) + subdirs = filter(lambda x: not x.name.startswith("."), subdirs) + subdirs_list = list(subdirs) + assert len(subdirs_list) == 1, ( + f"Multiple python directories found in {python_path} - " + " hatch_build.py can't tell where the build artifacts should go." + ) + subdir = subdirs_list[0] + destination = subdir / "_dist/lib" + destination.mkdir(parents=True, exist_ok=True) + for filename in lib_filenames: + lib_path = release_dir / filename + if lib_path.exists(): + self.hook.app.display_info( + f"Copying {lib_path} to {destination}" + ) + shutil.copy(lib_path, destination) + + class BundleBuildHook(BuildHookInterface): def get_cargo_release_dir(self) -> Path: target = os.environ.get("CARGO_BUILD_TARGET") @@ -124,9 +267,13 @@ def get_cargo_release_dir(self) -> Path: def initialize(self, version: str, build_data: dict) -> None: cargo_runner = CargoWorkspaceBuild(self) - cargo_runner.run() + cargo_runner.build_all() + cargo_runner.extract_libs() self.build_selene_c_interface() self.build_helios_qis() + addon_runner = CargoAddonBuild(self) + addon_runner.build_all() + addon_runner.extract_libs() packages = [Path("selene-sim/python/selene_sim")] for topic_dir in Path("selene-ext").iterdir(): diff --git a/pyproject.toml b/pyproject.toml index 402831bb..154d123c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,6 +85,7 @@ packages = [ "selene-ext/simulators/quantum-replay/python/selene_quantum_replay_plugin", "selene-ext/simulators/quest/python/selene_quest_plugin", "selene-ext/simulators/stim/python/selene_stim_plugin", + "selene-ext/utilities/argreader/python/selene_argreader_plugin", ] [tool.uv] @@ -142,6 +143,8 @@ known_first_party = [ "selene_quantum_replay_plugin", "selene_quest_plugin", "selene_stim_plugin", + # Utilities + "selene_argreader_plugin", ] extend_exclude = [ "target", diff --git a/selene-core/python/selene_core/build_utils/builtins/__init__.py b/selene-core/python/selene_core/build_utils/builtins/__init__.py index f4902a23..dc32e6c7 100644 --- a/selene-core/python/selene_core/build_utils/builtins/__init__.py +++ b/selene-core/python/selene_core/build_utils/builtins/__init__.py @@ -11,7 +11,7 @@ from .selene import ( SeleneExecutableKind, SeleneObjectFileKind, - SeleneObjectToSeleneExecutable, + SeleneObjectToSeleneExecutableStep, register_selene_builtins, ) @@ -25,9 +25,7 @@ LLVMIRStringToLLVMIRFileStep, HeliosLLVMIRFileToHeliosObjectFileStep, HeliosLLVMBitcodeFileToHeliosObjectFileStep, - HeliosObjectFileToSeleneObjectFileStep_Linux, - HeliosObjectFileToSeleneExecutableStep_Windows, - HeliosObjectFileToSeleneExecutableStep_Darwin, + HeliosObjectFileToSeleneExecutableStep, register_helios_builtins, ) @@ -112,7 +110,6 @@ def register_builtins(planner: BuildPlanner): __all__ = [ "SeleneExecutableKind", "SeleneObjectFileKind", - "SeleneObjectToSeleneExecutable", "HeliosLLVMIRStringKind", "HeliosLLVMIRFileKind", "HeliosLLVMBitcodeStringKind", @@ -122,8 +119,7 @@ def register_builtins(planner: BuildPlanner): "LLVMIRStringToLLVMIRFileStep", "HeliosLLVMIRFileToHeliosObjectFileStep", "HeliosLLVMBitcodeFileToHeliosObjectFileStep", - "HeliosObjectFileToSeleneObjectFileStep_Linux", - "HeliosObjectFileToSeleneExecutableStep_Windows", - "HeliosObjectFileToSeleneExecutableStep_Darwin", + "HeliosObjectFileToSeleneExecutableStep", + "SeleneObjectToSeleneExecutableStep", "register_builtins", ] + additional_exports diff --git a/selene-core/python/selene_core/build_utils/builtins/helios.py b/selene-core/python/selene_core/build_utils/builtins/helios.py index fdf5c6ae..f3c71ce6 100644 --- a/selene-core/python/selene_core/build_utils/builtins/helios.py +++ b/selene-core/python/selene_core/build_utils/builtins/helios.py @@ -11,7 +11,7 @@ from ..symbols import get_symbols_from_object, get_symbols_from_llvm, SymbolTable from ..planner import BuildPlanner -from .selene import SeleneObjectFileKind, SeleneExecutableKind +from .selene import SeleneExecutableKind def _match_helios_qis(symbol_table: SymbolTable) -> bool: @@ -274,49 +274,9 @@ def apply(cls, build_ctx: BuildCtx, input_artifact: Artifact) -> Artifact: return cls._make_artifact(content) -class HeliosObjectFileToSeleneObjectFileStep_Linux(Step): +class HeliosObjectFileToSeleneExecutableStep(Step): """ - Link helios object with interface + utility shared libs to rebind - to the selene interface and fill in any missing libraries. - """ - - input_kind = HeliosObjectFileKind - output_kind = SeleneObjectFileKind - - @classmethod - def get_cost(cls, build_ctx: BuildCtx) -> float: - """ - Rule out this step for non-linux platforms. - """ - if platform.system() == "Linux": - return 100 - else: - return float("inf") - - @classmethod - def apply(cls, build_ctx: BuildCtx, input_artifact: Artifact) -> Artifact: - out_path = build_ctx.artifact_dir / "program.selene.o" - lib_paths = [d.path for d in build_ctx.deps] - if build_ctx.verbose: - print("Linking helios object file with dependencies") - zig_cache_dir = build_ctx.artifact_dir / "zig-cache" - zig_cache_dir.mkdir(exist_ok=True) - invoke_zig( - "cc", - "-r", - input_artifact.resource, - *lib_paths, - "-o", - out_path, - verbose=build_ctx.verbose, - cache_dir=zig_cache_dir, - ) - return cls._make_artifact(out_path) - - -class HeliosObjectFileToSeleneExecutableStep_Windows(Step): - """ - Link helios object with the interface shim, utilities, and selene core library to create the final executable. + Link helios object with the shared Helios runtime and any shared utilities. """ input_kind = HeliosObjectFileKind @@ -324,78 +284,61 @@ class HeliosObjectFileToSeleneExecutableStep_Windows(Step): @classmethod def get_cost(cls, build_ctx: BuildCtx) -> float: - """ - Rule out this step for non-windows platforms. - """ - if platform.system() == "Windows": - return 100 - else: - return float("inf") + return 90 @classmethod def apply(cls, build_ctx: BuildCtx, input_artifact: Artifact) -> Artifact: - out_path = build_ctx.artifact_dir / "program.selene.exe" - if build_ctx.verbose: - print("Linking helios object file with dependencies") - - try: - from selene_sim import dist_dir as selene_dist - except ImportError: - raise ImportError( - "Selene simulation library not found. Please install selene_sim." - ) - - selene_lib_dir = selene_dist / "lib" - selene_lib = selene_lib_dir / "libselene.dll.a" - link_flags = ["-lc"] - libraries = [selene_lib] - library_search_dirs = [selene_lib_dir] - for dep in build_ctx.deps: - link_flags.extend(dep.link_flags) - library_search_dirs.extend(dep.library_search_dirs) - libraries.append(dep.path) + match platform.system(): + case "Linux": + executable_filename = "program.selene.x" + case "Darwin": + executable_filename = "program.selene.x" + case "Windows": + executable_filename = "program.selene.exe" + case _: + raise RuntimeError(f"Unsupported OS: {platform.system()}") + out_path = build_ctx.artifact_dir / executable_filename if build_ctx.verbose: - print("Linking selene object file with selene core library") - zig_cache_dir = build_ctx.artifact_dir / "zig-cache" - zig_cache_dir.mkdir(exist_ok=True) - invoke_zig( - "build-exe", - f"-femit-bin={out_path}", - input_artifact.resource, - *libraries, - *link_flags, - cache_dir=zig_cache_dir, - ) - return cls._make_artifact( - out_path, - metadata={"library_search_dirs": library_search_dirs}, - ) - - -class HeliosObjectFileToSeleneExecutableStep_Darwin(Step): - """ - Link helios object with the interface shim, utilities, and selene core library to create the final executable. - """ - - input_kind = HeliosObjectFileKind - output_kind = SeleneExecutableKind - - @classmethod - def get_cost(cls, build_ctx: BuildCtx) -> float: - """ - Rule out this step for non-Darwin platforms. - """ - if platform.system() == "Darwin": - return 100 + print("Linking helios object file with shared Helios runtime") + + interface_path = build_ctx.deps[0].path + lib_dir = interface_path.parent + stem = interface_path.stem + if stem.startswith("lib"): + stem = stem[3:] + if stem.startswith("helios_selene_interface_launcher"): + suffix = stem.removeprefix("helios_selene_interface_launcher") else: - return float("inf") + suffix = stem.removeprefix("helios_selene_interface") + match platform.system(): + case "Linux": + runtime_link = ( + lib_dir / f"libhelios_selene_interface_runtime{suffix}.so" + ) + launcher = lib_dir / f"libhelios_selene_interface_launcher{suffix}.a" + case "Darwin": + runtime_link = ( + lib_dir / f"libhelios_selene_interface_runtime{suffix}.dylib" + ) + launcher = lib_dir / f"libhelios_selene_interface_launcher{suffix}.a" + case "Windows": + runtime_link = ( + lib_dir / f"libhelios_selene_interface_runtime{suffix}.dll.a" + ) + if not runtime_link.exists(): + runtime_link = ( + lib_dir / f"helios_selene_interface_runtime{suffix}.lib" + ) + launcher = lib_dir / f"libhelios_selene_interface_launcher{suffix}.a" + if not launcher.exists(): + launcher = lib_dir / f"helios_selene_interface_launcher{suffix}.lib" + case _: + raise RuntimeError(f"Unsupported OS: {platform.system()}") - @classmethod - def apply(cls, build_ctx: BuildCtx, input_artifact: Artifact) -> Artifact: - out_path = build_ctx.artifact_dir / "program.selene.x" - if build_ctx.verbose: - print("Linking helios object file with dependencies") + link_flags = ["-lc"] + libraries = [launcher, runtime_link] + library_search_dirs = [runtime_link.parent] try: from selene_sim import dist_dir as selene_dist except ImportError: @@ -403,17 +346,13 @@ def apply(cls, build_ctx: BuildCtx, input_artifact: Artifact) -> Artifact: "Selene simulation library not found. Please install selene_sim." ) selene_lib_dir = selene_dist / "lib" - selene_lib = selene_lib_dir / "libselene.dylib" - link_flags = ["-lc"] - libraries = [selene_lib] - library_search_dirs = [selene_lib_dir] - for dep in build_ctx.deps: + library_search_dirs.append(selene_lib_dir) + + for dep in build_ctx.deps[1:]: link_flags.extend(dep.link_flags) library_search_dirs.extend(dep.library_search_dirs) libraries.append(dep.path) - if build_ctx.verbose: - print("Linking selene object file with selene core library") zig_cache_dir = build_ctx.artifact_dir / "zig-cache" zig_cache_dir.mkdir(exist_ok=True) invoke_zig( @@ -422,10 +361,12 @@ def apply(cls, build_ctx: BuildCtx, input_artifact: Artifact) -> Artifact: input_artifact.resource, *libraries, *link_flags, + verbose=build_ctx.verbose, cache_dir=zig_cache_dir, ) - return cls._make_artifact( + return Artifact( out_path, + SeleneExecutableKind, metadata={"library_search_dirs": library_search_dirs}, ) @@ -443,6 +384,4 @@ def register_helios_builtins(planner: BuildPlanner) -> None: planner.add_step(HeliosLLVMBitcodeFileToHeliosObjectFileStep) planner.add_step(HeliosObjectStringToHeliosObjectFileStep) planner.add_step(HeliosObjectFileToHeliosObjectStringStep) - planner.add_step(HeliosObjectFileToSeleneObjectFileStep_Linux) - planner.add_step(HeliosObjectFileToSeleneExecutableStep_Windows) - planner.add_step(HeliosObjectFileToSeleneExecutableStep_Darwin) + planner.add_step(HeliosObjectFileToSeleneExecutableStep) diff --git a/selene-core/python/selene_core/build_utils/builtins/selene.py b/selene-core/python/selene_core/build_utils/builtins/selene.py index 56b3d6fc..3a70ae41 100644 --- a/selene-core/python/selene_core/build_utils/builtins/selene.py +++ b/selene-core/python/selene_core/build_utils/builtins/selene.py @@ -96,7 +96,7 @@ def apply(cls, build_ctx: BuildCtx, input_artifact: Artifact) -> Artifact: return cls._make_artifact(content) -class SeleneObjectToSeleneExecutable(Step): +class SeleneObjectToSeleneExecutableStep(Step): """ Link selene object with selene core library to create the final executable. """ @@ -159,4 +159,4 @@ def register_selene_builtins(planner: BuildPlanner) -> None: planner.add_kind(SeleneObjectStringKind) planner.add_step(SeleneObjectStringToSeleneObjectFileStep) planner.add_step(SeleneObjectFileToSeleneObjectStringStep) - planner.add_step(SeleneObjectToSeleneExecutable) + planner.add_step(SeleneObjectToSeleneExecutableStep) diff --git a/selene-ext/interfaces/helios_qis/c/CMakeLists.txt b/selene-ext/interfaces/helios_qis/c/CMakeLists.txt index c2188548..aff13a18 100644 --- a/selene-ext/interfaces/helios_qis/c/CMakeLists.txt +++ b/selene-ext/interfaces/helios_qis/c/CMakeLists.txt @@ -12,25 +12,44 @@ set(base_name "helios_selene_interface") function(build_helios_interface log_level suffix) - set(target_name "${base_name}${suffix}") + set(runtime_target "${base_name}_runtime${suffix}") + set(launcher_target "${base_name}_launcher${suffix}") - add_library(${target_name} STATIC src/interface.c) - set_target_properties(${target_name} PROPERTIES + add_library(${runtime_target} SHARED src/interface.c) + set_target_properties(${runtime_target} PROPERTIES POSITION_INDEPENDENT_CODE ON C_STANDARD 11 ) - target_compile_definitions(${target_name} PRIVATE SELENE_LOG_LEVEL=${log_level}) - target_include_directories(${target_name} PRIVATE "${selene_include_dirs}") - target_include_directories(${target_name} PUBLIC + target_compile_definitions(${runtime_target} PRIVATE SELENE_LOG_LEVEL=${log_level}) + target_include_directories(${runtime_target} PRIVATE "${selene_include_dirs}") + target_include_directories(${runtime_target} PUBLIC "$" "$" ) + target_link_libraries(${runtime_target} PRIVATE Selene::Selene) if(MSVC) - target_compile_options(${target_name} PRIVATE /TC /Zl) # compile as C, not C++ + target_compile_options(${runtime_target} PRIVATE /TC /Zl) endif() - install(TARGETS ${target_name} + add_library(${launcher_target} STATIC src/main.c) + set_target_properties(${launcher_target} PROPERTIES + POSITION_INDEPENDENT_CODE ON + C_STANDARD 11 + ) + target_include_directories(${launcher_target} PRIVATE "${selene_include_dirs}") + target_include_directories(${launcher_target} PUBLIC + "$" + "$" + ) + target_link_libraries(${launcher_target} PUBLIC ${runtime_target}) + if(MSVC) + target_compile_options(${launcher_target} PRIVATE /TC /Zl) + endif() + + install(TARGETS ${runtime_target} ${launcher_target} EXPORT HeliosSeleneInterface + RUNTIME DESTINATION lib + LIBRARY DESTINATION lib ARCHIVE DESTINATION lib INCLUDES DESTINATION include ) diff --git a/selene-ext/interfaces/helios_qis/c/include/helios_qis/interface.h b/selene-ext/interfaces/helios_qis/c/include/helios_qis/interface.h index 20c54f32..2cb69162 100644 --- a/selene-ext/interfaces/helios_qis/c/include/helios_qis/interface.h +++ b/selene-ext/interfaces/helios_qis/c/include/helios_qis/interface.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -26,8 +27,7 @@ -// The entrypoint of the resulting executable -EXPORT int main(int argc, char** argv); +EXPORT int selene_helios_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)); EXPORT uint64_t ___qalloc(); EXPORT void ___qfree(uint64_t q); diff --git a/selene-ext/interfaces/helios_qis/c/src/interface.c b/selene-ext/interfaces/helios_qis/c/src/interface.c index 49e17097..a3c51f20 100644 --- a/selene-ext/interfaces/helios_qis/c/src/interface.c +++ b/selene-ext/interfaces/helios_qis/c/src/interface.c @@ -107,11 +107,7 @@ struct selene_string_t parse_c_string(char const* str) { SeleneInstance* selene_instance = 0; -// defined by hybrid user program compiler -extern uint64_t qmain(uint64_t); - -// The entrypoint of the resulting executable -int main(int argc, char** argv) { +int selene_helios_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { DIAGNOSTIC("selene_init() with args:\n"); for (int i = 0; i < argc; ++i) { DIAGNOSTIC(" %d: %s\n", i, argv[i]); @@ -143,7 +139,7 @@ int main(int argc, char** argv) { } int error_code = setjmp(user_program_jmpbuf); if (error_code == 0) { - qmain(0); + entrypoint(0); } else { // error_code >= 1000 means that the program should stop entirely, // error_code < 1000 means the shot stops but the next one is allowed @@ -156,6 +152,7 @@ int main(int argc, char** argv) { } } selene_exit(selene_instance); + return 0; } uint64_t ___qalloc() { @@ -424,4 +421,12 @@ void simulate_delay(uint64_t delay_ns) { DIAGNOSTIC("simulate_delay(%" PRIu64 ")\n", delay_ns); unwrap(selene_simulate_delay(selene_instance, delay_ns)); DIAGNOSTIC(" [done]\n"); -} \ No newline at end of file +} +void log_utility_call(uint64_t tag, void* data, uint64_t data_len) { + DIAGNOSTIC("log_utility_call(%" PRIu64 ", %p, %" PRIu64 ")\n", tag, data, data_len); + DIAGNOSTIC("Data:\n"); + for (uint64_t i = 0; i < data_len; ++i) { + DIAGNOSTIC(" %02X ", ((uint8_t*)data)[i]); + } + unwrap(selene_log_utility_call(selene_instance, tag, data, data_len)); +} diff --git a/selene-ext/interfaces/helios_qis/c/src/main.c b/selene-ext/interfaces/helios_qis/c/src/main.c new file mode 100644 index 00000000..dab934cb --- /dev/null +++ b/selene-ext/interfaces/helios_qis/c/src/main.c @@ -0,0 +1,10 @@ +#include + +#include + +// defined by hybrid user program compiler +extern uint64_t qmain(uint64_t); + +int main(int argc, char** argv) { + return selene_helios_run(argc, argv, qmain); +} diff --git a/selene-ext/interfaces/helios_qis/python/selene_helios_qis_plugin/plugin.py b/selene-ext/interfaces/helios_qis/python/selene_helios_qis_plugin/plugin.py index 5b81a1ec..67525a80 100644 --- a/selene-ext/interfaces/helios_qis/python/selene_helios_qis_plugin/plugin.py +++ b/selene-ext/interfaces/helios_qis/python/selene_helios_qis_plugin/plugin.py @@ -22,11 +22,10 @@ class HeliosInterface(QuantumInterface): @property def library_file(self): - # two libraries are currently provided: one for normal runs, - # and one for diagnostic runs. The latter prints quantum calls - # to stderr, and can be selected by setting the diagnostic_mode - # attribute to True. - lib_name = "helios_selene_interface" + # The Helios interface is now split into a static launcher and a shared + # runtime. The planner links the launcher into the final executable and + # adds the shared runtime separately. + lib_name = "helios_selene_interface_launcher" match self.log_level: case LogLevel.QUIET: lib_name += "" diff --git a/selene-ext/utilities/.gitignore b/selene-ext/utilities/.gitignore deleted file mode 100644 index f935021a..00000000 --- a/selene-ext/utilities/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!.gitignore diff --git a/selene-ext/utilities/argreader/Cargo.lock b/selene-ext/utilities/argreader/Cargo.lock new file mode 100644 index 00000000..087cf59b --- /dev/null +++ b/selene-ext/utilities/argreader/Cargo.lock @@ -0,0 +1,544 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bytesize" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libyml" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980" +dependencies = [ + "anyhow", + "version_check", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + +[[package]] +name = "quest-sys" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3193d99e88ae9aa9e058b786727d139e366364e5bc15fb1b9041716783d3bead" +dependencies = [ + "cc", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "selene-core" +version = "0.2.9" +dependencies = [ + "anyhow", + "delegate", + "derive_more", + "libloading", + "ouroboros", + "thiserror", +] + +[[package]] +name = "selene-utility-argreader" +version = "0.2.15" +dependencies = [ + "anyhow", + "bytesize", + "quest-sys", + "selene-core", + "serde", + "serde_yml", + "sysinfo", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yml" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" +dependencies = [ + "indexmap", + "itoa", + "libyml", + "memchr", + "ryu", + "serde", + "version_check", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysinfo" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/selene-ext/utilities/argreader/Cargo.toml b/selene-ext/utilities/argreader/Cargo.toml new file mode 100644 index 00000000..a71bb662 --- /dev/null +++ b/selene-ext/utilities/argreader/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "selene-utility-argreader" +version = "0.2.15" +edition = "2024" +rust-version = "1.94" +authors = ["Jake Arkinstall "] +repository = "https://github.com/quantinuum/selene" +license = "Apache-2.0" + +[package.metadata.selene] +build_after_helios_interface = true + +[lib] +name = "selene_argreader_plugin" +path = "rust/lib.rs" +doctest = false +crate-type = ["cdylib"] + +[profile.release] +panic = "abort" + +[dependencies] +anyhow = "1.0" +quest-sys = {version = "0.16"} +selene-core = { path = "../../../selene-core" } +sysinfo = { version = "0.35" } +bytesize = { version = "2.3" } +serde = { version = "1.0", features = ["derive"] } +serde_yml = "0.0.12" + +[workspace] diff --git a/selene-ext/utilities/argreader/build.rs b/selene-ext/utilities/argreader/build.rs new file mode 100644 index 00000000..cc587149 --- /dev/null +++ b/selene-ext/utilities/argreader/build.rs @@ -0,0 +1,24 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + println!("cargo:rerun-if-env-changed=SELENE_HELIOS_QIS_LIB_DIR"); + + let lib_dir = env::var_os("SELENE_HELIOS_QIS_LIB_DIR") + .map(PathBuf::from) + .unwrap_or_else(default_helios_lib_dir); + if !lib_dir.exists() { + panic!( + "Helios interface runtime directory not found: {}. Build the Helios QIS interface first or set SELENE_HELIOS_QIS_LIB_DIR.", + lib_dir.display() + ); + } + + println!("cargo:rustc-link-search=native={}", lib_dir.display()); + println!("cargo:rustc-link-lib=dylib=helios_selene_interface_runtime"); +} + +fn default_helios_lib_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../interfaces/helios_qis/python/selene_helios_qis_plugin/_dist/lib") +} diff --git a/selene-ext/utilities/argreader/python/selene_argreader_plugin/__init__.py b/selene-ext/utilities/argreader/python/selene_argreader_plugin/__init__.py new file mode 100644 index 00000000..c0cdb5a5 --- /dev/null +++ b/selene-ext/utilities/argreader/python/selene_argreader_plugin/__init__.py @@ -0,0 +1,4 @@ +from .plugin import ArgReaderPlugin +from .provider import ArgProvider, ShotInput + +__all__ = ["ArgReaderPlugin", "ArgProvider", "ShotInput"] diff --git a/selene-ext/utilities/argreader/python/selene_argreader_plugin/plugin.py b/selene-ext/utilities/argreader/python/selene_argreader_plugin/plugin.py new file mode 100644 index 00000000..428a5a8a --- /dev/null +++ b/selene-ext/utilities/argreader/python/selene_argreader_plugin/plugin.py @@ -0,0 +1,48 @@ +import platform +from dataclasses import dataclass +from pathlib import Path + +from selene_core import Utility + + +@dataclass +class ArgReaderPlugin(Utility): + @property + def library_file(self) -> Path: + lib_name = "selene_argreader_plugin" + lib_dir = Path(__file__).parent / "_dist/lib/" + match platform.system(): + case "Linux": + return lib_dir / f"lib{lib_name}.so" + case "Darwin": + return lib_dir / f"lib{lib_name}.dylib" + case "Windows": + import_lib = lib_dir / f"lib{lib_name}.dll.a" + if import_lib.exists(): + return import_lib + return lib_dir / f"{lib_name}.lib" + case system: + raise RuntimeError(f"Unsupported platform: {system}") + + @property + def library_search_dirs(self) -> list[Path]: + return [Path(__file__).parent / "_dist/lib/"] + + @property + def link_flags(self) -> list[str]: + match platform.system(): + case "Linux": + link_path = Path(__file__).parent / "_dist/lib/" + return ["-lgcc_s", f"-L{link_path}", "-lselene_argreader_plugin"] + case "Darwin": + link_path = Path(__file__).parent / "_dist/lib/" + return [f"-L{link_path}", "-lselene_argreader_plugin"] + case "Windows": + link_path = Path(__file__).parent / "_dist/lib/" + return [ + "-luserenv", + f"-L{link_path}", + "-lselene_argreader_plugin", + ] + case system: + raise RuntimeError(f"Unsupported platform: {system}") diff --git a/selene-ext/utilities/argreader/python/selene_argreader_plugin/provider.py b/selene-ext/utilities/argreader/python/selene_argreader_plugin/provider.py new file mode 100644 index 00000000..35edc51b --- /dev/null +++ b/selene-ext/utilities/argreader/python/selene_argreader_plugin/provider.py @@ -0,0 +1,64 @@ +import yaml +from dataclasses import dataclass, field +from contextlib import AbstractContextManager +from types import TracebackType +import os +import tempfile + +valid_value = int | float | bool | list[int] | list[float] | list[bool] + + +@dataclass +class ShotInput: + shot_start_idx: int + shot_end_idx: int + records: dict[str, valid_value] = field(default_factory=dict) + + +@dataclass +class RunInputs: + shot_inputs: list[ShotInput] = field(default_factory=list) + + +class ArgProvider(AbstractContextManager): + run_inputs: RunInputs | None + + def __init__(self): + self.run_inputs = None + + def set_constant_args(self, **kwargs: valid_value) -> None: + self.run_inputs = RunInputs( + shot_inputs=[ + ShotInput( + shot_start_idx=0, + shot_end_idx=2**64 - 1, + records=kwargs, + ) + ] + ) + + def set_variable_args(self, args: list[dict[str, valid_value]]) -> None: + self.run_inputs = RunInputs() + for i, arg in enumerate(args): + self.run_inputs.shot_inputs.append( + ShotInput( + shot_start_idx=i, + shot_end_idx=i + 1, + records=arg, + ) + ) + + def __enter__(self) -> "ArgProvider": + # write args to a new temporary file + with tempfile.NamedTemporaryFile("w", delete=False) as f: + yaml.dump(self.run_inputs, f) + os.environ["SELENE_ARGREADER_INPUT_FILE"] = f.name + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + del os.environ["SELENE_ARGREADER_INPUT_FILE"] diff --git a/selene-ext/utilities/argreader/python/tests/resources/argreader_example.ll b/selene-ext/utilities/argreader/python/tests/resources/argreader_example.ll new file mode 100644 index 00000000..4fb59a5e --- /dev/null +++ b/selene-ext/utilities/argreader/python/tests/resources/argreader_example.ll @@ -0,0 +1,127 @@ +; ModuleID = 'custom' +source_filename = "custom" +target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128" +target triple = "aarch64-unknown-linux-gnu" + +; standard QIS +declare ptr @heap_alloc(i64) local_unnamed_addr +declare void @heap_free(ptr) local_unnamed_addr +declare void @setup(i64) local_unnamed_addr +declare i64 @teardown() local_unnamed_addr +declare void @___rxy(i64, double, double) local_unnamed_addr +declare void @___dec_future_refcount(i64) local_unnamed_addr +declare i64 @___lazy_measure(i64) local_unnamed_addr +declare void @___qfree(i64) local_unnamed_addr +declare i1 @___read_future_bool(i64) local_unnamed_addr +declare void @print_bool(i8*, i64, i1) local_unnamed_addr +declare void @print_bool_arr(i8*, i64, i1) local_unnamed_addr +declare void @print_uint(i8*, i64, i64) local_unnamed_addr +declare void @print_uint_arr(ptr, i64, ptr) local_unnamed_addr +declare void @print_int(i8*, i64, i64) local_unnamed_addr +declare void @print_int_arr(ptr, i64, ptr) local_unnamed_addr +declare void @print_float(i8*, i64, double) local_unnamed_addr +declare void @print_float_arr(ptr, i64, ptr) local_unnamed_addr +declare i64 @___qalloc() local_unnamed_addr +declare void @___reset(i64) local_unnamed_addr +declare void @___inc_future_refcount(i64) local_unnamed_addr + +; arg reader plugin functions +declare i8 @argreader_get_bool(i8*) local_unnamed_addr +declare i64 @argreader_get_u64(i8*) local_unnamed_addr +declare i64 @argreader_get_i64(i8*) local_unnamed_addr +declare double @argreader_get_f64(i8*) local_unnamed_addr +declare void @argreader_get_bool_array(i8*, i8*, i64) local_unnamed_addr +declare void @argreader_get_u64_array(i8*, i64*, i64) local_unnamed_addr +declare void @argreader_get_i64_array(i8*, i64*, i64) local_unnamed_addr +declare void @argreader_get_f64_array(i8*, double*, i64) local_unnamed_addr + + +; labels for argument fetching (first byte encodes length) +@bool_label = private constant [11 x i8] c"\0Ainput_bool" +@u64_label = private constant [10 x i8] c"\09input_u64" +@i64_label = private constant [10 x i8] c"\09input_i64" +@f64_label = private constant [10 x i8] c"\09input_f64" +@bool_array_label = private constant [17 x i8] c"\10input_bool_array" +@u64_array_label = private constant [16 x i8] c"\0Finput_u64_array" +@i64_array_label = private constant [16 x i8] c"\0Finput_i64_array" +@f64_array_label = private constant [16 x i8] c"\0Finput_f64_array" + +; tags for printing (first byte encodes length) +@bool_tag = private constant [21 x i8] c"\14USER:BOOL:input_bool" +@u64_tag = private constant [19 x i8] c"\12USER:INT:input_u64" +@i64_tag = private constant [19 x i8] c"\12USER:INT:input_i64" +@f64_tag = private constant [21 x i8] c"\14USER:FLOAT:input_f64" +@bool_array_tag = private constant [30 x i8] c"\1DUSER:BOOLARR:input_bool_array" +@u64_array_tag = private constant [28 x i8] c"\1BUSER:INTARR:input_u64_array" +@i64_array_tag = private constant [28 x i8] c"\1BUSER:INTARR:input_i64_array" +@f64_array_tag = private constant [30 x i8] c"\1DUSER:FLOATARR:input_f64_array" + + +define private void @main_inner() unnamed_addr { +alloca_block: +; first, a demonstration of reading and printing of scalar values. + %bool_value = call i8 @argreader_get_bool(ptr @bool_label) + %u64_value = call i64 @argreader_get_u64(ptr @u64_label) + %i64_value = call i64 @argreader_get_i64(ptr @i64_label) + %f64_value = call double @argreader_get_f64(ptr @f64_label) + + %bool_value_i1 = trunc i8 %bool_value to i1 + call void @print_bool(ptr @bool_tag, i64 0, i1 %bool_value_i1) + call void @print_uint(ptr @u64_tag, i64 0, i64 %u64_value) + call void @print_int(ptr @i64_tag, i64 0, i64 %i64_value) + call void @print_float(ptr @f64_tag, i64 0, double %f64_value) + +; now we demonstrate reading and printing array values. +; first allocate space (here we do that on the stack), then point to it in the argreader array call, along with the size. + %bool_array_value = alloca [5 x i1], align 8 + %u64_array_value = alloca [5 x i64], align 8 + %i64_array_value = alloca [5 x i64], align 8 + %f64_array_value = alloca [5 x double], align 8 + call void @argreader_get_bool_array(ptr @bool_array_label, ptr %bool_array_value, i64 5) + call void @argreader_get_u64_array(ptr @u64_array_label, ptr %u64_array_value, i64 5) + call void @argreader_get_i64_array(ptr @i64_array_label, ptr %i64_array_value, i64 5) + call void @argreader_get_f64_array(ptr @f64_array_label, ptr %f64_array_value, i64 5) + + +; the print_T_arr functions require a struct holding array information, so we use a helper to create that struct on the heap. + %bool_array_cl = call ptr @_get_cl_array(ptr %bool_array_value, i32 5) + %u64_array_cl = call ptr @_get_cl_array(ptr %u64_array_value, i32 5) + %i64_array_cl = call ptr @_get_cl_array(ptr %i64_array_value, i32 5) + %f64_array_cl = call ptr @_get_cl_array(ptr %f64_array_value, i32 5) + + call void @print_bool_arr(ptr @bool_array_tag, i64 0, ptr nonnull %bool_array_cl) + call void @print_uint_arr(ptr @u64_array_tag, i64 0, ptr nonnull %u64_array_cl) + call void @print_int_arr(ptr @i64_array_tag, i64 0, ptr nonnull %i64_array_cl) + call void @print_float_arr(ptr @f64_array_tag, i64 0, ptr nonnull %f64_array_cl) + + call void @heap_free(ptr %bool_array_cl) + call void @heap_free(ptr %u64_array_cl) + call void @heap_free(ptr %i64_array_cl) + call void @heap_free(ptr %f64_array_cl) + + ret void +} + +define private ptr @_get_cl_array(ptr %array_contents, i32 %array_len) unnamed_addr { + %array_wrapper = call ptr @heap_alloc(i64 16) + %x_ptr = getelementptr inbounds <{ i32, i32, ptr }>, ptr %array_wrapper, i64 0, i32 0 + %y_ptr = getelementptr inbounds <{ i32, i32, ptr }>, ptr %array_wrapper, i64 0, i32 1 + %arr_ptr = getelementptr inbounds <{ i32, i32, ptr }>, ptr %array_wrapper, i64 0, i32 2 + store i32 %array_len, ptr %x_ptr, align 8 + store i32 1, ptr %y_ptr, align 4 + store ptr %array_contents, ptr %arr_ptr, align 8 + ret ptr %array_wrapper +} + + +define i64 @qmain(i64 %0) local_unnamed_addr { +entry: + tail call void @setup(i64 %0) + tail call fastcc void @main_inner() + %1 = tail call i64 @teardown() + ret i64 %1 +} + +!name = !{!0} + +!0 = !{!"mainlib"} diff --git a/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader/constant_args.yaml b/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader/constant_args.yaml new file mode 100644 index 00000000..bd171e73 --- /dev/null +++ b/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader/constant_args.yaml @@ -0,0 +1,400 @@ +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 diff --git a/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader/variable_args.yaml b/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader/variable_args.yaml new file mode 100644 index 00000000..93fd079a --- /dev/null +++ b/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader/variable_args.yaml @@ -0,0 +1,400 @@ +- - !!python/tuple + - input_bool + - 0 + - !!python/tuple + - input_u64 + - 4 + - !!python/tuple + - input_i64 + - 2 + - !!python/tuple + - input_f64 + - 0.025 + - !!python/tuple + - input_bool_array + - - 1 + - 0 + - 1 + - 1 + - 0 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.25 + - 0.5 + - 0.75 + - 1.0 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - -10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 0 + - !!python/tuple + - input_u64 + - 4 + - !!python/tuple + - input_i64 + - 2 + - !!python/tuple + - input_f64 + - 0.025 + - !!python/tuple + - input_bool_array + - - 1 + - 0 + - 1 + - 1 + - 0 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.25 + - 0.5 + - 0.75 + - 1.0 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - -10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 0 + - !!python/tuple + - input_u64 + - 4 + - !!python/tuple + - input_i64 + - 2 + - !!python/tuple + - input_f64 + - 0.025 + - !!python/tuple + - input_bool_array + - - 1 + - 0 + - 1 + - 1 + - 0 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.25 + - 0.5 + - 0.75 + - 1.0 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - -10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 0 + - !!python/tuple + - input_u64 + - 4 + - !!python/tuple + - input_i64 + - 2 + - !!python/tuple + - input_f64 + - 0.025 + - !!python/tuple + - input_bool_array + - - 1 + - 0 + - 1 + - 1 + - 0 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.25 + - 0.5 + - 0.75 + - 1.0 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - -10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 +- - !!python/tuple + - input_bool + - 0 + - !!python/tuple + - input_u64 + - 4 + - !!python/tuple + - input_i64 + - 2 + - !!python/tuple + - input_f64 + - 0.025 + - !!python/tuple + - input_bool_array + - - 1 + - 0 + - 1 + - 1 + - 0 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - 10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.25 + - 0.5 + - 0.75 + - 1.0 +- - !!python/tuple + - input_bool + - 1 + - !!python/tuple + - input_u64 + - 2 + - !!python/tuple + - input_i64 + - -4 + - !!python/tuple + - input_f64 + - 0.0125 + - !!python/tuple + - input_bool_array + - - 0 + - 1 + - 1 + - 1 + - 1 + - !!python/tuple + - input_u64_array + - - 8 + - 2 + - 5 + - 4 + - 1 + - !!python/tuple + - input_i64_array + - - -10 + - 20 + - 15 + - -25 + - 30 + - !!python/tuple + - input_f64_array + - - 0.0 + - 0.125 + - 0.25 + - 0.375 + - 0.5 diff --git a/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader_trace/trace.json b/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader_trace/trace.json new file mode 100644 index 00000000..7ad60a38 --- /dev/null +++ b/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader_trace/trace.json @@ -0,0 +1,116 @@ +{ + "events": [ + { + "source": { + "kind": "UserProgram", + "index": 0 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 43859750371, + "data": "a2V5OiBpbnB1dF9ib29sCnZhbHVlOiB0cnVlCg==" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 1 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 43859750371, + "data": "a2V5OiBpbnB1dF91NjQKdmFsdWU6IDIK" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 2 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 43859750371, + "data": "a2V5OiBpbnB1dF9pNjQKdmFsdWU6IC00Cg==" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 3 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 43859750371, + "data": "a2V5OiBpbnB1dF9mNjQKdmFsdWU6IDAuMDEyNQo=" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 4 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 43859750371, + "data": "a2V5OiBpbnB1dF9ib29sX2FycmF5CnZhbHVlOgotIGZhbHNlCi0gdHJ1ZQotIHRydWUKLSB0cnVlCi0gdHJ1ZQo=" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 5 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 43859750371, + "data": "a2V5OiBpbnB1dF91NjRfYXJyYXkKdmFsdWU6Ci0gOAotIDIKLSA1Ci0gNAotIDEK" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 6 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 43859750371, + "data": "a2V5OiBpbnB1dF9pNjRfYXJyYXkKdmFsdWU6Ci0gMTAKLSAyMAotIDE1Ci0gLTI1Ci0gMzAK" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 7 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 43859750371, + "data": "a2V5OiBpbnB1dF9mNjRfYXJyYXkKdmFsdWU6Ci0gMC4wCi0gMC4xMjUKLSAwLjI1Ci0gMC4zNzUKLSAwLjUK" + } + } + } + ] +} \ No newline at end of file diff --git a/selene-ext/utilities/argreader/python/tests/test_argreader.py b/selene-ext/utilities/argreader/python/tests/test_argreader.py new file mode 100644 index 00000000..f0b30849 --- /dev/null +++ b/selene-ext/utilities/argreader/python/tests/test_argreader.py @@ -0,0 +1,226 @@ +from pathlib import Path +import yaml +import pytest + +from selene_sim import build, Coinflip +from selene_sim.exceptions import SelenePanicError +from selene_sim.event_hooks import CircuitExtractor +from selene_argreader_plugin import ArgReaderPlugin, ArgProvider + +# Note to avoid confusion: +# parameter names are configured by the user program, and we +# are just using e.g. "input_bool" in argreader_example.ll as +# a clear example of what's what for testing purposes. + + +def test_arg_reader(snapshot): + llvm_file = Path(__file__).parent / "resources/argreader_example.ll" + instance = build(llvm_file, utilities=[ArgReaderPlugin()]) + + arg_provider = ArgProvider() + # We can set constant arguments that apply for every shot + # + arg_provider.set_constant_args( + input_bool=True, + input_u64=2, + input_i64=-4, + input_f64=0.0125, + input_bool_array=[False, True, True, True, True], + input_u64_array=[8, 2, 5, 4, 1], + input_i64_array=[10, 20, 15, -25, 30], + input_f64_array=list(i * 0.125 for i in range(5)), + ) + + with arg_provider: + result = list( + list(r) + for r in instance.run_shots(n_qubits=1, n_shots=10, simulator=Coinflip()) + ) + snapshot.assert_match(yaml.dump(result), "constant_args.yaml") + + # Or we can provide per-shot variable arguments. + even_shot_args = { + "input_bool": False, + "input_u64": 4, + "input_i64": 2, + "input_f64": 0.025, + "input_bool_array": [True, False, True, True, False], + "input_u64_array": [8, 2, 5, 4, 1], + "input_i64_array": [10, 20, 15, -25, 30], + "input_f64_array": list(i * 0.25 for i in range(5)), + } + + odd_shot_args = { + "input_bool": True, + "input_u64": 2, + "input_i64": -4, + "input_f64": 0.0125, + "input_bool_array": [False, True, True, True, True], + "input_u64_array": [8, 2, 5, 4, 1], + "input_i64_array": [-10, 20, 15, -25, 30], + "input_f64_array": list(i * 0.125 for i in range(5)), + } + + arg_provider.set_variable_args([even_shot_args, odd_shot_args] * 5) + + with arg_provider: + result = list( + list(r) + for r in instance.run_shots(n_qubits=1, n_shots=10, simulator=Coinflip()) + ) + snapshot.assert_match(yaml.dump(result), "variable_args.yaml") + + +def test_arg_reader_validation(): + llvm_file = Path(__file__).parent / "resources/argreader_example.ll" + instance = build(llvm_file, utilities=[ArgReaderPlugin()]) + + first_shot_args = { + "input_bool": False, + "input_u64": 4, + "input_i64": 2, + "input_f64": 0.025, + "input_bool_array": [True, False, True, True, False], + "input_u64_array": [8, 2, 5, 4, 1], + "input_i64_array": [10, 20, 15, -25, 30], + "input_f64_array": list(i * 0.25 for i in range(5)), + } + + second_shot_args = { + "input_bool": True, + "input_u64": 2, + "input_i64": -4, + "input_f64": 0.0125, + "input_bool_array": [False, True, True, True, True], + "input_u64_array": [8, 2, 5, 4, 1], + "input_i64_array": [-10, 20, 15, -25, 30], + "input_f64_array": list(i * 0.125 for i in range(5)), + } + + arg_provider = ArgProvider() + arg_provider.set_variable_args([first_shot_args, second_shot_args]) + + # if we try to run more shots than we have args for, it should panic + with pytest.raises( + SelenePanicError, match="No runtime arguments provided for shot 2" + ): + with arg_provider: + result = list( + list(r) + for r in instance.run_shots(n_qubits=1, n_shots=3, simulator=Coinflip()) + ) + + # if we try to run a shot with an arg that doesn't match the expected type, it should panic. + # here we pass 4.2 to the input_u64 argument, and expect a panic as a result. + arg_provider.set_constant_args( + input_bool=False, + input_u64=4.2, + input_i64=2, + input_f64=0.025, + ) + with pytest.raises( + SelenePanicError, + match="Runtime argument 'input_u64' has a non-integral floating point value 4.2", + ): + with arg_provider: + result = list( + list(r) + for r in instance.run_shots(n_qubits=1, n_shots=1, simulator=Coinflip()) + ) + + # if we miss a required arg, it should panic. Here we omit input_i64. + arg_provider.set_constant_args( + input_bool=False, + input_u64=4, + input_f64=0.025, + input_bool_array=[True, False, True, True, False], + input_u64_array=[8, 2, 5, 4, 1], + input_i64_array=[10, 20, 15, -25, 30], + ) + with pytest.raises(SelenePanicError, match="Missing runtime argument 'input_i64'"): + with arg_provider: + result = list( + list(r) + for r in instance.run_shots(n_qubits=1, n_shots=1, simulator=Coinflip()) + ) + + # If we provide an array of the wrong length, it should panic. Here we provide too few + # elements of the input_f64_array. + arg_provider.set_constant_args( + input_bool=False, + input_u64=4, + input_i64=2, + input_f64=0.025, + input_bool_array=[True, False, True, True, False], + input_u64_array=[8, 2, 5, 4, 1], + input_i64_array=[10, 20, 15, -25, 30], + input_f64_array=list(i * 0.25 for i in range(4)), + ) + with pytest.raises( + SelenePanicError, + match="Runtime argument 'input_f64_array' expects an array of 5 floats", + ): + with arg_provider: + result = list( + list(r) + for r in instance.run_shots(n_qubits=1, n_shots=1, simulator=Coinflip()) + ) + + # if the array has the wrong value types, it should try to convert and fail if not possible. + # Here, the only error is that a value in input_i64_array is set to a float that exceeds + # the bounds of an int64. This should cause a panic because the argument cannot be converted. + arg_provider.set_constant_args( + input_bool=False, + input_u64=4, + input_i64=2.0, + input_f64=5, + input_bool_array=[True, False, True, True, False], + input_u64_array=[8.0, 2, 5, 4, 1], + input_i64_array=[10, 20, 15, -25, float(2**64)], + input_f64_array=list(i for i in range(5)), + ) + with pytest.raises( + SelenePanicError, + match="Runtime argument 'input_i64_array' contains floating point values which exceed signed integer bounds", + ): + with arg_provider: + result = list( + list(r) + for r in instance.run_shots(n_qubits=1, n_shots=1, simulator=Coinflip()) + ) + + +def test_arg_reader_trace(snapshot): + llvm_file = Path(__file__).parent / "resources/argreader_example.ll" + instance = build(llvm_file, utilities=[ArgReaderPlugin()]) + + # We often wish to trace not just the quantum calls from a program, + # but also the interaction with plugins. The ArgReader plugin logs its + # calls with Selene so that if tracing is enabled, we can see the arguments, + # types and values, within the event-by-event trace. + + arg_provider = ArgProvider() + arg_provider.set_constant_args( + input_bool=True, + input_u64=2, + input_i64=-4, + input_f64=0.0125, + input_bool_array=[False, True, True, True, True], + input_u64_array=[8, 2, 5, 4, 1], + input_i64_array=[10, 20, 15, -25, 30], + input_f64_array=list(i * 0.125 for i in range(5)), + ) + + extractor = CircuitExtractor() + + with arg_provider: + result = list( + list(r) + for r in instance.run_shots( + n_qubits=1, n_shots=1, simulator=Coinflip(), event_hook=extractor + ) + ) + + trace = extractor.shots[0].get_trace() + json = trace.model_dump_json(indent=2) + snapshot.assert_match(json, "trace.json") diff --git a/selene-ext/utilities/argreader/rust/lib.rs b/selene-ext/utilities/argreader/rust/lib.rs new file mode 100644 index 00000000..e1031960 --- /dev/null +++ b/selene-ext/utilities/argreader/rust/lib.rs @@ -0,0 +1,559 @@ +use serde::{Deserialize, Serialize}; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::{fs, io}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(untagged)] +enum InputRecord { + Bool(bool), + U64(u64), + I64(i64), + F64(f64), + BoolArray(Vec), + U64Array(Vec), + I64Array(Vec), + F64Array(Vec), +} + +#[derive(Serialize, Deserialize, Clone)] +struct ShotInputs { + shot_start_idx: u64, + shot_end_idx: u64, + records: BTreeMap, +} + +#[derive(Serialize, Deserialize)] +struct RunInputs { + shot_inputs: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +struct LogEntry { + key: String, + value: InputRecord, +} + +impl RunInputs { + pub fn get_inputs_for_shot(&self, shot_id: u64) -> Option<&ShotInputs> { + self.shot_inputs + .iter() + .find(|shot| shot.shot_start_idx <= shot_id && shot.shot_end_idx > shot_id) + } +} + +thread_local! { + pub static INPUTS: RefCell> = const{ RefCell::new(None) }; +} + +unsafe extern "C" { + // this is imported from selene at link time. + // note that we can't use panic() -> this conflicts with libSystem on Darwin, + // and cargo really wants to link it in while this is compiling. + // + // Instead we use panic_str, an exposed function for registering a panic with + // the selene result stream based on a C string rather than a CL string. + fn panic_str(error_code: u32, message: *const std::ffi::c_char) -> !; + fn get_current_shot() -> u64; + fn log_utility_call(tag: u64, data_ptr: *const u8, data_len: u64); +} + +fn selene_panic(message: String) -> ! { + // Convert the message to a C string + let message = message.into_bytes(); + let message = std::ffi::CString::new(message).unwrap(); + // Issue the panic + // print just in case the panic_str fails to make it through to + // the results stream. + eprintln!("ArgReader: Panic: {}", message.to_string_lossy()); + unsafe { panic_str(60001, message.as_ptr()) } +} +fn log(key: &str, value: &InputRecord) { + let tag: u64 = 0xA363EADE3; + let entry = LogEntry { + key: key.to_string(), + value: value.clone(), + }; + // serde encode it + let yaml = serde_yml::to_string(&entry).unwrap_or_else(|e| { + selene_panic(format!( + "Failed to serialize log entry for key '{}': {}", + key, e + )); + }); + let data = yaml.into_bytes(); + unsafe { + log_utility_call(tag, data.as_ptr(), data.len() as u64); + } +} + +fn init() { + let filename = std::env::var("SELENE_ARGREADER_INPUT_FILE").unwrap_or_else(|_| { + selene_panic("When using runtime arguments, use the ArgProvider context wrapper to provide arguments into the selene environment.".to_string()); + }); + let file = match fs::File::open(&filename) { + Ok(f) => f, + Err(e) => { + selene_panic(format!("Failed to open file {filename}: {e}")); + } + }; + let reader = io::BufReader::new(file); + let data: RunInputs = match serde_yml::from_reader(reader) { + Ok(d) => d, + Err(e) => { + selene_panic(format!("Failed to parse YAML from file {filename}: {e}")); + } + }; + INPUTS.with(|cell| { + *cell.borrow_mut() = Some(data); + }); +} + +fn get_key(key_ptr: *const u8) -> String { + // As with result() calls and panic() calls, the format of the key starts with + // byte providing the length of the string that follows. + unsafe { + let length = *key_ptr; + let string_start = key_ptr.add(1); + let slice = std::slice::from_raw_parts(string_start, length as usize); + let Ok(key) = std::str::from_utf8(slice) else { + selene_panic("Runtime argument names must be valid UTF-8".to_string()); + }; + key.to_string() + } +} + +unsafe fn value_helper(key: *const u8) -> InputRecord { + let key = get_key(key); + if INPUTS.with(|cell| cell.borrow().is_none()) { + init(); + } + INPUTS.with(|cell| { + let binding = cell.borrow(); + let current_shot = unsafe { get_current_shot() }; + let shot_inputs = binding.as_ref().unwrap().get_inputs_for_shot(current_shot); + if shot_inputs.is_none() { + selene_panic(format!( + "No runtime arguments provided for shot {current_shot} (0-indexed)" + )); + } + if let Some(record) = shot_inputs.unwrap().records.get(&key) { + log(&key, record); + record.clone() + } else { + selene_panic(format!("Missing runtime argument '{key}'")); + } + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn argreader_get_bool(key_ptr: *const u8) -> bool { + match unsafe { value_helper(key_ptr) } { + InputRecord::Bool(value) => value, + InputRecord::U64(value) => { + if value == 0 { + false + } else if value == 1 { + true + } else { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects a boolean value, but received the unsigned integer {value} which is not 0 or 1", + )); + } + } + InputRecord::I64(value) => { + if value == 0 { + false + } else if value == 1 { + true + } else { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects a boolean value, but received the integer {value} which is not 0 or 1", + )); + } + } + InputRecord::F64(value) => { + if value == 0.0 { + false + } else if value == 1.0 { + true + } else { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects a boolean value, but received the float {value} which is not 0.0 or 1.0", + )); + } + } + value => { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects a boolean value, but received the non-boolean value {value:?}" + )); + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn argreader_get_u64(key_ptr: *const u8) -> u64 { + match unsafe { value_helper(key_ptr) } { + InputRecord::U64(value) => value, + InputRecord::I64(value) => { + if value < 0 { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' has a negative value ({value}), which cannot be converted to an unsigned integer", + )); + } + value as u64 + } + InputRecord::F64(value) => { + if value < 0.0 { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' has a negative floating point value ({value}), which cannot be converted to an unsigned integer", + )); + } + if value.fract() != 0.0 { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' has a non-integral floating point value {value}, which cannot be converted to an unsigned integer", + )); + } + value as u64 + } + value => { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an unsigned integer, but received the non-integer value {value:?}" + )); + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn argreader_get_i64(key_ptr: *const u8) -> i64 { + match unsafe { value_helper(key_ptr) } { + InputRecord::I64(value) => value, + InputRecord::U64(value) => { + if value > i64::MAX as u64 { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' has an unsigned value {value}, exceeding the maximum value of a signed integer", + )); + } + value as i64 + } + InputRecord::F64(value) => { + if value.fract() != 0.0 { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' has the non-integral floating point value {value}, which cannot be converted to an integer", + )); + } + if value < i64::MIN as f64 || value > i64::MAX as f64 { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' has the floating point value {value}, which exceeds signed integer bounds", + )); + } + value as i64 + } + value => { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' is not an integer: {value:?}" + )); + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn argreader_get_f64(key_ptr: *const u8) -> f64 { + match unsafe { value_helper(key_ptr) } { + InputRecord::F64(value) => value, + InputRecord::U64(value) => value as f64, + InputRecord::I64(value) => value as f64, + value => { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' is not a floating point number: {value:?}" + )); + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn argreader_get_u64_array(key_ptr: *const u8, out_ptr: *mut u64, len: u64) { + match unsafe { value_helper(key_ptr) } { + InputRecord::U64Array(values) => { + if values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} unsigned integers, but was provided an array {values:?} of length {}", + values.len() + )); + } + unsafe { + std::ptr::copy_nonoverlapping(values.as_ptr(), out_ptr, values.len()); + } + } + InputRecord::I64Array(values) => { + if values.iter().any(|&v| v < 0) { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' contains negative int values ({values:?}), and thus cannot be converted to an unsigned int array", + )); + } + let u64_values: Vec = values.clone().into_iter().map(|v| v as u64).collect(); + if u64_values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} unsigned integers, but was provided an array {values:?} of length {}", + values.len() + )); + } + unsafe { + std::ptr::copy_nonoverlapping(u64_values.as_ptr(), out_ptr, u64_values.len()); + } + } + InputRecord::F64Array(values) => { + if values.iter().any(|&v| v < 0.0) { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' contains negative floating point values ({values:?}), and thus cannot be converted to an unsigned int array", + )); + } + if values.iter().any(|&v| v.fract() != 0.0) { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' contains non-integral floating point values ({values:?}), and thus cannot be converted to an unsigned int array", + )); + } + let u64_values: Vec = values.clone().into_iter().map(|v| v as u64).collect(); + if u64_values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} unsigned integers, but was provided an array {values:?} of length {}", + values.len() + )); + } + unsafe { + std::ptr::copy_nonoverlapping(u64_values.as_ptr(), out_ptr, u64_values.len()); + } + } + value => { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} integers, but received the value {value:?}" + )); + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn argreader_get_i64_array(key_ptr: *const u8, out_ptr: *mut i64, len: u64) { + match unsafe { value_helper(key_ptr) } { + InputRecord::I64Array(values) => { + if values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} integers, but was provided an array {values:?} of length {}", + values.len() + )); + } + unsafe { + std::ptr::copy_nonoverlapping(values.as_ptr(), out_ptr, values.len()); + } + } + InputRecord::U64Array(values) => { + if values.iter().any(|&v| v > i64::MAX as u64) { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' contains unsigned int values which exceed the max value of signed ints ({values:?}), and thus cannot be converted to an int array", + )); + } + if values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} integers, but was provided an array {values:?} of length {}", + values.len() + )); + } + let i64_values: Vec = values.into_iter().map(|v| v as i64).collect(); + unsafe { + std::ptr::copy_nonoverlapping(i64_values.as_ptr(), out_ptr, i64_values.len()); + } + } + InputRecord::F64Array(values) => { + if values.iter().any(|&v| v.fract() != 0.0) { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' contains non-integral floating point numbers ({values:?}), and thus cannot be converted to an int array", + )); + } + if values + .iter() + .any(|&v| v < i64::MIN as f64 || v > i64::MAX as f64) + { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' contains floating point values which exceed signed integer bounds ({values:?})), which cannot be converted to an int array", + )); + } + let i64_values: Vec = values.clone().into_iter().map(|v| v as i64).collect(); + if i64_values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} integers, but was provided an array {values:?} of length {}", + values.len() + )); + } + unsafe { + std::ptr::copy_nonoverlapping(i64_values.as_ptr(), out_ptr, i64_values.len()); + } + } + value => { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} integers, but received the value {value:?}" + )); + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn argreader_get_f64_array(key_ptr: *const u8, out_ptr: *mut f64, len: u64) { + match unsafe { value_helper(key_ptr) } { + InputRecord::F64Array(values) => { + if values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} floats, but was provided an array {values:?} of length {}", + values.len() + )); + } + unsafe { + std::ptr::copy_nonoverlapping(values.as_ptr(), out_ptr, values.len()); + } + } + InputRecord::U64Array(values) => { + let f64_values: Vec = values.clone().into_iter().map(|v| v as f64).collect(); + if f64_values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} floats, but was provided an array {values:?} of length {}", + values.len() + )); + } + unsafe { + std::ptr::copy_nonoverlapping(f64_values.as_ptr(), out_ptr, f64_values.len()); + } + } + InputRecord::I64Array(values) => { + let f64_values: Vec = values.clone().into_iter().map(|v| v as f64).collect(); + if f64_values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} floats, but was provided an array {values:?} of length {}", + values.len() + )); + } + unsafe { + std::ptr::copy_nonoverlapping(f64_values.as_ptr(), out_ptr, f64_values.len()); + } + } + value => { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} floating point values, but received the value {value:?}" + )); + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn argreader_get_bool_array( + key_ptr: *const u8, + out_ptr: *mut bool, + len: u64, +) { + match unsafe { value_helper(key_ptr) } { + InputRecord::BoolArray(values) => { + if values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects a boolean array of length {len}, but received an array {values:?} of length {}", + values.len() + )); + } + unsafe { + std::ptr::copy_nonoverlapping(values.as_ptr(), out_ptr, values.len()); + } + } + InputRecord::I64Array(values) => { + if values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects a boolean array of length {len}, but received an array {values:?} of length {}", + values.len() + )); + } + if values.iter().any(|&v| v != 0 && v != 1) { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects a boolean array, but received the integer array {values:?} which contains non-boolean values", + )); + } + let bool_values: Vec = values.into_iter().map(|v| v == 1).collect(); + unsafe { + std::ptr::copy_nonoverlapping(bool_values.as_ptr(), out_ptr, bool_values.len()); + } + } + InputRecord::U64Array(values) => { + if values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects a boolean array of length {len}, but received an array {values:?} of length {}", + values.len() + )); + } + if values.iter().any(|&v| v != 0 && v != 1) { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects a boolean array, but received the unsigned integer array {values:?} which contains non-boolean values", + )); + } + let bool_values: Vec = values.into_iter().map(|v| v == 1).collect(); + unsafe { + std::ptr::copy_nonoverlapping(bool_values.as_ptr(), out_ptr, bool_values.len()); + } + } + InputRecord::F64Array(values) => { + if values.len() != len as usize { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects a boolean array of length {len}, but received an array {values:?} of length {}", + values.len() + )); + } + if values.iter().any(|&v| v != 0.0 && v != 1.0) { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects a boolean array, but received the float array {values:?} which contains non-boolean values", + )); + } + let bool_values: Vec = values.into_iter().map(|v| v == 1.0).collect(); + unsafe { + std::ptr::copy_nonoverlapping(bool_values.as_ptr(), out_ptr, bool_values.len()); + } + } + value => { + let key = get_key(key_ptr); + selene_panic(format!( + "Runtime argument '{key}' expects a boolean array of length {len}, but received the value {value:?}" + )); + } + } +} diff --git a/selene-sim/c/CMakeLists.txt b/selene-sim/c/CMakeLists.txt index a073088e..171d78b2 100644 --- a/selene-sim/c/CMakeLists.txt +++ b/selene-sim/c/CMakeLists.txt @@ -7,7 +7,13 @@ include(GNUInstallDirs) add_library(Selene INTERFACE) # Absolute path to Rust-built library -set(selene_lib "${CMAKE_INSTALL_PREFIX}/lib/libselene.so") +if(WIN32) + set(selene_lib "${CMAKE_INSTALL_PREFIX}/lib/libselene.dll.a") +elseif(APPLE) + set(selene_lib "${CMAKE_INSTALL_PREFIX}/lib/libselene.dylib") +else() + set(selene_lib "${CMAKE_INSTALL_PREFIX}/lib/libselene.so") +endif() # Populate properties target_include_directories(Selene INTERFACE diff --git a/selene-sim/c/include/selene/selene.h b/selene-sim/c/include/selene/selene.h index a50c7003..39c2d352 100644 --- a/selene-sim/c/include/selene/selene.h +++ b/selene-sim/c/include/selene/selene.h @@ -100,6 +100,34 @@ struct selene_void_result_t selene_local_barrier(struct SeleneInstance *instance uint64_t qubit_ids_length, uint64_t sleep_time); +/** + * Utilities can use `selene_log_custom_call` to log their activity through + * Selene traces. + * + * For example, consider a custom utility that allows the user program + * to invoke a call to some function `foo(x: u64) -> u64`, by definining + * it as a symbol that gets linked in when the utility is provided to + * `selene_sim.build()`. + * + * If `foo` invokes an opaque call to the runtime to trigger runtime-specific + * functionality, then a selene run with tracing enabled will log it as a normal + * Custom call. However, if `foo` has entirely classical behaviour (e.g. it + * calculates a SHA256 sum, or makes a HTTP request, or plays a sound, etc.) + * then it may not interact through libselene at all, and will not get logged. + * By calling `on_utility_call` by the FFI-exposed function, the utility plugin + * has the opportunity to log the foo call as a Custom operation, in any format + * it chooses. + * + * It is recommended that a utility's python frontend provides a way to decode + * the logged data back in a human-readable format, so that a user scanning the + * trace can easily understand what utility functions are being called and the + * arguments to them. + */ +struct selene_void_result_t selene_log_utility_call(struct SeleneInstance *instance, + uint64_t tag, + const uint8_t *data, + uint64_t data_length); + struct selene_void_result_t selene_on_shot_end(struct SeleneInstance *instance); struct selene_void_result_t selene_on_shot_start(struct SeleneInstance *instance, diff --git a/selene-sim/rust/emulator.rs b/selene-sim/rust/emulator.rs index bdb18644..796c3145 100644 --- a/selene-sim/rust/emulator.rs +++ b/selene-sim/rust/emulator.rs @@ -91,27 +91,23 @@ impl Emulator { } pub fn user_issued_rxy(&mut self, q0: u64, theta: f64, phi: f64) -> Result<()> { self.runtime.rxy_gate(q0, theta, phi)?; - //self.user_program_metrics.increment_rxy(); self.event_hooks .on_user_call(&Operation::RXY(q0, theta, phi)); self.process_runtime() } pub fn user_issued_rzz(&mut self, q0: u64, q1: u64, theta: f64) -> Result<()> { self.runtime.rzz_gate(q0, q1, theta)?; - //self.user_program_metrics.increment_rzz(); self.event_hooks .on_user_call(&Operation::RZZ(q0, q1, theta)); self.process_runtime() } pub fn user_issued_rz(&mut self, q0: u64, theta: f64) -> Result<()> { self.runtime.rz_gate(q0, theta)?; - //self.user_program_metrics.increment_rz(); self.event_hooks.on_user_call(&Operation::RZ(q0, theta)); self.process_runtime() } pub fn user_issued_reset(&mut self, q0: u64) -> Result<()> { self.runtime.reset(q0)?; - //self.user_program_metrics.increment_reset(); self.event_hooks.on_user_call(&Operation::Reset(q0)); self.process_runtime() } @@ -177,11 +173,18 @@ impl Emulator { } pub fn custom_runtime_call(&mut self, tag: u64, data: &[u8]) -> Result { + self.event_hooks + .on_user_call(&Operation::Custom(tag, data.to_vec())); let result = self.runtime.custom_call(tag, data)?; self.process_runtime()?; Ok(result) } + pub fn log_custom_call(&mut self, tag: u64, data: &[u8]) { + self.event_hooks + .on_user_call(&Operation::Custom(tag, data.to_vec())) + } + pub fn simulate_delay(&mut self, delay_ns: u64) -> Result<()> { self.runtime.simulate_delay(delay_ns)?; self.event_hooks diff --git a/selene-sim/rust/ffi_interface.rs b/selene-sim/rust/ffi_interface.rs index f2c7b093..b14f2cdf 100644 --- a/selene-sim/rust/ffi_interface.rs +++ b/selene-sim/rust/ffi_interface.rs @@ -658,6 +658,41 @@ pub unsafe extern "C" fn selene_custom_runtime_call( with_instance_u64(instance, |instance| instance.custom_runtime_call(tag, data)) } +/// Utilities can use `selene_log_custom_call` to log their activity through +/// Selene traces. +/// +/// For example, consider a custom utility that allows the user program +/// to invoke a call to some function `foo(x: u64) -> u64`, by definining +/// it as a symbol that gets linked in when the utility is provided to +/// `selene_sim.build()`. +/// +/// If `foo` invokes an opaque call to the runtime to trigger runtime-specific +/// functionality, then a selene run with tracing enabled will log it as a normal +/// Custom call. However, if `foo` has entirely classical behaviour (e.g. it +/// calculates a SHA256 sum, or makes a HTTP request, or plays a sound, etc.) +/// then it may not interact through libselene at all, and will not get logged. +/// By calling `on_utility_call` by the FFI-exposed function, the utility plugin +/// has the opportunity to log the foo call as a Custom operation, in any format +/// it chooses. +/// +/// It is recommended that a utility's python frontend provides a way to decode +/// the logged data back in a human-readable format, so that a user scanning the +/// trace can easily understand what utility functions are being called and the +/// arguments to them. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_log_utility_call( + instance: *mut SeleneInstance, + tag: u64, + data: *const u8, + data_length: u64, +) -> VoidResult { + let data = unsafe { std::slice::from_raw_parts(data, data_length as usize) }; + with_instance_void(instance, |instance| { + instance.emulator.log_custom_call(tag, data); + Ok(()) + }) +} + /// Simulates a delay by notifying the runtime of a period of inactivity. This may be used by utility plugins to /// emulate a classical process taking some period of time, allowing the runtime to acknowledge the time spent /// when providing timing information for subsequent batches, which in turn allows time-based noise modelling to