ceremonyclient/bedlam/compiler/circuits/compiler.go
Cassandra Heart dbd95bd9e9
v2.1.0 (#439)
* v2.1.0 [omit consensus and adjacent] - this commit will be amended with the full release after the file copy is complete

* 2.1.0 main node rollup
2025-09-30 02:48:15 -05:00

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
}