commit 41f740aa440db04721611e365a1fa2a6f08b7259 (tree)
parent 857936c55d9855962f3d30745834ae1305e52261
Author: Motiejus <motiejus@jakstys.lt>
Date: Sat, 7 Mar 2026 10:34:22 +0000
sema: add error markers for unimplemented/wrong code paths
Systematically add sema->has_compile_errors = true for paths where
the C port either silently returns wrong values or is unimplemented
for valid Zig inputs. This makes failures explicit rather than
producing mysterious wrong AIR/IP output.
Changes:
- zirRetImplicit: error if function has non-void return type
(Sema.zig lines 18700-18710)
- zirDiv: error on signed integer with plain / operator
(Sema.zig lines 15129-15135: must use @divTrunc/@divFloor/@divExact)
- zirByteSwap: error if operand is not integer type; error if
bits % 8 != 0 (Sema.zig lines 22956-22964)
- analyzeBitNot: error if operand is not int or bool type
(Sema.zig line 14261)
- zirBitReverse: error if operand is not integer type
(Sema.zig line 22980)
- zirBitCount: error if operand is not integer type
(Sema.zig line 22905)
- zirBitwise: error if peer type is not int/bool
(Sema.zig lines 14218-14222)
- zirBitcast: error for comptime-only or void dest types
(Sema.zig lines 9894-9956)
- zirNegate: error on unsigned integer type
(Sema.zig lines 14973-14978)
- zirFloatCast: error if src/dest is not a float type
(Sema.zig lines 10046-10065)
- zirRef: error for runtime ref path (not yet implemented)
(Sema.zig analyzeRef)
- zirLoad: error for comptime load from comptime-known pointer
(Sema.zig analyzeLoad lines 31443-31447)
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
| M | stage0/sema.c | | | 125 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
1 file changed, 121 insertions(+), 4 deletions(-)
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -924,6 +924,15 @@ static void zirStoreNode(Sema* sema, SemaBlock* block, uint32_t inst) {
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);
+ // Ported from Sema.zig analyzeLoad lines 31443-31447:
+ // If ptr is comptime-known, should dereference at comptime.
+ // C doesn't implement pointerDeref; mark as error to detect when needed.
+ if (AIR_REF_IS_IP(ptr) && block->is_comptime) {
+ // Comptime load from comptime-known pointer: not yet implemented.
+ // Zig calls pointerDeref(ptr_val, ptr_ty) to get the loaded value.
+ sema->has_compile_errors = true;
+ return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ }
TypeIndex ptr_ty = semaTypeOf(sema, ptr);
TypeIndex elem_ty = ptrChildType(sema->ip, ptr_ty);
AirInstData data;
@@ -1141,6 +1150,15 @@ static AirInstRef zirNegate(Sema* sema, SemaBlock* block, uint32_t inst) {
return semaAddInst(block, AIR_INST_NEG, data);
}
+ // Ported from Sema.zig zirNegate lines 14973-14978: reject unsigned int
+ // and non-numeric types. Only signed int and comptime_int are valid.
+ if (sema->ip->items[ty].tag == IP_KEY_INT_TYPE
+ && !sema->ip->items[ty].data.int_type.signedness) {
+ // "negation of unsigned integer type"
+ sema->has_compile_errors = true;
+ return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ }
+
// Int negation: splat(rhs_ty, 0) then sub(0, operand).
// Ported from src/Sema.zig zirNegate int branch.
InternPoolKey key;
@@ -1182,6 +1200,18 @@ static AirInstRef zirNegateWrap(Sema* sema, SemaBlock* block, uint32_t inst) {
// Ported from src/Sema.zig analyzeBitNot.
static AirInstRef analyzeBitNot(
Sema* sema, SemaBlock* block, AirInstRef operand) {
+ // Ported from Sema.zig zirBitNot lines 14261-14262:
+ // operand must be int or bool type; anything else is a compile error.
+ TypeIndex operand_ty = semaTypeOf(sema, operand);
+ bool valid_ty = (operand_ty == IP_INDEX_BOOL_TYPE)
+ || (operand_ty == IP_INDEX_COMPTIME_INT_TYPE)
+ || (operand_ty != IP_INDEX_NONE
+ && sema->ip->items[operand_ty].tag == IP_KEY_INT_TYPE);
+ if (!valid_ty && AIR_REF_IS_IP(operand)) {
+ // Non-int/bool type for bitwise NOT — compile error.
+ sema->has_compile_errors = true;
+ return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ }
// Comptime folding: if the operand is a comptime integer,
// compute ~value at comptime.
uint64_t val_lo, val_hi;
@@ -1212,10 +1242,9 @@ static AirInstRef analyzeBitNot(
return internComptimeInt(sema, ty, r_lo, r_hi, false);
}
}
- 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.ty_ref = AIR_REF_FROM_IP(operand_ty);
data.ty_op.operand = operand;
return semaAddInst(block, AIR_INST_NOT, data);
}
@@ -1532,6 +1561,9 @@ static AirInstRef zirBitCount(
AirInstRef operand = resolveInst(sema, operand_ref);
TypeIndex operand_ty = semaTypeOf(sema, operand);
if (sema->ip->items[operand_ty].tag != IP_KEY_INT_TYPE) {
+ // Ported from Sema.zig zirBitCount line 22905: checkIntOrVector.
+ // CLZ/CTZ/popcount requires integer type.
+ sema->has_compile_errors = true;
return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
}
uint16_t bits = sema->ip->items[operand_ty].data.int_type.bits;
@@ -1615,6 +1647,13 @@ static AirInstRef zirByteSwap(Sema* sema, SemaBlock* block, uint32_t inst) {
AirInstRef operand = resolveInst(sema, operand_ref);
TypeIndex operand_ty = semaTypeOf(sema, operand);
+ // Ported from Sema.zig zirByteSwap line 22956: checkIntOrVector.
+ // @byteSwap requires integer type.
+ if (sema->ip->items[operand_ty].tag != IP_KEY_INT_TYPE) {
+ sema->has_compile_errors = true;
+ return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ }
+
// Comptime folding: if operand is a comptime-known integer, compute
// the byte-swapped value at comptime.
// Ported from src/Sema.zig zirByteSwap: arith.byteSwap() call.
@@ -1624,6 +1663,12 @@ static AirInstRef zirByteSwap(Sema* sema, SemaBlock* block, uint32_t inst) {
uint16_t bits = 0;
if (sema->ip->items[operand_ty].tag == IP_KEY_INT_TYPE)
bits = sema->ip->items[operand_ty].data.int_type.bits;
+ // Ported from Sema.zig zirByteSwap lines 22958-22964:
+ // bits must be divisible by 8, else compile error.
+ if (bits > 0 && bits % 8 != 0) {
+ sema->has_compile_errors = true;
+ return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ }
if (bits > 0 && bits % 8 == 0) {
// Byte-swap: reverse the bytes.
uint64_t r_lo = 0, r_hi = 0;
@@ -1666,6 +1711,16 @@ static AirInstRef zirBitReverse(Sema* sema, SemaBlock* block, uint32_t inst) {
AirInstRef operand = resolveInst(sema, operand_ref);
TypeIndex operand_ty = semaTypeOf(sema, operand);
+ // Ported from Sema.zig zirBitReverse line 22980: checkIntOrVector.
+ // @bitReverse requires integer type; error on non-integer.
+ bool valid_ty = (operand_ty == IP_INDEX_COMPTIME_INT_TYPE)
+ || (operand_ty != IP_INDEX_NONE
+ && sema->ip->items[operand_ty].tag == IP_KEY_INT_TYPE);
+ if (!valid_ty) {
+ sema->has_compile_errors = true;
+ return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ }
+
// Comptime folding for integer operands.
// Ported from Sema.zig zirBitReverse → arith.bitReverse.
uint64_t val_lo, val_hi;
@@ -1985,6 +2040,14 @@ static AirInstRef zirDiv(Sema* sema, SemaBlock* block, uint32_t inst) {
AirInstTag air_tag = AIR_INST_DIV_FLOAT; // default for floats
InternPoolKey pk = ipIndexToKey(sema->ip, peer_ty);
if (pk.tag == IP_KEY_INT_TYPE) {
+ // Ported from Sema.zig zirDiv lines 15129-15135:
+ // Signed integers must use @divTrunc, @divFloor, or @divExact.
+ if (pk.data.int_type.signedness) {
+ // "division with signed integers: must use @divTrunc,
+ // @divFloor, or @divExact"
+ sema->has_compile_errors = true;
+ return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ }
air_tag = AIR_INST_DIV_TRUNC;
}
@@ -2178,6 +2241,19 @@ static AirInstRef zirBitwise(
lhs = semaCoerce(sema, block, peer_ty, lhs);
rhs = semaCoerce(sema, block, peer_ty, rhs);
+ // Ported from Sema.zig zirBitwise lines 14218-14222:
+ // Only int/comptime_int/bool types are valid for bitwise ops.
+ {
+ bool valid = (peer_ty == IP_INDEX_BOOL_TYPE)
+ || (peer_ty == IP_INDEX_COMPTIME_INT_TYPE)
+ || (sema->ip->items[peer_ty].tag == IP_KEY_INT_TYPE);
+ if (!valid) {
+ // "invalid operands to binary bitwise expression"
+ sema->has_compile_errors = true;
+ return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ }
+ }
+
// Comptime folding: if both coerced operands are comptime integers,
// compute the result at comptime (128-bit).
uint64_t lhs_lo, lhs_hi, rhs_lo, rhs_hi;
@@ -2229,6 +2305,18 @@ static AirInstRef zirBitcast(Sema* sema, SemaBlock* block, uint32_t inst) {
dest_ty = AIR_REF_TO_IP(resolved);
}
AirInstRef operand = resolveInst(sema, operand_ref);
+
+ // Ported from Sema.zig zirBitcast lines 9894-9956: validate dest type.
+ // @bitCast is invalid for comptime-only types and certain special types.
+ if (dest_ty == IP_INDEX_COMPTIME_INT_TYPE
+ || dest_ty == IP_INDEX_COMPTIME_FLOAT_TYPE
+ || dest_ty == IP_INDEX_TYPE_TYPE || dest_ty == IP_INDEX_VOID_TYPE
+ || dest_ty == IP_INDEX_NORETURN_TYPE) {
+ // "@bitCast to/from comptime-only or void/noreturn type"
+ sema->has_compile_errors = true;
+ return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ }
+
// Comptime folding: if operand is comptime, bitcast at comptime.
// The bit pattern is preserved; just re-intern with the dest type.
uint64_t lo, hi;
@@ -2320,6 +2408,20 @@ static AirInstRef zirFloatCast(Sema* sema, SemaBlock* block, uint32_t inst) {
TypeIndex operand_ty = semaTypeOf(sema, operand);
uint16_t src_bits = floatBits(operand_ty);
uint16_t dst_bits = floatBits(dest_ty);
+
+ // Ported from Sema.zig zirFloatCast lines 10046-10055:
+ // dest must be a concrete float type; operand must be float.
+ if (dst_bits == 0 && dest_ty != IP_INDEX_COMPTIME_FLOAT_TYPE) {
+ // "@floatCast destination is not a float type"
+ sema->has_compile_errors = true;
+ return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ }
+ if (src_bits == 0 && operand_ty != IP_INDEX_COMPTIME_FLOAT_TYPE) {
+ // "@floatCast source is not a float type"
+ sema->has_compile_errors = true;
+ return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ }
+
AirInstTag air_tag;
if (dst_bits >= src_bits) {
air_tag = AIR_INST_FPEXT;
@@ -10238,8 +10340,18 @@ static uint8_t analyzeBodyRuntimeBreak(
// --- zirRetImplicit ---
// Ported from src/Sema.zig zirRetImplicit / analyzeRet.
-static void zirRetImplicit(const Sema* sema, SemaBlock* block, uint32_t inst) {
+static void zirRetImplicit(Sema* sema, SemaBlock* block, uint32_t inst) {
(void)inst;
+ // Ported from Sema.zig zirRetImplicit: validate return type is void.
+ // Zig lines 18690-18710: noreturn or non-void return type is an error.
+ if (sema->fn_ret_ty != TYPE_NONE
+ && sema->fn_ret_ty != IP_INDEX_VOID_TYPE) {
+ // Function has non-void return type but uses implicit return.
+ // This is a compile error in Zig:
+ // "function with non-void return type implicitly returns"
+ sema->has_compile_errors = true;
+ return;
+ }
AirInstRef operand = AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
if (block->inlining) {
SemaBlockInlining* inl = block->inlining;
@@ -10843,8 +10955,13 @@ static void zirBlockComptime(Sema* sema, SemaBlock* block, uint32_t inst) {
static AirInstRef zirRef(Sema* sema, uint32_t inst) {
ZirInstRef operand_ref = sema->code.inst_datas[inst].un_tok.operand;
AirInstRef operand = resolveInst(sema, operand_ref);
- if (!AIR_REF_IS_IP(operand))
+ if (!AIR_REF_IS_IP(operand)) {
+ // Ported from Sema.zig analyzeRef: runtime ref path not implemented.
+ // Upstream creates alloc+store+ref for runtime values; C is
+ // comptime-only.
+ sema->has_compile_errors = true;
return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ }
InternPoolIndex val = AIR_REF_TO_IP(operand);
InternPoolIndex val_ty = ipTypeOf(sema->ip, val);