1use cubecl_core::{
2 e2m1, e2m1x2, e4m3, e5m2,
3 ir::{ConstantValue, Id},
4 ue8m0,
5};
6use cubecl_runtime::kernel::Visibility;
7use std::fmt::{Display, Formatter};
8
9use crate::shared::{FP4Kind, FP8Kind, PointerClass, binary::fmt_index};
10
11use super::{COUNTER_TMP_VAR, Dialect, Elem, Item};
12
13pub trait Component<D: Dialect>: Display + FmtLeft {
14 fn item(&self) -> Item<D>;
15 fn is_const(&self) -> bool;
16 fn index(&self, index: usize) -> IndexedValue<D>;
17 fn elem(&self) -> Elem<D> {
18 *self.item().elem()
19 }
20}
21
22pub trait FmtLeft: Display {
23 fn fmt_left(&self) -> String;
24}
25
26#[derive(new, Debug)]
27pub struct OptimizedArgs<const N: usize, D: Dialect> {
28 pub args: [Value<D>; N],
29 pub optimization_factor: Option<usize>,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum Value<D: Dialect> {
34 Constant(ConstantValue, Item<D>),
35 Value {
36 id: Id,
37 item: Item<D>,
38 },
39 Tmp {
40 id: Id,
41 item: Item<D>,
42 is_declared: bool,
43 is_ptr: bool,
44 is_const: bool,
45 },
46}
47
48#[derive(Debug, Clone, Copy)]
49pub enum Builtin<D: Dialect> {
50 AbsolutePos(Elem<D>),
51 AbsolutePosBaseName, AbsolutePosX,
53 AbsolutePosY,
54 AbsolutePosZ,
55 UnitPos,
56 UnitPosBaseName, UnitPosX,
58 UnitPosY,
59 UnitPosZ,
60 CubePos(Elem<D>),
61 CubePosBaseName, CubePosX,
63 CubePosY,
64 CubePosZ,
65 CubeDim,
66 CubeDimBaseName, CubeDimX,
68 CubeDimY,
69 CubeDimZ,
70 CubeCount(Elem<D>),
71 CubeCountBaseName, CubeCountX,
73 CubeCountY,
74 CubeCountZ,
75 PlaneDim,
76 PlaneDimChecked,
77 PlanePos,
78 UnitPosPlane,
79 ClusterRank,
80 ClusterIndexX,
81 ClusterIndexY,
82 ClusterIndexZ,
83}
84
85impl<D: Dialect> Builtin<D> {
86 pub fn fmt_cast_to(&self, item: Item<D>) -> String {
88 if self.item() == item {
89 self.to_string()
90 } else {
91 format!("{item}({self})")
92 }
93 }
94
95 pub fn item(&self) -> Item<D> {
96 match self {
97 Builtin::AbsolutePos(elem) => Item::Scalar(*elem),
98 Builtin::AbsolutePosBaseName => Item::NativeVector(Elem::U32, 3),
99 Builtin::AbsolutePosX => Item::Scalar(Elem::U32),
100 Builtin::AbsolutePosY => Item::Scalar(Elem::U32),
101 Builtin::AbsolutePosZ => Item::Scalar(Elem::U32),
102 Builtin::CubeCount(elem) => Item::Scalar(*elem),
103 Builtin::CubeCountBaseName => Item::NativeVector(Elem::U32, 3),
104 Builtin::CubeCountX => Item::Scalar(Elem::U32),
105 Builtin::CubeCountY => Item::Scalar(Elem::U32),
106 Builtin::CubeCountZ => Item::Scalar(Elem::U32),
107 Builtin::CubeDimBaseName => Item::NativeVector(Elem::U32, 3),
108 Builtin::CubeDim => Item::Scalar(Elem::U32),
109 Builtin::CubeDimX => Item::Scalar(Elem::U32),
110 Builtin::CubeDimY => Item::Scalar(Elem::U32),
111 Builtin::CubeDimZ => Item::Scalar(Elem::U32),
112 Builtin::CubePos(elem) => Item::Scalar(*elem),
113 Builtin::CubePosBaseName => Item::NativeVector(Elem::U32, 3),
114 Builtin::CubePosX => Item::Scalar(Elem::U32),
115 Builtin::CubePosY => Item::Scalar(Elem::U32),
116 Builtin::CubePosZ => Item::Scalar(Elem::U32),
117 Builtin::UnitPos => Item::Scalar(Elem::U32),
118 Builtin::UnitPosBaseName => Item::NativeVector(Elem::U32, 3),
119 Builtin::UnitPosX => Item::Scalar(Elem::U32),
120 Builtin::UnitPosY => Item::Scalar(Elem::U32),
121 Builtin::UnitPosZ => Item::Scalar(Elem::U32),
122 Builtin::PlaneDim => Item::Scalar(Elem::U32),
123 Builtin::PlaneDimChecked => Item::Scalar(Elem::U32),
124 Builtin::PlanePos => Item::Scalar(Elem::U32),
125 Builtin::UnitPosPlane => Item::Scalar(Elem::U32),
126 Builtin::ClusterRank => Item::Scalar(Elem::U32),
127 Builtin::ClusterIndexX => Item::Scalar(Elem::U32),
128 Builtin::ClusterIndexY => Item::Scalar(Elem::U32),
129 Builtin::ClusterIndexZ => Item::Scalar(Elem::U32),
130 }
131 }
132}
133
134impl<D: Dialect> Component<D> for Value<D> {
135 fn index(&self, index: usize) -> IndexedValue<D> {
136 self.index(index)
137 }
138
139 fn item(&self) -> Item<D> {
140 match self {
141 Value::Value { item, .. } => *item,
142 Value::Constant(_, e) => *e,
143 Value::Tmp { item, .. } => *item,
144 }
145 }
146
147 fn is_const(&self) -> bool {
148 if let Value::Tmp { is_const, .. } = self {
149 return *is_const;
150 }
151 if let Item::Pointer(_, PointerClass::Global(Visibility::Read)) = self.item() {
152 return true;
153 }
154
155 !self.item().is_ptr()
156 }
157}
158
159pub(crate) fn format_const<D: Dialect>(number: &ConstantValue, item: &Item<D>) -> String {
160 let number = match item.elem() {
162 Elem::FP4(FP4Kind::E2M1) => e2m1::from_f64(number.as_f64()).to_bits(),
163 Elem::FP4x2(FP4Kind::E2M1) => {
164 let v = number.as_f64() as f32;
165 let value = [v, v];
166 e2m1x2::from_f32_slice(&value).remove(0).to_bits()
167 }
168 Elem::FP6(_) | Elem::FP6x2(_) => {
169 todo!("FP6 constants are not yet supported")
170 }
171 Elem::FP8(FP8Kind::E4M3) => e4m3::from_f64(number.as_f64()).to_bits(),
172 Elem::FP8(FP8Kind::E5M2) => e5m2::from_f64(number.as_f64()).to_bits(),
173 Elem::FP8(FP8Kind::UE8M0) => ue8m0::from_f64(number.as_f64()).to_bits(),
174 _ => {
175 if let ConstantValue::Float(value) = number {
179 if value.is_infinite() {
180 return if *value < 0.0 {
181 "(-1.0f/0.0f)"
182 } else {
183 "(1.0f/0.0f)"
184 }
185 .to_string();
186 }
187 if value.is_nan() {
188 return "(0.0f/0.0f)".to_string();
189 }
190 }
191 return format!("{number}");
192 }
193 };
194 format!("{number}")
195}
196
197impl<D: Dialect> Display for Value<D> {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 match self {
200 Value::Value { id, .. } => write!(f, "val_{id}"),
201 Value::Constant(number, item) if item.vectorization() <= 1 => {
202 let value = format_const(number, item);
203 write!(f, "{item}({value})")
204 }
205 Value::Constant(number, item) => {
206 let number = format_const(number, item);
207 let values = (0..item.vectorization())
208 .map(|_| format!("{}({number})", item.elem()))
209 .collect::<Vec<_>>();
210 write!(f, "{item} {{ {} }}", values.join(","))
211 }
212 Value::Tmp { id, .. } => write!(f, "_tmp_{id}"),
213 }
214 }
215}
216
217impl<D: Dialect> Display for Builtin<D> {
218 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
219 match self {
220 Builtin::AbsolutePos(_) => D::compile_absolute_pos(f),
221 Builtin::AbsolutePosBaseName => D::compile_absolute_pos_base_name(f),
222 Builtin::AbsolutePosX => D::compile_absolute_pos_x(f),
223 Builtin::AbsolutePosY => D::compile_absolute_pos_y(f),
224 Builtin::AbsolutePosZ => D::compile_absolute_pos_z(f),
225 Builtin::CubeCount(_) => D::compile_cube_count(f),
226 Builtin::CubeCountBaseName => D::compile_cube_count_base_name(f),
227 Builtin::CubeCountX => D::compile_cube_count_x(f),
228 Builtin::CubeCountY => D::compile_cube_count_y(f),
229 Builtin::CubeCountZ => D::compile_cube_count_z(f),
230 Builtin::CubeDim => D::compile_cube_dim(f),
231 Builtin::CubeDimBaseName => D::compile_cube_dim_base_name(f),
232 Builtin::CubeDimX => D::compile_cube_dim_x(f),
233 Builtin::CubeDimY => D::compile_cube_dim_y(f),
234 Builtin::CubeDimZ => D::compile_cube_dim_z(f),
235 Builtin::CubePos(_) => D::compile_cube_pos(f),
236 Builtin::CubePosBaseName => D::compile_cube_pos_base_name(f),
237 Builtin::CubePosX => D::compile_cube_pos_x(f),
238 Builtin::CubePosY => D::compile_cube_pos_y(f),
239 Builtin::CubePosZ => D::compile_cube_pos_z(f),
240 Builtin::UnitPos => D::compile_unit_pos(f),
241 Builtin::UnitPosBaseName => D::compile_unit_pos_base_name(f),
242 Builtin::UnitPosX => D::compile_unit_pos_x(f),
243 Builtin::UnitPosY => D::compile_unit_pos_y(f),
244 Builtin::UnitPosZ => D::compile_unit_pos_z(f),
245 Builtin::PlaneDim => D::compile_plane_dim(f),
246 Builtin::PlaneDimChecked => D::compile_plane_dim_checked(f),
247 Builtin::PlanePos => D::compile_plane_pos(f),
248 Builtin::UnitPosPlane => D::compile_unit_pos_plane(f),
249 Builtin::ClusterRank => D::compile_cluster_pos(f),
250 Builtin::ClusterIndexX => D::compile_cluster_pos_x(f),
251 Builtin::ClusterIndexY => D::compile_cluster_pos_y(f),
252 Builtin::ClusterIndexZ => D::compile_cluster_pos_z(f),
253 }
254 }
255}
256
257impl<D: Dialect> Value<D> {
258 pub fn is_optimized(&self) -> bool {
259 self.item().is_optimized()
260 }
261
262 pub fn tmp(item: Item<D>) -> Self {
266 let inc = COUNTER_TMP_VAR.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
267
268 Value::Tmp {
269 id: inc as Id,
270 item,
271 is_declared: false,
272 is_ptr: false,
273 is_const: false,
274 }
275 }
276
277 pub fn to_const(&mut self) {
278 if let Value::Tmp { is_const, .. } = self {
279 *is_const = true;
280 }
281 }
282
283 pub fn reinterpret_ptr(&self, f: &mut Formatter<'_>, item: Item<D>) -> Self {
285 let mut out = Self::tmp_ptr(item);
286
287 if self.is_const() {
288 out.to_const();
289 }
290
291 let elem = out.elem();
292 let qualifier = out.const_qualifier();
293 let addr_space = D::address_space_for_value(self);
294 let out_fmt = out.fmt_left();
295
296 writeln!(
297 f,
298 "{out_fmt} = reinterpret_cast<{addr_space}{elem}{qualifier}*>({self});"
299 )
300 .unwrap();
301
302 out
303 }
304
305 pub fn tmp_ptr(item: Item<D>) -> Self {
309 let inc = COUNTER_TMP_VAR.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
310
311 Value::Tmp {
312 id: inc as Id,
313 item,
314 is_declared: false,
315 is_ptr: true,
316 is_const: false,
317 }
318 }
319
320 pub fn tmp_declared(item: Item<D>) -> Self {
326 let inc = COUNTER_TMP_VAR.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
327
328 Value::Tmp {
329 id: inc as Id,
330 item,
331 is_declared: true,
332 is_ptr: false,
333 is_const: false,
334 }
335 }
336
337 pub fn optimized_args<const N: usize>(args: [Self; N]) -> OptimizedArgs<N, D> {
338 let args_after = args.map(|a| a.optimized());
339
340 let is_optimized = args_after.iter().all(|val| val.is_optimized());
341
342 if is_optimized {
343 let vectorization_before = args
344 .iter()
345 .map(|val| val.item().vectorization())
346 .max()
347 .unwrap();
348 let vectorization_after = args_after
349 .iter()
350 .map(|val| val.item().vectorization())
351 .max()
352 .unwrap();
353
354 OptimizedArgs::new(args_after, Some(vectorization_before / vectorization_after))
355 } else {
356 OptimizedArgs::new(args, None)
357 }
358 }
359
360 pub fn optimized(&self) -> Self {
361 match self {
362 Value::Value { id, item } => Value::Value {
363 id: *id,
364 item: item.optimized(),
365 },
366 Value::Tmp {
367 id,
368 item,
369 is_declared,
370 is_ptr,
371 is_const,
372 } => Value::Tmp {
373 id: *id,
374 item: item.optimized(),
375 is_declared: *is_declared,
376 is_ptr: *is_ptr,
377 is_const: *is_const,
378 },
379 _ => *self,
380 }
381 }
382
383 pub fn index(&self, index: usize) -> IndexedValue<D> {
384 IndexedValue {
385 val: *self,
386 index,
387 optimized: self.is_optimized(),
388 }
389 }
390
391 pub fn const_qualifier(&self) -> &str {
392 if self.is_const() { " const" } else { "" }
393 }
394
395 pub fn id(&self) -> Option<Id> {
396 match self {
397 Value::Value { id, .. } => Some(*id),
398 Value::Tmp { id, .. } => Some(*id),
399 _ => None,
400 }
401 }
402
403 pub fn declare_local_ptr_backing(
417 &self,
418 f: &mut Formatter<'_>,
419 ) -> Result<bool, std::fmt::Error> {
420 if let Value::Value {
421 item: Item::Pointer(_, PointerClass::Local),
422 ..
423 } = self
424 {
425 writeln!(f, "{} {self}_store;", self.item().value_ty())?;
426 writeln!(f, "{} {self} = &{self}_store;", self.item())?;
427 Ok(true)
428 } else {
429 Ok(false)
430 }
431 }
432
433 pub fn fmt_ptr(&self) -> String {
436 match self.item() {
437 Item::Pointer(inner, _) if inner.is_array() => {
438 format!("{self}->data")
439 }
440 Item::Array(..) => format!("{self}.data"),
441 Item::DynamicArray(..) | Item::Pointer(..) => format!("{self}"),
442 _ => format!("&{self}"),
443 }
444 }
445
446 pub fn fmt_ref(&self) -> String {
448 match self.item() {
449 Item::DynamicArray(..) | Item::Pointer(..) => format!("*{self}"),
450 _ => format!("{self}"),
451 }
452 }
453
454 pub fn fmt_cast_to(&self, item: Item<D>) -> String {
456 if self.item() == item {
457 self.to_string()
458 } else {
459 format!("{item}({self})")
460 }
461 }
462
463 pub fn ensure_lvalue(&self, f: &mut Formatter<'_>) -> Result<Value<D>, core::fmt::Error> {
466 if matches!(self, Value::Constant(..)) {
467 let tmp = Value::tmp(self.item());
468 writeln!(f, "{} = {self};", tmp.fmt_left())?;
469 Ok(tmp)
470 } else if matches!(self.item(), Item::Pointer(..)) {
471 let tmp = Value::tmp(*self.item().value_ty());
472 writeln!(f, "{}& {tmp} = *{self};", tmp.item())?;
473 Ok(tmp)
474 } else {
475 Ok(*self)
476 }
477 }
478}
479
480impl<D: Dialect> FmtLeft for Value<D> {
481 fn fmt_left(&self) -> String {
482 match self {
483 Self::Value { item, .. } => match item {
484 Item::Pointer(..) => {
486 format!("{item} {self}")
487 }
488 Item::Barrier(..) => {
490 format!("{item}& {self}")
491 }
492 Item::BarrierToken(..) => {
496 format!("{item} {self}")
497 }
498 _ => {
499 format!("const {item} {self}")
500 }
501 },
502 Value::Tmp {
503 item,
504 is_declared,
505 is_ptr,
506 is_const,
507 ..
508 } => {
509 if *is_declared {
510 return format!("{self}");
511 }
512 if *is_const && !*is_ptr {
513 format!("const {item} {self}")
514 } else {
515 format!("{item} {self}")
516 }
517 }
518 var => format!("{var}"),
519 }
520 }
521}
522
523#[derive(Debug, Clone)]
524pub struct IndexedValue<D: Dialect> {
525 val: Value<D>,
526 optimized: bool,
527 index: usize,
528}
529
530impl<D: Dialect> Component<D> for IndexedValue<D> {
531 fn item(&self) -> Item<D> {
532 self.val.item()
533 }
534
535 fn index(&self, index: usize) -> IndexedValue<D> {
536 self.val.index(index)
537 }
538
539 fn is_const(&self) -> bool {
540 self.val.is_const()
541 }
542}
543
544impl<D: Dialect> Display for IndexedValue<D> {
545 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
546 let var = &self.val;
547
548 if let Value::Constant(value, item) = var {
549 let value = format_const(value, item);
550 return write!(f, "{}({value})", item.elem());
551 }
552
553 if var.item().unwrap_ptr().is_array_like() {
554 return write!(f, "{}", fmt_index(var, &self.index, &var.item()));
555 }
556
557 let item = var.item();
558 let addr_space = D::address_space_for_value(&self.val);
559 let ty = match var {
560 _ if item.is_ptr() => {
561 format!("{item}")
562 }
563 Value::Value { item, .. } => format!("{addr_space}{item} const&"),
564 _ => format!("{addr_space}{item}&"),
565 };
566 let accessor = match var.item().is_ptr() {
567 true => "->",
568 false => ".",
569 };
570
571 if self.val.item().vectorization() > 1 {
572 if self.optimized {
573 write!(
574 f,
575 "(reinterpret_cast<{ty}>({var})){accessor}i_{}",
576 self.index
577 )
578 } else {
579 write!(f, "{var}{accessor}i_{}", self.index)
580 }
581 } else if self.optimized {
582 write!(f, "reinterpret_cast<{ty}>({var})")
583 } else {
584 write!(f, "{var}")
585 }
586 }
587}
588
589impl<D: Dialect> FmtLeft for IndexedValue<D> {
590 fn fmt_left(&self) -> String {
591 let var = &self.val;
592 let ref_ = matches!(var, Value::Value { .. })
593 .then_some("const&")
594 .unwrap_or("&");
595
596 let name = if self.val.item().vectorization() > 1 {
597 if self.optimized {
598 let item = self.val.item();
599 let addr_space = D::address_space_for_value(&self.val);
600 format!(
601 "(reinterpret_cast<{addr_space}{item} {ref_}>({var})).i_{}",
602 self.index
603 )
604 } else {
605 format!("{var}.i_{}", self.index)
606 }
607 } else {
608 format!("{var}")
609 };
610 match var {
611 Value::Value { item, .. } => format!("const {item} {name}"),
612 Value::Tmp { item, is_ptr, .. } => {
613 if *is_ptr {
614 format!("{item} *{name}")
615 } else {
616 format!("{item} {name}")
617 }
618 }
619 _ => name,
620 }
621 }
622}
623
624impl FmtLeft for &String {
625 fn fmt_left(&self) -> String {
626 self.to_string()
627 }
628}