Kind Pure package
Name v0
Namespace moul / x / vm / riscv
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 ecall.gno gno
⧉
1 package riscv 2 3 import "gno.land/p/moul/x/vm/vmkit/v0" 4 5 // The syscall table: the entire interface between a guest and the chain. 6 // 7 // Register-passing, in the RISC-V Linux convention a compiler already knows: 8 not supported for pure packages by the node (vm/qfuncs)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
// a7 (x17) selects the call, a0..a2 (x10..x12) carry arguments, a0 receives the
9 // result. A guest written against these needs no gno-specific runtime, only the
10 // three lines of inline assembly every no_std program already has for ecall.
11 //
12 // Deliberately small. Every call here maps onto something [vmkit.Host] already
13 // offers, because the Host is the audited surface and a syscall that reaches
14 // past it would be a second, unreviewed one.
15 const (
16 sysExit = 93 // a0 = status. The Linux number, so a guest can reuse its libc shim.
17 sysWrite = 64 // a0 = fd (ignored), a1 = buf, a2 = len -> Host.Output
18 sysRead = 63 // a0 = fd (ignored), a1 = buf, a2 = len <- Host.Input
19 sysHeight = 90 // -> block height
20 sysNow = 91 // -> block time, Unix seconds
21 sysCaller = 92 // a0 = buf, a1 = len; writes the caller address, returns bytes written
22 sysLog = 94 // a0 = buf, a1 = len -> Host.Log
23 sysEmit = 95 // a0 = type buf, a1 = type len, a2 = body buf, a3 = body len
24 )
25
26 // maxSyscallBytes bounds one call's buffer. A guest asking to write a gigabyte
27 // is a guest that found the host's allocator, not one with something to say.
28 const maxSyscallBytes = 64 * 1024
29
30 // ecall dispatches a syscall. It returns the machine status and whether the
31 // machine should stop: only exit stops it, everything else returns to the guest
32 // with a0 set.
33 func (m *Machine) ecall(h vmkit.Host) (vmkit.Status, bool ) {
34 switch m.reg[ 17 ] { // a7
35 case sysExit:
36 // The guest's exit status is not the machine's: a program that returns
37 // 1 has halted, it has not trapped. Trapping is for what the machine
38 // could not do, and a chain should not confuse "your code said no"
39 // with "the VM broke".
40 return vmkit.Halted, true
41
42 case sysWrite:
43 buf, ok := m.readGuest(m.reg[ 11 ], m.reg[ 12 ])
44 if !ok {
45 m.reg[ 10 ] = ^ uint32 ( 0 )
46 return vmkit.Running, false
47 }
48 h.Output(buf)
49 m.outLen += len(buf)
50 m.reg[ 10 ] = uint32 (len(buf))
51
52 case sysRead:
53 in := h.Input()
54 n := int (m.reg[ 12 ])
55 if n > len(in) {
56 n = len(in)
57 }
58 if n > maxSyscallBytes {
59 n = maxSyscallBytes
60 }
61 if !m.writeGuest(m.reg[ 11 ], in[:n]) {
62 m.reg[ 10 ] = ^ uint32 ( 0 )
63 return vmkit.Running, false
64 }
65 m.reg[ 10 ] = uint32 (n)
66
67 case sysHeight:
68 m.reg[ 10 ] = uint32 (h.Height())
69
70 case sysNow:
71 m.reg[ 10 ] = uint32 (h.Now())
72
73 case sysCaller:
74 addr := [] byte (h.Caller().String())
75 n := int (m.reg[ 11 ])
76 if n > len(addr) {
77 n = len(addr)
78 }
79 if !m.writeGuest(m.reg[ 10 ], addr[:n]) {
80 m.reg[ 10 ] = ^ uint32 ( 0 )
81 return vmkit.Running, false
82 }
83 m.reg[ 10 ] = uint32 (n)
84
85 case sysLog:
86 buf, ok := m.readGuest(m.reg[ 10 ], m.reg[ 11 ])
87 if !ok {
88 m.reg[ 10 ] = ^ uint32 ( 0 )
89 return vmkit.Running, false
90 }
91 h.Log( string (buf))
92 m.reg[ 10 ] = uint32 (len(buf))
93
94 case sysEmit:
95 typ, ok1 := m.readGuest(m.reg[ 10 ], m.reg[ 11 ])
96 body, ok2 := m.readGuest(m.reg[ 12 ], m.reg[ 13 ])
97 if !ok1 || !ok2 {
98 m.reg[ 10 ] = ^ uint32 ( 0 )
99 return vmkit.Running, false
100 }
101 h.Emit( string (typ), "body" , string (body))
102 m.reg[ 10 ] = 0
103
104 default :
105 // An unknown syscall is a trap, not a silent zero. A guest compiled
106 // against a newer table must fail loudly on an older host rather than
107 // read a success it did not get.
108 m.trap = "unknown syscall"
109 return vmkit.Trapped, true
110 }
111 return vmkit.Running, false
112 }
113
114 // readGuest copies a buffer out of guest memory, refusing anything out of range
115 // or larger than maxSyscallBytes.
116 func (m *Machine) readGuest(addr, length uint32 ) ([] byte , bool ) {
117 if length > maxSyscallBytes || !m.mem.inRange(addr, int (length)) {
118 return nil , false
119 }
120 out := make([] byte , length)
121 copy(out, m.mem.b[addr:addr+length])
122 return out, true
123 }
124
125 // writeGuest copies into guest memory, marking the pages it touched.
126 func (m *Machine) writeGuest(addr uint32 , b [] byte ) bool {
127 if len(b) > maxSyscallBytes || !m.mem.inRange(addr, len(b)) {
128 return false
129 }
130 copy(m.mem.b[addr:], b)
131 for i := 0 ; i < len(b); i++ {
132 m.mem.dirty[( uint64 (addr)+ uint64 (i))/PageSize] = true
133 }
134 return true
135 }
136