Mark make_zero/make_zero! shadow-init builtins inactive in forward mode (#3135)#3137
Conversation
`make_zero` / `make_zero!` emit `jl_field_isdefined_checked` (the `isdefined(prev, i)` field walk over undef-able / abstract fields) and `jl_idset_peek_bp` (the `prev in seen` IdSet aliasing check). Forward mode had no rule for either, so forward-over-reverse nesting (HVPs/Hessians) over a value with an undef-able/abstract field failed with EnzymeNoDerivativeError. Add `jl_`/`ijl_` variants of both to `nofreefns` and `inactivefns` in src/llvm/attributes.jl, and a forward-mode regression testset exercising each builtin in isolation. This removes the two named errors; the full forward-over-reverse HVP still needs forward handling for the `seen`-table mutation builtins (`jl_eqtable_*`, `jl_idset_put_*`), tracked as a follow-up. See EnzymeAD#3135. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
|
Your PR requires formatting changes to meet the project's style guidelines. Click here to view the suggested changes.diff --git a/src/llvm/attributes.jl b/src/llvm/attributes.jl
index c240c1a2..467bea99 100644
--- a/src/llvm/attributes.jl
+++ b/src/llvm/attributes.jl
@@ -214,11 +214,11 @@ const nofreefns = Set{String}((
"ijl_eqtable_get",
"cuCtxGetApiVersion",
"cuCtxSetCurrent",
- # make_zero / make_zero! shadow-init bookkeeping (IdDict/IdSet seen-table queries)
- "jl_field_isdefined_checked",
- "ijl_field_isdefined_checked",
- "jl_idset_peek_bp",
- "ijl_idset_peek_bp",
+ # make_zero / make_zero! shadow-init bookkeeping (IdDict/IdSet seen-table queries)
+ "jl_field_isdefined_checked",
+ "ijl_field_isdefined_checked",
+ "jl_idset_peek_bp",
+ "ijl_idset_peek_bp",
))
const inactivefns = Set{String}((
@@ -349,11 +349,11 @@ const inactivefns = Set{String}((
"jl_array_to_string",
"ijl_array_to_string",
"pcre2_jit_compile_8",
- # make_zero / make_zero! shadow-init bookkeeping (IdDict/IdSet seen-table queries)
- "jl_field_isdefined_checked",
- "ijl_field_isdefined_checked",
- "jl_idset_peek_bp",
- "ijl_idset_peek_bp",
+ # make_zero / make_zero! shadow-init bookkeeping (IdDict/IdSet seen-table queries)
+ "jl_field_isdefined_checked",
+ "ijl_field_isdefined_checked",
+ "jl_idset_peek_bp",
+ "ijl_idset_peek_bp",
# "jl_"
))
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3137 +/- ##
==========================================
- Coverage 70.62% 69.96% -0.67%
==========================================
Files 66 66
Lines 21339 21958 +619
==========================================
+ Hits 15071 15363 +292
- Misses 6268 6595 +327 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@ChrisRackauckas-Claude your test doesn't work yet per: Error in testset forward inactivity of make_zero shadow-init builtins: Stacktrace: remove the test for now |
The `idset_membership` test exercised `prev in Base.IdSet` expecting it to route through `jl_idset_peek_bp`, but that is only the Julia 1.11+ IdSet implementation. On 1.10 `Base.IdSet` is backed by an `IdDict`, so `in` routes through `jl_eqtable_get` — which has an erroring forward rule — and the test fails with "Not yet implemented forward for jl_eqtable_get" on CI. Drop the idset_membership function and its assertion, keeping the jl_field_isdefined_checked regression test. The jl_idset_peek_bp attribute entries remain (they are correct on 1.11+); covering the membership path in a test belongs with the deferred jl_eqtable_*/jl_idset_put_* forward work. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
|
Done — removed the Confirmed the root cause locally on Julia 1.10.11: I kept the Covering the IdSet membership path in a test belongs with the deferred |
Summary
Follow-up to #3135 (per @wsmoses: "Open the pr for those two functions to start; we can do the eqtable stuff in a follow up").
make_zero/make_zero!emit two bookkeeping builtins while initializing the inner-reverse shadow:jl_field_isdefined_checked— theisdefined(prev, i)field walk over undef-able / abstract fields (make_zero.jl).jl_idset_peek_bp— theprev in seenIdSetaliasing check (make_zero!.jl→base/idset.jl).Enzyme forward mode had no rule for either, so nesting forward-over-reverse (HVPs/Hessians) over a value with an undef-able/abstract field failed with
EnzymeNoDerivativeError.This PR adds the
jl_/ijl_variants of both tonofreefnsandinactivefnsinsrc/llvm/attributes.jl, plus a forward-mode regression testset.What this fixes (verified locally)
On a dev'd checkout of
main(0670cc2, Julia 1.11.9), both isolated reproducers fail onmainand pass with this change, returning the correct derivative:mainjl_field_isdefined_checkedisdefined(m, i)field walk, fwd-modeNo forward mode derivative found for jl_field_isdefined_checked(2.0,)✓jl_idset_peek_bpv in Base.IdSet, fwd-modeNo forward mode derivative found for jl_idset_peek_bp(12.0,)✓The two named errors are gone from the issue's MWEs as well.
Scope / what's deferred
This is not sufficient for the full forward-over-reverse HVP yet. With the two builtins suppressed, the
make_zero/make_zero!seen-table path cascades to the table-mutation builtins:make_zero, IdDictseen) →jl_eqtable_get/jl_eqtable_putmake_zero!, IdSetseen) →jl_idset_put_key/jl_idset_put_idxjl_eqtable_get/jl_eqtable_puthave explicit erroring custom forward rules insrc/rules/llvmrules.jl, so list membership alone won't cover them. That's the agreed follow-up (forward rules for thejl_eqtable_*/jl_idset_put_*family, or making theseenbookkeeping provably inactive at the activity-analysis level).Tests
Added testset
"forward inactivity of make_zero shadow-init builtins"intest/make_zero.jl, exercising each builtin in isolation (without the deferred mutation builtins). Fulltest/make_zero.jlpasses locally on Julia 1.11.9:Note on formatting
src/llvm/attributes.jlis not Runic-formatted globally (full-file Runic proposes ~1100 line changes — the wholeSetblocks use 4-space indent where Runic wants 8). The new entries match the surrounding 4-space style rather than introducing 8-space stragglers in an otherwise 4-space block. If a green Runic check is required, the file needs a separate full reformat — happy to do that here or in its own PR, your call. (Thetest/make_zero.jladditions are Runic-clean.)Please ignore until reviewed by @ChrisRackauckas. Opened as a draft.
🤖 Generated with Claude Code