Skip to content

Commit 9ecf4e9

Browse files
committed
Simplify bytecode
1 parent 14e93c3 commit 9ecf4e9

7 files changed

Lines changed: 164 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: 150 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,117 @@
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+
pub enum BytecodeOp {
96+
Copy,
97+
Neg,
98+
Abs,
99+
Recip,
100+
Sqrt,
101+
Square,
102+
Floor,
103+
Ceil,
104+
Round,
105+
Sin,
106+
Cos,
107+
Tan,
108+
Asin,
109+
Acos,
110+
Atan,
111+
Exp,
112+
Ln,
113+
Not,
114+
Add,
115+
Sub,
116+
Mul,
117+
Div,
118+
Atan2,
119+
Compare,
120+
Mod,
121+
Min,
122+
Max,
123+
And,
124+
Or,
125+
Input,
126+
Output,
127+
Load,
128+
Store,
129+
}
130+
131+
impl From<RegOp> for BytecodeOp {
132+
fn from(op: RegOp) -> Self {
133+
match op {
134+
RegOp::Input(..) => BytecodeOp::Input,
135+
RegOp::Output(..) => BytecodeOp::Output,
136+
RegOp::NegReg(..) => BytecodeOp::Neg,
137+
RegOp::AbsReg(..) => BytecodeOp::Abs,
138+
RegOp::RecipReg(..) => BytecodeOp::Recip,
139+
RegOp::SqrtReg(..) => BytecodeOp::Sqrt,
140+
RegOp::SquareReg(..) => BytecodeOp::Square,
141+
RegOp::FloorReg(..) => BytecodeOp::Floor,
142+
RegOp::CeilReg(..) => BytecodeOp::Ceil,
143+
RegOp::RoundReg(..) => BytecodeOp::Round,
144+
RegOp::SinReg(..) => BytecodeOp::Sin,
145+
RegOp::CosReg(..) => BytecodeOp::Cos,
146+
RegOp::TanReg(..) => BytecodeOp::Tan,
147+
RegOp::AsinReg(..) => BytecodeOp::Asin,
148+
RegOp::AcosReg(..) => BytecodeOp::Acos,
149+
RegOp::AtanReg(..) => BytecodeOp::Atan,
150+
RegOp::ExpReg(..) => BytecodeOp::Exp,
151+
RegOp::LnReg(..) => BytecodeOp::Ln,
152+
RegOp::NotReg(..) => BytecodeOp::Not,
153+
RegOp::Load(..) => BytecodeOp::Load,
154+
RegOp::Store(..) => BytecodeOp::Store,
155+
RegOp::CopyImm(..) | RegOp::CopyReg(..) => BytecodeOp::Copy,
156+
157+
RegOp::AddRegReg(..) | RegOp::AddRegImm(..) => BytecodeOp::Add,
158+
RegOp::MulRegReg(..) | RegOp::MulRegImm(..) => BytecodeOp::Mul,
159+
RegOp::DivRegReg(..)
160+
| RegOp::DivRegImm(..)
161+
| RegOp::DivImmReg(..) => BytecodeOp::Div,
162+
RegOp::SubRegReg(..)
163+
| RegOp::SubRegImm(..)
164+
| RegOp::SubImmReg(..) => BytecodeOp::Sub,
165+
RegOp::AtanRegReg(..)
166+
| RegOp::AtanRegImm(..)
167+
| RegOp::AtanImmReg(..) => BytecodeOp::Atan2,
168+
RegOp::MinRegReg(..) | RegOp::MinRegImm(..) => BytecodeOp::Min,
169+
RegOp::MaxRegReg(..) | RegOp::MaxRegImm(..) => BytecodeOp::Max,
170+
RegOp::CompareRegReg(..)
171+
| RegOp::CompareRegImm(..)
172+
| RegOp::CompareImmReg(..) => BytecodeOp::Compare,
173+
RegOp::ModRegReg(..)
174+
| RegOp::ModRegImm(..)
175+
| RegOp::ModImmReg(..) => BytecodeOp::Mod,
176+
RegOp::AndRegReg(..) | RegOp::AndRegImm(..) => BytecodeOp::And,
177+
RegOp::OrRegReg(..) | RegOp::OrRegImm(..) => BytecodeOp::Or,
178+
}
179+
}
180+
}
78181

