Skip to content

Commit

Permalink
cmd/internal/obj/riscv: implement control transfer instructions
Browse files Browse the repository at this point in the history
Add support for assembling control transfer instructions.

Based on the riscv-go port.

Updates #27532

Change-Id: I205d3ccd0a48deeaace0f20fca8516f382a83fae
Reviewed-on: https://go-review.googlesource.com/c/go/+/196841
Reviewed-by: Cherry Zhang <[email protected]>
Run-TryBot: Cherry Zhang <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
  • Loading branch information
4a6f656c committed Sep 26, 2019
1 parent 430d2aa commit a37f2b4
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 3 deletions.
18 changes: 17 additions & 1 deletion src/cmd/asm/internal/asm/testdata/riscvenc.s
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include "../../../../../runtime/textflag.h"

TEXT asmtest(SB),DUPOK|NOSPLIT,$0

start:
// Unprivileged ISA

// 2.4: Integer Computational Instructions
Expand Down Expand Up @@ -83,6 +83,22 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0
SRA $1, X5, X6 // 13d31240
SRA $1, X5 // 93d21240

// 2.5: Control Transfer Instructions

// These jumps and branches get printed as a jump or branch
// to 2 because they transfer control to the second instruction
// in the function (the first instruction being an invisible
// stack pointer adjustment).
JAL X5, start // JAL X5, 2 // eff2dff0
JALR X6, (X5) // 67830200
JALR X6, 4(X5) // 67834200
BEQ X5, X6, start // BEQ X5, X6, 2 // e38062f0
BNE X5, X6, start // BNE X5, X6, 2 // e39e62ee
BLT X5, X6, start // BLT X5, X6, 2 // e3cc62ee
BLTU X5, X6, start // BLTU X5, X6, 2 // e3ea62ee
BGE X5, X6, start // BGE X5, X6, 2 // e3d862ee
BGEU X5, X6, start // BGEU X5, X6, 2 // e3f662ee

// 2.6: Load and Store Instructions
LW $0, X5, X6 // 03a30200
LW $4, X5, X6 // 03a34200
Expand Down
96 changes: 94 additions & 2 deletions src/cmd/internal/obj/riscv/obj.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@ 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

// Reset Reg so the string looks correct.
p.From.Type = obj.TYPE_CONST
p.From.Reg = 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 Down Expand Up @@ -70,6 +88,9 @@ func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
}

switch p.As {
case AJALR:
lowerJALR(p)

case obj.AUNDEF, AECALL, AEBREAK, ASCALL, ASBREAK, ARDCYCLE, ARDTIME, ARDINSTRET:
switch p.As {
case obj.AUNDEF:
Expand Down Expand Up @@ -151,6 +172,19 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {

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)
Expand Down Expand Up @@ -290,6 +324,13 @@ 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)
Expand Down Expand Up @@ -347,11 +388,28 @@ func validateSF(p *obj.Prog) {
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.
Expand Down Expand Up @@ -443,6 +501,18 @@ 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.
Expand All @@ -458,6 +528,17 @@ func encodeU(p *obj.Prog) uint32 {
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.
Expand All @@ -481,7 +562,7 @@ type encoding struct {
var (
// Encodings have the following naming convention:
//
// 1. the instruction encoding (R/I/S/SB/U/UJ), in lowercase
// 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"
Expand All @@ -503,7 +584,9 @@ var (
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}
Expand All @@ -519,7 +602,6 @@ var (
// 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{
// TODO(jsing): Implement remaining instructions.

// Unprivileged ISA

Expand All @@ -546,6 +628,16 @@ var encodingForAs = [ALAST & obj.AMask]encoding{
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,
Expand Down

0 comments on commit a37f2b4

Please sign in to comment.