Skip to content

Commit 39eec00

Browse files
committed
builder: use SPIR-V instructions for checked_{add,sub,mul} and saturating_{add,sub}.
1 parent 003dde7 commit 39eec00

File tree

2 files changed

+107
-62
lines changed

2 files changed

+107
-62
lines changed

crates/rustc_codegen_spirv/src/builder/builder_methods.rs

Lines changed: 81 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use rustc_codegen_ssa::traits::{
2727
};
2828
use rustc_middle::bug;
2929
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
30-
use rustc_middle::ty::layout::TyAndLayout;
30+
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
3131
use rustc_middle::ty::{self, AtomicOrdering, Ty};
3232
use rustc_span::Span;
3333
use rustc_target::callconv::FnAbi;
@@ -1721,30 +1721,15 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
17211721
fn checked_binop(
17221722
&mut self,
17231723
oop: OverflowOp,
1724-
ty: Ty<'_>,
1724+
ty: Ty<'tcx>,
17251725
lhs: Self::Value,
17261726
rhs: Self::Value,
17271727
) -> (Self::Value, Self::Value) {
1728-
// adopted partially from https://github.com/ziglang/zig/blob/master/src/codegen/spirv.zig
1729-
let is_add = match oop {
1730-
OverflowOp::Add => true,
1731-
OverflowOp::Sub => false,
1732-
OverflowOp::Mul => {
1733-
// NOTE(eddyb) this needs to be `undef`, not `false`/`true`, because
1734-
// we don't want the user's boolean constants to keep the zombie alive.
1735-
let bool = SpirvType::Bool.def(self.span(), self);
1736-
let overflowed = self.undef(bool);
1737-
1738-
let result = (self.mul(lhs, rhs), overflowed);
1739-
self.zombie(result.1.def(self), "checked mul is not supported yet");
1740-
return result;
1741-
}
1742-
};
17431728
let signed = match ty.kind() {
17441729
ty::Int(_) => true,
17451730
ty::Uint(_) => false,
1746-
other => self.fatal(format!(
1747-
"Unexpected {} type: {other:#?}",
1731+
_ => self.fatal(format!(
1732+
"unexpected {} type: {ty}",
17481733
match oop {
17491734
OverflowOp::Add => "checked add",
17501735
OverflowOp::Sub => "checked sub",
@@ -1753,13 +1738,17 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
17531738
)),
17541739
};
17551740

1756-
let result = if is_add {
1757-
self.add(lhs, rhs)
1758-
} else {
1759-
self.sub(lhs, rhs)
1760-
};
1741+
// HACK(eddyb) SPIR-V `OpIAddCarry`/`OpISubBorrow` are specifically for
1742+
// unsigned overflow, so signed overflow still needs this custom logic.
1743+
if signed && let OverflowOp::Add | OverflowOp::Sub = oop {
1744+
let result = match oop {
1745+
OverflowOp::Add => self.add(lhs, rhs),
1746+
OverflowOp::Sub => self.sub(lhs, rhs),
1747+
OverflowOp::Mul => unreachable!(),
1748+
};
1749+
1750+
// adopted partially from https://github.com/ziglang/zig/blob/master/src/codegen/spirv.zig
17611751

1762-
let overflowed = if signed {
17631752
// when adding, overflow could happen if
17641753
// - rhs is positive and result < lhs; or
17651754
// - rhs is negative and result > lhs
@@ -1771,30 +1760,80 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
17711760
// this is equivalent to (rhs < 0) == (result < lhs)
17721761
let rhs_lt_zero = self.icmp(IntPredicate::IntSLT, rhs, self.constant_int(rhs.ty, 0));
17731762
let result_gt_lhs = self.icmp(
1774-
if is_add {
1775-
IntPredicate::IntSGT
1776-
} else {
1777-
IntPredicate::IntSLT
1763+
match oop {
1764+
OverflowOp::Add => IntPredicate::IntSGT,
1765+
OverflowOp::Sub => IntPredicate::IntSLT,
1766+
OverflowOp::Mul => unreachable!(),
17781767
},
17791768
result,
17801769
lhs,
17811770
);
1782-
self.icmp(IntPredicate::IntEQ, rhs_lt_zero, result_gt_lhs)
1783-
} else {
1784-
// for unsigned addition, overflow occurred if the result is less than any of the operands.
1785-
// for subtraction, overflow occurred if the result is greater.
1786-
self.icmp(
1787-
if is_add {
1788-
IntPredicate::IntULT
1771+
1772+
let overflowed = self.icmp(IntPredicate::IntEQ, rhs_lt_zero, result_gt_lhs);
1773+
1774+
return (result, overflowed);
1775+
}
1776+
1777+
let result_type = self.layout_of(ty).spirv_type(self.span(), self);
1778+
let pair_result_type = {
1779+
let field_types = [result_type, result_type];
1780+
let (field_offsets, size, align) = crate::abi::auto_struct_layout(self, &field_types);
1781+
SpirvType::Adt {
1782+
def_id: None,
1783+
size,
1784+
align,
1785+
field_types: &field_types,
1786+
field_offsets: &field_offsets,
1787+
field_names: None,
1788+
}
1789+
.def(self.span(), self)
1790+
};
1791+
1792+
let lhs = lhs.def(self);
1793+
let rhs = rhs.def(self);
1794+
let pair_result = match oop {
1795+
OverflowOp::Add => self
1796+
.emit()
1797+
.i_add_carry(pair_result_type, None, lhs, rhs)
1798+
.unwrap(),
1799+
OverflowOp::Sub => self
1800+
.emit()
1801+
.i_sub_borrow(pair_result_type, None, lhs, rhs)
1802+
.unwrap(),
1803+
OverflowOp::Mul => {
1804+
if signed {
1805+
self.emit()
1806+
.s_mul_extended(pair_result_type, None, lhs, rhs)
1807+
.unwrap()
17891808
} else {
1790-
IntPredicate::IntUGT
1791-
},
1792-
result,
1793-
lhs,
1794-
)
1809+
self.emit()
1810+
.u_mul_extended(pair_result_type, None, lhs, rhs)
1811+
.unwrap()
1812+
}
1813+
}
1814+
}
1815+
.with_type(pair_result_type);
1816+
let result_lo = self.extract_value(pair_result, 0);
1817+
let result_hi = self.extract_value(pair_result, 1);
1818+
1819+
// HACK(eddyb) SPIR-V lacks any `(T, T) -> (T, bool)` instructions,
1820+
// so instead `result_hi` is compared with the value expected in the
1821+
// non-overflow case (`0`, or `-1` for negative signed multiply result).
1822+
let expected_nonoverflowing_hi = match (oop, signed) {
1823+
(OverflowOp::Add | OverflowOp::Sub, _) | (OverflowOp::Mul, false) => {
1824+
self.const_uint(result_type, 0)
1825+
}
1826+
(OverflowOp::Mul, true) => {
1827+
// HACK(eddyb) `(x: iN) >> (N - 1)` will spread the sign bit
1828+
// across all `N` bits of `iN`, and should be equivalent to
1829+
// `if x < 0 { -1 } else { 0 }`, without needing compare+select).
1830+
let result_width = u32::try_from(self.int_width(result_type)).unwrap();
1831+
self.ashr(result_lo, self.const_u32(result_width - 1))
1832+
}
17951833
};
1834+
let overflowed = self.icmp(IntPredicate::IntNE, result_hi, expected_nonoverflowing_hi);
17961835

1797-
(result, overflowed)
1836+
(result_lo, overflowed)
17981837
}
17991838

18001839
// rustc has the concept of an immediate vs. memory type - bools are compiled to LLVM bools as

crates/rustc_codegen_spirv/src/builder/intrinsics.rs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -118,33 +118,39 @@ impl<'a, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'tcx> {
118118

119119
sym::saturating_add => {
120120
assert_eq!(arg_tys[0], arg_tys[1]);
121-
let result = match arg_tys[0].kind() {
122-
TyKind::Int(_) | TyKind::Uint(_) => {
123-
self.add(args[0].immediate(), args[1].immediate())
124-
}
125-
TyKind::Float(_) => self.fadd(args[0].immediate(), args[1].immediate()),
121+
match arg_tys[0].kind() {
122+
TyKind::Int(_) | TyKind::Uint(_) => self
123+
.emit()
124+
.i_add_sat_intel(
125+
ret_ty,
126+
None,
127+
args[0].immediate().def(self),
128+
args[1].immediate().def(self),
129+
)
130+
.unwrap()
131+
.with_type(ret_ty),
126132
other => self.fatal(format!(
127-
"Unimplemented saturating_add intrinsic type: {other:#?}"
133+
"unimplemented saturating_add intrinsic type: {other:#?}"
128134
)),
129-
};
130-
// TODO: Implement this
131-
self.zombie(result.def(self), "saturating_add is not implemented yet");
132-
result
135+
}
133136
}
134137
sym::saturating_sub => {
135138
assert_eq!(arg_tys[0], arg_tys[1]);
136-
let result = match &arg_tys[0].kind() {
137-
TyKind::Int(_) | TyKind::Uint(_) => {
138-
self.sub(args[0].immediate(), args[1].immediate())
139-
}
140-
TyKind::Float(_) => self.fsub(args[0].immediate(), args[1].immediate()),
139+
match &arg_tys[0].kind() {
140+
TyKind::Int(_) | TyKind::Uint(_) => self
141+
.emit()
142+
.i_sub_sat_intel(
143+
ret_ty,
144+
None,
145+
args[0].immediate().def(self),
146+
args[1].immediate().def(self),
147+
)
148+
.unwrap()
149+
.with_type(ret_ty),
141150
other => self.fatal(format!(
142-
"Unimplemented saturating_sub intrinsic type: {other:#?}"
151+
"unimplemented saturating_sub intrinsic type: {other:#?}"
143152
)),
144-
};
145-
// TODO: Implement this
146-
self.zombie(result.def(self), "saturating_sub is not implemented yet");
147-
result
153+
}
148154
}
149155

150156
sym::sqrtf32 | sym::sqrtf64 | sym::sqrtf128 => {

0 commit comments

Comments
 (0)