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>
This commit is contained in:
2026-02-20 14:31:45 +00:00
parent befd8456c3
commit f4635b5ac0
2 changed files with 470 additions and 11 deletions

View File

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

View File

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