Skip to main content

softgpu_functional/
ir.rs

1//! SoftGPU Functional IR (`softgpu-sfir-v1`).
2
3use crate::error::{FunctionalError, Result};
4use serde::{Deserialize, Serialize};
5use std::path::Path;
6
7/// Schema id embedded in every program document.
8pub 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    /// SoftGPU global linear id for dimension 0/1/2.
27    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    /// Load pointer/value from kernarg blob at byte offset.
58    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}