The patch adds support for LDR(register offset) instruction.
And add the test cases and negative tests.
Change-Id: I5b32c6a5065afc4571116d4896f7ebec3c0416d3
Reviewed-on: https://go-review.googlesource.com/87955
Reviewed-by: Cherry Zhang <cherryyz@google.com>
return 0, false
}
+// rm is the Rm register value, o is the extension, amount is the left shift value.
+func roff(rm uint32, o uint32, amount int16) int64 {
+ return int64((rm&31)<<16 | o<<13 | uint32(amount)<<10)
+}
+
// ARM64RegisterExtension parses an ARM64 register with extension or arrangement.
func ARM64RegisterExtension(a *obj.Addr, ext string, reg, num int16, isAmount, isIndex bool) error {
rm := uint32(reg)
+ Rnum := (reg & 31) + int16(num<<5)
if isAmount {
if num < 0 || num > 7 {
- return errors.New("shift amount out of range")
+ return errors.New("index shift amount is out of range")
}
}
switch ext {
if !isAmount {
return errors.New("invalid register extension")
}
- a.Reg = arm64.REG_UXTB + (reg & 31) + num<<5
- a.Offset = int64(((rm & 31) << 16) | (uint32(num) << 10))
+ if a.Type == obj.TYPE_MEM {
+ return errors.New("invalid shift for the register offset addressing mode")
+ }
+ a.Reg = arm64.REG_UXTB + Rnum
+ a.Offset = roff(rm, 0, num)
case "UXTH":
if !isAmount {
return errors.New("invalid register extension")
}
- a.Reg = arm64.REG_UXTH + (reg & 31) + num<<5
- a.Offset = int64(((rm & 31) << 16) | (1 << 13) | (uint32(num) << 10))
+ if a.Type == obj.TYPE_MEM {
+ return errors.New("invalid shift for the register offset addressing mode")
+ }
+ a.Reg = arm64.REG_UXTH + Rnum
+ a.Offset = roff(rm, 1, num)
case "UXTW":
if !isAmount {
return errors.New("invalid register extension")
}
- a.Reg = arm64.REG_UXTW + (reg & 31) + num<<5
- a.Offset = int64(((rm & 31) << 16) | (2 << 13) | (uint32(num) << 10))
+ // effective address of memory is a base register value and an offset register value.
+ if a.Type == obj.TYPE_MEM {
+ a.Index = arm64.REG_UXTW + Rnum
+ if num == 0 {
+ a.Offset = roff(rm, 2, 2)
+ } else {
+ a.Offset = roff(rm, 2, 6)
+ }
+ } else {
+ a.Reg = arm64.REG_UXTW + Rnum
+ a.Offset = roff(rm, 2, num)
+ }
case "UXTX":
if !isAmount {
return errors.New("invalid register extension")
}
- a.Reg = arm64.REG_UXTX + (reg & 31) + num<<5
- a.Offset = int64(((rm & 31) << 16) | (3 << 13) | (uint32(num) << 10))
+ if a.Type == obj.TYPE_MEM {
+ return errors.New("invalid shift for the register offset addressing mode")
+ }
+ a.Reg = arm64.REG_UXTX + Rnum
+ a.Offset = roff(rm, 3, num)
case "SXTB":
if !isAmount {
return errors.New("invalid register extension")
}
- a.Reg = arm64.REG_SXTB + (reg & 31) + num<<5
- a.Offset = int64(((rm & 31) << 16) | (4 << 13) | (uint32(num) << 10))
+ a.Reg = arm64.REG_SXTB + Rnum
+ a.Offset = roff(rm, 4, num)
case "SXTH":
if !isAmount {
return errors.New("invalid register extension")
}
- a.Reg = arm64.REG_SXTH + (reg & 31) + num<<5
- a.Offset = int64(((rm & 31) << 16) | (5 << 13) | (uint32(num) << 10))
+ if a.Type == obj.TYPE_MEM {
+ return errors.New("invalid shift for the register offset addressing mode")
+ }
+ a.Reg = arm64.REG_SXTH + Rnum
+ a.Offset = roff(rm, 5, num)
case "SXTW":
if !isAmount {
return errors.New("invalid register extension")
}
- a.Reg = arm64.REG_SXTW + (reg & 31) + num<<5
- a.Offset = int64(((rm & 31) << 16) | (6 << 13) | (uint32(num) << 10))
+ if a.Type == obj.TYPE_MEM {
+ a.Index = arm64.REG_SXTW + Rnum
+ if num == 0 {
+ a.Offset = roff(rm, 6, 2)
+ } else {
+ a.Offset = roff(rm, 6, 6)
+ }
+ } else {
+ a.Reg = arm64.REG_SXTW + Rnum
+ a.Offset = roff(rm, 6, num)
+ }
case "SXTX":
if !isAmount {
return errors.New("invalid register extension")
}
- a.Reg = arm64.REG_SXTX + (reg & 31) + num<<5
- a.Offset = int64(((rm & 31) << 16) | (7 << 13) | (uint32(num) << 10))
+ if a.Type == obj.TYPE_MEM {
+ a.Index = arm64.REG_SXTX + Rnum
+ if num == 0 {
+ a.Offset = roff(rm, 7, 2)
+ } else {
+ a.Offset = roff(rm, 7, 6)
+ }
+ } else {
+ a.Reg = arm64.REG_SXTX + Rnum
+ a.Offset = roff(rm, 7, num)
+ }
+ case "LSL":
+ if !isAmount {
+ return errors.New("invalid register extension")
+ }
+ a.Index = arm64.REG_LSL + Rnum
+ a.Offset = roff(rm, 3, 6)
case "B8":
if isIndex {
return errors.New("invalid register extension")
default:
return errors.New("unsupported register extension type: " + ext)
}
- a.Type = obj.TYPE_REG
+
return nil
}
p.get(')')
}
} else if p.atRegisterExtension() {
+ a.Type = obj.TYPE_REG
p.registerExtension(a, tok.String(), prefix)
p.expectOperandEnd()
return
return
}
- p.get('.')
- tok := p.next()
- ext := tok.String()
isIndex := false
num := int16(0)
isAmount := true // Amount is zero by default
+ ext := ""
+ if p.peek() == lex.LSH {
+ // (Rn)(Rm<<2), the shifted offset register.
+ ext = "LSL"
+ } else {
+ // (Rn)(Rm.UXTW<1), the extended offset register.
+ // Rm.UXTW<<3, the extended register.
+ p.get('.')
+ tok := p.next()
+ ext = tok.String()
+ }
if p.peek() == lex.LSH {
// parses left shift amount applied after extension: <<Amount
p.get(lex.LSH)
}
// registerIndirect parses the general form of a register indirection.
-// It is can be (R1), (R2*scale), or (R1)(R2*scale) where R1 may be a simple
-// register or register pair R:R or (R, R) or (R+R).
+// It is can be (R1), (R2*scale), (R1)(R2*scale), (R1)(R2.SXTX<<3) or (R1)(R2<<3)
+// where R1 may be a simple register or register pair R:R or (R, R) or (R+R).
// Or it might be a pseudo-indirection like (FP).
// We are sitting on the opening parenthesis.
func (p *Parser) registerIndirect(a *obj.Addr, prefix rune) {
// General form (R)(R*scale).
p.next()
tok := p.next()
- r1, r2, scale, ok = p.register(tok.String(), 0)
- if !ok {
- p.errorf("indirect through non-register %s", tok)
- }
- if r2 != 0 {
- p.errorf("unimplemented two-register form")
- }
- a.Index = r1
- if scale == 0 && p.arch.Family == sys.ARM64 {
- // scale is 1 by default for ARM64
- a.Scale = 1
+ if p.atRegisterExtension() {
+ p.registerExtension(a, tok.String(), prefix)
+ } else if p.atRegisterShift() {
+ // (R1)(R2<<3)
+ p.registerExtension(a, tok.String(), prefix)
} else {
- a.Scale = int16(scale)
+ r1, r2, scale, ok = p.register(tok.String(), 0)
+ if !ok {
+ p.errorf("indirect through non-register %s", tok)
+ }
+ if r2 != 0 {
+ p.errorf("unimplemented two-register form")
+ }
+ a.Index = r1
+ if scale == 0 && p.arch.Family == sys.ARM64 {
+ // scale is 1 by default for ARM64
+ a.Scale = 1
+ } else {
+ a.Scale = int16(scale)
+ }
}
p.get(')')
} else if scale != 0 {
VSHL $8, V1.H8, V2.H8 // 2254184f
VSHL $2, V1.B8, V2.B8 // 22540a0f
VSHL $2, V1.B16, V2.B16 // 22540a4f
-
+ MOVD (R2)(R6.SXTW), R4 // 44c866f8
+ MOVD (R3)(R6), R5 // MOVD (R3)(R6*1), R5 // 656866f8
+ MOVD (R2)(R6), R4 // MOVD (R2)(R6*1), R4 // 446866f8
+ MOVWU (R19)(R18<<2), R18 // 727a72b8
+ MOVD (R2)(R6<<3), R4 // 447866f8
+ MOVD (R3)(R7.SXTX<<3), R8 // 68f867f8
+ MOVWU (R5)(R4.UXTW), R10 // aa4864b8
// LTYPE1 imsr ',' spreg ','
// {
// outcode($1, &$2, $4, &nullgen);
TEXT errors(SB),$0
AND $1, RSP // ERROR "illegal combination"
ANDS $1, R0, RSP // ERROR "illegal combination"
- MOVD.P 300(R2), R3 // ERROR "offset out of range [-255,254]"
- MOVD.P R3, 344(R2) // ERROR "offset out of range [-255,254]"
ADDSW R7->32, R14, R13 // ERROR "shift amount out of range 0 to 31"
- BICW R7@>33, R5, R16 // ERROR "shift amount out of range 0 to 31"
ADD R1.UXTB<<5, R2, R3 // ERROR "shift amount out of range 0 to 4"
ADDS R1.UXTX<<7, R2, R3 // ERROR "shift amount out of range 0 to 4"
+ BICW R7@>33, R5, R16 // ERROR "shift amount out of range 0 to 31"
+ MOVD.P 300(R2), R3 // ERROR "offset out of range [-255,254]"
+ MOVD.P R3, 344(R2) // ERROR "offset out of range [-255,254]"
+ MOVD (R3)(R7.SXTX<<2), R8 // ERROR "invalid index shift amount"
+ MOVWU (R5)(R4.UXTW<<3), R10 // ERROR "invalid index shift amount"
+ MOVWU (R5)(R4<<1), R10 // ERROR "invalid index shift amount"
VLD1 (R8)(R13), [V2.B16] // ERROR "illegal combination"
VLD1 8(R9), [V2.B16] // ERROR "illegal combination"
VST1 [V1.B16], (R8)(R13) // ERROR "illegal combination"
VRBIT V1.H4, V2.H4 // ERROR "invalid arrangement"
VUSHR $56, V1.D2, V2.H4 // ERROR "invalid arrangement"
VUSHR $127, V1.D2, V2.D2 // ERROR "shift out of range"
+ VLD1.P (R8)(R9.SXTX<<2), [V2.B16] // ERROR "invalid extended register"
+ VLD1.P (R8)(R9<<2), [V2.B16] // ERROR "invalid extended register"
+ VST1.P [V1.B16], (R8)(R9.UXTW) // ERROR "invalid extended register"
+ VST1.P [V1.B16], (R8)(R9<<1) // ERROR "invalid extended register"
RET
// constants to indicate extended register conversion. When checking,
// you should subtract obj.RBaseARM64 first. From this difference, bit 11
// indicates extended register, bits 8-10 select the conversion mode.
+// REG_LSL is the index shift specifier, bit 9 indicates shifted offset register.
+const REG_LSL = obj.RBaseARM64 + 1<<9
const REG_EXT = obj.RBaseARM64 + 1<<11
const (
{AFMOVD, C_FREG, C_NONE, C_LOREG, 23, 4, 0, 0, C_XPRE},
{AVMOVS, C_VREG, C_NONE, C_LOREG, 23, 4, 0, 0, C_XPRE},
+ /* load with shifted or extended register offset */
+ {AMOVD, C_ROFF, C_NONE, C_REG, 98, 4, 0, 0, 0},
+ {AMOVW, C_ROFF, C_NONE, C_REG, 98, 4, 0, 0, 0},
+
/* pre/post-indexed/signed-offset load/store register pair
(unscaled, signed 10-bit quad-aligned and long offset) */
{ALDP, C_NPAUTO, C_NONE, C_PAIR, 66, 4, REGSP, 0, 0},
return uint32(c.instoffset)
}
+func isRegShiftOrExt(a *obj.Addr) bool {
+ return (a.Index-obj.RBaseARM64)®_EXT != 0 || (a.Index-obj.RBaseARM64)®_LSL != 0
+}
+
// Maximum PC-relative displacement.
// The actual limit is ±2²⁰, but we are conservative
// to avoid needing to recompute the literal pool flush points
case obj.NAME_NONE:
if a.Index != 0 {
if a.Offset != 0 {
+ if isRegShiftOrExt(a) {
+ // extended or shifted register offset, (Rn)(Rm.UXTW<<2) or (Rn)(Rm<<2).
+ return C_ROFF
+ }
return C_GOK
}
+ // register offset, (Rn)(Rm)
return C_ROFF
}
c.instoffset = a.Offset
}
}
-/* checkoffset checks whether the immediate offset is valid for VLD1.P and VST1.P*/
+/* checkoffset checks whether the immediate offset is valid for VLD1.P and VST1.P */
func (c *ctxt7) checkoffset(p *obj.Prog, as obj.As) {
var offset, list, n int64
switch as {
}
}
+/* checkShiftAmount checks whether the index shift amount is valid */
+/* for load with register offset instructions */
+func (c *ctxt7) checkShiftAmount(p *obj.Prog, as obj.As) {
+ amount := (p.From.Index >> 5) & 7
+ switch as {
+ case AMOVWU:
+ if amount != 2 && amount != 0 {
+ c.ctxt.Diag("invalid index shift amount: %v", p)
+ }
+
+ case AMOVD:
+ if amount != 3 && amount != 0 {
+ c.ctxt.Diag("invalid index shift amount: %v", p)
+ }
+ }
+}
+
func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
o1 := uint32(0)
o2 := uint32(0)
c.ctxt.Diag("REGTMP used in large offset load: %v", p)
}
o1 = c.omovlit(AMOVD, p, &p.From, REGTMP)
- o2 = c.olsxrr(p, int32(c.opldrr(p, p.As)), int(p.To.Reg), r, REGTMP)
+ o2 = c.olsxrr(p, int32(c.opldrr(p, p.As, false)), int(p.To.Reg), r, REGTMP)
case 32: /* mov $con, R -> movz/movn */
o1 = c.omovconst(p.As, p, &p.From, int(p.To.Reg))
o1 |= 0x1f << 16
} else {
// register offset variant
+ if isRegShiftOrExt(&p.From) {
+ c.ctxt.Diag("invalid extended register op: %v\n", p)
+ }
o1 |= uint32(p.From.Index&31) << 16
}
}
o1 |= 0x1f << 16
} else {
// register offset variant
+ if isRegShiftOrExt(&p.To) {
+ c.ctxt.Diag("invalid extended register: %v\n", p)
+ }
o1 |= uint32(p.To.Index&31) << 16
}
}
case 90:
o1 = 0xbea71700
+ case 91: /* prfm imm(Rn), <prfop | $imm5> */
+ imm := uint32(p.From.Offset)
+ r := p.From.Reg
+ v := uint32(0xff)
+ if p.To.Type == obj.TYPE_CONST {
+ v = uint32(p.To.Offset)
+ if v > 31 {
+ c.ctxt.Diag("illegal prefetch operation\n%v", p)
+ }
+ } else {
+ for i := 0; i < len(prfopfield); i++ {
+ if prfopfield[i].reg == p.To.Reg {
+ v = prfopfield[i].enc
+ break
+ }
+ }
+ if v == 0xff {
+ c.ctxt.Diag("illegal prefetch operation:\n%v", p)
+ }
+ }
+
+ o1 = c.opldrpp(p, p.As)
+ o1 |= (uint32(r&31) << 5) | (uint32((imm>>3)&0xfff) << 10) | (uint32(v & 31))
+
case 92: /* vmov Vn.<T>[index], Vd.<T>[index] */
rf := int(p.From.Reg)
rt := int(p.To.Reg)
}
o1 |= (uint32(imm5&0x1f) << 16) | (uint32(imm4&0xf) << 11) | (uint32(rf&31) << 5) | uint32(rt&31)
- break
-
- case 91: /* prfm imm(Rn), <prfop | $imm5> */
- imm := uint32(p.From.Offset)
- r := p.From.Reg
- v := uint32(0xff)
- if p.To.Type == obj.TYPE_CONST {
- v = uint32(p.To.Offset)
- if v > 31 {
- c.ctxt.Diag("illegal prefetch operation\n%v", p)
- }
- } else {
- for i := 0; i < len(prfopfield); i++ {
- if prfopfield[i].reg == p.To.Reg {
- v = prfopfield[i].enc
- break
- }
- }
- if v == 0xff {
- c.ctxt.Diag("illegal prefetch operation:\n%v", p)
- }
- }
-
- o1 = c.opldrpp(p, p.As)
- o1 |= (uint32(r&31) << 5) | ((imm >> 3) & 0xfff << 10) | (v & 31)
-
case 93: /* vpmull{2} Vm.<T>, Vn.<T>, Vd */
af := int((p.From.Reg >> 5) & 15)
at := int((p.To.Reg >> 5) & 15)
}
o1 |= (uint32(Q&1) << 30) | (uint32(r&31) << 16) | ((opcode & 7) << 13) | (uint32(S&1) << 12) | (uint32(size&3) << 10) | (uint32(rf&31) << 5) | uint32(rt&31)
+
+ case 98: /* MOVD (Rn)(Rm.SXTW[<<amount]),Rd */
+ if p.From.Offset != 0 {
+ // extended or shifted offset register.
+ c.checkShiftAmount(p, p.As)
+ o1 = c.opldrr(p, p.As, true)
+ o1 |= uint32(p.From.Offset) /* includes reg, op, etc */
+ } else {
+ // (Rn)(Rm), no extension or shift.
+ o1 = c.opldrr(p, p.As, false)
+ o1 |= uint32(p.From.Index&31) << 16
+ }
+ o1 |= uint32(p.From.Reg&31) << 5
+ rt := int(p.To.Reg)
+ if p.To.Type == obj.TYPE_NONE {
+ rt = REGZERO
+ }
+ o1 |= uint32(rt & 31)
}
out[0] = o1
out[1] = o2
// opldrr returns the ARM64 opcode encoding corresponding to the obj.As opcode
// for load instruction with register offset.
-func (c *ctxt7) opldrr(p *obj.Prog, a obj.As) uint32 {
+// The offset register can be (Rn)(Rm.UXTW<<2) or (Rn)(Rm<<2) or (Rn)(Rm).
+func (c *ctxt7) opldrr(p *obj.Prog, a obj.As, extension bool) uint32 {
+ OptionS := uint32(0x1a)
+ if extension {
+ OptionS = uint32(0) // option value and S value have been encoded into p.From.Offset.
+ }
switch a {
case AMOVD:
- return 0x1a<<10 | 0x3<<21 | 0x1f<<27
+ return OptionS<<10 | 0x3<<21 | 0x1f<<27
case AMOVW:
- return 0x1a<<10 | 0x5<<21 | 0x17<<27
+ return OptionS<<10 | 0x5<<21 | 0x17<<27
case AMOVWU:
- return 0x1a<<10 | 0x3<<21 | 0x17<<27
+ return OptionS<<10 | 0x3<<21 | 0x17<<27
case AMOVH:
- return 0x1a<<10 | 0x5<<21 | 0x0f<<27
+ return OptionS<<10 | 0x5<<21 | 0x0f<<27
case AMOVHU:
- return 0x1a<<10 | 0x3<<21 | 0x0f<<27
+ return OptionS<<10 | 0x3<<21 | 0x0f<<27
case AMOVB:
- return 0x1a<<10 | 0x5<<21 | 0x07<<27
+ return OptionS<<10 | 0x5<<21 | 0x07<<27
case AMOVBU:
- return 0x1a<<10 | 0x3<<21 | 0x07<<27
+ return OptionS<<10 | 0x3<<21 | 0x07<<27
case AFMOVS:
- return 0x1a<<10 | 0x3<<21 | 0x17<<27 | 1<<26
+ return OptionS<<10 | 0x3<<21 | 0x17<<27 | 1<<26
case AFMOVD:
- return 0x1a<<10 | 0x3<<21 | 0x1f<<27 | 1<<26
+ return OptionS<<10 | 0x3<<21 | 0x1f<<27 | 1<<26
}
c.ctxt.Diag("bad opldrr %v\n%v", a, p)
return 0
1. Alphabetical list of basic instructions
// TODO
- PRFM: Prefetch Memory (immediate)
- PRFM imm(Rn), <prfop>
- prfop is the prefetch operation and can have the following values:
- PLDL1KEEP, PLDL1STRM, PLDL2KEEP, PLDL2STRM, PLDL3KEEP, PLDL3STRM,
- PLIL1KEEP, PLIL1STRM, PLIL2KEEP, PLIL2STRM, PLIL3KEEP, PLIL3STRM,
- PSTL1KEEP, PSTL1STRM, PSTL2KEEP, PSTL2STRM, PSTL3KEEP, PSTL3STRM.
- PRFM imm(Rn), $imm
- $imm prefetch operation is encoded as an immediate.
LDARB: Load-Acquire Register Byte
LDARB (<Rn>), <Rd>
LDXPW (<Rn>), (<Rt1>, <Rt2>)
Loads two 32-bit words from memory, and writes them to Rt1 and Rt2.
+ MOVD|MOVW: Load Register (register offset)
+ MOVD (Rn)(Rm.UXTW<<3), Rt
+ MOVD (Rn)(Rm.SXTX), Rt
+ MOVD (Rn)(Rm), Rt
+
+ PRFM: Prefetch Memory (immediate)
+ PRFM imm(Rn), <prfop>
+ prfop is the prefetch operation and can have the following values:
+ PLDL1KEEP, PLDL1STRM, PLDL2KEEP, PLDL2STRM, PLDL3KEEP, PLDL3STRM,
+ PLIL1KEEP, PLIL1STRM, PLIL2KEEP, PLIL2STRM, PLIL3KEEP, PLIL3STRM,
+ PSTL1KEEP, PSTL1STRM, PSTL2KEEP, PSTL2STRM, PSTL3KEEP, PSTL3STRM.
+ PRFM imm(Rn), $imm
+ $imm prefetch operation is encoded as an immediate.
+
STLRB: Store-Release Register Byte
STLRB <Rd>, (<Rn>)
Stores a byte from Rd to a memory location from Rn.
} else {
return fmt.Sprintf("%s.SXTX", regname(r))
}
+ // bits 0-4 indicate register, bits 5-7 indicate shift amount, bit 8 equals to 0.
+ case REG_LSL <= r && r < (REG_LSL+1<<8):
+ return fmt.Sprintf("R%d<<%d", r&31, (r>>5)&7)
case REG_ARNG <= r && r < REG_ELEM:
return fmt.Sprintf("V%d.%s", r&31, arrange((r>>5)&15))
case REG_ELEM <= r && r < REG_ELEM_END:
case TYPE_MEM:
str = Mconv(a)
if a.Index != REG_NONE {
- str += fmt.Sprintf("(%v*%d)", Rconv(int(a.Index)), int(a.Scale))
+ if a.Scale == 0 {
+ // arm64 shifted or extended register offset, scale = 0.
+ str += fmt.Sprintf("(%v)", Rconv(int(a.Index)))
+ } else {
+ str += fmt.Sprintf("(%v*%d)", Rconv(int(a.Index)), int(a.Scale))
+ }
}
case TYPE_CONST:
case a.Offset == 0:
str = fmt.Sprintf("(%v)", Rconv(int(a.Reg)))
case a.Offset != 0:
- str = fmt.Sprintf("%d(%v)", a.Offset, Rconv(int(a.Reg)))
+ switch objabi.GOARCH {
+ case "arm64":
+ // the register and the extension/shift are encoded in a.Offset.
+ if a.Index != 0 {
+ str = fmt.Sprintf("(%v)", Rconv(int(a.Reg)))
+ return str
+ }
+ fallthrough
+ default:
+ str = fmt.Sprintf("%d(%v)", a.Offset, Rconv(int(a.Reg)))
+ }
}
// Note: a.Reg == REG_NONE encodes the default base register for the NAME_ type.