]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/internal/obj/riscv: implement MOV pseudo-instructions
authorJoel Sing <joel@sing.id.au>
Thu, 3 Oct 2019 18:02:38 +0000 (04:02 +1000)
committerJoel Sing <joel@sing.id.au>
Tue, 15 Oct 2019 00:53:01 +0000 (00:53 +0000)
Add support for rewriting MOV pseudo-instructions into appropriate
RISC-V instructions.

Based on the riscv-go port.

Updates #27532

Change-Id: I22da6f5f12c841d56fb676ab2a37f6d0a686b033
Reviewed-on: https://go-review.googlesource.com/c/go/+/198677
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Run-TryBot: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/cmd/asm/internal/asm/testdata/riscvenc.s
src/cmd/internal/obj/riscv/obj.go

index f5cb10724c083f0129e8df61142936f4ea9b00c8..73f41dd8772c57fa461c286c8031e6ef32f2ab43 100644 (file)
@@ -237,3 +237,33 @@ start:
        // Arbitrary bytes (entered in little-endian mode)
        WORD    $0x12345678     // WORD $305419896      // 78563412
        WORD    $0x9abcdef0     // WORD $2596069104     // f0debc9a
+
+       // MOV pseudo-instructions
+       MOV     X5, X6                                  // 13830200
+       MOV     $2047, X5                               // 9b02f07f
+       MOV     $-2048, X5                              // 9b020080
+
+       MOV     (X5), X6                                // 03b30200
+       MOV     4(X5), X6                               // 03b34200
+       MOVB    (X5), X6                                // 03830200
+       MOVB    4(X5), X6                               // 03834200
+       MOVH    (X5), X6                                // 03930200
+       MOVH    4(X5), X6                               // 03934200
+       MOVW    (X5), X6                                // 03a30200
+       MOVW    4(X5), X6                               // 03a34200
+       MOV     X5, (X6)                                // 23305300
+       MOV     X5, 4(X6)                               // 23325300
+       MOVB    X5, (X6)                                // 23005300
+       MOVB    X5, 4(X6)                               // 23025300
+       MOVH    X5, (X6)                                // 23105300
+       MOVH    X5, 4(X6)                               // 23125300
+       MOVW    X5, (X6)                                // 23205300
+       MOVW    X5, 4(X6)                               // 23225300
+
+       MOVF    4(X5), F0                               // 07a04200
+       MOVF    F0, 4(X5)                               // 27a20200
+       MOVF    F0, F1                                  // d3000020
+
+       MOVD    4(X5), F0                               // 07b04200
+       MOVD    F0, 4(X5)                               // 27b20200
+       MOVD    F0, F1                                  // d3000022
index 31852a3a50dc33fcd14947db5494f12171a4affe..9a3930f5df8cb256e91fe63f46709d03a1890ce1 100644 (file)
@@ -148,6 +148,248 @@ func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
        }
 }
 
