From bdb596befbee8ac72e4a16d8b8c64f12839357da Mon Sep 17 00:00:00 2001 From: Erin Date: Mon, 12 Jun 2023 00:32:42 +0200 Subject: [PATCH] spec --- hbasm/src/lib.rs | 10 +- hbbytecode/src/lib.rs | 74 ++++++------- hbvm/src/validate.rs | 5 +- hbvm/src/vm/mod.rs | 34 +++--- spec.md | 240 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 307 insertions(+), 56 deletions(-) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index 570a1667..d8e306f1 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -52,11 +52,11 @@ macro_rules! tokendef { #[rustfmt::skip] tokendef![ - "nop", "add", "sub", "mul", "and", "or", "xor", "sl", "sr", "srs", "cmp", "cmpu", "not", - "dir", "addf", "subf", "mulf", "dirf", "addi", "muli", "andi", "ori", + "nop", "add", "sub", "mul", "and", "or", "xor", "sl", "sr", "srs", "cmp", "cmpu", "not", "neg", + "dir", "addf", "mulf", "dirf", "addi", "muli", "andi", "ori", "xori", "sli", "sri", "srsi", "cmpi", "cmpui", "addfi", "mulfi", "cp", "li", "lb", "ld", "lq", "lo", "sb", "sd", "sq", "so", "jmp", "jeq", "jne", "jlt", "jgt", - "jltu", "jgtu", "ret", "ecall", + "jltu", "jgtu", "ecall", ]; #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -112,12 +112,12 @@ pub fn assembly(code: &str, buf: &mut Vec) -> Result<(), Error> { Some(Ok(Token::OpCode(op))) => { self.buf.push(op); match op { - NOP | RET | ECALL => Ok(()), + NOP | ECALL => Ok(()), DIR | DIRF => self.rrrr(), ADD..=CMPU | ADDF..=MULF => self.rrr(), NOT | CP => self.rr(), LI | JMP => self.ri(), - ADDI..=MULFI | LB..=SO | JEQ..=JGTU => self.rri(), + ADDI..=CMPUI | ADDFI..=MULFI | LB..=SO | JEQ..=JGTU => self.rri(), _ => unreachable!(), }?; match self.next() { diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 872ab568..3f841331 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -32,46 +32,46 @@ constmod!(pub opcode(u8) { CMP = 10, "RRR; #0 ← #1 <=> #2"; CMPU = 11, "RRR; #0 ← #1 <=> #2 (unsigned)"; DIR = 12, "RRRR; #0 ← #2 / #3, #1 ← #2 % #3"; - NOT = 13, "RR; #0 ← !#1"; + NEG = 13, "RR; #0 ← ~#1"; + NOT = 14, "RR; #0 ← !#1"; - ADDF = 14, "RRR; #0 ← #1 +. #2"; - SUBF = 15, "RRR; #0 ← #1 +. #2"; - MULF = 16, "RRR; #0 ← #1 +. #2"; - ADDI = 17, "RRI; #0 ← #1 + imm #2"; - MULI = 18, "RRI; #0 ← #1 × imm #2"; - ANDI = 19, "RRI; #0 ← #1 & imm #2"; - ORI = 20, "RRI; #0 ← #1 | imm #2"; - XORI = 21, "RRI; #0 ← #1 ^ imm #2"; - SLI = 22, "RRI; #0 ← #1 « imm #2"; - SRI = 23, "RRI; #0 ← #1 » imm #2"; - SRSI = 24, "RRI; #0 ← #1 » imm #2 (signed)"; - CMPI = 25, "RRI; #0 ← #1 <=> imm #2"; - CMPUI = 26, "RRI; #0 ← #1 <=> imm #2 (unsigned)"; - DIRF = 27, "RRRR; #0 ← #2 / #3, #1 ← #2 % #3"; - - ADDFI = 28, "RRI; #0 ← #1 +. imm #2"; - MULFI = 29, "RRI; #0 ← #1 *. imm #2"; + ADDI = 18, "RRI; #0 ← #1 + imm #2"; + MULI = 19, "RRI; #0 ← #1 × imm #2"; + ANDI = 20, "RRI; #0 ← #1 & imm #2"; + ORI = 21, "RRI; #0 ← #1 | imm #2"; + XORI = 22, "RRI; #0 ← #1 ^ imm #2"; + SLI = 23, "RRI; #0 ← #1 « imm #2"; + SRI = 24, "RRI; #0 ← #1 » imm #2"; + SRSI = 25, "RRI; #0 ← #1 » imm #2 (signed)"; + CMPI = 26, "RRI; #0 ← #1 <=> imm #2"; + CMPUI = 27, "RRI; #0 ← #1 <=> imm #2 (unsigned)"; - CP = 30, "RR; Copy #0 ← #1"; - LI = 31, "RI; Load immediate, #0 ← imm #1"; - LB = 32, "RRI; Load byte (8 bits), #0 ← [#1 + imm #2]"; - LD = 33, "RRI; Load doublet (16 bits)"; - LQ = 34, "RRI; Load quadlet (32 bits)"; - LO = 35, "RRI; Load octlet (64 bits)"; - SB = 36, "RRI; Store byte, [#1 + imm #2] ← #0"; - SD = 37, "RRI; Store doublet"; - SQ = 38, "RRI; Store quadlet"; - SO = 39, "RRI; Store octlet"; + CP = 28, "RR; Copy #0 ← #1"; + LI = 29, "RI; Load immediate, #0 ← imm #1"; + LB = 30, "RRI; Load byte (8 bits), #0 ← [#1 + imm #2]"; + LD = 31, "RRI; Load doublet (16 bits)"; + LQ = 32, "RRI; Load quadlet (32 bits)"; + LO = 33, "RRI; Load octlet (64 bits)"; + SB = 34, "RRI; Store byte, [#1 + imm #2] ← #0"; + SD = 35, "RRI; Store doublet"; + SQ = 36, "RRI; Store quadlet"; + SO = 37, "RRI; Store octlet"; - JMP = 40, "RI; Unconditional jump [#0 + imm #1]"; - JEQ = 41, "RRI; if #0 = #1 → jump imm #2"; - JNE = 42, "RRI; if #0 ≠ #1 → jump imm #2"; - JLT = 43, "RRI; if #0 < #1 → jump imm #2"; - JGT = 44, "RRI; if #0 > #1 → jump imm #2"; - JLTU = 45, "RRI; if #0 < #1 → jump imm #2 (unsigned)"; - JGTU = 46, "RRI; if #0 > #1 → jump imm #2 (unsigned)"; - RET = 47, "N; Return"; - ECALL = 48, "N; Issue system call"; + JMP = 38, "RI; Unconditional jump [#0 + imm #1]"; + JEQ = 39, "RRI; if #0 = #1 → jump imm #2"; + JNE = 40, "RRI; if #0 ≠ #1 → jump imm #2"; + JLT = 41, "RRI; if #0 < #1 → jump imm #2"; + JGT = 42, "RRI; if #0 > #1 → jump imm #2"; + JLTU = 43, "RRI; if #0 < #1 → jump imm #2 (unsigned)"; + JGTU = 44, "RRI; if #0 > #1 → jump imm #2 (unsigned)"; + ECALL = 45, "N; Issue system call"; + + ADDF = 46, "RRR; #0 ← #1 +. #2"; + MULF = 47, "RRR; #0 ← #1 +. #2"; + DIRF = 48, "RRRR; #0 ← #2 / #3, #1 ← #2 % #3"; + + ADDFI = 49, "RRI; #0 ← #1 +. imm #2"; + MULFI = 50, "RRI; #0 ← #1 *. imm #2"; }); diff --git a/hbvm/src/validate.rs b/hbvm/src/validate.rs index f38f4a05..9d59f1c0 100644 --- a/hbvm/src/validate.rs +++ b/hbvm/src/validate.rs @@ -39,7 +39,7 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { program = match program { [] => return Ok(()), // N - [NOP | RET | ECALL, rest @ ..] => rest, + [NOP | ECALL, rest @ ..] => rest, // RRRR [DIR | DIRF, _, _, _, _, rest @ ..] => { if let Some(n) = reg(&program[1..=4]) { @@ -69,7 +69,8 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { rest } // RRI - [ADDI..=MULFI | LB..=SO | JEQ..=JGTU, _, _, _, _, _, _, _, _, _, _, rest @ ..] => { + [ADDI..=CMPUI | ADDFI..=MULFI | LB..=SO | JEQ..=JGTU, _, _, _, _, _, _, _, _, _, _, rest @ ..] => + { if let Some(n) = reg(&program[1..=2]) { bail!(InvalidRegister, start, program, n + 1); } diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs index f1ad9369..2f20da30 100644 --- a/hbvm/src/vm/mod.rs +++ b/hbvm/src/vm/mod.rs @@ -162,6 +162,17 @@ impl<'a> Vm<'a> { let param = param!(self, ParamRR); self.write_reg(param.0, (!self.read_reg(param.1).int()).into()); } + NEG => { + let param = param!(self, ParamRR); + self.write_reg( + param.0, + match self.read_reg(param.1).int() { + 0 => 1_u64, + _ => 0, + } + .into(), + ); + } DIR => { let ParamRRRR(dt, rt, a0, a1) = param!(self, ParamRRRR); let a0 = self.read_reg(a0).int(); @@ -169,16 +180,6 @@ impl<'a> Vm<'a> { self.write_reg(dt, (a0.checked_div(a1).unwrap_or(u64::MAX)).into()); self.write_reg(rt, (a0.checked_rem(a1).unwrap_or(u64::MAX)).into()); } - ADDF => binary_op!(self, float, ops::Add::add), - SUBF => binary_op!(self, float, ops::Sub::sub), - MULF => binary_op!(self, float, ops::Mul::mul), - DIRF => { - let ParamRRRR(dt, rt, a0, a1) = param!(self, ParamRRRR); - let a0 = self.read_reg(a0).float(); - let a1 = self.read_reg(a1).float(); - self.write_reg(dt, (a0 / a1).into()); - self.write_reg(rt, (a0 % a1).into()); - } ADDI => binary_op_imm!(self, int, ops::Add::add), MULI => binary_op_imm!(self, int, ops::Mul::mul), ANDI => binary_op_imm!(self, int, ops::BitAnd::bitand), @@ -187,8 +188,6 @@ impl<'a> Vm<'a> { SLI => binary_op_imm!(self, int, ops::Shl::shl), SRI => binary_op_imm!(self, int, ops::Shr::shr), SRSI => binary_op_imm!(self, sint, ops::Shr::shr), - ADDFI => binary_op_imm!(self, float, ops::Add::add), - MULFI => binary_op_imm!(self, float, ops::Mul::mul), CMPI => { let ParamRRI(tg, a0, imm) = param!(self, ParamRRI); self.write_reg( @@ -235,6 +234,17 @@ impl<'a> Vm<'a> { param!(self, ()); return HaltReason::Ecall; } + ADDF => binary_op!(self, float, ops::Add::add), + MULF => binary_op!(self, float, ops::Mul::mul), + DIRF => { + let ParamRRRR(dt, rt, a0, a1) = param!(self, ParamRRRR); + let a0 = self.read_reg(a0).float(); + let a1 = self.read_reg(a1).float(); + self.write_reg(dt, (a0 / a1).into()); + self.write_reg(rt, (a0 % a1).into()); + } + ADDFI => binary_op_imm!(self, float, ops::Add::add), + MULFI => binary_op_imm!(self, float, ops::Mul::mul), _ => core::hint::unreachable_unchecked(), } } diff --git a/spec.md b/spec.md index 09dbc35c..64ecd1c6 100644 --- a/spec.md +++ b/spec.md @@ -1 +1,241 @@ # HoleyBytes ISA Specification + +# Bytecode format +- All numbers are encoded little-endian +- There is 60 registers (0 – 59), they are represented by a byte +- Immediate values are 64 bit + +### Instruction encoding +- Instruction parameters are packed (no alignment) +- [opcode, …parameters…] + +### Instruction parameter types +- R = Register +- I = Immediate + +| Name | Size | +|:----:|:--------| +| RRRR | 32 bits | +| RRR | 24 bits | +| RRI | 80 bits | +| RR | 16 bits | +| RI | 72 bits | +| I | 64 bits | +| N | 0 bits | + +# Instructions +- `#n`: register in parameter *n* +- `imm #n`: for immediate in parameter *n* +- `P ← V`: Set register P to value V +- `[x]`: Address x + +## No-op +- N type + +| Opcode | Name | Action | +|:------:|:----:|:----------:| +| 0 | NOP | Do nothing | + +## Integer binary ops. +- RRR type +- `#0 ← #1 #2` + +| Opcode | Name | Action | +|:------:|:----:|:-----------------------:| +| 1 | ADD | Wrapping addition | +| 2 | SUB | Wrapping subtraction | +| 3 | MUL | Wrapping multiplication | +| 4 | AND | Bitand | +| 5 | OR | Bitor | +| 6 | XOR | Bitxor | +| 7 | SL | Unsigned left bitshift | +| 8 | SR | Unsigned right bitshift | +| 9 | SRS | Signed right bitshift | + +### Comparsion +| Opcode | Name | Action | +|:------:|:----:|:-------------------:| +| 10 | CMP | Signed comparsion | +| 11 | CMPU | Unsigned comparsion | + +#### Comparsion table +| #1 *op* #2 | Result | +|:----------:|:------:| +| < | -1 | +| = | 0 | +| > | 1 | + +### Division-remainder +- Type RRRR +- In case of `#3` is zero, the resulting value is all-ones +- `#0 ← #2 ÷ #3` +- `#1 ← #2 % #3` + +| Opcode | Name | Action | +|:------:|:----:|:-------------------------------:| +| 12 | DIR | Divide and remainder combinated | + +### Negations +- Type RR +- `#0 ← #1 #2` + +| Opcode | Name | Action | +|:------:|:----:|:----------------:| +| 13 | NEG | Bit negation | +| 14 | NOT | Logical negation | + +## Integer immediate binary ops. +- Type RRI +- `#0 ← #1 imm #2` + +| Opcode | Name | Action | +|:------:|:----:|:-----------------------:| +| 18 | ADDI | Wrapping addition | +| 19 | MULI | Wrapping subtraction | +| 20 | ANDI | Bitand | +| 21 | ORI | Bitor | +| 22 | XORI | Bitxor | +| 23 | SLI | Unsigned left bitshift | +| 24 | SRI | Unsigned right bitshift | +| 25 | SRSI | Signed right bitshift | + +### Comparsion +- Comparsion is the same as when RRR type + +| Opcode | Name | Action | +|:------:|:-----:|:-------------------:| +| 26 | CMPI | Signed comparsion | +| 27 | CMPUI | Unsigned comparsion | + +## Register value set / copy + +### Copy +- Type RR +- `#0 ← #1` + +| Opcode | Name | Action | +|:------:|:----:|:------:| +| 28 | CP | Copy | + +### Load immediate +- Type RI +- `#0 ← #1` + +| Opcode | Name | Action | +|:------:|:----:|:--------------:| +| 29 | LI | Load immediate | + +## Memory operations +- Type RRI + +### Load +- `#0 ← [#1 + imm #2]` + +| Opcode | Name | Action | +|:------:|:----:|:----------------------:| +| 30 | LB | Load byte (8 bits) | +| 31 | LD | Load doublet (16 bits) | +| 32 | LQ | Load quadlet (32 bits) | +| 33 | LO | Load octlet (64 bits) | + +### Store +- `[#1 + imm #2] ← #0` + +| Opcode | Name | Action | +|:------:|:----:|:-----------------------:| +| 34 | SB | Store byte (8 bits) | +| 35 | SD | Store doublet (16 bits) | +| 36 | SQ | Store quadlet (32 bits) | +| 37 | SO | Store octlet (64 bits) | + +## Control flow + +### Unconditional jump +- Type RI + +| Opcode | Name | Action | +|:------:|:----:|:---------------------:| +| 38 | JMP | Jump at `#0 + imm #1` | + +### Conditional jumps +- Type RRI +- Jump at `imm #2` if `#0 #1` + +| Opcode | Name | Comparsion | +|:------:|:----:|:------------:| +| 39 | JEQ | = | +| 40 | JNE | ≠ | +| 41 | JLT | < (signed) | +| 42 | JGT | > (signed) | +| 43 | JLTU | < (unsigned) | +| 44 | JGTU | > (unsigned) | + +### Environment call +- Type N + +| Opcode | Name | Action | +|:------:|:-----:|:-------------------------------------:| +| 45 | ECALL | Cause an trap to the host environment | + +## Floating point operations +- Type RRR +- `#0 ← #1 #2` + +| Opcode | Name | Action | +|:------:|:----:|:--------------:| +| 46 | ADDF | Addition | +| 47 | MULF | Multiplication | + +### Division-remainder +- Type RRRR + +| Opcode | Name | Action | +|:------:|:----:|:--------------------------------------:| +| 48 | DIRF | Same flow applies as for integer `DIR` | + +## Floating point immediate operations +- Type RRI +- `#0 ← #1 imm #2` + +| Opcode | Name | Action | +|:------:|:-----:|:--------------:| +| 49 | ADDFI | Addition | +| 50 | MULFI | Multiplication | + +# Registers +- There is 59 registers + one zero register (with index 0) +- Reading from zero register yields zero +- Writing to zero register is a no-op + +# Memory +- Addresses are 64 bit +- Memory implementation is arbitrary +- In case of accessing invalid address: + - Program shall trap (LoadAccessEx, StoreAccessEx) with parameter of accessed address + - Value of register when trapped is undefined + +## Recommendations +- Leave address `0x0` as invalid +- If paging used: + - Leave first page invalid + - Pages should be at least 4 KiB + +# Program execution +- The way of program execution is implementation defined +- The order of instruction is arbitrary, as long all observable + effects are applied in the program's order + +# Program validation +- Invalid program should cause runtime error: + - The form of error is arbitrary. Can be a trap or an interpreter-specified error + - It shall not be handleable from within the program +- Executing invalid opcode should trap +- Program can be validaded either before execution or when executing + +# Traps +| Name | Parameters | Cause | +|:-------------:|:----------------:|:--------------------------:| +| LoadAccessEx | Accessed address | Loading invalid address | +| StoreAccessEx | Accessed address | Storing to invalid address | +| InvalidOpcode | Loaded opcode | Executing invalid opcode | +| Ecall | None | Ecall instruction |