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>
This commit is contained in:
2026-02-20 14:05:18 +00:00
parent 904cdd923e
commit 19451d87f8
2 changed files with 141 additions and 3 deletions

View File

@@ -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: {

View File

@@ -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; }");
}