Skip to content

Commit 65342fd

Browse files
authored
Rollup merge of #73724 - CryZe:wasm-saturating-casts, r=alexcrichton
Use WASM's saturating casts if they are available WebAssembly supports saturating floating point to integer casts behind a target feature. The feature is already available on many browsers. Beginning with 1.45 Rust will start defining the behavior of floating point to integer casts to be saturating as well. For this Rust constructs additional checks on top of the `fptoui` / `fptosi` instructions it emits. Here we introduce the possibility for the codegen backend to construct saturating casts itself and only fall back to constructing the checks ourselves if that is not possible. Resolves part of #73591
2 parents dc6a19c + 838c497 commit 65342fd

File tree

12 files changed

+932
-507
lines changed

12 files changed

+932
-507
lines changed

src/ci/docker/test-various/Dockerfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
1717
wget \
1818
patch
1919

20-
RUN curl -sL https://nodejs.org/dist/v9.2.0/node-v9.2.0-linux-x64.tar.xz | \
20+
RUN curl -sL https://nodejs.org/dist/v14.4.0/node-v14.4.0-linux-x64.tar.xz | \
2121
tar -xJ
2222

2323
WORKDIR /build/
@@ -30,7 +30,7 @@ RUN sh /scripts/sccache.sh
3030

3131
ENV RUST_CONFIGURE_ARGS \
3232
--musl-root-x86_64=/usr/local/x86_64-linux-musl \
33-
--set build.nodejs=/node-v9.2.0-linux-x64/bin/node \
33+
--set build.nodejs=/node-v14.4.0-linux-x64/bin/node \
3434
--set rust.lld
3535

3636
# Some run-make tests have assertions about code size, and enabling debug

src/librustc_codegen_llvm/builder.rs

+51
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use rustc_data_structures::small_c_str::SmallCStr;
1818
use rustc_hir::def_id::DefId;
1919
use rustc_middle::ty::layout::TyAndLayout;
2020
use rustc_middle::ty::{self, Ty, TyCtxt};
21+
use rustc_span::sym;
2122
use rustc_target::abi::{self, Align, Size};
2223
use rustc_target::spec::{HasTargetSpec, Target};
2324
use std::borrow::Cow;
@@ -652,6 +653,56 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
652653
unsafe { llvm::LLVMBuildSExt(self.llbuilder, val, dest_ty, UNNAMED) }
653654
}
654655

656+
fn fptoui_sat(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> Option<&'ll Value> {
657+
// WebAssembly has saturating floating point to integer casts if the
658+
// `nontrapping-fptoint` target feature is activated. We'll use those if
659+
// they are available.
660+
if self.sess().target.target.arch == "wasm32"
661+
&& self.sess().target_features.contains(&sym::nontrapping_fptoint)
662+
{
663+
let src_ty = self.cx.val_ty(val);
664+
let float_width = self.cx.float_width(src_ty);
665+
let int_width = self.cx.int_width(dest_ty);
666+
let name = match (int_width, float_width) {
667+
(32, 32) => Some("llvm.wasm.trunc.saturate.unsigned.i32.f32"),
668+
(32, 64) => Some("llvm.wasm.trunc.saturate.unsigned.i32.f64"),
669+
(64, 32) => Some("llvm.wasm.trunc.saturate.unsigned.i64.f32"),
670+
(64, 64) => Some("llvm.wasm.trunc.saturate.unsigned.i64.f64"),
671+
_ => None,
672+
};
673+
if let Some(name) = name {
674+
let intrinsic = self.get_intrinsic(name);
675+
return Some(self.call(intrinsic, &[val], None));
676+
}
677+
}
678+
None
679+
}
680+
681+
fn fptosi_sat(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> Option<&'ll Value> {
682+
// WebAssembly has saturating floating point to integer casts if the
683+
// `nontrapping-fptoint` target feature is activated. We'll use those if
684+
// they are available.
685+
if self.sess().target.target.arch == "wasm32"
686+
&& self.sess().target_features.contains(&sym::nontrapping_fptoint)
687+
{
688+
let src_ty = self.cx.val_ty(val);
689+
let float_width = self.cx.float_width(src_ty);
690+
let int_width = self.cx.int_width(dest_ty);
691+
let name = match (int_width, float_width) {
692+
(32, 32) => Some("llvm.wasm.trunc.saturate.signed.i32.f32"),
693+
(32, 64) => Some("llvm.wasm.trunc.saturate.signed.i32.f64"),
694+
(64, 32) => Some("llvm.wasm.trunc.saturate.signed.i64.f32"),
695+
(64, 64) => Some("llvm.wasm.trunc.saturate.signed.i64.f64"),
696+
_ => None,
697+
};
698+
if let Some(name) = name {
699+
let intrinsic = self.get_intrinsic(name);
700+
return Some(self.call(intrinsic, &[val], None));
701+
}
702+
}
703+
None
704+
}
705+
655706
fn fptoui(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
656707
unsafe { llvm::LLVMBuildFPToUI(self.llbuilder, val, dest_ty, UNNAMED) }
657708
}

