@@ -15,61 +15,35 @@ package io
1515import (
1616 "math"
1717 "math/big"
18+ "sync"
1819
1920 "github.com/consensys/go-corset/pkg/schema"
2021 "github.com/consensys/go-corset/pkg/util/collection/set"
2122)
2223
23- // FunctionInstance captures the mapping from inputs (i.e. parameters) to outputs (i.e.
24- // returns) for a particular instance of a given function.
25- type FunctionInstance struct {
26- ninputs uint
27- state []big.Int
28- }
29-
30- // Cmp comparator for the I/O registers of a particular function instance.
31- // Observe that, since functions are always deterministic, this only considers
32- // the inputs (as the outputs follow directly from this).
33- func (p FunctionInstance ) Cmp (other FunctionInstance ) int {
34- for i := range p .ninputs {
35- if c := p .state [i ].Cmp (& other .state [i ]); c != 0 {
36- return c
37- }
38- }
39- //
40- return 0
41- }
42-
43- // Outputs returns the output values for this function instance.
44- func (p FunctionInstance ) Outputs () []big.Int {
45- return p .state [p .ninputs :]
46- }
47-
48- // Get value of given input or output argument for this instance.
49- func (p FunctionInstance ) Get (arg uint ) big.Int {
50- return p .state [arg ]
51- }
52-
5324// Executor provides a mechanism for executing a program efficiently and
5425// generating a suitable top-level trace. Executor implements the io.Map
5526// interface.
5627type Executor [T Instruction [T ]] struct {
57- program Program [T ]
58- states []set.AnySortedSet [FunctionInstance ]
28+ functions []* FunctionTrace [T ]
5929}
6030
6131// NewExecutor constructs a new executor.
6232func NewExecutor [T Instruction [T ]](program Program [T ]) * Executor [T ] {
63- // Construct initially empty set of states
64- states := make ([]set.AnySortedSet [FunctionInstance ], len (program .Functions ()))
33+ // Initialise executor traces
34+ traces := make ([]* FunctionTrace [T ], len (program .Functions ()))
35+ //
36+ for i := range traces {
37+ traces [i ] = NewFunctionTrace (program .functions [i ])
38+ }
6539 // Construct new executor
66- return & Executor [T ]{program , states }
40+ return & Executor [T ]{traces }
6741}
6842
6943// Instance returns a valid instance of the given bus.
7044func (p * Executor [T ]) Instance (bus uint ) FunctionInstance {
7145 var (
72- fn = p .program . Function ( bus )
46+ fn = p .functions [ bus ]. fn
7347 inputs = make ([]big.Int , fn .NumInputs ())
7448 )
7549 // Intialise inputs values
@@ -82,27 +56,17 @@ func (p *Executor[T]) Instance(bus uint) FunctionInstance {
8256 inputs [i ] = * ith .Set (& reg .Padding )
8357 }
8458 // Compute function instance
85- return p .call ( bus , inputs )
59+ return p .functions [ bus ]. Call ( inputs , p )
8660}
8761
8862// Read implementation for the io.Map interface.
8963func (p * Executor [T ]) Read (bus uint , address []big.Int ) []big.Int {
90- var (
91- iostate = FunctionInstance {uint (len (address )), address }
92- states = p .states [bus ]
93- )
94- // Check whether this instance has already been computed.
95- if index := states .Find (iostate ); index != math .MaxUint {
96- // Yes, therefore return precomputed outputs
97- return states [index ].Outputs ()
98- }
99- // Execute function to determine new outputs.
100- return p .call (bus , address ).Outputs ()
64+ return p .functions [bus ].Call (address , p ).Outputs ()
10165}
10266
10367// Instances returns accrued function instances for the given bus.
10468func (p * Executor [T ]) Instances (bus uint ) []FunctionInstance {
105- return p .states [bus ]
69+ return p .functions [bus ]. instances
10670}
10771
10872// Write implementation for the io.Map interface.
@@ -111,15 +75,72 @@ func (p *Executor[T]) Write(bus uint, address []big.Int, values []big.Int) {
11175 panic ("unsupported operation" )
11276}
11377
114- func (p * Executor [T ]) call (bus uint , inputs []big.Int ) FunctionInstance {
78+ // ============================================================================
79+ // FunctionTrace
80+ // ============================================================================
81+
82+ // FunctionTrace captures all instances for a given function, and provides a
83+ // (thread-safe) API for calling to compute its output for a given set of
84+ // inputs.
85+ type FunctionTrace [T Instruction [T ]] struct {
86+ // Function whose instances are captured here
87+ fn * Function [T ]
88+ // Cached instances of the given function
89+ instances set.AnySortedSet [FunctionInstance ]
90+ // mutex required to ensure thread safety.
91+ mux sync.RWMutex
92+ }
93+
94+ // NewFunctionTrace constructs an empty trace for a given function.
95+ func NewFunctionTrace [T Instruction [T ]](fn * Function [T ]) * FunctionTrace [T ] {
96+ instances := set .NewAnySortedSet [FunctionInstance ]()
97+ //
98+ return & FunctionTrace [T ]{
99+ fn : fn ,
100+ instances : * instances ,
101+ }
102+ }
103+
104+ // Call this function to determine its outputs for a given set of inputs. If
105+ // this instance has been seen before, it will simply return that. Otherwise,
106+ // it will execute the function to determine the correct outputs.
107+ func (p * FunctionTrace [T ]) Call (inputs []big.Int , iomap Map ) FunctionInstance {
108+ var iostate = FunctionInstance {uint (len (inputs )), inputs }
109+ // Obtain read lock
110+ p .mux .RLock ()
111+ // Look for cached instance
112+ index := p .instances .Find (iostate )
113+ // Release read lock
114+ p .mux .RUnlock ()
115+ // Check for cache hit.
116+ if index != math .MaxUint {
117+ // Yes, therefore return precomputed outputs
118+ return p .instances [index ]
119+ }
120+ // Execute function to determine new outputs.
121+ return p .executeCall (inputs , iomap )
122+ }
123+
124+ // Execute this function for a given set of inputs to determine its outputs and
125+ // produce a given instance. The created instance is recorded within the trace
126+ // so it can be reused rather than recomputed in the future. This function is
127+ // thread-safe, and will acquire the write lock on the cached instances
128+ // momentarily to insert the new instance.
129+ //
130+ // NOTE: this does not attempt any form of thread blocking (e.g. when a desired
131+ // instance if being computed by another thread). Instead, it eagerly computes
132+ // instances --- even if that means, occasionally, an instance is computed more
133+ // than once. This is safe since instances are always deterministic (i.e. same
134+ // output for a given input).
135+ func (p * FunctionTrace [T ]) executeCall (inputs []big.Int , iomap Map ) FunctionInstance {
115136 var (
116- fn = p .program . Function ( bus )
137+ fn = p .fn
117138 // Determine how many I/O registers
118139 nio = fn .NumInputs () + fn .NumOutputs ()
119140 //
120141 pc = uint (0 )
121142 //
122- state = InitialState (inputs , fn .Registers (), fn .Buses (), p )
143+ state = InitialState (inputs , fn .Registers (), fn .Buses (), iomap )
123144 )
124145 // Keep executing until we're done.
125146 for pc != RETURN && pc != FAIL {
@@ -131,7 +152,46 @@ func (p *Executor[T]) call(bus uint, inputs []big.Int) FunctionInstance {
131152 }
132153 // Cache I/O instance
133154 instance := FunctionInstance {fn .NumInputs (), state .state [:nio ]}
134- p .states [bus ].Insert (instance )
155+ // Obtain write lock
156+ p .mux .Lock ()
157+ // Insert new instance
158+ p .instances .Insert (instance )
159+ // Release write lock
160+ p .mux .Unlock ()
135161 // Done
136162 return instance
137163}
164+
165+ // ============================================================================
166+ // FunctionInstance
167+ // ============================================================================
168+
169+ // FunctionInstance captures the mapping from inputs (i.e. parameters) to outputs (i.e.
170+ // returns) for a particular instance of a given function.
171+ type FunctionInstance struct {
172+ ninputs uint
173+ state []big.Int
174+ }
175+
176+ // Cmp comparator for the I/O registers of a particular function instance.
177+ // Observe that, since functions are always deterministic, this only considers
178+ // the inputs (as the outputs follow directly from this).
179+ func (p FunctionInstance ) Cmp (other FunctionInstance ) int {
180+ for i := range p .ninputs {
181+ if c := p .state [i ].Cmp (& other .state [i ]); c != 0 {
182+ return c
183+ }
184+ }
185+ //
186+ return 0
187+ }
188+
189+ // Outputs returns the output values for this function instance.
190+ func (p FunctionInstance ) Outputs () []big.Int {
191+ return p .state [p .ninputs :]
192+ }
193+
194+ // Get value of given input or output argument for this instance.
195+ func (p FunctionInstance ) Get (arg uint ) big.Int {
196+ return p .state [arg ]
197+ }
0 commit comments