Skip to content

SPIR-V module binary size / compression #382

@dneto0

Description

@dneto0

We've heard reports that SPIR-V modules are larger than those compiled to other representations.

First, SPIR-V binary encoding is extremely regular and is designed to be very simple to handle. It has lots of redundancy. For example, the SPIRV-Tools binary parser is simple and nearly stateless.

Second, Glslang generates binaries with OpName for many objects (see KhronosGroup/glslang#316) Also, it doesn't attempt to use group decorations.

To make smaller binaries, we need to make tools smarter: Emit less redundant info in the first place, make tools to eliminate redundancy (but still produce valid SPIR-V binaries), and make semantically lossless compression and decompression.

This issue is a brain dump of a few ideas along these lines. (Keep in mind that the SPIRV-Tools must remain unencumbered, including a possible relicensing under the Apache 2 license.)

Random ideas include those that leave the result as valid SPIR-V binary:

  • Compilers top emitting debug info by default. (E.g. glslang, glslc.) Add option -g to emit the debug info
  • Remap Ids, for redundancy removal across many modules (LunarG's spirv-remap)
  • Link shaders together into a single SPIR-V module, to share common declarations (like types), and share helper function bodies.
  • Write transforms for grouping decorations
  • Dead code elimination
  • constant folding
  • redundant value elimination (global value numbering algorithm)

Generic compression ideas:

  • Since SPIR-V is 32-bit word-oriented, use compression algorithms working on a word level. E.g. Huffman encoding of the distinct words in a module.

Low level encoding ideas (stateless):

  • Bounded IDs: ID bound tells you how many bits of a 32-bit could be non-zero. Never have to write out the upper bytes if they are always zero. E.g. if you never have IDs more than 255, then write only a single byte for each ID in the module. (Of course, this breaks the word-orientation.)
  • Bounded integer constants. Same idea, for positive constants. But doesn’t work well for negative numbers in twos-complement. Fortunately almost all integer literals in a module are unsigned. (Exceptions are the values for OpConstant, OpSpecConstant, and in an OpSwitch, if I remember correctly)
  • Use "varint" style encoding of integers as in protocol buffers. It's a nice variable-number-of-bytes encoding of integers that gracefully handles negative numbers using a zig-zag encoding (signed-magnitude, where the sign bit is the LSB). Again, knowledge of the SPIR-V grammar tells you when you have an unsigned value, save a bit by not encoding the sign.
  • Lots of instructions have type IDs that can directly be inferred from their arguments, assuming valid SPIR-V. Some, like OpIAdd have a result type that is very restricted, e.g. either the signed or unsigned form of an argument type.

Stateful encoding:

  • Instead of writing out an Id value as itself, the encoder uses a dynamically updated table of IDs (mirrored in the decoder) to generate smaller values in the emitted binary. For example an arithmetic instruction will often operate on recently-generated values. So you can use a simple move-to-front heuristic to maintain the table of "most recently mentioned ids". E.g. if the most recently mentioned IDs are %a, %b, %c in that order, then a move-to-front heuristic will put %c in slot 0, %b in slot 1, %a in slot 2. Then to encode %d = OpIAdd %int %a %b then emit the instruction but use 2 for %a and 1 for %b, and suitably update the table. This works well with the varint encodings.
  • There are several other variants of the previous idea, not just move-to-front.
  • Types IDs can be in their own table, since they have different locality characteristics.
  • (Constants might best get separate treatment)
  • Don't explicitly write out result ids. Just generate them implicitly. (Requires mirroring of state between encoder and decoder.)

Anyway, this is just a start of what we could do.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions