Skip to content

Commit 13b3ac5

Browse files
committed
Detect early JIT compilation issues in CI/CD
This commit introduces a comprehensive JIT debugging infrastructure to catch register allocation conflicts and cache coherency issues before they cause subtle runtime failures in production.
1 parent 7ce348c commit 13b3ac5

File tree

4 files changed

+149
-0
lines changed

4 files changed

+149
-0
lines changed

.ci/jit-debug-test.sh

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/usr/bin/env bash
2+
3+
# JIT Debug Test Script
4+
# This script tests JIT compiler with debug mode enabled to catch issues early
5+
6+
set -e
7+
8+
PARALLEL="${PARALLEL:--j$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)}"
9+
10+
echo "======================================"
11+
echo "JIT Debug Mode Test"
12+
echo "======================================"
13+
14+
# Test 1: Standard JIT with debug
15+
echo ""
16+
echo "Test 1: Building with ENABLE_JIT_DEBUG=1..."
17+
make distclean
18+
make ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 $PARALLEL
19+
20+
echo ""
21+
echo "Running basic tests with JIT debug..."
22+
make ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 check
23+
24+
# Test 2: JIT with EXT_C=0 and debug (regression test)
25+
echo ""
26+
echo "Test 2: Building with ENABLE_EXT_C=0 ENABLE_JIT_DEBUG=1..."
27+
make distclean
28+
make ENABLE_EXT_C=0 ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 $PARALLEL
29+
30+
echo ""
31+
echo "Running tests with EXT_C=0 and JIT debug..."
32+
make ENABLE_EXT_C=0 ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 check
33+
34+
# Test 3: JIT with various extension combinations
35+
echo ""
36+
echo "Test 3: Testing multiple JIT configurations with debug..."
37+
for config in \
38+
"ENABLE_EXT_A=0" \
39+
"ENABLE_EXT_F=0" \
40+
"ENABLE_EXT_M=0" \
41+
"ENABLE_Zba=0" \
42+
"ENABLE_Zbb=0"
43+
do
44+
echo ""
45+
echo "Testing: $config with JIT debug"
46+
make distclean
47+
make $config ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 $PARALLEL
48+
make $config ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 check
49+
done
50+
51+
echo ""
52+
echo "======================================"
53+
echo "All JIT debug tests passed!"
54+
echo "======================================"

.github/workflows/main.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,14 @@ jobs:
461461
make ENABLE_JIT=1 clean && make ENABLE_MOP_FUSION=0 ENABLE_JIT=1 check $PARALLEL
462462
make ENABLE_JIT=1 clean && make ENABLE_BLOCK_CHAINING=0 ENABLE_JIT=1 check $PARALLEL
463463
if: ${{ always() }}
464+
- name: JIT debug test
465+
env:
466+
CC: ${{ steps.install_cc.outputs.cc }}
467+
run: |
468+
# Run JIT tests with debug mode to catch register allocation and cache coherency issues
469+
make distclean && make ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 check $PARALLEL
470+
make distclean && make ENABLE_EXT_C=0 ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 check $PARALLEL
471+
if: ${{ always() }}
464472
- name: undefined behavior test
465473
env:
466474
CC: ${{ steps.install_cc.outputs.cc }}

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,11 @@ ENABLE_JIT ?= 0
253253
$(call set-feature, JIT)
254254
ifeq ($(call has, JIT), 1)
255255
OBJS_EXT += jit.o
256+
# JIT debug mode for early issue detection in CI/CD
257+
ENABLE_JIT_DEBUG ?= 0
258+
ifeq ("$(ENABLE_JIT_DEBUG)", "1")
259+
CFLAGS += -DENABLE_JIT_DEBUG=1
260+
endif
256261
ENABLE_T2C ?= 1
257262
$(call set-feature, T2C)
258263
ifeq ($(call has, T2C), 1)

src/jit.c

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,79 @@ static inline void offset_map_insert(struct jit_state *state, block_t *block)
299299
__builtin___clear_cache((char *) (addr), (char *) (addr) + (size));
300300
#endif
301301

