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
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 "======================================"
244 changes: 233 additions & 11 deletions .github/workflows/main.yml

Large diffs are not rendered by default.

67 changes: 57 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,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 @@ -80,6 +73,49 @@ 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
# Note: setarch is Linux-only; macOS requires different approach (SIP disable)
ifeq ($(UNAME_S),Linux)
BIN_WRAPPER = setarch $(shell uname -m) -R
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setarch is Linux-only; with TSAN enabled on macOS the test recipe expands to setarch $(uname -m) -R … and fails because the binary is missing. Please gate the wrapper by host OS or provide a macOS-safe alternative so TSAN checks remain usable cross-platform.

Prompt for AI agents
Address the following comment on Makefile at line 109:

<comment>`setarch` is Linux-only; with TSAN enabled on macOS the test recipe expands to `setarch $(uname -m) -R …` and fails because the binary is missing. Please gate the wrapper by host OS or provide a macOS-safe alternative so TSAN checks remain usable cross-platform.</comment>

<file context>
@@ -80,6 +73,44 @@ endif
+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 =
</file context>
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TSAN runs now always prepend setarch, but macOS does not ship this binary; enabling TSAN on mac hosts will make make check fail before the emulator starts. Please gate the wrapper to Linux or gracefully skip it when setarch is unavailable.

Prompt for AI agents
Address the following comment on Makefile at line 109:

<comment>TSAN runs now always prepend `setarch`, but macOS does not ship this binary; enabling TSAN on mac hosts will make `make check` fail before the emulator starts. Please gate the wrapper to Linux or gracefully skip it when `setarch` is unavailable.</comment>

<file context>
@@ -80,6 +73,44 @@ endif
+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 =
</file context>
Suggested change
BIN_WRAPPER = setarch $(shell uname -m) -R
BIN_WRAPPER = $(if $(filter Linux,$(UNAME_S)),setarch $(shell uname -m) -R,)
Fix with Cubic

else
BIN_WRAPPER =
endif
else
BIN_WRAPPER =
endif

# Enable link-time optimization (LTO)
ENABLE_LTO ?= 1
ifeq ($(call has, LTO), 1)
Expand Down Expand Up @@ -280,6 +316,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 @@ -332,6 +373,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 All @@ -350,7 +397,7 @@ DTB_DEPS := $(BUILD_DTB) $(BUILD_DTB2C)
endif
endif

all: config $(DTB_DEPS) $(BUILD_DTB) $(BUILD_DTB2C) $(BIN)
all: config $(DTB_DEPS) $(BIN)

OBJS := \
map.o \
Expand Down Expand Up @@ -395,7 +442,7 @@ $(OUT):

$(BIN): $(OBJS) $(DEV_OBJS) | $(OUT)
$(VECHO) " LD\t$@\n"
$(Q)$(CC) -o $@ $(CFLAGS_emcc) $^ $(LDFLAGS)
$(Q)$(CC) -o $@ $(CFLAGS_emcc) $(filter-out %.dtb %.h,$^) $(LDFLAGS)

$(CONFIG_FILE): FORCE
$(Q)mkdir -p $(OUT)
Expand Down Expand Up @@ -445,7 +492,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
4 changes: 2 additions & 2 deletions mk/toolchain.mk
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ ifneq ($(shell $(CC) --version | head -n 1 | grep emcc),)
$(warning $(SDL_MUSIC_CANNOT_PLAY_WARNING))
endif
else
$(warning $(SDL_MUSIC_CANNOT_PLAY_WARNING))
$(warning $(SDL_MUSIC_CANNOT_PLAY_WARNING))
endif
else
$(warning $(SDL_MUSIC_CANNOT_PLAY_WARNING))
$(warning $(SDL_MUSIC_CANNOT_PLAY_WARNING))
endif

# see commit 165c1a3 of emscripten
Expand Down
18 changes: 18 additions & 0 deletions mk/wasm.mk
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,21 @@ start-web: $(start_web_deps)
.PHONY: check-demo-dir-exist start-web

endif

# For SYSTEM mode, DTB needs to be built regardless of whether we're using emcc
# DTB is only built when SYSTEM=1 and ELF_LOADER=0
ifeq ($(call has, SYSTEM), 1)
ifeq ($(call has, ELF_LOADER), 0)
# Add DTB as dependency for compilation stages
# This is used by mk/system.mk for device object files
deps_emcc += $(BUILD_DTB) $(BUILD_DTB2C)

# For emcc builds: ensure DTB exists before emcc embeds it
# Make BIN directly depend on DTB files as regular prerequisites
# This will cause them to be built, but they'll also be passed to the linker
# We need to filter them out in the linker command
ifeq ("$(CC_IS_EMCC)", "1")
$(BIN): $(BUILD_DTB) $(BUILD_DTB2C)
endif
endif
endif
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 @@ -304,6 +305,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 @@ -1176,22 +1178,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 @@ -1203,7 +1215,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 @@ -1215,10 +1231,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