Skip to content

Commit 0591347

Browse files
committed
Implement tier-2 JIT compiler to accelerate ISS
The T2C, designed for flexibility, efficiently transforms custom IR within a chained block into LLVM IR. After the translation, the built LLVM IR is offloaded to the LLVM backend, where it undergoes optimization through several selected LLVM passes. Subsequently, the optimized LLVM IR is passed to the LLVM execution engine, which compiles the optimized LLVM IR and returns a function pointer to the generated machine code. Because our design for T2C opts for a function pointer of the generated machine code instead of storing it in the code cache, we need not account for the binary format discrepancies among different compilers. This process allows for the possibility of substituting LLVM with alternative optimization frameworks if required.
1 parent 2cbd4aa commit 0591347

File tree

9 files changed

+1247
-20
lines changed

9 files changed

+1247
-20
lines changed

.github/workflows/main.yml

+18-11
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ jobs:
4444
sudo apt-get update -q -y
4545
sudo apt-get install -q -y libsdl2-dev libsdl2-mixer-dev
4646
.ci/riscv-toolchain-install.sh
47+
wget https://apt.llvm.org/llvm.sh
48+
sudo chmod +x ./llvm.sh
49+
sudo ./llvm.sh 17
4750
shell: bash
4851
- name: default build
4952
run: make
@@ -65,14 +68,14 @@ jobs:
6568
make distclean ENABLE_GDBSTUB=1 gdbstub-test
6669
- name: JIT test
6770
run: |
68-
make clean && make ENABLE_JIT=1 check -j$(nproc)
69-
make clean && make ENABLE_EXT_A=0 ENABLE_JIT=1 check -j$(nproc)
70-
make clean && make ENABLE_EXT_F=0 ENABLE_JIT=1 check -j$(nproc)
71-
make clean && make ENABLE_EXT_C=0 ENABLE_JIT=1 check -j$(nproc)
71+
make ENABLE_JIT=1 clean && make ENABLE_JIT=1 check -j$(nproc)
72+
make ENABLE_JIT=1 clean && make ENABLE_EXT_A=0 ENABLE_JIT=1 check -j$(nproc)
73+
make ENABLE_JIT=1 clean && make ENABLE_EXT_F=0 ENABLE_JIT=1 check -j$(nproc)
74+
make ENABLE_JIT=1 clean && make ENABLE_EXT_C=0 ENABLE_JIT=1 check -j$(nproc)
7275
- name: undefined behavior test
7376
run: |
7477
make clean && make ENABLE_UBSAN=1 check -j$(nproc)
75-
make clean && make ENABLE_JIT=1 ENABLE_UBSAN=1 check -j$(nproc)
78+
make ENABLE_JIT=1 clean clean && make ENABLE_JIT=1 ENABLE_UBSAN=1 check -j$(nproc)
7679
7780
host-arm64:
7881
needs: [detect-code-related-file-changes]
@@ -91,18 +94,21 @@ jobs:
9194
# No 'sudo' is available
9295
install: |
9396
apt-get update -q -y
94-
apt-get install -q -y git build-essential libsdl2-dev libsdl2-mixer-dev
97+
apt-get install -q -y git build-essential libsdl2-dev libsdl2-mixer-dev lsb-release wget software-properties-common gnupg
9598
git config --global --add safe.directory ${{ github.workspace }}
9699
git config --global --add safe.directory ${{ github.workspace }}/src/softfloat
97100
git config --global --add safe.directory ${{ github.workspace }}/src/mini-gdbstub
101+
wget https://apt.llvm.org/llvm.sh
102+
chmod +x ./llvm.sh
103+
./llvm.sh 17
98104
# Append custom commands here
99105
run: |
100106
make -j$(nproc)
101107
make check -j$(nproc)
102-
make clean && make ENABLE_JIT=1 check -j$(nproc)
103-
make clean && make ENABLE_EXT_A=0 ENABLE_JIT=1 check -j$(nproc)
104-
make clean && make ENABLE_EXT_F=0 ENABLE_JIT=1 check -j$(nproc)
105-
make clean && make ENABLE_EXT_C=0 ENABLE_JIT=1 check -j$(nproc)
108+
make ENABLE_JIT=1 clean && make ENABLE_JIT=1 check -j$(nproc)
109+
make ENABLE_JIT=1 clean && make ENABLE_EXT_A=0 ENABLE_JIT=1 check -j$(nproc)
110+
make ENABLE_JIT=1 clean && make ENABLE_EXT_F=0 ENABLE_JIT=1 check -j$(nproc)
111+
make ENABLE_JIT=1 clean && make ENABLE_EXT_C=0 ENABLE_JIT=1 check -j$(nproc)
106112
107113
coding-style:
108114
needs: [detect-code-related-file-changes]
@@ -132,7 +138,8 @@ jobs:
132138
- name: run scan-build without JIT
133139
run: make distclean && scan-build -v -o ~/scan-build --status-bugs --use-cc=clang --force-analyze-debug-code --show-description -analyzer-config stable-report-filename=true -enable-checker valist,nullability make ENABLE_EXT_F=0 ENABLE_SDL=0 ENABLE_JIT=0
134140
- name: run scan-build with JIT
135-
run: make distclean && scan-build -v -o ~/scan-build --status-bugs --use-cc=clang --force-analyze-debug-code --show-description -analyzer-config stable-report-filename=true -enable-checker valist,nullability make ENABLE_EXT_F=0 ENABLE_SDL=0 ENABLE_JIT=1
141+
run: |
142+
make ENABLE_JIT=1 distclean && scan-build -v -o ~/scan-build --status-bugs --use-cc=clang --force-analyze-debug-code --show-description -analyzer-config stable-report-filename=true -enable-checker valist,nullability make ENABLE_EXT_F=0 ENABLE_SDL=0 ENABLE_JIT=1
136143
137144
compliance-test:
138145
needs: [detect-code-related-file-changes]

