[llvm-dev] RFC: Implement variable-sized register classes

Alex Bradbury via llvm-dev llvm-dev at lists.llvm.org
Sat Sep 24 05:20:13 PDT 2016

On 20 September 2016 at 18:32, Krzysztof Parzyszek via llvm-dev
<llvm-dev at lists.llvm.org> wrote:
> I have posted a patch that switches the API to one that supports this (yet
> non-existent functionality) earlier:
> https://reviews.llvm.org/D24631
> The comments from that were incorporated into the following RFC.

Thank you for writing this up. Your proposal is now much clearer to me.

> 1. Introduce a concept of a hardware "mode". This "mode" should be
> immutable, that is, it should be treated as a fixed property of the hardware
> throughout the execution of the program being compiled. This is different
> from, for example, floating point rounding mode, which can be changed at
> run-time.  In LLVM, the mode would be determined by subtarget features
> (reflected in TargetSubtargetInfo).
> 2. Move the register/spill size and alignment information from
> MCRegisterClass, and into TargetRegisterInfo. This means that this data will
> no longer be available to the MC layer. Note that the size/alignment
> information will be provided by the TargetRegisterInfo object, and not by
> each individual TargetRegisterClass. A TargetRegisterInfo object would be
> created for a specific hardware mode, so that it would be able to provide
> the necessary information without having to consult TargetSubtargetInfo.

Having thought about it somewhat, the ways that come to my mind of
approaching this problem are:

* Put up with the code duplication and duplicate everything for
different register classes (current approach taken by in-tree
* Make use of a multiclass to define multiple instructions with
minimal duplication. I trialled this, but only on a RISC-V
InstrInfo.td that doesn't yet support codegen
* Use a for loop in tablegen and some !cast<> magic to do something
with a similar effect to the multiclass approach
* Extend TableGen with some sort of AST macro support that would
again, allow you to generate a second (and third..) version of each
instruction with with a different RegisterClass substituted
* Add support for implicit parameterisation. e.g. allowing def MyRC :
Predicated<Is32Bit, GPR32, GPR64>. Invasive and complex, but still an
* Adding support for variable-sized register classes, as you've done
here. This definitely feels like the least invasive and is potentially
less fiddly than using multiclasses.

> 1. Define a mode class. It will be recognized by TableGen as having a
> special meaning.
>   class HwMode<list<Predicate> Ps> {
>     // List of Predicate objects that determine whether this mode
>     // applies. This is used for situation where the code generated by
>     // TableGen needs to determine this, as opposed to TableGen itself,
>     // for example in the isel pattern-matching code.
>     list<Predicate> ModeDef = Ps;
>   }
> 2. To make a use of the mode information, provide a class to associate a
> HwMode object with a particular value. This will be done by having two
> lists: one with HwMode objects and another with the corresponding values.
> Since TableGen does not provide a way to define class templates (in the same
> sense as C++ does), the actual interface will be split in two parts.  First
> is the "mode selection" base class:
>   class HwModeSelect<list<HwMode> Ms> {
>     list<HwMode> Modes;  // List of unique hw modes.
>   }
> This will be a "built-in" class for TableGen. It will be a base class, and
> treated as "abstract" since it only contains half of the information.
> 3. The class RegisterClass would get new members to hold the configurable
> size/alignment information. If defined, they would take precedence over the
> existing members RegTypes/Size/Alignment.
>   class RegisterClass {
>     ...
>     ValueTypeListSelect VarRegTypes;  // The names of these members
>     IntSelect VarRegSize;             // could likely be improved...
>     IntSelect VarSpillSize;           //
>     IntSelect VarSpillAlignment       //
>   }
> To fully implement the AddReg instruction, the target would then define the
> register class:
>   class MyRegisterClass : RegisterClass<...> {
>     let VarRegTypes = ValueTypeListSelect<[Mode64, Mode128],
>             [[i64, v2i32, v4i16, v8i8],             // Mode64
>              [i128, v2i64, v4i32, v8i16, v16i8]]>;  // Mode128
>     let VarRegSize = IntSelect<[Mode64, Mode128], [64, 128]>;
>     let VarSpillSize = IntSelect<[Mode64, Mode128], [64, 128]>;
>     let VarSpillAlignment = IntSelect<[Mode64, Mode128], [64, 128]>;
>   }
>   def MyIntReg: MyRegisterClass { ... };

My concern is that all of the above adds yet more complexity to what
is already (in my view) a fairly difficult part of LLVM to understand.
The definition of MyRegisterClass is not so bad though, and perhaps it
doesn't matter how it works under the hood to the average backend

What if RegisterClass contained a `list<RCInfo>`. Each RCInfo contains
RegTypes, RegSize, SpillSize, and SpillAlignment as well as a
Predicate the determines whether this individual RCInfo is the one
that should apply. To my taste this seems easier to understand than
the {Int,ValueType,ValueTypeList}Select mechanism.

def Is64Bit : Predicate<"Subtarget->is64Bit()">;
def RCInfo64 : RCInfo<Is64Bit> {
  let RegTypes = [i64, v2i32, v4i16, v8i8];

class MyRegisterClass : RegisterClass<...> {
  let RCInfos = [RCInfo32, RCInfo64]

Then for e.g. RISC-V I might end up with one GPR RegisterClass that
contains RCInfo for 32-bit and 64-bit which is used in the definition
of all instruction. I might also want to define an explicit GPR32
RegisterClass for use with instructions like ADDW where the two input
operands will always come from the 32-bit subregisters.


More information about the llvm-dev mailing list