79182
/// Serialized bytecode for external evaluation
80183
pub struct Bytecode {
@@ -96,6 +199,8 @@ impl Bytecode {
96199
}
97200

98201
/// Maximum register index used by the tape
202+
///
203+
/// This does not include the virtual register `0xFF` used for immediates
99204
pub fn reg_count(&self) -> u8 {
100205
self.reg_count
101206
}
@@ -111,33 +216,42 @@ impl Bytecode {
111216
}
112217

113218
/// Builds a new bytecode object from VM data
114-
pub fn new<const N: usize>(t: &VmData<N>) -> Self {
219+
///
220+
/// Returns an error if the reserved register (255) is in use
221+
pub fn new<const N: usize>(
222+
t: &VmData<N>,
223+
) -> Result<Self, ReservedRegister> {
115224
// The initial opcode is `OP_JUMP 0x0000_0000`
116225
let mut data = vec![u32::MAX, 0u32];
117226
let mut reg_count = 0u8;
118227
let mut mem_count = 0u32;
119228
for op in t.iter_asm() {
120-
let r = BytecodeOp::from(op);
121-
let mut word = [r as u8, 0xFF, 0xFF, 0xFF];
229+
let mut word = [0xFF; 4];
122230
let mut imm = None;
123231
let mut store_reg = |i, r| {
124-
reg_count = reg_count.max(r); // update the max reg
125-
word[i] = r;
232+
if r == u8::MAX {
233+
Err(ReservedRegister)
234+
} else {
235+
reg_count = reg_count.max(r); // update the max reg
236+
word[i] = r;
237+
Ok(())
238+
}
126239
};
127240
match op {
128241
RegOp::Input(reg, slot) | RegOp::Output(reg, slot) => {
129-
store_reg(1, reg);
242+
store_reg(1, reg)?;
130243
imm = Some(slot);
131244
}
132245

133246
RegOp::Load(reg, slot) | RegOp::Store(reg, slot) => {
134-
store_reg(1, reg);
247+
store_reg(1, reg)?;
135248
mem_count = mem_count.max(slot);
136249
imm = Some(slot);
137250
}
138251

139252
RegOp::CopyImm(out, imm_f32) => {
140-
store_reg(1, out);
253+
store_reg(1, out)?;
254+
word[2] = u8::MAX;
141255
imm = Some(imm_f32.to_bits());
142256
}
143257
RegOp::NegReg(out, reg)
@@ -158,28 +272,35 @@ impl Bytecode {
158272
| RegOp::ExpReg(out, reg)
159273
| RegOp::LnReg(out, reg)
160274
| RegOp::NotReg(out, reg) => {
161-
store_reg(1, out);
162-
store_reg(2, reg);
275+
store_reg(1, out)?;
276+
store_reg(2, reg)?;
163277
}
164278

165279
RegOp::AddRegImm(out, reg, imm_f32)
166280
| RegOp::MulRegImm(out, reg, imm_f32)
167281
| RegOp::DivRegImm(out, reg, imm_f32)
168-
| RegOp::DivImmReg(out, reg, imm_f32)
169-
| RegOp::SubImmReg(out, reg, imm_f32)
170282
| RegOp::SubRegImm(out, reg, imm_f32)
171283
| RegOp::AtanRegImm(out, reg, imm_f32)
172-
| RegOp::AtanImmReg(out, reg, imm_f32)
173284
| RegOp::MinRegImm(out, reg, imm_f32)
174285
| RegOp::MaxRegImm(out, reg, imm_f32)
175286
| RegOp::CompareRegImm(out, reg, imm_f32)
176-
| RegOp::CompareImmReg(out, reg, imm_f32)
177287
| RegOp::ModRegImm(out, reg, imm_f32)
178-
| RegOp::ModImmReg(out, reg, imm_f32)
179288
| RegOp::AndRegImm(out, reg, imm_f32)
180289
| RegOp::OrRegImm(out, reg, imm_f32) => {
181-
store_reg(1, out);
182-
store_reg(2, reg);
290+
store_reg(1, out)?;
291+
store_reg(2, reg)?;
292+
word[3] = u8::MAX;
293+
imm = Some(imm_f32.to_bits());
294+
}
295+
296+
RegOp::DivImmReg(out, reg, imm_f32)
297+
| RegOp::SubImmReg(out, reg, imm_f32)
298+
| RegOp::AtanImmReg(out, reg, imm_f32)
299+
| RegOp::CompareImmReg(out, reg, imm_f32)
300+
| RegOp::ModImmReg(out, reg, imm_f32) => {
301+
store_reg(1, out)?;
302+
store_reg(3, reg)?;
303+
word[2] = u8::MAX;
183304
imm = Some(imm_f32.to_bits());
184305
}
185306

@@ -194,22 +315,23 @@ impl Bytecode {
194315
| RegOp::ModRegReg(out, lhs, rhs)
195316
| RegOp::AndRegReg(out, lhs, rhs)
196317
| RegOp::OrRegReg(out, lhs, rhs) => {
197-
store_reg(1, out);
198-
store_reg(2, lhs);
199-
store_reg(3, rhs);
318+
store_reg(1, out)?;
319+
store_reg(2, lhs)?;
320+
store_reg(3, rhs)?;
200321
}
201-
}
322+
};
323+
word[0] = BytecodeOp::from(op) as u8;
202324
data.push(u32::from_le_bytes(word));
203325
data.push(imm.unwrap_or(0xFF000000));
204326
}
205327
// Add the final `OP_JUMP 0xFFFF_FFFF`
206328
data.extend([u32::MAX, u32::MAX]);
207329

208-
Bytecode {
330+
Ok(Bytecode {
209331
data,
210332
mem_count,
211333
reg_count,
212-
}
334+
})
213335
}
214336
}
215337

@@ -236,7 +358,7 @@ mod test {
236358
let c = ctx.constant(1.0);
237359
let out = ctx.add(x, c).unwrap();
238360
let data = VmData::<255>::new(&ctx, &[out]).unwrap();
239-
let bc = Bytecode::new(&data);
361+
let bc = Bytecode::new(&data).unwrap();
240362
let mut iter = bc.data.iter();
241363
let mut next = || *iter.next().unwrap();
242364
assert_eq!(next(), 0xFFFFFFFF); // start marker
@@ -246,10 +368,7 @@ mod test {
246368
[BytecodeOp::Input as u8, 0, 0xFF, 0xFF]
247369
);
248370
assert_eq!(next(), 0); // input slot 0
249-
assert_eq!(
250-
next().to_le_bytes(),
251-
[BytecodeOp::AddRegImm as u8, 0, 0, 0xFF]
252-
);
371+
assert_eq!(next().to_le_bytes(), [BytecodeOp::Add as u8, 0, 0, 0xFF]);
253372
assert_eq!(f32::from_bits(next()), 1.0);
254373
assert_eq!(
255374
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)