128-cast-signedness.c (2580B)
1 /* Same-size and widening casts that flip signedness must canonicalize 2 * the slot for the destination type — i8 (sign-extended in the slot) 3 * cast to u8 / u16 / u32 / u64 needs the high bits cleared so a 4 * subsequent comparison or wider arithmetic sees the C-semantic value. 5 * 6 * The classic case (which broke the tcc-boot2 string-roundtrip test): 7 * const char *s = "...\xCA..."; 8 * if ((unsigned char)s[1] != 0xCA) ... // this MUST take the false branch 9 * 10 * On aarch64, s[1] sign-extends to int -54. Slot = 0xFFFFFFFFFFFFFFCA. 11 * The naive "same-size cast = relabel" path leaves the high bits set, 12 * so a 64-bit compare against 0x00000000000000CA returns "not equal". 13 */ 14 15 int main(int argc, char **argv) { 16 const signed char *s = (const signed char *)"a\312\377"; /* bytes 'a', 0xCA, 0xFF, NUL */ 17 18 /* --- signed → unsigned, same size (i8 → u8) ------------------- */ 19 if ((unsigned char) s[1] != 202) return 1; 20 if ((unsigned char) s[1] != 0xCA) return 2; 21 /* Must NOT equal -54 once cast to u8. */ 22 if ((unsigned char) s[1] == -54) return 3; 23 24 /* --- signed → unsigned, widening (i8 → u16/u32/u64) ----------- */ 25 /* C: (unsigned int)(signed char)(-54) = 4294967242 = 0xFFFFFFCA. */ 26 if ((unsigned int) s[1] != 0xFFFFFFCAu) return 4; 27 /* Comparison at u32 width: -54 (slot=…FFCA) ≠ 4294967242 only 28 * if the wider compare reads high bits correctly. */ 29 30 /* (unsigned long)(signed char)(-54) = 0xFFFFFFFFFFFFFFCA. */ 31 if ((unsigned long) s[1] != 0xFFFFFFFFFFFFFFCAul) return 5; 32 33 /* --- unsigned → signed, same size (u8 → i8) ------------------- */ 34 { 35 unsigned char uc = 0xCA; 36 signed char sc = (signed char) uc; 37 if (sc != -54) return 10; 38 /* And the reverse round-trip: */ 39 if ((unsigned char) sc != 202) return 11; 40 } 41 42 /* --- intermediate-cast doesn't lose the value ----------------- */ 43 { 44 unsigned char tmp = (unsigned char) s[1]; 45 if (tmp != 202) return 20; 46 if ((int) tmp != 202) return 21; 47 } 48 49 /* --- i32 → u32 same-size: the high bits matter for 64-bit ops. */ 50 { 51 int neg = -1; /* slot = 0xFFFFFFFFFFFFFFFF */ 52 unsigned int u = (unsigned int) neg; /* slot must zero high 32 */ 53 /* cast result canonical: 0x00000000FFFFFFFF = 4294967295 */ 54 if (u != 4294967295u) return 30; 55 if ((unsigned long) u != 4294967295ul) return 31; 56 } 57 58 return 0; 59 }