Skip to content

Commit c8f9876

Browse files
committed
Improve "JALR" execution using JIT-cache
Currently, the "JALR" indirect jump instruction turns the mode of rv32emu from T2C back to the interpreter. This commit introduces a "JIT-cache" table lookup to make it redirect to the JIT-ed code entry and avoids the mode change. There are several scenarios benefitting from this approach, e.g. function pointer invocation and far-way function call. The former like "qsort" can be speeded up two times, and the latter like "Fibonacci", which compiles from the hand-written assembly, can even reach x4.3 performance enhencement.
1 parent 9759ad2 commit c8f9876

File tree

10 files changed

+229
-30
lines changed

10 files changed

+229
-30
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ ifneq ("$(LLVM_CONFIG)", "")
141141
ifneq ("$(findstring -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS, "$(shell $(LLVM_CONFIG) --cflags)")", "")
142142
ENABLE_T2C := 1
143143
$(call set-feature, T2C)
144-
OBJS_EXT += t2c.o
144+
OBJS_EXT += jit-cache.o t2c.o
145145
CFLAGS += -g $(shell $(LLVM_CONFIG) --cflags)
146146
LDFLAGS += $(shell $(LLVM_CONFIG) --libs)
147147
else

build/Fibonacci.elf

217 KB
Binary file not shown.

src/jit-cache.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* rv32emu is freely redistributable under the MIT License. See the file
3+
* "LICENSE" for information on usage and redistribution of this file.
4+
*/
5+
6+
#include <stdlib.h>
7+
#include <string.h>
8+
9+
#include "jit-cache.h"
10+
11+
jit_cache_t *jit_cache_init()
12+
{
13+
return calloc(MAX_JIT_CACHE_TABLE_ENTRIES, sizeof(jit_cache_t));
14+
}
15+
16+
void jit_cache_exit(jit_cache_t *cache)
17+
{
18+
free(cache);
19+
}
20+
21+
void jit_cache_update(jit_cache_t *cache,
22+
uint32_t pc,
23+
uint32_t from,
24+
void *entry)
25+
{
26+
uint32_t pos = pc & (MAX_JIT_CACHE_TABLE_ENTRIES - 1);
27+
28+
cache[pos].pc = pc;
29+
cache[pos].from = from;
30+
cache[pos].entry = entry;
31+
}
32+
33+
void jit_cache_clear(jit_cache_t *cache)
34+
{
35+
memset(cache, 0, MAX_JIT_CACHE_TABLE_ENTRIES * sizeof(jit_cache_t));
36+
}

src/jit-cache.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* rv32emu is freely redistributable under the MIT License. See the file
3+
* "LICENSE" for information on usage and redistribution of this file.
4+
*/
5+
6+
#pragma once
7+
8+
#if !defined(__x86_64__) && !defined(__aarch64__)
9+
/* make sure LLVM can access the correct address of structure member */
10+
#error "The JIT-cache only supports 64-bit machine (LP64)."
11+
#endif
12+
13+
#include <stdint.h>
14+
15+
#define MAX_JIT_CACHE_TABLE_ENTRIES (1 << 12)
16+
17+
/* clang-format off */
18+
typedef struct jit_cache {
19+
__attribute__((aligned(4))) uint32_t pc; /* program counter of ELF */
20+
__attribute__((aligned(4))) uint32_t from; /* from whether t1c or t2c,
21+
easily to build LLVM IR
22+
because of the alignment
23+
*/
24+
void *entry; /* entry of JIT-ed code */
25+
} jit_cache_t;
26+
/* clang-format on */
27+
28+
jit_cache_t *jit_cache_init();
29+
30+
void jit_cache_exit(jit_cache_t *cache);
31+
32+
void jit_cache_update(jit_cache_t *cache,
33+
uint32_t pc,
34+
uint32_t from,
35+
void *entry);
36+
37+
void jit_cache_clear(jit_cache_t *cache);

