Commit 62728f8c93 for qemu.org
commit 62728f8c932f9572eeea22f91e3ee223978b1c7f
Author: Richard Henderson <richard.henderson@linaro.org>
Date: Wed Sep 23 15:06:53 2026 -0700
target/sh4: Replace TB_FLAG_GUSA_EXCLUSIVE with CF_STEP_ATOMIC
There was a complex bug with gUSA wherein:
* decode_gusa calls gen_restart_exclusive
* gen_restart_exclusive generates code that sets TB_FLAG_GUSA_EXCLUSIVE
and generates a call to helper_exclusive
* when the code is executed, TB_FLAG_GUSA_EXCLUSIVE is set
* helper_exclusive calls cpu_loop_exit_atomic, this makes cpu_exec exit
with EXCP_ATOMIC
* we go to cpu_loop, we execute cpu_exec_step_atomic
* suppose that exit request is set, cpu_exec_step_atomic does nothing, it
leaves the CPU in the same state as it was before
* we go back to cpu_loop
* suppose that no signal is delivered, so the gUSA is not rewound
* cpu_loop goes to cpu_exec
* there is one difference - now, TB_FLAG_GUSA_EXCLUSIVE is set and it was
clear before - so cpu_exec will not use the TB that calls
helper_exclusive, it will instead use the TB that performs the atomic
operation (both of these TBs have the same PC, they only differ in flags)
* the TB that performs the atomic operation is executed inside cpu_exec
=> race condition
Fix the bug by managing the "are we in cpu_exec_step_atomic" flag
from cpu_exec_step_atomic itself, rather from the translator.
Cc: qemu-stable@nongnu.org
Fixes: 4bfa602bc22 ("target/sh4: Handle user-space atomics")
Reported-by: Mikulas Patocka <mpatocka@redhat.com>
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Tested-by: Mikulas Patocka <mpatocka@redhat.com>
Signed-off-by: Helge Deller <deller@gmx.de>
diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c
index f4428e9f7b..5b4bca5084 100644
--- a/accel/tcg/cpu-exec.c
+++ b/accel/tcg/cpu-exec.c
@@ -566,7 +566,8 @@ void cpu_exec_step_atomic(CPUState *cpu)
/* Execute in a serial context. */
s.cflags &= ~CF_PARALLEL;
/* After 1 insn, return and release the exclusive lock. */
- s.cflags |= CF_NO_GOTO_TB | CF_NO_GOTO_PTR | CF_NOIRQ | 1;
+ s.cflags |= CF_NO_GOTO_TB | CF_NO_GOTO_PTR |
+ CF_NOIRQ | CF_STEP_ATOMIC | 1;
/*
* No need to check_for_breakpoints here.
diff --git a/include/exec/translation-block.h b/include/exec/translation-block.h
index 40cc699031..7c42c57cca 100644
--- a/include/exec/translation-block.h
+++ b/include/exec/translation-block.h
@@ -84,6 +84,7 @@ struct TranslationBlock {
#define CF_NOIRQ 0x00010000 /* Generate an uninterruptible TB */
#define CF_PCREL 0x00020000 /* Opcodes in TB are PC-relative */
#define CF_BP_PAGE 0x00040000 /* Breakpoint present in code page */
+#define CF_STEP_ATOMIC 0x00080000 /* Running in cpu_exec_step_atomic */
#define CF_CLUSTER_MASK 0xff000000 /* Top 8 bits are cluster ID */
#define CF_CLUSTER_SHIFT 24
diff --git a/target/sh4/cpu.h b/target/sh4/cpu.h
index 3302702376..6984bb462f 100644
--- a/target/sh4/cpu.h
+++ b/target/sh4/cpu.h
@@ -84,8 +84,7 @@
#define TB_FLAG_DELAY_SLOT_RTE (1 << 2)
#define TB_FLAG_PENDING_MOVCA (1 << 3)
#define TB_FLAG_GUSA_SHIFT 4 /* [11:4] */
-#define TB_FLAG_GUSA_EXCLUSIVE (1 << 12)
-#define TB_FLAG_UNALIGN (1 << 13)
+#define TB_FLAG_UNALIGN (1 << 12)
#define TB_FLAG_SR_FD (1 << SR_FD) /* 15 */
#define TB_FLAG_FPSCR_PR FPSCR_PR /* 19 */
#define TB_FLAG_FPSCR_SZ FPSCR_SZ /* 20 */
@@ -96,8 +95,7 @@
#define TB_FLAG_DELAY_SLOT_MASK (TB_FLAG_DELAY_SLOT | \
TB_FLAG_DELAY_SLOT_COND | \
TB_FLAG_DELAY_SLOT_RTE)
-#define TB_FLAG_GUSA_MASK ((0xff << TB_FLAG_GUSA_SHIFT) | \
- TB_FLAG_GUSA_EXCLUSIVE)
+#define TB_FLAG_GUSA_MASK (0xff << TB_FLAG_GUSA_SHIFT)
#define TB_FLAG_FPSCR_MASK (TB_FLAG_FPSCR_PR | \
TB_FLAG_FPSCR_SZ | \
TB_FLAG_FPSCR_FR)
diff --git a/target/sh4/translate.c b/target/sh4/translate.c
index 373950fd66..c15c0802f7 100644
--- a/target/sh4/translate.c
+++ b/target/sh4/translate.c
@@ -47,6 +47,9 @@ typedef struct DisasContext {
uint16_t opcode;
bool has_movcal;
+#ifdef CONFIG_USER_ONLY
+ bool in_gusa_exclusive;
+#endif
} DisasContext;
#if defined(CONFIG_USER_ONLY)
@@ -220,7 +223,11 @@ static inline void gen_save_cpu_state(DisasContext *ctx, bool save_pc)
static inline bool use_exit_tb(DisasContext *ctx)
{
- return (ctx->tbflags & TB_FLAG_GUSA_EXCLUSIVE) != 0;
+#ifdef CONFIG_USER_ONLY
+ return ctx->in_gusa_exclusive;
+#else
+ return false;
+#endif
}
static bool use_goto_tb(DisasContext *ctx, vaddr dest)
@@ -273,7 +280,8 @@ static void gen_conditional_jump(DisasContext *ctx, vaddr dest,
TCGLabel *l1 = gen_new_label();
TCGCond cond_not_taken = jump_if_true ? TCG_COND_EQ : TCG_COND_NE;
- if (ctx->tbflags & TB_FLAG_GUSA_EXCLUSIVE) {
+#ifdef CONFIG_USER_ONLY
+ if (ctx->in_gusa_exclusive) {
/* When in an exclusive region, we must continue to the end.
Therefore, exit the region on a taken branch, but otherwise
fall through to the next instruction. */
@@ -286,6 +294,7 @@ static void gen_conditional_jump(DisasContext *ctx, vaddr dest,
ctx->base.is_jmp = DISAS_NEXT;
return;
}
+#endif
gen_save_cpu_state(ctx, false);
tcg_gen_brcondi_i32(cond_not_taken, cpu_sr_t, 0, l1);
@@ -304,7 +313,8 @@ static void gen_delayed_conditional_jump(DisasContext * ctx)
tcg_gen_mov_i32(ds, cpu_delayed_cond);
tcg_gen_discard_i32(cpu_delayed_cond);
- if (ctx->tbflags & TB_FLAG_GUSA_EXCLUSIVE) {
+#ifdef CONFIG_USER_ONLY
+ if (ctx->in_gusa_exclusive) {
/* When in an exclusive region, we must continue to the end.
Therefore, exit the region on a taken branch, but otherwise
fall through to the next instruction. */
@@ -318,6 +328,7 @@ static void gen_delayed_conditional_jump(DisasContext * ctx)
ctx->base.is_jmp = DISAS_NEXT;
return;
}
+#endif
tcg_gen_brcondi_i32(TCG_COND_NE, ds, 0, l1);
gen_goto_tb(ctx, 1, ctx->base.pc_next + 2);
@@ -1793,16 +1804,18 @@ static void decode_opc(DisasContext * ctx)
/* go out of the delay slot */
ctx->envflags &= ~TB_FLAG_DELAY_SLOT_MASK;
+#ifdef CONFIG_USER_ONLY
/* When in an exclusive region, we must continue to the end
for conditional branches. */
- if (ctx->tbflags & TB_FLAG_GUSA_EXCLUSIVE
- && old_flags & TB_FLAG_DELAY_SLOT_COND) {
+ if (ctx->in_gusa_exclusive && old_flags & TB_FLAG_DELAY_SLOT_COND) {
gen_delayed_conditional_jump(ctx);
return;
}
+
/* Otherwise this is probably an invalid gUSA region.
Drop the GUSA bits so the next TB doesn't see them. */
ctx->envflags &= ~TB_FLAG_GUSA_MASK;
+#endif
tcg_gen_movi_i32(cpu_flags, ctx->envflags);
if (old_flags & TB_FLAG_DELAY_SLOT_COND) {
@@ -1820,7 +1833,6 @@ static void decode_opc(DisasContext * ctx)
*/
static void gen_restart_exclusive(DisasContext *ctx)
{
- ctx->envflags |= TB_FLAG_GUSA_EXCLUSIVE;
gen_save_cpu_state(ctx, false);
gen_helper_exclusive(tcg_env);
ctx->base.is_jmp = DISAS_NORETURN;
@@ -2208,11 +2220,13 @@ static void sh4_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs)
int backup = sextract32(ctx->tbflags, TB_FLAG_GUSA_SHIFT, 8);
int max_insns = (pc_end - pc) / 2;
+ ctx->in_gusa_exclusive = ctx->base.tb->cflags & CF_STEP_ATOMIC;
+
if (pc != pc_end + backup || max_insns < 2) {
/* This is a malformed gUSA region. Don't do anything special,
since the interpreter is likely to get confused. */
ctx->envflags &= ~TB_FLAG_GUSA_MASK;
- } else if (tbflags & TB_FLAG_GUSA_EXCLUSIVE) {
+ } else if (ctx->in_gusa_exclusive) {
/* Regardless of single-stepping or the end of the page,
we must complete execution of the gUSA region while
holding the exclusive lock. */
@@ -2246,7 +2260,7 @@ static void sh4_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs)
#ifdef CONFIG_USER_ONLY
if (unlikely(ctx->envflags & TB_FLAG_GUSA_MASK)
- && !(ctx->envflags & TB_FLAG_GUSA_EXCLUSIVE)) {
+ && !ctx->in_gusa_exclusive) {
/*
* We're in an gUSA region, and we have not already fallen
* back on using an exclusive region. Attempt to parse the
@@ -2276,10 +2290,12 @@ static void sh4_tr_tb_stop(DisasContextBase *dcbase, CPUState *cs)
{
DisasContext *ctx = container_of(dcbase, DisasContext, base);
- if (ctx->tbflags & TB_FLAG_GUSA_EXCLUSIVE) {
+#ifdef CONFIG_USER_ONLY
+ if (ctx->in_gusa_exclusive) {
/* Ending the region of exclusivity. Clear the bits. */
ctx->envflags &= ~TB_FLAG_GUSA_MASK;
}
+#endif
switch (ctx->base.is_jmp) {
case DISAS_STOP: