commit 411d225ad97accaeda6882acbb56d1115d03cd54 (tree)
parent 4f3930dbe27b40ef42bb327482d7f0283f8b331b
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Fri, 20 Feb 2026 21:22:15 +0000
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>
Diffstat:
2 files changed, 77 insertions(+), 0 deletions(-)
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -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));
diff --git a/stage0/sema_test.zig b/stage0/sema_test.zig
@@ -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;
+ \\}
+ );
+}