commit fc1600e196f0e0c4ef8c3bbef397c119e80476d7
parent c7dc4371042d8047389026a9448de6e156472fda
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 1 May 2026 18:49:48 -0700
merge: codegen fixes (4 bugs: spill canon, signed shr, narrow promote, arith conv)
# Conflicts:
# cc/cc.scm
# tests/cc-cg/37-struct-store.scm
Diffstat:
12 files changed, 148 insertions(+), 0 deletions(-)
diff --git a/tests/cc-cg/80-uneg-canonical.expected-exit b/tests/cc-cg/80-uneg-canonical.expected-exit
@@ -0,0 +1 @@
+1
diff --git a/tests/cc-cg/80-uneg-canonical.scm b/tests/cc-cg/80-uneg-canonical.scm
@@ -0,0 +1,23 @@
+;; tests/cc-cg/80-uneg-canonical.scm — unary minus on unsigned should
+;; leave the canonical 64-bit slot in the to-type's natural form.
+;;
+;; Models:
+;; unsigned int a = 1;
+;; return ((unsigned)-a) == 4294967295u; /* (u32)-1u == UINT_MAX */
+;;
+;; Bug: cg-unop neg computes 0 - canonical(1) = 0xFFFFFFFFFFFFFFFF and
+;; spills as u32. Without re-canonicalizing the spill (zext32), a
+;; subsequent compare against the literal 4294967295 (which %li loads
+;; as 0x00000000FFFFFFFF) sees mismatched upper bits → equality 0.
+;; Correct cg masks/zext on spill of an unsigned-typed result.
+;; Exit code: 1 if equal, 0 otherwise.
+
+(let ((cg (cg-init)))
+ (cg-fn-begin cg "main" '() %t-i32)
+ (cg-push-imm cg %t-u32 1)
+ (cg-unop cg 'neg)
+ (cg-push-imm cg %t-u32 4294967295)
+ (cg-binop cg 'eq)
+ (cg-return cg)
+ (cg-fn-end cg)
+ (write-bv-fd 1 (cg-finish cg)))
diff --git a/tests/cc-cg/81-ubnot-canonical.expected-exit b/tests/cc-cg/81-ubnot-canonical.expected-exit
@@ -0,0 +1 @@
+1
diff --git a/tests/cc-cg/81-ubnot-canonical.scm b/tests/cc-cg/81-ubnot-canonical.scm
@@ -0,0 +1,20 @@
+;; tests/cc-cg/81-ubnot-canonical.scm — bitwise-not on unsigned should
+;; leave a canonical 64-bit slot in the to-type's natural form.
+;;
+;; Models:
+;; unsigned int a = 0;
+;; return (~a) == 4294967295u;
+;;
+;; Same bug class as 80-uneg-canonical: ~0 in 64-bit is 0xFF..FF,
+;; spilled as u32 without re-canonicalizing causes a literal
+;; 4294967295u (loaded as 0x00000000FFFFFFFF) to compare unequal.
+
+(let ((cg (cg-init)))
+ (cg-fn-begin cg "main" '() %t-i32)
+ (cg-push-imm cg %t-u32 0)
+ (cg-unop cg 'bnot)
+ (cg-push-imm cg %t-u32 4294967295)
+ (cg-binop cg 'eq)
+ (cg-return cg)
+ (cg-fn-end cg)
+ (write-bv-fd 1 (cg-finish cg)))
diff --git a/tests/cc-cg/82-uadd-wrap-canonical.expected-exit b/tests/cc-cg/82-uadd-wrap-canonical.expected-exit
@@ -0,0 +1 @@
+1
diff --git a/tests/cc-cg/82-uadd-wrap-canonical.scm b/tests/cc-cg/82-uadd-wrap-canonical.scm
@@ -0,0 +1,21 @@
+;; tests/cc-cg/82-uadd-wrap-canonical.scm — unsigned add must wrap
+;; correctly into the canonical 64-bit slot form.
+;;
+;; Models:
+;; unsigned int a = 4294967295u;
+;; return (a + 1u) == 0u;
+;;
+;; Bug: cg-binop add computes 0xFFFFFFFF + 1 = 0x100000000 in 64-bit.
+;; Spilled as u32 without zero-extending the low 32 bits, the result
+;; compares unequal to 0u (which loads as 0x0).
+
+(let ((cg (cg-init)))
+ (cg-fn-begin cg "main" '() %t-i32)
+ (cg-push-imm cg %t-u32 4294967295)
+ (cg-push-imm cg %t-u32 1)
+ (cg-binop cg 'add)
+ (cg-push-imm cg %t-u32 0)
+ (cg-binop cg 'eq)
+ (cg-return cg)
+ (cg-fn-end cg)
+ (write-bv-fd 1 (cg-finish cg)))
diff --git a/tests/cc-cg/83-signed-shr-by-unsigned.expected-exit b/tests/cc-cg/83-signed-shr-by-unsigned.expected-exit
@@ -0,0 +1 @@
+1
diff --git a/tests/cc-cg/83-signed-shr-by-unsigned.scm b/tests/cc-cg/83-signed-shr-by-unsigned.scm
@@ -0,0 +1,28 @@
+;; tests/cc-cg/83-signed-shr-by-unsigned.scm — `int x = -16; x >> 1u`
+;; must use arithmetic shift right (sar) per C 6.5.7: shifts use the
+;; promoted left operand's signedness, not the result of the usual
+;; arithmetic conversions.
+;;
+;; Models: `(int)(-16) >> (unsigned)1 == -8`.
+;;
+;; Replicates the corrected parse-binary sequence for shift operators
+;; (no arith-conv): rval lhs; cg-promote; rval rhs; cg-promote;
+;; cg-binop 'shr.
+;;
+;; cg-binop currently dispatched signed/unsigned shr off either operand
+;; (`(or (unsigned? ta) (unsigned? tb))`), so a signed lhs with an
+;; unsigned shift count incorrectly used logical shift right. Correct
+;; cg keys signedness off the LEFT operand only (C 6.5.7).
+
+(let ((cg (cg-init)))
+ (cg-fn-begin cg "main" '() %t-i32)
+ (cg-push-imm cg %t-i32 -16)
+ (cg-promote cg)
+ (cg-push-imm cg %t-u32 1)
+ (cg-promote cg)
+ (cg-binop cg 'shr)
+ (cg-push-imm cg %t-i32 -8)
+ (cg-binop cg 'eq)
+ (cg-return cg)
+ (cg-fn-end cg)
+ (write-bv-fd 1 (cg-finish cg)))
diff --git a/tests/cc-cg/84-bool-promotes-int.expected-exit b/tests/cc-cg/84-bool-promotes-int.expected-exit
@@ -0,0 +1 @@
+1
diff --git a/tests/cc-cg/84-bool-promotes-int.scm b/tests/cc-cg/84-bool-promotes-int.scm
@@ -0,0 +1,28 @@
+;; tests/cc-cg/84-bool-promotes-int.scm — _Bool promotes to int per
+;; C 6.3.1.1 (integer promotions: _Bool, char, short → int, not unsigned).
+;;
+;; Models:
+;; _Bool b = 1;
+;; return (b * -1) == -1;
+;;
+;; cg-promote currently treats bool as unsigned and promotes to u32,
+;; so the subsequent arith-conv with i32 picks u32 as the common
+;; type. The product 1u * (uint32_t)-1 = 0xFFFFFFFF (canonical
+;; 0x00000000FFFFFFFF after spill), and the equality test against
+;; (int)-1 (canonical 0xFFFFFFFFFFFFFFFF) fails. Correct cg promotes
+;; bool to i32 → arith-conv keeps i32 → product is canonical i32 -1
+;; → equality holds.
+
+(let ((cg (cg-init)))
+ (cg-fn-begin cg "main" '() %t-i32)
+ (cg-push-imm cg %t-bool 1)
+ (cg-promote cg)
+ (cg-push-imm cg %t-i32 -1)
+ (cg-promote cg)
+ (cg-arith-conv cg)
+ (cg-binop cg 'mul)
+ (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-cg/85-i32-u32-eq.expected-exit b/tests/cc-cg/85-i32-u32-eq.expected-exit
@@ -0,0 +1 @@
+1
diff --git a/tests/cc-cg/85-i32-u32-eq.scm b/tests/cc-cg/85-i32-u32-eq.scm
@@ -0,0 +1,22 @@
+;; tests/cc-cg/85-i32-u32-eq.scm — comparing int -3 with unsigned 4294967293
+;; via the usual arithmetic conversions: both operands take the unsigned
+;; common type so the comparison must hold (C 6.3.1.8).
+;;
+;; cg-arith-conv currently relabels the i32 operand to u32 without
+;; rewriting its canonical 64-bit slot form (still sign-extended
+;; 0xFFFF...FFFD), while %li(t1, 4294967293) materializes the literal
+;; as 0x00000000FFFFFFFD. cmpset_eq compares full 64 bits and reports
+;; unequal. Correct cg makes the operand carry the canonical u32 form
+;; for u32-typed operands.
+
+(let ((cg (cg-init)))
+ (cg-fn-begin cg "main" '() %t-i32)
+ (cg-push-imm cg %t-i32 -3)
+ (cg-promote cg)
+ (cg-push-imm cg %t-u32 4294967293)
+ (cg-promote cg)
+ (cg-arith-conv cg)
+ (cg-binop cg 'eq)
+ (cg-return cg)
+ (cg-fn-end cg)
+ (write-bv-fd 1 (cg-finish cg)))