commit 8b8960344ffcc9a12161a218fece7e8bf2beaf8b
parent c8b9d53d52c4d396b6caed50da9e576413595199
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 6 Jun 2026 04:33:21 -0700
Keep volatile locals memory resident
Diffstat:
4 files changed, 12 insertions(+), 31 deletions(-)
diff --git a/doc/plan/TODO.md b/doc/plan/TODO.md
@@ -5,37 +5,6 @@ fixed, remove it instead of checking it off or keeping a closed entry.
Add new deferred fixes below as they are discovered.
-## setjmp/longjmp miscompiled at `-O1`: the longjmp'd `setjmp` return value is wrong
-
-A textbook setjmp/longjmp round-trip returns the right answer at `-O0` but the
-wrong one at `-O1` on **all three** native arches (aa64/x64/rv64) — a wrong-
-answer miscompile, not a crash. `test/rt/cases/setjmp_runtime.c` exits 42 at O0
-and **1** at O1: the `int rc = setjmp(env)` value observed after the `longjmp`
-is not `1`. (`marker` is `volatile`, so the test is well-formed — the bad read is
-the `setjmp` result itself, not the local.)
-
-Minimal repro (`kit cc -O1`, run it):
-
-```c
-#include <setjmp.h>
-int test_main(void) {
- jmp_buf env;
- volatile int marker = 11;
- int rc = setjmp(env); /* O1: second return value not observed */
- if (rc == 0) { marker = 31; longjmp(env, 1); }
- return (marker == 31 && rc == 1) ? 42 : 1; /* O0: 42, O1: 1 */
-}
-```
-
-Classic "`setjmp` not modeled as returns-twice": the optimizer treats the call as
-returning once, so the SSA value for `rc` (and anything else live across the
-`setjmp`) is folded/cached to the first-return value rather than reloaded on the
-`longjmp` re-entry. Target-independent (fails on every arch), so the fix is in the
-opt/IR layer — mark `setjmp`-family calls returns-twice (force a reload of values
-live across them; pin them to memory), as GCC/Clang do. Found sweeping the rt
-runtime corpus at O0+O1 for the WS4 backtrace work (doc/plan/BACKTRACE.md); left
-red (`test-rt-runtime`, `setjmp_runtime/O1`).
-
## `-no-pie` does not produce a non-PIE (ET_EXEC) executable
`-no-pie` sets `o->target.pic = KIT_PIC_NONE` (`driver/cmd/cc.c:1185`) but does
diff --git a/include/kit/cg.h b/include/kit/cg.h
@@ -492,6 +492,10 @@ typedef enum KitCgLocalFlag {
KIT_CG_LOCAL_ARTIFICIAL = 1u << 1,
KIT_CG_LOCAL_OPTIMIZED_OUT = 1u << 2,
KIT_CG_LOCAL_COMPILER_TEMP = 1u << 3,
+ /* Force an addressable storage home even when the type is scalar. Frontends
+ * use this for source objects whose accesses must remain memory operations
+ * (for example C volatile automatic objects). */
+ KIT_CG_LOCAL_MEMORY_REQUIRED = 1u << 4,
} KitCgLocalFlag;
typedef struct KitCgLocalAttrs {
diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c
@@ -383,12 +383,18 @@ KitCgAtomicOp pcg_atomic_op(AtomicOp op) {
KitCgMemOrder pcg_mem_order(MemOrder ord) { return (KitCgMemOrder)ord; }
+static int pcg_slot_is_volatile(const FrameSlotDesc* fsd) {
+ return fsd && ((fsd->flags & FSF_VOLATILE) ||
+ (fsd->type && (fsd->type->qual & Q_VOLATILE)));
+}
+
FrameSlot pcg_local(Parser* p, const FrameSlotDesc* fsd) {
KitCgLocalAttrs attrs;
memset(&attrs, 0, sizeof attrs);
if (!pcg_emit_enabled(p)) return FRAME_SLOT_NONE;
attrs.name = fsd->name;
attrs.align = fsd->align;
+ if (pcg_slot_is_volatile(fsd)) attrs.flags |= KIT_CG_LOCAL_MEMORY_REQUIRED;
/* FSF_ADDR_TAKEN is no longer propagated to CG: there is no
* KIT_CG_LOCAL_ADDRESS_TAKEN attribute. The C-side flag stays for any
* parser-internal uses; opt's opt_promote_scalar_locals (Stream I) decides
@@ -402,6 +408,7 @@ FrameSlot pcg_param_slot(Parser* p, u32 index, const FrameSlotDesc* fsd) {
memset(&attrs, 0, sizeof attrs);
attrs.name = fsd->name;
attrs.align = fsd->align;
+ if (pcg_slot_is_volatile(fsd)) attrs.flags |= KIT_CG_LOCAL_MEMORY_REQUIRED;
return kit_cg_param(p->cg, index, pcg_tid(p, fsd->type), attrs);
}
diff --git a/src/cg/local.c b/src/cg/local.c
@@ -3,6 +3,7 @@
int api_local_requires_memory(KitCg* g, KitCgTypeId ty, KitCgLocalAttrs attrs) {
u32 hidden_flags = KIT_CG_LOCAL_ARTIFICIAL | KIT_CG_LOCAL_OPTIMIZED_OUT |
KIT_CG_LOCAL_COMPILER_TEMP;
+ if (attrs.flags & KIT_CG_LOCAL_MEMORY_REQUIRED) return 1;
if (g && g->debug && attrs.name && (attrs.flags & hidden_flags) == 0)
return 1;
/* Aggregates (records, arrays), wide16 (f128/i128), split-lane wide8, vararg