sema: add add_sat, sub_sat, int_from_bool; more unit tests

Implement three new ZIR instruction handlers:
- ZIR_INST_ADD_SAT / ZIR_INST_SUB_SAT: saturating arithmetic, same
  pattern as existing wrapping ops (zirArithmetic + bin_op AIR)
- ZIR_INST_INT_FROM_BOOL: @intFromBool, emits bitcast to u1
  (ported from src/Sema.zig zirIntFromBool)

Add semaTypeOf entries for AIR_INST_ADD_SAT / AIR_INST_SUB_SAT.

Add 10 new sema_test.zig unit tests: intFromBool, add_sat, sub_sat,
bit_or, bit_and, f16 add, f64 mul, intcast with computed dest type,
and multiple exported functions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 21:22:15 +00:00
parent 6f0ac50719
commit ab42a22282
2 changed files with 77 additions and 0 deletions

View File

@@ -524,6 +524,8 @@ static TypeIndex semaTypeOf(Sema* sema, AirInstRef ref) {
case AIR_INST_SUB_WRAP:
case AIR_INST_MUL:
case AIR_INST_MUL_WRAP:
case AIR_INST_ADD_SAT:
case AIR_INST_SUB_SAT:
case AIR_INST_BIT_AND:
case AIR_INST_BIT_OR:
case AIR_INST_XOR:
@@ -720,6 +722,19 @@ static AirInstRef zirBoolNot(Sema* sema, SemaBlock* block, uint32_t inst) {
return blockAddInst(block, AIR_INST_NOT, data);
}
// zirIntFromBool: handle int_from_bool ZIR instruction (@intFromBool).
// Ported from src/Sema.zig zirIntFromBool.
// Emits AIR_INST_BITCAST to u1.
static AirInstRef zirIntFromBool(Sema* sema, SemaBlock* block, uint32_t inst) {
ZirInstRef operand_ref = sema->code.inst_datas[inst].un_node.operand;
AirInstRef operand = resolveInst(sema, operand_ref);
AirInstData data;
memset(&data, 0, sizeof(data));
data.ty_op.ty_ref = AIR_REF_FROM_IP(IP_INDEX_U1_TYPE);
data.ty_op.operand = operand;
return blockAddInst(block, AIR_INST_BITCAST, data);
}
// smallestUnsignedBits: compute the number of bits needed to represent max.
// Ported from src/Type.zig smallestUnsignedBits.
static uint16_t smallestUnsignedBits(uint16_t max) {
@@ -3189,6 +3204,16 @@ static bool analyzeBodyInner(
zirArithmetic(sema, block, inst, AIR_INST_MUL_WRAP));
i++;
continue;
case ZIR_INST_ADD_SAT:
instMapPut(&sema->inst_map, inst,
zirArithmetic(sema, block, inst, AIR_INST_ADD_SAT));
i++;
continue;
case ZIR_INST_SUB_SAT:
instMapPut(&sema->inst_map, inst,
zirArithmetic(sema, block, inst, AIR_INST_SUB_SAT));
i++;
continue;
// Comparisons: same binary pattern as arithmetic.
case ZIR_INST_CMP_LT:
@@ -3341,6 +3366,11 @@ static bool analyzeBodyInner(
zirBoolNot(sema, block, inst));
i++;
continue;
case ZIR_INST_INT_FROM_BOOL:
instMapPut(&sema->inst_map, inst,
zirIntFromBool(sema, block, inst));
i++;
continue;
case ZIR_INST_NEGATE_WRAP:
instMapPut(&sema->inst_map, inst,
zirNegateWrap(sema, block, inst));

View File

@@ -1019,3 +1019,50 @@ test "sema air: same-file inline call with two args" {
\\}
);
}
test "sema air: intFromBool" {
try semaAirRawCheck("export fn f(x: bool) u32 { return @intFromBool(x); }");
}
test "sema air: add_sat" {
try semaAirRawCheck("export fn f(x: u32, y: u32) u32 { return x +| y; }");
}
test "sema air: sub_sat" {
try semaAirRawCheck("export fn f(x: u32, y: u32) u32 { return x -| y; }");
}
test "sema air: bit_or" {
try semaAirRawCheck("export fn f(x: u32, y: u32) u32 { return x | y; }");
}
test "sema air: bit_and" {
try semaAirRawCheck("export fn f(x: u32, y: u32) u32 { return x & y; }");
}
test "sema air: f16 add" {
try semaAirRawCheck("export fn f(x: f16, y: f16) f16 { return x + y; }");
}
test "sema air: f64 mul" {
try semaAirRawCheck("export fn f(x: f64, y: f64) f64 { return x * y; }");
}
test "sema air: intcast computed dest type" {
try semaAirRawCheck(
\\export fn f(x: u16) u32 {
\\ return @intCast(x);
\\}
);
}
test "sema air: multiple return paths" {
try semaAirRawCheck(
\\export fn f(x: u32) u32 {
\\ return x + 1;
\\}
\\export fn g(x: u32) u32 {
\\ return x * 2;
\\}
);
}