+// addrToReg extracts the register from an Addr, handling special Addr.Names.
+func addrToReg(a obj.Addr) int16 {
+       switch a.Name {
+       case obj.NAME_PARAM, obj.NAME_AUTO:
+               return REG_SP
+       }
+       return a.Reg
+}
+
+// movToLoad converts a MOV mnemonic into the corresponding load instruction.
+func movToLoad(mnemonic obj.As) obj.As {
+       switch mnemonic {
+       case AMOV:
+               return ALD
+       case AMOVB:
+               return ALB
+       case AMOVH:
+               return ALH
+       case AMOVW:
+               return ALW
+       case AMOVBU:
+               return ALBU
+       case AMOVHU:
+               return ALHU
+       case AMOVWU:
+               return ALWU
+       case AMOVF:
+               return AFLW
+       case AMOVD:
+               return AFLD
+       default:
+               panic(fmt.Sprintf("%+v is not a MOV", mnemonic))
+       }
+}
+
+// movToStore converts a MOV mnemonic into the corresponding store instruction.
+func movToStore(mnemonic obj.As) obj.As {
+       switch mnemonic {
+       case AMOV:
+               return ASD
+       case AMOVB:
+               return ASB
+       case AMOVH:
+               return ASH
+       case AMOVW:
+               return ASW
+       case AMOVF:
+               return AFSW
+       case AMOVD:
+               return AFSD
+       default:
+               panic(fmt.Sprintf("%+v is not a MOV", mnemonic))
+       }
+}
+
+// rewriteMOV rewrites MOV pseudo-instructions.
+func rewriteMOV(ctxt *obj.Link, newprog obj.ProgAlloc, p *obj.Prog) {
+       switch p.As {
+       case AMOV, AMOVB, AMOVH, AMOVW, AMOVBU, AMOVHU, AMOVWU, AMOVF, AMOVD:
+       default:
+               panic(fmt.Sprintf("%+v is not a MOV pseudo-instruction", p.As))
+       }
+
+       switch p.From.Type {
+       case obj.TYPE_MEM: // MOV c(Rs), Rd -> L $c, Rs, Rd
+               switch p.From.Name {
+               case obj.NAME_AUTO, obj.NAME_PARAM, obj.NAME_NONE:
+                       if p.To.Type != obj.TYPE_REG {
+                               ctxt.Diag("unsupported load at %v", p)
+                       }
+                       p.As = movToLoad(p.As)
+                       p.Reg = addrToReg(p.From)
+                       p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: p.From.Offset}
+
+               case obj.NAME_EXTERN, obj.NAME_STATIC:
+                       // AUIPC $off_hi, R
+                       // L $off_lo, R
+                       as := p.As
+                       to := p.To
+
+                       // The offset is not really encoded with either instruction.
+                       // It will be extracted later for a relocation.
+                       p.As = AAUIPC
+                       p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: p.From.Offset, Sym: p.From.Sym}
+                       p.Reg = 0
+                       p.To = obj.Addr{Type: obj.TYPE_REG, Reg: to.Reg}
+                       p.Mark |= NEED_PCREL_ITYPE_RELOC
+                       p = obj.Appendp(p, newprog)
+
+                       p.As = movToLoad(as)
+                       p.From = obj.Addr{Type: obj.TYPE_CONST}
+                       p.Reg = to.Reg
+                       p.To = to
+
+               default:
+                       ctxt.Diag("unsupported name %d for %v", p.From.Name, p)
+               }
+
+       case obj.TYPE_REG:
+               switch p.To.Type {
+               case obj.TYPE_REG:
+                       switch p.As {
+                       case AMOV: // MOV Ra, Rb -> ADDI $0, Ra, Rb
+                               p.As = AADDI
+                               p.Reg = p.From.Reg
+                               p.From = obj.Addr{Type: obj.TYPE_CONST}
+
+                       case AMOVF: // MOVF Ra, Rb -> FSGNJS Ra, Ra, Rb
+                               p.As = AFSGNJS
+                               p.Reg = p.From.Reg
+
+                       case AMOVD: // MOVD Ra, Rb -> FSGNJD Ra, Ra, Rb
+                               p.As = AFSGNJD
+                               p.Reg = p.From.Reg
+
+                       default:
+                               ctxt.Diag("unsupported register-register move at %v", p)
+                       }
+
+               case obj.TYPE_MEM: // MOV Rs, c(Rd) -> S $c, Rs, Rd
+                       switch p.As {
+                       case AMOVBU, AMOVHU, AMOVWU:
+                               ctxt.Diag("unsupported unsigned store at %v", p)
+                       }
+                       switch p.To.Name {
+                       case obj.NAME_AUTO, obj.NAME_PARAM, obj.NAME_NONE:
+                               // The destination address goes in p.From and p.To here,
+                               // with the offset in p.From and the register in p.To.
+                               // The source register goes in Reg.
+                               p.As = movToStore(p.As)
+                               p.Reg = p.From.Reg
+                               p.From = p.To
+                               p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: p.From.Offset}
+                               p.To = obj.Addr{Type: obj.TYPE_REG, Reg: addrToReg(p.To)}
+
+                       case obj.NAME_EXTERN:
+                               // AUIPC $off_hi, TMP
+                               // S $off_lo, TMP, R
+                               as := p.As
+                               from := p.From
+
+                               // The offset is not really encoded with either instruction.
+                               // It will be extracted later for a relocation.
+                               p.As = AAUIPC
+                               p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: p.To.Offset, Sym: p.To.Sym}
+                               p.Reg = 0
+                               p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
+                               p.Mark |= NEED_PCREL_STYPE_RELOC
+                               p = obj.Appendp(p, newprog)
+
+                               p.As = movToStore(as)
+                               p.From = obj.Addr{Type: obj.TYPE_CONST}
+                               p.Reg = from.Reg
+                               p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
+
+                       default:
+                               ctxt.Diag("unsupported name %d for %v", p.From.Name, p)
+                       }
+
+               default:
+                       ctxt.Diag("unsupported MOV at %v", p)
+               }
+
+       case obj.TYPE_CONST:
+               // MOV $c, R
+               // If c is small enough, convert to:
+               //   ADD $c, ZERO, R
+               // If not, convert to:
+               //   LUI top20bits(c), R
+               //   ADD bottom12bits(c), R, R
+               if p.As != AMOV {
+                       ctxt.Diag("unsupported constant load at %v", p)
+               }
+               off := p.From.Offset
+               to := p.To
+
+               low, high, err := split32BitImmediate(off)
+               if err != nil {
+                       ctxt.Diag("%v: constant %d too large: %v", p, off, err)
+               }
+
+               // LUI is only necessary if the offset doesn't fit in 12-bits.
+               needLUI := high != 0
+               if needLUI {
+                       p.As = ALUI
+                       p.To = to
+                       // Pass top 20 bits to LUI.
+                       p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: high}
+                       p = obj.Appendp(p, newprog)
+               }
+               p.As = AADDIW
+               p.To = to
+               p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: low}
+               p.Reg = REG_ZERO
+               if needLUI {
+                       p.Reg = to.Reg
+               }
+
+       case obj.TYPE_ADDR: // MOV $sym+off(SP/SB), R
+               if p.To.Type != obj.TYPE_REG || p.As != AMOV {
+                       ctxt.Diag("unsupported addr MOV at %v", p)
+               }
+               switch p.From.Name {
+               case obj.NAME_EXTERN, obj.NAME_STATIC:
+                       // AUIPC $off_hi, R
+                       // ADDI $off_lo, R
+                       to := p.To
+
+                       // The offset is not really encoded with either instruction.
+                       // It will be extracted later for a relocation.
+                       p.As = AAUIPC
+                       p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: p.From.Offset, Sym: p.From.Sym}
+                       p.Reg = 0
+                       p.To = to
+                       p.Mark |= NEED_PCREL_ITYPE_RELOC
+                       p = obj.Appendp(p, newprog)
+
+                       p.As = AADDI
+                       p.From = obj.Addr{Type: obj.TYPE_CONST}
+                       p.Reg = to.Reg
+                       p.To = to
+
+               case obj.NAME_PARAM, obj.NAME_AUTO:
+                       p.As = AADDI
+                       p.Reg = REG_SP
+                       p.From.Type = obj.TYPE_CONST
+
+               case obj.NAME_NONE:
+                       p.As = AADDI
+                       p.Reg = p.From.Reg
+                       p.From.Type = obj.TYPE_CONST
+                       p.From.Reg = 0
+
+               default:
+                       ctxt.Diag("bad addr MOV from name %v at %v", p.From.Name, p)
+               }
+
+       default:
+               ctxt.Diag("unsupported MOV at %v", p)
+       }
+}
+
 // setPCs sets the Pc field in all instructions reachable from p.
 // It uses pc as the initial value.
 func setPCs(p *obj.Prog, pc int64) {
@@ -157,6 +399,44 @@ func setPCs(p *obj.Prog, pc int64) {
        }
 }
 
