Skip to content

Commit 3eabb5f

Browse files
authored
Merge pull request #305 from JuliaGPU/tb/asserts
Fix context usage and add assertions CI
2 parents 676e311 + afa47b7 commit 3eabb5f

File tree

9 files changed

+171
-89
lines changed

9 files changed

+171
-89
lines changed

.github/workflows/ci.yml

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,44 @@ on:
77
workflow_dispatch:
88
jobs:
99
test:
10-
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
10+
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - assertions=${{ matrix.assertions }}
1111
runs-on: ${{ matrix.os }}
1212
strategy:
1313
fail-fast: false
1414
matrix:
15-
version:
16-
- '1.6'
17-
- '^1.7.0-beta2'
18-
- 'nightly'
19-
os:
20-
- ubuntu-latest
21-
- macOS-latest
22-
- windows-latest
23-
arch:
24-
- x64
15+
version: ['1.6', '1.7', '^1.8.0-beta1', 'nightly']
16+
os: [ubuntu-latest, macOS-latest, windows-latest]
17+
arch: [x64]
18+
assertions: [false]
19+
include:
20+
# special test with LLVM assertions enabled
21+
# TODO: enable this across all versions
22+
# (needs julia-actions/setup-julia support)
23+
- os: ubuntu-latest
24+
arch: x64
25+
version: '1.7'
26+
assertions: true
27+
- os: ubuntu-latest
28+
arch: x64
29+
version: '1.8'
30+
assertions: true
2531
steps:
2632
- uses: actions/checkout@v2
2733
- uses: julia-actions/setup-julia@v1
34+
if: ${{ ! matrix.assertions }}
2835
with:
2936
version: ${{ matrix.version }}
3037
arch: ${{ matrix.arch }}
38+
- name: Download Julia with assertions
39+
if: ${{ matrix.assertions }}
40+
env:
41+
version: ${{ matrix.version }}
42+
arch: ${{ matrix.arch }}
43+
run: |
44+
wget https://julialangnightlies.s3.amazonaws.com/assert_bin/linux/$arch/$version/julia-latest-linux64.tar.gz
45+
tar -xvzf julia-latest-linux64.tar.gz
46+
rm -rf julia-latest-linux64.tar.gz
47+
echo $PWD/julia-*/bin >> $GITHUB_PATH
3148
- uses: actions/cache@v1
3249
env:
3350
cache-name: cache-artifacts

src/driver.jl

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# compiler driver and main interface
22

3+
export JuliaContext
4+
35
# NOTE: the keyword arguments to compile/codegen control those aspects of compilation that
46
# might have to be changed (e.g. set libraries=false when recursing, or set
57
# strip=true for reflection). What remains defines the compilation job itself,
@@ -31,19 +33,54 @@ Other keyword arguments can be found in the documentation of [`cufunction`](@ref
3133
function compile(target::Symbol, @nospecialize(job::CompilerJob);
3234
libraries::Bool=true, deferred_codegen::Bool=true,
3335
optimize::Bool=true, strip::Bool=false, validate::Bool=true,
34-
only_entry::Bool=false)
36+
only_entry::Bool=false, ctx::Union{Context,Nothing}=nothing)
3537
if compile_hook[] !== nothing
3638
compile_hook[](job)
3739
end
3840

3941
return codegen(target, job;
40-
libraries, deferred_codegen, optimize, strip, validate, only_entry)
42+
libraries, deferred_codegen, optimize, strip, validate, only_entry, ctx)
43+
end
44+
45+
# transitionary feature to deal versions of Julia that rely on a global context
46+
#
47+
# Julia 1.9 removed the global LLVM context, requiring to pass a context to codegen APIs,
48+
# so the GPUCompiler APIs have been adapted to require passing a Context object as well.
49+
# however, on older versions of Julia we cannot make codegen emit into that context. we
50+
# could use a hack (serialize + deserialize) to move code into the correct context, however
51+
# as it turns out some of our optimization passes erroneously rely on the context being
52+
# global and unique, resulting in segfaults when we use a local context instead.
53+
#
54+
# to work around this mess, and still present a reasonably unified API, we introduce the
55+
# JuliaContext helper below, which returns a local context on Julia 1.9, and the global
56+
# unique context on all other versions. Once we only support Julia 1.9, we'll deprecate
57+
# this helper to a regular `Contxet()` call.
58+
function JuliaContext()
59+
if VERSION >= v"1.9.0-DEV.115"
60+
# Julia 1.9 knows how to deal with arbitrary contexts
61+
Context()
62+
else
63+
# earlier versions of Julia claim so, but actually use a global context
64+
isboxed_ref = Ref{Bool}()
65+
typ = LLVMType(ccall(:jl_type_to_llvm, LLVM.API.LLVMTypeRef,
66+
(Any, Ptr{Bool}), Any, isboxed_ref))
67+
context(typ)
68+
end
69+
end
70+
function JuliaContext(f)
71+
if VERSION >= v"1.9.0-DEV.115"
72+
Context(f)
73+
else
74+
f(JuliaContext())
75+
# we cannot dispose of the global unique context
76+
end
4177
end
4278

