fuel.gno
1.83 Kb · 58 lines
1package vmkit
2
3// Unmetered is the fuel value that disables the budget: a [Meter] built with
4// it never reports exhaustion. It exists so a benchmark can measure the
5// dispatch loop without the meter in it, and so a trusted caller can run a
6// program to completion in one slice. Gas remains the real backstop.
7const Unmetered int64 = -1
8
9// Meter is a fuel budget being spent. It allocates nothing after
10// construction, because it sits in the hot path of every guest instruction.
11type Meter struct {
12 budget int64 // Unmetered, or the number of units this slice may spend
13 used int64
14}
15
16// NewMeter returns a meter good for `budget` units, or an unmetered one when
17// budget is [Unmetered]. A budget of zero is a meter with nothing to spend,
18// which is a legitimate way to ask "is this instance still runnable".
19func NewMeter(budget int64) *Meter {
20 if budget < 0 {
21 budget = Unmetered
22 }
23 return &Meter{budget: budget}
24}
25
26// Charge spends n units and reports whether they were available. On false
27// nothing is spent, so the caller can stop before executing the instruction
28// it could not pay for. That ordering is what makes a resumed machine
29// re-execute exactly the instruction it stopped at, and no other.
30func (m *Meter) Charge(n int64) bool {
31 if m.budget == Unmetered {
32 m.used += n
33 return true
34 }
35 if m.used+n > m.budget {
36 return false
37 }
38 m.used += n
39 return true
40}
41
42// Used reports how much fuel has been spent.
43func (m *Meter) Used() int64 { return m.used }
44
45// Remaining reports what is left, or [Unmetered].
46func (m *Meter) Remaining() int64 {
47 if m.budget == Unmetered {
48 return Unmetered
49 }
50 return m.budget - m.used
51}
52
53// Exhausted reports whether the next single unit would fail.
54func (m *Meter) Exhausted() bool { return !m.affordable(1) }
55
56func (m *Meter) affordable(n int64) bool {
57 return m.budget == Unmetered || m.used+n <= m.budget
58}