1use cubecl_core::ir::{self as gpu, BarrierLevel, ConstantScalarValue, Id};
2use std::fmt::{Display, Formatter};
3
4use super::{COUNTER_TMP_VAR, Dialect, Elem, Fragment, FragmentIdent, Item};
5
6pub trait Component<D: Dialect>: Display + FmtLeft {
7 fn item(&self) -> Item<D>;
8 fn is_const(&self) -> bool;
9 fn index(&self, index: usize) -> IndexedVariable<D>;
10 fn elem(&self) -> Elem<D> {
11 *self.item().elem()
12 }
13}
14
15pub trait FmtLeft: Display {
16 fn fmt_left(&self) -> String;
17}
18
19#[derive(new)]
20pub struct OptimizedArgs<const N: usize, D: Dialect> {
21 pub args: [Variable<D>; N],
22 pub optimization_factor: Option<usize>,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq)]
26pub enum Variable<D: Dialect> {
27 AbsolutePos,
28 AbsolutePosBaseName, AbsolutePosX,
30 AbsolutePosY,
31 AbsolutePosZ,
32 UnitPos,
33 UnitPosBaseName, UnitPosX,
35 UnitPosY,
36 UnitPosZ,
37 CubePos,
38 CubePosBaseName, CubePosX,
40 CubePosY,
41 CubePosZ,
42 CubeDim,
43 CubeDimBaseName, CubeDimX,
45 CubeDimY,
46 CubeDimZ,
47 CubeCount,
48 CubeCountBaseName, CubeCountX,
50 CubeCountY,
51 CubeCountZ,
52 PlaneDim,
53 PlaneDimChecked,
54 PlanePos,
55 UnitPosPlane,
56 ClusterRank,
57 ClusterIndexX,
58 ClusterIndexY,
59 ClusterIndexZ,
60 GlobalInputArray(Id, Item<D>),
61 GlobalOutputArray(Id, Item<D>),
62 GlobalScalar {
63 id: Id,
64 elem: Elem<D>,
65 in_struct: bool,
66 },
67 ConstantArray(Id, Item<D>, u32),
68 ConstantScalar(ConstantScalarValue, Elem<D>),
69 TensorMap(Id),
70 LocalMut {
71 id: Id,
72 item: Item<D>,
73 },
74 LocalConst {
75 id: Id,
76 item: Item<D>,
77 },
78 Named {
79 name: &'static str,
80 item: Item<D>,
81 },
82 Slice {
83 id: Id,
84 item: Item<D>,
85 },
86 SharedMemory(Id, Item<D>, u32),
87 LocalArray(Id, Item<D>, u32),
88 WmmaFragment {
89 id: Id,
90 frag: Fragment<D>,
91 },
92 Pipeline {
93 id: Id,
94 },
95 Barrier {
96 id: Id,
97 level: BarrierLevel,
98 },
99 Tmp {
100 id: Id,
101 item: Item<D>,
102 is_declared: bool,
103 is_ptr: bool,
104 is_const: bool,
105 },
106}
107
108impl<D: Dialect> Component<D> for Variable<D> {
109 fn index(&self, index: usize) -> IndexedVariable<D> {
110 self.index(index)
111 }
112
113 fn item(&self) -> Item<D> {
114 match self {
115 Variable::AbsolutePos => Item::scalar(Elem::U32, true),
116 Variable::AbsolutePosBaseName => Item {
117 elem: Elem::U32,
118 vectorization: 3,
119 native: true,
120 },
121 Variable::AbsolutePosX => Item::scalar(Elem::U32, true),
122 Variable::AbsolutePosY => Item::scalar(Elem::U32, true),
123 Variable::AbsolutePosZ => Item::scalar(Elem::U32, true),
124 Variable::CubeCount => Item::scalar(Elem::U32, true),
125 Variable::CubeCountBaseName => Item {
126 elem: Elem::U32,
127 vectorization: 3,
128 native: true,
129 },
130 Variable::CubeCountX => Item::scalar(Elem::U32, true),
131 Variable::CubeCountY => Item::scalar(Elem::U32, true),
132 Variable::CubeCountZ => Item::scalar(Elem::U32, true),
133 Variable::CubeDimBaseName => Item {
134 elem: Elem::U32,
135 vectorization: 3,
136 native: true,
137 },
138 Variable::CubeDim => Item::scalar(Elem::U32, true),
139 Variable::CubeDimX => Item::scalar(Elem::U32, true),
140 Variable::CubeDimY => Item::scalar(Elem::U32, true),
141 Variable::CubeDimZ => Item::scalar(Elem::U32, true),
142 Variable::CubePos => Item::scalar(Elem::U32, true),
143 Variable::CubePosBaseName => Item {
144 elem: Elem::U32,
145 vectorization: 3,
146 native: true,
147 },
148 Variable::CubePosX => Item::scalar(Elem::U32, true),
149 Variable::CubePosY => Item::scalar(Elem::U32, true),
150 Variable::CubePosZ => Item::scalar(Elem::U32, true),
151 Variable::UnitPos => Item::scalar(Elem::U32, true),
152 Variable::UnitPosBaseName => Item {
153 elem: Elem::U32,
154 vectorization: 3,
155 native: true,
156 },
157 Variable::UnitPosX => Item::scalar(Elem::U32, true),
158 Variable::UnitPosY => Item::scalar(Elem::U32, true),
159 Variable::UnitPosZ => Item::scalar(Elem::U32, true),
160 Variable::PlaneDim => Item::scalar(Elem::U32, true),
161 Variable::PlaneDimChecked => Item::scalar(Elem::U32, true),
162 Variable::PlanePos => Item::scalar(Elem::U32, true),
163 Variable::UnitPosPlane => Item::scalar(Elem::U32, true),
164 Variable::ClusterRank => Item::scalar(Elem::U32, true),
165 Variable::ClusterIndexX => Item::scalar(Elem::U32, true),
166 Variable::ClusterIndexY => Item::scalar(Elem::U32, true),
167 Variable::ClusterIndexZ => Item::scalar(Elem::U32, true),
168 Variable::GlobalInputArray(_, e) => *e,
169 Variable::GlobalOutputArray(_, e) => *e,
170 Variable::LocalArray(_, e, _) => *e,
171 Variable::SharedMemory(_, e, _) => *e,
172 Variable::ConstantArray(_, e, _) => *e,
173 Variable::LocalMut { item, .. } => *item,
174 Variable::LocalConst { item, .. } => *item,
175 Variable::Named { item, .. } => *item,
176 Variable::Slice { item, .. } => *item,
177 Variable::ConstantScalar(_, e) => Item::scalar(*e, false),
178 Variable::GlobalScalar { elem, .. } => Item::scalar(*elem, false),
179 Variable::WmmaFragment { frag, .. } => Item::scalar(frag.elem, false),
180 Variable::Tmp { item, .. } => *item,
181 Variable::Pipeline { .. } | Variable::Barrier { .. } => Item::new(Elem::Bool, 1, false),
182 Variable::TensorMap(_) => unreachable!(),
183 }
184 }
185
186 fn is_const(&self) -> bool {
187 if let Variable::Tmp { is_const, .. } = self {
188 return *is_const;
189 }
190
191 matches!(
192 self,
193 Variable::LocalConst { .. } | Variable::GlobalInputArray { .. }
194 )
195 }
196}
197
198impl<D: Dialect> Display for Variable<D> {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 match self {
201 Variable::GlobalInputArray(id, _) => f.write_fmt(format_args!("buffer_{id}")),
202 Variable::GlobalOutputArray(id, _) => write!(f, "buffer_{id}"),
203 Variable::TensorMap(id) => write!(f, "tensor_map_{id}"),
204 Variable::LocalMut { id, .. } => f.write_fmt(format_args!("l_mut_{id}")),
205 Variable::LocalConst { id, .. } => f.write_fmt(format_args!("l_{id}")),
206 Variable::Named { name, .. } => f.write_fmt(format_args!("{name}")),
207 Variable::Slice { id, .. } => {
208 write!(f, "slice_{id}")
209 }
210 Variable::GlobalScalar {
211 id,
212 elem,
213 in_struct,
214 } => match *in_struct {
215 true => write!(f, "scalars_{elem}.x[{id}]"),
216 false => write!(f, "scalars_{elem}[{id}]"),
217 },
218 Variable::ConstantScalar(number, elem) => match number {
219 ConstantScalarValue::Int(val, kind) => match kind {
220 gpu::IntKind::I8 => write!(f, "{elem}({})", *val as i8),
221 gpu::IntKind::I16 => write!(f, "{elem}({})", *val as i16),
222 gpu::IntKind::I32 => write!(f, "{elem}({})", *val as i32),
223 gpu::IntKind::I64 => write!(f, "{elem}({})", *val),
224 },
225 ConstantScalarValue::Float(val, kind) => match kind {
226 gpu::FloatKind::E2M1
227 | gpu::FloatKind::E2M3
228 | gpu::FloatKind::E3M2
229 | gpu::FloatKind::E4M3
230 | gpu::FloatKind::E5M2
231 | gpu::FloatKind::UE8M0 => todo!("Minifloat constants not supported yet"),
232 gpu::FloatKind::F16 => {
233 write!(f, "{elem}({:?})", half::f16::from_f64(*val))
234 }
235 gpu::FloatKind::BF16 => {
236 write!(f, "{elem}({:?})", half::bf16::from_f64(*val))
237 }
238 gpu::FloatKind::Flex32 => write!(f, "{elem}({:?})", *val as f32),
239 gpu::FloatKind::TF32 => write!(f, "{elem}({:?})", *val as f32),
240 gpu::FloatKind::F32 => write!(f, "{elem}({:?})", *val as f32),
241 gpu::FloatKind::F64 => write!(f, "{elem}({:?})", *val),
242 },
243 ConstantScalarValue::UInt(val, kind) => match kind {
244 gpu::UIntKind::U8 => write!(f, "{elem}({})", *val as u8),
245 gpu::UIntKind::U16 => write!(f, "{elem}({})", *val as u16),
246 gpu::UIntKind::U32 => write!(f, "{elem}({})", *val as u32),
247 gpu::UIntKind::U64 => write!(f, "{elem}({})", *val),
248 },
249 ConstantScalarValue::Bool(val) => write!(f, "{val}"),
250 },
251 Variable::SharedMemory(number, _, _) => {
252 write!(f, "shared_memory_{number}")
253 }
254
255 Variable::AbsolutePos => D::compile_absolute_pos(f),
256 Variable::AbsolutePosBaseName => D::compile_absolute_pos_base_name(f),
257 Variable::AbsolutePosX => D::compile_absolute_pos_x(f),
258 Variable::AbsolutePosY => D::compile_absolute_pos_y(f),
259 Variable::AbsolutePosZ => D::compile_absolute_pos_z(f),
260 Variable::CubeCount => D::compile_cube_count(f),
261 Variable::CubeCountBaseName => D::compile_cube_count_base_name(f),
262 Variable::CubeCountX => D::compile_cube_count_x(f),
263 Variable::CubeCountY => D::compile_cube_count_y(f),
264 Variable::CubeCountZ => D::compile_cube_count_z(f),
265 Variable::CubeDim => D::compile_cube_dim(f),
266 Variable::CubeDimBaseName => D::compile_cube_dim_base_name(f),
267 Variable::CubeDimX => D::compile_cube_dim_x(f),
268 Variable::CubeDimY => D::compile_cube_dim_y(f),
269 Variable::CubeDimZ => D::compile_cube_dim_z(f),
270 Variable::CubePos => D::compile_cube_pos(f),
271 Variable::CubePosBaseName => D::compile_cube_pos_base_name(f),
272 Variable::CubePosX => D::compile_cube_pos_x(f),
273 Variable::CubePosY => D::compile_cube_pos_y(f),
274 Variable::CubePosZ => D::compile_cube_pos_z(f),
275 Variable::UnitPos => D::compile_unit_pos(f),
276 Variable::UnitPosBaseName => D::compile_unit_pos_base_name(f),
277 Variable::UnitPosX => D::compile_unit_pos_x(f),
278 Variable::UnitPosY => D::compile_unit_pos_y(f),
279 Variable::UnitPosZ => D::compile_unit_pos_z(f),
280 Variable::PlaneDim => D::compile_plane_dim(f),
281 Variable::PlaneDimChecked => D::compile_plane_dim_checked(f),
282 Variable::PlanePos => D::compile_plane_pos(f),
283 Variable::UnitPosPlane => D::compile_unit_pos_plane(f),
284 Variable::ClusterRank => D::compile_cluster_pos(f),
285 Variable::ClusterIndexX => D::compile_cluster_pos_x(f),
286 Variable::ClusterIndexY => D::compile_cluster_pos_y(f),
287 Variable::ClusterIndexZ => D::compile_cluster_pos_z(f),
288
289 Variable::ConstantArray(number, _, _) => f.write_fmt(format_args!("arrays_{number}")),
290 Variable::LocalArray(id, _, _) => {
291 write!(f, "l_arr_{id}")
292 }
293 Variable::WmmaFragment { id: index, frag } => {
294 let name = match frag.ident {
295 FragmentIdent::A => "a",
296 FragmentIdent::B => "b",
297 FragmentIdent::Accumulator => "acc",
298 FragmentIdent::_Dialect(_) => "",
299 };
300 write!(f, "frag_{name}_{index}")
301 }
302 Variable::Tmp { id, .. } => write!(f, "_tmp_{id}"),
303 Variable::Pipeline { id, .. } => write!(f, "pipeline_{id}"),
304 Variable::Barrier { id, .. } => write!(f, "barrier_{id}"),
305 }
306 }
307}
308
309impl<D: Dialect> Variable<D> {
310 pub fn is_optimized(&self) -> bool {
311 self.item().is_optimized()
312 }
313
314 pub fn tmp(item: Item<D>) -> Self {
318 let inc = COUNTER_TMP_VAR.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
319
320 Variable::Tmp {
321 id: inc as Id,
322 item,
323 is_declared: false,
324 is_ptr: false,
325 is_const: false,
326 }
327 }
328
329 pub fn to_const(&mut self) {
330 if let Variable::Tmp { is_const, .. } = self {
331 *is_const = true;
332 }
333 }
334
335 pub fn reinterpret_ptr(&self, f: &mut Formatter<'_>, item: Item<D>) -> Self {
337 let mut out = Self::tmp_ptr(item);
338
339 if self.is_const() {
340 out.to_const();
341 }
342
343 let elem = out.elem();
344 let qualifier = out.const_qualifier();
345 let addr_space = D::address_space_for_variable(self);
346 let out_fmt = out.fmt_left();
347
348 writeln!(
349 f,
350 "{out_fmt} = reinterpret_cast<{addr_space}{elem}{qualifier}*>({self});"
351 )
352 .unwrap();
353
354 out
355 }
356
357 pub fn tmp_ptr(item: Item<D>) -> Self {
361 let inc = COUNTER_TMP_VAR.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
362
363 Variable::Tmp {
364 id: inc as Id,
365 item,
366 is_declared: false,
367 is_ptr: true,
368 is_const: false,
369 }
370 }
371
372 pub fn tmp_declared(item: Item<D>) -> Self {
378 let inc = COUNTER_TMP_VAR.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
379
380 Variable::Tmp {
381 id: inc as Id,
382 item,
383 is_declared: true,
384 is_ptr: false,
385 is_const: false,
386 }
387 }
388
389 pub fn optimized_args<const N: usize>(args: [Self; N]) -> OptimizedArgs<N, D> {
390 let args_after = args.map(|a| a.optimized());
391
392 let item_reference_after = args_after[0].item();
393
394 let is_optimized = args_after
395 .iter()
396 .all(|var| var.elem() == item_reference_after.elem && var.is_optimized());
397
398 if is_optimized {
399 let vectorization_before = args
400 .iter()
401 .map(|var| var.item().vectorization)
402 .max()
403 .unwrap();
404 let vectorization_after = args_after
405 .iter()
406 .map(|var| var.item().vectorization)
407 .max()
408 .unwrap();
409
410 OptimizedArgs::new(args_after, Some(vectorization_before / vectorization_after))
411 } else {
412 OptimizedArgs::new(args, None)
413 }
414 }
415
416 pub fn optimized(&self) -> Self {
417 match self {
418 Variable::GlobalInputArray(id, item) => {
419 Variable::GlobalInputArray(*id, item.optimized())
420 }
421 Variable::GlobalOutputArray(id, item) => {
422 Variable::GlobalOutputArray(*id, item.optimized())
423 }
424 Variable::LocalMut { id, item } => Variable::LocalMut {
425 id: *id,
426 item: item.optimized(),
427 },
428 Variable::LocalConst { id, item } => Variable::LocalConst {
429 id: *id,
430 item: item.optimized(),
431 },
432 Variable::Slice { id, item } => Variable::Slice {
433 id: *id,
434 item: item.optimized(),
435 },
436 Variable::Tmp {
437 id,
438 item,
439 is_declared,
440 is_ptr,
441 is_const,
442 } => Variable::Tmp {
443 id: *id,
444 item: item.optimized(),
445 is_declared: *is_declared,
446 is_ptr: *is_ptr,
447 is_const: *is_const,
448 },
449 Variable::SharedMemory(id, item, size) => {
450 let before = item.vectorization;
451 let item = item.optimized();
452 let after = item.vectorization;
453 let scaling = (before / after) as u32;
454
455 Variable::SharedMemory(*id, item, size / scaling)
456 }
457 Variable::LocalArray(id, item, size) => {
458 let before = item.vectorization;
459 let item = item.optimized();
460 let after = item.vectorization;
461 let scaling = (before / after) as u32;
462
463 Variable::LocalArray(*id, item.optimized(), size / scaling)
464 }
465 _ => *self,
466 }
467 }
468
469 pub fn is_always_scalar(&self) -> bool {
470 match self {
471 Variable::AbsolutePos => true,
472 Variable::AbsolutePosBaseName => false,
473 Variable::AbsolutePosX => true,
474 Variable::AbsolutePosY => true,
475 Variable::AbsolutePosZ => true,
476 Variable::CubeCount => true,
477 Variable::CubeCountBaseName => false,
478 Variable::CubeCountX => true,
479 Variable::CubeCountY => true,
480 Variable::CubeCountZ => true,
481 Variable::CubeDim => true,
482 Variable::CubeDimBaseName => false,
483 Variable::CubeDimX => true,
484 Variable::CubeDimY => true,
485 Variable::CubeDimZ => true,
486 Variable::CubePos => true,
487 Variable::CubePosBaseName => true,
488 Variable::CubePosX => true,
489 Variable::CubePosY => true,
490 Variable::CubePosZ => true,
491 Variable::UnitPos => true,
492 Variable::UnitPosBaseName => true,
493 Variable::UnitPosPlane => true,
494 Variable::UnitPosX => true,
495 Variable::UnitPosY => true,
496 Variable::UnitPosZ => true,
497 Variable::PlaneDim => true,
498 Variable::PlaneDimChecked => true,
499 Variable::PlanePos => true,
500 Variable::ClusterRank => true,
501 Variable::ClusterIndexX => true,
502 Variable::ClusterIndexY => true,
503 Variable::ClusterIndexZ => true,
504
505 Variable::Barrier { .. } => false,
506 Variable::ConstantArray(_, _, _) => false,
507 Variable::ConstantScalar(_, _) => true,
508 Variable::GlobalInputArray(_, _) => false,
509 Variable::GlobalOutputArray(_, _) => false,
510 Variable::GlobalScalar { .. } => true,
511 Variable::LocalArray(_, _, _) => false,
512 Variable::LocalConst { .. } => false,
513 Variable::LocalMut { .. } => false,
514 Variable::Named { .. } => false,
515 Variable::Pipeline { .. } => false,
516 Variable::SharedMemory(_, _, _) => false,
517 Variable::Slice { .. } => false,
518 Variable::Tmp { .. } => false,
519 Variable::WmmaFragment { .. } => false,
520 Variable::TensorMap { .. } => false,
521 }
522 }
523
524 pub fn index(&self, index: usize) -> IndexedVariable<D> {
525 IndexedVariable {
526 var: *self,
527 index,
528 optimized: self.is_optimized(),
529 }
530 }
531
532 pub fn const_qualifier(&self) -> &str {
533 if self.is_const() { " const" } else { "" }
534 }
535
536 pub fn id(&self) -> Option<Id> {
537 match self {
538 Variable::GlobalInputArray(id, ..) => Some(*id),
539 Variable::GlobalOutputArray(id, ..) => Some(*id),
540 Variable::GlobalScalar { id, .. } => Some(*id),
541 Variable::ConstantArray(id, ..) => Some(*id),
542 Variable::LocalMut { id, .. } => Some(*id),
543 Variable::LocalConst { id, .. } => Some(*id),
544 Variable::Slice { id, .. } => Some(*id),
545 Variable::SharedMemory(id, ..) => Some(*id),
546 Variable::LocalArray(id, ..) => Some(*id),
547 Variable::WmmaFragment { id, .. } => Some(*id),
548 Variable::Pipeline { id, .. } => Some(*id),
549 Variable::Barrier { id, .. } => Some(*id),
550 Variable::Tmp { id, .. } => Some(*id),
551 _ => None,
552 }
553 }
554
555 pub fn fmt_ptr(&self) -> String {
558 match self {
559 Variable::Slice { .. }
560 | Variable::SharedMemory(_, _, _)
561 | Variable::GlobalInputArray(_, _)
562 | Variable::GlobalOutputArray(_, _) => format!("{self}"),
563 _ => format!("&{self}"),
564 }
565 }
566}
567
568impl<D: Dialect> FmtLeft for Variable<D> {
569 fn fmt_left(&self) -> String {
570 match self {
571 Self::LocalConst { item, .. } => match item.elem {
572 Elem::Atomic(_) => {
573 format!("{item}* {self}")
574 }
575 _ => {
576 format!("const {item} {self}")
577 }
578 },
579 Variable::Tmp {
580 item,
581 is_declared,
582 is_ptr,
583 is_const,
584 ..
585 } => {
586 if *is_declared {
587 return format!("{self}");
588 }
589 if *is_ptr {
590 if *is_const {
591 return format!("const {item} *{self}");
592 }
593 return format!("{item} *{self}");
594 }
595
596 format!("{item} {self}")
597 }
598 var => format!("{var}"),
599 }
600 }
601}
602
603#[derive(Debug, Clone)]
604pub struct IndexedVariable<D: Dialect> {
605 var: Variable<D>,
606 optimized: bool,
607 index: usize,
608}
609
610impl<D: Dialect> Component<D> for IndexedVariable<D> {
611 fn item(&self) -> Item<D> {
612 self.var.item()
613 }
614
615 fn index(&self, index: usize) -> IndexedVariable<D> {
616 self.var.index(index)
617 }
618
619 fn is_const(&self) -> bool {
620 self.var.is_const()
621 }
622}
623
624impl<D: Dialect> Display for IndexedVariable<D> {
625 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
626 let var = &self.var;
627 let ref_ = matches!(var, Variable::LocalConst { .. })
628 .then_some("const&")
629 .unwrap_or("&");
630
631 if self.var.item().vectorization > 1 {
632 if self.optimized {
633 let item = self.var.item();
634 let addr_space = D::address_space_for_variable(&self.var);
635 write!(
636 f,
637 "(reinterpret_cast<{addr_space}{item} {ref_}>({var})).i_{}",
638 self.index
639 )
640 } else {
641 write!(f, "{var}.i_{}", self.index)
642 }
643 } else if self.optimized {
644 let item = self.var.item();
645 let addr_space = D::address_space_for_variable(&self.var);
646 write!(f, "reinterpret_cast<{addr_space}{item} {ref_}>({var})")
647 } else {
648 write!(f, "{var}")
649 }
650 }
651}
652
653impl<D: Dialect> FmtLeft for IndexedVariable<D> {
654 fn fmt_left(&self) -> String {
655 let var = &self.var;
656 let ref_ = matches!(var, Variable::LocalConst { .. })
657 .then_some("const&")
658 .unwrap_or("&");
659
660 let name = if self.var.item().vectorization > 1 {
661 if self.optimized {
662 let item = self.var.item();
663 let addr_space = D::address_space_for_variable(&self.var);
664 format!(
665 "(reinterpret_cast<{addr_space}{item} {ref_}>({var})).i_{}",
666 self.index
667 )
668 } else {
669 format!("{var}.i_{}", self.index)
670 }
671 } else {
672 format!("{var}")
673 };
674 match var {
675 Variable::LocalConst { item, .. } => format!("const {item} {name}"),
676 Variable::Tmp { item, is_ptr, .. } => {
677 if *is_ptr {
678 format!("{item} *{name}")
679 } else {
680 format!("{item} {name}")
681 }
682 }
683 _ => name,
684 }
685 }
686}
687
688impl FmtLeft for &String {
689 fn fmt_left(&self) -> String {
690 self.to_string()
691 }
692}