Skip to content

Support Julia 1.13's julia.atomicmodify pseudo-intrinsic#2880

Open
vchuravy wants to merge 1 commit into
mainfrom
vc/jl-atomicmodify
Open

Support Julia 1.13's julia.atomicmodify pseudo-intrinsic#2880
vchuravy wants to merge 1 commit into
mainfrom
vc/jl-atomicmodify

Conversation

@vchuravy

Copy link
Copy Markdown
Member

Julia 1.13 (JuliaLang/julia#57010) emits atomic modify operations on integer-typed pointer-free fields as a variadic pseudo-intrinsic

{old, new} = julia.atomicmodify.iN.pAS(ptr, op, ordering, syncscope, args...)

which atomically performs old = *ptr; new = op(old, args...); *ptr = new, where op's parameter i (i >= 1) is forwarded from call operand i + 3. It is only expanded to atomicrmw/cmpxchg loops by Julia's ExpandAtomicModify pass after GC lowering, so Enzyme must keep it intact in generated code. Previously these calls hit the generic call differentiation path, which rejects variadic calls with Number of arg operands != function parameters (EnzymeAD/Enzyme.jl#3086).

TypeAnalysis

Unify the pointee of ptr with both result elements and analyze op interprocedurally, relating the forwarded arguments with the modified memory. In particular this lets activity analysis prove atomic counter/lock manipulations inactive (Julia annotates real-world IR with enzyme_type already, but the interprocedural step recovers types for the modified value and forwarded operands).

AdjointGenerator

Handle the call akin to atomicrmw:

  • fully inactive calls are replayed via the constant fallback,
  • non-differentiable (integer/pointer) modifications within duplicated memory are replicated on the shadow location with the primal arguments, keeping counters and lock states of shadow objects consistent,
  • in forward mode, ops recognized as linear in (old, value) (fadd/fsub/xchg through bitcasts) compute the tangent by applying the same op to the shadow location and shadow value,
  • in reverse mode (with inactive result), fadd/fsub accumulate the adjoint of the value operand from a load of the shadow location with downgraded ordering (negated for fsub),
  • everything else reports Active atomic modify not yet handled.

Note that on Julia 1.13 codegen only emits the intrinsic for integer element types (float fields still lower to inline cmpxchg loops), so the float paths are exercised by the included lit tests; they implement the intrinsic's full semantics for when emission widens.

Companion Enzyme.jl change (running ExpandAtomicModify in its post-AD legalization pipeline): EnzymeAD/Enzyme.jl branch vc/atomicmodify. Tested end-to-end on Julia 1.13.0-rc1: the previously crashing locks and threads test suites pass, including reverse mode over atomic counters in duplicated structs.

🤖 Generated with Claude Code

Julia 1.13 (JuliaLang/julia#57010) emits atomic modify operations on
integer-typed pointer-free fields as a variadic pseudo-intrinsic

  {old, new} = julia.atomicmodify.iN.pAS(ptr, op, ordering, syncscope,
                                         args...)

which atomically performs old = *ptr; new = op(old, args...);
*ptr = new, and is only expanded to atomicrmw/cmpxchg by Julia's
ExpandAtomicModify pass after GC lowering. Previously these calls hit
the generic call differentiation path, which rejects variadic calls
with "Number of arg operands != function parameters".

TypeAnalysis: unify the pointee of ptr with both result elements and
analyze op interprocedurally, relating the forwarded arguments with the
modified memory. This in particular lets activity analysis prove
atomic counter/lock manipulations inactive.

AdjointGenerator: keep the pseudo-intrinsic intact in generated code
(it must survive until Julia's expansion pass) and handle it akin to
atomicrmw:
- fully inactive calls are replayed via the constant fallback,
- non-differentiable (integer/pointer) modifications within duplicated
  memory are replicated on the shadow location with the primal
  arguments, keeping counters and lock states of shadow objects
  consistent,
- in forward mode, ops recognized as linear in (old, value)
  (fadd/fsub/xchg through bitcasts) compute the tangent by applying the
  same op to the shadow location and shadow value,
- in reverse mode (with inactive result), fadd/fsub accumulate the
  adjoint of the value operand from a load of the shadow location with
  downgraded ordering (negated for fsub),
- everything else reports "Active atomic modify not yet handled".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@LuuOW LuuOW left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technical audit: code patterns and logic verified.

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.

2 participants