Files
vulcan/vcore/src/opcodes.rs
T
2022-08-06 01:19:56 -05:00

419 lines
11 KiB
Rust

use std::convert::TryFrom;
use std::fmt::{Display, Formatter};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Opcode {
Nop,
Add,
Sub,
Mul,
Div,
Mod,
Copy,
And,
Or,
Xor,
Not,
Gt,
Lt,
Agt,
Alt,
Lshift,
Rshift,
Arshift,
Pop,
Dup,
Swap,
Pick,
Rot,
Jmp,
Jmpr,
Call,
Ret,
Brz,
Brnz,
Hlt,
Load,
Loadw,
Store,
Storew,
Setint,
Setiv,
Sdp,
Setsdp,
Pushr,
Popr,
Peekr,
Debug,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct InvalidOpcode(pub u8);
impl Display for InvalidOpcode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Invalid opcode {:#02x}", self.0)
}
}
impl std::error::Error for InvalidOpcode {}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct InvalidMnemonic<'a>(pub &'a str);
impl Display for Opcode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Opcode::Nop => {
write!(f, "nop")
}
Opcode::Add => {
write!(f, "add")
}
Opcode::Sub => {
write!(f, "sub")
}
Opcode::Mul => {
write!(f, "mul")
}
Opcode::Div => {
write!(f, "div")
}
Opcode::Mod => {
write!(f, "mod")
}
Opcode::Copy => {
write!(f, "copy")
}
Opcode::And => {
write!(f, "and")
}
Opcode::Or => {
write!(f, "or")
}
Opcode::Xor => {
write!(f, "xor")
}
Opcode::Not => {
write!(f, "not")
}
Opcode::Gt => {
write!(f, "gt")
}
Opcode::Lt => {
write!(f, "lt")
}
Opcode::Agt => {
write!(f, "agt")
}
Opcode::Alt => {
write!(f, "alt")
}
Opcode::Lshift => {
write!(f, "lshift")
}
Opcode::Rshift => {
write!(f, "rshift")
}
Opcode::Arshift => {
write!(f, "arshift")
}
Opcode::Pop => {
write!(f, "pop")
}
Opcode::Dup => {
write!(f, "dup")
}
Opcode::Swap => {
write!(f, "swap")
}
Opcode::Pick => {
write!(f, "pick")
}
Opcode::Rot => {
write!(f, "rot")
}
Opcode::Jmp => {
write!(f, "jmp")
}
Opcode::Jmpr => {
write!(f, "jmpr")
}
Opcode::Call => {
write!(f, "call")
}
Opcode::Ret => {
write!(f, "ret")
}
Opcode::Brz => {
write!(f, "brz")
}
Opcode::Brnz => {
write!(f, "brnz")
}
Opcode::Hlt => {
write!(f, "hlt")
}
Opcode::Load => {
write!(f, "load")
}
Opcode::Loadw => {
write!(f, "loadw")
}
Opcode::Store => {
write!(f, "store")
}
Opcode::Storew => {
write!(f, "storew")
}
Opcode::Setint => {
write!(f, "setint")
}
Opcode::Setiv => {
write!(f, "setiv")
}
Opcode::Sdp => {
write!(f, "sdp")
}
Opcode::Setsdp => {
write!(f, "setsdp")
}
Opcode::Pushr => {
write!(f, "pushr")
}
Opcode::Popr => {
write!(f, "popr")
}
Opcode::Peekr => {
write!(f, "peekr")
}
Opcode::Debug => {
write!(f, "debug")
}
}
}
}
impl TryFrom<u8> for Opcode {
type Error = InvalidOpcode;
fn try_from(value: u8) -> Result<Self, Self::Error> {
use Opcode::*;
Ok(match value {
0 => Nop,
1 => Add,
2 => Sub,
3 => Mul,
4 => Div,
5 => Mod,
6 => Copy,
7 => And,
8 => Or,
9 => Xor,
10 => Not,
11 => Gt,
12 => Lt,
13 => Agt,
14 => Alt,
15 => Lshift,
16 => Rshift,
17 => Arshift,
18 => Pop,
19 => Dup,
20 => Swap,
21 => Pick,
22 => Rot,
23 => Jmp,
24 => Jmpr,
25 => Call,
26 => Ret,
27 => Brz,
28 => Brnz,
29 => Hlt,
30 => Load,
31 => Loadw,
32 => Store,
33 => Storew,
34 => Setint,
35 => Setiv,
36 => Sdp,
37 => Setsdp,
38 => Pushr,
39 => Popr,
40 => Peekr,
41 => Debug,
other => return Err(InvalidOpcode(other)),
})
}
}
impl<'a> TryFrom<&'a str> for Opcode {
type Error = InvalidMnemonic<'a>;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
use Opcode::*;
Ok(match value {
"nop" | "push" => Nop,
"add" => Add,
"sub" => Sub,
"mul" => Mul,
"div" => Div,
"mod" => Mod,
"copy" => Copy,
"and" => And,
"or" => Or,
"xor" => Xor,
"not" => Not,
"gt" => Gt,
"lt" => Lt,
"agt" => Agt,
"alt" => Alt,
"lshift" => Lshift,
"rshift" => Rshift,
"arshift" => Arshift,
"pop" => Pop,
"dup" => Dup,
"swap" => Swap,
"pick" => Pick,
"rot" => Rot,
"jmp" => Jmp,
"jmpr" => Jmpr,
"call" => Call,
"ret" => Ret,
"brz" => Brz,
"brnz" => Brnz,
"hlt" => Hlt,
"load" => Load,
"loadw" => Loadw,
"store" => Store,
"storew" => Storew,
"setint" => Setint,
"setiv" => Setiv,
"sdp" => Sdp,
"setsdp" => Setsdp,
"pushr" => Pushr,
"popr" => Popr,
"peekr" => Peekr,
"debug" => Debug,
_ => return Err(InvalidMnemonic(value)),
})
}
}
impl From<Opcode> for u8 {
fn from(opcode: Opcode) -> Self {
match opcode {
Opcode::Nop => 0,
Opcode::Add => 1,
Opcode::Sub => 2,
Opcode::Mul => 3,
Opcode::Div => 4,
Opcode::Mod => 5,
Opcode::Copy => 6,
Opcode::And => 7,
Opcode::Or => 8,
Opcode::Xor => 9,
Opcode::Not => 10,
Opcode::Gt => 11,
Opcode::Lt => 12,
Opcode::Agt => 13,
Opcode::Alt => 14,
Opcode::Lshift => 15,
Opcode::Rshift => 16,
Opcode::Arshift => 17,
Opcode::Pop => 18,
Opcode::Dup => 19,
Opcode::Swap => 20,
Opcode::Pick => 21,
Opcode::Rot => 22,
Opcode::Jmp => 23,
Opcode::Jmpr => 24,
Opcode::Call => 25,
Opcode::Ret => 26,
Opcode::Brz => 27,
Opcode::Brnz => 28,
Opcode::Hlt => 29,
Opcode::Load => 30,
Opcode::Loadw => 31,
Opcode::Store => 32,
Opcode::Storew => 33,
Opcode::Setint => 34,
Opcode::Setiv => 35,
Opcode::Sdp => 36,
Opcode::Setsdp => 37,
Opcode::Pushr => 38,
Opcode::Popr => 39,
Opcode::Peekr => 40,
Opcode::Debug => 41,
}
}
}
impl Opcode {
/// The number of things required to be on the data stack for this opcode to function.
pub fn arity(self) -> usize {
use Opcode::*;
match self {
Nop | Ret | Hlt | Sdp | Popr | Peekr | Debug => 0,
Not | Pop | Dup | Jmp | Jmpr | Call | Load | Loadw | Setint | Pushr | Pick => 1,
Add | Sub | Mul | Div | Mod | And | Or | Xor | Gt | Lt | Agt | Alt | Lshift
| Rshift | Arshift | Swap | Brz | Brnz | Store | Storew | Setsdp | Setiv => 2,
Rot | Copy => 3,
}
}
/// The number of things required to be on the return stack for this opcode to function.
pub fn r_arity(self) -> usize {
use Opcode::*;
match self {
Peekr | Popr | Ret => 1,
Nop | Copy | Hlt | Sdp | Debug | Not | Pop | Dup | Jmp | Jmpr | Call | Load | Loadw
| Setint | Setiv | Pushr | Add | Sub | Mul | Div | Mod | And | Or | Xor | Gt | Lt
| Agt | Alt | Lshift | Rshift | Arshift | Swap | Brz | Brnz | Store | Storew
| Setsdp | Rot | Pick => 0,
}
}
/// The net change in stack usage (in cells) after executing this instruction. This is used to
/// tell whether this instruction will cause an overflow. Execution happens in three phases:
/// first the argument is pushed (which might trigger overflow); then the operands are popped
/// (which cannot overflow); then the result is pushed (which might overflow). This fn tells
/// you the number of result cells minus the number of operand cells, so, the delta in stack
/// usage for steps 2 and 3. Additionally, this fn counts data and return stack cells together,
/// so,
/// ```
/// # use vcore::opcodes::Opcode;
/// assert_eq!(Opcode::Pushr.net_stack(), 0)
/// ```
/// because moving a cell from data to return doesn't increase stack usage.
pub fn net_stack(self) -> i32 {
use Opcode::*;
match self {
Sdp => 2,
Peekr | Dup => 1,
Nop | Hlt | Debug | Not | Call | Load | Loadw | Pushr | Popr | Pick | Rot | Swap => 0,
Pop | Jmp | Jmpr | Add | Sub | Mul | Div | Mod | And | Or | Xor | Gt | Lt | Agt
| Alt | Lshift | Rshift | Arshift | Ret => -1,
Setint | Store | Storew | Setsdp | Setiv | Brz | Brnz => -2,
Copy => -3,
}
}
}
#[test]
fn test_decode() {
assert_eq!(Opcode::try_from(18), Ok(Opcode::Pop));
//assert_eq!(str::fmt("{}", Opcode::try_from(136).unwrap_err()), Err(InvalidOpcode(136)));
}