1package riscv23import"testing"45// Does the GnoVM dispatch a switch by jump table or by scanning the cases in6// order? The answer decides the interpreter's shape: predecoding an image into7// (op, rd, rs1, rs2, imm) arrays deletes the fetch and the field extraction,
Functions
not supported for pure packages by the node (vm/qfuncs)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
8
// but it also flattens two small nested switches into one 48-case switch. If
9// cases are scanned, that trade buys nothing and costs the deeper arms.
10//
11// The experiment: the same 48-case switch, driven to the first case and to the
12// last. Equal cycles means a jump table. A large gap means a scan, and the
13// switch has to stay shallow no matter what else changes.
14
15//go:noinline
16func switch48(v uint32) uint32 {
17switch v {
18case0:
19return1
20case1:
21return2
22case2:
23return3
24case3:
25return4
26case4:
27return5
28case5:
29return6
30case6:
31return7
32case7:
33return8
34case8:
35return9
36case9:
37return10
38case10:
39return11
40case11:
41return12
42case12:
43return13
44case13:
45return14
46case14:
47return15
48case15:
49return16
50case16:
51return17
52case17:
53return18
54case18:
55return19
56case19:
57return20
58case20:
59return21
60case21:
61return22
62case22:
63return23
64case23:
65return24
66case24:
67return25
68case25:
69return26
70case26:
71return27
72case27:
73return28
74case28:
75return29
76case29:
77return30
78case30:
79return31
80case31:
81return32
82case32:
83return33
84case33:
85return34
86case34:
87return35
88case35:
89return36
90case36:
91return37
92case37:
93return38
94case38:
95return39
96case39:
97return40
98case40:
99return41
100case41:
101return42
102case42:
103return43
104case43:
105return44
106case44:
107return45
108case45:
109return46
110case46:
111return47
112case47:
113return48
114 }
115return0
116}
117
118const switchIters = 200000
119
120func TestSwitchCostFirstCase(t *testing.T) {
121var acc uint32
122for i := 0; i < switchIters; i++ {
123 acc += switch48(0)
124 }
125if acc == 0 {
126 t.Fatal("unreachable")
127 }
128}
129
130func TestSwitchCostLastCase(t *testing.T) {
131var acc uint32
132for i := 0; i < switchIters; i++ {
133 acc += switch48(47)
134 }
135if acc == 0 {
136 t.Fatal("unreachable")
137 }
138}
139
140// The control: the same loop with no switch at all, so the two above can be
141// read net of loop and call overhead.
142//
143//go:noinline
144func switchNone(v uint32) uint32 { return v + 1 }
145
146func TestSwitchCostNoSwitch(t *testing.T) {
147var acc uint32
148for i := 0; i < switchIters; i++ {
149 acc += switchNone(0)
150 }
151if acc == 0 {
152 t.Fatal("unreachable")
153 }
154}
155
156// A scan means the question is no longer "how few cases" but "is there a
157// dispatch that does not scan at all". These three isolate the alternatives:
158// the bare loop, a direct call, and a call through a table indexed by opcode.