use super::super::analyzer::*;
use super::super::types::*;
#[test]
fn test_empty_loop_body_detected() {
let ptx = r"
.visible .entry test() {
empty_loop:
// Just comments here
bra empty_loop;
ret;
}
";
let result = PtxBugAnalyzer::new().analyze(ptx);
assert!(result.has_bug(&PtxBugClass::EmptyLoopBody));
}
#[test]
fn test_valid_loop_body_not_flagged() {
let ptx = r"
.visible .entry test() {
.reg .f32 %f<4>;
.reg .u32 %r<4>;
compute_loop:
add.f32 %f0, %f0, %f1;
add.u32 %r0, %r0, 1;
setp.lt.u32 %p0, %r0, %r1;
@%p0 bra compute_loop;
ret;
}
";
let result = PtxBugAnalyzer::new().analyze(ptx);
assert!(!result.has_bug(&PtxBugClass::EmptyLoopBody));
}
#[test]
fn test_loop_with_exit_condition_not_flagged() {
let ptx = r"
.visible .entry test() {
.reg .u32 %r<4>;
.reg .pred %p<2>;
check_loop:
setp.lt.u32 %p0, %r0, %r1;
@%p0 bra check_loop;
ret;
}
";
let result = PtxBugAnalyzer::new().analyze(ptx);
assert!(!result.has_bug(&PtxBugClass::EmptyLoopBody));
}
#[test]
fn test_missing_bounds_check() {
let ptx = r"
.visible .entry test() {
.reg .u64 %rd<4>;
.reg .f32 %f<4>;
mov.u32 %r0, %tid.x;
ld.global.f32 %f0, [%rd0];
st.global.f32 [%rd1], %f0;
ret;
}
";
let result = PtxBugAnalyzer::new().analyze(ptx);
assert!(result.has_bug(&PtxBugClass::MissingBoundsCheck));
}
#[test]
fn test_proper_bounds_check_not_flagged() {
let ptx = r"
.visible .entry test() {
.reg .u64 %rd<4>;
.reg .f32 %f<4>;
.reg .u32 %r<4>;
.reg .pred %p<2>;
mov.u32 %r0, %tid.x;
setp.lt.u32 %p0, %r0, %r1;
@%p0 bra do_work;
bra done;
do_work:
ld.global.f32 %f0, [%rd0];
st.global.f32 [%rd1], %f0;
done:
ret;
}
";
let result = PtxBugAnalyzer::new().analyze(ptx);
assert!(!result.has_bug(&PtxBugClass::MissingBoundsCheck));
}
#[test]
fn test_no_global_mem_no_bounds_check_needed() {
let ptx = r"
.visible .entry test() {
.reg .u32 %r<4>;
mov.u32 %r0, %tid.x;
add.u32 %r1, %r0, 1;
ret;
}
";
let result = PtxBugAnalyzer::new().analyze(ptx);
assert!(!result.has_bug(&PtxBugClass::MissingBoundsCheck));
}
#[test]
fn test_dead_code_after_ret() {
let ptx = r"
.visible .entry test() {
.reg .f32 %f<4>;
add.f32 %f0, %f1, %f2;
ret;
mul.f32 %f3, %f0, %f1;
}
";
let result = PtxBugAnalyzer::new().analyze(ptx);
assert!(result.has_bug(&PtxBugClass::DeadCode));
}
#[test]
fn test_dead_code_after_branch() {
let ptx = r"
.visible .entry test() {
.reg .f32 %f<4>;
bra skip;
add.f32 %f0, %f1, %f2;
skip:
ret;
}
";
let result = PtxBugAnalyzer::new().analyze(ptx);
assert!(result.has_bug(&PtxBugClass::DeadCode));
}
#[test]
fn test_reachable_code_not_flagged() {
let ptx = r"
.visible .entry test() {
.reg .f32 %f<4>;
.reg .pred %p<2>;
@%p0 bra skip;
add.f32 %f0, %f1, %f2;
skip:
mul.f32 %f3, %f0, %f1;
ret;
}
";
let result = PtxBugAnalyzer::new().analyze(ptx);
assert!(!result.has_bug(&PtxBugClass::DeadCode));
}
#[test]
fn test_code_after_label_reachable() {
let ptx = r"
.visible .entry test() {
.reg .f32 %f<4>;
bra middle;
middle:
add.f32 %f0, %f1, %f2;
ret;
}
";
let result = PtxBugAnalyzer::new().analyze(ptx);
assert!(!result.has_bug(&PtxBugClass::DeadCode));
}
#[test]
fn test_extended_bug_severities() {
assert_eq!(PtxBugClass::EmptyLoopBody.severity(), BugSeverity::High);
assert_eq!(
PtxBugClass::MissingBoundsCheck.severity(),
BugSeverity::High
);
assert_eq!(PtxBugClass::DeadCode.severity(), BugSeverity::Medium);
}
#[test]
fn test_extended_bug_codes() {
assert_eq!(PtxBugClass::EmptyLoopBody.code(), "EMPTY_LOOP");
assert_eq!(PtxBugClass::MissingBoundsCheck.code(), "NO_BOUNDS_CHECK");
assert_eq!(PtxBugClass::DeadCode.code(), "DEAD_CODE");
}
#[test]
fn test_parity114_conditional_exit_before_barrier() {
let ptx = r"
.visible .entry kernel() {
mov.u32 %r0, %tid.x;
setp.lt.u32 %p0, %r0, 32;
loop_start:
@!%p0 bra exit;
ld.shared.f32 %f0, [%r0];
bar.sync 0;
st.shared.f32 [%r0], %f0;
bra loop_start;
loop_start_end:
done:
ret;
}
";
let result = PtxBugAnalyzer::strict().analyze(ptx);
assert!(result.has_bug(&PtxBugClass::EarlyExitBeforeBarrier));
assert_eq!(
PtxBugClass::EarlyExitBeforeBarrier.severity(),
BugSeverity::Critical
);
}
#[test]
fn test_parity114_unconditional_exit_before_barrier() {
let ptx = r"
.visible .entry kernel() {
loop_start:
bra exit;
bar.sync 0;
bra loop_start;
loop_start_end:
done:
ret;
}
";
let result = PtxBugAnalyzer::strict().analyze(ptx);
assert!(result.has_bug(&PtxBugClass::EarlyExitBeforeBarrier));
}
#[test]
fn test_parity114_safe_barrier_first() {
let ptx = r"
.visible .entry kernel() {
mov.u32 %r0, %tid.x;
setp.lt.u32 %p0, %r0, 32;
loop_start:
ld.shared.f32 %f0, [%r0];
bar.sync 0;
st.shared.f32 [%r0], %f0;
bra loop_start;
loop_start_end:
@!%p0 bra exit;
st.global.f32 [%r1], %f0;
exit:
ret;
}
";
let result = PtxBugAnalyzer::strict().analyze(ptx);
assert!(!result.has_bug(&PtxBugClass::EarlyExitBeforeBarrier));
}
#[test]
fn test_parity114_exit_after_loop_is_safe() {
let ptx = r"
.visible .entry kernel() {
k_tile_loop:
bar.sync 0;
ld.shared.f32 %f0, [%r0];
bra k_tile_loop;
k_tile_end:
@!%p0 bra exit;
st.global.f32 [%r1], %f0;
done:
ret;
}
";
let result = PtxBugAnalyzer::strict().analyze(ptx);
assert!(!result.has_bug(&PtxBugClass::EarlyExitBeforeBarrier));
}
#[test]
fn test_parity114_non_strict_mode() {
let ptx = r"
.visible .entry kernel() {
loop_start:
@!%p0 bra exit;
bar.sync 0;
bra loop_start;
loop_start_end:
done:
ret;
}
";
let result = PtxBugAnalyzer::new().analyze(ptx);
assert!(!result.has_bug(&PtxBugClass::EarlyExitBeforeBarrier));
let strict_result = PtxBugAnalyzer::strict().analyze(ptx);
assert!(strict_result.has_bug(&PtxBugClass::EarlyExitBeforeBarrier));
}
#[test]
fn test_parity114_bug_class_properties() {
assert_eq!(
PtxBugClass::EarlyExitBeforeBarrier.code(),
"EARLY_EXIT_BARRIER"
);
assert_eq!(
PtxBugClass::EarlyExitBeforeBarrier.severity(),
BugSeverity::Critical
);
}
#[test]
fn test_parity114_attention_kv_loop_safe() {
let ptx = r"
.visible .entry flash_attention() {
kv_loop:
bar.sync 0;
wmma.mma.sync.aligned.row.col.m16n16k16.f32.f16.f16.f32;
bra kv_loop;
kv_loop_end:
@!%p_valid bra exit;
st.global.f32 [%out], %f0;
done:
ret;
}
";
let result = PtxBugAnalyzer::strict().analyze(ptx);
assert!(!result.has_bug(&PtxBugClass::EarlyExitBeforeBarrier));
}