From 9159b1ae785b321d8e0e0bbf0328fb531c47b7b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 4 Dec 2024 14:22:14 -0800 Subject: [PATCH] add RISC-V VM which can run the imperative fib benchmark --- experimental/go-riscv-sim/decoder.go | 157 +++++++++++++++++++++++ experimental/go-riscv-sim/go.mod | 5 + experimental/go-riscv-sim/go.sum | 2 + experimental/go-riscv-sim/instruction.go | 122 ++++++++++++++++++ experimental/go-riscv-sim/main.go | 38 ++++++ experimental/go-riscv-sim/main_test.go | 46 +++++++ experimental/go-riscv-sim/registers.go | 36 ++++++ experimental/go-riscv-sim/vm.go | 121 +++++++++++++++++ 8 files changed, 527 insertions(+) create mode 100644 experimental/go-riscv-sim/decoder.go create mode 100644 experimental/go-riscv-sim/go.mod create mode 100644 experimental/go-riscv-sim/go.sum create mode 100644 experimental/go-riscv-sim/instruction.go create mode 100644 experimental/go-riscv-sim/main.go create mode 100644 experimental/go-riscv-sim/main_test.go create mode 100644 experimental/go-riscv-sim/registers.go create mode 100644 experimental/go-riscv-sim/vm.go diff --git a/experimental/go-riscv-sim/decoder.go b/experimental/go-riscv-sim/decoder.go new file mode 100644 index 000000000..7c7ad7675 --- /dev/null +++ b/experimental/go-riscv-sim/decoder.go @@ -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) +} diff --git a/experimental/go-riscv-sim/go.mod b/experimental/go-riscv-sim/go.mod new file mode 100644 index 000000000..e08e0336c --- /dev/null +++ b/experimental/go-riscv-sim/go.mod @@ -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 diff --git a/experimental/go-riscv-sim/go.sum b/experimental/go-riscv-sim/go.sum new file mode 100644 index 000000000..f90b856d8 --- /dev/null +++ b/experimental/go-riscv-sim/go.sum @@ -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= diff --git a/experimental/go-riscv-sim/instruction.go b/experimental/go-riscv-sim/instruction.go new file mode 100644 index 000000000..89bf0692d --- /dev/null +++ b/experimental/go-riscv-sim/instruction.go @@ -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() {} diff --git a/experimental/go-riscv-sim/main.go b/experimental/go-riscv-sim/main.go new file mode 100644 index 000000000..1b3f13e8e --- /dev/null +++ b/experimental/go-riscv-sim/main.go @@ -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]) +} diff --git a/experimental/go-riscv-sim/main_test.go b/experimental/go-riscv-sim/main_test.go new file mode 100644 index 000000000..ddce58841 --- /dev/null +++ b/experimental/go-riscv-sim/main_test.go @@ -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]) + } + } +} diff --git a/experimental/go-riscv-sim/registers.go b/experimental/go-riscv-sim/registers.go new file mode 100644 index 000000000..72178ae1e --- /dev/null +++ b/experimental/go-riscv-sim/registers.go @@ -0,0 +1,36 @@ +package main + +var GPRNoX0 = []string{ + "", + "ra", + "sp", + "gp", + "tp", + "t0", + "t1", + "t2", + "s0", + "s1", + "a0", + "a1", + "a2", + "a3", + "a4", + "a5", + "a6", + "a7", + "s2", + "s3", + "s4", + "s5", + "s6", + "s7", + "s8", + "s9", + "s10", + "s11", + "t3", + "t4", + "t5", + "t6", +} diff --git a/experimental/go-riscv-sim/vm.go b/experimental/go-riscv-sim/vm.go new file mode 100644 index 000000000..af69bf5e0 --- /dev/null +++ b/experimental/go-riscv-sim/vm.go @@ -0,0 +1,121 @@ +package main + +import ( + "fmt" + "unsafe" + + "golang.org/x/exp/constraints" +) + +type vm struct { + instructions []instruction + pc uint32 + registers [32]uint32 +} + +func reinterpret[T constraints.Integer, U constraints.Integer](value T) U { + return *(*U)(unsafe.Pointer(&value)) +} + +func (vm *vm) run(verbose bool) { + + regs := vm.registers + + if verbose { + fmt.Printf("# regs: %v\n\n", regs) + } + +loop: + for { + if verbose { + fmt.Printf("# pc:\t%d", vm.pc) + } + + ins := vm.instructions[vm.pc/2] + if verbose { + fmt.Printf(", ins: %#+v\n", ins) + } + + switch ins := ins.(type) { + case instructionNop: + // no-op + vm.pc += 2 + + case instructionCMv: + if verbose { + fmt.Printf("c.mv %s, %s\n", GPRNoX0[ins.rs1], GPRNoX0[ins.rs2]) + } + regs[ins.rs1] = regs[ins.rs2] + vm.pc += 2 + + case instructionCLi: + if verbose { + fmt.Printf("c.li %s, %d\n", GPRNoX0[ins.rd], ins.imm) + } + regs[ins.rd] = ins.imm + vm.pc += 2 + + case instructionBge: + if verbose { + fmt.Printf("bge %s, %s, %d\n", GPRNoX0[ins.rs1], GPRNoX0[ins.rs2], ins.imm12) + } + + a := reinterpret[uint32, int32](regs[ins.rs1]) + b := reinterpret[uint32, int32](regs[ins.rs2]) + if a >= b { + vm.pc = reinterpret[int32, uint32]( + reinterpret[uint32, int32](vm.pc) + ins.imm12, + ) + } else { + vm.pc += 4 + } + + case instructionBne: + if verbose { + fmt.Printf("bne %s, %s, %d\n", GPRNoX0[ins.rs1], GPRNoX0[ins.rs2], ins.imm12) + } + + a := reinterpret[uint32, int32](regs[ins.rs1]) + b := reinterpret[uint32, int32](regs[ins.rs2]) + if a != b { + vm.pc = reinterpret[int32, uint32]( + reinterpret[uint32, int32](vm.pc) + ins.imm12, + ) + } else { + vm.pc += 4 + } + + case instructionCAddi: + if verbose { + fmt.Printf("c.addi %s, %d\n", GPRNoX0[ins.rd], ins.imm) + } + a := reinterpret[uint32, int32](regs[ins.rd]) + regs[ins.rd] = reinterpret[int32, uint32](a + ins.imm) + vm.pc += 2 + + case instructionCAdd: + if verbose { + fmt.Printf("c.add %s, %s\n", GPRNoX0[ins.rs1], GPRNoX0[ins.rs2]) + } + a := reinterpret[uint32, int32](regs[ins.rs1]) + b := reinterpret[uint32, int32](regs[ins.rs2]) + regs[ins.rs1] = reinterpret[int32, uint32](a + b) + vm.pc += 2 + + case instructionCEbreak: + if verbose { + fmt.Printf("c.ebreak\n") + } + break loop + + default: + panic(fmt.Sprintf("unsupported instruction: %#+v", ins)) + } + + if verbose { + fmt.Printf("# regs: %v\n\n", regs) + } + } + + vm.registers = regs +}