Skip to main content

dsfb_gpu_debug_demo/cli/
run_gpu.rs

1//! `dsfb-gpu-debug run-gpu` — run the CUDA-accelerated pipeline.
2//!
3//! Required flags:
4//!
5//! * `--fixture PATH` — canonical fixture JSON.
6//! * `--out PATH` — destination for the case file.
7//!
8//! Optional flags:
9//!
10//! * `--contract PATH` — accepted but the v0 implementation pins
11//!   `Contract::canonical()`.
12//!
13//! Exit codes:
14//! * 0 — success.
15//! * 1 — usage error.
16//! * 2 — GPU unavailable (built without `cuda` feature, or kernel failed).
17//! * 5 — I/O failure.
18
19use std::fs;
20use std::path::Path;
21use std::process::ExitCode;
22
23use dsfb_gpu_debug_core::bank::bank_hash;
24use dsfb_gpu_debug_core::casefile::emit;
25use dsfb_gpu_debug_core::contract::Contract;
26use dsfb_gpu_debug_core::motif::registry_hash;
27use dsfb_gpu_debug_core::serialize::read_fixture;
28use dsfb_gpu_debug_cuda::{build_gpu, GpuError};
29
30use super::{parse_flags, require_flag, usage_error};
31
32pub fn parse_and_run(args: &[String]) -> ExitCode {
33    let flags = match parse_flags(args) {
34        Ok(f) => f,
35        Err(message) => return usage_error(&message),
36    };
37    let fixture_path = match require_flag(&flags, "fixture") {
38        Ok(s) => s.to_string(),
39        Err(message) => return usage_error(&message),
40    };
41    let out_path = match require_flag(&flags, "out") {
42        Ok(s) => s.to_string(),
43        Err(message) => return usage_error(&message),
44    };
45    let _contract_path = flags.get("contract").cloned();
46
47    let raw = match fs::read(&fixture_path) {
48        Ok(bytes) => bytes,
49        Err(error) => {
50            eprintln!("dsfb-gpu-debug: failed to read {fixture_path}: {error}");
51            return ExitCode::from(5);
52        }
53    };
54    let events = match read_fixture(&raw) {
55        Ok(events) => events,
56        Err(error) => {
57            eprintln!("dsfb-gpu-debug: fixture parse error: {error:?}");
58            return ExitCode::from(5);
59        }
60    };
61
62    let mut contract = Contract::canonical();
63    contract.pin_bank_hash(bank_hash());
64    contract.pin_detector_registry_hash(registry_hash());
65
66    let case = match build_gpu(&events, &contract) {
67        Ok(case) => case,
68        Err(GpuError::CudaUnavailable) => {
69            eprintln!("dsfb-gpu-debug: GPU pipeline unavailable (built without --features cuda)");
70            return ExitCode::from(2);
71        }
72        Err(GpuError::KernelFailed(code)) => {
73            eprintln!("dsfb-gpu-debug: GPU kernel failed with cuda status {code}");
74            return ExitCode::from(2);
75        }
76        Err(GpuError::InvalidInput(msg)) => {
77            eprintln!("dsfb-gpu-debug: GPU dispatcher rejected input: {msg}");
78            return ExitCode::from(2);
79        }
80    };
81
82    let bytes = emit(&case);
83
84    if let Some(parent) = Path::new(&out_path).parent() {
85        if !parent.as_os_str().is_empty() {
86            if let Err(error) = fs::create_dir_all(parent) {
87                eprintln!(
88                    "dsfb-gpu-debug: could not create {}: {error}",
89                    parent.display()
90                );
91                return ExitCode::from(5);
92            }
93        }
94    }
95    if let Err(error) = fs::write(&out_path, &bytes) {
96        eprintln!("dsfb-gpu-debug: failed to write {out_path}: {error}");
97        return ExitCode::from(5);
98    }
99
100    eprintln!(
101        "dsfb-gpu-debug: wrote GPU case file ({} bytes, {} episodes, verdict={}) to {out_path}",
102        bytes.len(),
103        case.episodes.len(),
104        case.final_verdict.name(),
105    );
106    ExitCode::from(case.final_verdict.exit_code())
107}