1use alloc::{vec, vec::Vec};
2use ruda_core::ir::{
3 Allocator, Arithmetic, BinaryOperator, Branch, CoopMma, CopyMemoryBulkOperator,
4 IndexAssignOperator, IndexOperator, Instruction, ManagedVariable, MatrixLayout, Metadata,
5 Operation, OperationReflect, Operator, Processor, ScopeProcessing, Type, Variable,
6 VariableKind, VectorSize,
7};
8use hashbrown::HashMap;
9
10pub enum TransformAction {
12 Ignore,
14 Replace(Vec<Instruction>),
16}
17
18#[derive(new, Debug)]
19pub struct UnrollProcessor {
20 max_vector_size: VectorSize,
21}
22
23struct Mappings(HashMap<Variable, Vec<ManagedVariable>>);
24
25impl Mappings {
26 fn get(
27 &mut self,
28 alloc: &Allocator,
29 var: Variable,
30 unroll_factor: usize,
31 vector_size: VectorSize,
32 ) -> Vec<Variable> {
33 self.0
34 .entry(var)
35 .or_insert_with(|| create_unrolled(alloc, &var, vector_size, unroll_factor))
36 .iter()
37 .map(|it| **it)
38 .collect()
39 }
40}
41
42impl UnrollProcessor {
43 fn maybe_transform(
44 &self,
45 alloc: &Allocator,
46 inst: &Instruction,
47 mappings: &mut Mappings,
48 ) -> TransformAction {
49 if let Operation::TypedMemory(memory) = &inst.operation {
50 let lowered = inst.clone().lower_typed_memory().expect("valid typed memory instruction");
51 return match self.maybe_transform(alloc, &lowered, mappings) {
52 TransformAction::Ignore => TransformAction::Ignore,
53 TransformAction::Replace(instructions) => TransformAction::Replace(
54 instructions.into_iter().map(|instruction| memory.retag(instruction)).collect()),
55 };
56 }
57 if matches!(inst.operation, Operation::Marker(_)) {
58 return TransformAction::Ignore;
59 }
60
61 if inst.operation.args().is_none() {
62 match &inst.operation {
64 Operation::CoopMma(op) => match op {
65 CoopMma::Load {
67 value,
68 stride,
69 offset,
70 layout,
71 } if value.vector_size() > self.max_vector_size => {
72 return TransformAction::Replace(self.transform_cmma_load(
73 alloc,
74 inst.out(),
75 value,
76 stride,
77 offset,
78 layout,
79 ));
80 }
81 CoopMma::Store {
82 mat,
83 stride,
84 offset,
85 layout,
86 } if inst.out().vector_size() > self.max_vector_size => {
87 return TransformAction::Replace(self.transform_cmma_store(
88 alloc,
89 inst.out(),
90 mat,
91 stride,
92 offset,
93 layout,
94 ));
95 }
96 _ => return TransformAction::Ignore,
97 },
98 Operation::Branch(_) | Operation::NonSemantic(_) | Operation::Marker(_) => {
99 return TransformAction::Ignore;
100 }
101 _ => {
102 panic!("Need special handling for unrolling non-reflectable operations")
103 }
104 }
105 }
106
107 let args = inst.operation.args().unwrap_or_default();
108 if (inst.out.is_some() && inst.ty().vector_size() > self.max_vector_size)
109 || args
110 .iter()
111 .any(|arg| arg.vector_size() > self.max_vector_size)
112 {
113 let vector_size = max_vector_size(&inst.out, &args);
114 let unroll_factor = vector_size / self.max_vector_size;
115
116 match &inst.operation {
117 Operation::Operator(Operator::CopyMemoryBulk(op)) => TransformAction::Replace(
118 self.transform_memcpy(alloc, op, inst.out(), unroll_factor),
119 ),
120 Operation::Operator(Operator::CopyMemory(op)) => {
121 TransformAction::Replace(self.transform_memcpy(
122 alloc,
123 &CopyMemoryBulkOperator {
124 out_index: op.out_index,
125 input: op.input,
126 in_index: op.in_index,
127 len: 1,
128 offset_input: 0.into(),
129 offset_out: 0.into(),
130 },
131 inst.out(),
132 unroll_factor,
133 ))
134 }
135 Operation::Operator(Operator::Index(op)) if op.list.is_array() => {
136 TransformAction::Replace(self.transform_array_index(
137 alloc,
138 inst.out(),
139 op,
140 Operator::Index,
141 unroll_factor,
142 mappings,
143 ))
144 }
145 Operation::Operator(Operator::UncheckedIndex(op)) if op.list.is_array() => {
146 TransformAction::Replace(self.transform_array_index(
147 alloc,
148 inst.out(),
149 op,
150 Operator::UncheckedIndex,
151 unroll_factor,
152 mappings,
153 ))
154 }
155 Operation::Operator(Operator::Index(op)) => {
156 TransformAction::Replace(self.transform_composite_index(
157 alloc,
158 inst.out(),
159 op,
160 Operator::Index,
161 unroll_factor,
162 mappings,
163 ))
164 }
165 Operation::Operator(Operator::UncheckedIndex(op)) => {
166 TransformAction::Replace(self.transform_composite_index(
167 alloc,
168 inst.out(),
169 op,
170 Operator::UncheckedIndex,
171 unroll_factor,
172 mappings,
173 ))
174 }
175 Operation::Operator(Operator::IndexAssign(op)) if inst.out().is_array() => {
176 TransformAction::Replace(self.transform_array_index_assign(
177 alloc,
178 inst.out(),
179 op,
180 Operator::IndexAssign,
181 unroll_factor,
182 mappings,
183 ))
184 }
185 Operation::Operator(Operator::UncheckedIndexAssign(op))
186 if inst.out().is_array() =>
187 {
188 TransformAction::Replace(self.transform_array_index_assign(
189 alloc,
190 inst.out(),
191 op,
192 Operator::UncheckedIndexAssign,
193 unroll_factor,
194 mappings,
195 ))
196 }
197 Operation::Operator(Operator::IndexAssign(op)) => {
198 TransformAction::Replace(self.transform_composite_index_assign(
199 alloc,
200 inst.out(),
201 op,
202 Operator::IndexAssign,
203 unroll_factor,
204 mappings,
205 ))
206 }
207 Operation::Operator(Operator::UncheckedIndexAssign(op)) => {
208 TransformAction::Replace(self.transform_composite_index_assign(
209 alloc,
210 inst.out(),
211 op,
212 Operator::UncheckedIndexAssign,
213 unroll_factor,
214 mappings,
215 ))
216 }
217 Operation::Metadata(op) => {
218 TransformAction::Replace(self.transform_metadata(inst.out(), op, args))
219 }
220 _ => TransformAction::Replace(self.transform_basic(
221 alloc,
222 inst,
223 args,
224 unroll_factor,
225 mappings,
226 )),
227 }
228 } else {
229 TransformAction::Ignore
230 }
231 }
232
233 fn transform_cmma_load(
235 &self,
236 alloc: &Allocator,
237 out: Variable,
238 value: &Variable,
239 stride: &Variable,
240 offset: &Variable,
241 layout: &Option<MatrixLayout>,
242 ) -> Vec<Instruction> {
243 let vector_size = value.vector_size();
244 let unroll_factor = vector_size / self.max_vector_size;
245
246 let value = unroll_array(*value, self.max_vector_size, unroll_factor);
247 let (mul, offset) = mul_index(alloc, *offset, unroll_factor);
248 let load = Instruction::new(
249 Operation::CoopMma(CoopMma::Load {
250 value,
251 stride: *stride,
252 offset: *offset,
253 layout: *layout,
254 }),
255 out,
256 );
257 vec![mul, load]
258 }
259
260 fn transform_cmma_store(
262 &self,
263 alloc: &Allocator,
264 out: Variable,
265 mat: &Variable,
266 stride: &Variable,
267 offset: &Variable,
268 layout: &MatrixLayout,
269 ) -> Vec<Instruction> {
270 let vector_size = out.vector_size();
271 let unroll_factor = vector_size / self.max_vector_size;
272
273 let out = unroll_array(out, self.max_vector_size, unroll_factor);
274 let (mul, offset) = mul_index(alloc, *offset, unroll_factor);
275 let store = Instruction::new(
276 Operation::CoopMma(CoopMma::Store {
277 mat: *mat,
278 stride: *stride,
279 offset: *offset,
280 layout: *layout,
281 }),
282 out,
283 );
284 vec![mul, store]
285 }
286
287 fn transform_memcpy(
289 &self,
290 alloc: &Allocator,
291 op: &CopyMemoryBulkOperator,
292 out: Variable,
293 unroll_factor: usize,
294 ) -> Vec<Instruction> {
295 let (mul1, in_index) = mul_index(alloc, op.in_index, unroll_factor);
296 let (mul2, offset_input) = mul_index(alloc, op.offset_input, unroll_factor);
297 let (mul3, out_index) = mul_index(alloc, op.out_index, unroll_factor);
298 let (mul4, offset_out) = mul_index(alloc, op.offset_out, unroll_factor);
299
300 let input = unroll_array(op.input, self.max_vector_size, unroll_factor);
301 let out = unroll_array(out, self.max_vector_size, unroll_factor);
302
303 vec![
304 mul1,
305 mul2,
306 mul3,
307 mul4,
308 Instruction::new(
309 Operator::CopyMemoryBulk(CopyMemoryBulkOperator {
310 input,
311 in_index: *in_index,
312 out_index: *out_index,
313 len: op.len * unroll_factor,
314 offset_input: *offset_input,
315 offset_out: *offset_out,
316 }),
317 out,
318 ),
319 ]
320 }
321
322 fn transform_array_index(
325 &self,
326 alloc: &Allocator,
327 out: Variable,
328 op: &IndexOperator,
329 operator: impl Fn(IndexOperator) -> Operator,
330 unroll_factor: usize,
331 mappings: &mut Mappings,
332 ) -> Vec<Instruction> {
333 let (mul, start_idx) = mul_index(alloc, op.index, unroll_factor);
334 let mut indices = (0..unroll_factor).map(|i| add_index(alloc, *start_idx, i));
335
336 let list = unroll_array(op.list, self.max_vector_size, unroll_factor);
337
338 let out = mappings.get(alloc, out, unroll_factor, self.max_vector_size);
339 let mut instructions = vec![mul];
340 instructions.extend((0..unroll_factor).flat_map(|i| {
341 let (add, idx) = indices.next().unwrap();
342 let index = Instruction::new(
343 operator(IndexOperator {
344 list,
345 index: *idx,
346 vector_size: 0,
347 unroll_factor,
348 }),
349 out[i],
350 );
351 [add, index]
352 }));
353
354 instructions
355 }
356
357 fn transform_array_index_assign(
360 &self,
361 alloc: &Allocator,
362 out: Variable,
363 op: &IndexAssignOperator,
364 operator: impl Fn(IndexAssignOperator) -> Operator,
365 unroll_factor: usize,
366 mappings: &mut Mappings,
367 ) -> Vec<Instruction> {
368 let (mul, start_idx) = mul_index(alloc, op.index, unroll_factor);
369 let mut indices = (0..unroll_factor).map(|i| add_index(alloc, *start_idx, i));
370
371 let out = unroll_array(out, self.max_vector_size, unroll_factor);
372
373 let value = mappings.get(alloc, op.value, unroll_factor, self.max_vector_size);
374
375 let mut instructions = vec![mul];
376 instructions.extend((0..unroll_factor).flat_map(|i| {
377 let (add, idx) = indices.next().unwrap();
378 let index = Instruction::new(
379 operator(IndexAssignOperator {
380 index: *idx,
381 vector_size: 0,
382 value: value[i],
383 unroll_factor,
384 }),
385 out,
386 );
387
388 [add, index]
389 }));
390
391 instructions
392 }
393
394 fn transform_composite_index(
399 &self,
400 alloc: &Allocator,
401 out: Variable,
402 op: &IndexOperator,
403 operator: impl Fn(IndexOperator) -> Operator,
404 unroll_factor: usize,
405 mappings: &mut Mappings,
406 ) -> Vec<Instruction> {
407 let index = op
408 .index
409 .as_const()
410 .expect("Can't unroll non-constant vector index")
411 .as_usize();
412
413 let unroll_idx = index / self.max_vector_size;
414 let sub_idx = index % self.max_vector_size;
415
416 let value = mappings.get(alloc, op.list, unroll_factor, self.max_vector_size);
417
418 vec![Instruction::new(
419 operator(IndexOperator {
420 list: value[unroll_idx],
421 index: sub_idx.into(),
422 vector_size: 1,
423 unroll_factor,
424 }),
425 out,
426 )]
427 }
428
429 fn transform_composite_index_assign(
434 &self,
435 alloc: &Allocator,
436 out: Variable,
437 op: &IndexAssignOperator,
438 operator: impl Fn(IndexAssignOperator) -> Operator,
439 unroll_factor: usize,
440 mappings: &mut Mappings,
441 ) -> Vec<Instruction> {
442 let index = op
443 .index
444 .as_const()
445 .expect("Can't unroll non-constant vector index")
446 .as_usize();
447
448 let unroll_idx = index / self.max_vector_size;
449 let sub_idx = index % self.max_vector_size;
450
451 let out = mappings.get(alloc, out, unroll_factor, self.max_vector_size);
452
453 vec![Instruction::new(
454 operator(IndexAssignOperator {
455 index: sub_idx.into(),
456 vector_size: 1,
457 value: op.value,
458 unroll_factor,
459 }),
460 out[unroll_idx],
461 )]
462 }
463
464 fn transform_metadata(
467 &self,
468 out: Variable,
469 op: &Metadata,
470 args: Vec<Variable>,
471 ) -> Vec<Instruction> {
472 let op_code = op.op_code();
473 let args = args
474 .into_iter()
475 .map(|mut var| {
476 if var.vector_size() > self.max_vector_size {
477 var.ty = var.ty.with_vector_size(self.max_vector_size);
478 }
479 var
480 })
481 .collect::<Vec<_>>();
482 let operation = Metadata::from_code_and_args(op_code, &args).unwrap();
483 vec![Instruction::new(operation, out)]
484 }
485
486 fn transform_basic(
489 &self,
490 alloc: &Allocator,
491 inst: &Instruction,
492 args: Vec<Variable>,
493 unroll_factor: usize,
494 mappings: &mut Mappings,
495 ) -> Vec<Instruction> {
496 let op_code = inst.operation.op_code();
497 let out = inst
498 .out
499 .map(|out| mappings.get(alloc, out, unroll_factor, self.max_vector_size));
500 let args = args
501 .into_iter()
502 .map(|arg| {
503 if arg.vector_size() > 1 {
504 mappings.get(alloc, arg, unroll_factor, self.max_vector_size)
505 } else {
506 vec![arg]
508 }
509 })
510 .collect::<Vec<_>>();
511
512 (0..unroll_factor)
513 .map(|i| {
514 let out = out.as_ref().map(|out| out[i]);
515 let args = args
516 .iter()
517 .map(|arg| if arg.len() == 1 { arg[0] } else { arg[i] })
518 .collect::<Vec<_>>();
519 let operation = Operation::from_code_and_args(op_code, &args)
520 .expect("Failed to reconstruct operation");
521 Instruction {
522 out,
523 source_loc: inst.source_loc.clone(),
524 modes: inst.modes,
525 operation,
526 }
527 })
528 .collect()
529 }
530
531 fn transform_instructions(
532 &self,
533 allocator: &Allocator,
534 instructions: Vec<Instruction>,
535 mappings: &mut Mappings,
536 ) -> Vec<Instruction> {
537 let mut new_instructions = Vec::with_capacity(instructions.len());
538
539 for mut instruction in instructions {
540 if let Operation::Branch(branch) = &mut instruction.operation {
541 match branch {
542 Branch::If(op) => {
543 op.scope.instructions = self.transform_instructions(
544 allocator,
545 op.scope.instructions.drain(..).collect(),
546 mappings,
547 );
548 }
549 Branch::IfElse(op) => {
550 op.scope_if.instructions = self.transform_instructions(
551 allocator,
552 op.scope_if.instructions.drain(..).collect(),
553 mappings,
554 );
555 op.scope_else.instructions = self.transform_instructions(
556 allocator,
557 op.scope_else.instructions.drain(..).collect(),
558 mappings,
559 );
560 }
561 Branch::Switch(op) => {
562 for (_, case) in &mut op.cases {
563 case.instructions = self.transform_instructions(
564 allocator,
565 case.instructions.drain(..).collect(),
566 mappings,
567 );
568 }
569 op.scope_default.instructions = self.transform_instructions(
570 allocator,
571 op.scope_default.instructions.drain(..).collect(),
572 mappings,
573 );
574 }
575 Branch::RangeLoop(op) => {
576 op.scope.instructions = self.transform_instructions(
577 allocator,
578 op.scope.instructions.drain(..).collect(),
579 mappings,
580 );
581 }
582 Branch::Loop(op) => {
583 op.scope.instructions = self.transform_instructions(
584 allocator,
585 op.scope.instructions.drain(..).collect(),
586 mappings,
587 );
588 }
589 _ => {}
590 }
591 }
592 match self.maybe_transform(allocator, &instruction, mappings) {
593 TransformAction::Ignore => {
594 new_instructions.push(instruction);
595 }
596 TransformAction::Replace(replacement) => {
597 new_instructions.extend(replacement);
598 }
599 }
600 }
601
602 new_instructions
603 }
604}
605
606impl Processor for UnrollProcessor {
607 fn transform(&self, processing: ScopeProcessing, allocator: Allocator) -> ScopeProcessing {
608 let mut mappings = Mappings(Default::default());
609
610 let instructions =
611 self.transform_instructions(&allocator, processing.instructions, &mut mappings);
612
613 ScopeProcessing {
614 variables: processing.variables,
615 instructions,
616 typemap: processing.typemap.clone(),
617 }
618 }
619}
620
621fn max_vector_size(out: &Option<Variable>, args: &[Variable]) -> VectorSize {
622 let vector_size = args.iter().map(|it| it.vector_size()).max().unwrap();
623 vector_size.max(out.map(|out| out.vector_size()).unwrap_or(1))
624}
625
626fn create_unrolled(
627 allocator: &Allocator,
628 var: &Variable,
629 max_vector_size: VectorSize,
630 unroll_factor: usize,
631) -> Vec<ManagedVariable> {
632 if var.vector_size() == 1 {
634 return vec![ManagedVariable::Plain(*var); unroll_factor];
635 }
636
637 let item = Type::new(var.storage_type()).with_vector_size(max_vector_size);
638 (0..unroll_factor)
639 .map(|_| match var.kind {
640 VariableKind::LocalMut { .. } | VariableKind::Versioned { .. } => {
641 allocator.create_local_mut(item)
642 }
643 VariableKind::Shared { .. } => {
644 let id = allocator.new_local_index();
645 let shared = VariableKind::Shared { id };
646 ManagedVariable::Plain(Variable::new(shared, item))
647 }
648 VariableKind::LocalConst { .. } => allocator.create_local(item),
649 other => panic!("Out must be local, found {other:?}"),
650 })
651 .collect()
652}
653
654fn add_index(alloc: &Allocator, idx: Variable, i: usize) -> (Instruction, ManagedVariable) {
655 let add_idx = alloc.create_local(idx.ty);
656 let add = Instruction::new(
657 Arithmetic::Add(BinaryOperator {
658 lhs: idx,
659 rhs: i.into(),
660 }),
661 *add_idx,
662 );
663 (add, add_idx)
664}
665
666fn mul_index(
667 alloc: &Allocator,
668 idx: Variable,
669 unroll_factor: usize,
670) -> (Instruction, ManagedVariable) {
671 let mul_idx = alloc.create_local(idx.ty);
672 let mul = Instruction::new(
673 Arithmetic::Mul(BinaryOperator {
674 lhs: idx,
675 rhs: unroll_factor.into(),
676 }),
677 *mul_idx,
678 );
679 (mul, mul_idx)
680}
681
682fn unroll_array(mut var: Variable, max_vector_size: VectorSize, factor: usize) -> Variable {
683 var.ty = var.ty.with_vector_size(max_vector_size);
684
685 match &mut var.kind {
686 VariableKind::LocalArray { unroll_factor, .. }
687 | VariableKind::ConstantArray { unroll_factor, .. }
688 | VariableKind::SharedArray { unroll_factor, .. } => {
689 *unroll_factor = factor;
690 }
691 _ => {}
692 }
693
694 var
695}