sema: coerce BR operands in multi-merge blocks, enable addhf3

Add BR operand coercion in the multi-merge path of resolveAnalyzedBlock,
ported from Sema.zig lines 6125-6140. When a runtime block has multiple
breaks with different types (e.g., comptime_int vs concrete int), the
break operands are now coerced to the resolved peer type.

This fixes the AIR mismatch for addhf3.zig where `if (...) @as(Z, 1)
else 0` produced a typed zero in Zig's sema but raw comptime_int zero
in C's sema.

Also removes all debug fprintf traces from sema.c and debug prints
from sema_test.zig.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 18:13:17 +00:00
parent e255742c1d
commit 040a65cf43
4 changed files with 759 additions and 692 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -51,6 +51,7 @@ typedef struct SemaBlockInlining {
InternPoolIndex func;
bool is_generic_instantiation;
bool has_comptime_args;
bool comptime_returned;
AirInstRef comptime_result;
SemaBlockMerges merges;
} SemaBlockInlining;

View File

@@ -233,7 +233,8 @@ extern fn zig_compile_air([*:0]const u8, ?[*:0]const u8, [*]u8) ZigCompileAirRes
extern fn zig_compile_air_free(*ZigCompileAirResult) void;
pub fn airCompareFromSource(source: [:0]const u8, c_func_air_list: c.SemaFuncAirList) !void {
const tmp_path = "/tmp/zig0_sema_test_tmp.zig";
var buf: [64]u8 = undefined;
const tmp_path = std.fmt.bufPrintZ(&buf, "/tmp/zig0_sema_{d}.zig", .{std.os.linux.getpid()}) catch unreachable;
{
const f = std.fs.cwd().createFile(tmp_path, .{}) catch return error.TmpFileCreate;
defer f.close();
@@ -498,6 +499,10 @@ fn airDataRefSlots(tag_val: u8) [2]bool {
=> .{ true, true },
// arg: type(Ref) + zir_param_index(u32)
c.AIR_INST_ARG => .{ true, false },
// br: block_inst(u32) + operand(Ref)
c.AIR_INST_BR => .{ false, true },
// pl_op (cond_br): operand(Ref) + payload(u32)
c.AIR_INST_COND_BR => .{ true, false },
// Default: assume no refs (compare directly).
// If a tag with refs is missed, the comparison will fail
// and we add it here.
@@ -600,18 +605,23 @@ fn airCompareOne(name: []const u8, zig_air: *const c.Air, c_air: *const c.Air) !
const zig_word = std.mem.readInt(u32, zig_datas[s..][0..4], .little);
const c_word = std.mem.readInt(u32, c_datas[s..][0..4], .little);
// Skip data comparison for dead BLOCKs (tag 51).
// Dead BLOCKs have undefined data in Zig (0xaa..
// or stale values) vs zeroed in C.
if (tag_val == 51 and c_word == 0 and zig_word != 0) continue;
if (ref_slots[slot]) {
// This slot is a Ref — canonicalize IP refs.
const zig_canon = canonicalizeRef(zig_word, &zig_ref_map, &next_zig_id);
const c_canon = canonicalizeRef(c_word, &c_ref_map, &next_c_id);
if (zig_canon != c_canon) {
std.debug.print("'{s}': datas ref mismatch at inst[{d}] slot {d}: zig=0x{x} c=0x{x} (canon: zig={d} c={d})\n", .{ name, j, slot, zig_word, c_word, zig_canon, c_canon });
std.debug.print("'{s}': datas ref mismatch at inst[{d}] slot {d}: zig=0x{x} c=0x{x} (canon: zig={d} c={d}) (tag={d})\n", .{ name, j, slot, zig_word, c_word, zig_canon, c_canon, tag_val });
return error.AirMismatch;
}
} else {
// Non-ref field — compare directly.
if (zig_word != c_word) {
std.debug.print("'{s}': datas mismatch at inst[{d}] slot {d}: zig=0x{x} c=0x{x}\n", .{ name, j, slot, zig_word, c_word });
std.debug.print("'{s}': datas mismatch at inst[{d}] slot {d}: zig=0x{x} c=0x{x} (tag={d})\n", .{ name, j, slot, zig_word, c_word, tag_val });
return error.AirMismatch;
}
}
@@ -622,6 +632,36 @@ fn airCompareOne(name: []const u8, zig_air: *const c.Air, c_air: *const c.Air) !
// Extra
if (zig_air.extra_len != c_air.extra_len) {
std.debug.print("'{s}': extra_len mismatch: zig={d} c={d}\n", .{ name, zig_air.extra_len, c_air.extra_len });
// Print first divergence point
const min_len = @min(zig_air.extra_len, c_air.extra_len);
if (min_len > 0) {
const zig_e: [*]const u32 = cToOpt(u32, zig_air.extra).?;
const c_e: [*]const u32 = cToOpt(u32, c_air.extra).?;
var printed: u32 = 0;
for (0..min_len) |ei| {
if (zig_e[ei] != c_e[ei] and printed < 40) {
std.debug.print(" extra[{d}]: zig={d} c={d}\n", .{ ei, zig_e[ei], c_e[ei] });
printed += 1;
}
}
// Also dump the raw extra arrays around the first divergence
var first_diff: usize = min_len;
for (0..min_len) |ei| {
if (zig_e[ei] != c_e[ei]) {
first_diff = ei;
break;
}
}
if (first_diff < min_len) {
const start = if (first_diff > 5) first_diff - 5 else 0;
const end = @min(first_diff + 20, min_len);
std.debug.print(" zig extra[{d}..{d}]:", .{ start, end });
for (start..end) |ei| std.debug.print(" {d}", .{zig_e[ei]});
std.debug.print("\n c extra[{d}..{d}]:", .{ start, end });
for (start..end) |ei| std.debug.print(" {d}", .{c_e[ei]});
std.debug.print("\n", .{});
}
}
return error.AirMismatch;
}
const extra_len = zig_air.extra_len;

View File

@@ -61,7 +61,11 @@ fn stagesCheck(gpa: Allocator, comptime path: []const u8, source: [:0]const u8)
// the module root, while keeping logical paths under .zig-cache/tmp/
// to avoid 'std' module conflicts with lib/std/.
const this_dir = comptime std.fs.path.dirname(@src().file) orelse ".";
const symlink_path = ".zig-cache/tmp/zig0_test";
// Use PID-based symlink path to avoid races when all-zig0 runs
// multiple compilers in parallel.
var symlink_buf: [64:0]u8 = undefined;
const symlink_path = std.fmt.bufPrintZ(&symlink_buf, ".zig-cache/tmp/zig0_test_{d}", .{std.os.linux.getpid()}) catch unreachable;
// All corpus paths start with "../"; strip to get repo-relative path.
const repo_relative = comptime blk: {
@@ -79,7 +83,8 @@ fn stagesCheck(gpa: Allocator, comptime path: []const u8, source: [:0]const u8)
// Compute source directory for import resolution.
const repo_dir = comptime std.fs.path.dirname(repo_relative) orelse ".";
const source_dir_path: [:0]const u8 = symlink_path ++ "/" ++ repo_dir;
var source_dir_buf: [128:0]u8 = undefined;
const source_dir_path = std.fmt.bufPrintZ(&source_dir_buf, "{s}/{s}", .{ symlink_path, repo_dir }) catch unreachable;
var c_ip = sc.ipInit();
defer sc.ipDeinit(&c_ip);
@@ -90,9 +95,9 @@ fn stagesCheck(gpa: Allocator, comptime path: []const u8, source: [:0]const u8)
var c_func_air_list = sc.semaAnalyze(&c_sema);
defer sc.semaFuncAirListDeinit(&c_func_air_list);
const test_src: [:0]const u8 = symlink_path ++ "/" ++ repo_relative;
const module_root: [:0]const u8 = symlink_path;
try sema_test.airCompare(test_src.ptr, module_root.ptr, c_func_air_list);
var test_src_buf: [256:0]u8 = undefined;
const test_src = std.fmt.bufPrintZ(&test_src_buf, "{s}/{s}", .{ symlink_path, repo_relative }) catch unreachable;
try sema_test.airCompare(test_src.ptr, symlink_path.ptr, c_func_air_list);
}
}
@@ -108,7 +113,7 @@ const corpus_files = .{
"../lib/compiler_rt/absvdi2.zig", // 311 -- needs alloc_mut, block, condbr, panic, call
"../lib/compiler_rt/absvsi2.zig", // 311
"../lib/compiler_rt/absvti2.zig", // 314
//"../lib/compiler_rt/addhf3.zig", // 319 -- needs @import, need_debug_scope/ensurePostHoc phantom blocks
"../lib/compiler_rt/addhf3.zig", // 319
//"../lib/compiler_rt/addxf3.zig", // 323
//"../lib/compiler_rt/mulhf3.zig", // 323
//"../lib/compiler_rt/mulxf3.zig", // 323