2
2
//! crate.
3
3
//!
4
4
//! The LLVM assembly language is documented here: <https://llvm.org/docs/LangRef.html>
5
+ //!
6
+ //! A quick glossary of jargon that may appear in this module, mostly paraphrasing LLVM's LangRef:
7
+ //! - poison: "undefined behavior as a value". specifically, it is like uninit memory (such as padding bytes). it is "safe" to create poison, BUT
8
+ //! poison MUST NOT be observed from safe code, as operations on poison return poison, like NaN. unlike NaN, which has defined comparisons,
9
+ //! poison is neither true nor false, and LLVM may also convert it to undef (at which point it is both). so, it can't be conditioned on, either.
10
+ //! - undef: "a value that is every value". functionally like poison, insofar as Rust is concerned. poison may become this. note:
11
+ //! this means that division by poison or undef is like division by zero, which means it inflicts...
12
+ //! - "UB": poison and undef cover most of what people call "UB". "UB" means this operation immediately invalidates the program:
13
+ //! LLVM is allowed to lower it to `ud2` or other opcodes that may cause an illegal instruction exception, and this is the "good end".
14
+ //! The "bad end" is that LLVM may reverse time to the moment control flow diverged on a path towards undefined behavior,
15
+ //! and destroy the other branch, potentially deleting safe code and violating Rust's `unsafe` contract.
16
+ //!
17
+ //! Note that according to LLVM, vectors are not arrays, but they are equivalent when stored to and loaded from memory.
18
+ //!
19
+ //! Unless stated otherwise, all intrinsics for binary operations require SIMD vectors of equal types and lengths.
5
20
6
21
/// These intrinsics aren't linked directly from LLVM and are mostly undocumented, however they are
7
- /// simply lowered to the matching LLVM instructions by the compiler. The associated instruction
8
- /// is documented alongside each intrinsic.
22
+ /// mostly lowered to the matching LLVM instructions by the compiler in a fairly straightforward manner.
23
+ /// The associated LLVM instruction or intrinsic is documented alongside each Rust intrinsic function .
9
24
extern "platform-intrinsic" {
10
25
/// add/fadd
11
26
pub ( crate ) fn simd_add < T > ( x : T , y : T ) -> T ;
12
27
13
28
/// sub/fsub
14
- pub ( crate ) fn simd_sub < T > ( x : T , y : T ) -> T ;
29
+ pub ( crate ) fn simd_sub < T > ( lhs : T , rhs : T ) -> T ;
15
30
16
31
/// mul/fmul
17
32
pub ( crate ) fn simd_mul < T > ( x : T , y : T ) -> T ;
18
33
19
34
/// udiv/sdiv/fdiv
20
- pub ( crate ) fn simd_div < T > ( x : T , y : T ) -> T ;
35
+ /// ints and uints: {s,u}div incur UB if division by zero occurs.
36
+ /// ints: sdiv is UB for int::MIN / -1.
37
+ /// floats: fdiv is never UB, but may create NaNs or infinities.
38
+ pub ( crate ) fn simd_div < T > ( lhs : T , rhs : T ) -> T ;
21
39
22
40
/// urem/srem/frem
23
- pub ( crate ) fn simd_rem < T > ( x : T , y : T ) -> T ;
41
+ /// ints and uints: {s,u}rem incur UB if division by zero occurs.
42
+ /// ints: srem is UB for int::MIN / -1.
43
+ /// floats: frem is equivalent to libm::fmod in the "default" floating point environment, sans errno.
44
+ pub ( crate ) fn simd_rem < T > ( lhs : T , rhs : T ) -> T ;
24
45
25
46
/// shl
26
- pub ( crate ) fn simd_shl < T > ( x : T , y : T ) -> T ;
47
+ /// for (u)ints. poison if rhs >= lhs::BITS
48
+ pub ( crate ) fn simd_shl < T > ( lhs : T , rhs : T ) -> T ;
27
49
28
- /// lshr/ashr
29
- pub ( crate ) fn simd_shr < T > ( x : T , y : T ) -> T ;
50
+ /// ints: ashr
51
+ /// uints: lshr
52
+ /// poison if rhs >= lhs::BITS
53
+ pub ( crate ) fn simd_shr < T > ( lhs : T , rhs : T ) -> T ;
30
54
31
55
/// and
32
56
pub ( crate ) fn simd_and < T > ( x : T , y : T ) -> T ;
@@ -38,12 +62,18 @@ extern "platform-intrinsic" {
38
62
pub ( crate ) fn simd_xor < T > ( x : T , y : T ) -> T ;
39
63
40
64
/// fptoui/fptosi/uitofp/sitofp
65
+ /// casting floats to integers is truncating, so it is safe to convert values like e.g. 1.5
66
+ /// but the truncated value must fit in the target type or the result is poison.
67
+ /// use `simd_as` instead for a cast that performs a saturating conversion.
41
68
pub ( crate ) fn simd_cast < T , U > ( x : T ) -> U ;
42
69
/// follows Rust's `T as U` semantics, including saturating float casts
43
70
/// which amounts to the same as `simd_cast` for many cases
44
71
pub ( crate ) fn simd_as < T , U > ( x : T ) -> U ;
45
72
46
73
/// neg/fneg
74
+ /// ints: ultimately becomes a call to cg_ssa's BuilderMethods::neg. cg_llvm equates this to `simd_sub(Simd::splat(0), x)`.
75
+ /// floats: LLVM's fneg, which changes the floating point sign bit. Some arches have instructions for it.
76
+ /// Rust panics for Neg::neg(int::MIN) due to overflow, but it is not UB in LLVM without `nsw`.
47
77
pub ( crate ) fn simd_neg < T > ( x : T ) -> T ;
48
78
49
79
/// fabs
@@ -53,6 +83,7 @@ extern "platform-intrinsic" {
53
83
pub ( crate ) fn simd_fmin < T > ( x : T , y : T ) -> T ;
54
84
pub ( crate ) fn simd_fmax < T > ( x : T , y : T ) -> T ;
55
85
86
+ // these return Simd<int, N> with the same BITS size as the inputs
56
87
pub ( crate ) fn simd_eq < T , U > ( x : T , y : T ) -> U ;
57
88
pub ( crate ) fn simd_ne < T , U > ( x : T , y : T ) -> U ;
58
89
pub ( crate ) fn simd_lt < T , U > ( x : T , y : T ) -> U ;
@@ -61,19 +92,31 @@ extern "platform-intrinsic" {
61
92
pub ( crate ) fn simd_ge < T , U > ( x : T , y : T ) -> U ;
62
93
63
94
// shufflevector
95
+ // idx: LLVM calls it a "shuffle mask vector constant", a vector of i32s
64
96
pub ( crate ) fn simd_shuffle < T , U , V > ( x : T , y : T , idx : U ) -> V ;
65
97
98
+ /// llvm.masked.gather
99
+ /// like a loop of pointer reads
100
+ /// val: vector of values to select if a lane is masked
101
+ /// ptr: vector of pointers to read from
102
+ /// mask: a "wide" mask of integers, selects as if simd_select(mask, read(ptr), val)
103
+ /// note, the LLVM intrinsic accepts a mask vector of <N x i1>
104
+ /// FIXME: review this if/when we fix up our mask story in general?
66
105
pub ( crate ) fn simd_gather < T , U , V > ( val : T , ptr : U , mask : V ) -> T ;
106
+ /// llvm.masked.scatter
107
+ /// like gather, but more spicy, as it writes instead of reads
67
108
pub ( crate ) fn simd_scatter < T , U , V > ( val : T , ptr : U , mask : V ) ;
68
109
69
110
// {s,u}add.sat
70
111
pub ( crate ) fn simd_saturating_add < T > ( x : T , y : T ) -> T ;
71
112
72
113
// {s,u}sub.sat
73
- pub ( crate ) fn simd_saturating_sub < T > ( x : T , y : T ) -> T ;
114
+ pub ( crate ) fn simd_saturating_sub < T > ( lhs : T , rhs : T ) -> T ;
74
115
75
116
// reductions
117
+ // llvm.vector.reduce.{add,fadd}
76
118
pub ( crate ) fn simd_reduce_add_ordered < T , U > ( x : T , y : U ) -> U ;
119
+ // llvm.vector.reduce.{mul,fmul}
77
120
pub ( crate ) fn simd_reduce_mul_ordered < T , U > ( x : T , y : U ) -> U ;
78
121
#[ allow( unused) ]
79
122
pub ( crate ) fn simd_reduce_all < T > ( x : T ) -> bool ;
@@ -90,7 +133,10 @@ extern "platform-intrinsic" {
90
133
pub ( crate ) fn simd_bitmask < T , U > ( x : T ) -> U ;
91
134
92
135
// select
93
- pub ( crate ) fn simd_select < M , T > ( m : M , a : T , b : T ) -> T ;
136
+ // first argument is a vector of integers, -1 (all bits 1) is "true"
137
+ // logically equivalent to (yes & m) | (no & (m^-1),
138
+ // but you can use it on floats.
139
+ pub ( crate ) fn simd_select < M , T > ( m : M , yes : T , no : T ) -> T ;
94
140
#[ allow( unused) ]
95
- pub ( crate ) fn simd_select_bitmask < M , T > ( m : M , a : T , b : T ) -> T ;
141
+ pub ( crate ) fn simd_select_bitmask < M , T > ( m : M , yes : T , no : T ) -> T ;
96
142
}
0 commit comments