Advanced Debugging

This guide covers debugging techniques for issues that go beyond Mooncake's rule system – problems at the boundary between Mooncake's generated code and Julia's compiler, runtime, or type system.

For rule-level debugging (eg, wrong tangent types, incorrect gradients), see Debug Mode and Debugging and MWEs.

IR inspection

Mooncake's AD pipeline transforms IR through several stages. The SkillUtils module in src/skill_utils.jl provides IR inspection utilities to view each stage and diff consecutive transformations. These are internal developer tools accessible via the Mooncake.SkillUtils prefix.

# Inspect all stages of the reverse-mode pipeline
ins = inspect_ir(demo_fn, 1.0)
show_ir(ins)                              # all stages
============================================================
IR Inspection: Tuple{typeof(Main.demo_fn), Float64}
Mode: reverse, World: 26965
============================================================

----------------------------------------
Stage: raw
Blocks: 1, Insts: 4, Edges: 0
----------------------------------------
3 1 ─ %1 = invoke Main.sin(_2::Float64)::Float64
  │   %2 = invoke Main.cos(_2::Float64)::Float64
  │   %3 = Base.mul_float(%1, %2)::Float64
  └──      return %3


----------------------------------------
Stage: normalized
Blocks: 1, Insts: 4, Edges: 0
----------------------------------------
3 1 ─ %1 = invoke Main.sin(_2::Float64)::Float64
  │   %2 = invoke Main.cos(_2::Float64)::Float64
  │   %3 = (Mooncake.IntrinsicsWrappers.mul_float)(%1, %2)::Float64
  └──      return %3


----------------------------------------
Stage: bbcode
Blocks: 1, Insts: 4, Edges: 0
----------------------------------------
Block 1 (id=Mooncake.BasicBlockCode.ID(4)):
  Mooncake.BasicBlockCode.ID(0): $(Expr(:invoke, MethodInstance for sin(::Float64), :(Main.sin), Core.Argument(2))) :: Float64
  Mooncake.BasicBlockCode.ID(1): $(Expr(:invoke, MethodInstance for cos(::Float64), :(Main.cos), Core.Argument(2))) :: Float64
  Mooncake.BasicBlockCode.ID(2): (Mooncake.IntrinsicsWrappers.mul_float)(Mooncake.BasicBlockCode.ID(0), Mooncake.BasicBlockCode.ID(1)) :: Float64
  Mooncake.BasicBlockCode.ID(3): return Mooncake.BasicBlockCode.ID(2) :: Any


----------------------------------------
Stage: fwd_ir
Blocks: 2, Insts: 23, Edges: 1
----------------------------------------
3 1 ─       (Mooncake.get_shared_data_field)(_1, 1)::Any
  │   %2  = (Mooncake.get_shared_data_field)(_1, 2)::Any
  │   %3  = (Mooncake.get_shared_data_field)(_1, 3)::Any
  └──       (Mooncake.__assemble_lazy_zero_rdata)(%2, _2, _3)::Any
  2 ─ %5  = (Mooncake.uninit_fcodual)(sin)::Any
  │   %6  = (Mooncake.rrule!!)(%5, _3)::Any
  │   %7  = (getfield)(%6, 1)::Any
  │   %8  = (getfield)(%6, 2)::Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}
  │   %9  = (typeassert)(%7, Mooncake.CoDual{Float64, Mooncake.NoFData})::Any
  │   %10 = (Mooncake.uninit_fcodual)(cos)::Any
  │   %11 = (Mooncake.rrule!!)(%10, _3)::Any
  │   %12 = (getfield)(%11, 1)::Any
  │   %13 = (getfield)(%11, 2)::Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}
  │   %14 = (typeassert)(%12, Mooncake.CoDual{Float64, Mooncake.NoFData})::Any
  │   %15 = (Mooncake.uninit_fcodual)(Mooncake.IntrinsicsWrappers.mul_float)::Any
  │   %16 = (Mooncake.rrule!!)(%15, %9, %14)::Any
  │   %17 = (getfield)(%16, 1)::Any
  │   %18 = (getfield)(%16, 2)::Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}
  │   %19 = (typeassert)(%17, Mooncake.CoDual{Float64, Mooncake.NoFData})::Any
  │   %20 = (typeassert)(%19, Mooncake.CoDual{Float64, Mooncake.NoFData})::Any
  │   %21 = (tuple)(%8, %13, %18)::Any
  │         (push!)(%3, %21)::Any
  └──       return %20


