1use std::borrow::Cow;
20
21use cubecl_core::ir::{self as core, CubeFnSource, Id, SourceLoc, Variable};
22use hashbrown::HashMap;
23use rspirv::spirv::{DebugInfoFlags, FunctionControl, Word};
24use rspirv::sr::{
25 nonsemantic_debugprintf::DebugPrintfBuilder, nonsemantic_shader_debuginfo_100::DebugInfoBuilder,
26};
27
28use crate::{SpirvCompiler, SpirvTarget};
29
30pub const SIGNATURE: &str = concat!(env!("CARGO_PKG_NAME"), " v", env!("CARGO_PKG_VERSION"));
31
32#[derive(Clone, Copy, Debug, Default)]
33pub struct FunctionDefinition {
34 id: Word,
35 source: SourceFile,
36 line: u32,
37 col: u32,
38}
39
40#[derive(Clone, Debug, Default)]
41pub struct FunctionCall {
42 definition: FunctionDefinition,
43 inlined_at: Option<Word>,
44}
45
46#[derive(Clone, Debug)]
47pub struct DebugInfo {
48 function_ty: Word,
49
50 stack: Vec<FunctionCall>,
51 definitions: Definitions,
52 previous_loc: Option<SourceLoc>,
53}
54
55#[derive(Clone, Copy, Debug, Default)]
56struct SourceFile {
57 id: Word,
59 compilation_unit: Word,
61}
62
63#[derive(Clone, Debug, Default)]
64struct Definitions {
65 source_files: HashMap<String, SourceFile>,
67 functions: HashMap<CubeFnSource, FunctionDefinition>,
69}
70
71impl<T: SpirvTarget> SpirvCompiler<T> {
72 pub fn init_debug(&mut self) {
73 if self.debug_enabled() {
74 let return_ty = self.type_void();
75 let function_ty = self.debug_type_function(DebugInfoFlags::NONE, return_ty, []);
76 let entry_loc = self.opt.root_scope.debug.entry_loc.clone().unwrap();
77
78 self.debug_info = Some(DebugInfo {
79 function_ty,
80 stack: Default::default(),
81 definitions: Default::default(),
82 previous_loc: Some(entry_loc.clone()),
83 });
84
85 self.collect_sources();
86
87 let entry_def = self.definitions().functions[&entry_loc.source];
88 self.debug_entry_point(
89 entry_def.id,
90 entry_def.source.compilation_unit,
91 SIGNATURE,
92 "",
93 );
94 } else if self.debug_symbols {
95 let return_ty = self.type_void();
96 let function_ty = self.debug_type_function(DebugInfoFlags::NONE, return_ty, []);
97 self.debug_info = Some(DebugInfo {
98 function_ty,
99 stack: Default::default(),
100 definitions: Default::default(),
101 previous_loc: None,
102 });
103 }
104 }
105
106 fn collect_sources(&mut self) {
108 let cube_fns = self.opt.root_scope.debug.sources.borrow().clone();
109 let mut sources = HashMap::new();
110 for cube_fn in cube_fns.iter() {
111 if cube_fn.source_text.is_empty() {
114 sources
115 .entry(cube_fn.file.clone())
116 .or_insert(cube_fn.source_text.clone());
117 } else {
118 sources.insert(cube_fn.file.clone(), cube_fn.source_text.clone());
119 }
120 }
121
122 for (file, source_text) in sources {
123 self.debug_source_dedup(file, source_text);
124 }
125
126 for cube_fn in cube_fns.into_iter() {
127 let source = self.definitions().source_files[cube_fn.file.as_ref()];
128 let name = cube_fn.function_name.as_ref();
129 let mut function = FunctionDefinition {
130 id: 0,
131 source,
132 line: cube_fn.line,
133 col: cube_fn.column,
134 };
135 self.declare_debug_function(name, &mut function);
136 self.definitions().functions.insert(cube_fn, function);
137 }
138 }
139
140 pub fn declare_main(&mut self, kernel_name: &str) -> (Word, impl Fn(&mut Self) + 'static) {
141 let void = self.type_void();
142 let voidf = self.type_function(void, vec![]);
143
144 let definition = self
145 .debug_info
146 .as_ref()
147 .and_then(|info| info.previous_loc.clone())
148 .map(|loc| self.definitions().functions[&loc.source]);
149
150 if let Some(definition) = definition {
151 let main_call = FunctionCall {
152 definition,
153 inlined_at: None,
154 };
155
156 self.stack().push(main_call);
157 }
158
159 let main = self
160 .begin_function(void, None, FunctionControl::NONE, voidf)
161 .unwrap();
162 self.debug_name(main, kernel_name);
163
164 let func_id = definition.map(|it| it.id);
165
166 let setup = move |b: &mut Self| {
167 if let Some(func_id) = func_id {
168 b.debug_start_block();
169 b.debug_function_definition(func_id, main).unwrap();
170 }
171 };
172
173 (main, setup)
174 }
175
176 pub fn set_source_loc(&mut self, loc: &Option<SourceLoc>) {
177 if let Some(loc) = loc {
178 match self.debug_info().previous_loc.clone() {
179 Some(prev) if &prev != loc => {
180 self.debug_info().previous_loc = Some(loc.clone());
181 if prev.source != loc.source {
182 self.update_call(loc.clone(), prev);
183 self.debug_start_block();
184 } else {
185 let source = self.stack_top().definition.source.id;
186 self.debug_line(source, loc.line, loc.line, loc.column, loc.column)
187 .unwrap();
188 }
189 }
190 _ => {}
191 }
192 }
193 }
194
195 fn update_call(&mut self, loc: SourceLoc, prev: SourceLoc) {
196 let is_inlined = self.stack().len() > 1;
197 let call_fn = self.definitions().functions[&loc.source];
198 let inlined_at = is_inlined.then(|| {
199 let parent = self.stack_prev().clone();
200 self.debug_inlined_at(parent.definition.id, prev.line, parent.inlined_at)
201 });
202 self.stack_top().definition = call_fn;
203 self.stack_top().inlined_at = inlined_at;
204 }
205
206 pub fn compile_debug(&mut self, debug: core::NonSemantic) {
207 if let core::NonSemantic::Print {
208 format_string,
209 args,
210 } = &debug
211 {
212 let args = args
213 .iter()
214 .map(|arg| {
215 let var = self.compile_variable(*arg);
216 self.read(&var)
217 })
218 .collect::<Vec<_>>();
219 DebugPrintfBuilder::debug_printf(&mut self.builder, format_string, args).unwrap();
220 return;
221 }
222 if self.debug_enabled() {
223 match debug {
224 core::NonSemantic::Print { .. } => panic!("Should already be handled"),
225 core::NonSemantic::Comment { .. } => {
226 }
228 core::NonSemantic::EnterDebugScope => {
229 let new_top = self.stack_top().clone();
230 self.stack().push(new_top);
231 }
232 core::NonSemantic::ExitDebugScope => {
233 self.stack().pop();
234 }
235 };
236 }
237 }
238
239 fn definitions(&mut self) -> &mut Definitions {
240 &mut self.debug_info().definitions
241 }
242
243 fn stack(&mut self) -> &mut Vec<FunctionCall> {
244 &mut self.debug_info().stack
245 }
246
247 fn stack_top(&mut self) -> &mut FunctionCall {
248 self.debug_info().stack.last_mut().unwrap()
249 }
250
251 fn stack_prev(&mut self) -> &mut FunctionCall {
252 self.debug_info().stack.iter_mut().nth_back(1).unwrap()
253 }
254
255 fn debug_source_dedup(
257 &mut self,
258 file: impl AsRef<str>,
259 source_text: impl AsRef<str>,
260 ) -> SourceFile {
261 let existing = self.definitions().source_files.get(file.as_ref());
262 if let Some(existing) = existing {
263 *existing
264 } else {
265 let source = self.debug_source(file.as_ref(), Some(source_text.as_ref()));
266
267 let comp_unit = {
268 let version = self.const_u32(1);
269 let dwarf_version = self.const_u32(5);
270 let language = self.const_u32(13);
271 self.shader_debug_compilation_unit_id(
272 None,
273 version,
274 dwarf_version,
275 source,
276 language,
277 )
278 };
279
280 let source_file = SourceFile {
281 id: source,
282 compilation_unit: comp_unit,
283 };
284 self.definitions()
285 .source_files
286 .insert(file.as_ref().into(), source_file);
287 source_file
288 }
289 }
290
291 fn declare_debug_function(&mut self, name: &str, function: &mut FunctionDefinition) {
292 let debug_type = self.debug_info().function_ty;
293
294 function.id = self.debug_function(
295 name,
296 debug_type,
297 function.source.id,
298 function.line,
299 function.col,
300 function.source.compilation_unit,
301 name,
302 DebugInfoFlags::NONE,
303 function.line,
304 None,
305 );
306 }
307
308 pub fn debug_start_block(&mut self) {
309 if self.debug_enabled() {
310 let loc = self.debug_info().previous_loc.clone().unwrap();
311 let func = self.stack_top().definition;
312 let inlined = self.stack_top().inlined_at;
313
314 if let Some(inlined) = inlined {
315 self.debug_scope(func.id, Some(inlined)).unwrap()
316 } else {
317 self.debug_scope(func.id, None).unwrap()
318 };
319 self.debug_line(func.source.id, loc.line, loc.line, loc.column, loc.column)
320 .unwrap();
321 }
322 }
323
324 fn debug_enabled(&self) -> bool {
325 self.debug_symbols && self.opt.root_scope.debug.entry_loc.is_some()
326 }
327
328 #[track_caller]
329 pub fn debug_info(&mut self) -> &mut DebugInfo {
330 self.debug_info.as_mut().unwrap()
331 }
332
333 pub fn debug_name(&mut self, var: Word, name: impl Into<String>) {
334 if self.debug_symbols {
335 self.name(var, name);
336 }
337 }
338
339 pub fn debug_var_name(&mut self, id: Word, var: Variable) {
340 if self.debug_symbols {
341 let name = self.name_of_var(var);
342 self.debug_name(id, name);
343 }
344 }
345
346 pub fn debug_shared(&mut self, id: Word, index: Id) {
347 if self.debug_symbols {
348 let name = format!("shared({index})");
349 self.debug_name(id, name);
350 }
351 }
352
353 pub fn name_of_var(&mut self, var: Variable) -> Cow<'static, str> {
354 let var_names = self.opt.root_scope.debug.variable_names.clone();
355 let debug_name = var_names.borrow().get(&var).cloned();
356 debug_name.unwrap_or_else(|| var.to_string().into())
357 }
358}