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:
170
stage0/sema.c
170
stage0/sema.c
@@ -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: {
|
||||
|
||||
@@ -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));
|
||||
\\}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user