Skip to main content

torsh_jit/
specialization.rs

1//! Type specialization for JIT compilation
2//!
3//! This module provides type specialization capabilities, allowing the JIT compiler
4//! to create optimized versions of functions and kernels for specific types and shapes.
5
6use crate::ir::{IrModule, IrOpcode, TypeKind};
7use crate::{JitError, JitResult};
8use indexmap::IndexMap;
9use torsh_core::{DType, Shape};
10
11/// Type specialization engine
12#[derive(Debug, Clone)]
13pub struct TypeSpecializer {
14    /// Registry of specialized functions
15    specializations: IndexMap<SpecializationKey, SpecializedFunction>,
16
17    /// Specialization statistics
18    stats: SpecializationStats,
19
20    /// Configuration for specialization
21    config: SpecializationConfig,
22}
23
24/// Key identifying a specialization
25#[derive(Debug, Clone, PartialEq, Eq, Hash)]
26pub struct SpecializationKey {
27    /// Original function name
28    pub function_name: String,
29
30    /// Specialized parameter types
31    pub param_types: Vec<SpecializedType>,
32
33    /// Return type specialization  
34    pub return_type: Option<SpecializedType>,
35}
36
37/// Specialized type information
38#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39pub struct SpecializedType {
40    /// Base type
41    pub base_type: TypeKind,
42
43    /// Shape specialization (for tensors)
44    pub shape: Option<Vec<usize>>,
45
46    /// Constant value (for constant propagation)
47    pub constant_value: Option<ConstantValue>,
48
49    /// Memory layout hints
50    pub layout_hints: LayoutHints,
51}
52
53/// Constant values for specialization
54#[derive(Debug, Clone, PartialEq, Eq, Hash)]
55pub enum ConstantValue {
56    Int(i64),
57    Float(u64), // Stored as bits for hashing
58    Bool(bool),
59    Shape(Vec<usize>),
60}
61
62/// Memory layout optimization hints
63#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
64pub struct LayoutHints {
65    /// Preferred memory alignment
66    pub alignment: Option<usize>,
67
68    /// Whether data is contiguous
69    pub contiguous: bool,
70
71    /// Preferred data layout (e.g., row-major, column-major)
72    pub layout: Option<DataLayout>,
73
74    /// Cache locality hints
75    pub locality: LocalityHint,
76}
77
78/// Data layout preferences
79#[derive(Debug, Clone, PartialEq, Eq, Hash)]
80pub enum DataLayout {
81    RowMajor,
82    ColumnMajor,
83    Packed,
84    Strided { strides: Vec<usize> },
85}
86
87/// Cache locality hints
88#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
89pub enum LocalityHint {
90    #[default]
91    None,
92    Temporal,    // Will be reused soon
93    NonTemporal, // Won't be reused
94    Streaming,   // Sequential access pattern
95}
96
97/// Specialized function implementation
98#[derive(Debug, Clone)]
99pub struct SpecializedFunction {
100    /// Specialization key
101    pub key: SpecializationKey,
102
103    /// Specialized IR module
104    pub module: IrModule,
105
106    /// Performance characteristics
107    pub perf_info: PerformanceInfo,
108
109    /// Usage statistics
110    pub usage_count: usize,
111
112    /// Compilation time
113    pub compile_time_ns: u64,
114}
115
116/// Performance information for specialized functions
117#[derive(Debug, Clone, Default)]
118pub struct PerformanceInfo {
119    /// Estimated execution time in nanoseconds
120    pub estimated_exec_time_ns: u64,
121
122    /// Memory bandwidth requirements (bytes/second)
123    pub memory_bandwidth: u64,
124
125    /// Arithmetic intensity (ops/byte)
126    pub arithmetic_intensity: f64,
127
128    /// Register pressure score (0-100)
129    pub register_pressure: u8,
130
131    /// Vectorization factor
132    pub vectorization_factor: usize,
133}
134
135/// Configuration for type specialization
136#[derive(Debug, Clone)]
137pub struct SpecializationConfig {
138    /// Maximum number of specializations per function
139    pub max_specializations_per_function: usize,
140
141    /// Minimum usage count before creating specialization
142    pub min_usage_threshold: usize,
143
144    /// Enable shape-based specialization
145    pub enable_shape_specialization: bool,
146
147    /// Enable constant propagation specialization
148    pub enable_constant_specialization: bool,
149
150    /// Enable layout optimization specialization
151    pub enable_layout_specialization: bool,
152
153    /// Performance improvement threshold (speedup ratio)
154    pub min_performance_improvement: f64,
155
156    /// Code size increase limit (ratio)
157    pub max_code_size_increase: f64,
158}
159
160/// Specialization statistics
161#[derive(Debug, Clone, Default)]
162pub struct SpecializationStats {
163    /// Total specializations created
164    pub total_specializations: usize,
165
166    /// Cache hits
167    pub cache_hits: usize,
168
169    /// Cache misses
170    pub cache_misses: usize,
171
172    /// Average compilation time
173    pub avg_compilation_time_ns: u64,
174
175    /// Total performance improvement
176    pub total_speedup: f64,
177
178    /// Code size overhead
179    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, // 20% improvement minimum
191            max_code_size_increase: 2.0,      // 2x size increase maximum
192        }
193    }
194}
195
196impl TypeSpecializer {
197    /// Create a new type specializer
198    pub fn new(config: SpecializationConfig) -> Self {
199        Self {
200            specializations: IndexMap::new(),
201            stats: SpecializationStats::default(),
202            config,
203        }
204    }
205
206    /// Create a new type specializer with default configuration
207    pub fn with_defaults() -> Self {
208        Self::new(SpecializationConfig::default())
209    }
210
211    /// Get or create a specialized version of a function
212    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        // Check if specialization already exists
226        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        // Check if we should create a new specialization (separate scope to avoid borrow issues)
235        let should_specialize = {
236            // Count existing specializations for this function
237            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        // Create the specialized function
257        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    /// Determine if a specialization would be beneficial
282    fn is_specialization_beneficial(&self, key: &SpecializationKey) -> bool {
283        // Always specialize for constant values
284        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        // Check for beneficial shape specializations
293        if self.config.enable_shape_specialization {
294            for param_type in &key.param_types {
295                if let Some(shape) = &param_type.shape {
296                    // Small, fixed shapes are good candidates
297                    if shape.iter().product::<usize>() < 1024 {
298                        return true;
299                    }
300                    // Power-of-2 shapes often vectorize well
301                    if shape.iter().all(|&dim| dim.is_power_of_two()) {
302                        return true;
303                    }
304                }
305            }
306        }
307
308        // Check for layout optimizations
309        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    /// Create a specialized version of the IR module
321    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        // Apply type-specific optimizations
334        self.apply_type_optimizations(&mut specialized, key)?;
335
336        // Apply shape-specific optimizations
337        self.apply_shape_optimizations(&mut specialized, key)?;
338
339        // Apply constant propagation
340        self.apply_constant_propagation(&mut specialized, key)?;
341
342        // Apply layout optimizations
343        self.apply_layout_optimizations(&mut specialized, key)?;
344
345        Ok(specialized)
346    }
347
348    /// Generate a unique suffix for the specialization
349    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    /// Apply type-specific optimizations
359    fn apply_type_optimizations(
360        &self,
361        module: &mut IrModule,
362        key: &SpecializationKey,
363    ) -> JitResult<()> {
364        // Replace generic operations with type-specific ones
365        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                        // Could specialize to SIMD instructions for specific types
370                        if let Some(param_type) = key.param_types.first() {
371                            match param_type.base_type {
372                                TypeKind::F32 => {
373                                    // Could use vectorized f32 operations
374                                }
375                                TypeKind::F64 => {
376                                    // Could use vectorized f64 operations
377                                }
378                                TypeKind::I32 => {
379                                    // Could use integer-specific optimizations
380                                }
381                                _ => {}
382                            }
383                        }
384                    }
385                    _ => {}
386                }
387            }
388        }
389
390        Ok(())
391    }
392
393    /// Apply shape-specific optimizations
394    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) = &param_type.shape {
401                // Unroll loops for small, known shapes
402                if shape.iter().product::<usize>() < 64 {
403                    self.unroll_small_loops(module, shape)?;
404                }
405
406                // Optimize memory access patterns for specific shapes
407                self.optimize_memory_access(module, shape)?;
408            }
409        }
410
411        Ok(())
412    }
413
414    /// Apply constant propagation optimizations
415    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) = &param_type.constant_value {
422                // Replace parameter with constant throughout the module
423                self.propagate_constant(module, const_val)?;
424            }
425        }
426
427        Ok(())
428    }
429
430    /// Apply layout-specific optimizations
431    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 &param_type.layout_hints.layout {
438                Some(DataLayout::RowMajor) => {
439                    // Optimize for row-major access patterns
440                    self.optimize_for_row_major(module)?;
441                }
442                Some(DataLayout::ColumnMajor) => {
443                    // Optimize for column-major access patterns
444                    self.optimize_for_column_major(module)?;
445                }
446                Some(DataLayout::Packed) => {
447                    // Optimize for packed data
448                    self.optimize_for_packed_data(module)?;
449                }
450                _ => {}
451            }
452        }
453
454        Ok(())
455    }
456
457    /// Unroll loops for small, known iteration counts
458    fn unroll_small_loops(&self, _module: &mut IrModule, shape: &[usize]) -> JitResult<()> {
459        // Find loops with small iteration counts (< 16)
460        let max_unroll_iterations = 16;
461
462        // Simple heuristic: if any dimension is small enough, we could unroll
463        let _small_dims: Vec<_> = shape
464            .iter()
465            .filter(|&&dim| dim <= max_unroll_iterations)
466            .collect();
467
468        // Loop unrolling would happen here by:
469        // 1. Identifying loop structures in the IR
470        // 2. Checking iteration bounds
471        // 3. Replicating loop body for each iteration
472        // 4. Eliminating loop control overhead
473
474        // For now, this is a placeholder that acknowledges the optimization opportunity
475        Ok(())
476    }
477
478    /// Optimize memory access patterns
479    fn optimize_memory_access(&self, module: &mut IrModule, shape: &[usize]) -> JitResult<()> {
480        use crate::ir::IrOpcode;
481        use std::collections::HashMap;
482
483        // Track memory accesses and their patterns
484        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        // Identify optimization opportunities
500        for (ptr_val, accesses) in &access_patterns {
501            if accesses.len() > 4 {
502                // Multiple accesses to same pointer - candidate for prefetching
503                self.insert_prefetch_hints(module, *ptr_val, accesses)?;
504            }
505
506            // Check for stride patterns
507            if self.has_regular_stride(accesses) {
508                self.optimize_strided_access(module, *ptr_val, shape)?;
509            }
510        }
511
512        Ok(())
513    }
514
515    /// Insert prefetch hints for frequently accessed memory
516    fn insert_prefetch_hints(
517        &self,
518        _module: &mut IrModule,
519        _ptr: crate::ir::IrValue,
520        _accesses: &[usize],
521    ) -> JitResult<()> {
522        // Prefetch hints would be inserted here
523        // Implementation depends on target architecture
524        Ok(())
525    }
526
527    /// Check if memory accesses follow a regular stride pattern
528    fn has_regular_stride(&self, accesses: &[usize]) -> bool {
529        if accesses.len() < 2 {
530            return false;
531        }
532
533        // Check if instruction indices have regular spacing
534        let mut strides = Vec::new();
535        for i in 1..accesses.len() {
536            strides.push(accesses[i] - accesses[i - 1]);
537        }
538
539        // Check if all strides are equal
540        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    /// Optimize strided memory accesses
549    fn optimize_strided_access(
550        &self,
551        _module: &mut IrModule,
552        _ptr: crate::ir::IrValue,
553        _shape: &[usize],
554    ) -> JitResult<()> {
555        // Could vectorize or reorder strided accesses
556        // Implementation would transform memory access patterns
557        Ok(())
558    }
559
560    /// Propagate constant values throughout the module
561    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        // Build constant value map by identifying constant values
570        let mut constants: HashMap<crate::ir::IrValue, crate::ir::IrValue> = HashMap::new();
571
572        // Identify constant values based on ValueKind
573        for (val_id, val_def) in &module.values {
574            match &val_def.kind {
575                ValueKind::Constant { .. } => {
576                    // This is a constant value
577                    constants.insert(*val_id, *val_id);
578                }
579                _ => {}
580            }
581        }
582
583        // Constant propagation would:
584        // 1. Identify all constant values in the module
585        // 2. Track constant values through the dataflow
586        // 3. Replace uses of computed constants with direct constant references
587        // 4. Fold constant expressions at compile time
588
589        // For now, this is a simplified implementation
590        let _constant_count = constants.len();
591
592        Ok(())
593    }
594
595    /// Optimize for row-major memory layout
596    fn optimize_for_row_major(&self, module: &mut IrModule) -> JitResult<()> {
597        use crate::ir::IrOpcode;
598
599        // Row-major layout optimization: optimize innermost loop first
600        // Collect blocks to optimize first to avoid borrow checker issues
601        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        // Now apply optimizations
616        for block_id in blocks_to_optimize {
617            self.apply_row_major_tiling(module, block_id)?;
618        }
619
620        Ok(())
621    }
622
623    /// Apply row-major tiling to a block
624    fn apply_row_major_tiling(
625        &self,
626        _module: &mut IrModule,
627        _block_id: crate::ir::BlockId,
628    ) -> JitResult<()> {
629        // Implementation would:
630        // 1. Identify loop nests
631        // 2. Reorder loops to access contiguous memory
632        // 3. Apply cache blocking/tiling
633        Ok(())
634    }
635
636    /// Optimize for column-major memory layout
637    fn optimize_for_column_major(&self, module: &mut IrModule) -> JitResult<()> {
638        use crate::ir::IrOpcode;
639
640        // Column-major layout optimization: iterate over columns first
641        // Collect blocks to optimize first to avoid borrow checker issues
642        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                        // Transpose operations are no-ops in column-major layout
653                        // Could eliminate redundant transposes
654                    }
655                    _ => {}
656                }
657            }
658        }
659
660        // Now apply optimizations
661        for block_id in blocks_to_optimize {
662            self.apply_column_major_tiling(module, block_id)?;
663        }
664
665        Ok(())
666    }
667
668    /// Apply column-major tiling to a block
669    fn apply_column_major_tiling(
670        &self,
671        _module: &mut IrModule,
672        _block_id: crate::ir::BlockId,
673    ) -> JitResult<()> {
674        // Implementation would:
675        // 1. Identify loop nests
676        // 2. Reorder loops for column-wise access
677        // 3. Insert appropriate prefetch hints
678        Ok(())
679    }
680
681    /// Optimize for packed data layout
682    fn optimize_for_packed_data(&self, module: &mut IrModule) -> JitResult<()> {
683        // Packed data optimization: eliminate padding, use SIMD efficiently
684        let mut packed_values = Vec::new();
685
686        for (val_id, val_def) in &module.values {
687            // Identify values that could benefit from packing
688            if self.is_packable_value(val_def) {
689                packed_values.push(*val_id);
690            }
691        }
692
693        // Apply packing transformations
694        for val_id in packed_values {
695            self.pack_value(module, val_id)?;
696        }
697
698        Ok(())
699    }
700
701    /// Check if a value can be packed
702    fn is_packable_value(&self, val_def: &crate::ir::ValueDef) -> bool {
703        use crate::ir::ValueKind;
704
705        // Values with small element types (i8, i16, f16) can be packed efficiently
706        // Check the value kind to determine if packing would be beneficial
707        matches!(val_def.kind, ValueKind::Instruction { .. })
708    }
709
710    /// Pack a value for more efficient storage and access
711    fn pack_value(&self, _module: &mut IrModule, _val_id: crate::ir::IrValue) -> JitResult<()> {
712        // Implementation would:
713        // 1. Analyze value usage patterns
714        // 2. Transform to packed representation
715        // 3. Update all uses to handle packed format
716        // 4. Insert pack/unpack operations where needed
717        Ok(())
718    }
719
720    /// Estimate performance characteristics of a specialized function
721    fn estimate_performance(&self, module: &IrModule) -> JitResult<PerformanceInfo> {
722        let mut perf_info = PerformanceInfo::default();
723
724        // Count operations and estimate execution time
725        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        // Simple heuristic estimates
739        perf_info.estimated_exec_time_ns = op_count * 10; // ~10ns per operation
740        perf_info.memory_bandwidth = memory_ops * 64; // ~64 bytes per memory op
741        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    /// Get specialization statistics
751    pub fn stats(&self) -> &SpecializationStats {
752        &self.stats
753    }
754
755    /// Clear all specializations (for memory management)
756    pub fn clear_cache(&mut self) {
757        self.specializations.clear();
758        self.stats = SpecializationStats::default();
759    }
760
761    /// Get the number of specializations for a function
762    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    /// List all specialized functions
770    pub fn list_specializations(&self) -> Vec<&SpecializationKey> {
771        self.specializations.keys().collect()
772    }
773}
774
775/// Helper function to create specialized type from DType and Shape
776pub 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, // Map BF16 to F16 for now
790        DType::C64 => TypeKind::C64,
791        DType::C128 => TypeKind::C128,
792        DType::QInt8 | DType::QUInt8 => TypeKind::I8, // Map quantized 8-bit types to base types
793        DType::QInt32 => TypeKind::I32,               // Map quantized 32-bit type to I32
794    };
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}