Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .ci/check-format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,21 @@ set -x

REPO_ROOT="$(git rev-parse --show-toplevel)"

C_SOURCES=$(find "${REPO_ROOT}" | egrep "\.(c|cxx|cpp|h|hpp)$")
# Use git ls-files to only check files tracked by git (excludes submodules and generated files)
C_SOURCES=$(git ls-files "${REPO_ROOT}" | egrep "\.(c|cxx|cpp|h|hpp)$")
for file in ${C_SOURCES}; do
clang-format-18 ${file} > expected-format
diff -u -p --label="${file}" --label="expected coding style" ${file} expected-format
done
C_MISMATCH_LINE_CNT=$(clang-format-18 --output-replacements-xml ${C_SOURCES} | egrep -c "</replacement>")

SH_SOURCES=$(find "${REPO_ROOT}" | egrep "\.sh$")
SH_SOURCES=$(git ls-files "${REPO_ROOT}" | egrep "\.sh$")
for file in ${SH_SOURCES}; do
shfmt -d "${file}"
done
SH_MISMATCH_FILE_CNT=$(shfmt -l ${SH_SOURCES})
SH_MISMATCH_FILE_CNT=$(shfmt -l ${SH_SOURCES} | wc -l)

PY_SOURCES=$(find "${REPO_ROOT}" | egrep "\.py$")
PY_SOURCES=$(git ls-files "${REPO_ROOT}" | egrep "\.py$")
for file in ${PY_SOURCES}; do
echo "Checking Python file: ${file}"
black --diff "${file}"
Expand Down
53 changes: 53 additions & 0 deletions .ci/jit-debug-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env bash

# JIT Debug Test Script
# This script tests JIT compiler with debug mode enabled to catch issues early

set -e

PARALLEL="${PARALLEL:--j$(nproc 2> /dev/null || sysctl -n hw.ncpu 2> /dev/null || echo 4)}"

echo "======================================"
echo "JIT Debug Mode Test"
echo "======================================"

# Test 1: Standard JIT with debug
echo ""
echo "Test 1: Building with ENABLE_JIT_DEBUG=1..."
make distclean
make ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 $PARALLEL

echo ""
echo "Running basic tests with JIT debug..."
make ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 check

# Test 2: JIT with EXT_C=0 and debug (regression test)
echo ""
echo "Test 2: Building with ENABLE_EXT_C=0 ENABLE_JIT_DEBUG=1..."
make distclean
make ENABLE_EXT_C=0 ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 $PARALLEL

echo ""
echo "Running tests with EXT_C=0 and JIT debug..."
make ENABLE_EXT_C=0 ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 check

# Test 3: JIT with various extension combinations
echo ""
echo "Test 3: Testing multiple JIT configurations with debug..."
for config in \
"ENABLE_EXT_A=0" \
"ENABLE_EXT_F=0" \
"ENABLE_EXT_M=0" \
"ENABLE_Zba=0" \
"ENABLE_Zbb=0"; do
echo ""
echo "Testing: $config with JIT debug"
make distclean
make $config ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 $PARALLEL
make $config ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 check
done

echo ""
echo "======================================"
echo "All JIT debug tests passed!"
echo "======================================"
8 changes: 8 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,14 @@ jobs:
make ENABLE_JIT=1 clean && make ENABLE_MOP_FUSION=0 ENABLE_JIT=1 check $PARALLEL
make ENABLE_JIT=1 clean && make ENABLE_BLOCK_CHAINING=0 ENABLE_JIT=1 check $PARALLEL
if: ${{ always() }}
- name: JIT debug test
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
# Run JIT tests with debug mode to catch register allocation and cache coherency issues
make distclean && make ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 check $PARALLEL
make distclean && make ENABLE_EXT_C=0 ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 check $PARALLEL
if: ${{ always() }}
- name: undefined behavior test
env:
CC: ${{ steps.install_cc.outputs.cc }}
Expand Down
58 changes: 50 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@ CFLAGS += -include src/common.h -Isrc/

OBJS_EXT :=

# In the system test suite, the executable is an ELF file (e.g., MMU).
# However, the Linux kernel emulation includes the Image, DT, and
# root filesystem (rootfs). Therefore, the test suite needs this
# flag to load the ELF and differentiate it from the kernel emulation.
ENABLE_ELF_LOADER ?= 0
$(call set-feature, ELF_LOADER)

# Enable MOP fusion, easier for ablation study
ENABLE_MOP_FUSION ?= 1
$(call set-feature, MOP_FUSION)
Expand Down Expand Up @@ -73,6 +66,44 @@ endif
ENABLE_ARCH_TEST ?= 0
$(call set-feature, ARCH_TEST)

