aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvan Cheng <evan.cheng@apple.com>2009-07-08 21:03:57 +0000
committerEvan Cheng <evan.cheng@apple.com>2009-07-08 21:03:57 +0000
commite7cbe4118b7ddf05032ff8772a98c51e1637bb5c (patch)
tree7a43bd988d438858c1635937312ee85b24a30697
parentc3d505c3c2a8c0e1f1db572f47451cfe2a1a58a3 (diff)
Change how so_imm and t2_so_imm are handled. At instruction selection time, the immediates are no longer encoded in the imm8 + rot format, that are left as it is. The encoding is now done in ams printing and code emission time instead.
git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@75048 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--lib/Target/ARM/ARMAddressingModes.h23
-rw-r--r--lib/Target/ARM/ARMBaseInstrInfo.cpp5
-rw-r--r--lib/Target/ARM/ARMBaseRegisterInfo.cpp17
-rw-r--r--lib/Target/ARM/ARMCodeEmitter.cpp17
-rw-r--r--lib/Target/ARM/ARMISelDAGToDAG.cpp6
-rw-r--r--lib/Target/ARM/ARMInstrInfo.td31
-rw-r--r--lib/Target/ARM/ARMInstrThumb2.td31
-rw-r--r--lib/Target/ARM/ARMLoadStoreOptimizer.cpp5
-rw-r--r--lib/Target/ARM/AsmPrinter/ARMAsmPrinter.cpp24
9 files changed, 48 insertions, 111 deletions
diff --git a/lib/Target/ARM/ARMAddressingModes.h b/lib/Target/ARM/ARMAddressingModes.h
index 15c9ec1fc2..6b90b73d13 100644
--- a/lib/Target/ARM/ARMAddressingModes.h
+++ b/lib/Target/ARM/ARMAddressingModes.h
@@ -273,29 +273,6 @@ namespace ARM_AM {
return V >> getThumbImmValShift(V);
}
- /// getT2SOImmValDecode - Given a 12-bit encoded Thumb-2 modified immediate,
- /// return the corresponding 32-bit immediate value.
- /// See ARM Reference Manual A6.3.2.
- static inline unsigned getT2SOImmValDecode(unsigned Imm) {
- unsigned Base = Imm & 0xff;
- switch ((Imm >> 8) & 0xf) {
- case 0:
- return Base;
- case 1:
- return Base | (Base << 16);
- case 2:
- return (Base << 8) | (Base << 24);
- case 3:
- return Base | (Base << 8) | (Base << 16) | (Base << 24);
- default:
- break;
- }
-
- // shifted immediate
- unsigned RotAmount = ((Imm >> 7) & 0x1f) - 8;
- return (Base | 0x80) << (24 - RotAmount);
- }
-
/// getT2SOImmValSplat - Return the 12-bit encoded representation
/// if the specified value can be obtained by splatting the low 8 bits
/// into every other byte or every byte of a 32-bit value. i.e.,
diff --git a/lib/Target/ARM/ARMBaseInstrInfo.cpp b/lib/Target/ARM/ARMBaseInstrInfo.cpp
index a1ea692d3b..f659cb50a0 100644
--- a/lib/Target/ARM/ARMBaseInstrInfo.cpp
+++ b/lib/Target/ARM/ARMBaseInstrInfo.cpp
@@ -91,15 +91,14 @@ ARMBaseInstrInfo::convertToThreeAddress(MachineFunction::iterator &MFI,
bool isSub = ARM_AM::getAM2Op(OffImm) == ARM_AM::sub;
unsigned Amt = ARM_AM::getAM2Offset(OffImm);
if (OffReg == 0) {
- int SOImmVal = ARM_AM::getSOImmVal(Amt);
- if (SOImmVal == -1)
+ if (ARM_AM::getSOImmVal(Amt) == -1)
// Can't encode it in a so_imm operand. This transformation will
// add more than 1 instruction. Abandon!
return NULL;
UpdateMI = BuildMI(MF, MI->getDebugLoc(),
get(isSub ? getOpcode(ARMII::SUBri) :
getOpcode(ARMII::ADDri)), WBReg)
- .addReg(BaseReg).addImm(SOImmVal)
+ .addReg(BaseReg).addImm(Amt)
.addImm(Pred).addReg(0).addReg(0);
} else if (Amt != 0) {
ARM_AM::ShiftOpc ShOpc = ARM_AM::getAM2ShiftOpc(OffImm);
diff --git a/lib/Target/ARM/ARMBaseRegisterInfo.cpp b/lib/Target/ARM/ARMBaseRegisterInfo.cpp
index 2bf7e51d83..0c05904d61 100644
--- a/lib/Target/ARM/ARMBaseRegisterInfo.cpp
+++ b/lib/Target/ARM/ARMBaseRegisterInfo.cpp
@@ -942,13 +942,11 @@ void emitARMRegPlusImmediate(MachineBasicBlock &MBB,
// We will handle these bits from offset, clear them.
NumBytes &= ~ThisVal;
- // Get the properly encoded SOImmVal field.
- int SOImmVal = ARM_AM::getSOImmVal(ThisVal);
- assert(SOImmVal != -1 && "Bit extraction didn't work?");
+ assert(ARM_AM::getSOImmVal(ThisVal) != -1 && "Bit extraction didn't work?");
// Build the new ADD / SUB.
BuildMI(MBB, MBBI, dl, TII.get(TII.getOpcode(isSub ? ARMII::SUBri : ARMII::ADDri)), DestReg)
- .addReg(BaseReg, RegState::Kill).addImm(SOImmVal)
+ .addReg(BaseReg, RegState::Kill).addImm(ThisVal)
.addImm((unsigned)Pred).addReg(PredReg).addReg(0);
BaseReg = DestReg;
}
@@ -1071,11 +1069,10 @@ eliminateFrameIndex(MachineBasicBlock::iterator II,
}
// Common case: small offset, fits into instruction.
- int ImmedOffset = ARM_AM::getSOImmVal(Offset);
- if (ImmedOffset != -1) {
+ if (ARM_AM::getSOImmVal(Offset) != -1) {
// Replace the FrameIndex with sp / fp
MI.getOperand(i).ChangeToRegister(FrameReg, false);
- MI.getOperand(i+1).ChangeToImmediate(ImmedOffset);
+ MI.getOperand(i+1).ChangeToImmediate(Offset);
return;
}
@@ -1089,9 +1086,9 @@ eliminateFrameIndex(MachineBasicBlock::iterator II,
Offset &= ~ThisImmVal;
// Get the properly encoded SOImmVal field.
- int ThisSOImmVal = ARM_AM::getSOImmVal(ThisImmVal);
- assert(ThisSOImmVal != -1 && "Bit extraction didn't work?");
- MI.getOperand(i+1).ChangeToImmediate(ThisSOImmVal);
+ assert(ARM_AM::getSOImmVal(ThisImmVal) != -1 &&
+ "Bit extraction didn't work?");
+ MI.getOperand(i+1).ChangeToImmediate(ThisImmVal);
} else {
unsigned ImmIdx = 0;
int InstrOffs = 0;
diff --git a/lib/Target/ARM/ARMCodeEmitter.cpp b/lib/Target/ARM/ARMCodeEmitter.cpp
index d43a76edc5..35502aa4cd 100644
--- a/lib/Target/ARM/ARMCodeEmitter.cpp
+++ b/lib/Target/ARM/ARMCodeEmitter.cpp
@@ -470,7 +470,8 @@ template<class CodeEmitter>
void Emitter<CodeEmitter>::emitMOVi2piecesInstruction(const MachineInstr &MI) {
const MachineOperand &MO0 = MI.getOperand(0);
const MachineOperand &MO1 = MI.getOperand(1);
- assert(MO1.isImm() && "Not a valid so_imm value!");
+ assert(MO1.isImm() && ARM_AM::getSOImmVal(MO1.isImm()) != -1 &&
+ "Not a valid so_imm value!");
unsigned V1 = ARM_AM::getSOImmTwoPartFirst(MO1.getImm());
unsigned V2 = ARM_AM::getSOImmTwoPartSecond(MO1.getImm());
@@ -486,7 +487,7 @@ void Emitter<CodeEmitter>::emitMOVi2piecesInstruction(const MachineInstr &MI) {
// Encode so_imm.
// Set bit I(25) to identify this is the immediate form of <shifter_op>
Binary |= 1 << ARMII::I_BitShift;
- Binary |= getMachineSoImmOpValue(ARM_AM::getSOImmVal(V1));
+ Binary |= getMachineSoImmOpValue(V1);
emitWordLE(Binary);
// Now the 'orr' instruction.
@@ -504,7 +505,7 @@ void Emitter<CodeEmitter>::emitMOVi2piecesInstruction(const MachineInstr &MI) {
// Encode so_imm.
// Set bit I(25) to identify this is the immediate form of <shifter_op>
Binary |= 1 << ARMII::I_BitShift;
- Binary |= getMachineSoImmOpValue(ARM_AM::getSOImmVal(V2));
+ Binary |= getMachineSoImmOpValue(V2);
emitWordLE(Binary);
}
@@ -714,12 +715,15 @@ unsigned Emitter<CodeEmitter>::getMachineSoRegOpValue(
template<class CodeEmitter>
unsigned Emitter<CodeEmitter>::getMachineSoImmOpValue(unsigned SoImm) {
+ int SoImmVal = ARM_AM::getSOImmVal(SoImm);
+ assert(SoImmVal != -1 && "Not a valid so_imm value!");
+
// Encode rotate_imm.
- unsigned Binary = (ARM_AM::getSOImmValRot(SoImm) >> 1)
+ unsigned Binary = (ARM_AM::getSOImmValRot((unsigned)SoImmVal) >> 1)
<< ARMII::SoRotImmShift;
// Encode immed_8.
- Binary |= ARM_AM::getSOImmValImm(SoImm);
+ Binary |= ARM_AM::getSOImmValImm((unsigned)SoImmVal);
return Binary;
}
@@ -796,8 +800,7 @@ void Emitter<CodeEmitter>::emitDataProcessingInstruction(
}
// Encode so_imm.
- Binary |= 1 << ARMII::I_BitShift;
- Binary |= getMachineSoImmOpValue(MO.getImm());
+ Binary |= getMachineSoImmOpValue((unsigned)MO.getImm());
emitWordLE(Binary);
}
diff --git a/lib/Target/ARM/ARMISelDAGToDAG.cpp b/lib/Target/ARM/ARMISelDAGToDAG.cpp
index 71a6305900..50db5896dd 100644
--- a/lib/Target/ARM/ARMISelDAGToDAG.cpp
+++ b/lib/Target/ARM/ARMISelDAGToDAG.cpp
@@ -1071,10 +1071,10 @@ SDNode *ARMDAGToDAGISel::Select(SDValue Op) {
}
// Pattern: (ARMcmov:i32 GPR:i32:$false,
- // (imm:i32)<<P:Predicate_so_imm>><<X:so_imm_XFORM>>:$true,
+ // (imm:i32)<<P:Predicate_so_imm>>:$true,
// (imm:i32):$cc)
// Emits: (MOVCCi:i32 GPR:i32:$false,
- // (so_imm_XFORM:i32 (imm:i32):$true), (imm:i32):$cc)
+ // (so_imm:i32 (imm:i32):$true), (imm:i32):$cc)
// Pattern complexity = 10 cost = 1 size = 0
if (N3.getOpcode() == ISD::Constant) {
if (Subtarget->isThumb()) {
@@ -1082,7 +1082,6 @@ SDNode *ARMDAGToDAGISel::Select(SDValue Op) {
SDValue Tmp1 = CurDAG->getTargetConstant(((unsigned)
cast<ConstantSDNode>(N1)->getZExtValue()),
MVT::i32);
- Tmp1 = Transform_t2_so_imm_XFORM(Tmp1.getNode());
SDValue Tmp2 = CurDAG->getTargetConstant(((unsigned)
cast<ConstantSDNode>(N2)->getZExtValue()),
MVT::i32);
@@ -1095,7 +1094,6 @@ SDNode *ARMDAGToDAGISel::Select(SDValue Op) {
SDValue Tmp1 = CurDAG->getTargetConstant(((unsigned)
cast<ConstantSDNode>(N1)->getZExtValue()),
MVT::i32);
- Tmp1 = Transform_so_imm_XFORM(Tmp1.getNode());
SDValue Tmp2 = CurDAG->getTargetConstant(((unsigned)
cast<ConstantSDNode>(N2)->getZExtValue()),
MVT::i32);
diff --git a/lib/Target/ARM/ARMInstrInfo.td b/lib/Target/ARM/ARMInstrInfo.td
index 6d812d6690..cea0de2b9e 100644
--- a/lib/Target/ARM/ARMInstrInfo.td
+++ b/lib/Target/ARM/ARMInstrInfo.td
@@ -118,25 +118,16 @@ class RegConstraint<string C> {
// ARM specific transformation functions and pattern fragments.
//
-// so_imm_XFORM - Return a so_imm value packed into the format described for
-// so_imm def below.
-def so_imm_XFORM : SDNodeXForm<imm, [{
- return CurDAG->getTargetConstant(ARM_AM::getSOImmVal(N->getZExtValue()),
- MVT::i32);
-}]>;
-
// so_imm_neg_XFORM - Return a so_imm value packed into the format described for
// so_imm_neg def below.
def so_imm_neg_XFORM : SDNodeXForm<imm, [{
- return CurDAG->getTargetConstant(ARM_AM::getSOImmVal(-(int)N->getZExtValue()),
- MVT::i32);
+ return CurDAG->getTargetConstant(-(int)N->getZExtValue(), MVT::i32);
}]>;
// so_imm_not_XFORM - Return a so_imm value packed into the format described for
// so_imm_not def below.
def so_imm_not_XFORM : SDNodeXForm<imm, [{
- return CurDAG->getTargetConstant(ARM_AM::getSOImmVal(~(int)N->getZExtValue()),
- MVT::i32);
+ return CurDAG->getTargetConstant(~(int)N->getZExtValue(), MVT::i32);
}]>;
// rot_imm predicate - True if the 32-bit immediate is equal to 8, 16, or 24.
@@ -234,9 +225,9 @@ def so_reg : Operand<i32>, // reg reg imm
// into so_imm instructions: the 8-bit immediate is the least significant bits
// [bits 0-7], the 4-bit shift amount is the next 4 bits [bits 8-11].
def so_imm : Operand<i32>,
- PatLeaf<(imm),
- [{ return ARM_AM::getSOImmVal(N->getZExtValue()) != -1; }],
- so_imm_XFORM> {
+ PatLeaf<(imm), [{
+ return ARM_AM::getSOImmVal(N->getZExtValue()) != -1;
+ }]> {
let PrintMethod = "printSOImmOperand";
}
@@ -252,12 +243,12 @@ def so_imm2part : Operand<i32>,
def so_imm2part_1 : SDNodeXForm<imm, [{
unsigned V = ARM_AM::getSOImmTwoPartFirst((unsigned)N->getZExtValue());
- return CurDAG->getTargetConstant(ARM_AM::getSOImmVal(V), MVT::i32);
+ return CurDAG->getTargetConstant(V, MVT::i32);
}]>;
def so_imm2part_2 : SDNodeXForm<imm, [{
unsigned V = ARM_AM::getSOImmTwoPartSecond((unsigned)N->getZExtValue());
- return CurDAG->getTargetConstant(ARM_AM::getSOImmVal(V), MVT::i32);
+ return CurDAG->getTargetConstant(V, MVT::i32);
}]>;
@@ -1440,11 +1431,11 @@ def MOVi2pieces : AI1x2<(outs GPR:$dst), (ins so_imm2part:$src), Pseudo,
[(set GPR:$dst, so_imm2part:$src)]>;
def : ARMPat<(or GPR:$LHS, so_imm2part:$RHS),
- (ORRri (ORRri GPR:$LHS, (so_imm2part_1 imm:$RHS)),
- (so_imm2part_2 imm:$RHS))>;
+ (ORRri (ORRri GPR:$LHS, (so_imm2part_1 imm:$RHS)),
+ (so_imm2part_2 imm:$RHS))>;
def : ARMPat<(xor GPR:$LHS, so_imm2part:$RHS),
- (EORri (EORri GPR:$LHS, (so_imm2part_1 imm:$RHS)),
- (so_imm2part_2 imm:$RHS))>;
+ (EORri (EORri GPR:$LHS, (so_imm2part_1 imm:$RHS)),
+ (so_imm2part_2 imm:$RHS))>;
// TODO: add,sub,and, 3-instr forms?
diff --git a/lib/Target/ARM/ARMInstrThumb2.td b/lib/Target/ARM/ARMInstrThumb2.td
index 330c7a9ffa..22d670ba24 100644
--- a/lib/Target/ARM/ARMInstrThumb2.td
+++ b/lib/Target/ARM/ARMInstrThumb2.td
@@ -20,23 +20,14 @@ def t2_so_reg : Operand<i32>, // reg imm
let MIOperandInfo = (ops GPR, i32imm);
}
-// t2_so_imm_XFORM - Return a t2_so_imm value packed into the format
-// described for t2_so_imm def below.
-def t2_so_imm_XFORM : SDNodeXForm<imm, [{
- return CurDAG->getTargetConstant(
- ARM_AM::getT2SOImmVal(N->getZExtValue()), MVT::i32);
-}]>;
-
// t2_so_imm_not_XFORM - Return the complement of a t2_so_imm value
def t2_so_imm_not_XFORM : SDNodeXForm<imm, [{
- return CurDAG->getTargetConstant(
- ARM_AM::getT2SOImmVal(~((uint32_t)N->getZExtValue())), MVT::i32);
+ return CurDAG->getTargetConstant(~((uint32_t)N->getZExtValue()), MVT::i32);
}]>;
// t2_so_imm_neg_XFORM - Return the negation of a t2_so_imm value
def t2_so_imm_neg_XFORM : SDNodeXForm<imm, [{
- return CurDAG->getTargetConstant(
- ARM_AM::getT2SOImmVal(-((int)N->getZExtValue())), MVT::i32);
+ return CurDAG->getTargetConstant(-((int)N->getZExtValue()), MVT::i32);
}]>;
// t2_so_imm - Match a 32-bit immediate operand, which is an
@@ -47,27 +38,21 @@ def t2_so_imm_neg_XFORM : SDNodeXForm<imm, [{
// [bits 0-7], the 4-bit shift/splat amount is the next 4 bits [bits 8-11].
def t2_so_imm : Operand<i32>,
PatLeaf<(imm), [{
- return ARM_AM::getT2SOImmVal((uint32_t)N->getZExtValue()) != -1;
- }], t2_so_imm_XFORM> {
- let PrintMethod = "printT2SOImmOperand";
-}
+ return ARM_AM::getT2SOImmVal((uint32_t)N->getZExtValue()) != -1;
+}]>;
// t2_so_imm_not - Match an immediate that is a complement
// of a t2_so_imm.
def t2_so_imm_not : Operand<i32>,
PatLeaf<(imm), [{
- return ARM_AM::getT2SOImmVal(~((uint32_t)N->getZExtValue())) != -1;
- }], t2_so_imm_not_XFORM> {
- let PrintMethod = "printT2SOImmOperand";
-}
+ return ARM_AM::getT2SOImmVal(~((uint32_t)N->getZExtValue())) != -1;
+}], t2_so_imm_not_XFORM>;
// t2_so_imm_neg - Match an immediate that is a negation of a t2_so_imm.
def t2_so_imm_neg : Operand<i32>,
PatLeaf<(imm), [{
- return ARM_AM::getT2SOImmVal(-((int)N->getZExtValue())) != -1;
- }], t2_so_imm_neg_XFORM> {
- let PrintMethod = "printT2SOImmOperand";
-}
+ return ARM_AM::getT2SOImmVal(-((int)N->getZExtValue())) != -1;
+}], t2_so_imm_neg_XFORM>;
/// imm1_31 predicate - True if the 32-bit immediate is in the range [1,31].
def imm1_31 : PatLeaf<(i32 imm), [{
diff --git a/lib/Target/ARM/ARMLoadStoreOptimizer.cpp b/lib/Target/ARM/ARMLoadStoreOptimizer.cpp
index 67c4258046..c449c50f46 100644
--- a/lib/Target/ARM/ARMLoadStoreOptimizer.cpp
+++ b/lib/Target/ARM/ARMLoadStoreOptimizer.cpp
@@ -171,12 +171,11 @@ ARMLoadStoreOpt::MergeOps(MachineBasicBlock &MBB,
BaseOpc = ARM::SUBri;
Offset = - Offset;
}
- int ImmedOffset = ARM_AM::getSOImmVal(Offset);
- if (ImmedOffset == -1)
+ if (ARM_AM::getSOImmVal(Offset) == -1)
return false; // Probably not worth it then.
BuildMI(MBB, MBBI, dl, TII->get(BaseOpc), NewBase)
- .addReg(Base, getKillRegState(BaseKill)).addImm(ImmedOffset)
+ .addReg(Base, getKillRegState(BaseKill)).addImm(Offset)
.addImm(Pred).addReg(PredReg).addReg(0);
Base = NewBase;
BaseKill = true; // New base is always killed right its use.
diff --git a/lib/Target/ARM/AsmPrinter/ARMAsmPrinter.cpp b/lib/Target/ARM/AsmPrinter/ARMAsmPrinter.cpp
index c4deb4bdb3..a228d2a0b4 100644
--- a/lib/Target/ARM/AsmPrinter/ARMAsmPrinter.cpp
+++ b/lib/Target/ARM/AsmPrinter/ARMAsmPrinter.cpp
@@ -119,7 +119,6 @@ namespace {
void printThumbAddrModeS4Operand(const MachineInstr *MI, int OpNum);
void printThumbAddrModeSPOperand(const MachineInstr *MI, int OpNum);
- void printT2SOImmOperand(const MachineInstr *MI, int OpNum);
void printT2SOOperand(const MachineInstr *MI, int OpNum);
void printT2AddrModeImm12Operand(const MachineInstr *MI, int OpNum);
void printT2AddrModeImm8Operand(const MachineInstr *MI, int OpNum);
@@ -370,7 +369,10 @@ void ARMAsmPrinter::printOperand(const MachineInstr *MI, int OpNum,
static void printSOImm(raw_ostream &O, int64_t V, bool VerboseAsm,
const TargetAsmInfo *TAI) {
- assert(V < (1 << 12) && "Not a valid so_imm value!");
+ // Break it up into two parts that make up a shifter immediate.
+ V = ARM_AM::getSOImmVal(V);
+ assert(V != -1 && "Not a valid so_imm value!");
+
unsigned Imm = ARM_AM::getSOImmValImm(V);
unsigned Rot = ARM_AM::getSOImmValRot(V);
@@ -402,7 +404,7 @@ void ARMAsmPrinter::printSOImm2PartOperand(const MachineInstr *MI, int OpNum) {
assert(MO.isImm() && "Not a valid so_imm value!");
unsigned V1 = ARM_AM::getSOImmTwoPartFirst(MO.getImm());
unsigned V2 = ARM_AM::getSOImmTwoPartSecond(MO.getImm());
- printSOImm(O, ARM_AM::getSOImmVal(V1), VerboseAsm, TAI);
+ printSOImm(O, V1, VerboseAsm, TAI);
O << "\n\torr";
printPredicateOperand(MI, 2);
O << " ";
@@ -410,7 +412,7 @@ void ARMAsmPrinter::printSOImm2PartOperand(const MachineInstr *MI, int OpNum) {
O << ", ";
printOperand(MI, 0);
O << ", ";
- printSOImm(O, ARM_AM::getSOImmVal(V2), VerboseAsm, TAI);
+ printSOImm(O, V2, VerboseAsm, TAI);
}
// so_reg is a 4-operand unit corresponding to register forms of the A5.1
@@ -687,20 +689,6 @@ void ARMAsmPrinter::printThumbAddrModeSPOperand(const MachineInstr *MI,int Op) {
//===--------------------------------------------------------------------===//
-/// printT2SOImmOperand - T2SOImm is:
-/// 1. a 4-bit splat control value and 8 bit immediate value
-/// 2. a 5-bit rotate amount and a non-zero 8-bit immediate value
-/// represented by a normalizedin 7-bit value (msb is always 1)
-void ARMAsmPrinter::printT2SOImmOperand(const MachineInstr *MI, int OpNum) {
- const MachineOperand &MO = MI->getOperand(OpNum);
- assert(MO.isImm() && "Not a valid so_imm value!");
-
- unsigned Imm = ARM_AM::getT2SOImmValDecode(MO.getImm());
- // Always print the immediate directly, as the "rotate" form
- // is deprecated in some contexts.
- O << "#" << Imm;
-}
-
// Constant shifts t2_so_reg is a 2-operand unit corresponding to the Thumb2
// register with shift forms.
// REG 0 0 - e.g. R5