Lowering to multiple instructions

Let's take an example of implementing a 32-bit immediate load with high/low pairs, where MOVW implies moving a 16-bit low immediate and a clear 16 high bit, and MOVT implies moving a 16-bit high immediate.

How to do it…

There can be various ways to implement this multiple instruction lowering. We can do this by using pseudo-instructions or in the selection DAG-to-DAG phase.

  1. To do it without pseudo-instructions, define some constraints. The two instructions must be ordered. MOVW clears the high 16 bits. Its output is read by MOVT to fill the high 16 bits. This can be done by specifying the constraints in tablegen:
    def MOVLOi16 : MOV<0b1000, "movw", (ins i32imm:$imm),
                      [(set i32:$dst, i32imm_lo:$imm)]>;
    def MOVHIi16 : MOV<0b1010, "movt", (ins GRRegs:$src1, i32imm:$imm),
                      [/* No Pattern */]>;

    The second way is to define a pseudo-instruction in the .td file:

    def MOVi32 : InstTOY<(outs GRRegs:$dst), (ins i32imm:$src), "", [(set i32:$dst, (movei32 imm:$src))]> {
      let isPseudo = 1;
    }
  2. The pseudo-instruction is then lowered by a target function in the TOYInstrInfo.cpp file:
    bool TOYInstrInfo::expandPostRAPseudo(MachineBasicBlock::iterato r MI) const {
      if (MI->getOpcode() == TOY::MOVi32){
        DebugLoc DL = MI->getDebugLoc();
        MachineBasicBlock &MBB = *MI->getParent();
    
        const unsigned DstReg = MI->getOperand(0).getReg();
        const bool DstIsDead = MI->getOperand(0).isDead();
    
        const MachineOperand &MO = MI->getOperand(1);
    
        auto LO16 = BuildMI(MBB, MI, DL, get(TOY::MOVLOi16), DstReg);
        auto HI16 = BuildMI(MBB, MI, DL, get(TOY::MOVHIi16))
                        .addReg(DstReg, RegState::Define | getDeadRegState(DstIsDead))
                        .addReg(DstReg);
    
      MBB.erase(MI);
        return true;
      }
    }
  3. Compile the entire LLVM project:

    For example, an ex.ll file with IR will look like this:

    define i32 @foo(i32 %a) #0 {
    %b = add nsw i32 %a, 65537 ; 0x00010001
    ret i32 %b
    }

    The assembly generated will look like this:

    movw r1, #1
    movt r1, #1
    add r0, r0, r1
    b lr

How it works…

The first instruction, movw, will move 1 in the lower 16 bits and clear the high 16 bits. So in r1, 0x00000001 will be written by the first instruction. In the next instruction, movt, the higher 16 bits will be written. So in r1, 0x0001XXXX will be written, without disturbing the lower bits. Finally, the r1 register will have 0x00010001 in it. Whenever a pseudo-instruction is encountered as specified in the .td file, its expand function is called to specify what the pseudo-instruction will expand to.

In the preceding case, the mov32 immediate was to be implemented by two instructions: movw (the lower 16 bits) and movt (the higher 16 bits). It was marked as a pseudo-instruction in the .td file. When this pseudo-instruction needs to be emitted, its expand function is called, which builds two machine instructions: MOVLOi16 and MOVHIi16. These map to the movw and movt instructions of the target architecture.

See also

  • To dive deep into implementing such lowering of multiple instructions, look at the ARM target implementation in the LLVM source code in the lib/Target/ARM/ARMInstrInfo.td file.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.234.188