Repository navigation
pipeline: Trim passes that do little for Julia code - #63747
KristofferC wants to merge 3 commits into
Conversation
|
| FPM.addPass(InferAlignmentPass()); | ||
| #endif | ||
| FPM.addPass(InstSimplifyPass()); | ||
| // No TailCallElim: `tail` lets AA assume the callee cannot access caller allocas, but GC frames are reachable through the task |
There was a problem hiding this comment.
Before GC lowering, GC frame allocas are reachable through the task's gcstack, and late lowering copies tail markers.
This is something that we have been debating. Yes, technically true, but practically false. We treat the gcstack mostly as inaccessiblemem. So the GC reads it, but we dont allow calls to read the gcstack of their parent (it knows nothing of that layout).
Not sure if there is a good way of telling LLVM that.
There was a problem hiding this comment.
I think we'd have to make a dummy gcframe in codegen, if we ever ran the InferFunctionAttrs pass which could add the maytail attribut, so that it wouldn't actually infer anything (though calling into question why we wasted the cycles to schedule the pass).
|
Generally looks LGTM. The only thing that is annoying to lose is:
Makes me wonder if IRCE annotates loops as cold, or if we should consider a hotness for branches pass. |
Yeah, that's why that one is in a separate commit but I couldn't really justify leaving it out (10% extra opt time) for no visible practical runtime performance that I could find. |
|
@nanosoldier |
|
Could we move the GVN pass to |
Sure, but we shouldn't have practically useless passes at any opt level. Imo, any optimization pass has to "earn its stay". So if there is code that actually benefits from it, we can of course add it but having a concrete example would be useful. |
|
Your job failed. Consult the server logs for more details. |
…stage Julia runs them there since JuliaLang/julia#63747. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UkUYyGrrrxDmwhV9mTKpr7
|
TTFX (job):
|
|
@nanosoldier |
|
@pkgeval |
|
those LLVM version guards make no sense |
| #if JL_LLVM_VERSION >= 180000 | ||
| FPM.addPass(InferAlignmentPass()); | ||
| #endif |
There was a problem hiding this comment.
We have long dropped support for LLVM v18
In statements like this, it is helpful to give some guidance as to why.
Okay, but Lines 13 to 15 in 85abe47 and there are many checks like this in the codebase already, so this is consistent and should probably be removed wholesale in another PR? Lines 1654 to 1658 in 85abe47 Lines 2348 to 2353 in 85abe47 Lines 2634 to 2638 in 85abe47 Lines 1283 to 1287 in 85abe47 Lines 28 to 32 in 85abe47 Lines 74 to 78 in 85abe47 Lines 623 to 647 in 85abe47 Lines 2375 to 2389 in 85abe47 Lines 2849 to 2853 in 85abe47 Lines 2875 to 2879 in 85abe47 Lines 987 to 991 in 85abe47 Lines 117 to 119 in 85abe47 Lines 127 to 131 in 85abe47 Lines 49 to 55 in 85abe47 Lines 111 to 115 in 85abe47 |
|
@nanosoldier |
|
Right, we haven't removed the old ones but shouldn't add new ones |
|
It's just a quite odd situation where we have a header file that claims v17 support, a bunch of v18 version checks, but no code is supposed to not add them... And adding them "makes no sense"? |
|
We don't actually support LLVM 18, so those branches are just dead and got forgotten. (I have a robot doing the cleanup right now) |
|
Yeah, I was just pushing back on the "makes sense" part. |
|
-500 #63791 😸 |
|
@KristofferC: run Full report: https://pkgeval-reports.julialang.org/?run=gh-6139757717 |
This comment was marked as outdated.
This comment was marked as outdated.
|
The benchmark job you requested has completed - possible performance regressions were detected. 4829 benchmarks were executed, 79 showed regressions, and 83 showed improvements. |
|
@KristofferC: run Full report: https://pkgeval-reports.julialang.org/?run=gh-6154693682 |
|
A lot of PkgEval noise due to JuliaCI/PkgEvalFarm.jl#63, rebasing this and rerunning should fix it. |
Several passes in the default pipeline cost compile time without changing the code we generate. Removed or replaced: - The O3-only scalar `GVN` and the second scalar `DSE`. They run right after `GVN`/`DSE`-like passes in the same stage and find almost nothing new. - `InferFunctionAttrsPass`. It only annotates known library calls, which Julia code rarely makes directly. - `LibCallsShrinkWrapPass`, which targets C math library calls with `errno`. - The `GVN` in the cleanup pipeline, now `EarlyCSE` with MemorySSA. After GC lowering there is little left for full `GVN` to do. `AllocOpt` no longer runs through a CGSCC walk unless a plugin registered CGSCC passes; without them the walk only costs call-graph construction. `Float2Int` and `LowerConstantIntrinsics` move to the start of the vector stage, where LLVM's own pipeline runs them. `InferAlignment` is added after loop vectorization and after the post-unroll `SROA`, as LLVM's pipeline does since LLVM 18. Measured on the sysimage corpus, LLVM pass time drops 12% at O2 and 20% at O3, and the CPU time of the sysimage output phase drops 6%. BaseBenchmarks runtime is unchanged within noise on Apple M4 and AMD Zen 4. Assisted-by: Claude Code (Opus 5.5)
After the intrinsic lowering passes, `GVN` mostly merges the address and load computations that GC lowering emits per allocation, which `EarlyCSE` with MemorySSA also handles. The cleanup `EarlyCSE` that followed becomes redundant and is removed. This trades `GVN`'s partial redundancy elimination and non-dominating load elimination at this late point for compile time: LLVM pass time on the sysimage drops a further 11% at O2 and 10% at O3, and the CPU time of the sysimage output phase a further 3%. Generated code is 0.09% smaller. BaseBenchmarks is unchanged within noise on Apple M4 and AMD Zen 4, as are package workloads (DataFrames, CSV, OrdinaryDiffEq, Optim). Assisted-by: Claude Code (Opus 5.5)
`IRCE` splits a short constant-trip inner loop under bounds checks (e.g. `for v in 1:5`) into pre/main/post loops with variable trip counts. That defeats the later `LoopFullUnroll`, and `LoopIdiomRecognize` instead turns the main loop into a variable-length `memcpy` per outer iteration. Running `LoopFullUnroll` once before `IRCE` only touches loops with a small constant trip count, so it removes this case without changing other loops. The kernel from JuliaLang#62798 goes from 269 µs to 84 µs (Apple M4). Compile cost is +0.4% pass instructions at O2/O3 on the sysimage, and optimized code size is unchanged (-0.01% text). Fixes JuliaLang#62798 Assisted-by: Claude Code (Opus 5.5)
c38b8a2 to
9106426
Compare
This comment was marked as outdated.
This comment was marked as outdated.
|
@KristofferC: run Full report: https://pkgeval-reports.julialang.org/?run=gh-6162282261 |
|
PkgEval looks good, Nanosoldier looks good, just gonna confirm the TTFX run here and then it should be good to go. |