+// stackOffset updates Addr offsets based on the current stack size.
+//
+// The stack looks like:
+// -------------------
+// |                 |
+// |      PARAMs     |
+// |                 |
+// |                 |
+// -------------------
+// |    Parent RA    |   SP on function entry
+// -------------------
+// |                 |
+// |                 |
+// |       AUTOs     |
+// |                 |
+// |                 |
+// -------------------
+// |        RA       |   SP during function execution
+// -------------------
+//
+// FixedFrameSize makes other packages aware of the space allocated for RA.
+//
+// A nicer version of this diagram can be found on slide 21 of the presentation
+// attached to:
+//
+//   https://golang.org/issue/16922#issuecomment-243748180
+//
+func stackOffset(a *obj.Addr, stacksize int64) {
+       switch a.Name {
+       case obj.NAME_AUTO:
+               // Adjust to the top of AUTOs.
+               a.Offset += stacksize
+       case obj.NAME_PARAM:
+               // Adjust to the bottom of PARAMs.
+               a.Offset += stacksize + 8
+       }
+}
+
 func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
        if cursym.Func.Text == nil || cursym.Func.Text.Link == nil {
                return
@@ -188,6 +468,24 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
 
        // TODO(jsing): Implement.
 
+       // Update stack-based offsets.
+       for p := cursym.Func.Text; p != nil; p = p.Link {
+               stackOffset(&p.From, stacksize)
+               stackOffset(&p.To, stacksize)
+       }
+
+       // Additional instruction rewriting. Any rewrites that change the number
+       // of instructions must occur here (before jump target resolution).
+       for p := cursym.Func.Text; p != nil; p = p.Link {
+               switch p.As {
+               case AMOV, AMOVB, AMOVH, AMOVW, AMOVBU, AMOVHU, AMOVWU, AMOVF, AMOVD:
+                       // Rewrite MOV pseudo-instructions. This cannot be done in
+                       // progedit, as SP offsets need to be applied before we split
+                       // up some of the Addrs.
+                       rewriteMOV(ctxt, newprog, p)
+               }
+       }
+
        setPCs(cursym.Func.Text, 0)
 
        // Resolve branch and jump targets.
@@ -209,6 +507,46 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
        }
 }
 
