[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:
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