hrx-rs 0.8.16

Owned native AMD GPU and NPU execution with Loom compilation
Documentation
use hrx::{
    Error, Result, Target,
    loom::{CompileReport, Compiler, CxxSource, CxxStandard, ReportMode, Specialization},
};
use std::{fs, path::Path};

pub fn compile(args: &[String]) -> Result<()> {
    let path = Path::new(&args[0]);
    let contents = fs::read_to_string(path)?;
    let mut target = Target::default();
    let mut language = match path.extension().and_then(|s| s.to_str()) {
        Some("c") => "c23".to_owned(),
        Some("cpp" | "cc" | "cxx") => "c++26".to_owned(),
        _ => "loom".to_owned(),
    };
    let mut source = CxxSource::new(path.to_string_lossy(), contents.clone());
    let mut request = Specialization::new(&args[1]);
    let mut report_output = None;
    for arg in &args[2..] {
        let (key, value) = arg.split_once('=').ok_or_else(|| {
            Error::Message("compile options use --name=value; configuration uses key=value".into())
        })?;
        match key {
            "--target" => target = Target::new(value)?,
            "--language" => language = value.into(),
            "--report" => {
                request.set_report(match value {
                    "none" => ReportMode::None,
                    "summary" => ReportMode::Summary,
                    "details" => ReportMode::Details,
                    _ => {
                        return Err(Error::Message(
                            "report mode must be none, summary, or details".into(),
                        ));
                    }
                });
            }
            "--report-output" => report_output = Some(value),
            "--include" => source.include_paths.push(value.into()),
            "--define" => {
                let (name, replacement) = value.split_once('=').unwrap_or((value, "1"));
                source.defines.push((name.into(), replacement.into()));
            }
            "--header" => {
                let (name, file) = value
                    .split_once('=')
                    .ok_or_else(|| Error::Message("--header=virtual/path=FILE".into()))?;
                source
                    .headers
                    .insert(name.into(), fs::read_to_string(file)?);
            }
            _ if key.starts_with('-') => {
                return Err(Error::Message(format!("unknown compile option {key}")));
            }
            _ => {
                request.set_config(key, value);
            }
        }
    }
    if report_output.is_some() && request.report_mode() == ReportMode::None {
        return Err(Error::Message(
            "--report-output requires --report=summary or details".into(),
        ));
    }
    let compiler = Compiler::for_target(None, &target)?;
    let module = match language.as_str() {
        "loom" => compiler.sources(vec![hrx::loom::Source::loom(
            path.to_string_lossy(),
            contents,
        )])?,
        "c23" | "c++26" => {
            source.standard = if language == "c23" {
                CxxStandard::C23
            } else {
                CxxStandard::Cpp26
            };
            compiler.import_cxx(source)?
        }
        _ => {
            return Err(Error::Message(
                "language must be loom, c23, or c++26".into(),
            ));
        }
    };
    let artifact = module.compile(&request)?;
    if let Some(path) = report_output {
        let report = artifact
            .report()
            .ok_or_else(|| Error::Message("compiler emitted no requested report".into()))?;
        fs::write(path, serde_json::to_vec_pretty(report)?)?;
    }
    println!("{}", artifact.path().display());
    Ok(())
}

pub fn report(args: &[String]) -> Result<()> {
    let load = |path: &str| -> Result<CompileReport> {
        let report: CompileReport = serde_json::from_slice(&fs::read(path)?)?;
        report.validate()?;
        Ok(report)
    };
    match args {
        [command, path] if command == "show" => {
            let report = load(path)?;
            println!(
                "{}",
                serde_json::to_string_pretty(&serde_json::json!({
                    "compiler": report.compiler_identity(), "target": report.target(),
                    "processor_mode": report.processor_mode(), "entries": report.entries()?, "wait_reasons": report.wait_reasons()?,
                    "guidance": report.guidance()?
                }))?
            );
        }
        [command, before, after] if command == "diff" => {
            let before = load(before)?;
            let after = load(after)?;
            #[derive(serde::Serialize)]
            struct Diff<'a> {
                compiler: &'a str,
                changes: Vec<hrx::loom::EntryChange>,
                wait_reason_changes: Option<Vec<hrx::loom::WaitReasonChange>>,
            }
            println!(
                "{}",
                serde_json::to_string_pretty(&Diff {
                    compiler: before.compiler_identity(),
                    changes: before.changes(&after)?,
                    wait_reason_changes: before.wait_reason_changes(&after)?,
                })?
            );
        }
        _ => {
            return Err(Error::Message(
                "usage: hrx report show FILE | report diff BEFORE AFTER".into(),
            ));
        }
    }
    Ok(())
}