commit c0af21b7d2e5511016c8989f404945b0d697b8ba
parent edbea61f7462e1584d862564470647372b578594
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sun, 26 Apr 2026 21:55:29 -0700
cc tests: lock in integer promotion preserves sign (§A.6)
cg-promote's relabel-only path is correct because §A.1's load-side
sign-extension already canonicalises the spill slot before promote
ever sees the opnd. cc-cg/20-promote-sign + cc-parse/20-promote-sign
verify that signed char -1 plus 2 equals 1 in 64-bit form (the
%ifelse_eq compare distinguishes 0x1 from 0x101).
Diffstat:
5 files changed, 48 insertions(+), 6 deletions(-)
diff --git a/docs/CC-PUNCHLIST.md b/docs/CC-PUNCHLIST.md
@@ -81,12 +81,13 @@ upstream of nearly everything else. Land this first.
correct; fixture locks the contrast with §A.4 in (same source,
same chain shape, divergent result via target signedness).
-- [ ] **Integer promotion preserves sign across operations**
- - cg: `cc-cg/NN-promote-sign.scm` — operate on a `signed char` slot
- holding `-1`; promote, add 1, return 0.
- - parse: `cc-parse/NN-promote-sign.c`
- - Needs: `cg-promote` is currently relabel-only; emit sext for
- `i8`/`i16` sources.
+- [x] **Integer promotion preserves sign across operations**
+ - cg: `cc-cg/20-promote-sign.scm` — `signed char x=-1; ((int)x)+2 == 1`
+ via 64-bit comparison so a non-canonical 0x101 result fails.
+ - parse: `cc-parse/20-promote-sign.c`
+ - Done: load-side sign-extension from §A.1 already canonicalises the
+ slot, so `cg-promote`'s relabel-only path is correct. Fixture
+ locks the invariant in.
### B. Lvalue mechanics
diff --git a/tests/cc-cg/20-promote-sign.expected-exit b/tests/cc-cg/20-promote-sign.expected-exit
@@ -0,0 +1 @@
+1
diff --git a/tests/cc-cg/20-promote-sign.scm b/tests/cc-cg/20-promote-sign.scm
@@ -0,0 +1,32 @@
+;; tests/cc-cg/20-promote-sign.scm — integer promotion preserves sign
+;; (§A.6 of docs/CC-PUNCHLIST.md).
+;;
+;; Models: signed char x = -1; return ((int)x + 2) == 1;
+;; If promotion doesn't sign-extend, the i8 -1 reads as 255 and
+;; 255+2 == 257 (mod 256 == 1) — exit code can't distinguish 1 vs
+;; 1 directly. So we instead compare the post-promote post-add
+;; value against 1 as a 64-bit comparison: properly-promoted
+;; -1 + 2 == 0x0000000000000001; without sext, 0xFF + 2 == 0x101.
+;; The %ifelse_eq compares full registers, so the 0x101 case fails
+;; → exit 0; the canonical 1 case → exit 1.
+
+(let ((cg (cg-init)))
+ (cg-fn-begin cg "main" '() %t-i32)
+ (let* ((off-x (cg-alloc-slot cg 1 1))
+ (sym-x (%sym "x" 'var 'auto %t-i8 off-x)))
+ ;; x = -1
+ (cg-push-sym cg sym-x)
+ (cg-push-imm cg %t-i8 -1)
+ (cg-assign cg) (cg-pop cg)
+ ;; (int)x + 2 == 1
+ (cg-push-sym cg sym-x)
+ (cg-load cg)
+ (cg-promote cg)
+ (cg-push-imm cg %t-i32 2)
+ (cg-arith-conv cg)
+ (cg-binop cg 'add)
+ (cg-push-imm cg %t-i32 1)
+ (cg-binop cg 'eq)
+ (cg-return cg))
+ (cg-fn-end cg)
+ (write-bv-fd 1 (cg-finish cg)))
diff --git a/tests/cc-parse/20-promote-sign.c b/tests/cc-parse/20-promote-sign.c
@@ -0,0 +1,7 @@
+// tests/cc-parse/20-promote-sign.c — integer promotion preserves sign.
+// §A.6 of docs/CC-PUNCHLIST.md.
+
+int main() {
+ signed char x = -1;
+ return (((int)x) + 2) == 1;
+}
diff --git a/tests/cc-parse/20-promote-sign.expected-exit b/tests/cc-parse/20-promote-sign.expected-exit
@@ -0,0 +1 @@
+1