Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RISC-V VM which can run the imperative fib benchmark #3709

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions experimental/go-riscv-sim/decoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package main

import "fmt"

type decoder struct {
code []byte
offset int
}

func (d *decoder) readUint16() uint16 {
offset := d.offset
code := d.code

lower := uint16(code[offset])
lower += uint16(code[offset+1]) << 8

d.offset += 2

return lower
}

const instHasUpperMask = 0x3

func (d *decoder) readInstruction() (instruction uint32, hasUpper bool) {
instruction = uint32(d.readUint16())

hasUpper = (instruction & instHasUpperMask) == instHasUpperMask
if hasUpper {
instruction |= uint32(d.readUint16()) << 16
}

return
}

func (d *decoder) decodeInstructions() []instruction {
var instructions []instruction

for d.offset < len(d.code) {
encodedInstruction, hasUpper := d.readInstruction()
decodedInstruction := d.decodeInstruction(encodedInstruction)
instructions = append(instructions, decodedInstruction)
if hasUpper {
instructions = append(instructions, instructionNop{})
}
}

return instructions
}

func (d *decoder) decodeInstruction(instruction uint32) instruction {

switch instruction & 0x3 {
case 0x1:
switch instruction & 0xE003 {
case 0x1:
if instruction&0xFFFF == 0x1 {
// "c.nop "
return instructionNop{}
}
if instruction&0xE003 == 0x1 {
// "c.addi $rd, $imm"
return decodeCAddi(instruction)
}
case 0x4001:
// "c.li $rd, $imm"
return decodeCLi(instruction)
}
case 0x2:
switch instruction & 0xE003 {
case 0x8002:
switch instruction & 0xF003 {
case 0x8002:
// "c.mv $rs1, $rs2"
return decodeCMv(instruction)
case 0x9002:
if instruction&0xFFFF == 0x9002 {
// "c.ebreak "
return instructionCEbreak{}
}
if instruction&0xF003 == 0x9002 {
// "c.add $rs1, $rs2"
return decodeCAdd(instruction)
}
}
}
case 0x3:
switch instruction & 0x7F {
case 0x63:
switch instruction & 0x707F {
case 0x1063:
// "bne $rs1, $rs2, $imm12"
return decodeBne(instruction)

case 0x5063:
// "bge $rs1, $rs2, $imm12"
return decodeBge(instruction)
}
}
}

panic(fmt.Sprintf("Unknown instruction: 0x%08X", instruction))
}

func decode_rs1_GPRNoX0_f11t7(instruction uint32) uint32 {
return (instruction & 0xF80) >> 7
}

func decode_rs2_GPRNoX0_f6t2(instruction uint32) uint32 {
return (instruction & 0x7C) >> 2
}

func decode_imm_simm6_f12t12f6t2(instruction uint32) uint32 {
return ((instruction & 0x1000) >> 7) | ((instruction & 0x7C) >> 2)
}

func decode_rd_GPRNoX0_f11t7(instruction uint32) uint32 {
return (instruction & 0xF80) >> 7
}
func decode_imm12_simm13_lsb0_f31t31f7t7f30t25f11t8(instruction uint32) uint32 {
return ((instruction & 0x80000000) >> 20) |
((instruction & 0x80) << 3) |
((instruction & 0x7E000000) >> 21) |
((instruction & 0xF00) >> 8)
}

func decode_rs1_GPR_f19t15(instruction uint32) uint32 {
return (instruction & 0xF8000) >> 15
}

func decode_rs2_GPR_f24t20(instruction uint32) uint32 {
return (instruction & 0x1F00000) >> 20
}

func decode_imm_simm6nonzero_f12t12f6t2(instruction uint32) uint32 {
return ((instruction & 0x1000) >> 7) |
((instruction & 0x7C) >> 2)
}

func decodeInt(bitPattern uint32, bitWidth uint8, shiftCount uint8) int32 {
var mask = (uint32(1) << bitWidth) - 1
var signMask = uint32(1) << (bitWidth - 1)
var maskedValue = bitPattern & mask
isNegative := (signMask & maskedValue) != 0
if isNegative {
maskedValue |= ^mask
}
shiftedValue := maskedValue << shiftCount
return int32(shiftedValue)
}

func decode_simm13_lsb0(value uint32) int32 {
return decodeInt(value, 12, 1)
}

func decode_simm6nonzero(value uint32) int32 {
return decodeInt(value, 6, 0)
}
5 changes: 5 additions & 0 deletions experimental/go-riscv-sim/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/onflow/cadence/experimental/go-riscv-sim

go 1.23.2

require golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
2 changes: 2 additions & 0 deletions experimental/go-riscv-sim/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
122 changes: 122 additions & 0 deletions experimental/go-riscv-sim/instruction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

type instruction interface {
isInstruction()
}

// Nop

type instructionNop struct{}

func (instructionNop) isInstruction() {}

// CLi

type instructionCLi struct {
rd uint32
imm uint32
}

func decodeCLi(instruction uint32) instructionCLi {
// "c.li $rd, $imm"
return instructionCLi{
rd: decode_rd_GPRNoX0_f11t7(instruction),
imm: decode_imm_simm6_f12t12f6t2(instruction),
}
}

