1use crate::builder_spirv::BuilderSpirv;
5use crate::custom_insts::{self, CustomInst};
6use either::Either;
7use rspirv::dr::{Instruction, Module, Operand};
8use rspirv::spirv::{Decoration, Op, Word};
9use rustc_data_structures::fx::FxIndexMap;
10use rustc_span::{FileName, SourceFile};
11use rustc_span::{Span, source_map::SourceMap};
12use smallvec::SmallVec;
13use std::borrow::Cow;
14use std::marker::PhantomData;
15use std::sync::Arc;
16use std::{fmt, iter, slice, str};
17
18pub trait CustomDecoration<'a>: Sized {
32 const ENCODING_PREFIX: &'static str;
33
34 fn encode(self, w: &mut impl fmt::Write) -> fmt::Result;
35 fn decode(s: &'a str) -> Self;
36
37 fn encode_to_inst(self, id: Word) -> Instruction {
38 let mut encoded = Self::ENCODING_PREFIX.to_string();
39 self.encode(&mut encoded).unwrap();
40
41 Instruction::new(
42 Op::DecorateString,
43 None,
44 None,
45 vec![
46 Operand::IdRef(id),
47 Operand::Decoration(Decoration::UserTypeGOOGLE),
48 Operand::LiteralString(encoded),
49 ],
50 )
51 }
52
53 fn try_decode_from_inst(inst: &Instruction) -> Option<(Word, LazilyDecoded<'_, Self>)> {
54 if inst.class.opcode == Op::DecorateString
55 && inst.operands[1].unwrap_decoration() == Decoration::UserTypeGOOGLE
56 {
57 let id = inst.operands[0].unwrap_id_ref();
58 let prefixed_encoded = inst.operands[2].unwrap_literal_string();
59 let encoded = prefixed_encoded.strip_prefix(Self::ENCODING_PREFIX)?;
60
61 Some((
62 id,
63 LazilyDecoded {
64 encoded,
65 _marker: PhantomData,
66 },
67 ))
68 } else {
69 None
70 }
71 }
72
73 fn decode_all(module: &Module) -> DecodeAllIter<'_, Self> {
74 module
75 .annotations
76 .iter()
77 .filter_map(Self::try_decode_from_inst as fn(_) -> _)
78 }
79
80 fn remove_all(module: &mut Module) {
81 module
82 .annotations
83 .retain(|inst| Self::try_decode_from_inst(inst).is_none());
84 }
85}
86
87type DecodeAllIter<'a, D> = iter::FilterMap<
90 slice::Iter<'a, Instruction>,
91 fn(&'a Instruction) -> Option<(Word, LazilyDecoded<'a, D>)>,
92>;
93
94pub struct LazilyDecoded<'a, D> {
98 encoded: &'a str,
99 _marker: PhantomData<D>,
100}
101
102impl<'a, D: CustomDecoration<'a>> LazilyDecoded<'a, D> {
103 pub fn decode(&self) -> D {
104 D::decode(self.encoded)
105 }
106}
107
108pub struct ZombieDecoration<'a> {
109 pub reason: Cow<'a, str>,
110}
111
112impl<'a> CustomDecoration<'a> for ZombieDecoration<'a> {
113 const ENCODING_PREFIX: &'static str = "Z";
114
115 fn encode(self, w: &mut impl fmt::Write) -> fmt::Result {
116 let Self { reason } = self;
117 w.write_str(&reason)
118 }
119 fn decode(s: &'a str) -> Self {
120 Self { reason: s.into() }
121 }
122}
123
124#[derive(Copy, Clone)]
132pub struct SrcLocDecoration<'a> {
133 pub file_name: &'a str,
134 pub line_start: u32,
135 pub line_end: u32,
136 pub col_start: u32,
137 pub col_end: u32,
138}
139
140impl<'a> CustomDecoration<'a> for SrcLocDecoration<'a> {
141 const ENCODING_PREFIX: &'static str = "L";
142
143 fn encode(self, w: &mut impl fmt::Write) -> fmt::Result {
144 let Self {
145 file_name,
146 line_start,
147 line_end,
148 col_start,
149 col_end,
150 } = self;
151 write!(
152 w,
153 "{file_name}:{line_start}:{col_start}-{line_end}:{col_end}"
154 )
155 }
156 fn decode(s: &'a str) -> Self {
157 #[derive(Copy, Clone, Debug)]
158 struct InvalidSrcLoc<'a>(
159 #[allow(dead_code)] &'a str,
161 );
162 let err = InvalidSrcLoc(s);
163
164 let (s, col_end) = s.rsplit_once(':').ok_or(err).unwrap();
165 let (s, line_end) = s.rsplit_once('-').ok_or(err).unwrap();
166 let (s, col_start) = s.rsplit_once(':').ok_or(err).unwrap();
167 let (s, line_start) = s.rsplit_once(':').ok_or(err).unwrap();
168 let file_name = s;
169
170 Self {
171 file_name,
172 line_start: line_start.parse().unwrap(),
173 line_end: line_end.parse().unwrap(),
174 col_start: col_start.parse().unwrap(),
175 col_end: col_end.parse().unwrap(),
176 }
177 }
178}
179
180impl<'tcx> SrcLocDecoration<'tcx> {
181 pub fn from_rustc_span(span: Span, builder: &BuilderSpirv<'tcx>) -> Option<Self> {
182 if span.is_dummy() {
185 return None;
186 }
187
188 let (file, line_col_range) = builder.file_line_col_range_for_debuginfo(span);
189 let ((line_start, col_start), (line_end, col_end)) =
190 (line_col_range.start, line_col_range.end);
191
192 Some(Self {
193 file_name: file.file_name,
194 line_start,
195 line_end,
196 col_start,
197 col_end,
198 })
199 }
200}
201
202pub struct SpanRegenerator<'a> {
205 source_map: &'a SourceMap,
206 module: Either<&'a Module, &'a spirt::Module>,
207
208 src_loc_decorations: Option<FxIndexMap<Word, LazilyDecoded<'a, SrcLocDecoration<'a>>>>,
209
210 zombie_decorations: Option<FxIndexMap<Word, LazilyDecoded<'a, ZombieDecoration<'a>>>>,
213
214 spv_debug_info: Option<SpvDebugInfo<'a>>,
216}
217
218#[derive(Default)]
219struct SpvDebugInfo<'a> {
220 custom_ext_inst_set_import: Option<Word>,
223
224 id_to_op_constant_operand: FxIndexMap<Word, &'a Operand>,
227
228 id_to_op_string: FxIndexMap<Word, &'a str>,
229 files: FxIndexMap<&'a str, SpvDebugFile<'a>>,
230}
231
232impl<'a> SpvDebugInfo<'a> {
233 fn collect(module: Either<&'a Module, &'a spirt::Module>) -> Self {
234 let mut this = Self::default();
235
236 let module = match module {
237 Either::Left(module) => module,
238
239 Either::Right(module) => {
243 let cx = module.cx_ref();
244 match &module.debug_info {
245 spirt::ModuleDebugInfo::Spv(debug_info) => {
246 for sources in debug_info.source_languages.values() {
247 for (&file_name, src) in &sources.file_contents {
248 this.files
251 .entry(&cx[file_name])
252 .or_default()
253 .op_source_parts = [&src[..]].into_iter().collect();
254 }
255 }
256 }
257 }
258 return this;
259 }
260 };
261
262 this.custom_ext_inst_set_import = module
264 .ext_inst_imports
265 .iter()
266 .find(|inst| {
267 assert_eq!(inst.class.opcode, Op::ExtInstImport);
268 inst.operands[0].unwrap_literal_string() == &custom_insts::CUSTOM_EXT_INST_SET[..]
269 })
270 .map(|inst| inst.result_id.unwrap());
271
272 this.id_to_op_constant_operand.extend(
273 module
274 .types_global_values
275 .iter()
276 .filter(|inst| inst.class.opcode == Op::Constant)
277 .map(|inst| (inst.result_id.unwrap(), &inst.operands[0])),
278 );
279
280 let mut insts = module.debug_string_source.iter().peekable();
281 while let Some(inst) = insts.next() {
282 match inst.class.opcode {
283 Op::String => {
284 this.id_to_op_string.insert(
285 inst.result_id.unwrap(),
286 inst.operands[0].unwrap_literal_string(),
287 );
288 }
289 Op::Source if inst.operands.len() == 4 => {
290 let file_name_id = inst.operands[2].unwrap_id_ref();
291 if let Some(&file_name) = this.id_to_op_string.get(&file_name_id) {
292 let mut file = SpvDebugFile::default();
293 file.op_source_parts
294 .push(inst.operands[3].unwrap_literal_string());
295 while let Some(&next_inst) = insts.peek() {
296 if next_inst.class.opcode != Op::SourceContinued {
297 break;
298 }
299 insts.next();
300
301 file.op_source_parts
302 .push(next_inst.operands[0].unwrap_literal_string());
303 }
304
305 this.files.insert(file_name, file);
308 }
309 }
310 _ => {}
311 }
312 }
313 this
314 }
315}
316
317#[derive(Default)]
319struct SpvDebugFile<'a> {
320 op_source_parts: SmallVec<[&'a str; 1]>,
322
323 regenerated_rustc_source_file: Option<Arc<SourceFile>>,
324}
325
326impl<'a> SpanRegenerator<'a> {
327 pub fn new(source_map: &'a SourceMap, module: &'a Module) -> Self {
328 Self {
329 source_map,
330 module: Either::Left(module),
331
332 src_loc_decorations: None,
333 zombie_decorations: None,
334
335 spv_debug_info: None,
336 }
337 }
338
339 pub fn new_spirt(source_map: &'a SourceMap, module: &'a spirt::Module) -> Self {
340 Self {
341 source_map,
342 module: Either::Right(module),
343
344 src_loc_decorations: None,
345 zombie_decorations: None,
346
347 spv_debug_info: None,
348 }
349 }
350
351 pub fn src_loc_for_id(&mut self, id: Word) -> Option<SrcLocDecoration<'a>> {
352 self.src_loc_decorations
353 .get_or_insert_with(|| {
354 SrcLocDecoration::decode_all(self.module.left().unwrap()).collect()
355 })
356 .get(&id)
357 .map(|src_loc| src_loc.decode())
358 }
359
360 pub(crate) fn zombie_for_id(&mut self, id: Word) -> Option<ZombieDecoration<'a>> {
363 self.zombie_decorations
364 .get_or_insert_with(|| {
365 ZombieDecoration::decode_all(self.module.left().unwrap()).collect()
366 })
367 .get(&id)
368 .map(|zombie| zombie.decode())
369 }
370
371 pub fn src_loc_from_debug_inst(&mut self, inst: &Instruction) -> Option<SrcLocDecoration<'a>> {
375 let spv_debug_info = self
376 .spv_debug_info
377 .get_or_insert_with(|| SpvDebugInfo::collect(self.module));
378
379 let (file_id, line_start, line_end, col_start, col_end) = match inst.class.opcode {
380 Op::Line => {
381 let file = inst.operands[0].unwrap_id_ref();
382 let line = inst.operands[1].unwrap_literal_bit32();
383 let col = inst.operands[2].unwrap_literal_bit32();
384 (file, line, line, col, col)
385 }
386 Op::ExtInst
387 if Some(inst.operands[0].unwrap_id_ref())
388 == spv_debug_info.custom_ext_inst_set_import =>
389 {
390 match CustomInst::decode(inst) {
391 CustomInst::SetDebugSrcLoc {
392 file,
393 line_start,
394 line_end,
395 col_start,
396 col_end,
397 } => {
398 let const_u32 = |operand: Operand| {
399 spv_debug_info.id_to_op_constant_operand[&operand.unwrap_id_ref()]
400 .unwrap_literal_bit32()
401 };
402 (
403 file.unwrap_id_ref(),
404 const_u32(line_start),
405 const_u32(line_end),
406 const_u32(col_start),
407 const_u32(col_end),
408 )
409 }
410 custom_inst => {
411 unreachable!("src_loc_from_debug_inst({inst:?} => {custom_inst:?})")
412 }
413 }
414 }
415 _ => unreachable!("src_loc_from_debug_inst({inst:?})"),
416 };
417
418 spv_debug_info
419 .id_to_op_string
420 .get(&file_id)
421 .map(|&file_name| SrcLocDecoration {
422 file_name,
423 line_start,
424 line_end,
425 col_start,
426 col_end,
427 })
428 }
429
430 fn regenerate_rustc_source_file(&mut self, file_name: &str) -> Option<&SourceFile> {
431 let spv_debug_file = self
432 .spv_debug_info
433 .get_or_insert_with(|| SpvDebugInfo::collect(self.module))
434 .files
435 .get_mut(file_name)?;
436
437 let file = &mut spv_debug_file.regenerated_rustc_source_file;
438 if file.is_none() {
439 let src = match &spv_debug_file.op_source_parts[..] {
443 &[part] => Cow::Borrowed(part),
444 parts => parts.concat().into(),
445 };
446
447 let mut sm_file_name_candidates = [FileName::Custom(file_name.to_string())]
450 .into_iter()
451 .chain((0..).map(|i| FileName::Custom(format!("outdated({i}) {file_name}"))));
452
453 *file = sm_file_name_candidates.find_map(|sm_file_name_candidate| {
454 let sf = self
455 .source_map
456 .new_source_file(sm_file_name_candidate, src.clone().into_owned());
457
458 self.source_map.ensure_source_file_source_present(&sf);
462 let sf_src_matches = sf
463 .src
464 .as_ref()
465 .map(|sf_src| sf_src[..] == src[..])
466 .or_else(|| {
467 sf.external_src
468 .borrow()
469 .get_source()
470 .map(|sf_src| sf_src[..] == src[..])
471 })
472 .unwrap_or(false);
473
474 if sf_src_matches { Some(sf) } else { None }
475 });
476 }
477 file.as_deref()
478 }
479
480 pub fn src_loc_to_rustc(&mut self, src_loc: SrcLocDecoration<'_>) -> Option<Span> {
481 let SrcLocDecoration {
482 file_name,
483 line_start,
484 line_end,
485 col_start,
486 col_end,
487 } = src_loc;
488
489 let file = self.regenerate_rustc_source_file(file_name)?;
490
491 let line_col_to_bpos = |line: u32, col: u32| {
495 let line_idx_in_file = line.checked_sub(1)? as usize;
496 let line_bpos_range = file.line_bounds(line_idx_in_file);
497 let line_contents = file.get_line(line_idx_in_file)?;
498
499 let (mut cur_bpos, mut cur_col_display) = (line_bpos_range.start, 0);
501 let mut line_chars = line_contents.chars();
502 while cur_bpos < line_bpos_range.end && cur_col_display < col {
503 let ch = line_chars.next()?;
505 cur_bpos.0 += ch.len_utf8() as u32;
506 cur_col_display += rustc_span::char_width(ch) as u32;
507 }
508 Some(cur_bpos)
509 };
510
511 Some(Span::with_root_ctxt(
512 line_col_to_bpos(line_start, col_start)?,
513 line_col_to_bpos(line_end, col_end)?,
514 ))
515 }
516}