Skip to content

Instantly share code, notes, and snippets.

@gregcotten
Created September 10, 2026 18:38
Show Gist options
  • Select an option

  • Save gregcotten/8a7b98e6553089c5b3dda037b6d0cb14 to your computer and use it in GitHub Desktop.

Select an option

Save gregcotten/8a7b98e6553089c5b3dda037b6d0cb14 to your computer and use it in GitHub Desktop.
Halide generator output replacement and COFF archive error reproducer

Windows generator output-error reproducer

This CPU-only example computes a one-dimensional integer buffer plus a constant. It needs CMake 3.28+, Visual Studio 2022, Python, and a Halide distribution with serialization enabled. Tested with halide-bin==22.0.0.dev390 (shared Windows wheel).

From this directory in PowerShell, set $halideDir to your Halide CMake package directory and build in a new temporary directory:

$build = Join-Path $env:TEMP ('halide-repro-build-' + [guid]::NewGuid().ToString('N'))
# Example value: C:/path/to/halide/lib/cmake/Halide
cmake -S . -B $build -G 'Visual Studio 17 2022' -A x64 "-DHalide_DIR=$halideDir"
cmake --build $build --config Release --target cache_add_generator
python .\repro.py "$build\Release\cache_add_generator.exe"

Ensure the selected distribution's Halide.dll is beside the generator or discoverable through its DLL search path. The investigation copied the SHA-verified wheel's DLL beside the executable.

The driver creates a new temporary output/cache/log root and prints its path. It varies only offset=1 versus offset=2, with target=x86-64-windows-no_runtime, and holds output files using CreateFileW(GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING). Every request is a new process; every handle is closed in finally.

On the affected wheel, it prints:

FALSE RESTORE HIT: True
FALSE RESTORE HIT: True
COLD CACHE POISONED: True
UNCACHED WRITE FALSE SUCCESS: True

The two restore checks cover object and static-library outputs. results.json and the per-invocation .log files contain structured diagnostics: commands with <generator> and <output> placeholders, full SHA-256 hashes, exit status and recognized cache messages. Raw native debug output is not saved. This is a diagnostic reproducer, so its own exit code is not a pass/fail verdict for a patched implementation. Inspect the four Boolean findings and detailed child statuses.

observed-results.md records the successful reproduction on Windows. All generated directories are disposable; the script does not delete existing output or cache directories.

The public archive contains only this README, the CMake file, the small generator, the Python driver and the observed-results table. Generated binaries, headers, caches and build directories are local verification artifacts and are not included.

#include "Halide.h"
class CacheAdd : public Halide::Generator<CacheAdd> {
public:
Halide::GeneratorParam<int> offset{"offset", 1};
Input<Halide::Buffer<int32_t, 1>> input{"input"};
Output<Halide::Buffer<int32_t, 1>> output{"output"};
void generate() {
Halide::Var x{"x"};
output(x) = input(x) + offset;
}
};
HALIDE_REGISTER_GENERATOR(CacheAdd, cache_add)
cmake_minimum_required(VERSION 3.28)
project(halide_output_repro LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Halide CONFIG REQUIRED COMPONENTS JIT)
add_halide_generator(cache_add_generator SOURCES cache_add.cpp)

Observed Windows results

Tested 2026-09-10 against halide-bin 22.0.0.dev390, shared Halide.dll, MSVC 19.42. No GPU execution. All generations exit 0, including the failures below.