# In the system test suite, the executable is an ELF file (e.g., MMU).
# However, the Linux kernel emulation includes the Image, DT, and
# root filesystem (rootfs). Therefore, the test suite needs this
# flag to load the ELF and differentiate it from the kernel emulation.
# User-space emulation (SYSTEM=0) always needs ELF loader, except for architecture tests.
ifeq ($(ENABLE_SYSTEM), 0)
ifneq ($(ENABLE_ARCH_TEST), 1)
override ENABLE_ELF_LOADER := 1
else
ENABLE_ELF_LOADER ?= 0
endif
else
ENABLE_ELF_LOADER ?= 0
endif
$(call set-feature, ELF_LOADER)

# ThreadSanitizer support
# TSAN on x86-64 memory layout:
# Shadow: 0x02a000000000 - 0x7cefffffffff (reserved by TSAN)
# App: 0x7cf000000000 - 0x7ffffffff000 (usable by application)
#
# We use MAP_FIXED to allocate FULL4G's 4GB memory at a fixed address
# (0x7d0000000000) within TSAN's app range, ensuring compatibility.
#
# IMPORTANT: TSAN requires ASLR (Address Space Layout Randomization) to be
# disabled to prevent system allocations from landing in TSAN's shadow memory.
# Tests are run with 'setarch $(uname -m) -R' to disable ASLR.
ENABLE_TSAN ?= 0
ifeq ("$(ENABLE_TSAN)", "1")
override ENABLE_SDL := 0 # SDL (uninstrumented system lib) creates threads TSAN cannot track
override ENABLE_LTO := 0 # LTO interferes with TSAN instrumentation
CFLAGS += -DTSAN_ENABLED # Signal code to use TSAN-compatible allocations
# Disable ASLR for TSAN tests to prevent allocations in TSAN shadow memory
BIN_WRAPPER = setarch $(shell uname -m) -R
else
BIN_WRAPPER =
endif

# Enable link-time optimization (LTO)
ENABLE_LTO ?= 1
ifeq ($(call has, LTO), 1)
Expand Down Expand Up @@ -231,6 +262,11 @@ ENABLE_JIT ?= 0
$(call set-feature, JIT)
ifeq ($(call has, JIT), 1)
OBJS_EXT += jit.o
# JIT debug mode for early issue detection in CI/CD
ENABLE_JIT_DEBUG ?= 0
ifeq ("$(ENABLE_JIT_DEBUG)", "1")
CFLAGS += -DENABLE_JIT_DEBUG=1
endif
ENABLE_T2C ?= 1
$(call set-feature, T2C)
ifeq ($(call has, T2C), 1)
Expand Down Expand Up @@ -281,6 +317,12 @@ CFLAGS += -fsanitize=undefined -fno-sanitize=alignment -fno-sanitize-recover=all
LDFLAGS += -fsanitize=undefined -fno-sanitize=alignment -fno-sanitize-recover=all
endif

# ThreadSanitizer flags (ENABLE_TSAN is set earlier to override SDL/FULL4G)
ifeq ("$(ENABLE_TSAN)", "1")
CFLAGS += -fsanitize=thread -g
LDFLAGS += -fsanitize=thread
endif

$(OUT)/emulate.o: CFLAGS += -foptimize-sibling-calls -fomit-frame-pointer -fno-stack-check -fno-stack-protector

# .DEFAULT_GOAL should be set to all since the very first target is not all
Expand Down Expand Up @@ -375,7 +417,7 @@ define check-test
$(Q)true; \
$(PRINTF) "Running $(3) ... "; \
OUTPUT_FILE="$$(mktemp)"; \
if (LC_ALL=C $(BIN) $(1) $(2) > "$$OUTPUT_FILE") && \
if (LC_ALL=C $(BIN_WRAPPER) $(BIN) $(1) $(2) > "$$OUTPUT_FILE") && \
[ "$$(cat "$$OUTPUT_FILE" | $(LOG_FILTER) | $(4))" = "$(5)" ]; then \
$(call notice, [OK]); \
else \
Expand Down
44 changes: 35 additions & 9 deletions src/emulate.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ extern struct target_ops gdbstub_ops;
#endif

