commit 19451d87f84f63a13c8cdebafbf3818066b75d6b (tree)
parent 904cdd923e0c5e34a423823d4bc2af60e271630e
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Fri, 20 Feb 2026 14:05:18 +0000
sema: add store, compare, ptr_type param, and validate_* handlers
New ZIR instruction handlers:
- STORE_NODE: store value to pointer (bin_op with coercion)
- CMP_LT/LTE/EQ/GTE/GT/NEQ: comparison operations
- VALIDATE_DEREF, VALIDATE_CONST: no-op validation (skip in body)
Infrastructure:
- ptrChildType: extract child type from pointer type
- Extend param type body resolution to handle ptr_type + break_inline
(2-instruction type body, e.g. *u32 param)
- Fix PARAM dispatch: skip without overwriting inst_map (was clobbering
arg mapping set by zirFunc)
New tests: pointer param identity, store to pointer, compare lt/eq,
f32 arithmetic, multi-param, nested bitcast xor, sub comptime.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
| M | stage0/sema.c | | | 98 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
| M | stage0/sema_test.zig | | | 46 | +++++++++++++++++++++++++++++++++++++++++++++- |
2 files changed, 141 insertions(+), 3 deletions(-)
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -510,6 +510,31 @@ static AirInstRef semaCoerce(
return ref;
}
+// ptrChildType: get the child (element) type of a pointer type.
+static TypeIndex ptrChildType(const InternPool* ip, TypeIndex ptr_ty) {
+ assert(ip->items[ptr_ty].tag == IP_KEY_PTR_TYPE);
+ return ip->items[ptr_ty].data.ptr_type.child;
+}
+
+// zirStoreNode: handle store_node ZIR instruction.
+// Ported from src/Sema.zig zirStoreNode (simplified, no safety).
+static void zirStoreNode(Sema* sema, SemaBlock* block, uint32_t inst) {
+ uint32_t payload_index
+ = sema->code.inst_datas[inst].pl_node.payload_index;
+ ZirInstRef ptr_ref = sema->code.extra[payload_index];
+ ZirInstRef val_ref = sema->code.extra[payload_index + 1];
+ AirInstRef ptr = resolveInst(sema, ptr_ref);
+ AirInstRef val = resolveInst(sema, val_ref);
+ TypeIndex ptr_ty = semaTypeOf(sema, ptr);
+ TypeIndex elem_ty = ptrChildType(sema->ip, ptr_ty);
+ val = semaCoerce(sema, block, elem_ty, val);
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.bin_op.lhs = ptr;
+ data.bin_op.rhs = val;
+ (void)blockAddInst(block, AIR_INST_STORE, data);
+}
+
// zirArithmetic: handle add/sub ZIR instructions.
// Ported from src/Sema.zig zirArithmetic.
static AirInstRef zirArithmetic(
@@ -856,6 +881,7 @@ static void zirFunc(Sema* sema, SemaBlock* block, uint32_t inst) {
// Resolve param type from type body.
// For simple (non-generic) types, the type body contains a single
// break_inline instruction whose operand is the type ref.
+ // For pointer types, the type body is [ptr_type, break_inline].
TypeIndex param_ty = IP_INDEX_VOID_TYPE; // fallback
if (type_body_len_p == 1) {
uint32_t type_inst = sema->code.extra[param_payload + 2];
@@ -865,6 +891,31 @@ static void zirFunc(Sema* sema, SemaBlock* block, uint32_t inst) {
= sema->code.inst_datas[type_inst].break_data.operand;
assert(type_ref < ZIR_REF_START_INDEX); // pre-interned
param_ty = type_ref;
+ } else if (type_body_len_p == 2) {
+ uint32_t first_inst = sema->code.extra[param_payload + 2];
+ ZirInstTag first_tag = sema->code.inst_tags[first_inst];
+ if (first_tag == ZIR_INST_PTR_TYPE) {
+ // ptr_type: flags(u8) + size(u8) + pad(u16) + payload_index
+ uint8_t zir_flags
+ = sema->code.inst_datas[first_inst].ptr_type.flags;
+ uint8_t zir_size
+ = sema->code.inst_datas[first_inst].ptr_type.size;
+ uint32_t pi
+ = sema->code.inst_datas[first_inst].ptr_type.payload_index;
+ ZirInstRef elem_ty_ref = sema->code.extra[pi]; // PtrType.elem_type
+ assert(elem_ty_ref < ZIR_REF_START_INDEX); // pre-interned
+ TypeIndex elem_ty = elem_ty_ref;
+ // Build IP PtrType key.
+ uint32_t ip_flags = (uint32_t)zir_size & PTR_FLAGS_SIZE_MASK;
+ if (!(zir_flags & 0x02)) // ZIR bit 1 = is_mutable; !is_mutable → const
+ ip_flags |= PTR_FLAGS_IS_CONST;
+ InternPoolKey key;
+ memset(&key, 0, sizeof(key));
+ key.tag = IP_KEY_PTR_TYPE;
+ key.data.ptr_type.child = elem_ty;
+ key.data.ptr_type.flags = ip_flags;
+ param_ty = ipIntern(sema->ip, key);
+ }
}
// Emit AIR_INST_ARG.
@@ -1131,12 +1182,11 @@ static bool analyzeBodyInner(
// param: parameter declaration (in comptime declaration body).
// At module level, params are not analyzed; zirFunc processes
// them separately. Map to void.
+ // Params: already mapped to arg AIR refs by zirFunc.
case ZIR_INST_PARAM:
case ZIR_INST_PARAM_COMPTIME:
case ZIR_INST_PARAM_ANYTYPE:
case ZIR_INST_PARAM_ANYTYPE_COMPTIME:
- instMapPut(
- &sema->inst_map, inst, AIR_REF_FROM_IP(IP_INDEX_VOID_TYPE));
i++;
continue;
@@ -1262,6 +1312,38 @@ static bool analyzeBodyInner(
i++;
continue;
+ // Comparisons: same binary pattern as arithmetic.
+ case ZIR_INST_CMP_LT:
+ instMapPut(&sema->inst_map, inst,
+ zirArithmetic(sema, block, inst, AIR_INST_CMP_LT));
+ i++;
+ continue;
+ case ZIR_INST_CMP_LTE:
+ instMapPut(&sema->inst_map, inst,
+ zirArithmetic(sema, block, inst, AIR_INST_CMP_LTE));
+ i++;
+ continue;
+ case ZIR_INST_CMP_EQ:
+ instMapPut(&sema->inst_map, inst,
+ zirArithmetic(sema, block, inst, AIR_INST_CMP_EQ));
+ i++;
+ continue;
+ case ZIR_INST_CMP_GTE:
+ instMapPut(&sema->inst_map, inst,
+ zirArithmetic(sema, block, inst, AIR_INST_CMP_GTE));
+ i++;
+ continue;
+ case ZIR_INST_CMP_GT:
+ instMapPut(&sema->inst_map, inst,
+ zirArithmetic(sema, block, inst, AIR_INST_CMP_GT));
+ i++;
+ continue;
+ case ZIR_INST_CMP_NEQ:
+ instMapPut(&sema->inst_map, inst,
+ zirArithmetic(sema, block, inst, AIR_INST_CMP_NEQ));
+ i++;
+ continue;
+
// Bitwise: xor, bit_and, bit_or.
case ZIR_INST_XOR:
instMapPut(&sema->inst_map, inst,
@@ -1331,6 +1413,18 @@ static bool analyzeBodyInner(
i++;
continue;
+ // Store.
+ case ZIR_INST_STORE_NODE:
+ zirStoreNode(sema, block, inst);
+ i++;
+ continue;
+
+ // Validation-only (no AIR emitted).
+ case ZIR_INST_VALIDATE_DEREF:
+ case ZIR_INST_VALIDATE_CONST:
+ i++;
+ continue;
+
// For all other instructions, produce a void mapping and skip.
// As handlers are implemented, they will replace this default.
default: {
diff --git a/stage0/sema_test.zig b/stage0/sema_test.zig
@@ -724,11 +724,19 @@ test "sema air: mul two args" {
try semaAirRawCheck("export fn f(x: u32, y: u32) u32 { return x * y; }");
}
-// TODO: bool and/or require block merges and conditional analysis.
+// TODO: bool and/or require block merges and conditional analysis (bool_br_and).
// test "sema air: bool and" {
// try semaAirRawCheck("export fn f(x: bool, y: bool) bool { return x and y; }");
// }
+test "sema air: compare lt" {
+ try semaAirRawCheck("export fn f(x: u32, y: u32) bool { return x < y; }");
+}
+
+test "sema air: compare eq" {
+ try semaAirRawCheck("export fn f(x: u32, y: u32) bool { return x == y; }");
+}
+
test "sema air: bit shift right" {
try semaAirRawCheck("export fn f(x: u32) u32 { return x >> 1; }");
}
@@ -762,6 +770,42 @@ test "sema air: shift and mask" {
);
}
+test "sema air: f32 arithmetic" {
+ try semaAirRawCheck("export fn f(x: f32, y: f32) f32 { return x + y; }");
+}
+
+test "sema air: multi-param function" {
+ try semaAirRawCheck(
+ \\export fn f(a: u32, b: u32, c: u32) u32 {
+ \\ return (a + b) * c;
+ \\}
+ );
+}
+
+test "sema air: nested bitcast xor" {
+ try semaAirRawCheck(
+ \\export fn f(a: f32) f32 {
+ \\ return @bitCast(@as(u32, @bitCast(a)) ^ @as(u32, 0x80000000));
+ \\}
+ );
+}
+
+test "sema air: pointer param identity" {
+ try semaAirRawCheck("export fn f(x: *u32) *u32 { return x; }");
+}
+
+test "sema air: store to pointer" {
+ try semaAirRawCheck(
+ \\export fn f(x: *u32) void {
+ \\ x.* = 42;
+ \\}
+ );
+}
+
+test "sema air: sub comptime" {
+ try semaAirRawCheck("export fn f(x: u32) u32 { return x - 1; }");
+}
+
test "sema air: bit shift left" {
try semaAirRawCheck("export fn f(x: u32) u32 { return x << 1; }");
}