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

Krzysztof Parzyszek via llvm-dev llvm-dev at lists.llvm.org
Tue Oct 4 11:50:56 PDT 2016


If there are no objections, I'd like to start working on this soon...

For the AMDGPU target this implies that RC->getSize will no longer be 
available in the MC layer.

-Krzysztof


On 9/20/2016 12:32 PM, Krzysztof Parzyszek 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.
>
>
> Motivation:
>
> 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
> TableGen.
>
> 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,
>                                            MyIntReg:$Rt))]>;
>   }
>
>
>
>
> -Krzysztof
>
>

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


More information about the llvm-dev mailing list