2019-09-08 01:56:26 +10:00
|
|
|
// 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"
|
2019-11-04 02:31:37 +11:00
|
|
|
"cmd/internal/objabi"
|
2019-09-08 01:56:26 +10:00
|
|
|
"cmd/internal/sys"
|
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
|
|
|
func buildop(ctxt *obj.Link) {}
|
|
|
|
|
2019-11-04 02:31:37 +11:00
|
|
|
// jalrToSym replaces p with a set of Progs needed to jump to the Sym in p.
|
|
|
|
// lr is the link register to use for the JALR.
|
|
|
|
// p must be a CALL, JMP or RET.
|
|
|
|
func jalrToSym(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc, lr int16) *obj.Prog {
|
|
|
|
if p.As != obj.ACALL && p.As != obj.AJMP && p.As != obj.ARET {
|
|
|
|
ctxt.Diag("unexpected Prog in jalrToSym: %v", p)
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(jsing): Consider using a single JAL instruction and teaching
|
|
|
|
// the linker to provide trampolines for the case where the destination
|
|
|
|
// offset is too large. This would potentially reduce instructions for
|
|
|
|
// the common case, but would require three instructions to go via the
|
|
|
|
// trampoline.
|
|
|
|
|
|
|
|
to := p.To
|
|
|
|
|
|
|
|
// This offset isn't really encoded with either instruction. It will be
|
|
|
|
// extracted for a relocation later.
|
|
|
|
p.As = AAUIPC
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: to.Offset, Sym: to.Sym}
|
|
|
|
p.Reg = 0
|
|
|
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
|
|
|
p.Mark |= NEED_PCREL_ITYPE_RELOC
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
|
2020-01-01 01:44:54 +11:00
|
|
|
// Leave Sym only for the CALL reloc in assemble.
|
2019-11-04 02:31:37 +11:00
|
|
|
p.As = AJALR
|
|
|
|
p.From.Type = obj.TYPE_REG
|
|
|
|
p.From.Reg = lr
|
2020-01-01 01:44:54 +11:00
|
|
|
p.From.Sym = to.Sym
|
2019-11-04 02:31:37 +11:00
|
|
|
p.Reg = 0
|
|
|
|
p.To.Type = obj.TYPE_REG
|
|
|
|
p.To.Reg = REG_TMP
|
|
|
|
lowerJALR(p)
|
|
|
|
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
2019-09-19 03:53:50 +10:00
|
|
|
// 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
|
2019-09-25 03:42:45 +10:00
|
|
|
p.From.Type, p.From.Reg = obj.TYPE_CONST, obj.REG_NONE
|
2019-09-19 03:53:50 +10:00
|
|
|
}
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
// progedit is called individually for each *obj.Prog. It normalizes instruction
|
|
|
|
// formats and eliminates as many pseudo-instructions as possible.
|
2019-09-08 01:56:26 +10:00
|
|
|
func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
|
2019-09-08 04:11:07 +10:00
|
|
|
|
|
|
|
// Expand binary instructions to ternary ones.
|
|
|
|
if p.Reg == 0 {
|
|
|
|
switch p.As {
|
|
|
|
case AADDI, ASLTI, ASLTIU, AANDI, AORI, AXORI, ASLLI, ASRLI, ASRAI,
|
2020-01-23 03:33:54 +11:00
|
|
|
AADD, AAND, AOR, AXOR, ASLL, ASRL, ASUB, ASRA,
|
|
|
|
AMUL, AMULH, AMULHU, AMULHSU, AMULW, ADIV, ADIVU, ADIVW, ADIVUW,
|
|
|
|
AREM, AREMU, AREMW, AREMUW:
|
2019-09-08 04:11:07 +10:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2019-09-08 04:11:07 +10:00
|
|
|
|
|
|
|
switch p.As {
|
2019-09-25 03:42:45 +10:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-11-04 02:31:37 +11:00
|
|
|
case obj.AJMP:
|
|
|
|
// Turn JMP into JAL ZERO or JALR ZERO.
|
|
|
|
// p.From is actually an _output_ for this instruction.
|
|
|
|
p.From.Type = obj.TYPE_REG
|
|
|
|
p.From.Reg = REG_ZERO
|
|
|
|
|
|
|
|
switch p.To.Type {
|
|
|
|
case obj.TYPE_BRANCH:
|
|
|
|
p.As = AJAL
|
|
|
|
case obj.TYPE_MEM:
|
|
|
|
switch p.To.Name {
|
|
|
|
case obj.NAME_NONE:
|
|
|
|
p.As = AJALR
|
|
|
|
lowerJALR(p)
|
|
|
|
case obj.NAME_EXTERN:
|
|
|
|
// Handled in preprocess.
|
|
|
|
default:
|
|
|
|
ctxt.Diag("progedit: unsupported name %d for %v", p.To.Name, p)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unhandled type %+v", p.To.Type))
|
|
|
|
}
|
|
|
|
|
|
|
|
case obj.ACALL:
|
|
|
|
switch p.To.Type {
|
|
|
|
case obj.TYPE_MEM:
|
|
|
|
// Handled in preprocess.
|
|
|
|
case obj.TYPE_REG:
|
|
|
|
p.As = AJALR
|
|
|
|
p.From.Type = obj.TYPE_REG
|
|
|
|
p.From.Reg = REG_LR
|
|
|
|
lowerJALR(p)
|
|
|
|
default:
|
|
|
|
ctxt.Diag("unknown destination type %+v in CALL: %v", p.To.Type, p)
|
|
|
|
}
|
|
|
|
|
2019-09-19 03:53:50 +10:00
|
|
|
case AJALR:
|
|
|
|
lowerJALR(p)
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
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
|
|
|
|
}
|
2019-09-19 01:01:07 +10:00
|
|
|
|
|
|
|
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
|
2019-11-04 04:08:26 +11:00
|
|
|
|
|
|
|
case ASEQZ:
|
|
|
|
// SEQZ rs, rd -> SLTIU $1, rs, rd
|
|
|
|
p.As = ASLTIU
|
|
|
|
p.Reg = p.From.Reg
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: 1}
|
|
|
|
|
|
|
|
case ASNEZ:
|
|
|
|
// SNEZ rs, rd -> SLTU rs, x0, rd
|
|
|
|
p.As = ASLTU
|
|
|
|
p.Reg = REG_ZERO
|
|
|
|
|
|
|
|
case AFNEGS:
|
|
|
|
// FNEGS rs, rd -> FSGNJNS rs, rs, rd
|
|
|
|
p.As = AFSGNJNS
|
|
|
|
p.Reg = p.From.Reg
|
|
|
|
|
|
|
|
case AFNEGD:
|
|
|
|
// FNEGD rs, rd -> FSGNJND rs, rs, rd
|
|
|
|
p.As = AFSGNJND
|
|
|
|
p.Reg = p.From.Reg
|
2019-09-08 04:11:07 +10:00
|
|
|
}
|
2019-09-08 01:56:26 +10:00
|
|
|
}
|
|
|
|
|
2019-10-04 04:02:38 +10:00
|
|
|
// 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
|
|
|
|
|
2019-11-04 01:05:46 +11:00
|
|
|
low, high, err := Split32BitImmediate(off)
|
2019-10-04 04:02:38 +10:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-04 04:40:47 +11:00
|
|
|
// InvertBranch inverts the condition of a conditional branch.
|
|
|
|
func InvertBranch(i obj.As) obj.As {
|
2019-11-04 02:31:37 +11:00
|
|
|
switch i {
|
|
|
|
case ABEQ:
|
|
|
|
return ABNE
|
|
|
|
case ABNE:
|
|
|
|
return ABEQ
|
|
|
|
case ABLT:
|
|
|
|
return ABGE
|
|
|
|
case ABGE:
|
|
|
|
return ABLT
|
|
|
|
case ABLTU:
|
|
|
|
return ABGEU
|
|
|
|
case ABGEU:
|
|
|
|
return ABLTU
|
|
|
|
default:
|
2019-11-04 04:40:47 +11:00
|
|
|
panic("InvertBranch: not a branch")
|
2019-11-04 02:31:37 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-04 04:32:32 +11:00
|
|
|
// containsCall reports whether the symbol contains a CALL (or equivalent)
|
|
|
|
// instruction. Must be called after progedit.
|
|
|
|
func containsCall(sym *obj.LSym) bool {
|
|
|
|
// CALLs are CALL or JAL(R) with link register LR.
|
|
|
|
for p := sym.Func.Text; p != nil; p = p.Link {
|
|
|
|
switch p.As {
|
|
|
|
case obj.ACALL:
|
|
|
|
return true
|
|
|
|
case AJAL, AJALR:
|
|
|
|
if p.To.Type == obj.TYPE_REG && p.To.Reg == REG_LR {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-09-08 01:56:26 +10:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-04 04:02:38 +10:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-04 04:32:32 +11:00
|
|
|
// preprocess generates prologue and epilogue code, computes PC-relative branch
|
|
|
|
// and jump offsets, and resolves pseudo-registers.
|
|
|
|
//
|
|
|
|
// preprocess is called once per linker symbol.
|
|
|
|
//
|
|
|
|
// When preprocess finishes, all instructions in the symbol are either
|
|
|
|
// concrete, real RISC-V instructions or directive pseudo-ops like TEXT,
|
|
|
|
// PCDATA, and FUNCDATA.
|
2019-09-08 01:56:26 +10:00
|
|
|
func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
|
|
|
|
if cursym.Func.Text == nil || cursym.Func.Text.Link == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-04 04:32:32 +11:00
|
|
|
// Generate the prologue.
|
2019-09-08 01:56:26 +10:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-04 04:32:32 +11:00
|
|
|
if !containsCall(cursym) {
|
|
|
|
text.From.Sym.Set(obj.AttrLeaf, true)
|
|
|
|
if stacksize == 0 {
|
|
|
|
// A leaf function with no locals has no frame.
|
|
|
|
text.From.Sym.Set(obj.AttrNoFrame, true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save LR unless there is no frame.
|
|
|
|
if !text.From.Sym.NoFrame() {
|
|
|
|
stacksize += ctxt.FixedFrameSize()
|
|
|
|
}
|
|
|
|
|
2019-09-08 01:56:26 +10:00
|
|
|
cursym.Func.Args = text.To.Val.(int32)
|
|
|
|
cursym.Func.Locals = int32(stacksize)
|
|
|
|
|
2019-11-04 04:32:32 +11:00
|
|
|
prologue := text
|
|
|
|
|
|
|
|
if !cursym.Func.Text.From.Sym.NoSplit() {
|
|
|
|
prologue = stacksplit(ctxt, prologue, cursym, newprog, stacksize) // emit split check
|
|
|
|
}
|
|
|
|
|
|
|
|
if stacksize != 0 {
|
|
|
|
prologue = ctxt.StartUnsafePoint(prologue, newprog)
|
|
|
|
|
|
|
|
// Actually save LR.
|
|
|
|
prologue = obj.Appendp(prologue, newprog)
|
|
|
|
prologue.As = AMOV
|
|
|
|
prologue.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_LR}
|
|
|
|
prologue.To = obj.Addr{Type: obj.TYPE_MEM, Reg: REG_SP, Offset: -stacksize}
|
|
|
|
|
|
|
|
// Insert stack adjustment.
|
|
|
|
prologue = obj.Appendp(prologue, newprog)
|
|
|
|
prologue.As = AADDI
|
|
|
|
prologue.From = obj.Addr{Type: obj.TYPE_CONST, Offset: -stacksize}
|
|
|
|
prologue.Reg = REG_SP
|
|
|
|
prologue.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_SP}
|
|
|
|
prologue.Spadj = int32(stacksize)
|
|
|
|
|
|
|
|
prologue = ctxt.EndUnsafePoint(prologue, newprog, -1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if cursym.Func.Text.From.Sym.Wrapper() {
|
|
|
|
// if(g->panic != nil && g->panic->argp == FP) g->panic->argp = bottom-of-frame
|
|
|
|
//
|
|
|
|
// MOV g_panic(g), X11
|
|
|
|
// BNE X11, ZERO, adjust
|
|
|
|
// end:
|
|
|
|
// NOP
|
|
|
|
// ...rest of function..
|
|
|
|
// adjust:
|
|
|
|
// MOV panic_argp(X11), X12
|
|
|
|
// ADD $(autosize+FIXED_FRAME), SP, X13
|
|
|
|
// BNE X12, X13, end
|
|
|
|
// ADD $FIXED_FRAME, SP, X12
|
|
|
|
// MOV X12, panic_argp(X11)
|
|
|
|
// JMP end
|
|
|
|
//
|
|
|
|
// The NOP is needed to give the jumps somewhere to land.
|
|
|
|
|
|
|
|
ldpanic := obj.Appendp(prologue, newprog)
|
|
|
|
|
|
|
|
ldpanic.As = AMOV
|
|
|
|
ldpanic.From = obj.Addr{Type: obj.TYPE_MEM, Reg: REGG, Offset: 4 * int64(ctxt.Arch.PtrSize)} // G.panic
|
|
|
|
ldpanic.Reg = 0
|
|
|
|
ldpanic.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X11}
|
|
|
|
|
|
|
|
bneadj := obj.Appendp(ldpanic, newprog)
|
|
|
|
bneadj.As = ABNE
|
|
|
|
bneadj.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X11}
|
|
|
|
bneadj.Reg = REG_ZERO
|
|
|
|
bneadj.To.Type = obj.TYPE_BRANCH
|
|
|
|
|
|
|
|
endadj := obj.Appendp(bneadj, newprog)
|
|
|
|
endadj.As = obj.ANOP
|
|
|
|
|
|
|
|
last := endadj
|
|
|
|
for last.Link != nil {
|
|
|
|
last = last.Link
|
|
|
|
}
|
|
|
|
|
|
|
|
getargp := obj.Appendp(last, newprog)
|
|
|
|
getargp.As = AMOV
|
|
|
|
getargp.From = obj.Addr{Type: obj.TYPE_MEM, Reg: REG_X11, Offset: 0} // Panic.argp
|
|
|
|
getargp.Reg = 0
|
|
|
|
getargp.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X12}
|
|
|
|
|
|
|
|
bneadj.Pcond = getargp
|
|
|
|
|
|
|
|
calcargp := obj.Appendp(getargp, newprog)
|
|
|
|
calcargp.As = AADDI
|
|
|
|
calcargp.From = obj.Addr{Type: obj.TYPE_CONST, Offset: stacksize + ctxt.FixedFrameSize()}
|
|
|
|
calcargp.Reg = REG_SP
|
|
|
|
calcargp.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X13}
|
|
|
|
|
|
|
|
testargp := obj.Appendp(calcargp, newprog)
|
|
|
|
testargp.As = ABNE
|
|
|
|
testargp.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X12}
|
|
|
|
testargp.Reg = REG_X13
|
|
|
|
testargp.To.Type = obj.TYPE_BRANCH
|
|
|
|
testargp.Pcond = endadj
|
|
|
|
|
|
|
|
adjargp := obj.Appendp(testargp, newprog)
|
|
|
|
adjargp.As = AADDI
|
|
|
|
adjargp.From = obj.Addr{Type: obj.TYPE_CONST, Offset: int64(ctxt.Arch.PtrSize)}
|
|
|
|
adjargp.Reg = REG_SP
|
|
|
|
adjargp.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X12}
|
|
|
|
|
|
|
|
setargp := obj.Appendp(adjargp, newprog)
|
|
|
|
setargp.As = AMOV
|
|
|
|
setargp.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X12}
|
|
|
|
setargp.Reg = 0
|
|
|
|
setargp.To = obj.Addr{Type: obj.TYPE_MEM, Reg: REG_X11, Offset: 0} // Panic.argp
|
|
|
|
|
|
|
|
godone := obj.Appendp(setargp, newprog)
|
|
|
|
godone.As = AJAL
|
|
|
|
godone.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_ZERO}
|
|
|
|
godone.To.Type = obj.TYPE_BRANCH
|
|
|
|
godone.Pcond = endadj
|
|
|
|
}
|
2019-09-08 01:56:26 +10:00
|
|
|
|
2019-10-04 04:02:38 +10:00
|
|
|
// Update stack-based offsets.
|
|
|
|
for p := cursym.Func.Text; p != nil; p = p.Link {
|
|
|
|
stackOffset(&p.From, stacksize)
|
|
|
|
stackOffset(&p.To, stacksize)
|
|
|
|
}
|
|
|
|
|
2019-11-04 04:32:32 +11:00
|
|
|
// Additional instruction rewriting.
|
2019-10-04 04:02:38 +10:00
|
|
|
for p := cursym.Func.Text; p != nil; p = p.Link {
|
2020-01-01 01:28:22 +11:00
|
|
|
switch p.As {
|
|
|
|
case obj.AGETCALLERPC:
|
2019-11-04 04:31:20 +11:00
|
|
|
if cursym.Leaf() {
|
|
|
|
// MOV LR, Rd
|
|
|
|
p.As = AMOV
|
|
|
|
p.From.Type = obj.TYPE_REG
|
|
|
|
p.From.Reg = REG_LR
|
|
|
|
} else {
|
|
|
|
// MOV (RSP), Rd
|
|
|
|
p.As = AMOV
|
|
|
|
p.From.Type = obj.TYPE_MEM
|
|
|
|
p.From.Reg = REG_SP
|
|
|
|
}
|
2019-11-04 02:31:37 +11:00
|
|
|
|
|
|
|
case obj.ACALL:
|
|
|
|
switch p.To.Type {
|
|
|
|
case obj.TYPE_MEM:
|
|
|
|
jalrToSym(ctxt, p, newprog, REG_LR)
|
|
|
|
}
|
|
|
|
|
|
|
|
case obj.AJMP:
|
|
|
|
switch p.To.Type {
|
|
|
|
case obj.TYPE_MEM:
|
|
|
|
switch p.To.Name {
|
|
|
|
case obj.NAME_EXTERN:
|
|
|
|
// JMP to symbol.
|
|
|
|
jalrToSym(ctxt, p, newprog, REG_ZERO)
|
|
|
|
}
|
|
|
|
}
|
2019-11-04 04:08:26 +11:00
|
|
|
|
2019-11-04 04:32:32 +11:00
|
|
|
case obj.ARET:
|
|
|
|
// Replace RET with epilogue.
|
|
|
|
retJMP := p.To.Sym
|
|
|
|
|
|
|
|
if stacksize != 0 {
|
|
|
|
// Restore LR.
|
|
|
|
p.As = AMOV
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_MEM, Reg: REG_SP, Offset: 0}
|
|
|
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_LR}
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
|
|
|
|
p.As = AADDI
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: stacksize}
|
|
|
|
p.Reg = REG_SP
|
|
|
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_SP}
|
|
|
|
p.Spadj = int32(-stacksize)
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
}
|
|
|
|
|
|
|
|
if retJMP != nil {
|
|
|
|
p.As = obj.ARET
|
|
|
|
p.To.Sym = retJMP
|
|
|
|
p = jalrToSym(ctxt, p, newprog, REG_ZERO)
|
|
|
|
} else {
|
|
|
|
p.As = AJALR
|
|
|
|
p.From.Type = obj.TYPE_CONST
|
|
|
|
p.From.Offset = 0
|
|
|
|
p.Reg = REG_LR
|
|
|
|
p.To.Type = obj.TYPE_REG
|
|
|
|
p.To.Reg = REG_ZERO
|
|
|
|
}
|
|
|
|
|
|
|
|
// "Add back" the stack removed in the previous instruction.
|
|
|
|
//
|
|
|
|
// This is to avoid confusing pctospadj, which sums
|
|
|
|
// Spadj from function entry to each PC, and shouldn't
|
|
|
|
// count adjustments from earlier epilogues, since they
|
|
|
|
// won't affect later PCs.
|
|
|
|
p.Spadj = int32(stacksize)
|
|
|
|
|
2019-11-04 04:08:26 +11:00
|
|
|
// Replace FNE[SD] with FEQ[SD] and NOT.
|
|
|
|
case AFNES:
|
|
|
|
if p.To.Type != obj.TYPE_REG {
|
|
|
|
ctxt.Diag("progedit: FNES needs an integer register output")
|
|
|
|
}
|
|
|
|
dst := p.To.Reg
|
|
|
|
p.As = AFEQS
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
|
|
|
|
p.As = AXORI // [bit] xor 1 = not [bit]
|
|
|
|
p.From.Type = obj.TYPE_CONST
|
|
|
|
p.From.Offset = 1
|
|
|
|
p.Reg = dst
|
|
|
|
p.To.Type = obj.TYPE_REG
|
|
|
|
p.To.Reg = dst
|
|
|
|
|
|
|
|
case AFNED:
|
|
|
|
if p.To.Type != obj.TYPE_REG {
|
|
|
|
ctxt.Diag("progedit: FNED needs an integer register output")
|
|
|
|
}
|
|
|
|
dst := p.To.Reg
|
|
|
|
p.As = AFEQD
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
|
|
|
|
p.As = AXORI // [bit] xor 1 = not [bit]
|
|
|
|
p.From.Type = obj.TYPE_CONST
|
|
|
|
p.From.Offset = 1
|
|
|
|
p.Reg = dst
|
|
|
|
p.To.Type = obj.TYPE_REG
|
|
|
|
p.To.Reg = dst
|
2019-10-04 04:02:38 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-01 01:28:22 +11:00
|
|
|
// 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.
|
|
|
|
for p := cursym.Func.Text; p != nil; p = p.Link {
|
|
|
|
switch p.As {
|
|
|
|
case AMOV, AMOVB, AMOVH, AMOVW, AMOVBU, AMOVHU, AMOVWU, AMOVF, AMOVD:
|
|
|
|
rewriteMOV(ctxt, newprog, p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-04 03:25:53 +11:00
|
|
|
// Split immediates larger than 12-bits.
|
|
|
|
for p := cursym.Func.Text; p != nil; p = p.Link {
|
|
|
|
switch p.As {
|
|
|
|
// <opi> $imm, REG, TO
|
|
|
|
case AADDI, AANDI, AORI, AXORI:
|
|
|
|
// LUI $high, TMP
|
|
|
|
// ADDI $low, TMP, TMP
|
|
|
|
// <op> TMP, REG, TO
|
|
|
|
q := *p
|
|
|
|
low, high, err := Split32BitImmediate(p.From.Offset)
|
|
|
|
if err != nil {
|
|
|
|
ctxt.Diag("%v: constant %d too large", p, p.From.Offset, err)
|
|
|
|
}
|
|
|
|
if high == 0 {
|
|
|
|
break // no need to split
|
|
|
|
}
|
|
|
|
|
|
|
|
p.As = ALUI
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: high}
|
|
|
|
p.Reg = 0
|
|
|
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
|
|
|
p.Spadj = 0 // needed if TO is SP
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
|
|
|
|
p.As = AADDIW
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: low}
|
|
|
|
p.Reg = REG_TMP
|
|
|
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
|
|
|
|
switch q.As {
|
|
|
|
case AADDI:
|
|
|
|
p.As = AADD
|
|
|
|
case AANDI:
|
|
|
|
p.As = AAND
|
|
|
|
case AORI:
|
|
|
|
p.As = AOR
|
|
|
|
case AXORI:
|
|
|
|
p.As = AXOR
|
|
|
|
default:
|
|
|
|
ctxt.Diag("progedit: unsupported inst %v for splitting", q)
|
|
|
|
}
|
|
|
|
p.Spadj = q.Spadj
|
|
|
|
p.To = q.To
|
|
|
|
p.Reg = q.Reg
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
|
|
|
|
|
|
|
// <load> $imm, REG, TO (load $imm+(REG), TO)
|
|
|
|
// <store> $imm, REG, TO (store $imm+(TO), REG)
|
2020-01-17 02:51:40 +11:00
|
|
|
case ALB, ALH, ALW, ALD, ALBU, ALHU, ALWU, AFLW, AFLD, ASB, ASH, ASW, ASD, AFSW, AFSD:
|
2019-11-04 03:25:53 +11:00
|
|
|
low, high, err := Split32BitImmediate(p.From.Offset)
|
|
|
|
if err != nil {
|
|
|
|
ctxt.Diag("%v: constant %d too large", p, p.From.Offset)
|
|
|
|
}
|
|
|
|
if high == 0 {
|
|
|
|
break // no need to split
|
|
|
|
}
|
|
|
|
|
2020-01-17 02:51:40 +11:00
|
|
|
q := *p
|
2019-11-04 03:25:53 +11:00
|
|
|
switch q.As {
|
2020-01-17 02:51:40 +11:00
|
|
|
case ALB, ALH, ALW, ALD, ALBU, ALHU, ALWU, AFLW, AFLD:
|
2019-11-04 03:25:53 +11:00
|
|
|
// LUI $high, TMP
|
|
|
|
// ADD TMP, REG, TMP
|
|
|
|
// <load> $low, TMP, TO
|
|
|
|
p.As = ALUI
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: high}
|
|
|
|
p.Reg = 0
|
|
|
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
|
|
|
p.Spadj = 0 // needed if TO is SP
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
|
|
|
|
p.As = AADD
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
|
|
|
p.Reg = q.Reg
|
|
|
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
|
|
|
|
p.As = q.As
|
|
|
|
p.To = q.To
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: low}
|
|
|
|
p.Reg = REG_TMP
|
|
|
|
|
2020-01-17 02:51:40 +11:00
|
|
|
case ASB, ASH, ASW, ASD, AFSW, AFSD:
|
2019-11-04 03:25:53 +11:00
|
|
|
// LUI $high, TMP
|
|
|
|
// ADD TMP, TO, TMP
|
|
|
|
// <store> $low, REG, TMP
|
|
|
|
p.As = ALUI
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: high}
|
|
|
|
p.Reg = 0
|
|
|
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
|
|
|
p.Spadj = 0 // needed if TO is SP
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
|
|
|
|
p.As = AADD
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
|
|
|
p.Reg = q.To.Reg
|
|
|
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
|
|
|
|
p.As = q.As
|
|
|
|
p.Reg = q.Reg
|
|
|
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: low}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-04 02:31:37 +11:00
|
|
|
// Compute instruction addresses. Once we do that, we need to check for
|
|
|
|
// overextended jumps and branches. Within each iteration, Pc differences
|
|
|
|
// are always lower bounds (since the program gets monotonically longer,
|
|
|
|
// a fixed point will be reached). No attempt to handle functions > 2GiB.
|
|
|
|
for {
|
|
|
|
rescan := false
|
|
|
|
setPCs(cursym.Func.Text, 0)
|
2019-09-08 01:56:26 +10:00
|
|
|
|
2019-11-04 02:31:37 +11:00
|
|
|
for p := cursym.Func.Text; p != nil; p = p.Link {
|
|
|
|
switch p.As {
|
|
|
|
case ABEQ, ABNE, ABLT, ABGE, ABLTU, ABGEU:
|
|
|
|
if p.To.Type != obj.TYPE_BRANCH {
|
|
|
|
panic("assemble: instruction with branch-like opcode lacks destination")
|
|
|
|
}
|
|
|
|
offset := p.Pcond.Pc - p.Pc
|
|
|
|
if offset < -4096 || 4096 <= offset {
|
|
|
|
// Branch is long. Replace it with a jump.
|
|
|
|
jmp := obj.Appendp(p, newprog)
|
|
|
|
jmp.As = AJAL
|
|
|
|
jmp.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_ZERO}
|
|
|
|
jmp.To = obj.Addr{Type: obj.TYPE_BRANCH}
|
|
|
|
jmp.Pcond = p.Pcond
|
|
|
|
|
2019-11-04 04:40:47 +11:00
|
|
|
p.As = InvertBranch(p.As)
|
2019-11-04 02:31:37 +11:00
|
|
|
p.Pcond = jmp.Link
|
|
|
|
|
|
|
|
// We may have made previous branches too long,
|
|
|
|
// so recheck them.
|
|
|
|
rescan = true
|
|
|
|
}
|
|
|
|
case AJAL:
|
|
|
|
if p.Pcond == nil {
|
|
|
|
panic("intersymbol jumps should be expressed as AUIPC+JALR")
|
|
|
|
}
|
|
|
|
offset := p.Pcond.Pc - p.Pc
|
|
|
|
if offset < -(1<<20) || (1<<20) <= offset {
|
|
|
|
// Replace with 2-instruction sequence. This assumes
|
|
|
|
// that TMP is not live across J instructions, since
|
|
|
|
// it is reserved by SSA.
|
|
|
|
jmp := obj.Appendp(p, newprog)
|
|
|
|
jmp.As = AJALR
|
|
|
|
jmp.From = obj.Addr{Type: obj.TYPE_CONST, Offset: 0}
|
|
|
|
jmp.To = p.From
|
|
|
|
jmp.Reg = REG_TMP
|
|
|
|
|
|
|
|
// p.From is not generally valid, however will be
|
|
|
|
// fixed up in the next loop.
|
|
|
|
p.As = AAUIPC
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_BRANCH, Sym: p.From.Sym}
|
|
|
|
p.Reg = 0
|
|
|
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
|
|
|
|
|
|
|
rescan = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !rescan {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now that there are no long branches, resolve branch and jump targets.
|
|
|
|
// At this point, instruction rewriting which changes the number of
|
|
|
|
// instructions will break everything--don't do it!
|
2019-09-19 03:53:50 +10:00
|
|
|
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")
|
|
|
|
}
|
2019-11-04 02:31:37 +11:00
|
|
|
|
|
|
|
case AAUIPC:
|
|
|
|
if p.From.Type == obj.TYPE_BRANCH {
|
|
|
|
low, high, err := Split32BitImmediate(p.Pcond.Pc - p.Pc)
|
|
|
|
if err != nil {
|
|
|
|
ctxt.Diag("%v: jump displacement %d too large", p, p.Pcond.Pc-p.Pc)
|
|
|
|
}
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: high, Sym: cursym}
|
|
|
|
p.Link.From.Offset = low
|
|
|
|
}
|
2019-09-19 03:53:50 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-08 01:56:26 +10:00
|
|
|
// Validate all instructions - this provides nice error messages.
|
|
|
|
for p := cursym.Func.Text; p != nil; p = p.Link {
|
|
|
|
encodingForProg(p).validate(p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-04 04:32:32 +11:00
|
|
|
func stacksplit(ctxt *obj.Link, p *obj.Prog, cursym *obj.LSym, newprog obj.ProgAlloc, framesize int64) *obj.Prog {
|
|
|
|
// Leaf function with no frame is effectively NOSPLIT.
|
|
|
|
if framesize == 0 {
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
// MOV g_stackguard(g), X10
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
p.As = AMOV
|
|
|
|
p.From.Type = obj.TYPE_MEM
|
|
|
|
p.From.Reg = REGG
|
|
|
|
p.From.Offset = 2 * int64(ctxt.Arch.PtrSize) // G.stackguard0
|
|
|
|
if cursym.CFunc() {
|
|
|
|
p.From.Offset = 3 * int64(ctxt.Arch.PtrSize) // G.stackguard1
|
|
|
|
}
|
|
|
|
p.To.Type = obj.TYPE_REG
|
|
|
|
p.To.Reg = REG_X10
|
|
|
|
|
|
|
|
var to_done, to_more *obj.Prog
|
|
|
|
|
|
|
|
if framesize <= objabi.StackSmall {
|
|
|
|
// small stack: SP < stackguard
|
|
|
|
// BLTU SP, stackguard, done
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
p.As = ABLTU
|
|
|
|
p.From.Type = obj.TYPE_REG
|
|
|
|
p.From.Reg = REG_X10
|
|
|
|
p.Reg = REG_SP
|
|
|
|
p.To.Type = obj.TYPE_BRANCH
|
|
|
|
to_done = p
|
|
|
|
} else if framesize <= objabi.StackBig {
|
|
|
|
// large stack: SP-framesize < stackguard-StackSmall
|
|
|
|
// ADD $-(framesize-StackSmall), SP, X11
|
|
|
|
// BLTU X11, stackguard, done
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
// TODO(sorear): logic inconsistent with comment, but both match all non-x86 arches
|
|
|
|
p.As = AADDI
|
|
|
|
p.From.Type = obj.TYPE_CONST
|
|
|
|
p.From.Offset = -(int64(framesize) - objabi.StackSmall)
|
|
|
|
p.Reg = REG_SP
|
|
|
|
p.To.Type = obj.TYPE_REG
|
|
|
|
p.To.Reg = REG_X11
|
|
|
|
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
p.As = ABLTU
|
|
|
|
p.From.Type = obj.TYPE_REG
|
|
|
|
p.From.Reg = REG_X10
|
|
|
|
p.Reg = REG_X11
|
|
|
|
p.To.Type = obj.TYPE_BRANCH
|
|
|
|
to_done = p
|
|
|
|
} else {
|
|
|
|
// Such a large stack we need to protect against wraparound.
|
|
|
|
// If SP is close to zero:
|
|
|
|
// SP-stackguard+StackGuard <= framesize + (StackGuard-StackSmall)
|
|
|
|
// The +StackGuard on both sides is required to keep the left side positive:
|
|
|
|
// SP is allowed to be slightly below stackguard. See stack.h.
|
|
|
|
//
|
|
|
|
// Preemption sets stackguard to StackPreempt, a very large value.
|
|
|
|
// That breaks the math above, so we have to check for that explicitly.
|
|
|
|
// // stackguard is X10
|
|
|
|
// MOV $StackPreempt, X11
|
|
|
|
// BEQ X10, X11, more
|
|
|
|
// ADD $StackGuard, SP, X11
|
|
|
|
// SUB X10, X11
|
|
|
|
// MOV $(framesize+(StackGuard-StackSmall)), X10
|
|
|
|
// BGTU X11, X10, done
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
p.As = AMOV
|
|
|
|
p.From.Type = obj.TYPE_CONST
|
|
|
|
p.From.Offset = objabi.StackPreempt
|
|
|
|
p.To.Type = obj.TYPE_REG
|
|
|
|
p.To.Reg = REG_X11
|
|
|
|
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
to_more = p
|
|
|
|
p.As = ABEQ
|
|
|
|
p.From.Type = obj.TYPE_REG
|
|
|
|
p.From.Reg = REG_X10
|
|
|
|
p.Reg = REG_X11
|
|
|
|
p.To.Type = obj.TYPE_BRANCH
|
|
|
|
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
p.As = AADDI
|
|
|
|
p.From.Type = obj.TYPE_CONST
|
|
|
|
p.From.Offset = int64(objabi.StackGuard)
|
|
|
|
p.Reg = REG_SP
|
|
|
|
p.To.Type = obj.TYPE_REG
|
|
|
|
p.To.Reg = REG_X11
|
|
|
|
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
p.As = ASUB
|
|
|
|
p.From.Type = obj.TYPE_REG
|
|
|
|
p.From.Reg = REG_X10
|
|
|
|
p.Reg = REG_X11
|
|
|
|
p.To.Type = obj.TYPE_REG
|
|
|
|
p.To.Reg = REG_X11
|
|
|
|
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
p.As = AMOV
|
|
|
|
p.From.Type = obj.TYPE_CONST
|
|
|
|
p.From.Offset = int64(framesize) + int64(objabi.StackGuard) - objabi.StackSmall
|
|
|
|
p.To.Type = obj.TYPE_REG
|
|
|
|
p.To.Reg = REG_X10
|
|
|
|
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
p.As = ABLTU
|
|
|
|
p.From.Type = obj.TYPE_REG
|
|
|
|
p.From.Reg = REG_X10
|
|
|
|
p.Reg = REG_X11
|
|
|
|
p.To.Type = obj.TYPE_BRANCH
|
|
|
|
to_done = p
|
|
|
|
}
|
|
|
|
|
|
|
|
p = ctxt.EmitEntryLiveness(cursym, p, newprog)
|
|
|
|
|
|
|
|
// CALL runtime.morestack(SB)
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
p.As = obj.ACALL
|
|
|
|
p.To.Type = obj.TYPE_BRANCH
|
|
|
|
if cursym.CFunc() {
|
|
|
|
p.To.Sym = ctxt.Lookup("runtime.morestackc")
|
|
|
|
} else if !cursym.Func.Text.From.Sym.NeedCtxt() {
|
|
|
|
p.To.Sym = ctxt.Lookup("runtime.morestack_noctxt")
|
|
|
|
} else {
|
|
|
|
p.To.Sym = ctxt.Lookup("runtime.morestack")
|
|
|
|
}
|
|
|
|
if to_more != nil {
|
|
|
|
to_more.Pcond = p
|
|
|
|
}
|
|
|
|
p = jalrToSym(ctxt, p, newprog, REG_X5)
|
|
|
|
|
|
|
|
// JMP start
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
p.As = AJAL
|
|
|
|
p.To = obj.Addr{Type: obj.TYPE_BRANCH}
|
|
|
|
p.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_ZERO}
|
|
|
|
p.Pcond = cursym.Func.Text.Link
|
|
|
|
|
|
|
|
// placeholder for to_done's jump target
|
|
|
|
p = obj.Appendp(p, newprog)
|
|
|
|
p.As = obj.ANOP // zero-width place holder
|
|
|
|
to_done.Pcond = p
|
|
|
|
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
2019-10-04 04:02:38 +10:00
|
|
|
// signExtend sign extends val starting at bit bit.
|
|
|
|
func signExtend(val int64, bit uint) int64 {
|
|
|
|
return val << (64 - bit) >> (64 - bit)
|
|
|
|
}
|
|
|
|
|
2019-11-04 01:05:46 +11:00
|
|
|
// Split32BitImmediate splits a signed 32-bit immediate into a signed 20-bit
|
2019-10-04 04:02:38 +10:00
|
|
|
// 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.
|
2019-11-04 01:05:46 +11:00
|
|
|
func Split32BitImmediate(imm int64) (low, high int64, err error) {
|
2019-10-04 04:02:38 +10:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-09-19 01:01:07 +10:00
|
|
|
// regF returns a float register.
|
|
|
|
func regF(r int16) uint32 {
|
|
|
|
return regVal(r, REG_F0, REG_F31)
|
|
|
|
}
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2019-09-19 01:01:07 +10:00
|
|
|
// regFAddr extracts the float register from an Addr.
|
|
|
|
func regFAddr(a obj.Addr) uint32 {
|
|
|
|
return regAddr(a, REG_F0, REG_F31)
|
|
|
|
}
|
|
|
|
|
2019-09-19 02:34:06 +10:00
|
|
|
// immIFits reports whether immediate value x fits in nbits bits
|
|
|
|
// as a signed integer.
|
|
|
|
func immIFits(x int64, nbits uint) bool {
|
2019-09-08 04:11:07 +10:00
|
|
|
nbits--
|
|
|
|
var min int64 = -1 << nbits
|
|
|
|
var max int64 = 1<<nbits - 1
|
|
|
|
return min <= x && x <= max
|
|
|
|
}
|
|
|
|
|
2019-09-19 02:34:06 +10:00
|
|
|
// immI extracts the signed integer literal of the specified size from an Addr.
|
2019-09-08 04:11:07 +10:00
|
|
|
func immI(a obj.Addr, nbits uint) uint32 {
|
|
|
|
if a.Type != obj.TYPE_CONST {
|
|
|
|
panic(fmt.Sprintf("ill typed: %+v", a))
|
|
|
|
}
|
2019-09-19 02:34:06 +10:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
func wantReg(p *obj.Prog, pos string, descr string, r, min, max int16) {
|
|
|
|
if r < min || r > max {
|
2019-11-04 04:40:47 +11:00
|
|
|
p.Ctxt.Diag("%v\texpected %s register in %s position but got non-%s register %s", p, descr, pos, descr, RegName(int(r)))
|
2019-09-08 04:11:07 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2019-09-19 01:01:07 +10:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-09-19 01:01:07 +10:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2019-09-19 03:53:50 +10:00
|
|
|
// 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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
func validateRIII(p *obj.Prog) {
|
|
|
|
wantIntRegAddr(p, "from", &p.From)
|
|
|
|
wantIntReg(p, "reg", p.Reg)
|
|
|
|
wantIntRegAddr(p, "to", &p.To)
|
|
|
|
}
|
|
|
|
|
2019-09-19 01:01:07 +10:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
func validateII(p *obj.Prog) {
|
2019-09-19 02:34:06 +10:00
|
|
|
wantImmI(p, "from", p.From, 12)
|
2019-09-08 04:11:07 +10:00
|
|
|
wantIntReg(p, "reg", p.Reg)
|
|
|
|
wantIntRegAddr(p, "to", &p.To)
|
|
|
|
}
|
|
|
|
|
2019-09-19 01:01:07 +10:00
|
|
|
func validateIF(p *obj.Prog) {
|
2019-09-19 02:34:06 +10:00
|
|
|
wantImmI(p, "from", p.From, 12)
|
2019-09-19 01:01:07 +10:00
|
|
|
wantIntReg(p, "reg", p.Reg)
|
|
|
|
wantFloatRegAddr(p, "to", &p.To)
|
|
|
|
}
|
|
|
|
|
2019-09-17 04:23:23 +10:00
|
|
|
func validateSI(p *obj.Prog) {
|
2019-09-19 02:34:06 +10:00
|
|
|
wantImmI(p, "from", p.From, 12)
|
2019-09-17 04:23:23 +10:00
|
|
|
wantIntReg(p, "reg", p.Reg)
|
|
|
|
wantIntRegAddr(p, "to", &p.To)
|
|
|
|
}
|
|
|
|
|
2019-09-19 01:01:07 +10:00
|
|
|
func validateSF(p *obj.Prog) {
|
2019-09-19 02:34:06 +10:00
|
|
|
wantImmI(p, "from", p.From, 12)
|
2019-09-19 01:01:07 +10:00
|
|
|
wantFloatReg(p, "reg", p.Reg)
|
|
|
|
wantIntRegAddr(p, "to", &p.To)
|
|
|
|
}
|
|
|
|
|
2019-09-19 03:53:50 +10:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-09-19 02:34:06 +10:00
|
|
|
func validateU(p *obj.Prog) {
|
2019-11-04 02:31:37 +11:00
|
|
|
if p.As == AAUIPC && p.Mark&(NEED_PCREL_ITYPE_RELOC|NEED_PCREL_STYPE_RELOC) != 0 {
|
|
|
|
// TODO(sorear): Hack. The Offset is being used here to temporarily
|
|
|
|
// store the relocation addend, not as an actual offset to assemble,
|
|
|
|
// so it's OK for it to be out of range. Is there a more valid way
|
|
|
|
// to represent this state?
|
|
|
|
return
|
|
|
|
}
|
2020-01-23 03:38:31 +11:00
|
|
|
wantImmI(p, "from", p.From, 20)
|
2019-09-19 02:34:06 +10:00
|
|
|
wantIntRegAddr(p, "to", &p.To)
|
|
|
|
}
|
|
|
|
|
2019-09-19 03:53:50 +10:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-09-08 01:56:26 +10:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
// 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))
|
|
|
|
}
|
|
|
|
|
2019-09-19 01:01:07 +10:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
// 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))
|
|
|
|
}
|
|
|
|
|
2019-09-19 01:01:07 +10:00
|
|
|
func encodeIF(p *obj.Prog) uint32 {
|
|
|
|
return encodeI(p, regFAddr(p.To))
|
|
|
|
}
|
|
|
|
|
2019-09-17 04:23:23 +10:00
|
|
|
// 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)
|
2019-09-19 02:34:06 +10:00
|
|
|
ins := encode(p.As)
|
|
|
|
if ins == nil {
|
2019-09-17 04:23:23 +10:00
|
|
|
panic("encodeS: could not encode instruction")
|
|
|
|
}
|
2019-09-19 02:34:06 +10:00
|
|
|
return (imm>>5)<<25 | rs2<<20 | rs1<<15 | ins.funct3<<12 | (imm&0x1f)<<7 | ins.opcode
|
2019-09-17 04:23:23 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
func encodeSI(p *obj.Prog) uint32 {
|
|
|
|
return encodeS(p, regI(p.Reg))
|
|
|
|
}
|
|
|
|
|
2019-09-19 01:01:07 +10:00
|
|
|
func encodeSF(p *obj.Prog) uint32 {
|
|
|
|
return encodeS(p, regF(p.Reg))
|
|
|
|
}
|
|
|
|
|
2019-09-19 03:53:50 +10:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-09-19 02:34:06 +10:00
|
|
|
// 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.
|
2020-01-23 03:38:31 +11:00
|
|
|
imm := immI(p.From, 20)
|
2019-09-19 02:34:06 +10:00
|
|
|
rd := regIAddr(p.To)
|
|
|
|
ins := encode(p.As)
|
|
|
|
if ins == nil {
|
|
|
|
panic("encodeU: could not encode instruction")
|
|
|
|
}
|
|
|
|
return imm<<12 | rd<<7 | ins.opcode
|
|
|
|
}
|
|
|
|
|
2019-09-19 03:53:50 +10:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
// encodeRaw encodes a raw instruction value.
|
2019-09-08 01:56:26 +10:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-11-04 01:05:46 +11:00
|
|
|
func EncodeIImmediate(imm int64) (int64, error) {
|
|
|
|
if !immIFits(imm, 12) {
|
|
|
|
return 0, fmt.Errorf("immediate %#x does not fit in 12 bits", imm)
|
|
|
|
}
|
|
|
|
return imm << 20, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func EncodeSImmediate(imm int64) (int64, error) {
|
|
|
|
if !immIFits(imm, 12) {
|
|
|
|
return 0, fmt.Errorf("immediate %#x does not fit in 12 bits", imm)
|
|
|
|
}
|
|
|
|
return ((imm >> 5) << 25) | ((imm & 0x1f) << 7), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func EncodeUImmediate(imm int64) (int64, error) {
|
2020-01-23 03:38:31 +11:00
|
|
|
if !immIFits(imm, 20) {
|
2019-11-04 01:05:46 +11:00
|
|
|
return 0, fmt.Errorf("immediate %#x does not fit in 20 bits", imm)
|
|
|
|
}
|
|
|
|
return imm << 12, nil
|
|
|
|
}
|
|
|
|
|
2019-09-08 01:56:26 +10:00
|
|
|
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 (
|
2019-09-08 04:11:07 +10:00
|
|
|
// Encodings have the following naming convention:
|
|
|
|
//
|
2019-09-19 03:53:50 +10:00
|
|
|
// 1. the instruction encoding (R/I/S/B/U/J), in lowercase
|
2019-09-08 04:11:07 +10:00
|
|
|
// 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}
|
2019-09-19 01:01:07 +10:00
|
|
|
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}
|
2019-09-08 04:11:07 +10:00
|
|
|
|
|
|
|
iIEncoding = encoding{encode: encodeII, validate: validateII, length: 4}
|
2019-09-19 01:01:07 +10:00
|
|
|
iFEncoding = encoding{encode: encodeIF, validate: validateIF, length: 4}
|
2019-09-08 04:11:07 +10:00
|
|
|
|
2019-09-17 04:23:23 +10:00
|
|
|
sIEncoding = encoding{encode: encodeSI, validate: validateSI, length: 4}
|
2019-09-19 01:01:07 +10:00
|
|
|
sFEncoding = encoding{encode: encodeSF, validate: validateSF, length: 4}
|
2019-09-17 04:23:23 +10:00
|
|
|
|
2019-09-19 03:53:50 +10:00
|
|
|
bEncoding = encoding{encode: encodeB, validate: validateB, length: 4}
|
2019-09-19 02:34:06 +10:00
|
|
|
uEncoding = encoding{encode: encodeU, validate: validateU, length: 4}
|
2019-09-19 03:53:50 +10:00
|
|
|
jEncoding = encoding{encode: encodeJ, validate: validateJ, length: 4}
|
2019-09-19 02:34:06 +10:00
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
// rawEncoding encodes a raw instruction byte sequence.
|
2019-09-08 01:56:26 +10:00
|
|
|
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{
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
// 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,
|
2019-09-19 02:34:06 +10:00
|
|
|
ALUI & obj.AMask: uEncoding,
|
|
|
|
AAUIPC & obj.AMask: uEncoding,
|
2019-09-08 04:11:07 +10:00
|
|
|
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,
|
|
|
|
|
2019-09-19 03:53:50 +10:00
|
|
|
// 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,
|
|
|
|
|
2019-09-17 04:23:23 +10:00
|
|
|
// 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,
|
|
|
|
|
2019-09-19 00:59:26 +10:00
|
|
|
// 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,
|
|
|
|
|
2019-09-17 04:23:23 +10:00
|
|
|
// 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,
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
// 10.1: Base Counters and Timers
|
|
|
|
ARDCYCLE & obj.AMask: iIEncoding,
|
|
|
|
ARDTIME & obj.AMask: iIEncoding,
|
|
|
|
ARDINSTRET & obj.AMask: iIEncoding,
|
|
|
|
|
2019-09-19 01:01:07 +10:00
|
|
|
// 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,
|
|
|
|
|
2019-09-08 04:11:07 +10:00
|
|
|
// Privileged ISA
|
|
|
|
|
|
|
|
// 3.2.1: Environment Call and Breakpoint
|
|
|
|
AECALL & obj.AMask: iIEncoding,
|
|
|
|
AEBREAK & obj.AMask: iIEncoding,
|
|
|
|
|
2019-09-08 01:56:26 +10:00
|
|
|
// 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) {
|
2020-01-17 13:54:30 -05:00
|
|
|
if ctxt.Retpoline {
|
|
|
|
ctxt.Diag("-spectre=ret not supported on riscv")
|
|
|
|
ctxt.Retpoline = false // don't keep printing
|
|
|
|
}
|
|
|
|
|
2019-09-08 01:56:26 +10:00
|
|
|
var symcode []uint32
|
|
|
|
for p := cursym.Func.Text; p != nil; p = p.Link {
|
2019-11-04 02:31:37 +11:00
|
|
|
switch p.As {
|
|
|
|
case AJALR:
|
|
|
|
if p.To.Sym != nil {
|
|
|
|
// This is a CALL/JMP. We add a relocation only
|
|
|
|
// for linker stack checking. No actual
|
|
|
|
// relocation is needed.
|
|
|
|
rel := obj.Addrel(cursym)
|
|
|
|
rel.Off = int32(p.Pc)
|
|
|
|
rel.Siz = 4
|
|
|
|
rel.Sym = p.To.Sym
|
|
|
|
rel.Add = p.To.Offset
|
|
|
|
rel.Type = objabi.R_CALLRISCV
|
|
|
|
}
|
|
|
|
case AAUIPC:
|
|
|
|
var rt objabi.RelocType
|
|
|
|
if p.Mark&NEED_PCREL_ITYPE_RELOC == NEED_PCREL_ITYPE_RELOC {
|
|
|
|
rt = objabi.R_RISCV_PCREL_ITYPE
|
|
|
|
} else if p.Mark&NEED_PCREL_STYPE_RELOC == NEED_PCREL_STYPE_RELOC {
|
|
|
|
rt = objabi.R_RISCV_PCREL_STYPE
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if p.Link == nil {
|
|
|
|
ctxt.Diag("AUIPC needing PC-relative reloc missing following instruction")
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if p.From.Sym == nil {
|
|
|
|
ctxt.Diag("AUIPC needing PC-relative reloc missing symbol")
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// The relocation offset can be larger than the maximum
|
|
|
|
// size of an AUIPC, so zero p.From.Offset to avoid any
|
|
|
|
// attempt to assemble it.
|
|
|
|
rel := obj.Addrel(cursym)
|
|
|
|
rel.Off = int32(p.Pc)
|
|
|
|
rel.Siz = 8
|
|
|
|
rel.Sym = p.From.Sym
|
|
|
|
rel.Add = p.From.Offset
|
|
|
|
p.From.Offset = 0
|
|
|
|
rel.Type = rt
|
|
|
|
}
|
|
|
|
|
2019-09-08 01:56:26 +10:00
|
|
|
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,
|
|
|
|
}
|