Skip to main content

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 let Some(grammar) = ext_inst_set_tracker.resolve(id, opcode) {
252            let mut operands = vec![inst.operands[0].disassemble(), grammar.opname.to_string()];
253            for operand in &inst.operands[2..] {
254                operands.push(operand.disassemble())
255            }
256            disas_instruction(inst, " ", |_| operands.join(" "))
257        } else {
258            inst.disassemble()
259        }
260    } else {
261        inst.disassemble()
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use crate::binary::Disassemble;
268    use crate::dr;
269    use crate::spirv;
270
271    #[test]
272    fn test_disassemble_operand_function_control() {
273        let o = dr::Operand::FunctionControl(spirv::FunctionControl::NONE);
274        assert_eq!("None", o.disassemble());
275        let o = dr::Operand::FunctionControl(spirv::FunctionControl::INLINE);
276        assert_eq!("Inline", o.disassemble());
277        let o = dr::Operand::FunctionControl(
278            spirv::FunctionControl::INLINE | spirv::FunctionControl::PURE,
279        );
280        assert_eq!("Inline|Pure", o.disassemble());
281        let o = dr::Operand::FunctionControl(spirv::FunctionControl::all());
282        assert_eq!("Inline|DontInline|Pure|Const|OptNoneEXT", o.disassemble());
283    }
284
285    #[test]
286    fn test_disassemble_operand_memory_semantics() {
287        let o = dr::Operand::MemorySemantics(spirv::MemorySemantics::RELAXED);
288        assert_eq!("None", o.disassemble());
289        let o = dr::Operand::MemorySemantics(spirv::MemorySemantics::RELEASE);
290        assert_eq!("Release", o.disassemble());
291        let o = dr::Operand::MemorySemantics(
292            spirv::MemorySemantics::RELEASE | spirv::MemorySemantics::WORKGROUP_MEMORY,
293        );
294        assert_eq!("Release|WorkgroupMemory", o.disassemble());
295    }
296
297    #[test]
298    fn test_disassemble_module_one_inst_in_each_section() {
299        let mut b = dr::Builder::new();
300
301        b.capability(spirv::Capability::Shader);
302        b.extension("awesome-extension");
303        b.ext_inst_import("GLSL.std.450");
304        b.memory_model(spirv::AddressingModel::Logical, spirv::MemoryModel::Simple);
305        b.source(spirv::SourceLanguage::GLSL, 450, None, None::<String>);
306
307        let void = b.type_void();
308        let float32 = b.type_float(32, None);
309        let voidfvoid = b.type_function(void, vec![void]);
310
311        let f = b
312            .begin_function(
313                void,
314                None,
315                spirv::FunctionControl::DONT_INLINE | spirv::FunctionControl::CONST,
316                voidfvoid,
317            )
318            .unwrap();
319        b.begin_block(None).unwrap();
320        let var = b.variable(float32, None, spirv::StorageClass::Function, None);
321        b.ret().unwrap();
322        b.end_function().unwrap();
323
324        b.entry_point(spirv::ExecutionModel::Fragment, f, "main", vec![]);
325        b.execution_mode(f, spirv::ExecutionMode::OriginUpperLeft, vec![]);
326        b.name(f, "main");
327        b.decorate(var, spirv::Decoration::RelaxedPrecision, vec![]);
328
329        assert_eq!(
330            b.module().disassemble(),
331            "; SPIR-V\n\
332                    ; Version: 1.6\n\
333                    ; Generator: rspirv\n\
334                    ; Bound: 8\n\
335                    OpCapability Shader\n\
336                    OpExtension \"awesome-extension\"\n\
337                    %1 = OpExtInstImport \"GLSL.std.450\"\n\
338                    OpMemoryModel Logical Simple\n\
339                    OpEntryPoint Fragment %5 \"main\"\n\
340                    OpExecutionMode %5 OriginUpperLeft\n\
341                    OpSource GLSL 450\n\
342                    OpName %5 \"main\"\n\
343                    OpDecorate %7 RelaxedPrecision\n\
344                    %2 = OpTypeVoid\n\
345                    %3 = OpTypeFloat 32\n\
346                    %4 = OpTypeFunction %2 %2\n\
347                    %5 = OpFunction  %2  DontInline|Const %4\n\
348                    %6 = OpLabel\n\
349                    %7 = OpVariable  %3  Function\n\
350                    OpReturn\n\
351                    OpFunctionEnd"
352        );
353    }
354
355    #[test]
356    fn test_disassemble_literal_bit_constants() {
357        let mut b = dr::Builder::new();
358
359        b.capability(spirv::Capability::Shader);
360        b.ext_inst_import("GLSL.std.450");
361        b.source(spirv::SourceLanguage::GLSL, 450, None, None::<String>);
362
363        let void = b.type_void();
364        let int32 = b.type_int(32, 1);
365        let int64 = b.type_int(64, 1);
366        let uint32 = b.type_int(32, 0);
367        let uint64 = b.type_int(64, 0);
368        let float32 = b.type_float(32, None);
369        let float64 = b.type_float(64, None);
370        let voidfvoid = b.type_function(void, vec![void]);
371
372        let f = b
373            .begin_function(
374                void,
375                None,
376                spirv::FunctionControl::DONT_INLINE | spirv::FunctionControl::CONST,
377                voidfvoid,
378            )
379            .unwrap();
380        b.begin_block(None).unwrap();
381        let signed_i32_value: i32 = -1;
382        let signed_i64_value: i64 = -1;
383        let f32_value: f32 = -2.0;
384        let f64_value: f64 = 9.26;
385        b.constant_bit32(int32, signed_i32_value as u32);
386        b.constant_bit64(int64, signed_i64_value as u64);
387        b.constant_bit32(uint32, signed_i32_value as u32);
388        b.constant_bit64(uint64, signed_i64_value as u64);
389        b.constant_bit32(float32, f32_value.to_bits());
390        b.constant_bit64(float64, f64_value.to_bits());
391        b.ret().unwrap();
392        b.end_function().unwrap();
393
394        b.entry_point(spirv::ExecutionModel::Fragment, f, "main", vec![]);
395        b.execution_mode(f, spirv::ExecutionMode::OriginUpperLeft, vec![]);
396        b.name(f, "main");
397
398        assert_eq!(
399            b.module().disassemble(),
400            "; SPIR-V\n\
401                    ; Version: 1.6\n\
402                    ; Generator: rspirv\n\
403                    ; Bound: 18\n\
404                    OpCapability Shader\n\
405                    %1 = OpExtInstImport \"GLSL.std.450\"\n\
406                    OpEntryPoint Fragment %10 \"main\"\n\
407                    OpExecutionMode %10 OriginUpperLeft\n\
408                    OpSource GLSL 450\n\
409                    OpName %10 \"main\"\n\
410                    %2 = OpTypeVoid\n\
411                    %3 = OpTypeInt 32 1\n\
412                    %4 = OpTypeInt 64 1\n\
413                    %5 = OpTypeInt 32 0\n\
414                    %6 = OpTypeInt 64 0\n\
415                    %7 = OpTypeFloat 32\n\
416                    %8 = OpTypeFloat 64\n\
417                    %9 = OpTypeFunction %2 %2\n\
418                    %12 = OpConstant  %3  -1\n\
419                    %13 = OpConstant  %4  -1\n\
420                    %14 = OpConstant  %5  4294967295\n\
421                    %15 = OpConstant  %6  18446744073709551615\n\
422                    %16 = OpConstant  %7  -2\n\
423                    %17 = OpConstant  %8  9.26\n\
424                    %10 = OpFunction  %2  DontInline|Const %9\n\
425                    %11 = OpLabel\n\
426                    OpReturn\n\
427                    OpFunctionEnd"
428        );
429    }
430
431    #[test]
432    fn test_disassemble_ext_inst_glsl() {
433        let mut b = dr::Builder::new();
434
435        b.capability(spirv::Capability::Shader);
436        let glsl = b.ext_inst_import("GLSL.std.450");
437        b.memory_model(spirv::AddressingModel::Logical, spirv::MemoryModel::Simple);
438
439        let void = b.type_void();
440        let float32 = b.type_float(32, None);
441        let voidfvoid = b.type_function(void, vec![void]);
442
443        assert!(b
444            .begin_function(void, None, spirv::FunctionControl::NONE, voidfvoid)
445            .is_ok());
446        b.begin_block(None).unwrap();
447        let var = b.variable(float32, None, spirv::StorageClass::Function, None);
448        let args = std::iter::once(dr::Operand::IdRef(var));
449        b.ext_inst(float32, None, glsl, 6, args).unwrap();
450        b.ret().unwrap();
451        b.end_function().unwrap();
452
453        assert_eq!(
454            b.module().disassemble(),
455            "; SPIR-V\n\
456                    ; Version: 1.6\n\
457                    ; Generator: rspirv\n\
458                    ; Bound: 9\n\
459                    OpCapability Shader\n\
460                    %1 = OpExtInstImport \"GLSL.std.450\"\n\
461                    OpMemoryModel Logical Simple\n\
462                    %2 = OpTypeVoid\n\
463                    %3 = OpTypeFloat 32\n\
464                    %4 = OpTypeFunction %2 %2\n\
465                    %5 = OpFunction  %2  None %4\n\
466                    %6 = OpLabel\n\
467                    %7 = OpVariable  %3  Function\n\
468                    %8 = OpExtInst  %3  %1 FSign %7\n\
469                    OpReturn\n\
470                    OpFunctionEnd"
471        );
472    }
473
474    #[test]
475    fn test_disassemble_ext_inst_opencl() {
476        let mut b = dr::Builder::new();
477
478        let opencl = b.ext_inst_import("OpenCL.std");
479        b.memory_model(spirv::AddressingModel::Logical, spirv::MemoryModel::OpenCL);
480
481        let void = b.type_void();
482        let float32 = b.type_float(32, None);
483        let voidfvoid = b.type_function(void, vec![void]);
484
485        assert!(b
486            .begin_function(void, None, spirv::FunctionControl::NONE, voidfvoid)
487            .is_ok());
488        b.begin_block(None).unwrap();
489        let var = b.variable(float32, None, spirv::StorageClass::Function, None);
490
491        let args = std::iter::once(dr::Operand::IdRef(var));
492        b.ext_inst(float32, None, opencl, 15, args).unwrap();
493        b.ret().unwrap();
494
495        b.end_function().unwrap();
496
497        assert_eq!(
498            b.module().disassemble(),
499            "; SPIR-V\n\
500                    ; Version: 1.6\n\
501                    ; Generator: rspirv\n\
502                    ; Bound: 9\n\
503                    %1 = OpExtInstImport \"OpenCL.std\"\n\
504                    OpMemoryModel Logical OpenCL\n\
505                    %2 = OpTypeVoid\n\
506                    %3 = OpTypeFloat 32\n\
507                    %4 = OpTypeFunction %2 %2\n\
508                    %5 = OpFunction  %2  None %4\n\
509                    %6 = OpLabel\n\
510                    %7 = OpVariable  %3  Function\n\
511                    %8 = OpExtInst  %3  %1 cosh %7\n\
512                    OpReturn\n\
513                    OpFunctionEnd"
514        );
515    }
516}