Skip to content

Commit 0eb5b0c

Browse files
committed
Simplify bytecode
1 parent 14e93c3 commit 0eb5b0c

7 files changed

Lines changed: 165 additions & 48 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
- Bumped many dependencies
55
- Add `Image::build` function to build an image from a `Vec<T>` and
66
`ImageSizeLike`, returning an error if the data size is incorrect.
7+
- Revamping `fidget-bytecode`
8+
- Reserve register `u8::MAX` to represent an inline immediate
9+
- Remove separate opcodes, e.g. `SubRegReg`, `SubRegImm`, and `SubImmReg`
10+
now all generate `BytecodeOp::Sub` (using the reserved register as needed
11+
for immediates).
12+
- This also removes the `enum RegOpDiscriminants` from `fidget_core`
713

814
# 0.4.2
915
- Change `depth` member in `GeometryPixel` from `u32` to `f32` ([#381](https://github.com/mkeeter/fidget/pull/381))

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demos/web-editor/crate/Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fidget-bytecode/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ rust-version.workspace = true
1414
fidget-core.workspace = true
1515
workspace-hack.workspace = true
1616

17+
serde.workspace = true
1718
strum.workspace = true
19+
thiserror.workspace = true
1820
zerocopy.workspace = true

fidget-bytecode/src/lib.rs

Lines changed: 151 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,118 @@
6767
//!
6868
//! ## Opcode values
6969
//!
70-
//! Opcode values are generated automatically from [`BytecodeOp`]
71-
//! values, which are one-to-one with [`RegOp`] variants.
70+
//! Opcode values are generated automatically from [`BytecodeOp`] tags.
7271
#![warn(missing_docs)]
7372

7473
use fidget_core::{compiler::RegOp, vm::VmData};
7574
use zerocopy::IntoBytes;
7675

77-
pub use fidget_core::compiler::RegOpDiscriminants as BytecodeOp;
76+
/// Error type for bytecode builder
77+
#[derive(thiserror::Error, Debug, PartialEq)]
78+
#[error("register 255 is reserved")]
79+
pub struct ReservedRegister;
80+
81+
/// Operations in the bytecode tape
82+
#[derive(
83+
Copy,
84+
Clone,
85+
Debug,
86+
PartialEq,
87+
serde::Serialize,
88+
serde::Deserialize,
89+
strum::EnumIter,
90+
strum::EnumCount,
91+
strum::IntoStaticStr,
92+
strum::FromRepr,
93+
)]
94+
#[expect(missing_docs)]
95+
#[repr(u8)]
96+
pub enum BytecodeOp {
97+
Copy,
98+
Neg,
99+
Abs,
100+
Recip,
101+
Sqrt,
102+
Square,
103+
Floor,
104+
Ceil,
105+
Round,
106+
Sin,
107+
Cos,
108+
Tan,
109+
Asin,
110+
Acos,
111+
Atan,
112+
Exp,
113+
Ln,
114+
Not,
115+
Add,
116+
Sub,
117+
Mul,
118+
Div,
119+
Atan2,
120+
Compare,
121+
Mod,
122+
Min,
123+
Max,
124+
And,
125+
Or,
126+
Input,
127+
Output,
128+
Load,
129+
Store,
130+
}
131+
132+
impl From<RegOp> for BytecodeOp {
133+
fn from(op: RegOp) -> Self {
134+
match op {
135+
RegOp::Input(..) => BytecodeOp::Input,
136+
RegOp::Output(..) => BytecodeOp::Output,
137+
RegOp::NegReg(..) => BytecodeOp::Neg,
138+
RegOp::AbsReg(..) => BytecodeOp::Abs,
139+
RegOp::RecipReg(..) => BytecodeOp::Recip,
140+
RegOp::SqrtReg(..) => BytecodeOp::Sqrt,
141+
RegOp::SquareReg(..) => BytecodeOp::Square,
142+
RegOp::FloorReg(..) => BytecodeOp::Floor,
143+
RegOp::CeilReg(..) => BytecodeOp::Ceil,
144+
RegOp::RoundReg(..) => BytecodeOp::Round,
145+
RegOp::SinReg(..) => BytecodeOp::Sin,
146+
RegOp::CosReg(..) => BytecodeOp::Cos,
147+
RegOp::TanReg(..) => BytecodeOp::Tan,
148+
RegOp::AsinReg(..) => BytecodeOp::Asin,
149+
RegOp::AcosReg(..) => BytecodeOp::Acos,
150+
RegOp::AtanReg(..) => BytecodeOp::Atan,
151+
RegOp::ExpReg(..) => BytecodeOp::Exp,
152+
RegOp::LnReg(..) => BytecodeOp::Ln,
153+
RegOp::NotReg(..) => BytecodeOp::Not,
154+
RegOp::Load(..) => BytecodeOp::Load,
155+
RegOp::Store(..) => BytecodeOp::Store,
156+
RegOp::CopyImm(..) | RegOp::CopyReg(..) => BytecodeOp::Copy,
157+
158+
RegOp::AddRegReg(..) | RegOp::AddRegImm(..) => BytecodeOp::Add,
159+
RegOp::MulRegReg(..) | RegOp::MulRegImm(..) => BytecodeOp::Mul,
160+
RegOp::DivRegReg(..)
161+
| RegOp::DivRegImm(..)
162+
| RegOp::DivImmReg(..) => BytecodeOp::Div,
163+
RegOp::SubRegReg(..)
164+
| RegOp::SubRegImm(..)
165+
| RegOp::SubImmReg(..) => BytecodeOp::Sub,
166+
RegOp::AtanRegReg(..)
167+
| RegOp::AtanRegImm(..)
168+
| RegOp::AtanImmReg(..) => BytecodeOp::Atan2,
169+
RegOp::MinRegReg(..) | RegOp::MinRegImm(..) => BytecodeOp::Min,
170+
RegOp::MaxRegReg(..) | RegOp::MaxRegImm(..) => BytecodeOp::Max,
171+
RegOp::CompareRegReg(..)
172+
| RegOp::CompareRegImm(..)
173+
| RegOp::CompareImmReg(..) => BytecodeOp::Compare,
174+
RegOp::ModRegReg(..)
175+
| RegOp::ModRegImm(..)
176+
| RegOp::ModImmReg(..) => BytecodeOp::Mod,
177+
RegOp::AndRegReg(..) | RegOp::AndRegImm(..) => BytecodeOp::And,
178+
RegOp::OrRegReg(..) | RegOp::OrRegImm(..) => BytecodeOp::Or,
179+
}
180+
}
181+
}
78182

