Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cutile-rs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

### Fixed

- Generated kernel entry setup and assumptions now use the user function's
signature as their source location, preserving the inlined body's own
statement locations and helper scopes.
- Generated constants without captured source spans use their containing
function definition, retaining exact user-expression locations and nested
cross-file inline caller chains.
Expand Down
7 changes: 5 additions & 2 deletions cutile-rs/cutile-compiler/src/kernel_entry_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use proc_macro2::Span;
use quote::ToTokens;
use std::collections::HashMap;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::visit_mut::VisitMut;
use syn::{Expr, FnArg, GenericArgument, ItemFn, ItemImpl, Lit, Stmt, Token};

Expand Down Expand Up @@ -657,8 +658,10 @@ pub fn generate_entry_point(
statements.push(final_stmnt);
fn_entry.block.stmts = statements;

// This source code is not visible.
let mut visitor = SpanSetter::new(Span::call_site());
// Generated entry setup has no user-written body. Attribute it, including
// assumptions and the call into the kernel, to the user's signature.
// The original kernel body keeps its own spans when it is inlined.
let mut visitor = SpanSetter::new(fn_item.sig.span());
visitor.visit_item_fn_mut(&mut fn_entry);

Ok((
Expand Down
173 changes: 172 additions & 1 deletion cutile-rs/cutile/tests/debug_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,121 @@ fn helper_instructions_keep_exact_lines_and_call_sites() {
});
}

#[test]
fn entry_wrapper_and_assumptions_use_the_user_signature() {
common::with_test_stack(|| {
use cutile_ir::bytecode::Opcode;

let signature_line = source_line(
include_str!("debug_info.rs"),
"fn source_kernel<const S: [i32; 1]>(out: &mut Tensor<i32, S>, x: i32) {",
);
for level in [DebugInfoLevel::Line, DebugInfoLevel::Full] {
let artifacts = compile(level);
let mut wrapper_ops = 0;
let mut wrapper_assumes = 0;
let mut inline_ops = 0;
visit_ops(
artifacts.module(),
&artifacts.module().functions,
&mut |op| {
let mut location = &op.location;
while let Location::CallSite { caller, .. } = location {
inline_ops += 1;
location = caller;
}
// Constants built from synthesized types have a separate source
// span path. This test covers generated syntax and inline callers.
if op.opcode == Opcode::Constant
&& !matches!(op.location, Location::CallSite { .. })
{
return;
}
if let Location::FileLineCol { filename, line, .. } = location {
assert!(filename.ends_with("debug_info.rs"), "{location:?}");
assert_eq!(*line, signature_line, "{:?}: {location:?}", op.opcode);
if !matches!(op.location, Location::CallSite { .. }) {
wrapper_ops += 1;
wrapper_assumes += usize::from(op.opcode == Opcode::Assume);
}
} else {
panic!("missing wrapper origin: {location:?}");
}
},
);
assert!(wrapper_ops > 1);
assert!(wrapper_assumes > 0);
assert!(inline_ops > 0);

// The wrapper origin must not replace the nested helper's own scope
// or either call site, including when the helper is in another file.
let helpers = include_str!("fixtures/debug_helpers.rs");
let offset_line = source_line(helpers, "x + 7");
let nested_line = source_line(helpers, "offset(x) * 2");
let kernel_line = source_line(include_str!("debug_info.rs"), "let value = nested(x);");
let mut nested_chain = false;
visit_ops(
artifacts.module(),
&artifacts.module().functions,
&mut |op| {
let Location::CallSite { callee, caller } = &op.location else {
return;
};
let Location::DebugInfo(info) = &**callee else {
return;
};
let DebugScope::Subprogram(scope) = &info.scope else {
return;
};
if scope.name != "offset" || info.line != offset_line {
return;
}
assert!(info.filename.ends_with("fixtures/debug_helpers.rs"));
let mut caller = &**caller;
for (name, file, line) in [
("nested", "fixtures/debug_helpers.rs", nested_line),
("source_kernel", "tests/debug_info.rs", kernel_line),
] {
let Location::CallSite {
callee,
caller: next,
} = caller
else {
panic!("missing {name}: {caller:?}");
};
let Location::DebugInfo(info) = &**callee else {
panic!("missing scope: {callee:?}");
};
let DebugScope::Subprogram(scope) = &info.scope else {
panic!("missing subprogram: {info:?}");
};
assert_eq!(scope.name, name);
assert!(info.filename.ends_with(file), "{info:?}");
assert_eq!(info.line, line);
caller = next;
}
assert!(
matches!(caller, Location::FileLineCol { line, .. } if *line == signature_line)
);
nested_chain = true;
},
);
assert!(
nested_chain,
"missing exact nested helper chain in {level:?}"
);

let dump = cutile_ir::decode_bytecode(&artifacts.bytecode().unwrap()).unwrap();
assert!(
dump.contains(&format!(
"line={signature_line}, name=\"source_kernel\", linkage=\"source_kernel_entry\""
)),
"{dump}"
);
}
});
}

#[test]
fn explicit_debug_modes_override_build_default_and_change_cache_keys() {
use cutile_compiler::cuda_tile_runtime_utils::TileirasOptions;
Expand Down Expand Up @@ -221,7 +336,12 @@ fn cubin_has_rust_lines_and_inline_frames() {
continue;
}
assert!(sections.contains(".debug_line"), "{sections}");
let sass = output(Command::new("nvdisasm").arg("--print-line-info").arg(&path));
let sass = output(
Command::new("nvdisasm")
.arg("--print-code")
.arg("--print-line-info")
.arg(&path),
);
assert!(sass.contains("debug_helpers.rs"), "{sass}");
assert!(sass.contains("debug_info.rs"), "{sass}");
assert!(!sass.contains("_core.rs"), "{sass}");
Expand All @@ -236,6 +356,57 @@ fn cubin_has_rust_lines_and_inline_frames() {
});
}

