Questions about load/store incrementing address modes

I have a rudimentary implementation for load and store instructions, where the memory address operand is automatically post-incremented when the load or store instruction is issued. However, this is currently coded using custom lowering, and explicit pattern matching in the ‘ISelDAGtoDAG’ implementation. But it seems to me that I ought to be able to achieve this exclusively using TableGen with minimal custom C++ code, but I can’t quite get the patterns to work to achieve this.

I have two types of post-increment:

  1. increment by an implied constant which is the size of the object being loaded or stored

  2. increment by the value contained in another register

It should be possible to do these with the same underlying pattern, but providing a DAG fragment like ‘(i32 2)’ for an ‘i16’ load explicitly in the TableGen specialisation for the first form, and the actual register chain for the second.

But I don’t seem to be quite get it to work, and in some cases the patterns I have attempted have crashed TableGen itself L The incrementing store sort of works, but the incrementing load doesn’t (at least not using TableGen alone).

The other form of addressing mode is where the address is a “base” plus signed “offset” register. My patterns for this work okay, but I have a limitation. The “offset” register is 16-bits, and in particular, it is the low-order 16-bits of a 32-bit actual register. But even though I have explicitly stated in TableGen that the offset register is 16-bits and stated the appropriate register class, the code-generation still uses the instructions “as if” the offset register is 32-bits.

Memory addresses in this architecture are 32-bits.

Has anybody solved similar problems, and have advice or examples of how to do this? Or is simply something that TableGen descriptions cannot completely describe? I have looked at the other targets for inspiration, but they don’t quite seem to do what I need.

Thanks,

Martin O’Riordan - Movidius Ltd.

I’ve implemented something similar, though maybe not similar enough to be able to help you. My target supports increment and decrement, both pre and post, by an explicit constant in the range 1 to 8.

I didn’t write any custom lowering code, just used setIndexedLoadAction() and setIndexedStoreAction() in my ISelLowering to identify the types and actions, e.g.

  setIndexedLoadAction(ISD::PRE_INC, MVT::i16, Legal);

I didn’t think it was possible to match an indexed load or store using a TableGen pattern because it can’t match instructions with more than one output operand, though maybe things have changed and I’ve not noticed. So, I wrote custom code in my ISelDAGToDAG to match indexed loads and store and to select these instructions. That seems to work fine for constant increments/decrements. I don’t know whether the indexed load and store actions would deal with increment or decrement by a register.

I’ve also got an addressing mode similar to yours in which an address can be formed from a 16-bit register plus a zero-extended 8-bit register. I used a ComplexPattern to match the address expression and MIOperandInfo to specify the classes of the registers, e.g.

  def memR16R8 : Operand<i16> {
    let MIOperandInfo = (ops Reg16Class, Reg8Class);
    ...
  }

Again, this seems to work OK for me.

Steve

Thanks Steve, I will try this out. I hadn’t realised that TableGen was restricted to matching instructions with more than one output operand. I’m assuming that this is only a limitation for inferring an instruction from the patterns, because it does seem to manage schedules okay.

Curiously, my memory Reg32+Reg16 pattern is very similar to yours (the 16-bit offset is sign-extended though):

// Memory address: 32-bit base register + 16-bit offset register

def ADDRrr : ComplexPattern<iPTR, 2, “SelectADDRrr”, >;

def MEMrr : Operand {

let PrintMethod = “printMemOffsetOperand”;

let MIOperandInfo = (ops RC32, RC16_l);

}

but it is still happy to select for offset’s > 16-bits. There is something I am just not yet getting right, but it looks like I am on the right track.

All the best,

MartinO

Thanks Steve, I will try this out. I hadn’t realised that TableGen was restricted to matching instructions with more than one output operand. I’m assuming that this is only a limitation for inferring an instruction from the patterns, because it does seem to manage schedules okay.

I’m basing my statement on the material at the end of the “Selection DAG Select Phase” in “The LLVM Target-Independent Code Generator”, http://llvm.org/docs/CodeGenerator.html#selectiondag-select-phase. I’ve not actually checked TableGen though so can’t be 100% sure that the documentation is still in date.

Curiously, my memory Reg32+Reg16 pattern is very similar to yours (the 16-bit offset is sign-extended though):

// Memory address: 32-bit base register + 16-bit offset register
def ADDRrr : ComplexPattern<iPTR, 2, "SelectADDRrr", >;
def MEMrr : Operand<iPTR> {
  let PrintMethod = "printMemOffsetOperand";
  let MIOperandInfo = (ops RC32, RC16_l);
}

but it is still happy to select for offset’s > 16-bits. There is something I am just not yet getting right, but it looks like I am on the right track.

I believe that the MIOperandInfo will constrain the register class for your 16-bit offset operand to RC16_1 but in itself it won’t affect the matching of the operand. Your SelectADDRrr will need to contain code to match an i32 added to a sign-extended i16. If you’ve already done that, then I’m out of ideas, sorry.

Steve

Thanks again for your help Steve,

I’m thinking perhaps my “SelectADDRrr” pattern is inadequate. The sign-extension is at the hardware level, the code generator sees (should see) it as a 16-bit signed register value. My implementation is just:

bool SHAVEISelDAGtoDAG::SelectADDRrr(SDValue &Addr, SDValue &Base, SDValue &Offset) {

if ((Addr.getOpcode() == ISD::ADD) {

Base = Addr.getOperand(0);

Offset = Addr.getOperand(1);

return true;

}

return false;

}

I don’t have any special checks on the offset (or the base for that matter) on the naive assumption that it would not have been invoked if the constraints were not already met. But don’t worry about it, you’ve given me a fresh avenue to investigate - a few DEBUG dumps should show me the error of my ways J I’m guessing that I need to check that the offset operand is truly a 16-bit register and return false if it isn’t. A nice simple fix if that is all that is needed - thanks again for shedding light on this for me.

MartinO

HI Martin,

Since your addresses are i32, I’d imagine that the ADD is i32 and therefore both its operands are also i32. Matching the i32 Offset operand with an i16 register is likely to lead to problems.

Essentially, you need to write code that matches:

  (add $base (sext (i16 $offset))

so something like:

  if (Addr.getOpcode() == ISD::ADD) {
    if (isSextFromi16(Addr.getOperand(1)) {
      Base = Addr.getOperand(0);
      Offset = Addr.getOperand(1).getOperand(0); // unextended i16
      return true;
    }
    if (isSextFromi16(Addr.getOperand(0)) {
      Base = Addr.getOperand(1);
      Offset = Addr.getOperand(0).getOperand(0);
      return true;
    }
    return false;
  }

You’ll need to write isSextFromi16 which could be as simple as checking that its parameter is ISD::SIGN_EXTEND whose operand is i16. You could extend it match other operands, such as:
- zext for which bit 15 of the operand is known to be 0 (use SelectionDAG::MaskedValueIsZero)
- sextload of an i16 (if your target supports it)
- any i32 operand for which bits 15-31 are the same (use SelectionDAG::ComputeNumSignBits)

In the last two cases you’d need to turn the sextload into an i16 load and truncate the i32 respectively so that they are i16 operands but I think that’s possible during instruction selection.

Hope that helps.

Steve

Thanks again Steve for going the extra mile to help out, much appreciated.

MartinO