1use crate::error::{FunctionalError, Result};
4use crate::ir::{Op, Program, TypeId};
5use crate::memory::{kernarg_load, GlobalArena};
6use serde::Serialize;
7use std::collections::BTreeMap;
8
9pub const FUNCTIONAL_MODE_MARKER: &str = "softgpu_functional_cpu_not_gfx1201_isa";
11
12pub const MAX_FLAT_WORKITEMS: u64 = 1_048_576;
14pub const MAX_WORKGROUP_FLAT: u32 = 1_024;
15pub const DEFAULT_STEP_BUDGET: u64 = 50_000_000;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct LaunchConfig {
19 pub grid: [u32; 3],
20 pub workgroup: [u32; 3],
21}
22
23impl LaunchConfig {
24 pub fn validate(&self) -> Result<()> {
25 for d in 0..3 {
26 if self.workgroup[d] == 0 {
27 return Err(FunctionalError::Validation {
28 detail: format!("workgroup[{d}] must be > 0"),
29 });
30 }
31 if self.grid[d] == 0 {
32 return Err(FunctionalError::Validation {
33 detail: format!("grid[{d}] must be > 0"),
34 });
35 }
36 if self.grid[d] % self.workgroup[d] != 0 {
37 return Err(FunctionalError::Validation {
38 detail: format!(
39 "grid[{d}]={} not divisible by workgroup[{d}]={}",
40 self.grid[d], self.workgroup[d]
41 ),
42 });
43 }
44 }
45 let wg_flat = u64::from(self.workgroup[0])
46 * u64::from(self.workgroup[1])
47 * u64::from(self.workgroup[2]);
48 if wg_flat > u64::from(MAX_WORKGROUP_FLAT) {
49 return Err(FunctionalError::Validation {
50 detail: format!("workgroup flat {wg_flat} > {MAX_WORKGROUP_FLAT}"),
51 });
52 }
53 let flat = u64::from(self.grid[0]) * u64::from(self.grid[1]) * u64::from(self.grid[2]);
54 if flat > MAX_FLAT_WORKITEMS {
55 return Err(FunctionalError::Validation {
56 detail: format!("flat workitems {flat} > {MAX_FLAT_WORKITEMS}"),
57 });
58 }
59 Ok(())
60 }
61
62 pub fn num_workgroups(&self) -> [u32; 3] {
63 [
64 self.grid[0] / self.workgroup[0],
65 self.grid[1] / self.workgroup[1],
66 self.grid[2] / self.workgroup[2],
67 ]
68 }
69}
70
71#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
72pub struct RunReport {
73 pub fidelity: &'static str,
74 pub mode: &'static str,
75 pub note: &'static str,
76 pub kernel: String,
77 pub workitems_executed: u64,
78 pub steps: u64,
79 pub grid: [u32; 3],
80 pub workgroup: [u32; 3],
81}
82
83pub fn run(
86 program: &Program,
87 launch: LaunchConfig,
88 arena: &mut GlobalArena,
89 kernarg: &[u8],
90) -> Result<RunReport> {
91 run_with_budget(program, launch, arena, kernarg, DEFAULT_STEP_BUDGET)
92}
93
94pub fn run_with_budget(
95 program: &Program,
96 launch: LaunchConfig,
97 arena: &mut GlobalArena,
98 kernarg: &[u8],
99 step_budget: u64,
100) -> Result<RunReport> {
101 program.validate()?;
102 launch.validate()?;
103
104 let nwg = launch.num_workgroups();
105 let mut steps = 0u64;
106 let mut workitems = 0u64;
107
108 for gz in 0..nwg[2] {
109 for gy in 0..nwg[1] {
110 for gx in 0..nwg[0] {
111 for lz in 0..launch.workgroup[2] {
112 for ly in 0..launch.workgroup[1] {
113 for lx in 0..launch.workgroup[0] {
114 let global = [
115 gx * launch.workgroup[0] + lx,
116 gy * launch.workgroup[1] + ly,
117 gz * launch.workgroup[2] + lz,
118 ];
119 let local = [lx, ly, lz];
120 let wg = [gx, gy, gz];
121 let used = run_workitem(
122 program,
123 arena,
124 kernarg,
125 global,
126 local,
127 wg,
128 step_budget.saturating_sub(steps),
129 )?;
130 steps = steps.saturating_add(used);
131 if steps > step_budget {
132 return Err(FunctionalError::StepBudgetExceeded { steps });
133 }
134 workitems += 1;
135 }
136 }
137 }
138 }
139 }
140 }
141
142 Ok(RunReport {
143 fidelity: "functional",
144 mode: FUNCTIONAL_MODE_MARKER,
145 note: "not_gfx1201_isa_emulation",
146 kernel: program.name.clone(),
147 workitems_executed: workitems,
148 steps,
149 grid: launch.grid,
150 workgroup: launch.workgroup,
151 })
152}
153
154fn run_workitem(
155 program: &Program,
156 arena: &mut GlobalArena,
157 kernarg: &[u8],
158 global: [u32; 3],
159 local: [u32; 3],
160 workgroup: [u32; 3],
161 budget: u64,
162) -> Result<u64> {
163 let mut regs: BTreeMap<String, i64> = BTreeMap::new();
164 let mut steps = 0u64;
165 for op in &program.body {
166 steps += 1;
167 if steps > budget {
168 return Err(FunctionalError::StepBudgetExceeded { steps });
169 }
170 match op {
171 Op::Const { dst, ty, value } => {
172 regs.insert(dst.clone(), narrow(*value, *ty)?);
173 }
174 Op::GlobalId { dst, dim } => {
175 regs.insert(dst.clone(), i64::from(global[*dim as usize]));
176 }
177 Op::LocalId { dst, dim } => {
178 regs.insert(dst.clone(), i64::from(local[*dim as usize]));
179 }
180 Op::WorkgroupId { dst, dim } => {
181 regs.insert(dst.clone(), i64::from(workgroup[*dim as usize]));
182 }
183 Op::Add { dst, lhs, rhs, ty } => {
184 let a = get_reg(®s, lhs)?;
185 let b = get_reg(®s, rhs)?;
186 regs.insert(dst.clone(), narrow(a.wrapping_add(b), *ty)?);
187 }
188 Op::Sub { dst, lhs, rhs, ty } => {
189 let a = get_reg(®s, lhs)?;
190 let b = get_reg(®s, rhs)?;
191 regs.insert(dst.clone(), narrow(a.wrapping_sub(b), *ty)?);
192 }
193 Op::Mul { dst, lhs, rhs, ty } => {
194 let a = get_reg(®s, lhs)?;
195 let b = get_reg(®s, rhs)?;
196 regs.insert(dst.clone(), narrow(a.wrapping_mul(b), *ty)?);
197 }
198 Op::KernargLoad { dst, offset, ty } => {
199 regs.insert(dst.clone(), kernarg_load(kernarg, *offset, *ty)?);
200 }
201 Op::LoadGlobal { dst, addr, ty } => {
202 let a = get_reg(®s, addr)? as u64;
203 regs.insert(dst.clone(), arena.load(a, *ty)?);
204 }
205 Op::StoreGlobal { addr, src, ty } => {
206 let a = get_reg(®s, addr)? as u64;
207 let v = get_reg(®s, src)?;
208 arena.store(a, *ty, v)?;
209 }
210 Op::Ret => return Ok(steps),
211 }
212 }
213 Err(FunctionalError::Internal(
214 "fell off end of body without ret".into(),
215 ))
216}
217
218fn get_reg(regs: &BTreeMap<String, i64>, name: &str) -> Result<i64> {
219 regs.get(name)
220 .copied()
221 .ok_or_else(|| FunctionalError::UndefinedReg {
222 name: name.to_string(),
223 })
224}
225
226fn narrow(v: i64, ty: TypeId) -> Result<i64> {
227 Ok(match ty {
228 TypeId::I32 => v as i32 as i64,
229 TypeId::U32 => (v as u32) as i64,
230 TypeId::U64 => v,
231 })
232}