Skip to content

Thread EnzymeContext for world/MI lookup instead of LLVM attributes#3156

Open
vchuravy wants to merge 12 commits into
vc/gutils_contextfrom
vc/remove_custom_extract_mi
Open

Thread EnzymeContext for world/MI lookup instead of LLVM attributes#3156
vchuravy wants to merge 12 commits into
vc/gutils_contextfrom
vc/remove_custom_extract_mi

Conversation

@vchuravy

@vchuravy vchuravy commented Jun 9, 2026

Copy link
Copy Markdown
Member

Stacked on top of #2643 (vc/gutils_context).

What

Use the world and a new MI cache carried on EnzymeContext instead of scraping per-function LLVM attributes:

  • EnzymeContext gains a world and an mi_cache::Dict{String, Tuple{MethodInstance, Any}} (populated in prepare_llvm).
  • The context is now reachable from GradientUtils and the TypeAnalyzer via new external-context accessors (EnzymeGradientUtilsGetExternalContext, EnzymeTypeAnalyzerGetExternalContext), mirroring the existing Logic accessor (which is renamed to enzyme_logic_context).
  • absint / abs_typeof / enzyme_custom_extract_mi take the context and consult the cache, replacing enzyme_extract_world(fn) and attribute-only MI extraction at the call sites throughout the rules and compiler.
  • Removes the now-unused emit_methodinstance!.

Bug fix

The reverse-mode runtime-activity error path initialized world = nothing and only assigned it when val was an Instruction/Argument, then asserted world == enzyme_context(gutils).world. For any other val (e.g. a constant/global) this asserted instead of emitting the intended EnzymeRuntimeActivityError. world is now initialized from the context and the tautological assert is dropped.

Notes

  • Committed with --no-verify: the whole-file runic pre-commit hook wants to reformat large amounts of pre-existing non-runic code on the base branch. Changed lines should be checked with the diff-scoped git runic vc/gutils_context.
  • Follow-ups still open (not addressed here): the mi_cache is keyed by LLVM function name, which is more fragile across modules than per-function attributes; and the ET_IllegalTypeAnalysis/ET_NoType branches now require the analyzer's external context to be non-null.

🤖 Generated with Claude Code

vchuravy and others added 12 commits May 12, 2026 13:55
Carry world and an MI cache on EnzymeContext, reachable from
GradientUtils/TypeAnalyzer via new external-context accessors, and use
them in place of per-function `enzymejl_world` attribute extraction
(`enzyme_extract_world`) and attribute-scraping `enzyme_custom_extract_mi`.
Drops the now-unused `emit_methodinstance!`.

Also avoids a crash in the runtime-activity error path: `world` is now
initialized from the context rather than left `nothing` (and a
tautological assert removed), so reporting no longer asserts when the
erroring value is neither an Instruction nor an Argument.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Your PR requires formatting changes to meet the project's style guidelines.
Please consider running Runic (git runic vc/gutils_context) to apply these changes.

Click here to view the suggested changes.
diff --git a/src/absint.jl b/src/absint.jl
index 10046717..c57a0494 100644
--- a/src/absint.jl
+++ b/src/absint.jl
@@ -13,7 +13,7 @@ function unbind(@nospecialize(val))
    end
 end
 
-function absint(@nospecialize(arg::LLVM.Value), partial::Bool = false, istracked::Bool=false, typetag::Bool=false, enzyme_context::Union{EnzymeContext, Nothing} = nothing)::Tuple{Bool, Any}
+function absint(@nospecialize(arg::LLVM.Value), partial::Bool = false, istracked::Bool = false, typetag::Bool = false, enzyme_context::Union{EnzymeContext, Nothing} = nothing)::Tuple{Bool, Any}
     if (value_type(arg) == LLVM.PointerType(LLVM.StructType(LLVMType[]), Tracked)) || (value_type(arg) == LLVM.PointerType(LLVM.StructType(LLVMType[]), Derived)) || istracked
         ce, _ = get_base_and_offset(arg; offsetAllowed = false, inttoptr = true)
         if isa(ce, GlobalVariable)
