Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions LocalPreferences.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
1 change: 1 addition & 0 deletions examples/constrained.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
5 changes: 5 additions & 0 deletions src/LLVM.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module LLVM

using Preferences
using Unicode
using Printf
using Libdl
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -133,6 +137,7 @@ function __init__()

_install_handlers()
_install_handlers(GlobalContext())
atexit(report_leaks)
end

end
14 changes: 8 additions & 6 deletions src/analysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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) = 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
Expand All @@ -48,10 +49,11 @@ export PostDomTree, dominates
end

Base.unsafe_convert(::Type{API.LLVMPostDominatorTreeRef}, postdomtree::PostDomTree) =
postdomtree.ref
mark_use(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
Expand Down
6 changes: 5 additions & 1 deletion src/bitcode.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions src/buffer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ 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)
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...)
Expand Down Expand Up @@ -48,7 +50,7 @@ function MemoryBufferFile(f::Core.Function, args...; kwargs...)
end
end

dispose(membuf::MemoryBuffer) = API.LLVMDisposeMemoryBuffer(membuf)
dispose(membuf::MemoryBuffer) = mark_dispose(API.LLVMDisposeMemoryBuffer, membuf)

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

Expand Down
6 changes: 3 additions & 3 deletions src/core/context.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ 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 = Context(API.LLVMContextCreate())
ctx = mark_alloc(Context(API.LLVMContextCreate()))
if opaque_pointers !== nothing
opaque_pointers!(ctx, opaque_pointers)
end
Expand All @@ -20,7 +20,7 @@ end

function dispose(ctx::Context)
deactivate(ctx)
API.LLVMContextDispose(ctx)
mark_dispose(API.LLVMContextDispose, ctx)
end

function Context(f::Core.Function; kwargs...)
Expand Down
2 changes: 1 addition & 1 deletion src/core/instructions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion src/core/metadata.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
12 changes: 4 additions & 8 deletions src/core/module.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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) = mark_use(mod).ref

Base.:(==)(x::Module, y::Module) = (x.ref === y.ref)

Expand All @@ -23,12 +19,12 @@ Base.:(==)(x::Module, y::Module) = (x.ref === y.ref)
end

Module(name::String) =
Module(API.LLVMModuleCreateWithNameInContext(name, context()))
mark_alloc(Module(API.LLVMModuleCreateWithNameInContext(name, context())))

Module(mod::Module) = Module(API.LLVMCloneModule(mod))
Module(mod::Module) = mark_alloc(Module(API.LLVMCloneModule(mod)))
Base.copy(mod::Module) = Module(mod)

dispose(mod::Module) = API.LLVMDisposeModule(mod)
dispose(mod::Module) = mark_dispose(API.LLVMDisposeModule, mod)

function Module(f::Core.Function, args...; kwargs...)
mod = Module(args...; kwargs...)
Expand Down
2 changes: 1 addition & 1 deletion src/core/type.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion src/core/value.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion src/core/value/constant.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/datalayout.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ 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) = 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...)
Expand All @@ -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))
Expand Down
111 changes: 111 additions & 0 deletions src/debug.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
## typecheck: ensuring that the types of objects is as expected

const typecheck_enabled = parse(Bool, @load_preference("typecheck", "false"))


## memcheck: keeping track when objects are valid

const memcheck_enabled = parse(Bool, @load_preference("memcheck", "false"))

const tracked_objects = Dict{Any,Any}()

function mark_alloc(obj::Any)
@static if memcheck_enabled
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_use(obj::Any)
@static if memcheck_enabled
io = Core.stdout

if !haskey(tracked_objects, obj)
# we have to ignore unknown objects, as they may originate externally.
# for example, a Julia-created Type we call `context` on.
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
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
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
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=0)
# if we errored, we can't trust the memory state
if code != 0
return
end

@static if memcheck_enabled
io = Core.stdout
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
Loading