I had Claude look at our LLVM opt pass pipeline to see if it had some unnecessary passes that didn't actually do anything in practice or if it was something that had a benefit to be added. It ran extensive tests on my laptop (mac) and on an x86 machines, doing benchmarks, checking instruction counts etc etc. The actual changes below is written by Claude but it is written well enough in my opinion that writing it myself wouldn't really help. There is also an artifact that has more data that could be interesting to read at https://claude.ai/artifact/FD1CouQPviN6hMDXxe1Bvs.
Several passes in the default pipeline cost compile time without changing
the code we generate. Removed or replaced:
GVNand the second scalarDSE. They run rightafter
GVN/DSE-like passes in the same stage and find almost nothingnew.
InferFunctionAttrsPass. It only annotates known library calls, whichJulia code rarely makes directly.
LibCallsShrinkWrapPass, which targets C math library calls witherrno.GVNin the cleanup pipeline, nowEarlyCSEwith MemorySSA. AfterGC lowering there is little left for full
GVNto do.AllocOptno longer runs through a CGSCC walk unless a plugin registeredCGSCC passes; without them the walk only costs call-graph construction.
Float2IntandLowerConstantIntrinsicsmove to the start of the vectorstage, where LLVM's own pipeline runs them.
InferAlignmentis added after loop vectorization and after thepost-unroll
SROA, as LLVM's pipeline does since LLVM 18.Measured on the sysimage corpus, LLVM pass time drops 12% at O2 and 20% at
O3, and the CPU time of the sysimage output phase drops 6%. BaseBenchmarks
runtime is unchanged within noise on Apple M4 and AMD Zen 4.
One pass is also added:
LoopFullUnrollnow runs once before theloop-pipeline
IRCE.IRCEsplits a short constant-trip inner loop underbounds checks (e.g.
for v in 1:5) into pre/main/post loops with variabletrip counts, which defeats the later full unroll and lets
LoopIdiomRecognizeturn the loop into a variable-lengthmemcpyper outeriteration. Unrolling first only touches loops with a small constant trip
count. The kernel from #62798 goes from 269 µs to 84 µs (Apple M4); the
extra pass costs +0.4% LLVM pass time, and optimized code size is unchanged.
Fixes #62798
Assisted-by: Claude Code (Opus 5.5)