Skip to content

Commit 16b0d0b

Browse files
authored
Add a memory checker (#420)
Detects leaks, double-frees, and use-after-frees of manually memory managed LLVM objects
1 parent 7284514 commit 16b0d0b

37 files changed

+286
-141
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,12 @@ jobs:
8888
if: runner.os == 'Windows'
8989

9090
- name: Run tests
91-
run: julia --project -e 'using Pkg; Pkg.test(; coverage=true, julia_args=`-g2`)'
91+
run: |
92+
julia -e 'open("LocalPreferences.toml", "a") do io
93+
println(io, "typecheck = \"true\"")
94+
println(io, "memcheck = \"true\"")
95+
end'
96+
julia --project -e 'using Pkg; Pkg.test(; coverage=true)'
9297
env:
9398
JULIA_LLVM_ARGS: ${{ matrix.llvm_args }}
9499
- uses: julia-actions/julia-processcoverage@v1
@@ -151,7 +156,12 @@ jobs:
151156
run: julia --project=deps deps/build_ci.jl
152157

153158
- name: Run tests
154-
run: julia --project -e 'using Pkg; Pkg.test(; coverage=true, julia_args=`-g2`)'
159+
run: |
160+
julia -e 'open("LocalPreferences.toml", "a") do io
161+
println(io, "typecheck = \"true\"")
162+
println(io, "memcheck = \"true\"")
163+
end'
164+
julia --project -e 'using Pkg; Pkg.test(; coverage=true)'
155165
env:
156166
JULIA_LLVM_ARGS: ${{ matrix.llvm_args }}
157167
- uses: julia-actions/julia-processcoverage@v1

LocalPreferences.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,10 @@
33
# but if you are using a custom version of LLVM you will need to provide your own,
44
# e.g., by running `deps/build_local.jl`.
55
#libLLVMExtra = "/path/to/libLLVMExtra.so"
6+
7+
# whether to enable additional object type checking
8+
#typecheck = "false"
9+
10+
# whether to enable object memory checking. these are expensive checks that keep
11+
# track of allocated objects, whether they are freed correctly, etc.
12+
#memcheck = "false"

examples/constrained.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ meta(::Type{FPExceptStrict}) = "fpexcept.strict"
4444
intrinsic = Intrinsic("llvm.experimental.constrained.$(func(F))")
4545
intrinsic_fun = LLVM.Function(mod, intrinsic, [typ])
4646
ftype = LLVM.FunctionType(intrinsic,[typ])
47+
4748
# generate IR
4849
@dispose builder=IRBuilder() begin
4950
entry = BasicBlock(llvm_f, "entry")

src/LLVM.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module LLVM
22

3+
using Preferences
34
using Unicode
45
using Printf
56
using Libdl
@@ -59,6 +60,9 @@ has_oldpm() = LLVM.version() < v"17"
5960
has_newpm() = LLVM.version() >= v"15"
6061
has_julia_ojit() = VERSION >= v"1.10.0-DEV.1395"
6162

63+
# helpers
64+
include("debug.jl")
65+
6266
# LLVM API wrappers
6367
include("support.jl")
6468
if LLVM.version() < v"17"
@@ -133,6 +137,7 @@ function __init__()
133137

134138
_install_handlers()
135139
_install_handlers(GlobalContext())
140+
atexit(report_leaks)
136141
end
137142

138143
end

src/analysis.jl

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ export DomTree, dominates
2929
ref::API.LLVMDominatorTreeRef
3030
end
3131

32-
Base.unsafe_convert(::Type{API.LLVMDominatorTreeRef}, domtree::DomTree) = domtree.ref
32+
Base.unsafe_convert(::Type{API.LLVMDominatorTreeRef}, domtree::DomTree) =
33+
mark_use(domtree).ref
3334

34-
DomTree(f::Function) = DomTree(API.LLVMCreateDominatorTree(f))
35-
dispose(domtree::DomTree) = API.LLVMDisposeDominatorTree(domtree)
35+
DomTree(f::Function) = mark_alloc(DomTree(API.LLVMCreateDominatorTree(f)))
36+
dispose(domtree::DomTree) = mark_dispose(API.LLVMDisposeDominatorTree, domtree)
3637

3738
function dominates(domtree::DomTree, A::Instruction, B::Instruction)
3839
API.LLVMDominatorTreeInstructionDominates(domtree, A, B) |> Bool
@@ -48,10 +49,11 @@ export PostDomTree, dominates
4849
end
4950

5051
Base.unsafe_convert(::Type{API.LLVMPostDominatorTreeRef}, postdomtree::PostDomTree) =
51-
postdomtree.ref
52+
mark_use(postdomtree).ref
5253

53-
PostDomTree(f::Function) = PostDomTree(API.LLVMCreatePostDominatorTree(f))
54-
dispose(postdomtree::PostDomTree) = API.LLVMDisposePostDominatorTree(postdomtree)
54+
PostDomTree(f::Function) = mark_alloc(PostDomTree(API.LLVMCreatePostDominatorTree(f)))
55+
dispose(postdomtree::PostDomTree) =
56+
mark_dispose(API.LLVMDisposePostDominatorTree, postdomtree)
5557

5658
function dominates(postdomtree::PostDomTree, A::Instruction, B::Instruction)
5759
API.LLVMPostDominatorTreeInstructionDominates(postdomtree, A, B) |> Bool

src/bitcode.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ function Base.parse(::Type{Module}, membuf::MemoryBuffer)
99
Module(out_ref[])
1010
end
1111

12-
Base.parse(::Type{Module}, data::Vector) = parse(Module, MemoryBuffer(data, "", false))
12+
function Base.parse(::Type{Module}, data::Vector)
13+
@dispose membuf = MemoryBuffer(data, "", false) begin
14+
parse(Module, membuf)
15+
end
16+
end
1317

1418

1519
## writer

src/buffer.jl

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ export MemoryBuffer, MemoryBufferFile, dispose
44
ref::API.LLVMMemoryBufferRef
55
end
66

7-
Base.unsafe_convert(::Type{API.LLVMMemoryBufferRef}, membuf::MemoryBuffer) = membuf.ref
7+
Base.unsafe_convert(::Type{API.LLVMMemoryBufferRef}, membuf::MemoryBuffer) =
8+
mark_use(membuf).ref
89

910
function MemoryBuffer(data::Vector{T}, name::String="", copy::Bool=true) where T<:Union{UInt8,Int8}
1011
ptr = pointer(data)
1112
len = Csize_t(length(data))
12-
if copy
13-
return MemoryBuffer(API.LLVMCreateMemoryBufferWithMemoryRangeCopy(ptr, len, name))
13+
membuf = if copy
14+
MemoryBuffer(API.LLVMCreateMemoryBufferWithMemoryRangeCopy(ptr, len, name))
1415
else
15-
return MemoryBuffer(API.LLVMCreateMemoryBufferWithMemoryRange(ptr, len, name, false))
16+
MemoryBuffer(API.LLVMCreateMemoryBufferWithMemoryRange(ptr, len, name, false))
1617
end
18+
mark_alloc(membuf)
1719
end
1820

1921
function MemoryBuffer(f::Core.Function, args...; kwargs...)
@@ -48,7 +50,7 @@ function MemoryBufferFile(f::Core.Function, args...; kwargs...)
4850
end
4951
end
5052

51-
dispose(membuf::MemoryBuffer) = API.LLVMDisposeMemoryBuffer(membuf)
53+
dispose(membuf::MemoryBuffer) = mark_dispose(API.LLVMDisposeMemoryBuffer, membuf)
5254

5355
Base.length(membuf::MemoryBuffer) = API.LLVMGetBufferSize(membuf)
5456

src/core/context.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ export Context, dispose, GlobalContext
66
ref::API.LLVMContextRef
77
end
88

9-
Base.unsafe_convert(::Type{API.LLVMContextRef}, ctx::Context) = ctx.ref
9+
Base.unsafe_convert(::Type{API.LLVMContextRef}, ctx::Context) = mark_use(ctx).ref
1010

1111
function Context(; opaque_pointers=nothing)
12-
ctx = Context(API.LLVMContextCreate())
12+
ctx = mark_alloc(Context(API.LLVMContextCreate()))
1313
if opaque_pointers !== nothing
1414
opaque_pointers!(ctx, opaque_pointers)
1515
end
@@ -20,7 +20,7 @@ end
2020

2121
function dispose(ctx::Context)
2222
deactivate(ctx)
23-
API.LLVMContextDispose(ctx)
23+
mark_dispose(API.LLVMContextDispose, ctx)
2424
end
2525

2626
function Context(f::Core.Function; kwargs...)

src/core/instructions.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ end
1818

1919
function refcheck(::Type{T}, ref::API.LLVMValueRef) where T<:Instruction
2020
ref==C_NULL && throw(UndefRefError())
21-
if Base.JLOptions().debug_level >= 2
21+
if typecheck_enabled
2222
T′ = identify(Instruction, ref)
2323
if T != T′
2424
error("invalid conversion of $T′ instruction reference to $T")

src/core/metadata.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ end
2121

2222
function refcheck(::Type{T}, ref::API.LLVMMetadataRef) where T<:Metadata
2323
ref==C_NULL && throw(UndefRefError())
24-
if Base.JLOptions().debug_level >= 2
24+
if typecheck_enabled
2525
T′ = identify(Metadata, ref)
2626
if T != T′
2727
error("invalid conversion of $T′ metadata reference to $T")

0 commit comments

Comments
 (0)