1use cubecl_core::{
2 self as cubecl,
3 frontend::polyfills::{erf, log1p, recip, to_degrees, to_radians},
4 ir::{
5 dialect::{
6 atomic::AtomicLoadOp,
7 bitwise::{
8 BitwiseNotOp, CountOnesOp, FindFirstSetOp, LeadingZerosBitsOp, ReverseBitsOp,
9 TrailingZerosBitsOp,
10 },
11 general::{BoolNotOp, CastOp, FreeOp, ReinterpretCastOp},
12 math::*,
13 memory::{LoadOp, StoreOp},
14 plane::{AtomicUniformLoadOp, UniformLoadOp},
15 synchronization::{SyncOp, SyncScope, SyncScopeAttr},
16 },
17 interfaces::TypedExt,
18 prelude::*,
19 },
20 prelude::*,
21};
22use half::bf16;
23use num_traits::{One, Zero};
24
25use crate::{
26 cuda::packed_ops::{PackableOp, packable},
27 shared::{
28 CppValue, OpToCPP,
29 convert::{no_half, promotes_int},
30 lowering::LowerOp,
31 shared_op, shared_op_with_out,
32 ty::{TypeExtCPP, TypedExtCPP},
33 unroll::unrolling,
34 },
35 target::{CtxTarget, Shared, Target},
36};
37
38pub trait FunctionFmt {
39 fn base_function_name() -> &'static str;
40 fn function_name(ctx: &Context, ty: impl Typed) -> String {
41 let prefix = ctx.target().ty_prefix(ctx, ty);
42 format!("{prefix}{}", Self::base_function_name())
43 }
44 fn format_unary(ctx: &Context, input: Value) -> String {
45 format!("{}({})", Self::function_name(ctx, input), input.name(ctx))
46 }
47}
48
49macro_rules! function {
50 ($name:ident, $func:expr, $($flags: ident),*) => {
51 impl FunctionFmt for $name {
52 fn base_function_name() -> &'static str {
53 $func
54 }
55 }
56
57 #[op_interface_impl]
58 impl OpToCPP<Shared> for $name {
59 fn to_cpp(&self, ctx: &Context) -> String {
60 format!(
61 "{} = {};",
62 self.get_result(ctx).fmt_left(ctx),
63 Self::format_unary(ctx, self.input(ctx))
64 )
65 }
66 }
67 unrolling!($name);
68 $($flags!($name);)*
69 };
70}
71
72function!(LogOp, "log", packable);
73function!(SinOp, "sin", packable);
75function!(CosOp, "cos", packable);
76function!(TanOp, "tan", no_half);
77function!(TanhOp, "tanh", packable);
78function!(SinhOp, "sinh", no_half);
79function!(CoshOp, "cosh", no_half);
80function!(ArcCosOp, "acos", no_half);
81function!(ArcSinOp, "asin", no_half);
82function!(ArcTanOp, "atan", no_half);
83function!(ArcSinhOp, "asinh", no_half);
84function!(ArcCoshOp, "acosh", no_half);
85function!(ArcTanhOp, "atanh", no_half);
86function!(SqrtOp, "sqrt", packable);
89function!(RsqrtOp, "rsqrt", packable);
90function!(ExpOp, "exp", packable);
93function!(Expm1Op, "expm1", no_half);
95function!(CeilOp, "ceil", packable);
96function!(TruncOp, "trunc", packable);
97function!(FloorOp, "floor", packable);
98function!(RoundOp, "rint", packable);
99function!(ErfOp, "erf", no_half);
103
104shared_op_with_out!(SAbsOp, |op, ctx| {
105 format!("abs({})", op.input(ctx).name(ctx))
106});
107unrolling!(SAbsOp);
108promotes_int!(SAbsOp);
109
110shared_op!(FreeOp, |_, _| String::new());
111
112shared_op_with_out!(CAbsOp, |op, ctx| {
113 format!("abs({})", op.input(ctx).name(ctx))
114});
115shared_op_with_out!(CConjOp, |op, ctx| {
116 let input = op.input(ctx);
117 let function = if input.size(ctx) == 8 {
118 "cuConjf"
119 } else {
120 "cuConj"
121 };
122 format!("{function}({})", input.name(ctx))
123});
124shared_op_with_out!(CRealOp, |op, ctx| {
125 let input = op.input(ctx);
126 let function = if input.size(ctx) == 8 {
127 "cuCrealf"
128 } else {
129 "cuCreal"
130 };
131 format!("{function}({})", input.name(ctx))
132});
133shared_op_with_out!(CImagOp, |op, ctx| {
134 let input = op.input(ctx);
135 let function = if input.size(ctx) == 8 {
136 "cuCimagf"
137 } else {
138 "cuCimag"
139 };
140 format!("{function}({})", input.name(ctx))
141});
142
143shared_op_with_out!(FAbsOp, |op, ctx| {
144 let input = op.input(ctx);
145 if input.is_half(ctx) {
146 format!("__habs({})", input.name(ctx))
147 } else if input.is_half2(ctx) {
148 format!("__habs2({})", input.name(ctx))
149 } else {
150 format!("fabs({})", input.name(ctx))
151 }
152});
153unrolling!(FAbsOp);
154packable!(FAbsOp);
155
156shared_op_with_out!(SNegOp, |op, ctx| format!("-{}", op.input(ctx).name(ctx)));
157unrolling!(SNegOp);
158promotes_int!(SNegOp);
159
160shared_op_with_out!(FNegOp, |op, ctx| format!("-{}", op.input(ctx).name(ctx)));
161unrolling!(FNegOp);
162packable!(FNegOp);
163
164shared_op_with_out!(BoolNotOp, |op, ctx| format!("!{}", op.input(ctx).name(ctx)));
165unrolling!(BoolNotOp);
166
167shared_op_with_out!(BitwiseNotOp, |op, ctx| format!(
168 "~{}",
169 op.input(ctx).name(ctx)
170));
171unrolling!(BitwiseNotOp);
172promotes_int!(BitwiseNotOp);
173
174shared_op_with_out!(CountOnesOp, |op, ctx| {
177 let input = op.input(ctx);
178 match input.size(ctx) {
179 4 => format!("__popc({})", input.name(ctx)),
180 8 => format!("__popcll({})", input.name(ctx)),
181 _ => unreachable!("Unsupported size"),
182 }
183});
184unrolling!(CountOnesOp);
185
186shared_op_with_out!(ReverseBitsOp, |op, ctx| {
187 let input = op.input(ctx);
188 match input.size(ctx) {
189 4 => format!("__brev({})", input.name(ctx)),
190 8 => format!("__brevll({})", input.name(ctx)),
191 _ => unreachable!("Unsupported size"),
192 }
193});
194unrolling!(ReverseBitsOp);
195
196shared_op_with_out!(LeadingZerosBitsOp, |op, ctx| {
197 let input = op.input(ctx);
198 match input.size(ctx) {
199 4 => format!("__clz({})", input.name(ctx)),
200 8 => format!("__clzll({})", input.name(ctx)),
201 _ => unreachable!("Unsupported size"),
202 }
203});
204unrolling!(LeadingZerosBitsOp);
205
206shared_op_with_out!(FindFirstSetOp, |op, ctx| {
207 let input = op.input(ctx);
208 match input.size(ctx) {
209 4 => format!("__ffs({})", input.name(ctx)),
210 8 => format!("__ffsll({})", input.name(ctx)),
211 _ => unreachable!("Unsupported size"),
212 }
213});
214unrolling!(FindFirstSetOp);
215
216shared_op_with_out!(CastOp, |op, ctx| {
217 let input = op.input(ctx);
218 let ty = op.get_result(ctx).get_type(ctx);
219 format!("{}({})", ty.to_cpp(ctx), input.name(ctx))
220});
221unrolling!(CastOp);
222
223#[op_interface_impl]
226impl PackableOp for CastOp {
227 fn should_pack(&self, ctx: &Context) -> bool {
228 let is_bf16_to_half = is_bf16(ctx, self.input(ctx)) && is_f16(ctx, self.get_result(ctx));
229 let is_half_to_bf16 = is_f16(ctx, self.input(ctx)) && is_bf16(ctx, self.get_result(ctx));
230 let can_pack_both = self.input(ctx).can_pack(ctx) && self.get_result(ctx).can_pack(ctx);
231 !is_bf16_to_half && !is_half_to_bf16 && can_pack_both
232 }
233}
234
235fn is_f16(ctx: &Context, val: Value) -> bool {
236 val.try_get_scalar_ty(ctx)
237 .is_some_and(|scalar| scalar.is_float16(ctx))
238}
239
240fn is_bf16(ctx: &Context, val: Value) -> bool {
241 val.try_get_scalar_ty(ctx)
242 .is_some_and(|scalar| scalar.is_bfloat16(ctx))
243}
244
245shared_op_with_out!(ReinterpretCastOp, |op, ctx| {
246 let input = op.input(ctx);
247 let ty = op.get_result(ctx).get_type(ctx).to_cpp(ctx);
248 if input.is_ptr(ctx) {
249 format!("reinterpret_cast<{ty}>({})", input.name(ctx))
250 } else {
251 format!("reinterpret_cast<const {ty}&>({})", input.name(ctx))
252 }
253});
254
255shared_op_with_out!(LoadOp, |op, ctx| format!("*{}", op.ptr(ctx).name(ctx)));
256shared_op!(StoreOp, |op, ctx| {
257 let value = op.value(ctx).name(ctx);
258 format!("*{} = {value};\n", op.ptr(ctx).name(ctx))
259});
260
261macro_rules! lower_unop {
262 ($ty: ty, $name: ident, $pred: expr) => {
263 $crate::shared::unary::lower_target_unop!($ty, $name, $crate::target::Shared, $pred);
264 };
265 ($ty: ty, $name: ident) => {
266 $crate::shared::unary::lower_unop!($ty, $name, |_, _| true);
267 };
268}
269pub(crate) use lower_unop;
270
271macro_rules! lower_target_unop {
272 ($ty: ty, $name: ident, $target: ty, $pred: expr) => {
273 #[::pliron::derive::op_interface_impl]
274 impl $crate::shared::lowering::LowerOp<$target> for $ty {
275 fn should_lower(&self, ctx: &pliron::context::Context) -> bool {
276 $crate::shared::closure_inference_hack::<$ty, bool>(self, ctx, $pred)
277 }
278
279 fn lower(&self, scope: &cubecl_core::ir::Scope) -> Vec<pliron::value::Value> {
280 use cubecl_core::ir::prelude::*;
281 use cubecl_core::prelude::*;
282 define_scalar!(T);
283 define_size!(S);
284 let input = self.get_operand(scope.ctx());
285 scope.register_value_type::<T, S>(input);
286 vec![$name::expand::<T, S>(scope, input.into()).read_value(scope)]
287 }
288 }
289 };
290 ($ty: ty, $name: ident, $target: ty) => {
291 lower_target_unop!($ty, $name, $target, |_, _| true);
292 };
293}
294pub(crate) use lower_target_unop;
295
296#[cube]
297fn find_first_set<T: Int, N: Size>(input: Vector<T, N>) -> Vector<u32, N> {
298 let bits = Vector::new(T::size_bits().comptime() as u32);
299 let out = bits - (input & (!input + Vector::one())).leading_zeros();
300 select_many(input.equal(&Vector::zero()), Vector::zero(), out)
301}
302
303#[cube]
304fn trailing_zeros<T: Int, N: Size>(input: Vector<T, N>) -> Vector<u32, N> {
305 let bits = Vector::new(T::size_bits().comptime() as u32);
306 let out = input.find_first_set() - Vector::one();
307 select_many(input.equal(&Vector::zero()), bits, out)
308}
309
310#[cube]
311fn cast_f16_bf16<T: Scalar, N: Size>(input: Vector<T, N>) -> Vector<bf16, N> {
312 Vector::<bf16, N>::cast_from(Vector::<f32, N>::cast_from(input))
313}
314
315#[cube]
316fn count_ones<T: Scalar, N: Size>(input: Vector<T, N>) -> Vector<u32, N> {
317 Vector::<u32, N>::cast_from(Vector::<u32, N>::cast_from(input).count_ones())
318}
319
320lower_unop!(RecipOp, recip);
321lower_unop!(Log1pOp, log1p);
322lower_unop!(DegreesOp, to_degrees);
323lower_unop!(RadiansOp, to_radians);
324lower_unop!(FindFirstSetOp, find_first_set, |_, ctx| {
325 ctx.target() == Target::Metal
326});
327lower_unop!(TrailingZerosBitsOp, trailing_zeros, |_, ctx| {
328 matches!(ctx.target(), Target::Cuda | Target::Hip)
329});
330lower_unop!(ErfOp, erf, |_, ctx| ctx.target() == Target::Metal);
331lower_unop!(CastOp, cast_f16_bf16, |op, ctx| {
332 op.input(ctx).is_float16(ctx)
333 && op.get_result(ctx).is_bfloat16(ctx)
334 && matches!(ctx.target(), Target::Cuda | Target::Hip)
335});
336
337fn elem_function_name(ctx: &Context, base_name: &'static str, ty: impl Typed) -> String {
340 let prefix = ctx.target().ty_prefix(ctx, ty);
342 if prefix.is_empty() {
343 base_name.to_string()
344 } else if prefix == "h" || prefix == "h2" {
345 format!("__{prefix}{base_name}")
346 } else {
347 panic!("Unknown prefix '{prefix}'");
348 }
349}
350
351shared_op_with_out!(IsNanOp, |op, ctx| {
352 let input = op.input(ctx);
353 let func = elem_function_name(ctx, "isnan", input);
354 format!("{func}({})", input.name(ctx))
355});
356unrolling!(IsNanOp);
357
358shared_op_with_out!(IsInfOp, |op, ctx| {
359 let input = op.input(ctx);
360 let func = elem_function_name(ctx, "isinf", input);
361 format!("{func}({})", input.name(ctx))
362});
363unrolling!(IsInfOp);
364
365#[op_interface_impl]
366impl LowerOp for UniformLoadOp {
367 fn lower(&self, scope: &Scope) -> Vec<Value> {
368 scope.register(&SyncOp::new(
369 scope.ctx_mut(),
370 SyncScopeAttr::new(SyncScope::Cube),
371 ));
372 let ptr = self.ptr(scope.ctx());
373 vec![scope.register_with_result(&LoadOp::new(scope.ctx_mut(), ptr))]
374 }
375}
376
377#[op_interface_impl]
378impl LowerOp for AtomicUniformLoadOp {
379 fn lower(&self, scope: &Scope) -> Vec<Value> {
380 scope.register(&SyncOp::new(
381 scope.ctx_mut(),
382 SyncScopeAttr::new(SyncScope::Cube),
383 ));
384 let ptr = self.ptr(scope.ctx());
385 vec![scope.register_with_result(&AtomicLoadOp::new(scope.ctx_mut(), ptr))]
386 }
387}