-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathapi.go
More file actions
182 lines (171 loc) · 6.41 KB
/
api.go
File metadata and controls
182 lines (171 loc) · 6.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// Package ExpanderCompilerCollection wraps the most commonly used compiler APIs and provides an entry point for compilation.
// This package simplifies the interaction with the compiler by exposing a unified API interface.
package ExpanderCompilerCollection
import (
"fmt"
"math/big"
"github.com/PolyhedraZK/ExpanderCompilerCollection/builder"
"github.com/PolyhedraZK/ExpanderCompilerCollection/ir"
"github.com/PolyhedraZK/ExpanderCompilerCollection/layered"
"github.com/PolyhedraZK/ExpanderCompilerCollection/layering"
"github.com/PolyhedraZK/ExpanderCompilerCollection/utils"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/logger"
)
// API encapsulates the ExpanderCompilerCollection's frontend.API along with two new APIs added to facilitate
// direct invocation of ExpanderCompilerCollection.API within the codebase.
type API interface {
frontend.API
builder.SubCircuitAPI
builder.API
}
// CompileResult represents the result of a compilation process.
// It contains unexported fields and provides methods to retrieve various components
// like the intermediate representation (IR) of the circuit, the InputSolver, and the Layered Circuit.
type CompileResult struct {
rc *ir.RootCircuit
compiled *layered.RootCircuit
inputOrder *ir.InputOrder
}
// Compile is similar to gnark's frontend.Compile. It compiles the given circuit and returns
// a pointer to CompileResult along with any error encountered during the compilation process.
func Compile(field *big.Int, circuit frontend.Circuit, opts ...frontend.CompileOption) (*CompileResult, error) {
var root *builder.Root
newBuilder_ := func(field *big.Int, config frontend.CompileConfig) (frontend.Builder, error) {
if root != nil {
panic("newBuilder can only be called once")
}
root = builder.NewRoot(field, config)
return root, nil
}
// returned R1CS is useless
_, err := frontend.Compile(field, newBuilder_, circuit, opts...)
if err != nil {
return nil, err
}
log := logger.Logger()
rc := root.Finalize()
if err := ir.Validate(rc); err != nil {
return nil, err
}
stats := rc.GetStats()
log.Info().
Int("nbRootInput", stats.NbRootInput).
Int("nbTotTerms", stats.NbTotTerms).
Int("nbExpandedTerms", stats.NbExpandedTerms).
Int("nbConstraints", stats.NbConstraints).
Msg("built circuit ir")
// There should be some optimizations, but it requires more work to make them correct
// rc = ir.Optimize(rc)
rc = ir.AdjustForLayering(rc)
if err := ir.ValidateForLayering(rc); err != nil {
return nil, err
}
stats = rc.GetStats()
log.Info().
Int("nbRootInput", stats.NbRootInput).
Int("nbTotTerms", stats.NbTotTerms).
Int("nbExpandedTerms", stats.NbExpandedTerms).
Int("nbConstraints", stats.NbConstraints).
Msg("adjusted circuit ir")
lrc, io := layering.Compile(rc)
if err := layered.Validate(lrc); err != nil {
return nil, err
}
if err := layered.ValidateInitialized(lrc); err != nil {
return nil, err
}
lstats := lrc.GetStats()
log1 := log.Info().
Int("nbLayer", lstats.NbLayer).
Int("nbCircuit", lstats.NbCircuit).
Int("numMul", lstats.NbExpandedMul).
Int("numAdd", lstats.NbExpandedAdd).
Int("numCst", lstats.NbExpandedCst).
Int("nbVariables", lstats.NbTotGates).
Int("nbUsedVariables", lstats.NbUsedGates).
Int("nbUsedInputs", lstats.NbInput).
Int("totalCost", lstats.TotalCost)
for k, v := range lstats.NbExpandedCustom {
log1 = log1.Int(fmt.Sprintf("numCustom%d", k), v)
}
log1.Msg("compiled layered circuit")
lrc = layered.Optimize(lrc)
if err := layered.Validate(lrc); err != nil {
return nil, err
}
if err := layered.ValidateInitialized(lrc); err != nil {
return nil, err
}
lstats = lrc.GetStats()
log2 := log.Info().
Int("nbLayer", lstats.NbLayer).
Int("nbCircuit", lstats.NbCircuit).
Int("numMul", lstats.NbExpandedMul).
Int("numAdd", lstats.NbExpandedAdd).
Int("numCst", lstats.NbExpandedCst).
Int("nbVariables", lstats.NbTotGates).
Int("nbUsedVariables", lstats.NbUsedGates).
Int("nbUsedInputs", lstats.NbInput).
Int("totalCost", lstats.TotalCost)
for k, v := range lstats.NbExpandedCustom {
log2 = log2.Int(fmt.Sprintf("numCustom%d", k), v)
}
log2.Msg("optimized layered circuit")
res := CompileResult{
rc: rc,
compiled: lrc,
inputOrder: io,
}
return &res, nil
}
// ProfilingCompile compiles the given circuit with profiling enabled, outputting the cost of each line of code.
// It does not return a compilation result as it does not complete the actual compilation process.
// Profiling is useful for performance analysis and optimization.
// TODO: Add support for sub-circuit profiling.
func ProfilingCompile(field *big.Int, circuit frontend.Circuit, opts ...frontend.CompileOption) error {
var root *builder.ProfilingRoot
newBuilder_ := func(field *big.Int, config frontend.CompileConfig) (frontend.Builder, error) {
if root != nil {
panic("newBuilder can only be called once")
}
root = builder.NewProfilingRoot(field, config)
return root.GetRootBuilder(), nil
}
// returned R1CS is useless
_, err := frontend.Compile(field, newBuilder_, circuit, opts...)
// make sure ExpanderCompilerCollection.API is implemented by ProfilingBuilder
_ = API(root.GetRootBuilder())
if err != nil {
return err
}
rc, varSourceInfo := root.Finalize()
if err := ir.ValidateForLayering(rc); err != nil {
return err
}
varCost := layering.ProfilingCompile(rc)
utils.ShowProfiling(varSourceInfo, varCost)
return nil
}
// GetCircuitIr returns the intermediate representation (IR) of the compiled circuit as *ir.RootCircuit.
func (c *CompileResult) GetCircuitIr() *ir.RootCircuit {
return c.rc
}
// GetInputSolver returns the InputSolver component of the compilation result as *ir.InputSolver.
func (c *CompileResult) GetLayeredCircuit() *layered.RootCircuit {
return c.compiled
}
// GetLayeredCircuit returns the Layered Circuit component of the compilation result as *layered.RootCircuit.
func (c *CompileResult) GetInputSolver() *ir.InputSolver {
return ir.GetInputSolver(c.rc, c.inputOrder)
}
// DeserializeLayeredCircuit takes a byte buffer and returns a pointer to a layered.RootCircuit
// which represents a deserialized layered circuit.
func DeserializeLayeredCircuit(buf []byte) *layered.RootCircuit {
return layered.DeserializeRootCircuit(buf)
}
// DeserializeInputSolver takes a byte buffer and returns a pointer to an ir.InputSolver
// which represents a deserialized input solver.
func DeserializeInputSolver(buf []byte) *ir.InputSolver {
return ir.DeserializeInputSolver(buf)
}