4379
function codegen(output::Symbol, @nospecialize(job::CompilerJob);
4480
libraries::Bool=true, deferred_codegen::Bool=true, optimize::Bool=true,
4581
strip::Bool=false, validate::Bool=true, only_entry::Bool=false,
46-
parent_job::Union{Nothing, CompilerJob} = nothing)
82+
parent_job::Union{Nothing, CompilerJob}=nothing,
83+
ctx::Union{Context,Nothing}=nothing)
4784
## Julia IR
4885

4986
mi, mi_meta = emit_julia(job)
@@ -52,37 +89,56 @@ function codegen(output::Symbol, @nospecialize(job::CompilerJob);
5289
return mi, mi_meta
5390
end
5491

92+
temporary_context = ctx === nothing
93+
if temporary_context && output == :llvm
94+
# if we return IR structures, we cannot construct a temporary context
95+
error("Request to return LLVM IR; please provide a context")
96+
end
5597

56-
## LLVM IR
57-
58-
ir, ir_meta = emit_llvm(job, mi; libraries, deferred_codegen, optimize, only_entry)
98+
try
99+
## LLVM IR
59100

60-
if output == :llvm
61-
if strip
62-
@timeit_debug to "strip debug info" strip_debuginfo!(ir)
101+
if temporary_context
102+
ctx = JuliaContext()
103+
elseif VERSION < v"1.9.0-DEV.115" && ctx != JuliaContext()
104+
error("""Julia <1.9 does not suppport generating code in an arbitrary LLVM context.
105+
Use a JuliaContext instead.""")
63106
end
64107

65-
return ir, ir_meta
66-
end
108+
ir, ir_meta = emit_llvm(job, mi; libraries, deferred_codegen, optimize, only_entry, ctx)
67109

110+
if output == :llvm
111+
if strip
112+
@timeit_debug to "strip debug info" strip_debuginfo!(ir)
113+
end
68114

69-
## machine code
115+
return ir, ir_meta
116+
end
70117

71-
format = if output == :asm
72-
LLVM.API.LLVMAssemblyFile
73-
elseif output == :obj
74-
LLVM.API.LLVMObjectFile
75-
else
76-
error("Unknown assembly format $output")
77-
end
78-
asm, asm_meta = emit_asm(job, ir; strip, validate, format)
79118

80-
if output == :asm || output == :obj
81-
return asm, asm_meta
82-
end
119+
## machine code
83120

121+
format = if output == :asm
122+
LLVM.API.LLVMAssemblyFile
123+
elseif output == :obj
124+
LLVM.API.LLVMObjectFile
125+
else
126+
error("Unknown assembly format $output")
127+
end
128+
asm, asm_meta = emit_asm(job, ir; strip, validate, format)
84129

85-
error("Unknown compilation output $output")
130+
if output == :asm || output == :obj
131+
return asm, asm_meta
132+
end
133+
134+
135+
error("Unknown compilation output $output")
136+
finally
137+
if temporary_context && VERSION >= v"1.9.0-DEV.115"
138+
@assert ctx != JuliaContext()
139+
dispose(ctx)
140+
end
141+
end
86142
end
87143

88144
@locked function emit_julia(@nospecialize(job::CompilerJob))
@@ -135,7 +191,7 @@ const __llvm_initialized = Ref(false)
135191

136192
@locked function emit_llvm(@nospecialize(job::CompilerJob), @nospecialize(method_instance);
137193
libraries::Bool=true, deferred_codegen::Bool=true, optimize::Bool=true,
138-
only_entry::Bool=false)
194+
only_entry::Bool=false, ctx::Context)
139195
if !__llvm_initialized[]
140196
InitializeAllTargets()
141197
InitializeAllTargetInfos()
@@ -146,8 +202,7 @@ const __llvm_initialized = Ref(false)
146202
end
147203

148204
@timeit_debug to "IR generation" begin
149-
ir, compiled = irgen(job, method_instance)
150-
ctx = context(ir)
205+
ir, compiled = irgen(job, method_instance; ctx)
151206
entry_fn = compiled[method_instance].specfunc
152207
entry = functions(ir)[entry_fn]
153208
end
@@ -226,7 +281,7 @@ const __llvm_initialized = Ref(false)
226281
# cached compilation
227282
dyn_entry_fn = get!(cache, dyn_job) do
228283
dyn_ir, dyn_meta = codegen(:llvm, dyn_job; optimize=false,
229-
deferred_codegen=false, parent_job=job)
284+
deferred_codegen=false, parent_job=job, ctx)
230285
dyn_entry_fn = LLVM.name(dyn_meta.entry)
231286
merge!(compiled, dyn_meta.compiled)
232287
@assert context(dyn_ir) == ctx

