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

Krzysztof Parzyszek via llvm-dev llvm-dev at lists.llvm.org
Tue Sep 20 10:32:05 PDT 2016

I have posted a patch that switches the API to one that supports this 
(yet non-existent functionality) earlier:

The comments from that were incorporated into the following RFC.


Certain targets feature "variable-sized" registers, i.e. a situation 
where the register size can be configured by a hardware switch.  A 
common instruction set would then operate on these registers regardless 
of what size they have been configured to have.  A specific example of 
that is the HVX coprocessor on Hexagon. HVX provides a set of vector 
registers, and can be configured in one of two modes: one in which 
vectors are 512 bits long, and one in vectors are 1024 bits in length. 
The size only determines the number of elements in the vector register, 
and so the semantics of each HVX instruction does not change: it 
performs a given operation on all vector elements. The encoding of the 
instruction does not change between modes, in fact, it is possible to 
have a binary that runs in both modes.

Currently the register size (strictly speaking, "spill slot size") and 
related properties are fixed and immutable in a RegisterClass. In order 
to allow multiple possible register sizes, several RegisterClass objects 
may need to be defined, which then will require each instruction to be 
defined twice. This is what the HVX code does.  Another approach may be 
to define several sets of physical registers corresponding to different 
sizes, and have a large RegisterClass which would be the union of all of 
them. This could avoid having to duplicate the instructions, but would 
lead to problems with getting the actual spill slot size or alignment.

Since the number of targets allowing this kind of variability is growing 
(besides Hexagon, there is RISC-V, MIPS, and out of tree targets, such 
as CHERI), LLVM should allow convenient handling of this type of a 
situation. See comments in https://reviews.llvm.org/D23561 for more details.

General approach:

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.

3. Introduce TableGen support for specifying instruction selection 
patterns involving data types depending on the hardware mode.

4. Require that the sub-/super-class relationships between register 
classes are the same across all hardware modes.

The largest impact of this change would be on TableGen, since it needs 
to be aware of the fact that value types under consideration would 
depend on a hardware mode. For example, when having an add-registers 
instruction defined to work on 64-bit registers, providing an additional 
selection pattern for 128-bit registers would present difficulties:

   def AddReg : Instruction {
     let OutOperandList = (outs GPR64:$Rd);
     let InOperandList = (ins GPR64:$Rs, GPR64:$Rt);
     let Pattern = [(set GPR64:$Rd, (add GPR64:$Rs, GPR64:$Rt))]>;

the pattern

   def: Pat<(add GPR128:$Rs, GPR128:$Rt), (AddReg $Rs, $Rt)>;

would result in a type interference error from TableGen. If the class 
GPR64 was amended to also allow the value type i128, TableGen would no 
longer complain, but may generate invalid instruction selection code.

To solve this, TableGen would need to be aware of the association 
between value types and hardware modes. The rest of this proposal 
describes the programming interface to provide necessary information to 

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;

 From the point of view of the code generated by TableGen, HwMode is 
equivalent to a list of Predicate objects. The difference is in how 
TableGen itself treats it: TableGen will distinguish two objects of 
class HwMode if they have different names, regardless of what sets of 
predicates they contain. One way to think of it is that the name of the 
object would serve as a tag denoting the hardware mode.

In the example of the AddReg instruction, we could define two modes:

   def Mode64: Mode<[...]>;
   def Mode128: Mode<[...]>;

but so far there would not be much more that we could do.

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.  Each derived class would then need to define a member 
"Values", which is a list of corresponding values, of the same length as 
the list of modes.  The following definitions will be useful for 
defining register classes and selection patterns:

   class IntSelect<list<Mode> Ms, list<int> Is>
       : HwModeSelect<Ms> {
     // Select an integer literal.
     list<int> Values = Is;

   class ValueTypeSelect<list<Mode> Ms, list<ValueType> Ts>
       : HwModeSelect<Ms> {
     // Select a value type.
     list<ValueType> Values = Ts;

   class ValueTypeListSelect<list<Mode> Ms, list<list<ValueType>> Ls>
       : HwModeSelect<Ms> {
     // Select a list of value types.
     list<list<ValueType>> Values = Ls;

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 { ... };

And following that, the instruction:

   def AddReg: Instruction {
     let OutOperandList = (outs MyIntReg:$Rd);
     let InOperandList = (ins MyIntReg:$Rs, MyIntReg:$Rt);
     let AsmString = "add $Rd, $Rs, $Rt";
     let Pattern = [(set MyIntReg:$Rd, (add MyIntReg:$Rs,


Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, 
hosted by The Linux Foundation

More information about the llvm-dev mailing list