Skip to content

Commit 8f02f2c

Browse files
authored
Rollup merge of rust-lang#74659 - alexcrichton:wasm-codegen, r=varkor
Improve codegen for unchecked float casts on wasm This commit improves codegen for unchecked casts on WebAssembly targets to use the singluar `iNN.trunc_fMM_{u,s}` instructions. Previously rustc would codegen a bare `fptosi` and `fptoui` for float casts but for WebAssembly targets the codegen for these instructions is quite large. This large codegen is due to the fact that LLVM can speculate these instructions so the trapping behavior of WebAssembly needs to be protected against in case they're speculated. The change here is to update the codegen for the unchecked cast intrinsics to have a wasm-specific case where they call the appropriate LLVM intrinsic to generate the right wasm instruction. The intrinsic is explicitly opting-in to undefined behavior so a trap here for out-of-bounds inputs on wasm should be acceptable. cc rust-lang#73591
2 parents e9d4134 + 618aeec commit 8f02f2c

File tree

4 files changed

+74
-38
lines changed

4 files changed

+74
-38
lines changed

src/librustc_codegen_llvm/context.rs

+8
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,14 @@ impl CodegenCx<'b, 'tcx> {
510510
ifn!("llvm.wasm.trunc.saturate.signed.i32.f64", fn(t_f64) -> t_i32);
511511
ifn!("llvm.wasm.trunc.saturate.signed.i64.f32", fn(t_f32) -> t_i64);
512512
ifn!("llvm.wasm.trunc.saturate.signed.i64.f64", fn(t_f64) -> t_i64);
513+
ifn!("llvm.wasm.trunc.unsigned.i32.f32", fn(t_f32) -> t_i32);
514+
ifn!("llvm.wasm.trunc.unsigned.i32.f64", fn(t_f64) -> t_i32);
515+
ifn!("llvm.wasm.trunc.unsigned.i64.f32", fn(t_f32) -> t_i64);
516+
ifn!("llvm.wasm.trunc.unsigned.i64.f64", fn(t_f64) -> t_i64);
517+
ifn!("llvm.wasm.trunc.signed.i32.f32", fn(t_f32) -> t_i32);
518+
ifn!("llvm.wasm.trunc.signed.i32.f64", fn(t_f64) -> t_i32);
519+
ifn!("llvm.wasm.trunc.signed.i64.f32", fn(t_f32) -> t_i64);
520+
ifn!("llvm.wasm.trunc.signed.i64.f64", fn(t_f64) -> t_i64);
513521

514522
ifn!("llvm.trap", fn() -> void);
515523
ifn!("llvm.debugtrap", fn() -> void);

src/librustc_codegen_llvm/intrinsic.rs

+57-18
Original file line numberDiff line numberDiff line change
@@ -629,27 +629,24 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
629629
}
630630

