rustc: Use LLVM's new saturating float-to-int intrinsics
This commit updates rustc, with an applicable LLVM version, to use LLVM's new `llvm.fpto{u,s}i.sat.*.*` intrinsics to implement saturating floating-point-to-int conversions. This results in a little bit tighter codegen for x86/x86_64, but the main purpose of this is to prepare for upcoming changes to the WebAssembly backend in LLVM where wasm's saturating float-to-int instructions will now be implemented with these intrinsics. This change allows simplifying a good deal of surrounding code, namely removing a lot of wasm-specific behavior. WebAssembly no longer has any special-casing of saturating arithmetic instructions and the need for `fptoint_may_trap` is gone and all handling code for that is now removed. This means that the only wasm-specific logic is in the `fpto{s,u}i` instructions which only get used for "out of bounds is undefined behavior". This does mean that for the WebAssembly target specifically the Rust compiler will no longer be 100% compatible with pre-LLVM 12 versions, but it seems like that's unlikely to be relied on by too many folks. Note that this change does immediately regress the codegen of saturating float-to-int casts on WebAssembly due to the specialization of the LLVM intrinsic not being present in our LLVM fork just yet. I'll be following up with an LLVM update to pull in those patches, but affects a few other SIMD things in flight for WebAssembly so I wanted to separate this change. Eventually the entire `cast_float_to_int` function can be removed when LLVM 12 is the minimum version, but that will require sinking the complexity of it into other backends such as Cranelfit.
This commit is contained in:
parent
62652865b6
commit
de2a4601ab
5 changed files with 84 additions and 205 deletions
|
@ -2,6 +2,7 @@ use crate::common::Funclet;
|
|||
use crate::context::CodegenCx;
|
||||
use crate::llvm::{self, BasicBlock, False};
|
||||
use crate::llvm::{AtomicOrdering, AtomicRmwBinOp, SynchronizationScope};
|
||||
use crate::llvm_util;
|
||||
use crate::type_::Type;
|
||||
use crate::type_of::LayoutLlvmExt;
|
||||
use crate::value::Value;
|
||||
|
@ -16,7 +17,7 @@ use rustc_data_structures::small_c_str::SmallCStr;
|
|||
use rustc_hir::def_id::DefId;
|
||||
use rustc_middle::ty::layout::TyAndLayout;
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_span::{sym, Span};
|
||||
use rustc_span::Span;
|
||||
use rustc_target::abi::{self, Align, Size};
|
||||
use rustc_target::spec::{HasTargetSpec, Target};
|
||||
use std::borrow::Cow;
|
||||
|
@ -669,81 +670,47 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
}
|
||||
|
||||
fn fptoui_sat(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> Option<&'ll Value> {
|
||||
// WebAssembly has saturating floating point to integer casts if the
|
||||
// `nontrapping-fptoint` target feature is activated. We'll use those if
|
||||
// they are available.
|
||||
if self.sess().target.arch == "wasm32"
|
||||
&& self.sess().target_features.contains(&sym::nontrapping_dash_fptoint)
|
||||
{
|
||||
if llvm_util::get_version() >= (12, 0, 0) {
|
||||
let src_ty = self.cx.val_ty(val);
|
||||
let float_width = self.cx.float_width(src_ty);
|
||||
let int_width = self.cx.int_width(dest_ty);
|
||||
let name = match (int_width, float_width) {
|
||||
(32, 32) => Some("llvm.wasm.trunc.saturate.unsigned.i32.f32"),
|
||||
(32, 64) => Some("llvm.wasm.trunc.saturate.unsigned.i32.f64"),
|
||||
(64, 32) => Some("llvm.wasm.trunc.saturate.unsigned.i64.f32"),
|
||||
(64, 64) => Some("llvm.wasm.trunc.saturate.unsigned.i64.f64"),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(name) = name {
|
||||
let intrinsic = self.get_intrinsic(name);
|
||||
return Some(self.call(intrinsic, &[val], None));
|
||||
}
|
||||
let name = format!("llvm.fptoui.sat.i{}.f{}", int_width, float_width);
|
||||
let intrinsic = self.get_intrinsic(&name);
|
||||
return Some(self.call(intrinsic, &[val], None));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn fptosi_sat(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> Option<&'ll Value> {
|
||||
// WebAssembly has saturating floating point to integer casts if the
|
||||
// `nontrapping-fptoint` target feature is activated. We'll use those if
|
||||
// they are available.
|
||||
if self.sess().target.arch == "wasm32"
|
||||
&& self.sess().target_features.contains(&sym::nontrapping_dash_fptoint)
|
||||
{
|
||||
if llvm_util::get_version() >= (12, 0, 0) {
|
||||
let src_ty = self.cx.val_ty(val);
|
||||
let float_width = self.cx.float_width(src_ty);
|
||||
let int_width = self.cx.int_width(dest_ty);
|
||||
let name = match (int_width, float_width) {
|
||||
(32, 32) => Some("llvm.wasm.trunc.saturate.signed.i32.f32"),
|
||||
(32, 64) => Some("llvm.wasm.trunc.saturate.signed.i32.f64"),
|
||||
(64, 32) => Some("llvm.wasm.trunc.saturate.signed.i64.f32"),
|
||||
(64, 64) => Some("llvm.wasm.trunc.saturate.signed.i64.f64"),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(name) = name {
|
||||
let intrinsic = self.get_intrinsic(name);
|
||||
return Some(self.call(intrinsic, &[val], None));
|
||||
}
|
||||
let name = format!("llvm.fptosi.sat.i{}.f{}", int_width, float_width);
|
||||
let intrinsic = self.get_intrinsic(&name);
|
||||
return Some(self.call(intrinsic, &[val], None));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn fptosui_may_trap(&self, val: &'ll Value, dest_ty: &'ll Type) -> bool {
|
||||
// Most of the time we'll be generating the `fptosi` or `fptoui`
|
||||
// instruction for floating-point-to-integer conversions. These
|
||||
// instructions by definition in LLVM do not trap. For the WebAssembly
|
||||
// target, however, we'll lower in some cases to intrinsic calls instead
|
||||
// which may trap. If we detect that this is a situation where we'll be
|
||||
// using the intrinsics then we report that the call map trap, which
|
||||
// callers might need to handle.
|
||||
if !self.wasm_and_missing_nontrapping_fptoint() {
|
||||
return false;
|
||||
}
|
||||
let src_ty = self.cx.val_ty(val);
|
||||
let float_width = self.cx.float_width(src_ty);
|
||||
let int_width = self.cx.int_width(dest_ty);
|
||||
matches!((int_width, float_width), (32, 32) | (32, 64) | (64, 32) | (64, 64))
|
||||
}
|
||||
|
||||
fn fptoui(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
|
||||
// When we can, use the native wasm intrinsics which have tighter
|
||||
// codegen. Note that this has a semantic difference in that the
|
||||
// intrinsic can trap whereas `fptoui` never traps. That difference,
|
||||
// however, is handled by `fptosui_may_trap` above.
|
||||
// On WebAssembly the `fptoui` and `fptosi` instructions currently have
|
||||
// poor codegen. The reason for this is that the corresponding wasm
|
||||
// instructions, `i32.trunc_f32_s` for example, will trap when the float
|
||||
// is out-of-bounds, infinity, or nan. This means that LLVM
|
||||
// automatically inserts control flow around `fptoui` and `fptosi`
|
||||
// because the LLVM instruction `fptoui` is defined as producing a
|
||||
// poison value, not having UB on out-of-bounds values.
|
||||
//
|
||||
// Note that we skip the wasm intrinsics for vector types where `fptoui`
|
||||
// must be used instead.
|
||||
if self.wasm_and_missing_nontrapping_fptoint() {
|
||||
// This method, however, is only used with non-saturating casts that
|
||||
// have UB on out-of-bounds values. This means that it's ok if we use
|
||||
// the raw wasm instruction since out-of-bounds values can do whatever
|
||||
// we like. To ensure that LLVM picks the right instruction we choose
|
||||
// the raw wasm intrinsic functions which avoid LLVM inserting all the
|
||||
// other control flow automatically.
|
||||
if self.sess().target.arch == "wasm32" {
|
||||
let src_ty = self.cx.val_ty(val);
|
||||
if self.cx.type_kind(src_ty) != TypeKind::Vector {
|
||||
let float_width = self.cx.float_width(src_ty);
|
||||
|
@ -765,7 +732,8 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
}
|
||||
|
||||
fn fptosi(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
|
||||
if self.wasm_and_missing_nontrapping_fptoint() {
|
||||
// see `fptoui` above for why wasm is different here
|
||||
if self.sess().target.arch == "wasm32" {
|
||||
let src_ty = self.cx.val_ty(val);
|
||||
if self.cx.type_kind(src_ty) != TypeKind::Vector {
|
||||
let float_width = self.cx.float_width(src_ty);
|
||||
|
@ -1419,9 +1387,4 @@ impl Builder<'a, 'll, 'tcx> {
|
|||
llvm::LLVMAddIncoming(phi, &val, &bb, 1 as c_uint);
|
||||
}
|
||||
}
|
||||
|
||||
fn wasm_and_missing_nontrapping_fptoint(&self) -> bool {
|
||||
self.sess().target.arch == "wasm32"
|
||||
&& !self.sess().target_features.contains(&sym::nontrapping_dash_fptoint)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue