sema: fix addhf3 — no AIR rollback on ComptimeReturn, comptime-only dbg_arg_inline skip, seen-calls for param type scanning

Three fixes to match upstream Sema.zig behavior for addhf3:

1. ComptimeReturn: don't rollback air_inst_len at all (upstream keeps
   all body instructions as dead instructions in the AIR array).
   This preserves nested dead blocks from comptime inline calls.

2. dbg_arg_inline: skip emission when the declared param type is
   comptime-only (comptime_int, comptime_float, enum_literal).
   Ported from addDbgVar's val_ty.comptimeOnlySema() check.
   The C sema doesn't coerce comptime IP values to the param type,
   so we check the ZIR param type body directly.

3. Param type body scanning: always register calls in the global
   seen_calls set (even when the dead block is skipped due to
   type_fn_created).  This ensures that after type_fn_created is
   reset by analyzeFuncBodyAndRecord, subsequent calls still dedup.

Enables num_passing = 9 (addhf3) and adds comptime_arg_dbg.zig unit test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Motiejus Jakštys
2026-02-24 22:16:28 +00:00
parent ed76303a44
commit fc02349111
3 changed files with 78 additions and 22 deletions

View File

@@ -3,7 +3,7 @@
/// `num_passing` controls how many files are tested and pre-generated.
/// Both build.zig and stages_test.zig import this file.
/// To enable more tests: just increment `num_passing`.
pub const num_passing: usize = 8;
pub const num_passing: usize = 9;
pub const files = [_][]const u8{
"lib/std/crypto/codecs.zig", // 165
@@ -203,7 +203,7 @@ pub const files = [_][]const u8{
"lib/std/math/expo2.zig", // 995
};
pub const num_sema_passing: usize = 94;
pub const num_sema_passing: usize = 95;
pub const sema_unit_tests = [_][]const u8{
"stage0/sema_tests/empty.zig",
@@ -300,5 +300,6 @@ pub const sema_unit_tests = [_][]const u8{
"stage0/sema_tests/inline_comptime_fn_call.zig",
"stage0/sema_tests/cross_fn_memoized_call.zig",
"stage0/sema_tests/nested_inline_dead_blocks.zig",
"stage0/sema_tests/comptime_arg_dbg.zig",
};

View File

@@ -3925,6 +3925,27 @@ static AirInstRef zirCall(
sema->type_fn_to_skip[sema->num_type_fn_to_skip++]
= callee_name;
}
// Always register in global seen-calls, even when
// skipped (type_fn_created match). This ensures
// that subsequent calls after type_fn_created is
// reset (by analyzeFuncBodyAndRecord) still dedup.
if (scan_name_idx != 0 && sema->num_seen_calls < 16) {
bool in_seen = false;
for (uint32_t k = 0; k < sema->num_seen_calls; k++) {
if (sema->seen_call_names[k] == scan_name_idx
&& sema->seen_call_nargs[k] == scan_nargs) {
in_seen = true;
break;
}
}
if (!in_seen) {
sema->seen_call_names[sema->num_seen_calls]
= scan_name_idx;
sema->seen_call_nargs[sema->num_seen_calls]
= scan_nargs;
sema->num_seen_calls++;
}
}
break; // only handle first call in type body
}
}
@@ -4724,22 +4745,24 @@ static AirInstRef zirCall(
}
instMapPut(&sema->inst_map, param_body[p], arg_refs[param_idx]);
// Emit dbg_arg_inline for params whose type is not
// comptime-only. Upstream skips params whose resolved
// type is comptime-only (type, comptime_int, etc.), not
// all comptime-declared params. E.g. `comptime bits: u16`
// still gets dbg_arg_inline because u16 is a runtime type.
// Detect comptime-only by checking if the arg IS a type
// value (i.e. the param's type is `type`).
// Emit dbg_arg_inline for params whose resolved type is
// not comptime-only. Ported from addDbgVar:
// if (try val_ty.comptimeOnlySema(pt)) return;
// Check (a): arg value IS a type (key in TYPE range).
// Check (b): for comptime params, the DECLARED param type
// is comptime-only (comptime_int, comptime_float, etc.).
// Upstream coerces args to param types before typeOf;
// the C sema doesn't, so we check the declared type.
{
bool arg_is_type = false;
bool arg_comptime_only = false;
if (AIR_REF_IS_IP(arg_refs[param_idx])) {
InternPoolIndex ip = AIR_REF_TO_IP(arg_refs[param_idx]);
InternPoolKey key = sema->ip->items[ip];
arg_is_type = (key.tag >= IP_KEY_INT_TYPE
// (a) arg is a type value.
arg_comptime_only = (key.tag >= IP_KEY_INT_TYPE
&& key.tag <= IP_KEY_INFERRED_ERROR_SET_TYPE);
if (!arg_is_type) {
arg_is_type = (ip == IP_INDEX_VOID_TYPE
if (!arg_comptime_only) {
arg_comptime_only = (ip == IP_INDEX_VOID_TYPE
|| ip == IP_INDEX_BOOL_TYPE
|| ip == IP_INDEX_TYPE_TYPE
|| ip == IP_INDEX_COMPTIME_INT_TYPE
@@ -4751,7 +4774,31 @@ static AirInstRef zirCall(
|| ip == IP_INDEX_F128_TYPE);
}
}
if (!child_block.is_comptime && !arg_is_type) {
// (b) declared param type is comptime-only.
if (!arg_comptime_only
&& (ptag == ZIR_INST_PARAM_COMPTIME
|| ptag == ZIR_INST_PARAM_ANYTYPE_COMPTIME)) {
uint32_t ppl = sema->code.inst_datas[param_body[p]]
.pl_tok.payload_index;
uint32_t type_raw = sema->code.extra[ppl + 1];
uint32_t tbody_len = type_raw & 0x7FFFFFFF;
if (tbody_len == 1) {
uint32_t ti = sema->code.extra[ppl + 2];
if (ti < sema->code.inst_len
&& sema->code.inst_tags[ti]
== ZIR_INST_BREAK_INLINE) {
ZirInstRef tref
= sema->code.inst_datas[ti].break_data.operand;
if (tref < ZIR_REF_START_INDEX) {
arg_comptime_only
= (tref == IP_INDEX_COMPTIME_INT_TYPE
|| tref == IP_INDEX_COMPTIME_FLOAT_TYPE
|| tref == IP_INDEX_ENUM_LITERAL_TYPE);
}
}
}
}
if (!child_block.is_comptime && !arg_comptime_only) {
uint32_t param_name_idx;
if (ptag == ZIR_INST_PARAM_ANYTYPE) {
// str_tok: name is at str_tok.start.
@@ -4833,16 +4880,13 @@ static AirInstRef zirCall(
// Upstream Sema.zig:7872: error.ComptimeReturn => break :result
// inlining.comptime_result
// Upstream does NOT roll back the AIR — the block instruction and
// a dead block in the AIR. Body instructions added to the child
// block are not referenced by any block, so they are dead too.
// Roll back to block_inst_idx+1 to keep the dead block but
// discard body instructions added to the global AIR array.
// TODO: upstream keeps ALL body instructions (no rollback);
// matching that requires also fixing dbg_arg_inline emission
// for comptime-known args within comptime inline contexts.
// Upstream Sema.zig:7872: error.ComptimeReturn => break :result
// inlining.comptime_result
// Upstream does NOT roll back the AIR — the block instruction and
// all body instructions (including nested dead blocks from
// comptime inline calls) remain in the AIR array.
if (inlining.comptime_returned) {
AirInstRef ct_result = inlining.comptime_result;
sema->air_inst_len = block_inst_idx + 1;
block->instructions_len = saved_block_inst_len;
// Cache comptime results for memoization.

View File

@@ -0,0 +1,11 @@
/// Test: inline function where all args are comptime-known.
/// Upstream Zig does NOT emit dbg_arg_inline when the arg operand
/// has a comptime-only type (e.g. comptime_int from a block_comptime
/// sub-expression). But it DOES emit when the arg is coerced to a
/// runtime type (e.g. u16) in the arg body.
inline fn helper(comptime T: type, a: T, b: T) T {
return a +% b;
}
export fn f(a: u16, b: u16) u16 {
return helper(u16, a, b);
}