nxpu_backend_stablehlo/
lib.rs1use nxpu_analysis::analyze;
7use nxpu_backend_core::{
8 Backend, BackendError, BackendOptions, BackendOutput, Diagnostic, DiagnosticLevel,
9 OutputContent, OutputFile,
10};
11use nxpu_ir::Module;
12
13mod lower;
14
15#[derive(Debug)]
17pub struct StableHloBackend;
18
19impl Backend for StableHloBackend {
20 fn name(&self) -> &str {
21 "StableHLO"
22 }
23
24 fn targets(&self) -> &[&str] {
25 &["stablehlo", "xla"]
26 }
27
28 fn compile(
29 &self,
30 module: &Module,
31 _opts: &BackendOptions,
32 ) -> Result<BackendOutput, BackendError> {
33 if module.entry_points.is_empty() {
34 return Err(BackendError::Other("no entry points in module".into()));
35 }
36
37 let mut files = Vec::new();
38 let mut diagnostics = Vec::new();
39
40 for (i, ep) in module.entry_points.iter().enumerate() {
41 let pattern = analyze::classify_entry_point(module, i).map_err(|e| {
42 BackendError::Unsupported(format!("entry point '{}': {e}", ep.name))
43 })?;
44 if let analyze::KernelPattern::Unknown { reason } = &pattern {
45 return Err(BackendError::Unsupported(format!(
46 "entry point '{}': unrecognized pattern: {reason}",
47 ep.name
48 )));
49 }
50
51 let summary = match &pattern {
52 analyze::KernelPattern::MatMul { .. } => "dot_general",
53 analyze::KernelPattern::QuantizedMatMul { .. } => "quantized matmul",
54 analyze::KernelPattern::ElementWise { op, .. } => op.op_name(),
55 analyze::KernelPattern::Conv2D { .. } => "convolution",
56 analyze::KernelPattern::Pool { .. } => "reduce_window",
57 analyze::KernelPattern::Activation { op, .. } => op.op_name(),
58 analyze::KernelPattern::Reduce { .. } => "reduce",
59 analyze::KernelPattern::Transpose { .. } => "transpose",
60 analyze::KernelPattern::Reshape { .. } => "reshape",
61 analyze::KernelPattern::Normalization { .. } => "batch_norm_inference",
62 analyze::KernelPattern::Concat { .. } => "concatenate",
63 analyze::KernelPattern::Split { .. } => "slice",
64 analyze::KernelPattern::Attention { .. } => "attention",
65 analyze::KernelPattern::Gather { .. } => "gather",
66 analyze::KernelPattern::Scatter { .. } => "scatter",
67 analyze::KernelPattern::ElementWiseChain { .. } => "element-wise chain",
68 analyze::KernelPattern::Unknown { .. } => "Unknown",
69 };
70
71 diagnostics.push(Diagnostic {
72 level: DiagnosticLevel::Info,
73 message: format!("entry point '{}': StableHLO {}", ep.name, summary),
74 });
75
76 let mlir = lower::build_mlir(&pattern, &ep.name)?;
77
78 let filename = if module.entry_points.len() == 1 {
79 "output.mlir".into()
80 } else {
81 format!("{}.mlir", ep.name)
82 };
83
84 files.push(OutputFile {
85 name: filename,
86 content: OutputContent::Text(mlir),
87 });
88 }
89
90 Ok(BackendOutput { files, diagnostics })
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use nxpu_backend_core::BackendOptions;
98
99 #[test]
100 fn backend_metadata() {
101 let backend = StableHloBackend;
102 assert_eq!(backend.name(), "StableHLO");
103 assert!(backend.targets().contains(&"stablehlo"));
104 assert!(backend.targets().contains(&"xla"));
105 }
106
107 #[test]
108 fn compile_matmul_wgsl() {
109 let source = std::fs::read_to_string(concat!(
110 env!("CARGO_MANIFEST_DIR"),
111 "/../../examples/matmul.wgsl"
112 ))
113 .unwrap();
114 let module = nxpu_parser::parse(&source).unwrap();
115
116 let output = StableHloBackend
117 .compile(&module, &BackendOptions::default())
118 .unwrap();
119 assert_eq!(output.files.len(), 1);
120 assert_eq!(output.files[0].name, "output.mlir");
121
122 let text = match &output.files[0].content {
123 OutputContent::Text(t) => t,
124 _ => panic!("expected text output"),
125 };
126 assert!(text.contains("stablehlo.dot_general"));
127 assert!(text.contains("module @main"));
128 }
129
130 #[test]
131 fn compile_vecadd_wgsl() {
132 let source = r#"
133@group(0) @binding(0) var<storage, read> a: array<f32>;
134@group(0) @binding(1) var<storage, read> b: array<f32>;
135@group(0) @binding(2) var<storage, read_write> c: array<f32>;
136struct Params { N: u32 }
137@group(0) @binding(3) var<uniform> params: Params;
138@compute @workgroup_size(256)
139fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
140 let idx = gid.x;
141 if (idx >= params.N) { return; }
142 c[idx] = a[idx] + b[idx];
143}
144"#;
145 let module = nxpu_parser::parse(source).unwrap();
146 let output = StableHloBackend
147 .compile(&module, &BackendOptions::default())
148 .unwrap();
149
150 let text = match &output.files[0].content {
151 OutputContent::Text(t) => t,
152 _ => panic!("expected text"),
153 };
154 assert!(text.contains("stablehlo.add"));
155 }
156}