diff --git a/debug/debug.go b/debug/debug.go index 05c36fba9d..027bc49689 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -7,15 +7,13 @@ import ( "strings" ) -var light = true - func Stack() string { var sbb strings.Builder WriteStack(&sbb) return sbb.String() } -func WriteStack(sbb *strings.Builder) { +func WriteStack(sbb *strings.Builder, forceClean ...bool) { // derived from: https://golang.org/pkg/runtime/#example_Frames // we stop when func name == Define as it is where the gnark circuit code should start @@ -37,7 +35,7 @@ func WriteStack(sbb *strings.Builder) { function := fe[len(fe)-1] file := frame.File - if light { + if !Debug || (len(forceClean) > 1 && forceClean[0]) { if strings.Contains(function, "runtime.gopanic") { continue } diff --git a/debug/debug_full.go b/debug/debug_set.go similarity index 61% rename from debug/debug_full.go rename to debug/debug_set.go index ae1b6d98ed..49e94a0755 100644 --- a/debug/debug_full.go +++ b/debug/debug_set.go @@ -3,6 +3,4 @@ package debug -func init() { - light = false -} +const Debug = true diff --git a/debug/debug_unset.go b/debug/debug_unset.go new file mode 100644 index 0000000000..fe1628f492 --- /dev/null +++ b/debug/debug_unset.go @@ -0,0 +1,6 @@ +//go:build !debug +// +build !debug + +package debug + +const Debug = false diff --git a/frontend/api.go b/frontend/api.go index 67ca554405..de064fcc29 100644 --- a/frontend/api.go +++ b/frontend/api.go @@ -109,4 +109,11 @@ type API interface { // from the backend point of view, it's equivalent to a user-supplied witness // except, the solver is going to assign it a value, not the caller NewHint(f hint.Function, inputs ...interface{}) Variable + + // Tag creates a tag at a given place in a circuit. The state of the tag may contain informations needed to + // measure constraints, variables and coefficients creations through AddCounter + Tag(name string) Tag + + // AddCounter measures the number of constraints, variables and coefficients created between two tags + AddCounter(from, to Tag) } diff --git a/frontend/counter.go b/frontend/counter.go new file mode 100644 index 0000000000..911e90632f --- /dev/null +++ b/frontend/counter.go @@ -0,0 +1,22 @@ +package frontend + +import ( + "fmt" +) + +// Tag contains informations needed to measure and display statistics of a delimited piece of circuit +type Tag struct { + Name string + vID, cID int +} + +// Counter contains measurements of useful statistics between two Tag +type Counter struct { + From, To Tag + NbVariables int + NbConstraints int +} + +func (c Counter) String() string { + return fmt.Sprintf("%s to %s: %d variables, %d constraints", c.From.Name, c.To.Name, c.NbVariables, c.NbConstraints) +} diff --git a/frontend/cs.go b/frontend/cs.go index 7cc42b9c5a..f89e549e2f 100644 --- a/frontend/cs.go +++ b/frontend/cs.go @@ -60,6 +60,8 @@ type constraintSystem struct { mDebug map[int]int // maps constraint ID to debugInfo id + counters []Counter // statistic counters + curveID ecc.ID } @@ -118,6 +120,7 @@ func newConstraintSystem(curveID ecc.ID, initialCapacity ...int) constraintSyste mDebug: make(map[int]int), mHints: make(map[int]compiled.Hint), mHintsConstrained: make(map[int]bool), + counters: make([]Counter, 0), } cs.coeffs[compiled.CoeffIdZero].SetInt64(0) diff --git a/frontend/cs_debug.go b/frontend/cs_debug.go index d30220d1a9..983ce1d219 100644 --- a/frontend/cs_debug.go +++ b/frontend/cs_debug.go @@ -149,3 +149,25 @@ func (cs *constraintSystem) addDebugInfo(errName string, i ...interface{}) int { cs.debugInfo = append(cs.debugInfo, l) return len(cs.debugInfo) - 1 } + +// Tag creates a tag at a given place in a circuit. The state of the tag may contain informations needed to +// measure constraints, variables and coefficients creations through AddCounter +func (cs *constraintSystem) Tag(name string) Tag { + _, file, line, _ := runtime.Caller(1) + + return Tag{ + Name: fmt.Sprintf("%s[%s:%d]", name, filepath.Base(file), line), + vID: len(cs.internal.variables), + cID: len(cs.constraints), + } +} + +// AddCounter measures the number of constraints, variables and coefficients created between two tags +func (cs *constraintSystem) AddCounter(from, to Tag) { + cs.counters = append(cs.counters, Counter{ + From: from, + To: to, + NbVariables: to.vID - from.vID, + NbConstraints: to.cID - from.cID, + }) +} diff --git a/frontend/frontend.go b/frontend/frontend.go index 21c0954639..85e0b29478 100644 --- a/frontend/frontend.go +++ b/frontend/frontend.go @@ -75,10 +75,22 @@ func Compile(curveID ecc.ID, zkpID backend.ID, circuit Circuit, opts ...func(opt case backend.GROTH16: ccs, err = cs.toR1CS(curveID) case backend.PLONK: + // TODO update cs.counters ccs, err = cs.toSparseR1CS(curveID) default: panic("not implemented") } + + // print counters + // TODO we need a way to access these through APIs, printing is not great. + if opt.displayCounters && len(cs.counters) > 0 { + fmt.Printf("counters [%s - %s]:\n", zkpID, curveID) + for _, c := range cs.counters { + fmt.Printf("\t%s\n", c) + } + fmt.Println() + } + if err != nil { return nil, err } @@ -153,6 +165,7 @@ func Value(value interface{}) Variable { type CompileOption struct { capacity int ignoreUnconstrainedInputs bool + displayCounters bool } // WithOutput is a Compile option that specifies the estimated capacity needed for internal variables and constraints @@ -168,3 +181,10 @@ func IgnoreUnconstrainedInputs(opt *CompileOption) error { opt.ignoreUnconstrainedInputs = true return nil } + +// DisplayCounters when set, the Compile function will display counters added through api.AddCounter +// after the post-compile phase ran +func DisplayCounters(opt *CompileOption) error { + opt.displayCounters = true + return nil +} diff --git a/test/assert.go b/test/assert.go index 4a1b8eed97..fd2099b510 100644 --- a/test/assert.go +++ b/test/assert.go @@ -28,6 +28,7 @@ import ( "github.com/consensys/gnark/backend/groth16" "github.com/consensys/gnark/backend/plonk" "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/debug" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/internal/utils" "github.com/stretchr/testify/require" @@ -364,7 +365,16 @@ func (assert *Assert) compile(circuit frontend.Circuit, curveID ecc.ID, backendI return nil, err } - _ccs, err := frontend.Compile(curveID, backendID, circuit, compileOpts...) + // if debug flag is set, add frontend.DisplayCounters + var cOpts []func(opt *frontend.CompileOption) error + if debug.Debug { + cOpts = make([]func(opt *frontend.CompileOption) error, len(compileOpts)+1) + copy(cOpts, compileOpts) + cOpts[len(cOpts)-1] = frontend.DisplayCounters // it's ok if we have a duplicate + } else { + cOpts = compileOpts + } + _ccs, err := frontend.Compile(curveID, backendID, circuit, cOpts...) if err != nil { return nil, fmt.Errorf("%w: %v", ErrCompilationNotDeterministic, err) } diff --git a/test/engine.go b/test/engine.go index 8085bb73e7..369b7bf55b 100644 --- a/test/engine.go +++ b/test/engine.go @@ -338,6 +338,15 @@ func (e *engine) NewHint(f hint.Function, inputs ...interface{}) frontend.Variab return frontend.Value(result) } +func (e *engine) Tag(name string) frontend.Tag { + // do nothing, we don't measure constraints with the test engine + return frontend.Tag{Name: name} +} + +func (e *engine) AddCounter(from, to frontend.Tag) { + // do nothing, we don't measure constraints with the test engine +} + func (e *engine) toBigInt(i1 interface{}) big.Int { if v1, ok := i1.(frontend.Variable); ok { return v1.GetWitnessValue(e.curveID)