diff --git a/internal/cmd/avogen/main.go b/internal/cmd/avogen/main.go new file mode 100644 index 0000000..84a7b64 --- /dev/null +++ b/internal/cmd/avogen/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "flag" + "go/build" + "log" + "os" + "path/filepath" + + "github.com/mmcloughlin/avo/internal/gen" + "github.com/mmcloughlin/avo/internal/load" +) + +var generators = map[string]gen.Interface{ + "asmtest": gen.NewAsmTest(), + "godata": gen.NewGoData(), +} + +var datadir = flag.String( + "data", + filepath.Join(build.Default.GOPATH, "src/github.com/mmcloughlin/avo/internal/data"), + "path to data directory", +) + +var output = flag.String("output", "", "path to output file (default stdout)") + +func main() { + flag.Parse() + + // Build generator. + t := flag.Arg(0) + g := generators[t] + if g == nil { + log.Fatalf("unknown generator type '%s'", t) + } + + // Determine output writer. + w := os.Stdout + if *output != "" { + f, err := os.Open(*output) + if err != nil { + log.Fatal(err) + } + w = f + } + + // Load instructions. + l := load.NewLoaderFromDataDir(*datadir) + is, err := l.Load() + if err != nil { + log.Fatal(err) + } + + // Generate output. + b, err := g.Generate(is) + if err != nil { + log.Fatal(err) + } + + // Write. + if _, err := w.Write(b); err != nil { + log.Fatal(err) + } +} diff --git a/internal/gen/loadertest.go b/internal/gen/asmtest.go similarity index 82% rename from internal/gen/loadertest.go rename to internal/gen/asmtest.go index b53d949..b61258f 100644 --- a/internal/gen/loadertest.go +++ b/internal/gen/asmtest.go @@ -2,7 +2,6 @@ package gen import ( "fmt" - "io" "math" "strconv" "strings" @@ -10,61 +9,65 @@ import ( "github.com/mmcloughlin/avo/internal/inst" ) -type LoaderTest struct { +type asmtest struct { sym string // reference to the test function symbol rel8 string // label to be used for near jumps rel32 string // label for far jumps } -func (l *LoaderTest) Generate(w io.Writer, is []*inst.Instruction) error { - p := &printer{w: w} +func NewAsmTest() Interface { + return &asmtest{} +} - l.sym = "\u00b7loadertest(SB)" - p.printf("TEXT %s, 0, $0\n", l.sym) +func (a *asmtest) Generate(is []*inst.Instruction) ([]byte, error) { + p := &printer{} + + a.sym = "\u00b7loadertest(SB)" + p.Printf("TEXT %s, 0, $0\n", a.sym) // Define a label for far jumps. - p.printf("rel32:\n") - l.rel32 = "rel32" + p.Printf("rel32:\n") + a.rel32 = "rel32" counts := map[string]int{} for _, i := range is { - p.printf("\t// %s %s\n", i.Opcode, i.Summary) - if skip, msg := l.skip(i.Opcode); skip { - p.printf("\t// SKIP: %s\n", msg) + p.Printf("\t// %s %s\n", i.Opcode, i.Summary) + if skip, msg := a.skip(i.Opcode); skip { + p.Printf("\t// SKIP: %s\n", msg) counts["skip"]++ continue } if i.Opcode[0] == 'J' { label := fmt.Sprintf("rel8_%s", strings.ToLower(i.Opcode)) - p.printf("%s:\n", label) - l.rel8 = label + p.Printf("%s:\n", label) + a.rel8 = label } for _, f := range i.Forms { - as := l.args(i.Opcode, f.Operands) + as := a.args(i.Opcode, f.Operands) if as == nil { - p.printf("\t// TODO: %s %#v\n", i.Opcode, f.Operands) + p.Printf("\t// TODO: %s %#v\n", i.Opcode, f.Operands) counts["todo"]++ continue } - p.printf("\t%s\t%s\n", i.Opcode, strings.Join(as, ", ")) + p.Printf("\t%s\t%s\n", i.Opcode, strings.Join(as, ", ")) counts["total"]++ } - p.printf("\n") + p.Printf("\n") } - p.printf("\tRET\n") + p.Printf("\tRET\n") for m, c := range counts { - p.printf("// %s: %d\n", m, c) + p.Printf("// %s: %d\n", m, c) } - return p.Err() + return p.Result() } -func (l LoaderTest) skip(opcode string) (bool, string) { +func (a asmtest) skip(opcode string) (bool, string) { prefixes := map[string]string{ "PUSH": "PUSH can produce 'unbalanced PUSH/POP' assembler error", "POP": "POP can produce 'unbalanced PUSH/POP' assembler error", @@ -77,15 +80,15 @@ func (l LoaderTest) skip(opcode string) (bool, string) { return false, "" } -func (l LoaderTest) args(opcode string, ops []inst.Operand) []string { +func (a asmtest) args(opcode string, ops []inst.Operand) []string { // Special case for CALL, since it needs a different type of rel32 argument than others. if opcode == "CALL" { - return []string{l.sym} + return []string{a.sym} } as := make([]string, len(ops)) for i, op := range ops { - a := l.arg(op.Type, i) + a := a.arg(op.Type, i) if a == "" { return nil } @@ -95,7 +98,7 @@ func (l LoaderTest) args(opcode string, ops []inst.Operand) []string { } // arg generates an argument for an operand of the given type. -func (l LoaderTest) arg(t string, i int) string { +func (a asmtest) arg(t string, i int) string { m := map[string]string{ "1": "$1", // "3": "$3", // @@ -165,8 +168,8 @@ func (l LoaderTest) arg(t string, i int) string { // // // - "rel8": l.rel8, // - "rel32": l.rel32, // + "rel8": a.rel8, // + "rel32": a.rel32, // // // diff --git a/internal/gen/gen.go b/internal/gen/gen.go index acc26a5..2f47255 100644 --- a/internal/gen/gen.go +++ b/internal/gen/gen.go @@ -1,34 +1,46 @@ package gen import ( + "bytes" "fmt" - "io" + "go/format" "github.com/mmcloughlin/avo/internal/inst" ) type Interface interface { - Generate(io.Writer, []*inst.Instruction) error + Generate([]*inst.Instruction) ([]byte, error) } -type Func func(io.Writer, []*inst.Instruction) error +type Func func([]*inst.Instruction) ([]byte, error) -func (f Func) Generate(w io.Writer, is []*inst.Instruction) error { - return f(w, is) +func (f Func) Generate(is []*inst.Instruction) ([]byte, error) { + return f(is) +} + +// GoFmt formats Go code produced from the given generator. +func GoFmt(i Interface) Interface { + return Func(func(is []*inst.Instruction) ([]byte, error) { + b, err := i.Generate(is) + if err != nil { + return nil, err + } + return format.Source(b) + }) } type printer struct { - w io.Writer + buf bytes.Buffer err error } -func (p *printer) printf(format string, args ...interface{}) { +func (p *printer) Printf(format string, args ...interface{}) { if p.err != nil { return } - _, p.err = fmt.Fprintf(p.w, format, args...) + _, p.err = fmt.Fprintf(&p.buf, format, args...) } -func (p *printer) Err() error { - return p.err +func (p *printer) Result() ([]byte, error) { + return p.buf.Bytes(), p.err } diff --git a/internal/gen/godata.go b/internal/gen/godata.go new file mode 100644 index 0000000..2a2b8e4 --- /dev/null +++ b/internal/gen/godata.go @@ -0,0 +1,61 @@ +package gen + +import ( + "github.com/mmcloughlin/avo/internal/inst" +) + +type godata struct { +} + +func NewGoData() Interface { + return GoFmt(godata{}) +} + +func (g godata) Generate(is []*inst.Instruction) ([]byte, error) { + p := &printer{} + + p.Printf("package inst\n\n") + + p.Printf("var Instructions = []Instruction{\n") + + for _, i := range is { + p.Printf("{\n") + + p.Printf("Opcode: %#v,\n", i.Opcode) + p.Printf("Summary: %#v,\n", i.Summary) + + p.Printf("Forms: []Form{\n") + for _, f := range i.Forms { + p.Printf("{\n") + + if f.ISA != nil { + p.Printf("ISA: %#v,\n", f.ISA) + } + + if f.Operands != nil { + p.Printf("Operands: []Operand{\n") + for _, op := range f.Operands { + p.Printf("{Type: %#v, Action: %#v},\n", op.Type, op.Action) + } + p.Printf("},\n") + } + + if f.ImplicitOperands != nil { + p.Printf("ImplicitOperands: []ImplicitOperand{\n") + for _, op := range f.ImplicitOperands { + p.Printf("{Register: %#v, Action: %#v},\n", op.Register, op.Action) + } + p.Printf("},\n") + } + + p.Printf("},\n") + } + p.Printf("},\n") + + p.Printf("},\n") + } + + p.Printf("}\n") + + return p.Result() +}