Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

riscv_test.gno

10.48 Kb · 311 lines
  1package riscv
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/nym-moultest123/x/vm/vmkit/v0"
  7	"gno.land/p/nt/uassert/v0"
  8)
  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}