Skip to content

Commit

Permalink
Add branchlessly parsable Instruction
Browse files Browse the repository at this point in the history
  • Loading branch information
Dentosal authored Oct 20, 2021
1 parent 7ad50ab commit e7ee1a4
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 28 deletions.
274 changes: 274 additions & 0 deletions fuel-asm/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
use fuel_types::{Immediate06, Immediate12, Immediate18, Immediate24, RegisterId, Word};

#[cfg(feature = "std")]
use std::{io, iter};

use crate::opcode::consts::OpcodeRepr;

/// A version of Opcode that can used without unnecessary branching
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(
feature = "serde-types-minimal",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct Instruction {
/// Opcode
op: u8,
/// Register A
ra: RegisterId,
/// Register B
rb: RegisterId,
/// Register C
rc: RegisterId,
/// Register D
rd: RegisterId,
/// Immediate with 6 bits
imm06: Immediate06,
/// Immediate with 12 bits
imm12: Immediate12,
/// Immediate with 18 bits
imm18: Immediate18,
/// Immediate with 24 bits
imm24: Immediate24,
}

impl Instruction {
/// Size of an opcode in bytes
pub const LEN: usize = 4;

/// Extracts fields from a raw instruction
pub const fn new(instruction: u32) -> Self {
// TODO Optimize with native architecture (eg SIMD?) or facilitate
// auto-vectorization

let op = (instruction >> 24) as u8;

let ra = ((instruction >> 18) & 0x3f) as RegisterId;
let rb = ((instruction >> 12) & 0x3f) as RegisterId;
let rc = ((instruction >> 6) & 0x3f) as RegisterId;
let rd = (instruction & 0x3f) as RegisterId;

let imm06 = (instruction & 0xff) as Immediate06;
let imm12 = (instruction & 0x0fff) as Immediate12;
let imm18 = (instruction & 0x3ffff) as Immediate18;
let imm24 = (instruction & 0xffffff) as Immediate24;

Self {
op,
ra,
rb,
rc,
rd,
imm06,
imm12,
imm18,
imm24,
}
}

/// Opcode
pub const fn op(&self) -> u8 {
self.op
}

/// Register A
pub const fn ra(&self) -> RegisterId {
self.ra
}

/// Register B
pub const fn rb(&self) -> RegisterId {
self.rb
}

/// Register C
pub const fn rc(&self) -> RegisterId {
self.rc
}

/// Register D
pub const fn rd(&self) -> RegisterId {
self.rd
}

/// Immediate with 6 bits
pub const fn imm06(&self) -> Immediate06 {
self.imm06
}

/// Immediate with 12 bits
pub const fn imm12(&self) -> Immediate12 {
self.imm12
}

/// Immediate with 18 bits
pub const fn imm18(&self) -> Immediate18 {
self.imm18
}

/// Immediate with 24 bits
pub const fn imm24(&self) -> Immediate24 {
self.imm24
}

/// Create a `Instruction` from a slice of bytes
///
/// # Safety
///
/// The caller must ensure that the slice is has at least `Self::LEN` bytes.
pub unsafe fn from_slice_unchecked(buf: &[u8]) -> Self {
debug_assert!(buf.len() >= 4);
let instr = fuel_types::bytes::from_slice_unchecked(buf);
let instr = u32::from_be_bytes(instr);
Self::from(instr)
}

/// Convert the opcode to bytes representation
pub fn to_bytes(self) -> [u8; Self::LEN] {
u32::from(self).to_be_bytes()
}

/// Splits a Word into two [`Instruction`] that can be used to construct [`Opcode`]
pub const fn parse_word(word: Word) -> (Instruction, Instruction) {
// Assumes Word is u64
// https://doc.rust-lang.org/nightly/reference/expressions/operator-expr.html#numeric-cast4
let lo = word as u32; // truncates, see link above
let hi = (word >> 32) as u32;

(Instruction::new(hi), Instruction::new(lo))
}
}

impl From<u32> for Instruction {
fn from(instruction: u32) -> Self {
Self::new(instruction)
}
}

impl From<Instruction> for u32 {
fn from(parsed: Instruction) -> u32 {
// Convert all fields to u32 with correct shifting to just OR together
// This truncates the field if they are too large

let a = (parsed.ra as u32) << 18;
let b = (parsed.rb as u32) << 12;
let c = (parsed.rc as u32) << 6;
let d = parsed.rd as u32;

let imm12 = parsed.imm12 as u32;
let imm18 = parsed.imm18 as u32;
let imm24 = parsed.imm24 as u32;

let args = match OpcodeRepr::from_u8(parsed.op) {
OpcodeRepr::ADD
| OpcodeRepr::AND
| OpcodeRepr::DIV
| OpcodeRepr::EQ
| OpcodeRepr::EXP
| OpcodeRepr::GT
| OpcodeRepr::LT
| OpcodeRepr::MLOG
| OpcodeRepr::MROO
| OpcodeRepr::MOD
| OpcodeRepr::MUL
| OpcodeRepr::OR
| OpcodeRepr::SLL
| OpcodeRepr::SRL
| OpcodeRepr::SUB
| OpcodeRepr::XOR
| OpcodeRepr::CIMV
| OpcodeRepr::MCP
| OpcodeRepr::LDC
| OpcodeRepr::SLDC
| OpcodeRepr::TR
| OpcodeRepr::ECR
| OpcodeRepr::K256
| OpcodeRepr::S256 => a | b | c,
OpcodeRepr::ADDI
| OpcodeRepr::ANDI
| OpcodeRepr::DIVI
| OpcodeRepr::EXPI
| OpcodeRepr::MODI
| OpcodeRepr::MULI
| OpcodeRepr::ORI
| OpcodeRepr::SLLI
| OpcodeRepr::SRLI
| OpcodeRepr::SUBI
| OpcodeRepr::XORI
| OpcodeRepr::JNEI
| OpcodeRepr::LB
| OpcodeRepr::LW
| OpcodeRepr::SB
| OpcodeRepr::SW => a | b | imm12,
OpcodeRepr::MOVE
| OpcodeRepr::NOT
| OpcodeRepr::CTMV
| OpcodeRepr::RETD
| OpcodeRepr::MCL
| OpcodeRepr::BHSH
| OpcodeRepr::CROO
| OpcodeRepr::CSIZ
| OpcodeRepr::SRW
| OpcodeRepr::SRWQ
| OpcodeRepr::SWW
| OpcodeRepr::SWWQ
| OpcodeRepr::XIL
| OpcodeRepr::XIS
| OpcodeRepr::XOL
| OpcodeRepr::XOS
| OpcodeRepr::XWL
| OpcodeRepr::XWS => a | b,
OpcodeRepr::RET
| OpcodeRepr::ALOC
| OpcodeRepr::BHEI
| OpcodeRepr::BURN
| OpcodeRepr::CB
| OpcodeRepr::MINT
| OpcodeRepr::RVRT
| OpcodeRepr::FLAG => a,
OpcodeRepr::JI | OpcodeRepr::CFEI | OpcodeRepr::CFSI => imm24,
OpcodeRepr::MCLI | OpcodeRepr::GM => a | imm18,
OpcodeRepr::MEQ
| OpcodeRepr::CALL
| OpcodeRepr::CCP
| OpcodeRepr::LOG
| OpcodeRepr::LOGD
| OpcodeRepr::TRO => a | b | c | d,
OpcodeRepr::NOOP | OpcodeRepr::UNDEFINED => 0,
};

((parsed.op as u32) << 24) | args
}
}

#[cfg(feature = "std")]
impl Instruction {
/// Create a `Instruction` from a slice of bytes
///
/// This function will fail if the length of the bytes is smaller than
/// [`Instruction::LEN`].
pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
if bytes.len() < Self::LEN {
Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"The provided buffer is not big enough!",
))
} else {
// Safety: we check the length above
unsafe { Ok(Self::from_slice_unchecked(bytes)) }
}
}
}

