1use crate::graph::{ComputationGraph, Node, NodeId, Operation};
4use crate::{CompiledKernel, JitError, JitResult, KernelMetadata, TensorDesc};
5use torsh_core::DeviceType;
6
7pub struct CodeGenerator {
9 device: DeviceType,
10}
11
12impl CodeGenerator {
13 pub fn new(device: DeviceType) -> Self {
15 Self { device }
16 }
17
18 pub fn generate(&self, graph: &ComputationGraph) -> JitResult<Vec<CompiledKernel>> {
20 match self.device {
21 DeviceType::Cpu => self.generate_cpu(graph),
22 DeviceType::Cuda(_) => self.generate_cuda(graph),
23 DeviceType::Metal(_) => self.generate_metal(graph),
24 _ => Err(JitError::UnsupportedOp(format!(
25 "Code generation not supported for {:?}",
26 self.device
27 ))),
28 }
29 }
30
31 fn generate_cpu(&self, graph: &ComputationGraph) -> JitResult<Vec<CompiledKernel>> {
38 #[cfg(feature = "cranelift-backend")]
39 {
40 let ir_module = crate::lowering::lower_graph_to_ir(graph, "cpu_kernel".to_string())?;
41 let mut codegen = crate::cranelift_backend::CraneliftCodeGen::new()?;
42 let kernels = codegen.generate(&ir_module)?;
43
44 if let Some(empty) = kernels.iter().find(|kernel| kernel.code.is_empty()) {
47 return Err(JitError::CodeGenError(format!(
48 "Cranelift returned kernel '{}' with no machine code",
49 empty.id
50 )));
51 }
52
53 Ok(kernels)
54 }
55
56 #[cfg(not(feature = "cranelift-backend"))]
58 {
59 self.generate_interpreter(graph)
60 }
61 }
62
63 fn generate_cuda(&self, graph: &ComputationGraph) -> JitResult<Vec<CompiledKernel>> {
72 let node_count = graph.nodes().count();
73 let operation_types: Vec<_> = graph
74 .nodes()
75 .map(|(_, node)| format!("{:?}", node.op))
76 .collect();
77
78 Err(JitError::UnsupportedOp(format!(
79 "CUDA code generation not yet implemented. \
80 Graph contains {} nodes with operations: {}. \
81 To enable CUDA support: \
82 1. Install CUDA toolkit (>=11.0) \
83 2. Enable 'cuda' feature flag \
84 3. Set CUDA_PATH environment variable \
85 \nFallback: Use CPU backend or interpreter mode.",
86 node_count,
87 operation_types.join(", ")
88 )))
89 }
90
91 fn generate_metal(&self, graph: &ComputationGraph) -> JitResult<Vec<CompiledKernel>> {
100 let node_count = graph.nodes().count();
101 let has_matmul = graph
102 .nodes()
103 .any(|(_, node)| matches!(node.op, Operation::MatMul));
104 let has_conv = graph
105 .nodes()
106 .any(|(_, node)| matches!(node.op, Operation::Conv2d { .. }));
107
108 let recommendations = if has_matmul || has_conv {
109 "Consider using Metal Performance Shaders (MPS) backend for matrix/convolution operations."
110 } else {
111 "For element-wise operations, CPU backend may provide sufficient performance."
112 };
113
114 Err(JitError::UnsupportedOp(format!(
115 "Metal code generation not yet implemented. \
116 Graph contains {} nodes. \
117 Detected: {} \
118 To enable Metal support: \
119 1. Ensure macOS 10.15+ or iOS 13+ \
120 2. Enable 'metal' feature flag \
121 3. Install Metal developer tools \
122 \n{} \
123 \nFallback: Use CPU backend or interpreter mode.",
124 node_count,
125 if has_matmul {
126 "matrix multiplication"
127 } else if has_conv {
128 "convolutions"
129 } else {
130 "element-wise ops"
131 },
132 recommendations
133 )))
134 }
135
136 pub fn generate_from_ir(
138 &self,
139 ir_module: &crate::ir::IrModule,
140 ) -> JitResult<Vec<CompiledKernel>> {
141 self.generate_interpreter_from_ir(ir_module)
144 }
145
146 pub fn generate_interpreter_from_ir(
148 &self,
149 ir_module: &crate::ir::IrModule,
150 ) -> JitResult<Vec<CompiledKernel>> {
151 let mut kernels = Vec::new();
152
153 for (block_id, block) in &ir_module.blocks {
155 let kernel_id = format!("ir_kernel_{}", block_id);
156
157 let metadata = KernelMetadata {
159 inputs: ir_module
160 .inputs
161 .iter()
162 .filter_map(|&input| self.ir_value_to_tensor_desc(ir_module, input))
163 .collect(),
164 outputs: ir_module
165 .outputs
166 .iter()
167 .filter_map(|&output| self.ir_value_to_tensor_desc(ir_module, output))
168 .collect(),
169 shared_memory: 0,
170 block_size: (1, 1, 1),
171 grid_size: (1, 1, 1),
172 };
173
174 let mut code = Vec::new();
176 for instruction in &block.instructions {
177 let opcode = self.encode_ir_instruction(instruction)?;
178 code.push(opcode);
179 }
180
181 let kernel = CompiledKernel {
182 id: kernel_id,
183 source_nodes: Vec::new(), code,
185 metadata,
186 };
187
188 kernels.push(kernel);
189 }
190
191 Ok(kernels)
192 }
193
194 fn ir_value_to_tensor_desc(
196 &self,
197 ir_module: &crate::ir::IrModule,
198 ir_value: crate::ir::IrValue,
199 ) -> Option<TensorDesc> {
200 if let Some(value_def) = ir_module.get_value(ir_value) {
201 if let Some(type_def) = ir_module.get_type(value_def.ty) {
202 match &type_def.kind {
203 crate::ir::TypeKind::Tensor { shape, .. } => {
204 Some(TensorDesc {
205 dtype: torsh_core::DType::F32, shape: shape.clone(),
207 strides: self.compute_strides(shape),
208 offset: 0,
209 })
210 }
211 _ => None,
212 }
213 } else {
214 None
215 }
216 } else {
217 None
218 }
219 }
220
221 fn encode_ir_instruction(&self, instruction: &crate::ir::Instruction) -> JitResult<u8> {
223 let opcode = match &instruction.opcode {
224 crate::ir::IrOpcode::Add => 1,
225 crate::ir::IrOpcode::Sub => 2,
226 crate::ir::IrOpcode::Mul => 3,
227 crate::ir::IrOpcode::Div => 4,
228 crate::ir::IrOpcode::Neg => 5,
229 crate::ir::IrOpcode::Abs => 6,
230 crate::ir::IrOpcode::Exp => 7,
231 crate::ir::IrOpcode::Log => 8,
232 crate::ir::IrOpcode::Sqrt => 9,
233 crate::ir::IrOpcode::Sin => 10,
234 crate::ir::IrOpcode::Cos => 11,
235 crate::ir::IrOpcode::Tanh => 12,
236 crate::ir::IrOpcode::Sigmoid => 13,
237 crate::ir::IrOpcode::Relu => 14,
238 crate::ir::IrOpcode::Gelu => 15,
239 crate::ir::IrOpcode::MatMul => 16,
240 crate::ir::IrOpcode::Conv2d => 17,
241 crate::ir::IrOpcode::Pool2d => 18,
242 crate::ir::IrOpcode::Reshape => 19,
243 crate::ir::IrOpcode::Transpose => 20,
244 crate::ir::IrOpcode::Sum => 21,
245 crate::ir::IrOpcode::Mean => 22,
246 crate::ir::IrOpcode::Max => 23,
247 crate::ir::IrOpcode::Min => 24,
248 crate::ir::IrOpcode::Load => 25,
249 crate::ir::IrOpcode::Store => 26,
250 crate::ir::IrOpcode::Const => 27,
251 _ => {
252 return Err(JitError::UnsupportedOp(format!(
253 "IR opcode {:?} not supported in interpreter",
254 instruction.opcode
255 )))
256 }
257 };
258
259 Ok(opcode)
260 }
261
262 pub fn generate_interpreter(&self, graph: &ComputationGraph) -> JitResult<Vec<CompiledKernel>> {
264 let mut kernels = Vec::new();
265
266 let order = graph
268 .topological_sort()
269 .map_err(|e| JitError::GraphError(format!("{:?}", e)))?;
270
271 for node_id in order {
273 if let Some(node) = graph.node(node_id) {
274 let kernel = self.generate_interpreter_kernel(graph, node_id, node)?;
275 kernels.push(kernel);
276 }
277 }
278
279 Ok(kernels)
280 }
281
282 fn generate_interpreter_kernel(
284 &self,
285 graph: &ComputationGraph,
286 node_id: NodeId,
287 node: &Node,
288 ) -> JitResult<CompiledKernel> {
289 let input_tensors: Vec<TensorDesc> = graph
291 .get_node_inputs(node_id)
292 .iter()
293 .filter_map(|&input_id| {
294 graph.node(input_id).map(|input_node| TensorDesc {
295 dtype: input_node.dtype,
296 shape: input_node.output_shape.dims().to_vec(),
297 strides: self.compute_strides(input_node.output_shape.dims()),
298 offset: 0,
299 })
300 })
301 .collect();
302
303 let metadata = KernelMetadata {
305 inputs: input_tensors,
306 outputs: vec![TensorDesc {
307 dtype: node.dtype,
308 shape: node.output_shape.dims().to_vec(),
309 strides: self.compute_strides(node.output_shape.dims()),
310 offset: 0,
311 }],
312 shared_memory: 0,
313 block_size: (1, 1, 1),
314 grid_size: (1, 1, 1),
315 };
316
317 let code = self.encode_operation(&node.op)?;
319
320 Ok(CompiledKernel {
321 id: format!("kernel_{:?}", node_id),
322 source_nodes: vec![node_id],
323 code,
324 metadata,
325 })
326 }
327
328 fn compute_strides(&self, shape: &[usize]) -> Vec<usize> {
330 let mut strides = vec![1; shape.len()];
331 for i in (0..shape.len() - 1).rev() {
332 strides[i] = strides[i + 1] * shape[i + 1];
333 }
334 strides
335 }
336
337 fn encode_operation(&self, op: &Operation) -> JitResult<Vec<u8>> {
339 let op_code = match op {
341 Operation::Add => 1,
342 Operation::Sub => 2,
343 Operation::Mul => 3,
344 Operation::Div => 4,
345 Operation::Relu => 5,
346 Operation::Sigmoid => 6,
347 Operation::Tanh => 7,
348 Operation::MatMul => 8,
349 _ => {
351 return Err(JitError::UnsupportedOp(format!(
352 "Operation {:?} not supported in interpreter",
353 op
354 )))
355 }
356 };
357
358 Ok(vec![op_code])
359 }
360}
361
362pub struct CudaKernelGenerator {
367 compute_capability: (u32, u32),
368 enable_tensor_cores: bool,
370 ptx_version: (u32, u32),
372 enable_cooperative_groups: bool,
374}
375
376impl CudaKernelGenerator {
377 pub fn new(compute_capability: (u32, u32)) -> Self {
382 let enable_tensor_cores = compute_capability.0 >= 7;
383 let enable_cooperative_groups = compute_capability.0 >= 6;
384
385 Self {
386 compute_capability,
387 enable_tensor_cores,
388 ptx_version: (7, 0), enable_cooperative_groups,
390 }
391 }
392
393 pub fn set_tensor_cores(&mut self, enable: bool) {
395 self.enable_tensor_cores = enable && self.compute_capability.0 >= 7;
396 }
397
398 pub fn generate_ptx(&self, graph: &ComputationGraph) -> JitResult<String> {
407 let node_count = graph.nodes().count();
408 let matmul_count = graph
409 .nodes()
410 .filter(|(_, n)| matches!(n.op, Operation::MatMul))
411 .count();
412 let conv_count = graph
413 .nodes()
414 .filter(|(_, n)| matches!(n.op, Operation::Conv2d { .. }))
415 .count();
416
417 let capability_str = format!(
418 "sm_{}{}",
419 self.compute_capability.0, self.compute_capability.1
420 );
421 let features = if self.enable_tensor_cores {
422 "tensor cores (WMMA), "
423 } else {
424 ""
425 };
426
427 Err(JitError::UnsupportedOp(format!(
428 "PTX generation not yet implemented.\n\
429 Target: {} (compute capability {}.{})\n\
430 Graph statistics:\n\
431 - Total nodes: {}\n\
432 - MatMul operations: {} {}\n\
433 - Conv2D operations: {} {}\n\
434 Features: {}cooperative groups\n\
435 \n\
436 Future PTX generation will support:\n\
437 - Automatic kernel fusion for {:.1}x speedup potential\n\
438 - Memory coalescing optimization\n\
439 - Shared memory tiling for matrix operations\n\
440 - Warp-level primitives for reduction operations\n\
441 \nFallback: Use CPU backend with BLAS/MKL for good performance.",
442 capability_str,
443 self.compute_capability.0,
444 self.compute_capability.1,
445 node_count,
446 matmul_count,
447 if self.enable_tensor_cores {
448 "(tensor core eligible)"
449 } else {
450 ""
451 },
452 conv_count,
453 if conv_count > 0 {
454 "(cudnn eligible)"
455 } else {
456 ""
457 },
458 features,
459 (matmul_count + conv_count).max(1) as f64 * 1.5 )))
461 }
462
463 pub fn estimate_launch_config(&self, graph: &ComputationGraph) -> LaunchConfiguration {
465 let total_ops: usize = graph
466 .nodes()
467 .map(|(_, node)| node.output_shape.dims().iter().product::<usize>())
468 .sum();
469
470 let threads_per_block = if total_ops < 1024 {
472 128
473 } else if total_ops < 1024 * 1024 {
474 256
475 } else {
476 512
477 };
478
479 let blocks = (total_ops + threads_per_block - 1) / threads_per_block;
480
481 LaunchConfiguration {
482 grid_dim: (blocks.min(65535), 1, 1),
483 block_dim: (threads_per_block, 1, 1),
484 shared_memory_bytes: 0, stream_id: 0,
486 }
487 }
488}
489
490#[derive(Debug, Clone)]
492pub struct LaunchConfiguration {
493 pub grid_dim: (usize, usize, usize),
495 pub block_dim: (usize, usize, usize),
497 pub shared_memory_bytes: usize,
499 pub stream_id: i32,
501}
502
503pub struct MetalKernelGenerator {
508 device_family: String,
509 enable_mps: bool,
511 metal_version: (u32, u32),
513 enable_ane: bool,
515}
516
517impl MetalKernelGenerator {
518 pub fn new(device_family: String) -> Self {
523 let enable_ane = device_family.starts_with("apple")
525 && device_family[5..].parse::<u32>().unwrap_or(0) >= 7;
526
527 Self {
528 device_family,
529 enable_mps: true, metal_version: (2, 4), enable_ane,
532 }
533 }
534
535 pub fn set_mps(&mut self, enable: bool) {
537 self.enable_mps = enable;
538 }
539
540 pub fn generate_metal(&self, graph: &ComputationGraph) -> JitResult<String> {
549 let node_count = graph.nodes().count();
550 let matmul_count = graph
551 .nodes()
552 .filter(|(_, n)| matches!(n.op, Operation::MatMul))
553 .count();
554 let conv_count = graph
555 .nodes()
556 .filter(|(_, n)| matches!(n.op, Operation::Conv2d { .. }))
557 .count();
558 let elementwise_count = node_count - matmul_count - conv_count;
559
560 let mps_eligible = matmul_count + conv_count;
561 let ane_hints = if self.enable_ane && conv_count > 0 {
562 format!(
563 "\n- {} convolution ops are ANE-eligible for ultra-low power inference",
564 conv_count
565 )
566 } else {
567 String::new()
568 };
569
570 Err(JitError::UnsupportedOp(format!(
571 "Metal shader generation not yet implemented.\n\
572 Target: {} (Metal {}. {})\n\
573 Graph statistics:\n\
574 - Total nodes: {}\n\
575 - Element-wise ops: {}\n\
576 - MatMul operations: {}\n\
577 - Conv2D operations: {}\n\
578 - MPS-eligible ops: {}{}\n\
579 \n\
580 Future Metal generation will support:\n\
581 - Metal Performance Shaders integration for {:.0}% of operations\n\
582 - Unified memory optimization (zero-copy on Apple Silicon)\n\
583 - Tile memory usage for {:.1}x bandwidth reduction\n\
584 - SIMD-group operations for efficient reduction\n\
585 - Concurrent kernel execution across multiple command buffers\n\
586 \nFallback: Use CPU backend with Accelerate framework for good performance.",
587 self.device_family,
588 self.metal_version.0,
589 self.metal_version.1,
590 node_count,
591 elementwise_count,
592 matmul_count,
593 conv_count,
594 mps_eligible,
595 ane_hints,
596 (mps_eligible as f64 / node_count as f64) * 100.0,
597 2.5 )))
599 }
600
601 pub fn estimate_threadgroup_size(&self, graph: &ComputationGraph) -> ThreadgroupSize {
603 let total_ops: usize = graph
604 .nodes()
605 .map(|(_, node)| node.output_shape.dims().iter().product::<usize>())
606 .sum();
607
608 let threads_per_threadgroup = if total_ops < 1024 {
610 128
611 } else if total_ops < 1024 * 1024 {
612 256
613 } else {
614 512
615 };
616
617 ThreadgroupSize {
618 width: threads_per_threadgroup,
619 height: 1,
620 depth: 1,
621 }
622 }
623}
624
625#[derive(Debug, Clone)]
627pub struct ThreadgroupSize {
628 pub width: usize,
629 pub height: usize,
630 pub depth: usize,
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 #[test]
638 fn test_code_generator_creation() {
639 let _gen = CodeGenerator::new(DeviceType::Cpu);
640 assert!(true);
642 }
643
644 #[test]
645 fn test_stride_computation() {
646 let gen = CodeGenerator::new(DeviceType::Cpu);
647
648 let strides = gen.compute_strides(&[2, 3, 4]);
649 assert_eq!(strides, vec![12, 4, 1]);
650
651 let strides = gen.compute_strides(&[10]);
652 assert_eq!(strides, vec![1]);
653 }
654}