mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-21 18:37:26 +08:00
400 lines
8.8 KiB
Go
400 lines
8.8 KiB
Go
//
|
|
// Copyright (c) 2019-2023 Markku Rossi
|
|
//
|
|
// All rights reserved.
|
|
//
|
|
|
|
package circuits
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"source.quilibrium.com/quilibrium/monorepo/bedlam/circuit"
|
|
"source.quilibrium.com/quilibrium/monorepo/bedlam/compiler/utils"
|
|
)
|
|
|
|
// Builtin implements a buitin circuit that uses input wires a and b
|
|
// and returns the circuit result in r.
|
|
type Builtin func(cc *Compiler, a, b, r []*Wire) error
|
|
|
|
// Compiler implements binary circuit compiler.
|
|
type Compiler struct {
|
|
Params *utils.Params
|
|
Calloc *Allocator
|
|
OutputsAssigned bool
|
|
Inputs circuit.IO
|
|
Outputs circuit.IO
|
|
InputWires []*Wire
|
|
OutputWires []*Wire
|
|
Gates []*Gate
|
|
nextWireID circuit.Wire
|
|
pending []*Gate
|
|
assigned []*Gate
|
|
compiled []circuit.Gate
|
|
invI0Wire *Wire
|
|
zeroWire *Wire
|
|
oneWire *Wire
|
|
}
|
|
|
|
// NewCompiler creates a new circuit compiler for the specified
|
|
// circuit input and output values.
|
|
func NewCompiler(params *utils.Params, calloc *Allocator,
|
|
inputs, outputs circuit.IO, inputWires, outputWires []*Wire) (
|
|
*Compiler, error) {
|
|
|
|
if len(inputWires) == 0 {
|
|
return nil, fmt.Errorf("no inputs defined")
|
|
}
|
|
return &Compiler{
|
|
Params: params,
|
|
Calloc: calloc,
|
|
Inputs: inputs,
|
|
Outputs: outputs,
|
|
InputWires: inputWires,
|
|
OutputWires: outputWires,
|
|
Gates: make([]*Gate, 0, 65536),
|
|
}, nil
|
|
}
|
|
|
|
// InvI0Wire returns a wire holding value INV(input[0]).
|
|
func (cc *Compiler) InvI0Wire() *Wire {
|
|
if cc.invI0Wire == nil {
|
|
cc.invI0Wire = cc.Calloc.Wire()
|
|
cc.AddGate(cc.Calloc.INVGate(cc.InputWires[0], cc.invI0Wire))
|
|
}
|
|
return cc.invI0Wire
|
|
}
|
|
|
|
// ZeroWire returns a wire holding value 0.
|
|
func (cc *Compiler) ZeroWire() *Wire {
|
|
if cc.zeroWire == nil {
|
|
cc.zeroWire = cc.Calloc.Wire()
|
|
cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, cc.InputWires[0],
|
|
cc.InvI0Wire(), cc.zeroWire))
|
|
cc.zeroWire.SetValue(Zero)
|
|
}
|
|
return cc.zeroWire
|
|
}
|
|
|
|
// OneWire returns a wire holding value 1.
|
|
func (cc *Compiler) OneWire() *Wire {
|
|
if cc.oneWire == nil {
|
|
cc.oneWire = cc.Calloc.Wire()
|
|
cc.AddGate(cc.Calloc.BinaryGate(circuit.OR, cc.InputWires[0],
|
|
cc.InvI0Wire(), cc.oneWire))
|
|
cc.oneWire.SetValue(One)
|
|
}
|
|
return cc.oneWire
|
|
}
|
|
|
|
// ZeroPad pads the argument wires x and y with zero values so that
|
|
// the resulting wires have the same number of bits.
|
|
func (cc *Compiler) ZeroPad(x, y []*Wire) ([]*Wire, []*Wire) {
|
|
if len(x) == len(y) {
|
|
return x, y
|
|
}
|
|
|
|
max := len(x)
|
|
if len(y) > max {
|
|
max = len(y)
|
|
}
|
|
|
|
rx := make([]*Wire, max)
|
|
for i := 0; i < max; i++ {
|
|
if i < len(x) {
|
|
rx[i] = x[i]
|
|
} else {
|
|
rx[i] = cc.ZeroWire()
|
|
}
|
|
}
|
|
|
|
ry := make([]*Wire, max)
|
|
for i := 0; i < max; i++ {
|
|
if i < len(y) {
|
|
ry[i] = y[i]
|
|
} else {
|
|
ry[i] = cc.ZeroWire()
|
|
}
|
|
}
|
|
|
|
return rx, ry
|
|
}
|
|
|
|
// ShiftLeft shifts the size number of bits of the input wires w,
|
|
// count bits left.
|
|
func (cc *Compiler) ShiftLeft(w []*Wire, size, count int) []*Wire {
|
|
result := make([]*Wire, size)
|
|
|
|
if count < size {
|
|
copy(result[count:], w)
|
|
}
|
|
for i := 0; i < count; i++ {
|
|
result[i] = cc.ZeroWire()
|
|
}
|
|
for i := count + len(w); i < size; i++ {
|
|
result[i] = cc.ZeroWire()
|
|
}
|
|
return result
|
|
}
|
|
|
|
// INV creates an inverse wire inverting the input wire i's value to
|
|
// the output wire o.
|
|
func (cc *Compiler) INV(i, o *Wire) {
|
|
cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, i, cc.OneWire(), o))
|
|
}
|
|
|
|
// ID creates an identity wire passing the input wire i's value to the
|
|
// output wire o.
|
|
func (cc *Compiler) ID(i, o *Wire) {
|
|
cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, i, cc.ZeroWire(), o))
|
|
}
|
|
|
|
// AddGate adds a get into the circuit.
|
|
func (cc *Compiler) AddGate(gate *Gate) {
|
|
cc.Gates = append(cc.Gates, gate)
|
|
}
|
|
|
|
// SetNextWireID sets the next unique wire ID to use.
|
|
func (cc *Compiler) SetNextWireID(next circuit.Wire) {
|
|
cc.nextWireID = next
|
|
}
|
|
|
|
// NextWireID returns the next unique wire ID.
|
|
func (cc *Compiler) NextWireID() circuit.Wire {
|
|
ret := cc.nextWireID
|
|
cc.nextWireID++
|
|
return ret
|
|
}
|
|
|
|
// ConstPropagate propagates constant wire values in the circuit and
|
|
// short circuits gates if their output does not depend on the gate's
|
|
// logical operation.
|
|
func (cc *Compiler) ConstPropagate() {
|
|
var stats circuit.Stats
|
|
|
|
start := time.Now()
|
|
|
|
for _, g := range cc.Gates {
|
|
switch g.Op {
|
|
case circuit.XOR:
|
|
if (g.A.Value() == Zero && g.B.Value() == Zero) ||
|
|
(g.A.Value() == One && g.B.Value() == One) {
|
|
g.O.SetValue(Zero)
|
|
stats[g.Op]++
|
|
} else if (g.A.Value() == Zero && g.B.Value() == One) ||
|
|
(g.A.Value() == One && g.B.Value() == Zero) {
|
|
g.O.SetValue(One)
|
|
stats[g.Op]++
|
|
} else if g.A.Value() == Zero {
|
|
// O = B
|
|
stats[g.Op]++
|
|
g.ShortCircuit(g.B)
|
|
} else if g.B.Value() == Zero {
|
|
// O = A
|
|
stats[g.Op]++
|
|
g.ShortCircuit(g.A)
|
|
}
|
|
|
|
case circuit.XNOR:
|
|
if (g.A.Value() == Zero && g.B.Value() == Zero) ||
|
|
(g.A.Value() == One && g.B.Value() == One) {
|
|
g.O.SetValue(One)
|
|
stats[g.Op]++
|
|
} else if (g.A.Value() == Zero && g.B.Value() == One) ||
|
|
(g.A.Value() == One && g.B.Value() == Zero) {
|
|
g.O.SetValue(Zero)
|
|
stats[g.Op]++
|
|
}
|
|
|
|
case circuit.AND:
|
|
if g.A.Value() == Zero || g.B.Value() == Zero {
|
|
g.O.SetValue(Zero)
|
|
stats[g.Op]++
|
|
} else if g.A.Value() == One && g.B.Value() == One {
|
|
g.O.SetValue(One)
|
|
stats[g.Op]++
|
|
} else if g.A.Value() == One {
|
|
// O = B
|
|
stats[g.Op]++
|
|
g.ShortCircuit(g.B)
|
|
} else if g.B.Value() == One {
|
|
// O = A
|
|
stats[g.Op]++
|
|
g.ShortCircuit(g.A)
|
|
}
|
|
|
|
case circuit.OR:
|
|
if g.A.Value() == One || g.B.Value() == One {
|
|
g.O.SetValue(One)
|
|
stats[g.Op]++
|
|
} else if g.A.Value() == Zero && g.B.Value() == Zero {
|
|
g.O.SetValue(Zero)
|
|
stats[g.Op]++
|
|
} else if g.A.Value() == Zero {
|
|
// O = B
|
|
stats[g.Op]++
|
|
g.ShortCircuit(g.B)
|
|
} else if g.B.Value() == Zero {
|
|
// O = A
|
|
stats[g.Op]++
|
|
g.ShortCircuit(g.A)
|
|
}
|
|
|
|
case circuit.INV:
|
|
if g.A.Value() == One {
|
|
g.O.SetValue(Zero)
|
|
stats[g.Op]++
|
|
} else if g.A.Value() == Zero {
|
|
g.O.SetValue(One)
|
|
stats[g.Op]++
|
|
}
|
|
}
|
|
|
|
if g.A.Value() == Zero {
|
|
g.A.RemoveOutput(g)
|
|
g.A = cc.ZeroWire()
|
|
g.A.AddOutput(g)
|
|
} else if g.A.Value() == One {
|
|
g.A.RemoveOutput(g)
|
|
g.A = cc.OneWire()
|
|
g.A.AddOutput(g)
|
|
}
|
|
if g.B != nil {
|
|
if g.B.Value() == Zero {
|
|
g.B.RemoveOutput(g)
|
|
g.B = cc.ZeroWire()
|
|
g.B.AddOutput(g)
|
|
} else if g.B.Value() == One {
|
|
g.B.RemoveOutput(g)
|
|
g.B = cc.OneWire()
|
|
g.B.AddOutput(g)
|
|
}
|
|
}
|
|
}
|
|
|
|
elapsed := time.Since(start)
|
|
|
|
if cc.Params.Diagnostics && stats.Count() > 0 {
|
|
fmt.Printf(" - ConstPropagate: %12s: %d/%d (%.2f%%)\n",
|
|
elapsed, stats.Count(), len(cc.Gates),
|
|
float64(stats.Count())/float64(len(cc.Gates))*100)
|
|
}
|
|
}
|
|
|
|
// ShortCircuitXORZero short circuits input to output where input is
|
|
// XOR'ed to zero.
|
|
func (cc *Compiler) ShortCircuitXORZero() {
|
|
var stats circuit.Stats
|
|
|
|
start := time.Now()
|
|
|
|
for _, g := range cc.Gates {
|
|
if g.Op != circuit.XOR {
|
|
continue
|
|
}
|
|
if g.A.Value() == Zero && !g.B.IsInput() &&
|
|
g.B.Input().O.NumOutputs() == 1 {
|
|
|
|
g.B.Input().ResetOutput(g.O)
|
|
|
|
// Disconnect gate's output wire.
|
|
g.O = cc.Calloc.Wire()
|
|
|
|
stats[g.Op]++
|
|
}
|
|
if g.B.Value() == Zero && !g.A.IsInput() &&
|
|
g.A.Input().O.NumOutputs() == 1 {
|
|
|
|
g.A.Input().ResetOutput(g.O)
|
|
|
|
// Disconnect gate's output wire.
|
|
g.O = cc.Calloc.Wire()
|
|
|
|
stats[g.Op]++
|
|
}
|
|
}
|
|
|
|
elapsed := time.Since(start)
|
|
|
|
if cc.Params.Diagnostics && stats.Count() > 0 {
|
|
fmt.Printf(" - ShortCircuitXORZero: %12s: %d/%d (%.2f%%)\n",
|
|
elapsed, stats.Count(), len(cc.Gates),
|
|
float64(stats.Count())/float64(len(cc.Gates))*100)
|
|
}
|
|
}
|
|
|
|
// Prune removes all gates whose output wires are unused.
|
|
func (cc *Compiler) Prune() int {
|
|
|
|
n := make([]*Gate, len(cc.Gates))
|
|
nPos := len(n)
|
|
|
|
for i := len(cc.Gates) - 1; i >= 0; i-- {
|
|
g := cc.Gates[i]
|
|
if !g.Prune() {
|
|
nPos--
|
|
n[nPos] = g
|
|
}
|
|
}
|
|
cc.Gates = n[nPos:]
|
|
|
|
return nPos
|
|
}
|
|
|
|
// Compile compiles the circuit.
|
|
func (cc *Compiler) Compile() *circuit.Circuit {
|
|
if len(cc.pending) != 0 {
|
|
panic("Compile: pending set")
|
|
}
|
|
cc.pending = make([]*Gate, 0, len(cc.Gates))
|
|
if len(cc.assigned) != 0 {
|
|
panic("Compile: assigned set")
|
|
}
|
|
cc.assigned = make([]*Gate, 0, len(cc.Gates))
|
|
if len(cc.compiled) != 0 {
|
|
panic("Compile: compiled set")
|
|
}
|
|
cc.compiled = make([]circuit.Gate, 0, len(cc.Gates))
|
|
|
|
for _, w := range cc.InputWires {
|
|
w.Assign(cc)
|
|
}
|
|
for len(cc.pending) > 0 {
|
|
gate := cc.pending[0]
|
|
cc.pending = cc.pending[1:]
|
|
gate.Assign(cc)
|
|
}
|
|
// Assign outputs.
|
|
for _, w := range cc.OutputWires {
|
|
if w.Assigned() {
|
|
if !cc.OutputsAssigned {
|
|
panic("Output already assigned")
|
|
}
|
|
} else {
|
|
w.SetID(cc.NextWireID())
|
|
}
|
|
}
|
|
|
|
// Compile circuit.
|
|
for _, gate := range cc.assigned {
|
|
gate.Compile(cc)
|
|
}
|
|
|
|
var stats circuit.Stats
|
|
for _, g := range cc.compiled {
|
|
stats[g.Op]++
|
|
}
|
|
|
|
result := &circuit.Circuit{
|
|
NumGates: len(cc.compiled),
|
|
NumWires: int(cc.nextWireID),
|
|
Inputs: cc.Inputs,
|
|
Outputs: cc.Outputs,
|
|
Gates: cc.compiled,
|
|
Stats: stats,
|
|
}
|
|
|
|
return result
|
|
}
|