Skip to content

Commit 199abb1

Browse files
committed
Simplify bytecode
1 parent 14e93c3 commit 199abb1

6 files changed

Lines changed: 159 additions & 46 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.

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: 147 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,109 @@
7474
use fidget_core::{compiler::RegOp, vm::VmData};
7575
use zerocopy::IntoBytes;
7676

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

79181
/// Serialized bytecode for external evaluation
80182
pub struct Bytecode {
@@ -96,6 +198,8 @@ impl Bytecode {
96198
}
97199

98200
/// Maximum register index used by the tape
201+
///
202+
/// This does not include the virtual register `0xFF` used for immediates
99203
pub fn reg_count(&self) -> u8 {
100204
self.reg_count
101205
}
@@ -111,33 +215,42 @@ impl Bytecode {
111215
}
112216

113217
/// Builds a new bytecode object from VM data
114-
pub fn new<const N: usize>(t: &VmData<N>) -> Self {
218+
///
219+
/// Returns an error if the reserved register (255) is in use
220+
pub fn new<const N: usize>(
221+
t: &VmData<N>,
222+
) -> Result<Self, ReservedRegister> {
115223
// The initial opcode is `OP_JUMP 0x0000_0000`
116224
let mut data = vec![u32::MAX, 0u32];
117225
let mut reg_count = 0u8;
118226
let mut mem_count = 0u32;
119227
for op in t.iter_asm() {
120-
let r = BytecodeOp::from(op);
121-
let mut word = [r as u8, 0xFF, 0xFF, 0xFF];
228+
let mut word = [0xFF; 4];
122229
let mut imm = None;
123230
let mut store_reg = |i, r| {
124-
reg_count = reg_count.max(r); // update the max reg
125-
word[i] = r;
231+
if r == u8::MAX {
232+
Err(ReservedRegister)
233+
} else {
234+
reg_count = reg_count.max(r); // update the max reg
235+
word[i] = r;
236+
Ok(())
237+
}
126238
};
127239
match op {
128240
RegOp::Input(reg, slot) | RegOp::Output(reg, slot) => {
129-
store_reg(1, reg);
241+
store_reg(1, reg)?;
130242
imm = Some(slot);
131243
}
132244

133245
RegOp::Load(reg, slot) | RegOp::Store(reg, slot) => {
134-
store_reg(1, reg);
246+
store_reg(1, reg)?;
135247
mem_count = mem_count.max(slot);
136248
imm = Some(slot);
137249
}
138250

139251
RegOp::CopyImm(out, imm_f32) => {
140-
store_reg(1, out);
252+
store_reg(1, out)?;
253+
word[2] = u8::MAX;
141254
imm = Some(imm_f32.to_bits());
142255
}
143256
RegOp::NegReg(out, reg)
@@ -158,28 +271,35 @@ impl Bytecode {
158271
| RegOp::ExpReg(out, reg)
159272
| RegOp::LnReg(out, reg)
160273
| RegOp::NotReg(out, reg) => {
161-
store_reg(1, out);
162-
store_reg(2, reg);
274+
store_reg(1, out)?;
275+
store_reg(2, reg)?;
163276
}
164277

165278
RegOp::AddRegImm(out, reg, imm_f32)
166279
| RegOp::MulRegImm(out, reg, imm_f32)
167280
| RegOp::DivRegImm(out, reg, imm_f32)
168-
| RegOp::DivImmReg(out, reg, imm_f32)
169-
| RegOp::SubImmReg(out, reg, imm_f32)
170281
| RegOp::SubRegImm(out, reg, imm_f32)
171282
| RegOp::AtanRegImm(out, reg, imm_f32)
172-
| RegOp::AtanImmReg(out, reg, imm_f32)
173283
| RegOp::MinRegImm(out, reg, imm_f32)
174284
| RegOp::MaxRegImm(out, reg, imm_f32)
175285
| RegOp::CompareRegImm(out, reg, imm_f32)
176-
| RegOp::CompareImmReg(out, reg, imm_f32)
177286
| RegOp::ModRegImm(out, reg, imm_f32)
178-
| RegOp::ModImmReg(out, reg, imm_f32)
179287
| RegOp::AndRegImm(out, reg, imm_f32)
180288
| RegOp::OrRegImm(out, reg, imm_f32) => {
181-
store_reg(1, out);
182-
store_reg(2, reg);
289+
store_reg(1, out)?;
290+
store_reg(2, reg)?;
291+
word[3] = u8::MAX;
292+
imm = Some(imm_f32.to_bits());
293+
}
294+
295+
RegOp::DivImmReg(out, reg, imm_f32)
296+
| RegOp::SubImmReg(out, reg, imm_f32)
297+
| RegOp::AtanImmReg(out, reg, imm_f32)
298+
| RegOp::CompareImmReg(out, reg, imm_f32)
299+
| RegOp::ModImmReg(out, reg, imm_f32) => {
300+
store_reg(1, out)?;
301+
store_reg(3, reg)?;
302+
word[2] = u8::MAX;
183303
imm = Some(imm_f32.to_bits());
184304
}
185305

@@ -194,22 +314,23 @@ impl Bytecode {
194314
| RegOp::ModRegReg(out, lhs, rhs)
195315
| RegOp::AndRegReg(out, lhs, rhs)
196316
| RegOp::OrRegReg(out, lhs, rhs) => {
197-
store_reg(1, out);
198-
store_reg(2, lhs);
199-
store_reg(3, rhs);
317+
store_reg(1, out)?;
318+
store_reg(2, lhs)?;
319+
store_reg(3, rhs)?;
200320
}
201-
}
321+
};
322+
word[0] = BytecodeOp::from(op) as u8;
202323
data.push(u32::from_le_bytes(word));
203324
data.push(imm.unwrap_or(0xFF000000));
204325
}
205326
// Add the final `OP_JUMP 0xFFFF_FFFF`
206327
data.extend([u32::MAX, u32::MAX]);
207328

208-
Bytecode {
329+
Ok(Bytecode {
209330
data,
210331
mem_count,
211332
reg_count,
212-
}
333+
})
213334
}
214335
}
215336

@@ -236,7 +357,7 @@ mod test {
236357
let c = ctx.constant(1.0);
237358
let out = ctx.add(x, c).unwrap();
238359
let data = VmData::<255>::new(&ctx, &[out]).unwrap();
239-
let bc = Bytecode::new(&data);
360+
let bc = Bytecode::new(&data).unwrap();
240361
let mut iter = bc.data.iter();
241362
let mut next = || *iter.next().unwrap();
242363
assert_eq!(next(), 0xFFFFFFFF); // start marker
@@ -246,10 +367,7 @@ mod test {
246367
[BytecodeOp::Input as u8, 0, 0xFF, 0xFF]
247368
);
248369
assert_eq!(next(), 0); // input slot 0
249-
assert_eq!(
250-
next().to_le_bytes(),
251-
[BytecodeOp::AddRegImm as u8, 0, 0, 0xFF]
252-
);
370+
assert_eq!(next().to_le_bytes(), [BytecodeOp::Add as u8, 0, 0, 0xFF]);
253371
assert_eq!(f32::from_bits(next()), 1.0);
254372
assert_eq!(
255373
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)