Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(wip): store instruction as single u64, perform bit operations on demand #1762

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 191 additions & 27 deletions vm/src/types/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::Felt252;
use num_traits::ToPrimitive;
use serde::{Deserialize, Serialize};

use crate::vm::decoding::decoder::decode_instruction;
use crate::vm::decoding::decoder::{decode_instruction, decode_offset};

#[cfg(feature = "test_utils")]
use arbitrary::Arbitrary;
Expand All @@ -14,19 +14,196 @@ pub enum Register {
FP,
}

// FIXME: impl Debug as decoded
// TODO: possibly exploit NonZero or NonMax so `Result<Instruction, _>` and `Option<Instruction>`
// can use the niche. Note NonMax is guaranteed by the restriction of MSB being 0.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Instruction {
pub off0: isize,
pub off1: isize,
pub off2: isize,
pub dst_register: Register,
pub op0_register: Register,
pub op1_addr: Op1Addr,
pub res: Res,
pub pc_update: PcUpdate,
pub ap_update: ApUpdate,
pub fp_update: FpUpdate,
pub opcode: Opcode,
pub struct Instruction(u64);

impl Instruction {
#[inline]
pub fn offset0(self) -> isize {
decode_offset(self.0 & 0xffff)
}

#[inline]
pub fn offset1(self) -> isize {
decode_offset((self.0 >> 16) & 0xffff)
}

#[inline]
pub fn offset2(self) -> isize {
decode_offset((self.0 >> 32) & 0xffff)
}

#[inline]
pub fn dst_register(self) -> Register {
const DST_REG_MSK: u64 = 1 << 48;
const AP: u64 = 0;
const FP: u64 = 1 << 48;
match self.0 & DST_REG_MSK {
AP => Register::AP,
FP => Register::FP,
_ => unreachable!(),
}
}

#[inline]
pub fn op0_register(self) -> Register {
const OP0_SRC_MSK: u64 = 1 << 49;
const AP: u64 = 0;
const FP: u64 = 1 << 49;
match self.0 & OP0_SRC_MSK {
AP => Register::AP,
FP => Register::FP,
_ => unreachable!(),
}
}

#[inline]
pub fn op1_addr(self) -> Op1Addr {
const OP1_SRC_MSK: u64 = 7 << 50;
const OP0: u64 = 0;
const IMM: u64 = 1 << 50;
const FP: u64 = 2 << 50;
const AP: u64 = 4 << 50;
match self.0 & OP1_SRC_MSK {
OP0 => Op1Addr::Op0,
IMM => Op1Addr::Imm,
FP => Op1Addr::FP,
AP => Op1Addr::AP,
_ => unreachable!(),
}
}

#[inline]
pub fn res(self) -> Res {
const RES_LOGIC_MSK: u64 = (3 << 53) | (1 << 57);
const OP1: u64 = 0;
const ADD: u64 = 1 << 53;
const MUL: u64 = 2 << 53;
const FREE: u64 = 1 << 57;
match self.0 & RES_LOGIC_MSK {
OP1 => Res::Op1,
ADD => Res::Add,
MUL => Res::Mul,
FREE => Res::Unconstrained,
_ => unreachable!(),
}
}

#[inline]
pub fn pc_update(self) -> PcUpdate {
const PC_UPD_MSK: u64 = 7 << 55;
const REG: u64 = 0;
const JMP: u64 = 1 << 55;
const JRL: u64 = 2 << 55;
const JNZ: u64 = 4 << 55;
match self.0 & PC_UPD_MSK {
REG => PcUpdate::Regular,
JMP => PcUpdate::Jump,
JRL => PcUpdate::JumpRel,
JNZ => PcUpdate::Jnz,
_ => unreachable!(),
}
}

#[inline]
pub fn ap_update(self) -> ApUpdate {
const AP_UPD_MSK: u64 = 7 << 58;
const REG: u64 = 0;
const ADD: u64 = 1 << 58;
const ADD1: u64 = 2 << 58;
const ADD2: u64 = 4 << 58;
match self.0 & AP_UPD_MSK {
REG => ApUpdate::Regular,
ADD => ApUpdate::Add,
ADD1 => ApUpdate::Add1,
ADD2 => ApUpdate::Add2,
_ => unreachable!(),
}
}

#[inline]
pub fn fp_update(self) -> FpUpdate {
const OPCODE_MSK: u64 = 7 << 60;
const NOP: u64 = 0;
const CALL: u64 = 1 << 60;
const RET: u64 = 2 << 60;
const ASSERT_EQ: u64 = 4 << 60;
match self.0 & OPCODE_MSK {
CALL => FpUpdate::APPlus2,
RET => FpUpdate::Dst,
NOP | ASSERT_EQ => FpUpdate::Regular,
_ => unreachable!(),
}
}

#[inline]
pub fn opcode(self) -> Opcode {
const OPCODE_MSK: u64 = 7 << 60;
const NOP: u64 = 0;
const CALL: u64 = 1 << 60;
const RET: u64 = 2 << 60;
const ASSERT_EQ: u64 = 4 << 60;
match self.0 & OPCODE_MSK {
NOP => Opcode::NOp,
CALL => Opcode::Call,
RET => Opcode::Ret,
ASSERT_EQ => Opcode::AssertEq,
_ => unreachable!(),
}
}

#[inline]
pub fn size(self) -> usize {
// 2 if immediate operand, 1 otherwise
(((self.0 >> 50) & 1) + 1) as usize
}

#[inline]
pub fn is_call(self) -> bool {
let flags = (self.0 >> 48) & 0x7fe0;
flags == 0b0001000100000000 || flags == 0b0001001000000000
// Res::Op1
// PcUpdate::Jump || PcUpdate::JumpRel
// ApUpdate::Add2
// FpUpdate::APPlus2
// Opcode::Call
}
}

