Skip to content

Commit bf3a0ee

Browse files
committed
builder: use SPIR-V instructions for checked_{add,sub,mul} and saturating_{add,sub}.
1 parent 22a926f commit bf3a0ee

3 files changed

Lines changed: 107 additions & 70 deletions

File tree

crates/rustc_codegen_spirv/src/abi.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -616,15 +616,6 @@ fn trans_aggregate<'tcx>(cx: &CodegenCx<'tcx>, span: Span, ty: TyAndLayout<'tcx>
616616
}
617617
}
618618

619-
#[cfg_attr(
620-
not(rustc_codegen_spirv_disable_pqp_cg_ssa),
621-
expect(
622-
unused,
623-
reason = "actually used from \
624-
`<rustc_codegen_ssa::traits::ConstCodegenMethods for CodegenCx<'_>>::const_struct`, \
625-
but `rustc_codegen_ssa` being `pqp_cg_ssa` makes that trait unexported"
626-
)
627-
)]
628619
// returns (field_offsets, size, align)
629620
pub fn auto_struct_layout(
630621
cx: &CodegenCx<'_>,

crates/rustc_codegen_spirv/src/builder/builder_methods.rs

Lines changed: 81 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use rustc_codegen_ssa::traits::{
2626
};
2727
use rustc_middle::bug;
2828
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
29+
use rustc_middle::ty::layout::LayoutOf;
2930
use rustc_middle::ty::{self, AtomicOrdering, Ty};
3031
use rustc_span::Span;
3132
use rustc_target::callconv::FnAbi;
@@ -1721,30 +1722,15 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
17211722
fn checked_binop(
17221723
&mut self,
17231724
oop: OverflowOp,
1724-
ty: Ty<'_>,
1725+
ty: Ty<'tcx>,
17251726
lhs: Self::Value,
17261727
rhs: Self::Value,
17271728
) -> (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-
};
17431729
let signed = match ty.kind() {
17441730
ty::Int(_) => true,
17451731
ty::Uint(_) => false,
1746-
other => self.fatal(format!(
1747-
"Unexpected {} type: {other:#?}",
1732+
_ => self.fatal(format!(
1733+
"unexpected {} type: {ty}",
17481734
match oop {
17491735
OverflowOp::Add => "checked add",
17501736
OverflowOp::Sub => "checked sub",
@@ -1753,13 +1739,17 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
17531739
)),
17541740
};
17551741

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

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

1797-
(result, overflowed)
1837+
(result_lo, overflowed)
17981838
}
17991839

18001840
// 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
@@ -115,33 +115,39 @@ impl<'a, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'tcx> {
115115

116116
sym::saturating_add => {
117117
assert_eq!(arg_tys[0], arg_tys[1]);
118-
let result = match arg_tys[0].kind() {
119-
TyKind::Int(_) | TyKind::Uint(_) => {
120-
self.add(args[0].immediate(), args[1].immediate())
121-
}
122-
TyKind::Float(_) => self.fadd(args[0].immediate(), args[1].immediate()),
118+
match arg_tys[0].kind() {
119+
TyKind::Int(_) | TyKind::Uint(_) => self
120+
.emit()
121+
.i_add_sat_intel(
122+
ret_ty,
123+
None,
124+
args[0].immediate().def(self),
125+
args[1].immediate().def(self),
126+
)
127+
.unwrap()
128+
.with_type(ret_ty),
123129
other => self.fatal(format!(
124-
"Unimplemented saturating_add intrinsic type: {other:#?}"
130+
"unimplemented saturating_add intrinsic type: {other:#?}"
125131
)),
126-
};
127-
// TODO: Implement this
128-
self.zombie(result.def(self), "saturating_add is not implemented yet");
129-
result
132+
}
130133
}
131134
sym::saturating_sub => {
132135
assert_eq!(arg_tys[0], arg_tys[1]);
133-
let result = match &arg_tys[0].kind() {
134-
TyKind::Int(_) | TyKind::Uint(_) => {
135-
self.sub(args[0].immediate(), args[1].immediate())
136-
}
137-
TyKind::Float(_) => self.fsub(args[0].immediate(), args[1].immediate()),
136+
match &arg_tys[0].kind() {
137+
TyKind::Int(_) | TyKind::Uint(_) => self
138+
.emit()
139+
.i_sub_sat_intel(
140+
ret_ty,
141+
None,
142+
args[0].immediate().def(self),
143+
args[1].immediate().def(self),
144+
)
145+
.unwrap()
146+
.with_type(ret_ty),
138147
other => self.fatal(format!(
139-
"Unimplemented saturating_sub intrinsic type: {other:#?}"
148+
"unimplemented saturating_sub intrinsic type: {other:#?}"
140149
)),
141-
};
142-
// TODO: Implement this
143-
self.zombie(result.def(self), "saturating_sub is not implemented yet");
144-
result
150+
}
145151
}
146152

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

0 commit comments

Comments
 (0)