----------------------------------------
Stage: rvs_ir
Blocks: 5, Insts: 57, Edges: 4
----------------------------------------
3 1 ─       (Mooncake.get_shared_data_field)(_1, 1)::Any
  │   %2  = (Mooncake.get_shared_data_field)(_1, 2)::Any
  │   %3  = (Mooncake.get_shared_data_field)(_1, 3)::Any
  │   %4  = %new(Base.RefValue{Mooncake.NoRData}, $(QuoteNode(Mooncake.NoRData())))::Any
  │   %5  = %new(Base.RefValue{Float64}, $(QuoteNode(0.0)))::Any
  │   %6  = %new(Base.RefValue{Float64}, $(QuoteNode(0.0)))::Any
  │   %7  = %new(Base.RefValue{Float64}, $(QuoteNode(0.0)))::Any
  │   %8  = %new(Base.RefValue{Float64}, $(QuoteNode(0.0)))::Any
  │         %new(Base.RefValue{Any}, $(QuoteNode(Mooncake.ZeroRData())))::Any
  └──       $(QuoteNode(Mooncake.BasicBlockCode.ID(4)))
  2 ─       goto #3
  3 ─ %12 = (pop!)(%3)::Any
  │   %13 = (getfield)(%12, 1)::Any
  │   %14 = (getfield)(%12, 2)::Any
  │   %15 = (getfield)(%12, 3)::Any
  │   %16 = (getfield)(%6, :x)::Any
  │   %17 = (Mooncake.increment!!)(%16, _2)::Any
  │         (setfield!)(%6, :x, %17)::Any
  │   %19 = (getfield)(%6, :x)::Any
  │         (setfield!)(%6, :x, 0.0)::Any
  │   %21 = (%15)(%19)::Any
  │   %22 = (getfield)(%21, 2)::Any
  │   %23 = (getfield)(%7, :x)::Any
  │   %24 = (Mooncake.increment!!)(%23, %22)::Any
  │         (setfield!)(%7, :x, %24)::Any
  │   %26 = (getfield)(%21, 3)::Any
  │   %27 = (getfield)(%8, :x)::Any
  │   %28 = (Mooncake.increment!!)(%27, %26)::Any
  │         (setfield!)(%8, :x, %28)::Any
  │   %30 = (getfield)(%8, :x)::Any
  │         (setfield!)(%8, :x, 0.0)::Any
  │   %32 = (%14)(%30)::Any
  │   %33 = (getfield)(%32, 2)::Any
  │   %34 = (getfield)(%5, :x)::Any
  │   %35 = (Mooncake.increment!!)(%34, %33)::Any
  │         (setfield!)(%5, :x, %35)::Any
  │   %37 = (getfield)(%7, :x)::Any
  │         (setfield!)(%7, :x, 0.0)::Any
  │   %39 = (%13)(%37)::Any
  │   %40 = (getfield)(%39, 2)::Any
  │   %41 = (getfield)(%5, :x)::Any
  │   %42 = (Mooncake.increment!!)(%41, %40)::Any
  │         (setfield!)(%5, :x, %42)::Any
  └──       $(QuoteNode(Mooncake.BasicBlockCode.ID(6)))
  4 ─       goto #5
  5 ─ %46 = (getindex)(%2)::Any
  │   %47 = (getfield)(%4, :x)::Any
  │   %48 = (getfield)(%46, 1)::Any
  │   %49 = (Mooncake.instantiate)(%48)::Any
  │   %50 = (Mooncake.increment!!)(%47, %49)::Any
  │   %51 = (getfield)(%5, :x)::Any
  │   %52 = (getfield)(%46, 2)::Any
  │   %53 = (Mooncake.instantiate)(%52)::Any
  │   %54 = (Mooncake.increment!!)(%51, %53)::Any
  │   %55 = (tuple)(%50, %54)::Any
  │   %56 = (typeassert)(%55, Tuple{Mooncake.NoRData, Float64})::Any
  └──       return %56


