Skip to content

Commit c3c5366

Browse files
committed
cmd/asm,cmd/internal/obj/riscv: implement integer computational instructions
Add support for assembling integer computational instructions. Based on the riscv-go port. Updates #27532 Change-Id: Ibf02649eebd65ce96002a9ca0624266d96def2cd Reviewed-on: https://go-review.googlesource.com/c/go/+/195079 Run-TryBot: Joel Sing <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Cherry Zhang <[email protected]>
1 parent 606fa2d commit c3c5366

File tree

3 files changed

+278
-3
lines changed

3 files changed

+278
-3
lines changed

src/cmd/asm/internal/asm/asm.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ func (p *Parser) asmJump(op obj.As, cond string, a []obj.Addr) {
417417
prog.Reg = reg
418418
break
419419
}
420-
if p.arch.Family == sys.MIPS || p.arch.Family == sys.MIPS64 {
420+
if p.arch.Family == sys.MIPS || p.arch.Family == sys.MIPS64 || p.arch.Family == sys.RISCV64 {
421421
// 3-operand jumps.
422422
// First two must be registers
423423
target = &a[2]
@@ -579,7 +579,7 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) {
579579
prog.To = a[1]
580580
case 3:
581581
switch p.arch.Family {
582-
case sys.MIPS, sys.MIPS64:
582+
case sys.MIPS, sys.MIPS64, sys.RISCV64:
583583
prog.From = a[0]
584584
prog.Reg = p.getRegister(prog, op, &a[1])
585585
prog.To = a[2]

src/cmd/asm/internal/asm/testdata/riscvenc.s

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,71 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0
99
// Arbitrary bytes (entered in little-endian mode)
1010
WORD $0x12345678 // WORD $305419896 // 78563412
1111
WORD $0x9abcdef0 // WORD $2596069104 // f0debc9a
12+
13+
// Unprivileged ISA
14+
15+
// 2.4: Integer Computational Instructions
16+
17+
ADDI $2047, X5, X6 // 1383f27f
18+
ADDI $-2048, X5, X6 // 13830280
19+
ADDI $2047, X5 // 9382f27f
20+
ADDI $-2048, X5 // 93820280
21+
22+
SLTI $55, X5, X7 // 93a37203
23+
SLTIU $55, X5, X7 // 93b37203
24+
25+
ANDI $1, X5, X6 // 13f31200
26+
ANDI $1, X5 // 93f21200
27+
ORI $1, X5, X6 // 13e31200
28+
ORI $1, X5 // 93e21200
29+
XORI $1, X5, X6 // 13c31200
30+
XORI $1, X5 // 93c21200
31+
32+
SLLI $1, X5, X6 // 13931200
33+
SLLI $1, X5 // 93921200
34+
SRLI $1, X5, X6 // 13d31200
35+
SRLI $1, X5 // 93d21200
36+
SRAI $1, X5, X6 // 13d31240
37+
SRAI $1, X5 // 93d21240
38+
39+
ADD X6, X5, X7 // b3836200
40+
ADD X5, X6 // 33035300
41+
ADD $2047, X5, X6 // 1383f27f
42+
ADD $-2048, X5, X6 // 13830280
43+
ADD $2047, X5 // 9382f27f
44+
ADD $-2048, X5 // 93820280
45+
46+
SLT X6, X5, X7 // b3a36200
47+
SLT $55, X5, X7 // 93a37203
48+
SLTU X6, X5, X7 // b3b36200
49+
SLTU $55, X5, X7 // 93b37203
50+
51+
AND X6, X5, X7 // b3f36200
52+
AND X5, X6 // 33735300
53+
AND $1, X5, X6 // 13f31200
54+
AND $1, X5 // 93f21200
55+
OR X6, X5, X7 // b3e36200
56+
OR X5, X6 // 33635300
57+
OR $1, X5, X6 // 13e31200
58+
OR $1, X5 // 93e21200
59+
XOR X6, X5, X7 // b3c36200
60+
XOR X5, X6 // 33435300
61+
XOR $1, X5, X6 // 13c31200
62+
XOR $1, X5 // 93c21200
63+
64+
SLL X6, X5, X7 // b3936200
65+
SLL X5, X6 // 33135300
66+
SLL $1, X5, X6 // 13931200
67+
SLL $1, X5 // 93921200
68+
SRL X6, X5, X7 // b3d36200
69+
SRL X5, X6 // 33535300
70+
SRL $1, X5, X6 // 13d31200
71+
SRL $1, X5 // 93d21200
72+
73+
SUB X6, X5, X7 // b3836240
74+
SUB X5, X6 // 33035340
75+
76+
SRA X6, X5, X7 // b3d36240
77+
SRA X5, X6 // 33535340
78+
SRA $1, X5, X6 // 13d31240
79+
SRA $1, X5 // 93d21240

src/cmd/internal/obj/riscv/obj.go

Lines changed: 208 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,43 @@ var RISCV64DWARFRegisters = map[int16]int16{}
3131

3232
func buildop(ctxt *obj.Link) {}
3333

34+
// progedit is called individually for each *obj.Prog. It normalizes instruction
35+
// formats and eliminates as many pseudo-instructions as possible.
3436
func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
35-
// TODO(jsing): Implement.
37+
38+
// Expand binary instructions to ternary ones.
39+
if p.Reg == 0 {
40+
switch p.As {
41+
case AADDI, ASLTI, ASLTIU, AANDI, AORI, AXORI, ASLLI, ASRLI, ASRAI,
42+
AADD, AAND, AOR, AXOR, ASLL, ASRL, ASUB, ASRA:
43+
p.Reg = p.To.Reg
44+
}
45+
}
46+
47+
// Rewrite instructions with constant operands to refer to the immediate
48+
// form of the instruction.
49+
if p.From.Type == obj.TYPE_CONST {
50+
switch p.As {
51+
case AADD:
52+
p.As = AADDI
53+
case ASLT:
54+
p.As = ASLTI
55+
case ASLTU:
56+
p.As = ASLTIU
57+
case AAND:
58+
p.As = AANDI
59+
case AOR:
60+
p.As = AORI
61+
case AXOR:
62+
p.As = AXORI
63+
case ASLL:
64+
p.As = ASLLI
65+
case ASRL:
66+
p.As = ASRLI
67+
case ASRA:
68+
p.As = ASRAI
69+
}
70+
}
3671
}
3772

3873
// setPCs sets the Pc field in all instructions reachable from p.
@@ -83,6 +118,103 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
83118
}
84119
}
85120

