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:
| M | stage0/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);