src/jit.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "cache.h"
3838
#include "decode.h"
3939
#include "io.h"
40+
#include "jit-cache.h"
4041
#include "jit.h"
4142
#include "riscv.h"
4243
#include "riscv_private.h"
@@ -1864,6 +1865,7 @@ static void code_cache_flush(struct jit_state *state, riscv_t *rv)
18641865
state->offset = state->org_size;
18651866
state->n_blocks = 0;
18661867
set_reset(&state->set);
1868+
jit_cache_clear(rv->jit_cache);
18671869
clear_cache_hot(rv->block_cache, (clear_func_t) clear_hot);
18681870
return;
18691871
}
@@ -1934,6 +1936,9 @@ static void translate_chained_block(struct jit_state *state,
19341936

19351937
set_add(&state->set, block->pc_start);
19361938
offset_map_insert(state, block->pc_start);
1939+
block->offset = state->offset;
1940+
jit_cache_update(rv->jit_cache, block->pc_start, 1,
1941+
state->buf + block->offset);
19371942
translate(state, rv, block);
19381943
if (unlikely(should_flush))
19391944
return;

src/jit.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ void jit_translate(riscv_t *rv, block_t *block);
5151
typedef void (*exec_block_func_t)(riscv_t *rv, uintptr_t);
5252

5353
#if RV32_HAS(T2C)
54-
void t2c_compile(block_t *block, uint64_t mem_base);
54+
void t2c_compile(riscv_t *, block_t *);
5555
typedef void (*exec_t2c_func_t)(riscv_t *);
5656
#endif

src/riscv.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <pthread.h>
3333
#endif
3434
#include "cache.h"
35+
#include "jit-cache.h"
3536
#include "jit.h"
3637
#define CODE_CACHE_SIZE (4 * 1024 * 1024)
3738
#endif
@@ -199,8 +200,7 @@ static void *t2c_runloop(void *arg)
199200
pthread_mutex_lock(&rv->wait_queue_lock);
200201
list_del_init(&entry->list);
201202
pthread_mutex_unlock(&rv->wait_queue_lock);
202-
t2c_compile(entry->block,
203-
(uint64_t) ((memory_t *) PRIV(rv)->mem)->mem_base);
203+
t2c_compile(rv, entry->block);
204204
free(entry);
205205
}
206206
}
@@ -291,6 +291,7 @@ riscv_t *rv_create(riscv_user_t rv_attr)
291291
mpool_create(sizeof(chain_entry_t) << BLOCK_IR_MAP_CAPACITY_BITS,
292292
sizeof(chain_entry_t));
293293
rv->jit_state = jit_state_init(CODE_CACHE_SIZE);
294+
rv->jit_cache = jit_cache_init();
294295
rv->block_cache = cache_create(BLOCK_MAP_CAPACITY_BITS);
295296
assert(rv->block_cache);
296297
#if RV32_HAS(T2C)
@@ -392,6 +393,7 @@ void rv_delete(riscv_t *rv)
392393
#endif
393394
mpool_destroy(rv->chain_entry_mp);
394395
jit_state_exit(rv->jit_state);
396+
jit_cache_exit(rv->jit_cache);
395397
cache_free(rv->block_cache);
396398
#endif
397399
free(rv);

