This change adds an option to specialize decoders per bitwidth, which can help reduce the (compiled) code size of the decoder code. **Current state**: Currently, the code generated by the decoder emitter consists of two key functions: `decodeInstruction` which is the entry point into the generated code and `decodeToMCInst` which is invoked when a decode op is reached while traversing through the decoder table. Both functions are templated on `InsnType` which is the raw instruction bits that are supplied to `decodeInstruction`. Several backends call `decodeInstruction` with different `InsnType` types, leading to several template instantiations of these functions in the final code. As an example, AMDGPU instantiates this function with type `DecoderUInt128` type for decoding 96/128-bit instructions, `uint64_t` for decoding 64-bit instructions, and `uint32_t` for decoding 32-bit instructions. Since there is just one `decodeToMCInst` in the generated code, it has code that handles decoding for *all* instruction sizes. However, the decoders emitted for different instructions sizes rarely have any intersection with each other. That means, in the AMDGPU case, the instantiation with InsnType == DecoderUInt128 has decoder code for 32/64-bit instructions that is *never exercised*. Conversely, the instantiation with InsnType == uint64_t has decoder code for 128/96/32-bit instructions that is never exercised. This leads to unnecessary dead code in the generated disassembler binary (that the compiler cannot eliminate by itself). **New state**: With this change, we introduce an option `specialize-decoders-per-bitwidth`. Under this mode, the DecoderEmitter will generate several versions of `decodeToMCInst` function, one for each bitwidth. The code is still templated, but will require backends to specify, for each `InsnType` used, the bitwidth of the instruction that the type is used to represent using a type-trait `InsnBitWidth`. This will enable the templated code to choose the right variant of `decodeToMCInst`. Under this mode, a particular instantiation will only end up instantiating a single variant of `decodeToMCInst` generated and that will include only those decoders that are applicable to a single bitwidth, resulting in elimination of the code duplication through instantiation and a reduction in code size. Additionally, under this mode, decoders are uniqued only within a given bitwidth (as opposed to across all bitwidths without this option), so the decoder index values assigned are smaller, and consume less bytes in their ULEB128 encoding. As a result, the generated decoder tables can also reduce in size. Adopt this feature for the AMDGPU and RISCV backend. In a release build, this results in a net 55% reduction in the .text size of libLLVMAMDGPUDisassembler.so and a 5% reduction in the .rodata size. For RISCV, which today uses a single `uint64_t` type, this results in a 3.7% increase in code size (expected as we instantiate the code 3 times now). Actual measured sizes are as follows: ``` Baseline commit: 72c04bb882ad70230bce309c3013d9cc2c99e9a7 Configuration: Ubuntu clang version 18.1.3, release build with asserts disabled. AMDGPU Before After Change ====================================================== .text 612327 275607 55% reduction .rodata 369728 351336 5% reduction RISCV: ====================================================== .text 47407 49187 3.7% increase .rodata 35768 35839 0.1% increase ```
176 lines
6.5 KiB
TableGen
176 lines
6.5 KiB
TableGen
// RUN: llvm-tblgen -gen-disassembler -I %p/../../include %s | FileCheck %s --check-prefix=CHECK-DEFAULT
|
|
// RUN: llvm-tblgen -gen-disassembler -specialize-decoders-per-bitwidth -I %p/../../include %s | FileCheck %s --check-prefix=CHECK-SPECIALIZE-NO-TABLE
|
|
// RUN: llvm-tblgen -gen-disassembler -specialize-decoders-per-bitwidth -use-fn-table-in-decode-to-mcinst -I %p/../../include %s | FileCheck %s --check-prefix=CHECK-SPECIALIZE-TABLE
|
|
|
|
|
|
include "llvm/Target/Target.td"
|
|
|
|
def archInstrInfo : InstrInfo { }
|
|
|
|
def arch : Target {
|
|
let InstructionSet = archInstrInfo;
|
|
}
|
|
|
|
let Namespace = "arch" in {
|
|
def R0 : Register<"r0">;
|
|
def R1 : Register<"r1">;
|
|
def R2 : Register<"r2">;
|
|
def R3 : Register<"r3">;
|
|
}
|
|
def Regs : RegisterClass<"Regs", [i32], 32, (add R0, R1, R2, R3)>;
|
|
|
|
// Bit 0 of the encoding determines the size (8 or 16 bits).
|
|
// Bits {3..1} define the number of operands encoded.
|
|
class Instruction8Bit<int NumOps> : Instruction {
|
|
let Size = 1;
|
|
let OutOperandList = (outs);
|
|
field bits<8> Inst;
|
|
let Inst{0} = 0;
|
|
let Inst{3-1} = NumOps;
|
|
}
|
|
|
|
class Instruction16Bit<int NumOps> : Instruction {
|
|
let Size = 2;
|
|
let OutOperandList = (outs);
|
|
field bits<16> Inst;
|
|
let Inst{0} = 1;
|
|
let Inst{3-1} = NumOps;
|
|
}
|
|
|
|
// Define instructions to generate 4 cases in decodeToMCInst.
|
|
// Each register operand needs 2 bits to encode.
|
|
|
|
// An instruction with no inputs.
|
|
def Inst0 : Instruction8Bit<0> {
|
|
let Inst{7-4} = 0;
|
|
let InOperandList = (ins);
|
|
let AsmString = "Inst0";
|
|
}
|
|
|
|
// An instruction with a single input.
|
|
def Inst1 : Instruction8Bit<1> {
|
|
bits<2> r0;
|
|
let Inst{5-4} = r0;
|
|
let Inst{7-6} = 0;
|
|
let InOperandList = (ins Regs:$r0);
|
|
let AsmString = "Inst1";
|
|
}
|
|
|
|
// An instruction with two inputs.
|
|
def Inst2 : Instruction16Bit<2> {
|
|
bits<2> r0;
|
|
bits<2> r1;
|
|
let Inst{5-4} = r0;
|
|
let Inst{7-6} = r1;
|
|
let Inst{15-8} = 0;
|
|
let InOperandList = (ins Regs:$r0, Regs:$r1);
|
|
let AsmString = "Inst2";
|
|
}
|
|
|
|
// An instruction with three inputs. .
|
|
def Inst3 : Instruction16Bit<3> {
|
|
bits<2> r0;
|
|
bits<2> r1;
|
|
bits<2> r2;
|
|
let Inst{5-4} = r0;
|
|
let Inst{7-6} = r1;
|
|
let Inst{9-8} = r2;
|
|
let Inst{15-10} = 0;
|
|
let InOperandList = (ins Regs:$r0, Regs:$r1, Regs:$r2);
|
|
let AsmString = "Inst3";
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// In the default case, we emit a single decodeToMCinst function and DecodeIdx
|
|
// is shared across all bitwidths.
|
|
|
|
// CHECK-DEFAULT-LABEL: DecoderTable8[25]
|
|
// CHECK-DEFAULT: DecodeIdx: 0
|
|
// CHECK-DEFAULT: DecodeIdx: 1
|
|
// CHECK-DEFAULT: };
|
|
|
|
// CHECK-DEFAULT-LABEL: DecoderTable16[25]
|
|
// CHECK-DEFAULT: DecodeIdx: 2
|
|
// CHECK-DEFAULT: DecodeIdx: 3
|
|
// CHECK-DEFAULT: };
|
|
|
|
// CHECK-DEFAULT-LABEL: template <typename InsnType>
|
|
// CHECK-DEFAULT-NEXT: static DecodeStatus decodeToMCInst
|
|
// CHECK-DEFAULT: case 0
|
|
// CHECK-DEFAULT: case 1
|
|
// CHECK-DEFAULT: case 2
|
|
// CHECK-DEFAULT: case 3
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// When we specialize per bitwidth, we emit 2 decodeToMCInst functions and
|
|
// DecodeIdx is assigned per bit width.
|
|
|
|
// CHECK-SPECIALIZE-NO-TABLE-LABEL: DecoderTable8[25]
|
|
// CHECK-SPECIALIZE-NO-TABLE: DecodeIdx: 0
|
|
// CHECK-SPECIALIZE-NO-TABLE: DecodeIdx: 1
|
|
// CHECK-SPECIALIZE-NO-TABLE: };
|
|
|
|
// CHECK-SPECIALIZE-NO-TABLE-LABEL: template <typename InsnType>
|
|
// CHECK-SPECIALIZE-NO-TABLE-NEXT: static std::enable_if_t<InsnBitWidth<InsnType> == 8, DecodeStatus>
|
|
// CHECK-SPECIALIZE-NO-TABLE-NEXT: decodeToMCInst
|
|
// CHECK-SPECIALIZE-NO-TABLE: case 0
|
|
// CHECK-SPECIALIZE-NO-TABLE: case 1
|
|
|
|
// CHECK-SPECIALIZE-NO-TABLE-LABEL: DecoderTable16[25]
|
|
// CHECK-SPECIALIZE-NO-TABLE: DecodeIdx: 0
|
|
// CHECK-SPECIALIZE-NO-TABLE: DecodeIdx: 1
|
|
// CHECK-SPECIALIZE-NO-TABLE: };
|
|
|
|
// CHECK-SPECIALIZE-NO-TABLE-LABEL: template <typename InsnType>
|
|
// CHECK-SPECIALIZE-NO-TABLE-NEXT: static std::enable_if_t<InsnBitWidth<InsnType> == 16, DecodeStatus>
|
|
// CHECK-SPECIALIZE-NO-TABLE-NEXT: decodeToMCInst
|
|
// CHECK-SPECIALIZE-NO-TABLE: case 0
|
|
// CHECK-SPECIALIZE-NO-TABLE: case 1
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Per bitwidth specialization with function table.
|
|
|
|
// 8 bit deccoder table, functions, and function table.
|
|
// CHECK-SPECIALIZE-TABLE-LABEL: DecoderTable8[25]
|
|
// CHECK-SPECIALIZE-TABLE: DecodeIdx: 0
|
|
// CHECK-SPECIALIZE-TABLE: DecodeIdx: 1
|
|
// CHECK-SPECIALIZE-TABLE: };
|
|
|
|
// CHECK-SPECIALIZE-TABLE-LABEL: template <typename InsnType>
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: static std::enable_if_t<InsnBitWidth<InsnType> == 8, DecodeStatus>
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: decodeFn_8bit_0
|
|
|
|
// CHECK-SPECIALIZE-TABLE-LABEL: template <typename InsnType>
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: static std::enable_if_t<InsnBitWidth<InsnType> == 8, DecodeStatus>
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: decodeFn_8bit_1
|
|
|
|
// CHECK-SPECIALIZE-TABLE-LABEL: template <typename InsnType>
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: static std::enable_if_t<InsnBitWidth<InsnType> == 8, DecodeStatus>
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: decodeToMCInst
|
|
// CHECK-SPECIALIZE-TABLE-LABEL: static constexpr DecodeFnTy decodeFnTable[] = {
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: decodeFn_8bit_0,
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: decodeFn_8bit_1,
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: };
|
|
|
|
// 16 bit deccoder table, functions, and function table.
|
|
// CHECK-SPECIALIZE-TABLE-LABEL: DecoderTable16[25]
|
|
// CHECK-SPECIALIZE-TABLE: DecodeIdx: 0
|
|
// CHECK-SPECIALIZE-TABLE: DecodeIdx: 1
|
|
// CHECK-SPECIALIZE-TABLE: };
|
|
|
|
// CHECK-SPECIALIZE-TABLE-LABEL: template <typename InsnType>
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: static std::enable_if_t<InsnBitWidth<InsnType> == 16, DecodeStatus>
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: decodeFn_16bit_0
|
|
|
|
// CHECK-SPECIALIZE-TABLE-LABEL: template <typename InsnType>
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: static std::enable_if_t<InsnBitWidth<InsnType> == 16, DecodeStatus>
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: decodeFn_16bit_1
|
|
|
|
// CHECK-SPECIALIZE-TABLE-LABEL: template <typename InsnType>
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: static std::enable_if_t<InsnBitWidth<InsnType> == 16, DecodeStatus>
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: decodeToMCInst
|
|
// CHECK-SPECIALIZE-TABLE-LABEL: static constexpr DecodeFnTy decodeFnTable[] = {
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: decodeFn_16bit_0,
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: decodeFn_16bit_1,
|
|
// CHECK-SPECIALIZE-TABLE-NEXT: };
|