|
1 | | -""" |
2 | | -- `status::Int32`: whether exception has been thrown (0 - no, 1 - yes). |
3 | | -""" |
4 | | -struct ExceptionInfo |
5 | | - status::Int32 |
6 | | - output_lock::Int32 |
| 1 | +# Exception reason codes — encoded in bits [7:0] of the packed exception UInt64 |
| 2 | +# 0 means no exception |
| 3 | +module ExceptionCode |
| 4 | + const NONE = UInt8(0) |
| 5 | + const UNKNOWN = UInt8(1) |
| 6 | + const BOUNDS_ERROR = UInt8(2) |
| 7 | + const DOMAIN_ERROR = UInt8(3) |
| 8 | + const OVERFLOW_ERROR = UInt8(4) |
| 9 | + const INEXACT_ERROR = UInt8(5) |
| 10 | + const ARGUMENT_ERROR = UInt8(6) |
| 11 | + const DIVIDE_ERROR = UInt8(7) |
| 12 | + const DIM_MISMATCH = UInt8(8) |
| 13 | +end |
7 | 14 |
|
8 | | - thread::@NamedTuple{x::UInt32, y::UInt32, z::UInt32} |
9 | | - block::@NamedTuple{x::UInt32, y::UInt32, z::UInt32} |
| 15 | +const EXCEPTION_REASON_STRINGS = Dict{UInt8, String}( |
| 16 | + ExceptionCode.NONE => "No exception", |
| 17 | + ExceptionCode.UNKNOWN => "Unknown exception", |
| 18 | + ExceptionCode.BOUNDS_ERROR => "BoundsError: Out-of-bounds array access", |
| 19 | + ExceptionCode.DOMAIN_ERROR => "DomainError", |
| 20 | + ExceptionCode.OVERFLOW_ERROR => "OverflowError", |
| 21 | + ExceptionCode.INEXACT_ERROR => "InexactError: Inexact conversion", |
| 22 | + ExceptionCode.ARGUMENT_ERROR => "ArgumentError", |
| 23 | + ExceptionCode.DIVIDE_ERROR => "DivideError: Integer division error", |
| 24 | + ExceptionCode.DIM_MISMATCH => "DimensionMismatch", |
| 25 | +) |
10 | 26 |
|
11 | | - reason::LLVMPtr{UInt8, AS.Global} |
12 | | - reason_length::Int64 |
| 27 | +# Packed exception format (UInt64): |
| 28 | +# [63:48] workgroup_x (16 bits) |
| 29 | +# [47:32] workgroup_y (16 bits) |
| 30 | +# [31:16] workgroup_z (16 bits) |
| 31 | +# [15:8] reserved |
| 32 | +# [7:0] error code (non-zero = exception occurred) |
13 | 33 |
|
14 | | - ExceptionInfo() = new( |
15 | | - Int32(0), Int32(0), |
16 | | - (; x=UInt32(0), y=UInt32(0), z=UInt32(0)), |
17 | | - (; x=UInt32(0), y=UInt32(0), z=UInt32(0)), |
18 | | - LLVMPtr{UInt8, AS.Global}(), 0) |
| 34 | +@inline function pack_exception(code::UInt8) |
| 35 | + wg = workgroupIdx() |
| 36 | + wg_x = UInt64(wg.x % UInt16) << 48 |
| 37 | + wg_y = UInt64(wg.y % UInt16) << 32 |
| 38 | + wg_z = UInt64(wg.z % UInt16) << 16 |
| 39 | + return wg_x | wg_y | wg_z | UInt64(code) |
19 | 40 | end |
20 | 41 |
|
21 | | -@inline function Base.getproperty(ei::Ptr{ExceptionInfo}, field::Symbol) |
22 | | - if field == :status |
23 | | - unsafe_load(convert(Ptr{Int32}, ei)) |
24 | | - elseif field == :output_lock |
25 | | - unsafe_load(convert(Ptr{Int32}, ei + sizeof(Int32))) |
26 | | - elseif field == :output_lock_ptr |
27 | | - reinterpret(LLVMPtr{Int32, AS.Generic}, ei + sizeof(Int32)) |
28 | | - elseif field == :thread |
29 | | - offset = 2 * sizeof(Int32) |
30 | | - unsafe_load(convert(Ptr{@NamedTuple{x::UInt32, y::UInt32, z::UInt32}}, ei + offset)) |
31 | | - elseif field == :block |
32 | | - offset = 2 * sizeof(Int32) + sizeof(@NamedTuple{x::UInt32, y::UInt32, z::UInt32}) |
33 | | - unsafe_load(convert(Ptr{@NamedTuple{x::UInt32, y::UInt32, z::UInt32}}, ei + offset)) |
34 | | - elseif field == :reason |
35 | | - offset = |
36 | | - 2 * sizeof(Int32) + |
37 | | - 2 * sizeof(@NamedTuple{x::UInt32, y::UInt32, z::UInt32}) |
38 | | - unsafe_load(convert(Ptr{LLVMPtr{UInt8, AS.Global}}, ei + offset)) |
39 | | - elseif field == :reason_length |
40 | | - offset = |
41 | | - 2 * sizeof(Int32) + |
42 | | - 2 * sizeof(@NamedTuple{x::UInt32, y::UInt32, z::UInt32}) + |
43 | | - sizeof(LLVMPtr{UInt8, AS.Global}) |
44 | | - unsafe_load(convert(Ptr{Int64}, ei + offset)) |
45 | | - else |
46 | | - getfield(ei, field) |
47 | | - end |
| 42 | +@inline function unpack_exception(packed::UInt64) |
| 43 | + wg_x = UInt16((packed >> 48) & 0xFFFF) |
| 44 | + wg_y = UInt16((packed >> 32) & 0xFFFF) |
| 45 | + wg_z = UInt16((packed >> 16) & 0xFFFF) |
| 46 | + code = UInt8(packed & 0xFF) |
| 47 | + return (; wg_x, wg_y, wg_z, code) |
48 | 48 | end |
49 | 49 |
|
50 | | -@inline function Base.setproperty!(ei::Ptr{ExceptionInfo}, field::Symbol, value) |
51 | | - if field == :status |
52 | | - unsafe_store!(convert(Ptr{Int32}, ei), value) |
53 | | - elseif field == :output_lock |
54 | | - unsafe_store!(convert(Ptr{Int32}, ei + sizeof(Int32)), value) |
55 | | - elseif field == :thread |
56 | | - offset = 2 * sizeof(Int32) |
57 | | - unsafe_store!(convert(Ptr{@NamedTuple{x::UInt32, y::UInt32, z::UInt32}}, ei + offset), value) |
58 | | - elseif field == :block |
59 | | - offset = 2 * sizeof(Int32) + sizeof(@NamedTuple{x::UInt32, y::UInt32, z::UInt32}) |
60 | | - unsafe_store!(convert(Ptr{@NamedTuple{x::UInt32, y::UInt32, z::UInt32}}, ei + offset), value) |
61 | | - elseif field == :reason |
62 | | - offset = |
63 | | - 2 * sizeof(Int32) + |
64 | | - 2 * sizeof(@NamedTuple{x::UInt32, y::UInt32, z::UInt32}) |
65 | | - unsafe_store!(convert(Ptr{LLVMPtr{UInt8, AS.Global}}, ei + offset), value) |
66 | | - elseif field == :reason_length |
67 | | - offset = |
68 | | - 2 * sizeof(Int32) + |
69 | | - 2 * sizeof(@NamedTuple{x::UInt32, y::UInt32, z::UInt32}) + |
70 | | - sizeof(LLVMPtr{UInt8, AS.Global}) |
71 | | - unsafe_store!(convert(Ptr{Int64}, ei + offset), value) |
72 | | - else |
73 | | - setfield!(ei, field, value) |
74 | | - end |
75 | | -end |
| 50 | +# Legacy compat — ExceptionInfo is now just a UInt64 |
| 51 | +const ExceptionInfo = UInt64 |
76 | 52 |
|
77 | 53 | function alloc_exception_info() |
78 | | - ei_ptr = Mem.HostBuffer(sizeof(ExceptionInfo), HIP.hipHostAllocDefault) |
79 | | - unsafe_store!(convert(Ptr{ExceptionInfo}, ei_ptr), ExceptionInfo()) |
| 54 | + ei_ptr = Mem.HostBuffer(sizeof(UInt64), HIP.hipHostAllocDefault) |
| 55 | + unsafe_store!(convert(Ptr{UInt64}, ei_ptr), UInt64(0)) |
80 | 56 | return ei_ptr |
81 | 57 | end |
82 | 58 |
|
83 | | -@inline function lock_output!(ei::Ptr{ExceptionInfo}) |
84 | | - # if llvm_atomic_cas(ei.output_lock_ptr, zero(Int32), one(Int32)) == zero(Int32) |
85 | | - if llvm_atomic_cas(ei.output_lock_ptr, Int32(0x0), Int32(0x1)) == Int32(0x0) |
86 | | - # Take the lock & write thread info. |
87 | | - ei.thread = workitemIdx() |
88 | | - ei.block = workgroupIdx() |
89 | | - sync_workgroup() |
90 | | - return true |
91 | | - elseif ( |
92 | | - ei.output_lock == Int32(0x1) && |
93 | | - ei.thread == workitemIdx() && |
94 | | - ei.block == workgroupIdx() |
95 | | - ) |
96 | | - # Thread already has the lock. |
97 | | - return true |
98 | | - else |
99 | | - # Other thread has the lock. |
100 | | - return false |
101 | | - end |
| 59 | +@inline function signal_exception!(ei::Ptr{UInt64}, code::UInt8) |
| 60 | + packed = pack_exception(code) |
| 61 | + # First writer wins via atomic CAS; losers are no-ops. |
| 62 | + ei_llvm = reinterpret(LLVMPtr{UInt64, AS.Generic}, ei) |
| 63 | + llvm_atomic_cas(ei_llvm, UInt64(0), packed) |
| 64 | + endpgm() |
| 65 | + return |
102 | 66 | end |
103 | 67 |
|
104 | 68 | macro gpu_throw(reason) |
| 69 | + code = _reason_to_code(reason) |
105 | 70 | quote |
106 | 71 | ei = kernel_state().exception_info |
107 | | - if lock_output!(ei) |
108 | | - reason_ptr, reason_length = @strptr $reason |
109 | | - ei.reason = reason_ptr |
110 | | - ei.reason_length = reason_length |
111 | | - end |
112 | | - throw(nothing) |
| 72 | + signal_exception!(ei, $code) |
| 73 | + throw(nothing) # unreachable, but keeps Julia's type system happy |
| 74 | + end |
| 75 | +end |
| 76 | + |
| 77 | +# Map reason strings to error codes at macro expansion time |
| 78 | +function _reason_to_code(reason::String) |
| 79 | + if startswith(reason, "BoundsError") |
| 80 | + ExceptionCode.BOUNDS_ERROR |
| 81 | + elseif startswith(reason, "DomainError") |
| 82 | + ExceptionCode.DOMAIN_ERROR |
| 83 | + elseif startswith(reason, "OverflowError") |
| 84 | + ExceptionCode.OVERFLOW_ERROR |
| 85 | + elseif startswith(reason, "InexactError") |
| 86 | + ExceptionCode.INEXACT_ERROR |
| 87 | + elseif startswith(reason, "ArgumentError") |
| 88 | + ExceptionCode.ARGUMENT_ERROR |
| 89 | + elseif startswith(reason, "DivideError") |
| 90 | + ExceptionCode.DIVIDE_ERROR |
| 91 | + elseif startswith(reason, "DimensionMismatch") |
| 92 | + ExceptionCode.DIM_MISMATCH |
| 93 | + else |
| 94 | + ExceptionCode.UNKNOWN |
113 | 95 | end |
114 | 96 | end |
| 97 | +_reason_to_code(reason) = ExceptionCode.UNKNOWN |
0 commit comments