1use cubecl_core::{
2 self as cubecl, define_scalar, define_size,
3 frontend::polyfills::{expand_dp4a_polyfill, powi_int},
4 ir::{
5 dialect::{
6 bitwise::*,
7 cmp::*,
8 general::{BoolAndOp, BoolOrOp},
9 math::*,
10 memory::{self, IndexOp},
11 },
12 interfaces::TypedExt,
13 prelude::*,
14 types::{ArrayType, PointerType},
15 },
16 prelude::*,
17};
18use itertools::Itertools;
19
20use crate::{
21 cuda::packed_ops::packable,
22 shared::{
23 CppValue,
24 convert::{no_half, promotes_int},
25 lowering::LowerOp,
26 shared_op, shared_op_with_out,
27 ty::{TypeExtCPP, TypedExtCPP},
28 unroll::unrolling,
29 },
30 target::{CtxTarget, Hip, Target},
31};
32
33#[op_interface_impl]
34impl LowerOp for Dp4aOp {
35 fn should_lower(&self, ctx: &Context) -> bool {
36 ctx.target() != Target::Cuda
37 }
38
39 fn lower(&self, scope: &Scope) -> Vec<Value> {
40 let ctx = scope.ctx();
41 vec![expand_dp4a_polyfill(
42 scope,
43 self.a(ctx),
44 self.b(ctx),
45 self.c(ctx),
46 )]
47 }
48}
49
50macro_rules! operator {
51 ($name:ident, $op:expr) => {
52 shared_op_with_out!($name, |op, ctx| {
53 let lhs = op.lhs(ctx).name(ctx);
54 let rhs = op.rhs(ctx).name(ctx);
55 format!("{lhs} {} {rhs}", $op)
56 });
57 unrolling!($name);
58 promotes_int!($name);
59 };
60}
61
62operator!(IAddOp, "+");
63operator!(FAddOp, "+");
64operator!(ISubOp, "-");
65operator!(FSubOp, "-");
66operator!(SDivOp, "/");
67operator!(UDivOp, "/");
68operator!(FDivOp, "/");
69operator!(IMulOp, "*");
70operator!(FMulOp, "*");
71operator!(IEqualOp, "==");
72operator!(FEqualOp, "==");
73operator!(INotEqualOp, "!=");
74operator!(FNotEqualOp, "!=");
75operator!(SLessThanOp, "<");
76operator!(ULessThanOp, "<");
77operator!(FLessThanOp, "<");
78operator!(SLessThanOrEqualOp, "<=");
79operator!(ULessThanOrEqualOp, "<=");
80operator!(FLessThanOrEqualOp, "<=");
81operator!(SGreaterThanOp, ">");
82operator!(UGreaterThanOp, ">");
83operator!(FGreaterThanOp, ">");
84operator!(SGreaterThanOrEqualOp, ">=");
85operator!(UGreaterThanOrEqualOp, ">=");
86operator!(FGreaterThanOrEqualOp, ">=");
87operator!(ShiftLeftOp, "<<");
88operator!(ShiftRightOp, ">>");
89operator!(BitwiseOrOp, "|");
90operator!(BitwiseAndOp, "&");
91operator!(BitwiseXorOp, "^");
92operator!(BoolOrOp, "||");
93operator!(BoolAndOp, "&&");
94
95shared_op_with_out!(SRemOp, |op, ctx| {
96 let lhs = op.lhs(ctx).name(ctx);
97 let rhs = op.rhs(ctx).name(ctx);
98 format!("{lhs} % {rhs}")
99});
100unrolling!(SRemOp);
101promotes_int!(SRemOp);
102
103shared_op_with_out!(URemOp, |op, ctx| {
104 let lhs = op.lhs(ctx).name(ctx);
105 let rhs = op.rhs(ctx).name(ctx);
106 format!("{lhs} % {rhs}")
107});
108unrolling!(URemOp);
109promotes_int!(URemOp);
110
111shared_op_with_out!(FRemOp, |op, ctx| {
112 let lhs = op.lhs(ctx).name(ctx);
113 let rhs = op.rhs(ctx).name(ctx);
114 format!("fmod({lhs}, {rhs})")
115});
116unrolling!(FRemOp);
117no_half!(FRemOp);
118
119shared_op_with_out!(SModFloorOp, |op, ctx| {
120 let lhs = op.lhs(ctx).name(ctx);
121 let rhs = op.rhs(ctx).name(ctx);
122 let out_elem = op.get_result(ctx).get_type(ctx).to_cpp(ctx);
123 format!("{lhs} - {rhs} * ({out_elem})floor((float){lhs} / (float){rhs})")
124});
125unrolling!(SModFloorOp);
126
127shared_op_with_out!(FModFloorOp, |op, ctx| {
128 let lhs = op.lhs(ctx).name(ctx);
129 let rhs = op.rhs(ctx).name(ctx);
130 let prefix = ctx.target().ty_prefix(ctx, op.get_result(ctx));
131 let floor = format!("{prefix}floor");
132 format!("{lhs} - {rhs} * {floor}({lhs} / {rhs})")
133});
134unrolling!(FModFloorOp);
135packable!(FModFloorOp);
136
137shared_op_with_out!(SMulHiOp, |op, ctx| {
152 let lhs = op.lhs(ctx);
153 let rhs = op.rhs(ctx).name(ctx);
154 match lhs.size(ctx) {
155 4 => format!("__mulhi({}, {rhs})", lhs.name(ctx)),
156 8 => format!("__mul64hi({}, {rhs})", lhs.name(ctx)),
157 _ => unreachable!("HiMul only supports 32 and 64 bit ints"),
158 }
159});
160unrolling!(SMulHiOp);
161
162shared_op_with_out!(UMulHiOp, |op, ctx| {
163 let lhs = op.lhs(ctx);
164 let rhs = op.rhs(ctx).name(ctx);
165 match lhs.size(ctx) {
166 4 => format!("__umulhi({}, {rhs})", lhs.name(ctx)),
167 8 => format!("__umul64hi({}, {rhs})", lhs.name(ctx)),
168 _ => unreachable!("HiMul only supports 32 and 64 bit ints"),
169 }
170});
171unrolling!(UMulHiOp);
172
173macro_rules! lower_binop {
174 ($ty: ty, $name: ident, $pred: expr) => {
175 $crate::shared::binary::lower_target_binop!($ty, $name, $crate::target::Shared, $pred);
176 };
177 ($ty: ty, $name: ident) => {
178 $crate::shared::binary::lower_binop!($ty, $name, |_, _| true);
179 };
180}
181pub(crate) use lower_binop;
182
183macro_rules! lower_target_binop {
184 ($ty: ty, $name: ident, $target: ty, $pred: expr) => {
185 #[op_interface_impl]
186 impl $crate::shared::lowering::LowerOp<$target> for $ty {
187 fn should_lower(&self, ctx: &Context) -> bool {
188 $crate::shared::closure_inference_hack::<$ty, bool>(self, ctx, $pred)
189 }
190
191 fn lower(&self, scope: &Scope) -> Vec<Value> {
192 use cubecl_core::ir::dialect::OperationPtrExt;
193 define_scalar!(T);
194 define_size!(S);
195 let lhs = self.get_operation().operand(scope.ctx(), 0);
196 let rhs = self.get_operation().operand(scope.ctx(), 1);
197 scope.register_value_type::<T, S>(rhs);
198 vec![$name::expand::<T, S>(scope, lhs.into(), rhs.into()).read_value(scope)]
199 }
200 }
201 };
202 ($ty: ty, $name: ident, $target: ty) => {
203 lower_target_binop!($ty, $name, $target, |_, _| true);
204 };
205}
206pub(crate) use lower_target_binop;
207
208#[cube]
209fn min_bf16<T: Numeric, N: Size>(lhs: Vector<T, N>, rhs: Vector<T, N>) -> Vector<T, N> {
210 let lhs = Vector::<f32, N>::cast_from(lhs);
211 let rhs = Vector::<f32, N>::cast_from(rhs);
212 Vector::cast_from(lhs.min(rhs))
213}
214
215#[cube]
216fn max_bf16<T: Numeric, N: Size>(lhs: Vector<T, N>, rhs: Vector<T, N>) -> Vector<T, N> {
217 let lhs = Vector::<f32, N>::cast_from(lhs);
218 let rhs = Vector::<f32, N>::cast_from(rhs);
219 Vector::cast_from(lhs.max(rhs))
220}
221
222lower_target_binop!(FMinOp, min_bf16, Hip, |op, ctx| {
223 op.lhs(ctx).is_bfloat16(ctx)
224});
225lower_target_binop!(FMaxOp, max_bf16, Hip, |op, ctx| {
226 op.lhs(ctx).is_bfloat16(ctx)
227});
228
229shared_op_with_out!(SMinOp, |op, ctx| {
230 let lhs = op.lhs(ctx).name(ctx);
231 let rhs = op.rhs(ctx).name(ctx);
232 format!("min({lhs}, {rhs})")
233});
234unrolling!(SMinOp);
235promotes_int!(SMinOp);
236
237shared_op_with_out!(UMinOp, |op, ctx| {
238 let lhs = op.lhs(ctx).name(ctx);
239 let rhs = op.rhs(ctx).name(ctx);
240 format!("min({lhs}, {rhs})")
241});
242unrolling!(UMinOp);
243promotes_int!(UMinOp);
244
245shared_op_with_out!(FMinOp, |op, ctx| {
246 let lhs = op.lhs(ctx);
247 let rhs = op.rhs(ctx).name(ctx);
248 if lhs.is_half(ctx) {
249 format!("__hmin({}, {rhs})", lhs.name(ctx))
250 } else if lhs.is_half2(ctx) {
251 format!("__hmin2({}, {rhs})", lhs.name(ctx))
252 } else {
253 format!("min({}, {rhs})", lhs.name(ctx))
254 }
255});
256unrolling!(FMinOp);
257packable!(FMinOp);
258
259shared_op_with_out!(SMaxOp, |op, ctx| {
260 let lhs = op.lhs(ctx).name(ctx);
261 let rhs = op.rhs(ctx).name(ctx);
262 format!("max({lhs}, {rhs})")
263});
264unrolling!(SMaxOp);
265promotes_int!(SMaxOp);
266
267shared_op_with_out!(UMaxOp, |op, ctx| {
268 let lhs = op.lhs(ctx).name(ctx);
269 let rhs = op.rhs(ctx).name(ctx);
270 format!("max({lhs}, {rhs})")
271});
272unrolling!(UMaxOp);
273promotes_int!(UMaxOp);
274
275shared_op_with_out!(FMaxOp, |op, ctx| {
276 let lhs = op.lhs(ctx);
277 let rhs = op.rhs(ctx).name(ctx);
278 if lhs.is_half(ctx) {
279 format!("__hmax({}, {rhs})", lhs.name(ctx))
280 } else if lhs.is_half2(ctx) {
281 format!("__hmax2({}, {rhs})", lhs.name(ctx))
282 } else {
283 format!("max({}, {rhs})", lhs.name(ctx))
284 }
285});
286unrolling!(FMaxOp);
287packable!(FMaxOp);
288
289shared_op_with_out!(SClampOp, |op, ctx| {
290 let input = op.input(ctx).name(ctx);
291 let min = op.min(ctx).name(ctx);
292 let max = op.max(ctx).name(ctx);
293 format!("max(min({input}, {max}), {min})")
294});
295unrolling!(SClampOp);
296promotes_int!(SClampOp);
297
298shared_op_with_out!(UClampOp, |op, ctx| {
299 let input = op.input(ctx).name(ctx);
300 let min = op.min(ctx).name(ctx);
301 let max = op.max(ctx).name(ctx);
302 format!("max(min({input}, {max}), {min})")
303});
304unrolling!(UClampOp);
305promotes_int!(UClampOp);
306
307shared_op_with_out!(FClampOp, |op, ctx| {
308 let input = op.input(ctx);
309 let min = op.min(ctx).name(ctx);
310 let max = op.max(ctx).name(ctx);
311 if input.is_half(ctx) {
312 format!("__hmax(__hmin({}, {max}), {min})", input.name(ctx))
313 } else if input.is_half2(ctx) {
314 format!("__hmax2(__hmin2({}, {max}), {min})", input.name(ctx))
315 } else {
316 format!("max(min({}, {max}), {min})", input.name(ctx))
317 }
318});
319unrolling!(FClampOp);
320packable!(FClampOp);
321
322shared_op_with_out!(PowfOp, |op, ctx| {
323 format!("pow({}, {})", op.lhs(ctx).name(ctx), op.rhs(ctx).name(ctx))
324});
325unrolling!(PowfOp);
326no_half!(PowfOp);
327
328#[op_interface_impl]
329impl LowerOp for PowiOp {
330 fn should_lower(&self, ctx: &Context) -> bool {
331 let ty = self.get_result(ctx).scalar_ty(ctx);
332 ty.is_int(ctx) || ty.is_index(ctx)
333 }
334
335 fn lower(&self, scope: &Scope) -> Vec<Value> {
336 use cubecl_core::ir::dialect::OperationPtrExt;
337
338 define_scalar!(T);
339 define_size!(S);
340 let lhs = self.get_operation().operand(scope.ctx(), 0);
341 let rhs = self.get_operation().operand(scope.ctx(), 1);
342 scope.register_value_type::<T, S>(lhs);
343 vec![powi_int::expand::<T, S>(scope, lhs.into(), rhs.into()).read_value(scope)]
344 }
345}
346
347shared_op_with_out!(PowiOp, |op, ctx| {
348 let lhs = op.lhs(ctx);
349 let rhs = op.rhs(ctx).name(ctx);
350 format!("pow({}, {rhs})", lhs.name(ctx))
351});
352unrolling!(PowiOp);
353no_half!(PowiOp);
354
355shared_op_with_out!(ArcTan2Op, |op, ctx| {
370 let lhs = op.lhs(ctx).name(ctx);
371 let rhs = op.rhs(ctx).name(ctx);
372 format!("atan2({lhs}, {rhs})")
373});
374unrolling!(ArcTan2Op);
375no_half!(ArcTan2Op);
376
377shared_op_with_out!(HypotOp, |op, ctx| {
378 let lhs = op.lhs(ctx);
379 let rhs = op.rhs(ctx).name(ctx);
380 format!("hypot({}, {rhs})", lhs.name(ctx))
381});
382unrolling!(HypotOp);
383no_half!(HypotOp);
384
385shared_op_with_out!(RhypotOp, |op, ctx| {
386 let lhs = op.lhs(ctx);
387 let rhs = op.rhs(ctx).name(ctx);
388 if lhs.is_float32(ctx) {
389 format!("rhypotf({}, {rhs})", lhs.name(ctx))
390 } else {
391 format!("rhypot({}, {rhs})", lhs.name(ctx))
392 }
393});
394unrolling!(RhypotOp);
395no_half!(RhypotOp);
396
397shared_op_with_out!(IndexOp, |op, ctx| {
398 format!("&{}", fmt_index(ctx, op.base(ctx), op.index(ctx)))
399});
400
401pub fn fmt_index(ctx: &Context, list: Value, index: Value) -> String {
402 let list_ty = list.get_type(ctx).deref(ctx);
403 let list = list.name(ctx);
404 let index = index.name(ctx);
405 if let Some(PointerType { inner, .. }) = list_ty.downcast_ref()
407 && inner.deref(ctx).is::<ArrayType>()
408 {
409 format!("(*{list})[{index}]")
410 } else {
411 format!("{list}[{index}]")
412 }
413}
414
415shared_op!(memory::CopyOp, |op, ctx| {
416 let source = op.source(ctx).name(ctx);
417 let dest = op.destination(ctx).name(ctx);
418 (0..op.len(ctx).0)
419 .map(|i| format!("*({dest} + {i}) = *({source} + {i});\n"))
420 .join("")
421});