1use core::any::TypeId;
2use std::fmt::Display;
3use std::{collections::HashSet, marker::PhantomData};
4
5use cubecl_core::{
6 ir::Processor, post_processing::saturating::SaturatingArithmeticProcessor, prelude::Visibility,
7};
8
9use crate::shared::{DialectWarpReduceCompiler, PointerClass};
10use crate::{
11 Dialect,
12 shared::{
13 self, DialectBindings, DialectCubeBuiltins, DialectIncludes, DialectTypes,
14 DialectWmmaCompiler, Flags, Item, KernelArg, ManualMma,
15 },
16};
17use crate::{
18 hip::processors::HipMmaProcessor,
19 shared::{
20 Component, DialectInstructions, DialectProcessors, Elem, Instruction, Value, unary,
21 value_to_frag,
22 },
23};
24
25use super::Extension;
26use super::arch::AMDArchitecture;
27use super::extension::{WmmaExtension, format_f162bf16, format_max, format_min};
28use super::mma::{WmmaCast, WmmaExecute, WmmaFill, WmmaIntrinsicCompiler, WmmaLoad, WmmaStore};
29
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
31pub struct HipDialect<M> {
32 _wmma_compiler: PhantomData<M>,
33}
34
35impl<M: DialectWmmaCompiler<Self>> Dialect for HipDialect<M> {
38 type Architecture = AMDArchitecture;
39}
40
41impl<M: DialectWmmaCompiler<Self>> DialectWarpReduceCompiler<Self> for HipDialect<M> {}
42
43impl<M: DialectWmmaCompiler<Self>> DialectIncludes<Self> for HipDialect<M> {
46 type Extension = Extension<Self>;
47
48 fn compile_includes(f: &mut std::fmt::Formatter<'_>, flags: &Flags<Self>) -> std::fmt::Result {
49 f.write_str("#include <hip/hip_runtime.h>\n")?;
50 if flags.elem_bf16 {
51 f.write_str("#include <hip/hip_bf16.h>\n")?;
52 }
53 if flags.elem_f16 {
54 f.write_str("#include <hip/hip_fp16.h>\n")?;
55 }
56 if flags.inst_wmma {
57 Self::compile_wmma_includes(f, flags)?;
58 }
59 Ok(())
60 }
61
62 fn compile_extensions(
63 f: &mut std::fmt::Formatter<'_>,
64 extensions: &[Self::Extension],
65 ) -> std::fmt::Result {
66 for extension in extensions {
67 match extension {
68 Extension::F162BF16 => format_f162bf16(f)?,
69 Extension::Max(val) => format_max::<Self>(f, val)?,
70 Extension::Min(val) => format_min::<Self>(f, val)?,
71 Extension::NoExtension => {}
72 Extension::Wmma(inst) => inst.format_wmma(f)?,
73 }
74 }
75 Ok(())
76 }
77
78 fn register_instruction_extension(
79 extensions: &mut Vec<Self::Extension>,
80 instruction: &Instruction<Self>,
81 ) {
82 let mut register_extension = |extension: Self::Extension| {
83 if !extensions.contains(&extension) {
84 extensions.push(extension);
85 }
86 };
87 #[allow(clippy::single_match)]
88 match instruction {
89 shared::Instruction::<Self>::Max(op) => {
90 register_extension(Extension::Max(*op.lhs.item().elem()));
91 }
92 shared::Instruction::<Self>::Min(op) => {
93 register_extension(Extension::Min(*op.lhs.item().elem()));
94 }
95 _ => {}
96 }
97 }
98
99 fn register_warp_instruction_extension(
100 extensions: &mut Vec<Self::Extension>,
101 instruction: &shared::WarpInstruction<Self>,
102 ) {
103 let mut register_extension = |extension: Self::Extension| {
104 if !extensions.contains(&extension) {
105 extensions.push(extension);
106 }
107 };
108
109 #[allow(clippy::single_match)]
110 match instruction {
111 shared::WarpInstruction::<Self>::ReduceMax { input, .. } => {
112 let input_item = input.item();
113 let input_elem = input_item.elem();
114 if *input_elem == Elem::<Self>::BF16 {
115 register_extension(Extension::F162BF16);
116 }
117 register_extension(Extension::Max(*input_elem));
118 }
119 shared::WarpInstruction::<Self>::ReduceMin { input, .. } => {
120 let input_item = input.item();
121 let input_elem = input_item.elem();
122 if *input_elem == Elem::<Self>::BF16 {
123 register_extension(Extension::F162BF16);
124 }
125 register_extension(Extension::Min(*input_elem));
126 }
127 shared::WarpInstruction::<Self>::ReduceProd { input, .. } => {
128 let input_item = input.item();
129 let input_elem = input_item.elem();
130 if *input_elem == Elem::<Self>::BF16 {
131 register_extension(Extension::F162BF16);
132 }
133 }
134 shared::WarpInstruction::<Self>::ReduceSum { input, .. } => {
135 let input_item = input.item();
136 let input_elem = input_item.elem();
137 if *input_elem == Elem::<Self>::BF16 {
138 register_extension(Extension::F162BF16);
139 }
140 }
141 _ => {}
142 }
143 }
144
145 fn register_wmma_instruction_extension(
146 extensions: &mut Vec<Self::Extension>,
147 instruction: &shared::WmmaInstruction<Self>,
148 ) {
149 if TypeId::of::<M>() == TypeId::of::<WmmaIntrinsicCompiler>() {
150 let extension = match instruction {
151 shared::WmmaInstruction::Fill { frag, .. } => {
152 Extension::Wmma(WmmaExtension::Fill(WmmaFill::new(value_to_frag(frag))))
153 }
154 shared::WmmaInstruction::Load { frag, layout, .. } => Extension::Wmma(
155 WmmaExtension::Load(WmmaLoad::new(value_to_frag(frag), *layout)),
156 ),
157 shared::WmmaInstruction::LdMatrix { .. }
158 | shared::WmmaInstruction::StMatrix { .. } => {
159 panic!("Invalid extension: StMatrix & LdMatrix not supported for HIP");
160 }
161 shared::WmmaInstruction::Execute {
162 frag_a,
163 frag_b,
164 frag_c,
165 frag_d,
166 warp_size: _,
167 } => Extension::Wmma(WmmaExtension::Execute(WmmaExecute::new(
168 value_to_frag(frag_a),
169 value_to_frag(frag_b),
170 value_to_frag(frag_c),
171 value_to_frag(frag_d),
172 ))),
173 shared::WmmaInstruction::ExecuteManual {
174 shape,
175 frag_a,
176 frag_c,
177 ..
178 } => Extension::Wmma(WmmaExtension::Execute(WmmaExecute::from_manual(
179 *shape,
180 frag_a.elem(),
181 frag_c.elem(),
182 ))),
183 shared::WmmaInstruction::ExecuteScaled { .. } => {
184 panic!("Invalid extension: ExecuteScaled not supported for HIP");
185 }
186 shared::WmmaInstruction::Store { frag, layout, .. } => Extension::Wmma(
187 WmmaExtension::Store(WmmaStore::new(value_to_frag(frag), *layout)),
188 ),
189 shared::WmmaInstruction::Cast { input, output } => Extension::Wmma(
190 WmmaExtension::Cast(WmmaCast::new(value_to_frag(input), value_to_frag(output))),
191 ),
192 };
193
194 if !extensions.contains(&extension) {
195 extensions.push(extension);
196 }
197 } else if let shared::WmmaInstruction::ExecuteManual {
198 shape,
199 frag_a,
200 frag_c,
201 ..
202 } = instruction
203 {
204 let extension = Extension::Wmma(WmmaExtension::Execute(WmmaExecute::from_manual(
205 *shape,
206 frag_a.elem(),
207 frag_c.elem(),
208 )));
209
210 if !extensions.contains(&extension) {
211 extensions.push(extension);
212 }
213 }
214 }
215}
216
217impl<M: DialectWmmaCompiler<Self>> DialectTypes<Self> for HipDialect<M> {
220 fn item_can_be_optimized() -> bool {
221 false
223 }
224
225 fn compile_type_definitions(
226 f: &mut std::fmt::Formatter<'_>,
227 items: &HashSet<Item<Self>>,
228 scalars: &[(Elem<Self>, usize)],
229 info: &cubecl_core::Info,
230 flags: &Flags<Self>,
231 ) -> std::fmt::Result {
232 let mut items_deduplicated = HashSet::new();
233
234 for item in items {
235 let mut item = *item.value_ty();
236 match item {
237 Item::NativeVector(..) => {
238 continue;
239 }
240 Item::Atomic(inner) => {
241 item = *inner;
242 }
243 _ => {}
244 }
245 items_deduplicated.insert(item);
246 }
247
248 shared::type_definitions::<Self>(f)?;
249 shared::type_vectorized_definitions::<Self>(f, &items_deduplicated)?;
250
251 shared::type_info_definition_sized(f, info, scalars, flags.address_type)?;
252
253 if flags.inst_wmma {
254 Self::compile_wmma_type_definitions(f, flags)?;
255 }
256
257 Ok(())
258 }
259
260 fn compile_elem(
261 f: &mut std::fmt::Formatter<'_>,
262 elem: &shared::Elem<Self>,
263 words: bool,
264 ) -> std::fmt::Result {
265 if words {
266 match elem {
267 shared::Elem::F32 => f.write_str("float"),
268 shared::Elem::F64 => f.write_str("double"),
269 shared::Elem::TF32 => f.write_str("float"),
270 shared::Elem::I8 => f.write_str("char"),
271 shared::Elem::I16 => f.write_str("short"),
272 shared::Elem::I32 => f.write_str("int"),
273 shared::Elem::I64 => f.write_str("long"),
274 shared::Elem::U8 => f.write_str("uchar"),
275 shared::Elem::U16 => f.write_str("ushort"),
276 shared::Elem::U32 => f.write_str("uint"),
277 shared::Elem::U64 => f.write_str("ulong"),
278 _ => Self::compile_elem(f, elem, false),
279 }
280 } else {
281 match elem {
282 shared::Elem::FP4(_)
283 | shared::Elem::FP4x2(_)
284 | shared::Elem::FP6(_)
285 | shared::Elem::FP6x2(_)
286 | shared::Elem::FP8(_)
287 | shared::Elem::FP8x2(_) => {
288 f.write_str("#error FP4/FP6/FP8 not supported in HIP\n")
289 }
290 shared::Elem::F16 => f.write_str("__half"),
291 shared::Elem::F16x2 => f.write_str("__half2"),
292 shared::Elem::F32 => f.write_str("float"),
293 shared::Elem::F64 => f.write_str("double"),
294 shared::Elem::BF16 => f.write_str("__hip_bfloat16"),
295 shared::Elem::BF16x2 => f.write_str("__hip_bfloat162"),
296 shared::Elem::TF32 => f.write_str("float"),
297 shared::Elem::I8 => f.write_str("int8"),
298 shared::Elem::I16 => f.write_str("int16"),
299 shared::Elem::I32 => f.write_str("int32"),
300 shared::Elem::I64 => f.write_str("int64"),
301 shared::Elem::U8 => f.write_str("uint8"),
302 shared::Elem::U16 => f.write_str("uint16"),
303 shared::Elem::U32 => f.write_str("uint32"),
304 shared::Elem::U64 => f.write_str("uint64"),
305 shared::Elem::Bool => f.write_str("bool"),
306 shared::Elem::None => f.write_str("<none>"),
307 shared::Elem::_Dialect(_) => Ok(()),
308 }
309 }
310 }
311
312 fn compile_item(f: &mut std::fmt::Formatter<'_>, item: &Item<Self>) -> std::fmt::Result {
313 match item {
314 Item::Scalar(elem) => write!(f, "{elem}"),
315 Item::Vector(inner, vectorization) => {
316 write!(f, "{inner}_{vectorization}")
317 }
318 Item::NativeVector(elem, vectorization) => {
319 Self::compile_elem(f, elem, true)?;
320 write!(f, "{vectorization}")
321 }
322 Item::Atomic(inner) => Self::compile_item(f, inner.as_ref()),
323 Item::Pointer(inner, class) => {
324 if let PointerClass::Global(Visibility::Read | Visibility::Uniform) = class {
325 f.write_str("const ")?;
326 }
327 match inner.as_ref() {
328 Item::DynamicArray(inner) => write!(f, "{inner}*"),
329 other => write!(f, "{other}*"),
330 }
331 }
332 Item::Array(inner, size) => {
333 write!(f, "array<{inner}, {size}>")
334 }
335 Item::DynamicArray(inner) => {
336 write!(f, "{inner}*")
337 }
338 Item::Fragment(fragment_type) => write!(f, "{fragment_type}"),
339 Item::Barrier(_) | Item::BarrierToken(_) => {
340 panic!("Barrier object not supported in HIP")
341 }
342 Item::TensorMap => panic!("TensorMap not supported on HIP"),
343 }
344 }
345
346 fn compile_local_memory_qualifier(_f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
347 Ok(())
348 }
349}
350
351impl<M: DialectWmmaCompiler<Self>> DialectBindings<Self> for HipDialect<M> {
354 fn compile_kernel_signature(
355 f: &mut std::fmt::Formatter<'_>,
356 kernel_name: &str,
357 tensor_maps: &[KernelArg<Self>],
358 buffers: &[KernelArg<Self>],
359 flags: &Flags<Self>,
360 ) -> std::fmt::Result {
361 write!(
362 f,
363 "
364
365extern \"C\" __global__ void __launch_bounds__({}) {kernel_name}(
366",
367 flags.cube_dim.num_elems()
368 )?;
369 shared::compile_bindings::<Self>(f, tensor_maps, buffers, flags.has_info)?;
370 shared::compile_info_dynamic::<Self>(f, flags)?;
371 f.write_str("\n)")?;
372
373 Ok(())
374 }
375
376 fn compile_bindings_body(
377 f: &mut std::fmt::Formatter<'_>,
378 body: &shared::Body<Self>,
379 ) -> std::fmt::Result {
380 if !body.shared_memories.is_empty() {
381 let max_align = body
382 .shared_memories
383 .iter()
384 .map(|smem| smem.align)
385 .max()
386 .unwrap();
387 writeln!(
390 f,
391 "extern __shared__ __align__({max_align}) uchar dynamic_shared_mem[];"
392 )?;
393 }
394 if body.info_by_ptr {
395 f.write_str("const info_st& info = *info_ptr;\n")?;
396 writeln!(
398 f,
399 "const {addr}* dynamic_meta = reinterpret_cast<const {addr}*>(
400 reinterpret_cast<const char*>(info_ptr) + sizeof(info_st)
401 );\n",
402 addr = body.address_type,
403 )?;
404 }
405 Ok(())
406 }
407}
408
409impl<M: DialectWmmaCompiler<Self>> DialectCubeBuiltins<Self> for HipDialect<M> {}
412
413impl<M: DialectWmmaCompiler<Self>> DialectInstructions<Self> for HipDialect<M> {
416 fn compile_instruction_sync_threads(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417 writeln!(f, "__syncthreads();\n")
418 }
419
420 fn compile_instruction_sync_warp(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
421 writeln!(f, "#error Sync warp is unimplemented on hip\n")
422 }
423
424 fn compile_instruction_thread_fence(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
425 writeln!(f, "__threadfence();")
426 }
427
428 fn compile_instruction_find_first_set<T: Component<Self>>(
430 f: &mut std::fmt::Formatter<'_>,
431 input: T,
432 out_elem: Elem<Self>,
433 ) -> std::fmt::Result {
434 write!(f, "{out_elem}(")?;
435 match input.elem() {
436 Elem::I32 | Elem::U32 => write!(f, "__ffs({input})"),
437 Elem::I64 | Elem::U64 => write!(f, "__ffsll({input})"),
438 _ => write!(f, "__ffs({}({input}))", Elem::<Self>::U32),
439 }?;
440 write!(f, ")")
441 }
442
443 fn compile_instruction_leading_zeros_scalar<T: Component<Self>>(
444 f: &mut std::fmt::Formatter<'_>,
445 input: T,
446 out_elem: Elem<Self>,
447 ) -> std::fmt::Result {
448 write!(f, "{out_elem}(")?;
449 match input.elem() {
450 Elem::I32 | Elem::U32 => write!(f, "__clz({input})"),
451 Elem::I64 | Elem::U64 => write!(f, "__clzll({input})"),
452 in_elem => write!(
453 f,
454 "__clz({}) - {}",
455 unary::zero_extend(input),
456 (size_of::<u32>() - in_elem.size()) * 8
457 ),
458 }?;
459 write!(f, ")")
460 }
461
462 fn compile_instruction_trailing_zeros_scalar<T: Component<Self>>(
463 f: &mut std::fmt::Formatter<'_>,
464 input: T,
465 out_elem: Elem<Self>,
466 ) -> std::fmt::Result {
467 write!(f, "{out_elem}(")?;
470 match input.elem() {
471 Elem::I32 | Elem::U32 => {
472 write!(f, "({input} == 0 ? 32 : __ffs({input}) - 1)")
473 }
474 Elem::I64 | Elem::U64 => {
475 write!(f, "({input} == 0 ? 64 : __ffsll({input}) - 1)")
476 }
477 in_elem => {
478 let bits = in_elem.size() * 8;
479 let extended = unary::zero_extend(input);
480 write!(f, "({extended} == 0 ? {bits} : __ffs({extended}) - 1)")
481 }
482 }?;
483 write!(f, ")")
484 }
485
486 fn compile_saturating_add(
487 f: &mut std::fmt::Formatter<'_>,
488 _lhs: impl Display,
489 _rhs: impl Display,
490 _item: Item<Self>,
491 ) -> std::fmt::Result {
492 f.write_str(
493 "#error No native saturating add exists, TODO: Should be replaced in a preprocessor\n",
494 )
495 }
496
497 fn compile_saturating_sub(
498 f: &mut std::fmt::Formatter<'_>,
499 _lhs: impl Display,
500 _rhs: impl Display,
501 _item: Item<Self>,
502 ) -> std::fmt::Result {
503 f.write_str(
504 "#error No native saturating sub exists, TODO: Should be replaced in a preprocessor\n",
505 )
506 }
507
508 fn compile_instruction_max_function_name(
510 f: &mut std::fmt::Formatter<'_>,
511 item: Item<Self>,
512 ) -> std::fmt::Result {
513 let max = match item.elem() {
514 Elem::F16 => "__hmax",
515 Elem::BF16 => "__hmax",
516 _ => "max",
517 };
518 write!(f, "{max}")
519 }
520
521 fn compile_instruction_min_function_name(
522 f: &mut std::fmt::Formatter<'_>,
523 item: Item<Self>,
524 ) -> std::fmt::Result {
525 let min = match item.elem() {
526 Elem::F16 => "__hmin",
527 Elem::BF16 => "__hmin",
528 _ => "min",
529 };
530 write!(f, "{min}")
531 }
532
533 fn compile_warp_shuffle(
535 f: &mut std::fmt::Formatter<'_>,
536 val: &str,
537 _elem: &Elem<Self>,
538 source: &str,
539 ) -> std::fmt::Result {
540 write!(f, "__shfl({val}, {source})")
541 }
542 fn compile_warp_shuffle_xor(
543 f: &mut std::fmt::Formatter<'_>,
544 val: &str,
545 elem: &Elem<Self>,
546 offset: &str,
547 ) -> std::fmt::Result {
548 match elem {
549 Elem::BF16 => write!(
550 f,
551 "half_to_bfloat16(__shfl_xor(reinterpret_cast<__half&>({val}), {offset}))"
552 ),
553 _ => write!(f, "__shfl_xor({val}, {offset})"),
554 }
555 }
556 fn compile_warp_shuffle_up(
557 f: &mut std::fmt::Formatter<'_>,
558 val: &str,
559 _elem: &Elem<Self>,
560 offset: &str,
561 ) -> std::fmt::Result {
562 write!(f, "__shfl_up({val}, {offset})")
563 }
564 fn compile_warp_shuffle_down(
565 f: &mut std::fmt::Formatter<'_>,
566 val: &str,
567 _elem: &Elem<Self>,
568 offset: &str,
569 ) -> std::fmt::Result {
570 write!(f, "__shfl_down({val}, {offset})")
571 }
572 fn compile_warp_all<T: Component<Self>>(
573 f: &mut std::fmt::Formatter<'_>,
574 input: &T,
575 ) -> std::fmt::Result {
576 let item = input.item();
577 let elem = item.elem();
578 write!(f, "static_cast<{elem}>(__all({input}))")
579 }
580 fn compile_warp_any<T: Component<Self>>(
581 f: &mut std::fmt::Formatter<'_>,
582 input: &T,
583 ) -> std::fmt::Result {
584 let item = input.item();
585 let elem = item.elem();
586 write!(f, "static_cast<{elem}>(__any({input}))")
587 }
588 fn compile_warp_ballot(
589 f: &mut std::fmt::Formatter<'_>,
590 input: &Value<Self>,
591 out_elem: &Elem<Self>,
592 ) -> std::fmt::Result {
593 write!(f, "{out_elem}(__ballot({input}))")
594 }
595
596 fn compile_unreachable(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
597 write!(f, "__builtin_unreachable();")
598 }
599}
600
601impl<M: DialectWmmaCompiler<Self>> DialectWmmaCompiler<Self> for HipDialect<M> {
604 fn compile_wmma_includes(
605 f: &mut std::fmt::Formatter<'_>,
606 flags: &Flags<Self>,
607 ) -> std::fmt::Result {
608 M::compile_wmma_includes(f, flags)
609 }
610
611 fn compile_wmma_type_definitions(
612 f: &mut std::fmt::Formatter<'_>,
613 flags: &Flags<Self>,
614 ) -> std::fmt::Result {
615 M::compile_wmma_type_definitions(f, flags)
616 }
617
618 fn compile_wmma_local_variables(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
619 M::compile_wmma_local_variables(f)
620 }
621
622 fn compile_wmma_fragment_declaration(
623 f: &mut std::fmt::Formatter<'_>,
624 val: &Value<Self>,
625 ty: &Item<Self>,
626 ) -> std::fmt::Result {
627 M::compile_wmma_fragment_declaration(f, val, ty)
628 }
629
630 fn compile_wwma_fragment_ident(
631 f: &mut std::fmt::Formatter<'_>,
632 ident: &crate::shared::FragmentIdent<Self>,
633 ) -> std::fmt::Result {
634 M::compile_wwma_fragment_ident(f, ident)
635 }
636
637 fn compile_wmma_fragment_layout(
638 f: &mut std::fmt::Formatter<'_>,
639 layout: &crate::shared::FragmentLayout<Self>,
640 ) -> std::fmt::Result {
641 M::compile_wmma_fragment_layout(f, layout)
642 }
643
644 fn compile_wmma_fragment(
645 f: &mut std::fmt::Formatter<'_>,
646 fragment: &crate::shared::FragmentType<Self>,
647 ) -> std::fmt::Result {
648 M::compile_wmma_fragment(f, fragment)
649 }
650
651 fn compile_wmma_instruction(
652 f: &mut std::fmt::Formatter<'_>,
653 instruction: &crate::shared::WmmaInstruction<Self>,
654 ) -> std::fmt::Result {
655 M::compile_wmma_instruction(f, instruction)
656 }
657
658 fn compile_manual_mma(
659 f: &mut std::fmt::Formatter<'_>,
660 mma: ManualMma<Self>,
661 ) -> std::fmt::Result {
662 M::compile_manual_mma(f, mma)
663 }
664
665 fn supported_wmma_combinations(
666 arch: &AMDArchitecture,
667 ) -> crate::shared::SupportedMmaCombinations {
668 M::supported_wmma_combinations(arch)
669 }
670
671 fn supported_mma_combinations(arch: &AMDArchitecture) -> shared::SupportedMmaCombinations {
672 M::supported_mma_combinations(arch)
673 }
674
675 fn compile_scaled_mma(
676 _f: &mut std::fmt::Formatter<'_>,
677 _mma: ManualMma<Self>,
678 _scales_a: Value<Self>,
679 _scales_b: Value<Self>,
680 _scales_factor: u32,
681 ) -> std::fmt::Result {
682 panic!("Scaled MMA not supporter in HIP")
683 }
684}
685
686impl<M: DialectWmmaCompiler<Self>> DialectProcessors<Self> for HipDialect<M> {
687 fn processors() -> Vec<Box<dyn Processor>> {
688 vec![
689 Box::new(HipMmaProcessor),
690 Box::new(SaturatingArithmeticProcessor::new(true)),
691 ]
692 }
693}