Skip to content

Limit non-local call dependency walks in MemoryDependenceAnalysis - #8997

Closed
Damyan Pepper (damyanp) wants to merge 2 commits into
microsoft:mainfrom
damyanp:damyanp-gvn-compile-time-investigation
Closed

Damyan Pepper (damyanp) wants to merge 2 commits into
microsoft:mainfrom
damyanp:damyanp-gvn-compile-time-investigation

Conversation

@damyanp

Copy link
Copy Markdown
Member

GVN can take a very long time on large shaders with many resource accesses. One Tint/Dawn-generated compute shader took about 45s to compile at -O3, and about 84% of that was spent in the two GVN runs (about 15s each).

Root cause

Profiling shows all of the GVN time is spent in this path:

GVN::processInstruction → ValueTable::lookup_or_add_call → MemoryDependenceAnalysis::getNonLocalCallDependency → getCallSiteDependencyFrom

The queries are for read-only calls, mostly dx.op.createHandleForLib on RW buffers. The rawBufferStore calls that clobber them are inside conditional blocks, so there is always a path around them. As a result, each query walks every block back to the function entry. With N inlined call sites, the total cost is O(N × function size), which is quadratic.

Upstream LLVM limits the number of blocks visited for non-local pointer dependencies, but has no such limit for call dependencies.

Fix

In getNonLocalCallDependency, once the walk has found a dependency that is not a Def (a clobber or unknown) and has visited more than 100 blocks, stop scanning. The remaining blocks are marked Unknown.

GVN only uses a non-local call dependency when it is a single Def whose block dominates the query. A result that already contains a clobber or unknown entry can never be used, so this cutoff does not change GVN output. The only other user of this API is MemDepPrinter.

Results

On the repro, -T cs_6_0 -O3 -HV 2018:

Total compile time Each GVN run
Before ~41–46s ~15s
After ~8–9s ~1.25s
  • The output DXIL is byte-identical.
  • With a generated N-call-site scaling repro, GVN time was quadratic before the change (about 4–5x per doubling of N) and is linear after it (about 2x per doubling). DXIL is identical at every N.
  • Without the gate (applying the cutoff unconditionally), the repro DXIL was also identical at limits from 5 up to 1e6 blocks.

Testing

  • New test tools/clang/test/DXC/Passes/GVN/nonlocal-call-dep-limit.ll. It checks two cases:
    • a far-away Def is still eliminated as redundant;
    • when a clobber exists, the walk is cut off and both calls are kept.
  • check-all passes with no failures (3025 passed, 7 expected failures).

Assisted by GitHub Copilot.

GVN queries non-local dependencies for read-only calls (e.g.
dx.op.createHandleForLib). When clobbers sit on conditional paths the
walk visits every block back to the function entry, making GVN
quadratic on large shaders. Once a non-Def dependency is found the
result is unusable by GVN, so stop scanning after 100 blocks and mark
the rest Unknown. Output is unchanged; on a Dawn-generated repro total
compile time drops from ~46s to ~8s.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant