sema: add arithmetic, bitwise, @bitCast, and @as handlers

Port binary operation infrastructure from upstream Sema.zig:
- semaTypeOf: resolve AIR ref types (IP refs and inst refs)
- resolvePeerType: peer type resolution (same type, comptime_int)
- semaCoerce: type coercion (pass-through, comptime_int)
- zirArithmetic: add/sub → AIR bin_op
- zirBitwise: xor/bit_and/bit_or → AIR bin_op
- zirBitcast: @bitCast → AIR ty_op
- zirAsNode: @as → pure coercion

Fix airDataRefSlots: move BITCAST from un_op to ty_op group.

Add 9 new C-vs-Zig AIR comparison tests including a neghf2
inline equivalent that produces the exact runtime AIR ops
(bitcast f16→u16, xor 0x8000, bitcast u16→f16).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 13:11:47 +00:00
parent 21b91fb556
commit 456a50694d
2 changed files with 211 additions and 1 deletions

View File

@@ -399,6 +399,133 @@ static AirInstRef coerceIntRef(
return AIR_REF_FROM_IP(new_ip_idx);
}
// semaTypeOf: resolve the type of an AIR ref.
// Ported from src/Air.zig typeOf / typeOfIndex.
static TypeIndex semaTypeOf(Sema* sema, AirInstRef ref) {
if (AIR_REF_IS_IP(ref)) {
return ipTypeOf(sema->ip, AIR_REF_TO_IP(ref));
}
uint32_t inst_idx = AIR_REF_TO_INST(ref);
AirInstTag inst_tag = (AirInstTag)sema->air_inst_tags[inst_idx];
switch (inst_tag) {
case AIR_INST_ARG:
return AIR_REF_TO_IP(sema->air_inst_datas[inst_idx].arg.ty_ref);
case AIR_INST_ADD:
case AIR_INST_SUB:
case AIR_INST_BIT_AND:
case AIR_INST_BIT_OR:
case AIR_INST_XOR:
return semaTypeOf(sema, sema->air_inst_datas[inst_idx].bin_op.lhs);
case AIR_INST_BITCAST:
return AIR_REF_TO_IP(sema->air_inst_datas[inst_idx].ty_op.ty_ref);
default:
assert(0 && "semaTypeOf: unhandled AIR tag");
return TYPE_NONE;
}
}
// resolvePeerType: determine the common type of two AIR refs.
// Ported from src/Sema.zig resolvePeerTypes (simplified).
static TypeIndex resolvePeerType(
Sema* sema, AirInstRef lhs, AirInstRef rhs) {
TypeIndex lhs_ty = semaTypeOf(sema, lhs);
TypeIndex rhs_ty = semaTypeOf(sema, rhs);
if (lhs_ty == rhs_ty)
return lhs_ty;
if (lhs_ty == IP_INDEX_COMPTIME_INT_TYPE)
return rhs_ty;
if (rhs_ty == IP_INDEX_COMPTIME_INT_TYPE)
return lhs_ty;
assert(0 && "resolvePeerType: unhandled type combination");
return TYPE_NONE;
}
// coerce: coerce an AIR ref to a target type.
// 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);
assert(0 && "semaCoerce: unhandled type combination");
return ref;
}
// zirArithmetic: handle add/sub ZIR instructions.
// Ported from src/Sema.zig zirArithmetic.
static AirInstRef zirArithmetic(
Sema* sema, SemaBlock* block, uint32_t inst, AirInstTag air_tag) {
uint32_t payload_index
= sema->code.inst_datas[inst].pl_node.payload_index;
ZirInstRef zir_lhs = sema->code.extra[payload_index];
ZirInstRef zir_rhs = sema->code.extra[payload_index + 1];
AirInstRef lhs = resolveInst(sema, zir_lhs);
AirInstRef rhs = resolveInst(sema, zir_rhs);
TypeIndex peer_ty = resolvePeerType(sema, lhs, rhs);
lhs = semaCoerce(sema, block, peer_ty, lhs);
rhs = semaCoerce(sema, block, peer_ty, rhs);
AirInstData data;
memset(&data, 0, sizeof(data));
data.bin_op.lhs = lhs;
data.bin_op.rhs = rhs;
return blockAddInst(block, air_tag, data);
}
// zirBitwise: handle bitwise operation ZIR instructions.
// Ported from src/Sema.zig zirBitwise.
static AirInstRef zirBitwise(
Sema* sema, SemaBlock* block, uint32_t inst, AirInstTag air_tag) {
uint32_t payload_index
= sema->code.inst_datas[inst].pl_node.payload_index;
ZirInstRef zir_lhs = sema->code.extra[payload_index];
ZirInstRef zir_rhs = sema->code.extra[payload_index + 1];
AirInstRef lhs = resolveInst(sema, zir_lhs);
AirInstRef rhs = resolveInst(sema, zir_rhs);
TypeIndex peer_ty = resolvePeerType(sema, lhs, rhs);
lhs = semaCoerce(sema, block, peer_ty, lhs);
rhs = semaCoerce(sema, block, peer_ty, rhs);
AirInstData data;
memset(&data, 0, sizeof(data));
data.bin_op.lhs = lhs;
data.bin_op.rhs = rhs;
return blockAddInst(block, air_tag, data);
}
// zirBitcast: handle @bitCast ZIR instruction.
// Ported from src/Sema.zig zirBitcast.
static AirInstRef zirBitcast(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];
// Resolve destination type (must be pre-interned for now).
assert(dest_ty_ref < ZIR_REF_START_INDEX);
TypeIndex dest_ty = dest_ty_ref;
AirInstRef operand = resolveInst(sema, operand_ref);
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_INST_BITCAST, data);
}
// zirAsNode: handle @as ZIR instruction.
// Ported from src/Sema.zig zirAs / zirAsNode.
static AirInstRef zirAsNode(
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);
return semaCoerce(sema, block, dest_ty, operand);
}
// zirInt: intern a comptime integer value.
// Ported from src/Sema.zig zirInt.
static void zirInt(Sema* sema, uint32_t inst) {
@@ -967,6 +1094,49 @@ static bool analyzeBodyInner(
continue;
}
// Arithmetic: add, sub.
case ZIR_INST_ADD:
instMapPut(&sema->inst_map, inst,
zirArithmetic(sema, block, inst, AIR_INST_ADD));
i++;
continue;
case ZIR_INST_SUB:
instMapPut(&sema->inst_map, inst,
zirArithmetic(sema, block, inst, AIR_INST_SUB));
i++;
continue;
// Bitwise: xor, bit_and, bit_or.
case ZIR_INST_XOR:
instMapPut(&sema->inst_map, inst,
zirBitwise(sema, block, inst, AIR_INST_XOR));
i++;
continue;
case ZIR_INST_BIT_AND:
instMapPut(&sema->inst_map, inst,
zirBitwise(sema, block, inst, AIR_INST_BIT_AND));
i++;
continue;
case ZIR_INST_BIT_OR:
instMapPut(&sema->inst_map, inst,
zirBitwise(sema, block, inst, AIR_INST_BIT_OR));
i++;
continue;
// @bitCast.
case ZIR_INST_BITCAST:
instMapPut(&sema->inst_map, inst,
zirBitcast(sema, block, inst));
i++;
continue;
// @as.
case ZIR_INST_AS_NODE:
instMapPut(&sema->inst_map, inst,
zirAsNode(sema, block, inst));
i++;
continue;
// For all other instructions, produce a void mapping and skip.
// As handlers are implemented, they will replace this default.
default: {

View File

@@ -397,7 +397,6 @@ fn airDataRefSlots(tag_val: u8) [2]bool {
c.AIR_INST_RET_SAFE,
c.AIR_INST_RET_LOAD,
c.AIR_INST_NOT,
c.AIR_INST_BITCAST,
c.AIR_INST_LOAD,
c.AIR_INST_NEG,
c.AIR_INST_NEG_OPTIMIZED,
@@ -458,6 +457,7 @@ fn airDataRefSlots(tag_val: u8) [2]bool {
c.AIR_INST_CMP_NEQ,
=> .{ true, true },
// ty_op: type(Ref) + operand(Ref)
c.AIR_INST_BITCAST,
c.AIR_INST_INTCAST,
c.AIR_INST_INTCAST_SAFE,
c.AIR_INST_TRUNC,
@@ -613,3 +613,43 @@ test "sema air: return integer" {
test "sema air: identity function" {
try semaAirRawCheck("export fn f(x: u32) u32 { return x; }");
}
test "sema air: add two args" {
try semaAirRawCheck("export fn f(x: u32, y: u32) u32 { return x + y; }");
}
test "sema air: add comptime int" {
try semaAirRawCheck("export fn f(x: u32) u32 { return x + 1; }");
}
test "sema air: sub two args" {
try semaAirRawCheck("export fn f(x: u32, y: u32) u32 { return x - y; }");
}
test "sema air: xor two args" {
try semaAirRawCheck("export fn f(x: u32, y: u32) u32 { return x ^ y; }");
}
test "sema air: xor comptime int" {
try semaAirRawCheck("export fn f(x: u16) u16 { return x ^ 0x8000; }");
}
test "sema air: bitcast u32 to f32" {
try semaAirRawCheck("export fn f(x: u32) f32 { return @bitCast(x); }");
}
test "sema air: bitcast f32 to u32" {
try semaAirRawCheck("export fn f(x: f32) u32 { return @bitCast(x); }");
}
test "sema air: as node" {
try semaAirRawCheck("export fn f(x: u32) u32 { return @as(u32, x); }");
}
test "sema air: neghf2 inline equivalent" {
try semaAirRawCheck(
\\export fn f(a: f16) f16 {
\\ return @bitCast(@as(u16, @bitCast(a)) ^ @as(u16, 0x8000));
\\}
);
}