Skip to content

Commit

Permalink
perf: add decoder instructions cache (#180)
Browse files Browse the repository at this point in the history
* perf: add decoder instructions cache

* fix: resolve PC out-of-bound error and apply review comment
  • Loading branch information
quake authored Jul 23, 2021
1 parent 595dac5 commit 56ece0c
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 13 deletions.
28 changes: 23 additions & 5 deletions src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,24 @@ use crate::instructions::{
InstructionFactory, Itype, R4type, Register, Rtype, Utype,
};
use crate::memory::Memory;
use crate::{Error, ISA_B, ISA_MOP, RISCV_PAGESIZE};
use crate::{Error, ISA_B, ISA_MOP, RISCV_MAX_MEMORY, RISCV_PAGESIZE};

const RISCV_PAGESIZE_MASK: u64 = RISCV_PAGESIZE as u64 - 1;
const INSTRUCTION_CACHE_SIZE: usize = 4096;

#[derive(Default)]
pub struct Decoder {
factories: Vec<InstructionFactory>,
mop: bool,
// use a cache of instructions to avoid decoding the same instruction twice, pc is the key and the instruction is the value
instructions_cache: [(u64, u64); INSTRUCTION_CACHE_SIZE],
}

impl Decoder {
pub fn new(mop: bool) -> Decoder {
Decoder {
factories: vec![],
mop,
instructions_cache: [(RISCV_MAX_MEMORY as u64, 0); INSTRUCTION_CACHE_SIZE],
}
}

Expand Down Expand Up @@ -80,10 +83,21 @@ impl Decoder {
}
}

pub fn decode_raw<M: Memory>(&self, memory: &mut M, pc: u64) -> Result<Instruction, Error> {
pub fn decode_raw<M: Memory>(&mut self, memory: &mut M, pc: u64) -> Result<Instruction, Error> {
// since we are using RISCV_MAX_MEMORY as the default key in the instruction cache, have to check out of bound error first
if pc as usize >= RISCV_MAX_MEMORY {
return Err(Error::OutOfBound);
}
// according to RISC-V instruction encoding, the lowest bit in PC will always be zero
let instruction_cache_key = (pc >> 1) as usize % INSTRUCTION_CACHE_SIZE;
let cached_instruction = self.instructions_cache[instruction_cache_key];
if cached_instruction.0 == pc {
return Ok(cached_instruction.1);
}
let instruction_bits = self.decode_bits(memory, pc)?;
for factory in &self.factories {
if let Some(instruction) = factory(instruction_bits) {
self.instructions_cache[instruction_cache_key] = (pc, instruction);
return Ok(instruction);
}
}
Expand All @@ -97,7 +111,7 @@ impl Decoder {
// - https://riscv.org/wp-content/uploads/2016/07/Tue1130celio-fusion-finalV2.pdf
// - https://en.wikichip.org/wiki/macro-operation_fusion#Proposed_fusion_operations
// - https://carrv.github.io/2017/papers/clark-rv8-carrv2017.pdf
pub fn decode_mop<M: Memory>(&self, memory: &mut M, pc: u64) -> Result<Instruction, Error> {
pub fn decode_mop<M: Memory>(&mut self, memory: &mut M, pc: u64) -> Result<Instruction, Error> {
let head_instruction = self.decode_raw(memory, pc)?;
let head_opcode = extract_opcode(head_instruction);
match head_opcode {
Expand Down Expand Up @@ -523,13 +537,17 @@ impl Decoder {
}
}

pub fn decode<M: Memory>(&self, memory: &mut M, pc: u64) -> Result<Instruction, Error> {
pub fn decode<M: Memory>(&mut self, memory: &mut M, pc: u64) -> Result<Instruction, Error> {
if self.mop {
self.decode_mop(memory, pc)
} else {
self.decode_raw(memory, pc)
}
}

pub fn reset_instructions_cache(&mut self) {
self.instructions_cache = [(RISCV_MAX_MEMORY as u64, 0); INSTRUCTION_CACHE_SIZE];
}
}

pub fn build_decoder<R: Register>(isa: u8) -> Decoder {
Expand Down
4 changes: 2 additions & 2 deletions src/machine/aot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ impl LabelGatheringMachine {
}

pub fn gather(&mut self) -> Result<(), Error> {
let decoder = build_decoder::<u64>(self.isa());
let mut decoder = build_decoder::<u64>(self.isa());
for i in 0..self.sections.len() {
let (section_start, section_end) = self.sections[i];
self.pc = Value::from_u64(section_start);
Expand Down Expand Up @@ -565,7 +565,7 @@ impl AotCompilingMachine {
}

pub fn compile(&mut self) -> Result<AotCode, Error> {
let decoder = build_decoder::<u64>(self.isa());
let mut decoder = build_decoder::<u64>(self.isa());
let mut instructions = [Instruction::default(); MAXIMUM_INSTRUCTIONS_PER_BLOCK];
for i in 0..self.sections.len() {
let (section_start, section_end) = self.sections[i];
Expand Down
5 changes: 3 additions & 2 deletions src/machine/asm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,10 +363,11 @@ impl<'a> AsmMachine<'a> {
if self.machine.isa() & ISA_MOP != 0 && self.machine.version() == VERSION0 {
return Err(Error::InvalidVersion);
}
let decoder = build_decoder::<u64>(self.machine.isa());
let mut decoder = build_decoder::<u64>(self.machine.isa());
self.machine.set_running(true);
while self.machine.running() {
if self.machine.reset_signal() {
decoder.reset_instructions_cache();
self.aot_code = None;
}
let result = if let Some(aot_code) = &self.aot_code {
Expand Down Expand Up @@ -442,7 +443,7 @@ impl<'a> AsmMachine<'a> {
Ok(self.machine.exit_code())
}

pub fn step(&mut self, decoder: &Decoder) -> Result<(), Error> {
pub fn step(&mut self, decoder: &mut Decoder) -> Result<(), Error> {
// Decode only one instruction into a trace
let pc = *self.machine.pc();
let slot = calculate_slot(pc);
Expand Down
9 changes: 6 additions & 3 deletions src/machine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,15 +566,18 @@ impl<'a, Inner: SupportMachine> DefaultMachine<'a, Inner> {
if self.isa() & ISA_MOP != 0 && self.version() == VERSION0 {
return Err(Error::InvalidVersion);
}
let decoder = build_decoder::<Inner::REG>(self.isa());
let mut decoder = build_decoder::<Inner::REG>(self.isa());
self.set_running(true);
while self.running() {
self.step(&decoder)?;
if self.reset_signal() {
decoder.reset_instructions_cache();
}
self.step(&mut decoder)?;
}
Ok(self.exit_code())
}

pub fn step(&mut self, decoder: &Decoder) -> Result<(), Error> {
pub fn step(&mut self, decoder: &mut Decoder) -> Result<(), Error> {
let instruction = {
let pc = self.pc().to_u64();
let memory = self.memory_mut();
Expand Down
3 changes: 2 additions & 1 deletion src/machine/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,15 @@ impl<'a, Inner: SupportMachine> TraceMachine<'a, Inner> {
}

pub fn run(&mut self) -> Result<i8, Error> {
let decoder = build_decoder::<Inner::REG>(self.isa());
let mut decoder = build_decoder::<Inner::REG>(self.isa());
self.machine.set_running(true);
// For current trace size this is acceptable, however we might want
// to tweak the code here if we choose to use a larger trace size or
// larger trace item length.
self.traces.resize_with(TRACE_SIZE, Trace::default);
while self.machine.running() {
if self.machine.reset_signal() {
decoder.reset_instructions_cache();
for i in self.traces.iter_mut() {
*i = Trace::default()
}
Expand Down
Binary file not shown.
19 changes: 19 additions & 0 deletions tests/test_asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,22 @@ pub fn test_asm_cycles_overflow() {
assert!(result.is_err());
assert_eq!(result.unwrap_err(), Error::CyclesOverflow);
}

#[test]
pub fn test_decoder_instructions_cache_pc_out_of_bound_timeout() {
let buffer = fs::read("tests/programs/decoder_instructions_cache_pc_out_of_bound_timeout")
.unwrap()
.into();
let asm_core = AsmCoreMachine::new(ISA_IMC, VERSION0, u64::MAX);
let core = DefaultMachineBuilder::<Box<AsmCoreMachine>>::new(asm_core)
.instruction_cycle_func(Box::new(|_| 1))
.build();
let mut machine = AsmMachine::new(core, None);
machine.machine.set_cycles(u64::MAX - 10);
machine
.load_program(&buffer, &vec!["simple64".into()])
.unwrap();
let result = machine.run();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), Error::OutOfBound);
}

0 comments on commit 56ece0c

Please sign in to comment.