cubecl_core/post_processing/
saturating.rs1use crate as cubecl;
2use alloc::vec::Vec;
3use cubecl_ir::{
4 Arithmetic, ElemType, Instruction, IntKind, Operation, Processor, Scope, ScopeProcessing,
5 StorageType, UIntKind, Value,
6};
7
8use crate::prelude::*;
9
10define_scalar!(ElemA);
11define_scalar!(ElemB);
12define_size!(SizeA);
13
14#[derive(new, Debug)]
16pub struct SaturatingArithmeticProcessor {
17 replace_i32: bool,
20}
21
22impl Processor for SaturatingArithmeticProcessor {
23 fn transform(&self, mut processing: cubecl_ir::ScopeProcessing) -> cubecl_ir::ScopeProcessing {
24 let mut instructions = Vec::new();
25 core::mem::swap(&mut processing.instructions, &mut instructions);
26
27 for instruction in instructions {
28 if let Operation::Arithmetic(arithmetic) = &instruction.operation {
29 match arithmetic {
30 Arithmetic::SaturatingAdd(op) if op.lhs.elem_type().is_unsigned_int() => {
31 run_polyfill(
32 &mut processing,
33 op.lhs,
34 op.rhs,
35 instruction.out(),
36 saturating_add_unsigned::expand::<ElemA, SizeA>,
37 );
38 continue;
39 }
40 Arithmetic::SaturatingAdd(op)
41 if op.lhs.elem_type().is_signed_int()
42 && self.should_replace(op.lhs.storage_type()) =>
43 {
44 run_polyfill(
45 &mut processing,
46 op.lhs,
47 op.rhs,
48 instruction.out(),
49 saturating_add_signed::expand::<ElemA, ElemB, SizeA>,
50 );
51 continue;
52 }
53 Arithmetic::SaturatingSub(op) if op.lhs.elem_type().is_unsigned_int() => {
54 run_polyfill(
55 &mut processing,
56 op.lhs,
57 op.rhs,
58 instruction.out(),
59 saturating_sub_unsigned::expand::<ElemA, SizeA>,
60 );
61 continue;
62 }
63 Arithmetic::SaturatingSub(op)
64 if op.lhs.elem_type().is_signed_int()
65 && self.should_replace(op.lhs.storage_type()) =>
66 {
67 run_polyfill(
68 &mut processing,
69 op.lhs,
70 op.rhs,
71 instruction.out(),
72 saturating_sub_signed::expand::<ElemA, ElemB, SizeA>,
73 );
74 continue;
75 }
76 _ => {}
77 }
78 }
79
80 processing.instructions.push(instruction);
82 }
83 processing
84 }
85}
86
87impl SaturatingArithmeticProcessor {
88 fn should_replace(&self, ty: StorageType) -> bool {
89 self.replace_i32 || !matches!(ty, StorageType::Scalar(ElemType::Int(IntKind::I32)))
90 }
91}
92
93fn run_polyfill<T: CubePrimitive>(
94 processing: &mut ScopeProcessing,
95 lhs: Value,
96 rhs: Value,
97 out: Value,
98 mut polyfill: impl FnMut(&Scope, NativeExpand<T>, NativeExpand<T>) -> NativeExpand<T>,
99) {
100 let scope = Scope::root(false).with_global_state(processing.global_state.clone());
101 scope.register_type::<ElemA>(lhs.storage_type());
102 scope.register_size::<SizeA>(lhs.vector_size());
103 if let ElemType::Int(kind) = lhs.elem_type() {
104 let unsigned_ty = match kind {
105 IntKind::I8 => UIntKind::U8,
106 IntKind::I16 => UIntKind::U16,
107 IntKind::I32 => UIntKind::U32,
108 IntKind::I64 => UIntKind::U64,
109 };
110 scope.register_type::<ElemB>(ElemType::UInt(unsigned_ty).into())
111 }
112
113 let out_poly = polyfill(&scope, lhs.into(), rhs.into()).expand;
114 let tmp_processing = scope.process([]);
115
116 for inst in tmp_processing.instructions {
117 processing.instructions.push(inst);
118 }
119
120 processing
121 .instructions
122 .push(Instruction::new(Operation::Copy(out_poly), out));
123}
124
125#[cube]
126fn saturating_add_unsigned<U: Int, N: Size>(a: Vector<U, N>, b: Vector<U, N>) -> Vector<U, N> {
127 let c = a.min(!b);
128 c + b
129}
130
131#[cube]
132fn saturating_sub_unsigned<U: Int, N: Size>(a: Vector<U, N>, b: Vector<U, N>) -> Vector<U, N> {
133 let a = a.max(b);
134 a - b
135}
136
137#[cube]
140fn saturating_add_signed<I: Int, U: Int, N: Size>(
141 x: Vector<I, N>,
142 y: Vector<I, N>,
143) -> Vector<I, N> {
144 let bit_width = I::type_size_bits();
145 let shift = Vector::<U, N>::new(U::new(comptime![(bit_width - 1) as i64]));
146
147 let ux = Vector::<U, N>::cast_from(x);
148 let uy = Vector::<U, N>::cast_from(y);
149 let res = ux + uy;
150 let ux = (ux >> shift) + Vector::<U, N>::cast_from(I::max_value());
151 let zero = Vector::new(I::new(0));
152 let cond = Vector::<I, N>::cast_from((ux ^ uy) | !(uy ^ res)).greater_equal(&zero);
153 select_many(cond, Vector::cast_from(ux), Vector::cast_from(res))
154}
155
156#[cube]
159fn saturating_sub_signed<I: Int, U: Int, N: Size>(
160 x: Vector<I, N>,
161 y: Vector<I, N>,
162) -> Vector<I, N> {
163 let bit_width = I::type_size_bits();
164 let shift = Vector::<U, N>::new(U::new(comptime![(bit_width - 1) as i64]));
165
166 let ux = Vector::<U, N>::cast_from(x);
167 let uy = Vector::<U, N>::cast_from(y);
168 let res = ux - uy;
169 let ux = (ux >> shift) + Vector::<U, N>::cast_from(I::max_value());
170 let zero = Vector::new(I::new(0));
171 let cond = Vector::<I, N>::cast_from((ux ^ uy) & (ux ^ res)).less_than(&zero);
172 select_many(cond, Vector::cast_from(ux), Vector::cast_from(res))
173}