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