blob: 9a3930f5df8cb256e91fe63f46709d03a1890ce1 [file] [log] [blame]
// Copyright © 2015 The Go Authors. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package riscv
import (
"cmd/internal/obj"
"cmd/internal/sys"
"fmt"
)
// TODO(jsing): Populate.
var RISCV64DWARFRegisters = map[int16]int16{}
func buildop(ctxt *obj.Link) {}
// lowerJALR normalizes a JALR instruction.
func lowerJALR(p *obj.Prog) {
if p.As != AJALR {
panic("lowerJALR: not a JALR")
}
// JALR gets parsed like JAL - the linkage pointer goes in From,
// and the target is in To. However, we need to assemble it as an
// I-type instruction, so place the linkage pointer in To, the
// target register in Reg, and the offset in From.
p.Reg = p.To.Reg
p.From, p.To = p.To, p.From
p.From.Type, p.From.Reg = obj.TYPE_CONST, obj.REG_NONE
}
// progedit is called individually for each *obj.Prog. It normalizes instruction
// formats and eliminates as many pseudo-instructions as possible.
func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
// Expand binary instructions to ternary ones.
if p.Reg == 0 {
switch p.As {
case AADDI, ASLTI, ASLTIU, AANDI, AORI, AXORI, ASLLI, ASRLI, ASRAI,
AADD, AAND, AOR, AXOR, ASLL, ASRL, ASUB, ASRA:
p.Reg = p.To.Reg
}
}
// Rewrite instructions with constant operands to refer to the immediate
// form of the instruction.
if p.From.Type == obj.TYPE_CONST {
switch p.As {
case AADD:
p.As = AADDI
case ASLT:
p.As = ASLTI
case ASLTU:
p.As = ASLTIU
case AAND:
p.As = AANDI
case AOR:
p.As = AORI
case AXOR:
p.As = AXORI
case ASLL:
p.As = ASLLI
case ASRL:
p.As = ASRLI
case ASRA:
p.As = ASRAI
}
}
switch p.As {
case ALW, ALWU, ALH, ALHU, ALB, ALBU, ALD, AFLW, AFLD:
switch p.From.Type {
case obj.TYPE_MEM:
// Convert loads from memory/addresses to ternary form.
p.Reg = p.From.Reg
p.From.Type, p.From.Reg = obj.TYPE_CONST, obj.REG_NONE
default:
p.Ctxt.Diag("%v\tmemory required for source", p)
}
case ASW, ASH, ASB, ASD, AFSW, AFSD:
switch p.To.Type {
case obj.TYPE_MEM:
// Convert stores to memory/addresses to ternary form.
p.Reg = p.From.Reg
p.From.Type, p.From.Offset, p.From.Reg = obj.TYPE_CONST, p.To.Offset, obj.REG_NONE
p.To.Type, p.To.Offset = obj.TYPE_REG, 0
default:
p.Ctxt.Diag("%v\tmemory required for destination", p)
}
case AJALR:
lowerJALR(p)
case obj.AUNDEF, AECALL, AEBREAK, ASCALL, ASBREAK, ARDCYCLE, ARDTIME, ARDINSTRET:
switch p.As {
case obj.AUNDEF:
p.As = AEBREAK
case ASCALL:
// SCALL is the old name for ECALL.
p.As = AECALL
case ASBREAK:
// SBREAK is the old name for EBREAK.
p.As = AEBREAK
}
ins := encode(p.As)
if ins == nil {
panic("progedit: tried to rewrite nonexistent instruction")
}
// The CSR isn't exactly an offset, but it winds up in the
// immediate area of the encoded instruction, so record it in
// the Offset field.
p.From.Type = obj.TYPE_CONST
p.From.Offset = ins.csr
p.Reg = REG_ZERO
if p.To.Type == obj.TYPE_NONE {
p.To.Type, p.To.Reg = obj.TYPE_REG, REG_ZERO
}
case AFSQRTS, AFSQRTD:
// These instructions expect a zero (i.e. float register 0)
// to be the second input operand.
p.Reg = p.From.Reg
p.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_F0}
case AFCVTWS, AFCVTLS, AFCVTWUS, AFCVTLUS, AFCVTWD, AFCVTLD, AFCVTWUD, AFCVTLUD:
// Set the rounding mode in funct3 to round to zero.
p.Scond = 1
}
}
// 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) {
for ; p != nil; p = p.Link {
p.Pc = pc
pc += int64(encodingForProg(p).length)
}
}
// 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
}
text := cursym.Func.Text
if text.As != obj.ATEXT {
ctxt.Diag("preprocess: found symbol that does not start with TEXT directive")
return
}
stacksize := text.To.Offset
if stacksize == -8 {
// Historical way to mark NOFRAME.
text.From.Sym.Set(obj.AttrNoFrame, true)
stacksize = 0
}
if stacksize < 0 {
ctxt.Diag("negative frame size %d - did you mean NOFRAME?", stacksize)
}
if text.From.Sym.NoFrame() {
if stacksize != 0 {
ctxt.Diag("NOFRAME functions must have a frame size of 0, not %d", stacksize)
}
}
cursym.Func.Args = text.To.Val.(int32)
cursym.Func.Locals = int32(stacksize)
// 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.
for p := cursym.Func.Text; p != nil; p = p.Link {
switch p.As {
case AJAL, ABEQ, ABNE, ABLT, ABLTU, ABGE, ABGEU:
switch p.To.Type {
case obj.TYPE_BRANCH:
p.To.Type, p.To.Offset = obj.TYPE_CONST, p.Pcond.Pc-p.Pc
case obj.TYPE_MEM:
panic("unhandled type")
}
}
}
// Validate all instructions - this provides nice error messages.
for p := cursym.Func.Text; p != nil; p = p.Link {
encodingForProg(p).validate(p)
}
}
// 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))
}
return uint32(r - min)
}
// regI returns an integer register.
func regI(r int16) uint32 {
return regVal(r, REG_X0, REG_X31)
}
// regF returns a float register.
func regF(r int16) uint32 {
return regVal(r, REG_F0, REG_F31)
}
// regAddr extracts a register from an Addr.
func regAddr(a obj.Addr, min, max int16) uint32 {
if a.Type != obj.TYPE_REG {
panic(fmt.Sprintf("ill typed: %+v", a))
}
return regVal(a.Reg, min, max)
}
// regIAddr extracts the integer register from an Addr.
func regIAddr(a obj.Addr) uint32 {
return regAddr(a, REG_X0, REG_X31)
}
// regFAddr extracts the float register from an Addr.
func regFAddr(a obj.Addr) uint32 {
return regAddr(a, REG_F0, REG_F31)
}
// immIFits reports whether immediate value x fits in nbits bits
// as a signed integer.
func immIFits(x int64, nbits uint) bool {
nbits--
var min int64 = -1 << nbits
var max int64 = 1<<nbits - 1
return min <= x && x <= max
}
// immUFits reports whether immediate value x fits in nbits bits
// as an unsigned integer.
func immUFits(x int64, nbits uint) bool {
var max int64 = 1<<nbits - 1
return 0 <= x && x <= max
}
// immI extracts the signed integer literal of the specified size from an Addr.
func immI(a obj.Addr, nbits uint) uint32 {
if a.Type != obj.TYPE_CONST {
panic(fmt.Sprintf("ill typed: %+v", a))
}
if !immIFits(a.Offset, nbits) {
panic(fmt.Sprintf("signed immediate %d in %v cannot fit in %d bits", a.Offset, a, nbits))
}
return uint32(a.Offset)
}
// immU extracts the unsigned integer literal of the specified size from an Addr.
func immU(a obj.Addr, nbits uint) uint32 {
if a.Type != obj.TYPE_CONST {
panic(fmt.Sprintf("ill typed: %+v", a))
}
if !immUFits(a.Offset, nbits) {
panic(fmt.Sprintf("unsigned immediate %d in %v cannot fit in %d bits", a.Offset, a, nbits))
}
return uint32(a.Offset)
}
func wantImmI(p *obj.Prog, pos string, a obj.Addr, nbits uint) {
if a.Type != obj.TYPE_CONST {
p.Ctxt.Diag("%v\texpected immediate in %s position but got %s", p, pos, obj.Dconv(p, &a))
return
}
if !immIFits(a.Offset, nbits) {
p.Ctxt.Diag("%v\tsigned immediate in %s position cannot be larger than %d bits but got %d", p, pos, nbits, a.Offset)
}
}
func wantImmU(p *obj.Prog, pos string, a obj.Addr, nbits uint) {
if a.Type != obj.TYPE_CONST {
p.Ctxt.Diag("%v\texpected immediate in %s position but got %s", p, pos, obj.Dconv(p, &a))
return
}
if !immUFits(a.Offset, nbits) {
p.Ctxt.Diag("%v\tunsigned immediate in %s position cannot be larger than %d bits but got %d", p, pos, nbits, a.Offset)
}
}
func wantReg(p *obj.Prog, pos string, descr string, r, min, max int16) {
if r < min || r > max {
p.Ctxt.Diag("%v\texpected %s register in %s position but got non-%s register %s", p, descr, pos, descr, regName(int(r)))
}
}
// wantIntReg checks that r is an integer register.
func wantIntReg(p *obj.Prog, pos string, r int16) {
wantReg(p, pos, "integer", r, REG_X0, REG_X31)
}
// wantFloatReg checks that r is a floating-point register.
func wantFloatReg(p *obj.Prog, pos string, r int16) {
wantReg(p, pos, "float", r, REG_F0, REG_F31)
}
func wantRegAddr(p *obj.Prog, pos string, a *obj.Addr, descr string, min int16, max int16) {
if a == nil {
p.Ctxt.Diag("%v\texpected register in %s position but got nothing", p, pos)
return
}
if a.Type != obj.TYPE_REG {
p.Ctxt.Diag("%v\texpected register in %s position but got %s", p, pos, obj.Dconv(p, a))
return
}
if a.Reg < min || a.Reg > max {
p.Ctxt.Diag("%v\texpected %s register in %s position but got non-%s register %s", p, descr, pos, descr, obj.Dconv(p, a))
}
}
// wantIntRegAddr checks that a contains an integer register.
func wantIntRegAddr(p *obj.Prog, pos string, a *obj.Addr) {
wantRegAddr(p, pos, a, "integer", REG_X0, REG_X31)
}
// wantFloatRegAddr checks that a contains a floating-point register.
func wantFloatRegAddr(p *obj.Prog, pos string, a *obj.Addr) {
wantRegAddr(p, pos, a, "float", REG_F0, REG_F31)
}
// wantEvenJumpOffset checks that the jump offset is a multiple of two.
func wantEvenJumpOffset(p *obj.Prog) {
if p.To.Offset%1 != 0 {
p.Ctxt.Diag("%v\tjump offset %v must be even", p, obj.Dconv(p, &p.To))
}
}
func validateRIII(p *obj.Prog) {
wantIntRegAddr(p, "from", &p.From)
wantIntReg(p, "reg", p.Reg)
wantIntRegAddr(p, "to", &p.To)
}
func validateRFFF(p *obj.Prog) {
wantFloatRegAddr(p, "from", &p.From)
wantFloatReg(p, "reg", p.Reg)
wantFloatRegAddr(p, "to", &p.To)
}
func validateRFFI(p *obj.Prog) {
wantFloatRegAddr(p, "from", &p.From)
wantFloatReg(p, "reg", p.Reg)
wantIntRegAddr(p, "to", &p.To)
}
func validateRFI(p *obj.Prog) {
wantFloatRegAddr(p, "from", &p.From)
wantIntRegAddr(p, "to", &p.To)
}
func validateRIF(p *obj.Prog) {
wantIntRegAddr(p, "from", &p.From)
wantFloatRegAddr(p, "to", &p.To)
}
func validateRFF(p *obj.Prog) {
wantFloatRegAddr(p, "from", &p.From)
wantFloatRegAddr(p, "to", &p.To)
}
func validateII(p *obj.Prog) {
wantImmI(p, "from", p.From, 12)
wantIntReg(p, "reg", p.Reg)
wantIntRegAddr(p, "to", &p.To)
}
func validateIF(p *obj.Prog) {
wantImmI(p, "from", p.From, 12)
wantIntReg(p, "reg", p.Reg)
wantFloatRegAddr(p, "to", &p.To)
}
func validateSI(p *obj.Prog) {
wantImmI(p, "from", p.From, 12)
wantIntReg(p, "reg", p.Reg)
wantIntRegAddr(p, "to", &p.To)
}
func validateSF(p *obj.Prog) {
wantImmI(p, "from", p.From, 12)
wantFloatReg(p, "reg", p.Reg)
wantIntRegAddr(p, "to", &p.To)
}
func validateB(p *obj.Prog) {
// Offsets are multiples of two, so accept 13 bit immediates for the
// 12 bit slot. We implicitly drop the least significant bit in encodeB.
wantEvenJumpOffset(p)
wantImmI(p, "to", p.To, 13)
wantIntReg(p, "reg", p.Reg)
wantIntRegAddr(p, "from", &p.From)
}
func validateU(p *obj.Prog) {
wantImmU(p, "from", p.From, 20)
wantIntRegAddr(p, "to", &p.To)
}
func validateJ(p *obj.Prog) {
// Offsets are multiples of two, so accept 21 bit immediates for the
// 20 bit slot. We implicitly drop the least significant bit in encodeJ.
wantEvenJumpOffset(p)
wantImmI(p, "to", p.To, 21)
wantIntRegAddr(p, "from", &p.From)
}
func validateRaw(p *obj.Prog) {
// Treat the raw value specially as a 32-bit unsigned integer.
// Nobody wants to enter negative machine code.
a := p.From
if a.Type != obj.TYPE_CONST {
p.Ctxt.Diag("%v\texpected immediate in raw position but got %s", p, obj.Dconv(p, &a))
return
}
if a.Offset < 0 || 1<<32 <= a.Offset {
p.Ctxt.Diag("%v\timmediate in raw position cannot be larger than 32 bits but got %d", p, a.Offset)
}
}
// encodeR encodes an R-type RISC-V instruction.
func encodeR(p *obj.Prog, rs1 uint32, rs2 uint32, rd uint32) uint32 {
ins := encode(p.As)
if ins == nil {
panic("encodeR: could not encode instruction")
}
if ins.rs2 != 0 && rs2 != 0 {
panic("encodeR: instruction uses rs2, but rs2 was nonzero")
}
// Use Scond for the floating-point rounding mode override.
// TODO(sorear): Is there a more appropriate way to handle opcode extension bits like this?
return ins.funct7<<25 | ins.rs2<<20 | rs2<<20 | rs1<<15 | ins.funct3<<12 | uint32(p.Scond)<<12 | rd<<7 | ins.opcode
}
func encodeRIII(p *obj.Prog) uint32 {
return encodeR(p, regI(p.Reg), regIAddr(p.From), regIAddr(p.To))
}
func encodeRFFF(p *obj.Prog) uint32 {
return encodeR(p, regF(p.Reg), regFAddr(p.From), regFAddr(p.To))
}
func encodeRFFI(p *obj.Prog) uint32 {
return encodeR(p, regF(p.Reg), regFAddr(p.From), regIAddr(p.To))
}
func encodeRFI(p *obj.Prog) uint32 {
return encodeR(p, regFAddr(p.From), 0, regIAddr(p.To))
}
func encodeRIF(p *obj.Prog) uint32 {
return encodeR(p, regIAddr(p.From), 0, regFAddr(p.To))
}
func encodeRFF(p *obj.Prog) uint32 {
return encodeR(p, regFAddr(p.From), 0, regFAddr(p.To))
}
// encodeI encodes an I-type RISC-V instruction.
func encodeI(p *obj.Prog, rd uint32) uint32 {
imm := immI(p.From, 12)
rs1 := regI(p.Reg)
ins := encode(p.As)
if ins == nil {
panic("encodeI: could not encode instruction")
}
imm |= uint32(ins.csr)
return imm<<20 | rs1<<15 | ins.funct3<<12 | rd<<7 | ins.opcode
}
func encodeII(p *obj.Prog) uint32 {
return encodeI(p, regIAddr(p.To))
}
func encodeIF(p *obj.Prog) uint32 {
return encodeI(p, regFAddr(p.To))
}
// encodeS encodes an S-type RISC-V instruction.
func encodeS(p *obj.Prog, rs2 uint32) uint32 {
imm := immI(p.From, 12)
rs1 := regIAddr(p.To)
ins := encode(p.As)
if ins == nil {
panic("encodeS: could not encode instruction")
}
return (imm>>5)<<25 | rs2<<20 | rs1<<15 | ins.funct3<<12 | (imm&0x1f)<<7 | ins.opcode
}
func encodeSI(p *obj.Prog) uint32 {
return encodeS(p, regI(p.Reg))
}
func encodeSF(p *obj.Prog) uint32 {
return encodeS(p, regF(p.Reg))
}
// encodeB encodes a B-type RISC-V instruction.
func encodeB(p *obj.Prog) uint32 {
imm := immI(p.To, 13)
rs2 := regI(p.Reg)
rs1 := regIAddr(p.From)
ins := encode(p.As)
if ins == nil {
panic("encodeB: could not encode instruction")
}
return (imm>>12)<<31 | ((imm>>5)&0x3f)<<25 | rs2<<20 | rs1<<15 | ins.funct3<<12 | ((imm>>1)&0xf)<<8 | ((imm>>11)&0x1)<<7 | ins.opcode
}
// encodeU encodes a U-type RISC-V instruction.
func encodeU(p *obj.Prog) uint32 {
// The immediates for encodeU are the upper 20 bits of a 32 bit value.
// Rather than have the user/compiler generate a 32 bit constant, the
// bottommost bits of which must all be zero, instead accept just the
// top bits.
imm := immU(p.From, 20)
rd := regIAddr(p.To)
ins := encode(p.As)
if ins == nil {
panic("encodeU: could not encode instruction")
}
return imm<<12 | rd<<7 | ins.opcode
}
// encodeJ encodes a J-type RISC-V instruction.
func encodeJ(p *obj.Prog) uint32 {
imm := immI(p.To, 21)
rd := regIAddr(p.From)
ins := encode(p.As)
if ins == nil {
panic("encodeJ: could not encode instruction")
}
return (imm>>20)<<31 | ((imm>>1)&0x3ff)<<21 | ((imm>>11)&0x1)<<20 | ((imm>>12)&0xff)<<12 | rd<<7 | ins.opcode
}
// encodeRaw encodes a raw instruction value.
func encodeRaw(p *obj.Prog) uint32 {
// Treat the raw value specially as a 32-bit unsigned integer.
// Nobody wants to enter negative machine code.
a := p.From
if a.Type != obj.TYPE_CONST {
panic(fmt.Sprintf("ill typed: %+v", a))
}
if a.Offset < 0 || 1<<32 <= a.Offset {
panic(fmt.Sprintf("immediate %d in %v cannot fit in 32 bits", a.Offset, a))
}
return uint32(a.Offset)
}
type encoding struct {
encode func(*obj.Prog) uint32 // encode returns the machine code for an *obj.Prog
validate func(*obj.Prog) // validate validates an *obj.Prog, calling ctxt.Diag for any issues
length int // length of encoded instruction; 0 for pseudo-ops, 4 otherwise
}
var (
// Encodings have the following naming convention:
//
// 1. the instruction encoding (R/I/S/B/U/J), in lowercase
// 2. zero or more register operand identifiers (I = integer
// register, F = float register), in uppercase
// 3. the word "Encoding"
//
// For example, rIIIEncoding indicates an R-type instruction with two
// integer register inputs and an integer register output; sFEncoding
// indicates an S-type instruction with rs2 being a float register.
rIIIEncoding = encoding{encode: encodeRIII, validate: validateRIII, length: 4}
rFFFEncoding = encoding{encode: encodeRFFF, validate: validateRFFF, length: 4}
rFFIEncoding = encoding{encode: encodeRFFI, validate: validateRFFI, length: 4}
rFIEncoding = encoding{encode: encodeRFI, validate: validateRFI, length: 4}
rIFEncoding = encoding{encode: encodeRIF, validate: validateRIF, length: 4}
rFFEncoding = encoding{encode: encodeRFF, validate: validateRFF, length: 4}
iIEncoding = encoding{encode: encodeII, validate: validateII, length: 4}
iFEncoding = encoding{encode: encodeIF, validate: validateIF, length: 4}
sIEncoding = encoding{encode: encodeSI, validate: validateSI, length: 4}
sFEncoding = encoding{encode: encodeSF, validate: validateSF, length: 4}
bEncoding = encoding{encode: encodeB, validate: validateB, length: 4}
uEncoding = encoding{encode: encodeU, validate: validateU, length: 4}
jEncoding = encoding{encode: encodeJ, validate: validateJ, length: 4}
// rawEncoding encodes a raw instruction byte sequence.
rawEncoding = encoding{encode: encodeRaw, validate: validateRaw, length: 4}
// pseudoOpEncoding panics if encoding is attempted, but does no validation.
pseudoOpEncoding = encoding{encode: nil, validate: func(*obj.Prog) {}, length: 0}
// badEncoding is used when an invalid op is encountered.
// An error has already been generated, so let anything else through.
badEncoding = encoding{encode: func(*obj.Prog) uint32 { return 0 }, validate: func(*obj.Prog) {}, length: 0}
)
// encodingForAs contains the encoding for a RISC-V instruction.
// Instructions are masked with obj.AMask to keep indices small.
var encodingForAs = [ALAST & obj.AMask]encoding{
// Unprivileged ISA
// 2.4: Integer Computational Instructions
AADDI & obj.AMask: iIEncoding,
ASLTI & obj.AMask: iIEncoding,
ASLTIU & obj.AMask: iIEncoding,
AANDI & obj.AMask: iIEncoding,
AORI & obj.AMask: iIEncoding,
AXORI & obj.AMask: iIEncoding,
ASLLI & obj.AMask: iIEncoding,
ASRLI & obj.AMask: iIEncoding,
ASRAI & obj.AMask: iIEncoding,
ALUI & obj.AMask: uEncoding,
AAUIPC & obj.AMask: uEncoding,
AADD & obj.AMask: rIIIEncoding,
ASLT & obj.AMask: rIIIEncoding,
ASLTU & obj.AMask: rIIIEncoding,
AAND & obj.AMask: rIIIEncoding,
AOR & obj.AMask: rIIIEncoding,
AXOR & obj.AMask: rIIIEncoding,
ASLL & obj.AMask: rIIIEncoding,
ASRL & obj.AMask: rIIIEncoding,
ASUB & obj.AMask: rIIIEncoding,
ASRA & obj.AMask: rIIIEncoding,
// 2.5: Control Transfer Instructions
AJAL & obj.AMask: jEncoding,
AJALR & obj.AMask: iIEncoding,
ABEQ & obj.AMask: bEncoding,
ABNE & obj.AMask: bEncoding,
ABLT & obj.AMask: bEncoding,
ABLTU & obj.AMask: bEncoding,
ABGE & obj.AMask: bEncoding,
ABGEU & obj.AMask: bEncoding,
// 2.6: Load and Store Instructions
ALW & obj.AMask: iIEncoding,
ALWU & obj.AMask: iIEncoding,
ALH & obj.AMask: iIEncoding,
ALHU & obj.AMask: iIEncoding,
ALB & obj.AMask: iIEncoding,
ALBU & obj.AMask: iIEncoding,
ASW & obj.AMask: sIEncoding,
ASH & obj.AMask: sIEncoding,
ASB & obj.AMask: sIEncoding,
// 5.2: Integer Computational Instructions (RV64I)
AADDIW & obj.AMask: iIEncoding,
ASLLIW & obj.AMask: iIEncoding,
ASRLIW & obj.AMask: iIEncoding,
ASRAIW & obj.AMask: iIEncoding,
AADDW & obj.AMask: rIIIEncoding,
ASLLW & obj.AMask: rIIIEncoding,
ASRLW & obj.AMask: rIIIEncoding,
ASUBW & obj.AMask: rIIIEncoding,
ASRAW & obj.AMask: rIIIEncoding,
// 5.3: Load and Store Instructions (RV64I)
ALD & obj.AMask: iIEncoding,
ASD & obj.AMask: sIEncoding,
// 7.1: Multiplication Operations
AMUL & obj.AMask: rIIIEncoding,
AMULH & obj.AMask: rIIIEncoding,
AMULHU & obj.AMask: rIIIEncoding,
AMULHSU & obj.AMask: rIIIEncoding,
AMULW & obj.AMask: rIIIEncoding,
ADIV & obj.AMask: rIIIEncoding,
ADIVU & obj.AMask: rIIIEncoding,
AREM & obj.AMask: rIIIEncoding,
AREMU & obj.AMask: rIIIEncoding,
ADIVW & obj.AMask: rIIIEncoding,
ADIVUW & obj.AMask: rIIIEncoding,
AREMW & obj.AMask: rIIIEncoding,
AREMUW & obj.AMask: rIIIEncoding,
// 10.1: Base Counters and Timers
ARDCYCLE & obj.AMask: iIEncoding,
ARDTIME & obj.AMask: iIEncoding,
ARDINSTRET & obj.AMask: iIEncoding,
// 11.5: Single-Precision Load and Store Instructions
AFLW & obj.AMask: iFEncoding,
AFSW & obj.AMask: sFEncoding,
// 11.6: Single-Precision Floating-Point Computational Instructions
AFADDS & obj.AMask: rFFFEncoding,
AFSUBS & obj.AMask: rFFFEncoding,
AFMULS & obj.AMask: rFFFEncoding,
AFDIVS & obj.AMask: rFFFEncoding,
AFMINS & obj.AMask: rFFFEncoding,
AFMAXS & obj.AMask: rFFFEncoding,
AFSQRTS & obj.AMask: rFFFEncoding,
// 11.7: Single-Precision Floating-Point Conversion and Move Instructions
AFCVTWS & obj.AMask: rFIEncoding,
AFCVTLS & obj.AMask: rFIEncoding,
AFCVTSW & obj.AMask: rIFEncoding,
AFCVTSL & obj.AMask: rIFEncoding,
AFCVTWUS & obj.AMask: rFIEncoding,
AFCVTLUS & obj.AMask: rFIEncoding,
AFCVTSWU & obj.AMask: rIFEncoding,
AFCVTSLU & obj.AMask: rIFEncoding,
AFSGNJS & obj.AMask: rFFFEncoding,
AFSGNJNS & obj.AMask: rFFFEncoding,
AFSGNJXS & obj.AMask: rFFFEncoding,
AFMVXS & obj.AMask: rFIEncoding,
AFMVSX & obj.AMask: rIFEncoding,
AFMVXW & obj.AMask: rFIEncoding,
AFMVWX & obj.AMask: rIFEncoding,
// 11.8: Single-Precision Floating-Point Compare Instructions
AFEQS & obj.AMask: rFFIEncoding,
AFLTS & obj.AMask: rFFIEncoding,
AFLES & obj.AMask: rFFIEncoding,
// 12.3: Double-Precision Load and Store Instructions
AFLD & obj.AMask: iFEncoding,
AFSD & obj.AMask: sFEncoding,
// 12.4: Double-Precision Floating-Point Computational Instructions
AFADDD & obj.AMask: rFFFEncoding,
AFSUBD & obj.AMask: rFFFEncoding,
AFMULD & obj.AMask: rFFFEncoding,
AFDIVD & obj.AMask: rFFFEncoding,
AFMIND & obj.AMask: rFFFEncoding,
AFMAXD & obj.AMask: rFFFEncoding,
AFSQRTD & obj.AMask: rFFFEncoding,
// 12.5: Double-Precision Floating-Point Conversion and Move Instructions
AFCVTWD & obj.AMask: rFIEncoding,
AFCVTLD & obj.AMask: rFIEncoding,
AFCVTDW & obj.AMask: rIFEncoding,
AFCVTDL & obj.AMask: rIFEncoding,
AFCVTWUD & obj.AMask: rFIEncoding,
AFCVTLUD & obj.AMask: rFIEncoding,
AFCVTDWU & obj.AMask: rIFEncoding,
AFCVTDLU & obj.AMask: rIFEncoding,
AFCVTSD & obj.AMask: rFFEncoding,
AFCVTDS & obj.AMask: rFFEncoding,
AFSGNJD & obj.AMask: rFFFEncoding,
AFSGNJND & obj.AMask: rFFFEncoding,
AFSGNJXD & obj.AMask: rFFFEncoding,
AFMVXD & obj.AMask: rFIEncoding,
AFMVDX & obj.AMask: rIFEncoding,
// 12.6: Double-Precision Floating-Point Compare Instructions
AFEQD & obj.AMask: rFFIEncoding,
AFLTD & obj.AMask: rFFIEncoding,
AFLED & obj.AMask: rFFIEncoding,
// Privileged ISA
// 3.2.1: Environment Call and Breakpoint
AECALL & obj.AMask: iIEncoding,
AEBREAK & obj.AMask: iIEncoding,
// Escape hatch
AWORD & obj.AMask: rawEncoding,
// Pseudo-operations
obj.AFUNCDATA: pseudoOpEncoding,
obj.APCDATA: pseudoOpEncoding,
obj.ATEXT: pseudoOpEncoding,
obj.ANOP: pseudoOpEncoding,
}
// encodingForProg returns the encoding (encode+validate funcs) for an *obj.Prog.
func encodingForProg(p *obj.Prog) encoding {
if base := p.As &^ obj.AMask; base != obj.ABaseRISCV && base != 0 {
p.Ctxt.Diag("encodingForProg: not a RISC-V instruction %s", p.As)
return badEncoding
}
as := p.As & obj.AMask
if int(as) >= len(encodingForAs) {
p.Ctxt.Diag("encodingForProg: bad RISC-V instruction %s", p.As)
return badEncoding
}
enc := encodingForAs[as]
if enc.validate == nil {
p.Ctxt.Diag("encodingForProg: no encoding for instruction %s", p.As)
return badEncoding
}
return enc
}
// assemble emits machine code.
// It is called at the very end of the assembly process.
func assemble(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
var symcode []uint32
for p := cursym.Func.Text; p != nil; p = p.Link {
enc := encodingForProg(p)
if enc.length > 0 {
symcode = append(symcode, enc.encode(p))
}
}
cursym.Size = int64(4 * len(symcode))
cursym.Grow(cursym.Size)
for p, i := cursym.P, 0; i < len(symcode); p, i = p[4:], i+1 {
ctxt.Arch.ByteOrder.PutUint32(p, symcode[i])
}
}
var LinkRISCV64 = obj.LinkArch{
Arch: sys.ArchRISCV64,
Init: buildop,
Preprocess: preprocess,
Assemble: assemble,
Progedit: progedit,
UnaryDst: unaryDst,
DWARFRegisters: RISCV64DWARFRegisters,
}