Zcu.saveZirCache: mark safety buffer defined for valgrind

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 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 19:51:25 +00:00
parent c7add584e5
commit 079d14b34f

View File

@@ -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 = .{