go_study/fabric-main/vendor/github.com/mmcloughlin/addchain/acc/build.go

159 lines
3.6 KiB
Go

package acc
import (
"fmt"
"github.com/mmcloughlin/addchain/acc/ast"
"github.com/mmcloughlin/addchain/acc/ir"
"github.com/mmcloughlin/addchain/acc/pass"
"github.com/mmcloughlin/addchain/internal/errutil"
)
// complexitylimit is the maximum number of operators the builder will allow an
// expression to have.
const complexitylimit = 5
// Build AST from a program in intermediate representation.
func Build(p *ir.Program) (*ast.Chain, error) {
// Run some analysis passes first.
err := pass.Exec(p,
pass.Func(pass.ReadCounts),
pass.NameByteValues,
pass.NameXRuns,
)
if err != nil {
return nil, err
}
// Delegate to builder.
b := newbuilder(p)
if err := b.process(); err != nil {
return nil, err
}
return b.chain, nil
}
type builder struct {
chain *ast.Chain
prog *ir.Program
expr map[int]ast.Expr
}
func newbuilder(p *ir.Program) *builder {
return &builder{
chain: &ast.Chain{},
prog: p,
expr: map[int]ast.Expr{},
}
}
func (b *builder) process() error {
insts := b.prog.Instructions
n := len(insts)
complexity := 0
for i := 0; i < n; i++ {
complexity++
inst := insts[i]
out := inst.Output
// Build expression for the result of this instruction.
e, err := b.operator(inst.Op)
if err != nil {
return err
}
b.expr[out.Index] = e
// If this output is read only by the following instruction, we don't need to
// commit it to a variable.
anon := out.Identifier == ""
usedonce := b.prog.ReadCount[out.Index] == 1
usednext := i+1 < n && ir.HasInput(insts[i+1].Op, out.Index)
if anon && usedonce && usednext && complexity < complexitylimit {
continue
}
// Otherwise write a statement for it.
b.commit(inst.Output)
complexity = 0
}
// Clear the name of the final statement.
b.chain.Statements[len(b.chain.Statements)-1].Name = ""
return nil
}
func (b *builder) operator(op ir.Op) (ast.Expr, error) {
switch o := op.(type) {
case ir.Add:
return b.add(o)
case ir.Double:
return ast.Double{
X: b.operand(o.X),
}, nil
case ir.Shift:
return ast.Shift{
X: b.operand(o.X),
S: o.S,
}, nil
default:
return nil, errutil.UnexpectedType(op)
}
}
func (b *builder) add(a ir.Add) (ast.Expr, error) {
// Addition operator construction is slightly delcate, since operand order
// determines ordering of execution. By the design of instruction processing
// above, the only way we can have multi-operator expressions is with a
// sequence of operands that are used only once and in the following
// instruction. This implies that only one of x and y can be an operator
// expression. In order to preserve execution order, whichever one that is
// needs to be the first operand.
x := b.operand(a.X)
y := b.operand(a.Y)
switch {
case ast.IsOp(x) && ast.IsOp(y):
return nil, errutil.AssertionFailure("only one of x and y should be an operator expression")
case ast.IsOp(y):
x, y = y, x
case ast.IsOp(x):
// Nothing, it's already the first operand.
}
return ast.Add{
X: x,
Y: y,
}, nil
}
func (b *builder) commit(op *ir.Operand) {
name := ast.Identifier(b.name(op))
stmt := ast.Statement{
Name: name,
Expr: b.operand(op),
}
b.chain.Statements = append(b.chain.Statements, stmt)
b.expr[op.Index] = name
}
// name returns the name for this operand. This is the identifier if available,
// otherwise a sensible default based on the index.
func (b *builder) name(op *ir.Operand) string {
if op.Identifier != "" {
return op.Identifier
}
return fmt.Sprintf("i%d", op.Index)
}
func (b *builder) operand(op *ir.Operand) ast.Expr {
e, ok := b.expr[op.Index]
if !ok {
return ast.Operand(op.Index)
}
return e
}