impl TryFrom<u64> for Instruction {
type Error = ();

fn try_from(v: u64) -> Result<Self, ()> {
let flags = v >> 48;
let dstreg_bits = (flags & 0x0001).count_ones();
let op0reg_bits = (flags & 0x0002).count_ones();
let op1src_bits = (flags & 0x001c).count_ones();
let reslog_bits = (flags & 0x0260).count_ones();
let pcupd_bits = (flags & 0x0380).count_ones();
let apupd_bits = (flags & 0x1c00).count_ones();
let opcode_bits = (flags & 0x7000).count_ones();
let fpupd_bits = (flags & 0x7000).count_ones();
let high_bit = flags as u32 & 0x8000;

if (high_bit
| opcode_bits
| fpupd_bits
| apupd_bits
| pcupd_bits
| reslog_bits
| dstreg_bits
| op0reg_bits
| op1src_bits)
> 1
{
return Err(());
}

Ok(Self(v))
}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -76,15 +253,6 @@ pub enum Opcode {
Ret,
}

impl Instruction {
pub fn size(&self) -> usize {
match self.op1_addr {
Op1Addr::Imm => 2,
_ => 1,
}
}
}

// Returns True if the given instruction looks like a call instruction
pub(crate) fn is_call_instruction(encoded_instruction: &Felt252) -> bool {
let encoded_i64_instruction = match encoded_instruction.to_u64() {
Expand All @@ -95,11 +263,7 @@ pub(crate) fn is_call_instruction(encoded_instruction: &Felt252) -> bool {
Ok(inst) => inst,
Err(_) => return false,
};
instruction.res == Res::Op1
&& (instruction.pc_update == PcUpdate::Jump || instruction.pc_update == PcUpdate::JumpRel)
&& instruction.ap_update == ApUpdate::Add2
&& instruction.fp_update == FpUpdate::APPlus2
&& instruction.opcode == Opcode::Call
instruction.is_call()
}

#[cfg(test)]
Expand Down
35 changes: 19 additions & 16 deletions vm/src/vm/context/run_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,43 +32,46 @@ impl RunContext {

pub fn compute_dst_addr(
&self,
instruction: &Instruction,
instruction: Instruction,
) -> Result<Relocatable, VirtualMachineError> {
let base_addr = match instruction.dst_register {
let base_addr = match instruction.dst_register() {
Register::AP => self.get_ap(),
Register::FP => self.get_fp(),
};
if instruction.off0 < 0 {
Ok((base_addr - abs(instruction.off0) as usize)?)
let off0 = instruction.offset0();
if off0 < 0 {
Ok((base_addr - abs(off0) as usize)?)
} else {
Ok((base_addr + (instruction.off0 as usize))?)
Ok((base_addr + (off0 as usize))?)
}
}

pub fn compute_op0_addr(
&self,
instruction: &Instruction,
instruction: Instruction,
) -> Result<Relocatable, VirtualMachineError> {
let base_addr = match instruction.op0_register {
let base_addr = match instruction.op0_register() {
Register::AP => self.get_ap(),
Register::FP => self.get_fp(),
};
if instruction.off1 < 0 {
Ok((base_addr - abs(instruction.off1) as usize)?)
let off1 = instruction.offset1();
if off1 < 0 {
Ok((base_addr - abs(off1) as usize)?)
} else {
Ok((base_addr + (instruction.off1 as usize))?)
Ok((base_addr + (off1 as usize))?)
}
}

pub fn compute_op1_addr(
&self,
instruction: &Instruction,
instruction: Instruction,
op0: Option<&MaybeRelocatable>,
) -> Result<Relocatable, VirtualMachineError> {
let base_addr = match instruction.op1_addr {
let off2 = instruction.offset2();
let base_addr = match instruction.op1_addr() {
Op1Addr::FP => self.get_fp(),
Op1Addr::AP => self.get_ap(),
Op1Addr::Imm => match instruction.off2 == 1 {
Op1Addr::Imm => match off2 == 1 {
true => self.pc,
false => return Err(VirtualMachineError::ImmShouldBe1),
},
Expand All @@ -78,10 +81,10 @@ impl RunContext {
None => return Err(VirtualMachineError::UnknownOp0),
},
};
if instruction.off2 < 0 {
Ok((base_addr - abs(instruction.off2) as usize)?)
if off2 < 0 {
Ok((base_addr - abs(off2) as usize)?)
} else {
Ok((base_addr + (instruction.off2 as usize))?)
Ok((base_addr + (off2 as usize))?)
}
}

Expand Down
15 changes: 8 additions & 7 deletions vm/src/vm/decoding/decoder.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::{
types::instruction::{
ApUpdate, FpUpdate, Instruction, Op1Addr, Opcode, PcUpdate, Register, Res,
},
vm::errors::vm_errors::VirtualMachineError,
};
use crate::{types::instruction::Instruction, vm::errors::vm_errors::VirtualMachineError};

// 0| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg
// 15|14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0

/// Decodes an instruction. The encoding is little endian, so flags go from bit 63 to 48.
pub fn decode_instruction(encoded_instr: u64) -> Result<Instruction, VirtualMachineError> {
// FIXME: replace the function and use the extended decoding for error when relevant
encoded_instr
.try_into()
.map_err(|_| VirtualMachineError::InstructionNonZeroHighBit)
/*
const HIGH_BIT: u64 = 1u64 << 63;
const DST_REG_MASK: u64 = 0x0001;
const DST_REG_OFF: u64 = 0;
Expand Down Expand Up @@ -125,9 +125,10 @@ pub fn decode_instruction(encoded_instr: u64) -> Result<Instruction, VirtualMach
fp_update,
opcode,
})
*/
}

fn decode_offset(offset: u64) -> isize {
pub(crate) fn decode_offset(offset: u64) -> isize {
let vectorized_offset: [u8; 8] = offset.to_le_bytes();
let offset_16b_encoded = u16::from_le_bytes([vectorized_offset[0], vectorized_offset[1]]);
let complement_const = 0x8000u16;
Expand Down
Loading
Loading