fern_vm/
opcode.rs

1// Copyright (C) 2024 Ethan Uppal. All rights reserved.
2
3use enum_tags::enum_tags;
4
5use crate::arch::{bitmask, LocalAddress, Word, LOCAL_ADDRESS_BITS};
6
7/// Smallest sized integer type that can fit an op code.
8pub type RawOpCode = u8;
9
10/// Smallest sized integer type that can fit an immediate value.
11pub type Immediate = u16;
12
13/// Smallest sized integer type that can fit an extended immediate value.
14pub type ExtendedImmediate = u32;
15
16/// Bits for opcode.
17pub const OPCODE_BITS: usize = 8; // not using `::BITS` here because types
18                                  // are just smallest thing that can fit,                                    //
19                                  // this is actual number of bits, which may be less.
20/// Bits for immediate value.
21pub const IMM_BITS: usize = 16;
22
23/// Bits for extended immediate value.
24pub const IMM_EXT_BITS: usize = 24;
25
26#[rustfmt::skip]
27 mod encoding_spec {
28     use super::*;
29     use static_assertions::{const_assert, const_assert_eq};
30
31     macro_rules! bits {
32         ($T:ty) => {
33             (8 * std::mem::size_of::<$T>())
34         };
35     }
36
37//   +---------------------------------------------------------------------------------+
38//   | Encodings (inspired by Lua). Ops fit in one `Word`.                             |
39//   +---------------------------------------------------------------------------------+
40//   | ABC (3 addresses):                                                              |
41       const_assert_eq!(bits![Word], OPCODE_BITS + 3 * LOCAL_ADDRESS_BITS); 
42//   | AB (2 addresses):                                                               |
43       const_assert!(OPCODE_BITS + 2 * LOCAL_ADDRESS_BITS <= bits![Word]); 
44//   | AI (address + immediate)                                                        |
45       const_assert_eq!(bits![Word], OPCODE_BITS + LOCAL_ADDRESS_BITS + IMM_BITS); 
46//   | IX (extended immediate)                                                         |
47       const_assert_eq!(bits![Word], OPCODE_BITS + IMM_EXT_BITS);
48//   | N (no operands)                                                                 | 
49       const_assert!(OPCODE_BITS <= bits![Word]);
50//   +---------------------------------------------------------------------------------+
51}
52
53/// A VM operation.
54#[repr(u8)]
55#[enum_tags]
56#[derive(Clone, Copy)]
57pub enum Op {
58    /// `Self::Mov(a, b)` copies the contents at address `b` to `a`.
59    Mov(LocalAddress, LocalAddress),
60    /// `Self::MovI(a, i)` loads `i` at address `a`.
61    MovI(LocalAddress, Immediate),
62    /// `Self::Add(a, b, c)` loads the sum of the contents at addresses `b` and
63    /// `c`  at address `a`.
64    Add(LocalAddress, LocalAddress, LocalAddress),
65    /// `Self::Call(ix)` saves the instruction pointer and jumps to the `ix`th
66    /// code instruction, pushing a new call frame.
67    Call(ExtendedImmediate),
68    /// `Self::Ret` restores the previous call frame and restores the
69    /// instruction pointer.
70    Ret,
71    /// `Self::Nop` has no effect.
72    Nop,
73}
74
75impl Op {
76    /// Encodes this operation as a [`Word`].
77    pub fn encode_packed(&self) -> Word {
78        let encoded_args = match *self {
79            Self::Mov(a, b) => Self::encode_packed_ab_args(a, b),
80            Self::MovI(a, i) => Self::encode_packed_ai_args(a, i),
81            Self::Add(a, b, c) => Self::encode_packed_abc_args(a, b, c),
82            Self::Call(ix) => Self::encode_packed_ix_args(ix),
83            Self::Ret | Self::Nop => 0,
84        };
85
86        (self.opcode() as Word) | (encoded_args << OPCODE_BITS)
87    }
88
89    /// Decodes this operation from a [`Word`].
90    pub fn decode_packed(word: Word) -> Option<Self> {
91        let opcode = (word & bitmask(OPCODE_BITS)) as RawOpCode;
92        let args = word >> OPCODE_BITS;
93        match opcode {
94            Self::MOV_TAG => Self::decode_packed_ab_args(args, Self::Mov),
95            Self::MOVI_TAG => Self::decode_packed_ai_args(args, Self::MovI),
96            Self::ADD_TAG => Self::decode_packed_abc_args(args, Self::Add),
97            Self::CALL_TAG => Self::decode_packed_ix_args(args, Self::Call),
98            Self::RET_TAG => Some(Self::Ret),
99            Self::NOP_TAG => Some(Self::Nop),
100            _ => None,
101        }
102    }
103
104    /// # Safety
105    /// See <https://doc.rust-lang.org/std/mem/fn.discriminant.html>
106    pub fn opcode(&self) -> RawOpCode {
107        unsafe { *<*const _>::from(self).cast::<RawOpCode>() }
108    }
109
110    fn encode_packed_ab_args(a: LocalAddress, b: LocalAddress) -> Word {
111        (a as Word) | ((b as Word) << LOCAL_ADDRESS_BITS)
112    }
113
114    fn decode_packed_ab_args(
115        args: Word,
116        f: impl FnOnce(LocalAddress, LocalAddress) -> Self,
117    ) -> Option<Self> {
118        let a = args & bitmask(LOCAL_ADDRESS_BITS);
119        let b = (args >> LOCAL_ADDRESS_BITS) & bitmask(LOCAL_ADDRESS_BITS);
120        Some(f(a as LocalAddress, b as LocalAddress))
121    }
122
123    fn encode_packed_abc_args(
124        a: LocalAddress,
125        b: LocalAddress,
126        c: LocalAddress,
127    ) -> Word {
128        (a as Word)
129            | ((b as Word) << LOCAL_ADDRESS_BITS)
130            | ((c as Word) << (2 * LOCAL_ADDRESS_BITS))
131    }
132    fn decode_packed_abc_args(
133        args: Word,
134        f: impl FnOnce(LocalAddress, LocalAddress, LocalAddress) -> Self,
135    ) -> Option<Self> {
136        let a = args & bitmask(LOCAL_ADDRESS_BITS);
137        let b = (args >> LOCAL_ADDRESS_BITS) & bitmask(LOCAL_ADDRESS_BITS);
138        let c =
139            (args >> (2 * LOCAL_ADDRESS_BITS)) & bitmask(LOCAL_ADDRESS_BITS);
140        Some(f(a as LocalAddress, b as LocalAddress, c as LocalAddress))
141    }
142
143    fn encode_packed_ai_args(a: LocalAddress, i: Immediate) -> Word {
144        (a as Word) | ((i as Word) << LOCAL_ADDRESS_BITS)
145    }
146
147    fn decode_packed_ai_args(
148        args: Word,
149        f: impl FnOnce(LocalAddress, Immediate) -> Self,
150    ) -> Option<Self> {
151        let a = args & bitmask(LOCAL_ADDRESS_BITS);
152        let i = (args >> LOCAL_ADDRESS_BITS) & bitmask(IMM_BITS);
153        Some(f(a as LocalAddress, i as Immediate))
154    }
155
156    fn encode_packed_ix_args(ix: ExtendedImmediate) -> Word {
157        ix as Word
158    }
159
160    fn decode_packed_ix_args(
161        args: Word,
162        f: impl FnOnce(ExtendedImmediate) -> Self,
163    ) -> Option<Self> {
164        let ix = args & bitmask(IMM_EXT_BITS);
165        Some(f(ix as ExtendedImmediate))
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use crate::{
172        arch::{Word, LOCAL_ADDRESS_BITS},
173        opcode::{Op, OPCODE_BITS},
174    };
175
176    #[test]
177    fn encodes_correctly() {
178        assert_eq!(0, Op::Mov(0, 0).encode_packed());
179        assert_eq!(1, Op::MovI(0, 0).encode_packed());
180        assert_eq!(
181            (1 << OPCODE_BITS) | (1 << (OPCODE_BITS + LOCAL_ADDRESS_BITS)),
182            Op::Mov(1, 1).encode_packed()
183        );
184        assert_eq!(Op::Ret.opcode() as Word, Op::Ret.encode_packed());
185    }
186}