Testing GC.@preserve when doing compiler passes

I’m looking to extend Umlaut.jl’s tracing to handle the :gc_preserve_begin and :gc_preserve_end statements that can be found in Julia’s IR. For example, you will find these if you use GC.@preserve:

foo(x) = GC.@preserve x 5x

@code_warntype foo(5.0)
MethodInstance for foo(::Float64)
  from foo(x) @ Main REPL[2]:1
Arguments
  #self#::Core.Const(foo)
  x::Float64
Body::Float64
1 ─ %1 = $(Expr(:gc_preserve_begin, :(x)))
│   %2 = (5 * x)::Float64
│        $(Expr(:gc_preserve_end, :(%1)))
└──      return %2

I’d basically really like to know how to construct a test which reliably fails if GC.@preserve is not used as, if I have such a test, I can verify whether or not Umlaut is doing the right thing when it encounters a situation where GC.@preserve is necessary for correctness. Does anyone have any ideas?

Something like this maybe:

mutable struct Object
    finalized::Bool
    @noinline function Object()
        finalizer(new(false)) do obj
            obj.finalized = true
        end
    end
end

function main()
    obj = Object()
    # GC.@preserve obj begin
        ptr = convert(Ptr{Bool}, Base.pointer_from_objref(obj))
        GC.gc(true)
        unsafe_load(ptr)
    # end
end

Seems to reliably return true resp. false depending on whether the GC.@preserve is used, at least on 1.9.

2 Likes

Thanks for the help here @maleadt – seems to be working well.