Skip to content
This repository was archived by the owner on May 27, 2021. It is now read-only.

Commit 655df5f

Browse files
authored
Merge pull request #331 from JuliaGPU/tb/lower_throw
Lower throw and friends in an LLVM pass.
2 parents 9b524d5 + 6070bc2 commit 655df5f

File tree

3 files changed

+99
-44
lines changed

3 files changed

+99
-44
lines changed

src/compiler/irgen.jl

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,6 @@ safe_fn(fn::String) = replace(fn, r"[^aA-zZ0-9_]"=>"_")
1515
safe_fn(f::Core.Function) = safe_fn(String(typeof(f).name.mt.name))
1616
safe_fn(f::LLVM.Function) = safe_fn(LLVM.name(f))
1717

18-
# NOTE: we remove `throw_...` functions in the ThrowRemoval pass, but that relies on
19-
# function names which is fragile. Remove actual calls to `jl_throw` here, to be safe.
20-
const exception_arguments = Vector{LLVM.Value}()
21-
function raise_exception(insblock::BasicBlock, ex::Value)
22-
fun = LLVM.parent(insblock)
23-
mod = LLVM.parent(fun)
24-
25-
let builder = Builder(JuliaContext())
26-
position!(builder, insblock)
27-
emit_exception!(builder, "unknown", ex)
28-
dispose(builder)
29-
end
30-
31-
# mark arguments that passed to `throw` for removal, as they are often GC-allocated.
32-
push!(exception_arguments, ex)
33-
end
34-
3518
# generate a pseudo-backtrace from a stack of methods being emitted
3619
function backtrace(ctx::CompilerContext, method_stack::Vector{Core.MethodInstance})
3720
bt = StackTraces.StackFrame[]
@@ -57,11 +40,6 @@ function compile_linfo(ctx::CompilerContext, linfo::Core.MethodInstance, world)
5740
ref = convert(LLVM.API.LLVMModuleRef, ref)
5841
module_setup(LLVM.Module(ref))
5942
end
60-
function hook_raise_exception(insblock::Ptr{Cvoid}, ex::Ptr{Cvoid})
61-
insblock = convert(LLVM.API.LLVMValueRef, insblock)
62-
ex = convert(LLVM.API.LLVMValueRef, ex)
63-
raise_exception(BasicBlock(insblock), Value(ex))
64-
end
6543
dependencies = Vector{LLVM.Module}()
6644
function hook_module_activation(ref::Ptr{Cvoid})
6745
ref = convert(LLVM.API.LLVMModuleRef, ref)
@@ -103,7 +81,6 @@ function compile_linfo(ctx::CompilerContext, linfo::Core.MethodInstance, world)
10381
prefer_specsig = true,
10482
module_setup = hook_module_setup,
10583
module_activation = hook_module_activation,
106-
raise_exception = hook_raise_exception,
10784
emit_function = hook_emit_function,
10885
emitted_function = hook_emitted_function)
10986

@@ -189,15 +166,6 @@ function irgen(ctx::CompilerContext)
189166
end
190167
end
191168

192-
# HACK: remove unused arguments to exception functions
193-
for val in exception_arguments
194-
if isa(val, LLVM.Instruction) && isempty(uses(val))
195-
bb = LLVM.parent(val)
196-
unsafe_delete!(bb, val)
197-
end
198-
end
199-
empty!(exception_arguments)
200-
201169
# rename the entry point
202170
llvmfn = replace(LLVM.name(entry), r"_\d+$"=>"")
203171
## append a global unique counter
@@ -211,6 +179,7 @@ function irgen(ctx::CompilerContext)
211179
global global_ctx
212180
global_ctx = ctx
213181

182+
add!(pm, ModulePass("LowerThrow", lower_throw!))
214183
add!(pm, FunctionPass("HideUnreachable", hide_unreachable!))
215184
add!(pm, FunctionPass("HideTrap", hide_trap!))
216185
always_inliner!(pm)
@@ -221,6 +190,75 @@ function irgen(ctx::CompilerContext)
221190
return mod, entry
222191
end
223192