Makefile

+30-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,32 @@ endif
125125
ENABLE_JIT ?= 0
126126
$(call set-feature, JIT)
127127
ifeq ($(call has, JIT), 1)
128-
OBJS_EXT += jit.o
128+
OBJS_EXT += jit.o
129+
# tier-2 JIT compiler powered LLVM
130+
LLVM_CONFIG = llvm-config-17
131+
LLVM_CONFIG := $(shell which $(LLVM_CONFIG))
132+
ifndef LLVM_CONFIG
133+
# Try Homebrew on macOS
134+
LLVM_CONFIG = /opt/homebrew/opt/llvm@17/bin/llvm-config
135+
LLVM_CONFIG := $(shell which $(LLVM_CONFIG))
136+
ifdef LLVM_CONFIG
137+
LDFLAGS += -L/opt/homebrew/opt/llvm@17/lib
138+
endif
139+
endif
140+
ifneq ("$(LLVM_CONFIG)", "")
141+
ifneq ("$(findstring -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS, "$(shell $(LLVM_CONFIG) --cflags)")", "")
142+
ENABLE_T2C := 1
143+
$(call set-feature, T2C)
144+
OBJS_EXT += t2c.o
145+
CFLAGS += -g $(shell $(LLVM_CONFIG) --cflags)
146+
LDFLAGS += $(shell $(LLVM_CONFIG) --libs)
147+
else
148+
ENABLE_T2C := 0
149+
$(call set-feature, T2C)
150+
$(warning No llvm-config-17 installed. Check llvm-config-17 installation in advance)
151+
endif
152+
endif
153+
129154
ifneq ($(processor),$(filter $(processor),x86_64 aarch64 arm64))
130155
$(error JIT mode only supports for x64 and arm64 target currently.)
131156
endif
@@ -136,6 +161,10 @@ src/rv32_jit.c:
136161
$(OUT)/jit.o: src/jit.c src/rv32_jit.c
137162
$(VECHO) " CC\t$@\n"
138163
$(Q)$(CC) -o $@ $(CFLAGS) -c -MMD -MF $@.d $<
164+
165+
$(OUT)/t2c.o: src/t2c.c src/t2c_template.c
166+
$(VECHO) " CC\t$@\n"
167+
$(Q)$(CC) -o $@ $(CFLAGS) -c -MMD -MF $@.d $<
139168
endif
140169
# For tail-call elimination, we need a specific set of build flags applied.
141170
# FIXME: On macOS + Apple Silicon, -fno-stack-protector might have a negative impact.

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Features:
3030
* Implementation of commonly used newlib system calls
3131
* Experimental SDL-based display/event/audio system calls for running video games
3232
* Support for remote GDB debugging
33-
* Experimental JIT compiler for performance boost while maintaining a small footprint
33+
* Tiered JIT compilation for performance boost while maintaining a small footprint
3434

3535
## Build and Verify
3636