func (instructionCLi) isInstruction() {}

// CMv

type instructionCMv struct {
rs1 uint32
rs2 uint32
}

func (instructionCMv) isInstruction() {}

func decodeCMv(instruction uint32) instructionCMv {
// "c.mv $rs1, $rs2"
return instructionCMv{
rs1: decode_rs1_GPRNoX0_f11t7(instruction),
rs2: decode_rs2_GPRNoX0_f6t2(instruction),
}
}

// Bge

type instructionBge struct {
rs1 uint32
rs2 uint32
imm12 int32
}

func (instructionBge) isInstruction() {}

func decodeBge(instruction uint32) instructionBge {
// "bge $rs1, $rs2, $imm12"
return instructionBge{
rs1: decode_rs1_GPR_f19t15(instruction),
rs2: decode_rs2_GPR_f24t20(instruction),
imm12: decode_simm13_lsb0(decode_imm12_simm13_lsb0_f31t31f7t7f30t25f11t8(instruction)),
}
}

// CAddi

type instructionCAddi struct {
rd uint32
imm int32
}

func (instructionCAddi) isInstruction() {}

func decodeCAddi(instruction uint32) instructionCAddi {
// "c.addi $rd, $imm"
return instructionCAddi{
rd: decode_rd_GPRNoX0_f11t7(instruction),
imm: decode_simm6nonzero(decode_imm_simm6nonzero_f12t12f6t2(instruction)),
}
}

// CAdd

type instructionCAdd struct {
rs1 uint32
rs2 uint32
}

func (instructionCAdd) isInstruction() {}

func decodeCAdd(instruction uint32) instructionCAdd {
// "c.add $rs1, $rs2"
return instructionCAdd{
rs1: decode_rs1_GPRNoX0_f11t7(instruction),
rs2: decode_rs2_GPRNoX0_f6t2(instruction),
}
}

// Bne

type instructionBne struct {
rs1 uint32
rs2 uint32
imm12 int32
}

func (instructionBne) isInstruction() {}

func decodeBne(instruction uint32) instructionBne {
return instructionBne{
rs1: decode_rs1_GPR_f19t15(instruction),
rs2: decode_rs2_GPR_f24t20(instruction),
imm12: decode_simm13_lsb0(decode_imm12_simm13_lsb0_f31t31f7t7f30t25f11t8(instruction)),
}
}

// CEbreak

type instructionCEbreak struct{}

func (instructionCEbreak) isInstruction() {}
38 changes: 38 additions & 0 deletions experimental/go-riscv-sim/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

func main() {
const input = 46

const ioRegister = 10

code := []byte{
// _Z14fib_imperativei
0x2a, 0x86, // 0: c.mv a2, a0
0x89, 0x47, // 2: c.li a5, 2
0x05, 0x45, // 4: c.li a0, 1
0x63, 0xda, 0xc7, 0x00, // 6: bge a5, a2, 20 # .L4
0x05, 0x47, // 10: c.li a4, 1
// .L3
0xaa, 0x86, // 12: c.mv a3, a0
0x85, 0x07, // 14: c.addi a5, 1
0x3a, 0x95, // 16: c.add a0, a4
0x36, 0x87, // 18: c.mv a4, a3
0xe3, 0x1c, 0xf6, 0xfe, // 20: bne a2, a5, -8 # .L3
0x02, 0x90, // c.ebreak
// .L4
0x02, 0x90, // c.ebreak
}

dec := decoder{code: code}
instructions := dec.decodeInstructions()

vm := vm{
instructions: instructions,
}

vm.registers[ioRegister] = input

vm.run(false)

println(vm.registers[ioRegister])
}
46 changes: 46 additions & 0 deletions experimental/go-riscv-sim/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package main

import "testing"

func BenchmarkFibImperative(b *testing.B) {

const input = 46

const ioRegister = 10

code := []byte{
// _Z14fib_imperativei
0x2a, 0x86, // 0: c.mv a2, a0
0x89, 0x47, // 2: c.li a5, 2
0x05, 0x45, // 4: c.li a0, 1
0x63, 0xda, 0xc7, 0x00, // 6: bge a5, a2, 20 # .L4
0x05, 0x47, // 10: c.li a4, 1
// .L3
0xaa, 0x86, // 12: c.mv a3, a0
0x85, 0x07, // 14: c.addi a5, 1
0x3a, 0x95, // 16: c.add a0, a4
0x36, 0x87, // 18: c.mv a4, a3
0xe3, 0x1c, 0xf6, 0xfe, // 20: bne a2, a5, -8 # .L3
0x02, 0x90, // c.ebreak
// .L4
0x02, 0x90, // c.ebreak
}

dec := decoder{code: code}
instructions := dec.decodeInstructions()

b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
vm := vm{instructions: instructions}

vm.registers[ioRegister] = input

vm.run(false)

if vm.registers[ioRegister] != 1836311903 {
b.Fatalf("unexpected result: %d", vm.registers[ioRegister])
}
}
}
Loading
Loading