boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

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:
Atests/cc-cg/80-uneg-canonical.expected-exit | 1+
Atests/cc-cg/80-uneg-canonical.scm | 23+++++++++++++++++++++++
Atests/cc-cg/81-ubnot-canonical.expected-exit | 1+
Atests/cc-cg/81-ubnot-canonical.scm | 20++++++++++++++++++++
Atests/cc-cg/82-uadd-wrap-canonical.expected-exit | 1+
Atests/cc-cg/82-uadd-wrap-canonical.scm | 21+++++++++++++++++++++
Atests/cc-cg/83-signed-shr-by-unsigned.expected-exit | 1+
Atests/cc-cg/83-signed-shr-by-unsigned.scm | 28++++++++++++++++++++++++++++
Atests/cc-cg/84-bool-promotes-int.expected-exit | 1+
Atests/cc-cg/84-bool-promotes-int.scm | 28++++++++++++++++++++++++++++
Atests/cc-cg/85-i32-u32-eq.expected-exit | 1+
Atests/cc-cg/85-i32-u32-eq.scm | 22++++++++++++++++++++++
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)))