1use crate::error::{FunctionalError, Result};
4use serde::{Deserialize, Serialize};
5use std::path::Path;
6
7pub const SFIR_SCHEMA: &str = "softgpu-sfir-v1";
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum TypeId {
13 I32,
14 U32,
15 U64,
16}
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19#[serde(tag = "op", rename_all = "snake_case")]
20pub enum Op {
21 Const {
22 dst: String,
23 ty: TypeId,
24 value: i64,
25 },
26 GlobalId {
28 dst: String,
29 dim: u8,
30 },
31 LocalId {
32 dst: String,
33 dim: u8,
34 },
35 WorkgroupId {
36 dst: String,
37 dim: u8,
38 },
39 Add {
40 dst: String,
41 lhs: String,
42 rhs: String,
43 ty: TypeId,
44 },
45 Sub {
46 dst: String,
47 lhs: String,
48 rhs: String,
49 ty: TypeId,
50 },
51 Mul {
52 dst: String,
53 lhs: String,
54 rhs: String,
55 ty: TypeId,
56 },
57 KernargLoad {
59 dst: String,
60 offset: u32,
61 ty: TypeId,
62 },
63 LoadGlobal {
64 dst: String,
65 addr: String,
66 ty: TypeId,
67 },
68 StoreGlobal {
69 addr: String,
70 src: String,
71 ty: TypeId,
72 },
73 Ret,
74}
75
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct KernargField {
78 pub name: String,
79 pub offset: u32,
80 pub size: u32,
81 pub kind: String,
82}
83
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85pub struct Program {
86 pub schema: String,
87 pub fidelity: String,
88 pub note: String,
89 pub name: String,
90 pub source_provenance: String,
91 #[serde(default)]
92 pub kernarg_layout: Vec<KernargField>,
93 pub body: Vec<Op>,
94}
95
96impl Program {
97 pub fn validate(&self) -> Result<()> {
98 if self.schema != SFIR_SCHEMA {
99 return Err(FunctionalError::Validation {
100 detail: format!("schema '{}' != '{SFIR_SCHEMA}'", self.schema),
101 });
102 }
103 if self.fidelity != "functional" {
104 return Err(FunctionalError::Validation {
105 detail: format!("fidelity must be 'functional', got '{}'", self.fidelity),
106 });
107 }
108 if self.note != "not_gfx1201_isa_emulation" {
109 return Err(FunctionalError::Validation {
110 detail: format!(
111 "note must be 'not_gfx1201_isa_emulation', got '{}'",
112 self.note
113 ),
114 });
115 }
116 if self.body.is_empty() {
117 return Err(FunctionalError::Validation {
118 detail: "empty body".into(),
119 });
120 }
121 if !matches!(self.body.last(), Some(Op::Ret)) {
122 return Err(FunctionalError::Validation {
123 detail: "body must end with ret".into(),
124 });
125 }
126 for op in &self.body {
127 match op {
128 Op::GlobalId { dim, .. }
129 | Op::LocalId { dim, .. }
130 | Op::WorkgroupId { dim, .. }
131 if *dim > 2 =>
132 {
133 return Err(FunctionalError::Validation {
134 detail: format!("dim {dim} out of range 0..=2"),
135 });
136 }
137 _ => {}
138 }
139 }
140 Ok(())
141 }
142}
143
144pub fn load_program_str(s: &str) -> Result<Program> {
145 let p: Program = serde_json::from_str(s).map_err(|e| FunctionalError::Parse(e.to_string()))?;
146 p.validate()?;
147 Ok(p)
148}
149
150pub fn load_program_path(path: impl AsRef<Path>) -> Result<Program> {
151 let s =
152 std::fs::read_to_string(path.as_ref()).map_err(|e| FunctionalError::Io(e.to_string()))?;
153 load_program_str(&s)
154}