121+
func regVal(r, min, max int16) uint32 {
122+
if r < min || r > max {
123+
panic(fmt.Sprintf("register out of range, want %d < %d < %d", min, r, max))
124+
}
125+
return uint32(r - min)
126+
}
127+
128+
// regI returns an integer register.
129+
func regI(r int16) uint32 {
130+
return regVal(r, REG_X0, REG_X31)
131+
}
132+
133+
// regAddr extracts a register from an Addr.
134+
func regAddr(a obj.Addr, min, max int16) uint32 {
135+
if a.Type != obj.TYPE_REG {
136+
panic(fmt.Sprintf("ill typed: %+v", a))
137+
}
138+
return regVal(a.Reg, min, max)
139+
}
140+
141+
// regIAddr extracts the integer register from an Addr.
142+
func regIAddr(a obj.Addr) uint32 {
143+
return regAddr(a, REG_X0, REG_X31)
144+
}
145+
146+
// immFits reports whether immediate value x fits in nbits bits as a
147+
// signed integer.
148+
func immFits(x int64, nbits uint) bool {
149+
nbits--
150+
var min int64 = -1 << nbits
151+
var max int64 = 1<<nbits - 1
152+
return min <= x && x <= max
153+
}
154+
155+
// immI extracts the integer literal of the specified size from an Addr.
156+
func immI(a obj.Addr, nbits uint) uint32 {
157+
if a.Type != obj.TYPE_CONST {
158+
panic(fmt.Sprintf("ill typed: %+v", a))
159+
}
160+
if !immFits(a.Offset, nbits) {
161+
panic(fmt.Sprintf("immediate %d in %v cannot fit in %d bits", a.Offset, a, nbits))
162+
}
163+
return uint32(a.Offset)
164+
}
165+
166+
func wantImm(p *obj.Prog, pos string, a obj.Addr, nbits uint) {
167+
if a.Type != obj.TYPE_CONST {
168+
p.Ctxt.Diag("%v\texpected immediate in %s position but got %s", p, pos, obj.Dconv(p, &a))
169+
return
170+
}
171+
if !immFits(a.Offset, nbits) {
172+
p.Ctxt.Diag("%v\timmediate in %s position cannot be larger than %d bits but got %d", p, pos, nbits, a.Offset)
173+
}
174+
}
175+
176+
func wantReg(p *obj.Prog, pos string, descr string, r, min, max int16) {
177+
if r < min || r > max {
178+
p.Ctxt.Diag("%v\texpected %s register in %s position but got non-%s register %s", p, descr, pos, descr, regName(int(r)))
179+
}
180+
}
181+
182+
// wantIntReg checks that r is an integer register.
183+
func wantIntReg(p *obj.Prog, pos string, r int16) {
184+
wantReg(p, pos, "integer", r, REG_X0, REG_X31)
185+
}
186+
187+
func wantRegAddr(p *obj.Prog, pos string, a *obj.Addr, descr string, min int16, max int16) {
188+
if a == nil {
189+
p.Ctxt.Diag("%v\texpected register in %s position but got nothing", p, pos)
190+
return
191+
}
192+
if a.Type != obj.TYPE_REG {
193+
p.Ctxt.Diag("%v\texpected register in %s position but got %s", p, pos, obj.Dconv(p, a))
194+
return
195+
}
196+
if a.Reg < min || a.Reg > max {
197+
p.Ctxt.Diag("%v\texpected %s register in %s position but got non-%s register %s", p, descr, pos, descr, obj.Dconv(p, a))
198+
}
199+
}
200+
201+
// wantIntRegAddr checks that a contains an integer register.
202+
func wantIntRegAddr(p *obj.Prog, pos string, a *obj.Addr) {
203+
wantRegAddr(p, pos, a, "integer", REG_X0, REG_X31)
204+
}
205+
206+
func validateRIII(p *obj.Prog) {
207+
wantIntRegAddr(p, "from", &p.From)
208+
wantIntReg(p, "reg", p.Reg)
209+
wantIntRegAddr(p, "to", &p.To)
210+
}
211+
212+
func validateII(p *obj.Prog) {
213+
wantImm(p, "from", p.From, 12)
214+
wantIntReg(p, "reg", p.Reg)
215+
wantIntRegAddr(p, "to", &p.To)
216+
}
217+
86218
func validateRaw(p *obj.Prog) {
87219
// Treat the raw value specially as a 32-bit unsigned integer.
88220
// Nobody wants to enter negative machine code.
@@ -96,6 +228,42 @@ func validateRaw(p *obj.Prog) {
96228
}
97229
}
98230

