Skip to main content

trueno_ptx_debug/analyzer/
address_space.rs

1//! Address Space Validator - validates correct address space usage
2
3use crate::bugs::Severity;
4use crate::parser::types::{Modifier, Opcode};
5use crate::parser::{Instruction, Operand, PtxModule, SourceLocation, Statement};
6use std::collections::HashSet;
7
8/// Defect: Generic addressing of shared memory
9#[derive(Debug, Clone)]
10pub struct GenericSharedBug {
11    /// Source location
12    pub location: SourceLocation,
13    /// Instruction that triggered the bug
14    pub instruction: Instruction,
15    /// Severity
16    pub severity: Severity,
17    /// Fix suggestion
18    pub fix: String,
19}
20
21/// Address Space Validator
22pub struct AddressSpaceValidator {
23    /// Registers holding cvta.shared results (generic shared addresses)
24    shared_base_regs: HashSet<String>,
25}
26
27impl AddressSpaceValidator {
28    /// Create a new address space validator
29    pub fn new() -> Self {
30        Self {
31            shared_base_regs: HashSet::new(),
32        }
33    }
34
35    /// Detect generic addressing of shared memory (F021)
36    ///
37    /// WRONG: cvta.shared.u64 %rd, smem; ld.u32 [%rd]
38    /// RIGHT: ld.shared.u32 [smem_offset]
39    pub fn detect_generic_shared_access(&mut self, module: &PtxModule) -> Vec<GenericSharedBug> {
40        let mut bugs = Vec::new();
41
42        for kernel in &module.kernels {
43            self.shared_base_regs.clear();
44
45            for stmt in &kernel.body {
46                if let Statement::Instruction(instr) = stmt {
47                    self.scan_generic_shared_instruction(instr, &mut bugs);
48                }
49            }
50        }
51
52        bugs
53    }
54
55    /// Track a `cvta.shared` destination, then flag a generic ld/st that uses it.
56    fn scan_generic_shared_instruction(
57        &mut self,
58        instr: &Instruction,
59        bugs: &mut Vec<GenericSharedBug>,
60    ) {
61        // Track cvta.shared destinations
62        if instr.opcode == Opcode::Cvta && self.has_shared_modifier(instr) {
63            if let Some(Operand::Register(dest)) = instr.operands.first() {
64                self.shared_base_regs.insert(dest.clone());
65            }
66        }
67
68        // Detect generic ld/st using tracked registers
69        if (instr.opcode != Opcode::Ld && instr.opcode != Opcode::St)
70            || self.has_space_modifier(instr)
71        {
72            return;
73        }
74
75        // Check if address operand uses a generic shared register
76        let addr_operand = if instr.opcode == Opcode::Ld {
77            instr.operands.get(1)
78        } else {
79            instr.operands.first()
80        };
81
82        if let Some(operand) = addr_operand {
83            if self.uses_generic_shared_reg(operand) {
84                bugs.push(GenericSharedBug {
85                    location: instr.location.clone(),
86                    instruction: instr.clone(),
87                    severity: Severity::Critical,
88                    fix: "Use ld.shared with 32-bit offset instead".into(),
89                });
90            }
91        }
92    }
93
94    /// Detect shared memory using 64-bit addresses (F022)
95    pub fn detect_shared_mem_u64(&self, module: &PtxModule) -> Vec<GenericSharedBug> {
96        let mut bugs = Vec::new();
97
98        for kernel in &module.kernels {
99            for stmt in &kernel.body {
100                if let Statement::Instruction(instr) = stmt {
101                    // Check for ld.shared.u64 or st.shared.u64 with 64-bit address
102                    if self.has_shared_modifier(instr) && self.has_u64_modifier(instr) {
103                        bugs.push(GenericSharedBug {
104                            location: instr.location.clone(),
105                            instruction: instr.clone(),
106                            severity: Severity::High,
107                            fix: "Use 32-bit offset for shared memory addressing".into(),
108                        });
109                    }
110                }
111            }
112        }
113
114        bugs
115    }
116
117    /// Detect cvta.shared inside loops (F083)
118    pub fn detect_loop_cvta_shared(&self, module: &PtxModule) -> Vec<GenericSharedBug> {
119        let mut bugs = Vec::new();
120
121        for kernel in &module.kernels {
122            let mut in_loop = false;
123            let mut loop_start_labels = HashSet::new();
124
125            for stmt in &kernel.body {
126                match stmt {
127                    Statement::Label(label) => {
128                        // Simple heuristic: label containing "loop" starts a loop
129                        if label.to_lowercase().contains("loop") {
130                            in_loop = true;
131                            loop_start_labels.insert(label.clone());
132                        }
133                    }
134                    Statement::Instruction(instr) => {
135                        self.scan_loop_cvta_instruction(
136                            instr,
137                            &mut in_loop,
138                            &loop_start_labels,
139                            &mut bugs,
140                        );
141                    }
142                    _ => {}
143                }
144            }
145        }
146
147        bugs
148    }
149
150    /// Clear the loop flag on a backward branch, then flag `cvta.shared` seen inside a loop.
151    fn scan_loop_cvta_instruction(
152        &self,
153        instr: &Instruction,
154        in_loop: &mut bool,
155        loop_start_labels: &HashSet<String>,
156        bugs: &mut Vec<GenericSharedBug>,
157    ) {
158        // Check for backward branch (loop end)
159        if instr.opcode == Opcode::Bra {
160            for operand in &instr.operands {
161                if let Operand::Label(target) = operand {
162                    if loop_start_labels.contains(target) {
163                        *in_loop = false;
164                    }
165                }
166            }
167        }
168
169        // Detect cvta.shared inside loop
170        if *in_loop && instr.opcode == Opcode::Cvta && self.has_shared_modifier(instr) {
171            bugs.push(GenericSharedBug {
172                location: instr.location.clone(),
173                instruction: instr.clone(),
174                severity: Severity::High,
175                fix: "Move cvta.shared outside loop to reduce register pressure".into(),
176            });
177        }
178    }
179
180    fn has_shared_modifier(&self, instr: &Instruction) -> bool {
181        instr
182            .modifiers
183            .iter()
184            .any(|m| matches!(m, Modifier::Shared))
185    }
186
187    fn has_space_modifier(&self, instr: &Instruction) -> bool {
188        instr
189            .modifiers
190            .iter()
191            .any(|m| m.as_address_space().is_some())
192    }
193
194    fn has_u64_modifier(&self, instr: &Instruction) -> bool {
195        instr
196            .modifiers
197            .iter()
198            .any(|m| matches!(m, Modifier::U64 | Modifier::B64))
199    }
200
201    fn uses_generic_shared_reg(&self, operand: &Operand) -> bool {
202        match operand {
203            Operand::Register(reg) => self.shared_base_regs.contains(reg),
204            Operand::Memory(addr) => {
205                // Check if the memory address contains a generic shared register
206                self.shared_base_regs.iter().any(|reg| addr.contains(reg))
207            }
208            _ => false,
209        }
210    }
211}
212
213impl Default for AddressSpaceValidator {
214    fn default() -> Self {
215        Self::new()
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use crate::parser::Parser;
223
224    // F021: No cvta.shared followed by generic ld/st
225    #[test]
226    fn f021_no_generic_shared_access() {
227        let ptx = r#"
228            .version 8.0
229            .target sm_70
230            .address_size 64
231
232            .entry test()
233            {
234                .reg .u32 %r<10>;
235                ld.shared.u32 %r0, [%r1];
236                ret;
237            }
238        "#;
239        let mut parser = Parser::new(ptx).expect("parser creation should succeed");
240        let module = parser.parse().expect("parsing should succeed");
241
242        let mut validator = AddressSpaceValidator::new();
243        let bugs = validator.detect_generic_shared_access(&module);
244
245        assert!(
246            bugs.is_empty(),
247            "F021: Should have no generic shared access bugs"
248        );
249    }
250
251    // F023: Direct .shared addressing preferred
252    #[test]
253    fn f023_direct_shared_addressing() {
254        let ptx = r#"
255            .version 8.0
256            .target sm_70
257            .address_size 64
258
259            .entry test()
260            {
261                .reg .u32 %r<10>;
262                ld.shared.u32 %r0, [%r1];
263                st.shared.u32 [%r2], %r0;
264                ret;
265            }
266        "#;
267        let mut parser = Parser::new(ptx).expect("parser creation should succeed");
268        let module = parser.parse().expect("parsing should succeed");
269
270        let mut validator = AddressSpaceValidator::new();
271        let bugs = validator.detect_generic_shared_access(&module);
272
273        assert!(
274            bugs.is_empty(),
275            "F023: Direct shared addressing should not trigger bugs"
276        );
277    }
278}