1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! SPIR-T passes related to debuginfo.

use crate::custom_insts::{self, CustomInst, CustomOp};
use rustc_data_structures::fx::FxIndexSet;
use smallvec::SmallVec;
use spirt::transform::{InnerInPlaceTransform, Transformer};
use spirt::visit::InnerVisit;
use spirt::{
    spv, Attr, AttrSetDef, ConstCtor, Context, ControlNode, ControlNodeKind, DataInstKind,
    InternedStr, Module, OrdAssertEq, Value,
};

/// Replace our custom extended instruction debuginfo with standard SPIR-V ones.
//
// FIXME(eddyb) also handle `SrcLocDecoration`s (when `rspirv` isn't used on the
// SPIR-V output of `spirt::spv::lift`, as it's lossy wrt `OpLine`).
pub fn convert_custom_debuginfo_to_spv(module: &mut Module) {
    let cx = &module.cx();

    // FIXME(eddyb) reuse this collection work in some kind of "pass manager".
    let all_funcs = {
        let mut collector = super::ReachableUseCollector {
            cx,
            module,

            seen_types: FxIndexSet::default(),
            seen_consts: FxIndexSet::default(),
            seen_data_inst_forms: FxIndexSet::default(),
            seen_global_vars: FxIndexSet::default(),
            seen_funcs: FxIndexSet::default(),
        };
        for (export_key, &exportee) in &module.exports {
            export_key.inner_visit_with(&mut collector);
            exportee.inner_visit_with(&mut collector);
        }
        collector.seen_funcs
    };

    let mut transformer = CustomDebuginfoToSpv {
        cx,
        wk: &super::SpvSpecWithExtras::get().well_known,
        custom_ext_inst_set: cx.intern(&custom_insts::CUSTOM_EXT_INST_SET[..]),
    };
    for func in all_funcs {
        transformer.in_place_transform_func_decl(&mut module.funcs[func]);
    }
}

struct CustomDebuginfoToSpv<'a> {
    cx: &'a Context,
    wk: &'static super::SpvWellKnownWithExtras,

    /// Interned name for our custom "extended instruction set"
    /// (see `crate::custom_insts` for more details).
    custom_ext_inst_set: InternedStr,
}

impl Transformer for CustomDebuginfoToSpv<'_> {
    fn in_place_transform_control_node_def(
        &mut self,
        mut func_at_control_node: spirt::func_at::FuncAtMut<'_, ControlNode>,
    ) {
        // HACK(eddyb) this relies on the fact that `ControlNodeKind::Block` maps
        // to one original SPIR-V block, which may not necessarily be true, and
        // steps should be taken elsewhere to explicitly unset debuginfo, instead
        // of relying on the end of a SPIR-V block implicitly unsetting it all.
        // NOTE(eddyb) allowing debuginfo to apply *outside* of a `Block` could
        // be useful in allowing *some* structured control-flow to have debuginfo,
        // but that would likely require more work on the SPIR-T side.
        if let ControlNodeKind::Block { mut insts } = func_at_control_node.reborrow().def().kind {
            let mut current_file_line_col = None;

            // HACK(eddyb) buffering the `DataInst`s to remove from this block,
            // as iterating and modifying a list at the same time isn't supported.
            let mut insts_to_remove = SmallVec::<[_; 8]>::new();

            let mut func_at_inst_iter = func_at_control_node.reborrow().at(insts).into_iter();
            while let Some(func_at_inst) = func_at_inst_iter.next() {
                let inst = func_at_inst.position;
                let data_inst_def = func_at_inst.def();

                // FIXME(eddyb) deduplicate with `spirt_passes::diagnostics`.
                if let DataInstKind::SpvExtInst {
                    ext_set,
                    inst: ext_inst,
                } = self.cx[data_inst_def.form].kind
                {
                    if ext_set == self.custom_ext_inst_set {
                        let custom_op = CustomOp::decode(ext_inst);
                        match custom_op.with_operands(&data_inst_def.inputs) {
                            CustomInst::SetDebugSrcLoc {
                                file,
                                line_start: line,
                                line_end: _,
                                col_start: col,
                                col_end: _,
                            } => {
                                let const_ctor = |v: Value| match v {
                                    Value::Const(ct) => &self.cx[ct].ctor,
                                    _ => unreachable!(),
                                };
                                let const_str = |v: Value| match const_ctor(v) {
                                    &ConstCtor::SpvStringLiteralForExtInst(s) => s,
                                    _ => unreachable!(),
                                };
                                let const_u32 = |v: Value| match const_ctor(v) {
                                    ConstCtor::SpvInst(spv_inst) => {
                                        assert!(spv_inst.opcode == self.wk.OpConstant);
                                        match spv_inst.imms[..] {
                                            [spv::Imm::Short(_, x)] => x,
                                            _ => unreachable!(),
                                        }
                                    }
                                    _ => unreachable!(),
                                };
                                current_file_line_col =
                                    Some((const_str(file), const_u32(line), const_u32(col)));
                                insts_to_remove.push(inst);
                                continue;
                            }
                            CustomInst::ClearDebugSrcLoc => {
                                current_file_line_col = None;
                                insts_to_remove.push(inst);
                                continue;
                            }
                            CustomInst::PushInlinedCallFrame { .. }
                            | CustomInst::PopInlinedCallFrame => {
                                insts_to_remove.push(inst);
                                continue;
                            }
                            CustomInst::Abort { .. } => {
                                assert!(
                                    !custom_op.is_debuginfo(),
                                    "`CustomOp::{custom_op:?}` debuginfo not lowered"
                                );
                            }
                        }
                    }
                }

                // Add/remove the equivalent `Attr::SpvDebugLine` attribute.
                // FIXME(eddyb) this could use more caching.
                data_inst_def.attrs = self.cx.intern(AttrSetDef {
                    attrs: self.cx[data_inst_def.attrs]
                        .attrs
                        .iter()
                        .filter(|attr| !matches!(attr, Attr::SpvDebugLine { .. }))
                        .cloned()
                        .chain(
                            current_file_line_col.map(|(file, line, col)| Attr::SpvDebugLine {
                                file_path: OrdAssertEq(file),
                                line,
                                col,
                            }),
                        )
                        .collect(),
                });
            }

            // Finally remove the `DataInst`s buffered for removal earlier.
            for inst in insts_to_remove {
                insts.remove(inst, func_at_control_node.data_insts);
            }
            func_at_control_node.reborrow().def().kind = ControlNodeKind::Block { insts };
        }

        func_at_control_node.inner_in_place_transform_with(self);
    }
}