@@ -40,6 +40,12 @@ and [SDL2_Mixer library](https://wiki.libsdl.org/SDL2_mixer) installed.
4040
* macOS: `brew install sdl2 sdl2_mixer`
4141
* Ubuntu Linux / Debian: `sudo apt install libsdl2-dev libsdl2-mixer-dev`
4242

43+
### JIT compiler
44+
The tier-2 JIT compiler in `rv32emu` leverages LLVM for powerful optimization. Therefore, the target system must have [`LLVM`](https://llvm.org/) installed, with version 17 recommended. If `LLVM` is not installed, only the tier-1 JIT compiler will be used for performance enhancement.
45+
46+
* macOS: `brew install llvm@17`
47+
* Ubuntu Linux / Debian: `sudo apt-get install llvm-17`
48+
4349
Build the emulator.
4450
```shell
4551
$ make

src/emulate.c

+21-5
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,9 @@ static block_t *block_alloc(riscv_t *rv)
304304
#if RV32_HAS(JIT)
305305
block->translatable = true;
306306
block->hot = false;
307+
block->hot2 = false;
307308
block->has_loops = false;
309+
block->n_invoke = 0;
308310
INIT_LIST_HEAD(&block->list);
309311
#endif
310312
return block;
@@ -910,8 +912,6 @@ static bool runtime_profiler(riscv_t *rv, block_t *block)
910912
return true;
911913
return false;
912914
}
913-
914-
typedef void (*exec_block_func_t)(riscv_t *rv, uintptr_t);
915915
#endif
916916

917917
void rv_step(void *arg)
@@ -984,15 +984,31 @@ void rv_step(void *arg)
984984
}
985985
last_pc = rv->PC;
986986
#if RV32_HAS(JIT)
987-
/* execute by tier-1 JIT compiler */
987+
#if RV32_HAS(T2C)
988+
/* executed through the tier-2 JIT compiler */
989+
if (block->hot2) {
990+
((exec_t2c_func_t) block->func)(rv);
991+
prev = NULL;
992+
continue;
993+
} /* check if the execution path is strong hotspot */
994+
if (block->n_invoke >= THRESHOLD) {
995+
t2_compile(block,
996+
(uint64_t) ((memory_t *) PRIV(rv)->mem)->mem_base);
997+
((exec_t2c_func_t) block->func)(rv);
998+
prev = NULL;
999+
continue;
1000+
}
1001+
#endif
1002+
/* executed through the tier-1 JIT compiler */
9881003
struct jit_state *state = rv->jit_state;
9891004
if (block->hot) {
1005+
block->n_invoke++;
9901006
((exec_block_func_t) state->buf)(
9911007
rv, (uintptr_t) (state->buf + block->offset));
9921008
prev = NULL;
9931009
continue;
994-
} /* check if using frequency of block exceed threshold */
995-
else if (block->translatable && runtime_profiler(rv, block)) {
1010+
} /* check if the execution path is potential hotspot */
1011+
if (block->translatable && runtime_profiler(rv, block)) {
9961012
jit_translate(rv, block);
9971013
((exec_block_func_t) state->buf)(
9981014
rv, (uintptr_t) (state->buf + block->offset));

src/feature.h

+5
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,10 @@
5252
#define RV32_FEATURE_JIT 0
5353
#endif
5454

55+
/* Experimental tier-2 just-in-time compiler */
56+
#ifndef RV32_FEATURE_T2C
57+
#define RV32_FEATURE_T2C 0
58+
#endif
59+
5560
/* Feature test macro */
5661
#define RV32_HAS(x) RV32_FEATURE_##x

src/jit.h

+6
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,9 @@ struct host_reg {
4848
struct jit_state *jit_state_init(size_t size);
4949
void jit_state_exit(struct jit_state *state);
5050
void jit_translate(riscv_t *rv, block_t *block);
51+
typedef void (*exec_block_func_t)(riscv_t *rv, uintptr_t);
52+
53+
#if RV32_HAS(T2C)
54+
void t2_compile(block_t *block, uint64_t mem_base);
55+
typedef void (*exec_t2c_func_t)(riscv_t *);
56+
#endif

src/riscv_private.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,14 @@ typedef struct block {
6565

6666
rv_insn_t *ir_head, *ir_tail; /**< the first and last ir for this block */
6767
#if RV32_HAS(JIT)
68-
bool hot; /**< Determine the block is hotspot or not */
69-
uint32_t offset;
68+
bool hot; /**< Determine the block is potential hotspot or not */
69+
bool hot2; /**< Determine the block is strong hotspot or not */
7070
bool
7171
translatable; /**< Determine the block has RV32AF insturctions or not */
7272
bool has_loops; /**< Determine the block has loop or not */
73+
uint32_t offset; /**< The machine code offset in T1 code cache */
74+
uint32_t n_invoke; /**< The invoking times of T1 machine code */
75+
void *func; /**< The function pointer of T2 machine code */
7376
struct list_head list;
7477
#endif
7578
} block_t;

0 commit comments

Comments
 (0)