From fc023491111ddcf2e2a4e9a3e93b119536240b4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Tue, 24 Feb 2026 22:16:28 +0000 Subject: [PATCH] =?UTF-8?q?sema:=20fix=20addhf3=20=E2=80=94=20no=20AIR=20r?= =?UTF-8?q?ollback=20on=20ComptimeReturn,=20comptime-only=20dbg=5Farg=5Fin?= =?UTF-8?q?line=20skip,=20seen-calls=20for=20param=20type=20scanning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- stage0/corpus.zig | 5 +- stage0/sema.c | 84 ++++++++++++++++++++------ stage0/sema_tests/comptime_arg_dbg.zig | 11 ++++ 3 files changed, 78 insertions(+), 22 deletions(-) create mode 100644 stage0/sema_tests/comptime_arg_dbg.zig diff --git a/stage0/corpus.zig b/stage0/corpus.zig index 0051987986..8240ed4b13 100644 --- a/stage0/corpus.zig +++ b/stage0/corpus.zig @@ -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", }; diff --git a/stage0/sema.c b/stage0/sema.c index 9a72016750..39e2632fb1 100644 --- a/stage0/sema.c +++ b/stage0/sema.c @@ -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. diff --git a/stage0/sema_tests/comptime_arg_dbg.zig b/stage0/sema_tests/comptime_arg_dbg.zig new file mode 100644 index 0000000000..b9f9b6b603 --- /dev/null +++ b/stage0/sema_tests/comptime_arg_dbg.zig @@ -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); +}