From ac19b6cbdfe47b6913a0c8bd0a538385f7abc560 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sat, 30 Oct 2021 20:25:29 -0700 Subject: [PATCH 01/16] wip interpreter --- acc/eval/interp.go | 150 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 acc/eval/interp.go diff --git a/acc/eval/interp.go b/acc/eval/interp.go new file mode 100644 index 0000000..2f24063 --- /dev/null +++ b/acc/eval/interp.go @@ -0,0 +1,150 @@ +// Package eval provides an interpreter for acc programs. +package eval + +import ( + "fmt" + "math/big" + + "github.com/mmcloughlin/addchain/acc/ir" +) + +type Interpreter struct { + state map[string]*big.Int +} + +func NewInterpreter() *Interpreter { + return &Interpreter{ + state: map[string]*big.Int{}, + } +} + +// Load the variable v. +func (i *Interpreter) Load(v string) (*big.Int, bool) { + x, ok := i.state[v] + return x, ok +} + +// Store x into the variable v. +func (i *Interpreter) Store(v string, x *big.Int) { + i.state[v] = x +} + +// Initialize the variable v to x. Errors if v is already defined. +func (i *Interpreter) Initialize(v string, x *big.Int) error { + if _, ok := i.Load(v); ok { + return fmt.Errorf("variable %q is already defined", v) + } + i.Store(v, x) + return nil +} + +// Execute the program p. +func (i *Interpreter) Execute(p *ir.Program) error { + for _, inst := range p.Instructions { + if err := i.instruction(inst); err != nil { + return err + } + } + return nil +} + +func (i *Interpreter) instruction(inst *ir.Instruction) error { + return nil +} + +// +// func (e *Evaluator) assignment(a ast.Assignment) error { +// lhs := e.dst(a.LHS) +// switch expr := a.RHS.(type) { +// case ast.Pow: +// x, err := e.operands(expr.X, expr.N) +// if err != nil { +// return err +// } +// lhs.Exp(x[0], x[1], e.m) +// case ast.Inv: +// x, err := e.operand(expr.X) +// if err != nil { +// return err +// } +// lhs.ModInverse(x, e.m) +// case ast.Mul: +// x, err := e.operands(expr.X, expr.Y) +// if err != nil { +// return err +// } +// lhs.Mul(x[0], x[1]) +// case ast.Neg: +// x, err := e.operand(expr.X) +// if err != nil { +// return err +// } +// lhs.Neg(x) +// case ast.Add: +// x, err := e.operands(expr.X, expr.Y) +// if err != nil { +// return err +// } +// lhs.Add(x[0], x[1]) +// case ast.Sub: +// x, err := e.operands(expr.X, expr.Y) +// if err != nil { +// return err +// } +// lhs.Sub(x[0], x[1]) +// case ast.Cond: +// x, err := e.operands(expr.X, expr.C) +// if err != nil { +// return err +// } +// if x[1].Sign() != 0 { +// lhs.Set(x[0]) +// } +// case ast.Variable, ast.Constant: +// x, err := e.operand(expr) +// if err != nil { +// return err +// } +// lhs.Set(x) +// default: +// return errutil.UnexpectedType(expr) +// } +// lhs.Mod(lhs, e.m) +// return nil +// } +// +// func (e Evaluator) dst(v ast.Variable) *big.Int { +// if x, ok := e.Load(v); ok { +// return x +// } +// x := new(big.Int) +// e.Store(v, x) +// return x +// } +// +// func (e *Evaluator) operands(operands ...ast.Operand) ([]*big.Int, error) { +// xs := make([]*big.Int, 0, len(operands)) +// for _, operand := range operands { +// x, err := e.operand(operand) +// if err != nil { +// return nil, err +// } +// xs = append(xs, x) +// } +// return xs, nil +// } +// +// func (e *Evaluator) operand(operand ast.Operand) (*big.Int, error) { +// switch op := operand.(type) { +// case ast.Variable: +// x, ok := e.Load(op) +// if !ok { +// return nil, xerrors.Errorf("variable %q is not defined", op) +// } +// return x, nil +// case ast.Constant: +// return new(big.Int).SetUint64(uint64(op)), nil +// default: +// return nil, errutil.UnexpectedType(op) +// } +// } From c1b2b80ceb8cf4ce74bdd78c8f5bfbc3572bfbea Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sat, 30 Oct 2021 22:00:13 -0700 Subject: [PATCH 02/16] first version of Interpreter --- acc/eval/interp.go | 155 +++++++++++++++++---------------------------- 1 file changed, 57 insertions(+), 98 deletions(-) diff --git a/acc/eval/interp.go b/acc/eval/interp.go index 2f24063..6473360 100644 --- a/acc/eval/interp.go +++ b/acc/eval/interp.go @@ -6,6 +6,7 @@ import ( "math/big" "github.com/mmcloughlin/addchain/acc/ir" + "github.com/mmcloughlin/addchain/internal/errutil" ) type Interpreter struct { @@ -18,13 +19,13 @@ func NewInterpreter() *Interpreter { } } -// Load the variable v. +// Load the named variable. func (i *Interpreter) Load(v string) (*big.Int, bool) { x, ok := i.state[v] return x, ok } -// Store x into the variable v. +// Store x into the named variable. func (i *Interpreter) Store(v string, x *big.Int) { i.state[v] = x } @@ -49,102 +50,60 @@ func (i *Interpreter) Execute(p *ir.Program) error { } func (i *Interpreter) instruction(inst *ir.Instruction) error { + output := i.output(inst.Output) + switch op := inst.Op.(type) { + case ir.Add: + x, err := i.operands(op.X, op.Y) + if err != nil { + return err + } + output.Add(x[0], x[1]) + case ir.Double: + x, err := i.operand(op.X) + if err != nil { + return err + } + output.Add(x, x) + case ir.Shift: + x, err := i.operand(op.X) + if err != nil { + return err + } + output.Lsh(x, op.S) + default: + return errutil.UnexpectedType(op) + } return nil } -// -// func (e *Evaluator) assignment(a ast.Assignment) error { -// lhs := e.dst(a.LHS) -// switch expr := a.RHS.(type) { -// case ast.Pow: -// x, err := e.operands(expr.X, expr.N) -// if err != nil { -// return err -// } -// lhs.Exp(x[0], x[1], e.m) -// case ast.Inv: -// x, err := e.operand(expr.X) -// if err != nil { -// return err -// } -// lhs.ModInverse(x, e.m) -// case ast.Mul: -// x, err := e.operands(expr.X, expr.Y) -// if err != nil { -// return err -// } -// lhs.Mul(x[0], x[1]) -// case ast.Neg: -// x, err := e.operand(expr.X) -// if err != nil { -// return err -// } -// lhs.Neg(x) -// case ast.Add: -// x, err := e.operands(expr.X, expr.Y) -// if err != nil { -// return err -// } -// lhs.Add(x[0], x[1]) -// case ast.Sub: -// x, err := e.operands(expr.X, expr.Y) -// if err != nil { -// return err -// } -// lhs.Sub(x[0], x[1]) -// case ast.Cond: -// x, err := e.operands(expr.X, expr.C) -// if err != nil { -// return err -// } -// if x[1].Sign() != 0 { -// lhs.Set(x[0]) -// } -// case ast.Variable, ast.Constant: -// x, err := e.operand(expr) -// if err != nil { -// return err -// } -// lhs.Set(x) -// default: -// return errutil.UnexpectedType(expr) -// } -// lhs.Mod(lhs, e.m) -// return nil -// } -// -// func (e Evaluator) dst(v ast.Variable) *big.Int { -// if x, ok := e.Load(v); ok { -// return x -// } -// x := new(big.Int) -// e.Store(v, x) -// return x -// } -// -// func (e *Evaluator) operands(operands ...ast.Operand) ([]*big.Int, error) { -// xs := make([]*big.Int, 0, len(operands)) -// for _, operand := range operands { -// x, err := e.operand(operand) -// if err != nil { -// return nil, err -// } -// xs = append(xs, x) -// } -// return xs, nil -// } -// -// func (e *Evaluator) operand(operand ast.Operand) (*big.Int, error) { -// switch op := operand.(type) { -// case ast.Variable: -// x, ok := e.Load(op) -// if !ok { -// return nil, xerrors.Errorf("variable %q is not defined", op) -// } -// return x, nil -// case ast.Constant: -// return new(big.Int).SetUint64(uint64(op)), nil -// default: -// return nil, errutil.UnexpectedType(op) -// } -// } +func (i *Interpreter) output(operand *ir.Operand) *big.Int { + if x, ok := i.Load(operand.Identifier); ok { + return x + } + x := new(big.Int) + i.Store(operand.Identifier, x) + return x +} + +func (i *Interpreter) operands(operands ...*ir.Operand) ([]*big.Int, error) { + xs := make([]*big.Int, 0, len(operands)) + for _, operand := range operands { + x, err := i.operand(operand) + if err != nil { + return nil, err + } + xs = append(xs, x) + } + return xs, nil +} + +func (i *Interpreter) operand(operand *ir.Operand) (*big.Int, error) { + if operand.Identifier == "" { + return nil, fmt.Errorf("operand %s missing identifier", operand) + } + x, ok := i.Load(operand.Identifier) + if !ok { + return nil, fmt.Errorf("operand %q is not defined", operand) + } + return x, nil +} From 78789449e34210acda06081c8f10f9b65287f574 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 11:12:03 -0700 Subject: [PATCH 03/16] interp unit test --- acc/eval/interp_test.go | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 acc/eval/interp_test.go diff --git a/acc/eval/interp_test.go b/acc/eval/interp_test.go new file mode 100644 index 0000000..1ba1a6b --- /dev/null +++ b/acc/eval/interp_test.go @@ -0,0 +1,55 @@ +package eval + +import ( + "math/big" + "testing" + + "github.com/mmcloughlin/addchain/acc/ir" + "github.com/mmcloughlin/addchain/internal/bigint" +) + +func TestInterpreter(t *testing.T) { + // Construct a test input program with named operands. The result should be + // 0b1111. + a := ir.NewOperand("a", 0) + b := ir.NewOperand("b", 1) + c := ir.NewOperand("c", 2) + d := ir.NewOperand("d", 4) + e := ir.NewOperand("e", 5) + p := &ir.Program{ + Instructions: []*ir.Instruction{ + {Output: b, Op: ir.Double{X: a}}, + {Output: c, Op: ir.Add{X: a, Y: b}}, + {Output: d, Op: ir.Shift{X: c, S: 2}}, + {Output: e, Op: ir.Add{X: d, Y: c}}, + }, + } + + t.Logf("program:\n%s", p) + + // Evaluate it. + i := NewInterpreter() + i.Store("a", big.NewInt(1)) + err := i.Execute(p) + if err != nil { + t.Fatal(err) + } + + // Check variable values. + expect := map[string]int64{ + "a": 1, + "b": 2, + "c": 3, + "d": 12, + "e": 15, + } + for v, x := range expect { + got, ok := i.Load(v) + if !ok { + t.Fatalf("missing value for %q", v) + } + if !bigint.EqualInt64(got, x) { + t.Errorf("got %s=%v; expect %v", v, got, x) + } + } +} From 66b8acf555cdf73da12bff6a797c98866d6fdf28 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 11:47:29 -0700 Subject: [PATCH 04/16] pass: failing allocator test for aliasing --- acc/pass/alloc_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/acc/pass/alloc_test.go b/acc/pass/alloc_test.go index e970039..05f7877 100644 --- a/acc/pass/alloc_test.go +++ b/acc/pass/alloc_test.go @@ -1,11 +1,13 @@ package pass import ( + "math/rand" "reflect" "strconv" "testing" "github.com/mmcloughlin/addchain/acc/ir" + "github.com/mmcloughlin/addchain/internal/test" ) func TestAllocator(t *testing.T) { @@ -76,3 +78,51 @@ func TestAllocator(t *testing.T) { t.Errorf("unexpected output name: got %q expect %q", p.Output().Identifier, a.Output) } } + +func TestAllocatorAlias(t *testing.T) { + test.Repeat(t, func(t *testing.T) bool { + // Generate a random program. + p := &ir.Program{} + for i := 1; i < 32; i++ { + p.AddInstruction(&ir.Instruction{ + Output: ir.Index(i), + Op: ir.Add{ + X: ir.Index(i - 1), // ensure every index is used + Y: ir.Index(rand.Intn(i)), + }, + }) + } + + // Execute allocation pass. + t.Logf("pre:\n%s", p) + + a := Allocator{ + Input: "in", + Output: "out", + Format: "tmp%d", + } + if err := a.Execute(p); err != nil { + t.Fatal(err) + } + + t.Logf("post:\n%s", p) + + // Verify the input and output are not live at the same time. + live := map[string]bool{} + for i := len(p.Instructions) - 1; i >= 0; i-- { + inst := p.Instructions[i] + + // Update live set. + delete(live, inst.Output.Identifier) + for _, input := range inst.Op.Inputs() { + live[input.Identifier] = true + } + + if live[a.Input] && live[a.Output] { + t.Fatalf("instruction %d: input %q and output %q both live", i, a.Input, a.Output) + } + } + + return false + }) +} From 08cd032bfafdc641482778d4ea0f4f80d92b0782 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 12:12:09 -0700 Subject: [PATCH 05/16] internal/examples/fp25519: aliasing test --- internal/examples/fp25519/fp_test.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/internal/examples/fp25519/fp_test.go b/internal/examples/fp25519/fp_test.go index b414335..a95b12d 100644 --- a/internal/examples/fp25519/fp_test.go +++ b/internal/examples/fp25519/fp_test.go @@ -6,6 +6,13 @@ import ( "testing" ) +func Trials() int { + if testing.Short() { + return 1 << 4 + } + return 1 << 12 +} + func RandElt(t *testing.T) *Elt { t.Helper() one := new(big.Int).SetInt64(1) @@ -19,8 +26,7 @@ func RandElt(t *testing.T) *Elt { } func TestInv(t *testing.T) { - const trials = 1 << 12 - for trial := 0; trial < trials; trial++ { + for trial := 0; trial < Trials(); trial++ { x := RandElt(t) got := new(Elt).Inv(x) expect := new(big.Int).ModInverse(x.Int(), p) @@ -29,3 +35,14 @@ func TestInv(t *testing.T) { } } } + +func TestInvAlias(t *testing.T) { + for trial := 0; trial < Trials(); trial++ { + x := RandElt(t) + expect := new(Elt).Inv(x) // non-aliased + x.Inv(x) // aliased + if x.Int().Cmp(expect.Int()) != 0 { + t.FailNow() + } + } +} From 7948405a12c3ea1de2b00b06ec50c7b77919a812 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 13:50:46 -0700 Subject: [PATCH 06/16] acc/rand: generator for acc programs --- acc/rand/rand.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 acc/rand/rand.go diff --git a/acc/rand/rand.go b/acc/rand/rand.go new file mode 100644 index 0000000..a010e17 --- /dev/null +++ b/acc/rand/rand.go @@ -0,0 +1,38 @@ +package rand + +import ( + "fmt" + "math/rand" + + "github.com/mmcloughlin/addchain/acc/ir" +) + +// Generator can generate random addition chain programs. +type Generator interface { + GenerateProgram() (*ir.Program, error) + String() string +} + +// AddsGenerator generates a random program with N adds, in such a way that +// every index is used. +type AddsGenerator struct { + N int +} + +func (a AddsGenerator) String() string { + return fmt.Sprintf("random_adds(%d)", a.N) +} + +func (a AddsGenerator) GenerateProgram() (*ir.Program, error) { + p := &ir.Program{} + for i := 1; i <= a.N; i++ { + p.AddInstruction(&ir.Instruction{ + Output: ir.Index(i), + Op: ir.Add{ + X: ir.Index(i - 1), // ensure every index is used + Y: ir.Index(rand.Intn(i)), + }, + }) + } + return p, nil +} From 381883b3cf3efc0121b1266358724f12c2bdc948 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 14:08:12 -0700 Subject: [PATCH 07/16] acc/pass: allocator test that interprets programs --- acc/pass/alloc_test.go | 73 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/acc/pass/alloc_test.go b/acc/pass/alloc_test.go index 05f7877..15b991c 100644 --- a/acc/pass/alloc_test.go +++ b/acc/pass/alloc_test.go @@ -1,12 +1,14 @@ package pass import ( - "math/rand" "reflect" "strconv" "testing" + "github.com/mmcloughlin/addchain/acc/eval" "github.com/mmcloughlin/addchain/acc/ir" + "github.com/mmcloughlin/addchain/acc/rand" + "github.com/mmcloughlin/addchain/internal/bigint" "github.com/mmcloughlin/addchain/internal/test" ) @@ -80,17 +82,12 @@ func TestAllocator(t *testing.T) { } func TestAllocatorAlias(t *testing.T) { + r := rand.AddsGenerator{N: 32} test.Repeat(t, func(t *testing.T) bool { // Generate a random program. - p := &ir.Program{} - for i := 1; i < 32; i++ { - p.AddInstruction(&ir.Instruction{ - Output: ir.Index(i), - Op: ir.Add{ - X: ir.Index(i - 1), // ensure every index is used - Y: ir.Index(rand.Intn(i)), - }, - }) + p, err := r.GenerateProgram() + if err != nil { + t.Fatal(err) } // Execute allocation pass. @@ -126,3 +123,59 @@ func TestAllocatorAlias(t *testing.T) { return false }) } + +func TestAllocatorExec(t *testing.T) { + r := rand.AddsGenerator{N: 64} + test.Repeat(t, func(t *testing.T) bool { + // Generate a random program. + p, err := r.GenerateProgram() + if err != nil { + t.Fatal(err) + } + + // Execute allocation pass. + t.Logf("pre:\n%s", p) + + a := Allocator{ + Input: "x", + Output: "z", + Format: "t%d", + } + if err := a.Execute(p); err != nil { + t.Fatal(err) + } + + t.Logf("post:\n%s", p) + + // Execute with interpreter. Deliberately setup the input and output to + // be aliased. + i := eval.NewInterpreter() + x := bigint.One() + i.Store(a.Input, x) + i.Store(a.Output, x) + + if err := i.Execute(p); err != nil { + t.Fatal(err) + } + + // Output should be the same as the target of the addition chain. + output, ok := i.Load(a.Output) + if !ok { + t.Fatalf("missing output variable %q", a.Output) + } + + if err := Eval(p); err != nil { + t.Fatal(err) + } + + expect := p.Chain.End() + + if !bigint.Equal(output, expect) { + t.Logf(" got = %#x", output) + t.Logf("expect = %#x", expect) + t.Fatal("mismatch") + } + + return true + }) +} From 534475fc8230caf51fd518c94d666382a019ab4d Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 17:02:32 -0700 Subject: [PATCH 08/16] acc/pass: Allocator can handle aliased input/output --- acc/pass/alloc.go | 61 +++++++++++++++++++++++------------------- acc/pass/alloc_test.go | 44 +++++++++++++++--------------- 2 files changed, 54 insertions(+), 51 deletions(-) diff --git a/acc/pass/alloc.go b/acc/pass/alloc.go index 5c8e97d..77fe05c 100644 --- a/acc/pass/alloc.go +++ b/acc/pass/alloc.go @@ -31,18 +31,17 @@ func (a Allocator) Execute(p *ir.Program) error { return err } - // Initialize allocation. This maps operand index to variable index. The - // inidicies 0 and 1 are special, reserved for the input and output - // respectively. Any indicies above that are temporaries. - out := p.Output() - allocation := map[int]int{ - 0: 0, - out.Index: 1, - } - n := 2 + // Keep an allocation map from operand index to variable index. + allocation := map[int]int{} - // Keep a heap of available indicies. Initially none. + // Keep a heap of available variables, and a total variable count. available := heap.NewMinInts() + n := 0 + + // Assign a variable for the output. + out := p.Output() + allocation[out.Index] = 0 + n = 1 // Process instructions in reverse. for i := len(p.Instructions) - 1; i >= 0; i-- { @@ -72,27 +71,33 @@ func (a Allocator) Execute(p *ir.Program) error { } } - // Record allocation. - for _, op := range p.Operands { - op.Identifier = a.name(allocation[op.Index]) + // Assign names to the operands. + lastinputread := 0 + for _, inst := range p.Instructions { + for _, input := range inst.Op.Inputs() { + if input.Index == 0 { + lastinputread = inst.Output.Index + } + } } - temps := []string{} - for i := 2; i < n; i++ { - temps = append(temps, a.name(i)) + name := map[int]string{} + for _, op := range p.Operands { + v := allocation[op.Index] + _, ok := name[v] + switch { + case op.Index == 0: + op.Identifier = a.Input + case v == 0 && op.Index >= lastinputread: + op.Identifier = a.Output + case !ok: + name[v] = fmt.Sprintf(a.Format, len(p.Temporaries)) + p.Temporaries = append(p.Temporaries, name[v]) + fallthrough + default: + op.Identifier = name[v] + } } - p.Temporaries = temps return nil } - -func (a Allocator) name(v int) string { - switch v { - case 0: - return a.Input - case 1: - return a.Output - default: - return fmt.Sprintf(a.Format, v-2) - } -} diff --git a/acc/pass/alloc_test.go b/acc/pass/alloc_test.go index 15b991c..141dcd3 100644 --- a/acc/pass/alloc_test.go +++ b/acc/pass/alloc_test.go @@ -41,18 +41,12 @@ func TestAllocator(t *testing.T) { } // Execute allocation pass. - t.Logf("pre:\n%s", p) - a := Allocator{ Input: "in", Output: "out", Format: "tmp%d", } - if err := a.Execute(p); err != nil { - t.Fatal(err) - } - - t.Logf("post:\n%s", p) + Allocate(t, a, p) // Every operand should have a name. for _, operand := range p.Operands { @@ -62,7 +56,7 @@ func TestAllocator(t *testing.T) { } // We expect to have n-1 temporaries. Note we do not expect n, since the output - // variable will be used as a temporary. + // variable should be used as a temporary. expect := make([]string, n-1) for i := 0; i < n-1; i++ { expect[i] = "tmp" + strconv.Itoa(i) @@ -91,18 +85,12 @@ func TestAllocatorAlias(t *testing.T) { } // Execute allocation pass. - t.Logf("pre:\n%s", p) - a := Allocator{ Input: "in", Output: "out", Format: "tmp%d", } - if err := a.Execute(p); err != nil { - t.Fatal(err) - } - - t.Logf("post:\n%s", p) + Allocate(t, a, p) // Verify the input and output are not live at the same time. live := map[string]bool{} @@ -120,7 +108,7 @@ func TestAllocatorAlias(t *testing.T) { } } - return false + return true }) } @@ -134,18 +122,12 @@ func TestAllocatorExec(t *testing.T) { } // Execute allocation pass. - t.Logf("pre:\n%s", p) - a := Allocator{ Input: "x", Output: "z", Format: "t%d", } - if err := a.Execute(p); err != nil { - t.Fatal(err) - } - - t.Logf("post:\n%s", p) + Allocate(t, a, p) // Execute with interpreter. Deliberately setup the input and output to // be aliased. @@ -179,3 +161,19 @@ func TestAllocatorExec(t *testing.T) { return true }) } + +func Allocate(t *testing.T, a Allocator, p *ir.Program) { + t.Helper() + + t.Logf("pre alloc:\n%s", p) + + if err := a.Execute(p); err != nil { + t.Fatal(err) + } + + for _, op := range p.Operands { + t.Logf("alloc %d: %s", op.Index, op.Identifier) + } + + t.Logf("post alloc:\n%s", p) +} From 438f73cc5d0360cbd7fa09f91a883b91878e42a5 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 17:06:54 -0700 Subject: [PATCH 09/16] all: regenerate --- doc/gen.md | 76 ++++++++++++------------ internal/examples/fp25519/01-listing.out | 50 ++++++++-------- internal/examples/fp25519/inv.go | 26 ++++---- 3 files changed, 78 insertions(+), 74 deletions(-) diff --git a/doc/gen.md b/doc/gen.md index f19b047..9bedf43 100644 --- a/doc/gen.md +++ b/doc/gen.md @@ -29,31 +29,31 @@ addchain gen inv.acc ``` Output: ``` -tmp t0 t1 t2 -double z x -add z x z -shift t0 z 2 -add t0 z t0 -shift t1 t0 4 -add t0 t0 t1 -shift t0 t0 2 -add t0 z t0 -shift t1 t0 10 -add t1 t0 t1 -shift t1 t1 10 -add t1 t0 t1 -shift t2 t1 30 -add t1 t1 t2 -shift t2 t1 60 -add t1 t1 t2 -shift t2 t1 120 -add t1 t1 t2 -shift t1 t1 10 -add t0 t0 t1 -shift t0 t0 2 -add t0 x t0 -shift t0 t0 3 -add z z t0 +tmp t0 t1 t2 t3 t4 +double t1 x +add t1 x t1 +shift t2 t1 2 +add t2 t1 t2 +shift t0 t2 4 +add t2 t2 t0 +shift t2 t2 2 +add t2 t1 t2 +shift t0 t2 10 +add t0 t2 t0 +shift t0 t0 10 +add t0 t2 t0 +shift t3 t0 30 +add t0 t0 t3 +shift t3 t0 60 +add t0 t0 t3 +shift t3 t0 120 +add t0 t0 t3 +shift t0 t0 10 +add t2 t2 t0 +shift t2 t2 2 +add t4 x t2 +shift t4 t4 3 +add z t1 t4 ``` This listing is intended to be a simple text format that could directly be @@ -162,22 +162,24 @@ func (z *Elt) Inv(x *Elt) *Elt { t0 = new(Elt) t1 = new(Elt) t2 = new(Elt) + t3 = new(Elt) + t4 = new(Elt) ) - // Step 1: z = x^0x2 - z.Sqr(x) + // Step 1: t3 = x^0x2 + t3.Sqr(x) - // Step 2: z = x^0x3 - z.Mul(x, z) + // Step 2: t3 = x^0x3 + t3.Mul(x, t3) // Step 4: t0 = x^0xc - t0.Sqr(z) + t0.Sqr(t3) for s := 1; s < 2; s++ { t0.Sqr(t0) } // Step 5: t0 = x^0xf - t0.Mul(z, t0) + t0.Mul(t3, t0) // Step 9: t1 = x^0xf0 t1.Sqr(t0) @@ -194,7 +196,7 @@ func (z *Elt) Inv(x *Elt) *Elt { } // Step 13: t0 = x^0x3ff - t0.Mul(z, t0) + t0.Mul(t3, t0) // Step 23: t1 = x^0xffc00 t1.Sqr(t0) @@ -253,16 +255,16 @@ func (z *Elt) Inv(x *Elt) *Elt { t0.Sqr(t0) } - // Step 262: t0 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd - t0.Mul(x, t0) + // Step 262: t4 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd + t4.Mul(x, t0) - // Step 265: t0 = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8 + // Step 265: t4 = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8 for s := 0; s < 3; s++ { - t0.Sqr(t0) + t4.Sqr(t4) } // Step 266: z = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb - z.Mul(z, t0) + z.Mul(t3, t4) return z } diff --git a/internal/examples/fp25519/01-listing.out b/internal/examples/fp25519/01-listing.out index 91b2ad0..276e91c 100644 --- a/internal/examples/fp25519/01-listing.out +++ b/internal/examples/fp25519/01-listing.out @@ -1,25 +1,25 @@ -tmp t0 t1 t2 -double z x -add z x z -shift t0 z 2 -add t0 z t0 -shift t1 t0 4 -add t0 t0 t1 -shift t0 t0 2 -add t0 z t0 -shift t1 t0 10 -add t1 t0 t1 -shift t1 t1 10 -add t1 t0 t1 -shift t2 t1 30 -add t1 t1 t2 -shift t2 t1 60 -add t1 t1 t2 -shift t2 t1 120 -add t1 t1 t2 -shift t1 t1 10 -add t0 t0 t1 -shift t0 t0 2 -add t0 x t0 -shift t0 t0 3 -add z z t0 +tmp t0 t1 t2 t3 t4 +double t1 x +add t1 x t1 +shift t2 t1 2 +add t2 t1 t2 +shift t0 t2 4 +add t2 t2 t0 +shift t2 t2 2 +add t2 t1 t2 +shift t0 t2 10 +add t0 t2 t0 +shift t0 t0 10 +add t0 t2 t0 +shift t3 t0 30 +add t0 t0 t3 +shift t3 t0 60 +add t0 t0 t3 +shift t3 t0 120 +add t0 t0 t3 +shift t0 t0 10 +add t2 t2 t0 +shift t2 t2 2 +add t4 x t2 +shift t4 t4 3 +add z t1 t4 diff --git a/internal/examples/fp25519/inv.go b/internal/examples/fp25519/inv.go index ef8120e..a3cfa85 100644 --- a/internal/examples/fp25519/inv.go +++ b/internal/examples/fp25519/inv.go @@ -30,22 +30,24 @@ func (z *Elt) Inv(x *Elt) *Elt { t0 = new(Elt) t1 = new(Elt) t2 = new(Elt) + t3 = new(Elt) + t4 = new(Elt) ) - // Step 1: z = x^0x2 - z.Sqr(x) + // Step 1: t3 = x^0x2 + t3.Sqr(x) - // Step 2: z = x^0x3 - z.Mul(x, z) + // Step 2: t3 = x^0x3 + t3.Mul(x, t3) // Step 4: t0 = x^0xc - t0.Sqr(z) + t0.Sqr(t3) for s := 1; s < 2; s++ { t0.Sqr(t0) } // Step 5: t0 = x^0xf - t0.Mul(z, t0) + t0.Mul(t3, t0) // Step 9: t1 = x^0xf0 t1.Sqr(t0) @@ -62,7 +64,7 @@ func (z *Elt) Inv(x *Elt) *Elt { } // Step 13: t0 = x^0x3ff - t0.Mul(z, t0) + t0.Mul(t3, t0) // Step 23: t1 = x^0xffc00 t1.Sqr(t0) @@ -121,16 +123,16 @@ func (z *Elt) Inv(x *Elt) *Elt { t0.Sqr(t0) } - // Step 262: t0 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd - t0.Mul(x, t0) + // Step 262: t4 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd + t4.Mul(x, t0) - // Step 265: t0 = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8 + // Step 265: t4 = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8 for s := 0; s < 3; s++ { - t0.Sqr(t0) + t4.Sqr(t4) } // Step 266: z = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb - z.Mul(z, t0) + z.Mul(t3, t4) return z } From 572ac75d9e56c8f4fefbe7d3fdda083247b35450 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 20:11:46 -0700 Subject: [PATCH 10/16] acc/pass: fix allocator non-determinism --- acc/ir/ir.go | 1 + acc/pass/alloc.go | 7 +- acc/pass/pass.go | 23 +++ acc/pass/pass_test.go | 19 ++ doc/gen.md | 186 +++++++++---------- internal/examples/fp25519/01-listing.out | 42 ++--- internal/examples/fp25519/inv.go | 100 +++++----- internal/gen/testdata/builtin/listing.golden | 44 ++--- 8 files changed, 233 insertions(+), 189 deletions(-) diff --git a/acc/ir/ir.go b/acc/ir/ir.go index 5f54fec..a0b3d2e 100644 --- a/acc/ir/ir.go +++ b/acc/ir/ir.go @@ -13,6 +13,7 @@ type Program struct { Instructions []*Instruction // Pass/analysis results. + Indexes []int Operands map[int]*Operand ReadCount map[int]int Program addchain.Program diff --git a/acc/pass/alloc.go b/acc/pass/alloc.go index 77fe05c..4f79b94 100644 --- a/acc/pass/alloc.go +++ b/acc/pass/alloc.go @@ -26,8 +26,8 @@ type Allocator struct { // Execute performs temporary variable allocation. func (a Allocator) Execute(p *ir.Program) error { - // Canonicalize operands and delete all names. - if err := Exec(p, Func(CanonicalizeOperands), Func(ClearNames)); err != nil { + // Canonicalize operands, collect unique indexes, and delete all names. + if err := Exec(p, Func(CanonicalizeOperands), Func(Indexes), Func(ClearNames)); err != nil { return err } @@ -82,7 +82,8 @@ func (a Allocator) Execute(p *ir.Program) error { } name := map[int]string{} - for _, op := range p.Operands { + for _, index := range p.Indexes { + op := p.Operands[index] v := allocation[op.Index] _, ok := name[v] switch { diff --git a/acc/pass/pass.go b/acc/pass/pass.go index a9f1ee2..11c3a8a 100644 --- a/acc/pass/pass.go +++ b/acc/pass/pass.go @@ -3,6 +3,7 @@ package pass import ( "fmt" + "sort" "github.com/mmcloughlin/addchain/acc/ir" "github.com/mmcloughlin/addchain/internal/errutil" @@ -100,6 +101,28 @@ func CanonicalizeOperands(p *ir.Program) error { return nil } +// Indexes computes the sorted list of unique operand indexes that appear in the +// program. Populates the Indexes field of the program. +func Indexes(p *ir.Program) error { + if p.Indexes != nil { + return nil + } + + // Canonicalize operands to populate the Operands field. + if err := CanonicalizeOperands(p); err != nil { + return err + } + + // Gather and sort indexes. + for index := range p.Operands { + p.Indexes = append(p.Indexes, index) + } + + sort.Ints(p.Indexes) + + return nil +} + // ReadCounts computes how many times each index is read in the program. This // populates the ReadCount field of the program. func ReadCounts(p *ir.Program) error { diff --git a/acc/pass/pass_test.go b/acc/pass/pass_test.go index a9c99fa..95439d9 100644 --- a/acc/pass/pass_test.go +++ b/acc/pass/pass_test.go @@ -91,6 +91,25 @@ func TestCanonicalizeOperandsIdentifierConflict(t *testing.T) { assert.ErrorContains(t, err, "conflict") } +func TestIndexes(t *testing.T) { + p := &ir.Program{ + Instructions: []*ir.Instruction{ + {Output: ir.Index(2), Op: ir.Shift{X: ir.Index(0), S: 2}}, + {Output: ir.Index(5), Op: ir.Shift{X: ir.Index(2), S: 3}}, + {Output: ir.Index(9), Op: ir.Shift{X: ir.Index(5), S: 4}}, + }, + } + + if err := Indexes(p); err != nil { + t.Fatal(err) + } + + expect := []int{0, 2, 5, 9} + if !reflect.DeepEqual(expect, p.Indexes) { + t.FailNow() + } +} + func TestReadCounts(t *testing.T) { p := &ir.Program{ Instructions: []*ir.Instruction{ diff --git a/doc/gen.md b/doc/gen.md index 9bedf43..e460f49 100644 --- a/doc/gen.md +++ b/doc/gen.md @@ -30,30 +30,30 @@ addchain gen inv.acc Output: ``` tmp t0 t1 t2 t3 t4 -double t1 x -add t1 x t1 -shift t2 t1 2 +double t0 x +add t0 x t0 +shift t1 t0 2 +add t1 t0 t1 +shift t2 t1 4 +add t1 t1 t2 +shift t1 t1 2 +add t1 t0 t1 +shift t2 t1 10 add t2 t1 t2 -shift t0 t2 4 -add t2 t2 t0 -shift t2 t2 2 +shift t2 t2 10 add t2 t1 t2 -shift t0 t2 10 -add t0 t2 t0 -shift t0 t0 10 -add t0 t2 t0 -shift t3 t0 30 -add t0 t0 t3 -shift t3 t0 60 -add t0 t0 t3 -shift t3 t0 120 -add t0 t0 t3 -shift t0 t0 10 -add t2 t2 t0 -shift t2 t2 2 -add t4 x t2 +shift t3 t2 30 +add t2 t2 t3 +shift t3 t2 60 +add t2 t2 t3 +shift t3 t2 120 +add t2 t2 t3 +shift t2 t2 10 +add t1 t1 t2 +shift t1 t1 2 +add t4 x t1 shift t4 t4 3 -add z t1 t4 +add z t0 t4 ``` This listing is intended to be a simple text format that could directly be @@ -166,97 +166,97 @@ func (z *Elt) Inv(x *Elt) *Elt { t4 = new(Elt) ) - // Step 1: t3 = x^0x2 - t3.Sqr(x) + // Step 1: t0 = x^0x2 + t0.Sqr(x) - // Step 2: t3 = x^0x3 - t3.Mul(x, t3) + // Step 2: t0 = x^0x3 + t0.Mul(x, t0) - // Step 4: t0 = x^0xc - t0.Sqr(t3) + // Step 4: t1 = x^0xc + t1.Sqr(t0) for s := 1; s < 2; s++ { - t0.Sqr(t0) + t1.Sqr(t1) } - // Step 5: t0 = x^0xf - t0.Mul(t3, t0) + // Step 5: t1 = x^0xf + t1.Mul(t0, t1) - // Step 9: t1 = x^0xf0 - t1.Sqr(t0) + // Step 9: t2 = x^0xf0 + t2.Sqr(t1) for s := 1; s < 4; s++ { - t1.Sqr(t1) + t2.Sqr(t2) } - // Step 10: t0 = x^0xff - t0.Mul(t0, t1) + // Step 10: t1 = x^0xff + t1.Mul(t1, t2) - // Step 12: t0 = x^0x3fc + // Step 12: t1 = x^0x3fc for s := 0; s < 2; s++ { - t0.Sqr(t0) + t1.Sqr(t1) } - // Step 13: t0 = x^0x3ff - t0.Mul(t3, t0) + // Step 13: t1 = x^0x3ff + t1.Mul(t0, t1) - // Step 23: t1 = x^0xffc00 - t1.Sqr(t0) + // Step 23: t2 = x^0xffc00 + t2.Sqr(t1) for s := 1; s < 10; s++ { - t1.Sqr(t1) + t2.Sqr(t2) } - // Step 24: t1 = x^0xfffff - t1.Mul(t0, t1) + // Step 24: t2 = x^0xfffff + t2.Mul(t1, t2) - // Step 34: t1 = x^0x3ffffc00 + // Step 34: t2 = x^0x3ffffc00 for s := 0; s < 10; s++ { - t1.Sqr(t1) + t2.Sqr(t2) } - // Step 35: t1 = x^0x3fffffff - t1.Mul(t0, t1) + // Step 35: t2 = x^0x3fffffff + t2.Mul(t1, t2) - // Step 65: t2 = x^0xfffffffc0000000 - t2.Sqr(t1) + // Step 65: t3 = x^0xfffffffc0000000 + t3.Sqr(t2) for s := 1; s < 30; s++ { - t2.Sqr(t2) + t3.Sqr(t3) } - // Step 66: t1 = x^0xfffffffffffffff - t1.Mul(t1, t2) + // Step 66: t2 = x^0xfffffffffffffff + t2.Mul(t2, t3) - // Step 126: t2 = x^0xfffffffffffffff000000000000000 - t2.Sqr(t1) + // Step 126: t3 = x^0xfffffffffffffff000000000000000 + t3.Sqr(t2) for s := 1; s < 60; s++ { - t2.Sqr(t2) + t3.Sqr(t3) } - // Step 127: t1 = x^0xffffffffffffffffffffffffffffff - t1.Mul(t1, t2) + // Step 127: t2 = x^0xffffffffffffffffffffffffffffff + t2.Mul(t2, t3) - // Step 247: t2 = x^0xffffffffffffffffffffffffffffff000000000000000000000000000000 - t2.Sqr(t1) + // Step 247: t3 = x^0xffffffffffffffffffffffffffffff000000000000000000000000000000 + t3.Sqr(t2) for s := 1; s < 120; s++ { - t2.Sqr(t2) + t3.Sqr(t3) } - // Step 248: t1 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - t1.Mul(t1, t2) + // Step 248: t2 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + t2.Mul(t2, t3) - // Step 258: t1 = x^0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00 + // Step 258: t2 = x^0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00 for s := 0; s < 10; s++ { - t1.Sqr(t1) + t2.Sqr(t2) } - // Step 259: t0 = x^0x3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - t0.Mul(t0, t1) + // Step 259: t1 = x^0x3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + t1.Mul(t1, t2) - // Step 261: t0 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc + // Step 261: t1 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc for s := 0; s < 2; s++ { - t0.Sqr(t0) + t1.Sqr(t1) } // Step 262: t4 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd - t4.Mul(x, t0) + t4.Mul(x, t1) // Step 265: t4 = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8 for s := 0; s < 3; s++ { @@ -264,7 +264,7 @@ func (z *Elt) Inv(x *Elt) *Elt { } // Step 266: z = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb - z.Mul(t3, t4) + z.Mul(t0, t4) return z } @@ -650,31 +650,31 @@ return (x250 << 2 + 1) << 3 + _11 Output of listing template ``` -tmp t0 t1 t2 -double z x -add z x z -shift t0 z 2 -add t0 z t0 -shift t1 t0 4 -add t0 t0 t1 -shift t0 t0 2 -add t0 z t0 -shift t1 t0 10 -add t1 t0 t1 -shift t1 t1 10 +tmp t0 t1 t2 t3 t4 +double t0 x +add t0 x t0 +shift t1 t0 2 add t1 t0 t1 -shift t2 t1 30 -add t1 t1 t2 -shift t2 t1 60 +shift t2 t1 4 add t1 t1 t2 -shift t2 t1 120 +shift t1 t1 2 +add t1 t0 t1 +shift t2 t1 10 +add t2 t1 t2 +shift t2 t2 10 +add t2 t1 t2 +shift t3 t2 30 +add t2 t2 t3 +shift t3 t2 60 +add t2 t2 t3 +shift t3 t2 120 +add t2 t2 t3 +shift t2 t2 10 add t1 t1 t2 -shift t1 t1 10 -add t0 t0 t1 -shift t0 t0 2 -add t0 x t0 -shift t0 t0 3 -add z z t0 +shift t1 t1 2 +add t4 x t1 +shift t4 t4 3 +add z t0 t4 ``` diff --git a/internal/examples/fp25519/01-listing.out b/internal/examples/fp25519/01-listing.out index 276e91c..6eb7342 100644 --- a/internal/examples/fp25519/01-listing.out +++ b/internal/examples/fp25519/01-listing.out @@ -1,25 +1,25 @@ tmp t0 t1 t2 t3 t4 -double t1 x -add t1 x t1 -shift t2 t1 2 +double t0 x +add t0 x t0 +shift t1 t0 2 +add t1 t0 t1 +shift t2 t1 4 +add t1 t1 t2 +shift t1 t1 2 +add t1 t0 t1 +shift t2 t1 10 add t2 t1 t2 -shift t0 t2 4 -add t2 t2 t0 -shift t2 t2 2 +shift t2 t2 10 add t2 t1 t2 -shift t0 t2 10 -add t0 t2 t0 -shift t0 t0 10 -add t0 t2 t0 -shift t3 t0 30 -add t0 t0 t3 -shift t3 t0 60 -add t0 t0 t3 -shift t3 t0 120 -add t0 t0 t3 -shift t0 t0 10 -add t2 t2 t0 -shift t2 t2 2 -add t4 x t2 +shift t3 t2 30 +add t2 t2 t3 +shift t3 t2 60 +add t2 t2 t3 +shift t3 t2 120 +add t2 t2 t3 +shift t2 t2 10 +add t1 t1 t2 +shift t1 t1 2 +add t4 x t1 shift t4 t4 3 -add z t1 t4 +add z t0 t4 diff --git a/internal/examples/fp25519/inv.go b/internal/examples/fp25519/inv.go index a3cfa85..6a6b6d5 100644 --- a/internal/examples/fp25519/inv.go +++ b/internal/examples/fp25519/inv.go @@ -34,97 +34,97 @@ func (z *Elt) Inv(x *Elt) *Elt { t4 = new(Elt) ) - // Step 1: t3 = x^0x2 - t3.Sqr(x) + // Step 1: t0 = x^0x2 + t0.Sqr(x) - // Step 2: t3 = x^0x3 - t3.Mul(x, t3) + // Step 2: t0 = x^0x3 + t0.Mul(x, t0) - // Step 4: t0 = x^0xc - t0.Sqr(t3) + // Step 4: t1 = x^0xc + t1.Sqr(t0) for s := 1; s < 2; s++ { - t0.Sqr(t0) + t1.Sqr(t1) } - // Step 5: t0 = x^0xf - t0.Mul(t3, t0) + // Step 5: t1 = x^0xf + t1.Mul(t0, t1) - // Step 9: t1 = x^0xf0 - t1.Sqr(t0) + // Step 9: t2 = x^0xf0 + t2.Sqr(t1) for s := 1; s < 4; s++ { - t1.Sqr(t1) + t2.Sqr(t2) } - // Step 10: t0 = x^0xff - t0.Mul(t0, t1) + // Step 10: t1 = x^0xff + t1.Mul(t1, t2) - // Step 12: t0 = x^0x3fc + // Step 12: t1 = x^0x3fc for s := 0; s < 2; s++ { - t0.Sqr(t0) + t1.Sqr(t1) } - // Step 13: t0 = x^0x3ff - t0.Mul(t3, t0) + // Step 13: t1 = x^0x3ff + t1.Mul(t0, t1) - // Step 23: t1 = x^0xffc00 - t1.Sqr(t0) + // Step 23: t2 = x^0xffc00 + t2.Sqr(t1) for s := 1; s < 10; s++ { - t1.Sqr(t1) + t2.Sqr(t2) } - // Step 24: t1 = x^0xfffff - t1.Mul(t0, t1) + // Step 24: t2 = x^0xfffff + t2.Mul(t1, t2) - // Step 34: t1 = x^0x3ffffc00 + // Step 34: t2 = x^0x3ffffc00 for s := 0; s < 10; s++ { - t1.Sqr(t1) + t2.Sqr(t2) } - // Step 35: t1 = x^0x3fffffff - t1.Mul(t0, t1) + // Step 35: t2 = x^0x3fffffff + t2.Mul(t1, t2) - // Step 65: t2 = x^0xfffffffc0000000 - t2.Sqr(t1) + // Step 65: t3 = x^0xfffffffc0000000 + t3.Sqr(t2) for s := 1; s < 30; s++ { - t2.Sqr(t2) + t3.Sqr(t3) } - // Step 66: t1 = x^0xfffffffffffffff - t1.Mul(t1, t2) + // Step 66: t2 = x^0xfffffffffffffff + t2.Mul(t2, t3) - // Step 126: t2 = x^0xfffffffffffffff000000000000000 - t2.Sqr(t1) + // Step 126: t3 = x^0xfffffffffffffff000000000000000 + t3.Sqr(t2) for s := 1; s < 60; s++ { - t2.Sqr(t2) + t3.Sqr(t3) } - // Step 127: t1 = x^0xffffffffffffffffffffffffffffff - t1.Mul(t1, t2) + // Step 127: t2 = x^0xffffffffffffffffffffffffffffff + t2.Mul(t2, t3) - // Step 247: t2 = x^0xffffffffffffffffffffffffffffff000000000000000000000000000000 - t2.Sqr(t1) + // Step 247: t3 = x^0xffffffffffffffffffffffffffffff000000000000000000000000000000 + t3.Sqr(t2) for s := 1; s < 120; s++ { - t2.Sqr(t2) + t3.Sqr(t3) } - // Step 248: t1 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - t1.Mul(t1, t2) + // Step 248: t2 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + t2.Mul(t2, t3) - // Step 258: t1 = x^0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00 + // Step 258: t2 = x^0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00 for s := 0; s < 10; s++ { - t1.Sqr(t1) + t2.Sqr(t2) } - // Step 259: t0 = x^0x3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - t0.Mul(t0, t1) + // Step 259: t1 = x^0x3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + t1.Mul(t1, t2) - // Step 261: t0 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc + // Step 261: t1 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc for s := 0; s < 2; s++ { - t0.Sqr(t0) + t1.Sqr(t1) } // Step 262: t4 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd - t4.Mul(x, t0) + t4.Mul(x, t1) // Step 265: t4 = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8 for s := 0; s < 3; s++ { @@ -132,7 +132,7 @@ func (z *Elt) Inv(x *Elt) *Elt { } // Step 266: z = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb - z.Mul(t3, t4) + z.Mul(t0, t4) return z } diff --git a/internal/gen/testdata/builtin/listing.golden b/internal/gen/testdata/builtin/listing.golden index 91b2ad0..6eb7342 100644 --- a/internal/gen/testdata/builtin/listing.golden +++ b/internal/gen/testdata/builtin/listing.golden @@ -1,25 +1,25 @@ -tmp t0 t1 t2 -double z x -add z x z -shift t0 z 2 -add t0 z t0 -shift t1 t0 4 -add t0 t0 t1 -shift t0 t0 2 -add t0 z t0 -shift t1 t0 10 -add t1 t0 t1 -shift t1 t1 10 +tmp t0 t1 t2 t3 t4 +double t0 x +add t0 x t0 +shift t1 t0 2 add t1 t0 t1 -shift t2 t1 30 -add t1 t1 t2 -shift t2 t1 60 +shift t2 t1 4 add t1 t1 t2 -shift t2 t1 120 +shift t1 t1 2 +add t1 t0 t1 +shift t2 t1 10 +add t2 t1 t2 +shift t2 t2 10 +add t2 t1 t2 +shift t3 t2 30 +add t2 t2 t3 +shift t3 t2 60 +add t2 t2 t3 +shift t3 t2 120 +add t2 t2 t3 +shift t2 t2 10 add t1 t1 t2 -shift t1 t1 10 -add t0 t0 t1 -shift t0 t0 2 -add t0 x t0 -shift t0 t0 3 -add z z t0 +shift t1 t1 2 +add t4 x t1 +shift t4 t4 3 +add z t0 t4 From b64bf882a547bd6985f8e80ce73bca07065b2bdc Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 20:20:12 -0700 Subject: [PATCH 11/16] doc --- acc/pass/alloc.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/acc/pass/alloc.go b/acc/pass/alloc.go index 4f79b94..0acdd12 100644 --- a/acc/pass/alloc.go +++ b/acc/pass/alloc.go @@ -71,7 +71,10 @@ func (a Allocator) Execute(p *ir.Program) error { } } - // Assign names to the operands. + // Assign names to the operands. Reuse of the output variable is handled + // specially, since we have to account for the possibility that it could be + // aliased with the input. Prior to the last use of the input, variable 0 + // will be a temporary, after it will be the output. lastinputread := 0 for _, inst := range p.Instructions { for _, input := range inst.Op.Inputs() { @@ -81,16 +84,21 @@ func (a Allocator) Execute(p *ir.Program) error { } } + // Map from variable index to name. name := map[int]string{} for _, index := range p.Indexes { op := p.Operands[index] v := allocation[op.Index] _, ok := name[v] switch { + // Operand index 0 is the input. case op.Index == 0: op.Identifier = a.Input + // Variable index 0 is the output, as long as we're past the last use of + // the input. case v == 0 && op.Index >= lastinputread: op.Identifier = a.Output + // Unnamed variable: allocate a temporary. case !ok: name[v] = fmt.Sprintf(a.Format, len(p.Temporaries)) p.Temporaries = append(p.Temporaries, name[v]) From 3a0e3a82fa60616c7e74926597abbe611fea9d4e Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 21:08:40 -0700 Subject: [PATCH 12/16] acc/pass: idempotency check --- acc/pass/pass_test.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/acc/pass/pass_test.go b/acc/pass/pass_test.go index 95439d9..d519cb7 100644 --- a/acc/pass/pass_test.go +++ b/acc/pass/pass_test.go @@ -100,13 +100,16 @@ func TestIndexes(t *testing.T) { }, } - if err := Indexes(p); err != nil { - t.Fatal(err) - } + // Twice to check idempotency. + for i := 0; i < 2; i++ { + if err := Indexes(p); err != nil { + t.Fatal(err) + } - expect := []int{0, 2, 5, 9} - if !reflect.DeepEqual(expect, p.Indexes) { - t.FailNow() + expect := []int{0, 2, 5, 9} + if !reflect.DeepEqual(expect, p.Indexes) { + t.FailNow() + } } } @@ -148,11 +151,14 @@ func TestReadCounts(t *testing.T) { 5: 1, } - if err := ReadCounts(p); err != nil { - t.Fatal(p) - } + // Twice to check idempotency. + for i := 0; i < 2; i++ { + if err := ReadCounts(p); err != nil { + t.Fatal(p) + } - if !reflect.DeepEqual(expect, p.ReadCount) { - t.FailNow() + if !reflect.DeepEqual(expect, p.ReadCount) { + t.FailNow() + } } } From faf05c70280d0047c52b20fe4c77b3c8204e4c60 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 21:09:17 -0700 Subject: [PATCH 13/16] acc/pass: use check functions in alloc test --- acc/pass/alloc_test.go | 119 +++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/acc/pass/alloc_test.go b/acc/pass/alloc_test.go index 141dcd3..8d3996b 100644 --- a/acc/pass/alloc_test.go +++ b/acc/pass/alloc_test.go @@ -46,7 +46,8 @@ func TestAllocator(t *testing.T) { Output: "out", Format: "tmp%d", } - Allocate(t, a, p) + + Allocate(t, p, a) // Every operand should have a name. for _, operand := range p.Operands { @@ -75,9 +76,16 @@ func TestAllocator(t *testing.T) { } } -func TestAllocatorAlias(t *testing.T) { - r := rand.AddsGenerator{N: 32} +func TestAllocatorRandom(t *testing.T) { + checks := []func(*testing.T, *ir.Program, Allocator){ + CheckInputOutputNotBothLive, + CheckExecute, + } + + r := rand.AddsGenerator{N: 64} test.Repeat(t, func(t *testing.T) bool { + t.Helper() + // Generate a random program. p, err := r.GenerateProgram() if err != nil { @@ -88,81 +96,74 @@ func TestAllocatorAlias(t *testing.T) { a := Allocator{ Input: "in", Output: "out", - Format: "tmp%d", + Format: "t%d", } - Allocate(t, a, p) - - // Verify the input and output are not live at the same time. - live := map[string]bool{} - for i := len(p.Instructions) - 1; i >= 0; i-- { - inst := p.Instructions[i] - - // Update live set. - delete(live, inst.Output.Identifier) - for _, input := range inst.Op.Inputs() { - live[input.Identifier] = true - } - - if live[a.Input] && live[a.Output] { - t.Fatalf("instruction %d: input %q and output %q both live", i, a.Input, a.Output) - } + + Allocate(t, p, a) + + // Checks. + for _, check := range checks { + check(t, p, a) } return true }) } -func TestAllocatorExec(t *testing.T) { - r := rand.AddsGenerator{N: 64} - test.Repeat(t, func(t *testing.T) bool { - // Generate a random program. - p, err := r.GenerateProgram() - if err != nil { - t.Fatal(err) +func CheckInputOutputNotBothLive(t *testing.T, p *ir.Program, a Allocator) { + t.Helper() + + // Verify the input and output are not live at the same time. + live := map[string]bool{} + for i := len(p.Instructions) - 1; i >= 0; i-- { + inst := p.Instructions[i] + + // Update live set. + delete(live, inst.Output.Identifier) + for _, input := range inst.Op.Inputs() { + live[input.Identifier] = true } - // Execute allocation pass. - a := Allocator{ - Input: "x", - Output: "z", - Format: "t%d", + if live[a.Input] && live[a.Output] { + t.Fatalf("instruction %d: input %q and output %q both live", i, a.Input, a.Output) } - Allocate(t, a, p) + } +} - // Execute with interpreter. Deliberately setup the input and output to - // be aliased. - i := eval.NewInterpreter() - x := bigint.One() - i.Store(a.Input, x) - i.Store(a.Output, x) +func CheckExecute(t *testing.T, p *ir.Program, a Allocator) { + t.Helper() - if err := i.Execute(p); err != nil { - t.Fatal(err) - } + // Execute with interpreter. Deliberately setup the input and output to + // be aliased. + i := eval.NewInterpreter() + x := bigint.One() + i.Store(a.Input, x) + i.Store(a.Output, x) - // Output should be the same as the target of the addition chain. - output, ok := i.Load(a.Output) - if !ok { - t.Fatalf("missing output variable %q", a.Output) - } + if err := i.Execute(p); err != nil { + t.Fatal(err) + } - if err := Eval(p); err != nil { - t.Fatal(err) - } + // Output should be the same as the target of the addition chain. + output, ok := i.Load(a.Output) + if !ok { + t.Fatalf("missing output variable %q", a.Output) + } - expect := p.Chain.End() + if err := Eval(p); err != nil { + t.Fatal(err) + } - if !bigint.Equal(output, expect) { - t.Logf(" got = %#x", output) - t.Logf("expect = %#x", expect) - t.Fatal("mismatch") - } + expect := p.Chain.End() - return true - }) + if !bigint.Equal(output, expect) { + t.Logf(" got = %#x", output) + t.Logf("expect = %#x", expect) + t.Fatal("mismatch") + } } -func Allocate(t *testing.T, a Allocator, p *ir.Program) { +func Allocate(t *testing.T, p *ir.Program, a Allocator) { t.Helper() t.Logf("pre alloc:\n%s", p) From de6e92ad53edd9c08c80f6a664c1d6d225a03af9 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 21:50:39 -0700 Subject: [PATCH 14/16] acc/pass: extend allocation checks Extends the suite of allocation checks: - every operand has a name - names used are: input, output, temporaries - input not written to - input and output not live at same time - live operands have unique names - executing the program gives the right result --- acc/pass/alloc_test.go | 126 +++++++++++++++++++++++++++++++++++------ 1 file changed, 108 insertions(+), 18 deletions(-) diff --git a/acc/pass/alloc_test.go b/acc/pass/alloc_test.go index 8d3996b..6b750f1 100644 --- a/acc/pass/alloc_test.go +++ b/acc/pass/alloc_test.go @@ -49,12 +49,8 @@ func TestAllocator(t *testing.T) { Allocate(t, p, a) - // Every operand should have a name. - for _, operand := range p.Operands { - if operand.Identifier == "" { - t.Errorf("operand %s does not have a name", operand) - } - } + // Check allocation properties. + CheckAllocation(t, p, a) // We expect to have n-1 temporaries. Note we do not expect n, since the output // variable should be used as a temporary. @@ -77,11 +73,6 @@ func TestAllocator(t *testing.T) { } func TestAllocatorRandom(t *testing.T) { - checks := []func(*testing.T, *ir.Program, Allocator){ - CheckInputOutputNotBothLive, - CheckExecute, - } - r := rand.AddsGenerator{N: 64} test.Repeat(t, func(t *testing.T) bool { t.Helper() @@ -101,19 +92,84 @@ func TestAllocatorRandom(t *testing.T) { Allocate(t, p, a) - // Checks. - for _, check := range checks { - check(t, p, a) - } + // Check. + CheckAllocation(t, p, a) return true }) } +func CheckAllocation(t *testing.T, p *ir.Program, a Allocator) { + t.Helper() + + checks := []func(*testing.T, *ir.Program, Allocator){ + CheckEveryOperandNamed, + CheckUsedVariables, + CheckInputNotWritten, + CheckInputOutputNotBothLive, + CheckLiveVariablesUnique, + CheckExecute, + } + for _, check := range checks { + check(t, p, a) + } +} + +// CheckEveryOperandNamed verfies the allocator assigned an identifier to every operand. +func CheckEveryOperandNamed(t *testing.T, p *ir.Program, a Allocator) { + t.Helper() + + for _, operand := range p.Operands { + if operand.Identifier == "" { + t.Errorf("operand %s does not have a name", operand) + } + } +} + +// CheckUsedVariables verifies that the set of used variables is exactly the +// input, output and temporaries list. +func CheckUsedVariables(t *testing.T, p *ir.Program, a Allocator) { + t.Helper() + + // Gather set of used variables. + used := map[string]bool{} + for _, operand := range p.Operands { + used[operand.Identifier] = true + } + + // Expect the set of used variables should be the input, output and + // temporaries list. + expect := []string{a.Input, a.Output} + expect = append(expect, p.Temporaries...) + + if len(used) != len(expect) { + t.Errorf("%d used variables; expected %d", len(used), len(expect)) + } + + for _, v := range expect { + if !used[v] { + t.Errorf("variable %q not used", v) + } + } +} + +// CheckInputNotWritten verifies the input is never written to. +func CheckInputNotWritten(t *testing.T, p *ir.Program, a Allocator) { + t.Helper() + + for i, inst := range p.Instructions { + if inst.Output.Identifier == a.Input { + t.Fatalf("instruction %d: write to input %q", i, a.Input) + } + } +} + +// CheckInputOutputNotBothLive verifies the input and output are not live at the +// same time. This is required to preserve correctness when input and output are +// aliased. func CheckInputOutputNotBothLive(t *testing.T, p *ir.Program, a Allocator) { t.Helper() - // Verify the input and output are not live at the same time. live := map[string]bool{} for i := len(p.Instructions) - 1; i >= 0; i-- { inst := p.Instructions[i] @@ -130,11 +186,45 @@ func CheckInputOutputNotBothLive(t *testing.T, p *ir.Program, a Allocator) { } } +// CheckLiveVariablesUnique verifies the primary property of the allocator: live +// variables are assigned different names. +func CheckLiveVariablesUnique(t *testing.T, p *ir.Program, a Allocator) { + t.Helper() + + // Collect the index to name map. + name := map[int]string{} + for _, op := range p.Operands { + name[op.Index] = op.Identifier + } + + // Maintain set of live indexes. + live := map[int]bool{} + for i := len(p.Instructions) - 1; i >= 0; i-- { + inst := p.Instructions[i] + + // Update live set. + delete(live, inst.Output.Index) + for _, input := range inst.Op.Inputs() { + live[input.Index] = true + } + + // Check all unique. + seen := map[string]bool{} + for i := range live { + if seen[name[i]] { + t.Fatalf("variable %q assigned to two live operands", name[i]) + } + seen[name[i]] = true + } + } +} + +// CheckExecute executes the program under the interpreter and verifies the +// output is the same as the the evaluated addition chain. func CheckExecute(t *testing.T, p *ir.Program, a Allocator) { t.Helper() - // Execute with interpreter. Deliberately setup the input and output to - // be aliased. + // Deliberately setup the input and output to be aliased. i := eval.NewInterpreter() x := bigint.One() i.Store(a.Input, x) From cfba09912ae9f5ab2e106882eb9b837ec1eb9b92 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 22:04:42 -0700 Subject: [PATCH 15/16] doc comments --- acc/eval/interp.go | 5 +++++ acc/rand/rand.go | 2 ++ 2 files changed, 7 insertions(+) diff --git a/acc/eval/interp.go b/acc/eval/interp.go index 6473360..3693c10 100644 --- a/acc/eval/interp.go +++ b/acc/eval/interp.go @@ -9,10 +9,15 @@ import ( "github.com/mmcloughlin/addchain/internal/errutil" ) +// Interpreter for acc programs. In contrast to evaluation using indexes, the +// interpreter executes the program using operand variable names, as if it was a +// block of source code. Internally it maintains the state of every variable, +// and program instructions update that state. type Interpreter struct { state map[string]*big.Int } +// NewInterpreter builds a new interpreter. Initially, all variables are unset. func NewInterpreter() *Interpreter { return &Interpreter{ state: map[string]*big.Int{}, diff --git a/acc/rand/rand.go b/acc/rand/rand.go index a010e17..eec54a7 100644 --- a/acc/rand/rand.go +++ b/acc/rand/rand.go @@ -1,3 +1,4 @@ +// Package rand provides random addition chain program generators. package rand import ( @@ -23,6 +24,7 @@ func (a AddsGenerator) String() string { return fmt.Sprintf("random_adds(%d)", a.N) } +// GenerateProgram builds a program with N random adds. func (a AddsGenerator) GenerateProgram() (*ir.Program, error) { p := &ir.Program{} for i := 1; i <= a.N; i++ { From 99f4b4b4402a625eda898ac52785dd941dbe0391 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 31 Oct 2021 22:04:52 -0700 Subject: [PATCH 16/16] acc/eval: test Initialize method --- acc/eval/interp_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/acc/eval/interp_test.go b/acc/eval/interp_test.go index 1ba1a6b..c34090e 100644 --- a/acc/eval/interp_test.go +++ b/acc/eval/interp_test.go @@ -29,9 +29,12 @@ func TestInterpreter(t *testing.T) { // Evaluate it. i := NewInterpreter() - i.Store("a", big.NewInt(1)) - err := i.Execute(p) - if err != nil { + + if err := i.Initialize("a", big.NewInt(1)); err != nil { + t.Fatal(err) + } + + if err := i.Execute(p); err != nil { t.Fatal(err) }