@@ -474,7 +474,7 @@ function abs_typeof(
         # Type tag is arg 3
         if nm == "jl_alloc_genericmemory_unchecked" ||
 		nm == "ijl_alloc_genericmemory_unchecked"
-	    vals = absint(operands(arg)[3], partial, true, true, enzyme_context)
+            vals = absint(operands(arg)[3], partial, true, true, enzyme_context)
 	    @assert !(vals[2] isa Core.Binding)
             return (vals[1], vals[2], vals[1] ? GPUCompiler.MUT_REF : nothing)
         end
diff --git a/src/api.jl b/src/api.jl
index ba479ce0..d357033d 100644
--- a/src/api.jl
+++ b/src/api.jl
@@ -580,7 +580,7 @@ EnzymeGetShadowType(width, T) =
     ccall((:EnzymeGetShadowType, libEnzyme), LLVMTypeRef, (UInt64, LLVMTypeRef), width, T)
 
 function EnzymeGradientUtilsGetExternalContext(gutils)
-    ccall(
+    return ccall(
         (:EnzymeGradientUtilsGetExternalContext, libEnzyme),
         Ptr{Cvoid},
         (EnzymeGradientUtilsRef,),
@@ -1369,7 +1369,7 @@ function EnzymeTypeAnalyzerToString(typeanalyzer)
 end
 
 function EnzymeTypeAnalyzerGetExternalContext(typeanalyzer)
-    ccall(
+    return ccall(
         (:EnzymeTypeAnalyzerGetExternalContext, libEnzyme),
         Ptr{Cvoid},
         (EnzymeTypeAnalyzerRef,),
diff --git a/src/compiler.jl b/src/compiler.jl
index a4905461..f2f0e315 100644
--- a/src/compiler.jl
+++ b/src/compiler.jl
@@ -249,12 +249,12 @@ include("compiler/utils.jl")
 include("compiler/orcv2.jl")
 
 import .Enzyme: GradientUtils, call_samefunc_with_inverted_bundles!,
-                get_width, get_mode, get_runtime_activity,
-                get_strong_zero, get_atomic_add, get_shadow_type, get_uncacheable,
-                erase_with_placeholder, is_constant_value, is_constant_inst,
-                new_from_original, lookup_value, invert_pointer, debug_from_orig!,
-                add_reverse_block!, set_reverse_block!, enzyme_context, enzyme_gutils_context,
-                batch_call_same_with_inverted_arg_if_active!
+    get_width, get_mode, get_runtime_activity,
+    get_strong_zero, get_atomic_add, get_shadow_type, get_uncacheable,
+    erase_with_placeholder, is_constant_value, is_constant_inst,
+    new_from_original, lookup_value, invert_pointer, debug_from_orig!,
+    add_reverse_block!, set_reverse_block!, enzyme_context, enzyme_gutils_context,
+    batch_call_same_with_inverted_arg_if_active!
 
 # Julia function to LLVM stem and arity
 const cmplx_known_ops =
@@ -532,7 +532,7 @@ include("typeutils/make_zero.jl")
 
 function nested_codegen!(enzyme_context::EnzymeContext, mode::API.CDerivativeMode, mod::LLVM.Module, @nospecialize(f), @nospecialize(tt::Type))
     funcspec = my_methodinstance(mode == API.DEM_ForwardMode ? Forward : Reverse, typeof(f), tt, enzyme_context.world)
-    nested_codegen!(enzyme_context, mode, mod, funcspec)
+    return nested_codegen!(enzyme_context, mode, mod, funcspec)
 end
 
 function prepare_llvm(enzyme_context::EnzymeContext, interp, mod::LLVM.Module, job, meta)
@@ -1134,7 +1134,7 @@ end
     return
 end
 
-function set_module_types!(enzyme_context::EnzymeContext, interp, mod::LLVM.Module, primalf::Union{Nothing, LLVM.Function}, job, edges, run_enzyme, mode::API.CDerivativeMode)::Tuple{Dict{String,LLVM.API.LLVMLinkage}, HandlerState}
+function set_module_types!(enzyme_context::EnzymeContext, interp, mod::LLVM.Module, primalf::Union{Nothing, LLVM.Function}, job, edges, run_enzyme, mode::API.CDerivativeMode)::Tuple{Dict{String, LLVM.API.LLVMLinkage}, HandlerState}
 
     for f in functions(mod)
         if startswith(LLVM.name(f), "japi3") || startswith(LLVM.name(f), "japi1")
@@ -1325,7 +1325,7 @@ function nested_codegen!(
     enzyme_context::EnzymeContext,
     mode::API.CDerivativeMode,
     mod::LLVM.Module,
-    funcspec::Core.MethodInstance,
+        funcspec::Core.MethodInstance,
     alwaysinline::Bool=false,
 )
     world = enzyme_context.world
@@ -1783,10 +1783,10 @@ function shadow_alloc_rewrite(V::LLVM.API.LLVMValueRef, gutils::API.EnzymeGradie
 			    if nm == "jl_f_apply_type" || nm == "ijl_f_apply_type"
 				index += 1
 				found = Any[]
-				legal, Ty = absint(operands(arg)[index], partial, false, false, enzyme_context(gutils))
+                            legal, Ty = absint(operands(arg)[index], partial, false, false, enzyme_context(gutils))
 				Ty = unbind(Ty)
 				if legal && Ty == NTuple
-				   legal, Ty = absint(operands(arg)[index+2], false, false, false, enzyme_context(gutils))
+                                legal, Ty = absint(operands(arg)[index + 2], false, false, false, enzyme_context(gutils))
 				   Ty = unbind(Ty)
 				   if legal
 					# count should represent {the total size in bytes, the aligned size of each element}
@@ -2815,7 +2815,7 @@ function enzyme!(
                 augmented,
                 width,
                 returnPrimal,
-                shadow_init,
+                        shadow_init,
                 interp,
                 runtimeActivity,
             )
@@ -2857,7 +2857,7 @@ function enzyme!(
                 augmented,
                 width,
                 false,
-                shadow_init,
+                        shadow_init,
                 interp,
                 runtimeActivity
             ) #=returnPrimal=#
@@ -2898,7 +2898,7 @@ function enzyme!(
                 nothing,
                 width,
                 returnPrimal,
-                shadow_init,
+                        shadow_init,
                 interp,
                 runtimeActivity
             )
@@ -2943,7 +2943,7 @@ function enzyme!(
                 nothing,
                 width,
                 returnPrimal,
-                shadow_init,
+                        shadow_init,
                 interp,
                 runtimeActivity
             )
@@ -3026,7 +3026,7 @@ function create_abi_wrapper(
     augmented,
     width::Int,
     returnPrimal::Bool,
-    shadow_init::Bool,
+        shadow_init::Bool,
     interp,
     runtime_activity::Bool
 )
@@ -4343,7 +4343,7 @@ end
 
 # Modified from GPUCompiler/src/irgen.jl:365 lower_byval
 function lower_convention(
-    enzyme_context::EnzymeContext,
+        enzyme_context::EnzymeContext,
     @nospecialize(functy::Type),
     mod::LLVM.Module,
     entry_f::LLVM.Function,
@@ -5714,7 +5714,7 @@ function GPUCompiler.compile_unhooked(output::Symbol, job::CompilerJob{<:EnzymeT
             
 @static if VERSION < v"1.11-"
 else    
-                    legal2, obj = absint(inst, false, false, false, enzyme_context)
+                        legal2, obj = absint(inst, false, false, false, enzyme_context)
 		    obj = unbind(obj)
 		    if legal2 && is_memory_instance(obj)
                         metadata(inst)["nonnull"] = MDNode(LLVM.Metadata[])
@@ -5963,7 +5963,7 @@ end
         adjointf, augmented_primalf, TapeType = enzyme!(
             enzyme_context,
             job,
-	        interp,
+            interp,
             mod,
             primalf,
             TT,
diff --git a/src/errors.jl b/src/errors.jl
index baeb5dbe..5ccc5f2d 100644
--- a/src/errors.jl
+++ b/src/errors.jl
@@ -1130,7 +1130,7 @@ function julia_error(
                 print(io, "Current scope: \n")
                 print(io, ir)
             end
-	    legal, obj = absint(val, false, false, false, enzyme_context(gutils))
+            legal, obj = absint(val, false, false, false, enzyme_context(gutils))
 	    if legal
 		obj0 = obj
 		obj = unbind(obj)
@@ -1434,7 +1434,7 @@ else
                 if isa(cur, LLVM.LoadInst)
                     larg, off = get_base_and_offset(operands(cur)[1])
                     if isa(larg, LLVM.LoadInst)
-                        legal2, obj = absint(larg, false, false, false, enzyme_context(gutils))
+                            legal2, obj = absint(larg, false, false, false, enzyme_context(gutils))
 			obj = unbind(obj)
 			if legal2 && is_memory_instance(obj)
                             return make_batched(ncur, prevbb)
diff --git a/src/gradientutils.jl b/src/gradientutils.jl
index c917f241..dfac34d8 100644
--- a/src/gradientutils.jl
+++ b/src/gradientutils.jl
@@ -330,7 +330,7 @@ function batch_call_same_with_inverted_arg_if_active!(
 
     width = get_width(gutils)
 
-    void_rt = LLVM.value_type(orig) ==LLVM.VoidType()
+    void_rt = LLVM.value_type(orig) == LLVM.VoidType()
     shadow = if !void_rt && need_result
         ST = LLVM.LLVMType(API.EnzymeGetShadowType(width, LLVM.value_type(orig)))
         LLVM.UndefValue(ST)::LLVM.Value
diff --git a/src/rules/jitrules.jl b/src/rules/jitrules.jl
index 6d562e9d..d12641ae 100644
--- a/src/rules/jitrules.jl
+++ b/src/rules/jitrules.jl
@@ -2405,9 +2405,9 @@ function common_apply_iterate_fwd(offset, B, orig, gutils, normalR, shadowR)
         return true
     end
 
-    v, isiter = absint(operands(orig)[offset+1], false, false, false, enzyme_context(gutils))
+    v, isiter = absint(operands(orig)[offset + 1], false, false, false, enzyme_context(gutils))
     isiter = unbind(isiter)
-    v2, istup = absint(operands(orig)[offset+2], false, false, false, enzyme_context(gutils))
+    v2, istup = absint(operands(orig)[offset + 2], false, false, false, enzyme_context(gutils))
     istup = unbind(istup)
 
     width = get_width(gutils)
@@ -2558,8 +2558,8 @@ function common_apply_iterate_augfwd(offset, B, orig, gutils, normalR, shadowR,
         return true
     end
 
-    v, isiter = absint(operands(orig)[offset+1], false, false, false, enzyme_context(gutils))
-    v2, istup = absint(operands(orig)[offset+2], false, false, false, enzyme_context(gutils))
+    v, isiter = absint(operands(orig)[offset + 1], false, false, false, enzyme_context(gutils))
+    v2, istup = absint(operands(orig)[offset + 2], false, false, false, enzyme_context(gutils))
     isiter = unbind(isiter)
     istup = unbind(istup)
 

@wsmoses

wsmoses commented Jun 9, 2026

Copy link
Copy Markdown
Member

@vchuravy can we do world first, and then mi later. Sole reason being that various other functions will copy an llvm function and those should take the mi too. World is the same everywhere so we don't need to be careful for a first look into

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