From af02be06baab1edda36fb3d269f0126580ab43d4 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 25 Nov 2018 21:50:46 -0800 Subject: [PATCH] add skeleton for instruction constructors --- internal/cmd/avogen/main.go | 7 +- internal/gen/constructors.go | 121 ++++++++++++++++++++++++++++++ internal/gen/constructors_test.go | 28 +++++++ internal/gen/gen_test.go | 1 + internal/inst/table_test.go | 3 + internal/inst/types.go | 8 ++ x86/make.go | 74 ------------------ 7 files changed, 165 insertions(+), 77 deletions(-) create mode 100644 internal/gen/constructors.go create mode 100644 internal/gen/constructors_test.go delete mode 100644 x86/make.go diff --git a/internal/cmd/avogen/main.go b/internal/cmd/avogen/main.go index bd21789..0e6db6d 100644 --- a/internal/cmd/avogen/main.go +++ b/internal/cmd/avogen/main.go @@ -13,9 +13,10 @@ import ( ) var generators = map[string]gen.Builder{ - "asmtest": gen.NewAsmTest, - "godata": gen.NewGoData, - "godatatest": gen.NewGoDataTest, + "asmtest": gen.NewAsmTest, + "godata": gen.NewGoData, + "godatatest": gen.NewGoDataTest, + "constructors": gen.NewConstructors, } // Command-line flags. diff --git a/internal/gen/constructors.go b/internal/gen/constructors.go new file mode 100644 index 0000000..9388bfc --- /dev/null +++ b/internal/gen/constructors.go @@ -0,0 +1,121 @@ +package gen + +import ( + "bytes" + "fmt" + "sort" + "strconv" + "strings" + "text/tabwriter" + + "github.com/mmcloughlin/avo/internal/inst" +) + +type constructors struct { + cfg Config + printer +} + +func NewConstructors(cfg Config) Interface { + return GoFmt(&constructors{cfg: cfg}) +} + +func (c *constructors) Generate(is []inst.Instruction) ([]byte, error) { + c.Printf("// %s\n\n", c.cfg.GeneratedWarning()) + c.Printf("package x86\n\n") + c.Printf("import \"github.com/mmcloughlin/avo\"\n\n") + + for _, i := range is { + c.instruction(i) + } + + return c.Result() +} + +func (c *constructors) instruction(i inst.Instruction) { + for _, line := range c.doc(i) { + c.Printf("// %s\n", line) + } + + paramlist, _ := params(i) + + c.Printf("func %s(%s) error {\n", i.Opcode, paramlist) + c.Printf("\treturn nil\n") + c.Printf("}\n\n") +} + +// doc generates the lines of the function comment. +func (c *constructors) doc(i inst.Instruction) []string { + lines := []string{ + fmt.Sprintf("%s: %s.", i.Opcode, i.Summary), + "", + "Forms:", + "", + } + + // Write a table of instruction forms. + buf := bytes.NewBuffer(nil) + w := tabwriter.NewWriter(buf, 0, 0, 1, ' ', 0) + for _, f := range i.Forms { + row := i.Opcode + "\t" + strings.Join(f.Signature(), "\t") + "\n" + fmt.Fprint(w, row) + } + w.Flush() + + tbl := strings.TrimSpace(buf.String()) + for _, line := range strings.Split(tbl, "\n") { + lines = append(lines, "\t"+line) + } + + return lines +} + +// params generates the function parameters and a function. +func params(i inst.Instruction) (string, func(int) string) { + a := i.Arities() + + // Handle the case of forms with multiple arities. + if len(a) > 1 { + return "ops ...avo.Operand", func(j int) string { + return fmt.Sprintf("ops[%d]", j) + } + } + + // All forms have the same arity. + n := a[0] + if n == 0 { + return "", func(int) string { panic("unreachable") } + } + + // Generate nice-looking variable names. + ops := make([]string, n) + count := map[string]int{} + for j := 0; j < n; j++ { + // Collect unique lowercase bytes from first characters of operand types. + s := map[byte]bool{} + for _, f := range i.Forms { + c := f.Operands[j].Type[0] + if 'a' <= c && c <= 'z' { + s[c] = true + } + } + + // Operand name is the sorted bytes. + var b []byte + for c := range s { + b = append(b, c) + } + sort.Slice(b, func(i, j int) bool { return b[i] < b[j] }) + name := string(b) + + // Append a counter if we've seen it already. + m := count[name] + count[name]++ + if m > 0 { + name += strconv.Itoa(m) + } + ops[j] = name + } + + return strings.Join(ops, ", ") + " avo.Operand", func(j int) string { return ops[j] } +} diff --git a/internal/gen/constructors_test.go b/internal/gen/constructors_test.go new file mode 100644 index 0000000..893d2f9 --- /dev/null +++ b/internal/gen/constructors_test.go @@ -0,0 +1,28 @@ +package gen + +import ( + "testing" + + "github.com/mmcloughlin/avo/internal/inst" +) + +func TestParamsUniqueArgNames(t *testing.T) { + for _, i := range inst.Instructions { + _, argname := params(i) + for _, n := range i.Arities() { + if n == 0 { + continue + } + names := map[string]bool{} + for j := 0; j < n; j++ { + names[argname(j)] = true + } + if len(names) != n { + t.Errorf("repeated argument for instruction %s", i.Opcode) + } + if _, found := names[""]; found { + t.Errorf("empty argument name for instruction %s", i.Opcode) + } + } + } +} diff --git a/internal/gen/gen_test.go b/internal/gen/gen_test.go index e74f6ab..0f5f370 100644 --- a/internal/gen/gen_test.go +++ b/internal/gen/gen_test.go @@ -7,5 +7,6 @@ func TestBuilderInterfaces(t *testing.T) { NewAsmTest, NewGoData, NewGoDataTest, + NewConstructors, } } diff --git a/internal/inst/table_test.go b/internal/inst/table_test.go index f3d5373..a455553 100644 --- a/internal/inst/table_test.go +++ b/internal/inst/table_test.go @@ -40,6 +40,9 @@ func TestInstructionProperties(t *testing.T) { if len(i.Forms) == 0 { t.Errorf("instruction %s has no forms", i.Opcode) } + if len(i.Arities()) == 0 { + t.Errorf("instruction %s has empty arities list", i.Opcode) + } } } diff --git a/internal/inst/types.go b/internal/inst/types.go index 1126270..4abd150 100644 --- a/internal/inst/types.go +++ b/internal/inst/types.go @@ -28,6 +28,14 @@ type Form struct { ImplicitOperands []ImplicitOperand } +func (f Form) Signature() []string { + s := make([]string, len(f.Operands)) + for i, op := range f.Operands { + s[i] = op.Type + } + return s +} + type Operand struct { Type string Action Action diff --git a/x86/make.go b/x86/make.go deleted file mode 100644 index 8c234e1..0000000 --- a/x86/make.go +++ /dev/null @@ -1,74 +0,0 @@ -// +build ignore - -package main - -import ( - "flag" - "fmt" - "go/build" - "log" - "os" - "path/filepath" - "regexp" - "sort" - - "golang.org/x/arch/x86/x86csv" -) - -const csvname = "x86.v0.2.csv" - -var csvpath = flag.String( - "csv", - filepath.Join(build.Default.GOPATH, "src/golang.org/x/arch/x86", csvname), - "path to "+csvname, -) - -func UniqueOpCodes(instrs []*x86csv.Inst) []string { - set := map[string]bool{} - for _, i := range instrs { - set[i.GoOpcode()] = true - } - - opcodes := make([]string, 0, len(set)) - for opcode := range set { - opcodes = append(opcodes, opcode) - } - - sort.Strings(opcodes) - - return opcodes -} - -var functionRegex = regexp.MustCompile(`^[A-Z0-9]+$`) - -func main() { - flag.Parse() - - f, err := os.Open(*csvpath) - if err != nil { - log.Fatal(err) - } - defer f.Close() - - r := x86csv.NewReader(f) - - instrs, err := r.ReadAll() - if err != nil { - log.Fatal(err) - } - - fmt.Print("package x86\n\nimport \"github.com/mmcloughlin/avo\"\n") - - for _, opcode := range UniqueOpCodes(instrs) { - if !functionRegex.MatchString(opcode) { - log.Printf("skip %s", opcode) - continue - } - fmt.Printf("\nfunc %s(f *avo.Function, operands ...string) {\n", opcode) - fmt.Printf("\tf.AddInstruction(avo.Instruction{\n") - fmt.Printf("\t\tMnemonic: \"%s\",\n", opcode) - fmt.Printf("\t\tOperands: operands,\n") - fmt.Printf("\t})\n") - fmt.Printf("}\n") - } -}