1use crate::error::{FunctionalError, Result};
8use serde::{Deserialize, Serialize};
9use std::path::Path;
10
11pub const SFIR_SCHEMA: &str = "softgpu-sfir-v1";
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum TypeId {
17 I32,
18 U32,
19 U64,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum AddrSpace {
25 Global,
26 Group,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum AtomicScope {
32 Workgroup,
34 Device,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "snake_case")]
40pub enum AtomicOrder {
41 Relaxed,
43 AcqRel,
44}
45
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47#[serde(tag = "op", rename_all = "snake_case")]
48pub enum Op {
49 Const {
50 dst: String,
51 ty: TypeId,
52 value: i64,
53 },
54 GlobalId {
55 dst: String,
56 dim: u8,
57 },
58 LocalId {
59 dst: String,
60 dim: u8,
61 },
62 WorkgroupId {
63 dst: String,
64 dim: u8,
65 },
66 LaneId {
68 dst: String,
69 },
70 WaveId {
72 dst: String,
73 },
74 WaveSize {
75 dst: String,
76 },
77 Add {
78 dst: String,
79 lhs: String,
80 rhs: String,
81 ty: TypeId,
82 },
83 Sub {
84 dst: String,
85 lhs: String,
86 rhs: String,
87 ty: TypeId,
88 },
89 Mul {
90 dst: String,
91 lhs: String,
92 rhs: String,
93 ty: TypeId,
94 },
95 CmpEq {
96 dst: String,
97 lhs: String,
98 rhs: String,
99 ty: TypeId,
100 },
101 CmpNe {
102 dst: String,
103 lhs: String,
104 rhs: String,
105 ty: TypeId,
106 },
107 And {
109 dst: String,
110 lhs: String,
111 rhs: String,
112 ty: TypeId,
113 },
114 KernargLoad {
115 dst: String,
116 offset: u32,
117 ty: TypeId,
118 },
119 LoadGlobal {
120 dst: String,
121 addr: String,
122 ty: TypeId,
123 },
124 StoreGlobal {
125 addr: String,
126 src: String,
127 ty: TypeId,
128 },
129 LoadGroup {
130 dst: String,
131 addr: String,
132 ty: TypeId,
133 },
134 StoreGroup {
135 addr: String,
136 src: String,
137 ty: TypeId,
138 },
139 Barrier,
141 If {
144 cond: String,
145 then_body: Vec<Op>,
146 #[serde(default)]
147 else_body: Vec<Op>,
148 },
149 While {
151 cond: String,
152 body: Vec<Op>,
153 },
154 AtomicAdd {
156 dst: String,
157 addr: String,
158 src: String,
159 space: AddrSpace,
160 scope: AtomicScope,
161 order: AtomicOrder,
162 },
163 Ret,
164}
165
166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167pub struct KernargField {
168 pub name: String,
169 pub offset: u32,
170 pub size: u32,
171 pub kind: String,
172}
173
174#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
175pub struct Program {
176 pub schema: String,
177 pub fidelity: String,
178 pub note: String,
179 pub name: String,
180 pub source_provenance: String,
181 #[serde(default)]
182 pub kernarg_layout: Vec<KernargField>,
183 #[serde(default)]
185 pub group_bytes: u32,
186 pub body: Vec<Op>,
187}
188
189impl Program {
190 pub fn validate(&self) -> Result<()> {
191 if self.schema != SFIR_SCHEMA {
192 return Err(FunctionalError::Validation {
193 detail: format!("schema '{}' != '{SFIR_SCHEMA}'", self.schema),
194 });
195 }
196 if self.fidelity != "functional" {
197 return Err(FunctionalError::Validation {
198 detail: format!("fidelity must be 'functional', got '{}'", self.fidelity),
199 });
200 }
201 if self.note != "not_gfx1201_isa_emulation" {
202 return Err(FunctionalError::Validation {
203 detail: format!(
204 "note must be 'not_gfx1201_isa_emulation', got '{}'",
205 self.note
206 ),
207 });
208 }
209 if self.body.is_empty() {
210 return Err(FunctionalError::Validation {
211 detail: "empty body".into(),
212 });
213 }
214 if !matches!(self.body.last(), Some(Op::Ret)) {
215 return Err(FunctionalError::Validation {
216 detail: "body must end with ret".into(),
217 });
218 }
219 validate_ops(&self.body, true)?;
220 Ok(())
221 }
222
223 pub fn has_barrier(&self) -> bool {
224 ops_have_barrier(&self.body)
225 }
226}
227
228fn ops_have_barrier(ops: &[Op]) -> bool {
229 for op in ops {
230 match op {
231 Op::Barrier => return true,
232 Op::If {
233 then_body,
234 else_body,
235 ..
236 } => {
237 if ops_have_barrier(then_body) || ops_have_barrier(else_body) {
238 return true;
239 }
240 }
241 Op::While { body, .. } if ops_have_barrier(body) => return true,
242 _ => {}
243 }
244 }
245 false
246}
247
248fn validate_ops(ops: &[Op], allow_barrier: bool) -> Result<()> {
249 for op in ops {
250 match op {
251 Op::GlobalId { dim, .. } | Op::LocalId { dim, .. } | Op::WorkgroupId { dim, .. }
252 if *dim > 2 =>
253 {
254 return Err(FunctionalError::Validation {
255 detail: format!("dim {dim} out of range 0..=2"),
256 });
257 }
258 Op::Barrier if !allow_barrier => {
259 return Err(FunctionalError::Validation {
260 detail: "barrier is illegal inside if/while (divergent barrier unsupported)"
261 .into(),
262 });
263 }
264 Op::If {
265 then_body,
266 else_body,
267 ..
268 } => {
269 validate_ops(then_body, false)?;
270 validate_ops(else_body, false)?;
271 }
272 Op::While { body, .. } => {
273 validate_ops(body, false)?;
274 }
275 Op::AtomicAdd { .. } => {}
276 Op::Ret => {}
277 _ => {}
278 }
279 }
280 Ok(())
281}
282
283pub fn barrier_segments(body: &[Op]) -> Result<Vec<Vec<Op>>> {
285 let mut segs = Vec::new();
286 let mut cur = Vec::new();
287 for op in body {
288 match op {
289 Op::Barrier => {
290 segs.push(std::mem::take(&mut cur));
291 }
292 Op::Ret => {
293 segs.push(std::mem::take(&mut cur));
294 break;
295 }
296 other => cur.push(other.clone()),
297 }
298 }
299 if segs.is_empty() {
300 return Err(FunctionalError::Validation {
301 detail: "empty barrier segments".into(),
302 });
303 }
304 Ok(segs)
305}
306
307pub fn load_program_str(s: &str) -> Result<Program> {
308 let p: Program = serde_json::from_str(s).map_err(|e| FunctionalError::Parse(e.to_string()))?;
309 p.validate()?;
310 Ok(p)
311}
312
313pub fn load_program_path(path: impl AsRef<Path>) -> Result<Program> {
314 let s =
315 std::fs::read_to_string(path.as_ref()).map_err(|e| FunctionalError::Io(e.to_string()))?;
316 load_program_str(&s)
317}