Skip to content

Commit 4a4be52

Browse files
authored
Merge pull request #880 from JuliaGPU/tb/static_assert
Add a static assertion macro
2 parents 081c892 + cba2ed9 commit 4a4be52

6 files changed

Lines changed: 147 additions & 40 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "GPUCompiler"
22
uuid = "61eb1bfa-7361-4325-ad38-22787b887f55"
3-
version = "2.0.1"
3+
version = "2.1.0"
44
authors = ["Tim Besard <tim.besard@gmail.com>"]
55

66
[workspace]

src/GPUCompiler.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ include("driver.jl")
7777

7878
# other reusable functionality
7979
include("execution.jl")
80+
include("static_assert.jl")
8081
include("reflection.jl")
8182

8283
include("precompile.jl")

src/ptx.jl

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,6 @@ const NVPTX_LLVM_Backend_jll =
99

1010
export PTXCompilerTarget
1111

12-
# Wire-format encoding of the feature set, stamped into the `sm_features` LLVM
13-
# global by `finish_module!` and read back by host-side runtime intrinsics (e.g.
14-
# CUDA.jl's `target_feature_set()`).
15-
@enum TargetFeatureSet::UInt32 begin
16-
BaselineFeatures = 0
17-
FamilyFeatures = 1
18-
ArchFeatures = 2
19-
end
20-
2112
Base.@kwdef struct PTXCompilerTarget <: AbstractCompilerTarget
2213
cap::VersionNumber
2314
ptx::VersionNumber = v"6.0" # for compatibility with older versions of CUDA.jl
@@ -151,26 +142,6 @@ function finish_module!(@nospecialize(job::CompilerJob{PTXCompilerTarget}),
151142
Metadata(ConstantInt(Int32(job.config.target.fastmath ? 1 : 0)))
152143
end
153144

154-
# emit the device capability and ptx isa version as constants in the module. this makes
155-
# it possible to 'query' these in device code, relying on LLVM to optimize the checks
156-
# away and generate static code. note that we only do so if there's actual uses of these
157-
# variables; unconditionally creating a gvar would result in duplicate declarations.
158-
sm_features = job.config.target.feature_set === :arch ? ArchFeatures :
159-
job.config.target.feature_set === :family ? FamilyFeatures :
160-
BaselineFeatures
161-
for (name, value) in ["sm_major" => job.config.target.cap.major,
162-
"sm_minor" => job.config.target.cap.minor,
163-
"sm_features" => UInt32(sm_features),
164-
"ptx_major" => job.config.target.ptx.major,
165-
"ptx_minor" => job.config.target.ptx.minor]
166-
if haskey(globals(mod), name)
167-
gv = globals(mod)[name]
168-
initializer!(gv, ConstantInt(LLVM.Int32Type(), value))
169-
# change the linkage so that we can inline the value
170-
linkage!(gv, LLVM.API.LLVMPrivateLinkage)
171-
end
172-
end
173-
174145
# update calling convention
175146
if LLVM.version() >= v"8"
176147
for f in functions(mod)
@@ -244,14 +215,6 @@ function finish_module!(@nospecialize(job::CompilerJob{PTXCompilerTarget}),
244215
end
245216
end
246217

247-
# we emit properties (of the device and ptx isa) as private global constants,
248-
# so run the optimizer so that they are inlined before the rest of the optimizer runs.
249-
@dispose pb=NewPMPassBuilder() begin
250-
add!(pb, RecomputeGlobalsAAPass())
251-
add!(pb, GlobalOptPass())
252-
run!(pb, mod, llvm_machine(job.config.target))
253-
end
254-
255218
return entry
256219
end
257220

src/static_assert.jl

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
export @static_assert
2+
3+
"""
4+
@static_assert condition message
5+
6+
Require `condition` to be proven true while compiling device code. Unlike
7+
`Base.@static`, the condition is evaluated after target-specific values have
8+
been propagated through LLVM. A condition that remains dynamic is rejected;
9+
this is not a runtime assertion mechanism.
10+
11+
`message` must be a string literal. It is reported with the device-code
12+
backtrace if the assertion cannot be proven.
13+
"""
14+
macro static_assert(condition, message)
15+
message isa String || throw(ArgumentError("@static_assert message must be a string literal"))
16+
marker = static_assert_marker(message)
17+
return quote
18+
if !$(esc(condition))
19+
$marker
20+
end
21+
nothing
22+
end
23+
end
24+
25+
const STATIC_ASSERT_MARKER = "gpu_static_assert"
26+
const STATIC_ASSERTION = "static assertion failed"
27+
28+
function static_assert_marker(message::String)
29+
LLVM.Context() do _
30+
entry, entry_type = LLVM.Interop.create_function()
31+
mod = LLVM.parent(entry)
32+
@dispose builder=IRBuilder() begin
33+
block = BasicBlock(entry, "entry")
34+
position!(builder, block)
35+
36+
# LLVM.jl owns the pointer representation and creates an anonymous private
37+
# string global, just like LLVM's annotation helpers.
38+
string = globalstring_ptr!(builder, message)
39+
marker_type = LLVM.FunctionType(LLVM.VoidType(), [value_type(string)])
40+
marker = LLVM.Function(mod, STATIC_ASSERT_MARKER, marker_type)
41+
call!(builder, marker_type, marker, [string])
42+
ret!(builder)
43+
end
44+
return LLVM.Interop.call_function(entry, Nothing, Tuple{})
45+
end
46+
end
47+
48+
function static_assert_message(inst::LLVM.CallInst)
49+
try
50+
value = only(arguments(inst))
51+
while value isa ConstantExpr
52+
value = first(operands(value))
53+
end
54+
value isa GlobalVariable || return nothing
55+
initializer = LLVM.initializer(value)
56+
initializer isa ConstantDataSequential || initializer isa ConstantArray || return nothing
57+
values = initializer isa ConstantDataSequential ? collect(initializer) : operands(initializer)
58+
bytes = UInt8[convert(UInt8, byte) for byte in values]
59+
!isempty(bytes) && bytes[end] == 0x00 && pop!(bytes)
60+
return String(bytes)
61+
catch err
62+
err isa ArgumentError || err isa BoundsError || rethrow()
63+
return nothing
64+
end
65+
end

src/validation.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,15 @@ const DYNAMIC_CALL = "dynamic function invocation"
135135
function Base.showerror(io::IO, err::InvalidIRError)
136136
print(io, "InvalidIRError: compiling ", err.job.source, " resulted in invalid LLVM IR")
137137
for (kind, bt, meta) in err.errors
138-
printstyled(io, "\nReason: unsupported $kind"; color=:red)
138+
prefix = kind == STATIC_ASSERTION ? "Reason: $kind" : "Reason: unsupported $kind"
139+
printstyled(io, "\n$prefix"; color=:red)
139140
if meta !== nothing
140141
if kind == RUNTIME_FUNCTION || kind == UNKNOWN_FUNCTION || kind == POINTER_FUNCTION || kind == DYNAMIC_CALL || kind == CCALL_FUNCTION || kind == LAZY_FUNCTION
141142
printstyled(io, " (call to ", meta, ")"; color=:red)
142143
elseif kind == DELAYED_BINDING
143144
printstyled(io, " (use of '", meta, "')"; color=:red)
145+
elseif kind == STATIC_ASSERTION
146+
printstyled(io, " (", meta, ")"; color=:red)
144147
end
145148
end
146149
Base.show_backtrace(io, bt)
@@ -225,7 +228,9 @@ function check_ir!(job, errors::Vector{IRError}, inst::LLVM.CallInst)
225228
fn = LLVM.name(dest)
226229

227230
# some special handling for runtime functions that we don't implement
228-
if fn == "jl_get_binding_or_error" || fn == "ijl_get_binding_or_error"
231+
if fn == STATIC_ASSERT_MARKER
232+
push!(errors, (STATIC_ASSERTION, bt, static_assert_message(inst)))
233+
elseif fn == "jl_get_binding_or_error" || fn == "ijl_get_binding_or_error"
229234
try
230235
m, sym = arguments(inst)
231236
sym = first(operands(sym::ConstantExpr))::ConstantInt

test/native.jl

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,79 @@ end
848848
end
849849
end
850850

851+
@testset "static assertions" begin
852+
mod = @eval module $(gensym())
853+
using ..GPUCompiler
854+
kernel() = (@static_assert true "this should disappear"; return)
855+
end
856+
857+
llvm = sprint(io -> Native.code_llvm(io, mod.kernel, Tuple{}; dump_module=true))
858+
@test !occursin(GPUCompiler.STATIC_ASSERT_MARKER, llvm)
859+
@test Native.code_execution(mod.kernel, Tuple{}) !== nothing
860+
861+
mod = @eval module $(gensym())
862+
using ..GPUCompiler
863+
kernel() = (@static_assert false "the target is too old"; return)
864+
end
865+
@test_throws_message(InvalidIRError,
866+
Native.code_execution(mod.kernel, Tuple{})) do msg
867+
occursin(GPUCompiler.STATIC_ASSERTION, msg) &&
868+
occursin("the target is too old", msg) &&
869+
occursin("kernel", msg)
870+
end
871+
872+
mod = @eval module $(gensym())
873+
using ..GPUCompiler
874+
function kernel(condition)
875+
@static_assert condition "condition was not proven"
876+
return
877+
end
878+
end
879+
@test_throws_message(InvalidIRError,
880+
Native.code_execution(mod.kernel, Tuple{Bool}; opt_level=0)) do msg
881+
occursin(GPUCompiler.STATIC_ASSERTION, msg) &&
882+
occursin("condition was not proven", msg)
883+
end
884+
885+
mod = @eval module $(gensym())
886+
using ..GPUCompiler
887+
function kernel()
888+
if false
889+
@static_assert false "dead assertion"
890+
end
891+
return
892+
end
893+
end
894+
@test Native.code_execution(mod.kernel, Tuple{}; opt_level=0) !== nothing
895+
896+
mod = @eval module $(gensym())
897+
using ..GPUCompiler
898+
function kernel(condition)
899+
@static_assert condition "first failure"
900+
@static_assert condition "second failure"
901+
return
902+
end
903+
end
904+
@test_throws_message(InvalidIRError,
905+
Native.code_execution(mod.kernel, Tuple{Bool})) do msg
906+
occursin("first failure", msg) && occursin("second failure", msg) &&
907+
!occursin("unknown function", msg)
908+
end
909+
910+
mod = @eval module $(gensym())
911+
using ..GPUCompiler
912+
@inline assertion() = @static_assert false "inlined failure"
913+
kernel() = (assertion(); return)
914+
end
915+
@test_throws_message(InvalidIRError,
916+
Native.code_execution(mod.kernel, Tuple{})) do msg
917+
occursin("inlined failure", msg) &&
918+
occursin("assertion", msg) && occursin("kernel", msg)
919+
end
920+
921+
@test_throws ArgumentError macroexpand(mod, :(@static_assert true string("message")))
922+
end
923+
851924
@testset "invalid LLVM IR (ccall)" begin
852925
mod = @eval module $(gensym())
853926
function foobar(p)

0 commit comments

Comments
 (0)