src/riscv_private.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ struct riscv_internal {
155155
struct mpool *block_mp, *block_ir_mp;
156156

157157
void *jit_state;
158+
void *jit_cache;
158159
#if RV32_HAS(GDBSTUB)
159160
/* gdbstub instance */
160161
gdbstub_t gdbstub;

src/t2c.c

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <llvm-c/Transforms/PassBuilder.h>
1212
#include <stdlib.h>
1313

14+
#include "jit-cache.h"
1415
#include "jit.h"
1516
#include "riscv_private.h"
1617

@@ -49,15 +50,15 @@ FORCE_INLINE LLVMBasicBlockRef t2c_block_map_search(struct LLVM_block_map *map,
4950
return NULL;
5051
}
5152

52-
#define T2C_OP(inst, code) \
53-
static void t2c_##inst( \
54-
LLVMBuilderRef *builder UNUSED, LLVMTypeRef *param_types UNUSED, \
55-
LLVMValueRef start UNUSED, LLVMBasicBlockRef *entry UNUSED, \
56-
LLVMBuilderRef *taken_builder UNUSED, \
57-
LLVMBuilderRef *untaken_builder UNUSED, uint64_t mem_base UNUSED, \
58-
rv_insn_t *ir UNUSED) \
59-
{ \
60-
code; \
53+
#define T2C_OP(inst, code) \
54+
static void t2c_##inst( \
55+
LLVMBuilderRef *builder UNUSED, LLVMTypeRef *param_types UNUSED, \
56+
LLVMValueRef start UNUSED, LLVMBasicBlockRef *entry UNUSED, \
57+
LLVMBuilderRef *taken_builder UNUSED, \
58+
LLVMBuilderRef *untaken_builder UNUSED, riscv_t *rv UNUSED, \
59+
uint64_t mem_base UNUSED, rv_insn_t *ir UNUSED) \
60+
{ \
61+
code; \
6162
}
6263

6364
#define T2C_LLVM_GEN_ADDR(reg, rv_member, ir_member) \
@@ -135,6 +136,12 @@ FORCE_INLINE void t2c_gen_call_io_func(LLVMValueRef start,
135136
&io_param, 1, "");
136137
}
137138

139+
static bool t2c_jit_cache_triggered;
140+
static LLVMTypeRef t2c_t1c_cache_func_proto;
141+
static LLVMValueRef t2c_t1c_cache_func;
142+
static LLVMTypeRef t2c_t2c_cache_func_proto;
143+
static LLVMTypeRef jit_cache_ty;
144+
138145
#include "t2c_template.c"
139146
#undef T2C_OP
140147

@@ -174,14 +181,15 @@ typedef void (*t2c_codegen_block_func_t)(LLVMBuilderRef *builder UNUSED,
174181
LLVMBasicBlockRef *entry UNUSED,
175182
LLVMBuilderRef *taken_builder UNUSED,
176183
LLVMBuilderRef *untaken_builder UNUSED,
184+
riscv_t *rv UNUSED,
177185
uint64_t mem_base UNUSED,
178186
rv_insn_t *ir UNUSED);
179187

180188
static void t2c_trace_ebb(LLVMBuilderRef *builder,
181189
LLVMTypeRef *param_types UNUSED,
182190
LLVMValueRef start,
183191
LLVMBasicBlockRef *entry,
184-
uint64_t mem_base,
192+
riscv_t *rv,
185193
rv_insn_t *ir,
186194
set_t *set,
187195
struct LLVM_block_map *map)
@@ -194,7 +202,8 @@ static void t2c_trace_ebb(LLVMBuilderRef *builder,
194202

195203
while (1) {
196204
((t2c_codegen_block_func_t) dispatch_table[ir->opcode])(
197-
builder, param_types, start, entry, &tk, &utk, mem_base, ir);
205+
builder, param_types, start, entry, &tk, &utk, rv,
206+
(uint64_t) ((memory_t *) PRIV(rv)->mem)->mem_base, ir);
198207
if (!ir->next)
199208
break;
200209
ir = ir->next;
@@ -214,8 +223,7 @@ static void t2c_trace_ebb(LLVMBuilderRef *builder,
214223
LLVMPositionBuilderAtEnd(untaken_builder, untaken_entry);
215224
LLVMBuildBr(utk, untaken_entry);
216225
t2c_trace_ebb(&untaken_builder, param_types, start,
217-
&untaken_entry, mem_base, ir->branch_untaken, set,
218-
map);
226+
&untaken_entry, rv, ir->branch_untaken, set, map);
219227
}
220228
}
221229
if (ir->branch_taken) {
@@ -230,14 +238,16 @@ static void t2c_trace_ebb(LLVMBuilderRef *builder,
230238
LLVMPositionBuilderAtEnd(taken_builder, taken_entry);
231239
LLVMBuildBr(tk, taken_entry);
232240
t2c_trace_ebb(&taken_builder, param_types, start, &taken_entry,
233-
mem_base, ir->branch_taken, set, map);
241+
rv, ir->branch_taken, set, map);
234242
}
235243
}
236244
}
237245
}
238246