302+
/* JIT debug helpers - enable with ENABLE_JIT_DEBUG=1 to detect issues early */
303+
#ifndef ENABLE_JIT_DEBUG
304+
#define ENABLE_JIT_DEBUG 0
305+
#endif
306+
307+
#if ENABLE_JIT_DEBUG
308+
static void jit_dump_regmap(const char *ctx)
309+
{
310+
rv_log_debug("JIT RegMap [%s]:", ctx);
311+
for (int i = 0; i < n_host_regs; i++) {
312+
if (register_map[i].vm_reg_idx >= 0) {
313+
rv_log_debug(" Host R%d -> VM x%d (dirty=%d)",
314+
register_map[i].reg_idx,
315+
register_map[i].vm_reg_idx,
316+
register_map[i].dirty);
317+
}
318+
}
319+
}
320+
321+
static void jit_check_regmap_conflict(int vm_reg, int host_reg, const char *insn)
322+
{
323+
int found_idx = -1;
324+
/* Check if VM register is already mapped */
325+
for (int i = 0; i < n_host_regs; i++) {
326+
if (register_map[i].vm_reg_idx == vm_reg) {
327+
if (found_idx >= 0 && found_idx != i) {
328+
/* VM register mapped to multiple host registers */
329+
rv_log_error("JIT RegMap CONFLICT in %s: VM x%d mapped to "
330+
"Host R%d (idx %d) and R%d (idx %d)",
331+
insn, vm_reg,
332+
register_map[found_idx].reg_idx, found_idx,
333+
register_map[i].reg_idx, i);
334+
jit_dump_regmap("CONFLICT");
335+
assert(false);
336+
}
337+
found_idx = i;
338+
/* Verify the found mapping is correct */
339+
if (register_map[i].reg_idx != host_reg) {
340+
rv_log_error("JIT RegMap CONFLICT in %s: VM x%d expected at "
341+
"Host R%d but found at R%d",
342+
insn, vm_reg, host_reg, register_map[i].reg_idx);
343+
jit_dump_regmap("CONFLICT");
344+
assert(false);
345+
}
346+
} else if (register_map[i].reg_idx == host_reg &&
347+
register_map[i].vm_reg_idx >= 0) {
348+
/* Host register holds different VM register */
349+
rv_log_error("JIT RegMap CONFLICT in %s: Host R%d already holds "
350+
"VM x%d, cannot map VM x%d",
351+
insn, host_reg, register_map[i].vm_reg_idx, vm_reg);
352+
jit_dump_regmap("CONFLICT");
353+
assert(false);
354+
}
355+
}
356+
}
357+
358+
static void jit_verify_cache_coherency(struct jit_state *state, uint32_t pc) UNUSED;
359+
static void jit_verify_cache_coherency(struct jit_state *state, uint32_t pc)
360+
{
361+
/* On ARM64, verify instruction cache was properly invalidated */
362+
#if defined(__aarch64__)
363+
if (state->offset > 0) {
364+
rv_log_debug("JIT: Cache coherency check at PC=0x%08x, offset=%u",
365+
pc, state->offset);
366+
}
367+
#endif
368+
}
369+
#else
370+
#define jit_dump_regmap(ctx) do { } while (0)
371+
#define jit_check_regmap_conflict(vm_reg, host_reg, insn) do { } while (0)
372+
#define jit_verify_cache_coherency(state, pc) do { } while (0)
373+
#endif
374+
302375
static bool should_flush = false;
303376
static void emit_bytes(struct jit_state *state, void *data, uint32_t len)
304377
{
@@ -1890,6 +1963,7 @@ static inline int map_vm_reg(struct jit_state *state, int vm_reg_idx)
18901963
save_reg(state, idx);
18911964
unmap_vm_reg(idx);
18921965
set_vm_reg(idx, vm_reg_idx);
1966+
jit_check_regmap_conflict(vm_reg_idx, target_reg, "map_vm_reg");
18931967
return target_reg;
18941968
}
18951969

@@ -1933,6 +2007,14 @@ static inline int map_vm_reg_reserved(struct jit_state *state,
19332007
save_reg(state, idx);
19342008
unmap_vm_reg(idx);
19352009
set_vm_reg(idx, vm_reg_idx);
2010+
jit_check_regmap_conflict(vm_reg_idx, target_reg, "map_vm_reg_reserved");
2011+
/* Additional check: ensure we didn't allocate the reserved register */
2012+
if (target_reg == reserved_reg_idx) {
2013+
rv_log_error("JIT RegMap ERROR: map_vm_reg_reserved allocated reserved "
2014+
"register R%d for VM x%d",
2015+
reserved_reg_idx, vm_reg_idx);
2016+
assert(false);
2017+
}
19362018
return target_reg;
19372019
}
19382020

0 commit comments

Comments
 (0)