231+
// encodeR encodes an R-type RISC-V instruction.
232+
func encodeR(p *obj.Prog, rs1 uint32, rs2 uint32, rd uint32) uint32 {
233+
ins := encode(p.As)
234+
if ins == nil {
235+
panic("encodeR: could not encode instruction")
236+
}
237+
if ins.rs2 != 0 && rs2 != 0 {
238+
panic("encodeR: instruction uses rs2, but rs2 was nonzero")
239+
}
240+
241+
// Use Scond for the floating-point rounding mode override.
242+
// TODO(sorear): Is there a more appropriate way to handle opcode extension bits like this?
243+
return ins.funct7<<25 | ins.rs2<<20 | rs2<<20 | rs1<<15 | ins.funct3<<12 | uint32(p.Scond)<<12 | rd<<7 | ins.opcode
244+
}
245+
246+
func encodeRIII(p *obj.Prog) uint32 {
247+
return encodeR(p, regI(p.Reg), regIAddr(p.From), regIAddr(p.To))
248+
}
249+
250+
// encodeI encodes an I-type RISC-V instruction.
251+
func encodeI(p *obj.Prog, rd uint32) uint32 {
252+
imm := immI(p.From, 12)
253+
rs1 := regI(p.Reg)
254+
ins := encode(p.As)
255+
if ins == nil {
256+
panic("encodeI: could not encode instruction")
257+
}
258+
imm |= uint32(ins.csr)
259+
return imm<<20 | rs1<<15 | ins.funct3<<12 | rd<<7 | ins.opcode
260+
}
261+
262+
func encodeII(p *obj.Prog) uint32 {
263+
return encodeI(p, regIAddr(p.To))
264+
}
265+
266+
// encodeRaw encodes a raw instruction value.
99267
func encodeRaw(p *obj.Prog) uint32 {
100268
// Treat the raw value specially as a 32-bit unsigned integer.
101269
// Nobody wants to enter negative machine code.
@@ -116,6 +284,22 @@ type encoding struct {
116284
}
117285

118286
var (
287+
// Encodings have the following naming convention:
288+
//
289+
// 1. the instruction encoding (R/I/S/SB/U/UJ), in lowercase
290+
// 2. zero or more register operand identifiers (I = integer
291+
// register, F = float register), in uppercase
292+
// 3. the word "Encoding"
293+
//
294+
// For example, rIIIEncoding indicates an R-type instruction with two
295+
// integer register inputs and an integer register output; sFEncoding
296+
// indicates an S-type instruction with rs2 being a float register.
297+
298+
rIIIEncoding = encoding{encode: encodeRIII, validate: validateRIII, length: 4}
299+
300+
iIEncoding = encoding{encode: encodeII, validate: validateII, length: 4}
301+
302+
// rawEncoding encodes a raw instruction byte sequence.
119303
rawEncoding = encoding{encode: encodeRaw, validate: validateRaw, length: 4}
120304

121305
// pseudoOpEncoding panics if encoding is attempted, but does no validation.
@@ -131,6 +315,29 @@ var (
131315
var encodingForAs = [ALAST & obj.AMask]encoding{
132316
// TODO(jsing): Implement remaining instructions.
133317

318+
// Unprivileged ISA
319+
320+
// 2.4: Integer Computational Instructions
321+
AADDI & obj.AMask: iIEncoding,
322+
ASLTI & obj.AMask: iIEncoding,
323+
ASLTIU & obj.AMask: iIEncoding,
324+
AANDI & obj.AMask: iIEncoding,
325+
AORI & obj.AMask: iIEncoding,
326+
AXORI & obj.AMask: iIEncoding,
327+
ASLLI & obj.AMask: iIEncoding,
328+
ASRLI & obj.AMask: iIEncoding,
329+
ASRAI & obj.AMask: iIEncoding,
330+
AADD & obj.AMask: rIIIEncoding,
331+
ASLT & obj.AMask: rIIIEncoding,
332+
ASLTU & obj.AMask: rIIIEncoding,
333+
AAND & obj.AMask: rIIIEncoding,
334+
AOR & obj.AMask: rIIIEncoding,
335+
AXOR & obj.AMask: rIIIEncoding,
336+
ASLL & obj.AMask: rIIIEncoding,
337+
ASRL & obj.AMask: rIIIEncoding,
338+
ASUB & obj.AMask: rIIIEncoding,
339+
ASRA & obj.AMask: rIIIEncoding,
340+
134341
// Escape hatch
135342
AWORD & obj.AMask: rawEncoding,
136343

0 commit comments

Comments
 (0)