#[cfg(feature = "std")]
impl iter::FromIterator<Instruction> for Vec<u8> {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = Instruction>,
{
iter.into_iter()
.map(Instruction::to_bytes)
.flatten()
.collect()
}
}
2 changes: 2 additions & 0 deletions fuel-asm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]

mod instruction;
mod opcode;

pub use fuel_types::{Immediate06, Immediate12, Immediate18, Immediate24, RegisterId, Word};
pub use instruction::Instruction;
pub use opcode::Opcode;
47 changes: 20 additions & 27 deletions fuel-asm/src/opcode.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use fuel_types::{Immediate06, Immediate12, Immediate18, Immediate24, RegisterId};
use fuel_types::{Immediate12, Immediate18, Immediate24, RegisterId};

use core::convert::TryFrom;

Expand All @@ -7,6 +7,8 @@ use consts::*;
#[cfg(feature = "std")]
use std::{io, iter};

use crate::Instruction;

pub mod consts;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -1256,17 +1258,16 @@ impl Opcode {
pub const BYTES_SIZE: usize = 4;

/// Create a new [`Opcode`] given the internal attributes
pub const fn new(
op: u8,
ra: RegisterId,
rb: RegisterId,
rc: RegisterId,
rd: RegisterId,
_imm06: Immediate06,
imm12: Immediate12,
imm18: Immediate18,
imm24: Immediate24,
) -> Self {
pub const fn new(instruction: Instruction) -> Self {
let op = instruction.op();
let ra = instruction.ra();
let rb = instruction.rb();
let rc = instruction.rc();
let rd = instruction.rd();
let imm12 = instruction.imm12();
let imm18 = instruction.imm18();
let imm24 = instruction.imm24();

let op = OpcodeRepr::from_u8(op);

match op {
Expand Down Expand Up @@ -1477,23 +1478,15 @@ impl Opcode {
}
}

impl From<Instruction> for Opcode {
fn from(parsed: Instruction) -> Self {
Self::new(parsed)
}
}

impl From<u32> for Opcode {
fn from(instruction: u32) -> Self {
// TODO Optimize with native architecture (eg SIMD?) or facilitate
// auto-vectorization
let op = (instruction >> 24) as u8;

let ra = ((instruction >> 18) & 0x3f) as RegisterId;
let rb = ((instruction >> 12) & 0x3f) as RegisterId;
let rc = ((instruction >> 6) & 0x3f) as RegisterId;
let rd = (instruction & 0x3f) as RegisterId;

let imm06 = (instruction & 0xff) as Immediate06;
let imm12 = (instruction & 0x0fff) as Immediate12;
let imm18 = (instruction & 0x3ffff) as Immediate18;
let imm24 = (instruction & 0xffffff) as Immediate24;

Self::new(op, ra, rb, rc, rd, imm06, imm12, imm18, imm24)
Self::new(Instruction::from(instruction))
}
}

Expand Down
2 changes: 1 addition & 1 deletion fuel-asm/src/opcode/consts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(non_camel_case_types)]
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
pub enum OpcodeRepr {
ADD = 0x10,
ADDI = 0x11,
Expand Down
Loading

0 comments on commit e7ee1a4

Please sign in to comment.