trueno_ptx_debug/analyzer/
address_space.rs1use crate::bugs::Severity;
4use crate::parser::types::{Modifier, Opcode};
5use crate::parser::{Instruction, Operand, PtxModule, SourceLocation, Statement};
6use std::collections::HashSet;
7
8#[derive(Debug, Clone)]
10pub struct GenericSharedBug {
11 pub location: SourceLocation,
13 pub instruction: Instruction,
15 pub severity: Severity,
17 pub fix: String,
19}
20
21pub struct AddressSpaceValidator {
23 shared_base_regs: HashSet<String>,
25}
26
27impl AddressSpaceValidator {
28 pub fn new() -> Self {
30 Self {
31 shared_base_regs: HashSet::new(),
32 }
33 }
34
35 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 fn scan_generic_shared_instruction(
57 &mut self,
58 instr: &Instruction,
59 bugs: &mut Vec<GenericSharedBug>,
60 ) {
61 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 if (instr.opcode != Opcode::Ld && instr.opcode != Opcode::St)
70 || self.has_space_modifier(instr)
71 {
72 return;
73 }
74
75 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 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 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 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 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 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 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 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 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 #[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 #[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}