1use crate::ir::{IrModule, IrOpcode, TypeKind};
7use crate::{JitError, JitResult};
8use indexmap::IndexMap;
9use torsh_core::{DType, Shape};
10
11#[derive(Debug, Clone)]
13pub struct TypeSpecializer {
14 specializations: IndexMap<SpecializationKey, SpecializedFunction>,
16
17 stats: SpecializationStats,
19
20 config: SpecializationConfig,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Hash)]
26pub struct SpecializationKey {
27 pub function_name: String,
29
30 pub param_types: Vec<SpecializedType>,
32
33 pub return_type: Option<SpecializedType>,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39pub struct SpecializedType {
40 pub base_type: TypeKind,
42
43 pub shape: Option<Vec<usize>>,
45
46 pub constant_value: Option<ConstantValue>,
48
49 pub layout_hints: LayoutHints,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Hash)]
55pub enum ConstantValue {
56 Int(i64),
57 Float(u64), Bool(bool),
59 Shape(Vec<usize>),
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
64pub struct LayoutHints {
65 pub alignment: Option<usize>,
67
68 pub contiguous: bool,
70
71 pub layout: Option<DataLayout>,
73
74 pub locality: LocalityHint,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Hash)]
80pub enum DataLayout {
81 RowMajor,
82 ColumnMajor,
83 Packed,
84 Strided { strides: Vec<usize> },
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
89pub enum LocalityHint {
90 #[default]
91 None,
92 Temporal, NonTemporal, Streaming, }
96
97#[derive(Debug, Clone)]
99pub struct SpecializedFunction {
100 pub key: SpecializationKey,
102
103 pub module: IrModule,
105
106 pub perf_info: PerformanceInfo,
108
109 pub usage_count: usize,
111
112 pub compile_time_ns: u64,
114}
115
116#[derive(Debug, Clone, Default)]
118pub struct PerformanceInfo {
119 pub estimated_exec_time_ns: u64,
121
122 pub memory_bandwidth: u64,
124
125 pub arithmetic_intensity: f64,
127
128 pub register_pressure: u8,
130
131 pub vectorization_factor: usize,
133}
134
135#[derive(Debug, Clone)]
137pub struct SpecializationConfig {
138 pub max_specializations_per_function: usize,
140
141 pub min_usage_threshold: usize,
143
144 pub enable_shape_specialization: bool,
146
147 pub enable_constant_specialization: bool,
149
150 pub enable_layout_specialization: bool,
152
153 pub min_performance_improvement: f64,
155
156 pub max_code_size_increase: f64,
158}
159
160#[derive(Debug, Clone, Default)]
162pub struct SpecializationStats {
163 pub total_specializations: usize,
165
166 pub cache_hits: usize,
168
169 pub cache_misses: usize,
171
172 pub avg_compilation_time_ns: u64,
174
175 pub total_speedup: f64,
177
178 pub code_size_overhead: f64,
180}
181
182impl Default for SpecializationConfig {
183 fn default() -> Self {
184 Self {
185 max_specializations_per_function: 16,
186 min_usage_threshold: 3,
187 enable_shape_specialization: true,
188 enable_constant_specialization: true,
189 enable_layout_specialization: true,
190 min_performance_improvement: 1.2, max_code_size_increase: 2.0, }
193 }
194}
195
196impl TypeSpecializer {
197 pub fn new(config: SpecializationConfig) -> Self {
199 Self {
200 specializations: IndexMap::new(),
201 stats: SpecializationStats::default(),
202 config,
203 }
204 }
205
206 pub fn with_defaults() -> Self {
208 Self::new(SpecializationConfig::default())
209 }
210
211 pub fn specialize_function(
213 &mut self,
214 function_name: &str,
215 param_types: &[SpecializedType],
216 return_type: Option<SpecializedType>,
217 original_module: &IrModule,
218 ) -> JitResult<SpecializedFunction> {
219 let key = SpecializationKey {
220 function_name: function_name.to_string(),
221 param_types: param_types.to_vec(),
222 return_type,
223 };
224
225 if let Some(specialized) = self.specializations.get_mut(&key) {
227 specialized.usage_count += 1;
228 self.stats.cache_hits += 1;
229 return Ok(specialized.clone());
230 }
231
232 self.stats.cache_misses += 1;
233
234 let should_specialize = {
236 let existing_count = self
238 .specializations
239 .keys()
240 .filter(|k| k.function_name == key.function_name)
241 .count();
242
243 if existing_count >= self.config.max_specializations_per_function {
244 false
245 } else {
246 self.is_specialization_beneficial(&key)
247 }
248 };
249
250 if !should_specialize {
251 return Err(JitError::OptimizationError(
252 "Specialization not beneficial".to_string(),
253 ));
254 }
255
256 let start_time = std::time::Instant::now();
258 let specialized_module = self.create_specialized_module(original_module, &key)?;
259 let compile_time = start_time.elapsed().as_nanos() as u64;
260
261 let perf_info = self.estimate_performance(&specialized_module)?;
262
263 let specialized_fn = SpecializedFunction {
264 key: key.clone(),
265 module: specialized_module,
266 perf_info,
267 usage_count: 1,
268 compile_time_ns: compile_time,
269 };
270
271 self.specializations.insert(key, specialized_fn.clone());
272 self.stats.total_specializations += 1;
273 self.stats.avg_compilation_time_ns = (self.stats.avg_compilation_time_ns
274 * (self.stats.total_specializations - 1) as u64
275 + compile_time)
276 / self.stats.total_specializations as u64;
277
278 Ok(specialized_fn)
279 }
280
281 fn is_specialization_beneficial(&self, key: &SpecializationKey) -> bool {
283 if self.config.enable_constant_specialization {
285 for param_type in &key.param_types {
286 if param_type.constant_value.is_some() {
287 return true;
288 }
289 }
290 }
291
292 if self.config.enable_shape_specialization {
294 for param_type in &key.param_types {
295 if let Some(shape) = ¶m_type.shape {
296 if shape.iter().product::<usize>() < 1024 {
298 return true;
299 }
300 if shape.iter().all(|&dim| dim.is_power_of_two()) {
302 return true;
303 }
304 }
305 }
306 }
307
308 if self.config.enable_layout_specialization {
310 for param_type in &key.param_types {
311 if param_type.layout_hints.contiguous || param_type.layout_hints.layout.is_some() {
312 return true;
313 }
314 }
315 }
316
317 false
318 }
319
320 fn create_specialized_module(
322 &self,
323 original: &IrModule,
324 key: &SpecializationKey,
325 ) -> JitResult<IrModule> {
326 let mut specialized = original.clone();
327 specialized.name = format!(
328 "{}_{}",
329 original.name,
330 self.generate_specialization_suffix(key)
331 );
332
333 self.apply_type_optimizations(&mut specialized, key)?;
335
336 self.apply_shape_optimizations(&mut specialized, key)?;
338
339 self.apply_constant_propagation(&mut specialized, key)?;
341
342 self.apply_layout_optimizations(&mut specialized, key)?;
344
345 Ok(specialized)
346 }
347
348 fn generate_specialization_suffix(&self, key: &SpecializationKey) -> String {
350 use std::collections::hash_map::DefaultHasher;
351 use std::hash::{Hash, Hasher};
352
353 let mut hasher = DefaultHasher::new();
354 key.hash(&mut hasher);
355 format!("{:x}", hasher.finish())
356 }
357
358 fn apply_type_optimizations(
360 &self,
361 module: &mut IrModule,
362 key: &SpecializationKey,
363 ) -> JitResult<()> {
364 for (_, block) in module.blocks.iter_mut() {
366 for instruction in &mut block.instructions {
367 match instruction.opcode {
368 IrOpcode::Add | IrOpcode::Sub | IrOpcode::Mul | IrOpcode::Div => {
369 if let Some(param_type) = key.param_types.first() {
371 match param_type.base_type {
372 TypeKind::F32 => {
373 }
375 TypeKind::F64 => {
376 }
378 TypeKind::I32 => {
379 }
381 _ => {}
382 }
383 }
384 }
385 _ => {}
386 }
387 }
388 }
389
390 Ok(())
391 }
392
393 fn apply_shape_optimizations(
395 &self,
396 module: &mut IrModule,
397 key: &SpecializationKey,
398 ) -> JitResult<()> {
399 for param_type in &key.param_types {
400 if let Some(shape) = ¶m_type.shape {
401 if shape.iter().product::<usize>() < 64 {
403 self.unroll_small_loops(module, shape)?;
404 }
405
406 self.optimize_memory_access(module, shape)?;
408 }
409 }
410
411 Ok(())
412 }
413
414 fn apply_constant_propagation(
416 &self,
417 module: &mut IrModule,
418 key: &SpecializationKey,
419 ) -> JitResult<()> {
420 for param_type in &key.param_types {
421 if let Some(const_val) = ¶m_type.constant_value {
422 self.propagate_constant(module, const_val)?;
424 }
425 }
426
427 Ok(())
428 }
429
430 fn apply_layout_optimizations(
432 &self,
433 module: &mut IrModule,
434 key: &SpecializationKey,
435 ) -> JitResult<()> {
436 for param_type in &key.param_types {
437 match ¶m_type.layout_hints.layout {
438 Some(DataLayout::RowMajor) => {
439 self.optimize_for_row_major(module)?;
441 }
442 Some(DataLayout::ColumnMajor) => {
443 self.optimize_for_column_major(module)?;
445 }
446 Some(DataLayout::Packed) => {
447 self.optimize_for_packed_data(module)?;
449 }
450 _ => {}
451 }
452 }
453
454 Ok(())
455 }
456
457 fn unroll_small_loops(&self, _module: &mut IrModule, shape: &[usize]) -> JitResult<()> {
459 let max_unroll_iterations = 16;
461
462 let _small_dims: Vec<_> = shape
464 .iter()
465 .filter(|&&dim| dim <= max_unroll_iterations)
466 .collect();
467
468 Ok(())
476 }
477
478 fn optimize_memory_access(&self, module: &mut IrModule, shape: &[usize]) -> JitResult<()> {
480 use crate::ir::IrOpcode;
481 use std::collections::HashMap;
482
483 let mut access_patterns: HashMap<crate::ir::IrValue, Vec<usize>> = HashMap::new();
485
486 for (_block_id, block) in &module.blocks {
487 for (idx, instruction) in block.instructions.iter().enumerate() {
488 match instruction.opcode {
489 IrOpcode::Load | IrOpcode::Store => {
490 if let Some(ptr_val) = instruction.operands.first() {
491 access_patterns.entry(*ptr_val).or_default().push(idx);
492 }
493 }
494 _ => {}
495 }
496 }
497 }
498
499 for (ptr_val, accesses) in &access_patterns {
501 if accesses.len() > 4 {
502 self.insert_prefetch_hints(module, *ptr_val, accesses)?;
504 }
505
506 if self.has_regular_stride(accesses) {
508 self.optimize_strided_access(module, *ptr_val, shape)?;
509 }
510 }
511
512 Ok(())
513 }
514
515 fn insert_prefetch_hints(
517 &self,
518 _module: &mut IrModule,
519 _ptr: crate::ir::IrValue,
520 _accesses: &[usize],
521 ) -> JitResult<()> {
522 Ok(())
525 }
526
527 fn has_regular_stride(&self, accesses: &[usize]) -> bool {
529 if accesses.len() < 2 {
530 return false;
531 }
532
533 let mut strides = Vec::new();
535 for i in 1..accesses.len() {
536 strides.push(accesses[i] - accesses[i - 1]);
537 }
538
539 if strides.is_empty() {
541 return false;
542 }
543
544 let first_stride = strides[0];
545 strides.iter().all(|&s| s == first_stride)
546 }
547
548 fn optimize_strided_access(
550 &self,
551 _module: &mut IrModule,
552 _ptr: crate::ir::IrValue,
553 _shape: &[usize],
554 ) -> JitResult<()> {
555 Ok(())
558 }
559
560 fn propagate_constant(
562 &self,
563 module: &mut IrModule,
564 _const_val: &ConstantValue,
565 ) -> JitResult<()> {
566 use crate::ir::ValueKind;
567 use std::collections::HashMap;
568
569 let mut constants: HashMap<crate::ir::IrValue, crate::ir::IrValue> = HashMap::new();
571
572 for (val_id, val_def) in &module.values {
574 match &val_def.kind {
575 ValueKind::Constant { .. } => {
576 constants.insert(*val_id, *val_id);
578 }
579 _ => {}
580 }
581 }
582
583 let _constant_count = constants.len();
591
592 Ok(())
593 }
594
595 fn optimize_for_row_major(&self, module: &mut IrModule) -> JitResult<()> {
597 use crate::ir::IrOpcode;
598
599 let mut blocks_to_optimize = Vec::new();
602
603 for (block_id, block) in &module.blocks {
604 for instruction in &block.instructions {
605 match instruction.opcode {
606 IrOpcode::MatMul | IrOpcode::Conv2d => {
607 blocks_to_optimize.push(*block_id);
608 break;
609 }
610 _ => {}
611 }
612 }
613 }
614
615 for block_id in blocks_to_optimize {
617 self.apply_row_major_tiling(module, block_id)?;
618 }
619
620 Ok(())
621 }
622
623 fn apply_row_major_tiling(
625 &self,
626 _module: &mut IrModule,
627 _block_id: crate::ir::BlockId,
628 ) -> JitResult<()> {
629 Ok(())
634 }
635
636 fn optimize_for_column_major(&self, module: &mut IrModule) -> JitResult<()> {
638 use crate::ir::IrOpcode;
639
640 let mut blocks_to_optimize = Vec::new();
643
644 for (block_id, block) in &module.blocks {
645 for instruction in &block.instructions {
646 match instruction.opcode {
647 IrOpcode::MatMul => {
648 blocks_to_optimize.push(*block_id);
649 break;
650 }
651 IrOpcode::Transpose => {
652 }
655 _ => {}
656 }
657 }
658 }
659
660 for block_id in blocks_to_optimize {
662 self.apply_column_major_tiling(module, block_id)?;
663 }
664
665 Ok(())
666 }
667
668 fn apply_column_major_tiling(
670 &self,
671 _module: &mut IrModule,
672 _block_id: crate::ir::BlockId,
673 ) -> JitResult<()> {
674 Ok(())
679 }
680
681 fn optimize_for_packed_data(&self, module: &mut IrModule) -> JitResult<()> {
683 let mut packed_values = Vec::new();
685
686 for (val_id, val_def) in &module.values {
687 if self.is_packable_value(val_def) {
689 packed_values.push(*val_id);
690 }
691 }
692
693 for val_id in packed_values {
695 self.pack_value(module, val_id)?;
696 }
697
698 Ok(())
699 }
700
701 fn is_packable_value(&self, val_def: &crate::ir::ValueDef) -> bool {
703 use crate::ir::ValueKind;
704
705 matches!(val_def.kind, ValueKind::Instruction { .. })
708 }
709
710 fn pack_value(&self, _module: &mut IrModule, _val_id: crate::ir::IrValue) -> JitResult<()> {
712 Ok(())
718 }
719
720 fn estimate_performance(&self, module: &IrModule) -> JitResult<PerformanceInfo> {
722 let mut perf_info = PerformanceInfo::default();
723
724 let mut op_count = 0;
726 let mut memory_ops = 0;
727
728 for (_, block) in &module.blocks {
729 for instruction in &block.instructions {
730 op_count += 1;
731 match instruction.opcode {
732 IrOpcode::Load | IrOpcode::Store => memory_ops += 1,
733 _ => {}
734 }
735 }
736 }
737
738 perf_info.estimated_exec_time_ns = op_count * 10; perf_info.memory_bandwidth = memory_ops * 64; perf_info.arithmetic_intensity = if memory_ops > 0 {
742 (op_count - memory_ops) as f64 / memory_ops as f64
743 } else {
744 f64::INFINITY
745 };
746
747 Ok(perf_info)
748 }
749
750 pub fn stats(&self) -> &SpecializationStats {
752 &self.stats
753 }
754
755 pub fn clear_cache(&mut self) {
757 self.specializations.clear();
758 self.stats = SpecializationStats::default();
759 }
760
761 pub fn specialization_count(&self, function_name: &str) -> usize {
763 self.specializations
764 .keys()
765 .filter(|k| k.function_name == function_name)
766 .count()
767 }
768
769 pub fn list_specializations(&self) -> Vec<&SpecializationKey> {
771 self.specializations.keys().collect()
772 }
773}
774
775pub fn create_specialized_type(dtype: DType, shape: Option<Shape>) -> SpecializedType {
777 let base_type = match dtype {
778 DType::F16 => TypeKind::F16,
779 DType::F32 => TypeKind::F32,
780 DType::F64 => TypeKind::F64,
781 DType::I8 => TypeKind::I8,
782 DType::I16 => TypeKind::I16,
783 DType::I32 => TypeKind::I32,
784 DType::I64 => TypeKind::I64,
785 DType::U8 => TypeKind::U8,
786 DType::U32 => TypeKind::U32,
787 DType::U64 => TypeKind::U64,
788 DType::Bool => TypeKind::Bool,
789 DType::BF16 => TypeKind::F16, DType::C64 => TypeKind::C64,
791 DType::C128 => TypeKind::C128,
792 DType::QInt8 | DType::QUInt8 => TypeKind::I8, DType::QInt32 => TypeKind::I32, };
795
796 let shape_vec = shape.map(|s| s.dims().to_vec());
797
798 SpecializedType {
799 base_type,
800 shape: shape_vec,
801 constant_value: None,
802 layout_hints: LayoutHints::default(),
803 }
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809
810 #[test]
811 fn test_specialization_key_equality() {
812 let key1 = SpecializationKey {
813 function_name: "test_fn".to_string(),
814 param_types: vec![SpecializedType {
815 base_type: TypeKind::F32,
816 shape: Some(vec![2, 2]),
817 constant_value: None,
818 layout_hints: LayoutHints::default(),
819 }],
820 return_type: None,
821 };
822
823 let key2 = key1.clone();
824 assert_eq!(key1, key2);
825 }
826
827 #[test]
828 fn test_specializer_creation() {
829 let specializer = TypeSpecializer::with_defaults();
830 assert_eq!(specializer.specializations.len(), 0);
831 assert_eq!(specializer.stats.total_specializations, 0);
832 }
833
834 #[test]
835 fn test_create_specialized_type() {
836 let dtype = DType::F32;
837 let shape = Some(Shape::new(vec![2, 3]));
838
839 let spec_type = create_specialized_type(dtype, shape);
840 assert_eq!(spec_type.base_type, TypeKind::F32);
841 assert_eq!(spec_type.shape, Some(vec![2, 3]));
842 }
843}