diff --git a/build/cli.go b/build/cli.go new file mode 100644 index 0000000..0e12753 --- /dev/null +++ b/build/cli.go @@ -0,0 +1,132 @@ +package build + +import ( + "flag" + "io" + "log" + "os" + + "github.com/mmcloughlin/avo/pass" + "github.com/mmcloughlin/avo/printer" +) + +type Config struct { + ErrOut io.Writer + Passes []pass.Interface +} + +func Main(cfg *Config, context *Context) int { + diag := log.New(cfg.ErrOut, "", 0) + + f, errs := context.Result() + if errs != nil { + for _, err := range errs { + diag.Println(err) + } + return 1 + } + + p := pass.Concat(cfg.Passes...) + if err := p.Execute(f); err != nil { + diag.Println(err) + return 1 + } + + return 0 +} + +type Flags struct { + errout *outputValue + printers []*printerValue +} + +func NewFlags(fs *flag.FlagSet) *Flags { + f := &Flags{} + + f.errout = newOutputValue(os.Stderr) + fs.Var(f.errout, "log", "diagnostics output") + + goasm := newPrinterValue(printer.NewGoAsm, os.Stdout) + fs.Var(goasm, "out", "assembly output") + f.printers = append(f.printers, goasm) + + stubs := newPrinterValue(printer.NewStubs, nil) + fs.Var(stubs, "stubs", "go stub file") + f.printers = append(f.printers, stubs) + + return f +} + +func (f *Flags) Config() *Config { + pc := printer.NewDefaultConfig() + passes := []pass.Interface{pass.Compile} + for _, pv := range f.printers { + p := pv.Build(pc) + if p != nil { + passes = append(passes, p) + } + } + return &Config{ + ErrOut: f.errout.w, + Passes: passes, + } +} + +type outputValue struct { + w io.WriteCloser + filename string +} + +func newOutputValue(dflt io.WriteCloser) *outputValue { + return &outputValue{w: dflt} +} + +func (o *outputValue) String() string { + if o == nil { + return "" + } + return o.filename +} + +func (o *outputValue) Set(s string) error { + o.filename = s + if s == "-" { + o.w = nopwritecloser{os.Stdout} + return nil + } + f, err := os.Create(s) + if err != nil { + return err + } + o.w = f + return nil +} + +type printerValue struct { + *outputValue + Builder printer.Builder +} + +func newPrinterValue(b printer.Builder, dflt io.WriteCloser) *printerValue { + return &printerValue{ + outputValue: newOutputValue(dflt), + Builder: b, + } +} + +func (p *printerValue) Build(cfg printer.Config) pass.Interface { + if p.outputValue.w == nil { + return nil + } + return &pass.Output{ + Writer: p.outputValue.w, + Printer: p.Builder(cfg), + } +} + +// nopwritecloser wraps a Writer and provides a null implementation of Close(). +type nopwritecloser struct { + io.Writer +} + +func (nopwritecloser) Close() error { return nil } diff --git a/build/context.go b/build/context.go index 400bfdd..38ad6f8 100644 --- a/build/context.go +++ b/build/context.go @@ -2,13 +2,9 @@ package build import ( "errors" - "io" - "log" - - "github.com/mmcloughlin/avo/gotypes" - "github.com/mmcloughlin/avo/pass" "github.com/mmcloughlin/avo" + "github.com/mmcloughlin/avo/gotypes" "github.com/mmcloughlin/avo/reg" ) @@ -73,28 +69,3 @@ func (c *Context) AddErrorMessage(msg string) { func (c *Context) Result() (*avo.File, []error) { return c.file, c.errs } - -func (c *Context) Main(wout, werr io.Writer) int { - diag := log.New(werr, "", 0) - - f, errs := c.Result() - if errs != nil { - for _, err := range errs { - diag.Println(err) - } - return 1 - } - - if err := pass.Compile.Execute(f); err != nil { - diag.Println(err) - return 1 - } - - p := avo.NewGoPrinter(wout) - if err := p.Print(f); err != nil { - diag.Println(err) - return 1 - } - - return 0 -} diff --git a/build/global.go b/build/global.go index 1a2d808..7307328 100644 --- a/build/global.go +++ b/build/global.go @@ -2,8 +2,6 @@ package build import ( "flag" - "io" - "log" "os" "github.com/mmcloughlin/avo/gotypes" @@ -23,26 +21,14 @@ func TEXT(name, signature string) { func LABEL(name string) { ctx.Label(avo.Label(name)) } -var ( - output = flag.String("output", "", "output filename (default stdout)") -) +var flags = NewFlags(flag.CommandLine) -func EOF() { +func Generate() { if !flag.Parsed() { flag.Parse() } - - var w io.Writer = os.Stdout - if *output != "" { - if f, err := os.Create(*output); err != nil { - log.Fatal(err) - } else { - defer f.Close() - w = f - } - } - - os.Exit(ctx.Main(w, os.Stderr)) + cfg := flags.Config() + os.Exit(Main(cfg, ctx)) } func GP8v() reg.Virtual { return ctx.GP8v() } diff --git a/examples/add/add.s b/examples/add/add.s new file mode 100644 index 0000000..82520ba --- /dev/null +++ b/examples/add/add.s @@ -0,0 +1,10 @@ + +#include "textflag.h" + +// func Add(x uint64, y uint64) uint64 +TEXT ·Add(SB),0,$0-24 + MOVQ x(FP), CX + MOVQ y+8(FP), AX + ADDQ CX, AX + MOVQ CX, ret+16(FP) + RET diff --git a/examples/add/add_test.go b/examples/add/add_test.go new file mode 100644 index 0000000..6252306 --- /dev/null +++ b/examples/add/add_test.go @@ -0,0 +1,12 @@ +package add + +import ( + "testing" + "testing/quick" +) + +//go:generate go run asm.go -out add.s -stubs stub.go + +func TestAdd(t *testing.T) { + quick.CheckEqual(Add, func(x, y uint64) uint64 { return x + y }, nil) +} diff --git a/examples/add/asm.go b/examples/add/asm.go index 63d99ad..4505854 100644 --- a/examples/add/asm.go +++ b/examples/add/asm.go @@ -1,3 +1,5 @@ +// +build ignore + package main import ( @@ -5,11 +7,11 @@ import ( ) func main() { - TEXT("add", "func(x, y uint64) uint64") + TEXT("Add", "func(x, y uint64) uint64") x := Load(Param("x"), GP64v()) y := Load(Param("y"), GP64v()) ADDQ(x, y) Store(x, ReturnIndex(0)) RET() - EOF() + Generate() } diff --git a/examples/add/stub.go b/examples/add/stub.go new file mode 100644 index 0000000..9e26631 --- /dev/null +++ b/examples/add/stub.go @@ -0,0 +1,3 @@ +package add + +func Add(x uint64, y uint64) uint64 diff --git a/internal/cmd/avogen/main.go b/internal/cmd/avogen/main.go index 8a90852..70e4ffc 100644 --- a/internal/cmd/avogen/main.go +++ b/internal/cmd/avogen/main.go @@ -10,6 +10,7 @@ import ( "github.com/mmcloughlin/avo/internal/gen" "github.com/mmcloughlin/avo/internal/inst" "github.com/mmcloughlin/avo/internal/load" + "github.com/mmcloughlin/avo/printer" ) var generators = map[string]gen.Builder{ @@ -45,9 +46,7 @@ func main() { log.Fatalf("unknown generator type '%s'", t) } - g := builder(gen.Config{ - Argv: os.Args, - }) + g := builder(printer.NewDefaultConfig()) // Determine output writer. w := os.Stdout diff --git a/internal/gen/asmtest.go b/internal/gen/asmtest.go index f97818b..ee43565 100644 --- a/internal/gen/asmtest.go +++ b/internal/gen/asmtest.go @@ -7,17 +7,19 @@ import ( "strings" "github.com/mmcloughlin/avo/internal/inst" + "github.com/mmcloughlin/avo/internal/prnt" + "github.com/mmcloughlin/avo/printer" ) type asmtest struct { - cfg Config + cfg printer.Config sym string // reference to the test function symbol rel8 string // label to be used for near jumps rel32 string // label for far jumps - generator + prnt.Generator } -func NewAsmTest(cfg Config) Interface { +func NewAsmTest(cfg printer.Config) Interface { return &asmtest{cfg: cfg} } diff --git a/internal/gen/build.go b/internal/gen/build.go index d77786c..0b6c4b4 100644 --- a/internal/gen/build.go +++ b/internal/gen/build.go @@ -1,13 +1,17 @@ package gen -import "github.com/mmcloughlin/avo/internal/inst" +import ( + "github.com/mmcloughlin/avo/internal/inst" + "github.com/mmcloughlin/avo/internal/prnt" + "github.com/mmcloughlin/avo/printer" +) type build struct { - cfg Config - generator + cfg printer.Config + prnt.Generator } -func NewBuild(cfg Config) Interface { +func NewBuild(cfg printer.Config) Interface { return GoFmt(&build{cfg: cfg}) } diff --git a/internal/gen/ctors.go b/internal/gen/ctors.go index 4af5304..af50351 100644 --- a/internal/gen/ctors.go +++ b/internal/gen/ctors.go @@ -6,14 +6,16 @@ import ( "strings" "github.com/mmcloughlin/avo/internal/inst" + "github.com/mmcloughlin/avo/internal/prnt" + "github.com/mmcloughlin/avo/printer" ) type ctors struct { - cfg Config - generator + cfg printer.Config + prnt.Generator } -func NewCtors(cfg Config) Interface { +func NewCtors(cfg printer.Config) Interface { return GoFmt(&ctors{cfg: cfg}) } diff --git a/internal/gen/ctorstest.go b/internal/gen/ctorstest.go index 9bc653e..d117e8e 100644 --- a/internal/gen/ctorstest.go +++ b/internal/gen/ctorstest.go @@ -3,15 +3,18 @@ package gen import ( "strings" + "github.com/mmcloughlin/avo/internal/prnt" + "github.com/mmcloughlin/avo/printer" + "github.com/mmcloughlin/avo/internal/inst" ) type ctorstest struct { - cfg Config - generator + cfg printer.Config + prnt.Generator } -func NewCtorsTest(cfg Config) Interface { +func NewCtorsTest(cfg printer.Config) Interface { return GoFmt(&ctorstest{cfg: cfg}) } diff --git a/internal/gen/gen.go b/internal/gen/gen.go index 76fd2a1..c202b83 100644 --- a/internal/gen/gen.go +++ b/internal/gen/gen.go @@ -1,12 +1,10 @@ package gen import ( - "bytes" - "fmt" "go/format" - "strings" "github.com/mmcloughlin/avo/internal/inst" + "github.com/mmcloughlin/avo/printer" ) type Interface interface { @@ -19,23 +17,7 @@ func (f Func) Generate(is []inst.Instruction) ([]byte, error) { return f(is) } -type Config struct { - Name string - Argv []string -} - -func (c Config) GeneratedBy() string { - if c.Argv == nil { - return c.Name - } - return fmt.Sprintf("command: %s", strings.Join(c.Argv, " ")) -} - -func (c Config) GeneratedWarning() string { - return fmt.Sprintf("Code generated by %s. DO NOT EDIT.", c.GeneratedBy()) -} - -type Builder func(Config) Interface +type Builder func(printer.Config) Interface // GoFmt formats Go code produced from the given generator. func GoFmt(i Interface) Interface { @@ -47,27 +29,3 @@ func GoFmt(i Interface) Interface { return format.Source(b) }) } - -type generator struct { - buf bytes.Buffer - err error -} - -func (g *generator) Printf(format string, args ...interface{}) { - if g.err != nil { - return - } - if _, err := fmt.Fprintf(&g.buf, format, args...); err != nil { - g.AddError(err) - } -} - -func (g *generator) AddError(err error) { - if err != nil && g.err == nil { - g.err = err - } -} - -func (g *generator) Result() ([]byte, error) { - return g.buf.Bytes(), g.err -} diff --git a/internal/gen/godata.go b/internal/gen/godata.go index ba083a8..221c374 100644 --- a/internal/gen/godata.go +++ b/internal/gen/godata.go @@ -2,14 +2,16 @@ package gen import ( "github.com/mmcloughlin/avo/internal/inst" + "github.com/mmcloughlin/avo/internal/prnt" + "github.com/mmcloughlin/avo/printer" ) type godata struct { - cfg Config - generator + cfg printer.Config + prnt.Generator } -func NewGoData(cfg Config) Interface { +func NewGoData(cfg printer.Config) Interface { return GoFmt(&godata{cfg: cfg}) } @@ -65,11 +67,11 @@ func (g *godata) Generate(is []inst.Instruction) ([]byte, error) { } type godatatest struct { - cfg Config - generator + cfg printer.Config + prnt.Generator } -func NewGoDataTest(cfg Config) Interface { +func NewGoDataTest(cfg printer.Config) Interface { return GoFmt(&godatatest{cfg: cfg}) } diff --git a/internal/gen/mov.go b/internal/gen/mov.go index 26b64fa..3e0f56d 100644 --- a/internal/gen/mov.go +++ b/internal/gen/mov.go @@ -7,14 +7,16 @@ import ( "strings" "github.com/mmcloughlin/avo/internal/inst" + "github.com/mmcloughlin/avo/internal/prnt" + "github.com/mmcloughlin/avo/printer" ) type mov struct { - cfg Config - generator + cfg printer.Config + prnt.Generator } -func NewMOV(cfg Config) Interface { +func NewMOV(cfg printer.Config) Interface { return GoFmt(&mov{cfg: cfg}) } diff --git a/internal/prnt/printer.go b/internal/prnt/printer.go new file mode 100644 index 0000000..33a9a2d --- /dev/null +++ b/internal/prnt/printer.go @@ -0,0 +1,40 @@ +package prnt + +import ( + "bytes" + "fmt" +) + +type Generator struct { + buf bytes.Buffer + err error +} + +func (g *Generator) Printf(format string, args ...interface{}) { + if g.err != nil { + return + } + if _, err := fmt.Fprintf(&g.buf, format, args...); err != nil { + g.AddError(err) + } +} + +func (g *Generator) NL() { + g.Printf("\n") +} + +func (g *Generator) Comment(lines ...string) { + for _, line := range lines { + g.Printf("// %s\n", line) + } +} + +func (g *Generator) AddError(err error) { + if err != nil && g.err == nil { + g.err = err + } +} + +func (g *Generator) Result() ([]byte, error) { + return g.buf.Bytes(), g.err +} diff --git a/pass/pass.go b/pass/pass.go index e60df46..b9e2a9f 100644 --- a/pass/pass.go +++ b/pass/pass.go @@ -1,6 +1,11 @@ package pass -import "github.com/mmcloughlin/avo" +import ( + "io" + + "github.com/mmcloughlin/avo" + "github.com/mmcloughlin/avo/printer" +) var Compile = Concat( FunctionPass(LabelTarget), @@ -42,3 +47,19 @@ func Concat(passes ...Interface) Interface { return nil }) } + +type Output struct { + Writer io.WriteCloser + Printer printer.Printer +} + +func (o *Output) Execute(f *avo.File) error { + b, err := o.Printer.Print(f) + if err != nil { + return err + } + if _, err = o.Writer.Write(b); err != nil { + return err + } + return o.Writer.Close() +} diff --git a/printer.go b/printer.go deleted file mode 100644 index 3ba5743..0000000 --- a/printer.go +++ /dev/null @@ -1,102 +0,0 @@ -package avo - -import ( - "fmt" - "io" - "strings" - - "github.com/mmcloughlin/avo/operand" -) - -// dot is the pesky unicode dot used in Go assembly. -const dot = "\u00b7" - -type Printer interface { - Print(*File) error -} - -type GoPrinter struct { - w io.Writer - by string // generated by - err error -} - -func NewGoPrinter(w io.Writer) *GoPrinter { - return &GoPrinter{ - w: w, - by: "avo", - } -} - -func (p *GoPrinter) SetGeneratedBy(by string) { - p.by = by -} - -func (p *GoPrinter) Print(f *File) error { - p.header() - - for _, fn := range f.Functions { - p.function(fn) - } - - return p.err -} - -func (p *GoPrinter) header() { - p.generated() - p.nl() - p.incl("textflag.h") - p.nl() -} - -func (p *GoPrinter) generated() { - p.comment(fmt.Sprintf("Code generated by %s. DO NOT EDIT.", p.by)) -} - -func (p *GoPrinter) incl(path string) { - p.printf("#include \"%s\"\n", path) -} - -func (p *GoPrinter) comment(line string) { - p.multicomment([]string{line}) -} - -func (p *GoPrinter) multicomment(lines []string) { - for _, line := range lines { - p.printf("// %s\n", line) - } -} - -func (p *GoPrinter) function(f *Function) { - p.printf("// %s\n", f.Stub()) - p.printf("TEXT %s%s(SB),0,$%d-%d\n", dot, f.Name, f.FrameBytes(), f.ArgumentBytes()) - - for _, node := range f.Nodes { - switch n := node.(type) { - case *Instruction: - p.printf("\t%s\t%s\n", n.Opcode, joinOperands(n.Operands)) - case Label: - p.printf("%s:\n", n) - default: - panic("unexpected node type") - } - } -} - -func (p *GoPrinter) nl() { - p.printf("\n") -} - -func (p *GoPrinter) printf(format string, args ...interface{}) { - if _, err := fmt.Fprintf(p.w, format, args...); err != nil { - p.err = err - } -} - -func joinOperands(operands []operand.Op) string { - asm := make([]string, len(operands)) - for i, op := range operands { - asm[i] = op.Asm() - } - return strings.Join(asm, ", ") -} diff --git a/printer/goasm.go b/printer/goasm.go new file mode 100644 index 0000000..8c615cb --- /dev/null +++ b/printer/goasm.go @@ -0,0 +1,63 @@ +package printer + +import ( + "strings" + + "github.com/mmcloughlin/avo" + "github.com/mmcloughlin/avo/internal/prnt" + "github.com/mmcloughlin/avo/operand" +) + +// dot is the pesky unicode dot used in Go assembly. +const dot = "\u00b7" + +type goasm struct { + cfg Config + prnt.Generator +} + +func NewGoAsm(cfg Config) Printer { + return &goasm{cfg: cfg} +} + +func (p *goasm) Print(f *avo.File) ([]byte, error) { + p.header() + for _, fn := range f.Functions { + p.function(fn) + } + return p.Result() +} + +func (p *goasm) header() { + p.NL() + p.include("textflag.h") + p.NL() +} + +func (p *goasm) include(path string) { + p.Printf("#include \"%s\"\n", path) +} + +func (p *goasm) function(f *avo.Function) { + p.Comment(f.Stub()) + p.Printf("TEXT %s%s(SB),0,$%d-%d\n", dot, f.Name, f.FrameBytes(), f.ArgumentBytes()) + + for _, node := range f.Nodes { + switch n := node.(type) { + case *avo.Instruction: + p.Printf("\t%s\t%s\n", n.Opcode, joinOperands(n.Operands)) + case avo.Label: + p.Printf("%s:\n", n) + default: + panic("unexpected node type") + } + } +} + +func joinOperands(operands []operand.Op) string { + asm := make([]string, len(operands)) + for i, op := range operands { + asm[i] = op.Asm() + } + return strings.Join(asm, ", ") +} diff --git a/printer/printer.go b/printer/printer.go new file mode 100644 index 0000000..7b1ee9b --- /dev/null +++ b/printer/printer.go @@ -0,0 +1,43 @@ +package printer + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/mmcloughlin/avo" +) + +type Printer interface { + Print(*avo.File) ([]byte, error) +} + +type Builder func(Config) Printer + +type Config struct { + Name string + Argv []string + Pkg string +} + +func NewDefaultConfig() Config { + cfg := Config{ + Argv: os.Args, + } + if cwd, err := os.Getwd(); err == nil { + cfg.Pkg = filepath.Base(cwd) + } + return cfg +} + +func (c Config) GeneratedBy() string { + if c.Argv == nil { + return c.Name + } + return fmt.Sprintf("command: %s", strings.Join(c.Argv, " ")) +} + +func (c Config) GeneratedWarning() string { + return fmt.Sprintf("Code generated by %s. DO NOT EDIT.", c.GeneratedBy()) +} diff --git a/printer/stubs.go b/printer/stubs.go new file mode 100644 index 0000000..9e7b518 --- /dev/null +++ b/printer/stubs.go @@ -0,0 +1,23 @@ +package printer + +import ( + "github.com/mmcloughlin/avo" + "github.com/mmcloughlin/avo/internal/prnt" +) + +type stubs struct { + cfg Config + prnt.Generator +} + +func NewStubs(cfg Config) Printer { + return &stubs{cfg: cfg} +} + +func (s *stubs) Print(f *avo.File) ([]byte, error) { + s.Printf("package %s\n\n", s.cfg.Pkg) + for _, fn := range f.Functions { + s.Printf("%s\n", fn.Stub()) + } + return s.Result() +}