Browse Source

Remove Duff's Device in main simulation loop (#721)

With recent compilers on recent computers, the much simpler version of
the code is actually slightly faster.  I suspect, but haven't proven,
that more accurate indirect jump prediction is the main explanation.
Reduced I$ pressure might be a secondary factor.
pull/808/head
Andrew Waterman 5 years ago
committed by GitHub
parent
commit
bf4b1e09ed
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 55
      riscv/execute.cc
  2. 8
      riscv/gen_icache
  3. 7
      riscv/riscv.mk.in

55
riscv/execute.cc

@ -256,6 +256,7 @@ void processor_t::step(size_t n)
if (unlikely(slow_path()))
{
// Main simulation loop, slow path.
while (instret < n)
{
if (unlikely(!state.serialized && state.single_step == state.STEP_STEPPED)) {
@ -280,49 +281,17 @@ void processor_t::step(size_t n)
}
else while (instret < n)
{
// This code uses a modified Duff's Device to improve the performance
// of executing instructions. While typical Duff's Devices are used
// for software pipelining, the switch statement below primarily
// benefits from separate call points for the fetch.func function call
// found in each execute_insn. This function call is an indirect jump
// that depends on the current instruction. By having an indirect jump
// dedicated for each icache entry, you improve the performance of the
// host's next address predictor. Each case in the switch statement
// allows for the program flow to contine to the next case if it
// corresponds to the next instruction in the program and instret is
// still less than n.
//
// According to Andrew Waterman's recollection, this optimization
// resulted in approximately a 2x performance increase.
// This figures out where to jump to in the switch statement
size_t idx = _mmu->icache_index(pc);
// This gets the cached decoded instruction from the MMU. If the MMU
// does not have the current pc cached, it will refill the MMU and
// return the correct entry. ic_entry->data.func is the C++ function
// corresponding to the instruction.
auto ic_entry = _mmu->access_icache(pc);
// This macro is included in "icache.h" included within the switch
// statement below. The indirect jump corresponding to the instruction
// is located within the execute_insn() function call.
#define ICACHE_ACCESS(i) { \
insn_fetch_t fetch = ic_entry->data; \
pc = execute_insn(this, pc, fetch); \
ic_entry = ic_entry->next; \
if (i == mmu_t::ICACHE_ENTRIES-1) break; \
if (unlikely(ic_entry->tag != pc)) break; \
if (unlikely(instret+1 == n)) break; \
instret++; \
state.pc = pc; \
}
// This switch statement implements the modified Duff's device as
// explained above.
switch (idx) {
// "icache.h" is generated by the gen_icache script
#include "icache.h"
// Main simulation loop, fast path.
for (auto ic_entry = _mmu->access_icache(pc); ; ) {
auto fetch = ic_entry->data;
pc = execute_insn(this, pc, fetch);
ic_entry = ic_entry->next;
if (unlikely(ic_entry->tag != pc))
break;
if (unlikely(instret + 1 == n))
break;
instret++;
state.pc = pc;
}
advance_pc();

8
riscv/gen_icache

@ -1,8 +0,0 @@
#!/bin/sh
i=0
while [ $i -lt $1 ]
do
echo case $i: ICACHE_ACCESS\($i\)\;
i=$((i+1))
done
echo

7
riscv/riscv.mk.in

@ -63,7 +63,6 @@ riscv_srcs = \
riscv_test_srcs =
riscv_gen_hdrs = \
icache.h \
insn_list.h \
@ -1286,12 +1285,6 @@ riscv_insn_list = \
riscv_gen_srcs = \
$(addsuffix .cc,$(riscv_insn_list))
icache_entries := `grep "ICACHE_ENTRIES =" $(src_dir)/riscv/mmu.h | sed 's/.* = \(.*\);/\1/'`
icache.h: mmu.h
$(src_dir)/riscv/gen_icache $(icache_entries) > $@.tmp
mv $@.tmp $@
insn_list.h: $(src_dir)/riscv/riscv.mk.in
for insn in $(foreach insn,$(riscv_insn_list),$(subst .,_,$(insn))) ; do \
printf 'DEFINE_INSN(%s)\n' "$${insn}" ; \

Loading…
Cancel
Save