----------------------------------------
Stage: optimized_fwd
Blocks: 11, Insts: 46, Edges: 12
----------------------------------------
3 1 ── %1  = Mooncake.getfield(_1, 2)::Base.RefValue{Tuple{Mooncake.LazyZeroRData{typeof(Main.demo_fn), Nothing}, Mooncake.LazyZeroRData{Float64, Nothing}}}
  │    %2  = Mooncake.getfield(_1, 3)::Mooncake.Stack{Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}}
  └───       Base.setfield!(%1, :x, (Mooncake.LazyZeroRData{typeof(Main.demo_fn), Nothing}(nothing), Mooncake.LazyZeroRData{Float64, Nothing}(nothing)))::Tuple{Mooncake.LazyZeroRData{typeof(Main.demo_fn), Nothing}, Mooncake.LazyZeroRData{Float64, Nothing}}
  2 ── %4  = Base.getfield(_3, :x)::Float64
  │    %5  = Core.tuple(%4)::Tuple{Float64}
  │    %6  = invoke sin(%4::Float64)::Float64
  │    %7  = %new(Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, sin, %5, (Mooncake.NoFData(),), $(QuoteNode(Mooncake.NoFData())))::Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}
  │    %8  = Base.getfield(_3, :x)::Float64
  │    %9  = Core.tuple(%8)::Tuple{Float64}
  │    %10 = invoke cos(%8::Float64)::Float64
  │    %11 = %new(Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, cos, %9, (Mooncake.NoFData(),), $(QuoteNode(Mooncake.NoFData())))::Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}
  │    %12 = %new(Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}, %10, %6)::Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}
  │    %13 = Core.Intrinsics.mul_float(%6, %10)::Float64
  │    %14 = %new(Mooncake.CoDual{Float64, Mooncake.NoFData}, %13, $(QuoteNode(Mooncake.NoFData())))::Mooncake.CoDual{Float64, Mooncake.NoFData}
  │    %15 = (tuple)(%7, %11, %12)::Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}
  │    %16 = Base.getfield(%2, :position)::Int64
  │    %17 = Base.add_int(%16, 1)::Int64
  │    %18 = Base.getfield(%2, :memory)::Vector{Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}}
  │          Base.setfield!(%2, :position, %17)::Int64
  │    %20 = Base.getfield(%18, :size)::Tuple{Int64}
  │    %21 = $(Expr(:boundscheck, true))::Bool
  │    %22 = Base.getfield(%20, 1, %21)::Int64
  │    %23 = Base.sle_int(%17, %22)::Bool
  └───       goto #9 if not %23
  3 ── %25 = $(Expr(:boundscheck, false))::Bool
  └───       goto #7 if not %25
  4 ── %27 = Base.sub_int(%17, 1)::Int64
  │    %28 = Base.bitcast(UInt64, %27)::UInt64
  │    %29 = Base.getfield(%18, :size)::Tuple{Int64}
  │    %30 = $(Expr(:boundscheck, true))::Bool
  │    %31 = Base.getfield(%29, 1, %30)::Int64
  │    %32 = Base.bitcast(UInt64, %31)::UInt64
  │    %33 = Base.ult_int(%28, %32)::Bool
  └───       goto #6 if not %33
  5 ──       goto #7
  6 ── %36 = Core.tuple(%17)::Tuple{Int64}
  │          invoke Base.throw_boundserror(%18::Vector{Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}}, %36::Tuple{Int64})::Union{}
  └───       unreachable
  7 ┄─ %39 = Base.getfield(%18, :ref)::MemoryRef{Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}}
  │    %40 = Base.memoryrefnew(%39, %17, false)::MemoryRef{Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}}
  │          Base.memoryrefset!(%40, %15, :not_atomic, false)::Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}
  └───       goto #8
  8 ──       goto #10
  9 ──       invoke Mooncake.push!(%18::Vector{Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}}, %15::Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}})::Vector{Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}}
  10 ┄       goto #11
  11 ─       return %14


