1use alloc::{string::String, vec::Vec};
2use cubecl_ir::{
3 dialect::general::PrintfOp,
4 pliron::{location::Source, value::Value},
5};
6
7use crate::ir::Scope;
8
9use super::CubeDebug;
10
11pub fn debug_pos_expand(scope: &Scope, line: u32, column: u32) {
19 if let Some(debug) = scope.debug_state() {
20 debug.set_pos(line, column);
21 }
22}
23
24pub fn debug_call_expand<C>(
26 scope: &Scope,
27 line: u32,
28 column: u32,
29 call: impl FnOnce(&Scope) -> C,
30) -> C {
31 debug_pos_expand(scope, line, column);
32 call(scope)
33}
34
35pub fn debug_source_expand<'a>(
38 scope: &'a Scope,
39 name: &'static str,
40 file: &'static str,
41 source_text: &'static str,
42 line: u32,
43 column: u32,
44) -> DebugFrame<'a> {
45 let Some(debug) = scope.debug_state() else {
46 return DebugFrame { scope: None };
47 };
48 let path = file.replace('\\', "/");
49 debug.add_source(&path, source_text);
50 debug.enter_fn(name, Source::new_from_file(scope.ctx(), path), line, column);
51 DebugFrame { scope: Some(scope) }
52}
53
54pub struct DebugFrame<'a> {
56 scope: Option<&'a Scope>,
57}
58
59impl Drop for DebugFrame<'_> {
60 fn drop(&mut self) {
61 if let Some(debug) = self.scope.and_then(Scope::debug_state) {
62 debug.exit_fn();
63 }
64 }
65}
66
67#[doc(hidden)]
74pub struct DebugVar<'a, E>(pub &'a E);
75
76#[doc(hidden)]
78pub trait DebugVarNamed {
79 fn debug_var(&self, scope: &Scope, name: &'static str);
80}
81
82impl<E: CubeDebug> DebugVarNamed for DebugVar<'_, E> {
83 fn debug_var(&self, scope: &Scope, name: &'static str) {
84 if scope.debug_state().is_some() {
85 self.0.set_debug_name(scope, name);
86 }
87 }
88}
89
90#[doc(hidden)]
92pub trait DebugVarUnnamed {
93 fn debug_var(&self, _scope: &Scope, _name: &'static str) {}
94}
95
96impl<E> DebugVarUnnamed for &DebugVar<'_, E> {}
97
98pub fn printf_expand(scope: &Scope, format_string: impl Into<String>, args: Vec<Value>) {
100 scope.register(&PrintfOp::new(scope.ctx_mut(), format_string.into(), args));
101}
102
103#[macro_export]
106macro_rules! debug_print {
107 ($format:literal, $($args:expr),*) => {
108 {
109 let _ = $format;
110 $(let _ = $args;)*
111 }
112 };
113 ($format:literal, $($args:expr,)*) => {
114 $crate::debug_print!($format, $($args),*);
115 };
116}
117
118#[macro_export]
121macro_rules! __expand_debug_print {
122 ($scope:expr, $format:expr, $($args:expr),*) => {
123 {
124 let args = $crate::__private::vec![$($crate::ir::ExpandValue::from($args).read_value($scope)),*];
125 $crate::frontend::printf_expand($scope, $format, args);
126 }
127 };
128 ($format:literal, $($args:expr,)*) => {
129 $crate::__expand_debug_print!($format, $($args),*)
130 };
131}
132
133pub mod cube_comment {
134 use alloc::string::ToString;
135
136 use cubecl_ir::{Scope, dialect::general::CommentOp};
137
138 pub fn expand(scope: &Scope, content: &str) {
139 scope.register(&CommentOp::new(scope.ctx_mut(), content.to_string()));
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use crate as cubecl;
146 use crate::prelude::*;
147 use alloc::vec::Vec;
148 use cubecl_ir::{
149 pliron::{linked_list::ContainsLinkedList, location::Location},
150 settings::{DebugInfo, Dim3, ExecutionMode, KernelSettings},
151 };
152
153 const DOUBLE_LINES: [u32; 2] = [line!() + 1, line!() + 3];
155 #[cube]
156 fn double(x: u32) -> u32 {
157 x + x
158 }
159
160 #[cube]
161 fn quadruple(x: u32) -> u32 {
162 double(x) + double(x)
163 }
164
165 #[cube]
166 fn add_double(x: u32) -> u32 {
167 x + double(x)
168 }
169
170 const TWO_SUMS_LINE: u32 = line!() + 3;
171 #[cube]
172 fn two_sums(x: u32) -> u32 {
173 (x + 1) * (x + 2)
174 }
175
176 const SPLIT_LINES: [u32; 2] = [line!() + 4, line!() + 5];
178 #[cube]
179 #[rustfmt::skip]
180 fn split_double(x: u32) -> u32 {
181 x
182 + double(x)
183 }
184
185 macro_rules! unary_cube_fn {
188 ($name:ident, $op:tt) => {
189 #[cube]
190 fn $name(x: u32) -> u32 {
191 $op(x + 1)
192 }
193 };
194 }
195
196 unary_cube_fn!(not_after_one, !);
197
198 #[cube(no_debug_symbols)]
199 fn plain_double(x: u32) -> u32 {
200 x + x
201 }
202
203 const CALL_PLAIN_LINE: u32 = line!() + 3;
204 #[cube]
205 fn call_plain(x: u32) -> u32 {
206 plain_double(x)
207 }
208
209 #[derive(CubeType, Clone, Copy)]
210 struct Pair {
211 a: u32,
212 }
213
214 const SUM_LINES: [u32; 2] = [line!() + 4, line!() + 5];
215 #[cube]
216 impl Pair {
217 fn sum(self, x: u32) -> u32 {
218 let y = self.a + x;
219 y * x
220 }
221 }
222
223 #[cube]
224 fn use_pair(x: u32) -> u32 {
225 let pair = Pair { a: x };
226 pair.sum(x)
227 }
228
229 #[derive(Clone, Copy, PartialEq, Eq)]
231 enum Sign {
232 Plus,
233 Minus,
234 }
235
236 #[cube]
237 fn pick(#[comptime] sign: Sign) -> comptime_type!(Sign) {
238 sign
239 }
240
241 #[cube]
242 fn signed(x: u32, #[comptime] sign: Sign) -> u32 {
243 let sign = pick(sign);
244 if comptime!(sign == Sign::Minus) {
245 x * x
246 } else {
247 x + x
248 }
249 }
250
251 fn locations(
253 debug_info: DebugInfo,
254 expand: impl FnOnce(&Scope, NativeExpand<u32>),
255 ) -> Vec<Location> {
256 let settings =
257 KernelSettings::new(Dim3::new_single(), ExecutionMode::Checked, AddressType::U32)
258 .debug_info(debug_info);
259 let scope = &Scope::root(settings);
260 let x = NativeExpand::<u32>::from_lit(scope, 2);
261 expand(scope, x);
262
263 let ctx = scope.ctx();
264 let block = scope.state().entry_func.get_entry_block(ctx);
265 let ops = block.deref(ctx).iter(ctx).collect::<Vec<_>>();
266 ops.into_iter()
267 .map(|op| op.deref(ctx).loc().clone())
268 .collect()
269 }
270
271 type Frame<'a> = (&'a str, u32, u32);
273
274 fn frame(loc: &Location) -> Option<Frame<'_>> {
275 match loc {
276 Location::Named { name, child_loc } => match child_loc.as_ref() {
277 Location::SrcPos { pos, .. } => Some((
278 name,
279 u32::try_from(pos.line).ok()?,
280 u32::try_from(pos.column).ok()?,
281 )),
282 _ => None,
283 },
284 _ => None,
285 }
286 }
287
288 fn call_sites(locations: &[Location]) -> Vec<(Frame<'_>, Frame<'_>)> {
290 locations
291 .iter()
292 .filter_map(|loc| match loc {
293 Location::CallSite { callee, caller } => Some((frame(callee)?, frame(caller)?)),
294 _ => None,
295 })
296 .collect()
297 }
298
299 #[test]
300 fn ops_get_the_line_of_their_expression() {
301 let locations = locations(DebugInfo::LineTables, |scope, x| {
302 double::expand(scope, x);
303 });
304 let frames = locations.iter().filter_map(frame).collect::<Vec<_>>();
305 assert!(
306 frames.iter().any(|(_, line, _)| *line == DOUBLE_LINES[1]),
307 "{frames:?}"
308 );
309 for (name, line, _) in frames {
310 assert_eq!(name, "double");
311 assert!(DOUBLE_LINES.contains(&line), "{line}");
312 }
313 }
314
315 #[test]
316 fn inlined_calls_keep_their_call_site() {
317 let locations = locations(DebugInfo::LineTables, |scope, x| {
318 quadruple::expand(scope, x);
319 });
320 let call_sites = call_sites(&locations);
321 assert!(!call_sites.is_empty(), "{locations:?}");
323 for ((inner, line, _), (outer, ..)) in call_sites {
324 assert_eq!(inner, "double");
325 assert!(DOUBLE_LINES.contains(&line), "{line}");
326 assert_eq!(outer, "quadruple");
327 }
328 }
329
330 #[test]
332 fn an_op_after_a_call_gets_the_position_of_its_expression() {
333 let locations = locations(DebugInfo::LineTables, |scope, x| {
334 add_double::expand(scope, x);
335 });
336 let (_, (_, call_line, call_column)) = call_sites(&locations)[0];
337 let (name, line, column) = locations.last().and_then(frame).expect("the addition");
338 assert_eq!((name, line), ("add_double", call_line));
339 assert!(column < call_column, "{column} {call_column}");
340 }
341
342 #[test]
345 fn ops_on_one_line_get_the_column_of_their_expression() {
346 let locations = locations(DebugInfo::LineTables, |scope, x| {
347 two_sums::expand(scope, x);
348 });
349 let mut columns = locations
350 .iter()
351 .filter_map(frame)
352 .filter(|(_, line, _)| *line == TWO_SUMS_LINE)
353 .map(|(.., column)| column)
354 .collect::<Vec<_>>();
355 let (.., product) = locations.last().and_then(frame).expect("the product");
356 columns.sort_unstable();
357 columns.dedup();
358 assert_eq!(columns.len(), 3, "{locations:?}");
359 assert_eq!(product, columns[0], "{locations:?}");
360 }
361
362 #[test]
365 fn a_call_on_a_continuation_line_keeps_its_line() {
366 let locations = locations(DebugInfo::LineTables, |scope, x| {
367 split_double::expand(scope, x);
368 });
369 let call_sites = call_sites(&locations);
370 assert!(!call_sites.is_empty(), "{locations:?}");
371 for (_, (outer, line, _)) in call_sites {
372 assert_eq!((outer, line), ("split_double", SPLIT_LINES[1]));
373 }
374 let (name, line, _) = locations.last().and_then(frame).expect("the addition");
375 assert_eq!((name, line), ("split_double", SPLIT_LINES[0]));
376 }
377
378 #[test]
381 fn a_function_from_macro_rules_gets_locations() {
382 let locations = locations(DebugInfo::LineTables, |scope, x| {
383 not_after_one::expand(scope, x);
384 });
385 assert!(
386 locations
387 .iter()
388 .filter_map(frame)
389 .any(|(name, ..)| name == "not_after_one"),
390 "{locations:?}"
391 );
392 }
393
394 #[test]
396 fn a_function_without_debug_symbols_has_the_location_of_its_call() {
397 let locations = locations(DebugInfo::LineTables, |scope, x| {
398 call_plain::expand(scope, x);
399 });
400 assert!(call_sites(&locations).is_empty(), "{locations:?}");
401 let last = locations.last().and_then(frame);
402 assert_eq!(
403 last.map(|(name, line, _)| (name, line)),
404 Some(("call_plain", CALL_PLAIN_LINE))
405 );
406 }
407
408 #[test]
410 fn a_method_is_one_frame() {
411 let locations = locations(DebugInfo::LineTables, |scope, x| {
412 use_pair::expand(scope, x);
413 });
414 let call_sites = call_sites(&locations);
415 for line in SUM_LINES {
416 assert!(
417 call_sites
418 .iter()
419 .any(|((inner, at, _), (outer, ..))| *inner == "Pair :: sum"
420 && *at == line
421 && *outer == "use_pair"),
422 "no op at line {line}: {call_sites:?}"
423 );
424 }
425 }
426
427 #[test]
430 fn a_plain_variable_needs_no_cube_debug() {
431 for sign in [Sign::Plus, Sign::Minus] {
432 let locations = locations(DebugInfo::LineTables, |scope, x| {
433 signed::expand(scope, x, sign);
434 });
435 assert!(
436 locations
437 .iter()
438 .filter_map(frame)
439 .any(|(name, ..)| name == "signed"),
440 "{locations:?}"
441 );
442 }
443 }
444
445 #[cube]
446 fn add_one(x: u32) -> u32 {
447 x + 1
448 }
449
450 #[cube]
451 fn sum_indices(n: u32) -> u32 {
452 let mut acc = 0u32;
453 for i in 0..n {
454 acc += add_one(i);
455 }
456 acc
457 }
458
459 #[test]
462 fn a_block_argument_keeps_its_name() {
463 locations(DebugInfo::Full, |scope, x| {
464 sum_indices::expand(scope, x);
465 });
466 }
467
468 #[test]
469 fn no_locations_without_debug_info() {
470 let locations = locations(DebugInfo::None, |scope, x| {
471 double::expand(scope, x);
472 });
473 assert!(locations.iter().all(Location::is_unknown));
474 }
475}