Skip to content

Commit 337440b

Browse files
authored
Rollup merge of rust-lang#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 rust-lang#73591
2 parents 3502df4 + 0e8a69a commit 337440b

File tree

12 files changed

+933
-507
lines changed

12 files changed

+933
-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
@@ -482,6 +482,15 @@ impl CodegenCx<'b, 'tcx> {
482482
t_v8f64: t_f64, 8;
483483
}
484484

485+
ifn!("llvm.wasm.trunc.saturate.unsigned.i32.f32", fn(t_f32) -> t_i32);
486+
ifn!("llvm.wasm.trunc.saturate.unsigned.i32.f64", fn(t_f64) -> t_i32);
487+
ifn!("llvm.wasm.trunc.saturate.unsigned.i64.f32", fn(t_f32) -> t_i64);
488+
ifn!("llvm.wasm.trunc.saturate.unsigned.i64.f64", fn(t_f64) -> t_i64);
489+
ifn!("llvm.wasm.trunc.saturate.signed.i32.f32", fn(t_f32) -> t_i32);
490+
ifn!("llvm.wasm.trunc.saturate.signed.i32.f64", fn(t_f64) -> t_i32);
491+
ifn!("llvm.wasm.trunc.saturate.signed.i64.f32", fn(t_f32) -> t_i64);
492+
ifn!("llvm.wasm.trunc.saturate.signed.i64.f64", fn(t_f64) -> t_i64);
493+
485494
ifn!("llvm.trap", fn() -> void);
486495
ifn!("llvm.debugtrap", fn() -> void);
487496
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
@@ -156,6 +156,8 @@ pub trait BuilderMethods<'a, 'tcx>:
156156

157157
fn trunc(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
158158
fn sext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
159+
fn fptoui_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Option<Self::Value>;
160+
fn fptosi_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Option<Self::Value>;
159161
fn fptoui(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
160162
fn fptosi(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
161163
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
@@ -514,6 +514,7 @@ symbols! {
514514
None,
515515
non_exhaustive,
516516
non_modrs_mods,
517+
nontrapping_fptoint: "nontrapping-fptoint",
517518
noreturn,
518519
no_niche,
519520
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)