----------------------------------------
Stage: optimized_rvs
Blocks: 23, Insts: 74, Edges: 23
----------------------------------------
3 1 ── %1  = Mooncake.getfield(_1, 3)::Mooncake.Stack{Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}}
  2 ──       goto #3
  3 ── %3  = Base.getfield(%1, :position)::Int64
  │    %4  = Base.getfield(%1, :memory)::Vector{Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}}
  │    %5  = $(Expr(:boundscheck, false))::Bool
  └───       goto #7 if not %5
  4 ── %7  = Base.sub_int(%3, 1)::Int64
  │    %8  = Base.bitcast(Base.UInt, %7)::UInt64
  │    %9  = Base.getfield(%4, :size)::Tuple{Int64}
  │    %10 = $(Expr(:boundscheck, true))::Bool
  │    %11 = Base.getfield(%9, 1, %10)::Int64
  │    %12 = Base.bitcast(Base.UInt, %11)::UInt64
  │    %13 = Base.ult_int(%8, %12)::Bool
  └───       goto #6 if not %13
  5 ──       goto #7
  6 ── %16 = Core.tuple(%3)::Tuple{Int64}
  │          invoke Base.throw_boundserror(%4::Vector{Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}}, %16::Tuple{Int64})::Union{}
  └───       unreachable
  7 ┄─ %19 = Base.getfield(%4, :ref)::MemoryRef{Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}}
  │    %20 = Base.memoryrefnew(%19, %3, false)::MemoryRef{Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}}
  │    %21 = Base.memoryrefget(%20, :not_atomic, false)::Tuple{Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}, Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}}
  └───       goto #8
  8 ── %23 = Base.sub_int(%3, 1)::Int64
  │          Base.setfield!(%1, :position, %23)::Int64
  └───       goto #9
  9 ── %26 = (getfield)(%21, 1)::Mooncake.NfwdMooncake.Pullback{typeof(sin), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}
  │    %27 = (getfield)(%21, 2)::Mooncake.NfwdMooncake.Pullback{typeof(cos), 1, Tuple{Float64}, Tuple{Mooncake.NoFData}, Mooncake.NoFData}
  │    %28 = (getfield)(%21, 3)::Mooncake.IntrinsicsWrappers.var"#mul_float_pb!!#12"{Float64, Float64}
  │    %29 = Base.add_float($(QuoteNode(0.0)), _2)::Float64
  │    %30 = Core.getfield(%28, :_b)::Float64
  │    %31 = Base.mul_float(%29, %30)::Float64
  │    %32 = Core.getfield(%28, :_a)::Float64
  │    %33 = Base.mul_float(%32, %29)::Float64
  │    %34 = Base.add_float($(QuoteNode(0.0)), %31)::Float64
  │    %35 = Base.add_float($(QuoteNode(0.0)), %33)::Float64
  │    %36 = Base.getfield(%27, :primals)::Tuple{Float64}
  │    %37 = $(Expr(:boundscheck, true))::Bool
  │    %38 = Base.getfield(%36, 1, %37)::Float64
  │    %39 = invoke Mooncake.Nfwd.sincos(%38::Float64)::Tuple{Float64, Float64}
  │    %40 = Base.getfield(%39, 1)::Float64
  │    %41 = Base.neg_float(%40)::Float64
  │    %42 = Base.mul_float(%41, 1.0)::Float64
  │    %43 = Base.eq_float(%35, 0.0)::Bool
  │    %44 = Core.ifelse(%43, 0.0, %42)::Float64
  │    %45 = Base.mul_float(%35, %44)::Float64
  └───       goto #10
  10 ─       goto #11
  11 ─       goto #12
  12 ─ %49 = Base.add_float(0.0, %45)::Float64
  └───       goto #13
  13 ─       goto #14
  14 ─       goto #15
  15 ─ %53 = Base.add_float($(QuoteNode(0.0)), %49)::Float64
  │    %54 = Base.getfield(%26, :primals)::Tuple{Float64}
  │    %55 = $(Expr(:boundscheck, true))::Bool
  │    %56 = Base.getfield(%54, 1, %55)::Float64
  │    %57 = invoke Mooncake.Nfwd.sincos(%56::Float64)::Tuple{Float64, Float64}
  │    %58 = Base.getfield(%57, 2)::Float64
  │    %59 = Base.mul_float(%58, 1.0)::Float64
  │    %60 = Base.eq_float(%34, 0.0)::Bool
  │    %61 = Core.ifelse(%60, 0.0, %59)::Float64
  │    %62 = Base.mul_float(%34, %61)::Float64
  └───       goto #16
  16 ─       goto #17
  17 ─       goto #18
  18 ─ %66 = Base.add_float(0.0, %62)::Float64
  └───       goto #19
  19 ─       goto #20
  20 ─       goto #21
  21 ─ %70 = Base.add_float(%53, %66)::Float64
  22 ─       goto #23
  23 ─ %72 = Base.add_float(%70, 0.0)::Float64
  │    %73 = (tuple)($(QuoteNode(Mooncake.NoRData())), %72)::Tuple{Mooncake.NoRData, Float64}
  └───       return %73
