1use std::collections::HashMap;
8
9use nxpu_ir::{
10 ArraySize, Expression, Function, GlobalVariable, Handle, Module, Statement, TypeInner,
11};
12
13pub use nxpu_backend_core::{BufferAllocation, MemoryPlan, TensorId};
15
16use crate::Pass;
17
18#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct LiveInterval {
26 pub start: usize,
28 pub end: usize,
30 pub size_bytes: usize,
32}
33
34#[derive(Clone, Debug)]
40struct TensorInfo {
41 id: TensorId,
42 #[allow(dead_code)]
44 name: Option<String>,
45 size_bytes: usize,
47}
48
49fn type_size_bytes(module: &Module, ty: Handle<nxpu_ir::Type>) -> usize {
55 match &module.types[ty].inner {
56 TypeInner::Scalar(s) => s.width as usize,
57 TypeInner::Vector { size, scalar } => (*size as usize) * (scalar.width as usize),
58 TypeInner::Matrix {
59 columns,
60 rows,
61 scalar,
62 } => (*columns as usize) * (*rows as usize) * (scalar.width as usize),
63 TypeInner::Atomic(s) => s.width as usize,
64 TypeInner::Pointer { .. } => 0, TypeInner::Array { base, size, stride } => match size {
66 ArraySize::Constant(n) => {
67 let elem = type_size_bytes(module, *base);
68 if elem == 0 {
69 (*n as usize) * (*stride as usize)
70 } else {
71 (*n as usize) * elem
72 }
73 }
74 ArraySize::Dynamic => 0, },
76 TypeInner::Struct { span, .. } => *span as usize,
77 TypeInner::Tensor { scalar, shape } => {
78 if shape.is_fully_static() {
79 let elem_count: usize = shape
80 .dims
81 .iter()
82 .map(|d| match d {
83 nxpu_ir::Dimension::Fixed(n) => *n as usize,
84 nxpu_ir::Dimension::Dynamic(_) | nxpu_ir::Dimension::Symbolic(_) => 0,
85 })
86 .product();
87 elem_count * (scalar.width as usize)
88 } else {
89 0
90 }
91 }
92 }
93}
94
95fn resolve_expr_tensor_id(
102 handle: Handle<Expression>,
103 func: &Function,
104 global_id_map: &HashMap<Handle<GlobalVariable>, TensorId>,
105 local_id_map: &HashMap<Handle<nxpu_ir::LocalVariable>, TensorId>,
106) -> Option<TensorId> {
107 let expr = func.expressions.try_get(handle)?;
108 match expr {
109 Expression::GlobalVariable(gv) => global_id_map.get(gv).copied(),
110 Expression::LocalVariable(lv) => local_id_map.get(lv).copied(),
111 Expression::Load { pointer } => {
112 resolve_expr_tensor_id(*pointer, func, global_id_map, local_id_map)
113 }
114 Expression::Access { base, .. } | Expression::AccessIndex { base, .. } => {
115 resolve_expr_tensor_id(*base, func, global_id_map, local_id_map)
116 }
117 _ => None,
118 }
119}
120
121fn try_resolve_push(
123 h: Handle<Expression>,
124 func: &Function,
125 global_id_map: &HashMap<Handle<GlobalVariable>, TensorId>,
126 local_id_map: &HashMap<Handle<nxpu_ir::LocalVariable>, TensorId>,
127 ids: &mut Vec<TensorId>,
128) {
129 if let Some(tid) = resolve_expr_tensor_id(h, func, global_id_map, local_id_map) {
130 ids.push(tid);
131 }
132}
133
134fn collect_stmt_tensor_ids(
137 stmt: &Statement,
138 func: &Function,
139 global_id_map: &HashMap<Handle<GlobalVariable>, TensorId>,
140 local_id_map: &HashMap<Handle<nxpu_ir::LocalVariable>, TensorId>,
141) -> Vec<TensorId> {
142 let mut ids = Vec::new();
143
144 match stmt {
145 Statement::Emit(range) => {
146 let idx_range = range.index_range();
149 for (expr_handle, _) in func.expressions.iter() {
150 if idx_range.contains(&(expr_handle.index() as u32)) {
151 try_resolve_push(expr_handle, func, global_id_map, local_id_map, &mut ids);
152 }
153 }
154 }
155 Statement::Store { pointer, value } => {
156 try_resolve_push(*pointer, func, global_id_map, local_id_map, &mut ids);
157 try_resolve_push(*value, func, global_id_map, local_id_map, &mut ids);
158 }
159 Statement::If {
160 condition,
161 accept,
162 reject,
163 } => {
164 try_resolve_push(*condition, func, global_id_map, local_id_map, &mut ids);
165 for s in accept {
166 ids.extend(collect_stmt_tensor_ids(
167 s,
168 func,
169 global_id_map,
170 local_id_map,
171 ));
172 }
173 for s in reject {
174 ids.extend(collect_stmt_tensor_ids(
175 s,
176 func,
177 global_id_map,
178 local_id_map,
179 ));
180 }
181 }
182 Statement::Loop {
183 body,
184 continuing,
185 break_if,
186 } => {
187 for s in body {
188 ids.extend(collect_stmt_tensor_ids(
189 s,
190 func,
191 global_id_map,
192 local_id_map,
193 ));
194 }
195 for s in continuing {
196 ids.extend(collect_stmt_tensor_ids(
197 s,
198 func,
199 global_id_map,
200 local_id_map,
201 ));
202 }
203 if let Some(brk) = break_if {
204 try_resolve_push(*brk, func, global_id_map, local_id_map, &mut ids);
205 }
206 }
207 Statement::Call {
208 arguments, result, ..
209 } => {
210 for arg in arguments {
211 try_resolve_push(*arg, func, global_id_map, local_id_map, &mut ids);
212 }
213 if let Some(r) = result {
214 try_resolve_push(*r, func, global_id_map, local_id_map, &mut ids);
215 }
216 }
217 Statement::Atomic {
218 pointer,
219 value,
220 result,
221 fun,
222 } => {
223 try_resolve_push(*pointer, func, global_id_map, local_id_map, &mut ids);
224 try_resolve_push(*value, func, global_id_map, local_id_map, &mut ids);
225 if let Some(r) = result {
226 try_resolve_push(*r, func, global_id_map, local_id_map, &mut ids);
227 }
228 if let nxpu_ir::AtomicFunction::Exchange {
229 compare: Some(cmp), ..
230 } = fun
231 {
232 try_resolve_push(*cmp, func, global_id_map, local_id_map, &mut ids);
233 }
234 }
235 Statement::Return { value } => {
236 if let Some(v) = value {
237 try_resolve_push(*v, func, global_id_map, local_id_map, &mut ids);
238 }
239 }
240 Statement::Barrier(_) | Statement::Break | Statement::Continue => {}
241 }
242 ids
243}
244
245fn analyze_function_lifetimes(
247 module: &Module,
248 func: &Function,
249 global_id_map: &HashMap<Handle<GlobalVariable>, TensorId>,
250 tensor_infos: &mut Vec<TensorInfo>,
251 next_id: &mut usize,
252) -> HashMap<TensorId, LiveInterval> {
253 let mut local_id_map: HashMap<Handle<nxpu_ir::LocalVariable>, TensorId> = HashMap::new();
255 for (lv_handle, lv) in func.local_variables.iter() {
256 let tid = TensorId(*next_id);
257 *next_id += 1;
258 let size = type_size_bytes(module, lv.ty);
259 tensor_infos.push(TensorInfo {
260 id: tid,
261 name: lv.name.clone(),
262 size_bytes: size,
263 });
264 local_id_map.insert(lv_handle, tid);
265 }
266
267 let mut intervals: HashMap<TensorId, LiveInterval> = HashMap::new();
268
269 for (stmt_idx, stmt) in func.body.iter().enumerate() {
270 let tensor_ids = collect_stmt_tensor_ids(stmt, func, global_id_map, &local_id_map);
271 for tid in tensor_ids {
272 let size = tensor_infos
274 .iter()
275 .find(|t| t.id == tid)
276 .map(|t| t.size_bytes)
277 .unwrap_or(0);
278 intervals
279 .entry(tid)
280 .and_modify(|interval| {
281 if stmt_idx < interval.start {
282 interval.start = stmt_idx;
283 }
284 if stmt_idx > interval.end {
285 interval.end = stmt_idx;
286 }
287 })
288 .or_insert(LiveInterval {
289 start: stmt_idx,
290 end: stmt_idx,
291 size_bytes: size,
292 });
293 }
294 }
295
296 intervals
297}
298
299#[derive(Clone, Debug)]
305struct ActiveAllocation {
306 #[allow(dead_code)]
307 tensor_id: TensorId,
308 offset: usize,
309 size: usize,
310 end: usize, }
312
313fn greedy_allocate(intervals: &HashMap<TensorId, LiveInterval>) -> MemoryPlan {
316 if intervals.is_empty() {
317 return MemoryPlan::default();
318 }
319
320 let mut sorted: Vec<(TensorId, &LiveInterval)> = intervals
322 .iter()
323 .map(|(tid, interval)| (*tid, interval))
324 .collect();
325 sorted.sort_by(|a, b| {
326 a.1.start
327 .cmp(&b.1.start)
328 .then(b.1.size_bytes.cmp(&a.1.size_bytes))
329 });
330
331 let mut active: Vec<ActiveAllocation> = Vec::new();
332 let mut allocations: Vec<BufferAllocation> = Vec::new();
333 let mut peak = 0usize;
334
335 for (tid, interval) in sorted {
336 if interval.size_bytes == 0 {
338 allocations.push(BufferAllocation {
339 tensor_id: tid,
340 offset: 0,
341 size_bytes: 0,
342 });
343 continue;
344 }
345
346 active.retain(|a| a.end >= interval.start);
348
349 active.sort_by_key(|a| a.offset);
351
352 let mut offset = 0usize;
354 for a in &active {
355 if offset + interval.size_bytes <= a.offset {
356 break;
357 }
358 offset = a.offset + a.size;
359 }
360
361 let end_of_alloc = offset + interval.size_bytes;
362 if end_of_alloc > peak {
363 peak = end_of_alloc;
364 }
365
366 allocations.push(BufferAllocation {
367 tensor_id: tid,
368 offset,
369 size_bytes: interval.size_bytes,
370 });
371
372 active.push(ActiveAllocation {
373 tensor_id: tid,
374 offset,
375 size: interval.size_bytes,
376 end: interval.end,
377 });
378 }
379
380 MemoryPlan {
381 allocations,
382 peak_bytes: peak,
383 }
384}
385
386pub fn plan_memory(module: &Module) -> MemoryPlan {
395 let mut next_id = 0usize;
396 let mut tensor_infos: Vec<TensorInfo> = Vec::new();
397 let mut global_id_map: HashMap<Handle<GlobalVariable>, TensorId> = HashMap::new();
398
399 for (handle, gv) in module.global_variables.iter() {
401 let tid = TensorId(next_id);
402 next_id += 1;
403 let size = type_size_bytes(module, gv.ty);
404 tensor_infos.push(TensorInfo {
405 id: tid,
406 name: gv.name.clone(),
407 size_bytes: size,
408 });
409 global_id_map.insert(handle, tid);
410 }
411
412 let mut all_intervals: HashMap<TensorId, LiveInterval> = HashMap::new();
414
415 for (_, func) in module.functions.iter() {
417 let intervals = analyze_function_lifetimes(
418 module,
419 func,
420 &global_id_map,
421 &mut tensor_infos,
422 &mut next_id,
423 );
424 merge_intervals(&mut all_intervals, &intervals);
425 }
426
427 for ep in &module.entry_points {
429 let intervals = analyze_function_lifetimes(
430 module,
431 &ep.function,
432 &global_id_map,
433 &mut tensor_infos,
434 &mut next_id,
435 );
436 merge_intervals(&mut all_intervals, &intervals);
437 }
438
439 for info in &tensor_infos {
442 if info.size_bytes > 0 && !all_intervals.contains_key(&info.id) {
443 all_intervals.insert(
445 info.id,
446 LiveInterval {
447 start: 0,
448 end: usize::MAX,
449 size_bytes: info.size_bytes,
450 },
451 );
452 }
453 }
454
455 greedy_allocate(&all_intervals)
456}
457
458fn merge_intervals(
460 dst: &mut HashMap<TensorId, LiveInterval>,
461 src: &HashMap<TensorId, LiveInterval>,
462) {
463 for (tid, interval) in src {
464 dst.entry(*tid)
465 .and_modify(|existing| {
466 if interval.start < existing.start {
467 existing.start = interval.start;
468 }
469 if interval.end > existing.end {
470 existing.end = interval.end;
471 }
472 })
473 .or_insert_with(|| interval.clone());
474 }
475}
476
477#[derive(Debug)]
486pub struct MemoryPlanning;
487
488impl Pass for MemoryPlanning {
489 fn name(&self) -> &str {
490 "MemoryPlanning"
491 }
492
493 fn run(&self, module: &mut Module) -> bool {
494 let plan = plan_memory(module);
495 log::debug!(
496 "memory plan: {} allocations, peak {} bytes",
497 plan.allocations.len(),
498 plan.peak_bytes
499 );
500 false
502 }
503}
504
505#[cfg(test)]
510mod tests {
511 use super::*;
512 use nxpu_ir::*;
513
514 fn f32_type(module: &mut Module) -> Handle<Type> {
516 module.types.insert(Type {
517 name: None,
518 inner: TypeInner::Scalar(Scalar::F32),
519 })
520 }
521
522 fn f32_array_type(module: &mut Module, count: u32) -> Handle<Type> {
524 let f32_ty = f32_type(module);
525 module.types.insert(Type {
526 name: None,
527 inner: TypeInner::Array {
528 base: f32_ty,
529 size: ArraySize::Constant(count),
530 stride: 4,
531 },
532 })
533 }
534
535 fn f32_dynamic_array_type(module: &mut Module) -> Handle<Type> {
537 let f32_ty = f32_type(module);
538 module.types.insert(Type {
539 name: None,
540 inner: TypeInner::Array {
541 base: f32_ty,
542 size: ArraySize::Dynamic,
543 stride: 4,
544 },
545 })
546 }
547
548 #[test]
549 fn empty_module_produces_empty_plan() {
550 let module = Module::default();
551 let plan = plan_memory(&module);
552 assert_eq!(plan.allocations.len(), 0);
553 assert_eq!(plan.peak_bytes, 0);
554 }
555
556 #[test]
557 fn single_tensor() {
558 let mut module = Module::default();
559 let arr_ty = f32_array_type(&mut module, 256); let gv = module.global_variables.append(GlobalVariable {
562 name: Some("buf".into()),
563 space: AddressSpace::Storage {
564 access: StorageAccess::LOAD | StorageAccess::STORE,
565 },
566 binding: Some(ResourceBinding {
567 group: 0,
568 binding: 0,
569 }),
570 ty: arr_ty,
571 init: None,
572 layout: None,
573 });
574
575 let mut func = Function::new("main");
577 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
578 let lit = func
579 .expressions
580 .append(Expression::Literal(Literal::F32(1.0)));
581 func.body.push(Statement::Store {
582 pointer: ptr,
583 value: lit,
584 });
585
586 module.entry_points.push(EntryPoint {
587 name: "main".into(),
588 workgroup_size: [1, 1, 1],
589 function: func,
590 });
591
592 let plan = plan_memory(&module);
593 assert_eq!(plan.allocations.len(), 1);
594 assert_eq!(plan.allocations[0].size_bytes, 1024);
595 assert_eq!(plan.peak_bytes, 1024);
596 }
597
598 #[test]
599 fn non_overlapping_lifetimes_reuse_buffer() {
600 let mut module = Module::default();
603 let arr_ty = f32_array_type(&mut module, 128); let mut func = Function::new("main");
606
607 let lv_a = func.local_variables.append(LocalVariable {
609 name: Some("temp_a".into()),
610 ty: arr_ty,
611 init: None,
612 });
613 let lv_b = func.local_variables.append(LocalVariable {
615 name: Some("temp_b".into()),
616 ty: arr_ty,
617 init: None,
618 });
619
620 let ptr_a = func.expressions.append(Expression::LocalVariable(lv_a));
621 let ptr_b = func.expressions.append(Expression::LocalVariable(lv_b));
622 let lit = func
623 .expressions
624 .append(Expression::Literal(Literal::F32(0.0)));
625
626 func.body.push(Statement::Store {
628 pointer: ptr_a,
629 value: lit,
630 });
631 func.body.push(Statement::Store {
633 pointer: ptr_b,
634 value: lit,
635 });
636
637 module.entry_points.push(EntryPoint {
638 name: "main".into(),
639 workgroup_size: [1, 1, 1],
640 function: func,
641 });
642
643 let plan = plan_memory(&module);
644
645 let non_zero: Vec<_> = plan
647 .allocations
648 .iter()
649 .filter(|a| a.size_bytes > 0)
650 .collect();
651 assert_eq!(non_zero.len(), 2);
652
653 assert_eq!(plan.peak_bytes, 512);
655 }
656
657 #[test]
658 fn overlapping_lifetimes_no_reuse() {
659 let mut module = Module::default();
661 let arr_ty = f32_array_type(&mut module, 128); let mut func = Function::new("main");
664
665 let lv_a = func.local_variables.append(LocalVariable {
666 name: Some("a".into()),
667 ty: arr_ty,
668 init: None,
669 });
670 let lv_b = func.local_variables.append(LocalVariable {
671 name: Some("b".into()),
672 ty: arr_ty,
673 init: None,
674 });
675
676 let ptr_a = func.expressions.append(Expression::LocalVariable(lv_a));
677 let ptr_b = func.expressions.append(Expression::LocalVariable(lv_b));
678 let lit = func
679 .expressions
680 .append(Expression::Literal(Literal::F32(0.0)));
681
682 func.body.push(Statement::Store {
684 pointer: ptr_a,
685 value: lit,
686 });
687 let load_a = func.expressions.append(Expression::Load { pointer: ptr_a });
689 func.body.push(Statement::Store {
690 pointer: ptr_b,
691 value: load_a,
692 });
693
694 module.entry_points.push(EntryPoint {
695 name: "main".into(),
696 workgroup_size: [1, 1, 1],
697 function: func,
698 });
699
700 let plan = plan_memory(&module);
701
702 let non_zero: Vec<_> = plan
704 .allocations
705 .iter()
706 .filter(|a| a.size_bytes > 0)
707 .collect();
708 assert_eq!(non_zero.len(), 2);
709 assert_eq!(plan.peak_bytes, 1024);
710 }
711
712 #[test]
713 fn peak_memory_less_than_sum_of_all_tensors() {
714 let mut module = Module::default();
717 let arr_ty = f32_array_type(&mut module, 128); let mut func = Function::new("main");
720
721 let lv_a = func.local_variables.append(LocalVariable {
722 name: Some("a".into()),
723 ty: arr_ty,
724 init: None,
725 });
726 let lv_b = func.local_variables.append(LocalVariable {
727 name: Some("b".into()),
728 ty: arr_ty,
729 init: None,
730 });
731 let lv_c = func.local_variables.append(LocalVariable {
732 name: Some("c".into()),
733 ty: arr_ty,
734 init: None,
735 });
736
737 let ptr_a = func.expressions.append(Expression::LocalVariable(lv_a));
738 let ptr_b = func.expressions.append(Expression::LocalVariable(lv_b));
739 let ptr_c = func.expressions.append(Expression::LocalVariable(lv_c));
740 let lit = func
741 .expressions
742 .append(Expression::Literal(Literal::F32(0.0)));
743
744 func.body.push(Statement::Store {
746 pointer: ptr_a,
747 value: lit,
748 });
749 func.body.push(Statement::Store {
750 pointer: ptr_b,
751 value: lit,
752 });
753 func.body.push(Statement::Store {
754 pointer: ptr_c,
755 value: lit,
756 });
757
758 module.entry_points.push(EntryPoint {
759 name: "main".into(),
760 workgroup_size: [1, 1, 1],
761 function: func,
762 });
763
764 let plan = plan_memory(&module);
765
766 let total: usize = plan.allocations.iter().map(|a| a.size_bytes).sum();
767 assert_eq!(total, 1536); assert!(
770 plan.peak_bytes < total,
771 "peak {} should be < total {}",
772 plan.peak_bytes,
773 total
774 );
775 assert_eq!(plan.peak_bytes, 512); }
777
778 #[test]
779 fn dynamic_tensors_get_zero_size() {
780 let mut module = Module::default();
781 let dyn_ty = f32_dynamic_array_type(&mut module);
782
783 module.global_variables.append(GlobalVariable {
784 name: Some("dyn_buf".into()),
785 space: AddressSpace::Storage {
786 access: StorageAccess::LOAD,
787 },
788 binding: Some(ResourceBinding {
789 group: 0,
790 binding: 0,
791 }),
792 ty: dyn_ty,
793 init: None,
794 layout: None,
795 });
796
797 let plan = plan_memory(&module);
798 assert_eq!(plan.peak_bytes, 0);
800 }
801
802 #[test]
803 fn tensor_type_size_computation() {
804 let mut module = Module::default();
805
806 let tensor_ty = module.types.insert(Type {
808 name: None,
809 inner: TypeInner::Tensor {
810 scalar: Scalar::F32,
811 shape: TensorShape::fixed(&[1, 224, 224, 3]),
812 },
813 });
814
815 assert_eq!(type_size_bytes(&module, tensor_ty), 602_112);
816
817 let dyn_tensor_ty = module.types.insert(Type {
819 name: None,
820 inner: TypeInner::Tensor {
821 scalar: Scalar::F32,
822 shape: TensorShape::all_dynamic(4),
823 },
824 });
825 assert_eq!(type_size_bytes(&module, dyn_tensor_ty), 0);
826 }
827
828 #[test]
829 fn memory_plan_display() {
830 let plan = MemoryPlan {
831 allocations: vec![
832 BufferAllocation {
833 tensor_id: TensorId(0),
834 offset: 0,
835 size_bytes: 1024,
836 },
837 BufferAllocation {
838 tensor_id: TensorId(1),
839 offset: 0,
840 size_bytes: 512,
841 },
842 ],
843 peak_bytes: 1024,
844 };
845
846 let text = format!("{plan}");
847 assert!(text.contains("Peak memory: 1024 bytes"));
848 assert!(text.contains("Buffers: 2"));
849 assert!(text.contains("Reuse savings:"));
850 assert!(text.contains("tensor_0"));
851 assert!(text.contains("tensor_1"));
852 }
853
854 #[test]
855 fn memory_planning_pass_runs() {
856 let mut module = Module::default();
857 let pass = MemoryPlanning;
858 assert_eq!(pass.name(), "MemoryPlanning");
859 let changed = pass.run(&mut module);
860 assert!(!changed); }
862
863 #[test]
864 fn input_compute_output_pipeline() {
865 let mut module = Module::default();
868 let arr_ty = f32_array_type(&mut module, 256); let gv_input = module.global_variables.append(GlobalVariable {
871 name: Some("input".into()),
872 space: AddressSpace::Storage {
873 access: StorageAccess::LOAD,
874 },
875 binding: Some(ResourceBinding {
876 group: 0,
877 binding: 0,
878 }),
879 ty: arr_ty,
880 init: None,
881 layout: None,
882 });
883 let gv_output = module.global_variables.append(GlobalVariable {
884 name: Some("output".into()),
885 space: AddressSpace::Storage {
886 access: StorageAccess::STORE,
887 },
888 binding: Some(ResourceBinding {
889 group: 0,
890 binding: 1,
891 }),
892 ty: arr_ty,
893 init: None,
894 layout: None,
895 });
896
897 let mut func = Function::new("main");
898
899 let temp = func.local_variables.append(LocalVariable {
900 name: Some("temp".into()),
901 ty: arr_ty,
902 init: None,
903 });
904
905 let ptr_in = func
906 .expressions
907 .append(Expression::GlobalVariable(gv_input));
908 let ptr_out = func
909 .expressions
910 .append(Expression::GlobalVariable(gv_output));
911 let ptr_temp = func.expressions.append(Expression::LocalVariable(temp));
912 let load_in = func
913 .expressions
914 .append(Expression::Load { pointer: ptr_in });
915 let load_temp = func
916 .expressions
917 .append(Expression::Load { pointer: ptr_temp });
918
919 func.body.push(Statement::Store {
921 pointer: ptr_temp,
922 value: load_in,
923 });
924 func.body.push(Statement::Store {
926 pointer: ptr_out,
927 value: load_temp,
928 });
929
930 module.entry_points.push(EntryPoint {
931 name: "main".into(),
932 workgroup_size: [256, 1, 1],
933 function: func,
934 });
935
936 let plan = plan_memory(&module);
937
938 assert!(
942 plan.peak_bytes <= 2048,
943 "peak {} should be <= 2048 (reuse between input and output)",
944 plan.peak_bytes
945 );
946 assert!(plan.peak_bytes > 0);
947 }
948
949 #[test]
950 fn many_temporaries_reuse() {
951 let mut module = Module::default();
953 let arr_ty = f32_array_type(&mut module, 64); let mut func = Function::new("main");
956 let lit = func
957 .expressions
958 .append(Expression::Literal(Literal::F32(0.0)));
959
960 for i in 0..10 {
961 let lv = func.local_variables.append(LocalVariable {
962 name: Some(format!("temp_{i}")),
963 ty: arr_ty,
964 init: None,
965 });
966 let ptr = func.expressions.append(Expression::LocalVariable(lv));
967 func.body.push(Statement::Store {
968 pointer: ptr,
969 value: lit,
970 });
971 }
972
973 module.entry_points.push(EntryPoint {
974 name: "main".into(),
975 workgroup_size: [1, 1, 1],
976 function: func,
977 });
978
979 let plan = plan_memory(&module);
980
981 let total: usize = plan.allocations.iter().map(|a| a.size_bytes).sum();
982 assert_eq!(total, 2560); assert_eq!(plan.peak_bytes, 256);
985 }
986
987 #[test]
988 fn struct_type_size() {
989 let mut module = Module::default();
990 let struct_ty = module.types.insert(Type {
991 name: Some("Params".into()),
992 inner: TypeInner::Struct {
993 members: vec![],
994 span: 64,
995 },
996 });
997 assert_eq!(type_size_bytes(&module, struct_ty), 64);
998 }
999
1000 #[test]
1001 fn vector_and_matrix_type_sizes() {
1002 let mut module = Module::default();
1003
1004 let vec4_ty = module.types.insert(Type {
1005 name: None,
1006 inner: TypeInner::Vector {
1007 size: VectorSize::Quad,
1008 scalar: Scalar::F32,
1009 },
1010 });
1011 assert_eq!(type_size_bytes(&module, vec4_ty), 16); let mat4x4_ty = module.types.insert(Type {
1014 name: None,
1015 inner: TypeInner::Matrix {
1016 columns: VectorSize::Quad,
1017 rows: VectorSize::Quad,
1018 scalar: Scalar::F32,
1019 },
1020 });
1021 assert_eq!(type_size_bytes(&module, mat4x4_ty), 64); }
1023
1024 #[test]
1027 fn atomic_type_size() {
1028 let mut module = Module::default();
1029 let atomic_ty = module.types.insert(Type {
1030 name: None,
1031 inner: TypeInner::Atomic(Scalar::U32),
1032 });
1033 assert_eq!(type_size_bytes(&module, atomic_ty), 4);
1034 }
1035
1036 #[test]
1039 fn pointer_type_size_is_zero() {
1040 let mut module = Module::default();
1041 let f32_ty = f32_type(&mut module);
1042 let ptr_ty = module.types.insert(Type {
1043 name: None,
1044 inner: TypeInner::Pointer {
1045 base: f32_ty,
1046 space: AddressSpace::Storage {
1047 access: StorageAccess::LOAD,
1048 },
1049 },
1050 });
1051 assert_eq!(type_size_bytes(&module, ptr_ty), 0);
1052 }
1053
1054 #[test]
1057 fn array_with_zero_base_uses_stride() {
1058 let mut module = Module::default();
1059 let f32_ty = f32_type(&mut module);
1061 let ptr_base = module.types.insert(Type {
1062 name: None,
1063 inner: TypeInner::Pointer {
1064 base: f32_ty,
1065 space: AddressSpace::Storage {
1066 access: StorageAccess::LOAD,
1067 },
1068 },
1069 });
1070 let arr_ty = module.types.insert(Type {
1071 name: None,
1072 inner: TypeInner::Array {
1073 base: ptr_base,
1074 size: ArraySize::Constant(10),
1075 stride: 8,
1076 },
1077 });
1078 assert_eq!(type_size_bytes(&module, arr_ty), 80);
1080 }
1081
1082 #[test]
1085 fn array_with_nonzero_base_uses_elem_size() {
1086 let mut module = Module::default();
1087 let arr_ty = f32_array_type(&mut module, 10);
1088 assert_eq!(type_size_bytes(&module, arr_ty), 40);
1090 }
1091
1092 #[test]
1095 fn dynamic_array_size_is_zero() {
1096 let mut module = Module::default();
1097 let dyn_arr_ty = f32_dynamic_array_type(&mut module);
1098 assert_eq!(type_size_bytes(&module, dyn_arr_ty), 0);
1099 }
1100
1101 #[test]
1104 fn scalar_type_size() {
1105 let mut module = Module::default();
1106 let f32_ty = f32_type(&mut module);
1107 assert_eq!(type_size_bytes(&module, f32_ty), 4);
1108 }
1109
1110 #[test]
1113 fn vec2_type_size() {
1114 let mut module = Module::default();
1115 let vec2_ty = module.types.insert(Type {
1116 name: None,
1117 inner: TypeInner::Vector {
1118 size: VectorSize::Bi,
1119 scalar: Scalar::F32,
1120 },
1121 });
1122 assert_eq!(type_size_bytes(&module, vec2_ty), 8); }
1124
1125 #[test]
1128 fn vec3_type_size() {
1129 let mut module = Module::default();
1130 let vec3_ty = module.types.insert(Type {
1131 name: None,
1132 inner: TypeInner::Vector {
1133 size: VectorSize::Tri,
1134 scalar: Scalar::F32,
1135 },
1136 });
1137 assert_eq!(type_size_bytes(&module, vec3_ty), 12); }
1139
1140 #[test]
1143 fn matrix_2x3_type_size() {
1144 let mut module = Module::default();
1145 let mat_ty = module.types.insert(Type {
1146 name: None,
1147 inner: TypeInner::Matrix {
1148 columns: VectorSize::Bi,
1149 rows: VectorSize::Tri,
1150 scalar: Scalar::F32,
1151 },
1152 });
1153 assert_eq!(type_size_bytes(&module, mat_ty), 24); }
1155
1156 #[test]
1159 fn greedy_allocate_empty() {
1160 let intervals = HashMap::new();
1161 let plan = greedy_allocate(&intervals);
1162 assert_eq!(plan.allocations.len(), 0);
1163 assert_eq!(plan.peak_bytes, 0);
1164 }
1165
1166 #[test]
1169 fn greedy_allocate_zero_size_tensor() {
1170 let mut intervals = HashMap::new();
1171 intervals.insert(
1172 TensorId(0),
1173 LiveInterval {
1174 start: 0,
1175 end: 5,
1176 size_bytes: 0,
1177 },
1178 );
1179 let plan = greedy_allocate(&intervals);
1180 assert_eq!(plan.allocations.len(), 1);
1181 assert_eq!(plan.allocations[0].size_bytes, 0);
1182 assert_eq!(plan.peak_bytes, 0);
1183 }
1184
1185 #[test]
1188 fn greedy_allocate_gap_finding() {
1189 let mut intervals = HashMap::new();
1194 intervals.insert(
1195 TensorId(0),
1196 LiveInterval {
1197 start: 0,
1198 end: 3,
1199 size_bytes: 100,
1200 },
1201 );
1202 intervals.insert(
1203 TensorId(1),
1204 LiveInterval {
1205 start: 0,
1206 end: 3,
1207 size_bytes: 50,
1208 },
1209 );
1210 intervals.insert(
1211 TensorId(2),
1212 LiveInterval {
1213 start: 0,
1214 end: 3,
1215 size_bytes: 100,
1216 },
1217 );
1218 intervals.insert(
1219 TensorId(3),
1220 LiveInterval {
1221 start: 5,
1222 end: 6,
1223 size_bytes: 50,
1224 },
1225 );
1226
1227 let plan = greedy_allocate(&intervals);
1228 assert_eq!(plan.allocations.len(), 4);
1229 assert_eq!(plan.peak_bytes, 250);
1232 }
1233
1234 #[test]
1237 fn greedy_allocate_size_tiebreak() {
1238 let mut intervals = HashMap::new();
1240 intervals.insert(
1241 TensorId(0),
1242 LiveInterval {
1243 start: 0,
1244 end: 0,
1245 size_bytes: 50,
1246 },
1247 );
1248 intervals.insert(
1249 TensorId(1),
1250 LiveInterval {
1251 start: 0,
1252 end: 0,
1253 size_bytes: 200,
1254 },
1255 );
1256
1257 let plan = greedy_allocate(&intervals);
1258 assert_eq!(plan.allocations.len(), 2);
1259 assert_eq!(plan.peak_bytes, 250);
1261
1262 let large_alloc = plan
1264 .allocations
1265 .iter()
1266 .find(|a| a.size_bytes == 200)
1267 .unwrap();
1268 let small_alloc = plan
1269 .allocations
1270 .iter()
1271 .find(|a| a.size_bytes == 50)
1272 .unwrap();
1273 assert!(
1274 large_alloc.offset < small_alloc.offset,
1275 "larger tensor should have lower offset"
1276 );
1277 }
1278
1279 #[test]
1282 fn merge_intervals_extends_existing() {
1283 let mut dst = HashMap::new();
1284 dst.insert(
1285 TensorId(0),
1286 LiveInterval {
1287 start: 2,
1288 end: 5,
1289 size_bytes: 100,
1290 },
1291 );
1292
1293 let mut src = HashMap::new();
1294 src.insert(
1295 TensorId(0),
1296 LiveInterval {
1297 start: 1,
1298 end: 3,
1299 size_bytes: 100,
1300 },
1301 );
1302 src.insert(
1303 TensorId(1),
1304 LiveInterval {
1305 start: 0,
1306 end: 4,
1307 size_bytes: 200,
1308 },
1309 );
1310
1311 merge_intervals(&mut dst, &src);
1312
1313 let interval_0 = dst.get(&TensorId(0)).unwrap();
1315 assert_eq!(interval_0.start, 1);
1316 assert_eq!(interval_0.end, 5);
1317
1318 let interval_1 = dst.get(&TensorId(1)).unwrap();
1320 assert_eq!(interval_1.start, 0);
1321 assert_eq!(interval_1.end, 4);
1322 assert_eq!(interval_1.size_bytes, 200);
1323 }
1324
1325 #[test]
1326 fn merge_intervals_end_extended() {
1327 let mut dst = HashMap::new();
1328 dst.insert(
1329 TensorId(0),
1330 LiveInterval {
1331 start: 0,
1332 end: 3,
1333 size_bytes: 100,
1334 },
1335 );
1336
1337 let mut src = HashMap::new();
1338 src.insert(
1339 TensorId(0),
1340 LiveInterval {
1341 start: 2,
1342 end: 10,
1343 size_bytes: 100,
1344 },
1345 );
1346
1347 merge_intervals(&mut dst, &src);
1348 let interval = dst.get(&TensorId(0)).unwrap();
1349 assert_eq!(interval.start, 0);
1350 assert_eq!(interval.end, 10);
1351 }
1352
1353 #[test]
1356 fn plan_memory_with_helper_function() {
1357 let mut module = Module::default();
1358 let arr_ty = f32_array_type(&mut module, 64); let gv = module.global_variables.append(GlobalVariable {
1361 name: Some("buf".into()),
1362 space: AddressSpace::Storage {
1363 access: StorageAccess::LOAD | StorageAccess::STORE,
1364 },
1365 binding: Some(ResourceBinding {
1366 group: 0,
1367 binding: 0,
1368 }),
1369 ty: arr_ty,
1370 init: None,
1371 layout: None,
1372 });
1373
1374 let mut helper = Function::new("helper");
1376 let ptr = helper.expressions.append(Expression::GlobalVariable(gv));
1377 let lit = helper
1378 .expressions
1379 .append(Expression::Literal(Literal::F32(0.0)));
1380 helper.body.push(Statement::Store {
1381 pointer: ptr,
1382 value: lit,
1383 });
1384 module.functions.append(helper);
1385
1386 let mut ep_func = Function::new("main");
1388 let ptr2 = ep_func.expressions.append(Expression::GlobalVariable(gv));
1389 let lit2 = ep_func
1390 .expressions
1391 .append(Expression::Literal(Literal::F32(1.0)));
1392 ep_func.body.push(Statement::Store {
1393 pointer: ptr2,
1394 value: lit2,
1395 });
1396 module.entry_points.push(EntryPoint {
1397 name: "main".into(),
1398 workgroup_size: [1, 1, 1],
1399 function: ep_func,
1400 });
1401
1402 let plan = plan_memory(&module);
1403 assert_ne!(plan.allocations.len(), 0);
1405 assert!(plan.peak_bytes >= 256);
1406 }
1407
1408 #[test]
1411 fn unreferenced_globals_get_allocated() {
1412 let mut module = Module::default();
1413 let arr_ty = f32_array_type(&mut module, 128); module.global_variables.append(GlobalVariable {
1417 name: Some("unused".into()),
1418 space: AddressSpace::Storage {
1419 access: StorageAccess::LOAD,
1420 },
1421 binding: Some(ResourceBinding {
1422 group: 0,
1423 binding: 0,
1424 }),
1425 ty: arr_ty,
1426 init: None,
1427 layout: None,
1428 });
1429
1430 let plan = plan_memory(&module);
1431 let non_zero: Vec<_> = plan
1433 .allocations
1434 .iter()
1435 .filter(|a| a.size_bytes > 0)
1436 .collect();
1437 assert_eq!(non_zero.len(), 1);
1438 assert_eq!(non_zero[0].size_bytes, 512);
1439 assert_eq!(plan.peak_bytes, 512);
1440 }
1441
1442 #[test]
1445 fn lifetime_analysis_with_if_statement() {
1446 let mut module = Module::default();
1447 let arr_ty = f32_array_type(&mut module, 64); let gv = module.global_variables.append(GlobalVariable {
1450 name: Some("buf".into()),
1451 space: AddressSpace::Storage {
1452 access: StorageAccess::LOAD | StorageAccess::STORE,
1453 },
1454 binding: Some(ResourceBinding {
1455 group: 0,
1456 binding: 0,
1457 }),
1458 ty: arr_ty,
1459 init: None,
1460 layout: None,
1461 });
1462
1463 let mut func = Function::new("main");
1464 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
1465 let cond = func
1466 .expressions
1467 .append(Expression::Literal(Literal::Bool(true)));
1468 let val = func
1469 .expressions
1470 .append(Expression::Literal(Literal::F32(1.0)));
1471
1472 func.body.push(Statement::If {
1474 condition: cond,
1475 accept: vec![Statement::Store {
1476 pointer: ptr,
1477 value: val,
1478 }],
1479 reject: vec![],
1480 });
1481
1482 module.entry_points.push(EntryPoint {
1483 name: "main".into(),
1484 workgroup_size: [1, 1, 1],
1485 function: func,
1486 });
1487
1488 let plan = plan_memory(&module);
1489 assert!(plan.peak_bytes >= 256);
1490 }
1491
1492 #[test]
1495 fn lifetime_analysis_with_loop_statement() {
1496 let mut module = Module::default();
1497 let arr_ty = f32_array_type(&mut module, 64); let gv = module.global_variables.append(GlobalVariable {
1500 name: Some("buf".into()),
1501 space: AddressSpace::Storage {
1502 access: StorageAccess::LOAD | StorageAccess::STORE,
1503 },
1504 binding: Some(ResourceBinding {
1505 group: 0,
1506 binding: 0,
1507 }),
1508 ty: arr_ty,
1509 init: None,
1510 layout: None,
1511 });
1512
1513 let mut func = Function::new("main");
1514 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
1515 let val = func
1516 .expressions
1517 .append(Expression::Literal(Literal::F32(1.0)));
1518 let break_cond = func
1519 .expressions
1520 .append(Expression::Literal(Literal::Bool(false)));
1521
1522 func.body.push(Statement::Loop {
1524 body: vec![Statement::Store {
1525 pointer: ptr,
1526 value: val,
1527 }],
1528 continuing: vec![Statement::Store {
1529 pointer: ptr,
1530 value: val,
1531 }],
1532 break_if: Some(break_cond),
1533 });
1534
1535 module.entry_points.push(EntryPoint {
1536 name: "main".into(),
1537 workgroup_size: [1, 1, 1],
1538 function: func,
1539 });
1540
1541 let plan = plan_memory(&module);
1542 assert!(plan.peak_bytes >= 256);
1543 }
1544
1545 #[test]
1548 fn lifetime_analysis_with_call_statement() {
1549 let mut module = Module::default();
1550 let arr_ty = f32_array_type(&mut module, 64); let gv = module.global_variables.append(GlobalVariable {
1553 name: Some("buf".into()),
1554 space: AddressSpace::Storage {
1555 access: StorageAccess::LOAD | StorageAccess::STORE,
1556 },
1557 binding: Some(ResourceBinding {
1558 group: 0,
1559 binding: 0,
1560 }),
1561 ty: arr_ty,
1562 init: None,
1563 layout: None,
1564 });
1565
1566 let helper = Function::new("helper");
1568 let helper_handle = module.functions.append(helper);
1569
1570 let mut func = Function::new("main");
1571 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
1572 let result_expr = func
1573 .expressions
1574 .append(Expression::Literal(Literal::F32(0.0)));
1575
1576 func.body.push(Statement::Call {
1577 function: helper_handle,
1578 arguments: vec![ptr],
1579 result: Some(result_expr),
1580 });
1581
1582 module.entry_points.push(EntryPoint {
1583 name: "main".into(),
1584 workgroup_size: [1, 1, 1],
1585 function: func,
1586 });
1587
1588 let plan = plan_memory(&module);
1589 assert!(plan.peak_bytes >= 256);
1590 }
1591
1592 #[test]
1595 fn lifetime_analysis_with_atomic_statement() {
1596 let mut module = Module::default();
1597 let u32_ty = module.types.insert(Type {
1598 name: None,
1599 inner: TypeInner::Scalar(Scalar::U32),
1600 });
1601
1602 let gv = module.global_variables.append(GlobalVariable {
1603 name: Some("counter".into()),
1604 space: AddressSpace::Storage {
1605 access: StorageAccess::LOAD | StorageAccess::STORE,
1606 },
1607 binding: Some(ResourceBinding {
1608 group: 0,
1609 binding: 0,
1610 }),
1611 ty: u32_ty,
1612 init: None,
1613 layout: None,
1614 });
1615
1616 let mut func = Function::new("main");
1617 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
1618 let val = func
1619 .expressions
1620 .append(Expression::Literal(Literal::U32(1)));
1621 let result_expr = func
1622 .expressions
1623 .append(Expression::Literal(Literal::U32(0)));
1624
1625 func.body.push(Statement::Atomic {
1626 pointer: ptr,
1627 fun: AtomicFunction::Add,
1628 value: val,
1629 result: Some(result_expr),
1630 });
1631
1632 module.entry_points.push(EntryPoint {
1633 name: "main".into(),
1634 workgroup_size: [1, 1, 1],
1635 function: func,
1636 });
1637
1638 let plan = plan_memory(&module);
1639 assert!(plan.peak_bytes >= 4);
1641 }
1642
1643 #[test]
1646 fn lifetime_analysis_with_atomic_exchange_compare() {
1647 let mut module = Module::default();
1648 let u32_ty = module.types.insert(Type {
1649 name: None,
1650 inner: TypeInner::Scalar(Scalar::U32),
1651 });
1652
1653 let gv = module.global_variables.append(GlobalVariable {
1654 name: Some("counter".into()),
1655 space: AddressSpace::Storage {
1656 access: StorageAccess::LOAD | StorageAccess::STORE,
1657 },
1658 binding: Some(ResourceBinding {
1659 group: 0,
1660 binding: 0,
1661 }),
1662 ty: u32_ty,
1663 init: None,
1664 layout: None,
1665 });
1666
1667 let mut func = Function::new("main");
1668 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
1669 let val = func
1670 .expressions
1671 .append(Expression::Literal(Literal::U32(1)));
1672 let cmp = func
1673 .expressions
1674 .append(Expression::Literal(Literal::U32(0)));
1675
1676 func.body.push(Statement::Atomic {
1677 pointer: ptr,
1678 fun: AtomicFunction::Exchange { compare: Some(cmp) },
1679 value: val,
1680 result: None,
1681 });
1682
1683 module.entry_points.push(EntryPoint {
1684 name: "main".into(),
1685 workgroup_size: [1, 1, 1],
1686 function: func,
1687 });
1688
1689 let plan = plan_memory(&module);
1690 assert!(plan.peak_bytes >= 4);
1691 }
1692
1693 #[test]
1696 fn lifetime_analysis_with_return_value() {
1697 let mut module = Module::default();
1698 let arr_ty = f32_array_type(&mut module, 64); let gv = module.global_variables.append(GlobalVariable {
1701 name: Some("buf".into()),
1702 space: AddressSpace::Storage {
1703 access: StorageAccess::LOAD,
1704 },
1705 binding: Some(ResourceBinding {
1706 group: 0,
1707 binding: 0,
1708 }),
1709 ty: arr_ty,
1710 init: None,
1711 layout: None,
1712 });
1713
1714 let mut func = Function::new("main");
1715 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
1716 func.body.push(Statement::Return { value: Some(ptr) });
1717
1718 module.entry_points.push(EntryPoint {
1719 name: "main".into(),
1720 workgroup_size: [1, 1, 1],
1721 function: func,
1722 });
1723
1724 let plan = plan_memory(&module);
1725 assert!(plan.peak_bytes >= 256);
1726 }
1727
1728 #[test]
1731 fn lifetime_analysis_break_continue_no_effect() {
1732 let mut module = Module::default();
1733 let mut func = Function::new("main");
1734 func.body.push(Statement::Break);
1735 func.body.push(Statement::Continue);
1736
1737 module.entry_points.push(EntryPoint {
1738 name: "main".into(),
1739 workgroup_size: [1, 1, 1],
1740 function: func,
1741 });
1742
1743 let plan = plan_memory(&module);
1744 assert_eq!(plan.allocations.len(), 0);
1745 assert_eq!(plan.peak_bytes, 0);
1746 }
1747
1748 #[test]
1751 fn lifetime_analysis_barrier_no_effect() {
1752 let mut module = Module::default();
1753 let mut func = Function::new("main");
1754 func.body.push(Statement::Barrier(Barrier::STORAGE));
1755
1756 module.entry_points.push(EntryPoint {
1757 name: "main".into(),
1758 workgroup_size: [1, 1, 1],
1759 function: func,
1760 });
1761
1762 let plan = plan_memory(&module);
1763 assert_eq!(plan.allocations.len(), 0);
1764 assert_eq!(plan.peak_bytes, 0);
1765 }
1766
1767 #[test]
1770 fn lifetime_analysis_with_emit() {
1771 let mut module = Module::default();
1772 let arr_ty = f32_array_type(&mut module, 64); let gv = module.global_variables.append(GlobalVariable {
1775 name: Some("buf".into()),
1776 space: AddressSpace::Storage {
1777 access: StorageAccess::LOAD,
1778 },
1779 binding: Some(ResourceBinding {
1780 group: 0,
1781 binding: 0,
1782 }),
1783 ty: arr_ty,
1784 init: None,
1785 layout: None,
1786 });
1787
1788 let mut func = Function::new("main");
1789 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
1790 let _load = func.expressions.append(Expression::Load { pointer: ptr });
1791
1792 let range = Range::from_index_range(0..func.expressions.len() as u32);
1794 func.body.push(Statement::Emit(range));
1795
1796 module.entry_points.push(EntryPoint {
1797 name: "main".into(),
1798 workgroup_size: [1, 1, 1],
1799 function: func,
1800 });
1801
1802 let plan = plan_memory(&module);
1803 assert!(plan.peak_bytes >= 256);
1804 }
1805
1806 #[test]
1809 fn access_index_resolves_to_global() {
1810 let mut module = Module::default();
1811 let arr_ty = f32_array_type(&mut module, 64); let gv = module.global_variables.append(GlobalVariable {
1814 name: Some("buf".into()),
1815 space: AddressSpace::Storage {
1816 access: StorageAccess::LOAD | StorageAccess::STORE,
1817 },
1818 binding: Some(ResourceBinding {
1819 group: 0,
1820 binding: 0,
1821 }),
1822 ty: arr_ty,
1823 init: None,
1824 layout: None,
1825 });
1826
1827 let mut func = Function::new("main");
1828 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
1829 let access_idx = func.expressions.append(Expression::AccessIndex {
1830 base: ptr,
1831 index: 0,
1832 });
1833 let val = func
1834 .expressions
1835 .append(Expression::Literal(Literal::F32(1.0)));
1836
1837 func.body.push(Statement::Store {
1839 pointer: access_idx,
1840 value: val,
1841 });
1842
1843 module.entry_points.push(EntryPoint {
1844 name: "main".into(),
1845 workgroup_size: [1, 1, 1],
1846 function: func,
1847 });
1848
1849 let plan = plan_memory(&module);
1850 assert!(plan.peak_bytes >= 256);
1851 }
1852
1853 #[test]
1856 fn access_resolves_to_global() {
1857 let mut module = Module::default();
1858 let arr_ty = f32_array_type(&mut module, 64); let gv = module.global_variables.append(GlobalVariable {
1861 name: Some("buf".into()),
1862 space: AddressSpace::Storage {
1863 access: StorageAccess::LOAD | StorageAccess::STORE,
1864 },
1865 binding: Some(ResourceBinding {
1866 group: 0,
1867 binding: 0,
1868 }),
1869 ty: arr_ty,
1870 init: None,
1871 layout: None,
1872 });
1873
1874 let mut func = Function::new("main");
1875 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
1876 let idx = func
1877 .expressions
1878 .append(Expression::Literal(Literal::U32(0)));
1879 let access = func.expressions.append(Expression::Access {
1880 base: ptr,
1881 index: idx,
1882 });
1883 let val = func
1884 .expressions
1885 .append(Expression::Literal(Literal::F32(1.0)));
1886
1887 func.body.push(Statement::Store {
1889 pointer: access,
1890 value: val,
1891 });
1892
1893 module.entry_points.push(EntryPoint {
1894 name: "main".into(),
1895 workgroup_size: [1, 1, 1],
1896 function: func,
1897 });
1898
1899 let plan = plan_memory(&module);
1900 assert!(plan.peak_bytes >= 256);
1901 }
1902
1903 #[test]
1906 fn local_variable_in_helper_function() {
1907 let mut module = Module::default();
1908 let arr_ty = f32_array_type(&mut module, 64); let mut helper = Function::new("helper");
1911 let lv = helper.local_variables.append(LocalVariable {
1912 name: Some("temp".into()),
1913 ty: arr_ty,
1914 init: None,
1915 });
1916 let ptr = helper.expressions.append(Expression::LocalVariable(lv));
1917 let lit = helper
1918 .expressions
1919 .append(Expression::Literal(Literal::F32(0.0)));
1920 helper.body.push(Statement::Store {
1921 pointer: ptr,
1922 value: lit,
1923 });
1924 module.functions.append(helper);
1925
1926 let plan = plan_memory(&module);
1927 let non_zero: Vec<_> = plan
1929 .allocations
1930 .iter()
1931 .filter(|a| a.size_bytes > 0)
1932 .collect();
1933 assert_eq!(non_zero.len(), 1);
1934 assert_eq!(non_zero[0].size_bytes, 256);
1935 }
1936
1937 #[test]
1940 fn plan_memory_multiple_entry_points() {
1941 let mut module = Module::default();
1942 let arr_ty = f32_array_type(&mut module, 128); let gv = module.global_variables.append(GlobalVariable {
1945 name: Some("shared".into()),
1946 space: AddressSpace::Storage {
1947 access: StorageAccess::LOAD | StorageAccess::STORE,
1948 },
1949 binding: Some(ResourceBinding {
1950 group: 0,
1951 binding: 0,
1952 }),
1953 ty: arr_ty,
1954 init: None,
1955 layout: None,
1956 });
1957
1958 let mut func1 = Function::new("ep1");
1960 let ptr1 = func1.expressions.append(Expression::GlobalVariable(gv));
1961 let lit1 = func1
1962 .expressions
1963 .append(Expression::Literal(Literal::F32(1.0)));
1964 func1.body.push(Statement::Store {
1965 pointer: ptr1,
1966 value: lit1,
1967 });
1968 module.entry_points.push(EntryPoint {
1969 name: "ep1".into(),
1970 workgroup_size: [1, 1, 1],
1971 function: func1,
1972 });
1973
1974 let mut func2 = Function::new("ep2");
1976 let ptr2 = func2.expressions.append(Expression::GlobalVariable(gv));
1977 let lit2 = func2
1978 .expressions
1979 .append(Expression::Literal(Literal::F32(2.0)));
1980 func2.body.push(Statement::Store {
1981 pointer: ptr2,
1982 value: lit2,
1983 });
1984 module.entry_points.push(EntryPoint {
1985 name: "ep2".into(),
1986 workgroup_size: [1, 1, 1],
1987 function: func2,
1988 });
1989
1990 let plan = plan_memory(&module);
1991 assert_eq!(plan.peak_bytes, 512);
1993 }
1994
1995 #[test]
1998 fn memory_planning_pass_with_content() {
1999 let mut module = Module::default();
2000 let arr_ty = f32_array_type(&mut module, 128); let gv = module.global_variables.append(GlobalVariable {
2003 name: Some("buf".into()),
2004 space: AddressSpace::Storage {
2005 access: StorageAccess::LOAD | StorageAccess::STORE,
2006 },
2007 binding: Some(ResourceBinding {
2008 group: 0,
2009 binding: 0,
2010 }),
2011 ty: arr_ty,
2012 init: None,
2013 layout: None,
2014 });
2015
2016 let mut func = Function::new("main");
2017 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
2018 let lit = func
2019 .expressions
2020 .append(Expression::Literal(Literal::F32(0.0)));
2021 func.body.push(Statement::Store {
2022 pointer: ptr,
2023 value: lit,
2024 });
2025 module.entry_points.push(EntryPoint {
2026 name: "main".into(),
2027 workgroup_size: [1, 1, 1],
2028 function: func,
2029 });
2030
2031 let pass = MemoryPlanning;
2032 let changed = pass.run(&mut module);
2033 assert!(!changed);
2034 }
2035
2036 #[test]
2039 fn memory_plan_display_empty() {
2040 let plan = MemoryPlan::default();
2041 let text = format!("{plan}");
2042 assert!(text.contains("Peak memory: 0 bytes"));
2043 assert!(text.contains("Buffers: 0"));
2044 }
2045
2046 #[test]
2049 fn memory_plan_display_no_reuse_line_for_zero_total() {
2050 let plan = MemoryPlan {
2051 allocations: vec![BufferAllocation {
2052 tensor_id: TensorId(0),
2053 offset: 0,
2054 size_bytes: 0,
2055 }],
2056 peak_bytes: 0,
2057 };
2058 let text = format!("{plan}");
2059 assert!(text.contains("Buffers: 1"));
2060 assert!(!text.contains("Reuse savings:"));
2062 }
2063
2064 #[test]
2067 fn if_with_reject_branch_references() {
2068 let mut module = Module::default();
2069 let arr_ty = f32_array_type(&mut module, 64); let gv_a = module.global_variables.append(GlobalVariable {
2072 name: Some("a".into()),
2073 space: AddressSpace::Storage {
2074 access: StorageAccess::LOAD | StorageAccess::STORE,
2075 },
2076 binding: Some(ResourceBinding {
2077 group: 0,
2078 binding: 0,
2079 }),
2080 ty: arr_ty,
2081 init: None,
2082 layout: None,
2083 });
2084 let gv_b = module.global_variables.append(GlobalVariable {
2085 name: Some("b".into()),
2086 space: AddressSpace::Storage {
2087 access: StorageAccess::LOAD | StorageAccess::STORE,
2088 },
2089 binding: Some(ResourceBinding {
2090 group: 0,
2091 binding: 1,
2092 }),
2093 ty: arr_ty,
2094 init: None,
2095 layout: None,
2096 });
2097
2098 let mut func = Function::new("main");
2099 let ptr_a = func.expressions.append(Expression::GlobalVariable(gv_a));
2100 let ptr_b = func.expressions.append(Expression::GlobalVariable(gv_b));
2101 let cond = func
2102 .expressions
2103 .append(Expression::Literal(Literal::Bool(true)));
2104 let val = func
2105 .expressions
2106 .append(Expression::Literal(Literal::F32(1.0)));
2107
2108 func.body.push(Statement::If {
2109 condition: cond,
2110 accept: vec![Statement::Store {
2111 pointer: ptr_a,
2112 value: val,
2113 }],
2114 reject: vec![Statement::Store {
2115 pointer: ptr_b,
2116 value: val,
2117 }],
2118 });
2119
2120 module.entry_points.push(EntryPoint {
2121 name: "main".into(),
2122 workgroup_size: [1, 1, 1],
2123 function: func,
2124 });
2125
2126 let plan = plan_memory(&module);
2127 let non_zero: Vec<_> = plan
2129 .allocations
2130 .iter()
2131 .filter(|a| a.size_bytes > 0)
2132 .collect();
2133 assert_eq!(non_zero.len(), 2);
2134 }
2135
2136 #[test]
2139 fn loop_break_if_references_global() {
2140 let mut module = Module::default();
2141 let u32_ty = module.types.insert(Type {
2142 name: None,
2143 inner: TypeInner::Scalar(Scalar::U32),
2144 });
2145
2146 let gv = module.global_variables.append(GlobalVariable {
2147 name: Some("flag".into()),
2148 space: AddressSpace::Storage {
2149 access: StorageAccess::LOAD,
2150 },
2151 binding: Some(ResourceBinding {
2152 group: 0,
2153 binding: 0,
2154 }),
2155 ty: u32_ty,
2156 init: None,
2157 layout: None,
2158 });
2159
2160 let mut func = Function::new("main");
2161 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
2162
2163 func.body.push(Statement::Loop {
2164 body: vec![],
2165 continuing: vec![],
2166 break_if: Some(ptr),
2167 });
2168
2169 module.entry_points.push(EntryPoint {
2170 name: "main".into(),
2171 workgroup_size: [1, 1, 1],
2172 function: func,
2173 });
2174
2175 let plan = plan_memory(&module);
2176 assert!(plan.peak_bytes >= 4);
2177 }
2178
2179 #[test]
2182 fn resolve_expr_non_variable_returns_none() {
2183 let mut module = Module::default();
2184 let mut func = Function::new("main");
2185 let lit = func
2186 .expressions
2187 .append(Expression::Literal(Literal::F32(1.0)));
2188
2189 func.body.push(Statement::Store {
2191 pointer: lit,
2192 value: lit,
2193 });
2194
2195 module.entry_points.push(EntryPoint {
2196 name: "main".into(),
2197 workgroup_size: [1, 1, 1],
2198 function: func,
2199 });
2200
2201 let plan = plan_memory(&module);
2202 assert_eq!(plan.peak_bytes, 0);
2204 }
2205
2206 #[test]
2209 fn return_no_value_no_effect_on_lifetimes() {
2210 let mut module = Module::default();
2211 let mut func = Function::new("main");
2212 func.body.push(Statement::Return { value: None });
2213
2214 module.entry_points.push(EntryPoint {
2215 name: "main".into(),
2216 workgroup_size: [1, 1, 1],
2217 function: func,
2218 });
2219
2220 let plan = plan_memory(&module);
2221 assert_eq!(plan.peak_bytes, 0);
2222 }
2223
2224 #[test]
2227 fn single_store_function() {
2228 let mut module = Module::default();
2229 let f32_ty = f32_type(&mut module);
2230
2231 let gv = module.global_variables.append(GlobalVariable {
2232 name: Some("x".into()),
2233 space: AddressSpace::Storage {
2234 access: StorageAccess::STORE,
2235 },
2236 binding: Some(ResourceBinding {
2237 group: 0,
2238 binding: 0,
2239 }),
2240 ty: f32_ty,
2241 init: None,
2242 layout: None,
2243 });
2244
2245 let mut func = Function::new("main");
2246 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
2247 let val = func
2248 .expressions
2249 .append(Expression::Literal(Literal::F32(42.0)));
2250 func.body.push(Statement::Store {
2251 pointer: ptr,
2252 value: val,
2253 });
2254
2255 module.entry_points.push(EntryPoint {
2256 name: "main".into(),
2257 workgroup_size: [1, 1, 1],
2258 function: func,
2259 });
2260
2261 let plan = plan_memory(&module);
2262 assert_eq!(plan.peak_bytes, 4); }
2264
2265 #[test]
2268 fn call_no_result_lifetime() {
2269 let mut module = Module::default();
2270 let arr_ty = f32_array_type(&mut module, 64); let gv = module.global_variables.append(GlobalVariable {
2273 name: Some("buf".into()),
2274 space: AddressSpace::Storage {
2275 access: StorageAccess::LOAD,
2276 },
2277 binding: Some(ResourceBinding {
2278 group: 0,
2279 binding: 0,
2280 }),
2281 ty: arr_ty,
2282 init: None,
2283 layout: None,
2284 });
2285
2286 let helper = Function::new("helper");
2287 let helper_handle = module.functions.append(helper);
2288
2289 let mut func = Function::new("main");
2290 let ptr = func.expressions.append(Expression::GlobalVariable(gv));
2291
2292 func.body.push(Statement::Call {
2293 function: helper_handle,
2294 arguments: vec![ptr],
2295 result: None,
2296 });
2297
2298 module.entry_points.push(EntryPoint {
2299 name: "main".into(),
2300 workgroup_size: [1, 1, 1],
2301 function: func,
2302 });
2303
2304 let plan = plan_memory(&module);
2305 assert!(plan.peak_bytes >= 256);
2306 }
2307
2308 #[test]
2311 fn lifetime_interval_start_updated_backwards() {
2312 let mut module = Module::default();
2315 let arr_ty = f32_array_type(&mut module, 64); let gv_a = module.global_variables.append(GlobalVariable {
2318 name: Some("a".into()),
2319 space: AddressSpace::Storage {
2320 access: StorageAccess::LOAD | StorageAccess::STORE,
2321 },
2322 binding: Some(ResourceBinding {
2323 group: 0,
2324 binding: 0,
2325 }),
2326 ty: arr_ty,
2327 init: None,
2328 layout: None,
2329 });
2330 let gv_b = module.global_variables.append(GlobalVariable {
2331 name: Some("b".into()),
2332 space: AddressSpace::Storage {
2333 access: StorageAccess::LOAD | StorageAccess::STORE,
2334 },
2335 binding: Some(ResourceBinding {
2336 group: 0,
2337 binding: 1,
2338 }),
2339 ty: arr_ty,
2340 init: None,
2341 layout: None,
2342 });
2343
2344 let mut func = Function::new("main");
2345 let ptr_a = func.expressions.append(Expression::GlobalVariable(gv_a));
2346 let ptr_b = func.expressions.append(Expression::GlobalVariable(gv_b));
2347 let load_a = func.expressions.append(Expression::Load { pointer: ptr_a });
2348 let val = func
2349 .expressions
2350 .append(Expression::Literal(Literal::F32(1.0)));
2351
2352 func.body.push(Statement::Store {
2354 pointer: ptr_a,
2355 value: val,
2356 });
2357 func.body.push(Statement::Store {
2359 pointer: ptr_b,
2360 value: val,
2361 });
2362 func.body.push(Statement::Store {
2364 pointer: ptr_b,
2365 value: load_a,
2366 });
2367
2368 module.entry_points.push(EntryPoint {
2369 name: "main".into(),
2370 workgroup_size: [1, 1, 1],
2371 function: func,
2372 });
2373
2374 let plan = plan_memory(&module);
2375 assert_eq!(plan.peak_bytes, 512);
2377 }
2378}