From 2039c56a1d319450f7608423edd6ff7c1ca88add Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Fri, 12 Jun 2026 16:46:14 +0200 Subject: [PATCH 1/3] Run ExpandAtomicModify when lowering for the JIT Julia 1.13 (JuliaLang/julia#57010) emits `@atomic` modify operations as calls to the `julia.atomicmodify.*` pseudo-intrinsic family, which must be expanded to atomicrmw/cmpxchg by the ExpandAtomicModify pass before machine code emission; there is no runtime fallback symbol. Since our legalization pipeline never ran this pass, the unexpanded calls reached the ORC JIT as unresolvable symbols and aborted the process. Mirror Julia's pipeline placement (after FinalLowerGC, before LowerPTLS) in addJuliaLegalizationPasses!. LLVM.jl does not wrap this pass yet, so define the pass-name wrapper locally. Differentiating *through* julia.atomicmodify in reverse mode still requires support for the pseudo-intrinsic in Enzyme proper, which is handled separately. Fixes #3086 Co-Authored-By: Claude Fable 5 --- src/compiler/optimize.jl | 4 ++++ test/locks.jl | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/compiler/optimize.jl b/src/compiler/optimize.jl index ee145ed35b..6d4d3fa80a 100644 --- a/src/compiler/optimize.jl +++ b/src/compiler/optimize.jl @@ -362,6 +362,10 @@ function addJuliaLegalizationPasses!(mpm::LLVM.NewPMPassManager, lower_intrinsic if VERSION >= v"1.11.0-DEV.208" add!(fpm, FinalLowerGCPass()) end + if VERSION >= v"1.13.0-DEV.321" + # after LateLowerGCPass so that all IPO is valid + add!(fpm, ExpandAtomicModifyPass()) + end end if VERSION < v"1.11.0-DEV.208" add!(mpm, FinalLowerGCPass()) diff --git a/test/locks.jl b/test/locks.jl index 9e5daf67dc..1b6a7b1895 100644 --- a/test/locks.jl +++ b/test/locks.jl @@ -13,3 +13,21 @@ end Enzyme.autodiff(Forward, my_lock, Const) end +# https://github.com/EnzymeAD/Enzyme.jl/issues/3086 +mutable struct AtomicCounter + @atomic count::Int +end + +const my_counter = AtomicCounter(0) + +function my_atomic_modify(x) + @atomic my_counter.count += 1 + return x * x +end + +@testset "Atomic modify" begin + @test Enzyme.autodiff(Reverse, my_atomic_modify, Active, Active(2.0))[1][1] ≈ 4.0 + @test Enzyme.autodiff(Forward, my_atomic_modify, Duplicated(3.0, 1.0))[1] ≈ 6.0 + @test (@atomic my_counter.count) == 2 +end + From 6456799fa7eda607eb45b7d4d860332464a68288 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Fri, 12 Jun 2026 21:36:07 +0200 Subject: [PATCH 2/3] Test atomic modify within duplicated memory Replace the active float atomic-modify test: Julia only emits julia.atomicmodify for integer-typed pointer-free fields; float fields still lower to an inline cmpxchg loop, whose reverse-mode differentiation is a pre-existing, unrelated limitation. Instead test an atomic counter inside a Duplicated struct, which exercises Enzyme replicating the modification on the shadow object. Requires julia.atomicmodify support in Enzyme proper. Co-Authored-By: Claude Fable 5 --- test/locks.jl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/locks.jl b/test/locks.jl index 1b6a7b1895..dc5153b3e5 100644 --- a/test/locks.jl +++ b/test/locks.jl @@ -31,3 +31,23 @@ end @test (@atomic my_counter.count) == 2 end +mutable struct DupAtomicCounter + @atomic count::Int + x::Float64 +end + +function my_dup_atomic_modify(c) + @atomic c.count += 1 + return c.x * c.x +end + +@testset "Atomic modify duplicated" begin + c = DupAtomicCounter(0, 2.0) + dc = DupAtomicCounter(0, 0.0) + Enzyme.autodiff(Reverse, my_dup_atomic_modify, Active, Duplicated(c, dc)) + @test dc.x ≈ 4.0 + @test (@atomic c.count) == 1 + # the modification is replicated on the shadow + @test (@atomic dc.count) == 1 +end + From 55809ef6b45d921c73ca208f384a0d853d6aaff2 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Wed, 24 Jun 2026 09:07:19 +0200 Subject: [PATCH 3/3] use LLVM 9.10 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6493459ba9..aa6cabdd01 100644 --- a/Project.toml +++ b/Project.toml @@ -45,7 +45,7 @@ EnzymeCore = "0.8.16" Enzyme_jll = "0.0.274" GPUArraysCore = "0.1.6, 0.2" GPUCompiler = "1.6.2" -LLVM = "9.8.1" +LLVM = "9.10" LogExpFunctions = "0.3, 1" ObjectFile = "0.4, 0.5" PrecompileTools = "1"