#[test]
#[ignore = "requires tileiras and nvdisasm"]
fn entry_setup_cubin_uses_signature_in_line_and_full_modes() {
common::with_test_stack(|| {
use cutile_compiler::cuda_tile_runtime_utils::{run_tileiras, TileirasOptions};
let signature = source_line(
include_str!("debug_info.rs"),
"fn source_kernel<const S: [i32; 1]>(out: &mut Tensor<i32, S>, x: i32) {",
);
let directory = CubinDirectory::new();
for level in [DebugInfoLevel::Line, DebugInfoLevel::Full] {
let artifacts = compile(level);
let flags =
TileirasOptions::from_compile_options(&CompileOptions::new().debug_info(level));
let cubin = run_tileiras(&artifacts.bytecode().unwrap(), "sm_120", &flags).unwrap();
let path = directory.0.join(format!("entry-{level:?}.cubin"));
std::fs::write(&path, cubin).unwrap();
let sass = output(
Command::new("nvdisasm")
.arg("--print-code")
.arg("--print-line-info")
.arg(&path),
);
let mut location = None;
let mut entry_found = false;
for line in sass.lines() {
if let Some(row) = line.trim().strip_prefix("//## File \"") {
let (file, line) = row.split_once("\", line ").unwrap();
location = Some((file, line.trim().parse::<u32>().unwrap()));
}
if line.trim_start().starts_with("/*0000*/") {
let (file, line) = location.unwrap_or_else(|| {
panic!("{level:?}: entry instruction must have a source location\n{sass}")
});
assert!(
file.ends_with("tests/debug_info.rs"),
"{level:?}: {file}\n{sass}"
);
assert_eq!(
line, signature,
"{level:?}: entry setup must use the signature\n{sass}"
);
entry_found = true;
break;
}
}
assert!(entry_found, "{level:?}: no entry instruction\n{sass}");
}
});
}

fn output(command: &mut Command) -> String {
let result = command
.output()
Expand Down
4 changes: 4 additions & 0 deletions cutile-rs/scripts/run_cpu_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ run_step \
"cutile debug provenance through the assembler" \
cargo test -p cutile --test debug_info nested_helper_cubin_lines_stay_inside_their_functions -- --ignored --nocapture

run_step \
"cutile entry setup locations through the assembler" \
cargo test -p cutile --test debug_info entry_setup_cubin_uses_signature_in_line_and_full_modes -- --ignored

run_step \
"cutile generated source locations through the assembler" \
cargo test -p cutile --test generated_source_locations generated_locations_survive_assembly -- --ignored
Expand Down
Loading