193+
# this pass lowers `jl_throw` and friends to GPU-compatible exceptions.
194+
# this isn't strictly necessary, but has a couple of advantages:
195+
# - we can kill off unused exception arguments that otherwise would allocate or invoke
196+
# - we can fake debug information (lacking a stack unwinder)
197+
#
198+
# once we have thorough inference (ie. discarding `@nospecialize` and thus supporting
199+
# exception arguments) and proper debug info to unwind the stack, this pass can go.
200+
function lower_throw!(mod::LLVM.Module)
201+
ctx = global_ctx::CompilerContext
202+
changed = false
203+
204+
throw_functions = Dict{String,String}(
205+
"jl_throw" => "exception",
206+
"jl_error" => "error",
207+
"jl_too_few_args" => "too few arguments exception",
208+
"jl_too_many_args" => "too many arguments exception",
209+
"jl_type_error_rt" => "type error",
210+
"jl_undefined_var_error" => "undefined variable error",
211+
"jl_bounds_error" => "bounds error",
212+
"jl_bounds_error_v" => "bounds error",
213+
"jl_bounds_error_int" => "bounds error",
214+
"jl_bounds_error_tuple_int" => "bounds error",
215+
"jl_bounds_error_unboxed_int" => "bounds error",
216+
"jl_bounds_error_ints" => "bounds error",
217+
"jl_eof_error" => "EOF error"
218+
)
219+
220+
for (fn, name) in throw_functions
221+
if haskey(functions(mod), fn)
222+
f = functions(mod)[fn]
223+
224+
for use in uses(f)
225+
call = user(use)::LLVM.CallInst
226+
227+
# replace the throw with a PTX-compatible exception
228+
let builder = Builder(JuliaContext())
229+
position!(builder, call)
230+
emit_exception!(builder, name, call)
231+
dispose(builder)
232+
end
233+
234+
# remove the call
235+
call_args = collect(operands(call))[1:end-1] # last arg is function itself
236+
unsafe_delete!(LLVM.parent(call), call)
237+
238+
# HACK: kill the exceptions' unused arguments
239+
for arg in call_args
240+
# peek through casts
241+
if isa(arg, LLVM.AddrSpaceCastInst)
242+
cast = arg
243+
arg = first(operands(cast))
244+
isempty(uses(cast)) && unsafe_delete!(LLVM.parent(cast), cast)
245+
end
246+
247+
if isa(arg, LLVM.Instruction) && isempty(uses(arg))
248+
unsafe_delete!(LLVM.parent(arg), arg)
249+
end
250+
end
251+
252+
changed = true
253+
end
254+
255+
@compiler_assert isempty(uses(f)) ctx
256+
end
257+
end
258+
259+
return changed
260+
end
261+
224262
# report an exception in a GPU-compatible manner
225263
#
226264
# the exact behavior depends on the debug level. in all cases, a `trap` will be emitted, On

src/device/runtime_intrinsics.jl

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ end
7979

8080
function report_exception(ex)
8181
@cuprintf("""
82-
ERROR: a %s exception occurred during kernel execution.
82+
ERROR: a %s was thrown during kernel execution.
8383
Run Julia on debug level 2 for device stack traces.
8484
""", ex)
8585
return
@@ -89,7 +89,7 @@ compile(report_exception, Nothing, (Ptr{Cchar},))
8989

9090
function report_exception_name(ex)
9191
@cuprintf("""
92-
ERROR: a %s exception occurred during kernel execution.
92+
ERROR: a %s was thrown during kernel execution.
9393
Stacktrace:
9494
""", ex)
9595
return
@@ -103,14 +103,8 @@ end
103103
compile(report_exception_frame, Nothing, (Cint, Ptr{Cchar}, Ptr{Cchar}, Cint))
104104
compile(report_exception_name, Nothing, (Ptr{Cchar},))
105105

106-
function bounds_error_unboxed_int(data, vt, i)
107-
@cuprintf("ERROR: a bounds error occurred during kernel execution.")
108-
# FIXME: have this call emit_exception somehow
109-
return
110-
end
111-
112-
compile(bounds_error_unboxed_int, Nothing, (Ptr{Cvoid}, Any, Csize_t);
113-
llvm_name="jl_bounds_error_unboxed_int")
106+
# NOTE: no throw functions are provided here, but replaced by an LLVM pass instead
107+
# in order to provide some debug information without stack unwinding.
114108

115109

116110
## GC

test/device/execution.jl

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,21 +524,44 @@ let (code, out, err) = julia_script(script, `-g1`)
524524
@test code == 1
525525
@test occursin("ERROR: CUDA error: an illegal instruction was encountered", err) ||
526526
occursin("ERROR: CUDA error: unspecified launch failure", err)
527-
@test occursin(r"ERROR: a .+ exception occurred during kernel execution", out)
527+
@test occursin(r"ERROR: a exception was thrown during kernel execution", out)
528528
@test occursin(r"Run Julia on debug level 2 for device stack traces", out)
529529
end
530530

531531
let (code, out, err) = julia_script(script, `-g2`)
532532
@test code == 1
533533
@test occursin("ERROR: CUDA error: an illegal instruction was encountered", err) ||
534534
occursin("ERROR: CUDA error: unspecified launch failure", err)
535-
@test occursin(r"ERROR: a .+ exception occurred during kernel execution", out)
535+
@test occursin(r"ERROR: a exception was thrown during kernel execution", out)
536536
@test occursin("[1] Type at float.jl", out)
537537
@test occursin("[2] kernel at none:2", out)
538538
end
539539

540540
end
541541

542+
@testset "#329" begin
543+
544+
script = """
545+
@noinline foo(a, i) = a[1] = i
546+
bar(a) = (foo(a, 42); nothing)
547+
548+
ptr = CUDAnative.DevicePtr{Int,AS.Global}(convert(Ptr{Int}, C_NULL))
549+
arr = CuDeviceArray{Int,1,AS.Global}((0,), ptr)
550+
@cuda bar(arr)
551+
CUDAdrv.synchronize()
552+
"""
553+
554+
let (code, out, err) = julia_script(script, `-g2`)
555+
@test code == 1
556+
@test occursin("ERROR: CUDA error: an illegal instruction was encountered", err) ||
557+
occursin("ERROR: CUDA error: unspecified launch failure", err)
558+
@test occursin(r"ERROR: a exception was thrown during kernel execution", out)
559+
@test occursin("foo at none:1", out)
560+
@test occursin("bar at none:2", out)
561+
end
562+
563+
end
564+
542565
end
543566

544567
############################################################################################

0 commit comments

Comments
 (0)