From 885e12d21d945ad5d6d07e81af8040cadfc79be1 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 23:06:35 -0700 Subject: [PATCH] and-not fold --- src/big_int.cpp | 12 +- tests/internal/test_constant_folding.odin | 139 ++++++++++++++++++++++ 2 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 tests/internal/test_constant_folding.odin diff --git a/src/big_int.cpp b/src/big_int.cpp index 6db3301da..1d81a0483 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -489,7 +489,8 @@ gb_internal void big_int_and(BigInt *dst, BigInt const *x, BigInt const *y) { gb_internal void big_int_and_not(BigInt *dst, BigInt const *x, BigInt const *y) { if (mp_iszero(x)) { - big_int_init(dst, y); + // 0 &~ y == 0 & ~y == 0 + big_int_from_i64(dst, 0); return; } if (mp_iszero(y)) { @@ -505,13 +506,13 @@ gb_internal void big_int_and_not(BigInt *dst, BigInt const *x, BigInt const *y) mp_decr(&x1); mp_decr(&y1); - BigInt ny1 = {}; - mp_complement(&y1, &ny1); - mp_and(&x1, &ny1, dst); + BigInt nx1 = {}; + mp_complement(&x1, &nx1); + mp_and(&y1, &nx1, dst); big_int_dealloc(&x1); big_int_dealloc(&y1); - big_int_dealloc(&ny1); + big_int_dealloc(&nx1); return; } @@ -532,6 +533,7 @@ gb_internal void big_int_and_not(BigInt *dst, BigInt const *x, BigInt const *y) BigInt z1 = {}; big_int_or(&z1, &x1, &y1); mp_add_d(&z1, 1, dst); + big_int_neg(dst, dst); big_int_dealloc(&x1); big_int_dealloc(&y1); diff --git a/tests/internal/test_constant_folding.odin b/tests/internal/test_constant_folding.odin new file mode 100644 index 000000000..71e70488d --- /dev/null +++ b/tests/internal/test_constant_folding.odin @@ -0,0 +1,139 @@ +package test_internal + +import "core:testing" + +// Constant folding against the answer the backend produces. A folded constant that is merely +// wrong still compiles, so a harness comparing accept/reject sees agreement +// Every case here pairs a constant with the same expression on variables for that reason. +// +// `a &~ b` is `a & ~b`. `big_int_and_not` had three independent faults: `0 &~ y` returned `y`, +// the both-negative branch used its operands the wrong way round, and the negative-left branch +// dropped the sign of its result. + +@(test) +and_not_constant_folding_matches_runtime :: proc(t: ^testing.T) { + // the zero short-circuit: 0 &~ anything is 0 + { + a, b := 0, 3 + testing.expect_value(t, 0 &~ 3, a &~ b) + testing.expect_value(t, 0 &~ 3, 0) + } + { + a, b := 0, -3 + testing.expect_value(t, 0 &~ -3, a &~ b) + testing.expect_value(t, 0 &~ -3, 0) + } + + // negative left operand: the result must stay negative + { + a, b := -7, 3 + testing.expect_value(t, -7 &~ 3, a &~ b) + testing.expect_value(t, -7 &~ 3, -8) + } + { + a, b := -255, 5 + testing.expect_value(t, -255 &~ 5, a &~ b) + testing.expect_value(t, -255 &~ 5, -256) + } + + // both negative + { + a, b := -7, -3 + testing.expect_value(t, -7 &~ -3, a &~ b) + testing.expect_value(t, -7 &~ -3, 0) + } + { + a, b := -3, -7 + testing.expect_value(t, -3 &~ -7, a &~ b) + testing.expect_value(t, -3 &~ -7, 4) + } + + // the cases that were already correct, so a fix cannot regress them + { + a, b := 7, 3 + testing.expect_value(t, 7 &~ 3, a &~ b) + testing.expect_value(t, 7 &~ 3, 4) + } + { + a, b := 7, -3 + testing.expect_value(t, 7 &~ -3, a &~ b) + testing.expect_value(t, 7 &~ -3, 2) + } + { + a, b := 7, 0 + testing.expect_value(t, 7 &~ 0, a &~ b) + testing.expect_value(t, 7 &~ 0, 7) + } +} + +@(test) +and_not_constant_folding_every_width :: proc(t: ^testing.T) { + // the zero-left shape reaches unsigned types too + { + a, b := u8(0), u8(1) + testing.expect_value(t, u8(0) &~ u8(1), a &~ b) + testing.expect_value(t, u8(0) &~ u8(1), u8(0)) + } + { + a, b := u64(0), u64(255) + testing.expect_value(t, u64(0) &~ u64(255), a &~ b) + testing.expect_value(t, u64(0) &~ u64(255), u64(0)) + } + + // signed, at the extremes of each width + { + a, b := i8(-128), i8(1) + testing.expect_value(t, i8(-128) &~ i8(1), a &~ b) + testing.expect_value(t, i8(-128) &~ i8(1), i8(-128)) + } + { + a, b := i8(-128), i8(127) + testing.expect_value(t, i8(-128) &~ i8(127), a &~ b) + testing.expect_value(t, i8(-128) &~ i8(127), i8(-128)) + } + { + a, b := i8(-7), i8(-128) + testing.expect_value(t, i8(-7) &~ i8(-128), a &~ b) + testing.expect_value(t, i8(-7) &~ i8(-128), i8(121)) + } + { + a, b := i16(-7), i16(3) + testing.expect_value(t, i16(-7) &~ i16(3), a &~ b) + testing.expect_value(t, i16(-7) &~ i16(3), i16(-8)) + } + { + a, b := i32(-255), i32(5) + testing.expect_value(t, i32(-255) &~ i32(5), a &~ b) + testing.expect_value(t, i32(-255) &~ i32(5), i32(-256)) + } + { + a, b := i64(-7), i64(-3) + testing.expect_value(t, i64(-7) &~ i64(-3), a &~ b) + testing.expect_value(t, i64(-7) &~ i64(-3), i64(0)) + } +} + +// `&~` was the only operator found divergent; the rest of the bitwise family shares the sign +// handling and must stay agreeing. + +@(test) +bitwise_constant_folding_matches_runtime :: proc(t: ^testing.T) { + { + a, b := -7, 3 + testing.expect_value(t, -7 & 3, a & b) + testing.expect_value(t, -7 | 3, a | b) + testing.expect_value(t, -7 ~ 3, a ~ b) + } + { + a, b := -7, -3 + testing.expect_value(t, -7 & -3, a & b) + testing.expect_value(t, -7 | -3, a | b) + testing.expect_value(t, -7 ~ -3, a ~ b) + } + { + a, b := 0, -3 + testing.expect_value(t, 0 & -3, a & b) + testing.expect_value(t, 0 | -3, a | b) + testing.expect_value(t, 0 ~ -3, a ~ b) + } +}