commit 745b2702bf760001d16dc82728e09f37158e8eff (tree)
parent f1a5644812a9904bff60c4a1db5f5fdfbb7da18d
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Fri, 20 Feb 2026 14:31:45 +0000
sema: add wrapping ops, bool_not, float/int casts, bit counting, byte_swap
New ZIR handlers:
- addwrap/subwrap/mulwrap → AIR add_wrap/sub_wrap/mul_wrap
- negate_wrap → AIR sub_wrap(0, operand)
- bool_not → AIR not(bool, operand)
- float_cast → AIR fpext or fptrunc (width comparison)
- int_from_float, float_from_int → AIR ty_op casts
- clz, ctz, pop_count → AIR ty_op with computed result type
- byte_swap → AIR ty_op(operand_ty, operand)
Infrastructure improvements:
- semaCoerce: handle runtime int→int coercion via AIR intcast
- ret_node: use semaCoerce instead of coerceIntRef for full coercion
- Return type resolution: handle 2-instruction ptr_type bodies
- func_fancy: properly track ret_ty_body_len and ret_ty_ref_pos
- semaTypeOf: add wrapping ops, cmp, load, not, float/int casts, bit ops
- airDataRefSlots: fix LOAD/NOT classification (ty_op not un_op),
add wrap ops, float/int casts, bit count ops
- zirAsShiftOperand: pass block for proper coercion support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
| M | stage0/sema.c | | | 346 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
| M | stage0/sema_test.zig | | | 135 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
2 files changed, 470 insertions(+), 11 deletions(-)
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -461,18 +461,39 @@ static TypeIndex semaTypeOf(Sema* sema, AirInstRef ref) {
return AIR_REF_TO_IP(sema->air_inst_datas[inst_idx].arg.ty_ref);
// bin_op: type derived from LHS.
case AIR_INST_ADD:
+ case AIR_INST_ADD_WRAP:
case AIR_INST_SUB:
+ case AIR_INST_SUB_WRAP:
case AIR_INST_MUL:
+ case AIR_INST_MUL_WRAP:
case AIR_INST_BIT_AND:
case AIR_INST_BIT_OR:
case AIR_INST_XOR:
case AIR_INST_SHL:
case AIR_INST_SHR:
return semaTypeOf(sema, sema->air_inst_datas[inst_idx].bin_op.lhs);
+ // cmp bin_op: result type is bool.
+ case AIR_INST_CMP_LT:
+ case AIR_INST_CMP_LTE:
+ case AIR_INST_CMP_EQ:
+ case AIR_INST_CMP_GTE:
+ case AIR_INST_CMP_GT:
+ case AIR_INST_CMP_NEQ:
+ return IP_INDEX_BOOL_TYPE;
// ty_op: type from ty_ref field.
case AIR_INST_BITCAST:
case AIR_INST_INTCAST:
case AIR_INST_TRUNC:
+ case AIR_INST_LOAD:
+ case AIR_INST_NOT:
+ case AIR_INST_FPTRUNC:
+ case AIR_INST_FPEXT:
+ case AIR_INST_INT_FROM_FLOAT:
+ case AIR_INST_FLOAT_FROM_INT:
+ case AIR_INST_CLZ:
+ case AIR_INST_CTZ:
+ case AIR_INST_POPCOUNT:
+ case AIR_INST_BYTE_SWAP:
return AIR_REF_TO_IP(sema->air_inst_datas[inst_idx].ty_op.ty_ref);
default:
assert(0 && "semaTypeOf: unhandled AIR tag");
@@ -500,12 +521,21 @@ static TypeIndex resolvePeerType(
// Ported from src/Sema.zig coerce (simplified).
static AirInstRef semaCoerce(
Sema* sema, SemaBlock* block, TypeIndex target_ty, AirInstRef ref) {
- (void)block;
TypeIndex src_ty = semaTypeOf(sema, ref);
if (src_ty == target_ty)
return ref;
if (src_ty == IP_INDEX_COMPTIME_INT_TYPE)
return coerceIntRef(sema, ref, target_ty);
+ // Runtime int→int coercion: emit intcast.
+ if (AIR_REF_IS_INST(ref)
+ && sema->ip->items[src_ty].tag == IP_KEY_INT_TYPE
+ && sema->ip->items[target_ty].tag == IP_KEY_INT_TYPE) {
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.ty_op.ty_ref = AIR_REF_FROM_IP(target_ty);
+ data.ty_op.operand = ref;
+ return blockAddInst(block, AIR_INST_INTCAST, data);
+ }
assert(0 && "semaCoerce: unhandled type combination");
return ref;
}
@@ -535,6 +565,139 @@ static void zirStoreNode(Sema* sema, SemaBlock* block, uint32_t inst) {
(void)blockAddInst(block, AIR_INST_STORE, data);
}
+// zirLoad: handle load ZIR instruction (pointer dereference).
+// Ported from src/Sema.zig zirLoad.
+static AirInstRef zirLoad(Sema* sema, SemaBlock* block, uint32_t inst) {
+ ZirInstRef operand_ref = sema->code.inst_datas[inst].un_node.operand;
+ AirInstRef ptr = resolveInst(sema, operand_ref);
+ TypeIndex ptr_ty = semaTypeOf(sema, ptr);
+ TypeIndex elem_ty = ptrChildType(sema->ip, ptr_ty);
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.ty_op.ty_ref = AIR_REF_FROM_IP(elem_ty);
+ data.ty_op.operand = ptr;
+ return blockAddInst(block, AIR_INST_LOAD, data);
+}
+
+// zirNegate: handle negate ZIR instruction (unary -).
+// Ported from src/Sema.zig zirNegate.
+// Lowers to sub(0, operand).
+static AirInstRef zirNegate(Sema* sema, SemaBlock* block, uint32_t inst) {
+ ZirInstRef operand_ref = sema->code.inst_datas[inst].un_node.operand;
+ AirInstRef operand = resolveInst(sema, operand_ref);
+ TypeIndex ty = semaTypeOf(sema, operand);
+ // Create a zero value of the operand type.
+ InternPoolKey key;
+ memset(&key, 0, sizeof(key));
+ key.tag = IP_KEY_INT;
+ key.data.int_val.ty = ty;
+ key.data.int_val.value = 0;
+ key.data.int_val.is_negative = false;
+ AirInstRef zero = AIR_REF_FROM_IP(ipIntern(sema->ip, key));
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.bin_op.lhs = zero;
+ data.bin_op.rhs = operand;
+ return blockAddInst(block, AIR_INST_SUB, data);
+}
+
+// zirNegateWrap: handle negate_wrap ZIR instruction (wrapping unary -).
+// Ported from src/Sema.zig zirNegateWrap.
+// Lowers to sub_wrap(0, operand).
+static AirInstRef zirNegateWrap(Sema* sema, SemaBlock* block, uint32_t inst) {
+ ZirInstRef operand_ref = sema->code.inst_datas[inst].un_node.operand;
+ AirInstRef operand = resolveInst(sema, operand_ref);
+ TypeIndex ty = semaTypeOf(sema, operand);
+ InternPoolKey key;
+ memset(&key, 0, sizeof(key));
+ key.tag = IP_KEY_INT;
+ key.data.int_val.ty = ty;
+ key.data.int_val.value = 0;
+ key.data.int_val.is_negative = false;
+ AirInstRef zero = AIR_REF_FROM_IP(ipIntern(sema->ip, key));
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.bin_op.lhs = zero;
+ data.bin_op.rhs = operand;
+ return blockAddInst(block, AIR_INST_SUB_WRAP, data);
+}
+
+// zirBitNot: handle bit_not ZIR instruction (bitwise ~).
+// Ported from src/Sema.zig zirBitNot.
+static AirInstRef zirBitNot(Sema* sema, SemaBlock* block, uint32_t inst) {
+ ZirInstRef operand_ref = sema->code.inst_datas[inst].un_node.operand;
+ AirInstRef operand = resolveInst(sema, operand_ref);
+ TypeIndex ty = semaTypeOf(sema, operand);
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.ty_op.ty_ref = AIR_REF_FROM_IP(ty);
+ data.ty_op.operand = operand;
+ return blockAddInst(block, AIR_INST_NOT, data);
+}
+
+// zirBoolNot: handle bool_not ZIR instruction.
+// Ported from src/Sema.zig zirBoolNot.
+// Emits AIR_INST_NOT with type = bool.
+static AirInstRef zirBoolNot(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_BOOL_TYPE);
+ data.ty_op.operand = operand;
+ return blockAddInst(block, AIR_INST_NOT, data);
+}
+
+// smallestUnsignedBits: compute the number of bits needed to represent max.
+// Ported from src/Type.zig smallestUnsignedBits.
+static uint16_t smallestUnsignedBits(uint16_t max) {
+ if (max == 0) return 0;
+ uint16_t count = 0;
+ uint16_t s = max;
+ while (s != 0) {
+ count++;
+ s >>= 1;
+ }
+ return count;
+}
+
+// zirBitCount: handle clz/ctz/pop_count ZIR instructions.
+// Ported from src/Sema.zig zirBitCount.
+// Result type is smallestUnsignedInt(operand_bits).
+static AirInstRef zirBitCount(
+ Sema* sema, SemaBlock* block, uint32_t inst, AirInstTag air_tag) {
+ ZirInstRef operand_ref = sema->code.inst_datas[inst].un_node.operand;
+ AirInstRef operand = resolveInst(sema, operand_ref);
+ TypeIndex operand_ty = semaTypeOf(sema, operand);
+ assert(sema->ip->items[operand_ty].tag == IP_KEY_INT_TYPE);
+ uint16_t bits = sema->ip->items[operand_ty].data.int_type.bits;
+ uint16_t result_bits = smallestUnsignedBits(bits);
+ InternPoolKey key;
+ memset(&key, 0, sizeof(key));
+ key.tag = IP_KEY_INT_TYPE;
+ key.data.int_type.bits = result_bits;
+ key.data.int_type.signedness = 0;
+ TypeIndex result_ty = ipIntern(sema->ip, key);
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.ty_op.ty_ref = AIR_REF_FROM_IP(result_ty);
+ data.ty_op.operand = operand;
+ return blockAddInst(block, air_tag, data);
+}
+
+// zirByteSwap: handle byte_swap ZIR instruction (@byteSwap).
+// Ported from src/Sema.zig zirByteSwap.
+static AirInstRef zirByteSwap(Sema* sema, SemaBlock* block, uint32_t inst) {
+ ZirInstRef operand_ref = sema->code.inst_datas[inst].un_node.operand;
+ AirInstRef operand = resolveInst(sema, operand_ref);
+ TypeIndex operand_ty = semaTypeOf(sema, operand);
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.ty_op.ty_ref = AIR_REF_FROM_IP(operand_ty);
+ data.ty_op.operand = operand;
+ return blockAddInst(block, AIR_INST_BYTE_SWAP, data);
+}
+
// zirArithmetic: handle add/sub ZIR instructions.
// Ported from src/Sema.zig zirArithmetic.
static AirInstRef zirArithmetic(
@@ -616,6 +779,55 @@ static AirInstRef zirTyOpCast(
return blockAddInst(block, air_tag, data);
}
+// floatBits: get the bit width of a float type from its IP index.
+static uint16_t floatBits(TypeIndex ty) {
+ switch (ty) {
+ case IP_INDEX_F16_TYPE:
+ return 16;
+ case IP_INDEX_F32_TYPE:
+ return 32;
+ case IP_INDEX_F64_TYPE:
+ return 64;
+ case IP_INDEX_F80_TYPE:
+ return 80;
+ case IP_INDEX_F128_TYPE:
+ return 128;
+ default:
+ assert(0 && "floatBits: not a float type");
+ return 0;
+ }
+}
+
+// zirFloatCast: handle float_cast ZIR instruction (@floatCast).
+// Ported from src/Sema.zig zirFloatCast.
+// Emits FPEXT (widening) or FPTRUNC (narrowing).
+static AirInstRef zirFloatCast(Sema* sema, SemaBlock* block, uint32_t inst) {
+ uint32_t payload_index
+ = sema->code.inst_datas[inst].pl_node.payload_index;
+ ZirInstRef dest_ty_ref = sema->code.extra[payload_index];
+ ZirInstRef operand_ref = sema->code.extra[payload_index + 1];
+ assert(dest_ty_ref < ZIR_REF_START_INDEX);
+ TypeIndex dest_ty = dest_ty_ref;
+ AirInstRef operand = resolveInst(sema, operand_ref);
+ if (!AIR_REF_IS_INST(operand)) {
+ return semaCoerce(sema, block, dest_ty, operand);
+ }
+ TypeIndex operand_ty = semaTypeOf(sema, operand);
+ uint16_t src_bits = floatBits(operand_ty);
+ uint16_t dst_bits = floatBits(dest_ty);
+ AirInstTag air_tag;
+ if (dst_bits >= src_bits) {
+ air_tag = AIR_INST_FPEXT;
+ } else {
+ air_tag = AIR_INST_FPTRUNC;
+ }
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.ty_op.ty_ref = AIR_REF_FROM_IP(dest_ty);
+ data.ty_op.operand = operand;
+ return blockAddInst(block, air_tag, data);
+}
+
// zirTypeofLog2IntType: compute the log2 integer type for shift amounts.
// Ported from src/Sema.zig zirTypeofLog2IntType.
// For an integer type with N bits, returns an unsigned integer type with
@@ -646,7 +858,7 @@ static void zirTypeofLog2IntType(Sema* sema, uint32_t inst) {
// zirAsShiftOperand: coerce a shift amount to the correct type.
// Ported from src/Sema.zig zirAsShiftOperand.
// Uses pl_node + As payload (dest_type, operand) — same layout as as_node.
-static void zirAsShiftOperand(Sema* sema, uint32_t inst) {
+static void zirAsShiftOperand(Sema* sema, SemaBlock* block, uint32_t inst) {
uint32_t payload_index
= sema->code.inst_datas[inst].pl_node.payload_index;
ZirInstRef dest_ty_ref = sema->code.extra[payload_index];
@@ -657,7 +869,7 @@ static void zirAsShiftOperand(Sema* sema, uint32_t inst) {
TypeIndex dest_ty = (TypeIndex)dest_ty_air;
AirInstRef operand = resolveInst(sema, operand_ref);
// Coerce the operand (typically a comptime int) to the shift type.
- AirInstRef result = semaCoerce(sema, NULL, dest_ty, operand);
+ AirInstRef result = semaCoerce(sema, block, dest_ty, operand);
instMapPut(&sema->inst_map, inst, result);
}
@@ -748,6 +960,8 @@ static void zirFunc(Sema* sema, SemaBlock* block, uint32_t inst) {
}
if (has_ret_ty_body) {
uint32_t rtb_len = sema->code.extra[extra_index];
+ ret_ty_body_len = rtb_len;
+ ret_ty_ref_pos = extra_index + 1;
extra_index += 1 + rtb_len;
} else if (has_ret_ty_ref) {
ret_ty_body_len = 1; // single ref
@@ -766,10 +980,8 @@ static void zirFunc(Sema* sema, SemaBlock* block, uint32_t inst) {
param_block_pi = 1; // param_block at payload_index + 1
extra_index = payload_index + 3;
- if (ret_ty_body_len == 1) {
+ if (ret_ty_body_len >= 1) {
ret_ty_ref_pos = extra_index;
- extra_index += 1;
- } else if (ret_ty_body_len > 1) {
extra_index += ret_ty_body_len;
}
}
@@ -825,6 +1037,33 @@ static void zirFunc(Sema* sema, SemaBlock* block, uint32_t inst) {
// For pre-interned refs, the ZIR ref == IP index.
assert(ret_ty_ref < ZIR_REF_START_INDEX);
sema->fn_ret_ty = ret_ty_ref;
+ } else if (ret_ty_body_len == 2) {
+ // Two-instruction return type body: [ptr_type, break_inline].
+ uint32_t ret_body_start = ret_ty_ref_pos; // set by both func and func_fancy paths
+ uint32_t first_inst = sema->code.extra[ret_body_start];
+ ZirInstTag first_tag = sema->code.inst_tags[first_inst];
+ if (first_tag == ZIR_INST_PTR_TYPE) {
+ 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];
+ assert(elem_ty_ref < ZIR_REF_START_INDEX);
+ TypeIndex elem_ty = elem_ty_ref;
+ uint32_t ip_flags = (uint32_t)zir_size & PTR_FLAGS_SIZE_MASK;
+ if (!(zir_flags & 0x02))
+ 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;
+ sema->fn_ret_ty = ipIntern(sema->ip, key);
+ } else {
+ sema->fn_ret_ty = IP_INDEX_VOID_TYPE;
+ }
} else {
// Multi-instruction return type body — not yet supported.
sema->fn_ret_ty = IP_INDEX_VOID_TYPE;
@@ -1159,7 +1398,7 @@ static bool analyzeBodyInner(
= sema->code.inst_datas[inst].un_node.operand;
AirInstRef operand = resolveInst(sema, operand_ref);
// Coerce the operand to the function return type.
- operand = coerceIntRef(sema, operand, sema->fn_ret_ty);
+ operand = semaCoerce(sema, block, sema->fn_ret_ty, operand);
AirInstData ret_data;
memset(&ret_data, 0, sizeof(ret_data));
ret_data.un_op.operand = operand;
@@ -1295,22 +1534,37 @@ static bool analyzeBodyInner(
continue;
}
- // Arithmetic: add, sub, mul.
+ // Arithmetic: add, sub, mul and wrapping variants.
case ZIR_INST_ADD:
instMapPut(&sema->inst_map, inst,
zirArithmetic(sema, block, inst, AIR_INST_ADD));
i++;
continue;
+ case ZIR_INST_ADDWRAP:
+ instMapPut(&sema->inst_map, inst,
+ zirArithmetic(sema, block, inst, AIR_INST_ADD_WRAP));
+ i++;
+ continue;
case ZIR_INST_SUB:
instMapPut(&sema->inst_map, inst,
zirArithmetic(sema, block, inst, AIR_INST_SUB));
i++;
continue;
+ case ZIR_INST_SUBWRAP:
+ instMapPut(&sema->inst_map, inst,
+ zirArithmetic(sema, block, inst, AIR_INST_SUB_WRAP));
+ i++;
+ continue;
case ZIR_INST_MUL:
instMapPut(&sema->inst_map, inst,
zirArithmetic(sema, block, inst, AIR_INST_MUL));
i++;
continue;
+ case ZIR_INST_MULWRAP:
+ instMapPut(&sema->inst_map, inst,
+ zirArithmetic(sema, block, inst, AIR_INST_MUL_WRAP));
+ i++;
+ continue;
// Comparisons: same binary pattern as arithmetic.
case ZIR_INST_CMP_LT:
@@ -1382,6 +1636,27 @@ static bool analyzeBodyInner(
i++;
continue;
+ // @intFromFloat.
+ case ZIR_INST_INT_FROM_FLOAT:
+ instMapPut(&sema->inst_map, inst,
+ zirTyOpCast(sema, block, inst, AIR_INST_INT_FROM_FLOAT));
+ i++;
+ continue;
+
+ // @floatFromInt.
+ case ZIR_INST_FLOAT_FROM_INT:
+ instMapPut(&sema->inst_map, inst,
+ zirTyOpCast(sema, block, inst, AIR_INST_FLOAT_FROM_INT));
+ i++;
+ continue;
+
+ // @floatCast.
+ case ZIR_INST_FLOAT_CAST:
+ instMapPut(&sema->inst_map, inst,
+ zirFloatCast(sema, block, inst));
+ i++;
+ continue;
+
// Shift operations.
case ZIR_INST_SHL:
instMapPut(&sema->inst_map, inst,
@@ -1402,7 +1677,7 @@ static bool analyzeBodyInner(
// Shift operand coercion.
case ZIR_INST_AS_SHIFT_OPERAND:
- zirAsShiftOperand(sema, inst);
+ zirAsShiftOperand(sema, block, inst);
i++;
continue;
@@ -1419,6 +1694,59 @@ static bool analyzeBodyInner(
i++;
continue;
+ // Load (pointer dereference).
+ case ZIR_INST_LOAD:
+ instMapPut(&sema->inst_map, inst,
+ zirLoad(sema, block, inst));
+ i++;
+ continue;
+
+ // Unary operations.
+ case ZIR_INST_NEGATE:
+ instMapPut(&sema->inst_map, inst,
+ zirNegate(sema, block, inst));
+ i++;
+ continue;
+ case ZIR_INST_BIT_NOT:
+ instMapPut(&sema->inst_map, inst,
+ zirBitNot(sema, block, inst));
+ i++;
+ continue;
+ case ZIR_INST_BOOL_NOT:
+ instMapPut(&sema->inst_map, inst,
+ zirBoolNot(sema, block, inst));
+ i++;
+ continue;
+ case ZIR_INST_NEGATE_WRAP:
+ instMapPut(&sema->inst_map, inst,
+ zirNegateWrap(sema, block, inst));
+ i++;
+ continue;
+
+ // Bit counting operations.
+ case ZIR_INST_CLZ:
+ instMapPut(&sema->inst_map, inst,
+ zirBitCount(sema, block, inst, AIR_INST_CLZ));
+ i++;
+ continue;
+ case ZIR_INST_CTZ:
+ instMapPut(&sema->inst_map, inst,
+ zirBitCount(sema, block, inst, AIR_INST_CTZ));
+ i++;
+ continue;
+ case ZIR_INST_POP_COUNT:
+ instMapPut(&sema->inst_map, inst,
+ zirBitCount(sema, block, inst, AIR_INST_POPCOUNT));
+ i++;
+ continue;
+
+ // @byteSwap.
+ case ZIR_INST_BYTE_SWAP:
+ instMapPut(&sema->inst_map, inst,
+ zirByteSwap(sema, block, inst));
+ i++;
+ continue;
+
// Validation-only (no AIR emitted).
case ZIR_INST_VALIDATE_DEREF:
case ZIR_INST_VALIDATE_CONST:
diff --git a/stage0/sema_test.zig b/stage0/sema_test.zig
@@ -398,8 +398,6 @@ fn airDataRefSlots(tag_val: u8) [2]bool {
c.AIR_INST_RET,
c.AIR_INST_RET_SAFE,
c.AIR_INST_RET_LOAD,
- c.AIR_INST_NOT,
- c.AIR_INST_LOAD,
c.AIR_INST_NEG,
c.AIR_INST_NEG_OPTIMIZED,
c.AIR_INST_IS_NULL,
@@ -438,12 +436,15 @@ fn airDataRefSlots(tag_val: u8) [2]bool {
c.AIR_INST_ADD,
c.AIR_INST_ADD_SAFE,
c.AIR_INST_ADD_OPTIMIZED,
+ c.AIR_INST_ADD_WRAP,
c.AIR_INST_SUB,
c.AIR_INST_SUB_SAFE,
c.AIR_INST_SUB_OPTIMIZED,
+ c.AIR_INST_SUB_WRAP,
c.AIR_INST_MUL,
c.AIR_INST_MUL_SAFE,
c.AIR_INST_MUL_OPTIMIZED,
+ c.AIR_INST_MUL_WRAP,
c.AIR_INST_BOOL_AND,
c.AIR_INST_BOOL_OR,
c.AIR_INST_STORE,
@@ -478,6 +479,17 @@ fn airDataRefSlots(tag_val: u8) [2]bool {
c.AIR_INST_WRAP_ERRUNION_PAYLOAD,
c.AIR_INST_WRAP_ERRUNION_ERR,
c.AIR_INST_ARRAY_TO_SLICE,
+ c.AIR_INST_LOAD,
+ c.AIR_INST_NOT,
+ c.AIR_INST_INT_FROM_FLOAT,
+ c.AIR_INST_INT_FROM_FLOAT_OPTIMIZED,
+ c.AIR_INST_INT_FROM_FLOAT_SAFE,
+ c.AIR_INST_INT_FROM_FLOAT_OPTIMIZED_SAFE,
+ c.AIR_INST_FLOAT_FROM_INT,
+ c.AIR_INST_CLZ,
+ c.AIR_INST_CTZ,
+ c.AIR_INST_POPCOUNT,
+ c.AIR_INST_BYTE_SWAP,
=> .{ true, true },
// arg: type(Ref) + zir_param_index(u32)
c.AIR_INST_ARG => .{ true, false },
@@ -806,6 +818,30 @@ test "sema air: sub comptime" {
try semaAirRawCheck("export fn f(x: u32) u32 { return x - 1; }");
}
+test "sema air: store runtime value" {
+ try semaAirRawCheck(
+ \\export fn f(p: *u32, x: u32) void {
+ \\ p.* = x;
+ \\}
+ );
+}
+
+test "sema air: load from pointer" {
+ try semaAirRawCheck(
+ \\export fn f(p: *u32) u32 {
+ \\ return p.*;
+ \\}
+ );
+}
+
+test "sema air: negate" {
+ try semaAirRawCheck("export fn f(x: i32) i32 { return -x; }");
+}
+
+test "sema air: bit not" {
+ try semaAirRawCheck("export fn f(x: u32) u32 { return ~x; }");
+}
+
test "sema air: bit shift left" {
try semaAirRawCheck("export fn f(x: u32) u32 { return x << 1; }");
}
@@ -827,3 +863,98 @@ test "sema air: two local bindings" {
\\}
);
}
+
+test "sema air: wrapping add" {
+ try semaAirRawCheck("export fn f(x: u32, y: u32) u32 { return x +% y; }");
+}
+
+test "sema air: wrapping sub" {
+ try semaAirRawCheck("export fn f(x: u32, y: u32) u32 { return x -% y; }");
+}
+
+test "sema air: wrapping mul" {
+ try semaAirRawCheck("export fn f(x: u32, y: u32) u32 { return x *% y; }");
+}
+
+// test "sema air: div" {
+// // Requires zirDiv with safety checks (div_trunc + remainder check).
+// try semaAirRawCheck("export fn f(x: u32, y: u32) u32 { return x / y; }");
+// }
+
+test "sema air: bool not" {
+ try semaAirRawCheck("export fn f(x: bool) bool { return !x; }");
+}
+
+// test "sema air: if simple" {
+// // Requires condbr, block merging, conditional branching.
+// try semaAirRawCheck(
+// \\export fn f(x: u32, y: u32) u32 {
+// \\ if (x > y) return x;
+// \\ return y;
+// \\}
+// );
+// }
+
+test "sema air: wrapping negate" {
+ try semaAirRawCheck("export fn f(x: i32) i32 { return -%x; }");
+}
+
+test "sema air: clz" {
+ try semaAirRawCheck("export fn f(x: u32) u32 { return @clz(x); }");
+}
+
+test "sema air: ctz" {
+ try semaAirRawCheck("export fn f(x: u32) u32 { return @ctz(x); }");
+}
+
+test "sema air: popcount" {
+ try semaAirRawCheck("export fn f(x: u32) u32 { return @popCount(x); }");
+}
+
+test "sema air: byteswap" {
+ try semaAirRawCheck("export fn f(x: u32) u32 { return @byteSwap(x); }");
+}
+
+test "sema air: float cast widen" {
+ try semaAirRawCheck("export fn f(x: f32) f64 { return @floatCast(x); }");
+}
+
+test "sema air: float cast narrow" {
+ try semaAirRawCheck("export fn f(x: f64) f32 { return @floatCast(x); }");
+}
+
+test "sema air: int from float" {
+ try semaAirRawCheck("export fn f(x: f32) u32 { return @intFromFloat(x); }");
+}
+
+test "sema air: float from int" {
+ try semaAirRawCheck("export fn f(x: u32) f32 { return @floatFromInt(x); }");
+}
+
+test "sema air: bitmask shift and" {
+ try semaAirRawCheck("export fn f(x: u32) u32 { return (x >> 16) & 0xFF; }");
+}
+
+test "sema air: double negate" {
+ try semaAirRawCheck("export fn f(x: i32) i32 { return -(-x); }");
+}
+
+test "sema air: return ptr type" {
+ try semaAirRawCheck("export fn f(p: *u32) *u32 { return p; }");
+}
+
+test "sema air: float cast f16 to f32" {
+ try semaAirRawCheck("export fn f(x: f16) f32 { return @floatCast(x); }");
+}
+
+test "sema air: wrapping add comptime" {
+ try semaAirRawCheck("export fn f(x: u32) u32 { return x +% 1; }");
+}
+
+test "sema air: byteswap and xor" {
+ try semaAirRawCheck(
+ \\export fn f(x: u32) u32 {
+ \\ return @byteSwap(x) ^ 0xFF;
+ \\}
+ );
+}