From 4df6e78e50d8e0e1f37f5bad7a67e5d20016a33d Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 12:10:25 +0200 Subject: [PATCH 01/21] Add rudimentary leak check mechanism. --- src/LLVM.jl | 5 +++++ src/debug.jl | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/debug.jl diff --git a/src/LLVM.jl b/src/LLVM.jl index 70552371a..af961a148 100644 --- a/src/LLVM.jl +++ b/src/LLVM.jl @@ -1,5 +1,6 @@ module LLVM +using Preferences using Unicode using Printf using Libdl @@ -59,6 +60,9 @@ has_oldpm() = LLVM.version() < v"17" has_newpm() = LLVM.version() >= v"15" has_julia_ojit() = VERSION >= v"1.10.0-DEV.1395" +# helpers +include("debug.jl") + # LLVM API wrappers include("support.jl") if LLVM.version() < v"17" @@ -133,6 +137,7 @@ function __init__() _install_handlers() _install_handlers(GlobalContext()) + atexit(check_leaks) end end diff --git a/src/debug.jl b/src/debug.jl new file mode 100644 index 000000000..bb1cb248f --- /dev/null +++ b/src/debug.jl @@ -0,0 +1,30 @@ +function leakcheck_enabled() + parse(Bool, @load_preference("leakcheck", "false")) +end + +const tracked_objects = Dict{Any,Any}() + +function mark_alloc(obj::Any) + @static leakcheck_enabled() || return + tracked_objects[obj] = backtrace()[3:end] +end + +function mark_dispose(obj) + @static leakcheck_enabled() || return + delete!(tracked_objects, obj) +end + +function check_leaks(code) + @static leakcheck_enabled() || return + if code != 0 + # if we errorred, we don't care about leaks + return + end + + io = Core.stdout + for (obj, bt) in tracked_objects + print(io, "WARNING: Object $obj was not properly disposed of.") + Base.show_backtrace(io, bt) + println(io) + end +end From b374ef6632f3758d59d98c73adf98ed9fa890777 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 12:10:46 +0200 Subject: [PATCH 02/21] Fix TargetMachine leaks. --- src/orc.jl | 1 + src/targetmachine.jl | 22 ++++++++++++++-------- test/jljit_tests.jl | 15 ++++++++------- test/orc_tests.jl | 17 +++++++++-------- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/orc.jl b/src/orc.jl index 24f7bc431..77735abd7 100644 --- a/src/orc.jl +++ b/src/orc.jl @@ -21,6 +21,7 @@ end function TargetMachineBuilder(tm::TargetMachine) tmb = API.LLVMOrcJITTargetMachineBuilderCreateFromTargetMachine(tm) + mark_dispose(tm) TargetMachineBuilder(tmb) end diff --git a/src/targetmachine.jl b/src/targetmachine.jl index d93db1f87..b081770ac 100644 --- a/src/targetmachine.jl +++ b/src/targetmachine.jl @@ -11,14 +11,20 @@ end Base.unsafe_convert(::Type{API.LLVMTargetMachineRef}, tm::TargetMachine) = tm.ref -TargetMachine(t::Target, triple::String, cpu::String="", features::String=""; - optlevel::API.LLVMCodeGenOptLevel=API.LLVMCodeGenLevelDefault, - reloc::API.LLVMRelocMode=API.LLVMRelocDefault, - code::API.LLVMCodeModel=API.LLVMCodeModelDefault) = - TargetMachine(API.LLVMCreateTargetMachine(t, triple, cpu, features, optlevel, - reloc, code)) - -dispose(tm::TargetMachine) = API.LLVMDisposeTargetMachine(tm) +function TargetMachine(t::Target, triple::String, cpu::String="", features::String=""; + optlevel::API.LLVMCodeGenOptLevel=API.LLVMCodeGenLevelDefault, + reloc::API.LLVMRelocMode=API.LLVMRelocDefault, + code::API.LLVMCodeModel=API.LLVMCodeModelDefault) + obj = TargetMachine(API.LLVMCreateTargetMachine(t, triple, cpu, features, optlevel, + reloc, code)) + mark_alloc(obj) + return obj +end + +function dispose(tm::TargetMachine) + API.LLVMDisposeTargetMachine(tm) + mark_dispose(tm) +end function TargetMachine(f::Core.Function, args...; kwargs...) tm = TargetMachine(args...; kwargs...) diff --git a/test/jljit_tests.jl b/test/jljit_tests.jl index 30676cc22..1fbea510e 100644 --- a/test/jljit_tests.jl +++ b/test/jljit_tests.jl @@ -78,10 +78,9 @@ end ret!(builder, tmp) end - # TODO: Get TM from jljit? - tm = JITTargetMachine() triple!(mod, triple(jljit)) - @dispose pm=ModulePassManager() begin + @dispose pm=ModulePassManager() tm=JITTargetMachine() begin + # TODO: Get TM from jljit? add_library_info!(pm, triple(mod)) add_transform_info!(pm, tm) run!(pm, mod) @@ -117,8 +116,9 @@ end end verify(mod) - tm = JITTargetMachine() - emit(tm, mod, LLVM.API.LLVMObjectFile) + @dispose tm=JITTargetMachine() begin + emit(tm, mod, LLVM.API.LLVMObjectFile) + end end add!(jljit, jd, MemoryBuffer(obj)) @@ -150,8 +150,9 @@ end end verify(mod) - tm = JITTargetMachine() - emit(tm, mod, LLVM.API.LLVMObjectFile) + @dispose tm=JITTargetMachine() begin + emit(tm, mod, LLVM.API.LLVMObjectFile) + end end data = Ref{Int32}(42) diff --git a/test/orc_tests.jl b/test/orc_tests.jl index 543ad36b2..f10978e7a 100644 --- a/test/orc_tests.jl +++ b/test/orc_tests.jl @@ -78,10 +78,9 @@ end ret!(builder, tmp) end - # TODO: Get TM from lljit? - tm = JITTargetMachine() triple!(mod, triple(lljit)) - @dispose pm=ModulePassManager() begin + @dispose pm=ModulePassManager() tm=JITTargetMachine() begin + # TODO: Get TM from lljit? add_library_info!(pm, triple(mod)) add_transform_info!(pm, tm) run!(pm, mod) @@ -115,8 +114,9 @@ end end verify(mod) - tm = JITTargetMachine() - emit(tm, mod, LLVM.API.LLVMObjectFile) + @dispose tm=JITTargetMachine() begin + emit(tm, mod, LLVM.API.LLVMObjectFile) + end end add!(lljit, jd, MemoryBuffer(obj)) @@ -128,7 +128,7 @@ end @test_throws LLVMException lookup(lljit, sym) end - @dispose lljit=LLJIT(;tm=JITTargetMachine()) begin + @dispose lljit=LLJIT(; tm=JITTargetMachine()) begin jd = JITDylib(lljit) sym = "SomeFunction" @@ -148,8 +148,9 @@ end end verify(mod) - tm = JITTargetMachine() - emit(tm, mod, LLVM.API.LLVMObjectFile) + @dispose tm=JITTargetMachine() begin + emit(tm, mod, LLVM.API.LLVMObjectFile) + end end data = Ref{Int32}(42) From 504620f2fd3f3bb695089bcf60156ff2531afad4 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 13:05:37 +0200 Subject: [PATCH 03/21] Fix Module leaks. --- examples/constrained.jl | 1 + src/core/module.jl | 24 +++++++++++++++--------- src/core/value/constant.jl | 2 +- src/execution.jl | 7 +++++-- src/executionengine/ts_module.jl | 1 + src/interop/base.jl | 1 + src/linker.jl | 2 ++ src/moduleprovider.jl | 11 ++++++++--- test/core_tests.jl | 8 +++----- test/execution_tests.jl | 4 +--- test/jljit_tests.jl | 6 ++---- test/linker_tests.jl | 1 + test/orc_tests.jl | 6 ++---- test/util_tests.jl | 4 +--- 14 files changed, 44 insertions(+), 34 deletions(-) diff --git a/examples/constrained.jl b/examples/constrained.jl index 2a436994a..777ea7f65 100644 --- a/examples/constrained.jl +++ b/examples/constrained.jl @@ -44,6 +44,7 @@ meta(::Type{FPExceptStrict}) = "fpexcept.strict" intrinsic = Intrinsic("llvm.experimental.constrained.$(func(F))") intrinsic_fun = LLVM.Function(mod, intrinsic, [typ]) ftype = LLVM.FunctionType(intrinsic,[typ]) + # generate IR @dispose builder=IRBuilder() begin entry = BasicBlock(llvm_f, "entry") diff --git a/src/core/module.jl b/src/core/module.jl index 1880e0904..025653375 100644 --- a/src/core/module.jl +++ b/src/core/module.jl @@ -9,11 +9,7 @@ export dispose, context, # forward definition of Module in src/core/value/constant.jl -function Base.unsafe_convert(::Type{API.LLVMModuleRef}, mod::Module) - # modules can get destroyed, so be sure to check for validity - mod.ref == C_NULL && throw(UndefRefError()) - mod.ref -end +Base.unsafe_convert(::Type{API.LLVMModuleRef}, mod::Module) = mod.ref Base.:(==)(x::Module, y::Module) = (x.ref === y.ref) @@ -22,13 +18,23 @@ Base.:(==)(x::Module, y::Module) = (x.ref === y.ref) ref::API.LLVMTargetDataRef end -Module(name::String) = - Module(API.LLVMModuleCreateWithNameInContext(name, context())) +function Module(name::String) + mod = Module(API.LLVMModuleCreateWithNameInContext(name, context())) + mark_alloc(mod) + return mod +end -Module(mod::Module) = Module(API.LLVMCloneModule(mod)) +function Module(mod::Module) + mod = Module(API.LLVMCloneModule(mod)) + mark_alloc(mod) + return mod +end Base.copy(mod::Module) = Module(mod) -dispose(mod::Module) = API.LLVMDisposeModule(mod) +function dispose(mod::Module) + API.LLVMDisposeModule(mod) + mark_dispose(mod) +end function Module(f::Core.Function, args...; kwargs...) mod = Module(args...; kwargs...) diff --git a/src/core/value/constant.jl b/src/core/value/constant.jl index af1aa1a89..4308317e3 100644 --- a/src/core/value/constant.jl +++ b/src/core/value/constant.jl @@ -12,7 +12,7 @@ abstract type Constant <: User end unsafe_destroy!(constant::Constant) = API.LLVMDestroyConstant(constant) # forward declarations -@checked mutable struct Module +@checked struct Module ref::API.LLVMModuleRef end abstract type Instruction <: User end diff --git a/src/execution.jl b/src/execution.jl index d01fd21e6..5d0d1e224 100644 --- a/src/execution.jl +++ b/src/execution.jl @@ -104,7 +104,7 @@ end function dispose(engine::ExecutionEngine) for mod in engine.mods - mod.ref = C_NULL + mark_dispose(mod) end API.LLVMDisposeExecutionEngine(engine) end @@ -120,7 +120,10 @@ for x in [:ExecutionEngine, :Interpreter, :JIT] end end -Base.push!(engine::ExecutionEngine, mod::Module) = API.LLVMAddModule(engine.ref, mod.ref) +function Base.push!(engine::ExecutionEngine, mod::Module) + push!(engine.mods, mod) + API.LLVMAddModule(engine.ref, mod.ref) +end # up to user to free the deleted module function Base.delete!(engine::ExecutionEngine, mod::Module) diff --git a/src/executionengine/ts_module.jl b/src/executionengine/ts_module.jl index fa6799924..cad35daff 100644 --- a/src/executionengine/ts_module.jl +++ b/src/executionengine/ts_module.jl @@ -48,6 +48,7 @@ function ThreadSafeModule(name::String) mod = context!(ctx) do Module(name) end + mark_dispose(mod) ThreadSafeModule(mod) end diff --git a/src/interop/base.jl b/src/interop/base.jl index 1fbb345de..2c876edf9 100644 --- a/src/interop/base.jl +++ b/src/interop/base.jl @@ -33,6 +33,7 @@ function call_function(llvmf::LLVM.Function, rettyp::Type=Nothing, argtyp::Type= ir = string(mod) fn = LLVM.name(llvmf) @assert !isempty(fn) + dispose(mod) quote Base.@inline Base.llvmcall(($ir,$fn), $rettyp, $argtyp, $(args...)) diff --git a/src/linker.jl b/src/linker.jl index 413650e14..3b290eafe 100644 --- a/src/linker.jl +++ b/src/linker.jl @@ -4,5 +4,7 @@ function link!(dst::Module, src::Module) status = API.LLVMLinkModules2(dst, src) |> Bool @assert !status # caught by diagnostics handler + mark_dispose(src) + return nothing end diff --git a/src/moduleprovider.jl b/src/moduleprovider.jl index 5a2da9a6a..eebdcf185 100644 --- a/src/moduleprovider.jl +++ b/src/moduleprovider.jl @@ -5,12 +5,13 @@ export ModuleProvider, dispose @checked struct ModuleProvider ref::API.LLVMModuleProviderRef + mod::Module end Base.unsafe_convert(::Type{API.LLVMModuleProviderRef}, mp::ModuleProvider) = mp.ref ModuleProvider(mod::Module) = - ModuleProvider(API.LLVMCreateModuleProviderForExistingModule(mod)) + ModuleProvider(API.LLVMCreateModuleProviderForExistingModule(mod), mod) function ModuleProvider(f::Core.Function, args...; kwargs...) mp = ModuleProvider(args...; kwargs...) @@ -21,5 +22,9 @@ function ModuleProvider(f::Core.Function, args...; kwargs...) end end -# NOTE: this destroys the underlying module -dispose(mp::ModuleProvider) = API.LLVMDisposeModuleProvider(mp) +function dispose(mp::ModuleProvider) + API.LLVMDisposeModuleProvider(mp) + + # NOTE: this destroys the underlying module + mark_dispose(mp.mod) +end diff --git a/test/core_tests.jl b/test/core_tests.jl index 8442d5b69..57007f70e 100644 --- a/test/core_tests.jl +++ b/test/core_tests.jl @@ -882,8 +882,7 @@ end @test convert(ConstantInt, val) == int end -@dispose ctx=Context() begin - mod = LLVM.Module("SomeModule") +@dispose ctx=Context() mod=LLVM.Module("SomeModule") begin ft = LLVM.FunctionType(LLVM.VoidType()) f1 = LLVM.Function(mod, "f1", ft) @@ -896,8 +895,7 @@ end end # different type; requires a hack -@dispose ctx=Context() begin - mod = LLVM.Module("SomeModule") +@dispose ctx=Context() mod=LLVM.Module("SomeModule") begin ft1 = LLVM.FunctionType(LLVM.VoidType()) f1 = LLVM.Function(mod, "f1", ft1) @@ -1017,7 +1015,7 @@ end @testset "module" begin @dispose ctx=Context() begin - let mod = LLVM.Module("SomeModule") + @dispose mod=LLVM.Module("SomeModule") begin @test context(mod) == ctx @test name(mod) == "SomeModule" diff --git a/test/execution_tests.jl b/test/execution_tests.jl index 6d9e3c5a5..aa4705272 100644 --- a/test/execution_tests.jl +++ b/test/execution_tests.jl @@ -154,9 +154,9 @@ end @test convert(Int, res) == 3 dispose(res) end - @test_throws UndefRefError show(mod) end + dispose(mod) dispose.(args) end @@ -178,7 +178,6 @@ end @test convert(Int, res) == 42 dispose(res) end - @test_throws UndefRefError show(mod) end end @@ -200,7 +199,6 @@ end @test convert(Int, res) == 42 dispose(res) end - @test_throws UndefRefError show(mod) end end diff --git a/test/jljit_tests.jl b/test/jljit_tests.jl index 1fbea510e..8a27e5895 100644 --- a/test/jljit_tests.jl +++ b/test/jljit_tests.jl @@ -104,8 +104,7 @@ end jd = JITDylib(jljit) sym = "SomeFunction" - obj = @dispose ctx=Context() begin - mod = LLVM.Module("jit") + obj = @dispose ctx=Context() mod=LLVM.Module("jit") begin ft = LLVM.FunctionType(LLVM.VoidType()) fn = LLVM.Function(mod, sym, ft) @@ -134,8 +133,7 @@ end jd = JITDylib(jljit) sym = "SomeFunction" - obj = @dispose ctx=Context() begin - mod = LLVM.Module("jit") + obj = @dispose ctx=Context() mod=LLVM.Module("jit") begin ft = LLVM.FunctionType(LLVM.Int32Type()) fn = LLVM.Function(mod, sym, ft) diff --git a/test/linker_tests.jl b/test/linker_tests.jl index a4cd40661..2d04124c0 100644 --- a/test/linker_tests.jl +++ b/test/linker_tests.jl @@ -30,6 +30,7 @@ link!(mod1, mod2) @test haskey(functions(mod1), "SomeFunction") @test haskey(functions(mod1), "SomeOtherFunction") + dispose(mod1) end end diff --git a/test/orc_tests.jl b/test/orc_tests.jl index f10978e7a..fefc147cb 100644 --- a/test/orc_tests.jl +++ b/test/orc_tests.jl @@ -102,8 +102,7 @@ end jd = JITDylib(lljit) sym = "SomeFunction" - obj = @dispose ctx=Context() begin - mod = LLVM.Module("jit") + obj = @dispose ctx=Context() mod=LLVM.Module("jit") begin ft = LLVM.FunctionType(LLVM.VoidType()) fn = LLVM.Function(mod, sym, ft) @@ -132,8 +131,7 @@ end jd = JITDylib(lljit) sym = "SomeFunction" - obj = @dispose ctx=Context() begin - mod = LLVM.Module("jit") + obj = @dispose ctx=Context() mod=LLVM.Module("jit") begin ft = LLVM.FunctionType(LLVM.Int32Type()) fn = LLVM.Function(mod, sym, ft) diff --git a/test/util_tests.jl b/test/util_tests.jl index 089f29ae0..23690132a 100644 --- a/test/util_tests.jl +++ b/test/util_tests.jl @@ -1,10 +1,8 @@ @testitem "utils" begin @testset "function cloning" begin - @dispose ctx=Context() begin + @dispose ctx=Context() mod=LLVM.Module("my_module") begin # set-up - mod = LLVM.Module("my_module") - param_types = [LLVM.Int32Type(), LLVM.Int32Type()] ret_type = LLVM.Int32Type() fun_type = LLVM.FunctionType(ret_type, param_types) From 3e43dc99b6b37faaacd527c6efb19d51a13402b2 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 13:09:22 +0200 Subject: [PATCH 04/21] Rename to memcheck. --- src/LLVM.jl | 2 +- src/debug.jl | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/LLVM.jl b/src/LLVM.jl index af961a148..1bcc6079c 100644 --- a/src/LLVM.jl +++ b/src/LLVM.jl @@ -137,7 +137,7 @@ function __init__() _install_handlers() _install_handlers(GlobalContext()) - atexit(check_leaks) + atexit(check_memory) end end diff --git a/src/debug.jl b/src/debug.jl index bb1cb248f..21b5dfa61 100644 --- a/src/debug.jl +++ b/src/debug.jl @@ -1,21 +1,25 @@ -function leakcheck_enabled() - parse(Bool, @load_preference("leakcheck", "false")) -end +## memcheck: keeping track of allocations and disposals const tracked_objects = Dict{Any,Any}() +function memcheck_enabled() + parse(Bool, @load_preference("memcheck", "false")) +end + function mark_alloc(obj::Any) - @static leakcheck_enabled() || return + @static memcheck_enabled() || return obj tracked_objects[obj] = backtrace()[3:end] + return obj end function mark_dispose(obj) - @static leakcheck_enabled() || return + @static memcheck_enabled() || return obj delete!(tracked_objects, obj) + return obj end -function check_leaks(code) - @static leakcheck_enabled() || return +function check_memory(code) + @static memcheck_enabled() || return if code != 0 # if we errorred, we don't care about leaks return @@ -23,7 +27,7 @@ function check_leaks(code) io = Core.stdout for (obj, bt) in tracked_objects - print(io, "WARNING: Object $obj was not properly disposed of.") + print(io, "WARNING: An instance of $(typeof(obj)) was not properly disposed of.") Base.show_backtrace(io, bt) println(io) end From 1de182bc8666025cab87b04f3f14174eaa9ab030 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 13:09:35 +0200 Subject: [PATCH 05/21] Detect MemoryBuffer leaks. --- src/buffer.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/buffer.jl b/src/buffer.jl index 07c87f1cf..3e626417e 100644 --- a/src/buffer.jl +++ b/src/buffer.jl @@ -9,11 +9,12 @@ Base.unsafe_convert(::Type{API.LLVMMemoryBufferRef}, membuf::MemoryBuffer) = mem function MemoryBuffer(data::Vector{T}, name::String="", copy::Bool=true) where T<:Union{UInt8,Int8} ptr = pointer(data) len = Csize_t(length(data)) - if copy - return MemoryBuffer(API.LLVMCreateMemoryBufferWithMemoryRangeCopy(ptr, len, name)) + membuf = if copy + MemoryBuffer(API.LLVMCreateMemoryBufferWithMemoryRangeCopy(ptr, len, name)) else - return MemoryBuffer(API.LLVMCreateMemoryBufferWithMemoryRange(ptr, len, name, false)) + MemoryBuffer(API.LLVMCreateMemoryBufferWithMemoryRange(ptr, len, name, false)) end + mark_alloc(membuf) end function MemoryBuffer(f::Core.Function, args...; kwargs...) @@ -48,7 +49,7 @@ function MemoryBufferFile(f::Core.Function, args...; kwargs...) end end -dispose(membuf::MemoryBuffer) = API.LLVMDisposeMemoryBuffer(membuf) +dispose(membuf::MemoryBuffer) = API.LLVMDisposeMemoryBuffer(mark_dispose(membuf)) Base.length(membuf::MemoryBuffer) = API.LLVMGetBufferSize(membuf) From 1f4623d837749ef48e26f9ba36dab28810b45582 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 13:13:20 +0200 Subject: [PATCH 06/21] Shorten. --- src/core/module.jl | 18 ++++-------------- src/execution.jl | 4 +--- src/executionengine/ts_module.jl | 3 +-- src/linker.jl | 4 +--- src/moduleprovider.jl | 4 ++-- src/orc.jl | 3 +-- src/targetmachine.jl | 22 ++++++++-------------- 7 files changed, 18 insertions(+), 40 deletions(-) diff --git a/src/core/module.jl b/src/core/module.jl index 025653375..8b13f96e0 100644 --- a/src/core/module.jl +++ b/src/core/module.jl @@ -18,23 +18,13 @@ Base.:(==)(x::Module, y::Module) = (x.ref === y.ref) ref::API.LLVMTargetDataRef end -function Module(name::String) - mod = Module(API.LLVMModuleCreateWithNameInContext(name, context())) - mark_alloc(mod) - return mod -end +Module(name::String) = + mark_alloc(Module(API.LLVMModuleCreateWithNameInContext(name, context()))) -function Module(mod::Module) - mod = Module(API.LLVMCloneModule(mod)) - mark_alloc(mod) - return mod -end +Module(mod::Module) = mark_alloc(Module(API.LLVMCloneModule(mod))) Base.copy(mod::Module) = Module(mod) -function dispose(mod::Module) - API.LLVMDisposeModule(mod) - mark_dispose(mod) -end +dispose(mod::Module) = API.LLVMDisposeModule(mark_dispose(mod)) function Module(f::Core.Function, args...; kwargs...) mod = Module(args...; kwargs...) diff --git a/src/execution.jl b/src/execution.jl index 5d0d1e224..3d085ea70 100644 --- a/src/execution.jl +++ b/src/execution.jl @@ -103,9 +103,7 @@ function JIT(mod::Module, optlevel::API.LLVMCodeGenOptLevel=API.LLVMCodeGenLevel end function dispose(engine::ExecutionEngine) - for mod in engine.mods - mark_dispose(mod) - end + mark_dispose.(engine.mods) API.LLVMDisposeExecutionEngine(engine) end diff --git a/src/executionengine/ts_module.jl b/src/executionengine/ts_module.jl index cad35daff..fc7a3daba 100644 --- a/src/executionengine/ts_module.jl +++ b/src/executionengine/ts_module.jl @@ -48,8 +48,7 @@ function ThreadSafeModule(name::String) mod = context!(ctx) do Module(name) end - mark_dispose(mod) - ThreadSafeModule(mod) + ThreadSafeModule(mark_dispose(mod)) end function dispose(mod::ThreadSafeModule) diff --git a/src/linker.jl b/src/linker.jl index 3b290eafe..cef66491e 100644 --- a/src/linker.jl +++ b/src/linker.jl @@ -1,10 +1,8 @@ export link! function link!(dst::Module, src::Module) - status = API.LLVMLinkModules2(dst, src) |> Bool + status = API.LLVMLinkModules2(dst, mark_dispose(src)) |> Bool @assert !status # caught by diagnostics handler - mark_dispose(src) - return nothing end diff --git a/src/moduleprovider.jl b/src/moduleprovider.jl index eebdcf185..3a41f0820 100644 --- a/src/moduleprovider.jl +++ b/src/moduleprovider.jl @@ -23,8 +23,8 @@ function ModuleProvider(f::Core.Function, args...; kwargs...) end function dispose(mp::ModuleProvider) - API.LLVMDisposeModuleProvider(mp) - # NOTE: this destroys the underlying module mark_dispose(mp.mod) + + API.LLVMDisposeModuleProvider(mp) end diff --git a/src/orc.jl b/src/orc.jl index 77735abd7..7b490c458 100644 --- a/src/orc.jl +++ b/src/orc.jl @@ -20,8 +20,7 @@ function TargetMachineBuilder() end function TargetMachineBuilder(tm::TargetMachine) - tmb = API.LLVMOrcJITTargetMachineBuilderCreateFromTargetMachine(tm) - mark_dispose(tm) + tmb = API.LLVMOrcJITTargetMachineBuilderCreateFromTargetMachine(mark_dispose(tm)) TargetMachineBuilder(tmb) end diff --git a/src/targetmachine.jl b/src/targetmachine.jl index b081770ac..995271f33 100644 --- a/src/targetmachine.jl +++ b/src/targetmachine.jl @@ -11,20 +11,14 @@ end Base.unsafe_convert(::Type{API.LLVMTargetMachineRef}, tm::TargetMachine) = tm.ref -function TargetMachine(t::Target, triple::String, cpu::String="", features::String=""; - optlevel::API.LLVMCodeGenOptLevel=API.LLVMCodeGenLevelDefault, - reloc::API.LLVMRelocMode=API.LLVMRelocDefault, - code::API.LLVMCodeModel=API.LLVMCodeModelDefault) - obj = TargetMachine(API.LLVMCreateTargetMachine(t, triple, cpu, features, optlevel, - reloc, code)) - mark_alloc(obj) - return obj -end - -function dispose(tm::TargetMachine) - API.LLVMDisposeTargetMachine(tm) - mark_dispose(tm) -end +TargetMachine(t::Target, triple::String, cpu::String="", features::String=""; + optlevel::API.LLVMCodeGenOptLevel=API.LLVMCodeGenLevelDefault, + reloc::API.LLVMRelocMode=API.LLVMRelocDefault, + code::API.LLVMCodeModel=API.LLVMCodeModelDefault) = + mark_alloc(TargetMachine(API.LLVMCreateTargetMachine(t, triple, cpu, features, optlevel, + reloc, code))) + +dispose(tm::TargetMachine) = API.LLVMDisposeTargetMachine(mark_dispose(tm)) function TargetMachine(f::Core.Function, args...; kwargs...) tm = TargetMachine(args...; kwargs...) From c82d39a0255bd21636514ea11e616d2f42728f31 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 13:14:42 +0200 Subject: [PATCH 07/21] Detect PassManager leaks. --- src/passmanager.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/passmanager.jl b/src/passmanager.jl index 3b2e498e7..49ee4d7dd 100644 --- a/src/passmanager.jl +++ b/src/passmanager.jl @@ -11,7 +11,7 @@ function add!(pm::PassManager, pass::Pass) API.LLVMAddPass(pm, pass) end -dispose(pm::PassManager) = API.LLVMDisposePassManager(pm) +dispose(pm::PassManager) = API.LLVMDisposePassManager(mark_dispose(pm)) # @@ -25,7 +25,7 @@ export ModulePassManager, run! roots::Vector{Any} end -ModulePassManager() = ModulePassManager(API.LLVMCreatePassManager(), []) +ModulePassManager() = mark_alloc(ModulePassManager(API.LLVMCreatePassManager(), [])) function ModulePassManager(f::Core.Function, args...; kwargs...) mpm = ModulePassManager(args...; kwargs...) @@ -53,7 +53,7 @@ export FunctionPassManager, end FunctionPassManager(mod::Module) = - FunctionPassManager(API.LLVMCreateFunctionPassManagerForModule(mod), []) + mark_alloc(FunctionPassManager(API.LLVMCreateFunctionPassManagerForModule(mod), [])) function FunctionPassManager(f::Core.Function, args...; kwargs...) fpm = FunctionPassManager(args...; kwargs...) From 6e6cb89db1cbd2ad056ee50d890a2c420658855a Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 13:16:49 +0200 Subject: [PATCH 08/21] Detect ExecutionEngine leaks. --- src/execution.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/execution.jl b/src/execution.jl index 3d085ea70..414522760 100644 --- a/src/execution.jl +++ b/src/execution.jl @@ -71,7 +71,7 @@ function ExecutionEngine(mod::Module) throw(LLVMException(error)) end - return ExecutionEngine(out_ref[], Set([mod])) + return mark_alloc(ExecutionEngine(out_ref[], Set([mod]))) end function Interpreter(mod::Module) API.LLVMLinkInInterpreter() @@ -85,7 +85,7 @@ function Interpreter(mod::Module) throw(LLVMException(error)) end - return ExecutionEngine(out_ref[], Set([mod])) + return mark_alloc(ExecutionEngine(out_ref[], Set([mod]))) end function JIT(mod::Module, optlevel::API.LLVMCodeGenOptLevel=API.LLVMCodeGenLevelDefault) API.LLVMLinkInMCJIT() @@ -99,12 +99,12 @@ function JIT(mod::Module, optlevel::API.LLVMCodeGenOptLevel=API.LLVMCodeGenLevel throw(LLVMException(error)) end - return ExecutionEngine(out_ref[], Set([mod])) + return mark_alloc(ExecutionEngine(out_ref[], Set([mod]))) end function dispose(engine::ExecutionEngine) mark_dispose.(engine.mods) - API.LLVMDisposeExecutionEngine(engine) + API.LLVMDisposeExecutionEngine(mark_dispose(engine)) end for x in [:ExecutionEngine, :Interpreter, :JIT] From a590599fa28d1f256d8ea78941d79d797d5fb17e Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 13:18:09 +0200 Subject: [PATCH 09/21] Detect GenericValue leaks. --- src/execution.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/execution.jl b/src/execution.jl index 414522760..fc7416a5b 100644 --- a/src/execution.jl +++ b/src/execution.jl @@ -13,14 +13,14 @@ end Base.unsafe_convert(::Type{API.LLVMGenericValueRef}, val::GenericValue) = val.ref GenericValue(typ::IntegerType, N::Signed) = - GenericValue( + mark_alloc(GenericValue( API.LLVMCreateGenericValueOfInt(typ, - reinterpret(Culonglong, convert(Int64, N)), true)) + reinterpret(Culonglong, convert(Int64, N)), true))) GenericValue(typ::IntegerType, N::Unsigned) = - GenericValue( + mark_alloc(GenericValue( API.LLVMCreateGenericValueOfInt(typ, - reinterpret(Culonglong, convert(UInt64, N)), false)) + reinterpret(Culonglong, convert(UInt64, N)), false))) intwidth(val::GenericValue) = API.LLVMGenericValueIntWidth(val) @@ -45,7 +45,7 @@ GenericValue(ptr::Ptr) = Base.convert(::Type{Ptr{T}}, val::GenericValue) where {T} = convert(Ptr{T}, API.LLVMGenericValueToPointer(val)) -dispose(val::GenericValue) = API.LLVMDisposeGenericValue(val) +dispose(val::GenericValue) = mark_dispose(API.LLVMDisposeGenericValue(val)) ## execution engine From 20aea1e08769f34d4cf589c5f85614741c62ec7a Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 13:21:48 +0200 Subject: [PATCH 10/21] Detect DataLayout leaks. --- src/datalayout.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/datalayout.jl b/src/datalayout.jl index 410f8c3d5..27b72e39e 100644 --- a/src/datalayout.jl +++ b/src/datalayout.jl @@ -10,9 +10,9 @@ export DataLayout, dispose, Base.unsafe_convert(::Type{API.LLVMTargetDataRef}, dl::DataLayout) = dl.ref -DataLayout(rep::String) = DataLayout(API.LLVMCreateTargetData(rep)) +DataLayout(rep::String) = mark_alloc(DataLayout(API.LLVMCreateTargetData(rep))) -DataLayout(tm::TargetMachine) = DataLayout(API.LLVMCreateTargetDataLayout(tm)) +DataLayout(tm::TargetMachine) = mark_alloc(DataLayout(API.LLVMCreateTargetDataLayout(tm))) function DataLayout(f::Core.Function, args...; kwargs...) data = DataLayout(args...; kwargs...) @@ -23,7 +23,7 @@ function DataLayout(f::Core.Function, args...; kwargs...) end end -dispose(data::DataLayout) = API.LLVMDisposeTargetData(data) +dispose(data::DataLayout) = mark_dispose(API.LLVMDisposeTargetData(data)) Base.string(data::DataLayout) = unsafe_message(API.LLVMCopyStringRepOfTargetData(data)) From 041248d884a8a0ec9ae6d5fb93a2de92306f6d4c Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 13:21:55 +0200 Subject: [PATCH 11/21] Detect ModuleProvider leaks. --- src/moduleprovider.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/moduleprovider.jl b/src/moduleprovider.jl index 3a41f0820..fb6642478 100644 --- a/src/moduleprovider.jl +++ b/src/moduleprovider.jl @@ -11,7 +11,7 @@ end Base.unsafe_convert(::Type{API.LLVMModuleProviderRef}, mp::ModuleProvider) = mp.ref ModuleProvider(mod::Module) = - ModuleProvider(API.LLVMCreateModuleProviderForExistingModule(mod), mod) + mark_alloc(ModuleProvider(API.LLVMCreateModuleProviderForExistingModule(mod), mod)) function ModuleProvider(f::Core.Function, args...; kwargs...) mp = ModuleProvider(args...; kwargs...) @@ -26,5 +26,5 @@ function dispose(mp::ModuleProvider) # NOTE: this destroys the underlying module mark_dispose(mp.mod) - API.LLVMDisposeModuleProvider(mp) + API.LLVMDisposeModuleProvider(mark_dispose(mp)) end From 62dc1403cdf3bd76f3f6445f7292f237c649a3a1 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 13:23:05 +0200 Subject: [PATCH 12/21] Detect analysis leaks. --- src/analysis.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/analysis.jl b/src/analysis.jl index f08f7c822..a51cd52ae 100644 --- a/src/analysis.jl +++ b/src/analysis.jl @@ -31,8 +31,8 @@ end Base.unsafe_convert(::Type{API.LLVMDominatorTreeRef}, domtree::DomTree) = domtree.ref -DomTree(f::Function) = DomTree(API.LLVMCreateDominatorTree(f)) -dispose(domtree::DomTree) = API.LLVMDisposeDominatorTree(domtree) +DomTree(f::Function) = mark_alloc(DomTree(API.LLVMCreateDominatorTree(f))) +dispose(domtree::DomTree) = mark_dispose(API.LLVMDisposeDominatorTree(domtree)) function dominates(domtree::DomTree, A::Instruction, B::Instruction) API.LLVMDominatorTreeInstructionDominates(domtree, A, B) |> Bool @@ -50,8 +50,9 @@ end Base.unsafe_convert(::Type{API.LLVMPostDominatorTreeRef}, postdomtree::PostDomTree) = postdomtree.ref -PostDomTree(f::Function) = PostDomTree(API.LLVMCreatePostDominatorTree(f)) -dispose(postdomtree::PostDomTree) = API.LLVMDisposePostDominatorTree(postdomtree) +PostDomTree(f::Function) = mark_alloc(PostDomTree(API.LLVMCreatePostDominatorTree(f))) +dispose(postdomtree::PostDomTree) = + mark_dispose(API.LLVMDisposePostDominatorTree(postdomtree)) function dominates(postdomtree::PostDomTree, A::Instruction, B::Instruction) API.LLVMPostDominatorTreeInstructionDominates(postdomtree, A, B) |> Bool From 306fc887eca07ac7aa2b11a83d40cbe03aa17999 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 13:25:29 +0200 Subject: [PATCH 13/21] Detect IRBuilder leaks. --- src/irbuilder.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/irbuilder.jl b/src/irbuilder.jl index 2b5000e63..b28cebf26 100644 --- a/src/irbuilder.jl +++ b/src/irbuilder.jl @@ -11,9 +11,9 @@ end Base.unsafe_convert(::Type{API.LLVMBuilderRef}, builder::IRBuilder) = builder.ref -IRBuilder() = IRBuilder(API.LLVMCreateBuilderInContext(context())) +IRBuilder() = mark_alloc(IRBuilder(API.LLVMCreateBuilderInContext(context()))) -dispose(builder::IRBuilder) = API.LLVMDisposeBuilder(builder) +dispose(builder::IRBuilder) = mark_dispose(API.LLVMDisposeBuilder(builder)) context(builder::IRBuilder) = Context(API.LLVMGetBuilderContext(builder)) From 470cb67598ec37ec1836d7a41ce2ed24b91e4574 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 13:27:02 +0200 Subject: [PATCH 14/21] Detect PassBuilder leaks. --- src/newpm.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/newpm.jl b/src/newpm.jl index f15056cc9..524ef2487 100644 --- a/src/newpm.jl +++ b/src/newpm.jl @@ -180,7 +180,7 @@ Base.unsafe_convert(::Type{API.LLVMPassBuilderOptionsRef}, pb::NewPMPassBuilder) function NewPMPassBuilder(; kwargs...) opts = API.LLVMCreatePassBuilderOptions() exts = API.LLVMCreatePassBuilderExtensions() - obj = NewPMPassBuilder(opts, exts, [], []) + obj = mark_alloc(NewPMPassBuilder(opts, exts, [], [])) for (name, value) in pairs(kwargs) if name == :verify_each @@ -214,6 +214,7 @@ function NewPMPassBuilder(; kwargs...) end function dispose(pb::NewPMPassBuilder) + mark_dispose(pb) API.LLVMDisposePassBuilderOptions(pb.opts) API.LLVMDisposePassBuilderExtensions(pb.exts) end From 82c8d5d1ffb9baad770e3837c92706b29355ad61 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 14:51:05 +0200 Subject: [PATCH 15/21] Convert to preferences. --- LocalPreferences.toml | 7 +++++++ src/core/instructions.jl | 2 +- src/core/metadata.jl | 2 +- src/core/type.jl | 2 +- src/core/value.jl | 2 +- src/debug.jl | 17 ++++++++++------- test/core_tests.jl | 4 ++-- test/runtests.jl | 6 ++---- 8 files changed, 25 insertions(+), 17 deletions(-) diff --git a/LocalPreferences.toml b/LocalPreferences.toml index ea77953f0..a3d2d9899 100644 --- a/LocalPreferences.toml +++ b/LocalPreferences.toml @@ -3,3 +3,10 @@ # but if you are using a custom version of LLVM you will need to provide your own, # e.g., by running `deps/build_local.jl`. #libLLVMExtra = "/path/to/libLLVMExtra.so" + +# whether to enable additional object type checking +#typecheck = "false" + +# whether to enable object memory checking. these are expensive checks that keep +# track of allocated objects, whether they are freed correctly, etc. +#memcheck = "false" diff --git a/src/core/instructions.jl b/src/core/instructions.jl index ffa148e29..0971a683a 100644 --- a/src/core/instructions.jl +++ b/src/core/instructions.jl @@ -18,7 +18,7 @@ end function refcheck(::Type{T}, ref::API.LLVMValueRef) where T<:Instruction ref==C_NULL && throw(UndefRefError()) - if Base.JLOptions().debug_level >= 2 + if typecheck_enabled T′ = identify(Instruction, ref) if T != T′ error("invalid conversion of $T′ instruction reference to $T") diff --git a/src/core/metadata.jl b/src/core/metadata.jl index e9de6037f..94e56483f 100644 --- a/src/core/metadata.jl +++ b/src/core/metadata.jl @@ -21,7 +21,7 @@ end function refcheck(::Type{T}, ref::API.LLVMMetadataRef) where T<:Metadata ref==C_NULL && throw(UndefRefError()) - if Base.JLOptions().debug_level >= 2 + if typecheck_enabled T′ = identify(Metadata, ref) if T != T′ error("invalid conversion of $T′ metadata reference to $T") diff --git a/src/core/type.jl b/src/core/type.jl index 23ba1c6b2..ace71f736 100644 --- a/src/core/type.jl +++ b/src/core/type.jl @@ -23,7 +23,7 @@ end function refcheck(::Type{T}, ref::API.LLVMTypeRef) where T<:LLVMType ref==C_NULL && throw(UndefRefError()) - if Base.JLOptions().debug_level >= 2 + if typecheck_enabled T′ = identify(LLVMType, ref) if T != T′ error("invalid conversion of $T′ type reference to $T") diff --git a/src/core/value.jl b/src/core/value.jl index 6f8ef8a67..d21cde5c8 100644 --- a/src/core/value.jl +++ b/src/core/value.jl @@ -21,7 +21,7 @@ end function refcheck(::Type{T}, ref::API.LLVMValueRef) where T<:Value ref==C_NULL && throw(UndefRefError()) - if Base.JLOptions().debug_level >= 2 + if typecheck_enabled T′ = identify(Value, ref) if T != T′ error("invalid conversion of $T′ value reference to $T") diff --git a/src/debug.jl b/src/debug.jl index 21b5dfa61..77ef24981 100644 --- a/src/debug.jl +++ b/src/debug.jl @@ -1,25 +1,28 @@ +## typecheck: ensuring that the types of objects is as expected + +const typecheck_enabled = parse(Bool, @load_preference("typecheck", "false")) + + ## memcheck: keeping track of allocations and disposals -const tracked_objects = Dict{Any,Any}() +const memcheck_enabled = parse(Bool, @load_preference("memcheck", "false")) -function memcheck_enabled() - parse(Bool, @load_preference("memcheck", "false")) -end +const tracked_objects = Dict{Any,Any}() function mark_alloc(obj::Any) - @static memcheck_enabled() || return obj + @static memcheck_enabled || return obj tracked_objects[obj] = backtrace()[3:end] return obj end function mark_dispose(obj) - @static memcheck_enabled() || return obj + @static memcheck_enabled || return obj delete!(tracked_objects, obj) return obj end function check_memory(code) - @static memcheck_enabled() || return + @static memcheck_enabled || return if code != 0 # if we errorred, we don't care about leaks return diff --git a/test/core_tests.jl b/test/core_tests.jl index 57007f70e..6f573f4ee 100644 --- a/test/core_tests.jl +++ b/test/core_tests.jl @@ -50,7 +50,7 @@ end @test typeof(typ.ref) == LLVM.API.LLVMTypeRef # untyped @test typeof(LLVM.IntegerType(typ.ref)) == LLVM.IntegerType # type reconstructed - if Base.JLOptions().debug_level >= 2 + if LLVM.typecheck_enabled @test_throws ErrorException LLVM.FunctionType(typ.ref) # wrong type end @test_throws UndefRefError LLVM.FunctionType(LLVM.API.LLVMTypeRef(C_NULL)) @@ -224,7 +224,7 @@ end @test typeof(val.ref) == LLVM.API.LLVMValueRef # untyped @test typeof(LLVM.Instruction(val.ref)) == LLVM.AllocaInst # type reconstructed - if Base.JLOptions().debug_level >= 2 + if LLVM.typecheck_enabled @test_throws ErrorException LLVM.Function(val.ref) # wrong end @test_throws UndefRefError LLVM.Function(LLVM.API.LLVMValueRef(C_NULL)) diff --git a/test/runtests.jl b/test/runtests.jl index 759d8fe16..247c30f99 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,9 +1,5 @@ using LLVM -if Base.JLOptions().debug_level < 2 - @warn "It is recommended to run the LLVM.jl test suite with -g2" -end - using InteractiveUtils @info "System information:\n" * sprint(io->versioninfo(io)) @@ -22,6 +18,8 @@ julia_typed_pointers = let end @info "Pointer settings: Julia uses $(julia_typed_pointers ? "typed" : "opaque") pointers, default contexts use $(ctx_typed_pointers ? "typed" : "opaque") pointers" +@info "Debug settings: typecheck = $(LLVM.typecheck_enabled), memcheck = $(LLVM.memcheck_enabled)" + worker_init_expr = quote using LLVM From 9cd34cddab698e1e018b8228e70119d8fb7153df Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 15:17:41 +0200 Subject: [PATCH 16/21] Fix bugs and leaks. --- src/LLVM.jl | 2 +- src/analysis.jl | 4 ++-- src/bitcode.jl | 6 +++++- src/datalayout.jl | 2 +- src/debug.jl | 27 ++++++++++++++------------ src/execution.jl | 2 +- src/ir.jl | 2 +- src/irbuilder.jl | 2 +- src/newpm.jl | 7 ++++--- src/orc.jl | 8 ++++---- test/bitcode_tests.jl | 38 +++++++++++++++++-------------------- test/newpm_tests.jl | 4 +--- test/targetmachine_tests.jl | 2 +- 13 files changed, 54 insertions(+), 52 deletions(-) diff --git a/src/LLVM.jl b/src/LLVM.jl index 1bcc6079c..811ea4513 100644 --- a/src/LLVM.jl +++ b/src/LLVM.jl @@ -137,7 +137,7 @@ function __init__() _install_handlers() _install_handlers(GlobalContext()) - atexit(check_memory) + atexit(report_leaks) end end diff --git a/src/analysis.jl b/src/analysis.jl index a51cd52ae..3ba7d2c7b 100644 --- a/src/analysis.jl +++ b/src/analysis.jl @@ -32,7 +32,7 @@ end Base.unsafe_convert(::Type{API.LLVMDominatorTreeRef}, domtree::DomTree) = domtree.ref DomTree(f::Function) = mark_alloc(DomTree(API.LLVMCreateDominatorTree(f))) -dispose(domtree::DomTree) = mark_dispose(API.LLVMDisposeDominatorTree(domtree)) +dispose(domtree::DomTree) = API.LLVMDisposeDominatorTree(mark_dispose(domtree)) function dominates(domtree::DomTree, A::Instruction, B::Instruction) API.LLVMDominatorTreeInstructionDominates(domtree, A, B) |> Bool @@ -52,7 +52,7 @@ Base.unsafe_convert(::Type{API.LLVMPostDominatorTreeRef}, postdomtree::PostDomTr PostDomTree(f::Function) = mark_alloc(PostDomTree(API.LLVMCreatePostDominatorTree(f))) dispose(postdomtree::PostDomTree) = - mark_dispose(API.LLVMDisposePostDominatorTree(postdomtree)) + API.LLVMDisposePostDominatorTree(mark_dispose(postdomtree)) function dominates(postdomtree::PostDomTree, A::Instruction, B::Instruction) API.LLVMPostDominatorTreeInstructionDominates(postdomtree, A, B) |> Bool diff --git a/src/bitcode.jl b/src/bitcode.jl index 465ed7cf3..1f72879f6 100644 --- a/src/bitcode.jl +++ b/src/bitcode.jl @@ -9,7 +9,11 @@ function Base.parse(::Type{Module}, membuf::MemoryBuffer) Module(out_ref[]) end -Base.parse(::Type{Module}, data::Vector) = parse(Module, MemoryBuffer(data, "", false)) +function Base.parse(::Type{Module}, data::Vector) + @dispose membuf = MemoryBuffer(data, "", false) begin + parse(Module, membuf) + end +end ## writer diff --git a/src/datalayout.jl b/src/datalayout.jl index 27b72e39e..6016c911e 100644 --- a/src/datalayout.jl +++ b/src/datalayout.jl @@ -23,7 +23,7 @@ function DataLayout(f::Core.Function, args...; kwargs...) end end -dispose(data::DataLayout) = mark_dispose(API.LLVMDisposeTargetData(data)) +dispose(data::DataLayout) = API.LLVMDisposeTargetData(mark_dispose(data)) Base.string(data::DataLayout) = unsafe_message(API.LLVMCopyStringRepOfTargetData(data)) diff --git a/src/debug.jl b/src/debug.jl index 77ef24981..0e0deafc3 100644 --- a/src/debug.jl +++ b/src/debug.jl @@ -10,28 +10,31 @@ const memcheck_enabled = parse(Bool, @load_preference("memcheck", "false")) const tracked_objects = Dict{Any,Any}() function mark_alloc(obj::Any) - @static memcheck_enabled || return obj - tracked_objects[obj] = backtrace()[3:end] + @static if memcheck_enabled + tracked_objects[obj] = backtrace()[2:end] + end return obj end function mark_dispose(obj) - @static memcheck_enabled || return obj - delete!(tracked_objects, obj) + @static if memcheck_enabled + delete!(tracked_objects, obj) + end return obj end -function check_memory(code) - @static memcheck_enabled || return +function report_leaks(code) + # if we errorred, we can't trust the memory state if code != 0 - # if we errorred, we don't care about leaks return end - io = Core.stdout - for (obj, bt) in tracked_objects - print(io, "WARNING: An instance of $(typeof(obj)) was not properly disposed of.") - Base.show_backtrace(io, bt) - println(io) + @static if memcheck_enabled + io = Core.stdout + for (obj, bt) in tracked_objects + print(io, "WARNING: An instance of $(typeof(obj)) was not properly disposed of.") + Base.show_backtrace(io, bt) + println(io) + end end end diff --git a/src/execution.jl b/src/execution.jl index fc7416a5b..7fc6342f9 100644 --- a/src/execution.jl +++ b/src/execution.jl @@ -45,7 +45,7 @@ GenericValue(ptr::Ptr) = Base.convert(::Type{Ptr{T}}, val::GenericValue) where {T} = convert(Ptr{T}, API.LLVMGenericValueToPointer(val)) -dispose(val::GenericValue) = mark_dispose(API.LLVMDisposeGenericValue(val)) +dispose(val::GenericValue) = API.LLVMDisposeGenericValue(mark_dispose(val)) ## execution engine diff --git a/src/ir.jl b/src/ir.jl index 5d5446510..59d86243f 100644 --- a/src/ir.jl +++ b/src/ir.jl @@ -6,7 +6,7 @@ function Base.parse(::Type{Module}, ir::String) out_ref = Ref{API.LLVMModuleRef}() out_error = Ref{Cstring}() - status = API.LLVMParseIRInContext(context(), membuf, out_ref, out_error) |> Bool + status = API.LLVMParseIRInContext(context(), mark_dispose(membuf), out_ref, out_error) |> Bool if status error = unsafe_message(out_error[]) diff --git a/src/irbuilder.jl b/src/irbuilder.jl index b28cebf26..47313bd59 100644 --- a/src/irbuilder.jl +++ b/src/irbuilder.jl @@ -13,7 +13,7 @@ Base.unsafe_convert(::Type{API.LLVMBuilderRef}, builder::IRBuilder) = builder.re IRBuilder() = mark_alloc(IRBuilder(API.LLVMCreateBuilderInContext(context()))) -dispose(builder::IRBuilder) = mark_dispose(API.LLVMDisposeBuilder(builder)) +dispose(builder::IRBuilder) = API.LLVMDisposeBuilder(mark_dispose(builder)) context(builder::IRBuilder) = Context(API.LLVMGetBuilderContext(builder)) diff --git a/src/newpm.jl b/src/newpm.jl index 524ef2487..a2b1c9c21 100644 --- a/src/newpm.jl +++ b/src/newpm.jl @@ -274,9 +274,10 @@ function run!(pb::NewPMPassBuilder, mod::Module, tm::Union{Nothing,TargetMachine end function run!(pass::String, args...; kwargs...) - pb = NewPMPassBuilder(; kwargs...) - add!(pb, pass) - run!(pb, args...) + @dispose pb=NewPMPassBuilder(; kwargs...) begin + add!(pb, pass) + run!(pb, args...) + end end diff --git a/src/orc.jl b/src/orc.jl index 7b490c458..ad0bf3c47 100644 --- a/src/orc.jl +++ b/src/orc.jl @@ -192,14 +192,14 @@ function lookup_dylib(es::ExecutionSession, name) end function add!(lljit::LLJIT, jd::JITDylib, obj::MemoryBuffer) - @check API.LLVMOrcLLJITAddObjectFile(lljit, jd, obj) + @check API.LLVMOrcLLJITAddObjectFile(lljit, jd, mark_dispose(obj)) return nothing end # LLVMOrcLLJITAddObjectFileWithRT(J, RT, ObjBuffer) function add!(lljit::LLJIT, jd::JITDylib, mod::ThreadSafeModule) - @check API.LLVMOrcLLJITAddLLVMIRModule(lljit, jd, mod) + @check API.LLVMOrcLLJITAddLLVMIRModule(lljit, jd, mark_dispose(mod)) return nothing end @@ -396,7 +396,7 @@ function JITDylib(jljit::JuliaOJIT) end function add!(jljit::JuliaOJIT, jd::JITDylib, obj::MemoryBuffer) - @check API.JLJITAddObjectFile(jljit, jd, obj) + @check API.JLJITAddObjectFile(jljit, jd, mark_dispose(obj)) return nothing end @@ -425,7 +425,7 @@ function add!(jljit::JuliaOJIT, jd::JITDylib, tsm::ThreadSafeModule) tsm() do mod decorate_module(mod) end - @check API.JLJITAddLLVMIRModule(jljit, jd, tsm) + @check API.JLJITAddLLVMIRModule(jljit, jd, mark_dispose(tsm)) return nothing end diff --git a/test/bitcode_tests.jl b/test/bitcode_tests.jl index dbb52fb24..3e8d77ab3 100644 --- a/test/bitcode_tests.jl +++ b/test/bitcode_tests.jl @@ -17,32 +17,28 @@ end verify(source_mod) - bitcode_buf = convert(MemoryBuffer, source_mod) - - let - mod = parse(LLVM.Module, bitcode_buf) - verify(mod) - @test haskey(functions(mod), "SomeFunction") - dispose(mod) + @dispose bitcode_buf = convert(MemoryBuffer, source_mod) begin + @dispose mod=parse(LLVM.Module, bitcode_buf) begin + verify(mod) + @test haskey(functions(mod), "SomeFunction") + end end - bitcode = convert(Vector{UInt8}, source_mod) - - let - mod = parse(LLVM.Module, bitcode) - verify(mod) - @test haskey(functions(mod), "SomeFunction") - dispose(mod) - end + let bitcode = convert(Vector{UInt8}, source_mod) + @dispose mod = parse(LLVM.Module, bitcode) begin + verify(mod) + @test haskey(functions(mod), "SomeFunction") + end - mktemp() do path, io - mark(io) - @test write(io, source_mod) > 0 - flush(io) - reset(io) + mktemp() do path, io + mark(io) + @test write(io, source_mod) > 0 + flush(io) + reset(io) - @test read(io) == bitcode + @test read(io) == bitcode + end end end diff --git a/test/newpm_tests.jl b/test/newpm_tests.jl index a09a380bd..add21d2b2 100644 --- a/test/newpm_tests.jl +++ b/test/newpm_tests.jl @@ -200,9 +200,7 @@ end CustomModulePass() = NewPMModulePass("custom_module_pass", custom_module_pass!) CustomFunctionPass() = NewPMFunctionPass("custom_function_pass", custom_function_pass!) - @dispose ctx=Context() mod=test_module() begin - pb = NewPMPassBuilder() - + @dispose ctx=Context() mod=test_module() pb=NewPMPassBuilder() begin register!(pb, CustomModulePass()) register!(pb, CustomFunctionPass()) diff --git a/test/targetmachine_tests.jl b/test/targetmachine_tests.jl index 7ea0b924f..0f31616ce 100644 --- a/test/targetmachine_tests.jl +++ b/test/targetmachine_tests.jl @@ -51,7 +51,7 @@ end end end - DataLayout(tm) + dispose(DataLayout(tm)) end end From a9af04d2879abe23984fe3f59a707b8225ac9b75 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 14 Jun 2024 15:26:29 +0200 Subject: [PATCH 17/21] Detect context leaks. --- src/core/context.jl | 4 ++-- test/runtests.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/context.jl b/src/core/context.jl index 38d3eb678..b87ab7e56 100644 --- a/src/core/context.jl +++ b/src/core/context.jl @@ -9,7 +9,7 @@ end Base.unsafe_convert(::Type{API.LLVMContextRef}, ctx::Context) = ctx.ref function Context(; opaque_pointers=nothing) - ctx = Context(API.LLVMContextCreate()) + ctx = mark_alloc(Context(API.LLVMContextCreate())) if opaque_pointers !== nothing opaque_pointers!(ctx, opaque_pointers) end @@ -20,7 +20,7 @@ end function dispose(ctx::Context) deactivate(ctx) - API.LLVMContextDispose(ctx) + API.LLVMContextDispose(mark_dispose(ctx)) end function Context(f::Core.Function; kwargs...) diff --git a/test/runtests.jl b/test/runtests.jl index 247c30f99..1384ca96e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -26,7 +26,7 @@ worker_init_expr = quote # HACK: if a test throws within a Context() do block, displaying the LLVM value may # crash because the context has been disposed already. avoid that by disabling # `dispose`, and only have it pop the context off the stack (but not destroy it). - LLVM.dispose(ctx::Context) = LLVM.deactivate(ctx) + LLVM.dispose(ctx::Context) = LLVM.deactivate(LLVM.mark_dispose(ctx)) end using ReTestItems From 992bd0d0447dd6c3ff3525d0a30517820b0fb2d1 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Sat, 15 Jun 2024 12:41:14 +0200 Subject: [PATCH 18/21] Update CI. --- .github/workflows/ci.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ece3dacb..27030d748 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,12 @@ jobs: if: runner.os == 'Windows' - name: Run tests - run: julia --project -e 'using Pkg; Pkg.test(; coverage=true, julia_args=`-g2`)' + run: | + julia -e 'open("LocalPreferences.toml", "a") do io + println(io, "typecheck = \"true\"") + println(io, "memcheck = \"true\"") + end' + julia --project -e 'using Pkg; Pkg.test(; coverage=true)' env: JULIA_LLVM_ARGS: ${{ matrix.llvm_args }} - uses: julia-actions/julia-processcoverage@v1 @@ -151,7 +156,12 @@ jobs: run: julia --project=deps deps/build_ci.jl - name: Run tests - run: julia --project -e 'using Pkg; Pkg.test(; coverage=true, julia_args=`-g2`)' + run: | + julia -e 'open("LocalPreferences.toml", "a") do io + println(io, "typecheck = \"true\"") + println(io, "memcheck = \"true\"") + end' + julia --project -e 'using Pkg; Pkg.test(; coverage=true)' env: JULIA_LLVM_ARGS: ${{ matrix.llvm_args }} - uses: julia-actions/julia-processcoverage@v1 From 20206c2a81b726a04651764334762f12428c4856 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Mon, 17 Jun 2024 10:46:39 +0200 Subject: [PATCH 19/21] Improve reporting. --- src/debug.jl | 53 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/src/debug.jl b/src/debug.jl index 0e0deafc3..4360408c6 100644 --- a/src/debug.jl +++ b/src/debug.jl @@ -11,30 +11,69 @@ const tracked_objects = Dict{Any,Any}() function mark_alloc(obj::Any) @static if memcheck_enabled - tracked_objects[obj] = backtrace()[2:end] + io = Core.stdout + new_alloc_bt = backtrace()[2:end] + + if haskey(tracked_objects, obj) + old_alloc_bt, dispose_bt = tracked_objects[obj] + if dispose_bt == nothing + print("\nWARNING: An instance of $(typeof(obj)) was not properly disposed of, and a new allocation will overwrite it.") + print("\nThe original allocation was at:") + Base.show_backtrace(io, old_alloc_bt) + print("\nThe new allocation is at:") + Base.show_backtrace(io, new_alloc_bt) + println(io) + end + end + + tracked_objects[obj] = (new_alloc_bt, nothing) end return obj end function mark_dispose(obj) @static if memcheck_enabled - delete!(tracked_objects, obj) + io = Core.stdout + new_dispose_bt = backtrace()[2:end] + + if !haskey(tracked_objects, obj) + print(io, "\nWARNING: An unknown instance of $(typeof(obj)) is being disposed of.") + Base.show_backtrace(io, new_dispose_bt) + return obj + end + + alloc_bt, old_dispose_bt = tracked_objects[obj] + if old_dispose_bt !== nothing + print("\nWARNING: An instance of $(typeof(obj)) is being disposed twice.") + print("\nThe object was allocated at:") + Base.show_backtrace(io, alloc_bt) + print("\nThe object was already disposed at:") + Base.show_backtrace(io, old_dispose_bt) + print("\nThe object is being disposed again at:") + Base.show_backtrace(io, new_dispose_bt) + println(io) + end + + tracked_objects[obj] = (alloc_bt, new_dispose_bt) end return obj end function report_leaks(code) - # if we errorred, we can't trust the memory state + # if we errored, we can't trust the memory state if code != 0 return end @static if memcheck_enabled io = Core.stdout - for (obj, bt) in tracked_objects - print(io, "WARNING: An instance of $(typeof(obj)) was not properly disposed of.") - Base.show_backtrace(io, bt) - println(io) + for (obj, (alloc_bt, dispose_bt)) in tracked_objects + if dispose_bt === nothing + print(io, "\nWARNING: An instance of $(typeof(obj)) was not properly disposed of.") + print("\nThe object was allocated at:") + Base.show_backtrace(io, alloc_bt) + println(io) + end end end end From 63b88266c65b219d3eb0b7ed6d71c82f443508f8 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Mon, 17 Jun 2024 11:52:13 +0200 Subject: [PATCH 20/21] Also memcheck uses. --- src/LLVM.jl | 2 +- src/analysis.jl | 9 +++--- src/buffer.jl | 5 ++-- src/core/context.jl | 4 +-- src/core/module.jl | 4 +-- src/datalayout.jl | 4 +-- src/debug.jl | 51 ++++++++++++++++++++++++++++++-- src/execution.jl | 9 +++--- src/executionengine/ts_module.jl | 4 ++- src/ir.jl | 3 +- src/irbuilder.jl | 5 ++-- src/linker.jl | 3 +- src/moduleprovider.jl | 5 ++-- src/newpm.jl | 5 ++-- src/orc.jl | 25 +++++++++------- src/passmanager.jl | 4 +-- src/targetmachine.jl | 4 +-- test/runtests.jl | 2 +- 18 files changed, 104 insertions(+), 44 deletions(-) diff --git a/src/LLVM.jl b/src/LLVM.jl index 811ea4513..8cc0ca00a 100644 --- a/src/LLVM.jl +++ b/src/LLVM.jl @@ -136,7 +136,7 @@ function __init__() end _install_handlers() - _install_handlers(GlobalContext()) + _install_handlers(mark_foreign(GlobalContext())) atexit(report_leaks) end diff --git a/src/analysis.jl b/src/analysis.jl index 3ba7d2c7b..6becf7525 100644 --- a/src/analysis.jl +++ b/src/analysis.jl @@ -29,10 +29,11 @@ export DomTree, dominates ref::API.LLVMDominatorTreeRef end -Base.unsafe_convert(::Type{API.LLVMDominatorTreeRef}, domtree::DomTree) = domtree.ref +Base.unsafe_convert(::Type{API.LLVMDominatorTreeRef}, domtree::DomTree) = + mark_use(domtree).ref DomTree(f::Function) = mark_alloc(DomTree(API.LLVMCreateDominatorTree(f))) -dispose(domtree::DomTree) = API.LLVMDisposeDominatorTree(mark_dispose(domtree)) +dispose(domtree::DomTree) = mark_dispose(API.LLVMDisposeDominatorTree, domtree) function dominates(domtree::DomTree, A::Instruction, B::Instruction) API.LLVMDominatorTreeInstructionDominates(domtree, A, B) |> Bool @@ -48,11 +49,11 @@ export PostDomTree, dominates end Base.unsafe_convert(::Type{API.LLVMPostDominatorTreeRef}, postdomtree::PostDomTree) = - postdomtree.ref + mark_use(postdomtree).ref PostDomTree(f::Function) = mark_alloc(PostDomTree(API.LLVMCreatePostDominatorTree(f))) dispose(postdomtree::PostDomTree) = - API.LLVMDisposePostDominatorTree(mark_dispose(postdomtree)) + mark_dispose(API.LLVMDisposePostDominatorTree, postdomtree) function dominates(postdomtree::PostDomTree, A::Instruction, B::Instruction) API.LLVMPostDominatorTreeInstructionDominates(postdomtree, A, B) |> Bool diff --git a/src/buffer.jl b/src/buffer.jl index 3e626417e..294e0f462 100644 --- a/src/buffer.jl +++ b/src/buffer.jl @@ -4,7 +4,8 @@ export MemoryBuffer, MemoryBufferFile, dispose ref::API.LLVMMemoryBufferRef end -Base.unsafe_convert(::Type{API.LLVMMemoryBufferRef}, membuf::MemoryBuffer) = membuf.ref +Base.unsafe_convert(::Type{API.LLVMMemoryBufferRef}, membuf::MemoryBuffer) = + mark_use(membuf).ref function MemoryBuffer(data::Vector{T}, name::String="", copy::Bool=true) where T<:Union{UInt8,Int8} ptr = pointer(data) @@ -49,7 +50,7 @@ function MemoryBufferFile(f::Core.Function, args...; kwargs...) end end -dispose(membuf::MemoryBuffer) = API.LLVMDisposeMemoryBuffer(mark_dispose(membuf)) +dispose(membuf::MemoryBuffer) = mark_dispose(API.LLVMDisposeMemoryBuffer, membuf) Base.length(membuf::MemoryBuffer) = API.LLVMGetBufferSize(membuf) diff --git a/src/core/context.jl b/src/core/context.jl index b87ab7e56..ac4c8c688 100644 --- a/src/core/context.jl +++ b/src/core/context.jl @@ -6,7 +6,7 @@ export Context, dispose, GlobalContext ref::API.LLVMContextRef end -Base.unsafe_convert(::Type{API.LLVMContextRef}, ctx::Context) = ctx.ref +Base.unsafe_convert(::Type{API.LLVMContextRef}, ctx::Context) = mark_use(ctx).ref function Context(; opaque_pointers=nothing) ctx = mark_alloc(Context(API.LLVMContextCreate())) @@ -20,7 +20,7 @@ end function dispose(ctx::Context) deactivate(ctx) - API.LLVMContextDispose(mark_dispose(ctx)) + mark_dispose(API.LLVMContextDispose, ctx) end function Context(f::Core.Function; kwargs...) diff --git a/src/core/module.jl b/src/core/module.jl index 8b13f96e0..8bc79a60b 100644 --- a/src/core/module.jl +++ b/src/core/module.jl @@ -9,7 +9,7 @@ export dispose, context, # forward definition of Module in src/core/value/constant.jl -Base.unsafe_convert(::Type{API.LLVMModuleRef}, mod::Module) = mod.ref +Base.unsafe_convert(::Type{API.LLVMModuleRef}, mod::Module) = mark_use(mod).ref Base.:(==)(x::Module, y::Module) = (x.ref === y.ref) @@ -24,7 +24,7 @@ Module(name::String) = Module(mod::Module) = mark_alloc(Module(API.LLVMCloneModule(mod))) Base.copy(mod::Module) = Module(mod) -dispose(mod::Module) = API.LLVMDisposeModule(mark_dispose(mod)) +dispose(mod::Module) = mark_dispose(API.LLVMDisposeModule, mod) function Module(f::Core.Function, args...; kwargs...) mod = Module(args...; kwargs...) diff --git a/src/datalayout.jl b/src/datalayout.jl index 6016c911e..279d4da96 100644 --- a/src/datalayout.jl +++ b/src/datalayout.jl @@ -8,7 +8,7 @@ export DataLayout, dispose, # forward definition of DataLayout in src/module.jl -Base.unsafe_convert(::Type{API.LLVMTargetDataRef}, dl::DataLayout) = dl.ref +Base.unsafe_convert(::Type{API.LLVMTargetDataRef}, dl::DataLayout) = mark_use(dl).ref DataLayout(rep::String) = mark_alloc(DataLayout(API.LLVMCreateTargetData(rep))) @@ -23,7 +23,7 @@ function DataLayout(f::Core.Function, args...; kwargs...) end end -dispose(data::DataLayout) = API.LLVMDisposeTargetData(mark_dispose(data)) +dispose(data::DataLayout) = mark_dispose(API.LLVMDisposeTargetData, data) Base.string(data::DataLayout) = unsafe_message(API.LLVMCopyStringRepOfTargetData(data)) diff --git a/src/debug.jl b/src/debug.jl index 4360408c6..7a1785ee5 100644 --- a/src/debug.jl +++ b/src/debug.jl @@ -3,12 +3,20 @@ const typecheck_enabled = parse(Bool, @load_preference("typecheck", "false")) -## memcheck: keeping track of allocations and disposals +## memcheck: keeping track when objects are valid const memcheck_enabled = parse(Bool, @load_preference("memcheck", "false")) +const foreign_objects = Set{Any}() const tracked_objects = Dict{Any,Any}() +function mark_foreign(obj::Any) + @static if memcheck_enabled + push!(foreign_objects, obj) + end + return obj +end + function mark_alloc(obj::Any) @static if memcheck_enabled io = Core.stdout @@ -31,6 +39,36 @@ function mark_alloc(obj::Any) return obj end +function mark_use(obj::Any) + @static if memcheck_enabled + io = Core.stdout + + if obj in foreign_objects + return obj + end + + if !haskey(tracked_objects, obj) + print("\nWARNING: An unknown instance of $(typeof(obj)) is being used.") + Base.show_backtrace(io, backtrace()[2:end]) + println(io) + return obj + end + + alloc_bt, dispose_bt = tracked_objects[obj] + if dispose_bt !== nothing + print("\nWARNING: An instance of $(typeof(obj)) is being used after it was disposed.") + print("\nThe object was allocated at:") + Base.show_backtrace(io, alloc_bt) + print("\nThe object was disposed at:") + Base.show_backtrace(io, dispose_bt) + print("\nThe object is being used at:") + Base.show_backtrace(io, backtrace()[2:end]) + println(io) + end + end + return obj +end + function mark_dispose(obj) @static if memcheck_enabled io = Core.stdout @@ -39,7 +77,7 @@ function mark_dispose(obj) if !haskey(tracked_objects, obj) print(io, "\nWARNING: An unknown instance of $(typeof(obj)) is being disposed of.") Base.show_backtrace(io, new_dispose_bt) - return obj + return end alloc_bt, old_dispose_bt = tracked_objects[obj] @@ -56,7 +94,14 @@ function mark_dispose(obj) tracked_objects[obj] = (alloc_bt, new_dispose_bt) end - return obj + return +end + +# helper for single-line disposal without a use-after-free warning +function mark_dispose(f, obj) + ret = f(obj) + mark_dispose(obj) + return ret end function report_leaks(code) diff --git a/src/execution.jl b/src/execution.jl index 7fc6342f9..cc87f700d 100644 --- a/src/execution.jl +++ b/src/execution.jl @@ -10,7 +10,7 @@ export GenericValue, dispose, ref::API.LLVMGenericValueRef end -Base.unsafe_convert(::Type{API.LLVMGenericValueRef}, val::GenericValue) = val.ref +Base.unsafe_convert(::Type{API.LLVMGenericValueRef}, val::GenericValue) = mark_use(val).ref GenericValue(typ::IntegerType, N::Signed) = mark_alloc(GenericValue( @@ -45,7 +45,7 @@ GenericValue(ptr::Ptr) = Base.convert(::Type{Ptr{T}}, val::GenericValue) where {T} = convert(Ptr{T}, API.LLVMGenericValueToPointer(val)) -dispose(val::GenericValue) = API.LLVMDisposeGenericValue(mark_dispose(val)) +dispose(val::GenericValue) = mark_dispose(API.LLVMDisposeGenericValue, val) ## execution engine @@ -58,7 +58,8 @@ export ExecutionEngine, Interpreter, JIT, mods::Set{Module} end -Base.unsafe_convert(::Type{API.LLVMExecutionEngineRef}, engine::ExecutionEngine) = engine.ref +Base.unsafe_convert(::Type{API.LLVMExecutionEngineRef}, engine::ExecutionEngine) = + mark_use(engine).ref # NOTE: these takes ownership of the module function ExecutionEngine(mod::Module) @@ -104,7 +105,7 @@ end function dispose(engine::ExecutionEngine) mark_dispose.(engine.mods) - API.LLVMDisposeExecutionEngine(mark_dispose(engine)) + mark_dispose(API.LLVMDisposeExecutionEngine, engine) end for x in [:ExecutionEngine, :Interpreter, :JIT] diff --git a/src/executionengine/ts_module.jl b/src/executionengine/ts_module.jl index fc7a3daba..7df6371f9 100644 --- a/src/executionengine/ts_module.jl +++ b/src/executionengine/ts_module.jl @@ -48,7 +48,9 @@ function ThreadSafeModule(name::String) mod = context!(ctx) do Module(name) end - ThreadSafeModule(mark_dispose(mod)) + tsm = ThreadSafeModule(mod) + mark_dispose(mod) + return tsm end function dispose(mod::ThreadSafeModule) diff --git a/src/ir.jl b/src/ir.jl index 59d86243f..e3d22aee9 100644 --- a/src/ir.jl +++ b/src/ir.jl @@ -6,7 +6,8 @@ function Base.parse(::Type{Module}, ir::String) out_ref = Ref{API.LLVMModuleRef}() out_error = Ref{Cstring}() - status = API.LLVMParseIRInContext(context(), mark_dispose(membuf), out_ref, out_error) |> Bool + status = API.LLVMParseIRInContext(context(), membuf, out_ref, out_error) |> Bool + mark_dispose(membuf) if status error = unsafe_message(out_error[]) diff --git a/src/irbuilder.jl b/src/irbuilder.jl index 47313bd59..b846b27d3 100644 --- a/src/irbuilder.jl +++ b/src/irbuilder.jl @@ -9,11 +9,12 @@ export IRBuilder, ref::API.LLVMBuilderRef end -Base.unsafe_convert(::Type{API.LLVMBuilderRef}, builder::IRBuilder) = builder.ref +Base.unsafe_convert(::Type{API.LLVMBuilderRef}, builder::IRBuilder) = + mark_use(builder).ref IRBuilder() = mark_alloc(IRBuilder(API.LLVMCreateBuilderInContext(context()))) -dispose(builder::IRBuilder) = API.LLVMDisposeBuilder(mark_dispose(builder)) +dispose(builder::IRBuilder) = mark_dispose(API.LLVMDisposeBuilder, builder) context(builder::IRBuilder) = Context(API.LLVMGetBuilderContext(builder)) diff --git a/src/linker.jl b/src/linker.jl index cef66491e..672ead7bb 100644 --- a/src/linker.jl +++ b/src/linker.jl @@ -1,8 +1,9 @@ export link! function link!(dst::Module, src::Module) - status = API.LLVMLinkModules2(dst, mark_dispose(src)) |> Bool + status = API.LLVMLinkModules2(dst, src) |> Bool @assert !status # caught by diagnostics handler + mark_dispose(src) return nothing end diff --git a/src/moduleprovider.jl b/src/moduleprovider.jl index fb6642478..f90924eec 100644 --- a/src/moduleprovider.jl +++ b/src/moduleprovider.jl @@ -8,7 +8,8 @@ export ModuleProvider, dispose mod::Module end -Base.unsafe_convert(::Type{API.LLVMModuleProviderRef}, mp::ModuleProvider) = mp.ref +Base.unsafe_convert(::Type{API.LLVMModuleProviderRef}, mp::ModuleProvider) = + mark_use(mp).ref ModuleProvider(mod::Module) = mark_alloc(ModuleProvider(API.LLVMCreateModuleProviderForExistingModule(mod), mod)) @@ -26,5 +27,5 @@ function dispose(mp::ModuleProvider) # NOTE: this destroys the underlying module mark_dispose(mp.mod) - API.LLVMDisposeModuleProvider(mark_dispose(mp)) + mark_dispose(API.LLVMDisposeModuleProvider, mp) end diff --git a/src/newpm.jl b/src/newpm.jl index a2b1c9c21..9e5af61ae 100644 --- a/src/newpm.jl +++ b/src/newpm.jl @@ -175,7 +175,8 @@ end Base.string(pm::NewPMPassBuilder) = join(pm.passes, ",") -Base.unsafe_convert(::Type{API.LLVMPassBuilderOptionsRef}, pb::NewPMPassBuilder) = pb.opts +Base.unsafe_convert(::Type{API.LLVMPassBuilderOptionsRef}, pb::NewPMPassBuilder) = + mark_use(pb).opts function NewPMPassBuilder(; kwargs...) opts = API.LLVMCreatePassBuilderOptions() @@ -214,9 +215,9 @@ function NewPMPassBuilder(; kwargs...) end function dispose(pb::NewPMPassBuilder) - mark_dispose(pb) API.LLVMDisposePassBuilderOptions(pb.opts) API.LLVMDisposePassBuilderExtensions(pb.exts) + mark_dispose(pb) end """ diff --git a/src/orc.jl b/src/orc.jl index ad0bf3c47..50bf1527b 100644 --- a/src/orc.jl +++ b/src/orc.jl @@ -20,7 +20,8 @@ function TargetMachineBuilder() end function TargetMachineBuilder(tm::TargetMachine) - tmb = API.LLVMOrcJITTargetMachineBuilderCreateFromTargetMachine(mark_dispose(tm)) + tmb = API.LLVMOrcJITTargetMachineBuilderCreateFromTargetMachine(tm) + mark_dispose(tm) TargetMachineBuilder(tmb) end @@ -186,21 +187,23 @@ end function lookup_dylib(es::ExecutionSession, name) ref = API.LLVMOrcExecutionSessionGetJITDylibByName(es, name) if ref == C_NULL - return nothing + return end JITDylib(ref) end function add!(lljit::LLJIT, jd::JITDylib, obj::MemoryBuffer) - @check API.LLVMOrcLLJITAddObjectFile(lljit, jd, mark_dispose(obj)) - return nothing + @check API.LLVMOrcLLJITAddObjectFile(lljit, jd, obj) + mark_dispose(obj) + return end # LLVMOrcLLJITAddObjectFileWithRT(J, RT, ObjBuffer) function add!(lljit::LLJIT, jd::JITDylib, mod::ThreadSafeModule) - @check API.LLVMOrcLLJITAddLLVMIRModule(lljit, jd, mark_dispose(mod)) - return nothing + @check API.LLVMOrcLLJITAddLLVMIRModule(lljit, jd, mod) + mark_dispose(mod) + return end # LLVMOrcLLJITAddLLVMIRModuleWithRT(J, JD, TSM) @@ -396,8 +399,9 @@ function JITDylib(jljit::JuliaOJIT) end function add!(jljit::JuliaOJIT, jd::JITDylib, obj::MemoryBuffer) - @check API.JLJITAddObjectFile(jljit, jd, mark_dispose(obj)) - return nothing + @check API.JLJITAddObjectFile(jljit, jd, obj) + mark_dispose(obj) + return end function decorate_module(mod) @@ -425,8 +429,9 @@ function add!(jljit::JuliaOJIT, jd::JITDylib, tsm::ThreadSafeModule) tsm() do mod decorate_module(mod) end - @check API.JLJITAddLLVMIRModule(jljit, jd, mark_dispose(tsm)) - return nothing + @check API.JLJITAddLLVMIRModule(jljit, jd, tsm) + mark_dispose(tsm) + return end function lookup(jljit::JuliaOJIT, name, external_jd=true) diff --git a/src/passmanager.jl b/src/passmanager.jl index 49ee4d7dd..3b366eb0a 100644 --- a/src/passmanager.jl +++ b/src/passmanager.jl @@ -4,14 +4,14 @@ export PassManager, # subtypes are expected to have a 'ref::API.LLVMPassManagerRef' field abstract type PassManager end -Base.unsafe_convert(::Type{API.LLVMPassManagerRef}, pm::PassManager) = pm.ref +Base.unsafe_convert(::Type{API.LLVMPassManagerRef}, pm::PassManager) = mark_use(pm).ref function add!(pm::PassManager, pass::Pass) push!(pm.roots, pass) API.LLVMAddPass(pm, pass) end -dispose(pm::PassManager) = API.LLVMDisposePassManager(mark_dispose(pm)) +dispose(pm::PassManager) = mark_dispose(API.LLVMDisposePassManager, pm) # diff --git a/src/targetmachine.jl b/src/targetmachine.jl index 995271f33..c1a85bd14 100644 --- a/src/targetmachine.jl +++ b/src/targetmachine.jl @@ -9,7 +9,7 @@ export JITTargetMachine ref::API.LLVMTargetMachineRef end -Base.unsafe_convert(::Type{API.LLVMTargetMachineRef}, tm::TargetMachine) = tm.ref +Base.unsafe_convert(::Type{API.LLVMTargetMachineRef}, tm::TargetMachine) = mark_use(tm).ref TargetMachine(t::Target, triple::String, cpu::String="", features::String=""; optlevel::API.LLVMCodeGenOptLevel=API.LLVMCodeGenLevelDefault, @@ -18,7 +18,7 @@ TargetMachine(t::Target, triple::String, cpu::String="", features::String=""; mark_alloc(TargetMachine(API.LLVMCreateTargetMachine(t, triple, cpu, features, optlevel, reloc, code))) -dispose(tm::TargetMachine) = API.LLVMDisposeTargetMachine(mark_dispose(tm)) +dispose(tm::TargetMachine) = mark_dispose(API.LLVMDisposeTargetMachine, tm) function TargetMachine(f::Core.Function, args...; kwargs...) tm = TargetMachine(args...; kwargs...) diff --git a/test/runtests.jl b/test/runtests.jl index 1384ca96e..7a56b7fb3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -26,7 +26,7 @@ worker_init_expr = quote # HACK: if a test throws within a Context() do block, displaying the LLVM value may # crash because the context has been disposed already. avoid that by disabling # `dispose`, and only have it pop the context off the stack (but not destroy it). - LLVM.dispose(ctx::Context) = LLVM.deactivate(LLVM.mark_dispose(ctx)) + LLVM.dispose(ctx::Context) = LLVM.mark_dispose(LLVM.deactivate, ctx) end using ReTestItems From 7f5dd9d95692646c8efb2fd34d1ee37f4c4c25b1 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Mon, 17 Jun 2024 11:57:29 +0200 Subject: [PATCH 21/21] Ignore foreign objects. --- src/LLVM.jl | 2 +- src/debug.jl | 19 +++---------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/LLVM.jl b/src/LLVM.jl index 8cc0ca00a..811ea4513 100644 --- a/src/LLVM.jl +++ b/src/LLVM.jl @@ -136,7 +136,7 @@ function __init__() end _install_handlers() - _install_handlers(mark_foreign(GlobalContext())) + _install_handlers(GlobalContext()) atexit(report_leaks) end diff --git a/src/debug.jl b/src/debug.jl index 7a1785ee5..97db3b82a 100644 --- a/src/debug.jl +++ b/src/debug.jl @@ -7,16 +7,8 @@ const typecheck_enabled = parse(Bool, @load_preference("typecheck", "false")) const memcheck_enabled = parse(Bool, @load_preference("memcheck", "false")) -const foreign_objects = Set{Any}() const tracked_objects = Dict{Any,Any}() -function mark_foreign(obj::Any) - @static if memcheck_enabled - push!(foreign_objects, obj) - end - return obj -end - function mark_alloc(obj::Any) @static if memcheck_enabled io = Core.stdout @@ -43,14 +35,9 @@ function mark_use(obj::Any) @static if memcheck_enabled io = Core.stdout - if obj in foreign_objects - return obj - end - if !haskey(tracked_objects, obj) - print("\nWARNING: An unknown instance of $(typeof(obj)) is being used.") - Base.show_backtrace(io, backtrace()[2:end]) - println(io) + # we have to ignore unknown objects, as they may originate externally. + # for example, a Julia-created Type we call `context` on. return obj end @@ -104,7 +91,7 @@ function mark_dispose(f, obj) return ret end -function report_leaks(code) +function report_leaks(code=0) # if we errored, we can't trust the memory state if code != 0 return