From 218192d5f7775729677a6448730b70e76bfa1230 Mon Sep 17 00:00:00 2001 From: Mario Rugiero Date: Sun, 12 May 2024 23:14:26 -0300 Subject: [PATCH] perf(wip): store instruction as single u64, perform bit operations on demand --- vm/src/types/instruction.rs | 218 +++++++++++++++++++++++++++---- vm/src/vm/context/run_context.rs | 35 ++--- vm/src/vm/decoding/decoder.rs | 15 ++- vm/src/vm/vm_core.rs | 56 ++++---- 4 files changed, 246 insertions(+), 78 deletions(-) diff --git a/vm/src/types/instruction.rs b/vm/src/types/instruction.rs index 133c691302..a3bb4d9730 100644 --- a/vm/src/types/instruction.rs +++ b/vm/src/types/instruction.rs @@ -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; @@ -14,19 +14,196 @@ pub enum Register { FP, } +// FIXME: impl Debug as decoded +// TODO: possibly exploit NonZero or NonMax so `Result` and `Option` +// 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 for Instruction { + type Error = (); + + fn try_from(v: u64) -> Result { + 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)] @@ -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() { @@ -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)] diff --git a/vm/src/vm/context/run_context.rs b/vm/src/vm/context/run_context.rs index d5de5d41f5..3469dc9da4 100644 --- a/vm/src/vm/context/run_context.rs +++ b/vm/src/vm/context/run_context.rs @@ -32,43 +32,46 @@ impl RunContext { pub fn compute_dst_addr( &self, - instruction: &Instruction, + instruction: Instruction, ) -> Result { - 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 { - 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 { - 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), }, @@ -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))?) } } diff --git a/vm/src/vm/decoding/decoder.rs b/vm/src/vm/decoding/decoder.rs index 35a98e7812..08ba20b8d5 100644 --- a/vm/src/vm/decoding/decoder.rs +++ b/vm/src/vm/decoding/decoder.rs @@ -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 { + // 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; @@ -125,9 +125,10 @@ pub fn decode_instruction(encoded_instr: u64) -> Result 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; diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 5b54b18b5d..e058e24580 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -131,10 +131,10 @@ impl VirtualMachine { fn update_fp( &mut self, - instruction: &Instruction, + instruction: Instruction, operands: &Operands, ) -> Result<(), VirtualMachineError> { - let new_fp_offset: usize = match instruction.fp_update { + let new_fp_offset: usize = match instruction.fp_update() { FpUpdate::APPlus2 => self.run_context.ap + 2, FpUpdate::Dst => match operands.dst { MaybeRelocatable::RelocatableValue(ref rel) => rel.offset, @@ -150,10 +150,10 @@ impl VirtualMachine { fn update_ap( &mut self, - instruction: &Instruction, + instruction: Instruction, operands: &Operands, ) -> Result<(), VirtualMachineError> { - let new_apset: usize = match instruction.ap_update { + let new_apset: usize = match instruction.ap_update() { ApUpdate::Add => match &operands.res { Some(res) => (self.run_context.get_ap() + res)?.offset, None => return Err(VirtualMachineError::UnconstrainedResAdd), @@ -168,10 +168,10 @@ impl VirtualMachine { fn update_pc( &mut self, - instruction: &Instruction, + instruction: Instruction, operands: &Operands, ) -> Result<(), VirtualMachineError> { - let new_pc: Relocatable = match instruction.pc_update { + let new_pc: Relocatable = match instruction.pc_update() { PcUpdate::Regular => (self.run_context.pc + instruction.size())?, PcUpdate::Jump => match operands.res.as_ref().and_then(|x| x.get_relocatable()) { Some(ref res) => *res, @@ -195,7 +195,7 @@ impl VirtualMachine { fn update_registers( &mut self, - instruction: &Instruction, + instruction: Instruction, operands: Operands, ) -> Result<(), VirtualMachineError> { self.update_fp(instruction, &operands)?; @@ -218,18 +218,18 @@ impl VirtualMachine { ///If res was already deduced, returns its deduced value as well. fn deduce_op0( &self, - instruction: &Instruction, + instruction: Instruction, dst: Option<&MaybeRelocatable>, op1: Option<&MaybeRelocatable>, ) -> Result<(Option, Option), VirtualMachineError> { - match instruction.opcode { + match instruction.opcode() { Opcode::Call => Ok(( Some(MaybeRelocatable::from( (self.run_context.pc + instruction.size())?, )), None, )), - Opcode::AssertEq => match (&instruction.res, dst, op1) { + Opcode::AssertEq => match (&instruction.res(), dst, op1) { (Res::Add, Some(dst_addr), Some(op1_addr)) => { Ok((Some(dst_addr.sub(op1_addr)?), dst.cloned())) } @@ -254,12 +254,12 @@ impl VirtualMachine { ///If res was already deduced, returns its deduced value as well. fn deduce_op1( &self, - instruction: &Instruction, + instruction: Instruction, dst: Option<&MaybeRelocatable>, op0: Option, ) -> Result<(Option, Option), VirtualMachineError> { - if let Opcode::AssertEq = instruction.opcode { - match instruction.res { + if let Opcode::AssertEq = instruction.opcode() { + match instruction.res() { Res::Op1 => return Ok((dst.cloned(), dst.cloned())), Res::Add => { return Ok(( @@ -305,11 +305,11 @@ impl VirtualMachine { ///Computes the value of res if possible fn compute_res( &self, - instruction: &Instruction, + instruction: Instruction, op0: &MaybeRelocatable, op1: &MaybeRelocatable, ) -> Result, VirtualMachineError> { - match instruction.res { + match instruction.res() { Res::Op1 => Ok(Some(op1.clone())), Res::Add => Ok(Some(op0.add(op1)?)), Res::Mul => { @@ -328,10 +328,10 @@ impl VirtualMachine { fn deduce_dst( &self, - instruction: &Instruction, + instruction: Instruction, res: &Option, ) -> Result { - let dst = match (instruction.opcode, res) { + let dst = match (instruction.opcode(), res) { (Opcode::AssertEq, Some(res)) => res.clone(), (Opcode::Call, _) => MaybeRelocatable::from(self.run_context.get_fp()), _ => return Err(VirtualMachineError::NoDst), @@ -341,10 +341,10 @@ impl VirtualMachine { fn opcode_assertions( &self, - instruction: &Instruction, + instruction: Instruction, operands: &Operands, ) -> Result<(), VirtualMachineError> { - match instruction.opcode { + match instruction.opcode() { Opcode::AssertEq => match &operands.res { None => Err(VirtualMachineError::UnconstrainedResAssertEq), Some(res) if res != &operands.dst => Err(VirtualMachineError::DiffAssertValues( @@ -401,7 +401,7 @@ impl VirtualMachine { Ok(()) } - fn run_instruction(&mut self, instruction: &Instruction) -> Result<(), VirtualMachineError> { + fn run_instruction(&mut self, instruction: Instruction) -> Result<(), VirtualMachineError> { let (operands, operands_addresses, deduced_operands) = self.compute_operands(instruction)?; self.insert_deduced_operands(deduced_operands, &operands, &operands_addresses)?; @@ -418,9 +418,9 @@ impl VirtualMachine { // Update range check limits const OFFSET_BITS: u32 = 16; let (off0, off1, off2) = ( - instruction.off0 + (1_isize << (OFFSET_BITS - 1)), - instruction.off1 + (1_isize << (OFFSET_BITS - 1)), - instruction.off2 + (1_isize << (OFFSET_BITS - 1)), + instruction.offset0() + (1_isize << (OFFSET_BITS - 1)), + instruction.offset1() + (1_isize << (OFFSET_BITS - 1)), + instruction.offset2() + (1_isize << (OFFSET_BITS - 1)), ); let (min, max) = self.rc_limits.unwrap_or((off0, off0)); self.rc_limits = Some(( @@ -521,7 +521,7 @@ impl VirtualMachine { if instruction.is_none() { *instruction = Some(self.decode_current_instruction()?); } - let instruction = instruction.as_ref().unwrap(); + let instruction = instruction.unwrap(); if !self.skip_instruction_execution { self.run_instruction(instruction)?; @@ -535,7 +535,7 @@ impl VirtualMachine { let instruction = self.decode_current_instruction()?; if !self.skip_instruction_execution { - self.run_instruction(&instruction)?; + self.run_instruction(instruction)?; } else { self.run_context.pc += instruction.size(); self.skip_instruction_execution = false; @@ -575,7 +575,7 @@ impl VirtualMachine { &self, op0_addr: Relocatable, res: &mut Option, - instruction: &Instruction, + instruction: Instruction, dst_op: &Option, op1_op: &Option, ) -> Result { @@ -597,7 +597,7 @@ impl VirtualMachine { &self, op1_addr: Relocatable, res: &mut Option, - instruction: &Instruction, + instruction: Instruction, dst_op: &Option, op0: &MaybeRelocatable, ) -> Result { @@ -622,7 +622,7 @@ impl VirtualMachine { /// value. pub fn compute_operands( &self, - instruction: &Instruction, + instruction: Instruction, ) -> Result<(Operands, OperandsAddresses, DeducedOperands), VirtualMachineError> { //Get operands from memory let dst_addr = self.run_context.compute_dst_addr(instruction)?;