631631
sym::float_to_int_unchecked => {
632-
if float_type_width(arg_tys[0]).is_none() {
633-
span_invalid_monomorphization_error(
634-
tcx.sess,
635-
span,
636-
&format!(
637-
"invalid monomorphization of `float_to_int_unchecked` \
632+
let float_width = match float_type_width(arg_tys[0]) {
633+
Some(width) => width,
634+
None => {
635+
span_invalid_monomorphization_error(
636+
tcx.sess,
637+
span,
638+
&format!(
639+
"invalid monomorphization of `float_to_int_unchecked` \
638640
intrinsic: expected basic float type, \
639641
found `{}`",
640-
arg_tys[0]
641-
),
642-
);
643-
return;
644-
}
645-
match int_type_width_signed(ret_ty, self.cx) {
646-
Some((width, signed)) => {
647-
if signed {
648-
self.fptosi(args[0].immediate(), self.cx.type_ix(width))
649-
} else {
650-
self.fptoui(args[0].immediate(), self.cx.type_ix(width))
651-
}
642+
arg_tys[0]
643+
),
644+
);
645+
return;
652646
}
647+
};
648+
let (width, signed) = match int_type_width_signed(ret_ty, self.cx) {
649+
Some(pair) => pair,
653650
None => {
654651
span_invalid_monomorphization_error(
655652
tcx.sess,
@@ -663,7 +660,49 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
663660
);
664661
return;
665662
}
663+
};
664+
665+
// The LLVM backend can reorder and speculate `fptosi` and
666+
// `fptoui`, so on WebAssembly the codegen for this instruction
667+
// is quite heavyweight. To avoid this heavyweight codegen we
668+
// instead use the raw wasm intrinsics which will lower to one
669+
// instruction in WebAssembly (`iNN.trunc_fMM_{s,u}`). This one
670+
// instruction will trap if the operand is out of bounds, but
671+
// that's ok since this intrinsic is UB if the operands are out
672+
// of bounds, so the behavior can be different on WebAssembly
673+
// than other targets.
674+
//
675+
// Note, however, that when the `nontrapping-fptoint` feature is
676+
// enabled in LLVM then LLVM will lower `fptosi` to
677+
// `iNN.trunc_sat_fMM_{s,u}`, so if that's the case we don't
678+
// bother with intrinsics.
679+
let mut result = None;
680+
if self.sess().target.target.arch == "wasm32"
681+
&& !self.sess().target_features.contains(&sym::nontrapping_dash_fptoint)
682+
{
683+
let name = match (width, float_width, signed) {
684+
(32, 32, true) => Some("llvm.wasm.trunc.signed.i32.f32"),
685+
(32, 64, true) => Some("llvm.wasm.trunc.signed.i32.f64"),
686+
(64, 32, true) => Some("llvm.wasm.trunc.signed.i64.f32"),
687+
(64, 64, true) => Some("llvm.wasm.trunc.signed.i64.f64"),
688+
(32, 32, false) => Some("llvm.wasm.trunc.unsigned.i32.f32"),
689+
(32, 64, false) => Some("llvm.wasm.trunc.unsigned.i32.f64"),
690+
(64, 32, false) => Some("llvm.wasm.trunc.unsigned.i64.f32"),
691+
(64, 64, false) => Some("llvm.wasm.trunc.unsigned.i64.f64"),
692+
_ => None,
693+
};
694+
if let Some(name) = name {
695+
let intrinsic = self.get_intrinsic(name);
696+
result = Some(self.call(intrinsic, &[args[0].immediate()], None));
697+
}
666698
}
699+
result.unwrap_or_else(|| {
700+
if signed {
701+
self.fptosi(args[0].immediate(), self.cx.type_ix(width))
702+
} else {
703+
self.fptoui(args[0].immediate(), self.cx.type_ix(width))
704+
}
705+
})
667706
}
668707

669708
sym::discriminant_value => {

src/test/codegen/unchecked-float-casts.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// unchecked intrinsics.
33

44
// compile-flags: -C opt-level=3
5+
// ignore-wasm32 the wasm target is tested in `wasm_casts_*`
56

67
#![crate_type = "lib"]
78

src/test/codegen/wasm_casts_trapping.rs

+8-20
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ pub fn cast_f32_i32(a: f32) -> i32 {
3838
a as _
3939
}
4040

41-
4241
// CHECK-LABEL: @cast_f64_u64
4342
#[no_mangle]
4443
pub fn cast_f64_u64(a: f64) -> u64 {
@@ -84,77 +83,66 @@ pub fn cast_f32_u8(a: f32) -> u8 {
8483
a as _
8584
}
8685

87-
88-
8986
// CHECK-LABEL: @cast_unchecked_f64_i64
9087
#[no_mangle]
9188
pub unsafe fn cast_unchecked_f64_i64(a: f64) -> i64 {
92-
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
93-
// CHECK: fptosi double {{.*}} to i64
89+
// CHECK: {{.*}} call {{.*}} @llvm.wasm.trunc.signed.{{.*}}
9490
// CHECK-NEXT: ret i64 {{.*}}
9591
a.to_int_unchecked()
9692
}
9793

9894
// CHECK-LABEL: @cast_unchecked_f64_i32
9995
#[no_mangle]
10096
pub unsafe fn cast_unchecked_f64_i32(a: f64) -> i32 {
101-
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
102-
// CHECK: fptosi double {{.*}} to i32
97+
// CHECK: {{.*}} call {{.*}} @llvm.wasm.trunc.signed.{{.*}}
10398
// CHECK-NEXT: ret i32 {{.*}}
10499
a.to_int_unchecked()
105100
}
106101

107102
// CHECK-LABEL: @cast_unchecked_f32_i64
108103
#[no_mangle]
109104
pub unsafe fn cast_unchecked_f32_i64(a: f32) -> i64 {
110-
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
111-
// CHECK: fptosi float {{.*}} to i64
105+
// CHECK: {{.*}} call {{.*}} @llvm.wasm.trunc.signed.{{.*}}
112106
// CHECK-NEXT: ret i64 {{.*}}
113107
a.to_int_unchecked()
114108
}
115109

116110
// CHECK-LABEL: @cast_unchecked_f32_i32
117111
#[no_mangle]
118112
pub unsafe fn cast_unchecked_f32_i32(a: f32) -> i32 {
119-
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
120-
// CHECK: fptosi float {{.*}} to i32
113+
// CHECK: {{.*}} call {{.*}} @llvm.wasm.trunc.signed.{{.*}}
121114
// CHECK-NEXT: ret i32 {{.*}}
122115
a.to_int_unchecked()
123116
}
124117

125-
126118
// CHECK-LABEL: @cast_unchecked_f64_u64
127119
#[no_mangle]
128120
pub unsafe fn cast_unchecked_f64_u64(a: f64) -> u64 {
129-
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
130-
// CHECK: fptoui double {{.*}} to i64
121+
// CHECK: {{.*}} call {{.*}} @llvm.wasm.trunc.unsigned.{{.*}}
131122
// CHECK-NEXT: ret i64 {{.*}}
132123
a.to_int_unchecked()
133124
}
134125

135126
// CHECK-LABEL: @cast_unchecked_f64_u32
136127
#[no_mangle]
137128
pub unsafe fn cast_unchecked_f64_u32(a: f64) -> u32 {
138-
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
139-
// CHECK: fptoui double {{.*}} to i32
129+
// CHECK: {{.*}} call {{.*}} @llvm.wasm.trunc.unsigned.{{.*}}
140130
// CHECK-NEXT: ret i32 {{.*}}
141131
a.to_int_unchecked()
142132
}
143133

144134
// CHECK-LABEL: @cast_unchecked_f32_u64
145135
#[no_mangle]
146136
pub unsafe fn cast_unchecked_f32_u64(a: f32) -> u64 {
147-
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
148-
// CHECK: fptoui float {{.*}} to i64
137+
// CHECK: {{.*}} call {{.*}} @llvm.wasm.trunc.unsigned.{{.*}}
149138
// CHECK-NEXT: ret i64 {{.*}}
150139
a.to_int_unchecked()
151140
}
152141

153142
// CHECK-LABEL: @cast_unchecked_f32_u32
154143
#[no_mangle]
155144
pub unsafe fn cast_unchecked_f32_u32(a: f32) -> u32 {
156-
// CHECK-NOT: {{.*}} call {{.*}} @llvm.wasm.trunc.{{.*}}
157-
// CHECK: fptoui float {{.*}} to i32
145+
// CHECK: {{.*}} call {{.*}} @llvm.wasm.trunc.unsigned.{{.*}}
158146
// CHECK-NEXT: ret i32 {{.*}}
159147
a.to_int_unchecked()
160148
}

0 commit comments

Comments
 (0)