+// signExtend sign extends val starting at bit bit.
+func signExtend(val int64, bit uint) int64 {
+       return val << (64 - bit) >> (64 - bit)
+}
+
+// split32BitImmediate splits a signed 32-bit immediate into a signed 20-bit
+// upper immediate and a signed 12-bit lower immediate to be added to the upper
+// result. For example, high may be used in LUI and low in a following ADDI to
+// generate a full 32-bit constant.
+func split32BitImmediate(imm int64) (low, high int64, err error) {
+       if !immIFits(imm, 32) {
+               return 0, 0, fmt.Errorf("immediate does not fit in 32-bits: %d", imm)
+       }
+
+       // Nothing special needs to be done if the immediate fits in 12-bits.
+       if immIFits(imm, 12) {
+               return imm, 0, nil
+       }
+
+       high = imm >> 12
+
+       // The bottom 12 bits will be treated as signed.
+       //
+       // If that will result in a negative 12 bit number, add 1 to
+       // our upper bits to adjust for the borrow.
+       //
+       // It is not possible for this increment to overflow. To
+       // overflow, the 20 top bits would be 1, and the sign bit for
+       // the low 12 bits would be set, in which case the entire 32
+       // bit pattern fits in a 12 bit signed value.
+       if imm&(1<<11) != 0 {
+               high++
+       }
+
+       low = signExtend(imm, 12)
+       high = signExtend(high, 20)
+
+       return low, high, nil
+}
+
 func regVal(r, min, max int16) uint32 {
        if r < min || r > max {
                panic(fmt.Sprintf("register out of range, want %d < %d < %d", min, r, max))