rspirv/binary/
disassemble.rs

1use crate::binary::tracker::Type;
2use crate::binary::tracker::Type::{Float, Integer};
3use crate::dr;
4use crate::dr::Operand;
5use crate::dr::Operand::{LiteralBit32, LiteralBit64};
6use crate::spirv;
7
8use super::tracker;
9
10/// Trait for disassembling functionalities.
11pub trait Disassemble {
12    /// Disassembles the current object and returns the assembly code.
13    fn disassemble(&self) -> String;
14}
15
16impl Disassemble for dr::ModuleHeader {
17    fn disassemble(&self) -> String {
18        let (major, minor) = self.version();
19        let (vendor, _) = self.generator();
20        format!(
21            "; SPIR-V\n; Version: {}.{}\n; Generator: {}\n; Bound: {}",
22            major, minor, vendor, self.bound
23        )
24    }
25}
26
27include!("autogen_disas_operand.rs");
28
29impl Disassemble for dr::Operand {
30    fn disassemble(&self) -> String {
31        match *self {
32            dr::Operand::IdMemorySemantics(v) | dr::Operand::IdScope(v) | dr::Operand::IdRef(v) => {
33                format!("%{}", v)
34            }
35            dr::Operand::ImageOperands(v) => v.disassemble(),
36            dr::Operand::FPFastMathMode(v) => v.disassemble(),
37            dr::Operand::SelectionControl(v) => v.disassemble(),
38            dr::Operand::LoopControl(v) => v.disassemble(),
39            dr::Operand::FunctionControl(v) => v.disassemble(),
40            dr::Operand::MemorySemantics(v) => v.disassemble(),
41            dr::Operand::MemoryAccess(v) => v.disassemble(),
42            dr::Operand::KernelProfilingInfo(v) => v.disassemble(),
43            _ => format!("{}", self),
44        }
45    }
46}
47
48/// Disassembles each instruction in `insts` and joins them together
49/// with the given `delimiter`.
50fn disas_join(insts: &[impl Disassemble], delimiter: &str) -> String {
51    insts
52        .iter()
53        .map(|i| i.disassemble())
54        .collect::<Vec<String>>()
55        .join(delimiter)
56}
57
58fn disas_instruction<F>(inst: &dr::Instruction, space: &str, disas_operands: F) -> String
59where
60    F: Fn(&Vec<Operand>) -> String,
61{
62    format!(
63        "{rid}Op{opcode}{rtype}{space}{operands}",
64        rid = inst
65            .result_id
66            .map_or(String::new(), |w| format!("%{} = ", w)),
67        opcode = inst.class.opname,
68        // extra space both before and after the result type
69        rtype = inst
70            .result_type
71            .map_or(String::new(), |w| format!("  %{}{}", w, space)),
72        space = space,
73        operands = disas_operands(&inst.operands)
74    )
75}
76
77impl Disassemble for dr::Instruction {
78    fn disassemble(&self) -> String {
79        let space = if !self.operands.is_empty() { " " } else { "" };
80        disas_instruction(self, space, |operands| disas_join(operands, " "))
81    }
82}
83
84impl Disassemble for dr::Block {
85    fn disassemble(&self) -> String {
86        let label = self
87            .label
88            .as_ref()
89            .map_or(String::new(), |i| i.disassemble());
90        format!(
91            "{label}\n{insts}",
92            label = label,
93            insts = disas_join(&self.instructions, "\n")
94        )
95    }
96}
97
98impl Disassemble for dr::Function {
99    fn disassemble(&self) -> String {
100        let def = self.def.as_ref().map_or(String::new(), |i| i.disassemble());
101        let end = self.end.as_ref().map_or(String::new(), |i| i.disassemble());
102        if self.parameters.is_empty() {
103            format!(
104                "{def}\n{blocks}\n{end}",
105                def = def,
106                blocks = disas_join(&self.blocks, "\n"),
107                end = end
108            )
109        } else {
110            format!(
111                "{def}\n{params}\n{blocks}\n{end}",
112                def = def,
113                params = disas_join(&self.parameters, "\n"),
114                blocks = disas_join(&self.blocks, "\n"),
115                end = end
116            )
117        }
118    }
119}
120
121/// Pushes the given value to the given container if the value is not empty.
122macro_rules! push {
123    ($container: expr, $val: expr) => {
124        if !$val.is_empty() {
125            $container.push($val)
126        }
127    };
128}
129
130impl Disassemble for dr::Module {
131    /// Disassembles this module and returns the disassembly text.
132    ///
133    /// This method will try to link information together to be wise. E.g.,
134    /// If the extended instruction set is recognized, the symbolic opcode for
135    /// instructions in it will be shown.
136    fn disassemble(&self) -> String {
137        let mut ext_inst_set_tracker = tracker::ExtInstSetTracker::new();
138        for i in &self.ext_inst_imports {
139            ext_inst_set_tracker.track(i)
140        }
141
142        let mut text = vec![];
143        if let Some(ref header) = self.header {
144            push!(&mut text, header.disassemble());
145        }
146
147        let mut global_type_tracker = tracker::TypeTracker::new();
148        for t in &self.types_global_values {
149            global_type_tracker.track(t)
150        }
151
152        let global_insts = self
153            .global_inst_iter()
154            .map(|i| match i.class.opcode {
155                spirv::Op::Constant => disas_constant(i, &global_type_tracker),
156                _ => i.disassemble(),
157            })
158            .collect::<Vec<String>>()
159            .join("\n");
160        push!(&mut text, global_insts);
161
162        // TODO: Code here is essentially duplicated. Ideally we should be able
163        // to call dr::Function and dr::BasicBlock's disassemble() method here
164        // but because of the ExtInstSetTracker, we are not able to directly.
165        for f in &self.functions {
166            push!(
167                &mut text,
168                f.def.as_ref().map_or(String::new(), |i| i.disassemble())
169            );
170            push!(&mut text, disas_join(&f.parameters, "\n"));
171            for bb in &f.blocks {
172                push!(
173                    &mut text,
174                    bb.label.as_ref().map_or(String::new(), |i| i.disassemble())
175                );
176                for inst in &bb.instructions {
177                    match inst.class.opcode {
178                        spirv::Op::ExtInst => {
179                            push!(&mut text, disas_ext_inst(inst, &ext_inst_set_tracker))
180                        }
181                        _ => push!(&mut text, inst.disassemble()),
182                    }
183                }
184            }
185            push!(
186                &mut text,
187                f.end.as_ref().map_or(String::new(), |i| i.disassemble())
188            );
189        }
190
191        text.join("\n")
192    }
193}
194
195// TODO: properly disassemble float literals (handle infinity, NaN, 16-bit floats, etc.)
196// in order to match `spirv-dis`'s output
197fn disas_constant(inst: &dr::Instruction, type_tracker: &tracker::TypeTracker) -> String {
198    debug_assert_eq!(inst.class.opcode, spirv::Op::Constant);
199    debug_assert_eq!(inst.operands.len(), 1);
200    let literal_type = type_tracker.resolve(inst.result_type.unwrap());
201    match inst.operands[0] {
202        LiteralBit32(value) => disas_instruction(inst, " ", |_| {
203            disas_literal_bit_operand(value, &literal_type.unwrap())
204        }),
205        LiteralBit64(value) => disas_instruction(inst, " ", |_| {
206            disas_literal_bit_operand(value, &literal_type.unwrap())
207        }),
208        _ => inst.disassemble(),
209    }
210}
211
212#[inline]
213fn disas_literal_bit_operand<T: DisassembleLiteralBit>(value: T, literal_type: &Type) -> String {
214    DisassembleLiteralBit::disas_literal_bit(value, literal_type)
215}
216
217trait DisassembleLiteralBit {
218    fn disas_literal_bit(value: Self, literal_type: &Type) -> String;
219}
220
221impl DisassembleLiteralBit for u32 {
222    fn disas_literal_bit(value: u32, literal_type: &Type) -> String {
223        match literal_type {
224            Integer(_, true) => (value as i32).to_string(),
225            Integer(_, false) => value.to_string(),
226            Float(_) => f32::from_bits(value).to_string(),
227        }
228    }
229}
230
231impl DisassembleLiteralBit for u64 {
232    fn disas_literal_bit(value: u64, literal_type: &Type) -> String {
233        match literal_type {
234            Integer(_, true) => (value as i64).to_string(),
235            Integer(_, false) => value.to_string(),
236            Float(_) => f64::from_bits(value).to_string(),
237        }
238    }
239}
240
241fn disas_ext_inst(
242    inst: &dr::Instruction,
243    ext_inst_set_tracker: &tracker::ExtInstSetTracker,
244) -> String {
245    if inst.operands.len() < 2 {
246        return inst.disassemble();
247    }
248    if let (&dr::Operand::IdRef(id), &dr::Operand::LiteralExtInstInteger(opcode)) =
249        (&inst.operands[0], &inst.operands[1])
250    {
251        if !ext_inst_set_tracker.have(id) {
252            return inst.disassemble();
253        }
254        if let Some(grammar) = ext_inst_set_tracker.resolve(id, opcode) {
255            let mut operands = vec![inst.operands[0].disassemble(), grammar.opname.to_string()];
256            for operand in &inst.operands[2..] {
257                operands.push(operand.disassemble())
258            }
259            disas_instruction(inst, " ", |_| operands.join(" "))
260        } else {
261            inst.disassemble()
262        }
263    } else {
264        inst.disassemble()
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use crate::binary::Disassemble;
271    use crate::dr;
272    use crate::spirv;
273
274    #[test]
275    fn test_disassemble_operand_function_control() {
276        let o = dr::Operand::FunctionControl(spirv::FunctionControl::NONE);
277        assert_eq!("None", o.disassemble());
278        let o = dr::Operand::FunctionControl(spirv::FunctionControl::INLINE);
279        assert_eq!("Inline", o.disassemble());
280        let o = dr::Operand::FunctionControl(
281            spirv::FunctionControl::INLINE | spirv::FunctionControl::PURE,
282        );
283        assert_eq!("Inline|Pure", o.disassemble());
284        let o = dr::Operand::FunctionControl(spirv::FunctionControl::all());
285        assert_eq!("Inline|DontInline|Pure|Const|OptNoneINTEL", o.disassemble());
286    }
287
288    #[test]
289    fn test_disassemble_operand_memory_semantics() {
290        let o = dr::Operand::MemorySemantics(spirv::MemorySemantics::NONE);
291        assert_eq!("None", o.disassemble());
292        let o = dr::Operand::MemorySemantics(spirv::MemorySemantics::RELAXED);
293        assert_eq!("None", o.disassemble());
294        let o = dr::Operand::MemorySemantics(spirv::MemorySemantics::RELEASE);
295        assert_eq!("Release", o.disassemble());
296        let o = dr::Operand::MemorySemantics(
297            spirv::MemorySemantics::RELEASE | spirv::MemorySemantics::WORKGROUP_MEMORY,
298        );
299        assert_eq!("Release|WorkgroupMemory", o.disassemble());
300    }
301
302    #[test]
303    fn test_disassemble_module_one_inst_in_each_section() {
304        let mut b = dr::Builder::new();
305
306        b.capability(spirv::Capability::Shader);
307        b.extension("awesome-extension");
308        b.ext_inst_import("GLSL.std.450");
309        b.memory_model(spirv::AddressingModel::Logical, spirv::MemoryModel::Simple);
310        b.source(spirv::SourceLanguage::GLSL, 450, None, None::<String>);
311
312        let void = b.type_void();
313        let float32 = b.type_float(32);
314        let voidfvoid = b.type_function(void, vec![void]);
315
316        let f = b
317            .begin_function(
318                void,
319                None,
320                spirv::FunctionControl::DONT_INLINE | spirv::FunctionControl::CONST,
321                voidfvoid,
322            )
323            .unwrap();
324        b.begin_block(None).unwrap();
325        let var = b.variable(float32, None, spirv::StorageClass::Function, None);
326        b.ret().unwrap();
327        b.end_function().unwrap();
328
329        b.entry_point(spirv::ExecutionModel::Fragment, f, "main", vec![]);
330        b.execution_mode(f, spirv::ExecutionMode::OriginUpperLeft, vec![]);
331        b.name(f, "main");
332        b.decorate(var, spirv::Decoration::RelaxedPrecision, vec![]);
333
334        assert_eq!(
335            b.module().disassemble(),
336            "; SPIR-V\n\
337                    ; Version: 1.6\n\
338                    ; Generator: rspirv\n\
339                    ; Bound: 8\n\
340                    OpCapability Shader\n\
341                    OpExtension \"awesome-extension\"\n\
342                    %1 = OpExtInstImport \"GLSL.std.450\"\n\
343                    OpMemoryModel Logical Simple\n\
344                    OpEntryPoint Fragment %5 \"main\"\n\
345                    OpExecutionMode %5 OriginUpperLeft\n\
346                    OpSource GLSL 450\n\
347                    OpName %5 \"main\"\n\
348                    OpDecorate %7 RelaxedPrecision\n\
349                    %2 = OpTypeVoid\n\
350                    %3 = OpTypeFloat 32\n\
351                    %4 = OpTypeFunction %2 %2\n\
352                    %5 = OpFunction  %2  DontInline|Const %4\n\
353                    %6 = OpLabel\n\
354                    %7 = OpVariable  %3  Function\n\
355                    OpReturn\n\
356                    OpFunctionEnd"
357        );
358    }
359
360    #[test]
361    fn test_disassemble_literal_bit_constants() {
362        let mut b = dr::Builder::new();
363
364        b.capability(spirv::Capability::Shader);
365        b.ext_inst_import("GLSL.std.450");
366        b.source(spirv::SourceLanguage::GLSL, 450, None, None::<String>);
367
368        let void = b.type_void();
369        let int32 = b.type_int(32, 1);
370        let int64 = b.type_int(64, 1);
371        let uint32 = b.type_int(32, 0);
372        let uint64 = b.type_int(64, 0);
373        let float32 = b.type_float(32);
374        let float64 = b.type_float(64);
375        let voidfvoid = b.type_function(void, vec![void]);
376
377        let f = b
378            .begin_function(
379                void,
380                None,
381                spirv::FunctionControl::DONT_INLINE | spirv::FunctionControl::CONST,
382                voidfvoid,
383            )
384            .unwrap();
385        b.begin_block(None).unwrap();
386        let signed_i32_value: i32 = -1;
387        let signed_i64_value: i64 = -1;
388        let f32_value: f32 = -2.0;
389        let f64_value: f64 = 9.26;
390        b.constant_bit32(int32, signed_i32_value as u32);
391        b.constant_bit64(int64, signed_i64_value as u64);
392        b.constant_bit32(uint32, signed_i32_value as u32);
393        b.constant_bit64(uint64, signed_i64_value as u64);
394        b.constant_bit32(float32, f32_value.to_bits());
395        b.constant_bit64(float64, f64_value.to_bits());
396        b.ret().unwrap();
397        b.end_function().unwrap();
398
399        b.entry_point(spirv::ExecutionModel::Fragment, f, "main", vec![]);
400        b.execution_mode(f, spirv::ExecutionMode::OriginUpperLeft, vec![]);
401        b.name(f, "main");
402
403        assert_eq!(
404            b.module().disassemble(),
405            "; SPIR-V\n\
406                    ; Version: 1.6\n\
407                    ; Generator: rspirv\n\
408                    ; Bound: 18\n\
409                    OpCapability Shader\n\
410                    %1 = OpExtInstImport \"GLSL.std.450\"\n\
411                    OpEntryPoint Fragment %10 \"main\"\n\
412                    OpExecutionMode %10 OriginUpperLeft\n\
413                    OpSource GLSL 450\n\
414                    OpName %10 \"main\"\n\
415                    %2 = OpTypeVoid\n\
416                    %3 = OpTypeInt 32 1\n\
417                    %4 = OpTypeInt 64 1\n\
418                    %5 = OpTypeInt 32 0\n\
419                    %6 = OpTypeInt 64 0\n\
420                    %7 = OpTypeFloat 32\n\
421                    %8 = OpTypeFloat 64\n\
422                    %9 = OpTypeFunction %2 %2\n\
423                    %12 = OpConstant  %3  -1\n\
424                    %13 = OpConstant  %4  -1\n\
425                    %14 = OpConstant  %5  4294967295\n\
426                    %15 = OpConstant  %6  18446744073709551615\n\
427                    %16 = OpConstant  %7  -2\n\
428                    %17 = OpConstant  %8  9.26\n\
429                    %10 = OpFunction  %2  DontInline|Const %9\n\
430                    %11 = OpLabel\n\
431                    OpReturn\n\
432                    OpFunctionEnd"
433        );
434    }
435
436    #[test]
437    fn test_disassemble_ext_inst_glsl() {
438        let mut b = dr::Builder::new();
439
440        b.capability(spirv::Capability::Shader);
441        let glsl = b.ext_inst_import("GLSL.std.450");
442        b.memory_model(spirv::AddressingModel::Logical, spirv::MemoryModel::Simple);
443
444        let void = b.type_void();
445        let float32 = b.type_float(32);
446        let voidfvoid = b.type_function(void, vec![void]);
447
448        assert!(b
449            .begin_function(void, None, spirv::FunctionControl::NONE, voidfvoid)
450            .is_ok());
451        b.begin_block(None).unwrap();
452        let var = b.variable(float32, None, spirv::StorageClass::Function, None);
453        let args = std::iter::once(dr::Operand::IdRef(var));
454        b.ext_inst(float32, None, glsl, 6, args).unwrap();
455        b.ret().unwrap();
456        b.end_function().unwrap();
457
458        assert_eq!(
459            b.module().disassemble(),
460            "; SPIR-V\n\
461                    ; Version: 1.6\n\
462                    ; Generator: rspirv\n\
463                    ; Bound: 9\n\
464                    OpCapability Shader\n\
465                    %1 = OpExtInstImport \"GLSL.std.450\"\n\
466                    OpMemoryModel Logical Simple\n\
467                    %2 = OpTypeVoid\n\
468                    %3 = OpTypeFloat 32\n\
469                    %4 = OpTypeFunction %2 %2\n\
470                    %5 = OpFunction  %2  None %4\n\
471                    %6 = OpLabel\n\
472                    %7 = OpVariable  %3  Function\n\
473                    %8 = OpExtInst  %3  %1 FSign %7\n\
474                    OpReturn\n\
475                    OpFunctionEnd"
476        );
477    }
478
479    #[test]
480    fn test_disassemble_ext_inst_opencl() {
481        let mut b = dr::Builder::new();
482
483        let opencl = b.ext_inst_import("OpenCL.std");
484        b.memory_model(spirv::AddressingModel::Logical, spirv::MemoryModel::OpenCL);
485
486        let void = b.type_void();
487        let float32 = b.type_float(32);
488        let voidfvoid = b.type_function(void, vec![void]);
489
490        assert!(b
491            .begin_function(void, None, spirv::FunctionControl::NONE, voidfvoid)
492            .is_ok());
493        b.begin_block(None).unwrap();
494        let var = b.variable(float32, None, spirv::StorageClass::Function, None);
495
496        let args = std::iter::once(dr::Operand::IdRef(var));
497        b.ext_inst(float32, None, opencl, 15, args).unwrap();
498        b.ret().unwrap();
499
500        b.end_function().unwrap();
501
502        assert_eq!(
503            b.module().disassemble(),
504            "; SPIR-V\n\
505                    ; Version: 1.6\n\
506                    ; Generator: rspirv\n\
507                    ; Bound: 9\n\
508                    %1 = OpExtInstImport \"OpenCL.std\"\n\
509                    OpMemoryModel Logical OpenCL\n\
510                    %2 = OpTypeVoid\n\
511                    %3 = OpTypeFloat 32\n\
512                    %4 = OpTypeFunction %2 %2\n\
513                    %5 = OpFunction  %2  None %4\n\
514                    %6 = OpLabel\n\
515                    %7 = OpVariable  %3  Function\n\
516                    %8 = OpExtInst  %3  %1 cosh %7\n\
517                    OpReturn\n\
518                    OpFunctionEnd"
519        );
520    }
521}