zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 7f478b70ea6aa41063266c42ff127c0d88695bfa (tree)
parent 039a00606b3880ee66130c47fb165d8d41c0c9ab
Author: Motiejus <motiejus@jakstys.lt>
Date:   Sat,  7 Mar 2026 08:58:42 +0000

sema: fix sign-magnitude arithmetic for comptime integers

Several comptime folding paths in sema.c ignored the is_negative flag
of sign-magnitude InternPool integers:

- internComptimeInt: add `neg` parameter (was hardcoded false).
- analyzeArithmetic: implement sign-aware add/sub/mul via addSignedMag128
  helper. add(a,b) handles mixed signs by subtracting smaller magnitude;
  sub(a,b) = add(a,-b); mul result_neg = lhs_neg XOR rhs_neg.
- zirDiv: pass correct sign to result (lhs_neg != rhs_neg).
- zirShl/zirShr: preserve lhs sign through comptime shift.
- zirMinMax: use isComptimeIntWide (128-bit) instead of isComptimeInt
  (64-bit) and implement full 128-bit signed comparison.
- semaTypeOf: add DIV_FLOOR, DIV_EXACT, DIV_FLOAT_OPTIMIZED, MOD, REM
  to the bin_op type-from-lhs group.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

Diffstat:
Mstage0/sema.c | 113++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
1 file changed, 86 insertions(+), 27 deletions(-)

