1use std::fmt;
61
62use oxicuda_ptx::PtxType;
63use oxicuda_ptx::arch::SmVersion;
64use oxicuda_ptx::error::PtxGenError;
65
66use crate::error::LaunchError;
67use crate::grid::Dim3;
68
69const CUDA_MAX_NESTING_DEPTH: u32 = 24;
75
76const DEFAULT_MAX_PENDING_LAUNCHES: u32 = 2048;
78
79const BASE_LAUNCH_OVERHEAD_BYTES: u64 = 2048;
83
84const PER_DEPTH_OVERHEAD_BYTES: u64 = 4096;
87
88#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct DynamicParallelismConfig {
105 pub max_nesting_depth: u32,
107 pub max_pending_launches: u32,
109 pub sync_depth: u32,
114 pub child_grid: Dim3,
116 pub child_block: Dim3,
118 pub child_shared_mem: u32,
120 pub sm_version: SmVersion,
122}
123
124impl DynamicParallelismConfig {
125 #[must_use]
135 pub fn new(sm_version: SmVersion) -> Self {
136 Self {
137 max_nesting_depth: 4,
138 max_pending_launches: DEFAULT_MAX_PENDING_LAUNCHES,
139 sync_depth: 2,
140 child_grid: Dim3::x(128),
141 child_block: Dim3::x(256),
142 child_shared_mem: 0,
143 sm_version,
144 }
145 }
146}
147
148impl fmt::Display for DynamicParallelismConfig {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 write!(
151 f,
152 "DynParallelism(depth={}, pending={}, sync@{}, grid={}, block={}, smem={}, {})",
153 self.max_nesting_depth,
154 self.max_pending_launches,
155 self.sync_depth,
156 self.child_grid,
157 self.child_block,
158 self.child_shared_mem,
159 self.sm_version,
160 )
161 }
162}
163
164#[derive(Debug, Clone)]
173pub struct DynamicLaunchPlan {
174 pub config: DynamicParallelismConfig,
176 pub parent_kernel_name: String,
178 pub child_kernel_name: String,
180 pub estimated_child_launches: u64,
182 pub memory_overhead_bytes: u64,
184}
185
186impl fmt::Display for DynamicLaunchPlan {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 write!(
189 f,
190 "DynamicLaunchPlan {{ parent: '{}', child: '{}', \
191 est_launches: {}, overhead: {} bytes, config: {} }}",
192 self.parent_kernel_name,
193 self.child_kernel_name,
194 self.estimated_child_launches,
195 self.memory_overhead_bytes,
196 self.config,
197 )
198 }
199}
200
201#[derive(Debug, Clone)]
210pub struct ChildKernelSpec {
211 pub name: String,
213 pub param_types: Vec<PtxType>,
215 pub grid_dim: GridSpec,
217 pub block_dim: Dim3,
219 pub shared_mem_bytes: u32,
221}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
232pub enum GridSpec {
233 Fixed(Dim3),
235 DataDependent {
241 param_index: u32,
243 },
244 ThreadDependent,
249}
250
251impl fmt::Display for GridSpec {
252 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253 match self {
254 Self::Fixed(dim) => write!(f, "Fixed({dim})"),
255 Self::DataDependent { param_index } => {
256 write!(f, "DataDependent(param[{param_index}])")
257 }
258 Self::ThreadDependent => write!(f, "ThreadDependent"),
259 }
260 }
261}
262
263pub fn validate_dynamic_config(config: &DynamicParallelismConfig) -> Result<(), LaunchError> {
281 if config.max_nesting_depth == 0 || config.max_nesting_depth > CUDA_MAX_NESTING_DEPTH {
283 return Err(LaunchError::InvalidDimension {
284 dim: "max_nesting_depth",
285 value: config.max_nesting_depth,
286 });
287 }
288
289 if config.max_pending_launches == 0 {
291 return Err(LaunchError::InvalidDimension {
292 dim: "max_pending_launches",
293 value: 0,
294 });
295 }
296
297 if config.sync_depth > config.max_nesting_depth {
299 return Err(LaunchError::InvalidDimension {
300 dim: "sync_depth",
301 value: config.sync_depth,
302 });
303 }
304
305 if config.child_grid.x == 0 {
307 return Err(LaunchError::InvalidDimension {
308 dim: "child_grid.x",
309 value: 0,
310 });
311 }
312 if config.child_grid.y == 0 {
313 return Err(LaunchError::InvalidDimension {
314 dim: "child_grid.y",
315 value: 0,
316 });
317 }
318 if config.child_grid.z == 0 {
319 return Err(LaunchError::InvalidDimension {
320 dim: "child_grid.z",
321 value: 0,
322 });
323 }
324
325 if config.child_block.x == 0 {
327 return Err(LaunchError::InvalidDimension {
328 dim: "child_block.x",
329 value: 0,
330 });
331 }
332 if config.child_block.y == 0 {
333 return Err(LaunchError::InvalidDimension {
334 dim: "child_block.y",
335 value: 0,
336 });
337 }
338 if config.child_block.z == 0 {
339 return Err(LaunchError::InvalidDimension {
340 dim: "child_block.z",
341 value: 0,
342 });
343 }
344
345 let max_threads = config.sm_version.max_threads_per_block();
347 let block_total = config.child_block.total();
348 if block_total > max_threads {
349 return Err(LaunchError::BlockSizeExceedsLimit {
350 requested: block_total,
351 max: max_threads,
352 });
353 }
354
355 let max_smem = config.sm_version.max_shared_mem_per_block();
357 if config.child_shared_mem > max_smem {
358 return Err(LaunchError::SharedMemoryExceedsLimit {
359 requested: config.child_shared_mem,
360 max: max_smem,
361 });
362 }
363
364 Ok(())
365}
366
367pub fn plan_dynamic_launch(
381 config: &DynamicParallelismConfig,
382) -> Result<DynamicLaunchPlan, LaunchError> {
383 validate_dynamic_config(config)?;
384
385 let parent_grid_total = config.child_grid.total() as u64;
386 let estimated_child_launches =
387 parent_grid_total.saturating_mul(config.child_block.total() as u64);
388 let memory_overhead_bytes =
389 estimate_launch_overhead(config.max_nesting_depth, config.max_pending_launches);
390
391 Ok(DynamicLaunchPlan {
392 config: config.clone(),
393 parent_kernel_name: String::from("parent_kernel"),
394 child_kernel_name: String::from("child_kernel"),
395 estimated_child_launches,
396 memory_overhead_bytes,
397 })
398}
399
400pub fn estimate_launch_overhead(depth: u32, pending: u32) -> u64 {
419 let per_launch = BASE_LAUNCH_OVERHEAD_BYTES.saturating_mul(pending as u64);
420 let per_depth = PER_DEPTH_OVERHEAD_BYTES.saturating_mul(depth as u64);
421 per_launch.saturating_add(per_depth)
422}
423
424pub fn max_nesting_for_sm(sm: SmVersion) -> u32 {
433 match sm {
437 SmVersion::Sm75 => CUDA_MAX_NESTING_DEPTH,
438 SmVersion::Sm80 | SmVersion::Sm86 => CUDA_MAX_NESTING_DEPTH,
439 SmVersion::Sm89 => CUDA_MAX_NESTING_DEPTH,
440 SmVersion::Sm90 | SmVersion::Sm90a => CUDA_MAX_NESTING_DEPTH,
441 SmVersion::Sm100 => CUDA_MAX_NESTING_DEPTH,
442 SmVersion::Sm120 => CUDA_MAX_NESTING_DEPTH,
443 }
444}
445
446pub fn generate_child_launch_ptx(
475 parent_name: &str,
476 child: &ChildKernelSpec,
477 sm: SmVersion,
478) -> Result<String, PtxGenError> {
479 if child.name.is_empty() {
481 return Err(PtxGenError::GenerationFailed(
482 "child kernel name must not be empty".to_string(),
483 ));
484 }
485 if child.block_dim.x == 0 || child.block_dim.y == 0 || child.block_dim.z == 0 {
486 return Err(PtxGenError::GenerationFailed(
487 "child block dimensions must be non-zero".to_string(),
488 ));
489 }
490
491 let (isa_major, isa_minor) = sm.ptx_isa_version();
492 let target = sm.as_ptx_str();
493
494 let mut ptx = String::with_capacity(2048);
495
496 ptx.push_str(&format!(
498 "// Dynamic parallelism: {parent_name} -> {child_name}\n",
499 child_name = child.name,
500 ));
501 ptx.push_str(&format!(
502 ".version {isa_major}.{isa_minor}\n\
503 .target {target}\n\
504 .address_size 64\n\n"
505 ));
506
507 append_device_runtime_externs(&mut ptx);
509
510 ptx.push_str(&format!(
512 "// Child kernel declaration\n\
513 .extern .entry {child_name}(\n",
514 child_name = child.name,
515 ));
516 for (i, ptype) in child.param_types.iter().enumerate() {
517 let comma = if i + 1 < child.param_types.len() {
518 ","
519 } else {
520 ""
521 };
522 ptx.push_str(&format!(
523 " .param {ty} _param_{i}{comma}\n",
524 ty = ptype.as_ptx_str(),
525 ));
526 }
527 ptx.push_str(")\n\n");
528
529 let func_name = format!(
531 "__{parent_name}_launch_{child_name}",
532 child_name = child.name
533 );
534 ptx.push_str("// Device-side launch helper\n");
535 ptx.push_str(&format!(".func (.param .s32 _retval) {func_name}(\n"));
536
537 for (i, ptype) in child.param_types.iter().enumerate() {
539 let comma = if i + 1 < child.param_types.len() {
540 ","
541 } else {
542 ""
543 };
544 ptx.push_str(&format!(
545 " .param {ty} arg_{i}{comma}\n",
546 ty = ptype.as_ptx_str(),
547 ));
548 }
549 ptx.push_str(")\n{\n");
550
551 ptx.push_str(" // Register declarations\n");
553 ptx.push_str(" .reg .s32 %retval;\n");
554 ptx.push_str(" .reg .u32 %grid_x, %grid_y, %grid_z;\n");
555 ptx.push_str(" .reg .u32 %block_x, %block_y, %block_z;\n");
556 ptx.push_str(" .reg .u32 %shared_mem;\n");
557 ptx.push_str(" .reg .u64 %stream;\n");
558 ptx.push_str(" .reg .u64 %func_addr, %param_buf;\n");
560 ptx.push_str(" .reg .pred %p_buf_null;\n");
561
562 if let GridSpec::DataDependent { .. } = &child.grid_dim {
564 ptx.push_str(" .reg .u32 %n_elements, %block_size;\n");
565 }
566 if matches!(&child.grid_dim, GridSpec::ThreadDependent) {
567 ptx.push_str(" .reg .u32 %tid_x, %ntid_x, %ctaid_x;\n");
568 }
569
570 ptx.push('\n');
571
572 match &child.grid_dim {
574 GridSpec::Fixed(dim) => {
575 ptx.push_str(&format!(
576 " // Fixed grid dimensions\n\
577 mov.u32 %grid_x, {gx};\n\
578 mov.u32 %grid_y, {gy};\n\
579 mov.u32 %grid_z, {gz};\n",
580 gx = dim.x,
581 gy = dim.y,
582 gz = dim.z,
583 ));
584 }
585 GridSpec::DataDependent { param_index } => {
586 ptx.push_str(&format!(
587 " // Data-dependent grid: ceil(param[{param_index}] / block.x)\n\
588 ld.param.u32 %n_elements, [arg_{param_index}];\n\
589 mov.u32 %block_size, {bx};\n\
590 add.u32 %grid_x, %n_elements, %block_size;\n\
591 sub.u32 %grid_x, %grid_x, 1;\n\
592 div.u32 %grid_x, %grid_x, %block_size;\n\
593 mov.u32 %grid_y, 1;\n\
594 mov.u32 %grid_z, 1;\n",
595 bx = child.block_dim.x,
596 ));
597 }
598 GridSpec::ThreadDependent => {
599 ptx.push_str(
600 " // Thread-dependent: one child launch per parent thread\n\
601 mov.u32 %tid_x, %tid.x;\n\
602 mov.u32 %ntid_x, %ntid.x;\n\
603 mov.u32 %ctaid_x, %ctaid.x;\n\
604 // Each thread launches a 1-block child grid\n\
605 mov.u32 %grid_x, 1;\n\
606 mov.u32 %grid_y, 1;\n\
607 mov.u32 %grid_z, 1;\n",
608 );
609 }
610 }
611
612 ptx.push_str(&format!(
614 "\n // Block dimensions\n\
615 mov.u32 %block_x, {bx};\n\
616 mov.u32 %block_y, {by};\n\
617 mov.u32 %block_z, {bz};\n",
618 bx = child.block_dim.x,
619 by = child.block_dim.y,
620 bz = child.block_dim.z,
621 ));
622
623 ptx.push_str(&format!(
625 "\n // Shared memory and stream (NULL = default stream)\n\
626 mov.u32 %shared_mem, {smem};\n\
627 mov.u64 %stream, 0;\n",
628 smem = child.shared_mem_bytes,
629 ));
630
631 ptx.push_str(&format!(
634 "\n // Resolve child kernel entry address\n\
635 mov.u64 %func_addr, {child_name};\n",
636 child_name = child.name,
637 ));
638
639 ptx.push_str(
643 "\n // Allocate parameter buffer via the CUDA device runtime\n\
644 // void *cudaGetParameterBufferV2(void *func,\n\
645 // dim3 gridDim, dim3 blockDim,\n\
646 // unsigned int sharedMem)\n\
647 {\n\
648 .param .b64 retval_pb;\n\
649 .param .b64 param0_pb;\n\
650 .param .align 4 .b8 param1_pb[12];\n\
651 .param .align 4 .b8 param2_pb[12];\n\
652 .param .b32 param3_pb;\n\
653 st.param.b64 [param0_pb], %func_addr;\n\
654 st.param.b32 [param1_pb+0], %grid_x;\n\
655 st.param.b32 [param1_pb+4], %grid_y;\n\
656 st.param.b32 [param1_pb+8], %grid_z;\n\
657 st.param.b32 [param2_pb+0], %block_x;\n\
658 st.param.b32 [param2_pb+4], %block_y;\n\
659 st.param.b32 [param2_pb+8], %block_z;\n\
660 st.param.b32 [param3_pb], %shared_mem;\n\
661 call.uni (retval_pb), cudaGetParameterBufferV2,\n\
662 \x20 (param0_pb, param1_pb, param2_pb, param3_pb);\n\
663 ld.param.b64 %param_buf, [retval_pb];\n\
664 }\n",
665 );
666
667 ptx.push_str(
670 "\n // Bail out if the device runtime could not provide a buffer\n\
671 setp.eq.u64 %p_buf_null, %param_buf, 0;\n\
672 @%p_buf_null mov.s32 %retval, 209;\n\
673 @%p_buf_null bra $L_launch_done;\n",
674 );
675
676 ptx.push_str("\n // Marshal child kernel arguments into the buffer\n");
682 let mut buf_offset: u32 = 0;
683 for (i, ptype) in child.param_types.iter().enumerate() {
684 let size = ptx_param_size_bytes(*ptype);
685 buf_offset = align_up(buf_offset, size);
687 let move_ty = ptx_param_move_type(*ptype);
688 let scratch = format!("%arg_scratch_{i}");
689 ptx.push_str(&format!(
690 " .reg {move_ty} {scratch};\n\
691 ld.param{move_ty} {scratch}, [arg_{i}];\n\
692 st.global{move_ty} [%param_buf+{buf_offset}], {scratch};\n",
693 ));
694 buf_offset = buf_offset.saturating_add(size);
695 }
696
697 ptx.push_str(&format!(
701 "\n // Launch child kernel {child_name} via the CUDA device runtime\n\
702 // cudaError_t cudaLaunchDeviceV2(void *parameterBuffer,\n\
703 // cudaStream_t stream)\n\
704 {{\n\
705 .param .b32 retval_ld;\n\
706 .param .b64 param0_ld;\n\
707 .param .b64 param1_ld;\n\
708 st.param.b64 [param0_ld], %param_buf;\n\
709 st.param.b64 [param1_ld], %stream;\n\
710 call.uni (retval_ld), cudaLaunchDeviceV2, (param0_ld, param1_ld);\n\
711 ld.param.b32 %retval, [retval_ld];\n\
712 }}\n",
713 child_name = child.name,
714 ));
715
716 ptx.push_str(
718 "\n$L_launch_done:\n\
719 \x20 st.param.s32 [_retval], %retval;\n\
720 ret;\n\
721 }\n",
722 );
723
724 Ok(ptx)
725}
726
727fn ptx_param_size_bytes(ty: PtxType) -> u32 {
734 u32::try_from(ty.size_bytes()).unwrap_or(u32::MAX)
736}
737
738fn ptx_param_move_type(ty: PtxType) -> &'static str {
746 match ptx_param_size_bytes(ty) {
747 0 | 1 => ".b8",
748 2 => ".b16",
749 3..=4 => ".b32",
750 5..=8 => ".b64",
751 _ => ".b128",
752 }
753}
754
755fn align_up(value: u32, align: u32) -> u32 {
760 if align <= 1 {
761 return value;
762 }
763 value.div_ceil(align).saturating_mul(align)
764}
765
766fn append_device_runtime_externs(ptx: &mut String) {
779 ptx.push_str(
780 "// CUDA device runtime (cudadevrt) entry points — resolved at link time\n\
781 .extern .func (.param .b64 func_retval0) cudaGetParameterBufferV2\n\
782 (\n\
783 \x20 .param .b64 cudaGetParameterBufferV2_param_0,\n\
784 \x20 .param .align 4 .b8 cudaGetParameterBufferV2_param_1[12],\n\
785 \x20 .param .align 4 .b8 cudaGetParameterBufferV2_param_2[12],\n\
786 \x20 .param .b32 cudaGetParameterBufferV2_param_3\n\
787 )\n\
788 ;\n\
789 .extern .func (.param .b32 func_retval0) cudaLaunchDeviceV2\n\
790 (\n\
791 \x20 .param .b64 cudaLaunchDeviceV2_param_0,\n\
792 \x20 .param .b64 cudaLaunchDeviceV2_param_1\n\
793 )\n\
794 ;\n\n",
795 );
796}
797
798pub fn generate_device_sync_ptx(sm: SmVersion) -> Result<String, PtxGenError> {
815 let (isa_major, isa_minor) = sm.ptx_isa_version();
816 let target = sm.as_ptx_str();
817
818 let ptx = format!(
819 "// Device-side synchronization\n\
820 .version {isa_major}.{isa_minor}\n\
821 .target {target}\n\
822 .address_size 64\n\
823 \n\
824 // CUDA device runtime (cudadevrt) entry point — resolved at link time\n\
825 // cudaError_t cudaDeviceSynchronize(void)\n\
826 .extern .func (.param .b32 func_retval0) cudaDeviceSynchronize\n\
827 ;\n\
828 \n\
829 // cudaDeviceSynchronize() from device code.\n\
830 // Blocks the calling thread until every child grid it launched has\n\
831 // completed, then returns the device runtime cudaError_t status.\n\
832 .func (.param .s32 _retval) __device_synchronize()\n\
833 {{\n\
834 .reg .s32 %retval;\n\
835 \n\
836 // Invoke the device runtime synchronization entry point.\n\
837 {{\n\
838 .param .b32 retval_sync;\n\
839 call.uni (retval_sync), cudaDeviceSynchronize, ();\n\
840 ld.param.b32 %retval, [retval_sync];\n\
841 }}\n\
842 \n\
843 st.param.s32 [_retval], %retval;\n\
844 ret;\n\
845 }}\n"
846 );
847
848 Ok(ptx)
849}
850
851#[cfg(test)]
856mod tests {
857 use super::*;
858
859 fn default_config() -> DynamicParallelismConfig {
860 DynamicParallelismConfig::new(SmVersion::Sm80)
861 }
862
863 #[test]
866 fn validate_default_config_ok() {
867 let config = default_config();
868 assert!(validate_dynamic_config(&config).is_ok());
869 }
870
871 #[test]
872 fn validate_zero_nesting_depth_fails() {
873 let mut config = default_config();
874 config.max_nesting_depth = 0;
875 let err = validate_dynamic_config(&config);
876 assert!(err.is_err());
877 let err = err.err();
878 assert!(matches!(
879 err,
880 Some(LaunchError::InvalidDimension {
881 dim: "max_nesting_depth",
882 ..
883 })
884 ));
885 }
886
887 #[test]
888 fn validate_excessive_nesting_depth_fails() {
889 let mut config = default_config();
890 config.max_nesting_depth = 25;
891 let err = validate_dynamic_config(&config);
892 assert!(err.is_err());
893 }
894
895 #[test]
896 fn validate_max_nesting_depth_boundary() {
897 let mut config = default_config();
898 config.max_nesting_depth = CUDA_MAX_NESTING_DEPTH;
899 config.sync_depth = CUDA_MAX_NESTING_DEPTH;
900 assert!(validate_dynamic_config(&config).is_ok());
901 }
902
903 #[test]
904 fn validate_zero_pending_launches_fails() {
905 let mut config = default_config();
906 config.max_pending_launches = 0;
907 assert!(validate_dynamic_config(&config).is_err());
908 }
909
910 #[test]
911 fn validate_sync_depth_exceeds_nesting_fails() {
912 let mut config = default_config();
913 config.max_nesting_depth = 4;
914 config.sync_depth = 5;
915 assert!(validate_dynamic_config(&config).is_err());
916 }
917
918 #[test]
919 fn validate_zero_child_block_fails() {
920 let mut config = default_config();
921 config.child_block = Dim3::new(0, 256, 1);
922 assert!(validate_dynamic_config(&config).is_err());
923 }
924
925 #[test]
926 fn validate_zero_child_grid_fails() {
927 let mut config = default_config();
928 config.child_grid = Dim3::new(128, 0, 1);
929 assert!(validate_dynamic_config(&config).is_err());
930 }
931
932 #[test]
933 fn validate_block_size_exceeds_limit() {
934 let mut config = default_config();
935 config.child_block = Dim3::new(32, 32, 2);
937 let err = validate_dynamic_config(&config);
938 assert!(matches!(
939 err,
940 Err(LaunchError::BlockSizeExceedsLimit { .. })
941 ));
942 }
943
944 #[test]
945 fn validate_shared_mem_exceeds_limit() {
946 let mut config = default_config();
947 config.child_shared_mem = 500_000; let err = validate_dynamic_config(&config);
949 assert!(matches!(
950 err,
951 Err(LaunchError::SharedMemoryExceedsLimit { .. })
952 ));
953 }
954
955 #[test]
958 fn plan_dynamic_launch_ok() {
959 let config = default_config();
960 let plan = plan_dynamic_launch(&config);
961 assert!(plan.is_ok());
962 let plan = plan.ok();
963 assert!(plan.is_some());
964 if let Some(plan) = plan {
965 assert!(plan.estimated_child_launches > 0);
966 assert!(plan.memory_overhead_bytes > 0);
967 assert_eq!(plan.parent_kernel_name, "parent_kernel");
968 assert_eq!(plan.child_kernel_name, "child_kernel");
969 }
970 }
971
972 #[test]
973 fn plan_dynamic_launch_invalid_config_fails() {
974 let mut config = default_config();
975 config.max_nesting_depth = 0;
976 let plan = plan_dynamic_launch(&config);
977 assert!(plan.is_err());
978 }
979
980 #[test]
981 fn plan_display() {
982 let config = default_config();
983 let plan = plan_dynamic_launch(&config);
984 if let Ok(plan) = plan {
985 let display = format!("{plan}");
986 assert!(display.contains("parent_kernel"));
987 assert!(display.contains("child_kernel"));
988 assert!(display.contains("bytes"));
989 }
990 }
991
992 #[test]
995 fn estimate_overhead_basic() {
996 let overhead = estimate_launch_overhead(1, 1);
997 assert_eq!(
998 overhead,
999 BASE_LAUNCH_OVERHEAD_BYTES + PER_DEPTH_OVERHEAD_BYTES
1000 );
1001 }
1002
1003 #[test]
1004 fn estimate_overhead_default() {
1005 let overhead = estimate_launch_overhead(4, 2048);
1006 let expected = BASE_LAUNCH_OVERHEAD_BYTES * 2048 + PER_DEPTH_OVERHEAD_BYTES * 4;
1007 assert_eq!(overhead, expected);
1008 }
1009
1010 #[test]
1011 fn estimate_overhead_zero() {
1012 let overhead = estimate_launch_overhead(0, 0);
1013 assert_eq!(overhead, 0);
1014 }
1015
1016 #[test]
1019 fn max_nesting_all_sm_versions() {
1020 assert_eq!(max_nesting_for_sm(SmVersion::Sm75), 24);
1021 assert_eq!(max_nesting_for_sm(SmVersion::Sm80), 24);
1022 assert_eq!(max_nesting_for_sm(SmVersion::Sm86), 24);
1023 assert_eq!(max_nesting_for_sm(SmVersion::Sm89), 24);
1024 assert_eq!(max_nesting_for_sm(SmVersion::Sm90), 24);
1025 assert_eq!(max_nesting_for_sm(SmVersion::Sm90a), 24);
1026 assert_eq!(max_nesting_for_sm(SmVersion::Sm100), 24);
1027 assert_eq!(max_nesting_for_sm(SmVersion::Sm120), 24);
1028 }
1029
1030 #[test]
1033 fn generate_child_launch_ptx_basic() {
1034 let child = ChildKernelSpec {
1035 name: "child_add".to_string(),
1036 param_types: vec![PtxType::U64, PtxType::U64, PtxType::U32],
1037 grid_dim: GridSpec::Fixed(Dim3::x(64)),
1038 block_dim: Dim3::x(256),
1039 shared_mem_bytes: 0,
1040 };
1041 let result = generate_child_launch_ptx("parent_add", &child, SmVersion::Sm80);
1042 assert!(result.is_ok());
1043 let ptx = result.ok();
1044 assert!(ptx.is_some());
1045 if let Some(ptx) = ptx {
1046 assert!(ptx.contains("child_add"));
1047 assert!(ptx.contains("parent_add"));
1048 assert!(ptx.contains(".version 7.0"));
1049 assert!(ptx.contains("sm_80"));
1050 assert!(ptx.contains("mov.u32 %grid_x, 64"));
1051 assert!(ptx.contains(".u64"));
1052 assert!(ptx.contains(".u32"));
1053 }
1054 }
1055
1056 #[test]
1057 fn generate_child_launch_ptx_emits_device_runtime_externs() {
1058 let child = ChildKernelSpec {
1061 name: "child_dr".to_string(),
1062 param_types: vec![PtxType::U64, PtxType::U32],
1063 grid_dim: GridSpec::Fixed(Dim3::x(32)),
1064 block_dim: Dim3::x(128),
1065 shared_mem_bytes: 0,
1066 };
1067 let result = generate_child_launch_ptx("parent_dr", &child, SmVersion::Sm80);
1068 assert!(result.is_ok());
1069 if let Ok(ptx) = result {
1070 assert!(
1072 ptx.contains(".extern .func (.param .b64 func_retval0) cudaGetParameterBufferV2")
1073 );
1074 assert!(ptx.contains(".extern .func (.param .b32 func_retval0) cudaLaunchDeviceV2"));
1075 assert!(ptx.contains(".param .align 4 .b8 cudaGetParameterBufferV2_param_1[12]"));
1077 assert!(ptx.contains(".param .align 4 .b8 cudaGetParameterBufferV2_param_2[12]"));
1078 assert!(ptx.contains("call.uni (retval_pb), cudaGetParameterBufferV2"));
1080 assert!(
1081 ptx.contains("call.uni (retval_ld), cudaLaunchDeviceV2, (param0_ld, param1_ld);")
1082 );
1083 }
1084 }
1085
1086 #[test]
1087 fn generate_child_launch_ptx_sets_up_parameter_buffer() {
1088 let child = ChildKernelSpec {
1091 name: "child_buf".to_string(),
1092 param_types: vec![PtxType::U64, PtxType::U64, PtxType::F32],
1093 grid_dim: GridSpec::Fixed(Dim3::x(16)),
1094 block_dim: Dim3::x(64),
1095 shared_mem_bytes: 256,
1096 };
1097 let result = generate_child_launch_ptx("parent_buf", &child, SmVersion::Sm90);
1098 assert!(result.is_ok());
1099 if let Ok(ptx) = result {
1100 assert!(ptx.contains(".reg .u64 %func_addr, %param_buf;"));
1102 assert!(ptx.contains("mov.u64 %func_addr, child_buf;"));
1103 assert!(ptx.contains("ld.param.b64 %param_buf, [retval_pb];"));
1104 assert!(ptx.contains("st.param.b32 [param1_pb+0], %grid_x;"));
1106 assert!(ptx.contains("st.param.b32 [param2_pb+8], %block_z;"));
1107 assert!(ptx.contains("st.param.b32 [param3_pb], %shared_mem;"));
1108 assert!(ptx.contains("setp.eq.u64 %p_buf_null, %param_buf, 0;"));
1110 assert!(ptx.contains("@%p_buf_null mov.s32 %retval, 209;"));
1111 assert!(ptx.contains("st.global.b64 [%param_buf+0],"));
1113 assert!(ptx.contains("st.global.b64 [%param_buf+8],"));
1114 assert!(ptx.contains("st.global.b32 [%param_buf+16],"));
1115 assert!(ptx.contains("ld.param.b32 %retval, [retval_ld];"));
1117 }
1118 }
1119
1120 #[test]
1121 fn generate_child_launch_ptx_no_stub_comments() {
1122 let child = ChildKernelSpec {
1124 name: "child_clean".to_string(),
1125 param_types: vec![PtxType::U64],
1126 grid_dim: GridSpec::Fixed(Dim3::x(8)),
1127 block_dim: Dim3::x(32),
1128 shared_mem_bytes: 0,
1129 };
1130 let result = generate_child_launch_ptx("parent_clean", &child, SmVersion::Sm80);
1131 assert!(result.is_ok());
1132 if let Ok(ptx) = result {
1133 assert!(!ptx.contains("We model this with"));
1134 assert!(!ptx.contains("actual device-side launch uses cudaLaunchDeviceV2"));
1135 assert!(!ptx.contains("mov.s32 %retval, 0; // cudaSuccess"));
1136 }
1137 }
1138
1139 #[test]
1140 fn generate_child_launch_ptx_aligns_mixed_arguments() {
1141 let child = ChildKernelSpec {
1143 name: "child_align".to_string(),
1144 param_types: vec![PtxType::U32, PtxType::U64],
1145 grid_dim: GridSpec::Fixed(Dim3::x(4)),
1146 block_dim: Dim3::x(32),
1147 shared_mem_bytes: 0,
1148 };
1149 let result = generate_child_launch_ptx("parent_align", &child, SmVersion::Sm80);
1150 assert!(result.is_ok());
1151 if let Ok(ptx) = result {
1152 assert!(ptx.contains("st.global.b32 [%param_buf+0],"));
1153 assert!(ptx.contains("st.global.b64 [%param_buf+8],"));
1155 }
1156 }
1157
1158 #[test]
1159 fn generate_child_launch_ptx_data_dependent() {
1160 let child = ChildKernelSpec {
1161 name: "child_scale".to_string(),
1162 param_types: vec![PtxType::U64, PtxType::U32],
1163 grid_dim: GridSpec::DataDependent { param_index: 1 },
1164 block_dim: Dim3::x(128),
1165 shared_mem_bytes: 1024,
1166 };
1167 let result = generate_child_launch_ptx("parent_scale", &child, SmVersion::Sm90);
1168 assert!(result.is_ok());
1169 if let Ok(ptx) = result {
1170 assert!(ptx.contains("Data-dependent"));
1171 assert!(ptx.contains("arg_1"));
1172 assert!(ptx.contains("div.u32"));
1173 }
1174 }
1175
1176 #[test]
1177 fn generate_child_launch_ptx_thread_dependent() {
1178 let child = ChildKernelSpec {
1179 name: "child_per_thread".to_string(),
1180 param_types: vec![PtxType::U64],
1181 grid_dim: GridSpec::ThreadDependent,
1182 block_dim: Dim3::x(32),
1183 shared_mem_bytes: 0,
1184 };
1185 let result = generate_child_launch_ptx("parent", &child, SmVersion::Sm80);
1186 assert!(result.is_ok());
1187 if let Ok(ptx) = result {
1188 assert!(ptx.contains("Thread-dependent"));
1189 assert!(ptx.contains("%tid.x"));
1190 }
1191 }
1192
1193 #[test]
1194 fn generate_child_launch_ptx_empty_name_fails() {
1195 let child = ChildKernelSpec {
1196 name: String::new(),
1197 param_types: vec![],
1198 grid_dim: GridSpec::Fixed(Dim3::x(1)),
1199 block_dim: Dim3::x(1),
1200 shared_mem_bytes: 0,
1201 };
1202 let result = generate_child_launch_ptx("parent", &child, SmVersion::Sm80);
1203 assert!(result.is_err());
1204 }
1205
1206 #[test]
1207 fn generate_child_launch_ptx_zero_block_fails() {
1208 let child = ChildKernelSpec {
1209 name: "child".to_string(),
1210 param_types: vec![],
1211 grid_dim: GridSpec::Fixed(Dim3::x(1)),
1212 block_dim: Dim3::new(0, 1, 1),
1213 shared_mem_bytes: 0,
1214 };
1215 let result = generate_child_launch_ptx("parent", &child, SmVersion::Sm80);
1216 assert!(result.is_err());
1217 }
1218
1219 #[test]
1220 fn generate_device_sync_ptx_basic() {
1221 let result = generate_device_sync_ptx(SmVersion::Sm80);
1222 assert!(result.is_ok());
1223 if let Ok(ptx) = result {
1224 assert!(ptx.contains("__device_synchronize"));
1225 assert!(ptx.contains(".version 7.0"));
1226 assert!(ptx.contains("sm_80"));
1227 assert!(ptx.contains("cudaDeviceSynchronize"));
1228 }
1229 }
1230
1231 #[test]
1232 fn generate_device_sync_ptx_hopper() {
1233 let result = generate_device_sync_ptx(SmVersion::Sm90);
1234 assert!(result.is_ok());
1235 if let Ok(ptx) = result {
1236 assert!(ptx.contains(".version 8.0"));
1237 assert!(ptx.contains("sm_90"));
1238 }
1239 }
1240
1241 #[test]
1242 fn generate_device_sync_ptx_emits_real_extern_and_call() {
1243 let result = generate_device_sync_ptx(SmVersion::Sm80);
1245 assert!(result.is_ok());
1246 if let Ok(ptx) = result {
1247 assert!(ptx.contains(".extern .func (.param .b32 func_retval0) cudaDeviceSynchronize"));
1248 assert!(ptx.contains("call.uni (retval_sync), cudaDeviceSynchronize, ();"));
1249 assert!(ptx.contains("ld.param.b32 %retval, [retval_sync];"));
1251 }
1252 }
1253
1254 #[test]
1255 fn generate_device_sync_ptx_no_stub_comments() {
1256 let result = generate_device_sync_ptx(SmVersion::Sm80);
1258 assert!(result.is_ok());
1259 if let Ok(ptx) = result {
1260 assert!(!ptx.contains("(placeholder)"));
1261 assert!(!ptx.contains("For code generation, we emit the call pattern"));
1262 assert!(!ptx.contains("mov.s32 %retval, 0;"));
1263 }
1264 }
1265
1266 #[test]
1267 fn ptx_param_layout_helpers() {
1268 assert_eq!(ptx_param_size_bytes(PtxType::U8), 1);
1270 assert_eq!(ptx_param_size_bytes(PtxType::F16), 2);
1271 assert_eq!(ptx_param_size_bytes(PtxType::U32), 4);
1272 assert_eq!(ptx_param_size_bytes(PtxType::U64), 8);
1273 assert_eq!(ptx_param_size_bytes(PtxType::B128), 16);
1274 assert_eq!(ptx_param_move_type(PtxType::S8), ".b8");
1276 assert_eq!(ptx_param_move_type(PtxType::BF16), ".b16");
1277 assert_eq!(ptx_param_move_type(PtxType::F32), ".b32");
1278 assert_eq!(ptx_param_move_type(PtxType::F64), ".b64");
1279 assert_eq!(ptx_param_move_type(PtxType::B128), ".b128");
1280 assert_eq!(align_up(0, 8), 0);
1282 assert_eq!(align_up(4, 8), 8);
1283 assert_eq!(align_up(8, 8), 8);
1284 assert_eq!(align_up(9, 4), 12);
1285 assert_eq!(align_up(7, 1), 7);
1286 }
1287
1288 #[test]
1291 fn config_display() {
1292 let config = default_config();
1293 let display = format!("{config}");
1294 assert!(display.contains("depth=4"));
1295 assert!(display.contains("pending=2048"));
1296 assert!(display.contains("sync@2"));
1297 assert!(display.contains("sm_80"));
1298 }
1299
1300 #[test]
1301 fn grid_spec_display() {
1302 assert_eq!(format!("{}", GridSpec::Fixed(Dim3::x(64))), "Fixed(64)");
1303 assert_eq!(
1304 format!("{}", GridSpec::DataDependent { param_index: 2 }),
1305 "DataDependent(param[2])"
1306 );
1307 assert_eq!(format!("{}", GridSpec::ThreadDependent), "ThreadDependent");
1308 }
1309}