src/librustc_codegen_llvm/context.rs

+9
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,15 @@ impl CodegenCx<'b, 'tcx> {
497497
t_v8f64: t_f64, 8;
498498
}
499499

500+
ifn!("llvm.wasm.trunc.saturate.unsigned.i32.f32", fn(t_f32) -> t_i32);
501+
ifn!("llvm.wasm.trunc.saturate.unsigned.i32.f64", fn(t_f64) -> t_i32);
502+
ifn!("llvm.wasm.trunc.saturate.unsigned.i64.f32", fn(t_f32) -> t_i64);
503+
ifn!("llvm.wasm.trunc.saturate.unsigned.i64.f64", fn(t_f64) -> t_i64);
504+
ifn!("llvm.wasm.trunc.saturate.signed.i32.f32", fn(t_f32) -> t_i32);
505+
ifn!("llvm.wasm.trunc.saturate.signed.i32.f64", fn(t_f64) -> t_i32);
506+
ifn!("llvm.wasm.trunc.saturate.signed.i64.f32", fn(t_f32) -> t_i64);
507+
ifn!("llvm.wasm.trunc.saturate.signed.i64.f64", fn(t_f64) -> t_i64);
508+
500509
ifn!("llvm.trap", fn() -> void);
501510
ifn!("llvm.debugtrap", fn() -> void);
502511
ifn!("llvm.frameaddress", fn(t_i32) -> i8p);

src/librustc_codegen_llvm/llvm_util.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,11 @@ const RISCV_WHITELIST: &[(&str, Option<Symbol>)] = &[
250250
("e", Some(sym::riscv_target_feature)),
251251
];
252252

253-
const WASM_WHITELIST: &[(&str, Option<Symbol>)] =
254-
&[("simd128", Some(sym::wasm_target_feature)), ("atomics", Some(sym::wasm_target_feature))];
253+
const WASM_WHITELIST: &[(&str, Option<Symbol>)] = &[
254+
("simd128", Some(sym::wasm_target_feature)),
255+
("atomics", Some(sym::wasm_target_feature)),
256+
("nontrapping-fptoint", Some(sym::wasm_target_feature)),
257+
];
255258

256259
/// When rustdoc is running, provide a list of all known features so that all their respective
257260
/// primitives may be documented.

