authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-24 16:20:39-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-26 19:58:56-08:00
logd7bf0dab8243d23e95c49ad9b27f54d21df116f0
treed75635fd5d0982da152aab3d4028a48036279714
parent3ad9a6a6045aec32b590387eddbc8ba3d598bcfc

link.Wasm: fix incremental update crash

Description of problem: - wasm linker does GC in flush() - it has the mechanism where it tracks the end index of a bunch of ArrayHashMap before flush() and after flush, shrinkRetainingCapacity() them to restore them to pre-flush() state - this includes `functions`, which contains `__divti3` - flush() notices the call to `__divti3` and calls markFunctionImport(), but that function does nothing on a second update because `alive` is already set to `true` so it incorrectly skips adding the intrinsic back to `functions` I tried to remember why I thought it was OK to use this `alive` flag which is state that's not being restored after flush(). If I remember correctly, I was just leaving the code how it was before, with the plan to change the data layout after encountering this exact problem. However, I found a solution that doesn't require changing data layout, and still takes advantage of the 1-bit-per-symbol data layout.

2 files changed, 27 insertions(+), 12 deletions(-)

src/link/Wasm.zig+20-8
......@@ -429,7 +429,11 @@ pub const OutputFunctionIndex = enum(u32) {
429429
430430 pub fn fromSymbolName(wasm: *const Wasm, name: String) OutputFunctionIndex {
431431 if (wasm.flush_buffer.function_imports.getIndex(name)) |i| return @enumFromInt(i);
432 return fromFunctionIndex(wasm, FunctionIndex.fromSymbolName(wasm, name).?);
432 return fromFunctionIndex(wasm, FunctionIndex.fromSymbolName(wasm, name) orelse {
433 if (std.debug.runtime_safety) {
434 std.debug.panic("function index for symbol not found: {s}", .{name.slice(wasm)});
435 } else unreachable;
436 });
433437 }
434438};
435439
......@@ -3534,7 +3538,10 @@ pub fn markFunctionImport(
35343538 import: *FunctionImport,
35353539 func_index: FunctionImport.Index,
35363540) link.File.FlushError!void {
3537 if (import.flags.alive) return;
3541 // import.flags.alive might be already true from a previous update. In such
3542 // case, we must still run the logic in this function, in case the item
3543 // being marked was reverted by the `flush` logic that resets the hash
3544 // table watermarks.
35383545 import.flags.alive = true;
35393546
35403547 const comp = wasm.base.comp;
......@@ -3554,8 +3561,9 @@ pub fn markFunctionImport(
35543561 } else {
35553562 try wasm.function_imports.put(gpa, name, .fromObject(func_index, wasm));
35563563 }
3557 } else {
3558 try markFunction(wasm, import.resolution.unpack(wasm).object_function, import.flags.exported);
3564 } else switch (import.resolution.unpack(wasm)) {
3565 .object_function => try markFunction(wasm, import.resolution.unpack(wasm).object_function, import.flags.exported),
3566 else => return,
35593567 }
35603568}
35613569
......@@ -3594,7 +3602,10 @@ fn markGlobalImport(
35943602 import: *GlobalImport,
35953603 global_index: GlobalImport.Index,
35963604) link.File.FlushError!void {
3597 if (import.flags.alive) return;
3605 // import.flags.alive might be already true from a previous update. In such
3606 // case, we must still run the logic in this function, in case the item
3607 // being marked was reverted by the `flush` logic that resets the hash
3608 // table watermarks.
35983609 import.flags.alive = true;
35993610
36003611 const comp = wasm.base.comp;
......@@ -3624,8 +3635,9 @@ fn markGlobalImport(
36243635 } else {
36253636 try wasm.global_imports.put(gpa, name, .fromObject(global_index, wasm));
36263637 }
3627 } else {
3628 try markGlobal(wasm, import.resolution.unpack(wasm).object_global, import.flags.exported);
3638 } else switch (import.resolution.unpack(wasm)) {
3639 .object_global => try markGlobal(wasm, import.resolution.unpack(wasm).object_global, import.flags.exported),
3640 else => return,
36293641 }
36303642}
36313643
......@@ -4043,7 +4055,7 @@ pub fn tagNameSymbolIndex(wasm: *Wasm, ip_index: InternPool.Index) Allocator.Err
40434055 const comp = wasm.base.comp;
40444056 assert(comp.config.output_mode == .Obj);
40454057 const gpa = comp.gpa;
4046 const name = try wasm.internStringFmt("__zig_tag_name_{d}", .{@intFromEnum(ip_index)});
4058 const name = try wasm.internStringFmt("__zig_tag_name_{d}", .{ip_index});
40474059 const gop = try wasm.symbol_table.getOrPut(gpa, name);
40484060 gop.value_ptr.* = {};
40494061 return @enumFromInt(gop.index);
src/link/Wasm/Flush.zig+7-4
......@@ -128,17 +128,20 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
128128 if (comp.zcu) |zcu| {
129129 const ip: *const InternPool = &zcu.intern_pool; // No mutations allowed!
130130
131 log.debug("total MIR instructions: {d}", .{wasm.mir_instructions.len});
132
131133 // Detect any intrinsics that were called; they need to have dependencies on the symbols marked.
132134 // Likewise detect `@tagName` calls so those functions can be included in the output and synthesized.
133135 for (wasm.mir_instructions.items(.tag), wasm.mir_instructions.items(.data)) |tag, *data| switch (tag) {
134136 .call_intrinsic => {
135137 const symbol_name = try wasm.internString(@tagName(data.intrinsic));
136138 const i: Wasm.FunctionImport.Index = @enumFromInt(wasm.object_function_imports.getIndex(symbol_name) orelse {
137 return diags.fail("missing compiler runtime intrinsic '{s}' (undefined linker symbol)", .{
138 @tagName(data.intrinsic),
139 return diags.fail("missing compiler runtime intrinsic '{t}' (undefined linker symbol)", .{
140 data.intrinsic,
139141 });
140142 });
141143 try wasm.markFunctionImport(symbol_name, i.value(wasm), i);
144 log.debug("markFunctionImport intrinsic {d}={t}", .{ i, data.intrinsic });
142145 },
143146 .call_tag_name => {
144147 assert(ip.indexToKey(data.ip_index) == .enum_type);
......@@ -147,11 +150,10 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
147150 wasm.tag_name_table_ref_count += 1;
148151 const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).intTagType(zcu);
149152 gop.value_ptr.* = .{ .tag_name = .{
150 .symbol_name = try wasm.internStringFmt("__zig_tag_name_{d}", .{@intFromEnum(data.ip_index)}),
153 .symbol_name = try wasm.internStringFmt("__zig_tag_name_{d}", .{data.ip_index}),
151154 .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .slice_const_u8_sentinel_0, target),
152155 .table_index = @intCast(wasm.tag_name_offs.items.len),
153156 } };
154 try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {});
155157 const tag_names = ip.loadEnumType(data.ip_index).names;
156158 for (tag_names.get(ip)) |tag_name| {
157159 const slice = tag_name.toSlice(ip);
......@@ -159,6 +161,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
159161 try wasm.tag_name_bytes.appendSlice(gpa, slice[0 .. slice.len + 1]);
160162 }
161163 }
164 try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {});
162165 },
163166 else => continue,
164167 };