Skip to main content

xlog_cuda_tests/categories/
c01_toolchain.rs

1//! Category 1: Toolchain, PTX, and SASS edge cases
2//!
3//! This category tests:
4//! - PTX module loading and JIT compilation
5//! - Compute capability verification
6//! - Kernel function resolution from all modules
7//! - 64-bit addressing verification
8//! - JIT cache behavior under repeated kernel execution
9
10use crate::harness::{CategoryResult, TestContext, TestResult};
11use std::time::Instant;
12use xlog_cuda::{join_kernels, JOIN_MODULE};
13
14/// Run all tests in this category.
15pub fn run_all(ctx: &TestContext) -> CategoryResult {
16    let mut results = CategoryResult::new("c01_toolchain");
17    let start = Instant::now();
18
19    results.add_result(test_ptx_loads_successfully(ctx));
20    results.add_result(test_compute_capability_check(ctx));
21    results.add_result(test_kernel_function_resolution(ctx));
22    results.add_result(test_ptx_module_attributes(ctx));
23    results.add_result(test_repeated_jit_compilation(ctx));
24
25    results.set_duration(start.elapsed());
26    results
27}
28
29/// Test 1: Verify all PTX modules loaded during TestContext creation.
30///
31/// Since we got here via TestContext creation, PTX loading already succeeded.
32/// This test synchronizes the device and verifies no errors occurred during
33/// the loading process.
34fn test_ptx_loads_successfully(ctx: &TestContext) -> TestResult {
35    let start = Instant::now();
36
37    // If we got here, the TestContext was created successfully, which means
38    // CudaProviderBuilder::build() succeeded, which loads all PTX modules.
39    // Sync and verify no async errors occurred during module loading.
40    if let Err(e) = ctx.sync_and_check() {
41        return TestResult::error(
42            "test_ptx_loads_successfully",
43            start.elapsed(),
44            format!("Device sync after PTX load failed: {}", e),
45        );
46    }
47
48    // Verify the provider exists and device is valid
49    let device_ordinal = ctx.device.ordinal();
50    if device_ordinal > 100 {
51        // Sanity check - device ordinal should be reasonable
52        return TestResult::error(
53            "test_ptx_loads_successfully",
54            start.elapsed(),
55            format!("Invalid device ordinal: {}", device_ordinal),
56        );
57    }
58
59    TestResult::passed("test_ptx_loads_successfully", start.elapsed())
60}
61
62/// Test 2: Verify device compute capability meets minimum (7.0 for sm_70).
63///
64/// The xlog CUDA kernels are compiled for sm_70 (Volta) and above.
65/// This test verifies the device meets this minimum requirement.
66fn test_compute_capability_check(ctx: &TestContext) -> TestResult {
67    let start = Instant::now();
68
69    let (major, minor) = match ctx.compute_capability() {
70        Ok(v) => v,
71        Err(e) => {
72            return TestResult::error(
73                "test_compute_capability_check",
74                start.elapsed(),
75                format!("Failed to query compute capability: {}", e),
76            );
77        }
78    };
79
80    // Minimum requirement is sm_70 (Volta)
81    // major >= 7 is the effective check since minor is always >= 0 for u32
82    let meets_minimum = major >= 7;
83
84    if !meets_minimum {
85        return TestResult::error(
86            "test_compute_capability_check",
87            start.elapsed(),
88            format!(
89                "Compute capability {}.{} does not meet minimum sm_70 requirement",
90                major, minor
91            ),
92        );
93    }
94
95    if let Err(e) = ctx.sync_and_check() {
96        return TestResult::error(
97            "test_compute_capability_check",
98            start.elapsed(),
99            format!("Sync failed: {}", e),
100        );
101    }
102
103    TestResult::passed("test_compute_capability_check", start.elapsed())
104}
105
106/// Test 3: Verify all kernel functions can be resolved from the loaded modules.
107///
108/// This test verifies that `CudaKernelProvider` loaded every canonical kernel
109/// entry under the expected module name. PTX artifact structure and manifest
110/// coverage are validated separately by the `xlog-cuda` PTX validation tests.
111fn test_kernel_function_resolution(ctx: &TestContext) -> TestResult {
112    let start = Instant::now();
113
114    let device = ctx.device.inner();
115
116    let mut total_functions = 0;
117    let mut resolved_functions = 0;
118
119    for spec in xlog_cuda::kernel_manifest_data::KERNEL_MODULES {
120        let module_name = spec.module_name;
121
122        for entry in spec.kernels {
123            total_functions += 1;
124            if device.get_func(module_name, entry).is_some() {
125                resolved_functions += 1;
126            } else {
127                return TestResult::error(
128                    "test_kernel_function_resolution",
129                    start.elapsed(),
130                    format!(
131                        "{}: failed to resolve canonical kernel function '{}' from module '{}'",
132                        spec.cu_name, entry, module_name
133                    ),
134                );
135            }
136        }
137    }
138
139    if let Err(e) = ctx.sync_and_check() {
140        return TestResult::error(
141            "test_kernel_function_resolution",
142            start.elapsed(),
143            format!("Sync failed after function resolution: {}", e),
144        );
145    }
146
147    if resolved_functions != total_functions {
148        return TestResult::error(
149            "test_kernel_function_resolution",
150            start.elapsed(),
151            format!(
152                "Not all functions resolved: {}/{}",
153                resolved_functions, total_functions
154            ),
155        );
156    }
157
158    TestResult::passed("test_kernel_function_resolution", start.elapsed())
159}
160
161/// Test 4: Verify PTX modules work correctly with 64-bit addressing.
162///
163/// This test allocates a small GPU buffer, uploads u64::MAX values,
164/// downloads them, and verifies they're correct. This ensures the PTX
165/// modules are correctly compiled for 64-bit addressing mode.
166fn test_ptx_module_attributes(ctx: &TestContext) -> TestResult {
167    let start = Instant::now();
168
169    // Allocate a small buffer for 64-bit values
170    let num_elements = 16;
171    let test_values: Vec<u64> = vec![u64::MAX; num_elements];
172
173    // Allocate GPU memory
174    let mut gpu_buffer = match ctx.memory.alloc::<u64>(num_elements) {
175        Ok(buf) => buf,
176        Err(e) => {
177            return TestResult::error(
178                "test_ptx_module_attributes",
179                start.elapsed(),
180                format!("Failed to allocate GPU buffer: {}", e),
181            );
182        }
183    };
184
185    // Upload data to GPU
186    if let Err(e) = ctx
187        .device
188        .inner()
189        .htod_sync_copy_into(&test_values, &mut gpu_buffer)
190    {
191        return TestResult::error(
192            "test_ptx_module_attributes",
193            start.elapsed(),
194            format!("Failed to upload data to GPU: {}", e),
195        );
196    }
197
198    // Synchronize to ensure upload is complete
199    if let Err(e) = ctx.sync_and_check() {
200        return TestResult::error(
201            "test_ptx_module_attributes",
202            start.elapsed(),
203            format!("Sync failed after upload: {}", e),
204        );
205    }
206
207    // Download data back from GPU
208    let downloaded: Vec<u64> = match ctx.device.inner().dtoh_sync_copy(&gpu_buffer) {
209        Ok(data) => data,
210        Err(e) => {
211            return TestResult::error(
212                "test_ptx_module_attributes",
213                start.elapsed(),
214                format!("Failed to download data from GPU: {}", e),
215            );
216        }
217    };
218
219    // Verify all values are correct
220    for (i, &val) in downloaded.iter().enumerate() {
221        if val != u64::MAX {
222            return TestResult::error(
223                "test_ptx_module_attributes",
224                start.elapsed(),
225                format!(
226                    "64-bit value mismatch at index {}: expected {}, got {}",
227                    i,
228                    u64::MAX,
229                    val
230                ),
231            );
232        }
233    }
234
235    // Also test with specific bit patterns to catch endianness issues
236    let patterns: Vec<u64> = vec![
237        0x0000_0000_0000_0001,
238        0x0000_0000_FFFF_FFFF,
239        0xFFFF_FFFF_0000_0000,
240        0x8000_0000_0000_0000,
241        0xDEAD_BEEF_CAFE_BABE,
242        0x0123_4567_89AB_CDEF,
243    ];
244
245    let mut pattern_buffer = match ctx.memory.alloc::<u64>(patterns.len()) {
246        Ok(buf) => buf,
247        Err(e) => {
248            return TestResult::error(
249                "test_ptx_module_attributes",
250                start.elapsed(),
251                format!("Failed to allocate pattern buffer: {}", e),
252            );
253        }
254    };
255
256    if let Err(e) = ctx
257        .device
258        .inner()
259        .htod_sync_copy_into(&patterns, &mut pattern_buffer)
260    {
261        return TestResult::error(
262            "test_ptx_module_attributes",
263            start.elapsed(),
264            format!("Failed to upload patterns: {}", e),
265        );
266    }
267
268    let downloaded_patterns: Vec<u64> = match ctx.device.inner().dtoh_sync_copy(&pattern_buffer) {
269        Ok(data) => data,
270        Err(e) => {
271            return TestResult::error(
272                "test_ptx_module_attributes",
273                start.elapsed(),
274                format!("Failed to download patterns: {}", e),
275            );
276        }
277    };
278
279    for (i, (&expected, &actual)) in patterns.iter().zip(downloaded_patterns.iter()).enumerate() {
280        if expected != actual {
281            return TestResult::error(
282                "test_ptx_module_attributes",
283                start.elapsed(),
284                format!(
285                    "64-bit pattern mismatch at index {}: expected 0x{:016X}, got 0x{:016X}",
286                    i, expected, actual
287                ),
288            );
289        }
290    }
291
292    if let Err(e) = ctx.sync_and_check() {
293        return TestResult::error(
294            "test_ptx_module_attributes",
295            start.elapsed(),
296            format!("Final sync failed: {}", e),
297        );
298    }
299
300    TestResult::passed("test_ptx_module_attributes", start.elapsed())
301}
302
303/// Test 5: Run the same kernel multiple times to verify JIT cache behavior.
304///
305/// This test performs repeated memory operations (allocate, upload, download)
306/// to verify that JIT compilation caching works correctly and doesn't cause
307/// issues with repeated kernel execution. We run 15 iterations.
308fn test_repeated_jit_compilation(ctx: &TestContext) -> TestResult {
309    let start = Instant::now();
310
311    const NUM_ITERATIONS: usize = 15;
312    const BUFFER_SIZE: usize = 1024;
313
314    for iteration in 0..NUM_ITERATIONS {
315        // Create test data for this iteration
316        let test_data: Vec<u32> = (0..BUFFER_SIZE)
317            .map(|i| (i + iteration * 1000) as u32)
318            .collect();
319
320        // Allocate GPU buffer
321        let mut gpu_buffer = match ctx.memory.alloc::<u32>(BUFFER_SIZE) {
322            Ok(buf) => buf,
323            Err(e) => {
324                return TestResult::error(
325                    "test_repeated_jit_compilation",
326                    start.elapsed(),
327                    format!("Allocation failed at iteration {}: {}", iteration, e),
328                );
329            }
330        };
331
332        // Upload data
333        if let Err(e) = ctx
334            .device
335            .inner()
336            .htod_sync_copy_into(&test_data, &mut gpu_buffer)
337        {
338            return TestResult::error(
339                "test_repeated_jit_compilation",
340                start.elapsed(),
341                format!("Upload failed at iteration {}: {}", iteration, e),
342            );
343        }
344
345        // Download data
346        let downloaded: Vec<u32> = match ctx.device.inner().dtoh_sync_copy(&gpu_buffer) {
347            Ok(data) => data,
348            Err(e) => {
349                return TestResult::error(
350                    "test_repeated_jit_compilation",
351                    start.elapsed(),
352                    format!("Download failed at iteration {}: {}", iteration, e),
353                );
354            }
355        };
356
357        // Verify data integrity
358        for (i, (&expected, &actual)) in test_data.iter().zip(downloaded.iter()).enumerate() {
359            if expected != actual {
360                return TestResult::error(
361                    "test_repeated_jit_compilation",
362                    start.elapsed(),
363                    format!(
364                        "Data mismatch at iteration {}, index {}: expected {}, got {}",
365                        iteration, i, expected, actual
366                    ),
367                );
368            }
369        }
370
371        // Sync after each iteration to check for async errors
372        if let Err(e) = ctx.sync_and_check() {
373            return TestResult::error(
374                "test_repeated_jit_compilation",
375                start.elapsed(),
376                format!("Sync failed at iteration {}: {}", iteration, e),
377            );
378        }
379
380        // gpu_buffer will be dropped at the end of the loop iteration
381    }
382
383    // Final verification: resolve a kernel function to ensure JIT cache is still valid
384    let device = ctx.device.inner();
385    if device
386        .get_func(JOIN_MODULE, join_kernels::HASH_JOIN_BUILD)
387        .is_none()
388    {
389        return TestResult::error(
390            "test_repeated_jit_compilation",
391            start.elapsed(),
392            "Failed to resolve kernel after repeated iterations".to_string(),
393        );
394    }
395
396    if let Err(e) = ctx.sync_and_check() {
397        return TestResult::error(
398            "test_repeated_jit_compilation",
399            start.elapsed(),
400            format!("Final sync failed: {}", e),
401        );
402    }
403
404    TestResult::passed("test_repeated_jit_compilation", start.elapsed())
405}