239-
void t2c_compile(block_t *block, uint64_t mem_base)
247+
void t2c_compile(riscv_t *rv, block_t *block)
240248
{
249+
t2c_jit_cache_triggered = false;
250+
241251
LLVMModuleRef module = LLVMModuleCreateWithName("my_module");
242252
LLVMTypeRef io_members[] = {
243253
LLVMPointerType(LLVMVoidType(), 0), LLVMPointerType(LLVMVoidType(), 0),
@@ -254,6 +264,22 @@ void t2c_compile(block_t *block, uint64_t mem_base)
254264
LLVMTypeRef param_types[] = {LLVMPointerType(struct_rv, 0)};
255265
LLVMValueRef start = LLVMAddFunction(
256266
module, "start", LLVMFunctionType(LLVMVoidType(), param_types, 1, 0));
267+
268+
LLVMTypeRef t1c_args[2] = {LLVMInt64Type(), LLVMInt64Type()};
269+
t2c_t1c_cache_func_proto =
270+
LLVMFunctionType(LLVMVoidType(), t1c_args, 2, false);
271+
t2c_t1c_cache_func =
272+
LLVMAddFunction(module, "t2c_invoke_cache", t2c_t1c_cache_func_proto);
273+
274+
LLVMTypeRef t2c_args[1] = {LLVMInt64Type()};
275+
t2c_t2c_cache_func_proto =
276+
LLVMFunctionType(LLVMVoidType(), t2c_args, 1, false);
277+
278+
/* Notice to the alignment */
279+
LLVMTypeRef jit_cache_memb[3] = {LLVMInt32Type(), LLVMInt32Type(),
280+
LLVMPointerType(LLVMVoidType(), 0)};
281+
jit_cache_ty = LLVMStructType(jit_cache_memb, 3, false);
282+
257283
LLVMBasicBlockRef first_block = LLVMAppendBasicBlock(start, "first_block");
258284
LLVMBuilderRef first_builder = LLVMCreateBuilder();
259285
LLVMPositionBuilderAtEnd(first_builder, first_block);
@@ -266,8 +292,8 @@ void t2c_compile(block_t *block, uint64_t mem_base)
266292
struct LLVM_block_map map;
267293
map.count = 0;
268294
/* Translate custon IR into LLVM IR */
269-
t2c_trace_ebb(&builder, param_types, start, &entry, mem_base,
270-
block->ir_head, &set, &map);
295+
t2c_trace_ebb(&builder, param_types, start, &entry, rv, block->ir_head,
296+
&set, &map);
271297
/* Offload LLVM IR to LLVM backend */
272298
char *error = NULL, *triple = LLVMGetDefaultTargetTriple();
273299
LLVMExecutionEngineRef engine;
@@ -296,7 +322,13 @@ void t2c_compile(block_t *block, uint64_t mem_base)
296322
abort();
297323
}
298324

325+
if (t2c_jit_cache_triggered) {
326+
LLVMAddGlobalMapping(engine, t2c_t1c_cache_func,
327+
((struct jit_state *) rv->jit_state)->buf);
328+
}
329+
299330
/* Return the function pointer of T2C generated machine code */
300331
block->func = (exec_t2c_func_t) LLVMGetPointerToGlobal(engine, start);
332+
jit_cache_update(rv->jit_cache, block->pc_start, 2, block->func);
301333
block->hot2 = true;
302334
}

0 commit comments

Comments
 (0)