From 19451d87f84f63a13c8cdebafbf3818066b75d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Fri, 20 Feb 2026 14:05:18 +0000 Subject: [PATCH] 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 --- stage0/sema.c | 98 +++++++++++++++++++++++++++++++++++++++++++- stage0/sema_test.zig | 46 ++++++++++++++++++++- 2 files changed, 141 insertions(+), 3 deletions(-) diff --git a/stage0/sema.c b/stage0/sema.c index 279feaf222..d77cbef765 100644 --- 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 index 2ff592d56b..2e50af0f38 100644 --- 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; }"); }