PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/moul/x/vm/riscv/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / x / vm / riscv
Files
17 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/x/vm/riscv/v0
gno
0.9

Files (17)

  • README.mdmarkdown
  • gnomod.tomltoml
  • decode.gnogno
  • ecall.gnogno
  • exec.gnogno
  • guests.gnogno
  • mem.gnogno
  • predecode.gnogno
  • snapshot.gnogno
  • bench_test.gnogno
  • clang_test.gnogno
  • conformance_test.gnogno
  • decode_test.gnogno
  • predecode_test.gnogno
  • riscv_test.gnogno
  • rust_test.gnogno
  • switchcost_test.gnogno
  • riscv_test.gnogno
    1package riscv23import (4	"testing"56	"gno.land/p/moul/x/vm/vmkit/v0"7	"gno.land/p/nt/uassert/v0"8

    Functions

    not supported for pure packages by the node (vm/qfuncs)

    Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

    )
    9
    10// A tiny assembler, so the programs below read as instructions rather than as
    11// hex. Encoding here and decoding in decode.gno are written from the spec
    12// independently: if one has a field in the wrong place the tests fail, which is
    13// the point of not sharing a helper between them.
    14
    15func rType(f7, rs2, rs1, f3, rd, op uint32) uint32 {
    16 return f7<<25 | rs2<<20 | rs1<<15 | f3<<12 | rd<<7 | op
    17}
    18func iType(imm, rs1, f3, rd, op uint32) uint32 {
    19 return (imm&0xFFF)<<20 | rs1<<15 | f3<<12 | rd<<7 | op
    20}
    21func sType(imm, rs2, rs1, f3, op uint32) uint32 {
    22 return (imm>>5&0x7F)<<25 | rs2<<20 | rs1<<15 | f3<<12 | (imm&0x1F)<<7 | op
    23}
    24func bType(imm, rs2, rs1, f3, op uint32) uint32 {
    25 return (imm>>12&0x1)<<31 | (imm>>5&0x3F)<<25 | rs2<<20 | rs1<<15 |
    26 f3<<12 | (imm>>1&0xF)<<8 | (imm>>11&0x1)<<7 | op
    27}
    28func uType(imm, rd, op uint32) uint32 { return imm&0xFFFFF000 | rd<<7 | op }
    29func jType(imm, rd, op uint32) uint32 {
    30 return (imm>>20&0x1)<<31 | (imm>>1&0x3FF)<<21 | (imm>>11&0x1)<<20 |
    31 (imm>>12&0xFF)<<12 | rd<<7 | op
    32}
    33
    34func addi(rd, rs1, imm uint32) uint32 { return iType(imm, rs1, 0x0, rd, opImm) }
    35func add(rd, rs1, rs2 uint32) uint32 { return rType(0x00, rs2, rs1, 0x0, rd, opReg) }
    36func sub(rd, rs1, rs2 uint32) uint32 { return rType(0x20, rs2, rs1, 0x0, rd, opReg) }
    37func mul(rd, rs1, rs2 uint32) uint32 { return rType(0x01, rs2, rs1, 0x0, rd, opReg) }
    38func divi(rd, rs1, rs2 uint32) uint32 { return rType(0x01, rs2, rs1, 0x4, rd, opReg) }
    39func remi(rd, rs1, rs2 uint32) uint32 { return rType(0x01, rs2, rs1, 0x6, rd, opReg) }
    40func sw(rs2, rs1, imm uint32) uint32 { return sType(imm, rs2, rs1, 0x2, opStore) }
    41func lw(rd, rs1, imm uint32) uint32 { return iType(imm, rs1, 0x2, rd, opLoad) }
    42func lbu(rd, rs1, imm uint32) uint32 { return iType(imm, rs1, 0x4, rd, opLoad) }
    43func bne(rs1, rs2, imm uint32) uint32 { return bType(imm, rs2, rs1, 0x1, opBranch) }
    44func ecall() uint32 { return iType(0, 0, 0, 0, opSystem) }
    45
    46// exitWith is the two instructions every test program ends with: set a7 to the
    47// exit syscall and trap into the host.
    48func exitWith() []uint32 { return []uint32{addi(17, 0, sysExit), ecall()} }
    49
    50func asm(words []uint32) []byte {
    51 out := make([]byte, 0, len(words)*4)
    52 for _, w := range words {
    53 out = append(out, byte(w), byte(w>>8), byte(w>>16), byte(w>>24))
    54 }
    55 return out
    56}
    57
    58const entry = 0x1000
    59
    60func run(t *testing.T, words []uint32, h vmkit.Host) *Machine {
    61 t.Helper()
    62 m, err := NewMachine(asm(words), entry)
    63 uassert.NoError(t, err)
    64 if m == nil {
    65 return nil
    66 }
    67 _, status := m.Step(h, vmkit.Unmetered)
    68 uassert.Equal(t, "halted", status.String())
    69 return m
    70}
    71
    72func TestArithmetic(t *testing.T) {
    73 // x5 = 7 + 35; x6 = x5 - 2; x7 = x6 * 3
    74 prog := append([]uint32{
    75 addi(5, 0, 7),
    76 addi(5, 5, 35),
    77 addi(6, 0, 2),
    78 sub(6, 5, 6),
    79 addi(7, 0, 3),
    80 mul(7, 6, 7),
    81 }, exitWith()...)
    82 m := run(t, prog, vmkit.NewTestHost())
    83 r := m.Registers()
    84 uassert.Equal(t, uint64(42), uint64(r[5]))
    85 uassert.Equal(t, uint64(40), uint64(r[6]))
    86 uassert.Equal(t, uint64(120), uint64(r[7]))
    87}
    88
    89// Every immediate in the base set is sign extended from its top bit. Small
    90// positive programs work either way, which is what makes this the classic
    91// decoder bug: it only shows up on a negative constant.
    92func TestImmediatesAreSignExtended(t *testing.T) {
    93 prog := append([]uint32{
    94 addi(5, 0, 10),
    95 addi(5, 5, 0xFFF), // -1
    96 addi(6, 0, 0x800), // -2048, the most negative 12-bit immediate
    97 }, exitWith()...)
    98 m := run(t, prog, vmkit.NewTestHost())
    99 r := m.Registers()
    100 uassert.Equal(t, uint64(9), uint64(r[5]))
    101 uassert.Equal(t, int64(-2048), int64(int32(r[6])))
    102}
    103
    104// x0 reads as zero and discards every write, and the spec has no exception.
    105func TestRegisterZeroIsHardwired(t *testing.T) {
    106 prog := append([]uint32{
    107 addi(0, 0, 99),
    108 add(5, 0, 0),
    109 }, exitWith()...)
    110 m := run(t, prog, vmkit.NewTestHost())
    111 uassert.Equal(t, uint64(0), uint64(m.Registers()[0]))
    112 uassert.Equal(t, uint64(0), uint64(m.Registers()[5]))
    113}
    114
    115func TestLoadsAndStores(t *testing.T) {
    116 prog := append([]uint32{
    117 addi(5, 0, 0x7F0), // an address well clear of the program
    118 addi(6, 0, 0x123),
    119 sw(6, 5, 0),
    120 lw(7, 5, 0),
    121 lbu(8, 5, 0), // little-endian: the low byte is 0x23
    122 }, exitWith()...)
    123 m := run(t, prog, vmkit.NewTestHost())
    124 r := m.Registers()
    125 uassert.Equal(t, uint64(0x123), uint64(r[7]))
    126 uassert.Equal(t, uint64(0x23), uint64(r[8]))
    127}
    128
    129// A loop, which is the only way to know branches and the pc agree.
    130func TestBranchLoopSumsToTen(t *testing.T) {
    131 // x5 = 0; x6 = 5; do { x5 += x6; x6 -= 1 } while (x6 != 0) => 15
    132 prog := append([]uint32{
    133 addi(5, 0, 0),
    134 addi(6, 0, 5),
    135 add(5, 5, 6), // loop:
    136 addi(6, 6, 0xFFF), // x6 -= 1
    137 bne(6, 0, 0x1FF8), // -8, back to loop
    138 }, exitWith()...)
    139 m := run(t, prog, vmkit.NewTestHost())
    140 uassert.Equal(t, uint64(15), uint64(m.Registers()[5]))
    141}
    142
    143// The M extension's defined answers. These are the cases a chain cares about,
    144// because the spec makes them RETURN rather than trap and every implementation
    145// therefore has to agree.
    146func TestDivisionEdgeCases(t *testing.T) {
    147 const minInt32 = 0x80000000
    148 prog := append([]uint32{
    149 addi(5, 0, 7),
    150 addi(6, 0, 0),
    151 divi(7, 5, 6), // 7 / 0 = all ones
    152 remi(8, 5, 6), // 7 % 0 = 7
    153 uType(minInt32, 9, opLUI),
    154 addi(10, 0, 0xFFF), // -1
    155 divi(11, 9, 10), // overflow: stays minInt32
    156 remi(12, 9, 10), // overflow: 0
    157 }, exitWith()...)
    158 m := run(t, prog, vmkit.NewTestHost())
    159 r := m.Registers()
    160 uassert.Equal(t, uint64(0xFFFFFFFF), uint64(r[7]))
    161 uassert.Equal(t, uint64(7), uint64(r[8]))
    162 uassert.Equal(t, uint64(minInt32), uint64(r[11]))
    163 uassert.Equal(t, uint64(0), uint64(r[12]))
    164}
    165
    166// The syscall table is the whole interface between guest and chain.
    167func TestWriteSyscallReachesTheHost(t *testing.T) {
    168 // Store "hi" at 0x800, then write(1, 0x800, 2).
    169 prog := append([]uint32{
    170 addi(5, 0, 0x7F0),
    171 addi(6, 0, 'h'),
    172 sType(0, 6, 5, 0x0, opStore), // sb
    173 addi(6, 0, 'i'),
    174 sType(1, 6, 5, 0x0, opStore),
    175 addi(17, 0, sysWrite),
    176 addi(10, 0, 1),
    177 addi(11, 0, 0x7F0),
    178 addi(12, 0, 2),
    179 ecall(),
    180 }, exitWith()...)
    181 h := vmkit.NewTestHost()
    182 run(t, prog, h)
    183 uassert.Equal(t, "hi", h.OutString())
    184}
    185
    186// A guest that exits non-zero has HALTED, not trapped: "your code said no" is
    187// not "the VM broke", and a chain should not conflate them.
    188func TestNonZeroExitIsStillHalted(t *testing.T) {
    189 prog := []uint32{addi(10, 0, 1), addi(17, 0, sysExit), ecall()}
    190 m, err := NewMachine(asm(prog), entry)
    191 uassert.NoError(t, err)
    192 _, status := m.Step(vmkit.NewTestHost(), vmkit.Unmetered)
    193 uassert.Equal(t, "halted", status.String())
    194 uassert.Equal(t, "", m.Trap())
    195 uassert.Equal(t, uint64(1), uint64(m.Registers()[10]))
    196}
    197
    198func TestUnknownSyscallTraps(t *testing.T) {
    199 prog := []uint32{addi(17, 0, 777), ecall()}
    200 m, err := NewMachine(asm(prog), entry)
    201 uassert.NoError(t, err)
    202 _, status := m.Step(vmkit.NewTestHost(), vmkit.Unmetered)
    203 uassert.Equal(t, "trapped", status.String())
    204 uassert.Equal(t, "unknown syscall", m.Trap())
    205}
    206
    207func TestIllegalInstructionTraps(t *testing.T) {
    208 m, err := NewMachine(asm([]uint32{0xFFFFFFFF}), entry)
    209 uassert.NoError(t, err)
    210 _, status := m.Step(vmkit.NewTestHost(), vmkit.Unmetered)
    211 uassert.Equal(t, "trapped", status.String())
    212}
    213
    214func TestFuelStopsTheHart(t *testing.T) {
    215 prog := append([]uint32{
    216 addi(5, 0, 1), addi(5, 5, 1), addi(5, 5, 1), addi(5, 5, 1),
    217 }, exitWith()...)
    218 m, err := NewMachine(asm(prog), entry)
    219 uassert.NoError(t, err)
    220 used, status := m.Step(vmkit.NewTestHost(), 2)
    221 uassert.Equal(t, int64(2), used)
    222 uassert.Equal(t, "running", status.String())
    223 uassert.Equal(t, uint64(2), uint64(m.Registers()[5]))
    224}
    225
    226// The property that makes a guest program a contract: slices must equal one go.
    227func TestSlicedRunEqualsOneShot(t *testing.T) {
    228 prog := append([]uint32{
    229 addi(5, 0, 0),
    230 addi(6, 0, 12),
    231 add(5, 5, 6),
    232 addi(6, 6, 0xFFF),
    233 bne(6, 0, 0x1FF8),
    234 addi(7, 0, 0x7F0),
    235 sw(5, 7, 0),
    236 }, exitWith()...)
    237 image := asm(prog)
    238
    239 one, err := NewMachine(image, entry)
    240 uassert.NoError(t, err)
    241 one.Step(vmkit.NewTestHost(), vmkit.Unmetered)
    242
    243 sliced, err := NewMachine(image, entry)
    244 uassert.NoError(t, err)
    245 h := vmkit.NewTestHost()
    246 status := vmkit.Running
    247 for i := 0; i < 5000 && status == vmkit.Running; i++ {
    248 fresh, err := NewMachine(image, entry)
    249 uassert.NoError(t, err)
    250 uassert.NoError(t, fresh.Restore(sliced.Snapshot()))
    251 _, status = fresh.Step(h, 3)
    252 sliced = fresh
    253 }
    254 uassert.Equal(t, "halted", status.String())
    255 uassert.Equal(t, uint64(one.Registers()[5]), uint64(sliced.Registers()[5]))
    256 uassert.Equal(t, uint64(78), uint64(sliced.Registers()[5]))
    257}
    258
    259// A 1 MiB address space must not cost 1 MiB to pause. This is the kill
    260// criterion vmkit set for continuations, measured rather than asserted.
    261func TestSnapshotCarriesOnlyTouchedPages(t *testing.T) {
    262 prog := append([]uint32{
    263 addi(5, 0, 0x7F0),
    264 addi(6, 0, 1),
    265 sw(6, 5, 0),
    266 }, exitWith()...)
    267 m := run(t, prog, vmkit.NewTestHost())
    268
    269 // Two: the page holding the program at 0x1000, and the one holding the
    270 // store at 0x7F0. Two out of 256, which is the whole point.
    271 uassert.Equal(t, 2, m.Memory().DirtyPages())
    272 // The property is proportionality: a snapshot costs what the guest
    273 // touched plus a fixed header, not what the address space is. Two pages
    274 // out of 256 means about 8 KiB out of a megabyte.
    275 snap := m.Snapshot()
    276 pages := m.Memory().DirtyPages()
    277 uassert.True(t, len(snap) <= pages*PageSize+512,
    278 "a snapshot must not exceed its dirty pages plus a header")
    279 uassert.True(t, len(snap) < MemSize/100,
    280 "a snapshot of a lightly-used hart must be a rounding error against the address space")
    281}
    282
    283func TestRestoreRejectsJunk(t *testing.T) {
    284 m, err := NewMachine(asm(exitWith()), entry)
    285 uassert.NoError(t, err)
    286 uassert.Error(t, m.Restore([]byte{}))
    287 uassert.Error(t, m.Restore([]byte{1, 2, 3, 4, 5}))
    288
    289 good := m.Snapshot()
    290 bad := make([]byte, len(good))
    291 copy(bad, good)
    292 bad[4] = 99 // version
    293 uassert.ErrorIs(t, m.Restore(bad), vmkit.ErrBadSnapshot)
    294}
    295
    296// vmkit's plumbing has to work for a second guest, which is the only way to
    297// know the ABI was not shaped around the first one.
    298func TestInstanceIntegration(t *testing.T) {
    299 prog := append([]uint32{addi(5, 0, 21), add(5, 5, 5)}, exitWith()...)
    300 image := asm(prog)
    301
    302 inst := vmkit.NewInstance("001", address("g1x"), VMName, image, vmkit.Unmetered)
    303 h := vmkit.NewTestHost()
    304 for i := 0; i < 100 && inst.Status == vmkit.Running; i++ {
    305 m, err := NewMachine(image, entry)
    306 uassert.NoError(t, err)
    307 uassert.NoError(t, inst.Run(m, h, 2))
    308 }
    309 uassert.Equal(t, "halted", inst.Status.String())
    310 uassert.True(t, inst.Slices > 1)
    311}
    312