1#![warn(missing_docs)]
45
46use fidget_core::{compiler::RegOp, vm::VmData};
47use zerocopy::IntoBytes;
48
49#[derive(thiserror::Error, Debug, PartialEq)]
51#[error("register 255 is reserved")]
52pub struct ReservedRegister;
53
54#[derive(
56 Copy,
57 Clone,
58 Debug,
59 PartialEq,
60 serde::Serialize,
61 serde::Deserialize,
62 strum::EnumIter,
63 strum::EnumCount,
64 strum::IntoStaticStr,
65 strum::FromRepr,
66)]
67#[expect(missing_docs)]
68#[repr(u8)]
69pub enum BytecodeOp {
70 Output,
71 Input,
72 Copy,
73 Neg,
74 Abs,
75 Recip,
76 Sqrt,
77 Square,
78 Floor,
79 Ceil,
80 Round,
81 Not,
82 Sin,
83 Cos,
84 Tan,
85 Asin,
86 Acos,
87 Atan,
88 Exp,
89 Ln,
90 Add,
91 Sub,
92 Mul,
93 Div,
94 Atan2,
95 Compare,
96 Mod,
97 Min,
98 Max,
99 And,
100 Or,
101 Mem,
102}
103
104impl From<RegOp> for BytecodeOp {
105 fn from(op: RegOp) -> Self {
106 match op {
107 RegOp::Input(..) => BytecodeOp::Input,
108 RegOp::Output(..) => BytecodeOp::Output,
109 RegOp::NegReg(..) => BytecodeOp::Neg,
110 RegOp::AbsReg(..) => BytecodeOp::Abs,
111 RegOp::RecipReg(..) => BytecodeOp::Recip,
112 RegOp::SqrtReg(..) => BytecodeOp::Sqrt,
113 RegOp::SquareReg(..) => BytecodeOp::Square,
114 RegOp::FloorReg(..) => BytecodeOp::Floor,
115 RegOp::CeilReg(..) => BytecodeOp::Ceil,
116 RegOp::RoundReg(..) => BytecodeOp::Round,
117 RegOp::SinReg(..) => BytecodeOp::Sin,
118 RegOp::CosReg(..) => BytecodeOp::Cos,
119 RegOp::TanReg(..) => BytecodeOp::Tan,
120 RegOp::AsinReg(..) => BytecodeOp::Asin,
121 RegOp::AcosReg(..) => BytecodeOp::Acos,
122 RegOp::AtanReg(..) => BytecodeOp::Atan,
123 RegOp::ExpReg(..) => BytecodeOp::Exp,
124 RegOp::LnReg(..) => BytecodeOp::Ln,
125 RegOp::NotReg(..) => BytecodeOp::Not,
126 RegOp::Load(..) | RegOp::Store(..) => BytecodeOp::Mem,
127 RegOp::CopyImm(..) | RegOp::CopyReg(..) => BytecodeOp::Copy,
128
129 RegOp::AddRegReg(..) | RegOp::AddRegImm(..) => BytecodeOp::Add,
130 RegOp::MulRegReg(..) | RegOp::MulRegImm(..) => BytecodeOp::Mul,
131 RegOp::DivRegReg(..)
132 | RegOp::DivRegImm(..)
133 | RegOp::DivImmReg(..) => BytecodeOp::Div,
134 RegOp::SubRegReg(..)
135 | RegOp::SubRegImm(..)
136 | RegOp::SubImmReg(..) => BytecodeOp::Sub,
137 RegOp::AtanRegReg(..)
138 | RegOp::AtanRegImm(..)
139 | RegOp::AtanImmReg(..) => BytecodeOp::Atan2,
140 RegOp::MinRegReg(..) | RegOp::MinRegImm(..) => BytecodeOp::Min,
141 RegOp::MaxRegReg(..) | RegOp::MaxRegImm(..) => BytecodeOp::Max,
142 RegOp::CompareRegReg(..)
143 | RegOp::CompareRegImm(..)
144 | RegOp::CompareImmReg(..) => BytecodeOp::Compare,
145 RegOp::ModRegReg(..)
146 | RegOp::ModRegImm(..)
147 | RegOp::ModImmReg(..) => BytecodeOp::Mod,
148 RegOp::AndRegReg(..) | RegOp::AndRegImm(..) => BytecodeOp::And,
149 RegOp::OrRegReg(..) | RegOp::OrRegImm(..) => BytecodeOp::Or,
150 }
151 }
152}
153
154pub struct Bytecode {
156 reg_count: u8,
157 mem_count: u32,
158 data: Vec<u32>,
159}
160
161impl Bytecode {
162 #[allow(clippy::len_without_is_empty)]
164 pub fn len(&self) -> usize {
165 self.data.len()
166 }
167
168 pub fn data(&self) -> &[u32] {
170 &self.data
171 }
172
173 pub fn reg_count(&self) -> u8 {
177 self.reg_count
178 }
179
180 pub fn mem_count(&self) -> u32 {
182 self.mem_count
183 }
184
185 pub fn as_bytes(&self) -> &[u8] {
187 self.data.as_bytes()
188 }
189
190 pub fn new<const N: usize>(
198 t: &VmData<N>,
199 ) -> Result<Self, ReservedRegister> {
200 let map = t.asm().repack_map();
202 let mut data = vec![u32::MAX, 0u32];
204 let mut reg_count = 0u8;
205 let mut mem_count = 0u32;
206 let mem_offset = u32::try_from(N).unwrap();
207 for op in t.iter_asm() {
208 let mut word = [0xFF; 4];
209 let mut imm = None;
210 let mut store_reg = |i, r| {
211 let r = map[&r];
212 if r == u8::MAX {
213 Err(ReservedRegister)
214 } else {
215 reg_count = reg_count.max(r + 1);
216 word[i] = r;
217 Ok(())
218 }
219 };
220 match op {
221 RegOp::Input(reg, slot) | RegOp::Output(reg, slot) => {
222 store_reg(1, reg)?;
223 imm = Some(slot);
224 }
225
226 RegOp::Load(reg, slot) => {
227 store_reg(1, reg)?;
228 word[2] = u8::MAX;
229 mem_count = mem_count.max(slot + 1 - mem_offset);
230 imm = Some(slot - mem_offset);
231 }
232 RegOp::Store(reg, slot) => {
233 store_reg(2, reg)?;
234 word[1] = u8::MAX;
235 mem_count = mem_count.max(slot + 1 - mem_offset);
236 imm = Some(slot - mem_offset);
237 }
238
239 RegOp::CopyImm(out, imm_f32) => {
240 store_reg(1, out)?;
241 word[2] = u8::MAX;
242 imm = Some(imm_f32.to_bits());
243 }
244 RegOp::NegReg(out, reg)
245 | RegOp::AbsReg(out, reg)
246 | RegOp::RecipReg(out, reg)
247 | RegOp::SqrtReg(out, reg)
248 | RegOp::SquareReg(out, reg)
249 | RegOp::FloorReg(out, reg)
250 | RegOp::CeilReg(out, reg)
251 | RegOp::RoundReg(out, reg)
252 | RegOp::CopyReg(out, reg)
253 | RegOp::SinReg(out, reg)
254 | RegOp::CosReg(out, reg)
255 | RegOp::TanReg(out, reg)
256 | RegOp::AsinReg(out, reg)
257 | RegOp::AcosReg(out, reg)
258 | RegOp::AtanReg(out, reg)
259 | RegOp::ExpReg(out, reg)
260 | RegOp::LnReg(out, reg)
261 | RegOp::NotReg(out, reg) => {
262 store_reg(1, out)?;
263 store_reg(2, reg)?;
264 }
265
266 RegOp::AddRegImm(out, reg, imm_f32)
267 | RegOp::MulRegImm(out, reg, imm_f32)
268 | RegOp::DivRegImm(out, reg, imm_f32)
269 | RegOp::SubRegImm(out, reg, imm_f32)
270 | RegOp::AtanRegImm(out, reg, imm_f32)
271 | RegOp::MinRegImm(out, reg, imm_f32)
272 | RegOp::MaxRegImm(out, reg, imm_f32)
273 | RegOp::CompareRegImm(out, reg, imm_f32)
274 | RegOp::ModRegImm(out, reg, imm_f32)
275 | RegOp::AndRegImm(out, reg, imm_f32)
276 | RegOp::OrRegImm(out, reg, imm_f32) => {
277 store_reg(1, out)?;
278 store_reg(2, reg)?;
279 word[3] = u8::MAX;
280 imm = Some(imm_f32.to_bits());
281 }
282
283 RegOp::DivImmReg(out, reg, imm_f32)
284 | RegOp::SubImmReg(out, reg, imm_f32)
285 | RegOp::AtanImmReg(out, reg, imm_f32)
286 | RegOp::CompareImmReg(out, reg, imm_f32)
287 | RegOp::ModImmReg(out, reg, imm_f32) => {
288 store_reg(1, out)?;
289 store_reg(3, reg)?;
290 word[2] = u8::MAX;
291 imm = Some(imm_f32.to_bits());
292 }
293
294 RegOp::AddRegReg(out, lhs, rhs)
295 | RegOp::MulRegReg(out, lhs, rhs)
296 | RegOp::DivRegReg(out, lhs, rhs)
297 | RegOp::SubRegReg(out, lhs, rhs)
298 | RegOp::AtanRegReg(out, lhs, rhs)
299 | RegOp::MinRegReg(out, lhs, rhs)
300 | RegOp::MaxRegReg(out, lhs, rhs)
301 | RegOp::CompareRegReg(out, lhs, rhs)
302 | RegOp::ModRegReg(out, lhs, rhs)
303 | RegOp::AndRegReg(out, lhs, rhs)
304 | RegOp::OrRegReg(out, lhs, rhs) => {
305 store_reg(1, out)?;
306 store_reg(2, lhs)?;
307 store_reg(3, rhs)?;
308 }
309 };
310 word[0] = BytecodeOp::from(op) as u8;
311 data.push(u32::from_le_bytes(word));
312 data.push(imm.unwrap_or(0xFF000000));
313 }
314 data.extend([u32::MAX, u32::MAX]);
316
317 Ok(Bytecode {
318 data,
319 mem_count,
320 reg_count,
321 })
322 }
323}
324
325pub fn iter_ops<'a>() -> impl Iterator<Item = (&'a str, u8)> {
329 use strum::IntoEnumIterator;
330
331 BytecodeOp::iter().enumerate().map(|(i, op)| {
332 let s: &'static str = op.into();
333 (s, i as u8)
334 })
335}
336
337#[cfg(test)]
338mod test {
339 use super::*;
340
341 #[test]
342 fn simple_bytecode() {
343 let mut ctx = fidget_core::Context::new();
344 let x = ctx.x();
345 let c = ctx.constant(1.0);
346 let out = ctx.add(x, c).unwrap();
347 let data = VmData::<255>::new(&ctx, &[out]).unwrap();
348 let bc = Bytecode::new(&data).unwrap();
349 let mut iter = bc.data.iter();
350 let mut next = || *iter.next().unwrap();
351 assert_eq!(next(), 0xFFFFFFFF); assert_eq!(next(), 0);
353 assert_eq!(
354 next().to_le_bytes(),
355 [BytecodeOp::Input as u8, 0, 0xFF, 0xFF]
356 );
357 assert_eq!(next(), 0); assert_eq!(next().to_le_bytes(), [BytecodeOp::Add as u8, 0, 0, 0xFF]);
359 assert_eq!(f32::from_bits(next()), 1.0);
360 assert_eq!(
361 next().to_le_bytes(),
362 [BytecodeOp::Output as u8, 0, 0xFF, 0xFF]
363 );
364 assert_eq!(next(), 0); assert_eq!(next(), 0xFFFFFFFF); assert_eq!(next(), 0xFFFFFFFF);
367 assert!(iter.next().is_none());
368 }
369
370 #[test]
371 fn load_store() {
372 let mut ctx = fidget_core::Context::new();
374 let x = ctx.x();
375 let y = ctx.y();
376 let z = ctx.z();
377 let xy = ctx.max(x, y).unwrap();
378 let out = ctx.max(xy, z).unwrap();
379 let data = VmData::<2>::new(&ctx, &[out]).unwrap();
380 let bc = Bytecode::new(&data).unwrap();
381 assert_eq!(bc.reg_count(), 2);
382 assert_eq!(bc.mem_count(), 1);
383 let mut iter = bc.data.iter();
384 let mut next = || *iter.next().unwrap();
385 assert_eq!(next(), 0xFFFFFFFF); assert_eq!(next(), 0);
387
388 assert_eq!(
390 next().to_le_bytes(),
391 [BytecodeOp::Input as u8, 1, 0xFF, 0xFF]
392 );
393 assert_eq!(next(), 2); assert_eq!(
397 next().to_le_bytes(),
398 [BytecodeOp::Mem as u8, 0xFF, 1, 0xFF]
399 );
400 assert_eq!(next(), 0);
401
402 assert_eq!(
404 next().to_le_bytes(),
405 [BytecodeOp::Input as u8, 1, 0xFF, 0xFF]
406 );
407 assert_eq!(next(), 1); assert_eq!(
411 next().to_le_bytes(),
412 [BytecodeOp::Input as u8, 0, 0xFF, 0xFF]
413 );
414 assert_eq!(next(), 0); assert_eq!(next().to_le_bytes(), [BytecodeOp::Max as u8, 1, 1, 0]);
418 assert_eq!(next(), 0xFF000000);
419
420 assert_eq!(
422 next().to_le_bytes(),
423 [BytecodeOp::Mem as u8, 0, 0xFF, 0xFF]
424 );
425 assert_eq!(next(), 0);
426
427 assert_eq!(next().to_le_bytes(), [BytecodeOp::Max as u8, 0, 0, 1]);
429 assert_eq!(next(), 0xFF000000);
430
431 assert_eq!(
433 next().to_le_bytes(),
434 [BytecodeOp::Output as u8, 0, 0xFF, 0xFF]
435 );
436 assert_eq!(next(), 0);
437
438 assert_eq!(next(), 0xFFFFFFFF); assert_eq!(next(), 0xFFFFFFFF);
440 assert!(iter.next().is_none());
441 }
442}