diff --git a/stage0/sema.c b/stage0/sema.c index d77cbef765..b570633267 100644 --- 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 index 2e50af0f38..053df083fb 100644 --- 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; + \\} + ); +}