src/irgen.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# LLVM IR generation
22

3-
function irgen(@nospecialize(job::CompilerJob), method_instance::Core.MethodInstance)
4-
mod, compiled = @timeit_debug to "emission" compile_method_instance(job, method_instance)
3+
function irgen(@nospecialize(job::CompilerJob), method_instance::Core.MethodInstance;
4+
ctx::Context)
5+
mod, compiled = @timeit_debug to "emission" compile_method_instance(job, method_instance; ctx)
56
entry_fn = compiled[method_instance].specfunc
6-
ctx = context(mod)
77

88
# clean up incompatibilities
99
@timeit_debug to "clean-up" begin

src/jlgen.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ function _lookup_fun(mi, min_world, max_world)
326326
end
327327

328328
function compile_method_instance(@nospecialize(job::CompilerJob),
329-
method_instance::MethodInstance)
329+
method_instance::MethodInstance; ctx::Context)
330330
# populate the cache
331331
cache = ci_cache(job)
332332
mt = method_table(job)
@@ -362,11 +362,17 @@ function compile_method_instance(@nospecialize(job::CompilerJob),
362362

363363
# generate IR
364364
GC.@preserve lookup_cb begin
365-
native_code = if VERSION >= v"1.8.0-DEV.661"
365+
native_code = if VERSION >= v"1.9.0-DEV.115"
366+
ccall(:jl_create_native, Ptr{Cvoid},
367+
(Vector{MethodInstance}, LLVM.API.LLVMContextRef, Ptr{Base.CodegenParams}, Cint),
368+
[method_instance], ctx, Ref(params), #=extern policy=# 1)
369+
elseif VERSION >= v"1.8.0-DEV.661"
370+
@assert ctx == JuliaContext()
366371
ccall(:jl_create_native, Ptr{Cvoid},
367372
(Vector{MethodInstance}, Ptr{Base.CodegenParams}, Cint),
368373
[method_instance], Ref(params), #=extern policy=# 1)
369374
else
375+
@assert ctx == JuliaContext()
370376
ccall(:jl_create_native, Ptr{Cvoid},
371377
(Vector{MethodInstance}, Base.CodegenParams, Cint),
372378
[method_instance], params, #=extern policy=# 1)

src/precompile.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ function _precompile_()
5959
#@assert precompile(Tuple{typeof(GPUCompiler.split_kwargs),Tuple{},Vector{Symbol},Vararg{Vector{Symbol}, N} where N})
6060
let fbody = try __lookup_kwbody__(which(GPUCompiler.compile, (Symbol,GPUCompiler.CompilerJob,))) catch missing end
6161
if !ismissing(fbody)
62-
@assert precompile(fbody, (Bool,Bool,Bool,Bool,Bool,Bool,typeof(GPUCompiler.compile),Symbol,GPUCompiler.CompilerJob,))
62+
@assert precompile(fbody, (Bool,Bool,Bool,Bool,Bool,Bool,Context,typeof(GPUCompiler.compile),Symbol,GPUCompiler.CompilerJob,))
63+
@assert precompile(fbody, (Bool,Bool,Bool,Bool,Bool,Bool,Nothing,typeof(GPUCompiler.compile),Symbol,GPUCompiler.CompilerJob,))
6364
end
6465
end
6566
end

src/reflection.jl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,12 @@ See also: [`@device_code_llvm`](@ref), `InteractiveUtils.code_llvm`
100100
function code_llvm(io::IO, @nospecialize(job::CompilerJob); optimize::Bool=true, raw::Bool=false,
101101
debuginfo::Symbol=:default, dump_module::Bool=false)
102102
# NOTE: jl_dump_function_ir supports stripping metadata, so don't do it in the driver
103-
ir, meta = codegen(:llvm, job; optimize=optimize, strip=false, validate=false)
104-
str = ccall(:jl_dump_function_ir, Ref{String},
105-
(LLVM.API.LLVMValueRef, Bool, Bool, Ptr{UInt8}),
106-
meta.entry, !raw, dump_module, debuginfo)
103+
str = JuliaContext() do ctx
104+
ir, meta = codegen(:llvm, job; optimize=optimize, strip=false, validate=false, ctx)
105+
ccall(:jl_dump_function_ir, Ref{String},
106+
(LLVM.API.LLVMValueRef, Bool, Bool, Ptr{UInt8}),
107+
meta.entry, !raw, dump_module, debuginfo)
108+
end
107109
highlight(io, str, "llvm")
108110
end
109111
code_llvm(@nospecialize(job::CompilerJob); kwargs...) = code_llvm(stdout, job; kwargs...)

src/rtlib.jl

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ end
7575

7676
## functionality to build the runtime library
7777

78-
function emit_function!(mod, @nospecialize(job::CompilerJob), f, method)
78+
function emit_function!(mod, @nospecialize(job::CompilerJob), f, method; ctx::Context)
7979
tt = Base.to_tuple_type(method.types)
8080
new_mod, meta = codegen(:llvm, similar(job, FunctionSpec(f, tt, #=kernel=# false));
81-
optimize=false, libraries=false)
81+
optimize=false, libraries=false, ctx)
8282
ft = eltype(llvmtype(meta.entry))
8383
expected_ft = convert(LLVM.FunctionType, method; ctx=context(new_mod))
8484
if return_type(ft) != return_type(expected_ft)
@@ -92,12 +92,6 @@ function emit_function!(mod, @nospecialize(job::CompilerJob), f, method)
9292
dispose(pm)
9393

9494
temp_name = LLVM.name(meta.entry)
95-
# FIXME: on 1.6, there's no single global LLVM context anymore,
96-
# but there's no API yet to pass a context to codegen.
97-
# round-trip the module through serialization to get it in the proper context.
98-
buf = convert(MemoryBuffer, new_mod)
99-
new_mod = parse(LLVM.Module, buf; ctx=context(mod))
100-
@assert context(mod) == context(new_mod)
10195
link!(mod, new_mod)
10296
entry = functions(mod)[temp_name]
10397

@@ -128,7 +122,7 @@ function build_runtime(@nospecialize(job::CompilerJob); ctx)
128122
else
129123
method.def
130124
end
131-
emit_function!(mod, job, def, method)
125+
emit_function!(mod, job, def, method; ctx)
132126
end
133127

134128
# we cannot optimize the runtime library, because the code would then be optimized again

test/definitions/native.jl

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ module LazyCodegen
101101
@assert !cc.compiled
102102
job = cc.job
103103

104-
ir, meta = GPUCompiler.codegen(:llvm, job; validate=false)
105-
entry_name = name(meta.entry)
106-
107-
jitted_mod = compile!(orc, ir)
104+
entry_name, jitted_mod = JuliaContext() do ctx
105+
ir, meta = GPUCompiler.codegen(:llvm, job; validate=false, ctx)
106+
name(meta.entry), compile!(orc, ir)
107+
end
108108

109109
addr = addressin(orc, jitted_mod, entry_name)
110110
ptr = pointer(addr)
@@ -195,21 +195,24 @@ module LazyCodegen
195195
sym = LLVM.API.LLVMOrcCSymbolFlagsMapPair(mangle(lljit, target_sym), flags)
196196

197197
function materialize(mr)
198-
ir, meta = GPUCompiler.codegen(:llvm, job; validate=false)
198+
JuliaContext() do ctx
199+
ir, meta = GPUCompiler.codegen(:llvm, job; validate=false, ctx)
199200

200-
# Rename entry to match target_sym
201-
LLVM.name!(meta.entry, target_sym)
201+
# Rename entry to match target_sym
202+
LLVM.name!(meta.entry, target_sym)
202203

203-
# So 1. serialize the module
204-
buf = convert(MemoryBuffer, ir)
204+
# So 1. serialize the module
205+
buf = convert(MemoryBuffer, ir)
205206

206-
# 2. deserialize and wrap by a ThreadSafeModule
207-
ctx = ThreadSafeContext()
208-
mod = parse(LLVM.Module, buf; ctx=context(ctx))
209-
tsm = ThreadSafeModule(mod; ctx)
207+
# 2. deserialize and wrap by a ThreadSafeModule
208+
ThreadSafeContext() do ctx
209+
mod = parse(LLVM.Module, buf; ctx=context(ctx))
210+
tsm = ThreadSafeModule(mod; ctx)
210211

211-
il = LLVM.IRTransformLayer(lljit)
212-
LLVM.emit(il, mr, tsm)
212+
il = LLVM.IRTransformLayer(lljit)
213+
LLVM.emit(il, mr, tsm)
214+
end
215+
end
213216

214217
return nothing
215218
end
@@ -252,7 +255,7 @@ module LazyCodegen
252255
after = :(ret)
253256

254257
# Note this follows: emit_call_specfun_other
255-
Context() do ctx
258+
JuliaContext() do ctx
256259
T_jlvalue = LLVM.StructType(LLVMType[],;ctx)
257260
T_prjlvalue = LLVM.PointerType(T_jlvalue, #= AddressSpace::Tracked =# 10)
258261

0 commit comments

Comments
 (0)