src/librustc_codegen_ssa/mir/rvalue.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -774,12 +774,17 @@ fn cast_float_to_int<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
774774
float_ty: Bx::Type,
775775
int_ty: Bx::Type,
776776
) -> Bx::Value {
777-
let fptosui_result = if signed { bx.fptosi(x, int_ty) } else { bx.fptoui(x, int_ty) };
778-
779777
if let Some(false) = bx.cx().sess().opts.debugging_opts.saturating_float_casts {
780-
return fptosui_result;
778+
return if signed { bx.fptosi(x, int_ty) } else { bx.fptoui(x, int_ty) };
779+
}
780+
781+
let try_sat_result = if signed { bx.fptosi_sat(x, int_ty) } else { bx.fptoui_sat(x, int_ty) };
782+
if let Some(try_sat_result) = try_sat_result {
783+
return try_sat_result;
781784
}
782785

786+
let fptosui_result = if signed { bx.fptosi(x, int_ty) } else { bx.fptoui(x, int_ty) };
787+
783788
let int_width = bx.cx().int_width(int_ty);
784789
let float_width = bx.cx().float_width(float_ty);
785790
// LLVM's fpto[su]i returns undef when the input x is infinite, NaN, or does not fit into the

src/librustc_codegen_ssa/traits/builder.rs

+2
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ pub trait BuilderMethods<'a, 'tcx>:
158158

159159
fn trunc(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
160160
fn sext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
161+
fn fptoui_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Option<Self::Value>;
162+
fn fptosi_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Option<Self::Value>;
161163
fn fptoui(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
162164
fn fptosi(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
163165
fn uitofp(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;

src/librustc_span/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ symbols! {
517517
None,
518518
non_exhaustive,
519519
non_modrs_mods,
520+
nontrapping_fptoint: "nontrapping-fptoint",
520521
noreturn,
521522
no_niche,
522523
no_sanitize,
+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// only-wasm32
2+
// compile-flags: -C target-feature=+nontrapping-fptoint
3+
#![crate_type = "lib"]
4+
5+
// CHECK-LABEL: @cast_f64_i64
6+
#[no_mangle]
7+
pub fn cast_f64_i64(a: f64) -> i64 {
8+
// CHECK: tail call i64 @llvm.wasm.trunc.saturate.signed.i64.f64(double {{.*}})
9+
// CHECK-NEXT: ret i64 {{.*}}
10+
a as _
11+
}
12+
13+
// CHECK-LABEL: @cast_f64_i32
14+
#[no_mangle]
15+
pub fn cast_f64_i32(a: f64) -> i32 {
16+
// CHECK: tail call i32 @llvm.wasm.trunc.saturate.signed.i32.f64(double {{.*}})
17+
// CHECK-NEXT: ret i32 {{.*}}
18+
a as _
19+
}
20+
21+
// CHECK-LABEL: @cast_f32_i64
22+
#[no_mangle]
23+
pub fn cast_f32_i64(a: f32) -> i64 {
24+
// CHECK: tail call i64 @llvm.wasm.trunc.saturate.signed.i64.f32(float {{.*}})
25+
// CHECK-NEXT: ret i64 {{.*}}
26+
a as _
27+
}
28+
29+
// CHECK-LABEL: @cast_f32_i32
30+
#[no_mangle]
31+
pub fn cast_f32_i32(a: f32) -> i32 {
32+
// CHECK: tail call i32 @llvm.wasm.trunc.saturate.signed.i32.f32(float {{.*}})
33+
// CHECK-NEXT: ret i32 {{.*}}
34+
a as _
35+
}
36+
37+
38+
// CHECK-LABEL: @cast_f64_u64
39+
#[no_mangle]
40+
pub fn cast_f64_u64(a: f64) -> u64 {
41+
// CHECK: tail call i64 @llvm.wasm.trunc.saturate.unsigned.i64.f64(double {{.*}})
42+
// CHECK-NEXT: ret i64 {{.*}}
43+
a as _
44+
}
45+
46+
// CHECK-LABEL: @cast_f64_u32
47+
#[no_mangle]
48+
pub fn cast_f64_u32(a: f64) -> u32 {
49+
// CHECK: tail call i32 @llvm.wasm.trunc.saturate.unsigned.i32.f64(double {{.*}})
50+
// CHECK-NEXT: ret i32 {{.*}}
51+
a as _
52+
}
53+
54+
// CHECK-LABEL: @cast_f32_u64
55+
#[no_mangle]
56+
pub fn cast_f32_u64(a: f32) -> u64 {
57+
// CHECK: tail call i64 @llvm.wasm.trunc.saturate.unsigned.i64.f32(float {{.*}})
58+
// CHECK-NEXT: ret i64 {{.*}}
59+
a as _
60+
}
61+
62+
// CHECK-LABEL: @cast_f32_u32
63+
#[no_mangle]
64+
pub fn cast_f32_u32(a: f32) -> u32 {
65+
// CHECK: tail call i32 @llvm.wasm.trunc.saturate.unsigned.i32.f32(float {{.*}})
66+
// CHECK-NEXT: ret i32 {{.*}}
67+
a as _
68+
}
69+
70+
// CHECK-LABEL: @cast_f32_u8
71+
#[no_mangle]
72+
pub fn cast_f32_u8(a: f32) -> u8 {
73+
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
74+
// CHECK: fptoui float {{.*}} to i8
75+
// CHECK-NEXT: select i1 {{.*}}, i8 {{.*}}, i8 {{.*}}
76+
// CHECK-NEXT: ret i8 {{.*}}
77+
a as _
78+
}
79+
80+
81+
82+
// CHECK-LABEL: @cast_unchecked_f64_i64
83+
#[no_mangle]
84+
pub unsafe fn cast_unchecked_f64_i64(a: f64) -> i64 {
85+
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
86+
// CHECK: fptosi double {{.*}} to i64
87+
// CHECK-NEXT: ret i64 {{.*}}
88+
a.to_int_unchecked()
89+
}
90+
91+
// CHECK-LABEL: @cast_unchecked_f64_i32
92+
#[no_mangle]
93+
pub unsafe fn cast_unchecked_f64_i32(a: f64) -> i32 {
94+
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
95+
// CHECK: fptosi double {{.*}} to i32
96+
// CHECK-NEXT: ret i32 {{.*}}
97+
a.to_int_unchecked()
98+
}
99+
100+
// CHECK-LABEL: @cast_unchecked_f32_i64
101+
#[no_mangle]
102+
pub unsafe fn cast_unchecked_f32_i64(a: f32) -> i64 {
103+
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
104+
// CHECK: fptosi float {{.*}} to i64
105+
// CHECK-NEXT: ret i64 {{.*}}
106+
a.to_int_unchecked()
107+
}
108+
109+
// CHECK-LABEL: @cast_unchecked_f32_i32
110+
#[no_mangle]
111+
pub unsafe fn cast_unchecked_f32_i32(a: f32) -> i32 {
112+
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
113+
// CHECK: fptosi float {{.*}} to i32
114+
// CHECK-NEXT: ret i32 {{.*}}
115+
a.to_int_unchecked()
116+
}
117+
118+
119+
// CHECK-LABEL: @cast_unchecked_f64_u64
120+
#[no_mangle]
121+
pub unsafe fn cast_unchecked_f64_u64(a: f64) -> u64 {
122+
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
123+
// CHECK: fptoui double {{.*}} to i64
124+
// CHECK-NEXT: ret i64 {{.*}}
125+
a.to_int_unchecked()
126+
}
127+
128+
// CHECK-LABEL: @cast_unchecked_f64_u32
129+
#[no_mangle]
130+
pub unsafe fn cast_unchecked_f64_u32(a: f64) -> u32 {
131+
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
132+
// CHECK: fptoui double {{.*}} to i32
133+
// CHECK-NEXT: ret i32 {{.*}}
134+
a.to_int_unchecked()
135+
}
136+
137+
// CHECK-LABEL: @cast_unchecked_f32_u64
138+
#[no_mangle]
139+
pub unsafe fn cast_unchecked_f32_u64(a: f32) -> u64 {
140+
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
141+
// CHECK: fptoui float {{.*}} to i64
142+
// CHECK-NEXT: ret i64 {{.*}}
143+
a.to_int_unchecked()
144+
}
145+
146+
// CHECK-LABEL: @cast_unchecked_f32_u32
147+
#[no_mangle]
148+
pub unsafe fn cast_unchecked_f32_u32(a: f32) -> u32 {
149+
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
150+
// CHECK: fptoui float {{.*}} to i32
151+
// CHECK-NEXT: ret i32 {{.*}}
152+
a.to_int_unchecked()
153+
}
154+
155+
// CHECK-LABEL: @cast_unchecked_f32_u8
156+
#[no_mangle]
157+
pub unsafe fn cast_unchecked_f32_u8(a: f32) -> u8 {
158+
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
159+
// CHECK: fptoui float {{.*}} to i8
160+
// CHECK-NEXT: ret i8 {{.*}}
161+
a.to_int_unchecked()
162+
}

0 commit comments

Comments
 (0)