# Hotswap failure reducer `utils/hotswap/hotswap_reduce.py` turns a failing hotswap corpus bundle into a small, standalone reproducer. It only accepts a transformation when a caller-provided interestingness command continues to return the configured exit code. The original inputs are never modified and the output directory is published atomically. The reducer works from the outside in: 1. Delta-debug the list of code objects. 2. Delta-debug the top-level `kernels`, `cases`, and `arguments` metadata lists, followed by each retained case's `arguments` list. 3. Optionally remove explicitly allowed, non-allocated ELF sections with `llvm-objcopy`. 4. Repeat the hierarchy until a complete pass makes no change. This gives each retained dimension another reduction opportunity after lower-level changes. The section pass gets section names and flags from `llvm-readobj` JSON. It does not parse ELF itself. Allocated sections and the default runtime-critical section set are always protected. Section removal is disabled unless at least one `--allow-remove-section` glob is provided. If multiple sections have the same name, one allocated or protected instance protects every instance because `llvm-objcopy` removes sections by name. Every transformed output is parsed again to verify that it is still AMDGPU ELF, every requested section is gone, and no unrequested section disappeared. ## Interestingness command The command is an argv vector, not a shell string. Pass the executable with `--predicate` and repeat `--predicate-arg` for its arguments. These literal placeholders are expanded for each candidate: - `{bundle}`: candidate `bundle.json` - `{input}`: the sole candidate code object (only valid with one object) - `{metadata}`: candidate `metadata.json` - `{workspace}`: candidate directory Exit zero means interesting by default. Use `--interesting-exit-code` when the reproducer intentionally reports a failure with a nonzero exit. A timeout, launch error, or exit-code disagreement between `--predicate-runs` repetitions rejects a candidate. Predicate output retained in the log is bounded to avoid unbounded memory use. Timed-out predicate process trees are terminated before their temporary workspace is removed. Stable outcomes are cached by the complete candidate content and predicate configuration; `--cache-file` makes that cache persistent. The resolved predicate executable and regular-file argv items are identified by path, content, and file mode. Repeat `--cache-dependency` for dynamically loaded libraries, reference data, or other file inputs, and repeat `--cache-tag` for relevant environment or configuration identity. A dependency that changes during a run aborts the reduction instead of consulting a stale cache entry. Predicates must be deterministic and must not rely on side effects outside their candidate workspace because a cache hit replaces the process execution. For example: ```console python3 utils/hotswap/hotswap_reduce.py \ --bundle failing-bundle.json \ --output reduced \ --predicate /usr/bin/python3 \ --predicate-arg=/absolute/path/is_interesting.py \ --predicate-arg='{bundle}' \ --predicate-runs 3 \ --timeout 20 \ --allow-remove-section='.debug_*' \ --allow-remove-section='.comment' ``` No argument is evaluated by a shell. Supplying an absolute predicate and absolute script path makes the recorded reproduction argv unambiguous on the same filesystem. Existing regular-file argv items are resolved to absolute paths before the first predicate run. ## PR #3646 offline differential workflow An A0 runtime failure is not required. The predicate can be any deterministic offline check, which makes the reducer useful while validating the hotswap implementation in PR #3646. For a discrepant hipBLASLt or hipSOLVER case, first put the involved code objects and launch records in a regular bundle. Then use one of these predicate shapes: - A wrapper rewrites the candidate with the PR #3646 build, emits its structural manifest, compares it with a fixed PR #3598 reference manifest, and exits with the interesting code while a difference remains. - A wrapper runs `hotswap-audit` over every retained object and exits with the interesting code while the audit reports an invariant violation. - A wrapper runs `hotswap-semcheck` for the retained kernels and exits with the interesting code while it returns a counterexample. For example, an offline manifest differential can be driven as: ```console python3 utils/hotswap/hotswap_reduce.py \ --bundle hipblaslt-discrepancy.json \ --output reduced-3646-difference \ --predicate /absolute/path/manifest-diff-predicate \ --predicate-arg=/absolute/path/pr3598-reference.json \ --predicate-arg='{bundle}' \ --predicate-runs 2 ``` The wrapper owns the meaning of the comparison and the selected build. The reducer only sees an argv vector and an exit code, so no PR-specific opcode, kernel name, or library test name is encoded in its reduction logic. Keep the reference manifest outside the candidate bundle so ddmin cannot reduce the oracle itself. ## Input and output bundle Input bundles use this versioned JSON form. Object paths and a string-valued `metadata` path are relative to the bundle file and cannot escape its directory, including through a symlink. Use direct `--code-object` or `--worklist` input when the object files intentionally live elsewhere. ```json { "format": "comgr-hotswap-reducer-bundle", "version": 1, "code_objects": [ {"id": "hipblaslt-case-17", "path": "objects/case 17.co"} ], "metadata": { "kernels": [{"name": "kernel"}], "cases": [{"name": "case", "arguments": [1, 2, 3]}], "arguments": [] } } ``` `--code-object` can be repeated instead of providing a bundle, with optional metadata from `--metadata`. An inventory or test-selection pipeline can provide a NUL-delimited worklist: ```console python3 utils/hotswap/hotswap_reduce.py \ --worklist unique-code-objects.list \ --metadata selection.json \ --output reduced \ --predicate /absolute/path/offline-predicate \ --predicate-arg='{bundle}' ``` Every worklist entry is treated as a path, never shell text. Absolute paths from the inventory utility work directly; relative paths are resolved from the worklist's directory. Empty, unterminated, or duplicate entries are rejected. A selector document can be passed directly as metadata. In addition to `kernels`, `cases`, and `arguments`, a top-level `selected_tests` list is delta-debugged generically; all unknown selector fields are preserved. The output contains: - `bundle.json`, `metadata.json`, and the retained objects under `objects/` - `reduction-log.json`, a versioned deterministic record of every accepted or rejected transformation - a final reproduction argv and interesting exit code in that log The log also records original and final content digests. It intentionally contains no timestamps, elapsed times, or cache-hit state so identical inputs and stable predicates produce identical logs. ## Safety limits - Existing output directories are never overwritten. - Cache files cannot overwrite an input (including external bundle metadata), predicate identity, dependency, or live inside the atomically published output directory. - Malformed bundles and metadata fail before a predicate runs. - Section pruning requires AMDGPU ELF input and available LLVM tools. - Protected or allocated sections cannot be opted into removal. - Temporary candidates are removed after interruption, timeout, or failure. - `llvm-reduce` is not run on linked ELF code objects; it is an IR/MIR reducer. Use it before linking when a retained bundle also has LLVM IR or MIR source. - Persistent cache writes are atomic but are not merged across concurrent reducer processes; give each concurrent process a distinct cache file.