From 079d14b34fd522584ffa1b18cfbb5eda66da74ba Mon Sep 17 00:00:00 2001 From: Motiejus Date: Sun, 22 Feb 2026 19:51:25 +0000 Subject: [PATCH] Zcu.saveZirCache: mark safety buffer defined for valgrind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In safety-checked builds, Zir.Inst.Data is a tagged union where @sizeOf(Data) > 8 due to the safety tag. saveZirCache strips these tags by reinterpreting each Data as a HackDataLayout and copying the first 8 bytes into a safety_buffer. Union variants that use fewer than 8 bytes of payload leave the remaining bytes uninitialised. The bulk copy propagates these uninitialised V-bits into safety_buffer, causing valgrind to report: Syscall param pwritev(vector[...]) points to uninitialised byte(s) when the buffer is written to the cache file. This is harmless: loadZirCache reconstructs the safety tag from the tag array, and each variant only reads its own fields — the padding is never interpreted. @memset before the copy does not help: the assignment `safety_buffer[i] = as_struct.data` copies all 8 bytes from the source union, and valgrind propagates the per-byte defined/undefined status (V-bits) from source to destination, re-tainting the padding. Use makeMemDefined after the copy loop to inform valgrind that the padding contents are intentional. This compiles to a no-op when not running under valgrind. Co-Authored-By: Claude Opus 4.6 --- src/Zcu.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Zcu.zig b/src/Zcu.zig index d26417c8f7..523c14251e 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -2932,6 +2932,12 @@ pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.S const as_struct: *const HackDataLayout = @ptrCast(data); safety_buffer[i] = as_struct.data; } + // Union variants smaller than 8 bytes leave padding uninitialised. + // The copy above propagates those V-bits into safety_buffer, so + // valgrind flags the subsequent pwritev. The padding is never + // interpreted on read-back (the tag array determines the active + // variant), so tell valgrind the buffer contents are intentional. + std.valgrind.memcheck.makeMemDefined(std.mem.sliceAsBytes(safety_buffer)); } const header: Zir.Header = .{