clang_test.gno
3.74 Kb · 110 lines
1package riscv
2
3import (
4 "testing"
5
6 "gno.land/p/moul/x/vm/vmkit/v0"
7 "gno.land/p/nt/uassert/v0"
8)
9
10// A guest compiled by a real compiler, which is the only test here that was
11// not written by the same person who wrote the emulator.
12//
13// The image is guests.gno's, built from tools/riscv-guests/fnv by clang. It
14// exercises what hand-written tests do not: a real function prologue spilling
15// ra and s0 to a stack the host set up, .bss addressed far above a read-only
16// text segment, LLVM's constant materialization through lui/addi pairs, and
17// MUL in an inner loop.
18
19// The expected hashes come from an independent FNV-1a, not from this VM.
20func TestClangCompiledGuest(t *testing.T) {
21 cases := []struct {
22 in string
23 want string
24 }{
25 {"", "811c9dc5"},
26 {"a", "e40c292c"},
27 {"Hello", "f55c314b"},
28 {"gno.land", "fb7ffba0"},
29 }
30 for _, c := range cases {
31 m, err := NewMachine(GuestFNV(), entry)
32 uassert.NoError(t, err)
33 if m == nil {
34 continue
35 }
36 h := vmkit.NewTestHost().WithInput([]byte(c.in))
37 _, status := m.Step(h, vmkit.Unmetered)
38 uassert.Equal(t, "halted", status.String(), "input "+c.in+": "+m.Trap())
39 uassert.Equal(t, c.want, string(h.Out()), "fnv1a of "+c.in)
40 }
41}
42
43// Compiled code pauses and resumes like anything else. This is the property a
44// realm depends on, run against output a compiler produced rather than against
45// a loop written to be sliceable.
46func TestClangGuestSurvivesSlicing(t *testing.T) {
47 image := GuestFNV()
48 h := vmkit.NewTestHost().WithInput([]byte("gno.land"))
49
50 sliced, err := NewMachine(image, entry)
51 uassert.NoError(t, err)
52 status := vmkit.Running
53 slices := 0
54 for i := 0; i < 500 && status == vmkit.Running; i++ {
55 var fresh Machine
56 uassert.NoError(t, fresh.Restore(sliced.Snapshot()))
57 _, status = fresh.Step(h, 3)
58 sliced = &fresh
59 slices++
60 }
61 uassert.Equal(t, "halted", status.String())
62 uassert.True(t, slices > 10, "a sliced run must actually have been sliced")
63 uassert.Equal(t, "fb7ffba0", string(h.Out()))
64}
65
66// The guest writes its globals at 0x40000, which the linker script put well
67// above the text segment on purpose. If it had put them adjacent, W xor X
68// would trap on the first store, so this is the test that the memory layout
69// the toolchain was told to use is the one the host actually provides.
70func TestClangGuestWritesAboveItsText(t *testing.T) {
71 m, err := NewMachine(GuestFNV(), entry)
72 uassert.NoError(t, err)
73 h := vmkit.NewTestHost().WithInput([]byte("gno.land"))
74 _, status := m.Step(h, vmkit.Unmetered)
75 uassert.Equal(t, "halted", status.String())
76
77 // The text pages, the .bss page at 0x40000, and the stack page at the top.
78 uassert.True(t, m.Memory().DirtyPages() >= 3,
79 "the guest must have written outside its text segment")
80 uassert.True(t, m.Memory().DirtyPages() <= 8,
81 "a 196-byte program must not have dirtied the address space")
82}
83
84// Image is what every caller uses to turn words into a program, so a byte
85// order mistake here would look like a decoder bug everywhere else.
86func TestImageIsLittleEndian(t *testing.T) {
87 b := Image([]uint32{0x04030201, 0xFF000000})
88 uassert.Equal(t, 8, len(b))
89 uassert.Equal(t, uint64(0x01), uint64(b[0]))
90 uassert.Equal(t, uint64(0x04), uint64(b[3]))
91 uassert.Equal(t, uint64(0x00), uint64(b[4]))
92 uassert.Equal(t, uint64(0xFF), uint64(b[7]))
93
94 // And it agrees with the assembler the other tests use.
95 words := []uint32{addi(5, 0, 1), ecall()}
96 a, i := asm(words), Image(words)
97 uassert.Equal(t, len(a), len(i))
98 for k := range a {
99 uassert.Equal(t, uint64(a[k]), uint64(i[k]))
100 }
101}
102
103// GuestFNV must not hand out a slice a caller can scribble on, because the
104// next caller gets the same program.
105func TestGuestFNVIsACopy(t *testing.T) {
106 a := GuestFNV()
107 a[0] = 0xFF
108 b := GuestFNV()
109 uassert.False(t, b[0] == 0xFF, "GuestFNV must return a fresh image each call")
110}