diff --git a/stage0/sema.c b/stage0/sema.c @@ -569,7 +569,12 @@ static TypeIndex semaTypeOf(Sema* sema, AirInstRef ref) { case AIR_INST_MAX: case AIR_INST_MIN: case AIR_INST_DIV_FLOAT: + case AIR_INST_DIV_FLOAT_OPTIMIZED: case AIR_INST_DIV_TRUNC: + case AIR_INST_DIV_FLOOR: + case AIR_INST_DIV_EXACT: + case AIR_INST_MOD: + case AIR_INST_REM: return semaTypeOf(sema, sema->air_inst_datas[inst_idx].bin_op.lhs); // cmp bin_op: result type is bool. case AIR_INST_CMP_LT: @@ -884,7 +889,10 @@ static void sub128(uint64_t a_lo, uint64_t a_hi, uint64_t b_lo, uint64_t b_hi, static bool isComptimeIntWide( const Sema* sema, AirInstRef ref, uint64_t* lo, uint64_t* hi, bool* neg); static AirInstRef internComptimeInt( - Sema* sema, TypeIndex ty, uint64_t lo, uint64_t hi); + Sema* sema, TypeIndex ty, uint64_t lo, uint64_t hi, bool neg); +static void addSignedMag128(uint64_t a_lo, uint64_t a_hi, bool a_neg, + uint64_t b_lo, uint64_t b_hi, bool b_neg, uint64_t* r_lo, uint64_t* r_hi, + bool* r_neg); // analyzeArithmetic: inner worker for arithmetic binary operations. // Ported from src/Sema.zig analyzeArithmetic (arithmetic subset). @@ -910,21 +918,26 @@ static AirInstRef analyzeArithmetic(Sema* sema, SemaBlock* block, lhs = semaCoerce(sema, block, result_ty, lhs); rhs = semaCoerce(sema, block, result_ty, rhs); } + bool r_neg = false; switch (air_tag) { case AIR_INST_ADD: case AIR_INST_ADD_WRAP: case AIR_INST_ADD_SAT: - add128(lhs_lo, lhs_hi, rhs_lo, rhs_hi, &r_lo, &r_hi); + // Sign-magnitude addition: handles mixed signs correctly. + addSignedMag128(lhs_lo, lhs_hi, lhs_neg, rhs_lo, rhs_hi, rhs_neg, + &r_lo, &r_hi, &r_neg); break; case AIR_INST_SUB: case AIR_INST_SUB_WRAP: case AIR_INST_SUB_SAT: - sub128(lhs_lo, lhs_hi, rhs_lo, rhs_hi, &r_lo, &r_hi); + // sub(a, b) = add(a, -b): flip rhs sign. + addSignedMag128(lhs_lo, lhs_hi, lhs_neg, rhs_lo, rhs_hi, !rhs_neg, + &r_lo, &r_hi, &r_neg); break; case AIR_INST_MUL: case AIR_INST_MUL_WRAP: case AIR_INST_MUL_SAT: - // Simple 128-bit mul: only lo*lo (sufficient for small values). + // Multiply magnitudes; result_neg = lhs_neg XOR rhs_neg. r_lo = lhs_lo * rhs_lo; r_hi = lhs_hi * rhs_lo + lhs_lo * rhs_hi; // Add the high part of lo*lo. @@ -938,11 +951,12 @@ static AirInstRef analyzeArithmetic(Sema* sema, SemaBlock* block, uint64_t mid = (a_lo * b_lo >> 32) + (cross & 0xFFFFFFFFU); (void)mid; // carry already in r_hi via full product } + r_neg = (lhs_neg != rhs_neg) && (r_lo != 0 || r_hi != 0); break; default: goto emit_runtime; } - return internComptimeInt(sema, result_ty, r_lo, r_hi); + return internComptimeInt(sema, result_ty, r_lo, r_hi, r_neg); } emit_runtime:; @@ -1055,7 +1069,7 @@ static AirInstRef analyzeBitNot( } uint64_t r_lo = ~val_lo & mask_lo; uint64_t r_hi = ~val_hi & mask_hi; - return internComptimeInt(sema, ty, r_lo, r_hi); + return internComptimeInt(sema, ty, r_lo, r_hi, false); } } TypeIndex ty = semaTypeOf(sema, operand); @@ -1208,18 +1222,46 @@ static bool isComptimeIntWide( } // internComptimeInt: intern a comptime integer value with given type. +// neg=true for negative values (sign-magnitude representation). static AirInstRef internComptimeInt( - Sema* sema, TypeIndex ty, uint64_t lo, uint64_t hi) { + Sema* sema, TypeIndex ty, uint64_t lo, uint64_t hi, bool neg) { InternPoolKey key; memset(&key, 0, sizeof(key)); key.tag = IP_KEY_INT; key.data.int_val.ty = ty; key.data.int_val.value_lo = lo; key.data.int_val.value_hi = hi; - key.data.int_val.is_negative = false; + key.data.int_val.is_negative = neg && (lo != 0 || hi != 0); return AIR_REF_FROM_IP(ipIntern(sema->ip, key)); } +// addSignedMag128: add two 128-bit sign-magnitude integers. +// Ported from Zig's BigInt arithmetic used in comptime evaluation. +// Handles the case where operands have different signs by converting +// to a subtraction of magnitudes. +static void addSignedMag128(uint64_t a_lo, uint64_t a_hi, bool a_neg, + uint64_t b_lo, uint64_t b_hi, bool b_neg, uint64_t* r_lo, uint64_t* r_hi, + bool* r_neg) { + if (a_neg == b_neg) { + // Same sign: add magnitudes, keep sign. + add128(a_lo, a_hi, b_lo, b_hi, r_lo, r_hi); + *r_neg = a_neg; + } else { + // Different signs: subtract smaller magnitude from larger. + bool a_gte_b = (a_hi > b_hi) || (a_hi == b_hi && a_lo >= b_lo); + if (a_gte_b) { + sub128(a_lo, a_hi, b_lo, b_hi, r_lo, r_hi); + *r_neg = a_neg; + } else { + sub128(b_lo, b_hi, a_lo, a_hi, r_lo, r_hi); + *r_neg = b_neg; + } + } + // Zero is never negative. + if (*r_lo == 0 && *r_hi == 0) + *r_neg = false; +} + // smallestUnsignedBits: compute the number of bits needed to represent max. // Ported from src/Type.zig smallestUnsignedBits. static uint16_t smallestUnsignedBits(uint16_t max) { @@ -1362,7 +1404,7 @@ static AirInstRef zirBitCount( default: break; } - return internComptimeInt(sema, result_ty, result, 0); + return internComptimeInt(sema, result_ty, result, 0, false); } AirInstData data; @@ -1410,7 +1452,7 @@ static AirInstRef zirByteSwap(Sema* sema, SemaBlock* block, uint32_t inst) { << (b * 8); } } - return internComptimeInt(sema, operand_ty, r_lo, r_hi); + return internComptimeInt(sema, operand_ty, r_lo, r_hi, false); } } @@ -1624,7 +1666,9 @@ static AirInstRef zirDiv(Sema* sema, SemaBlock* block, uint32_t inst) { // Only handle simple 64-bit integer division (rhs_hi==0, lhs_hi==0). if (rhs_hi == 0 && rhs_lo != 0 && lhs_hi == 0) { uint64_t result = lhs_lo / rhs_lo; - return internComptimeInt(sema, peer_ty, result, 0); + // Result is negative iff operands have different signs. + bool result_neg = (lhs_neg != rhs_neg) && (result != 0); + return internComptimeInt(sema, peer_ty, result, 0, result_neg); } } @@ -1683,7 +1727,7 @@ static AirInstRef zirBitwise( default: goto emit_bitwise_runtime; } - return internComptimeInt(sema, peer_ty, r_lo, r_hi); + return internComptimeInt(sema, peer_ty, r_lo, r_hi, false); } emit_bitwise_runtime:; @@ -1716,7 +1760,8 @@ static AirInstRef zirBitcast(Sema* sema, SemaBlock* block, uint32_t inst) { uint64_t lo, hi; bool neg; if (isComptimeIntWide(sema, operand, &lo, &hi, &neg)) { - return internComptimeInt(sema, dest_ty, lo, hi); + // For @bitCast, the bit pattern is reinterpreted; don't carry sign. + return internComptimeInt(sema, dest_ty, lo, hi, false); } AirInstData data; memset(&data, 0, sizeof(data)); @@ -1900,7 +1945,8 @@ static AirInstRef zirShl( shl128(lhs_lo, lhs_hi, shift, &r_lo, &r_hi); else shr128(lhs_lo, lhs_hi, shift, &r_lo, &r_hi); - return internComptimeInt(sema, lhs_ty, r_lo, r_hi); + // Shift preserves the sign for comptime_int (arithmetic semantics). + return internComptimeInt(sema, lhs_ty, r_lo, r_hi, lhs_neg); } // For regular shl/shr, the rhs is already coerced by @@ -10056,19 +10102,32 @@ static AirInstRef zirMinMax(Sema* sema, SemaBlock* block, uint32_t inst) { ZirInstRef zir_rhs = sema->code.extra[pl + 1]; AirInstRef min_lhs = resolveInst(sema, zir_lhs); AirInstRef min_rhs = resolveInst(sema, zir_rhs); - int64_t lv, rv; - if (isComptimeInt(sema, min_lhs, &lv) - && isComptimeInt(sema, min_rhs, &rv)) { - int64_t result_val - = is_max ? (lv > rv ? lv : rv) : (lv < rv ? lv : rv); - InternPoolKey key; - memset(&key, 0, sizeof(key)); - key.tag = IP_KEY_INT; - key.data.int_val.ty = IP_INDEX_COMPTIME_INT_TYPE; - key.data.int_val.value_lo - = (uint64_t)(result_val < 0 ? -result_val : result_val); - key.data.int_val.is_negative = (result_val < 0); - return AIR_REF_FROM_IP(ipIntern(sema->ip, key)); + // Comptime folding: 128-bit sign-magnitude comparison. + // Ported from Sema.zig analyzeMinMax → Value.numberMin/Max. + uint64_t lv_lo, lv_hi, rv_lo, rv_hi; + bool lv_neg, rv_neg; + if (isComptimeIntWide(sema, min_lhs, &lv_lo, &lv_hi, &lv_neg) + && isComptimeIntWide(sema, min_rhs, &rv_lo, &rv_hi, &rv_neg)) { + // Compare signed 128-bit: determine if lhs < rhs. + bool lhs_lt_rhs; + if (lv_neg != rv_neg) { + lhs_lt_rhs = lv_neg; // negative < positive + } else if (!lv_neg) { + // Both non-negative: compare magnitudes. + lhs_lt_rhs = (lv_hi < rv_hi) || (lv_hi == rv_hi && lv_lo < rv_lo); + } else { + // Both negative: larger magnitude = smaller value. + lhs_lt_rhs = (lv_hi > rv_hi) || (lv_hi == rv_hi && lv_lo > rv_lo); + } + // Pick the appropriate operand. + bool pick_lhs = is_max ? !lhs_lt_rhs : lhs_lt_rhs; + uint64_t r_lo = pick_lhs ? lv_lo : rv_lo; + uint64_t r_hi = pick_lhs ? lv_hi : rv_hi; + bool r_neg = pick_lhs ? lv_neg : rv_neg; + TypeIndex result_ty = semaTypeOf(sema, min_lhs); + if (result_ty == IP_INDEX_COMPTIME_INT_TYPE) + result_ty = semaTypeOf(sema, min_rhs); + return internComptimeInt(sema, result_ty, r_lo, r_hi, r_neg); } TypeIndex peer_ty = resolvePeerTypes(sema, min_lhs, min_rhs); AirInstRef c_lhs = semaCoerce(sema, block, peer_ty, min_lhs);