Invocation Exit Artifact SHA-256 Cache message
object-A 0 3063cd74ca08d2fa0c65eb3fad8e75d0faac2fbef0f39aefb4afcb314d76c8fe GeneratorCache: stored key 43c7318ed00887bcdb406515ba15e182ca8c24be1d996456c5d2811c0d32fe97
object-B 0 ef9bbb0410fc1d45b6bfabda18826a2832490c9ef8f8423577657c0eeb32db3f GeneratorCache: stored key 06a1f21736b7e2cfa6d79a3180182748b0af2b25d243d2d0ea4af3deb4f0a711
object-restore-A 0 3063cd74ca08d2fa0c65eb3fad8e75d0faac2fbef0f39aefb4afcb314d76c8fe GeneratorCache: hit for key 43c7318ed00887bcdb406515ba15e182ca8c24be1d996456c5d2811c0d32fe97
object-locked-B 0 3063cd74ca08d2fa0c65eb3fad8e75d0faac2fbef0f39aefb4afcb314d76c8fe GeneratorCache: hit for key 06a1f21736b7e2cfa6d79a3180182748b0af2b25d243d2d0ea4af3deb4f0a711
object-unlocked-B 0 ef9bbb0410fc1d45b6bfabda18826a2832490c9ef8f8423577657c0eeb32db3f GeneratorCache: hit for key 06a1f21736b7e2cfa6d79a3180182748b0af2b25d243d2d0ea4af3deb4f0a711
static_library-A 0 705cdf8d44bdae16893212e6197cb9b52eb4fc515815dcc6e26ffeef3c9a3373 GeneratorCache: stored key 975e6a2fc3b9278ec443ac84c611c3f89136f5568abf1c19b7cf690728ba9b48
static_library-B 0 bedc7d16a66dcd26cc3da43ab7da057e1c30156624cf5775df638c8af5908e1f GeneratorCache: stored key 76cd390cb00b16aa7ec35b64cbe632e08cc9d6aac7600241feea438e5a82938b
static_library-restore-A 0 705cdf8d44bdae16893212e6197cb9b52eb4fc515815dcc6e26ffeef3c9a3373 GeneratorCache: hit for key 975e6a2fc3b9278ec443ac84c611c3f89136f5568abf1c19b7cf690728ba9b48
static_library-locked-B 0 705cdf8d44bdae16893212e6197cb9b52eb4fc515815dcc6e26ffeef3c9a3373 GeneratorCache: hit for key 76cd390cb00b16aa7ec35b64cbe632e08cc9d6aac7600241feea438e5a82938b
static_library-unlocked-B 0 bedc7d16a66dcd26cc3da43ab7da057e1c30156624cf5775df638c8af5908e1f GeneratorCache: hit for key 76cd390cb00b16aa7ec35b64cbe632e08cc9d6aac7600241feea438e5a82938b
cold-restore-A 0 705cdf8d44bdae16893212e6197cb9b52eb4fc515815dcc6e26ffeef3c9a3373 GeneratorCache: hit for key 975e6a2fc3b9278ec443ac84c611c3f89136f5568abf1c19b7cf690728ba9b48
cold-locked-B 0 705cdf8d44bdae16893212e6197cb9b52eb4fc515815dcc6e26ffeef3c9a3373 GeneratorCache: stored key 76cd390cb00b16aa7ec35b64cbe632e08cc9d6aac7600241feea438e5a82938b
cold-unlocked-B 0 705cdf8d44bdae16893212e6197cb9b52eb4fc515815dcc6e26ffeef3c9a3373 GeneratorCache: hit for key 76cd390cb00b16aa7ec35b64cbe632e08cc9d6aac7600241feea438e5a82938b
no-cache-restore-A 0 705cdf8d44bdae16893212e6197cb9b52eb4fc515815dcc6e26ffeef3c9a3373 GeneratorCache: hit for key 975e6a2fc3b9278ec443ac84c611c3f89136f5568abf1c19b7cf690728ba9b48
no-cache-locked-B 0 705cdf8d44bdae16893212e6197cb9b52eb4fc515815dcc6e26ffeef3c9a3373 (none)
"""Windows-only public-API reproduction. Creates a new disposable root each run."""
import argparse
from contextlib import contextmanager
import ctypes
from ctypes import wintypes
import hashlib
import json
import os
from pathlib import Path
import re
import subprocess
import tempfile
parser = argparse.ArgumentParser()
parser.add_argument("generator", type=Path)
args = parser.parse_args()
generator = args.generator.resolve(strict=True)
root = Path(tempfile.mkdtemp(prefix="halide-output-repro-"))
print(f"Results: {root}", flush=True)
records = []
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
kernel32.CreateFileW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD,
ctypes.c_void_p, wintypes.DWORD, wintypes.DWORD,
wintypes.HANDLE]
kernel32.CreateFileW.restype = wintypes.HANDLE
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
kernel32.CloseHandle.restype = wintypes.BOOL
@contextmanager
def lock_output(path):
handle = kernel32.CreateFileW(str(path), 0x80000000, 1, None, 3, 0x80, None)
# GENERIC_READ, FILE_SHARE_READ only, OPEN_EXISTING: allow reads, deny writes/deletion.
if handle == ctypes.c_void_p(-1).value:
raise ctypes.WinError(ctypes.get_last_error())
try:
yield
finally:
if not kernel32.CloseHandle(handle):
raise ctypes.WinError(ctypes.get_last_error())
def run(offset, kind, cache, tag):
out = root / kind
out.mkdir(exist_ok=True)
artifact = out / ("cache_add.lib" if kind == "static_library" else "cache_add.obj")
env = {k: v for k, v in os.environ.items() if not k.upper().startswith("HL_")}
env.update(HL_DEBUG_CODEGEN="1", HL_CACHE_MAX_SIZE="2G")
if cache is not None:
env["HL_CACHE_DIR"] = str(root / cache)
cmd = [str(generator), "-g", "cache_add", "-f", "cache_add", "-n", "cache_add",
"-o", str(out), "-e", f"c_header,{kind}",
"target=x86-64-windows-no_runtime", f"offset={offset}"]
proc = subprocess.run(cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
timeout=60, check=False)
log = proc.stdout.decode(errors="replace")
# Save only structured diagnostics: native debug output can contain local
# source, compiler, executable and temporary-directory paths.
messages = re.findall(r"GeneratorCache: (?:stored key|hit for key) [0-9a-f]{64}", log)
if "GeneratorCache: failed to restore" in log:
messages.append("GeneratorCache: failed to restore <output>")
public_command = ["<generator>" if arg == str(generator)
else "<output>/" + kind if arg == str(out)
else arg for arg in cmd]
row = {"tag": tag, "exit": proc.returncode, "command": public_command,
"cache_messages": messages,
"sha256": hashlib.sha256(artifact.read_bytes()).hexdigest() if artifact.is_file() else None}
(root / (tag + ".log")).write_text(json.dumps(row, indent=2), encoding="utf-8")
records.append(row)
(root / "results.json").write_text(json.dumps(records, indent=2), encoding="utf-8")
print(tag, row["exit"], row["sha256"], row["cache_messages"], flush=True)
return artifact, row
for kind in ("object", "static_library"):
cache = "warm-" + kind
artifact, a = run(1, kind, cache, kind + "-A")
_, b = run(2, kind, cache, kind + "-B")
assert a["exit"] == b["exit"] == 0 and a["sha256"] != b["sha256"]
run(1, kind, cache, kind + "-restore-A")
with lock_output(artifact):
_, locked = run(2, kind, cache, kind + "-locked-B")
_, unlocked = run(2, kind, cache, kind + "-unlocked-B")
assert unlocked["exit"] == 0 and unlocked["sha256"] == b["sha256"]
print("FALSE RESTORE HIT:", locked["exit"] == 0 and locked["sha256"] == a["sha256"]
and any("hit for key" in msg for msg in locked["cache_messages"]), flush=True)
# Existing A library, cold B generation. The same output names and target are retained.
artifact, a = run(1, "static_library", "warm-static_library", "cold-restore-A")
with lock_output(artifact):
_, cold = run(2, "static_library", "cold-cache", "cold-locked-B")
_, poisoned = run(2, "static_library", "cold-cache", "cold-unlocked-B")
print("COLD CACHE POISONED:", cold["exit"] == poisoned["exit"] == 0
and cold["sha256"] == poisoned["sha256"] == a["sha256"], flush=True)
# Also expose the writer failure with the semantic cache disabled.
artifact, a = run(1, "static_library", "warm-static_library", "no-cache-restore-A")
with lock_output(artifact):
_, uncached = run(2, "static_library", None, "no-cache-locked-B")
print("UNCACHED WRITE FALSE SUCCESS:", uncached["exit"] == 0
and uncached["sha256"] == a["sha256"], flush=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment