Skip to content

HipDynMem: collect direct users in insertion order - #1663

Draft
pvelesko wants to merge 2 commits into
mainfrom
2026-09-17-github-1661-dynmem-order
Draft

pvelesko wants to merge 2 commits into
mainfrom
2026-09-17-github-1661-dynmem-order

Conversation

@pvelesko

Copy link
Copy Markdown
Collaborator

HipDynMemExternReplacePass collected the direct users of a dynamic shared memory global in a SmallPtrSet, which iterates in pointer hash order once it holds more than 16 elements, and appended a clone of each user to the module in that order. Compiling the same source twice could then emit the kernels in a different order, so caches keyed on the SPIR-V missed. The direct users now go into the SetVector the pass already uses for indirect users.

The new compile-only test runs the post-link pipeline several times on a module with 32 users and requires identical output. It relies on heap addresses changing between processes, so it cannot fail where ASLR is disabled.

Fixes #1661

This PR was generated using AI

Running hip-post-link-passes twice on the same module can give different
output when a dynamic shared memory global (extern __shared__ T x[]) has
more than 16 direct users: HipDynMemExternReplacePass collects them into a
SmallPtrSet<Function *, 16>, which past its inline capacity iterates in
pointer hash order, and it appends one clone per user to the module in that
order. Function objects land at different heap addresses in every process
under ASLR, so the order of the lowered kernels, and the SPIR-V emitted from
them, changes from run to run for an unchanged source. Caches keyed on the
emitted IL (the OpenCL module cache, PoCL's kernel cache) then miss.

The test generates 32 spir_kernel functions that each store to one
external addrspace(3) [0 x i32] global, runs opt with the chipStar plugin
and -passes=hip-post-link-passes eight times, and requires every textual
output to match the first. The module is generated in the script, so there
is no fixture file. 32 users puts the set well past its inline size; with 16
or fewer it iterates in insertion order and cannot show the bug.

Several runs are needed because the order only changes between processes.
Measured on main (7db0385, LLVM 22, x86_64 Linux, ASLR on): the test
failed 200 of 200 invocations. Run 2 already differed from run 1 in 192 of
them and run 3 in the other 8, so two runs agree about 4% of the time and
eight agreeing by chance is not a realistic outcome. On the reduction sample
that motivated the issue, 20 runs gave 11 distinct modules.
HipDynMemExternReplacePass gathered the functions that use a dynamic
shared memory global into FSet, a SmallPtrSet<Function *, 16>. That set
iterates in insertion order only while it holds 16 or fewer elements; past
that it switches to a hash table keyed on the pointer value, so iteration
follows heap addresses. The loop over it decides both the order in which
indirect users are discovered and the order in which each direct user is
cloned with the extra argument, and every clone is appended to the end of
the module by Function::Create. With ASLR, two opt runs on the same bitcode
therefore produced the same functions in a different order, and different
SPIR-V, whenever a global had more than 16 direct users. The cuda-reduction
sample has 88 and 44.

Collect the direct users into OrderedFSet (llvm::SetVector<Function *>),
the type the pass already uses for indirect users. Iteration is then in
use-list order, which depends only on the input module. Membership and the
set of clones are unchanged; only their order in the module is fixed. FSet
and the SmallPtrSet include had no other users and go with it.

The rest of the pass does not depend on addresses: the global variables
come from the ValueSymbolTable, a StringMap hashed on names; users are
walked through use-lists; the ValueToValueMap given to CloneFunctionInto is
only looked up, never iterated for output.

Measured with LLVM 22 on x86_64 Linux: TestFix1661DynMemOrder passed 200
of 200 invocations (1600 opt runs), and 30 runs of hip-post-link-passes on
reduction_kernel-hip-spirv64-generic-link.bc gave one module, with the same
set of defined functions as before the change. Before it, the test failed
200 of 200 and 20 runs of the sample gave 11 distinct modules.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HipDynMem clones shared memory users in pointer-hash order, so the lowered module is not reproducible

1 participant