79183
/// Serialized bytecode for external evaluation
80184
pub struct Bytecode {
@@ -96,6 +200,8 @@ impl Bytecode {
96200
}
97201

98202
/// Maximum register index used by the tape
203+
///
204+
/// This does not include the virtual register `0xFF` used for immediates
99205
pub fn reg_count(&self) -> u8 {
100206
self.reg_count
101207
}
@@ -111,33 +217,42 @@ impl Bytecode {
111217
}
112218

113219
/// Builds a new bytecode object from VM data
114-
pub fn new<const N: usize>(t: &VmData<N>) -> Self {
220+
///
221+
/// Returns an error if the reserved register (255) is in use
222+
pub fn new<const N: usize>(
223+
t: &VmData<N>,
224+
) -> Result<Self, ReservedRegister> {
115225
// The initial opcode is `OP_JUMP 0x0000_0000`
116226
let mut data = vec![u32::MAX, 0u32];
117227
let mut reg_count = 0u8;
118228
let mut mem_count = 0u32;
119229
for op in t.iter_asm() {
120-
let r = BytecodeOp::from(op);
121-
let mut word = [r as u8, 0xFF, 0xFF, 0xFF];
230+
let mut word = [0xFF; 4];
122231
let mut imm = None;
123232
let mut store_reg = |i, r| {
124-
reg_count = reg_count.max(r); // update the max reg
125-
word[i] = r;
233+
if r == u8::MAX {
234+
Err(ReservedRegister)
235+
} else {
236+
reg_count = reg_count.max(r); // update the max reg
237+
word[i] = r;
238+
Ok(())
239+
}
126240
};
127241
match op {
128242
RegOp::Input(reg, slot) | RegOp::Output(reg, slot) => {
129-
store_reg(1, reg);
243+
store_reg(1, reg)?;
130244
imm = Some(slot);
131245
}
132246

133247
RegOp::Load(reg, slot) | RegOp::Store(reg, slot) => {
134-
store_reg(1, reg);
248+
store_reg(1, reg)?;
135249
mem_count = mem_count.max(slot);
136250
imm = Some(slot);
137251
}
138252

139253
RegOp::CopyImm(out, imm_f32) => {
140-
store_reg(1, out);
254+
store_reg(1, out)?;
255+
word[2] = u8::MAX;
141256
imm = Some(imm_f32.to_bits());
142257
}
143258
RegOp::NegReg(out, reg)
@@ -158,28 +273,35 @@ impl Bytecode {
158273
| RegOp::ExpReg(out, reg)
159274
| RegOp::LnReg(out, reg)
160275
| RegOp::NotReg(out, reg) => {
161-
store_reg(1, out);
162-
store_reg(2, reg);
276+
store_reg(1, out)?;
277+
store_reg(2, reg)?;
163278
}
164279

165280
RegOp::AddRegImm(out, reg, imm_f32)
166281
| RegOp::MulRegImm(out, reg, imm_f32)
167282
| RegOp::DivRegImm(out, reg, imm_f32)
168-
| RegOp::DivImmReg(out, reg, imm_f32)
169-
| RegOp::SubImmReg(out, reg, imm_f32)
170283
| RegOp::SubRegImm(out, reg, imm_f32)
171284
| RegOp::AtanRegImm(out, reg, imm_f32)
172-
| RegOp::AtanImmReg(out, reg, imm_f32)
173285
| RegOp::MinRegImm(out, reg, imm_f32)
174286
| RegOp::MaxRegImm(out, reg, imm_f32)
175287
| RegOp::CompareRegImm(out, reg, imm_f32)
176-
| RegOp::CompareImmReg(out, reg, imm_f32)
177288
| RegOp::ModRegImm(out, reg, imm_f32)
178-
| RegOp::ModImmReg(out, reg, imm_f32)
179289
| RegOp::AndRegImm(out, reg, imm_f32)
180290
| RegOp::OrRegImm(out, reg, imm_f32) => {
181-
store_reg(1, out);
182-
store_reg(2, reg);
291+
store_reg(1, out)?;
292+
store_reg(2, reg)?;
293+
word[3] = u8::MAX;
294+
imm = Some(imm_f32.to_bits());
295+
}
296+
297+
RegOp::DivImmReg(out, reg, imm_f32)
298+
| RegOp::SubImmReg(out, reg, imm_f32)
299+
| RegOp::AtanImmReg(out, reg, imm_f32)
300+
| RegOp::CompareImmReg(out, reg, imm_f32)
301+
| RegOp::ModImmReg(out, reg, imm_f32) => {
302+
store_reg(1, out)?;
303+
store_reg(3, reg)?;
304+
word[2] = u8::MAX;
183305
imm = Some(imm_f32.to_bits());
184306
}
185307

@@ -194,22 +316,23 @@ impl Bytecode {
194316
| RegOp::ModRegReg(out, lhs, rhs)
195317
| RegOp::AndRegReg(out, lhs, rhs)
196318
| RegOp::OrRegReg(out, lhs, rhs) => {
197-
store_reg(1, out);
198-
store_reg(2, lhs);
199-
store_reg(3, rhs);
319+
store_reg(1, out)?;
320+
store_reg(2, lhs)?;
321+
store_reg(3, rhs)?;
200322
}
201-
}
323+
};
324+
word[0] = BytecodeOp::from(op) as u8;
202325
data.push(u32::from_le_bytes(word));
203326
data.push(imm.unwrap_or(0xFF000000));
204327
}
205328
// Add the final `OP_JUMP 0xFFFF_FFFF`
206329
data.extend([u32::MAX, u32::MAX]);
207330

208-
Bytecode {
331+
Ok(Bytecode {
209332
data,
210333
mem_count,
211334
reg_count,
212-
}
335+
})
213336
}
214337
}
215338

@@ -236,7 +359,7 @@ mod test {
236359
let c = ctx.constant(1.0);
237360
let out = ctx.add(x, c).unwrap();
238361
let data = VmData::<255>::new(&ctx, &[out]).unwrap();
239-
let bc = Bytecode::new(&data);
362+
let bc = Bytecode::new(&data).unwrap();
240363
let mut iter = bc.data.iter();
241364
let mut next = || *iter.next().unwrap();
242365
assert_eq!(next(), 0xFFFFFFFF); // start marker
@@ -246,10 +369,7 @@ mod test {
246369
[BytecodeOp::Input as u8, 0, 0xFF, 0xFF]
247370
);
248371
assert_eq!(next(), 0); // input slot 0
249-
assert_eq!(
250-
next().to_le_bytes(),
251-
[BytecodeOp::AddRegImm as u8, 0, 0, 0xFF]
252-
);
372+
assert_eq!(next().to_le_bytes(), [BytecodeOp::Add as u8, 0, 0, 0xFF]);
253373
assert_eq!(f32::from_bits(next()), 1.0);
254374
assert_eq!(
255375
next().to_le_bytes(),

fidget-core/src/compiler/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ mod op;
1414

1515
mod lru;
1616
pub(crate) use lru::Lru;
17-
pub use op::{RegOp, RegOpDiscriminants, SsaOp};
17+
pub use op::{RegOp, SsaOp};
1818

1919
mod reg_tape;
2020
mod ssa_tape;

fidget-core/src/compiler/op.rs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -275,22 +275,7 @@ opcodes!(
275275
///
276276
/// We have a maximum of 256 registers, though some tapes (e.g. ones
277277
/// targeting physical hardware) may choose to use fewer.
278-
#[derive(
279-
Copy,
280-
Clone,
281-
Debug,
282-
PartialEq,
283-
Serialize,
284-
Deserialize,
285-
strum::EnumDiscriminants,
286-
)]
287-
#[strum_discriminants(derive(
288-
strum::EnumIter,
289-
strum::EnumCount,
290-
strum::IntoStaticStr,
291-
strum::FromRepr,
292-
))]
293-
#[strum_discriminants(doc = "[`RegOp`] discriminant value")]
278+
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
294279
pub enum RegOp<u8> {
295280
// default variants
296281
/// Read from a memory slot to a register

0 commit comments

Comments
 (0)