#include "decode.h"
#include "log.h"
#include "mpool.h"
#include "riscv.h"
#include "riscv_private.h"
Expand Down Expand Up @@ -283,6 +284,7 @@ static block_t *block_alloc(riscv_t *rv)
block->hot2 = false;
block->has_loops = false;
block->n_invoke = 0;
block->func = NULL;
INIT_LIST_HEAD(&block->list);
#if RV32_HAS(T2C)
block->compiled = false;
Expand Down Expand Up @@ -1151,22 +1153,32 @@ void rv_step(void *arg)
#if RV32_HAS(JIT)
#if RV32_HAS(T2C)
/* executed through the tier-2 JIT compiler */
if (block->hot2) {
/* Use acquire semantics to ensure we see func write before using it */
if (__atomic_load_n(&block->hot2, __ATOMIC_ACQUIRE)) {
((exec_t2c_func_t) block->func)(rv);
prev = NULL;
continue;
} /* check if invoking times of t1 generated code exceed threshold */
else if (!block->compiled && block->n_invoke >= THRESHOLD) {
block->compiled = true;
else if (!__atomic_load_n(&block->compiled, __ATOMIC_RELAXED) &&
__atomic_load_n(&block->n_invoke, __ATOMIC_RELAXED) >=
THRESHOLD) {
__atomic_store_n(&block->compiled, true, __ATOMIC_RELAXED);
queue_entry_t *entry = malloc(sizeof(queue_entry_t));
if (unlikely(!entry)) {
/* Malloc failed - reset compiled flag to allow retry later */
block->compiled = false;
__atomic_store_n(&block->compiled, false, __ATOMIC_RELAXED);
continue;
}
entry->block = block;
/* Store cache key instead of pointer to prevent use-after-free */
#if RV32_HAS(SYSTEM)
entry->key =
(uint64_t) block->pc_start | ((uint64_t) block->satp << 32);
#else
entry->key = (uint64_t) block->pc_start;
#endif
pthread_mutex_lock(&rv->wait_queue_lock);
list_add(&entry->list, &rv->wait_queue);
pthread_cond_signal(&rv->wait_queue_cond);
pthread_mutex_unlock(&rv->wait_queue_lock);
}
#endif
Expand All @@ -1178,7 +1190,11 @@ void rv_step(void *arg)
* entry in compiled binary buffer.
*/
if (block->hot) {
#if RV32_HAS(T2C)
__atomic_fetch_add(&block->n_invoke, 1, __ATOMIC_RELAXED);
#else
block->n_invoke++;
#endif
((exec_block_func_t) state->buf)(
rv, (uintptr_t) (state->buf + block->offset));
prev = NULL;
Expand All @@ -1190,10 +1206,20 @@ void rv_step(void *arg)
#endif
) {
jit_translate(rv, block);
((exec_block_func_t) state->buf)(
rv, (uintptr_t) (state->buf + block->offset));
prev = NULL;
continue;
/* Only execute if translation succeeded (block is hot) */
if (block->hot) {
rv_log_debug("JIT: Executing block pc=0x%08x, offset=%u",
block->pc_start, block->offset);
((exec_block_func_t) state->buf)(
rv, (uintptr_t) (state->buf + block->offset));
prev = NULL;
continue;
}
/* Fall through to interpreter if translation failed */
rv_log_debug(
"JIT: Translation failed for block pc=0x%08x, using "
"interpreter",
block->pc_start);
}
set_reset(&pc_set);
has_loops = false;
Expand Down
35 changes: 35 additions & 0 deletions src/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,47 @@ memory_t *memory_new(uint32_t size)
return NULL;
assert(mem);
#if HAVE_MMAP
#if defined(TSAN_ENABLED)
/* ThreadSanitizer compatibility: Use MAP_FIXED to allocate at a specific
* address to avoid conflicts with TSAN's shadow memory.
*/
#if defined(__x86_64__)
/* x86_64: Allocate within TSAN's range (0x7cf000000000 - 0x7ffffffff000).
*
* Fixed address: 0x7d0000000000
* Size: up to 4GB (0x100000000)
* End: 0x7d0100000000 (well within app range)
*/
void *fixed_addr = (void *) 0x7d0000000000UL;
#elif defined(__aarch64__)
/* ARM64 (macOS/Apple Silicon): Use higher address range.
*
* Fixed address: 0x150000000000 (21TB)
* Size: up to 4GB (0x100000000)
* End: 0x150100000000
*
* This avoids TSAN's shadow memory and typical process allocations.
* Requires ASLR disabled via: setarch $(uname -m) -R
*/
void *fixed_addr = (void *) 0x150000000000UL;
#else
#error "TSAN is only supported on x86_64 and aarch64"
#endif
data_memory_base = mmap(fixed_addr, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
if (data_memory_base == MAP_FAILED) {
free(mem);
return NULL;
}
#else
/* Standard allocation without TSAN */
data_memory_base = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (data_memory_base == MAP_FAILED) {
free(mem);
return NULL;
}
#endif
#else
data_memory_base = malloc(size);
if (!data_memory_base) {
Expand Down
Loading
Loading