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