show_stage(ins, :raw)                     # one stage
============================================================
IR Inspection: Tuple{typeof(Main.demo_fn), Float64}
Mode: reverse, World: 26965
============================================================

----------------------------------------
Stage: raw
Blocks: 1, Insts: 4, Edges: 0
----------------------------------------
3 1 ─ %1 = invoke Main.sin(_2::Float64)::Float64
  │   %2 = invoke Main.cos(_2::Float64)::Float64
  │   %3 = Base.mul_float(%1, %2)::Float64
  └──      return %3
show_diff(ins; from=:raw, to=:normalized) # diff between stages
============================================================
Diff: raw → normalized
============================================================
--- stage1
+++ stage2
-  │   %3 = Base.mul_float(%1, %2)::Float64
+  │   %3 = (Mooncake.IntrinsicsWrappers.mul_float)(%1, %2)::Float64
# Forward mode
ins = inspect_ir(demo_fn, 1.0; mode=:forward)

# World age info (useful for debugging stale code)
show_world_info(ins)
============================================================
World Age Report
============================================================
Inspection world: 26965

Stage worlds:
  bbcode: N/A
  dual_ir: N/A
  normalized: N/A
  raw: N/A
  optimized: N/A

Reverse mode stages

:raw:normalized:bbcode:fwd_ir / :rvs_ir:optimized_fwd / :optimized_rvs

Forward mode stages

:raw:normalized:dual_ir:optimized

Note

The inspection tool also shows a :bbcode stage for cross-mode comparison, but forward mode does not use BBCode internally.

Note

Primitive signatures such as sin do not generate AD IR stages here. Mooncake dispatches those calls straight to build_primitive_frule / build_primitive_rrule, so inspect_ir reports that path in notes instead of forcing derived IR generation.

When something looks wrong in generated code, diff consecutive stages to find which transformation introduced the issue.

Allocations

Unexpected allocations are the most common performance issue. Diagnostic approach:

  1. @code_typed optimize=true – check return types are concrete (no Union or Any).
  2. @code_llvm debuginfo=:none – search for gc_pool_alloc, gc_alloc_obj, jl_box_*, or jl_apply_generic.
  3. Base.specializations – verify whether the runtime MethodInstance matches the concrete types you expect (see warning below).
Warning

@code_typed f(args...) creates a fresh, fully-concrete specialization on demand. If the issue is specialization widening, it will show optimized code while the runtime path uses a widened MethodInstance. Cross-check with Base.specializations(method).

World age

World age issues arise when compiled code references methods from a different world than expected. Symptoms include MethodError at runtime or stale compiled code not picking up new method definitions.

In Mooncake, this most commonly affects:

  • DerivedRule / DerivedFRule: compiled at a fixed world, can become stale if methods they depend on are redefined.
  • LazyDerivedRule: compiles on first call (static dispatch via :invoke), obtaining a fresh interpreter at that point.
  • DynamicDerivedRule: resolves per-call by runtime argument types (dynamic dispatch via :call), checking a cache and obtaining a fresh interpreter on miss.

To debug, inspect the world age of generated code:

ins = inspect_ir(demo_fn, 1.0)
show_world_info(ins)
============================================================
World Age Report
============================================================
Inspection world: 26965

Stage worlds:
  bbcode: N/A
  optimized_fwd: N/A
  optimized_rvs: N/A
  normalized: N/A
  fwd_ir: N/A
  raw: N/A
  rvs_ir: N/A

This reports the world at which each stage was compiled and flags mismatches.