-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
/
simdloop.jl
139 lines (116 loc) · 5.45 KB
/
simdloop.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# This file is a part of Julia. License is MIT: https://julialang.org/license
# Support for @simd for
module SimdLoop
export @simd, simd_outer_range, simd_inner_length, simd_index
# Error thrown from ill-formed uses of @simd
struct SimdError <: Exception
msg::String
end
# Parse iteration space expression
# symbol '=' range
# symbol 'in' range
function parse_iteration_space(x)
(isa(x, Expr) && (x.head === :(=) || x.head === :in)) || throw(SimdError("= or in expected"))
length(x.args) == 2 || throw(SimdError("simd range syntax is wrong"))
isa(x.args[1], Symbol) || throw(SimdError("simd loop index must be a symbol"))
x.args # symbol, range
end
# reject invalid control flow statements in @simd loop body
function check_body!(x::Expr)
if x.head === :break || x.head === :continue
throw(SimdError("$(x.head) is not allowed inside a @simd loop body"))
elseif x.head === :macrocall && x.args[1] === Symbol("@goto")
throw(SimdError("@goto is not allowed inside a @simd loop body"))
end
for arg in x.args
check_body!(arg)
end
return true
end
check_body!(x::QuoteNode) = check_body!(x.value)
check_body!(x) = true
# @simd splits a for loop into two loops: an outer scalar loop and
# an inner loop marked with :loopinfo. The simd_... functions define
# the splitting.
# Custom iterators that do not support random access cannot support
# vectorization. In order to be compatible with `@simd` annotated loops,
#they should override `simd_inner_length(v::MyIter, j) = 1`,
#`simd_outer_range(v::MyIter) = v`, and `simd_index(v::MyIter, j, i) = j`.
# Get range for outer loop.
simd_outer_range(r) = 0:0
# Get trip count for inner loop.
@inline simd_inner_length(r, j) = Base.length(r)
# Construct user-level element from original range, outer loop index j, and inner loop index i.
@inline simd_index(r, j, i) = (@inbounds ret = r[i+firstindex(r)]; ret)
# Compile Expr x in context of @simd.
function compile(x, ivdep)
(isa(x, Expr) && x.head === :for) || throw(SimdError("for loop expected"))
length(x.args) == 2 || throw(SimdError("1D for loop expected"))
check_body!(x)
var,range = parse_iteration_space(x.args[1])
r = gensym("r") # Range value
j = gensym("i") # Iteration variable for outer loop
n = gensym("n") # Trip count for inner loop
i = gensym("i") # Trip index for inner loop
quote
# Evaluate range value once, to enhance type and data flow analysis by optimizers.
let $r = $range
for $j in Base.simd_outer_range($r)
let $n = Base.simd_inner_length($r,$j)
if zero($n) < $n
# Lower loop in way that seems to work best for LLVM 3.3 vectorizer.
let $i = zero($n)
while $i < $n
local $var = Base.simd_index($r,$j,$i)
$(x.args[2]) # Body of loop
$i += 1
$(Expr(:loopinfo, Symbol("julia.simdloop"), ivdep)) # Mark loop as SIMD loop
end
end
end
end
end
end
nothing
end
end
"""
@simd
Annotate a `for` loop to allow the compiler to take extra liberties to allow loop re-ordering
!!! warning
This feature is experimental and could change or disappear in future versions of Julia.
Incorrect use of the `@simd` macro may cause unexpected results.
The object iterated over in a `@simd for` loop should be a one-dimensional range.
By using `@simd`, you are asserting several properties of the loop:
* It is safe to execute iterations in arbitrary or overlapping order, with special consideration for reduction variables.
* Floating-point operations on reduction variables can be reordered or contracted, possibly causing different results than without `@simd`.
In many cases, Julia is able to automatically vectorize inner for loops without the use of `@simd`.
Using `@simd` gives the compiler a little extra leeway to make it possible in more situations. In
either case, your inner loop should have the following properties to allow vectorization:
* The loop must be an innermost loop
* The loop body must be straight-line code. Therefore, [`@inbounds`](@ref) is
currently needed for all array accesses. The compiler can sometimes turn
short `&&`, `||`, and `?:` expressions into straight-line code if it is safe
to evaluate all operands unconditionally. Consider using the [`ifelse`](@ref)
function instead of `?:` in the loop if it is safe to do so.
* Accesses must have a stride pattern and cannot be "gathers" (random-index
reads) or "scatters" (random-index writes).
* The stride should be unit stride.
!!! note
The `@simd` does not assert by default that the loop is completely free of loop-carried
memory dependencies, which is an assumption that can easily be violated in generic code.
If you are writing non-generic code, you can use `@simd ivdep for ... end` to also assert that:
* There exists no loop-carried memory dependencies
* No iteration ever waits on a previous iteration to make forward progress.
"""
macro simd(forloop)
esc(compile(forloop, nothing))
end
macro simd(ivdep, forloop)
if ivdep === :ivdep
esc(compile(forloop, Symbol("julia.ivdep")))
else
throw(SimdError("Only ivdep is valid as the first argument to @simd"))
end
end
end # module SimdLoop