<div dir="ltr"><div>Hi Florian,</div><div><br></div>I have a few questions about thereduction builtins.<div><br></div><div>llvm.reduce.fadd is currently defined as ordered unless the reassociate fast math flag is present. Are you proposing to change that to make it pairwise? <div><br></div><div>llvm.reduce.fmin/fmax change behavior based on the nonans fast math flag. And I think they always imply no signed zeros regardless of whether the fast math flag is present. The vectorizers check the fast math flags before creating the intrinsics today. What are the semantics of the proposed builtin?<div><br></div><div>Thanks,<br><div><div><div dir="ltr" class="gmail_signature" data-smartmail="gmail_signature">~Craig</div></div><br></div></div></div></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Sep 27, 2021 at 11:50 AM Florian Hahn via cfe-dev <<a href="mailto:cfe-dev@lists.llvm.org">cfe-dev@lists.llvm.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div style="overflow-wrap: break-word;"><span>Hi,</span><div><span><br></span><span>I would like to provide a convenient way for our users to perform additional operations like min, max, abs and round on vector (and possibly matrix) types. To do so, I’d like to propose adding a new set of builtins that operate on vector-like types. The new builtins can be used to perform each operation element wise and as reduction on the elements of a vector or a matrix. The proposal also includes arithmetic reductions. Those are performed pairwise, rather then sequential to ensure they can lowered directly to vector instructions on targets like AArch64 and X86.</span><span><br></span><span><br></span><span>I considered overloading the existing math builtins, but think we can provide a better and more consistent user-experience with a new set of builtins. The drawbacks of overloading math builtins is discussed at the end.</span><span><br></span><span><br></span><span>Below is the proposed specification for element-wise and reductions builtins. They will be lowered to the corresponding LLVM intrinsic. Note that not all possible builtins are listed here. Once agreed on the general scheme, it should be easy to add additional builtins. </span><span><br></span><span><br></span><span><br></span><b><span>Specification of the element-wise builtins with 1 operands</span><span><br></span></b><span><br></span><span>Provided builtins:</span><span><br></span><span><div><span style="white-space:pre-wrap"> </span>• __builtin_elementwise_abs<br></div><div><span style="white-space:pre-wrap">  </span>• __builtin_elementwise_ceil<br></div><div><span style="white-space:pre-wrap"> </span>• __builtin_elementwise_floor<br></div><div><span style="white-space:pre-wrap">        </span>• __builtin_elementwise_rint<br></div><div><span style="white-space:pre-wrap"> </span>• __builtin_elementwise_round<br></div><div><span style="white-space:pre-wrap">        </span>• __builtin_elementwise_trunc<br></div></span><span><br></span><span>---- </span><span><br></span><span>T__builtin_elementwise_<name>(T x)</span><span><br></span><span><br></span><span>T must be one of the following types:</span><span><br></span><span><div><span style="white-space:pre-wrap">  </span>• an integer type (as in C2x 6.2.5p19), but excluding enumerated types and _Bool<br></div><div><span style="white-space:pre-wrap">    </span>• the standard floating types float or double<br></div><div><span style="white-space:pre-wrap">     </span>• a half-precision floating point type, if one is supported on the target<br></div><div><span style="white-space:pre-wrap">    </span>• a vector or matrix type.<br></div><div><br></div></span><span>For scalar types, consider the operation applied to a vector with a single element.</span> For matrix types, consider them as a vector formed by concatenating its columns for the definitions below.<span><br></span><span><br></span><span>Returns: A vector Res equivalent to applying </span><span>fn</span><span> elementwise to the input, where </span><span>fn</span><span> depends on (name, element type of VT):</span><span><br></span><span><br></span><span><div><span style="white-space:pre-wrap">   </span>• (abs, floating point type) → return the absolute value of a floating-point number x<br></div><div><span style="white-space:pre-wrap">      </span>• (abs, integer ty) → (a < 0) ? a * -1 : a<br></div><div><span style="white-space:pre-wrap">      </span>• (ceil, floating point type) → return the smallest integral value greater than or equal to x<br></div><div><span style="white-space:pre-wrap">      </span>• (ceil, integer type) → invalid<br></div><div><span style="white-space:pre-wrap">   </span>• (floor, floating point type) → return the largest integral value less than or equal to x<br></div><div><span style="white-space:pre-wrap"> </span>• (floor, integer type) → invalid<br></div><div><span style="white-space:pre-wrap">  </span>• (rint, floating point type) → return the integral value nearest to x (according to the prevailing rounding mode) in floating-point format<br></div><div><span style="white-space:pre-wrap">        </span>• (rint, integer type) → invalid<br></div><div><span style="white-space:pre-wrap">   </span>• (round, floating point type) → return the integral value nearest to x rounding half-way cases away from zero, regardless of the current rounding direction<br></div><div><span style="white-space:pre-wrap">       </span>• (round, integer type) → invalid<br></div><div><span style="white-space:pre-wrap">  </span>• (trunc, floating point type) → return the integral value nearest to but no larger in magnitude than x<br></div><div><span style="white-space:pre-wrap">    </span>• (trunc, integer type) → invalid<br></div></span><span><br></span><span>Special values:</span><span><br></span><span>Unless specified otherwise fn(±0)= ±0 and fn(±infinity) = ±infinity</span><span><br></span><span><br></span><span>T Res</span><span><br></span><span>for (int I = 0; I < NumElements; ++I)</span><span><br></span><span>  Res[I] = fn(a[I])</span><span><br></span><span>----</span><span><br></span><span><br></span><span><br></span><b><span>Specification of the element-wise builtins with 2 operands:</span><span><br></span></b><span><br></span><span>Provided builtins:</span><span><br></span><span><div><span style="white-space:pre-wrap">       </span>• __builtin_elementwise_max<br></div><div><span style="white-space:pre-wrap">  </span>• __builtin_elementwise_min<br></div></span><span><br></span><span>---- </span><span><br></span><span>T __builtin_elementwise_<name>(T x, T y)</span><span><br></span><span><br></span><span>T must be one of the following types:</span><span><br></span><span><div><span style="white-space:pre-wrap">      </span>• an integer type (as in C2x 6.2.5p19), but excluding enumerated types and _Bool<br></div><div><span style="white-space:pre-wrap">    </span>• the standard floating types float or double<br></div><div><span style="white-space:pre-wrap">     </span>• a half-precision floating point type, if one is supported on the target<br></div><div><span style="white-space:pre-wrap">    </span>• a vector or matrix type.<br></div><div><br></div></span><span>For scalar types, consider the operation applied to a vector with a single element. </span>For matrix types, consider them as a vector formed by concatenating its columns for the definitions below.<span><br></span><span><br></span><span>Returns: A vector Res equivalent to applying </span><span>fn(x, y)</span><span> elementwise to the inputs, where </span><span>fn</span><span> depends on the name:</span><span><br></span><span><br></span><span><div><span style="white-space:pre-wrap">      </span>• min → return x or y, whichever is smaller<br></div><div><span style="white-space:pre-wrap">        </span>• max → return x or y, whichever is larger<br></div></span><span><br></span><span>Special values:</span><span><br></span><span>Unless otherwise specified, the following holds. If exactly one argument is a NaN, return the other argument. If both arguments are NaNs, fmax() return a NaN.</span><span><br></span><span><br></span><span>VT Res</span><span><br></span><span>for (int I = 0; I < NumElements; ++I)</span><span><br></span><span>  Res[I] = fn(a[I], b[I])</span><span><br></span><span>----</span><span><br></span><span><br></span><span><br></span><b><span>Specification of reduction builtins:</span><span><br></span></b><span><br></span><span>Provided builtins:</span><span><br></span><span><div><span style="white-space:pre-wrap">       </span>• __builtin_reduce_min<br></div><div><span style="white-space:pre-wrap">       </span>• __builtin_reduce_max<br></div><div><span style="white-space:pre-wrap">       </span>• __builtin_reduce_add<br></div><div><span style="white-space:pre-wrap">       </span>• __builtin_reduce_and<br></div><div><span style="white-space:pre-wrap">       </span>• __builtin_reduce_or<br></div><div><span style="white-space:pre-wrap">        </span>• __builtin_reduce_xor<br></div></span><span><br></span><span>---- </span><span><br></span><span>ET__builtin_reduce_<name>(VT a)</span><span><br></span><span><br></span><span>VT must be a vector or matrix type with element type ET. For matrix types, consider them as a vector formed by concatenating its columns for the definitions below.</span><span><br></span><span><br></span><span>Returns: A scalar Res equivalent to applying </span><span>fn</span><span> as pairwise tree reduction to the input, where </span><span>fn(x, y)</span><span> depends on the name :</span></div><div><span><br></span><span><div><span style="white-space:pre-wrap">   </span>• min → return x or y, whichever is smaller; If exactly one argument is a NaN, return the other argument. If both arguments are NaNs, fmax() return a NaN.<br></div><div><span style="white-space:pre-wrap"> </span>• max → return x or y, whichever is larger; If exactly one argument is a NaN, return the other argument. If both arguments are NaNs, fmax() return a NaN.<br></div><div><span style="white-space:pre-wrap">  </span>• add → +<br></div><div><span style="white-space:pre-wrap">  </span>• and → & (integer (element) types only)<br></div><div><span style="white-space:pre-wrap">       </span>• or → | (integer (element) types only)<br></div><div><span style="white-space:pre-wrap">    </span>• xor → ^ (integer (element) types only)<br></div></span><span>----</span><span><br></span><span></span><span><br><br></span><span>The intended semantics should allow for the following lowering using LLVM intrinsics (using </span><span>__builtin_{elementwise,reduce}_min</span><span> as example):</span><span><br></span><span><br></span><span>declare float @llvm.vector.reduce.fmin.v4f32(<4 x float>)<br>define float @float_min_red(<4 x float> %a) {<br>  %r = call float @llvm.vector.reduce.fmin.v4f32(<4 x float> %a)<br>  ret float %r<br>}<br><br>declare <4 x float> @llvm.minnum.v4f32(<4 x float>, <4 x float>)<br>define <4 x float> @float_min(<4 x float> %a, <4 x float> %b) {<br>  %r = call <4 x float> @llvm.minnum.v4f32(<4 x float> %a, <4 x float> %b)<br>  ret <4 x float> %r<br>}<br><br>declare <4 x i32> @llvm.smin.v4i32(<4 x i32>, <4 x i32>)<br>define <4 x i32> @int_min(<4 x i32> %a, <4 x i32> %b) {<br>  %r = call <4 x i32> @llvm.smin.v4i32(<4 x i32> %a, <4 x i32> %b)<br>  ret <4 x i32> %r<br>}<br><br>declare i32 @llvm.vector.reduce.smin.v4i32(<4 x i32>)<br>define i32 @int_min_red(<4 x i32> %b) {<br>  %r = call i32 @llvm.vector.reduce.smin.v4i32(<4 x i32> %b)<br>  ret i32 %r<br>}<br></span><span><a href="https://llvm.godbolt.org/z/7Yv3v8aP9" target="_blank">https://llvm.godbolt.org/z/7Yv3v8aP9</a></span></div><div><span><br></span><span><br></span><span><b>Alternatives Considered<br></b><br></span><span>Instead of adding a set of completely new builtins, we could overload the existing libm-based builtins (</span><span>__builtin_fminf</span><span>& co). The main issue with that approach is convenience for the users I think. The libm-based builtins encode the type in their name which allows us to only cover a small subset of types. For example, AFAIK there’s no builtin for integer min/max or floating point max for 16 bit floating point types. The proposal above provides a set of more user friendly builtins that work across a large range of types, similar to the existing math operators (i.e. there’s a single </span><span>__builtin_max</span><span>which works with both integer and floating points, as well as vector & matrix versions). The reduction versions are also explicitly marked in the name.</span><br></div><div><span><br></span></div><div><span><br></span></div><div><span>Cheers</span></div><div><span>Florian</span></div></div>_______________________________________________<br>
cfe-dev mailing list<br>
<a href="mailto:cfe-dev@lists.llvm.org" target="_blank">cfe-dev@lists.llvm.org</a><br>
<a href="https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev" rel="noreferrer" target="_blank">https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev</a><br>
</blockquote></div>