From 987a587fc0ed7033c111533407e5acbb6ff2ff96 Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Mon, 21 Aug 2023 10:46:42 +0200 Subject: [PATCH 01/19] Changed wire allocator API to use Value as the key instead of Value.String(). --- compiler/ssa/circuitgen.go | 58 +++++++++++++++++----------------- compiler/ssa/instructions.go | 14 ++++---- compiler/ssa/program.go | 9 ++++-- compiler/ssa/streamer.go | 25 +++++++-------- compiler/ssa/value.go | 14 ++++++-- compiler/ssa/wire_allocator.go | 35 +++++++++++++------- 6 files changed, 90 insertions(+), 65 deletions(-) diff --git a/compiler/ssa/circuitgen.go b/compiler/ssa/circuitgen.go index c6df5fc7..d0d85574 100644 --- a/compiler/ssa/circuitgen.go +++ b/compiler/ssa/circuitgen.go @@ -78,7 +78,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { instr := step.Instr var wires [][]*circuits.Wire for _, in := range instr.In { - w, err := prog.Wires(in.String(), in.Type.Bits) + w, err := prog.Wires(in, in.Type.Bits) if err != nil { return err } @@ -86,7 +86,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } switch instr.Op { case Iadd, Uadd: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -96,7 +96,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Isub, Usub: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -106,7 +106,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Imult, Umult: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -117,7 +117,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Idiv, Udiv: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -128,7 +128,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Imod, Umod: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -158,7 +158,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } o[bit] = w } - err = prog.SetWires(instr.Out.String(), o) + err = prog.SetWires(*instr.Out, o) if err != nil { return err } @@ -189,7 +189,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } o[bit] = w } - err = prog.SetWires(instr.Out.String(), o) + err = prog.SetWires(*instr.Out, o) if err != nil { return err } @@ -225,13 +225,13 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { for bit := to - from; int(bit) < len(o); bit++ { o[bit] = cc.ZeroWire() } - err = prog.SetWires(instr.Out.String(), o) + err = prog.SetWires(*instr.Out, o) if err != nil { return err } case Index: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -247,7 +247,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Ilt, Ult: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -257,7 +257,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Ile, Ule: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -267,7 +267,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Igt, Ugt: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -277,7 +277,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Ige, Uge: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -287,7 +287,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Eq: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -297,7 +297,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Neq: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -312,7 +312,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { return fmt.Errorf("%s unsupported index type %T: %s", instr.Op, instr.In[1], err) } - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -327,7 +327,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { return fmt.Errorf("%s unsupported index type %T: %s", instr.Op, instr.In[1], err) } - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -337,7 +337,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case And: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -347,7 +347,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Or: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -357,7 +357,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Band: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -367,7 +367,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Bclr: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -377,7 +377,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Bor: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -387,7 +387,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Bxor: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -415,7 +415,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } o[bit] = w } - err := prog.SetWires(instr.Out.String(), o) + err := prog.SetWires(*instr.Out, o) if err != nil { return err } @@ -457,13 +457,13 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } o[bit] = w } - err = prog.SetWires(instr.Out.String(), o) + err = prog.SetWires(*instr.Out, o) if err != nil { return err } case Phi: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -502,7 +502,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { var circOut []*circuits.Wire for _, r := range instr.Ret { - o, err := prog.Wires(r.String(), r.Type.Bits) + o, err := prog.Wires(r, r.Type.Bits) if err != nil { return err } @@ -549,7 +549,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Builtin: - o, err := prog.Wires(instr.Out.String(), instr.Out.Type.Bits) + o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } diff --git a/compiler/ssa/instructions.go b/compiler/ssa/instructions.go index f41a79ca..c364ea72 100644 --- a/compiler/ssa/instructions.go +++ b/compiler/ssa/instructions.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2020-2022 Markku Rossi +// Copyright (c) 2020-2023 Markku Rossi // // All rights reserved. // @@ -151,7 +151,7 @@ type Instr struct { Label *Block Circ *circuit.Circuit Builtin circuits.Builtin - GC string + GC *Value Ret []Value } @@ -541,10 +541,10 @@ func NewBuiltinInstr(builtin circuits.Builtin, a, b, r Value) Instr { } // NewGCInstr creates a new GC instruction. -func NewGCInstr(v string) Instr { +func NewGCInstr(v Value) Instr { return Instr{ Op: GC, - GC: v, + GC: &v, } } @@ -560,7 +560,7 @@ func (i Instr) StringTyped() string { func (i Instr) string(maxLen int, typesOnly bool) string { result := i.Op.String() - if len(i.In) == 0 && i.Out == nil && i.Label == nil && len(i.GC) == 0 { + if len(i.In) == 0 && i.Out == nil && i.Label == nil && i.GC == nil { return result } @@ -590,9 +590,9 @@ func (i Instr) string(maxLen int, typesOnly bool) string { if i.Circ != nil { result += fmt.Sprintf(" {G=%d, W=%d}", i.Circ.NumGates, i.Circ.NumWires) } - if len(i.GC) > 0 { + if i.GC != nil { result += " " - result += i.GC + result += i.GC.String() } for _, r := range i.Ret { result += " " diff --git a/compiler/ssa/program.go b/compiler/ssa/program.go index 86721a1f..02fcd495 100644 --- a/compiler/ssa/program.go +++ b/compiler/ssa/program.go @@ -62,7 +62,10 @@ func NewProgram(params *utils.Params, in, out circuit.IO, if len(arg.Name) == 0 { arg.Name = fmt.Sprintf("arg{%d}", idx) } - wires, err := prog.Wires(arg.Name, types.Size(arg.Size)) + wires, err := prog.Wires(Value{ + Const: true, + Name: arg.Name, + }, types.Size(arg.Size)) if err != nil { return nil, err } @@ -194,7 +197,7 @@ func (prog *Program) GC() { if !live { // Input is not live. gcs = append(gcs, Step{ - Instr: NewGCInstr(in.String()), + Instr: NewGCInstr(in), }) } } @@ -255,7 +258,7 @@ func (prog *Program) DefineConstants(zero, one *circuits.Wire) error { wires = append(wires, w) } - err := prog.SetWires(c.String(), wires) + err := prog.SetWires(c, wires) if err != nil { return err } diff --git a/compiler/ssa/streamer.go b/compiler/ssa/streamer.go index e06d06a3..ace07ece 100644 --- a/compiler/ssa/streamer.go +++ b/compiler/ssa/streamer.go @@ -177,7 +177,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, instr := step.Instr wires = wires[:0] for _, in := range instr.In { - w, err := prog.AssignedWires(in.String(), in.Type.Bits) + w, err := prog.AssignedWires(in, in.Type.Bits) if err != nil { return nil, nil, err } @@ -187,8 +187,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, var out []*circuits.Wire var err error if instr.Out != nil { - out, err = prog.AssignedWires(instr.Out.String(), - instr.Out.Type.Bits) + out, err = prog.AssignedWires(*instr.Out, instr.Out.Type.Bits) if err != nil { return nil, nil, err } @@ -388,7 +387,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, } // Return wires. for i, ret := range instr.Ret { - wires, err := prog.AssignedWires(ret.String(), ret.Type.Bits) + wires, err := prog.AssignedWires(ret, ret.Type.Bits) if err != nil { return nil, nil, err } @@ -416,13 +415,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, } case GC: - alloc, ok := prog.wires[instr.GC] - if ok { - delete(prog.wires, instr.GC) - prog.recycleWires(alloc) - } else { - fmt.Printf("GC: %s not known\n", instr.GC) - } + prog.GCWires(*instr.GC) default: f, ok := circuitGenerators[instr.Op] @@ -681,7 +674,10 @@ func (prog *Program) ZeroWire(conn *p2p.Conn, streaming *circuit.Streaming) ( *circuits.Wire, error) { if prog.zeroWire == nil { - wires, err := prog.AssignedWires("{zero}", 1) + wires, err := prog.AssignedWires(Value{ + Const: true, + Name: "{zero}", + }, 1) if err != nil { return nil, err } @@ -727,7 +723,10 @@ func (prog *Program) OneWire(conn *p2p.Conn, streaming *circuit.Streaming) ( *circuits.Wire, error) { if prog.oneWire == nil { - wires, err := prog.AssignedWires("{one}", 1) + wires, err := prog.AssignedWires(Value{ + Const: true, + Name: "{one}", + }, 1) if err != nil { return nil, err } diff --git a/compiler/ssa/value.go b/compiler/ssa/value.go index 1f5948cb..67b2db2d 100644 --- a/compiler/ssa/value.go +++ b/compiler/ssa/value.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2020-2022 Markku Rossi +// Copyright (c) 2020-2023 Markku Rossi // // All rights reserved. // @@ -26,7 +26,7 @@ type Value struct { ConstValue interface{} } -// Scope defines variable scope (max 256 levels of nested blocks). +// Scope defines variable scope (max 65536 levels of nested blocks). type Scope int16 // PtrInfo defines context information for pointer values. @@ -121,6 +121,16 @@ func (v *Value) ConstInt() (types.Size, error) { } } +// HashCode returns a hash code for the value. +func (v *Value) HashCode() (hash int) { + for r := range v.Name { + hash = hash<<8 ^ int(r) ^ hash>>24 + } + hash ^= int(v.Scope) << 15 + hash ^= int(v.Version) << 7 + return +} + // Equal implements BindingValue.Equal. func (v *Value) Equal(other BindingValue) bool { o, ok := other.(*Value) diff --git a/compiler/ssa/wire_allocator.go b/compiler/ssa/wire_allocator.go index 3e62cfcb..b870c7bf 100644 --- a/compiler/ssa/wire_allocator.go +++ b/compiler/ssa/wire_allocator.go @@ -15,29 +15,31 @@ import ( ) // Wires allocates unassigned wires for the argument value. -func (prog *Program) Wires(v string, bits types.Size) ( +func (prog *Program) Wires(v Value, bits types.Size) ( []*circuits.Wire, error) { if bits <= 0 { return nil, fmt.Errorf("size not set for value %v", v) } - alloc, ok := prog.wires[v] + key := v.String() + alloc, ok := prog.wires[key] if !ok { alloc = prog.allocWires(bits) - prog.wires[v] = alloc + prog.wires[key] = alloc } return alloc.Wires, nil } // AssignedWires allocates assigned wires for the argument value. -func (prog *Program) AssignedWires(v string, bits types.Size) ( +func (prog *Program) AssignedWires(v Value, bits types.Size) ( []*circuits.Wire, error) { if bits <= 0 { return nil, fmt.Errorf("size not set for value %v", v) } - alloc, ok := prog.wires[v] + key := v.String() + alloc, ok := prog.wires[key] if !ok { alloc = prog.allocWires(bits) - prog.wires[v] = alloc + prog.wires[key] = alloc // Assign wire IDs. if alloc.Base == circuits.UnassignedID { @@ -76,7 +78,17 @@ func (prog *Program) allocWires(bits types.Size) *wireAlloc { return result } -func (prog *Program) recycleWires(alloc *wireAlloc) { +// GCWires recycles the wires of the argument value. The wires must +// have been previously allocated with Wires, AssignedWires, or +// SetWires; the function panics if the wires have not been allocated. +func (prog *Program) GCWires(v Value) { + key := v.String() + alloc, ok := prog.wires[key] + if !ok { + panic(fmt.Sprintf("GC: %s not known", key)) + } + delete(prog.wires, key) + if alloc.Base == circuits.UnassignedID { alloc.Base = alloc.Wires[0].ID() } @@ -99,10 +111,11 @@ func (prog *Program) recycleWires(alloc *wireAlloc) { } // SetWires allocates wire IDs for the value's wires. -func (prog *Program) SetWires(v string, w []*circuits.Wire) error { - _, ok := prog.wires[v] +func (prog *Program) SetWires(v Value, w []*circuits.Wire) error { + key := v.String() + _, ok := prog.wires[key] if ok { - return fmt.Errorf("wires already set for %v", v) + return fmt.Errorf("wires already set for %v", key) } alloc := &wireAlloc{ Wires: w, @@ -113,7 +126,7 @@ func (prog *Program) SetWires(v string, w []*circuits.Wire) error { alloc.Base = w[0].ID() } - prog.wires[v] = alloc + prog.wires[key] = alloc return nil } From 652eada655c3db5ae49811a9d29569b51b02450d Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Wed, 23 Aug 2023 10:33:12 +0200 Subject: [PATCH 02/19] Value Equal() and value type statistics. --- compiler/ssa/value.go | 25 +++++++++++++++--- compiler/ssa/wire_allocator.go | 46 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/compiler/ssa/value.go b/compiler/ssa/value.go index 67b2db2d..4c4a6c18 100644 --- a/compiler/ssa/value.go +++ b/compiler/ssa/value.go @@ -42,6 +42,17 @@ func (ptr PtrInfo) String() string { return fmt.Sprintf("*%s@%d", ptr.Name, ptr.Scope) } +// Equal tests if this PtrInfo is equal to the argument PtrInfo. +func (ptr *PtrInfo) Equal(o *PtrInfo) bool { + if ptr == nil { + return o == nil + } + if o == nil { + return false + } + return ptr.Name == o.Name && ptr.Scope == o.Scope && ptr.Offset == o.Offset +} + // Undefined defines an undefined value. var Undefined Value @@ -126,8 +137,12 @@ func (v *Value) HashCode() (hash int) { for r := range v.Name { hash = hash<<8 ^ int(r) ^ hash>>24 } - hash ^= int(v.Scope) << 15 - hash ^= int(v.Version) << 7 + hash ^= int(v.Type.Bits) << 5 + hash ^= int(v.Scope) << 3 + hash ^= int(v.Version) << 1 + if hash < 0 { + hash = -hash + } return } @@ -137,7 +152,11 @@ func (v *Value) Equal(other BindingValue) bool { if !ok { return false } - return o.Name == v.Name && o.Scope == v.Scope && o.Version == v.Version + if o.Name != v.Name || o.Scope != v.Scope || o.Version != v.Version || + v.Type.Bits != o.Type.Bits { + return false + } + return v.PtrInfo.Equal(o.PtrInfo) } // Value implements BindingValue.Value. diff --git a/compiler/ssa/wire_allocator.go b/compiler/ssa/wire_allocator.go index b870c7bf..6f5bcfd0 100644 --- a/compiler/ssa/wire_allocator.go +++ b/compiler/ssa/wire_allocator.go @@ -14,12 +14,35 @@ import ( "github.com/markkurossi/mpc/types" ) +var ( + vConst int + vTypeRef int + vPtr int + vDefault int + hash [1024]int +) + +func addValueStats(v Value) { + if v.Const { + vConst++ + } else if v.TypeRef { + vTypeRef++ + } else if v.Type.Type == types.TPtr { + vPtr++ + } else { + vDefault++ + } + + hash[v.HashCode()%len(hash)]++ +} + // Wires allocates unassigned wires for the argument value. func (prog *Program) Wires(v Value, bits types.Size) ( []*circuits.Wire, error) { if bits <= 0 { return nil, fmt.Errorf("size not set for value %v", v) } + addValueStats(v) key := v.String() alloc, ok := prog.wires[key] if !ok { @@ -35,6 +58,7 @@ func (prog *Program) AssignedWires(v Value, bits types.Size) ( if bits <= 0 { return nil, fmt.Errorf("size not set for value %v", v) } + addValueStats(v) key := v.String() alloc, ok := prog.wires[key] if !ok { @@ -112,6 +136,7 @@ func (prog *Program) GCWires(v Value) { // SetWires allocates wire IDs for the value's wires. func (prog *Program) SetWires(v Value, w []*circuits.Wire) error { + addValueStats(v) key := v.String() _, ok := prog.wires[key] if ok { @@ -151,4 +176,25 @@ func (prog *Program) StreamDebug() { fmt.Printf(" %d:\t%d\n", k, len(prog.freeWires[types.Size(k)])) } fmt.Println() + + fmt.Println("Value Stats:") + + sum := float64(vConst + vTypeRef + vPtr + vDefault) + + fmt.Printf(" - vConst:\t%v\t%f%%\n", vConst, float64(vConst)/sum*100) + fmt.Printf(" - vTypeRef:\t%v\t%f%%\n", vTypeRef, float64(vTypeRef)/sum*100) + fmt.Printf(" - vPtr:\t%v\t%f%%\n", vPtr, float64(vPtr)/sum*100) + fmt.Printf(" - vDefault:\t%v\t%f%%\n", vDefault, float64(vDefault)/sum*100) + + if false { + var zeroes int + for idx, count := range hash { + if count == 0 { + zeroes++ + } else { + fmt.Printf("%v:\t%v\n", idx, count) + } + } + fmt.Printf("%v zero buckets\n", zeroes) + } } From bccdf3b40d2f425a9bdcca02c6647c00bbf4c9ef Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Thu, 24 Aug 2023 15:13:33 +0200 Subject: [PATCH 03/19] WireAllocator interface. --- compiler/ssa/circuitgen.go | 73 +++---- compiler/ssa/program.go | 24 +-- compiler/ssa/streamer.go | 18 +- compiler/ssa/wire_allocator.go | 200 ++---------------- compiler/ssa/wire_allocator_string.go | 224 ++++++++++++++++++++ compiler/ssa/wire_allocator_value.go | 283 ++++++++++++++++++++++++++ 6 files changed, 574 insertions(+), 248 deletions(-) create mode 100644 compiler/ssa/wire_allocator_string.go create mode 100644 compiler/ssa/wire_allocator_value.go diff --git a/compiler/ssa/circuitgen.go b/compiler/ssa/circuitgen.go index d0d85574..a901df97 100644 --- a/compiler/ssa/circuitgen.go +++ b/compiler/ssa/circuitgen.go @@ -78,7 +78,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { instr := step.Instr var wires [][]*circuits.Wire for _, in := range instr.In { - w, err := prog.Wires(in, in.Type.Bits) + w, err := prog.walloc.Wires(in, in.Type.Bits) if err != nil { return err } @@ -86,7 +86,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } switch instr.Op { case Iadd, Uadd: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -96,7 +96,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Isub, Usub: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -106,7 +106,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Imult, Umult: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -117,7 +117,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Idiv, Udiv: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -128,7 +128,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Imod, Umod: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -158,10 +158,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } o[bit] = w } - err = prog.SetWires(*instr.Out, o) - if err != nil { - return err - } + prog.walloc.SetWires(*instr.Out, o) case Rshift, Srshift: var signWire *circuits.Wire @@ -189,10 +186,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } o[bit] = w } - err = prog.SetWires(*instr.Out, o) - if err != nil { - return err - } + prog.walloc.SetWires(*instr.Out, o) case Slice: from, err := instr.In[1].ConstInt() @@ -225,13 +219,10 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { for bit := to - from; int(bit) < len(o); bit++ { o[bit] = cc.ZeroWire() } - err = prog.SetWires(*instr.Out, o) - if err != nil { - return err - } + prog.walloc.SetWires(*instr.Out, o) case Index: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -247,7 +238,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Ilt, Ult: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -257,7 +248,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Ile, Ule: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -267,7 +258,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Igt, Ugt: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -277,7 +268,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Ige, Uge: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -287,7 +278,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Eq: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -297,7 +288,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Neq: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -312,7 +303,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { return fmt.Errorf("%s unsupported index type %T: %s", instr.Op, instr.In[1], err) } - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -327,7 +318,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { return fmt.Errorf("%s unsupported index type %T: %s", instr.Op, instr.In[1], err) } - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -337,7 +328,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case And: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -347,7 +338,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Or: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -357,7 +348,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Band: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -367,7 +358,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Bclr: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -377,7 +368,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Bor: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -387,7 +378,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Bxor: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -415,10 +406,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } o[bit] = w } - err := prog.SetWires(*instr.Out, o) - if err != nil { - return err - } + prog.walloc.SetWires(*instr.Out, o) case Amov: // v arr from to: @@ -457,13 +445,10 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } o[bit] = w } - err = prog.SetWires(*instr.Out, o) - if err != nil { - return err - } + prog.walloc.SetWires(*instr.Out, o) case Phi: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } @@ -502,7 +487,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { var circOut []*circuits.Wire for _, r := range instr.Ret { - o, err := prog.Wires(r, r.Type.Bits) + o, err := prog.walloc.Wires(r, r.Type.Bits) if err != nil { return err } @@ -549,7 +534,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { } case Builtin: - o, err := prog.Wires(*instr.Out, instr.Out.Type.Bits) + o, err := prog.walloc.Wires(*instr.Out, instr.Out.Type.Bits) if err != nil { return err } diff --git a/compiler/ssa/program.go b/compiler/ssa/program.go index 02fcd495..d5acc212 100644 --- a/compiler/ssa/program.go +++ b/compiler/ssa/program.go @@ -29,11 +29,7 @@ type Program struct { OutputWires []*circuits.Wire Constants map[string]ConstantInst Steps []Step - wires map[string]*wireAlloc - freeWires map[types.Size][][]*circuits.Wire - nextWireID uint32 - flHit int - flMiss int + walloc WireAllocator zeroWire *circuits.Wire oneWire *circuits.Wire stats circuit.Stats @@ -53,8 +49,7 @@ func NewProgram(params *utils.Params, in, out circuit.IO, Outputs: out, Constants: consts, Steps: steps, - wires: make(map[string]*wireAlloc), - freeWires: make(map[types.Size][][]*circuits.Wire), + walloc: NewWAllocValue(), } // Inputs into wires. @@ -62,8 +57,9 @@ func NewProgram(params *utils.Params, in, out circuit.IO, if len(arg.Name) == 0 { arg.Name = fmt.Sprintf("arg{%d}", idx) } - wires, err := prog.Wires(Value{ + wires, err := prog.walloc.Wires(Value{ Const: true, + Scope: 1, // Arguments are at scope 1. Name: arg.Name, }, types.Size(arg.Size)) if err != nil { @@ -240,8 +236,7 @@ func (prog *Program) DefineConstants(zero, one *circuits.Wire) error { var constWires int for _, c := range consts { - _, ok := prog.wires[c.String()] - if ok { + if prog.walloc.Allocated(c) { continue } @@ -258,10 +253,7 @@ func (prog *Program) DefineConstants(zero, one *circuits.Wire) error { wires = append(wires, w) } - err := prog.SetWires(c, wires) - if err != nil { - return err - } + prog.walloc.SetWires(c, wires) } if len(consts) > 0 && prog.Params.Verbose { fmt.Printf("Defined %d constants: %d wires\n", @@ -270,6 +262,10 @@ func (prog *Program) DefineConstants(zero, one *circuits.Wire) error { return nil } +func (prog *Program) StreamDebug() { + prog.walloc.Debug() +} + // PP pretty-prints the program to the argument io.Writer. func (prog *Program) PP(out io.Writer) { for i, in := range prog.Inputs { diff --git a/compiler/ssa/streamer.go b/compiler/ssa/streamer.go index ace07ece..fbba104d 100644 --- a/compiler/ssa/streamer.go +++ b/compiler/ssa/streamer.go @@ -67,8 +67,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, for _, w := range prog.InputWires { // Program's inputs are unassigned because parser is shared // between streaming and non-streaming modes. - w.SetID(prog.nextWireID) - prog.nextWireID++ + w.SetID(prog.walloc.NextWireID()) ids = append(ids, circuit.Wire(w.ID())) } @@ -177,7 +176,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, instr := step.Instr wires = wires[:0] for _, in := range instr.In { - w, err := prog.AssignedWires(in, in.Type.Bits) + w, err := prog.walloc.AssignedWires(in, in.Type.Bits) if err != nil { return nil, nil, err } @@ -187,7 +186,8 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, var out []*circuits.Wire var err error if instr.Out != nil { - out, err = prog.AssignedWires(*instr.Out, instr.Out.Type.Bits) + out, err = prog.walloc.AssignedWires(*instr.Out, + instr.Out.Type.Bits) if err != nil { return nil, nil, err } @@ -387,7 +387,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, } // Return wires. for i, ret := range instr.Ret { - wires, err := prog.AssignedWires(ret, ret.Type.Bits) + wires, err := prog.walloc.AssignedWires(ret, ret.Type.Bits) if err != nil { return nil, nil, err } @@ -415,7 +415,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, } case GC: - prog.GCWires(*instr.GC) + prog.walloc.GCWires(*instr.GC) default: f, ok := circuitGenerators[instr.Op] @@ -560,7 +560,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, } fmt.Printf("Max permanent wires: %d, cached circuits: %d\n", - prog.nextWireID, len(cache)) + prog.walloc.NextWireID(), len(cache)) fmt.Printf("#gates=%d (%s) #w=%d\n", prog.stats.Count(), prog.stats, prog.numWires) @@ -674,7 +674,7 @@ func (prog *Program) ZeroWire(conn *p2p.Conn, streaming *circuit.Streaming) ( *circuits.Wire, error) { if prog.zeroWire == nil { - wires, err := prog.AssignedWires(Value{ + wires, err := prog.walloc.AssignedWires(Value{ Const: true, Name: "{zero}", }, 1) @@ -723,7 +723,7 @@ func (prog *Program) OneWire(conn *p2p.Conn, streaming *circuit.Streaming) ( *circuits.Wire, error) { if prog.oneWire == nil { - wires, err := prog.AssignedWires(Value{ + wires, err := prog.walloc.AssignedWires(Value{ Const: true, Name: "{one}", }, 1) diff --git a/compiler/ssa/wire_allocator.go b/compiler/ssa/wire_allocator.go index 6f5bcfd0..7d0bb541 100644 --- a/compiler/ssa/wire_allocator.go +++ b/compiler/ssa/wire_allocator.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2020-2023 Markku Rossi +// Copyright (c) 2023 Markku Rossi // // All rights reserved. // @@ -7,194 +7,32 @@ package ssa import ( - "fmt" - "sort" - "github.com/markkurossi/mpc/compiler/circuits" "github.com/markkurossi/mpc/types" ) -var ( - vConst int - vTypeRef int - vPtr int - vDefault int - hash [1024]int -) - -func addValueStats(v Value) { - if v.Const { - vConst++ - } else if v.TypeRef { - vTypeRef++ - } else if v.Type.Type == types.TPtr { - vPtr++ - } else { - vDefault++ - } - - hash[v.HashCode()%len(hash)]++ -} - -// Wires allocates unassigned wires for the argument value. -func (prog *Program) Wires(v Value, bits types.Size) ( - []*circuits.Wire, error) { - if bits <= 0 { - return nil, fmt.Errorf("size not set for value %v", v) - } - addValueStats(v) - key := v.String() - alloc, ok := prog.wires[key] - if !ok { - alloc = prog.allocWires(bits) - prog.wires[key] = alloc - } - return alloc.Wires, nil -} - -// AssignedWires allocates assigned wires for the argument value. -func (prog *Program) AssignedWires(v Value, bits types.Size) ( - []*circuits.Wire, error) { - if bits <= 0 { - return nil, fmt.Errorf("size not set for value %v", v) - } - addValueStats(v) - key := v.String() - alloc, ok := prog.wires[key] - if !ok { - alloc = prog.allocWires(bits) - prog.wires[key] = alloc - - // Assign wire IDs. - if alloc.Base == circuits.UnassignedID { - alloc.Base = prog.nextWireID - for i := 0; i < int(bits); i++ { - alloc.Wires[i].SetID(prog.nextWireID + uint32(i)) - } - prog.nextWireID += uint32(bits) - } - } - - return alloc.Wires, nil -} - -type wireAlloc struct { - Base uint32 - Wires []*circuits.Wire -} - -func (prog *Program) allocWires(bits types.Size) *wireAlloc { - result := &wireAlloc{ - Base: circuits.UnassignedID, - } - - fl, ok := prog.freeWires[bits] - if ok && len(fl) > 0 { - result.Wires = fl[len(fl)-1] - result.Base = result.Wires[0].ID() - prog.freeWires[bits] = fl[:len(fl)-1] - prog.flHit++ - } else { - result.Wires = circuits.MakeWires(bits) - prog.flMiss++ - } - - return result -} - -// GCWires recycles the wires of the argument value. The wires must -// have been previously allocated with Wires, AssignedWires, or -// SetWires; the function panics if the wires have not been allocated. -func (prog *Program) GCWires(v Value) { - key := v.String() - alloc, ok := prog.wires[key] - if !ok { - panic(fmt.Sprintf("GC: %s not known", key)) - } - delete(prog.wires, key) - - if alloc.Base == circuits.UnassignedID { - alloc.Base = alloc.Wires[0].ID() - } - // Clear wires and reassign their IDs. - bits := types.Size(len(alloc.Wires)) - for i := 0; i < int(bits); i++ { - alloc.Wires[i].Reset(alloc.Base + uint32(i)) - } - - fl := prog.freeWires[bits] - fl = append(fl, alloc.Wires) - prog.freeWires[bits] = fl - if false { - fmt.Printf("FL: %d: ", bits) - for k, v := range prog.freeWires { - fmt.Printf(" %d:%d", k, len(v)) - } - fmt.Println() - } -} - -// SetWires allocates wire IDs for the value's wires. -func (prog *Program) SetWires(v Value, w []*circuits.Wire) error { - addValueStats(v) - key := v.String() - _, ok := prog.wires[key] - if ok { - return fmt.Errorf("wires already set for %v", key) - } - alloc := &wireAlloc{ - Wires: w, - } - if len(w) == 0 { - alloc.Base = circuits.UnassignedID - } else { - alloc.Base = w[0].ID() - } - - prog.wires[key] = alloc - - return nil -} - -// StreamDebug prints debugging information about the circuit -// streaming. -func (prog *Program) StreamDebug() { - total := float64(prog.flHit + prog.flMiss) - fmt.Printf("Wire freelist: hit=%v (%.2f%%), miss=%v (%.2f%%)\n", - prog.flHit, float64(prog.flHit)/total*100, - prog.flMiss, float64(prog.flMiss)/total*100) +type WireAllocator interface { + // Allocated tests if the wires have been allocated for the value. + Allocated(v Value) bool - var keys []types.Size - for k := range prog.freeWires { - keys = append(keys, k) - } - sort.Slice(keys, func(i, j int) bool { - return keys[i] < keys[j] - }) + // NextWireID allocated and returns the next unassigned wire ID. + NextWireID() uint32 - for _, k := range keys { - fmt.Printf(" %d:\t%d\n", k, len(prog.freeWires[types.Size(k)])) - } - fmt.Println() + // Wires allocates unassigned wires for the argument value. + Wires(v Value, bits types.Size) ([]*circuits.Wire, error) - fmt.Println("Value Stats:") + // AssignedWires allocates assigned wires for the argument value. + AssignedWires(v Value, bits types.Size) ([]*circuits.Wire, error) - sum := float64(vConst + vTypeRef + vPtr + vDefault) + // SetWires allocates wire IDs for the value's wires. + SetWires(v Value, w []*circuits.Wire) - fmt.Printf(" - vConst:\t%v\t%f%%\n", vConst, float64(vConst)/sum*100) - fmt.Printf(" - vTypeRef:\t%v\t%f%%\n", vTypeRef, float64(vTypeRef)/sum*100) - fmt.Printf(" - vPtr:\t%v\t%f%%\n", vPtr, float64(vPtr)/sum*100) - fmt.Printf(" - vDefault:\t%v\t%f%%\n", vDefault, float64(vDefault)/sum*100) + // GCWires recycles the wires of the argument value. The wires + // must have been previously allocated with Wires, AssignedWires, + // or SetWires; the function panics if the wires have not been + // allocated. + GCWires(v Value) - if false { - var zeroes int - for idx, count := range hash { - if count == 0 { - zeroes++ - } else { - fmt.Printf("%v:\t%v\n", idx, count) - } - } - fmt.Printf("%v zero buckets\n", zeroes) - } + // Debug prints debugging information about the wire allocator. + Debug() } diff --git a/compiler/ssa/wire_allocator_string.go b/compiler/ssa/wire_allocator_string.go new file mode 100644 index 00000000..ae188696 --- /dev/null +++ b/compiler/ssa/wire_allocator_string.go @@ -0,0 +1,224 @@ +// +// Copyright (c) 2020-2023 Markku Rossi +// +// All rights reserved. +// + +package ssa + +import ( + "fmt" + "sort" + + "github.com/markkurossi/mpc/compiler/circuits" + "github.com/markkurossi/mpc/types" +) + +type WAllocString struct { + freeWires map[types.Size][][]*circuits.Wire + wires map[string]*wireAlloc + nextWireID uint32 + flHit int + flMiss int +} + +func NewWAllocString() WireAllocator { + return &WAllocString{ + wires: make(map[string]*wireAlloc), + freeWires: make(map[types.Size][][]*circuits.Wire), + } +} + +var ( + vConst int + vTypeRef int + vPtr int + vDefault int + hash [1024]int +) + +func addValueStats(v Value) { + if v.Const { + vConst++ + } else if v.TypeRef { + vTypeRef++ + } else if v.Type.Type == types.TPtr { + vPtr++ + } else { + vDefault++ + } + + hash[v.HashCode()%len(hash)]++ +} + +// Allocated implements WireAllocator.Allocated. +func (walloc *WAllocString) Allocated(v Value) bool { + key := v.String() + _, ok := walloc.wires[key] + return ok +} + +// NextWireID implements WireAllocator.NextWireID. +func (walloc *WAllocString) NextWireID() uint32 { + ret := walloc.nextWireID + walloc.nextWireID++ + return ret +} + +// Wires implements WireAllocator.Wires. +func (walloc *WAllocString) Wires(v Value, bits types.Size) ( + []*circuits.Wire, error) { + if bits <= 0 { + return nil, fmt.Errorf("size not set for value %v", v) + } + addValueStats(v) + key := v.String() + alloc, ok := walloc.wires[key] + if !ok { + alloc = walloc.allocWires(bits) + walloc.wires[key] = alloc + } + return alloc.Wires, nil +} + +// AssignedWires implements WireAllocator.AssignedWires. +func (walloc *WAllocString) AssignedWires(v Value, bits types.Size) ( + []*circuits.Wire, error) { + if bits <= 0 { + return nil, fmt.Errorf("size not set for value %v", v) + } + addValueStats(v) + key := v.String() + alloc, ok := walloc.wires[key] + if !ok { + alloc = walloc.allocWires(bits) + walloc.wires[key] = alloc + + // Assign wire IDs. + if alloc.Base == circuits.UnassignedID { + alloc.Base = walloc.nextWireID + for i := 0; i < int(bits); i++ { + alloc.Wires[i].SetID(walloc.nextWireID + uint32(i)) + } + walloc.nextWireID += uint32(bits) + } + } + + return alloc.Wires, nil +} + +type wireAlloc struct { + Base uint32 + Wires []*circuits.Wire +} + +func (walloc *WAllocString) allocWires(bits types.Size) *wireAlloc { + result := &wireAlloc{ + Base: circuits.UnassignedID, + } + + fl, ok := walloc.freeWires[bits] + if ok && len(fl) > 0 { + result.Wires = fl[len(fl)-1] + result.Base = result.Wires[0].ID() + walloc.freeWires[bits] = fl[:len(fl)-1] + walloc.flHit++ + } else { + result.Wires = circuits.MakeWires(bits) + walloc.flMiss++ + } + + return result +} + +// SetWires implements WireAllocator.SetWires. +func (walloc *WAllocString) SetWires(v Value, w []*circuits.Wire) { + addValueStats(v) + key := v.String() + _, ok := walloc.wires[key] + if ok { + panic(fmt.Sprintf("wires already set for %v", key)) + } + alloc := &wireAlloc{ + Wires: w, + } + if len(w) == 0 { + alloc.Base = circuits.UnassignedID + } else { + alloc.Base = w[0].ID() + } + + walloc.wires[key] = alloc +} + +// GCWires implements WireAllocator.GCWires. +func (walloc *WAllocString) GCWires(v Value) { + key := v.String() + alloc, ok := walloc.wires[key] + if !ok { + panic(fmt.Sprintf("GC: %s not known", key)) + } + delete(walloc.wires, key) + + if alloc.Base == circuits.UnassignedID { + alloc.Base = alloc.Wires[0].ID() + } + // Clear wires and reassign their IDs. + bits := types.Size(len(alloc.Wires)) + for i := 0; i < int(bits); i++ { + alloc.Wires[i].Reset(alloc.Base + uint32(i)) + } + + fl := walloc.freeWires[bits] + fl = append(fl, alloc.Wires) + walloc.freeWires[bits] = fl + if false { + fmt.Printf("FL: %d: ", bits) + for k, v := range walloc.freeWires { + fmt.Printf(" %d:%d", k, len(v)) + } + fmt.Println() + } +} + +// Debug implements WireAllocator.Debug. +func (walloc *WAllocString) Debug() { + total := float64(walloc.flHit + walloc.flMiss) + fmt.Printf("Wire freelist: hit=%v (%.2f%%), miss=%v (%.2f%%)\n", + walloc.flHit, float64(walloc.flHit)/total*100, + walloc.flMiss, float64(walloc.flMiss)/total*100) + + var keys []types.Size + for k := range walloc.freeWires { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + fmt.Printf(" %d:\t%d\n", k, len(walloc.freeWires[types.Size(k)])) + } + fmt.Println() + + fmt.Println("Value Stats:") + + sum := float64(vConst + vTypeRef + vPtr + vDefault) + + fmt.Printf(" - vConst:\t%v\t%f%%\n", vConst, float64(vConst)/sum*100) + fmt.Printf(" - vTypeRef:\t%v\t%f%%\n", vTypeRef, float64(vTypeRef)/sum*100) + fmt.Printf(" - vPtr:\t%v\t%f%%\n", vPtr, float64(vPtr)/sum*100) + fmt.Printf(" - vDefault:\t%v\t%f%%\n", vDefault, float64(vDefault)/sum*100) + + if false { + var zeroes int + for idx, count := range hash { + if count == 0 { + zeroes++ + } else { + fmt.Printf("%v:\t%v\n", idx, count) + } + } + fmt.Printf("%v zero buckets\n", zeroes) + } +} diff --git a/compiler/ssa/wire_allocator_value.go b/compiler/ssa/wire_allocator_value.go new file mode 100644 index 00000000..0c276db8 --- /dev/null +++ b/compiler/ssa/wire_allocator_value.go @@ -0,0 +1,283 @@ +// +// Copyright (c) 2023 Markku Rossi +// +// All rights reserved. +// + +package ssa + +import ( + "fmt" + + "github.com/markkurossi/mpc/compiler/circuits" + "github.com/markkurossi/mpc/types" +) + +type WAllocValue struct { + freeWires map[types.Size][][]*circuits.Wire + wires [10240]*allocByValue + debug map[string]*allocByValue + nextWireID uint32 + flHit int + flMiss int +} + +type allocByValue struct { + next *allocByValue + key Value + base uint32 + wires []*circuits.Wire +} + +func (alloc *allocByValue) String() string { + return fmt.Sprintf("%v[%v]: base=%v, len(wires)=%v", + alloc.key.String(), alloc.key.Type, + alloc.base, len(alloc.wires)) +} + +func NewWAllocValue() WireAllocator { + return &WAllocValue{ + freeWires: make(map[types.Size][][]*circuits.Wire), + debug: make(map[string]*allocByValue), + } +} + +// Allocated implements WireAllocator.Allocated. +func (walloc *WAllocValue) Allocated(v Value) bool { + hash := v.HashCode() % len(walloc.wires) + alloc := walloc.lookup(hash, v) + return alloc != nil +} + +// NextWireID implements WireAllocator.NextWireID. +func (walloc *WAllocValue) NextWireID() uint32 { + ret := walloc.nextWireID + walloc.nextWireID++ + return ret +} + +func (walloc *WAllocValue) verify(v Value, expected *allocByValue) { + dbg, ok := walloc.debug[v.String()] + if expected == nil { + if ok { + panic(fmt.Sprintf("debug has key %v", v.String())) + } + if walloc.Allocated(v) { + panic(fmt.Sprintf("hash has key %v", v.String())) + } + return + } + if !ok { + panic(fmt.Sprintf("debug has not key %v", v.String())) + } + if !walloc.Allocated(v) { + panic(fmt.Sprintf("hash has not key %v", v.String())) + } + if dbg != expected { + fmt.Printf("debug %v != expected %v\n", dbg, expected) + fmt.Printf(" - debug.key: %v\n", dbg.key) + fmt.Printf(" - expected.key: %v\n", expected.key) + if dbg.key.Equal(&expected.key) { + fmt.Printf(" - keys Equal\n") + } else { + fmt.Printf(" - keys not Equal\n") + fmt.Printf(" - dbg: %v, expected: %v\n", + dbg.key.HashCode()%len(walloc.wires), + expected.key.HashCode()%len(walloc.wires)) + + } + panic("done") + } + if dbg.key.String() != v.String() { + panic("dbg.String() mismatch") + } + + hash := v.HashCode() % len(walloc.wires) + for alloc := walloc.wires[hash]; alloc != nil; alloc = alloc.next { + if alloc.key.Equal(&v) { + if alloc != expected { + panic("wires 1") + } + if alloc != dbg { + panic("wires 2") + } + if alloc.key.String() != v.String() { + panic("alloc.String() mismatch") + } + return + } + } + panic("wires not found") +} + +func (walloc *WAllocValue) verifyAdd(v Value, alloc *allocByValue) { + walloc.debug[v.String()] = alloc + walloc.verify(v, alloc) +} + +func (walloc *WAllocValue) lookup(hash int, v Value) *allocByValue { + const key = "g{1,0}struct1024" + var keyMatch bool + if v.String() == key { + fmt.Printf("*** lookup %v: Const=%v, Bits=%v, Scope=%v, Version=%v, hash=%v\n", + key, v.Const, v.Type.Bits, v.Scope, v.Version, hash) + keyMatch = true + } + for a := walloc.wires[hash]; a != nil; a = a.next { + if a.key.Equal(&v) { + if keyMatch { + fmt.Printf(" - found!\n") + } + return a + } + } + if keyMatch { + fmt.Printf(" - not found!\n") + } + return nil +} + +func (walloc *WAllocValue) remove(hash int, v Value) *allocByValue { + for ptr := &walloc.wires[hash]; *ptr != nil; ptr = &(*ptr).next { + if (*ptr).key.Equal(&v) { + ret := *ptr + delete(walloc.debug, v.String()) + *ptr = (*ptr).next + return ret + } + } + return nil +} + +func (walloc *WAllocValue) alloc(bits types.Size, v Value) *allocByValue { + result := &allocByValue{ + key: v, + base: circuits.UnassignedID, + } + + fl, ok := walloc.freeWires[bits] + if ok && len(fl) > 0 { + result.wires = fl[len(fl)-1] + result.base = result.wires[0].ID() + walloc.freeWires[bits] = fl[:len(fl)-1] + walloc.flHit++ + } else { + result.wires = circuits.MakeWires(bits) + walloc.flMiss++ + } + + return result +} + +// Wires implements WireAllocator.Wires. +func (walloc *WAllocValue) Wires(v Value, bits types.Size) ( + []*circuits.Wire, error) { + if bits <= 0 { + return nil, fmt.Errorf("size not set for value %v", v) + } + hash := v.HashCode() % len(walloc.wires) + alloc := walloc.lookup(hash, v) + if alloc == nil { + _, ok := walloc.debug[v.String()] + if ok { + panic(fmt.Sprintf("Wires: dbg has %v, TypeRef=%v", + v.String(), v.TypeRef)) + } + + alloc = walloc.alloc(bits, v) + alloc.next = walloc.wires[hash] + walloc.wires[hash] = alloc + walloc.verifyAdd(v, alloc) + } + walloc.verify(v, alloc) + return alloc.wires, nil +} + +// AssignedWires implements WireAllocator.AssignedWires. +func (walloc *WAllocValue) AssignedWires(v Value, bits types.Size) ( + []*circuits.Wire, error) { + if bits <= 0 { + return nil, fmt.Errorf("size not set for value %v", v) + } + hash := v.HashCode() % len(walloc.wires) + alloc := walloc.lookup(hash, v) + if alloc == nil { + _, ok := walloc.debug[v.String()] + if ok { + panic(fmt.Sprintf("AssignedWires: dbg has %v: Const=%v, Bits=%v, Scope=%v, Version=%v", + v.String(), v.Const, v.Type.Bits, v.Scope, v.Version)) + } + alloc = walloc.alloc(bits, v) + alloc.next = walloc.wires[hash] + walloc.wires[hash] = alloc + walloc.verifyAdd(v, alloc) + + // Assign wire IDs. + if alloc.base == circuits.UnassignedID { + alloc.base = walloc.nextWireID + for i := 0; i < int(bits); i++ { + alloc.wires[i].SetID(walloc.nextWireID + uint32(i)) + } + walloc.nextWireID += uint32(bits) + } + } + walloc.verify(v, alloc) + return alloc.wires, nil +} + +// SetWires implements WireAllocator.SetWires. +func (walloc *WAllocValue) SetWires(v Value, w []*circuits.Wire) { + hash := v.HashCode() % len(walloc.wires) + alloc := walloc.lookup(hash, v) + if alloc != nil { + panic(fmt.Sprintf("wires already set for %v", v)) + } + alloc = &allocByValue{ + key: v, + wires: w, + } + if len(w) == 0 { + alloc.base = circuits.UnassignedID + } else { + alloc.base = w[0].ID() + } + + alloc.next = walloc.wires[hash] + walloc.wires[hash] = alloc + walloc.verifyAdd(v, alloc) + walloc.verify(v, alloc) +} + +// GCWires implements WireAllocator.GCWires. +func (walloc *WAllocValue) GCWires(v Value) { + hash := v.HashCode() % len(walloc.wires) + alloc := walloc.remove(hash, v) + if alloc == nil { + panic(fmt.Sprintf("GC: %s not known", v)) + } + walloc.verify(v, nil) + + if alloc.base == circuits.UnassignedID { + alloc.base = alloc.wires[0].ID() + } + // Clear wires and reassign their IDs. + bits := types.Size(len(alloc.wires)) + for i := 0; i < int(bits); i++ { + alloc.wires[i].Reset(alloc.base + uint32(i)) + } + + fl := walloc.freeWires[bits] + fl = append(fl, alloc.wires) + walloc.freeWires[bits] = fl + if false { + fmt.Printf("FL: %d: ", bits) + for k, v := range walloc.freeWires { + fmt.Printf(" %d:%d", k, len(v)) + } + fmt.Println() + } +} + +// Debug implements WireAllocator.Debug. +func (walloc *WAllocValue) Debug() { +} From 9b6314ced56ea961f97d316a94a4e029bf56e0f8 Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Fri, 25 Aug 2023 08:49:35 +0200 Subject: [PATCH 04/19] Updated documentation. --- compiler/ssa/program.go | 3 ++- compiler/ssa/wire_allocator.go | 1 + compiler/ssa/wire_allocator_string.go | 3 +++ compiler/ssa/wire_allocator_value.go | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/compiler/ssa/program.go b/compiler/ssa/program.go index d5acc212..a2c5d731 100644 --- a/compiler/ssa/program.go +++ b/compiler/ssa/program.go @@ -49,7 +49,7 @@ func NewProgram(params *utils.Params, in, out circuit.IO, Outputs: out, Constants: consts, Steps: steps, - walloc: NewWAllocValue(), + walloc: NewWAllocString(), } // Inputs into wires. @@ -262,6 +262,7 @@ func (prog *Program) DefineConstants(zero, one *circuits.Wire) error { return nil } +// StreamDebug print debugging information about streaming mode. func (prog *Program) StreamDebug() { prog.walloc.Debug() } diff --git a/compiler/ssa/wire_allocator.go b/compiler/ssa/wire_allocator.go index 7d0bb541..3dc4c8dc 100644 --- a/compiler/ssa/wire_allocator.go +++ b/compiler/ssa/wire_allocator.go @@ -11,6 +11,7 @@ import ( "github.com/markkurossi/mpc/types" ) +// WireAllocator implements dynamic wire allocation. type WireAllocator interface { // Allocated tests if the wires have been allocated for the value. Allocated(v Value) bool diff --git a/compiler/ssa/wire_allocator_string.go b/compiler/ssa/wire_allocator_string.go index ae188696..3425be0c 100644 --- a/compiler/ssa/wire_allocator_string.go +++ b/compiler/ssa/wire_allocator_string.go @@ -14,6 +14,8 @@ import ( "github.com/markkurossi/mpc/types" ) +// WAllocString implements WireAllocator using Value.String to map +// values to wires. type WAllocString struct { freeWires map[types.Size][][]*circuits.Wire wires map[string]*wireAlloc @@ -22,6 +24,7 @@ type WAllocString struct { flMiss int } +// NewWAllocString creates a new WAllocString. func NewWAllocString() WireAllocator { return &WAllocString{ wires: make(map[string]*wireAlloc), diff --git a/compiler/ssa/wire_allocator_value.go b/compiler/ssa/wire_allocator_value.go index 0c276db8..fed628ee 100644 --- a/compiler/ssa/wire_allocator_value.go +++ b/compiler/ssa/wire_allocator_value.go @@ -13,6 +13,8 @@ import ( "github.com/markkurossi/mpc/types" ) +// WAllocValue implements WireAllocator using Value.HashCode to map +// values to wires. type WAllocValue struct { freeWires map[types.Size][][]*circuits.Wire wires [10240]*allocByValue @@ -35,6 +37,7 @@ func (alloc *allocByValue) String() string { alloc.base, len(alloc.wires)) } +// NewWAllocValue creates a new WAllocValue. func NewWAllocValue() WireAllocator { return &WAllocValue{ freeWires: make(map[types.Size][][]*circuits.Wire), From 7bde8af16f8cd1409c28d30e3df3be39b872c966 Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Fri, 25 Aug 2023 09:14:28 +0200 Subject: [PATCH 05/19] Changed circuit.IOArg to use types.Info instead of string as argument type. --- apps/garbled/main.go | 80 ++++++++++--------- circuit/circuit.go | 154 +----------------------------------- circuit/ioarg.go | 122 ++++++++++++++++++++++++++++ circuit/marshal.go | 4 +- circuit/parser.go | 19 ++++- circuit/stream_evaluator.go | 6 +- compiler/ast/package.go | 8 +- compiler/compiler.go | 2 +- compiler/ssa/streamer.go | 22 ++++-- types/parse.go | 9 ++- 10 files changed, 218 insertions(+), 208 deletions(-) create mode 100644 circuit/ioarg.go diff --git a/apps/garbled/main.go b/apps/garbled/main.go index 821edf49..6ae5e7b6 100644 --- a/apps/garbled/main.go +++ b/apps/garbled/main.go @@ -27,6 +27,7 @@ import ( "github.com/markkurossi/mpc/compiler/utils" "github.com/markkurossi/mpc/ot" "github.com/markkurossi/mpc/p2p" + "github.com/markkurossi/mpc/types" ) var ( @@ -407,7 +408,8 @@ func printResults(results []*big.Int, outputs circuit.IO) { func printResult(result *big.Int, output circuit.IOArg, short bool) string { var str string - if strings.HasPrefix(output.Type, "string") { + switch output.Type.Type { + case types.TString: mask := big.NewInt(0xff) for i := 0; i < output.Size/8; i++ { @@ -419,11 +421,10 @@ func printResult(result *big.Int, output circuit.IOArg, short bool) string { str += fmt.Sprintf("\\u%04x", r) } } - } else if strings.HasPrefix(output.Type, "uint") || - strings.HasPrefix(output.Type, "int") { - if output.Type[0] == 'i' { - bits := circuit.Size(output.Type) + case types.TUint, types.TInt: + if output.Type.Type == types.TInt { + bits := int(output.Type.Bits) if result.Bit(bits-1) == 1 { // Negative number. tmp := new(big.Int) @@ -444,42 +445,49 @@ func printResult(result *big.Int, output circuit.IOArg, short bool) string { } else { str = fmt.Sprintf("0x%x", bytes) } - } else if strings.HasPrefix(output.Type, "bool") { + + case types.TBool: str = fmt.Sprintf("%v", result.Uint64() != 0) - } else { - ok, count, elSize, elType := circuit.ParseArrayType(output.Type) - if ok { - mask := new(big.Int) - for i := 0; i < elSize; i++ { - mask.SetBit(mask, i, 1) - } - hexString := elType == "uint8" - if !hexString { - str = "[" - } - for i := 0; i < count; i++ { - r := new(big.Int).Rsh(result, uint(i*elSize)) - r = r.And(r, mask) - - if hexString { - str += fmt.Sprintf("%02x", r.Int64()) - } else { - if i > 0 { - str += " " - } - str += printResult(r, circuit.IOArg{ - Type: elType, - Size: elSize, - }, true) + case types.TArray: + count := int(output.Type.ArraySize) + elSize := int(output.Type.ElementType.Bits) + + mask := new(big.Int) + for i := 0; i < elSize; i++ { + mask.SetBit(mask, i, 1) + } + + var hexString bool + if output.Type.ElementType.Type == types.TUint && + output.Type.ElementType.Bits == 8 { + hexString = true + } + if !hexString { + str = "[" + } + for i := 0; i < count; i++ { + r := new(big.Int).Rsh(result, uint(i*elSize)) + r = r.And(r, mask) + + if hexString { + str += fmt.Sprintf("%02x", r.Int64()) + } else { + if i > 0 { + str += " " } + str += printResult(r, circuit.IOArg{ + Type: *output.Type.ElementType, + Size: int(output.Type.ElementType.Bits), + }, true) } - if !hexString { - str += "]" - } - } else { - str = fmt.Sprintf("%v (%s)", result, output.Type) } + if !hexString { + str += "]" + } + + default: + str = fmt.Sprintf("%v (%s)", result, output.Type) } return str diff --git a/circuit/circuit.go b/circuit/circuit.go index 72cb6021..6dc1e6aa 100644 --- a/circuit/circuit.go +++ b/circuit/circuit.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2019-2022 Markku Rossi +// Copyright (c) 2019-2023 Markku Rossi // // All rights reserved. // @@ -11,9 +11,6 @@ import ( "io" "math" "math/big" - "regexp" - "strconv" - "strings" "github.com/markkurossi/tabulate" ) @@ -100,153 +97,6 @@ func (op Operation) String() string { } } -// IOArg describes circuit input argument. -type IOArg struct { - Name string - Type string - Size int - Compound IO -} - -func (io IOArg) String() string { - if len(io.Compound) > 0 { - return io.Compound.String() - } - - if len(io.Name) > 0 { - return io.Name + ":" + io.Type - } - return io.Type -} - -// Parse parses the I/O argument from the input string values. -func (io IOArg) Parse(inputs []string) (*big.Int, error) { - result := new(big.Int) - - if len(io.Compound) == 0 { - if len(inputs) != 1 { - return nil, - fmt.Errorf("invalid amount of arguments, got %d, expected 1", - len(inputs)) - } - - if strings.HasPrefix(io.Type, "u") || strings.HasPrefix(io.Type, "i") { - _, ok := result.SetString(inputs[0], 0) - if !ok { - return nil, fmt.Errorf("invalid input: %s", inputs[0]) - } - } else if io.Type == "bool" { - switch inputs[0] { - case "0", "f", "false": - case "1", "t", "true": - result.SetInt64(1) - default: - return nil, fmt.Errorf("invalid bool constant: %s", inputs[0]) - } - } else { - ok, count, elSize, _ := ParseArrayType(io.Type) - if ok { - val := new(big.Int) - _, ok := val.SetString(inputs[0], 0) - if !ok { - return nil, fmt.Errorf("invalid input: %s", inputs[0]) - } - - valElCount := val.BitLen() / elSize - if val.BitLen()%elSize != 0 { - valElCount++ - } - if valElCount > count { - return nil, fmt.Errorf("too many values for input: %s", - inputs[0]) - } - pad := count - valElCount - val.Lsh(val, uint(pad*elSize)) - - mask := new(big.Int) - for i := 0; i < elSize; i++ { - mask.SetBit(mask, i, 1) - } - - for i := 0; i < count; i++ { - next := new(big.Int).Rsh(val, uint((count-i-1)*elSize)) - next = next.And(next, mask) - - next.Lsh(next, uint(i*elSize)) - result.Or(result, next) - } - } else { - return nil, fmt.Errorf("unsupported input type: %s", io.Type) - } - } - - return result, nil - } - if len(inputs) != len(io.Compound) { - return nil, - fmt.Errorf("invalid amount of arguments, got %d, expected %d", - len(inputs), len(io.Compound)) - } - - var offset int - - for idx, arg := range io.Compound { - input, err := arg.Parse(inputs[idx : idx+1]) - if err != nil { - return nil, err - } - - input.Lsh(input, uint(offset)) - result.Or(result, input) - - offset += arg.Size - } - return result, nil -} - -var reArr = regexp.MustCompilePOSIX(`^\[([[:digit:]]+)\](.+)$`) -var reSized = regexp.MustCompilePOSIX(`^[[:^digit:]]+([[:digit:]]+)$`) - -// ParseArrayType parses the argument value as array type. -func ParseArrayType(val string) (ok bool, count, elementSize int, - elementType string) { - - matches := reArr.FindStringSubmatch(val) - if matches == nil { - return - } - var err error - count, err = strconv.Atoi(matches[1]) - if err != nil { - panic(fmt.Sprintf("invalid array size: %s", matches[1])) - } - ok = true - elementSize = Size(matches[2]) - elementType = matches[2] - return -} - -// Size returns the type size in bits. -func Size(t string) int { - matches := reArr.FindStringSubmatch(t) - if matches != nil { - count, err := strconv.Atoi(matches[1]) - if err != nil { - panic(fmt.Sprintf("invalid array size: %s", matches[1])) - } - return count * Size(matches[2]) - } - matches = reSized.FindStringSubmatch(t) - if matches == nil { - panic(fmt.Sprintf("invalid type: %s", t)) - } - bits, err := strconv.Atoi(matches[1]) - if err != nil { - panic(fmt.Sprintf("invalid bit count: %s", matches[1])) - } - return bits -} - // IO specifies circuit input and output arguments. type IO []IOArg @@ -269,7 +119,7 @@ func (io IO) String() string { if len(a.Name) > 0 { str += a.Name + ":" } - str += a.Type + str += a.Type.String() } return str } diff --git a/circuit/ioarg.go b/circuit/ioarg.go new file mode 100644 index 00000000..fd11fead --- /dev/null +++ b/circuit/ioarg.go @@ -0,0 +1,122 @@ +// +// Copyright (c) 2019-2023 Markku Rossi +// +// All rights reserved. +// + +package circuit + +import ( + "fmt" + "math/big" + + "github.com/markkurossi/mpc/types" +) + +// IOArg describes circuit input argument. +type IOArg struct { + Name string + Type types.Info + Size int + Compound IO +} + +func (io IOArg) String() string { + if len(io.Compound) > 0 { + return io.Compound.String() + } + + if len(io.Name) > 0 { + return io.Name + ":" + io.Type.String() + } + return io.Type.String() +} + +// Parse parses the I/O argument from the input string values. +func (io IOArg) Parse(inputs []string) (*big.Int, error) { + result := new(big.Int) + + if len(io.Compound) == 0 { + if len(inputs) != 1 { + return nil, + fmt.Errorf("invalid amount of arguments, got %d, expected 1", + len(inputs)) + } + + switch io.Type.Type { + case types.TInt, types.TUint: + _, ok := result.SetString(inputs[0], 0) + if !ok { + return nil, fmt.Errorf("invalid input: %s", inputs[0]) + } + + case types.TBool: + switch inputs[0] { + case "0", "f", "false": + case "1", "t", "true": + result.SetInt64(1) + default: + return nil, fmt.Errorf("invalid bool constant: %s", inputs[0]) + } + + case types.TArray: + count := int(io.Type.ArraySize) + elSize := int(io.Type.ElementType.Bits) + + val := new(big.Int) + _, ok := val.SetString(inputs[0], 0) + if !ok { + return nil, fmt.Errorf("invalid input: %s", inputs[0]) + } + + valElCount := val.BitLen() / elSize + if val.BitLen()%elSize != 0 { + valElCount++ + } + if valElCount > count { + return nil, fmt.Errorf("too many values for input: %s", + inputs[0]) + } + pad := count - valElCount + val.Lsh(val, uint(pad*elSize)) + + mask := new(big.Int) + for i := 0; i < elSize; i++ { + mask.SetBit(mask, i, 1) + } + + for i := 0; i < count; i++ { + next := new(big.Int).Rsh(val, uint((count-i-1)*elSize)) + next = next.And(next, mask) + + next.Lsh(next, uint(i*elSize)) + result.Or(result, next) + } + + default: + return nil, fmt.Errorf("unsupported input type: %s", io.Type) + } + + return result, nil + } + if len(inputs) != len(io.Compound) { + return nil, + fmt.Errorf("invalid amount of arguments, got %d, expected %d", + len(inputs), len(io.Compound)) + } + + var offset int + + for idx, arg := range io.Compound { + input, err := arg.Parse(inputs[idx : idx+1]) + if err != nil { + return nil, err + } + + input.Lsh(input, uint(offset)) + result.Or(result, input) + + offset += arg.Size + } + return result, nil +} diff --git a/circuit/marshal.go b/circuit/marshal.go index 1249c915..3a88a5d6 100644 --- a/circuit/marshal.go +++ b/circuit/marshal.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2020-2021 Markku Rossi +// Copyright (c) 2020-2021, 2023 Markku Rossi // // All rights reserved. // @@ -87,7 +87,7 @@ func marshalIOArg(out io.Writer, arg IOArg) error { if err := marshalString(out, arg.Name); err != nil { return err } - if err := marshalString(out, arg.Type); err != nil { + if err := marshalString(out, arg.Type.String()); err != nil { return err } if err := binary.Write(out, bo, uint32(arg.Size)); err != nil { diff --git a/circuit/parser.go b/circuit/parser.go index 87465e21..e58fe186 100644 --- a/circuit/parser.go +++ b/circuit/parser.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2019-2022 Markku Rossi +// Copyright (c) 2019-2023 Markku Rossi // // All rights reserved. // @@ -17,6 +17,8 @@ import ( "regexp" "strconv" "strings" + + "github.com/markkurossi/mpc/types" ) var reParts = regexp.MustCompilePOSIX("[[:space:]]+") @@ -222,7 +224,10 @@ func parseIOArg(r *bufio.Reader) (arg IOArg, err error) { return arg, err } arg.Name = name - arg.Type = t + arg.Type, err = types.Parse(t) + if err != nil { + return arg, err + } arg.Size = int(ui32) // Compound @@ -309,7 +314,10 @@ func ParseBristol(in io.Reader) (*Circuit, error) { } inputs = append(inputs, IOArg{ Name: fmt.Sprintf("NI%d", i), - Type: fmt.Sprintf("u%d", bits), + Type: types.Info{ + Type: types.TUint, + Bits: types.Size(bits), + }, Size: bits, }) inputWires += bits @@ -348,7 +356,10 @@ func ParseBristol(in io.Reader) (*Circuit, error) { } outputs = append(outputs, IOArg{ Name: fmt.Sprintf("NO%d", i), - Type: fmt.Sprintf("u%d", bits), + Type: types.Info{ + Type: types.TUint, + Bits: types.Size(bits), + }, Size: bits, }) } diff --git a/circuit/stream_evaluator.go b/circuit/stream_evaluator.go index 5d2017c5..5c693502 100644 --- a/circuit/stream_evaluator.go +++ b/circuit/stream_evaluator.go @@ -15,6 +15,7 @@ import ( "github.com/markkurossi/mpc/ot" "github.com/markkurossi/mpc/p2p" + "github.com/markkurossi/mpc/types" ) // Protocol operation codes. @@ -474,7 +475,10 @@ func receiveArgument(conn *p2p.Conn) (arg IOArg, err error) { return arg, err } arg.Name = name - arg.Type = t + arg.Type, err = types.Parse(t) + if err != nil { + return arg, err + } arg.Size = size count, err := conn.ReceiveUint32() diff --git a/compiler/ast/package.go b/compiler/ast/package.go index 71f3797e..7662d5a6 100644 --- a/compiler/ast/package.go +++ b/compiler/ast/package.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2020-2022 Markku Rossi +// Copyright (c) 2020-2023 Markku Rossi // // All rights reserved. // @@ -83,7 +83,7 @@ func (pkg *Package) Compile(ctx *Codegen) (*ssa.Program, Annotations, error) { arg := circuit.IOArg{ Name: a.String(), - Type: a.Type.String(), + Type: a.Type, Size: int(a.Type.Bits), } if typeInfo.Type == types.TStruct { @@ -128,7 +128,7 @@ func (pkg *Package) Compile(ctx *Codegen) (*ssa.Program, Annotations, error) { v := returnVars[idx] outputs = append(outputs, circuit.IOArg{ Name: v.String(), - Type: v.Type.String(), + Type: v.Type, Size: int(v.Type.Bits), }) } @@ -171,7 +171,7 @@ func flattenStruct(t types.Info) circuit.IO { } else { result = append(result, circuit.IOArg{ Name: f.Name, - Type: f.Type.String(), + Type: f.Type, Size: int(f.Type.Bits), }) } diff --git a/compiler/compiler.go b/compiler/compiler.go index 2ed7c596..e8aa1d50 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -148,7 +148,7 @@ func (c *Compiler) stream(conn *p2p.Conn, oti ot.OT, source string, if err != nil { return nil, nil, err } - if false { + if true { program.StreamDebug() } return out, bits, err diff --git a/compiler/ssa/streamer.go b/compiler/ssa/streamer.go index fbba104d..f5679a2a 100644 --- a/compiler/ssa/streamer.go +++ b/compiler/ssa/streamer.go @@ -687,14 +687,20 @@ func (prog *Program) ZeroWire(conn *p2p.Conn, streaming *circuit.Streaming) ( Inputs: []circuit.IOArg{ { Name: "i0", - Type: "uint1", + Type: types.Info{ + Type: types.TUint, + Bits: 1, + }, Size: 1, }, }, Outputs: []circuit.IOArg{ { Name: "o0", - Type: "uint1", + Type: types.Info{ + Type: types.TUint, + Bits: 1, + }, Size: 1, }, }, @@ -736,14 +742,20 @@ func (prog *Program) OneWire(conn *p2p.Conn, streaming *circuit.Streaming) ( Inputs: []circuit.IOArg{ { Name: "i0", - Type: "uint1", + Type: types.Info{ + Type: types.TUint, + Bits: 1, + }, Size: 1, }, }, Outputs: []circuit.IOArg{ { Name: "o0", - Type: "uint1", + Type: types.Info{ + Type: types.TUint, + Bits: 1, + }, Size: 1, }, }, @@ -771,7 +783,7 @@ func sendArgument(conn *p2p.Conn, arg circuit.IOArg) error { if err := conn.SendString(arg.Name); err != nil { return err } - if err := conn.SendString(arg.Type); err != nil { + if err := conn.SendString(arg.Type.String()); err != nil { return err } if err := conn.SendUint32(arg.Size); err != nil { diff --git a/types/parse.go b/types/parse.go index d16da956..13ec3b54 100644 --- a/types/parse.go +++ b/types/parse.go @@ -1,7 +1,7 @@ // // parse.go // -// Copyright (c) 2021 Markku Rossi +// Copyright (c) 2021-2023 Markku Rossi // // All rights reserved. // @@ -49,8 +49,11 @@ func Parse(val string) (info Info, err error) { case "s", "string": info.Type = TString + case "struct": + info.Type = TStruct + default: - return info, fmt.Errorf("unknown type: %s", val) + return info, fmt.Errorf("types.Parse: unknown type: %s", val) } var bits int64 if len(m[2]) > 0 { @@ -66,7 +69,7 @@ func Parse(val string) (info Info, err error) { m = reArr.FindStringSubmatch(val) if m == nil { - return info, fmt.Errorf("unknown type: %s", val) + return info, fmt.Errorf("types.Parse: unknown type: %s", val) } var elType Info elType, err = Parse(m[2]) From 66fc61c0286c2f8f43e24b46b07c3376f30b5a19 Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Fri, 25 Aug 2023 09:46:04 +0200 Subject: [PATCH 06/19] Use circuit.IOArg.Type.Bits instead of circuit.IOArg.Size. --- apps/garbled/examples/aesblock.mpcl | 11 +++++++ apps/garbled/examples/aesblock2.mpcl | 11 +++++++ apps/garbled/examples/aesexpand.mpcl | 11 +++++++ apps/garbled/examples/encrypt.mpcl | 43 ++++++++++++++++++++++++++++ apps/garbled/main.go | 5 ++-- bmr/player.go | 6 ++-- circuit/circuit.go | 4 +-- circuit/computer.go | 6 ++-- circuit/dot.go | 4 +-- circuit/evaluator.go | 12 ++++---- circuit/garbler.go | 5 ++-- circuit/ioarg.go | 3 +- circuit/marshal.go | 6 ++-- circuit/parser.go | 8 ++---- circuit/stream_evaluator.go | 13 +++++---- compiler/ast/builtin.go | 8 +++--- compiler/ast/package.go | 3 -- compiler/circuits/circuits_test.go | 6 +++- compiler/ssa/circuitgen.go | 4 +-- compiler/ssa/program.go | 2 +- compiler/ssa/streamer.go | 16 ++++------- 21 files changed, 129 insertions(+), 58 deletions(-) create mode 100644 apps/garbled/examples/aesblock.mpcl create mode 100644 apps/garbled/examples/aesblock2.mpcl create mode 100644 apps/garbled/examples/aesexpand.mpcl create mode 100644 apps/garbled/examples/encrypt.mpcl diff --git a/apps/garbled/examples/aesblock.mpcl b/apps/garbled/examples/aesblock.mpcl new file mode 100644 index 00000000..51c772c8 --- /dev/null +++ b/apps/garbled/examples/aesblock.mpcl @@ -0,0 +1,11 @@ +// -*- go -*- + +package main + +import ( + "crypto/aes" +) + +func main(key, data [16]byte) []byte { + return aes.EncryptBlock(key, data) +} diff --git a/apps/garbled/examples/aesblock2.mpcl b/apps/garbled/examples/aesblock2.mpcl new file mode 100644 index 00000000..afb72a8f --- /dev/null +++ b/apps/garbled/examples/aesblock2.mpcl @@ -0,0 +1,11 @@ +// -*- go -*- + +package main + +import ( + "crypto/aes" +) + +func main(key, data [16]byte) []byte { + return aes.Block128(key, data) +} diff --git a/apps/garbled/examples/aesexpand.mpcl b/apps/garbled/examples/aesexpand.mpcl new file mode 100644 index 00000000..8d2786b2 --- /dev/null +++ b/apps/garbled/examples/aesexpand.mpcl @@ -0,0 +1,11 @@ +// -*- go -*- + +package main + +import ( + "crypto/aes" +) + +func main(key, data [16]byte) []uint { + return aes.ExpandEncryptionKey(key) +} diff --git a/apps/garbled/examples/encrypt.mpcl b/apps/garbled/examples/encrypt.mpcl new file mode 100644 index 00000000..191a5ebe --- /dev/null +++ b/apps/garbled/examples/encrypt.mpcl @@ -0,0 +1,43 @@ +// -*- go -*- + +// Run the Evaluator with two inputs: evaluator's key and nonce shares: +// +// $ ./garbled -e -i 0x8cd98b88adab08d6d60fe57c8b8a33f3,0xfd5e0f8f155e7102aa526ad0 examples/encrypt.mpcl +// +// The Garbler takes three arguments: the message to encrypt, and its +// key and noce shares: +// +// $ ./garbled -i 0x48656c6c6f2c20776f726c6421,0xed800b17b0c9d2334b249332155ddef5,0xa300751458c775a08762c2cd examples/encrypt.mpcl + +package main + +import ( + "crypto/cipher/gcm" +) + +type Garbler struct { + msg [64]byte + keyShare [16]byte + nonceShare [12]byte +} + +type Evaluator struct { + keyShare [16]byte + nonceShare [12]byte +} + +func main(g Garbler, e Evaluator) []byte { + var key [16]byte + + for i := 0; i < len(key); i++ { + key[i] = g.keyShare[i] ^ e.keyShare[i] + } + + var nonce [12]byte + + for i := 0; i < len(nonce); i++ { + nonce[i] = g.nonceShare[i] ^ e.nonceShare[i] + } + + return gcm.EncryptAES128(key, nonce, g.msg, []byte("unused")) +} diff --git a/apps/garbled/main.go b/apps/garbled/main.go index 6ae5e7b6..a57faa70 100644 --- a/apps/garbled/main.go +++ b/apps/garbled/main.go @@ -412,7 +412,7 @@ func printResult(result *big.Int, output circuit.IOArg, short bool) string { case types.TString: mask := big.NewInt(0xff) - for i := 0; i < output.Size/8; i++ { + for i := 0; i < int(output.Type.Bits)/8; i++ { tmp := new(big.Int).Rsh(result, uint(i*8)) r := rune(tmp.And(tmp, mask).Uint64()) if unicode.IsPrint(r) { @@ -440,7 +440,7 @@ func printResult(result *big.Int, output circuit.IOArg, short bool) string { } if short { str = fmt.Sprintf("%v", result) - } else if output.Size <= 64 { + } else if output.Type.Bits <= 64 { str = fmt.Sprintf("0x%x\t%v", bytes, result) } else { str = fmt.Sprintf("0x%x", bytes) @@ -478,7 +478,6 @@ func printResult(result *big.Int, output circuit.IOArg, short bool) string { } str += printResult(r, circuit.IOArg{ Type: *output.Type.ElementType, - Size: int(output.Type.ElementType.Bits), }, true) } } diff --git a/bmr/player.go b/bmr/player.go index ac7cf532..883f18e0 100644 --- a/bmr/player.go +++ b/bmr/player.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Markku Rossi +// Copyright (c) 2022-2023 Markku Rossi // // All rights reserved. // @@ -86,11 +86,11 @@ func (p *Player) offlinePhase() error { var inputIndex int for id, input := range p.c.Inputs { if id != p.id { - for i := 0; i < input.Size; i++ { + for i := 0; i < int(input.Type.Bits); i++ { p.lambda.SetBit(p.lambda, inputIndex+i, 0) } } - inputIndex += input.Size + inputIndex += int(input.Type.Bits) } wires := make([]Wire, p.c.NumWires) diff --git a/circuit/circuit.go b/circuit/circuit.go index 6dc1e6aa..663f0b49 100644 --- a/circuit/circuit.go +++ b/circuit/circuit.go @@ -105,7 +105,7 @@ type IO []IOArg func (io IO) Size() int { var sum int for _, a := range io { - sum += a.Size + sum += int(a.Type.Bits) } return sum } @@ -130,7 +130,7 @@ func (io IO) Split(in *big.Int) []*big.Int { var bit int for _, arg := range io { r := big.NewInt(0) - for i := 0; i < arg.Size; i++ { + for i := 0; i < int(arg.Type.Bits); i++ { if in.Bit(bit) == 1 { r = big.NewInt(0).SetBit(r, i, 1) } diff --git a/circuit/computer.go b/circuit/computer.go index c3e59c78..6e8f27a7 100644 --- a/circuit/computer.go +++ b/circuit/computer.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Markku Rossi +// Copyright (c) 2020-2023 Markku Rossi // // All rights reserved. // @@ -32,7 +32,7 @@ func (c *Circuit) Compute(inputs []*big.Int) ([]*big.Int, error) { var w int for idx, io := range args { - for bit := 0; bit < io.Size; bit++ { + for bit := 0; bit < int(io.Type.Bits); bit++ { wires[w] = byte(inputs[idx].Bit(bit)) w++ } @@ -78,7 +78,7 @@ func (c *Circuit) Compute(inputs []*big.Int) ([]*big.Int, error) { var result []*big.Int for _, io := range c.Outputs { r := new(big.Int) - for bit := 0; bit < io.Size; bit++ { + for bit := 0; bit < int(io.Type.Bits); bit++ { if wires[w] != 0 { r.SetBit(r, bit, 1) } diff --git a/circuit/dot.go b/circuit/dot.go index 534d6aa7..7a7f0628 100644 --- a/circuit/dot.go +++ b/circuit/dot.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2019-2022 Markku Rossi +// Copyright (c) 2019-2023 Markku Rossi // // All rights reserved. // @@ -32,7 +32,7 @@ func (c *Circuit) Dot(out io.Writer) { fmt.Fprintf(out, " { rank=same") var numInputs int for _, input := range c.Inputs { - numInputs += input.Size + numInputs += int(input.Type.Bits) } for w := 0; w < numInputs; w++ { fmt.Fprintf(out, "; w%d", w) diff --git a/circuit/evaluator.go b/circuit/evaluator.go index fcfca9c2..733f94f5 100644 --- a/circuit/evaluator.go +++ b/circuit/evaluator.go @@ -72,7 +72,7 @@ func Evaluator(conn *p2p.Conn, oti ot.OT, circ *Circuit, inputs *big.Int, wires := make([]ot.Label, circ.NumWires) // Receive peer inputs. - for i := 0; i < circ.Inputs[0].Size; i++ { + for i := 0; i < int(circ.Inputs[0].Type.Bits); i++ { err := conn.ReceiveLabel(&label, &labelData) if err != nil { return nil, err @@ -93,23 +93,23 @@ func Evaluator(conn *p2p.Conn, oti ot.OT, circ *Circuit, inputs *big.Int, fmt.Printf(" - Querying our inputs...\n") } // Wire offset. - if err := conn.SendUint32(circ.Inputs[0].Size); err != nil { + if err := conn.SendUint32(int(circ.Inputs[0].Type.Bits)); err != nil { return nil, err } // Wire count. - if err := conn.SendUint32(circ.Inputs[1].Size); err != nil { + if err := conn.SendUint32(int(circ.Inputs[1].Type.Bits)); err != nil { return nil, err } if err := conn.Flush(); err != nil { return nil, err } - flags := make([]bool, circ.Inputs[1].Size) - for i := 0; i < circ.Inputs[1].Size; i++ { + flags := make([]bool, int(circ.Inputs[1].Type.Bits)) + for i := 0; i < int(circ.Inputs[1].Type.Bits); i++ { if inputs.Bit(i) == 1 { flags[i] = true } } - if err := oti.Receive(flags, wires[circ.Inputs[0].Size:]); err != nil { + if err := oti.Receive(flags, wires[circ.Inputs[0].Type.Bits:]); err != nil { return nil, err } xfer := conn.Stats.Sum() - ioStats diff --git a/circuit/garbler.go b/circuit/garbler.go index a19ccba1..691ae4f6 100644 --- a/circuit/garbler.go +++ b/circuit/garbler.go @@ -82,7 +82,7 @@ func Garbler(conn *p2p.Conn, oti ot.OT, circ *Circuit, inputs *big.Int, // Select our inputs. var n1 []ot.Label - for i := 0; i < circ.Inputs[0].Size; i++ { + for i := 0; i < int(circ.Inputs[0].Type.Bits); i++ { wire := garbled.Wires[i] var n ot.Label @@ -128,7 +128,8 @@ func Garbler(conn *p2p.Conn, oti ot.OT, circ *Circuit, inputs *big.Int, if err != nil { return nil, err } - if offset != circ.Inputs[0].Size || count != circ.Inputs[1].Size { + if offset != int(circ.Inputs[0].Type.Bits) || + count != int(circ.Inputs[1].Type.Bits) { return nil, fmt.Errorf("peer can't OT wires [%d...%d[", offset, offset+count) } diff --git a/circuit/ioarg.go b/circuit/ioarg.go index fd11fead..f6e14c9a 100644 --- a/circuit/ioarg.go +++ b/circuit/ioarg.go @@ -17,7 +17,6 @@ import ( type IOArg struct { Name string Type types.Info - Size int Compound IO } @@ -116,7 +115,7 @@ func (io IOArg) Parse(inputs []string) (*big.Int, error) { input.Lsh(input, uint(offset)) result.Or(result, input) - offset += arg.Size + offset += int(arg.Type.Bits) } return result, nil } diff --git a/circuit/marshal.go b/circuit/marshal.go index 3a88a5d6..fb3282f9 100644 --- a/circuit/marshal.go +++ b/circuit/marshal.go @@ -90,7 +90,7 @@ func marshalIOArg(out io.Writer, arg IOArg) error { if err := marshalString(out, arg.Type.String()); err != nil { return err } - if err := binary.Write(out, bo, uint32(arg.Size)); err != nil { + if err := binary.Write(out, bo, uint32(arg.Type.Bits)); err != nil { return err } if err := binary.Write(out, bo, uint32(len(arg.Compound))); err != nil { @@ -118,12 +118,12 @@ func (c *Circuit) MarshalBristol(out io.Writer) error { fmt.Fprintf(out, "%d %d\n", c.NumGates, c.NumWires) fmt.Fprintf(out, "%d", len(c.Inputs)) for _, input := range c.Inputs { - fmt.Fprintf(out, " %d", input.Size) + fmt.Fprintf(out, " %d", input.Type.Bits) } fmt.Fprintln(out) fmt.Fprintf(out, "%d", len(c.Outputs)) for _, ret := range c.Outputs { - fmt.Fprintf(out, " %d", ret.Size) + fmt.Fprintf(out, " %d", ret.Type.Bits) } fmt.Fprintln(out) fmt.Fprintln(out) diff --git a/circuit/parser.go b/circuit/parser.go index e58fe186..6e11ad92 100644 --- a/circuit/parser.go +++ b/circuit/parser.go @@ -92,7 +92,7 @@ func ParseMPCLC(in io.Reader) (*Circuit, error) { return nil, err } inputs = append(inputs, arg) - inputWires += arg.Size + inputWires += int(arg.Type.Bits) } for i := 0; i < int(header.NumOutputs); i++ { out, err := parseIOArg(r) @@ -100,7 +100,7 @@ func ParseMPCLC(in io.Reader) (*Circuit, error) { return nil, err } outputs = append(outputs, out) - outputWires += out.Size + outputWires += int(out.Type.Bits) } // Mark input wires seen. @@ -228,7 +228,7 @@ func parseIOArg(r *bufio.Reader) (arg IOArg, err error) { if err != nil { return arg, err } - arg.Size = int(ui32) + arg.Type.Bits = types.Size(ui32) // Compound if err := binary.Read(r, bo, &ui32); err != nil { @@ -318,7 +318,6 @@ func ParseBristol(in io.Reader) (*Circuit, error) { Type: types.TUint, Bits: types.Size(bits), }, - Size: bits, }) inputWires += bits } @@ -360,7 +359,6 @@ func ParseBristol(in io.Reader) (*Circuit, error) { Type: types.TUint, Bits: types.Size(bits), }, - Size: bits, }) } diff --git a/circuit/stream_evaluator.go b/circuit/stream_evaluator.go index 5c693502..e5e7bdb8 100644 --- a/circuit/stream_evaluator.go +++ b/circuit/stream_evaluator.go @@ -142,7 +142,8 @@ func StreamEvaluator(conn *p2p.Conn, oti ot.OT, inputFlag []string, fmt.Printf(" - Out: %s\n", outputs) fmt.Printf(" - In: %s\n", inputFlag) - streaming, err := NewStreamEval(key, in1.Size+in2.Size, outputs.Size()) + streaming, err := NewStreamEval(key, int(in1.Type.Bits+in2.Type.Bits), + outputs.Size()) if err != nil { return nil, nil, err } @@ -150,7 +151,7 @@ func StreamEvaluator(conn *p2p.Conn, oti ot.OT, inputFlag []string, // Receive peer inputs. var label ot.Label var labelData ot.LabelData - for w := 0; w < in1.Size; w++ { + for w := 0; w < int(in1.Type.Bits); w++ { err := conn.ReceiveLabel(&label, &labelData) if err != nil { return nil, nil, err @@ -170,13 +171,13 @@ func StreamEvaluator(conn *p2p.Conn, oti ot.OT, inputFlag []string, if verbose { fmt.Printf(" - Querying our inputs...\n") } - flags := make([]bool, in2.Size) - for i := 0; i < in2.Size; i++ { + flags := make([]bool, in2.Type.Bits) + for i := 0; i < int(in2.Type.Bits); i++ { if inputs.Bit(i) == 1 { flags[i] = true } } - inputLabels := streaming.GetInputs(in1.Size, in2.Size) + inputLabels := streaming.GetInputs(int(in1.Type.Bits), int(in2.Type.Bits)) if err := oti.Receive(flags, inputLabels); err != nil { return nil, nil, err } @@ -479,7 +480,7 @@ func receiveArgument(conn *p2p.Conn) (arg IOArg, err error) { if err != nil { return arg, err } - arg.Size = size + arg.Type.Bits = types.Size(size) count, err := conn.ReceiveUint32() if err != nil { diff --git a/compiler/ast/builtin.go b/compiler/ast/builtin.go index a2546166..21e1b279 100644 --- a/compiler/ast/builtin.go +++ b/compiler/ast/builtin.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2019-2022 Markku Rossi +// Copyright (c) 2019-2023 Markku Rossi // // All rights reserved. // @@ -365,11 +365,11 @@ func nativeCircuit(name string, block *ssa.Block, ctx *Codegen, // Check that the argument types match. for idx, io := range circ.Inputs { arg := args[idx] - if io.Size < int(arg.Type.Bits) || io.Size > int(arg.Type.Bits) && + if io.Type.Bits < arg.Type.Bits || io.Type.Bits > arg.Type.Bits && !arg.Const { return nil, nil, ctx.Errorf(loc, "invalid argument %d for native circuit: got %s, need %d", - idx, arg.Type, io.Size) + idx, arg.Type, io.Type.Bits) } } @@ -384,7 +384,7 @@ func nativeCircuit(name string, block *ssa.Block, ctx *Codegen, for _, io := range circ.Outputs { result = append(result, gen.AnonVal(types.Info{ Type: types.TUndefined, - Bits: types.Size(io.Size), + Bits: io.Type.Bits, })) } diff --git a/compiler/ast/package.go b/compiler/ast/package.go index 7662d5a6..c0385380 100644 --- a/compiler/ast/package.go +++ b/compiler/ast/package.go @@ -84,7 +84,6 @@ func (pkg *Package) Compile(ctx *Codegen) (*ssa.Program, Annotations, error) { arg := circuit.IOArg{ Name: a.String(), Type: a.Type, - Size: int(a.Type.Bits), } if typeInfo.Type == types.TStruct { arg.Compound = flattenStruct(typeInfo) @@ -129,7 +128,6 @@ func (pkg *Package) Compile(ctx *Codegen) (*ssa.Program, Annotations, error) { outputs = append(outputs, circuit.IOArg{ Name: v.String(), Type: v.Type, - Size: int(v.Type.Bits), }) } @@ -172,7 +170,6 @@ func flattenStruct(t types.Info) circuit.IO { result = append(result, circuit.IOArg{ Name: f.Name, Type: f.Type, - Size: int(f.Type.Bits), }) } } diff --git a/compiler/circuits/circuits_test.go b/compiler/circuits/circuits_test.go index b9825ace..2fa2fb92 100644 --- a/compiler/circuits/circuits_test.go +++ b/compiler/circuits/circuits_test.go @@ -15,6 +15,7 @@ import ( "github.com/markkurossi/mpc/circuit" "github.com/markkurossi/mpc/compiler/utils" + "github.com/markkurossi/mpc/types" ) const ( @@ -39,7 +40,10 @@ func NewIO(size int, name string) circuit.IO { return circuit.IO{ circuit.IOArg{ Name: name, - Size: size, + Type: types.Info{ + Type: types.TUint, + Bits: types.Size(size), + }, }, } } diff --git a/compiler/ssa/circuitgen.go b/compiler/ssa/circuitgen.go index a901df97..96378fa6 100644 --- a/compiler/ssa/circuitgen.go +++ b/compiler/ssa/circuitgen.go @@ -474,9 +474,9 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { var circWires []*circuits.Wire // Flatten input wires. - for idx, w := range wires { + for wi, w := range wires { circWires = append(circWires, w...) - for i := len(w); i < instr.Circ.Inputs[idx].Size; i++ { + for i := len(w); i < int(instr.Circ.Inputs[wi].Type.Bits); i++ { // Zeroes for unset input wires. zw := cc.ZeroWire() circWires = append(circWires, zw) diff --git a/compiler/ssa/program.go b/compiler/ssa/program.go index a2c5d731..ef879b7a 100644 --- a/compiler/ssa/program.go +++ b/compiler/ssa/program.go @@ -61,7 +61,7 @@ func NewProgram(params *utils.Params, in, out circuit.IO, Const: true, Scope: 1, // Arguments are at scope 1. Name: arg.Name, - }, types.Size(arg.Size)) + }, arg.Type.Bits) if err != nil { return nil, err } diff --git a/compiler/ssa/streamer.go b/compiler/ssa/streamer.go index f5679a2a..496b7c04 100644 --- a/compiler/ssa/streamer.go +++ b/compiler/ssa/streamer.go @@ -78,7 +78,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, // Select our inputs. var n1 []ot.Label - for i := 0; i < prog.Inputs[0].Size; i++ { + for i := 0; i < int(prog.Inputs[0].Type.Bits); i++ { wire := streaming.GetInput(circuit.Wire(i)) var n ot.Label @@ -114,8 +114,8 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, timing.Sample("OT Init", []string{circuit.FileSize(xfer).String()}) // Peer OTs its inputs. - err = oti.Send(streaming.GetInputs(prog.Inputs[0].Size, - prog.Inputs[1].Size)) + err = oti.Send(streaming.GetInputs(int(prog.Inputs[0].Type.Bits), + int(prog.Inputs[1].Type.Bits))) if err != nil { return nil, nil, err } @@ -377,7 +377,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, iIDs = iIDs[:0] oIDs = oIDs[:0] for i := 0; i < len(wires); i++ { - for j := 0; j < instr.Circ.Inputs[i].Size; j++ { + for j := 0; j < int(instr.Circ.Inputs[i].Type.Bits); j++ { if j < len(wires[i]) { iIDs = append(iIDs, circuit.Wire(wires[i][j].ID())) } else { @@ -391,7 +391,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, if err != nil { return nil, nil, err } - for j := 0; j < instr.Circ.Outputs[i].Size; j++ { + for j := 0; j < int(instr.Circ.Outputs[i].Type.Bits); j++ { if j < len(wires) { oIDs = append(oIDs, circuit.Wire(wires[j].ID())) } else { @@ -691,7 +691,6 @@ func (prog *Program) ZeroWire(conn *p2p.Conn, streaming *circuit.Streaming) ( Type: types.TUint, Bits: 1, }, - Size: 1, }, }, Outputs: []circuit.IOArg{ @@ -701,7 +700,6 @@ func (prog *Program) ZeroWire(conn *p2p.Conn, streaming *circuit.Streaming) ( Type: types.TUint, Bits: 1, }, - Size: 1, }, }, Gates: []circuit.Gate{ @@ -746,7 +744,6 @@ func (prog *Program) OneWire(conn *p2p.Conn, streaming *circuit.Streaming) ( Type: types.TUint, Bits: 1, }, - Size: 1, }, }, Outputs: []circuit.IOArg{ @@ -756,7 +753,6 @@ func (prog *Program) OneWire(conn *p2p.Conn, streaming *circuit.Streaming) ( Type: types.TUint, Bits: 1, }, - Size: 1, }, }, Gates: []circuit.Gate{ @@ -786,7 +782,7 @@ func sendArgument(conn *p2p.Conn, arg circuit.IOArg) error { if err := conn.SendString(arg.Type.String()); err != nil { return err } - if err := conn.SendUint32(arg.Size); err != nil { + if err := conn.SendUint32(int(arg.Type.Bits)); err != nil { return err } From 3e648065bcfb6f01ef27c8ed4d1d2158ecf6c2ad Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Fri, 25 Aug 2023 09:48:07 +0200 Subject: [PATCH 07/19] Updated comments. --- apps/garbled/examples/encrypt.mpcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/garbled/examples/encrypt.mpcl b/apps/garbled/examples/encrypt.mpcl index 191a5ebe..fd84d402 100644 --- a/apps/garbled/examples/encrypt.mpcl +++ b/apps/garbled/examples/encrypt.mpcl @@ -5,7 +5,7 @@ // $ ./garbled -e -i 0x8cd98b88adab08d6d60fe57c8b8a33f3,0xfd5e0f8f155e7102aa526ad0 examples/encrypt.mpcl // // The Garbler takes three arguments: the message to encrypt, and its -// key and noce shares: +// key and nonce shares: // // $ ./garbled -i 0x48656c6c6f2c20776f726c6421,0xed800b17b0c9d2334b249332155ddef5,0xa300751458c775a08762c2cd examples/encrypt.mpcl From f0aee73b975cd535ebf4fbf022b877ac31a8ecbb Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Fri, 25 Aug 2023 14:03:15 +0200 Subject: [PATCH 08/19] Value wire allocator works. --- compiler/ast/package.go | 8 ++++---- compiler/ssa/program.go | 6 +++--- compiler/ssa/value.go | 15 ++++++++++++--- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/compiler/ast/package.go b/compiler/ast/package.go index c0385380..d6ba280c 100644 --- a/compiler/ast/package.go +++ b/compiler/ast/package.go @@ -81,15 +81,15 @@ func (pkg *Package) Compile(ctx *Codegen) (*ssa.Program, Annotations, error) { a := gen.NewVal(arg.Name, typeInfo, ctx.Scope()) ctx.Start().Bindings.Set(a, nil) - arg := circuit.IOArg{ - Name: a.String(), + input := circuit.IOArg{ + Name: arg.Name, Type: a.Type, } if typeInfo.Type == types.TStruct { - arg.Compound = flattenStruct(typeInfo) + input.Compound = flattenStruct(typeInfo) } - inputs = append(inputs, arg) + inputs = append(inputs, input) } // Compile main. diff --git a/compiler/ssa/program.go b/compiler/ssa/program.go index ef879b7a..d2bdeba1 100644 --- a/compiler/ssa/program.go +++ b/compiler/ssa/program.go @@ -49,7 +49,7 @@ func NewProgram(params *utils.Params, in, out circuit.IO, Outputs: out, Constants: consts, Steps: steps, - walloc: NewWAllocString(), + walloc: NewWAllocValue(), } // Inputs into wires. @@ -58,9 +58,9 @@ func NewProgram(params *utils.Params, in, out circuit.IO, arg.Name = fmt.Sprintf("arg{%d}", idx) } wires, err := prog.walloc.Wires(Value{ - Const: true, - Scope: 1, // Arguments are at scope 1. Name: arg.Name, + Scope: 1, // Arguments are at scope 1. + Type: arg.Type, }, arg.Type.Bits) if err != nil { return nil, err diff --git a/compiler/ssa/value.go b/compiler/ssa/value.go index 4c4a6c18..a0736799 100644 --- a/compiler/ssa/value.go +++ b/compiler/ssa/value.go @@ -137,9 +137,13 @@ func (v *Value) HashCode() (hash int) { for r := range v.Name { hash = hash<<8 ^ int(r) ^ hash>>24 } - hash ^= int(v.Type.Bits) << 5 hash ^= int(v.Scope) << 3 hash ^= int(v.Version) << 1 + + if !v.Const { + hash ^= int(v.Type.Bits) << 5 + } + if hash < 0 { hash = -hash } @@ -152,8 +156,13 @@ func (v *Value) Equal(other BindingValue) bool { if !ok { return false } - if o.Name != v.Name || o.Scope != v.Scope || o.Version != v.Version || - v.Type.Bits != o.Type.Bits { + if o.Const != v.Const { + return false + } + if o.Name != v.Name || o.Scope != v.Scope || o.Version != v.Version { + return false + } + if !v.Const && v.Type.Bits != o.Type.Bits { return false } return v.PtrInfo.Equal(o.PtrInfo) From 2e3f7c2c85866b6b00d152f65d9e5b5ed2b37511 Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Fri, 25 Aug 2023 14:14:54 +0200 Subject: [PATCH 09/19] Removed debugging statements from the value wire allocator. --- apps/garbled/default.pgo | Bin 23279 -> 24408 bytes benchmarks.md | 25 +++++++ compiler/ssa/wire_allocator_value.go | 93 --------------------------- 3 files changed, 25 insertions(+), 93 deletions(-) diff --git a/apps/garbled/default.pgo b/apps/garbled/default.pgo index 549cc95d9fa564db1569d3fb89e5fd12376898a9..6debd042caedfc5c3dab2209518fe0e8ac3c9ddd 100644 GIT binary patch literal 24408 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
3^yIptk&yFG4QD{(7ULsZ!9 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 literal 23279 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 diff --git a/benchmarks.md b/benchmarks.md index e6f5e9f6..203d1cc2 100644 --- a/benchmarks.md +++ b/benchmarks.md @@ -538,6 +538,31 @@ Max permanent wires: 53913890, cached circuits: 25 #gates=830166294 (XOR=533177896 XNOR=28813441 AND=267575026 OR=496562 INV=103369 xor=561991337 !xor=268174957 levels=10548 width=1796) #w=853882864 ``` +Value.HashValue based `WireAllocator`: + +``` +┌──────────────┬────────────────┬─────────┬────────┐ +│ Op │ Time │ % │ Xfer │ +├──────────────┼────────────────┼─────────┼────────┤ +│ Compile │ 1.89192514s │ 2.70% │ │ +│ Init │ 2.706353ms │ 0.00% │ 0B │ +│ OT Init │ 11.731µs │ 0.00% │ 16kB │ +│ Peer Inputs │ 45.549977ms │ 0.07% │ 57kB │ +│ Stream │ 1m8.029901416s │ 97.23% │ 15GB │ +│ ├╴InstrInit │ 2.933100244s │ 4.31% │ │ +│ ├╴CircComp │ 30.339124ms │ 0.04% │ │ +│ ├╴StreamInit │ 2.608974096s │ 3.84% │ │ +│ ╰╴Garble │ 1m1.560371684s │ 90.49% │ │ +│ Result │ 324.555µs │ 0.00% │ 8kB │ +│ Total │ 1m9.970419172s │ │ 15GB │ +│ ├╴Sent │ │ 100.00% │ 15GB │ +│ ├╴Rcvd │ │ 0.00% │ 45kB │ +│ ╰╴Flcd │ │ │ 231284 │ +└──────────────┴────────────────┴─────────┴────────┘ +Max permanent wires: 53913890, cached circuits: 25 +#gates=830166294 (XOR=533177896 XNOR=28813441 AND=267575026 OR=496562 INV=103369 xor=561991337 !xor=268174957 levels=10548 width=1796) #w=853882864 +``` + Theoretical minimum single-threaded garbling time: ``` diff --git a/compiler/ssa/wire_allocator_value.go b/compiler/ssa/wire_allocator_value.go index fed628ee..0d431364 100644 --- a/compiler/ssa/wire_allocator_value.go +++ b/compiler/ssa/wire_allocator_value.go @@ -18,7 +18,6 @@ import ( type WAllocValue struct { freeWires map[types.Size][][]*circuits.Wire wires [10240]*allocByValue - debug map[string]*allocByValue nextWireID uint32 flHit int flMiss int @@ -41,7 +40,6 @@ func (alloc *allocByValue) String() string { func NewWAllocValue() WireAllocator { return &WAllocValue{ freeWires: make(map[types.Size][][]*circuits.Wire), - debug: make(map[string]*allocByValue), } } @@ -59,84 +57,12 @@ func (walloc *WAllocValue) NextWireID() uint32 { return ret } -func (walloc *WAllocValue) verify(v Value, expected *allocByValue) { - dbg, ok := walloc.debug[v.String()] - if expected == nil { - if ok { - panic(fmt.Sprintf("debug has key %v", v.String())) - } - if walloc.Allocated(v) { - panic(fmt.Sprintf("hash has key %v", v.String())) - } - return - } - if !ok { - panic(fmt.Sprintf("debug has not key %v", v.String())) - } - if !walloc.Allocated(v) { - panic(fmt.Sprintf("hash has not key %v", v.String())) - } - if dbg != expected { - fmt.Printf("debug %v != expected %v\n", dbg, expected) - fmt.Printf(" - debug.key: %v\n", dbg.key) - fmt.Printf(" - expected.key: %v\n", expected.key) - if dbg.key.Equal(&expected.key) { - fmt.Printf(" - keys Equal\n") - } else { - fmt.Printf(" - keys not Equal\n") - fmt.Printf(" - dbg: %v, expected: %v\n", - dbg.key.HashCode()%len(walloc.wires), - expected.key.HashCode()%len(walloc.wires)) - - } - panic("done") - } - if dbg.key.String() != v.String() { - panic("dbg.String() mismatch") - } - - hash := v.HashCode() % len(walloc.wires) - for alloc := walloc.wires[hash]; alloc != nil; alloc = alloc.next { - if alloc.key.Equal(&v) { - if alloc != expected { - panic("wires 1") - } - if alloc != dbg { - panic("wires 2") - } - if alloc.key.String() != v.String() { - panic("alloc.String() mismatch") - } - return - } - } - panic("wires not found") -} - -func (walloc *WAllocValue) verifyAdd(v Value, alloc *allocByValue) { - walloc.debug[v.String()] = alloc - walloc.verify(v, alloc) -} - func (walloc *WAllocValue) lookup(hash int, v Value) *allocByValue { - const key = "g{1,0}struct1024" - var keyMatch bool - if v.String() == key { - fmt.Printf("*** lookup %v: Const=%v, Bits=%v, Scope=%v, Version=%v, hash=%v\n", - key, v.Const, v.Type.Bits, v.Scope, v.Version, hash) - keyMatch = true - } for a := walloc.wires[hash]; a != nil; a = a.next { if a.key.Equal(&v) { - if keyMatch { - fmt.Printf(" - found!\n") - } return a } } - if keyMatch { - fmt.Printf(" - not found!\n") - } return nil } @@ -144,7 +70,6 @@ func (walloc *WAllocValue) remove(hash int, v Value) *allocByValue { for ptr := &walloc.wires[hash]; *ptr != nil; ptr = &(*ptr).next { if (*ptr).key.Equal(&v) { ret := *ptr - delete(walloc.debug, v.String()) *ptr = (*ptr).next return ret } @@ -181,18 +106,10 @@ func (walloc *WAllocValue) Wires(v Value, bits types.Size) ( hash := v.HashCode() % len(walloc.wires) alloc := walloc.lookup(hash, v) if alloc == nil { - _, ok := walloc.debug[v.String()] - if ok { - panic(fmt.Sprintf("Wires: dbg has %v, TypeRef=%v", - v.String(), v.TypeRef)) - } - alloc = walloc.alloc(bits, v) alloc.next = walloc.wires[hash] walloc.wires[hash] = alloc - walloc.verifyAdd(v, alloc) } - walloc.verify(v, alloc) return alloc.wires, nil } @@ -205,15 +122,9 @@ func (walloc *WAllocValue) AssignedWires(v Value, bits types.Size) ( hash := v.HashCode() % len(walloc.wires) alloc := walloc.lookup(hash, v) if alloc == nil { - _, ok := walloc.debug[v.String()] - if ok { - panic(fmt.Sprintf("AssignedWires: dbg has %v: Const=%v, Bits=%v, Scope=%v, Version=%v", - v.String(), v.Const, v.Type.Bits, v.Scope, v.Version)) - } alloc = walloc.alloc(bits, v) alloc.next = walloc.wires[hash] walloc.wires[hash] = alloc - walloc.verifyAdd(v, alloc) // Assign wire IDs. if alloc.base == circuits.UnassignedID { @@ -224,7 +135,6 @@ func (walloc *WAllocValue) AssignedWires(v Value, bits types.Size) ( walloc.nextWireID += uint32(bits) } } - walloc.verify(v, alloc) return alloc.wires, nil } @@ -247,8 +157,6 @@ func (walloc *WAllocValue) SetWires(v Value, w []*circuits.Wire) { alloc.next = walloc.wires[hash] walloc.wires[hash] = alloc - walloc.verifyAdd(v, alloc) - walloc.verify(v, alloc) } // GCWires implements WireAllocator.GCWires. @@ -258,7 +166,6 @@ func (walloc *WAllocValue) GCWires(v Value) { if alloc == nil { panic(fmt.Sprintf("GC: %s not known", v)) } - walloc.verify(v, nil) if alloc.base == circuits.UnassignedID { alloc.base = alloc.wires[0].ID() From 133dc02b622124059508db563e3de925df1d7a7d Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Fri, 25 Aug 2023 14:41:50 +0200 Subject: [PATCH 10/19] MRU ordering for hash buckets. --- compiler/ssa/wire_allocator_value.go | 57 ++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/compiler/ssa/wire_allocator_value.go b/compiler/ssa/wire_allocator_value.go index 0d431364..56bb8215 100644 --- a/compiler/ssa/wire_allocator_value.go +++ b/compiler/ssa/wire_allocator_value.go @@ -8,6 +8,7 @@ package ssa import ( "fmt" + "math" "github.com/markkurossi/mpc/compiler/circuits" "github.com/markkurossi/mpc/types" @@ -16,11 +17,13 @@ import ( // WAllocValue implements WireAllocator using Value.HashCode to map // values to wires. type WAllocValue struct { - freeWires map[types.Size][][]*circuits.Wire - wires [10240]*allocByValue - nextWireID uint32 - flHit int - flMiss int + freeWires map[types.Size][][]*circuits.Wire + wires [10240]*allocByValue + nextWireID uint32 + flHit int + flMiss int + lookupCount int + lookupFound int } type allocByValue struct { @@ -58,9 +61,22 @@ func (walloc *WAllocValue) NextWireID() uint32 { } func (walloc *WAllocValue) lookup(hash int, v Value) *allocByValue { - for a := walloc.wires[hash]; a != nil; a = a.next { - if a.key.Equal(&v) { - return a + var count int + for ptr := &walloc.wires[hash]; *ptr != nil; ptr = &(*ptr).next { + count++ + if (*ptr).key.Equal(&v) { + alloc := *ptr + + if count > 2 { + // MRU in the hash bucket. + *ptr = alloc.next + alloc.next = walloc.wires[hash] + walloc.wires[hash] = alloc + } + + walloc.lookupCount++ + walloc.lookupFound += count + return alloc } } return nil @@ -190,4 +206,29 @@ func (walloc *WAllocValue) GCWires(v Value) { // Debug implements WireAllocator.Debug. func (walloc *WAllocValue) Debug() { + total := float64(walloc.flHit + walloc.flMiss) + fmt.Printf("Wire freelist: hit=%v (%.2f%%), miss=%v (%.2f%%)\n", + walloc.flHit, float64(walloc.flHit)/total*100, + walloc.flMiss, float64(walloc.flMiss)/total*100) + + var sum, max int + min := math.MaxInt + + for i := 0; i < len(walloc.wires); i++ { + var count int + for alloc := walloc.wires[i]; alloc != nil; alloc = alloc.next { + count++ + } + sum += count + if count < min { + min = count + } + if count > max { + max = count + } + } + fmt.Printf("Hash: min=%v, max=%v, avg=%.4f, lookup=%v (avg=%.4f)\n", + min, max, float64(sum)/float64(len(walloc.wires)), + walloc.lookupCount, + float64(walloc.lookupFound)/float64(walloc.lookupCount)) } From 1499b44fa19e5ba7fadc34ac1838c957a6b8ada1 Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Sat, 26 Aug 2023 11:45:29 +0200 Subject: [PATCH 11/19] Fixed Value.HashCode to use actual Value.Name runes. --- compiler/ssa/value.go | 4 ++-- compiler/ssa/value_test.go | 35 ++++++++++++++++++++++++++++ compiler/ssa/wire_allocator_value.go | 10 ++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 compiler/ssa/value_test.go diff --git a/compiler/ssa/value.go b/compiler/ssa/value.go index a0736799..5cc73b04 100644 --- a/compiler/ssa/value.go +++ b/compiler/ssa/value.go @@ -134,8 +134,8 @@ func (v *Value) ConstInt() (types.Size, error) { // HashCode returns a hash code for the value. func (v *Value) HashCode() (hash int) { - for r := range v.Name { - hash = hash<<8 ^ int(r) ^ hash>>24 + for _, r := range v.Name { + hash = hash<<4 ^ int(r) ^ hash>>24 } hash ^= int(v.Scope) << 3 hash ^= int(v.Version) << 1 diff --git a/compiler/ssa/value_test.go b/compiler/ssa/value_test.go new file mode 100644 index 00000000..148ee4b2 --- /dev/null +++ b/compiler/ssa/value_test.go @@ -0,0 +1,35 @@ +// +// Copyright (c) 2023 Markku Rossi +// +// All rights reserved. +// + +package ssa + +import ( + "testing" +) + +var inputs = []string{ + "$127", "$126", "$125", "$124", "$123", "$122", "$121", "$119", + "$118", "$117", "$116", "$115", "$114", "$113", "$111", "$110", + "$109", "$108", "$107", "$106", "$105", "$103", "$102", "$101", + "$100", +} + +func TestHashCode(t *testing.T) { + counts := make(map[int]int) + for _, input := range inputs { + v := Value{ + Name: input, + Const: true, + } + counts[v.HashCode()]++ + } + + for k, v := range counts { + if v > 1 { + t.Errorf("HashCode %v: count=%v\n", k, v) + } + } +} diff --git a/compiler/ssa/wire_allocator_value.go b/compiler/ssa/wire_allocator_value.go index 56bb8215..791d7aea 100644 --- a/compiler/ssa/wire_allocator_value.go +++ b/compiler/ssa/wire_allocator_value.go @@ -214,6 +214,8 @@ func (walloc *WAllocValue) Debug() { var sum, max int min := math.MaxInt + var maxIndex int + for i := 0; i < len(walloc.wires); i++ { var count int for alloc := walloc.wires[i]; alloc != nil; alloc = alloc.next { @@ -225,10 +227,18 @@ func (walloc *WAllocValue) Debug() { } if count > max { max = count + maxIndex = i } } fmt.Printf("Hash: min=%v, max=%v, avg=%.4f, lookup=%v (avg=%.4f)\n", min, max, float64(sum)/float64(len(walloc.wires)), walloc.lookupCount, float64(walloc.lookupFound)/float64(walloc.lookupCount)) + + if false { + fmt.Printf("Max bucket:\n") + for alloc := walloc.wires[maxIndex]; alloc != nil; alloc = alloc.next { + fmt.Printf(" %v: %v\n", alloc.key.String(), len(alloc.wires)) + } + } } From b4486b04034120c882a4389f3fe83fcc2e5e4ec7 Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Mon, 28 Aug 2023 11:44:21 +0200 Subject: [PATCH 12/19] Implemented compiler.circuits.Allocator to allocate wires and gates. --- compiler/circuits/allocator.go | 58 +++++++++++++++++++++++++++ compiler/circuits/circ_adder.go | 12 +++--- compiler/circuits/circ_binary.go | 4 +- compiler/circuits/circ_comparators.go | 18 ++++----- compiler/circuits/circ_divider.go | 12 +++--- compiler/circuits/circ_hamming.go | 6 +-- compiler/circuits/circ_index.go | 6 +-- compiler/circuits/circ_multiplier.go | 28 ++++++------- compiler/circuits/circ_mux.go | 6 +-- compiler/circuits/circ_subtractor.go | 10 ++--- compiler/circuits/circuits_test.go | 19 ++++----- compiler/circuits/compiler.go | 17 ++++---- compiler/circuits/wire.go | 21 ---------- compiler/circuits/wire_test.go | 2 +- compiler/ssa/circuitgen.go | 8 ++-- compiler/ssa/program.go | 7 +++- compiler/ssa/streamer.go | 7 ++-- compiler/ssa/wire_allocator_string.go | 6 ++- compiler/ssa/wire_allocator_value.go | 6 ++- 19 files changed, 153 insertions(+), 100 deletions(-) create mode 100644 compiler/circuits/allocator.go diff --git a/compiler/circuits/allocator.go b/compiler/circuits/allocator.go new file mode 100644 index 00000000..41b9fadf --- /dev/null +++ b/compiler/circuits/allocator.go @@ -0,0 +1,58 @@ +// +// Copyright (c) 2023 Markku Rossi +// +// All rights reserved. +// + +package circuits + +import ( + "fmt" + + "github.com/markkurossi/mpc/types" +) + +// Allocator implements circuit wire and gate allocation. +type Allocator struct { + block []Wire + ofs int + numWire uint64 + numWires uint64 +} + +// NewAllocator creates a new circuit allocator. +func NewAllocator() *Allocator { + return new(Allocator) +} + +// Wire allocates a new Wire. +func (alloc *Allocator) Wire() *Wire { + alloc.numWire++ + w := new(Wire) + w.Reset(UnassignedID) + return w +} + +// Wires allocate an array of Wires. +func (alloc *Allocator) Wires(bits types.Size) []*Wire { + alloc.numWires += uint64(bits) + result := make([]*Wire, bits) + for i := 0; i < int(bits); i++ { + if alloc.ofs == 0 { + alloc.ofs = 8192 + alloc.block = make([]Wire, alloc.ofs) + } + alloc.ofs-- + w := &alloc.block[alloc.ofs] + + w.id = UnassignedID + result[i] = w + } + return result +} + +// Debug print debugging information about the circuit allocator. +func (alloc *Allocator) Debug() { + fmt.Printf("circuits.Allocator: #wire=%v, #wires=%v\n", + alloc.numWire, alloc.numWires) +} diff --git a/compiler/circuits/circ_adder.go b/compiler/circuits/circ_adder.go index 1aed71ed..5b634e23 100644 --- a/compiler/circuits/circ_adder.go +++ b/compiler/circuits/circ_adder.go @@ -1,7 +1,7 @@ // // circ_adder.go // -// Copyright (c) 2019-2021 Markku Rossi +// Copyright (c) 2019-2023 Markku Rossi // // All rights reserved. // @@ -25,9 +25,9 @@ func NewHalfAdder(compiler *Compiler, a, b, s, c *Wire) { // NewFullAdder creates a full adder circuit func NewFullAdder(compiler *Compiler, a, b, cin, s, cout *Wire) { - w1 := NewWire() - w2 := NewWire() - w3 := NewWire() + w1 := compiler.Calloc.Wire() + w2 := compiler.Calloc.Wire() + w3 := compiler.Calloc.Wire() // s = a XOR b XOR cin // cout = cin XOR ((a XOR cin) AND (b XOR cin)). @@ -65,7 +65,7 @@ func NewAdder(compiler *Compiler, x, y, z []*Wire) error { } NewHalfAdder(compiler, x[0], y[0], z[0], cin) } else { - cin := NewWire() + cin := compiler.Calloc.Wire() NewHalfAdder(compiler, x[0], y[0], z[0], cin) for i := 1; i < len(x); i++ { @@ -78,7 +78,7 @@ func NewAdder(compiler *Compiler, x, y, z []*Wire) error { cout = z[len(x)] } } else { - cout = NewWire() + cout = compiler.Calloc.Wire() } NewFullAdder(compiler, x[i], y[i], cin, z[i], cout) diff --git a/compiler/circuits/circ_binary.go b/compiler/circuits/circ_binary.go index efbfed88..45e51fa0 100644 --- a/compiler/circuits/circ_binary.go +++ b/compiler/circuits/circ_binary.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2020-2021 Markku Rossi +// Copyright (c) 2020-2021, 2023 Markku Rossi // // All rights reserved. // @@ -31,7 +31,7 @@ func NewBinaryClear(compiler *Compiler, x, y, r []*Wire) error { y = y[0:len(r)] } for i := 0; i < len(x); i++ { - w := NewWire() + w := compiler.Calloc.Wire() compiler.INV(y[i], w) compiler.AddGate(NewBinary(circuit.AND, x[i], w, r[i])) } diff --git a/compiler/circuits/circ_comparators.go b/compiler/circuits/circ_comparators.go index 6c0c2ee0..c31c2489 100644 --- a/compiler/circuits/circ_comparators.go +++ b/compiler/circuits/circ_comparators.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2020-2021 Markku Rossi +// Copyright (c) 2020-2023 Markku Rossi // // All rights reserved. // @@ -21,16 +21,16 @@ func comparator(compiler *Compiler, cin *Wire, x, y, r []*Wire) error { } for i := 0; i < len(x); i++ { - w1 := NewWire() + w1 := compiler.Calloc.Wire() compiler.AddGate(NewBinary(circuit.XNOR, cin, y[i], w1)) - w2 := NewWire() + w2 := compiler.Calloc.Wire() compiler.AddGate(NewBinary(circuit.XOR, cin, x[i], w2)) - w3 := NewWire() + w3 := compiler.Calloc.Wire() compiler.AddGate(NewBinary(circuit.AND, w1, w2, w3)) var cout *Wire if i+1 < len(x) { - cout = NewWire() + cout = compiler.Calloc.Wire() } else { cout = r[0] } @@ -72,18 +72,18 @@ func NewNeqComparator(compiler *Compiler, x, y, r []*Wire) error { return nil } - c := NewWire() + c := compiler.Calloc.Wire() compiler.AddGate(NewBinary(circuit.XOR, x[0], y[0], c)) for i := 1; i < len(x); i++ { - xor := NewWire() + xor := compiler.Calloc.Wire() compiler.AddGate(NewBinary(circuit.XOR, x[i], y[i], xor)) var out *Wire if i+1 >= len(x) { out = r[0] } else { - out = NewWire() + out = compiler.Calloc.Wire() } compiler.AddGate(NewBinary(circuit.OR, c, xor, out)) c = out @@ -98,7 +98,7 @@ func NewEqComparator(compiler *Compiler, x, y, r []*Wire) error { } // w = x == y - w := NewWire() + w := compiler.Calloc.Wire() err := NewNeqComparator(compiler, x, y, []*Wire{w}) if err != nil { return err diff --git a/compiler/circuits/circ_divider.go b/compiler/circuits/circ_divider.go index 55ae0577..31dc7c64 100644 --- a/compiler/circuits/circ_divider.go +++ b/compiler/circuits/circ_divider.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2019 Markku Rossi +// Copyright (c) 2019-2023 Markku Rossi // // All rights reserved. // @@ -16,7 +16,7 @@ func NewDivider(compiler *Compiler, a, b, q, r []*Wire) error { // Init bINV. bINV := make([]*Wire, len(b)) for i := 0; i < len(b); i++ { - bINV[i] = NewWire() + bINV[i] = compiler.Calloc.Wire() compiler.INV(b[i], bINV[i]) } @@ -40,8 +40,8 @@ func NewDivider(compiler *Compiler, a, b, q, r []*Wire) error { } else { bw = compiler.OneWire() // INV(0) } - co := NewWire() - ro := NewWire() + co := compiler.Calloc.Wire() + ro := compiler.Calloc.Wire() NewFullAdder(compiler, rIn[x], bw, cIn, ro, co) rOut[x] = ro cIn = co @@ -49,7 +49,7 @@ func NewDivider(compiler *Compiler, a, b, q, r []*Wire) error { // Quotient y. if len(a)-1-y < len(q) { - w := NewWire() + w := compiler.Calloc.Wire() compiler.INV(cIn, w) compiler.INV(w, q[len(a)-1-y]) } @@ -60,7 +60,7 @@ func NewDivider(compiler *Compiler, a, b, q, r []*Wire) error { if y+1 >= len(a) && x < len(r) { ro = r[x] } else { - ro = NewWire() + ro = compiler.Calloc.Wire() } err := NewMUX(compiler, []*Wire{cIn}, rOut[x:x+1], rIn[x:x+1], diff --git a/compiler/circuits/circ_hamming.go b/compiler/circuits/circ_hamming.go index 8baaf15a..8190e707 100644 --- a/compiler/circuits/circ_hamming.go +++ b/compiler/circuits/circ_hamming.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2020-2021 Markku Rossi +// Copyright (c) 2020-2023 Markku Rossi // // All rights reserved. // @@ -18,7 +18,7 @@ func Hamming(compiler *Compiler, a, b, r []*Wire) error { var arr [][]*Wire for i := 0; i < len(a); i++ { - w := NewWire() + w := compiler.Calloc.Wire() compiler.AddGate(NewBinary(circuit.XOR, a[i], b[i], w)) arr = append(arr, []*Wire{w}) } @@ -27,7 +27,7 @@ func Hamming(compiler *Compiler, a, b, r []*Wire) error { var n [][]*Wire for i := 0; i < len(arr); i += 2 { if i+1 < len(arr) { - result := MakeWires(types.Size(len(arr[i]) + 1)) + result := compiler.Calloc.Wires(types.Size(len(arr[i]) + 1)) err := NewAdder(compiler, arr[i], arr[i+1], result) if err != nil { return err diff --git a/compiler/circuits/circ_index.go b/compiler/circuits/circ_index.go index 7e2509e3..22c9605a 100644 --- a/compiler/circuits/circ_index.go +++ b/compiler/circuits/circ_index.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2021 Markku Rossi +// Copyright (c) 2021-2023 Markku Rossi // // All rights reserved. // @@ -65,7 +65,7 @@ func newIndex(compiler *Compiler, bit, length, size int, fVal := make([]*Wire, size) for i := 0; i < size; i++ { - fVal[i] = NewWire() + fVal[i] = compiler.Calloc.Wire() } fArray := array if n > length { @@ -80,7 +80,7 @@ func newIndex(compiler *Compiler, bit, length, size int, if n > length { tVal = make([]*Wire, size) for i := 0; i < size; i++ { - tVal[i] = NewWire() + tVal[i] = compiler.Calloc.Wire() } err = newIndex(compiler, bit-1, length, size, array[length*size:], index, tVal) diff --git a/compiler/circuits/circ_multiplier.go b/compiler/circuits/circ_multiplier.go index aa02ad08..f33761c2 100644 --- a/compiler/circuits/circ_multiplier.go +++ b/compiler/circuits/circ_multiplier.go @@ -55,7 +55,7 @@ func NewArrayMultiplier(compiler *Compiler, x, y, z []*Wire) error { if i == 0 { s = z[0] } else { - s = NewWire() + s = compiler.Calloc.Wire() sums = append(sums, s) } compiler.AddGate(NewBinary(circuit.AND, xn, y[0], s)) @@ -67,7 +67,7 @@ func NewArrayMultiplier(compiler *Compiler, x, y, z []*Wire) error { // ANDs for y(j) var ands []*Wire for _, xn := range x { - wire := NewWire() + wire := compiler.Calloc.Wire() compiler.AddGate(NewBinary(circuit.AND, xn, y[j], wire)) ands = append(ands, wire) } @@ -76,13 +76,13 @@ func NewArrayMultiplier(compiler *Compiler, x, y, z []*Wire) error { var nsums []*Wire var c *Wire for i := 0; i < len(ands); i++ { - cout := NewWire() + cout := compiler.Calloc.Wire() var s *Wire if i == 0 { s = z[j] } else { - s = NewWire() + s = compiler.Calloc.Wire() nsums = append(nsums, s) } @@ -102,14 +102,14 @@ func NewArrayMultiplier(compiler *Compiler, x, y, z []*Wire) error { // Construct final layer. var c *Wire for i, xn := range x { - and := NewWire() + and := compiler.Calloc.Wire() compiler.AddGate(NewBinary(circuit.AND, xn, y[j], and)) var cout *Wire if i+1 >= len(x) && j+i+1 < len(z) { cout = z[j+i+1] } else { - cout = NewWire() + cout = compiler.Calloc.Wire() } if j+i < len(z) { @@ -169,34 +169,34 @@ func NewKaratsubaMultiplier(cc *Compiler, limit int, a, b, r []*Wire) error { bLow := b[:mid] bHigh := b[mid:] - z0 := MakeWires(types.Size(min(max(len(aLow), len(bLow))*2, len(r)))) + z0 := cc.Calloc.Wires(types.Size(min(max(len(aLow), len(bLow))*2, len(r)))) if err := NewKaratsubaMultiplier(cc, limit, aLow, bLow, z0); err != nil { return err } aSumLen := max(len(aLow), len(aHigh)) + 1 - aSum := MakeWires(types.Size(aSumLen)) + aSum := cc.Calloc.Wires(types.Size(aSumLen)) if err := NewAdder(cc, aLow, aHigh, aSum); err != nil { return err } bSumLen := max(len(bLow), len(bHigh)) + 1 - bSum := MakeWires(types.Size(bSumLen)) + bSum := cc.Calloc.Wires(types.Size(bSumLen)) if err := NewAdder(cc, bLow, bHigh, bSum); err != nil { return err } - z1 := MakeWires(types.Size(min(max(aSumLen, bSumLen)*2, len(r)))) + z1 := cc.Calloc.Wires(types.Size(min(max(aSumLen, bSumLen)*2, len(r)))) if err := NewKaratsubaMultiplier(cc, limit, aSum, bSum, z1); err != nil { return err } - z2 := MakeWires(types.Size(min(max(len(aHigh), len(bHigh))*2, len(r)))) + z2 := cc.Calloc.Wires(types.Size(min(max(len(aHigh), len(bHigh))*2, len(r)))) if err := NewKaratsubaMultiplier(cc, limit, aHigh, bHigh, z2); err != nil { return err } - sub1 := MakeWires(types.Size(len(r))) + sub1 := cc.Calloc.Wires(types.Size(len(r))) if err := NewSubtractor(cc, z1, z2, sub1); err != nil { return err } - sub2 := MakeWires(types.Size(len(r))) + sub2 := cc.Calloc.Wires(types.Size(len(r))) if err := NewSubtractor(cc, sub1, z0, sub2); err != nil { return err } @@ -204,7 +204,7 @@ func NewKaratsubaMultiplier(cc *Compiler, limit int, a, b, r []*Wire) error { shift1 := cc.ShiftLeft(z2, len(r), mid*2) shift2 := cc.ShiftLeft(sub2, len(r), mid) - add1 := MakeWires(types.Size(len(r))) + add1 := cc.Calloc.Wires(types.Size(len(r))) if err := NewAdder(cc, shift1, shift2, add1); err != nil { return err } diff --git a/compiler/circuits/circ_mux.go b/compiler/circuits/circ_mux.go index c653945e..ea4d0d7b 100644 --- a/compiler/circuits/circ_mux.go +++ b/compiler/circuits/circ_mux.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Markku Rossi +// Copyright (c) 2020-2023 Markku Rossi // // All rights reserved. // @@ -22,8 +22,8 @@ func NewMUX(compiler *Compiler, cond, t, f, out []*Wire) error { } for i := 0; i < len(t); i++ { - w1 := NewWire() - w2 := NewWire() + w1 := compiler.Calloc.Wire() + w2 := compiler.Calloc.Wire() // w1 = XOR(f[i], t[i]) compiler.AddGate(NewBinary(circuit.XOR, f[i], t[i], w1)) diff --git a/compiler/circuits/circ_subtractor.go b/compiler/circuits/circ_subtractor.go index 7648edd3..68a2f677 100644 --- a/compiler/circuits/circ_subtractor.go +++ b/compiler/circuits/circ_subtractor.go @@ -1,7 +1,7 @@ // // circ_subtractor.go // -// Copyright (c) 2019-2021 Markku Rossi +// Copyright (c) 2019-2023 Markku Rossi // // All rights reserved. // @@ -14,15 +14,15 @@ import ( // NewFullSubtractor creates a full subtractor circuit. func NewFullSubtractor(compiler *Compiler, x, y, cin, d, cout *Wire) { - w1 := NewWire() + w1 := compiler.Calloc.Wire() compiler.AddGate(NewBinary(circuit.XNOR, y, cin, w1)) compiler.AddGate(NewBinary(circuit.XNOR, x, w1, d)) if cout != nil { - w2 := NewWire() + w2 := compiler.Calloc.Wire() compiler.AddGate(NewBinary(circuit.XOR, x, cin, w2)) - w3 := NewWire() + w3 := compiler.Calloc.Wire() compiler.AddGate(NewBinary(circuit.AND, w1, w2, w3)) compiler.AddGate(NewBinary(circuit.XOR, w3, cin, cout)) @@ -48,7 +48,7 @@ func NewSubtractor(compiler *Compiler, x, y, z []*Wire) error { cout = z[i+1] } } else { - cout = NewWire() + cout = compiler.Calloc.Wire() } // Note y-x here. diff --git a/compiler/circuits/circuits_test.go b/compiler/circuits/circuits_test.go index 2fa2fb92..d6779c5b 100644 --- a/compiler/circuits/circuits_test.go +++ b/compiler/circuits/circuits_test.go @@ -24,12 +24,13 @@ const ( var ( params = utils.NewParams() + calloc = NewAllocator() ) func makeWires(count int, output bool) []*Wire { var result []*Wire for i := 0; i < count; i++ { - w := NewWire() + w := calloc.Wire() w.SetOutput(output) result = append(result, w) } @@ -54,13 +55,13 @@ func TestAdd4(t *testing.T) { // 2xbits inputs, bits+1 outputs inputs := makeWires(bits*2, false) outputs := makeWires(bits+1, true) - c, err := NewCompiler(params, NewIO(bits*2, "in"), NewIO(bits+1, "out"), - inputs, outputs) + c, err := NewCompiler(params, calloc, NewIO(bits*2, "in"), + NewIO(bits+1, "out"), inputs, outputs) if err != nil { t.Fatalf("NewCompiler: %s", err) } - cin := NewWire() + cin := calloc.Wire() NewHalfAdder(c, inputs[0], inputs[bits], outputs[0], cin) for i := 1; i < bits; i++ { @@ -68,7 +69,7 @@ func TestAdd4(t *testing.T) { if i+1 >= bits { cout = outputs[bits] } else { - cout = NewWire() + cout = calloc.Wire() } NewFullAdder(c, inputs[i], inputs[bits+i], cin, outputs[i], cout) @@ -86,7 +87,7 @@ func TestAdd4(t *testing.T) { func TestFullSubtractor(t *testing.T) { inputs := makeWires(1+2, false) outputs := makeWires(2, true) - c, err := NewCompiler(params, NewIO(1+2, "in"), NewIO(2, "out"), + c, err := NewCompiler(params, calloc, NewIO(1+2, "in"), NewIO(2, "out"), inputs, outputs) if err != nil { t.Fatalf("NewCompiler: %s", err) @@ -105,7 +106,7 @@ func TestFullSubtractor(t *testing.T) { func TestMultiply1(t *testing.T) { inputs := makeWires(2, false) outputs := makeWires(2, true) - c, err := NewCompiler(params, NewIO(2, "in"), NewIO(2, "out"), + c, err := NewCompiler(params, calloc, NewIO(2, "in"), NewIO(2, "out"), inputs, outputs) if err != nil { t.Fatalf("NewCompiler: %s", err) @@ -123,8 +124,8 @@ func TestMultiply(t *testing.T) { inputs := makeWires(bits*2, false) outputs := makeWires(bits*2, true) - c, err := NewCompiler(params, NewIO(bits*2, "in"), NewIO(bits*2, "out"), - inputs, outputs) + c, err := NewCompiler(params, calloc, NewIO(bits*2, "in"), + NewIO(bits*2, "out"), inputs, outputs) if err != nil { t.Fatalf("NewCompiler: %s", err) } diff --git a/compiler/circuits/compiler.go b/compiler/circuits/compiler.go index 61e4e93c..56822d6a 100644 --- a/compiler/circuits/compiler.go +++ b/compiler/circuits/compiler.go @@ -21,6 +21,7 @@ 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 @@ -39,14 +40,16 @@ type Compiler struct { // NewCompiler creates a new circuit compiler for the specified // circuit input and output values. -func NewCompiler(params *utils.Params, inputs, outputs circuit.IO, - inputWires, outputWires []*Wire) (*Compiler, error) { +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, @@ -58,7 +61,7 @@ func NewCompiler(params *utils.Params, inputs, outputs circuit.IO, // InvI0Wire returns a wire holding value INV(input[0]). func (c *Compiler) InvI0Wire() *Wire { if c.invI0Wire == nil { - c.invI0Wire = NewWire() + c.invI0Wire = c.Calloc.Wire() c.AddGate(NewINV(c.InputWires[0], c.invI0Wire)) } return c.invI0Wire @@ -67,7 +70,7 @@ func (c *Compiler) InvI0Wire() *Wire { // ZeroWire returns a wire holding value 0. func (c *Compiler) ZeroWire() *Wire { if c.zeroWire == nil { - c.zeroWire = NewWire() + c.zeroWire = c.Calloc.Wire() c.AddGate(NewBinary(circuit.AND, c.InputWires[0], c.InvI0Wire(), c.zeroWire)) c.zeroWire.SetValue(Zero) @@ -78,7 +81,7 @@ func (c *Compiler) ZeroWire() *Wire { // OneWire returns a wire holding value 1. func (c *Compiler) OneWire() *Wire { if c.oneWire == nil { - c.oneWire = NewWire() + c.oneWire = c.Calloc.Wire() c.AddGate(NewBinary(circuit.OR, c.InputWires[0], c.InvI0Wire(), c.oneWire)) c.oneWire.SetValue(One) @@ -297,7 +300,7 @@ func (c *Compiler) ShortCircuitXORZero() { g.B.Input().ResetOutput(g.O) // Disconnect gate's output wire. - g.O = NewWire() + g.O = c.Calloc.Wire() stats[g.Op]++ } @@ -307,7 +310,7 @@ func (c *Compiler) ShortCircuitXORZero() { g.A.Input().ResetOutput(g.O) // Disconnect gate's output wire. - g.O = NewWire() + g.O = c.Calloc.Wire() stats[g.Op]++ } diff --git a/compiler/circuits/wire.go b/compiler/circuits/wire.go index f9119650..ba20fc49 100644 --- a/compiler/circuits/wire.go +++ b/compiler/circuits/wire.go @@ -9,8 +9,6 @@ package circuits import ( "fmt" "math" - - "github.com/markkurossi/mpc/types" ) const ( @@ -51,25 +49,6 @@ func (v WireValue) String() string { } } -// NewWire creates an unassigned wire. -func NewWire() *Wire { - w := new(Wire) - w.Reset(UnassignedID) - return w -} - -// MakeWires creates bits number of wires. -func MakeWires(bits types.Size) []*Wire { - result := make([]*Wire, bits) - wires := make([]Wire, bits) - for i := 0; i < int(bits); i++ { - w := &wires[i] - w.id = UnassignedID - result[i] = w - } - return result -} - // Reset resets the wire with the new ID. func (w *Wire) Reset(id uint32) { w.SetOutput(false) diff --git a/compiler/circuits/wire_test.go b/compiler/circuits/wire_test.go index 71efa6c6..7f573974 100644 --- a/compiler/circuits/wire_test.go +++ b/compiler/circuits/wire_test.go @@ -11,7 +11,7 @@ import ( ) func TestWire(t *testing.T) { - w := NewWire() + w := calloc.Wire() if w.ID() != UnassignedID { t.Error("w.ID") } diff --git a/compiler/ssa/circuitgen.go b/compiler/ssa/circuitgen.go index 96378fa6..4cb5255e 100644 --- a/compiler/ssa/circuitgen.go +++ b/compiler/ssa/circuitgen.go @@ -19,7 +19,9 @@ import ( func (prog *Program) CompileCircuit(params *utils.Params) ( *circuit.Circuit, error) { - cc, err := circuits.NewCompiler(params, prog.Inputs, prog.Outputs, + calloc := circuits.NewAllocator() + + cc, err := circuits.NewCompiler(params, calloc, prog.Inputs, prog.Outputs, prog.InputWires, prog.OutputWires) if err != nil { return nil, err @@ -461,7 +463,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { // Assign output wires. for _, wg := range wires { for _, w := range wg { - o := circuits.NewWire() + o := cc.Calloc.Wire() cc.ID(w, o) cc.OutputWires = append(cc.OutputWires, o) } @@ -497,7 +499,7 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { // Add intermediate wires. nint := instr.Circ.NumWires - len(circWires) - len(circOut) for i := 0; i < nint; i++ { - circWires = append(circWires, circuits.NewWire()) + circWires = append(circWires, cc.Calloc.Wire()) } // Append output wires. diff --git a/compiler/ssa/program.go b/compiler/ssa/program.go index d2bdeba1..0a0da0ff 100644 --- a/compiler/ssa/program.go +++ b/compiler/ssa/program.go @@ -30,6 +30,7 @@ type Program struct { Constants map[string]ConstantInst Steps []Step walloc WireAllocator + calloc *circuits.Allocator zeroWire *circuits.Wire oneWire *circuits.Wire stats circuit.Stats @@ -43,13 +44,16 @@ type Program struct { func NewProgram(params *utils.Params, in, out circuit.IO, consts map[string]ConstantInst, steps []Step) (*Program, error) { + calloc := circuits.NewAllocator() + prog := &Program{ Params: params, Inputs: in, Outputs: out, Constants: consts, Steps: steps, - walloc: NewWAllocValue(), + walloc: NewWAllocValue(calloc), + calloc: calloc, } // Inputs into wires. @@ -265,6 +269,7 @@ func (prog *Program) DefineConstants(zero, one *circuits.Wire) error { // StreamDebug print debugging information about streaming mode. func (prog *Program) StreamDebug() { prog.walloc.Debug() + prog.calloc.Debug() } // PP pretty-prints the program to the argument io.Writer. diff --git a/compiler/ssa/streamer.go b/compiler/ssa/streamer.go index 496b7c04..001d0416 100644 --- a/compiler/ssa/streamer.go +++ b/compiler/ssa/streamer.go @@ -434,17 +434,18 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, startTime := time.Now() for _, in := range wires { - w := circuits.MakeWires(types.Size(len(in))) + w := prog.calloc.Wires(types.Size(len(in))) cIn = append(cIn, w) flat = append(flat, w...) } - cOut := circuits.MakeWires(instr.Out.Type.Bits) + cOut := prog.calloc.Wires(instr.Out.Type.Bits) for i := types.Size(0); i < instr.Out.Type.Bits; i++ { cOut[i].SetOutput(true) } - cc, err := circuits.NewCompiler(params, nil, nil, flat, cOut) + cc, err := circuits.NewCompiler(params, prog.calloc, nil, nil, + flat, cOut) if err != nil { return nil, nil, err } diff --git a/compiler/ssa/wire_allocator_string.go b/compiler/ssa/wire_allocator_string.go index 3425be0c..809096e8 100644 --- a/compiler/ssa/wire_allocator_string.go +++ b/compiler/ssa/wire_allocator_string.go @@ -17,6 +17,7 @@ import ( // WAllocString implements WireAllocator using Value.String to map // values to wires. type WAllocString struct { + calloc *circuits.Allocator freeWires map[types.Size][][]*circuits.Wire wires map[string]*wireAlloc nextWireID uint32 @@ -25,8 +26,9 @@ type WAllocString struct { } // NewWAllocString creates a new WAllocString. -func NewWAllocString() WireAllocator { +func NewWAllocString(calloc *circuits.Allocator) WireAllocator { return &WAllocString{ + calloc: calloc, wires: make(map[string]*wireAlloc), freeWires: make(map[types.Size][][]*circuits.Wire), } @@ -127,7 +129,7 @@ func (walloc *WAllocString) allocWires(bits types.Size) *wireAlloc { walloc.freeWires[bits] = fl[:len(fl)-1] walloc.flHit++ } else { - result.Wires = circuits.MakeWires(bits) + result.Wires = walloc.calloc.Wires(bits) walloc.flMiss++ } diff --git a/compiler/ssa/wire_allocator_value.go b/compiler/ssa/wire_allocator_value.go index 791d7aea..50b60a15 100644 --- a/compiler/ssa/wire_allocator_value.go +++ b/compiler/ssa/wire_allocator_value.go @@ -17,6 +17,7 @@ import ( // WAllocValue implements WireAllocator using Value.HashCode to map // values to wires. type WAllocValue struct { + calloc *circuits.Allocator freeWires map[types.Size][][]*circuits.Wire wires [10240]*allocByValue nextWireID uint32 @@ -40,8 +41,9 @@ func (alloc *allocByValue) String() string { } // NewWAllocValue creates a new WAllocValue. -func NewWAllocValue() WireAllocator { +func NewWAllocValue(calloc *circuits.Allocator) WireAllocator { return &WAllocValue{ + calloc: calloc, freeWires: make(map[types.Size][][]*circuits.Wire), } } @@ -106,7 +108,7 @@ func (walloc *WAllocValue) alloc(bits types.Size, v Value) *allocByValue { walloc.freeWires[bits] = fl[:len(fl)-1] walloc.flHit++ } else { - result.wires = circuits.MakeWires(bits) + result.wires = walloc.calloc.Wires(bits) walloc.flMiss++ } From 2829b3aa0547ff1a5e322cdf33731b4d44c1a47b Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Wed, 30 Aug 2023 09:00:09 +0200 Subject: [PATCH 13/19] Optimized compiler/circuits/Wire memory usage. Cleaned up circuits compiler API. --- apps/garbled/default.pgo | Bin 24408 -> 23713 bytes benchmarks.md | 25 ++++ compiler/circuits/allocator.go | 65 +++++++++-- compiler/circuits/circ_adder.go | 40 +++---- compiler/circuits/circ_binary.go | 28 ++--- compiler/circuits/circ_comparators.go | 90 +++++++------- compiler/circuits/circ_divider.go | 34 +++--- compiler/circuits/circ_hamming.go | 14 +-- compiler/circuits/circ_index.go | 22 ++-- compiler/circuits/circ_multiplier.go | 40 +++---- compiler/circuits/circ_mux.go | 14 +-- compiler/circuits/circ_subtractor.go | 30 ++--- compiler/circuits/compiler.go | 162 +++++++++++++------------- compiler/circuits/gates.go | 46 ++------ compiler/circuits/wire.go | 55 ++++++--- compiler/compiler.go | 2 +- compiler/ssa/circuitgen.go | 8 +- 17 files changed, 365 insertions(+), 310 deletions(-) diff --git a/apps/garbled/default.pgo b/apps/garbled/default.pgo index 6debd042caedfc5c3dab2209518fe0e8ac3c9ddd..1c5af0ae0cfaeed7fae96c7576f8376be15551f8 100644 GIT binary patch literal 23713 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 Q9{>RV|9~3bw!qf_0P`0ebN~PV literal 24408 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
3^yIptk&yFG4QD{(7ULsZ!9 zx~Q0{^jy?+cgG6rRTw-+WFMzf{*>?{rc>Tsf$oaYL-9Xp0^6X%hHV052IXL7z~2>+ zGbrz=Ku^W!rTD8#^Q>1lsIpv`X>6kkD=@R5VQIiFD3nStY?RlbvrQ@t+vO~IYvPhO zNuWB|+mM|4L}D1Ir*q&%vt~t;ea#gkk){&1n1h$22vcX9Rk)sDC-8HDDr}kT)Wz|r zr{tDy7Pe!H=OS-a;oGl;i`><%6yxueN!zwnUW2!ra{bUuW7||%^F4IiCJ&VER2+RH ziIEt)NdXB~rR8``f7A{7EK(($=SySTRhWwGr?Huo|H%}E?{+9JDfptd0=*TZkK+HM zw2tjER*yPccs%&bq$512n@M;^a2&b>c7&agf(?BY=&Rsao2XPb z|Hciov0$UZ!V``J+9uMDA1d#aur0+{GFeQWeXBwX_cYGsMB;ucjbf&=eJU(yD&n3Z z%4bLzOBn8xG2E|0i|dYS{1v*!UZK-slKn1|m^wS4LVp*PD~YHakWulmgDNZ^P|U2m zLs5cTxb7URDDjQ&t)A~ zPpBtV{H-S|?~|y(Or4!lVc~g)CnqPmZKo2~a$1E=_eoTpR!eQl85LH!M)To>b)Qia z3+9rfW9}`1+tb)t6>i=~@jIe(Rzg!^70fmz7)_m>Q{l>LXR(`b`@M}jCyjzvH&j@O z8d|944vDONJ`wbS3I|=oIv^4BLL%rz6$azM9x>|iLs*$lQ8MjABXpx*h{uCl}6Z9y1?mUmsMzU)se}+a@oFKmLw8CK&Zme4x4eVIaK`E zOhzg`^;e+3(1PpG#$Q$8+HGOun^N8ai!C0(uc%jD)gwt;w?tOb*)jqYo;Xxs<00QJG8m00jmp#z4iNiiIN;I$ju#XwRb@KR1_=icfW7gYCN=NfFMXrw_kVi!`rTgFii`!Aw^J|2jbfH`QdtAc;EGn`G`S z#g++L09Z2(T3V_n;n0YLR5LBXXs5I08fg1h>_R(8~$#hMyH{aNJUo z&Y6w*lm|_XEucKl+Vd$`hDgB|Llqb*=-5!=nqrA!5^G@j#qn|_3ZQ9_>1OJzg$7fm ziG0LD%3E5AxrLMuQ(%~4j3xf{NoG20slmLR0%#HCd+`+2KuQn`ZKrjTbK>c&y#~kc{b>;IAWC~@M*MW4 z1}88ru3i+}2v+H!$?5CrtfK}iacGS#rTif?2{YeUkpJw=cSZ^}j8b5fVvJV& zx-?@)cSfCc(qNXm9ATI}3G9@x9I0UtmDeey6Gq>aV8Az@)yBh)oi$kOoI)HepF(`n zk%{!qnsjp{K?z9~22YcSL_iwrqCrc{RVU$blBM@dd34dNl!y1V?Sx6%l`7ilPdS0u zqX}?{QK(s+b=9CPt|kf3FnDMAy`zBM*~+f+suBm1NQa+0!Ay*gb<^O4l^wG`D(Hm} z-88XsVBIxnYr%x@>mc_?OLf;Uar6|g%!~T>|M^|ZNr)7I_0XWxy_2T4udMaJ@l2id z)L@F6Cz_U+=IW_gRfKfbOM_Wf&0!hkcaSYh2wz=Eq-vH?K1P8tLPgw=ugmn(O5JNI zg=B9HhC9h8>DMx(>{xFb$vzqkc9AS~F1C+_q{LB}-m(Fu9$Mk)+E;^B&J^sD|P9p)EEOam|$(q*VNJ&C4{fAw&rWf$0{&ZY|Ul4J~Tjcc6Oi! zvs@HL$tZYv#(~-hk*4$*cZHSm^Q7>uYhnpl>THk(6Rf*NC4_HHWV1mwGQb9F(A{BM zIfd;_JlK*Loej}oh;`$qnDUj0`!z(f?(s<5yh8|MIvc7%vjxKTw4}VR&B*v`ypre+ zweiu}Fb(z{7tqToUu9XL<&=+8V4Pyq^{>K;Rfz_NTZ>Sw zT7!euHYD6=GLx_uUc8dV9&PoKcC&}Gn`1N>=X#`g%Ns8+X)xA3+%eb6D%jeKkI~Re z9jm#AGt${O4W?KewvzHySaPvBEh7crOi*9~M!qXm38Qh=PD^j_S21w?)!BFrnyzcl zOpOu#EiPyk<=ybVBk>2%1i@EPK2d>*ic!(uRgso8UR!1T?92oWCb+kxhT{@k=9r){ zaZ75V2HV_oJ$Uoe)Y&8rI=X>`2*@6Cj@~3s)Fx^6ffSuh*5HEMc?9m!L%bp{>Tx+E zIeD@RDCPaUqa=?1h;>N~)}9o+w3_m7Esu0H<&zYcguJvDe^hm<2E~Iq;Fr^D^7j(H z;W9;=s{QPhpTGKA#xr`llN+?so~3R#Ow(XsNoQszu{D%WdR&lj4ds&+n5-Dj`b!?i zpDfeh%xD3#mhv?g%v#E)C@=+KRv^p_4OUI)j^ivRd@yidE{7stykGq~a}>OG=Gt+H`G(HWNL`SsENK9Rb^PWV0M{r?c4_3~_^lF{nK3 zRG#Eq&US+X{IZz_7u=N3G~Azu9V{x(k$ihoXLB`Zff)-C9zBr7jVB<9x|9CC>{ z1bN82&h)iR%;g%4a{)V8fJxRjfs&WYfGae(a8#sM&JpWux994ta8UKJl^QISPqs)5 zxl$Y9m08l+Dh-a@^V^-0=2@kUDeWhDX?#q&CB*JJTdlzgcQw7N)!4po@@jcCYcy!@ zCVILi=F!$9l5(vEV_X@-Umlf~v{nZ7v2_|OaWj!Vk%_eThp*G--Y*A%mFV4ym*tR_ zh?R1>MzH}O74!uj1&N8Pr_U39ipgCnOL`gKP3@#wcT5p>lC^sP8{IE>;AF-g4IsynouS|8aZ(%CKzmbgR^ z=lwh)?2=X{?J-tiWgCH2GYtl~7d;a`a^Wq0w`}$HXmI9=s6A|=e6y8M-9-6J1!gM7 zfBfq)q56#medjhug}Iq>`DW9dlECqk(emeee>Rt_6%n_%D8}sRSX3e@=E#)}( zXy0hH_i9C?wGbdT-|g+djs(Rb3FH$-DO0DjZ#B4c#9^9JXQaQC=w+s{eHxrVrqS?7 z`RA5omAcWhFR{md4K86RD~WBPd=cK@IzX_bg`aq(e6|9!#VNRjn1Hg`FJ-?b7qqZ+ zra{jW4u&lg+4_JqpR~@6rPB3MnP;YQqUeN)9oOK7yNFeZ z3h21J2%VkKV61z5x=Z5zpO8V**+~t0exI{?QbyyH29w>8@&J|*)7fbarWHA*;8F5g zJmY;z#FD4wq0VTq+RA)4ry|EKQR|G1rq0f4(EOysV)z@E66je8RGP47sZyZnW9KxO z;<|u|)!cKMtk0xj@EA@h@9?|^LoPbVh!dJ#J?6ZGjA^n98qC3a=m~dQ9djlwqlE^BZd z?-?f;SlcbNby4NZ(j1b0J*>*oj-PqcVC;Ri;!2`5)Y(-HI=Y8>L}A@CDHFA;wmrok zAk$!qdza#sgjB{gE#cn8y<-~mt{mfF>g>7(-CbJVOr+&?X`N;oyP-ipbj0K{H(L{B z^hRQjn;P`~o*p-)9uh-fbt}PN(Ag~wX1ltulnK9e?@`d1E~Qa#Yp~n(>ZT+1o`Jlb zNIy@mQ9fjBq$VuJCrsgEcQjaJonYHa?QcMCrF@P8a};B);{U1C`;J!XT$+!4r@=P& zz{AVJ-M0CTe^<)gHhrup3C7%q#fp*=*fE_oO@cF)#@t4ESJYs5bk9n1Y@>Xh0`n9j z-``2rJ57`B>B1vH5*%!uHA{jvu01ZLx|>O(;El2*xZ&mkrY5TH=82%iNifmE*~V4U zCB-sm8f%dRd(o>Fe#pP>>4&tCd!)0LNwCUQ4pWdIcblh{Nlt~+tj<~`!EDR>cFLGN zsB`tal#Xa6O(ktO9>pr9%~~hHQA>R`zw09)t))>+owZ4VIVeveGqMYr*VC+RBv5HW z)T{|RmB!j8K}$TymBhAF-tt9LV^8sA)&+y@_%R`vkNKr$FG^{=wn=!v%k;5!NwCtY zBU|63k))?xQo=ksYo7$)+)uT%m#{U{S%)Mz=33~n_uh#P?oLR{!vim+ELX=Q=;(&F zA~Nxa)KQ)`jde1db50~P_F1qX zlo^Xh3$xLEVC2*q+|_*I(sDR&yZz8Q8sUC|)D8pfL7X;Kwx!@3;DVgp7sanK_Zb6JG!P ztWY=}4d&IzEDUD_vpx&qtYwa4cffH#9tS(u$c%;>Z_WqzSFP>YFmzx!g=QaxcAPV9l-ptO<6$8}z zfq2x8gCBPSf>i0JS1yReGJmz)?1#2#Z~o zNzZK%%n~NOJm#ooFc#1B3vQ3tmb^yh+l_*G56FNn^@Fi^%}}GDaE{7ji?KtdKjU}7 zXf6`*tx$s+nRV;F8L0ikHXo;t#ez8@ApzyF0N6!%6j=>}IiVVv?}c;YfwzA6>;=|9 z$bb*9{yJ8#8kx4<2|Vg-dO`fNXegLn4+DaFvD}<+FfZ_v`-@_%NvvLWFxog*5Wr=f zSSUZ(IM^^$SP*#n{+%QD>>G~6L*L&wFOt=;9!`TRcNsqx4@Tn>lZf2jb!>W5Ee}y{`X!xB;c%nW51N#hyZ;v{(dMJiwDy0o+UR@GgrV?vSHsT%*zW@ z4TZBJ*}37In)P!r+z_J#p1HewDL4l_ClYwJEPI+8%__`|TmIw+b!xv~`$5gOHGmWf z`pY|Ax!l*9Xz0QsI;m|<^?y@0--hCa(XiF6JWi14_I_?GE-dc@VE0a8ILn&m0kC^BhC12m=IXipV0NS2SSa9T zFJet%pT$4rdOq;DO_S0sHVNE3U8#<;iENPjg&52xURe%Gz5|Pks_smkNPLksd_R)aFqAE= zE-M(eLyo4o0}%RMwYf-3+{KRlt^5U6Z}%*6jO~JC&(=#(?lsP};oeD5uS<>rN~w+}!8kwLWy=eqb9oPT7cAIkE&>*vPv zgF>6T;!q&RT#bXdacQ}s{9r6E6e{puVSHr0^5XkC!DvJ8&saQCkY7Gdh{b}LwQ|Gg zsl_sD=V5&E_777uu~-nDH*2JvNcr_fZmdqEaVYvxgMb%>)-TL!SSuKf=7yqmq9Mxy zxj|mz`X59Zg~|+$>RWvgj|bt%uP z?-8mvVq=5YK~AiEZWQkJhoQ!G3+ulZj>V&a_kQ?R=i&f`S@GORIEG|)asMnB&W{axg}oKQFv4aOs4(|=JOQ-{>>XT0n5!(>P}77vEw6uzSkvv5wVsRSzKhw?+8*Nws)=Jfx7yD(meyX$G9BU39k9(yMeeKR{d>S^_GsPUW8P&oKO zu*s)kT(Xz%2!|SDx&*t)K7FilC{$p%k~K2LA7v`JoGbx@*tnDwZ%~W7@>oAdyepp` zM3VWDH-#Fnky$rXzFfL33~z@US$$mUV2%ryStAqqwjjZY#`-v>Jg|N(Vu9Mhtj|Io zm8|@;r5MWPEhF{~^5g##YEnK)MEC>+@L{O&?}B;d6AMfH-~X-Puo{`~i~p8yUaPBZ zG=u^W&z^-t%6!z|9&U8~U@YW$968ZY6FbUy&an%wp77kM+;IHm7g4+A7X+iBH^bR= z8wU#lrrjgcpJ9RiIuy+QB$%5gaH)}*6^zMVIih48dx#|NBJzI?af|@{?sp>5y2!Cr%>KQ4N$gy*q1%+{m;hV%h2o`))S0bKxBX_iM6k&twt~IV- ztFXa4d3cDUtg{o1>(?(7uB02G#G4d^vPD@$s4REDAPT!6Uir20hoQ!`zKBFEm0E5c zj`YcoG_rcRUZg+?r7Rw?g{F*kSrDi?M6CXHG#ZIE2t0AO-NCp%gssBLKV62wK)S@N zj9lQg2eJ;LrqGEq9+#rIf)L101Q3%t=;jGMDjELUHGfDT#Rm7TvvuYMZ zBBE-<@h_~%L?U@LG7ECUrQV>OOu>9cIMi6AG(ypUNQTrE>5c~FiyLgVu8Rj`tbKF& zNm7xy@kH4|p{tRJ>2}ni?;xXOhrYOn63xzSl$#x@)#UBGP=4LqFGC(z_?-wmQvdH3 zb3TX|e&BFfnQpC>;KcNy^;9GCO_6kaACCyc0>647X0S(d{)-Gzo`W)i!hXk1iHek@ z?d$x2{#HuU>F)-gDDYO5!n|8jY<*f&03h0~Q4WV1i0vJ<0fX9~{K2xTZkbM` z>9AgojA8xd8C_J#_`Yr^jsjBl<~ccIu~0NOn1_tyMqPzg%<1|Skyw-5P+qpT z%2;8nAQaAix2)*bE`uR;T>eM)jz%JJJB8;`&3W&U9}f;#lS+wH$tu(e#2{0C3N zKkwy{yqETTE*}1jyar;MYT${&vQ}Zk;VNA1ACET43B?;=?L#`G=O7o4HbFh&uFbBq zedx`elZ7%9iwj5Sd2gc=-rFd0yQ#oLhn@#L;ys8J{!_v9`dieoYIUbQqw1KA+;8h&0FDikKwWjySPdl+!t z2HcaZtWYfWyn|)UGK_qJx`>7vg`%-g;K{6LlY)4}^F70uv(G|5rB2|bm&K8uz>9Zl z%zJmMSrB;X<$A%G*rQ+AFrmD>+=93`iHw8SE{rw`c}IjZ3UkBpSN@8En;VY56OH7* z*`PsgICL)}Wm&j713I|oL;}y3YRdGNIoF%n*<}_DBf=tngo-jar?i~ld_JnOJnNMuw}aaTSl%zMv186Nog1MjCBy+3Ue+Kp9kOG8se#P$^pW6SvEdzl_?}Idde$nhm`J+o ztq`(hZOr)u!|_=8U4A^Wlvfz4ky%U54(A6OhJ@Q+zKoUaTE#Ly2sRA;J~tYQ{m_J) zGg8gQxly5D%AJt$XZ&86I1w&8CAg0tNH}C2BoL@468M<=tLNrt%5%Wn7Muix&}!y{ zL)jL2+zdOOJ!#@u*jOl>otYC6Z*8MoX4GQ&#hLyLOEz=EIW;mv;aFkR-i8Ovxm&~8 z@>$P1+ldqa&9>+*$+HjCo%EmRjGJN{8& z9P?A}mc`jl$R+i+&tW=6h&s_oK`;kd?>7%b{kw*C-yl)TjQnkJOHff5v!o00i_{E; zVva0|S^yg8dz{hspc4)io8Eg&IEG@ia|=ETMc<6&*T{U^mhgKVr}KK%%n}_Ozp%`F zu)P#pbNo7(*T4%33xYWzkq7yxLBJ?=_$;%2ZqD1`?A%~jTpD}%#lTZG0DxRj#>bKf8BMB=F?@XRcEiw=%Y#(;h7hf1VQ!*7qV|d?U_> z%jJW--2B{lDEciKc@{K0BH@PjC~(Sh*SxG=R$&s;DA|(o zh=j9(aVv)^>lEp=6w?K>J}=CThVskW4O>e-kL!|jU$k+4VI0qPL`4DVsk?n{yJJlo z68o2F$Q|8-ba9=^x3QM^AVt#zmnlyin+g2bLB$i6xN~_F!!tZz6U6!UM3y+AdgG;B0&?j=b3fm!Ekmknq6DOxq(;iW|{kU#sSb7!L+&ln1z0lXxiRjtAkD|CQdB$16aD!+tKZI8eLeWH6k!|&i58gz_#oo)UaC8f{*9%7) z1RDx+xOreq7GugA4H1^sWhdNi%Z=Gh&)CBW|GakTD;NlmPCMSoh!lAEH4f(G;ryP0 zMh2;!7mUR+!x3C*;3wX|mhuy)Lq$#lWxTF?Yu-iNIr}B_d_AG%UFDS%5m&q3%P+{2 zU)o9ZdifqT^CQuaMa?RRVJ6-s%dA@{dkn$Gmc8;EWA{}&zd%}3C~hB%v(MT6#95R5 zS_In(2kLAm+RcSq9X|-h8V1sAWmDRFd^d70+3y*hYRy-WQIdu#Q*;Q00960Xc#lD@!bFb?6ZU= diff --git a/benchmarks.md b/benchmarks.md index 203d1cc2..b6b08efa 100644 --- a/benchmarks.md +++ b/benchmarks.md @@ -563,6 +563,31 @@ Max permanent wires: 53913890, cached circuits: 25 #gates=830166294 (XOR=533177896 XNOR=28813441 AND=267575026 OR=496562 INV=103369 xor=561991337 !xor=268174957 levels=10548 width=1796) #w=853882864 ``` +Optimized `compiler/circuits/Wire`: + +``` +┌──────────────┬────────────────┬─────────┬────────┐ +│ Op │ Time │ % │ Xfer │ +├──────────────┼────────────────┼─────────┼────────┤ +│ Compile │ 1.870993069s │ 2.71% │ │ +│ Init │ 2.331431ms │ 0.00% │ 0B │ +│ OT Init │ 10.949µs │ 0.00% │ 16kB │ +│ Peer Inputs │ 44.089085ms │ 0.06% │ 57kB │ +│ Stream │ 1m7.0813688s │ 97.22% │ 15GB │ +│ ├╴InstrInit │ 2.421297578s │ 3.61% │ │ +│ ├╴CircComp │ 17.09415ms │ 0.03% │ │ +│ ├╴StreamInit │ 2.155089182s │ 3.21% │ │ +│ ╰╴Garble │ 1m1.550598148s │ 91.76% │ │ +│ Result │ 432.27µs │ 0.00% │ 8kB │ +│ Total │ 1m8.999225604s │ │ 15GB │ +│ ├╴Sent │ │ 100.00% │ 15GB │ +│ ├╴Rcvd │ │ 0.00% │ 45kB │ +│ ╰╴Flcd │ │ │ 231284 │ +└──────────────┴────────────────┴─────────┴────────┘ +Max permanent wires: 53913890, cached circuits: 25 +#gates=830166294 (XOR=533177896 XNOR=28813441 AND=267575026 OR=496562 INV=103369 xor=561991337 !xor=268174957 levels=10548 width=1796) #w=853882864 +``` + Theoretical minimum single-threaded garbling time: ``` diff --git a/compiler/circuits/allocator.go b/compiler/circuits/allocator.go index 41b9fadf..50fbf550 100644 --- a/compiler/circuits/allocator.go +++ b/compiler/circuits/allocator.go @@ -8,16 +8,22 @@ package circuits import ( "fmt" + "unsafe" + "github.com/markkurossi/mpc/circuit" "github.com/markkurossi/mpc/types" ) +var ( + sizeofWire = uint64(unsafe.Sizeof(Wire{})) + sizeofGate = uint64(unsafe.Sizeof(Gate{})) +) + // Allocator implements circuit wire and gate allocation. type Allocator struct { - block []Wire - ofs int numWire uint64 numWires uint64 + numGates uint64 } // NewAllocator creates a new circuit allocator. @@ -36,23 +42,60 @@ func (alloc *Allocator) Wire() *Wire { // Wires allocate an array of Wires. func (alloc *Allocator) Wires(bits types.Size) []*Wire { alloc.numWires += uint64(bits) + + wires := make([]Wire, bits) result := make([]*Wire, bits) for i := 0; i < int(bits); i++ { - if alloc.ofs == 0 { - alloc.ofs = 8192 - alloc.block = make([]Wire, alloc.ofs) - } - alloc.ofs-- - w := &alloc.block[alloc.ofs] - + w := &wires[i] w.id = UnassignedID result[i] = w } return result } +// BinaryGate creates a new binary gate. +func (alloc *Allocator) BinaryGate(op circuit.Operation, a, b, o *Wire) *Gate { + alloc.numGates++ + gate := &Gate{ + Op: op, + A: a, + B: b, + O: o, + } + a.AddOutput(gate) + b.AddOutput(gate) + o.SetInput(gate) + + return gate +} + +// INVGate creates a new INV gate. +func (alloc *Allocator) INVGate(i, o *Wire) *Gate { + alloc.numGates++ + gate := &Gate{ + Op: circuit.INV, + A: i, + O: o, + } + i.AddOutput(gate) + o.SetInput(gate) + + return gate +} + // Debug print debugging information about the circuit allocator. func (alloc *Allocator) Debug() { - fmt.Printf("circuits.Allocator: #wire=%v, #wires=%v\n", - alloc.numWire, alloc.numWires) + wireSize := circuit.FileSize(alloc.numWire * sizeofWire) + wiresSize := circuit.FileSize(alloc.numWires * sizeofWire) + gatesSize := circuit.FileSize(alloc.numGates * sizeofGate) + + total := float64(wireSize + wiresSize + gatesSize) + + fmt.Println("circuits.Allocator:") + fmt.Printf(" wire : %9v %5s %5.2f%%\n", + alloc.numWire, wireSize, float64(wireSize)/total*100.0) + fmt.Printf(" wires: %9v %5s %5.2f%%\n", + alloc.numWires, wiresSize, float64(wiresSize)/total*100.0) + fmt.Printf(" gates: %9v %5s %5.2f%%\n", + alloc.numGates, gatesSize, float64(gatesSize)/total*100.0) } diff --git a/compiler/circuits/circ_adder.go b/compiler/circuits/circ_adder.go index 5b634e23..0515c7f9 100644 --- a/compiler/circuits/circ_adder.go +++ b/compiler/circuits/circ_adder.go @@ -13,46 +13,46 @@ import ( ) // NewHalfAdder creates a half adder circuit. -func NewHalfAdder(compiler *Compiler, a, b, s, c *Wire) { +func NewHalfAdder(cc *Compiler, a, b, s, c *Wire) { // S = XOR(A, B) - compiler.AddGate(NewBinary(circuit.XOR, a, b, s)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, a, b, s)) if c != nil { // C = AND(A, B) - compiler.AddGate(NewBinary(circuit.AND, a, b, c)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, a, b, c)) } } // NewFullAdder creates a full adder circuit -func NewFullAdder(compiler *Compiler, a, b, cin, s, cout *Wire) { - w1 := compiler.Calloc.Wire() - w2 := compiler.Calloc.Wire() - w3 := compiler.Calloc.Wire() +func NewFullAdder(cc *Compiler, a, b, cin, s, cout *Wire) { + w1 := cc.Calloc.Wire() + w2 := cc.Calloc.Wire() + w3 := cc.Calloc.Wire() // s = a XOR b XOR cin // cout = cin XOR ((a XOR cin) AND (b XOR cin)). // w1 = XOR(b, cin) - compiler.AddGate(NewBinary(circuit.XOR, b, cin, w1)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, b, cin, w1)) // s = XOR(a, w1) - compiler.AddGate(NewBinary(circuit.XOR, a, w1, s)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, a, w1, s)) if cout != nil { // w2 = XOR(a, cin) - compiler.AddGate(NewBinary(circuit.XOR, a, cin, w2)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, a, cin, w2)) // w3 = AND(w1, w2) - compiler.AddGate(NewBinary(circuit.AND, w1, w2, w3)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, w1, w2, w3)) // cout = XOR(cin, w3) - compiler.AddGate(NewBinary(circuit.XOR, cin, w3, cout)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, cin, w3, cout)) } } // NewAdder creates a new adder circuit implementing z=x+y. -func NewAdder(compiler *Compiler, x, y, z []*Wire) error { - x, y = compiler.ZeroPad(x, y) +func NewAdder(cc *Compiler, x, y, z []*Wire) error { + x, y = cc.ZeroPad(x, y) if len(x) > len(z) { x = x[0:len(z)] y = y[0:len(z)] @@ -63,10 +63,10 @@ func NewAdder(compiler *Compiler, x, y, z []*Wire) error { if len(z) > 1 { cin = z[1] } - NewHalfAdder(compiler, x[0], y[0], z[0], cin) + NewHalfAdder(cc, x[0], y[0], z[0], cin) } else { - cin := compiler.Calloc.Wire() - NewHalfAdder(compiler, x[0], y[0], z[0], cin) + cin := cc.Calloc.Wire() + NewHalfAdder(cc, x[0], y[0], z[0], cin) for i := 1; i < len(x); i++ { var cout *Wire @@ -78,10 +78,10 @@ func NewAdder(compiler *Compiler, x, y, z []*Wire) error { cout = z[len(x)] } } else { - cout = compiler.Calloc.Wire() + cout = cc.Calloc.Wire() } - NewFullAdder(compiler, x[i], y[i], cin, z[i], cout) + NewFullAdder(cc, x[i], y[i], cin, z[i], cout) cin = cout } @@ -89,7 +89,7 @@ func NewAdder(compiler *Compiler, x, y, z []*Wire) error { // Set all leftover bits to zero. for i := len(x) + 1; i < len(z); i++ { - z[i] = compiler.ZeroWire() + z[i] = cc.ZeroWire() } return nil diff --git a/compiler/circuits/circ_binary.go b/compiler/circuits/circ_binary.go index 45e51fa0..066576dd 100644 --- a/compiler/circuits/circ_binary.go +++ b/compiler/circuits/circ_binary.go @@ -11,55 +11,55 @@ import ( ) // NewBinaryAND creates a new binary AND circuit implementing r=x&y -func NewBinaryAND(compiler *Compiler, x, y, r []*Wire) error { - x, y = compiler.ZeroPad(x, y) +func NewBinaryAND(cc *Compiler, x, y, r []*Wire) error { + x, y = cc.ZeroPad(x, y) if len(r) < len(x) { x = x[0:len(r)] y = y[0:len(r)] } for i := 0; i < len(x); i++ { - compiler.AddGate(NewBinary(circuit.AND, x[i], y[i], r[i])) + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, x[i], y[i], r[i])) } return nil } // NewBinaryClear creates a new binary clear circuit implementing r=x&^y. -func NewBinaryClear(compiler *Compiler, x, y, r []*Wire) error { - x, y = compiler.ZeroPad(x, y) +func NewBinaryClear(cc *Compiler, x, y, r []*Wire) error { + x, y = cc.ZeroPad(x, y) if len(r) < len(x) { x = x[0:len(r)] y = y[0:len(r)] } for i := 0; i < len(x); i++ { - w := compiler.Calloc.Wire() - compiler.INV(y[i], w) - compiler.AddGate(NewBinary(circuit.AND, x[i], w, r[i])) + w := cc.Calloc.Wire() + cc.INV(y[i], w) + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, x[i], w, r[i])) } return nil } // NewBinaryOR creates a new binary OR circuit implementing r=x|y. -func NewBinaryOR(compiler *Compiler, x, y, r []*Wire) error { - x, y = compiler.ZeroPad(x, y) +func NewBinaryOR(cc *Compiler, x, y, r []*Wire) error { + x, y = cc.ZeroPad(x, y) if len(r) < len(x) { x = x[0:len(r)] y = y[0:len(r)] } for i := 0; i < len(x); i++ { - compiler.AddGate(NewBinary(circuit.OR, x[i], y[i], r[i])) + cc.AddGate(cc.Calloc.BinaryGate(circuit.OR, x[i], y[i], r[i])) } return nil } // NewBinaryXOR creates a new binary XOR circuit implementing r=x^y. -func NewBinaryXOR(compiler *Compiler, x, y, r []*Wire) error { - x, y = compiler.ZeroPad(x, y) +func NewBinaryXOR(cc *Compiler, x, y, r []*Wire) error { + x, y = cc.ZeroPad(x, y) if len(r) < len(x) { x = x[0:len(r)] y = y[0:len(r)] } for i := 0; i < len(x); i++ { - compiler.AddGate(NewBinary(circuit.XOR, x[i], y[i], r[i])) + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, x[i], y[i], r[i])) } return nil } diff --git a/compiler/circuits/circ_comparators.go b/compiler/circuits/circ_comparators.go index c31c2489..3160f75c 100644 --- a/compiler/circuits/circ_comparators.go +++ b/compiler/circuits/circ_comparators.go @@ -14,152 +14,148 @@ import ( ) // comparator tests if x>y if cin=0, and x>=y if cin=1. -func comparator(compiler *Compiler, cin *Wire, x, y, r []*Wire) error { - x, y = compiler.ZeroPad(x, y) +func comparator(cc *Compiler, cin *Wire, x, y, r []*Wire) error { + x, y = cc.ZeroPad(x, y) if len(r) != 1 { return fmt.Errorf("invalid lt comparator arguments: r=%d", len(r)) } for i := 0; i < len(x); i++ { - w1 := compiler.Calloc.Wire() - compiler.AddGate(NewBinary(circuit.XNOR, cin, y[i], w1)) - w2 := compiler.Calloc.Wire() - compiler.AddGate(NewBinary(circuit.XOR, cin, x[i], w2)) - w3 := compiler.Calloc.Wire() - compiler.AddGate(NewBinary(circuit.AND, w1, w2, w3)) + w1 := cc.Calloc.Wire() + cc.AddGate(cc.Calloc.BinaryGate(circuit.XNOR, cin, y[i], w1)) + w2 := cc.Calloc.Wire() + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, cin, x[i], w2)) + w3 := cc.Calloc.Wire() + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, w1, w2, w3)) var cout *Wire if i+1 < len(x) { - cout = compiler.Calloc.Wire() + cout = cc.Calloc.Wire() } else { cout = r[0] } - compiler.AddGate(NewBinary(circuit.XOR, cin, w3, cout)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, cin, w3, cout)) cin = cout } return nil } // NewGtComparator tests if x>y. -func NewGtComparator(compiler *Compiler, x, y, r []*Wire) error { - return comparator(compiler, compiler.ZeroWire(), x, y, r) +func NewGtComparator(cc *Compiler, x, y, r []*Wire) error { + return comparator(cc, cc.ZeroWire(), x, y, r) } // NewGeComparator tests if x>=y. -func NewGeComparator(compiler *Compiler, x, y, r []*Wire) error { - return comparator(compiler, compiler.OneWire(), x, y, r) +func NewGeComparator(cc *Compiler, x, y, r []*Wire) error { + return comparator(cc, cc.OneWire(), x, y, r) } // NewLtComparator tests if x= len(x) { out = r[0] } else { - out = compiler.Calloc.Wire() + out = cc.Calloc.Wire() } - compiler.AddGate(NewBinary(circuit.OR, c, xor, out)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.OR, c, xor, out)) c = out } return nil } // NewEqComparator tests if x==y. -func NewEqComparator(compiler *Compiler, x, y, r []*Wire) error { +func NewEqComparator(cc *Compiler, x, y, r []*Wire) error { if len(r) != 1 { return fmt.Errorf("invalid eq comparator arguments: r=%d", len(r)) } // w = x == y - w := compiler.Calloc.Wire() - err := NewNeqComparator(compiler, x, y, []*Wire{w}) + w := cc.Calloc.Wire() + err := NewNeqComparator(cc, x, y, []*Wire{w}) if err != nil { return err } // r = !w - compiler.INV(w, r[0]) + cc.INV(w, r[0]) return nil } // NewLogicalAND implements logical AND implementing r=x&y. The input // and output wires must be 1 bit wide. -func NewLogicalAND(compiler *Compiler, x, y, r []*Wire) error { +func NewLogicalAND(cc *Compiler, x, y, r []*Wire) error { if len(x) != 1 || len(y) != 1 || len(r) != 1 { return fmt.Errorf("invalid logical and arguments: x=%d, y=%d, r=%d", len(x), len(y), len(r)) } - compiler.AddGate(NewBinary(circuit.AND, x[0], y[0], r[0])) + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, x[0], y[0], r[0])) return nil } // NewLogicalOR implements logical OR implementing r=x|y. The input // and output wires must be 1 bit wide. -func NewLogicalOR(compiler *Compiler, x, y, r []*Wire) error { +func NewLogicalOR(cc *Compiler, x, y, r []*Wire) error { if len(x) != 1 || len(y) != 1 || len(r) != 1 { return fmt.Errorf("invalid logical or arguments: x=%d, y=%d, r=%d", len(x), len(y), len(r)) } - compiler.AddGate(NewBinary(circuit.OR, x[0], y[0], r[0])) + cc.AddGate(cc.Calloc.BinaryGate(circuit.OR, x[0], y[0], r[0])) return nil } // NewBitSetTest tests if the index'th bit of x is set. -func NewBitSetTest(compiler *Compiler, x []*Wire, index types.Size, - r []*Wire) error { - +func NewBitSetTest(cc *Compiler, x []*Wire, index types.Size, r []*Wire) error { if len(r) != 1 { return fmt.Errorf("invalid bit set test arguments: x=%d, r=%d", len(x), len(r)) } if index < types.Size(len(x)) { - w := compiler.ZeroWire() - compiler.AddGate(NewBinary(circuit.XOR, x[index], w, r[0])) + w := cc.ZeroWire() + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, x[index], w, r[0])) } else { - r[0] = compiler.ZeroWire() + r[0] = cc.ZeroWire() } return nil } // NewBitClrTest tests if the index'th bit of x is unset. -func NewBitClrTest(compiler *Compiler, x []*Wire, index types.Size, - r []*Wire) error { - +func NewBitClrTest(cc *Compiler, x []*Wire, index types.Size, r []*Wire) error { if len(r) != 1 { return fmt.Errorf("invalid bit clear test arguments: x=%d, r=%d", len(x), len(r)) } if index < types.Size(len(x)) { - w := compiler.OneWire() - compiler.AddGate(NewBinary(circuit.XOR, x[index], w, r[0])) + w := cc.OneWire() + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, x[index], w, r[0])) } else { - r[0] = compiler.OneWire() + r[0] = cc.OneWire() } return nil } diff --git a/compiler/circuits/circ_divider.go b/compiler/circuits/circ_divider.go index 31dc7c64..75ad5479 100644 --- a/compiler/circuits/circ_divider.go +++ b/compiler/circuits/circ_divider.go @@ -7,8 +7,8 @@ package circuits // NewDivider creates a division circuit computing r=a/b, q=a%b. -func NewDivider(compiler *Compiler, a, b, q, r []*Wire) error { - a, b = compiler.ZeroPad(a, b) +func NewDivider(cc *Compiler, a, b, q, r []*Wire) error { + a, b = cc.ZeroPad(a, b) rIn := make([]*Wire, len(b)+1) rOut := make([]*Wire, len(b)+1) @@ -16,13 +16,13 @@ func NewDivider(compiler *Compiler, a, b, q, r []*Wire) error { // Init bINV. bINV := make([]*Wire, len(b)) for i := 0; i < len(b); i++ { - bINV[i] = compiler.Calloc.Wire() - compiler.INV(b[i], bINV[i]) + bINV[i] = cc.Calloc.Wire() + cc.INV(b[i], bINV[i]) } // Init for the first row. for i := 0; i < len(b); i++ { - rOut[i] = compiler.ZeroWire() + rOut[i] = cc.ZeroWire() } // Generate matrix. @@ -32,26 +32,26 @@ func NewDivider(compiler *Compiler, a, b, q, r []*Wire) error { copy(rIn[1:], rOut) // Adders from b{0} to b{n-1}, 0 - cIn := compiler.OneWire() + cIn := cc.OneWire() for x := 0; x < len(b)+1; x++ { var bw *Wire if x < len(b) { bw = bINV[x] } else { - bw = compiler.OneWire() // INV(0) + bw = cc.OneWire() // INV(0) } - co := compiler.Calloc.Wire() - ro := compiler.Calloc.Wire() - NewFullAdder(compiler, rIn[x], bw, cIn, ro, co) + co := cc.Calloc.Wire() + ro := cc.Calloc.Wire() + NewFullAdder(cc, rIn[x], bw, cIn, ro, co) rOut[x] = ro cIn = co } // Quotient y. if len(a)-1-y < len(q) { - w := compiler.Calloc.Wire() - compiler.INV(cIn, w) - compiler.INV(w, q[len(a)-1-y]) + w := cc.Calloc.Wire() + cc.INV(cIn, w) + cc.INV(w, q[len(a)-1-y]) } // MUXes from high to low bit. @@ -60,10 +60,10 @@ func NewDivider(compiler *Compiler, a, b, q, r []*Wire) error { if y+1 >= len(a) && x < len(r) { ro = r[x] } else { - ro = compiler.Calloc.Wire() + ro = cc.Calloc.Wire() } - err := NewMUX(compiler, []*Wire{cIn}, rOut[x:x+1], rIn[x:x+1], + err := NewMUX(cc, []*Wire{cIn}, rOut[x:x+1], rIn[x:x+1], []*Wire{ro}) if err != nil { return err @@ -74,12 +74,12 @@ func NewDivider(compiler *Compiler, a, b, q, r []*Wire) error { // Set extra quotient bits to zero. for y := len(a); y < len(q); y++ { - q[y] = compiler.ZeroWire() + q[y] = cc.ZeroWire() } // Set extra remainder bits to zero. for x := len(b); x < len(r); x++ { - r[x] = compiler.ZeroWire() + r[x] = cc.ZeroWire() } return nil diff --git a/compiler/circuits/circ_hamming.go b/compiler/circuits/circ_hamming.go index 8190e707..f9a6faa2 100644 --- a/compiler/circuits/circ_hamming.go +++ b/compiler/circuits/circ_hamming.go @@ -13,13 +13,13 @@ import ( // Hamming creates a hamming distance circuit computing the hamming // distance between a and b and returning the distance in r. -func Hamming(compiler *Compiler, a, b, r []*Wire) error { - a, b = compiler.ZeroPad(a, b) +func Hamming(cc *Compiler, a, b, r []*Wire) error { + a, b = cc.ZeroPad(a, b) var arr [][]*Wire for i := 0; i < len(a); i++ { - w := compiler.Calloc.Wire() - compiler.AddGate(NewBinary(circuit.XOR, a[i], b[i], w)) + w := cc.Calloc.Wire() + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, a[i], b[i], w)) arr = append(arr, []*Wire{w}) } @@ -27,8 +27,8 @@ func Hamming(compiler *Compiler, a, b, r []*Wire) error { var n [][]*Wire for i := 0; i < len(arr); i += 2 { if i+1 < len(arr) { - result := compiler.Calloc.Wires(types.Size(len(arr[i]) + 1)) - err := NewAdder(compiler, arr[i], arr[i+1], result) + result := cc.Calloc.Wires(types.Size(len(arr[i]) + 1)) + err := NewAdder(cc, arr[i], arr[i+1], result) if err != nil { return err } @@ -40,5 +40,5 @@ func Hamming(compiler *Compiler, a, b, r []*Wire) error { arr = n } - return NewAdder(compiler, arr[0], arr[1], r) + return NewAdder(cc, arr[0], arr[1], r) } diff --git a/compiler/circuits/circ_index.go b/compiler/circuits/circ_index.go index 22c9605a..b76ec137 100644 --- a/compiler/circuits/circ_index.go +++ b/compiler/circuits/circ_index.go @@ -11,7 +11,7 @@ import ( ) // NewIndex creates a new array element selection (index) circuit. -func NewIndex(compiler *Compiler, size int, array, index, out []*Wire) error { +func NewIndex(cc *Compiler, size int, array, index, out []*Wire) error { if len(array)%size != 0 { return fmt.Errorf("array width %d must be multiple of element size %d", len(array), size) @@ -23,7 +23,7 @@ func NewIndex(compiler *Compiler, size int, array, index, out []*Wire) error { n := len(array) / size if n == 0 { for i := 0; i < len(out); i++ { - out[i] = compiler.ZeroWire() + out[i] = cc.ZeroWire() } return nil } @@ -35,16 +35,16 @@ func NewIndex(compiler *Compiler, size int, array, index, out []*Wire) error { bits++ } - return newIndex(compiler, bits-1, length, size, array, index, out) + return newIndex(cc, bits-1, length, size, array, index, out) } -func newIndex(compiler *Compiler, bit, length, size int, +func newIndex(cc *Compiler, bit, length, size int, array, index, out []*Wire) error { // Default "not found" value. def := make([]*Wire, size) for i := 0; i < size; i++ { - def[i] = compiler.ZeroWire() + def[i] = cc.ZeroWire() } n := len(array) / size @@ -58,20 +58,20 @@ func newIndex(compiler *Compiler, bit, length, size int, } else { tVal = def } - return NewMUX(compiler, index[0:1], tVal, fVal, out) + return NewMUX(cc, index[0:1], tVal, fVal, out) } length /= 2 fVal := make([]*Wire, size) for i := 0; i < size; i++ { - fVal[i] = compiler.Calloc.Wire() + fVal[i] = cc.Calloc.Wire() } fArray := array if n > length { fArray = fArray[:length*size] } - err := newIndex(compiler, bit-1, length, size, fArray, index, fVal) + err := newIndex(cc, bit-1, length, size, fArray, index, fVal) if err != nil { return err } @@ -80,9 +80,9 @@ func newIndex(compiler *Compiler, bit, length, size int, if n > length { tVal = make([]*Wire, size) for i := 0; i < size; i++ { - tVal[i] = compiler.Calloc.Wire() + tVal[i] = cc.Calloc.Wire() } - err = newIndex(compiler, bit-1, length, size, + err = newIndex(cc, bit-1, length, size, array[length*size:], index, tVal) if err != nil { return err @@ -91,5 +91,5 @@ func newIndex(compiler *Compiler, bit, length, size int, tVal = def } - return NewMUX(compiler, index[bit:bit+1], tVal, fVal, out) + return NewMUX(cc, index[bit:bit+1], tVal, fVal, out) } diff --git a/compiler/circuits/circ_multiplier.go b/compiler/circuits/circ_multiplier.go index f33761c2..21bb431c 100644 --- a/compiler/circuits/circ_multiplier.go +++ b/compiler/circuits/circ_multiplier.go @@ -31,8 +31,8 @@ func NewMultiplier(c *Compiler, arrayTreshold int, x, y, z []*Wire) error { // NewArrayMultiplier creates a multiplier circuit implementing // x*y=z. This function implements Array Multiplier Circuit. -func NewArrayMultiplier(compiler *Compiler, x, y, z []*Wire) error { - x, y = compiler.ZeroPad(x, y) +func NewArrayMultiplier(cc *Compiler, x, y, z []*Wire) error { + x, y = cc.ZeroPad(x, y) if len(x) > len(z) { x = x[0:len(z)] y = y[0:len(z)] @@ -40,9 +40,9 @@ func NewArrayMultiplier(compiler *Compiler, x, y, z []*Wire) error { // One bit multiplication is AND. if len(x) == 1 { - compiler.AddGate(NewBinary(circuit.AND, x[0], y[0], z[0])) + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, x[0], y[0], z[0])) if len(z) > 1 { - z[1] = compiler.ZeroWire() + z[1] = cc.ZeroWire() } return nil } @@ -55,10 +55,10 @@ func NewArrayMultiplier(compiler *Compiler, x, y, z []*Wire) error { if i == 0 { s = z[0] } else { - s = compiler.Calloc.Wire() + s = cc.Calloc.Wire() sums = append(sums, s) } - compiler.AddGate(NewBinary(circuit.AND, xn, y[0], s)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, xn, y[0], s)) } // Construct len(y)-2 intermediate layers @@ -67,8 +67,8 @@ func NewArrayMultiplier(compiler *Compiler, x, y, z []*Wire) error { // ANDs for y(j) var ands []*Wire for _, xn := range x { - wire := compiler.Calloc.Wire() - compiler.AddGate(NewBinary(circuit.AND, xn, y[j], wire)) + wire := cc.Calloc.Wire() + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, xn, y[j], wire)) ands = append(ands, wire) } @@ -76,22 +76,22 @@ func NewArrayMultiplier(compiler *Compiler, x, y, z []*Wire) error { var nsums []*Wire var c *Wire for i := 0; i < len(ands); i++ { - cout := compiler.Calloc.Wire() + cout := cc.Calloc.Wire() var s *Wire if i == 0 { s = z[j] } else { - s = compiler.Calloc.Wire() + s = cc.Calloc.Wire() nsums = append(nsums, s) } if i == 0 { - NewHalfAdder(compiler, ands[i], sums[i], s, cout) + NewHalfAdder(cc, ands[i], sums[i], s, cout) } else if i >= len(sums) { - NewHalfAdder(compiler, ands[i], c, s, cout) + NewHalfAdder(cc, ands[i], c, s, cout) } else { - NewFullAdder(compiler, ands[i], sums[i], c, s, cout) + NewFullAdder(cc, ands[i], sums[i], c, s, cout) } c = cout } @@ -102,29 +102,29 @@ func NewArrayMultiplier(compiler *Compiler, x, y, z []*Wire) error { // Construct final layer. var c *Wire for i, xn := range x { - and := compiler.Calloc.Wire() - compiler.AddGate(NewBinary(circuit.AND, xn, y[j], and)) + and := cc.Calloc.Wire() + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, xn, y[j], and)) var cout *Wire if i+1 >= len(x) && j+i+1 < len(z) { cout = z[j+i+1] } else { - cout = compiler.Calloc.Wire() + cout = cc.Calloc.Wire() } if j+i < len(z) { if i == 0 { - NewHalfAdder(compiler, and, sums[i], z[j+i], cout) + NewHalfAdder(cc, and, sums[i], z[j+i], cout) } else if i >= len(sums) { - NewHalfAdder(compiler, and, c, z[j+i], cout) + NewHalfAdder(cc, and, c, z[j+i], cout) } else { - NewFullAdder(compiler, and, sums[i], c, z[j+i], cout) + NewFullAdder(cc, and, sums[i], c, z[j+i], cout) } } c = cout } for i := j + len(x) + 1; i < len(z); i++ { - z[1] = compiler.ZeroWire() + z[1] = cc.ZeroWire() } return nil diff --git a/compiler/circuits/circ_mux.go b/compiler/circuits/circ_mux.go index ea4d0d7b..d0a5377c 100644 --- a/compiler/circuits/circ_mux.go +++ b/compiler/circuits/circ_mux.go @@ -14,25 +14,25 @@ import ( // NewMUX creates a multiplexer circuit that selects the input t or f // to output, based on the value of the condition cond. -func NewMUX(compiler *Compiler, cond, t, f, out []*Wire) error { - t, f = compiler.ZeroPad(t, f) +func NewMUX(cc *Compiler, cond, t, f, out []*Wire) error { + t, f = cc.ZeroPad(t, f) if len(cond) != 1 || len(t) != len(f) || len(t) != len(out) { return fmt.Errorf("invalid mux arguments: cond=%d, l=%d, r=%d, out=%d", len(cond), len(t), len(f), len(out)) } for i := 0; i < len(t); i++ { - w1 := compiler.Calloc.Wire() - w2 := compiler.Calloc.Wire() + w1 := cc.Calloc.Wire() + w2 := cc.Calloc.Wire() // w1 = XOR(f[i], t[i]) - compiler.AddGate(NewBinary(circuit.XOR, f[i], t[i], w1)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, f[i], t[i], w1)) // w2 = AND(w1, cond) - compiler.AddGate(NewBinary(circuit.AND, w1, cond[0], w2)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, w1, cond[0], w2)) // out[i] = XOR(w2, f[i]) - compiler.AddGate(NewBinary(circuit.XOR, w2, f[i], out[i])) + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, w2, f[i], out[i])) } return nil diff --git a/compiler/circuits/circ_subtractor.go b/compiler/circuits/circ_subtractor.go index 68a2f677..fd6ed5f2 100644 --- a/compiler/circuits/circ_subtractor.go +++ b/compiler/circuits/circ_subtractor.go @@ -13,30 +13,30 @@ import ( ) // NewFullSubtractor creates a full subtractor circuit. -func NewFullSubtractor(compiler *Compiler, x, y, cin, d, cout *Wire) { - w1 := compiler.Calloc.Wire() - compiler.AddGate(NewBinary(circuit.XNOR, y, cin, w1)) - compiler.AddGate(NewBinary(circuit.XNOR, x, w1, d)) +func NewFullSubtractor(cc *Compiler, x, y, cin, d, cout *Wire) { + w1 := cc.Calloc.Wire() + cc.AddGate(cc.Calloc.BinaryGate(circuit.XNOR, y, cin, w1)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.XNOR, x, w1, d)) if cout != nil { - w2 := compiler.Calloc.Wire() - compiler.AddGate(NewBinary(circuit.XOR, x, cin, w2)) + w2 := cc.Calloc.Wire() + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, x, cin, w2)) - w3 := compiler.Calloc.Wire() - compiler.AddGate(NewBinary(circuit.AND, w1, w2, w3)) + w3 := cc.Calloc.Wire() + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, w1, w2, w3)) - compiler.AddGate(NewBinary(circuit.XOR, w3, cin, cout)) + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, w3, cin, cout)) } } // NewSubtractor creates a new subtractor circuit implementing z=x-y. -func NewSubtractor(compiler *Compiler, x, y, z []*Wire) error { - x, y = compiler.ZeroPad(x, y) +func NewSubtractor(cc *Compiler, x, y, z []*Wire) error { + x, y = cc.ZeroPad(x, y) if len(x) > len(z) { x = x[0:len(z)] y = y[0:len(z)] } - cin := compiler.ZeroWire() + cin := cc.ZeroWire() for i := 0; i < len(x); i++ { var cout *Wire @@ -48,16 +48,16 @@ func NewSubtractor(compiler *Compiler, x, y, z []*Wire) error { cout = z[i+1] } } else { - cout = compiler.Calloc.Wire() + cout = cc.Calloc.Wire() } // Note y-x here. - NewFullSubtractor(compiler, y[i], x[i], cin, z[i], cout) + NewFullSubtractor(cc, y[i], x[i], cin, z[i], cout) cin = cout } for i := len(x) + 1; i < len(z); i++ { - z[i] = compiler.ZeroWire() + z[i] = cc.ZeroWire() } return nil } diff --git a/compiler/circuits/compiler.go b/compiler/circuits/compiler.go index 56822d6a..38262517 100644 --- a/compiler/circuits/compiler.go +++ b/compiler/circuits/compiler.go @@ -59,39 +59,39 @@ func NewCompiler(params *utils.Params, calloc *Allocator, } // InvI0Wire returns a wire holding value INV(input[0]). -func (c *Compiler) InvI0Wire() *Wire { - if c.invI0Wire == nil { - c.invI0Wire = c.Calloc.Wire() - c.AddGate(NewINV(c.InputWires[0], c.invI0Wire)) +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 c.invI0Wire + return cc.invI0Wire } // ZeroWire returns a wire holding value 0. -func (c *Compiler) ZeroWire() *Wire { - if c.zeroWire == nil { - c.zeroWire = c.Calloc.Wire() - c.AddGate(NewBinary(circuit.AND, c.InputWires[0], c.InvI0Wire(), - c.zeroWire)) - c.zeroWire.SetValue(Zero) +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 c.zeroWire + return cc.zeroWire } // OneWire returns a wire holding value 1. -func (c *Compiler) OneWire() *Wire { - if c.oneWire == nil { - c.oneWire = c.Calloc.Wire() - c.AddGate(NewBinary(circuit.OR, c.InputWires[0], c.InvI0Wire(), - c.oneWire)) - c.oneWire.SetValue(One) +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 c.oneWire + 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 (c *Compiler) ZeroPad(x, y []*Wire) ([]*Wire, []*Wire) { +func (cc *Compiler) ZeroPad(x, y []*Wire) ([]*Wire, []*Wire) { if len(x) == len(y) { return x, y } @@ -106,7 +106,7 @@ func (c *Compiler) ZeroPad(x, y []*Wire) ([]*Wire, []*Wire) { if i < len(x) { rx[i] = x[i] } else { - rx[i] = c.ZeroWire() + rx[i] = cc.ZeroWire() } } @@ -115,7 +115,7 @@ func (c *Compiler) ZeroPad(x, y []*Wire) ([]*Wire, []*Wire) { if i < len(y) { ry[i] = y[i] } else { - ry[i] = c.ZeroWire() + ry[i] = cc.ZeroWire() } } @@ -124,59 +124,59 @@ func (c *Compiler) ZeroPad(x, y []*Wire) ([]*Wire, []*Wire) { // ShiftLeft shifts the size number of bits of the input wires w, // count bits left. -func (c *Compiler) ShiftLeft(w []*Wire, size, count int) []*Wire { +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] = c.ZeroWire() + result[i] = cc.ZeroWire() } for i := count + len(w); i < size; i++ { - result[i] = c.ZeroWire() + result[i] = cc.ZeroWire() } return result } // INV creates an inverse wire inverting the input wire i's value to // the output wire o. -func (c *Compiler) INV(i, o *Wire) { - c.AddGate(NewBinary(circuit.XOR, i, c.OneWire(), 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 (c *Compiler) ID(i, o *Wire) { - c.AddGate(NewBinary(circuit.XOR, i, c.ZeroWire(), 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 (c *Compiler) AddGate(gate *Gate) { - c.Gates = append(c.Gates, gate) +func (cc *Compiler) AddGate(gate *Gate) { + cc.Gates = append(cc.Gates, gate) } // SetNextWireID sets the next unique wire ID to use. -func (c *Compiler) SetNextWireID(next uint32) { - c.nextWireID = next +func (cc *Compiler) SetNextWireID(next uint32) { + cc.nextWireID = next } // NextWireID returns the next unique wire ID. -func (c *Compiler) NextWireID() uint32 { - ret := c.nextWireID - c.nextWireID++ +func (cc *Compiler) NextWireID() uint32 { + 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 (c *Compiler) ConstPropagate() { +func (cc *Compiler) ConstPropagate() { var stats circuit.Stats start := time.Now() - for _, g := range c.Gates { + for _, g := range cc.Gates { switch g.Op { case circuit.XOR: if (g.A.Value() == Zero && g.B.Value() == Zero) || @@ -254,21 +254,21 @@ func (c *Compiler) ConstPropagate() { if g.A.Value() == Zero { g.A.RemoveOutput(g) - g.A = c.ZeroWire() + g.A = cc.ZeroWire() g.A.AddOutput(g) } else if g.A.Value() == One { g.A.RemoveOutput(g) - g.A = c.OneWire() + g.A = cc.OneWire() g.A.AddOutput(g) } if g.B != nil { if g.B.Value() == Zero { g.B.RemoveOutput(g) - g.B = c.ZeroWire() + g.B = cc.ZeroWire() g.B.AddOutput(g) } else if g.B.Value() == One { g.B.RemoveOutput(g) - g.B = c.OneWire() + g.B = cc.OneWire() g.B.AddOutput(g) } } @@ -276,21 +276,21 @@ func (c *Compiler) ConstPropagate() { elapsed := time.Since(start) - if c.Params.Diagnostics && stats.Count() > 0 { + if cc.Params.Diagnostics && stats.Count() > 0 { fmt.Printf(" - ConstPropagate: %12s: %d/%d (%.2f%%)\n", - elapsed, stats.Count(), len(c.Gates), - float64(stats.Count())/float64(len(c.Gates))*100) + 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 (c *Compiler) ShortCircuitXORZero() { +func (cc *Compiler) ShortCircuitXORZero() { var stats circuit.Stats start := time.Now() - for _, g := range c.Gates { + for _, g := range cc.Gates { if g.Op != circuit.XOR { continue } @@ -300,7 +300,7 @@ func (c *Compiler) ShortCircuitXORZero() { g.B.Input().ResetOutput(g.O) // Disconnect gate's output wire. - g.O = c.Calloc.Wire() + g.O = cc.Calloc.Wire() stats[g.Op]++ } @@ -310,7 +310,7 @@ func (c *Compiler) ShortCircuitXORZero() { g.A.Input().ResetOutput(g.O) // Disconnect gate's output wire. - g.O = c.Calloc.Wire() + g.O = cc.Calloc.Wire() stats[g.Op]++ } @@ -318,81 +318,81 @@ func (c *Compiler) ShortCircuitXORZero() { elapsed := time.Since(start) - if c.Params.Diagnostics && stats.Count() > 0 { + if cc.Params.Diagnostics && stats.Count() > 0 { fmt.Printf(" - ShortCircuitXORZero: %12s: %d/%d (%.2f%%)\n", - elapsed, stats.Count(), len(c.Gates), - float64(stats.Count())/float64(len(c.Gates))*100) + elapsed, stats.Count(), len(cc.Gates), + float64(stats.Count())/float64(len(cc.Gates))*100) } } // Prune removes all gates whose output wires are unused. -func (c *Compiler) Prune() int { +func (cc *Compiler) Prune() int { - n := make([]*Gate, len(c.Gates)) + n := make([]*Gate, len(cc.Gates)) nPos := len(n) - for i := len(c.Gates) - 1; i >= 0; i-- { - g := c.Gates[i] + for i := len(cc.Gates) - 1; i >= 0; i-- { + g := cc.Gates[i] if !g.Prune() { nPos-- n[nPos] = g } } - c.Gates = n[nPos:] + cc.Gates = n[nPos:] return nPos } // Compile compiles the circuit. -func (c *Compiler) Compile() *circuit.Circuit { - if len(c.pending) != 0 { +func (cc *Compiler) Compile() *circuit.Circuit { + if len(cc.pending) != 0 { panic("Compile: pending set") } - c.pending = make([]*Gate, 0, len(c.Gates)) - if len(c.assigned) != 0 { + cc.pending = make([]*Gate, 0, len(cc.Gates)) + if len(cc.assigned) != 0 { panic("Compile: assigned set") } - c.assigned = make([]*Gate, 0, len(c.Gates)) - if len(c.compiled) != 0 { + cc.assigned = make([]*Gate, 0, len(cc.Gates)) + if len(cc.compiled) != 0 { panic("Compile: compiled set") } - c.compiled = make([]circuit.Gate, 0, len(c.Gates)) + cc.compiled = make([]circuit.Gate, 0, len(cc.Gates)) - for _, w := range c.InputWires { - w.Assign(c) + for _, w := range cc.InputWires { + w.Assign(cc) } - for len(c.pending) > 0 { - gate := c.pending[0] - c.pending = c.pending[1:] - gate.Assign(c) + for len(cc.pending) > 0 { + gate := cc.pending[0] + cc.pending = cc.pending[1:] + gate.Assign(cc) } // Assign outputs. - for _, w := range c.OutputWires { + for _, w := range cc.OutputWires { if w.Assigned() { - if !c.OutputsAssigned { + if !cc.OutputsAssigned { panic("Output already assigned") } } else { - w.SetID(c.NextWireID()) + w.SetID(cc.NextWireID()) } } // Compile circuit. - for _, gate := range c.assigned { - gate.Compile(c) + for _, gate := range cc.assigned { + gate.Compile(cc) } var stats circuit.Stats - for _, g := range c.compiled { + for _, g := range cc.compiled { stats[g.Op]++ } result := &circuit.Circuit{ - NumGates: len(c.compiled), - NumWires: int(c.nextWireID), - Inputs: c.Inputs, - Outputs: c.Outputs, - Gates: c.compiled, + NumGates: len(cc.compiled), + NumWires: int(cc.nextWireID), + Inputs: cc.Inputs, + Outputs: cc.Outputs, + Gates: cc.compiled, Stats: stats, } diff --git a/compiler/circuits/gates.go b/compiler/circuits/gates.go index 4929fcf7..6e9154c6 100644 --- a/compiler/circuits/gates.go +++ b/compiler/circuits/gates.go @@ -25,51 +25,23 @@ type Gate struct { O *Wire } -// NewBinary creates a new binary gate. -func NewBinary(op circuit.Operation, a, b, o *Wire) *Gate { - gate := &Gate{ - Op: op, - A: a, - B: b, - O: o, - } - a.AddOutput(gate) - b.AddOutput(gate) - o.SetInput(gate) - - return gate -} - -// NewINV creates a new INV gate. -func NewINV(i, o *Wire) *Gate { - gate := &Gate{ - Op: circuit.INV, - A: i, - O: o, - } - i.AddOutput(gate) - o.SetInput(gate) - - return gate -} - func (g *Gate) String() string { return fmt.Sprintf("%s %x %x %x", g.Op, g.A.ID(), g.B.ID(), g.O.ID()) } // Visit adds gate to the list of pending gates to be compiled. -func (g *Gate) Visit(c *Compiler) { +func (g *Gate) Visit(cc *Compiler) { switch g.Op { case circuit.INV: if !g.Dead && !g.Visited && g.A.Assigned() { g.Visited = true - c.pending = append(c.pending, g) + cc.pending = append(cc.pending, g) } default: if !g.Dead && !g.Visited && g.A.Assigned() && g.B.Assigned() { g.Visited = true - c.pending = append(c.pending, g) + cc.pending = append(cc.pending, g) } } } @@ -128,29 +100,29 @@ func (g *Gate) Prune() bool { } // Assign assigns gate's output wire ID. -func (g *Gate) Assign(c *Compiler) { +func (g *Gate) Assign(cc *Compiler) { if !g.Dead { - g.O.Assign(c) - c.assigned = append(c.assigned, g) + g.O.Assign(cc) + cc.assigned = append(cc.assigned, g) } } // Compile adds gate's binary circuit into compile circuit. -func (g *Gate) Compile(c *Compiler) { +func (g *Gate) Compile(cc *Compiler) { if g.Dead || g.Compiled { return } g.Compiled = true switch g.Op { case circuit.INV: - c.compiled = append(c.compiled, circuit.Gate{ + cc.compiled = append(cc.compiled, circuit.Gate{ Input0: circuit.Wire(g.A.ID()), Output: circuit.Wire(g.O.ID()), Op: g.Op, }) default: - c.compiled = append(c.compiled, circuit.Gate{ + cc.compiled = append(cc.compiled, circuit.Gate{ Input0: circuit.Wire(g.A.ID()), Input1: circuit.Wire(g.B.ID()), Output: circuit.Wire(g.O.ID()), diff --git a/compiler/circuits/wire.go b/compiler/circuits/wire.go index ba20fc49..28342e74 100644 --- a/compiler/circuits/wire.go +++ b/compiler/circuits/wire.go @@ -22,10 +22,9 @@ const ( // Wire implements a wire connecting binary gates. type Wire struct { - ovnum uint32 - id uint32 - input *Gate - outputs []*Gate + ovnum uint32 + id uint32 + gates []*Gate } // WireValue defines wire values. @@ -54,7 +53,7 @@ func (w *Wire) Reset(id uint32) { w.SetOutput(false) w.SetValue(Unknown) w.SetID(id) - w.input = nil + w.SetInput(nil) w.DisconnectOutputs() } @@ -115,56 +114,76 @@ func (w *Wire) SetNumOutputs(num uint32) { // DisconnectOutputs disconnects wire from its output gates. func (w *Wire) DisconnectOutputs() { w.SetNumOutputs(0) - w.outputs = w.outputs[0:0] + if len(w.gates) > 1 { + w.gates = w.gates[0:1] + } } func (w *Wire) String() string { return fmt.Sprintf("Wire{%x, Input:%s, Value:%s, Outputs:%v, Output=%v}", - w.ID(), w.input, w.Value(), w.outputs, w.Output()) + w.ID(), w.Input(), w.Value(), w.gates[1:], w.Output()) } // Assign assings wire ID. -func (w *Wire) Assign(c *Compiler) { +func (w *Wire) Assign(cc *Compiler) { if w.Output() { return } if !w.Assigned() { - w.id = c.NextWireID() + w.id = cc.NextWireID() } w.ForEachOutput(func(gate *Gate) { - gate.Visit(c) + gate.Visit(cc) }) } // Input returns the wire's input gate. func (w *Wire) Input() *Gate { - return w.input + if len(w.gates) == 0 { + return nil + } + return w.gates[0] } // SetInput sets the wire's input gate. func (w *Wire) SetInput(gate *Gate) { - if w.input != nil { - panic("Input gate already set") + if gate == nil { + if len(w.gates) > 0 { + w.gates[0] = nil + } + } else { + if len(w.gates) == 0 { + w.gates = append(w.gates, gate) + } else { + if w.gates[0] != nil { + panic("Input gate already set") + } + w.gates[0] = gate + } } - w.input = gate } // IsInput tests if the wire is an input wire. func (w *Wire) IsInput() bool { - return w.input == nil + return w.Input() == nil } // ForEachOutput calls the argument function for each output gate of // the wire. func (w *Wire) ForEachOutput(f func(gate *Gate)) { - for _, gate := range w.outputs { - f(gate) + if len(w.gates) > 1 { + for _, gate := range w.gates[1:] { + f(gate) + } } } // AddOutput adds gate to the wire's output gates. func (w *Wire) AddOutput(gate *Gate) { - w.outputs = append(w.outputs, gate) + if len(w.gates) == 0 { + w.gates = append(w.gates, nil) + } + w.gates = append(w.gates, gate) w.SetNumOutputs(w.NumOutputs() + 1) } diff --git a/compiler/compiler.go b/compiler/compiler.go index e8aa1d50..2ed7c596 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -148,7 +148,7 @@ func (c *Compiler) stream(conn *p2p.Conn, oti ot.OT, source string, if err != nil { return nil, nil, err } - if true { + if false { program.StreamDebug() } return out, bits, err diff --git a/compiler/ssa/circuitgen.go b/compiler/ssa/circuitgen.go index 4cb5255e..a5db5ed4 100644 --- a/compiler/ssa/circuitgen.go +++ b/compiler/ssa/circuitgen.go @@ -509,22 +509,22 @@ func (prog *Program) Circuit(cc *circuits.Compiler) error { for _, gate := range instr.Circ.Gates { switch gate.Op { case circuit.XOR: - cc.AddGate(circuits.NewBinary(circuit.XOR, + cc.AddGate(cc.Calloc.BinaryGate(circuit.XOR, circWires[gate.Input0], circWires[gate.Input1], circWires[gate.Output])) case circuit.XNOR: - cc.AddGate(circuits.NewBinary(circuit.XNOR, + cc.AddGate(cc.Calloc.BinaryGate(circuit.XNOR, circWires[gate.Input0], circWires[gate.Input1], circWires[gate.Output])) case circuit.AND: - cc.AddGate(circuits.NewBinary(circuit.AND, + cc.AddGate(cc.Calloc.BinaryGate(circuit.AND, circWires[gate.Input0], circWires[gate.Input1], circWires[gate.Output])) case circuit.OR: - cc.AddGate(circuits.NewBinary(circuit.OR, + cc.AddGate(cc.Calloc.BinaryGate(circuit.OR, circWires[gate.Input0], circWires[gate.Input1], circWires[gate.Output])) From ccd5fc7ae32aefa34d51e2de941e9bf7451e3f3b Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Wed, 30 Aug 2023 09:02:50 +0200 Subject: [PATCH 14/19] Updated documentation. --- compiler/circuits/wire.go | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/circuits/wire.go b/compiler/circuits/wire.go index 28342e74..157d011e 100644 --- a/compiler/circuits/wire.go +++ b/compiler/circuits/wire.go @@ -24,6 +24,7 @@ const ( type Wire struct { ovnum uint32 id uint32 + // The gates[0] is the input gate, gates[1:] are the output gates. gates []*Gate } From 200a819a04c27545c661d9e6f9d1cece7ca73055 Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Fri, 1 Sep 2023 12:35:36 +0200 Subject: [PATCH 15/19] Benchmark for time.Now(). --- circuit/stream_garble_test.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/circuit/stream_garble_test.go b/circuit/stream_garble_test.go index a4a3473a..9b60b976 100644 --- a/circuit/stream_garble_test.go +++ b/circuit/stream_garble_test.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2020-2022 Markku Rossi +// Copyright (c) 2020-2023 Markku Rossi // // All rights reserved. // @@ -8,6 +8,7 @@ package circuit import ( "testing" + "time" "github.com/markkurossi/mpc/ot" ) @@ -344,3 +345,12 @@ func encode7var5(b []byte, v uint32) { b[3] = byte(v >> 7) b[4] = byte(v) } + +func BenchmarkTimeDuration(b *testing.B) { + var total time.Duration + + for i := 0; i < b.N; i++ { + start := time.Now() + total += time.Now().Sub(start) + } +} From c292dbff1b00e94df45f8da9e522a4cdfe858255 Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Mon, 4 Sep 2023 10:06:28 +0200 Subject: [PATCH 16/19] Optimized streamer wire handling. --- apps/garbled/default.pgo | Bin 23713 -> 18584 bytes circuit/circuit.go | 4 +- circuit/garble.go | 8 +- compiler/circuits/compiler.go | 6 +- compiler/circuits/wire.go | 20 +-- compiler/compiler.go | 2 +- compiler/ssa/streamer.go | 87 ++++++----- compiler/ssa/wire_allocator.go | 23 ++- compiler/ssa/wire_allocator_string.go | 23 ++- compiler/ssa/wire_allocator_value.go | 214 +++++++++++++++++++------- 10 files changed, 253 insertions(+), 134 deletions(-) diff --git a/apps/garbled/default.pgo b/apps/garbled/default.pgo index 1c5af0ae0cfaeed7fae96c7576f8376be15551f8..ef43e117193e70921d8cf9011e029215af48dd9c 100644 GIT binary patch literal 18584 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 literal 23713 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 Q9{>RV|9~3bw!qf_0P`0ebN~PV diff --git a/circuit/circuit.go b/circuit/circuit.go index 663f0b49..e0a6575f 100644 --- a/circuit/circuit.go +++ b/circuit/circuit.go @@ -277,8 +277,8 @@ type Wire uint32 // InvalidWire specifies an invalid wire ID. const InvalidWire Wire = math.MaxUint32 -// ID returns the wire ID as integer. -func (w Wire) ID() int { +// Int returns the wire ID as integer. +func (w Wire) Int() int { return int(w) } diff --git a/circuit/garble.go b/circuit/garble.go index 3424e4b3..d79d9141 100644 --- a/circuit/garble.go +++ b/circuit/garble.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2019-2022 Markku Rossi +// Copyright (c) 2019-2023 Markku Rossi // // All rights reserved. // @@ -244,11 +244,11 @@ func (g *Gate) garble(wires []ot.Wire, enc cipher.Block, r ot.Label, // Inputs. switch g.Op { case XOR, XNOR, AND, OR: - b = wires[g.Input1.ID()] + b = wires[g.Input1.Int()] fallthrough case INV: - a = wires[g.Input0.ID()] + a = wires[g.Input0.Int()] default: return nil, fmt.Errorf("invalid gate type %s", g.Op) @@ -404,7 +404,7 @@ func (g *Gate) garble(wires []ot.Wire, enc cipher.Block, r ot.Label, default: return nil, fmt.Errorf("invalid operand %s", g.Op) } - wires[g.Output.ID()] = c + wires[g.Output.Int()] = c return table[start : start+count], nil } diff --git a/compiler/circuits/compiler.go b/compiler/circuits/compiler.go index 38262517..a6454fc9 100644 --- a/compiler/circuits/compiler.go +++ b/compiler/circuits/compiler.go @@ -28,7 +28,7 @@ type Compiler struct { InputWires []*Wire OutputWires []*Wire Gates []*Gate - nextWireID uint32 + nextWireID circuit.Wire pending []*Gate assigned []*Gate compiled []circuit.Gate @@ -157,12 +157,12 @@ func (cc *Compiler) AddGate(gate *Gate) { } // SetNextWireID sets the next unique wire ID to use. -func (cc *Compiler) SetNextWireID(next uint32) { +func (cc *Compiler) SetNextWireID(next circuit.Wire) { cc.nextWireID = next } // NextWireID returns the next unique wire ID. -func (cc *Compiler) NextWireID() uint32 { +func (cc *Compiler) NextWireID() circuit.Wire { ret := cc.nextWireID cc.nextWireID++ return ret diff --git a/compiler/circuits/wire.go b/compiler/circuits/wire.go index 157d011e..39fb1d9d 100644 --- a/compiler/circuits/wire.go +++ b/compiler/circuits/wire.go @@ -9,21 +9,23 @@ package circuits import ( "fmt" "math" + + "github.com/markkurossi/mpc/circuit" ) const ( // UnassignedID identifies an unassigned wire ID. - UnassignedID uint32 = math.MaxUint32 - outputMask = 0b10000000000000000000000000000000 - valueMask = 0b01100000000000000000000000000000 - numMask = 0b00011111111111111111111111111111 - valueShift = 29 + UnassignedID circuit.Wire = math.MaxUint32 + outputMask = 0b10000000000000000000000000000000 + valueMask = 0b01100000000000000000000000000000 + numMask = 0b00011111111111111111111111111111 + valueShift = 29 ) // Wire implements a wire connecting binary gates. type Wire struct { ovnum uint32 - id uint32 + id circuit.Wire // The gates[0] is the input gate, gates[1:] are the output gates. gates []*Gate } @@ -50,7 +52,7 @@ func (v WireValue) String() string { } // Reset resets the wire with the new ID. -func (w *Wire) Reset(id uint32) { +func (w *Wire) Reset(id circuit.Wire) { w.SetOutput(false) w.SetValue(Unknown) w.SetID(id) @@ -59,12 +61,12 @@ func (w *Wire) Reset(id uint32) { } // ID returns the wire ID. -func (w *Wire) ID() uint32 { +func (w *Wire) ID() circuit.Wire { return w.id } // SetID sets the wire ID. -func (w *Wire) SetID(id uint32) { +func (w *Wire) SetID(id circuit.Wire) { w.id = id } diff --git a/compiler/compiler.go b/compiler/compiler.go index 2ed7c596..5d36c2cf 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -144,7 +144,7 @@ func (c *Compiler) stream(conn *p2p.Conn, oti ot.OT, source string, fmt.Printf(" - Out: %s\n", program.Outputs) fmt.Printf(" - In: %s\n", inputFlag) - out, bits, err := program.StreamCircuit(conn, oti, c.params, input, timing) + out, bits, err := program.Stream(conn, oti, c.params, input, timing) if err != nil { return nil, nil, err } diff --git a/compiler/ssa/streamer.go b/compiler/ssa/streamer.go index 001d0416..2e27abe9 100644 --- a/compiler/ssa/streamer.go +++ b/compiler/ssa/streamer.go @@ -23,8 +23,8 @@ import ( "github.com/markkurossi/tabulate" ) -// StreamCircuit streams the program circuit into the P2P connection. -func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, +// Stream streams the program circuit into the P2P connection. +func (prog *Program) Stream(conn *p2p.Conn, oti ot.OT, params *utils.Params, inputs *big.Int, timing *circuit.Timing) ( circuit.IO, []*big.Int, error) { @@ -68,7 +68,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, // Program's inputs are unassigned because parser is shared // between streaming and non-streaming modes. w.SetID(prog.walloc.NextWireID()) - ids = append(ids, circuit.Wire(w.ID())) + ids = append(ids, w.ID()) } streaming, err := circuit.NewStreaming(key[:], ids, conn) @@ -140,7 +140,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, // Stream circuit. cache := make(map[string]*circuit.Circuit) - var returnIDs []uint32 + var returnIDs []circuit.Wire start := time.Now() lastReport := start @@ -150,7 +150,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, istats := make(map[string]circuit.Stats) - var wires [][]*circuits.Wire + var wires [][]circuit.Wire var iIDs, oIDs []circuit.Wire for idx, step := range prog.Steps { @@ -183,7 +183,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, wires = append(wires, w) } - var out []*circuits.Wire + var out []circuit.Wire var err error if instr.Out != nil { out, err = prog.walloc.AssignedWires(*instr.Out, @@ -212,9 +212,9 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, fmt.Errorf("%s: negative shift count %d", instr.Op, count) } for bit := 0; bit < len(out); bit++ { - var id uint32 + var id circuit.Wire if bit-int(count) >= 0 && bit-int(count) < len(wires[0]) { - id = wires[0][bit-int(count)].ID() + id = wires[0][bit-int(count)] } else { w, err := prog.ZeroWire(conn, streaming) if err != nil { @@ -222,11 +222,11 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, } id = w.ID() } - out[bit].SetID(id) + out[bit] = id } case Rshift, Srshift: - var signWire *circuits.Wire + var signWire circuit.Wire if instr.Op == Srshift { signWire = wires[0][len(wires[0])-1] } else { @@ -234,7 +234,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, if err != nil { return nil, nil, err } - signWire = zero + signWire = zero.ID() } count, err := instr.In[1].ConstInt() if err != nil { @@ -247,13 +247,13 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, fmt.Errorf("%s: negative shift count %d", instr.Op, count) } for bit := 0; bit < len(out); bit++ { - var id uint32 + var id circuit.Wire if bit+int(count) < len(wires[0]) { - id = wires[0][bit+int(count)].ID() + id = wires[0][bit+int(count)] } else { - id = signWire.ID() + id = signWire } - out[bit].SetID(id) + out[bit] = id } case Slice: @@ -274,9 +274,9 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, instr.Op, from, to) } for bit := from; bit < to; bit++ { - var id uint32 + var id circuit.Wire if int(bit) < len(wires[0]) { - id = wires[0][bit].ID() + id = wires[0][bit] } else { w, err := prog.ZeroWire(conn, streaming) if err != nil { @@ -284,11 +284,11 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, } id = w.ID() } - out[bit-from].SetID(id) + out[bit-from] = id } case Mov, Smov: - var signWire *circuits.Wire + var signWire circuit.Wire if instr.Op == Smov { signWire = wires[0][len(wires[0])-1] } else { @@ -296,16 +296,16 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, if err != nil { return nil, nil, err } - signWire = zero + signWire = zero.ID() } for bit := types.Size(0); bit < instr.Out.Type.Bits; bit++ { - var id uint32 + var id circuit.Wire if bit < types.Size(len(wires[0])) { - id = wires[0][bit].ID() + id = wires[0][bit] } else { - id = signWire.ID() + id = signWire } - out[bit].SetID(id) + out[bit] = id } case Amov: @@ -327,10 +327,10 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, } for bit := types.Size(0); bit < instr.Out.Type.Bits; bit++ { - var id uint32 + var id circuit.Wire if bit < from || bit >= to { if bit < types.Size(len(wires[1])) { - id = wires[1][bit].ID() + id = wires[1][bit] } else { w, err := prog.ZeroWire(conn, streaming) if err != nil { @@ -341,7 +341,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, } else { idx := bit - from if idx < types.Size(len(wires[0])) { - id = wires[0][idx].ID() + id = wires[0][idx] } else { w, err := prog.ZeroWire(conn, streaming) if err != nil { @@ -350,7 +350,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, id = w.ID() } } - out[bit].SetID(id) + out[bit] = id } case Ret: @@ -359,10 +359,10 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, } for _, arg := range wires { for _, w := range arg { - if err := conn.SendUint32(int(w.ID())); err != nil { + if err := conn.SendUint32(w.Int()); err != nil { return nil, nil, err } - returnIDs = append(returnIDs, w.ID()) + returnIDs = append(returnIDs, w) } } if circuit.StreamDebug { @@ -379,9 +379,9 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, for i := 0; i < len(wires); i++ { for j := 0; j < int(instr.Circ.Inputs[i].Type.Bits); j++ { if j < len(wires[i]) { - iIDs = append(iIDs, circuit.Wire(wires[i][j].ID())) + iIDs = append(iIDs, wires[i][j]) } else { - iIDs = append(iIDs, circuit.Wire(prog.zeroWire.ID())) + iIDs = append(iIDs, prog.zeroWire.ID()) } } } @@ -393,9 +393,9 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, } for j := 0; j < int(instr.Circ.Outputs[i].Type.Bits); j++ { if j < len(wires) { - oIDs = append(oIDs, circuit.Wire(wires[j].ID())) + oIDs = append(oIDs, wires[j]) } else { - oIDs = append(oIDs, circuit.Wire(prog.zeroWire.ID())) + oIDs = append(oIDs, prog.zeroWire.ID()) } } } @@ -421,7 +421,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, f, ok := circuitGenerators[instr.Op] if !ok { return nil, nil, - fmt.Errorf("Program.Stream: %s not implemented yet", + fmt.Errorf("Program.StreamCircuit: %s not implemented yet", instr.Op) } if params.Verbose && circuit.StreamDebug { @@ -456,8 +456,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, cc.ConstPropagate() pruned := cc.Prune() if params.Verbose && circuit.StreamDebug { - fmt.Printf("%05d: - pruned %d gates\n", - idx, pruned) + fmt.Printf("%05d: - pruned %d gates\n", idx, pruned) } circ = cc.Compile() if cacheable { @@ -482,11 +481,11 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, oIDs = oIDs[:0] for _, vars := range wires { for _, w := range vars { - iIDs = append(iIDs, circuit.Wire(w.ID())) + iIDs = append(iIDs, w) } } for _, w := range out { - oIDs = append(oIDs, circuit.Wire(w.ID())) + oIDs = append(oIDs, w) } err = prog.garble(conn, streaming, idx, circ, iIDs, oIDs) @@ -533,7 +532,7 @@ func (prog *Program) StreamCircuit(conn *p2p.Conn, oti ot.OT, if err != nil { return nil, nil, err } - wire := streaming.GetInput(circuit.Wire(returnIDs[i])) + wire := streaming.GetInput(returnIDs[i]) var bit uint if label.Equal(wire.L0) { bit = 0 @@ -675,7 +674,7 @@ func (prog *Program) ZeroWire(conn *p2p.Conn, streaming *circuit.Streaming) ( *circuits.Wire, error) { if prog.zeroWire == nil { - wires, err := prog.walloc.AssignedWires(Value{ + wires, err := prog.walloc.AssignedWiresAndIDs(Value{ Const: true, Name: "{zero}", }, 1) @@ -714,7 +713,7 @@ func (prog *Program) ZeroWire(conn *p2p.Conn, streaming *circuit.Streaming) ( Stats: circuit.Stats{ circuit.XOR: 1, }, - }, []circuit.Wire{0}, []circuit.Wire{circuit.Wire(wires[0].ID())}) + }, []circuit.Wire{0}, []circuit.Wire{wires[0].ID()}) if err != nil { return nil, err } @@ -728,7 +727,7 @@ func (prog *Program) OneWire(conn *p2p.Conn, streaming *circuit.Streaming) ( *circuits.Wire, error) { if prog.oneWire == nil { - wires, err := prog.walloc.AssignedWires(Value{ + wires, err := prog.walloc.AssignedWiresAndIDs(Value{ Const: true, Name: "{one}", }, 1) @@ -767,7 +766,7 @@ func (prog *Program) OneWire(conn *p2p.Conn, streaming *circuit.Streaming) ( Stats: circuit.Stats{ circuit.XNOR: 1, }, - }, []circuit.Wire{0}, []circuit.Wire{circuit.Wire(wires[0].ID())}) + }, []circuit.Wire{0}, []circuit.Wire{wires[0].ID()}) if err != nil { return nil, err } diff --git a/compiler/ssa/wire_allocator.go b/compiler/ssa/wire_allocator.go index 3dc4c8dc..b3eb0ffe 100644 --- a/compiler/ssa/wire_allocator.go +++ b/compiler/ssa/wire_allocator.go @@ -7,6 +7,7 @@ package ssa import ( + "github.com/markkurossi/mpc/circuit" "github.com/markkurossi/mpc/compiler/circuits" "github.com/markkurossi/mpc/types" ) @@ -16,17 +17,17 @@ type WireAllocator interface { // Allocated tests if the wires have been allocated for the value. Allocated(v Value) bool - // NextWireID allocated and returns the next unassigned wire ID. - NextWireID() uint32 + // Streamer API. - // Wires allocates unassigned wires for the argument value. - Wires(v Value, bits types.Size) ([]*circuits.Wire, error) + // NextWireID allocated and returns the next unassigned wire ID. + // XXX is this sync with circuits.Compiler.NextWireID()? + NextWireID() circuit.Wire // AssignedWires allocates assigned wires for the argument value. - AssignedWires(v Value, bits types.Size) ([]*circuits.Wire, error) + AssignedWires(v Value, bits types.Size) ([]circuit.Wire, error) - // SetWires allocates wire IDs for the value's wires. - SetWires(v Value, w []*circuits.Wire) + // AssignedWires allocates assigned wires for the argument value. + AssignedWiresAndIDs(v Value, bits types.Size) ([]*circuits.Wire, error) // GCWires recycles the wires of the argument value. The wires // must have been previously allocated with Wires, AssignedWires, @@ -34,6 +35,14 @@ type WireAllocator interface { // allocated. GCWires(v Value) + // Circuit compilation API. + + // Wires allocates unassigned wires for the argument value. + Wires(v Value, bits types.Size) ([]*circuits.Wire, error) + + // SetWires allocates wire IDs for the value's wires. + SetWires(v Value, w []*circuits.Wire) + // Debug prints debugging information about the wire allocator. Debug() } diff --git a/compiler/ssa/wire_allocator_string.go b/compiler/ssa/wire_allocator_string.go index 809096e8..de0b7635 100644 --- a/compiler/ssa/wire_allocator_string.go +++ b/compiler/ssa/wire_allocator_string.go @@ -10,6 +10,7 @@ import ( "fmt" "sort" + "github.com/markkurossi/mpc/circuit" "github.com/markkurossi/mpc/compiler/circuits" "github.com/markkurossi/mpc/types" ) @@ -20,7 +21,7 @@ type WAllocString struct { calloc *circuits.Allocator freeWires map[types.Size][][]*circuits.Wire wires map[string]*wireAlloc - nextWireID uint32 + nextWireID circuit.Wire flHit int flMiss int } @@ -64,7 +65,7 @@ func (walloc *WAllocString) Allocated(v Value) bool { } // NextWireID implements WireAllocator.NextWireID. -func (walloc *WAllocString) NextWireID() uint32 { +func (walloc *WAllocString) NextWireID() circuit.Wire { ret := walloc.nextWireID walloc.nextWireID++ return ret @@ -88,7 +89,7 @@ func (walloc *WAllocString) Wires(v Value, bits types.Size) ( // AssignedWires implements WireAllocator.AssignedWires. func (walloc *WAllocString) AssignedWires(v Value, bits types.Size) ( - []*circuits.Wire, error) { + []circuit.Wire, error) { if bits <= 0 { return nil, fmt.Errorf("size not set for value %v", v) } @@ -103,18 +104,24 @@ func (walloc *WAllocString) AssignedWires(v Value, bits types.Size) ( if alloc.Base == circuits.UnassignedID { alloc.Base = walloc.nextWireID for i := 0; i < int(bits); i++ { - alloc.Wires[i].SetID(walloc.nextWireID + uint32(i)) + alloc.Wires[i].SetID(walloc.nextWireID + circuit.Wire(i)) } - walloc.nextWireID += uint32(bits) + walloc.nextWireID += circuit.Wire(bits) } } - return alloc.Wires, nil + return alloc.IDs, nil +} + +func (walloc *WAllocString) AssignedWiresAndIDs(v Value, bits types.Size) ( + []*circuits.Wire, error) { + return nil, fmt.Errorf("not implemented") } type wireAlloc struct { - Base uint32 + Base circuit.Wire Wires []*circuits.Wire + IDs []circuit.Wire } func (walloc *WAllocString) allocWires(bits types.Size) *wireAlloc { @@ -171,7 +178,7 @@ func (walloc *WAllocString) GCWires(v Value) { // Clear wires and reassign their IDs. bits := types.Size(len(alloc.Wires)) for i := 0; i < int(bits); i++ { - alloc.Wires[i].Reset(alloc.Base + uint32(i)) + alloc.Wires[i].Reset(alloc.Base + circuit.Wire(i)) } fl := walloc.freeWires[bits] diff --git a/compiler/ssa/wire_allocator_value.go b/compiler/ssa/wire_allocator_value.go index 50b60a15..8279f3f6 100644 --- a/compiler/ssa/wire_allocator_value.go +++ b/compiler/ssa/wire_allocator_value.go @@ -10,6 +10,7 @@ import ( "fmt" "math" + "github.com/markkurossi/mpc/circuit" "github.com/markkurossi/mpc/compiler/circuits" "github.com/markkurossi/mpc/types" ) @@ -18,9 +19,11 @@ import ( // values to wires. type WAllocValue struct { calloc *circuits.Allocator + freeHdrs []*allocByValue freeWires map[types.Size][][]*circuits.Wire - wires [10240]*allocByValue - nextWireID uint32 + freeIDs map[types.Size][][]circuit.Wire + hash [10240]*allocByValue + nextWireID circuit.Wire flHit int flMiss int lookupCount int @@ -30,8 +33,9 @@ type WAllocValue struct { type allocByValue struct { next *allocByValue key Value - base uint32 + base circuit.Wire wires []*circuits.Wire + ids []circuit.Wire } func (alloc *allocByValue) String() string { @@ -45,18 +49,64 @@ func NewWAllocValue(calloc *circuits.Allocator) WireAllocator { return &WAllocValue{ calloc: calloc, freeWires: make(map[types.Size][][]*circuits.Wire), + freeIDs: make(map[types.Size][][]circuit.Wire), } } +func (walloc *WAllocValue) hashCode(v Value) int { + return v.HashCode() % len(walloc.hash) +} + +func (walloc *WAllocValue) newHeader(v Value) (ret *allocByValue) { + if len(walloc.freeHdrs) == 0 { + ret = new(allocByValue) + } else { + ret = walloc.freeHdrs[len(walloc.freeHdrs)-1] + walloc.freeHdrs = walloc.freeHdrs[:len(walloc.freeHdrs)-1] + } + ret.key = v + ret.base = circuits.UnassignedID + return ret +} + +func (walloc *WAllocValue) newWires(bits types.Size) (result []*circuits.Wire) { + fl, ok := walloc.freeWires[bits] + if ok && len(fl) > 0 { + result = fl[len(fl)-1] + walloc.freeWires[bits] = fl[:len(fl)-1] + walloc.flHit++ + } else { + result = walloc.calloc.Wires(bits) + walloc.flMiss++ + } + return result +} + +func (walloc *WAllocValue) newIDs(bits types.Size) (result []circuit.Wire) { + fl, ok := walloc.freeIDs[bits] + if ok && len(fl) > 0 { + result = fl[len(fl)-1] + walloc.freeIDs[bits] = fl[:len(fl)-1] + walloc.flHit++ + } else { + result = make([]circuit.Wire, bits) + for i := 0; i < int(bits); i++ { + result[i] = circuits.UnassignedID + } + walloc.flMiss++ + } + return result +} + // Allocated implements WireAllocator.Allocated. func (walloc *WAllocValue) Allocated(v Value) bool { - hash := v.HashCode() % len(walloc.wires) + hash := walloc.hashCode(v) alloc := walloc.lookup(hash, v) return alloc != nil } // NextWireID implements WireAllocator.NextWireID. -func (walloc *WAllocValue) NextWireID() uint32 { +func (walloc *WAllocValue) NextWireID() circuit.Wire { ret := walloc.nextWireID walloc.nextWireID++ return ret @@ -64,7 +114,7 @@ func (walloc *WAllocValue) NextWireID() uint32 { func (walloc *WAllocValue) lookup(hash int, v Value) *allocByValue { var count int - for ptr := &walloc.wires[hash]; *ptr != nil; ptr = &(*ptr).next { + for ptr := &walloc.hash[hash]; *ptr != nil; ptr = &(*ptr).next { count++ if (*ptr).key.Equal(&v) { alloc := *ptr @@ -72,8 +122,8 @@ func (walloc *WAllocValue) lookup(hash int, v Value) *allocByValue { if count > 2 { // MRU in the hash bucket. *ptr = alloc.next - alloc.next = walloc.wires[hash] - walloc.wires[hash] = alloc + alloc.next = walloc.hash[hash] + walloc.hash[hash] = alloc } walloc.lookupCount++ @@ -85,7 +135,7 @@ func (walloc *WAllocValue) lookup(hash int, v Value) *allocByValue { } func (walloc *WAllocValue) remove(hash int, v Value) *allocByValue { - for ptr := &walloc.wires[hash]; *ptr != nil; ptr = &(*ptr).next { + for ptr := &walloc.hash[hash]; *ptr != nil; ptr = &(*ptr).next { if (*ptr).key.Equal(&v) { ret := *ptr *ptr = (*ptr).next @@ -95,23 +145,26 @@ func (walloc *WAllocValue) remove(hash int, v Value) *allocByValue { return nil } -func (walloc *WAllocValue) alloc(bits types.Size, v Value) *allocByValue { - result := &allocByValue{ - key: v, - base: circuits.UnassignedID, - } +func (walloc *WAllocValue) alloc(bits types.Size, v Value, + wires, ids bool) *allocByValue { - fl, ok := walloc.freeWires[bits] - if ok && len(fl) > 0 { - result.wires = fl[len(fl)-1] + result := walloc.newHeader(v) + + if wires && ids { + result.wires = walloc.newWires(bits) + result.ids = walloc.newIDs(bits) + result.base = result.wires[0].ID() + + for i := 0; i < int(bits); i++ { + result.ids[i] = result.wires[i].ID() + } + } else if wires { + result.wires = walloc.newWires(bits) result.base = result.wires[0].ID() - walloc.freeWires[bits] = fl[:len(fl)-1] - walloc.flHit++ } else { - result.wires = walloc.calloc.Wires(bits) - walloc.flMiss++ + result.ids = walloc.newIDs(bits) + result.base = result.ids[0] } - return result } @@ -121,36 +174,72 @@ func (walloc *WAllocValue) Wires(v Value, bits types.Size) ( if bits <= 0 { return nil, fmt.Errorf("size not set for value %v", v) } - hash := v.HashCode() % len(walloc.wires) + hash := walloc.hashCode(v) alloc := walloc.lookup(hash, v) if alloc == nil { - alloc = walloc.alloc(bits, v) - alloc.next = walloc.wires[hash] - walloc.wires[hash] = alloc + alloc = walloc.alloc(bits, v, true, false) + alloc.next = walloc.hash[hash] + walloc.hash[hash] = alloc } return alloc.wires, nil } // AssignedWires implements WireAllocator.AssignedWires. func (walloc *WAllocValue) AssignedWires(v Value, bits types.Size) ( + []circuit.Wire, error) { + if bits <= 0 { + return nil, fmt.Errorf("size not set for value %v", v) + } + hash := walloc.hashCode(v) + alloc := walloc.lookup(hash, v) + if alloc == nil { + alloc = walloc.alloc(bits, v, false, true) + alloc.next = walloc.hash[hash] + walloc.hash[hash] = alloc + + // Assign wire IDs. + if alloc.base == circuits.UnassignedID { + alloc.base = walloc.nextWireID + for i := 0; i < int(bits); i++ { + alloc.ids[i] = walloc.nextWireID + circuit.Wire(i) + } + walloc.nextWireID += circuit.Wire(bits) + } + } + if alloc.ids == nil { + alloc.ids = walloc.newIDs(bits) + for i := 0; i < int(bits); i++ { + alloc.ids[i] = alloc.wires[i].ID() + } + } + return alloc.ids, nil +} + +func (walloc *WAllocValue) AssignedWiresAndIDs(v Value, bits types.Size) ( []*circuits.Wire, error) { if bits <= 0 { return nil, fmt.Errorf("size not set for value %v", v) } - hash := v.HashCode() % len(walloc.wires) + hash := walloc.hashCode(v) alloc := walloc.lookup(hash, v) if alloc == nil { - alloc = walloc.alloc(bits, v) - alloc.next = walloc.wires[hash] - walloc.wires[hash] = alloc + alloc = walloc.alloc(bits, v, true, true) + alloc.next = walloc.hash[hash] + walloc.hash[hash] = alloc // Assign wire IDs. if alloc.base == circuits.UnassignedID { alloc.base = walloc.nextWireID for i := 0; i < int(bits); i++ { - alloc.wires[i].SetID(walloc.nextWireID + uint32(i)) + alloc.wires[i].SetID(walloc.nextWireID + circuit.Wire(i)) } - walloc.nextWireID += uint32(bits) + walloc.nextWireID += circuit.Wire(bits) + } + } + if alloc.ids == nil { + alloc.ids = walloc.newIDs(bits) + for i := 0; i < int(bits); i++ { + alloc.ids[i] = alloc.wires[i].ID() } } return alloc.wires, nil @@ -158,7 +247,7 @@ func (walloc *WAllocValue) AssignedWires(v Value, bits types.Size) ( // SetWires implements WireAllocator.SetWires. func (walloc *WAllocValue) SetWires(v Value, w []*circuits.Wire) { - hash := v.HashCode() % len(walloc.wires) + hash := walloc.hashCode(v) alloc := walloc.lookup(hash, v) if alloc != nil { panic(fmt.Sprintf("wires already set for %v", v)) @@ -166,44 +255,57 @@ func (walloc *WAllocValue) SetWires(v Value, w []*circuits.Wire) { alloc = &allocByValue{ key: v, wires: w, + ids: make([]circuit.Wire, len(w)), } if len(w) == 0 { alloc.base = circuits.UnassignedID } else { alloc.base = w[0].ID() + for i := 0; i < len(w); i++ { + alloc.ids[i] = w[i].ID() + } } - alloc.next = walloc.wires[hash] - walloc.wires[hash] = alloc + alloc.next = walloc.hash[hash] + walloc.hash[hash] = alloc } // GCWires implements WireAllocator.GCWires. func (walloc *WAllocValue) GCWires(v Value) { - hash := v.HashCode() % len(walloc.wires) + hash := walloc.hashCode(v) alloc := walloc.remove(hash, v) if alloc == nil { panic(fmt.Sprintf("GC: %s not known", v)) } - if alloc.base == circuits.UnassignedID { - alloc.base = alloc.wires[0].ID() - } - // Clear wires and reassign their IDs. - bits := types.Size(len(alloc.wires)) - for i := 0; i < int(bits); i++ { - alloc.wires[i].Reset(alloc.base + uint32(i)) + if alloc.wires != nil { + if alloc.base == circuits.UnassignedID { + alloc.base = alloc.wires[0].ID() + } + // Clear wires and reassign their IDs. + for i := 0; i < len(alloc.wires); i++ { + alloc.wires[i].Reset(alloc.base + circuit.Wire(i)) + } + bits := types.Size(len(alloc.wires)) + walloc.freeWires[bits] = append(walloc.freeWires[bits], alloc.wires) } - - fl := walloc.freeWires[bits] - fl = append(fl, alloc.wires) - walloc.freeWires[bits] = fl - if false { - fmt.Printf("FL: %d: ", bits) - for k, v := range walloc.freeWires { - fmt.Printf(" %d:%d", k, len(v)) + if alloc.ids != nil { + if alloc.base == circuits.UnassignedID { + alloc.base = alloc.ids[0] } - fmt.Println() + // Clear IDs. + for i := 0; i < len(alloc.ids); i++ { + alloc.ids[i] = alloc.base + circuit.Wire(i) + } + bits := types.Size(len(alloc.ids)) + walloc.freeIDs[bits] = append(walloc.freeIDs[bits], alloc.ids) } + + alloc.next = nil + alloc.base = circuits.UnassignedID + alloc.wires = nil + alloc.ids = nil + walloc.freeHdrs = append(walloc.freeHdrs, alloc) } // Debug implements WireAllocator.Debug. @@ -218,9 +320,9 @@ func (walloc *WAllocValue) Debug() { var maxIndex int - for i := 0; i < len(walloc.wires); i++ { + for i := 0; i < len(walloc.hash); i++ { var count int - for alloc := walloc.wires[i]; alloc != nil; alloc = alloc.next { + for alloc := walloc.hash[i]; alloc != nil; alloc = alloc.next { count++ } sum += count @@ -233,13 +335,13 @@ func (walloc *WAllocValue) Debug() { } } fmt.Printf("Hash: min=%v, max=%v, avg=%.4f, lookup=%v (avg=%.4f)\n", - min, max, float64(sum)/float64(len(walloc.wires)), + min, max, float64(sum)/float64(len(walloc.hash)), walloc.lookupCount, float64(walloc.lookupFound)/float64(walloc.lookupCount)) if false { fmt.Printf("Max bucket:\n") - for alloc := walloc.wires[maxIndex]; alloc != nil; alloc = alloc.next { + for alloc := walloc.hash[maxIndex]; alloc != nil; alloc = alloc.next { fmt.Printf(" %v: %v\n", alloc.key.String(), len(alloc.wires)) } } From 8fee344b68a15c37ff788a4ee4117209d03c5ec2 Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Mon, 4 Sep 2023 12:02:19 +0200 Subject: [PATCH 17/19] Cleaned wire allocation API. --- compiler/ssa/program.go | 4 +- compiler/ssa/wire_allocator.go | 354 ++++++++++++++++++++++++-- compiler/ssa/wire_allocator_string.go | 236 ----------------- compiler/ssa/wire_allocator_value.go | 348 ------------------------- 4 files changed, 332 insertions(+), 610 deletions(-) delete mode 100644 compiler/ssa/wire_allocator_string.go delete mode 100644 compiler/ssa/wire_allocator_value.go diff --git a/compiler/ssa/program.go b/compiler/ssa/program.go index 0a0da0ff..ff42ca78 100644 --- a/compiler/ssa/program.go +++ b/compiler/ssa/program.go @@ -29,7 +29,7 @@ type Program struct { OutputWires []*circuits.Wire Constants map[string]ConstantInst Steps []Step - walloc WireAllocator + walloc *WireAllocator calloc *circuits.Allocator zeroWire *circuits.Wire oneWire *circuits.Wire @@ -52,7 +52,7 @@ func NewProgram(params *utils.Params, in, out circuit.IO, Outputs: out, Constants: consts, Steps: steps, - walloc: NewWAllocValue(calloc), + walloc: NewWireAllocator(calloc), calloc: calloc, } diff --git a/compiler/ssa/wire_allocator.go b/compiler/ssa/wire_allocator.go index b3eb0ffe..67ded381 100644 --- a/compiler/ssa/wire_allocator.go +++ b/compiler/ssa/wire_allocator.go @@ -7,42 +7,348 @@ package ssa import ( + "fmt" + "math" + "github.com/markkurossi/mpc/circuit" "github.com/markkurossi/mpc/compiler/circuits" "github.com/markkurossi/mpc/types" ) -// WireAllocator implements dynamic wire allocation. -type WireAllocator interface { - // Allocated tests if the wires have been allocated for the value. - Allocated(v Value) bool +// WireAllocator implements wire allocation using Value.HashCode to +// map values to wires. +type WireAllocator struct { + calloc *circuits.Allocator + freeHdrs []*allocByValue + freeWires map[types.Size][][]*circuits.Wire + freeIDs map[types.Size][][]circuit.Wire + hash [10240]*allocByValue + nextWireID circuit.Wire + flHit int + flMiss int + lookupCount int + lookupFound int +} + +type allocByValue struct { + next *allocByValue + key Value + base circuit.Wire + wires []*circuits.Wire + ids []circuit.Wire +} + +func (alloc *allocByValue) String() string { + return fmt.Sprintf("%v[%v]: base=%v, len(wires)=%v", + alloc.key.String(), alloc.key.Type, + alloc.base, len(alloc.wires)) +} + +// NewWireAllocator creates a new WireAllocator. +func NewWireAllocator(calloc *circuits.Allocator) *WireAllocator { + return &WireAllocator{ + calloc: calloc, + freeWires: make(map[types.Size][][]*circuits.Wire), + freeIDs: make(map[types.Size][][]circuit.Wire), + } +} + +func (walloc *WireAllocator) hashCode(v Value) int { + return v.HashCode() % len(walloc.hash) +} + +func (walloc *WireAllocator) newHeader(v Value) (ret *allocByValue) { + if len(walloc.freeHdrs) == 0 { + ret = new(allocByValue) + } else { + ret = walloc.freeHdrs[len(walloc.freeHdrs)-1] + walloc.freeHdrs = walloc.freeHdrs[:len(walloc.freeHdrs)-1] + } + ret.key = v + ret.base = circuits.UnassignedID + return ret +} + +func (walloc *WireAllocator) newWires(bits types.Size) ( + result []*circuits.Wire) { + + fl, ok := walloc.freeWires[bits] + if ok && len(fl) > 0 { + result = fl[len(fl)-1] + walloc.freeWires[bits] = fl[:len(fl)-1] + walloc.flHit++ + } else { + result = walloc.calloc.Wires(bits) + walloc.flMiss++ + } + return result +} - // Streamer API. +func (walloc *WireAllocator) newIDs(bits types.Size) (result []circuit.Wire) { + fl, ok := walloc.freeIDs[bits] + if ok && len(fl) > 0 { + result = fl[len(fl)-1] + walloc.freeIDs[bits] = fl[:len(fl)-1] + walloc.flHit++ + } else { + result = make([]circuit.Wire, bits) + for i := 0; i < int(bits); i++ { + result[i] = circuits.UnassignedID + } + walloc.flMiss++ + } + return result +} - // NextWireID allocated and returns the next unassigned wire ID. - // XXX is this sync with circuits.Compiler.NextWireID()? - NextWireID() circuit.Wire +func (walloc *WireAllocator) lookup(hash int, v Value) *allocByValue { + var count int + for ptr := &walloc.hash[hash]; *ptr != nil; ptr = &(*ptr).next { + count++ + if (*ptr).key.Equal(&v) { + alloc := *ptr - // AssignedWires allocates assigned wires for the argument value. - AssignedWires(v Value, bits types.Size) ([]circuit.Wire, error) + if count > 2 { + // MRU in the hash bucket. + *ptr = alloc.next + alloc.next = walloc.hash[hash] + walloc.hash[hash] = alloc + } - // AssignedWires allocates assigned wires for the argument value. - AssignedWiresAndIDs(v Value, bits types.Size) ([]*circuits.Wire, error) + walloc.lookupCount++ + walloc.lookupFound += count + return alloc + } + } + return nil +} + +func (walloc *WireAllocator) alloc(bits types.Size, v Value, + wires, ids bool) *allocByValue { + + result := walloc.newHeader(v) + + if wires && ids { + result.wires = walloc.newWires(bits) + result.ids = walloc.newIDs(bits) + result.base = result.wires[0].ID() + + for i := 0; i < int(bits); i++ { + result.ids[i] = result.wires[i].ID() + } + } else if wires { + result.wires = walloc.newWires(bits) + result.base = result.wires[0].ID() + } else { + result.ids = walloc.newIDs(bits) + result.base = result.ids[0] + } + return result +} + +func (walloc *WireAllocator) remove(hash int, v Value) *allocByValue { + for ptr := &walloc.hash[hash]; *ptr != nil; ptr = &(*ptr).next { + if (*ptr).key.Equal(&v) { + ret := *ptr + *ptr = (*ptr).next + return ret + } + } + return nil +} + +// Allocated tests if the wires have been allocated for the value. +func (walloc *WireAllocator) Allocated(v Value) bool { + hash := walloc.hashCode(v) + alloc := walloc.lookup(hash, v) + return alloc != nil +} + +// NextWireID allocated and returns the next unassigned wire ID. +// XXX is this sync with circuits.Compiler.NextWireID()? +func (walloc *WireAllocator) NextWireID() circuit.Wire { + ret := walloc.nextWireID + walloc.nextWireID++ + return ret +} + +// AssignedWires allocates assigned wires for the argument value. +func (walloc *WireAllocator) AssignedWires(v Value, bits types.Size) ( + []circuit.Wire, error) { + if bits <= 0 { + return nil, fmt.Errorf("size not set for value %v", v) + } + hash := walloc.hashCode(v) + alloc := walloc.lookup(hash, v) + if alloc == nil { + alloc = walloc.alloc(bits, v, false, true) + alloc.next = walloc.hash[hash] + walloc.hash[hash] = alloc + + // Assign wire IDs. + if alloc.base == circuits.UnassignedID { + alloc.base = walloc.nextWireID + for i := 0; i < int(bits); i++ { + alloc.ids[i] = walloc.nextWireID + circuit.Wire(i) + } + walloc.nextWireID += circuit.Wire(bits) + } + } + if alloc.ids == nil { + alloc.ids = walloc.newIDs(bits) + for i := 0; i < int(bits); i++ { + alloc.ids[i] = alloc.wires[i].ID() + } + } + return alloc.ids, nil +} + +// AssignedWiresAndIDs allocates assigned wires for the argument value. +func (walloc *WireAllocator) AssignedWiresAndIDs(v Value, bits types.Size) ( + []*circuits.Wire, error) { + if bits <= 0 { + return nil, fmt.Errorf("size not set for value %v", v) + } + hash := walloc.hashCode(v) + alloc := walloc.lookup(hash, v) + if alloc == nil { + alloc = walloc.alloc(bits, v, true, true) + alloc.next = walloc.hash[hash] + walloc.hash[hash] = alloc + + // Assign wire IDs. + if alloc.base == circuits.UnassignedID { + alloc.base = walloc.nextWireID + for i := 0; i < int(bits); i++ { + alloc.wires[i].SetID(walloc.nextWireID + circuit.Wire(i)) + } + walloc.nextWireID += circuit.Wire(bits) + } + } + if alloc.ids == nil { + alloc.ids = walloc.newIDs(bits) + for i := 0; i < int(bits); i++ { + alloc.ids[i] = alloc.wires[i].ID() + } + } + return alloc.wires, nil +} + +// GCWires recycles the wires of the argument value. The wires must +// have been previously allocated with Wires, AssignedWires, or +// SetWires; the function panics if the wires have not been allocated. +func (walloc *WireAllocator) GCWires(v Value) { + hash := walloc.hashCode(v) + alloc := walloc.remove(hash, v) + if alloc == nil { + panic(fmt.Sprintf("GC: %s not known", v)) + } + + if alloc.wires != nil { + if alloc.base == circuits.UnassignedID { + alloc.base = alloc.wires[0].ID() + } + // Clear wires and reassign their IDs. + for i := 0; i < len(alloc.wires); i++ { + alloc.wires[i].Reset(alloc.base + circuit.Wire(i)) + } + bits := types.Size(len(alloc.wires)) + walloc.freeWires[bits] = append(walloc.freeWires[bits], alloc.wires) + } + if alloc.ids != nil { + if alloc.base == circuits.UnassignedID { + alloc.base = alloc.ids[0] + } + // Clear IDs. + for i := 0; i < len(alloc.ids); i++ { + alloc.ids[i] = alloc.base + circuit.Wire(i) + } + bits := types.Size(len(alloc.ids)) + walloc.freeIDs[bits] = append(walloc.freeIDs[bits], alloc.ids) + } + + alloc.next = nil + alloc.base = circuits.UnassignedID + alloc.wires = nil + alloc.ids = nil + walloc.freeHdrs = append(walloc.freeHdrs, alloc) +} + +// Wires allocates unassigned wires for the argument value. +func (walloc *WireAllocator) Wires(v Value, bits types.Size) ( + []*circuits.Wire, error) { + if bits <= 0 { + return nil, fmt.Errorf("size not set for value %v", v) + } + hash := walloc.hashCode(v) + alloc := walloc.lookup(hash, v) + if alloc == nil { + alloc = walloc.alloc(bits, v, true, false) + alloc.next = walloc.hash[hash] + walloc.hash[hash] = alloc + } + return alloc.wires, nil +} + +// SetWires allocates wire IDs for the value's wires. +func (walloc *WireAllocator) SetWires(v Value, w []*circuits.Wire) { + hash := walloc.hashCode(v) + alloc := walloc.lookup(hash, v) + if alloc != nil { + panic(fmt.Sprintf("wires already set for %v", v)) + } + alloc = &allocByValue{ + key: v, + wires: w, + ids: make([]circuit.Wire, len(w)), + } + if len(w) == 0 { + alloc.base = circuits.UnassignedID + } else { + alloc.base = w[0].ID() + for i := 0; i < len(w); i++ { + alloc.ids[i] = w[i].ID() + } + } + + alloc.next = walloc.hash[hash] + walloc.hash[hash] = alloc +} - // GCWires recycles the wires of the argument value. The wires - // must have been previously allocated with Wires, AssignedWires, - // or SetWires; the function panics if the wires have not been - // allocated. - GCWires(v Value) +// Debug prints debugging information about the wire allocator. +func (walloc *WireAllocator) Debug() { + total := float64(walloc.flHit + walloc.flMiss) + fmt.Printf("Wire freelist: hit=%v (%.2f%%), miss=%v (%.2f%%)\n", + walloc.flHit, float64(walloc.flHit)/total*100, + walloc.flMiss, float64(walloc.flMiss)/total*100) - // Circuit compilation API. + var sum, max int + min := math.MaxInt - // Wires allocates unassigned wires for the argument value. - Wires(v Value, bits types.Size) ([]*circuits.Wire, error) + var maxIndex int - // SetWires allocates wire IDs for the value's wires. - SetWires(v Value, w []*circuits.Wire) + for i := 0; i < len(walloc.hash); i++ { + var count int + for alloc := walloc.hash[i]; alloc != nil; alloc = alloc.next { + count++ + } + sum += count + if count < min { + min = count + } + if count > max { + max = count + maxIndex = i + } + } + fmt.Printf("Hash: min=%v, max=%v, avg=%.4f, lookup=%v (avg=%.4f)\n", + min, max, float64(sum)/float64(len(walloc.hash)), + walloc.lookupCount, + float64(walloc.lookupFound)/float64(walloc.lookupCount)) - // Debug prints debugging information about the wire allocator. - Debug() + if false { + fmt.Printf("Max bucket:\n") + for alloc := walloc.hash[maxIndex]; alloc != nil; alloc = alloc.next { + fmt.Printf(" %v: %v\n", alloc.key.String(), len(alloc.wires)) + } + } } diff --git a/compiler/ssa/wire_allocator_string.go b/compiler/ssa/wire_allocator_string.go deleted file mode 100644 index de0b7635..00000000 --- a/compiler/ssa/wire_allocator_string.go +++ /dev/null @@ -1,236 +0,0 @@ -// -// Copyright (c) 2020-2023 Markku Rossi -// -// All rights reserved. -// - -package ssa - -import ( - "fmt" - "sort" - - "github.com/markkurossi/mpc/circuit" - "github.com/markkurossi/mpc/compiler/circuits" - "github.com/markkurossi/mpc/types" -) - -// WAllocString implements WireAllocator using Value.String to map -// values to wires. -type WAllocString struct { - calloc *circuits.Allocator - freeWires map[types.Size][][]*circuits.Wire - wires map[string]*wireAlloc - nextWireID circuit.Wire - flHit int - flMiss int -} - -// NewWAllocString creates a new WAllocString. -func NewWAllocString(calloc *circuits.Allocator) WireAllocator { - return &WAllocString{ - calloc: calloc, - wires: make(map[string]*wireAlloc), - freeWires: make(map[types.Size][][]*circuits.Wire), - } -} - -var ( - vConst int - vTypeRef int - vPtr int - vDefault int - hash [1024]int -) - -func addValueStats(v Value) { - if v.Const { - vConst++ - } else if v.TypeRef { - vTypeRef++ - } else if v.Type.Type == types.TPtr { - vPtr++ - } else { - vDefault++ - } - - hash[v.HashCode()%len(hash)]++ -} - -// Allocated implements WireAllocator.Allocated. -func (walloc *WAllocString) Allocated(v Value) bool { - key := v.String() - _, ok := walloc.wires[key] - return ok -} - -// NextWireID implements WireAllocator.NextWireID. -func (walloc *WAllocString) NextWireID() circuit.Wire { - ret := walloc.nextWireID - walloc.nextWireID++ - return ret -} - -// Wires implements WireAllocator.Wires. -func (walloc *WAllocString) Wires(v Value, bits types.Size) ( - []*circuits.Wire, error) { - if bits <= 0 { - return nil, fmt.Errorf("size not set for value %v", v) - } - addValueStats(v) - key := v.String() - alloc, ok := walloc.wires[key] - if !ok { - alloc = walloc.allocWires(bits) - walloc.wires[key] = alloc - } - return alloc.Wires, nil -} - -// AssignedWires implements WireAllocator.AssignedWires. -func (walloc *WAllocString) AssignedWires(v Value, bits types.Size) ( - []circuit.Wire, error) { - if bits <= 0 { - return nil, fmt.Errorf("size not set for value %v", v) - } - addValueStats(v) - key := v.String() - alloc, ok := walloc.wires[key] - if !ok { - alloc = walloc.allocWires(bits) - walloc.wires[key] = alloc - - // Assign wire IDs. - if alloc.Base == circuits.UnassignedID { - alloc.Base = walloc.nextWireID - for i := 0; i < int(bits); i++ { - alloc.Wires[i].SetID(walloc.nextWireID + circuit.Wire(i)) - } - walloc.nextWireID += circuit.Wire(bits) - } - } - - return alloc.IDs, nil -} - -func (walloc *WAllocString) AssignedWiresAndIDs(v Value, bits types.Size) ( - []*circuits.Wire, error) { - return nil, fmt.Errorf("not implemented") -} - -type wireAlloc struct { - Base circuit.Wire - Wires []*circuits.Wire - IDs []circuit.Wire -} - -func (walloc *WAllocString) allocWires(bits types.Size) *wireAlloc { - result := &wireAlloc{ - Base: circuits.UnassignedID, - } - - fl, ok := walloc.freeWires[bits] - if ok && len(fl) > 0 { - result.Wires = fl[len(fl)-1] - result.Base = result.Wires[0].ID() - walloc.freeWires[bits] = fl[:len(fl)-1] - walloc.flHit++ - } else { - result.Wires = walloc.calloc.Wires(bits) - walloc.flMiss++ - } - - return result -} - -// SetWires implements WireAllocator.SetWires. -func (walloc *WAllocString) SetWires(v Value, w []*circuits.Wire) { - addValueStats(v) - key := v.String() - _, ok := walloc.wires[key] - if ok { - panic(fmt.Sprintf("wires already set for %v", key)) - } - alloc := &wireAlloc{ - Wires: w, - } - if len(w) == 0 { - alloc.Base = circuits.UnassignedID - } else { - alloc.Base = w[0].ID() - } - - walloc.wires[key] = alloc -} - -// GCWires implements WireAllocator.GCWires. -func (walloc *WAllocString) GCWires(v Value) { - key := v.String() - alloc, ok := walloc.wires[key] - if !ok { - panic(fmt.Sprintf("GC: %s not known", key)) - } - delete(walloc.wires, key) - - if alloc.Base == circuits.UnassignedID { - alloc.Base = alloc.Wires[0].ID() - } - // Clear wires and reassign their IDs. - bits := types.Size(len(alloc.Wires)) - for i := 0; i < int(bits); i++ { - alloc.Wires[i].Reset(alloc.Base + circuit.Wire(i)) - } - - fl := walloc.freeWires[bits] - fl = append(fl, alloc.Wires) - walloc.freeWires[bits] = fl - if false { - fmt.Printf("FL: %d: ", bits) - for k, v := range walloc.freeWires { - fmt.Printf(" %d:%d", k, len(v)) - } - fmt.Println() - } -} - -// Debug implements WireAllocator.Debug. -func (walloc *WAllocString) Debug() { - total := float64(walloc.flHit + walloc.flMiss) - fmt.Printf("Wire freelist: hit=%v (%.2f%%), miss=%v (%.2f%%)\n", - walloc.flHit, float64(walloc.flHit)/total*100, - walloc.flMiss, float64(walloc.flMiss)/total*100) - - var keys []types.Size - for k := range walloc.freeWires { - keys = append(keys, k) - } - sort.Slice(keys, func(i, j int) bool { - return keys[i] < keys[j] - }) - - for _, k := range keys { - fmt.Printf(" %d:\t%d\n", k, len(walloc.freeWires[types.Size(k)])) - } - fmt.Println() - - fmt.Println("Value Stats:") - - sum := float64(vConst + vTypeRef + vPtr + vDefault) - - fmt.Printf(" - vConst:\t%v\t%f%%\n", vConst, float64(vConst)/sum*100) - fmt.Printf(" - vTypeRef:\t%v\t%f%%\n", vTypeRef, float64(vTypeRef)/sum*100) - fmt.Printf(" - vPtr:\t%v\t%f%%\n", vPtr, float64(vPtr)/sum*100) - fmt.Printf(" - vDefault:\t%v\t%f%%\n", vDefault, float64(vDefault)/sum*100) - - if false { - var zeroes int - for idx, count := range hash { - if count == 0 { - zeroes++ - } else { - fmt.Printf("%v:\t%v\n", idx, count) - } - } - fmt.Printf("%v zero buckets\n", zeroes) - } -} diff --git a/compiler/ssa/wire_allocator_value.go b/compiler/ssa/wire_allocator_value.go deleted file mode 100644 index 8279f3f6..00000000 --- a/compiler/ssa/wire_allocator_value.go +++ /dev/null @@ -1,348 +0,0 @@ -// -// Copyright (c) 2023 Markku Rossi -// -// All rights reserved. -// - -package ssa - -import ( - "fmt" - "math" - - "github.com/markkurossi/mpc/circuit" - "github.com/markkurossi/mpc/compiler/circuits" - "github.com/markkurossi/mpc/types" -) - -// WAllocValue implements WireAllocator using Value.HashCode to map -// values to wires. -type WAllocValue struct { - calloc *circuits.Allocator - freeHdrs []*allocByValue - freeWires map[types.Size][][]*circuits.Wire - freeIDs map[types.Size][][]circuit.Wire - hash [10240]*allocByValue - nextWireID circuit.Wire - flHit int - flMiss int - lookupCount int - lookupFound int -} - -type allocByValue struct { - next *allocByValue - key Value - base circuit.Wire - wires []*circuits.Wire - ids []circuit.Wire -} - -func (alloc *allocByValue) String() string { - return fmt.Sprintf("%v[%v]: base=%v, len(wires)=%v", - alloc.key.String(), alloc.key.Type, - alloc.base, len(alloc.wires)) -} - -// NewWAllocValue creates a new WAllocValue. -func NewWAllocValue(calloc *circuits.Allocator) WireAllocator { - return &WAllocValue{ - calloc: calloc, - freeWires: make(map[types.Size][][]*circuits.Wire), - freeIDs: make(map[types.Size][][]circuit.Wire), - } -} - -func (walloc *WAllocValue) hashCode(v Value) int { - return v.HashCode() % len(walloc.hash) -} - -func (walloc *WAllocValue) newHeader(v Value) (ret *allocByValue) { - if len(walloc.freeHdrs) == 0 { - ret = new(allocByValue) - } else { - ret = walloc.freeHdrs[len(walloc.freeHdrs)-1] - walloc.freeHdrs = walloc.freeHdrs[:len(walloc.freeHdrs)-1] - } - ret.key = v - ret.base = circuits.UnassignedID - return ret -} - -func (walloc *WAllocValue) newWires(bits types.Size) (result []*circuits.Wire) { - fl, ok := walloc.freeWires[bits] - if ok && len(fl) > 0 { - result = fl[len(fl)-1] - walloc.freeWires[bits] = fl[:len(fl)-1] - walloc.flHit++ - } else { - result = walloc.calloc.Wires(bits) - walloc.flMiss++ - } - return result -} - -func (walloc *WAllocValue) newIDs(bits types.Size) (result []circuit.Wire) { - fl, ok := walloc.freeIDs[bits] - if ok && len(fl) > 0 { - result = fl[len(fl)-1] - walloc.freeIDs[bits] = fl[:len(fl)-1] - walloc.flHit++ - } else { - result = make([]circuit.Wire, bits) - for i := 0; i < int(bits); i++ { - result[i] = circuits.UnassignedID - } - walloc.flMiss++ - } - return result -} - -// Allocated implements WireAllocator.Allocated. -func (walloc *WAllocValue) Allocated(v Value) bool { - hash := walloc.hashCode(v) - alloc := walloc.lookup(hash, v) - return alloc != nil -} - -// NextWireID implements WireAllocator.NextWireID. -func (walloc *WAllocValue) NextWireID() circuit.Wire { - ret := walloc.nextWireID - walloc.nextWireID++ - return ret -} - -func (walloc *WAllocValue) lookup(hash int, v Value) *allocByValue { - var count int - for ptr := &walloc.hash[hash]; *ptr != nil; ptr = &(*ptr).next { - count++ - if (*ptr).key.Equal(&v) { - alloc := *ptr - - if count > 2 { - // MRU in the hash bucket. - *ptr = alloc.next - alloc.next = walloc.hash[hash] - walloc.hash[hash] = alloc - } - - walloc.lookupCount++ - walloc.lookupFound += count - return alloc - } - } - return nil -} - -func (walloc *WAllocValue) remove(hash int, v Value) *allocByValue { - for ptr := &walloc.hash[hash]; *ptr != nil; ptr = &(*ptr).next { - if (*ptr).key.Equal(&v) { - ret := *ptr - *ptr = (*ptr).next - return ret - } - } - return nil -} - -func (walloc *WAllocValue) alloc(bits types.Size, v Value, - wires, ids bool) *allocByValue { - - result := walloc.newHeader(v) - - if wires && ids { - result.wires = walloc.newWires(bits) - result.ids = walloc.newIDs(bits) - result.base = result.wires[0].ID() - - for i := 0; i < int(bits); i++ { - result.ids[i] = result.wires[i].ID() - } - } else if wires { - result.wires = walloc.newWires(bits) - result.base = result.wires[0].ID() - } else { - result.ids = walloc.newIDs(bits) - result.base = result.ids[0] - } - return result -} - -// Wires implements WireAllocator.Wires. -func (walloc *WAllocValue) Wires(v Value, bits types.Size) ( - []*circuits.Wire, error) { - if bits <= 0 { - return nil, fmt.Errorf("size not set for value %v", v) - } - hash := walloc.hashCode(v) - alloc := walloc.lookup(hash, v) - if alloc == nil { - alloc = walloc.alloc(bits, v, true, false) - alloc.next = walloc.hash[hash] - walloc.hash[hash] = alloc - } - return alloc.wires, nil -} - -// AssignedWires implements WireAllocator.AssignedWires. -func (walloc *WAllocValue) AssignedWires(v Value, bits types.Size) ( - []circuit.Wire, error) { - if bits <= 0 { - return nil, fmt.Errorf("size not set for value %v", v) - } - hash := walloc.hashCode(v) - alloc := walloc.lookup(hash, v) - if alloc == nil { - alloc = walloc.alloc(bits, v, false, true) - alloc.next = walloc.hash[hash] - walloc.hash[hash] = alloc - - // Assign wire IDs. - if alloc.base == circuits.UnassignedID { - alloc.base = walloc.nextWireID - for i := 0; i < int(bits); i++ { - alloc.ids[i] = walloc.nextWireID + circuit.Wire(i) - } - walloc.nextWireID += circuit.Wire(bits) - } - } - if alloc.ids == nil { - alloc.ids = walloc.newIDs(bits) - for i := 0; i < int(bits); i++ { - alloc.ids[i] = alloc.wires[i].ID() - } - } - return alloc.ids, nil -} - -func (walloc *WAllocValue) AssignedWiresAndIDs(v Value, bits types.Size) ( - []*circuits.Wire, error) { - if bits <= 0 { - return nil, fmt.Errorf("size not set for value %v", v) - } - hash := walloc.hashCode(v) - alloc := walloc.lookup(hash, v) - if alloc == nil { - alloc = walloc.alloc(bits, v, true, true) - alloc.next = walloc.hash[hash] - walloc.hash[hash] = alloc - - // Assign wire IDs. - if alloc.base == circuits.UnassignedID { - alloc.base = walloc.nextWireID - for i := 0; i < int(bits); i++ { - alloc.wires[i].SetID(walloc.nextWireID + circuit.Wire(i)) - } - walloc.nextWireID += circuit.Wire(bits) - } - } - if alloc.ids == nil { - alloc.ids = walloc.newIDs(bits) - for i := 0; i < int(bits); i++ { - alloc.ids[i] = alloc.wires[i].ID() - } - } - return alloc.wires, nil -} - -// SetWires implements WireAllocator.SetWires. -func (walloc *WAllocValue) SetWires(v Value, w []*circuits.Wire) { - hash := walloc.hashCode(v) - alloc := walloc.lookup(hash, v) - if alloc != nil { - panic(fmt.Sprintf("wires already set for %v", v)) - } - alloc = &allocByValue{ - key: v, - wires: w, - ids: make([]circuit.Wire, len(w)), - } - if len(w) == 0 { - alloc.base = circuits.UnassignedID - } else { - alloc.base = w[0].ID() - for i := 0; i < len(w); i++ { - alloc.ids[i] = w[i].ID() - } - } - - alloc.next = walloc.hash[hash] - walloc.hash[hash] = alloc -} - -// GCWires implements WireAllocator.GCWires. -func (walloc *WAllocValue) GCWires(v Value) { - hash := walloc.hashCode(v) - alloc := walloc.remove(hash, v) - if alloc == nil { - panic(fmt.Sprintf("GC: %s not known", v)) - } - - if alloc.wires != nil { - if alloc.base == circuits.UnassignedID { - alloc.base = alloc.wires[0].ID() - } - // Clear wires and reassign their IDs. - for i := 0; i < len(alloc.wires); i++ { - alloc.wires[i].Reset(alloc.base + circuit.Wire(i)) - } - bits := types.Size(len(alloc.wires)) - walloc.freeWires[bits] = append(walloc.freeWires[bits], alloc.wires) - } - if alloc.ids != nil { - if alloc.base == circuits.UnassignedID { - alloc.base = alloc.ids[0] - } - // Clear IDs. - for i := 0; i < len(alloc.ids); i++ { - alloc.ids[i] = alloc.base + circuit.Wire(i) - } - bits := types.Size(len(alloc.ids)) - walloc.freeIDs[bits] = append(walloc.freeIDs[bits], alloc.ids) - } - - alloc.next = nil - alloc.base = circuits.UnassignedID - alloc.wires = nil - alloc.ids = nil - walloc.freeHdrs = append(walloc.freeHdrs, alloc) -} - -// Debug implements WireAllocator.Debug. -func (walloc *WAllocValue) Debug() { - total := float64(walloc.flHit + walloc.flMiss) - fmt.Printf("Wire freelist: hit=%v (%.2f%%), miss=%v (%.2f%%)\n", - walloc.flHit, float64(walloc.flHit)/total*100, - walloc.flMiss, float64(walloc.flMiss)/total*100) - - var sum, max int - min := math.MaxInt - - var maxIndex int - - for i := 0; i < len(walloc.hash); i++ { - var count int - for alloc := walloc.hash[i]; alloc != nil; alloc = alloc.next { - count++ - } - sum += count - if count < min { - min = count - } - if count > max { - max = count - maxIndex = i - } - } - fmt.Printf("Hash: min=%v, max=%v, avg=%.4f, lookup=%v (avg=%.4f)\n", - min, max, float64(sum)/float64(len(walloc.hash)), - walloc.lookupCount, - float64(walloc.lookupFound)/float64(walloc.lookupCount)) - - if false { - fmt.Printf("Max bucket:\n") - for alloc := walloc.hash[maxIndex]; alloc != nil; alloc = alloc.next { - fmt.Printf(" %v: %v\n", alloc.key.String(), len(alloc.wires)) - } - } -} From 821d2e982a5ac861dff6d7b77f91a67bcbd557e8 Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Mon, 4 Sep 2023 13:55:31 +0200 Subject: [PATCH 18/19] Updated API and statistics. --- compiler/circuits/allocator.go | 6 ++--- compiler/ssa/streamer.go | 11 ++++----- compiler/ssa/wire_allocator.go | 43 +++++++++++++++++++++++----------- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/compiler/circuits/allocator.go b/compiler/circuits/allocator.go index 50fbf550..597861ca 100644 --- a/compiler/circuits/allocator.go +++ b/compiler/circuits/allocator.go @@ -92,10 +92,10 @@ func (alloc *Allocator) Debug() { total := float64(wireSize + wiresSize + gatesSize) fmt.Println("circuits.Allocator:") - fmt.Printf(" wire : %9v %5s %5.2f%%\n", + fmt.Printf(" wire : %9v %5s %5.2f%%\n", alloc.numWire, wireSize, float64(wireSize)/total*100.0) - fmt.Printf(" wires: %9v %5s %5.2f%%\n", + fmt.Printf(" wires: %9v %5s %5.2f%%\n", alloc.numWires, wiresSize, float64(wiresSize)/total*100.0) - fmt.Printf(" gates: %9v %5s %5.2f%%\n", + fmt.Printf(" gates: %9v %5s %5.2f%%\n", alloc.numGates, gatesSize, float64(gatesSize)/total*100.0) } diff --git a/compiler/ssa/streamer.go b/compiler/ssa/streamer.go index 2e27abe9..bf4093c8 100644 --- a/compiler/ssa/streamer.go +++ b/compiler/ssa/streamer.go @@ -176,7 +176,7 @@ func (prog *Program) Stream(conn *p2p.Conn, oti ot.OT, instr := step.Instr wires = wires[:0] for _, in := range instr.In { - w, err := prog.walloc.AssignedWires(in, in.Type.Bits) + w, err := prog.walloc.AssignedIDs(in, in.Type.Bits) if err != nil { return nil, nil, err } @@ -186,8 +186,7 @@ func (prog *Program) Stream(conn *p2p.Conn, oti ot.OT, var out []circuit.Wire var err error if instr.Out != nil { - out, err = prog.walloc.AssignedWires(*instr.Out, - instr.Out.Type.Bits) + out, err = prog.walloc.AssignedIDs(*instr.Out, instr.Out.Type.Bits) if err != nil { return nil, nil, err } @@ -387,7 +386,7 @@ func (prog *Program) Stream(conn *p2p.Conn, oti ot.OT, } // Return wires. for i, ret := range instr.Ret { - wires, err := prog.walloc.AssignedWires(ret, ret.Type.Bits) + wires, err := prog.walloc.AssignedIDs(ret, ret.Type.Bits) if err != nil { return nil, nil, err } @@ -674,7 +673,7 @@ func (prog *Program) ZeroWire(conn *p2p.Conn, streaming *circuit.Streaming) ( *circuits.Wire, error) { if prog.zeroWire == nil { - wires, err := prog.walloc.AssignedWiresAndIDs(Value{ + wires, err := prog.walloc.AssignedWires(Value{ Const: true, Name: "{zero}", }, 1) @@ -727,7 +726,7 @@ func (prog *Program) OneWire(conn *p2p.Conn, streaming *circuit.Streaming) ( *circuits.Wire, error) { if prog.oneWire == nil { - wires, err := prog.walloc.AssignedWiresAndIDs(Value{ + wires, err := prog.walloc.AssignedWires(Value{ Const: true, Name: "{one}", }, 1) diff --git a/compiler/ssa/wire_allocator.go b/compiler/ssa/wire_allocator.go index 67ded381..0110b4dc 100644 --- a/compiler/ssa/wire_allocator.go +++ b/compiler/ssa/wire_allocator.go @@ -24,12 +24,25 @@ type WireAllocator struct { freeIDs map[types.Size][][]circuit.Wire hash [10240]*allocByValue nextWireID circuit.Wire - flHit int - flMiss int + flHdrs cacheStats + flWires cacheStats + flIDs cacheStats lookupCount int lookupFound int } +type cacheStats struct { + hit int + miss int +} + +func (cs cacheStats) String() string { + total := float64(cs.hit + cs.miss) + return fmt.Sprintf("hit=%v (%.2f%%), miss=%v (%.2f%%)", + cs.hit, float64(cs.hit)/total*100, + cs.miss, float64(cs.miss)/total*100) +} + type allocByValue struct { next *allocByValue key Value @@ -60,9 +73,11 @@ func (walloc *WireAllocator) hashCode(v Value) int { func (walloc *WireAllocator) newHeader(v Value) (ret *allocByValue) { if len(walloc.freeHdrs) == 0 { ret = new(allocByValue) + walloc.flHdrs.miss++ } else { ret = walloc.freeHdrs[len(walloc.freeHdrs)-1] walloc.freeHdrs = walloc.freeHdrs[:len(walloc.freeHdrs)-1] + walloc.flHdrs.hit++ } ret.key = v ret.base = circuits.UnassignedID @@ -76,10 +91,10 @@ func (walloc *WireAllocator) newWires(bits types.Size) ( if ok && len(fl) > 0 { result = fl[len(fl)-1] walloc.freeWires[bits] = fl[:len(fl)-1] - walloc.flHit++ + walloc.flWires.hit++ } else { result = walloc.calloc.Wires(bits) - walloc.flMiss++ + walloc.flWires.miss++ } return result } @@ -89,13 +104,13 @@ func (walloc *WireAllocator) newIDs(bits types.Size) (result []circuit.Wire) { if ok && len(fl) > 0 { result = fl[len(fl)-1] walloc.freeIDs[bits] = fl[:len(fl)-1] - walloc.flHit++ + walloc.flIDs.hit++ } else { result = make([]circuit.Wire, bits) for i := 0; i < int(bits); i++ { result[i] = circuits.UnassignedID } - walloc.flMiss++ + walloc.flIDs.miss++ } return result } @@ -171,8 +186,8 @@ func (walloc *WireAllocator) NextWireID() circuit.Wire { return ret } -// AssignedWires allocates assigned wires for the argument value. -func (walloc *WireAllocator) AssignedWires(v Value, bits types.Size) ( +// AssignedIDs allocates assigned wire IDs for the argument value. +func (walloc *WireAllocator) AssignedIDs(v Value, bits types.Size) ( []circuit.Wire, error) { if bits <= 0 { return nil, fmt.Errorf("size not set for value %v", v) @@ -202,8 +217,8 @@ func (walloc *WireAllocator) AssignedWires(v Value, bits types.Size) ( return alloc.ids, nil } -// AssignedWiresAndIDs allocates assigned wires for the argument value. -func (walloc *WireAllocator) AssignedWiresAndIDs(v Value, bits types.Size) ( +// AssignedWires allocates assigned wires for the argument value. +func (walloc *WireAllocator) AssignedWires(v Value, bits types.Size) ( []*circuits.Wire, error) { if bits <= 0 { return nil, fmt.Errorf("size not set for value %v", v) @@ -316,10 +331,10 @@ func (walloc *WireAllocator) SetWires(v Value, w []*circuits.Wire) { // Debug prints debugging information about the wire allocator. func (walloc *WireAllocator) Debug() { - total := float64(walloc.flHit + walloc.flMiss) - fmt.Printf("Wire freelist: hit=%v (%.2f%%), miss=%v (%.2f%%)\n", - walloc.flHit, float64(walloc.flHit)/total*100, - walloc.flMiss, float64(walloc.flMiss)/total*100) + fmt.Printf("WireAllocator:\n") + fmt.Printf(" hdrs : %s\n", walloc.flHdrs) + fmt.Printf(" wires: %s\n", walloc.flWires) + fmt.Printf(" ids : %s\n", walloc.flIDs) var sum, max int min := math.MaxInt From dbb9761537ac03e171fd8c9d25dc8c9febb5e51c Mon Sep 17 00:00:00 2001 From: Markku Rossi Date: Mon, 4 Sep 2023 14:07:42 +0200 Subject: [PATCH 19/19] Updated statistics. --- benchmarks.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/benchmarks.md b/benchmarks.md index b6b08efa..2654070d 100644 --- a/benchmarks.md +++ b/benchmarks.md @@ -588,6 +588,35 @@ Max permanent wires: 53913890, cached circuits: 25 #gates=830166294 (XOR=533177896 XNOR=28813441 AND=267575026 OR=496562 INV=103369 xor=561991337 !xor=268174957 levels=10548 width=1796) #w=853882864 ``` +Optimized streamer to use `circuit.Wire` instead of +`compiler/circuits/Wire` in wire cache: + +``` +┌──────────────┬────────────────┬─────────┬────────┐ +│ Op │ Time │ % │ Xfer │ +├──────────────┼────────────────┼─────────┼────────┤ +│ Compile │ 1.755362404s │ 2.64% │ │ +│ Init │ 2.79287ms │ 0.00% │ 0B │ +│ OT Init │ 13.525µs │ 0.00% │ 16kB │ +│ Peer Inputs │ 45.376796ms │ 0.07% │ 57kB │ +│ Stream │ 1m4.624303427s │ 97.28% │ 15GB │ +│ ├╴InstrInit │ 1.166489974s │ 1.81% │ │ +│ ├╴CircComp │ 18.17144ms │ 0.03% │ │ +│ ├╴StreamInit │ 1.886360054s │ 2.92% │ │ +│ ╰╴Garble │ 1m0.866348862s │ 94.18% │ │ +│ Result │ 225.299µs │ 0.00% │ 8kB │ +│ Total │ 1m6.428074321s │ │ 15GB │ +│ ├╴Sent │ │ 100.00% │ 15GB │ +│ ├╴Rcvd │ │ 0.00% │ 45kB │ +│ ╰╴Flcd │ │ │ 231284 │ +└──────────────┴────────────────┴─────────┴────────┘ +Max permanent wires: 53913890, cached circuits: 25 +#gates=830166294 (XOR=533177896 XNOR=28813441 AND=267575026 OR=496562 INV=103369 xor=561991337 !xor=268174957 levels=10548 width=1796) #w=853882864 + 66.59 real 69.15 user 6.69 sys + 3568140288 maximum resident set size + 4119990272 peak memory footprint +``` + Theoretical minimum single-threaded garbling time: ```