| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // +build gen |
| |
| // This program generates Go code that applies rewrite rules to a Value. |
| // The generated code implements a function of type func (v *Value) bool |
| // which reports whether if did something. |
| // Ideas stolen from Swift: http://www.hpl.hp.com/techreports/Compaq-DEC/WRL-2000-2.html |
| |
| package main |
| |
| import ( |
| "bufio" |
| "bytes" |
| "flag" |
| "fmt" |
| "go/ast" |
| "go/format" |
| "go/parser" |
| "go/printer" |
| "go/token" |
| "go/types" |
| "io" |
| "log" |
| "os" |
| "regexp" |
| "sort" |
| "strings" |
| |
| "golang.org/x/tools/go/ast/astutil" |
| ) |
| |
| // rule syntax: |
| // sexpr [&& extra conditions] -> [@block] sexpr |
| // |
| // sexpr are s-expressions (lisp-like parenthesized groupings) |
| // sexpr ::= [variable:](opcode sexpr*) |
| // | variable |
| // | <type> |
| // | [auxint] |
| // | {aux} |
| // |
| // aux ::= variable | {code} |
| // type ::= variable | {code} |
| // variable ::= some token |
| // opcode ::= one of the opcodes from the *Ops.go files |
| |
| // extra conditions is just a chunk of Go that evaluates to a boolean. It may use |
| // variables declared in the matching sexpr. The variable "v" is predefined to be |
| // the value matched by the entire rule. |
| |
| // If multiple rules match, the first one in file order is selected. |
| |
| var genLog = flag.Bool("log", false, "generate code that logs; for debugging only") |
| |
| type Rule struct { |
| rule string |
| loc string // file name & line number |
| } |
| |
| func (r Rule) String() string { |
| return fmt.Sprintf("rule %q at %s", r.rule, r.loc) |
| } |
| |
| func normalizeSpaces(s string) string { |
| return strings.Join(strings.Fields(strings.TrimSpace(s)), " ") |
| } |
| |
| // parse returns the matching part of the rule, additional conditions, and the result. |
| func (r Rule) parse() (match, cond, result string) { |
| s := strings.Split(r.rule, "->") |
| if len(s) != 2 { |
| log.Fatalf("no arrow in %s", r) |
| } |
| match = normalizeSpaces(s[0]) |
| result = normalizeSpaces(s[1]) |
| cond = "" |
| if i := strings.Index(match, "&&"); i >= 0 { |
| cond = normalizeSpaces(match[i+2:]) |
| match = normalizeSpaces(match[:i]) |
| } |
| return match, cond, result |
| } |
| |
| func genRules(arch arch) { genRulesSuffix(arch, "") } |
| func genSplitLoadRules(arch arch) { genRulesSuffix(arch, "splitload") } |
| |
| func genRulesSuffix(arch arch, suff string) { |
| // Open input file. |
| text, err := os.Open(arch.name + suff + ".rules") |
| if err != nil { |
| if suff == "" { |
| // All architectures must have a plain rules file. |
| log.Fatalf("can't read rule file: %v", err) |
| } |
| // Some architectures have bonus rules files that others don't share. That's fine. |
| return |
| } |
| |
| // oprules contains a list of rules for each block and opcode |
| blockrules := map[string][]Rule{} |
| oprules := map[string][]Rule{} |
| |
| // read rule file |
| scanner := bufio.NewScanner(text) |
| rule := "" |
| var lineno int |
| var ruleLineno int // line number of "->" |
| for scanner.Scan() { |
| lineno++ |
| line := scanner.Text() |
| if i := strings.Index(line, "//"); i >= 0 { |
| // Remove comments. Note that this isn't string safe, so |
| // it will truncate lines with // inside strings. Oh well. |
| line = line[:i] |
| } |
| rule += " " + line |
| rule = strings.TrimSpace(rule) |
| if rule == "" { |
| continue |
| } |
| if !strings.Contains(rule, "->") { |
| continue |
| } |
| if ruleLineno == 0 { |
| ruleLineno = lineno |
| } |
| if strings.HasSuffix(rule, "->") { |
| continue |
| } |
| if unbalanced(rule) { |
| continue |
| } |
| |
| loc := fmt.Sprintf("%s%s.rules:%d", arch.name, suff, ruleLineno) |
| for _, rule2 := range expandOr(rule) { |
| for _, rule3 := range commute(rule2, arch) { |
| r := Rule{rule: rule3, loc: loc} |
| if rawop := strings.Split(rule3, " ")[0][1:]; isBlock(rawop, arch) { |
| blockrules[rawop] = append(blockrules[rawop], r) |
| continue |
| } |
| // Do fancier value op matching. |
| match, _, _ := r.parse() |
| op, oparch, _, _, _, _ := parseValue(match, arch, loc) |
| opname := fmt.Sprintf("Op%s%s", oparch, op.name) |
| oprules[opname] = append(oprules[opname], r) |
| } |
| } |
| rule = "" |
| ruleLineno = 0 |
| } |
| if err := scanner.Err(); err != nil { |
| log.Fatalf("scanner failed: %v\n", err) |
| } |
| if unbalanced(rule) { |
| log.Fatalf("%s.rules:%d: unbalanced rule: %v\n", arch.name, lineno, rule) |
| } |
| |
| // Order all the ops. |
| var ops []string |
| for op := range oprules { |
| ops = append(ops, op) |
| } |
| sort.Strings(ops) |
| |
| genFile := &File{arch: arch, suffix: suff} |
| const chunkSize = 10 |
| // Main rewrite routine is a switch on v.Op. |
| fn := &Func{kind: "Value"} |
| |
| sw := &Switch{expr: exprf("v.Op")} |
| for _, op := range ops { |
| var ors []string |
| for chunk := 0; chunk < len(oprules[op]); chunk += chunkSize { |
| ors = append(ors, fmt.Sprintf("rewriteValue%s%s_%s_%d(v)", arch.name, suff, op, chunk)) |
| } |
| swc := &Case{expr: exprf(op)} |
| swc.add(stmtf("return %s", strings.Join(ors, " || "))) |
| sw.add(swc) |
| } |
| fn.add(sw) |
| fn.add(stmtf("return false")) |
| genFile.add(fn) |
| |
| // Generate a routine per op. Note that we don't make one giant routine |
| // because it is too big for some compilers. |
| for _, op := range ops { |
| rules := oprules[op] |
| // rr is kept between chunks, so that a following chunk checks |
| // that the previous one ended with a rule that wasn't |
| // unconditional. |
| var rr *RuleRewrite |
| for chunk := 0; chunk < len(rules); chunk += chunkSize { |
| endchunk := chunk + chunkSize |
| if endchunk > len(rules) { |
| endchunk = len(rules) |
| } |
| fn := &Func{ |
| kind: "Value", |
| suffix: fmt.Sprintf("_%s_%d", op, chunk), |
| } |
| fn.add(declf("b", "v.Block")) |
| fn.add(declf("config", "b.Func.Config")) |
| fn.add(declf("fe", "b.Func.fe")) |
| fn.add(declf("typ", "&b.Func.Config.Types")) |
| for _, rule := range rules[chunk:endchunk] { |
| if rr != nil && !rr.canFail { |
| log.Fatalf("unconditional rule %s is followed by other rules", rr.match) |
| } |
| rr = &RuleRewrite{loc: rule.loc} |
| rr.match, rr.cond, rr.result = rule.parse() |
| pos, _ := genMatch(rr, arch, rr.match) |
| if pos == "" { |
| pos = "v.Pos" |
| } |
| if rr.cond != "" { |
| rr.add(breakf("!(%s)", rr.cond)) |
| } |
| genResult(rr, arch, rr.result, pos) |
| if *genLog { |
| rr.add(stmtf("logRule(%q)", rule.loc)) |
| } |
| fn.add(rr) |
| } |
| if rr.canFail { |
| fn.add(stmtf("return false")) |
| } |
| genFile.add(fn) |
| } |
| } |
| |
| // Generate block rewrite function. There are only a few block types |
| // so we can make this one function with a switch. |
| fn = &Func{kind: "Block"} |
| fn.add(declf("config", "b.Func.Config")) |
| fn.add(declf("typ", "&config.Types")) |
| fn.add(declf("v", "b.Control")) |
| |
| sw = &Switch{expr: exprf("b.Kind")} |
| ops = ops[:0] |
| for op := range blockrules { |
| ops = append(ops, op) |
| } |
| sort.Strings(ops) |
| for _, op := range ops { |
| swc := &Case{expr: exprf("%s", blockName(op, arch))} |
| for _, rule := range blockrules[op] { |
| swc.add(genBlockRewrite(rule, arch)) |
| } |
| sw.add(swc) |
| } |
| fn.add(sw) |
| fn.add(stmtf("return false")) |
| genFile.add(fn) |
| |
| // Remove unused imports and variables. |
| buf := new(bytes.Buffer) |
| fprint(buf, genFile) |
| fset := token.NewFileSet() |
| file, err := parser.ParseFile(fset, "", buf, parser.ParseComments) |
| if err != nil { |
| log.Fatal(err) |
| } |
| tfile := fset.File(file.Pos()) |
| |
| for n := 0; n < 3; n++ { |
| unused := make(map[token.Pos]bool) |
| conf := types.Config{Error: func(err error) { |
| if terr, ok := err.(types.Error); ok && strings.Contains(terr.Msg, "not used") { |
| unused[terr.Pos] = true |
| } |
| }} |
| _, _ = conf.Check("ssa", fset, []*ast.File{file}, nil) |
| if len(unused) == 0 { |
| break |
| } |
| pre := func(c *astutil.Cursor) bool { |
| if node := c.Node(); node != nil && unused[node.Pos()] { |
| c.Delete() |
| // Unused imports and declarations use exactly |
| // one line. Prevent leaving an empty line. |
| tfile.MergeLine(tfile.Position(node.Pos()).Line) |
| return false |
| } |
| return true |
| } |
| post := func(c *astutil.Cursor) bool { |
| switch node := c.Node().(type) { |
| case *ast.GenDecl: |
| if len(node.Specs) == 0 { |
| c.Delete() |
| } |
| } |
| return true |
| } |
| file = astutil.Apply(file, pre, post).(*ast.File) |
| } |
| |
| // Write the well-formatted source to file |
| f, err := os.Create("../rewrite" + arch.name + suff + ".go") |
| if err != nil { |
| log.Fatalf("can't write output: %v", err) |
| } |
| defer f.Close() |
| // gofmt result; use a buffered writer, as otherwise go/format spends |
| // far too much time in syscalls. |
| bw := bufio.NewWriter(f) |
| if err := format.Node(bw, fset, file); err != nil { |
| log.Fatalf("can't format output: %v", err) |
| } |
| if err := bw.Flush(); err != nil { |
| log.Fatalf("can't write output: %v", err) |
| } |
| if err := f.Close(); err != nil { |
| log.Fatalf("can't write output: %v", err) |
| } |
| } |
| |
| func fprint(w io.Writer, n Node) { |
| switch n := n.(type) { |
| case *File: |
| fmt.Fprintf(w, "// Code generated from gen/%s%s.rules; DO NOT EDIT.\n", n.arch.name, n.suffix) |
| fmt.Fprintf(w, "// generated with: cd gen; go run *.go\n") |
| fmt.Fprintf(w, "\npackage ssa\n") |
| for _, path := range []string{ |
| "fmt", "math", |
| "cmd/internal/obj", "cmd/internal/objabi", |
| "cmd/compile/internal/types", |
| } { |
| fmt.Fprintf(w, "import %q\n", path) |
| } |
| for _, f := range n.list { |
| f := f.(*Func) |
| fmt.Fprintf(w, "func rewrite%s%s%s%s(", f.kind, n.arch.name, n.suffix, f.suffix) |
| fmt.Fprintf(w, "%c *%s) bool {\n", strings.ToLower(f.kind)[0], f.kind) |
| for _, n := range f.list { |
| fprint(w, n) |
| } |
| fmt.Fprintf(w, "}\n") |
| } |
| case *Switch: |
| fmt.Fprintf(w, "switch ") |
| fprint(w, n.expr) |
| fmt.Fprintf(w, " {\n") |
| for _, n := range n.list { |
| fprint(w, n) |
| } |
| fmt.Fprintf(w, "}\n") |
| case *Case: |
| fmt.Fprintf(w, "case ") |
| fprint(w, n.expr) |
| fmt.Fprintf(w, ":\n") |
| for _, n := range n.list { |
| fprint(w, n) |
| } |
| case *RuleRewrite: |
| fmt.Fprintf(w, "// match: %s\n", n.match) |
| fmt.Fprintf(w, "// cond: %s\n", n.cond) |
| fmt.Fprintf(w, "// result: %s\n", n.result) |
| if n.checkOp != "" { |
| fmt.Fprintf(w, "for v.Op == %s {\n", n.checkOp) |
| } else { |
| fmt.Fprintf(w, "for {\n") |
| } |
| for _, n := range n.list { |
| fprint(w, n) |
| } |
| fmt.Fprintf(w, "return true\n}\n") |
| case *Declare: |
| fmt.Fprintf(w, "%s := ", n.name) |
| fprint(w, n.value) |
| fmt.Fprintln(w) |
| case *CondBreak: |
| fmt.Fprintf(w, "if ") |
| fprint(w, n.expr) |
| fmt.Fprintf(w, " {\nbreak\n}\n") |
| case ast.Node: |
| printer.Fprint(w, emptyFset, n) |
| if _, ok := n.(ast.Stmt); ok { |
| fmt.Fprintln(w) |
| } |
| default: |
| log.Fatalf("cannot print %T", n) |
| } |
| } |
| |
| var emptyFset = token.NewFileSet() |
| |
| // Node can be a Statement or an ast.Expr. |
| type Node interface{} |
| |
| // Statement can be one of our high-level statement struct types, or an |
| // ast.Stmt under some limited circumstances. |
| type Statement interface{} |
| |
| // bodyBase is shared by all of our statement psuedo-node types which can |
| // contain other statements. |
| type bodyBase struct { |
| list []Statement |
| canFail bool |
| } |
| |
| func (w *bodyBase) body() []Statement { return w.list } |
| func (w *bodyBase) add(nodes ...Statement) { |
| w.list = append(w.list, nodes...) |
| for _, node := range nodes { |
| if _, ok := node.(*CondBreak); ok { |
| w.canFail = true |
| } |
| } |
| } |
| |
| // declared reports if the body contains a Declare with the given name. |
| func (w *bodyBase) declared(name string) bool { |
| for _, s := range w.list { |
| if decl, ok := s.(*Declare); ok && decl.name == name { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // These types define some high-level statement struct types, which can be used |
| // as a Statement. This allows us to keep some node structs simpler, and have |
| // higher-level nodes such as an entire rule rewrite. |
| // |
| // Note that ast.Expr is always used as-is; we don't declare our own expression |
| // nodes. |
| type ( |
| File struct { |
| bodyBase // []*Func |
| arch arch |
| suffix string |
| } |
| Func struct { |
| bodyBase |
| kind string // "Value" or "Block" |
| suffix string |
| } |
| Switch struct { |
| bodyBase // []*Case |
| expr ast.Expr |
| } |
| Case struct { |
| bodyBase |
| expr ast.Expr |
| } |
| RuleRewrite struct { |
| bodyBase |
| match, cond, result string // top comments |
| checkOp string |
| |
| alloc int // for unique var names |
| loc string // file name & line number of the original rule |
| } |
| Declare struct { |
| name string |
| value ast.Expr |
| } |
| CondBreak struct { |
| expr ast.Expr |
| } |
| ) |
| |
| // exprf parses a Go expression generated from fmt.Sprintf, panicking if an |
| // error occurs. |
| func exprf(format string, a ...interface{}) ast.Expr { |
| src := fmt.Sprintf(format, a...) |
| expr, err := parser.ParseExpr(src) |
| if err != nil { |
| log.Fatalf("expr parse error on %q: %v", src, err) |
| } |
| return expr |
| } |
| |
| // stmtf parses a Go statement generated from fmt.Sprintf. This function is only |
| // meant for simple statements that don't have a custom Statement node declared |
| // in this package, such as ast.ReturnStmt or ast.ExprStmt. |
| func stmtf(format string, a ...interface{}) Statement { |
| src := fmt.Sprintf(format, a...) |
| fsrc := "package p\nfunc _() {\n" + src + "\n}\n" |
| file, err := parser.ParseFile(token.NewFileSet(), "", fsrc, 0) |
| if err != nil { |
| log.Fatalf("stmt parse error on %q: %v", src, err) |
| } |
| return file.Decls[0].(*ast.FuncDecl).Body.List[0] |
| } |
| |
| // declf constructs a simple "name := value" declaration, using exprf for its |
| // value. |
| func declf(name, format string, a ...interface{}) *Declare { |
| return &Declare{name, exprf(format, a...)} |
| } |
| |
| // breakf constructs a simple "if cond { break }" statement, using exprf for its |
| // condition. |
| func breakf(format string, a ...interface{}) *CondBreak { |
| return &CondBreak{exprf(format, a...)} |
| } |
| |
| func genBlockRewrite(rule Rule, arch arch) *RuleRewrite { |
| rr := &RuleRewrite{loc: rule.loc} |
| rr.match, rr.cond, rr.result = rule.parse() |
| _, _, _, aux, s := extract(rr.match) // remove parens, then split |
| |
| // check match of control value |
| pos := "" |
| if s[0] != "nil" { |
| if strings.Contains(s[0], "(") { |
| pos, rr.checkOp = genMatch0(rr, arch, s[0], "v") |
| } else { |
| rr.add(declf(s[0], "b.Control")) |
| } |
| } |
| if aux != "" { |
| rr.add(declf(aux, "b.Aux")) |
| } |
| if rr.cond != "" { |
| rr.add(breakf("!(%s)", rr.cond)) |
| } |
| |
| // Rule matches. Generate result. |
| outop, _, _, aux, t := extract(rr.result) // remove parens, then split |
| newsuccs := t[1:] |
| |
| // Check if newsuccs is the same set as succs. |
| succs := s[1:] |
| m := map[string]bool{} |
| for _, succ := range succs { |
| if m[succ] { |
| log.Fatalf("can't have a repeat successor name %s in %s", succ, rule) |
| } |
| m[succ] = true |
| } |
| for _, succ := range newsuccs { |
| if !m[succ] { |
| log.Fatalf("unknown successor %s in %s", succ, rule) |
| } |
| delete(m, succ) |
| } |
| if len(m) != 0 { |
| log.Fatalf("unmatched successors %v in %s", m, rule) |
| } |
| |
| rr.add(stmtf("b.Kind = %s", blockName(outop, arch))) |
| if t[0] == "nil" { |
| rr.add(stmtf("b.SetControl(nil)")) |
| } else { |
| if pos == "" { |
| pos = "v.Pos" |
| } |
| v := genResult0(rr, arch, t[0], false, false, pos) |
| rr.add(stmtf("b.SetControl(%s)", v)) |
| } |
| if aux != "" { |
| rr.add(stmtf("b.Aux = %s", aux)) |
| } else { |
| rr.add(stmtf("b.Aux = nil")) |
| } |
| |
| succChanged := false |
| for i := 0; i < len(succs); i++ { |
| if succs[i] != newsuccs[i] { |
| succChanged = true |
| } |
| } |
| if succChanged { |
| if len(succs) != 2 { |
| log.Fatalf("changed successors, len!=2 in %s", rule) |
| } |
| if succs[0] != newsuccs[1] || succs[1] != newsuccs[0] { |
| log.Fatalf("can only handle swapped successors in %s", rule) |
| } |
| rr.add(stmtf("b.swapSuccessors()")) |
| } |
| |
| if *genLog { |
| rr.add(stmtf("logRule(%q)", rule.loc)) |
| } |
| return rr |
| } |
| |
| // genMatch returns the variable whose source position should be used for the |
| // result (or "" if no opinion), and a boolean that reports whether the match can fail. |
| func genMatch(rr *RuleRewrite, arch arch, match string) (pos, checkOp string) { |
| return genMatch0(rr, arch, match, "v") |
| } |
| |
| func genMatch0(rr *RuleRewrite, arch arch, match, v string) (pos, checkOp string) { |
| if match[0] != '(' || match[len(match)-1] != ')' { |
| log.Fatalf("non-compound expr in genMatch0: %q", match) |
| } |
| op, oparch, typ, auxint, aux, args := parseValue(match, arch, rr.loc) |
| |
| checkOp = fmt.Sprintf("Op%s%s", oparch, op.name) |
| |
| if op.faultOnNilArg0 || op.faultOnNilArg1 { |
| // Prefer the position of an instruction which could fault. |
| pos = v + ".Pos" |
| } |
| |
| if typ != "" { |
| if !token.IsIdentifier(typ) || rr.declared(typ) { |
| // code or variable |
| rr.add(breakf("%s.Type != %s", v, typ)) |
| } else { |
| rr.add(declf(typ, "%s.Type", v)) |
| } |
| } |
| if auxint != "" { |
| if !token.IsIdentifier(auxint) || rr.declared(auxint) { |
| // code or variable |
| rr.add(breakf("%s.AuxInt != %s", v, auxint)) |
| } else { |
| rr.add(declf(auxint, "%s.AuxInt", v)) |
| } |
| } |
| if aux != "" { |
| if !token.IsIdentifier(aux) || rr.declared(aux) { |
| // code or variable |
| rr.add(breakf("%s.Aux != %s", v, aux)) |
| } else { |
| rr.add(declf(aux, "%s.Aux", v)) |
| } |
| } |
| |
| // Access last argument first to minimize bounds checks. |
| if n := len(args); n > 1 { |
| a := args[n-1] |
| if a != "_" && !rr.declared(a) && token.IsIdentifier(a) { |
| rr.add(declf(a, "%s.Args[%d]", v, n-1)) |
| |
| // delete the last argument so it is not reprocessed |
| args = args[:n-1] |
| } else { |
| rr.add(stmtf("_ = %s.Args[%d]", v, n-1)) |
| } |
| } |
| for i, arg := range args { |
| if arg == "_" { |
| continue |
| } |
| if !strings.Contains(arg, "(") { |
| // leaf variable |
| if rr.declared(arg) { |
| // variable already has a definition. Check whether |
| // the old definition and the new definition match. |
| // For example, (add x x). Equality is just pointer equality |
| // on Values (so cse is important to do before lowering). |
| rr.add(breakf("%s != %s.Args[%d]", arg, v, i)) |
| } else { |
| rr.add(declf(arg, "%s.Args[%d]", v, i)) |
| } |
| continue |
| } |
| // compound sexpr |
| argname := fmt.Sprintf("%s_%d", v, i) |
| colon := strings.Index(arg, ":") |
| openparen := strings.Index(arg, "(") |
| if colon >= 0 && openparen >= 0 && colon < openparen { |
| // rule-specified name |
| argname = arg[:colon] |
| arg = arg[colon+1:] |
| } |
| if argname == "b" { |
| log.Fatalf("don't name args 'b', it is ambiguous with blocks") |
| } |
| |
| rr.add(declf(argname, "%s.Args[%d]", v, i)) |
| bexpr := exprf("%s.Op != addLater", argname) |
| rr.add(&CondBreak{expr: bexpr}) |
| rr.canFail = true // since we're not using breakf |
| argPos, argCheckOp := genMatch0(rr, arch, arg, argname) |
| bexpr.(*ast.BinaryExpr).Y.(*ast.Ident).Name = argCheckOp |
| |
| if argPos != "" { |
| // Keep the argument in preference to the parent, as the |
| // argument is normally earlier in program flow. |
| // Keep the argument in preference to an earlier argument, |
| // as that prefers the memory argument which is also earlier |
| // in the program flow. |
| pos = argPos |
| } |
| } |
| |
| if op.argLength == -1 { |
| rr.add(breakf("len(%s.Args) != %d", v, len(args))) |
| } |
| return pos, checkOp |
| } |
| |
| func genResult(rr *RuleRewrite, arch arch, result, pos string) { |
| move := result[0] == '@' |
| if move { |
| // parse @block directive |
| s := strings.SplitN(result[1:], " ", 2) |
| rr.add(stmtf("b = %s", s[0])) |
| result = s[1] |
| } |
| genResult0(rr, arch, result, true, move, pos) |
| } |
| |
| func genResult0(rr *RuleRewrite, arch arch, result string, top, move bool, pos string) string { |
| // TODO: when generating a constant result, use f.constVal to avoid |
| // introducing copies just to clean them up again. |
| if result[0] != '(' { |
| // variable |
| if top { |
| // It in not safe in general to move a variable between blocks |
| // (and particularly not a phi node). |
| // Introduce a copy. |
| rr.add(stmtf("v.reset(OpCopy)")) |
| rr.add(stmtf("v.Type = %s.Type", result)) |
| rr.add(stmtf("v.AddArg(%s)", result)) |
| } |
| return result |
| } |
| |
| op, oparch, typ, auxint, aux, args := parseValue(result, arch, rr.loc) |
| |
| // Find the type of the variable. |
| typeOverride := typ != "" |
| if typ == "" && op.typ != "" { |
| typ = typeName(op.typ) |
| } |
| |
| v := "v" |
| if top && !move { |
| rr.add(stmtf("v.reset(Op%s%s)", oparch, op.name)) |
| if typeOverride { |
| rr.add(stmtf("v.Type = %s", typ)) |
| } |
| } else { |
| if typ == "" { |
| log.Fatalf("sub-expression %s (op=Op%s%s) at %s must have a type", result, oparch, op.name, rr.loc) |
| } |
| v = fmt.Sprintf("v%d", rr.alloc) |
| rr.alloc++ |
| rr.add(declf(v, "b.NewValue0(%s, Op%s%s, %s)", pos, oparch, op.name, typ)) |
| if move && top { |
| // Rewrite original into a copy |
| rr.add(stmtf("v.reset(OpCopy)")) |
| rr.add(stmtf("v.AddArg(%s)", v)) |
| } |
| } |
| |
| if auxint != "" { |
| rr.add(stmtf("%s.AuxInt = %s", v, auxint)) |
| } |
| if aux != "" { |
| rr.add(stmtf("%s.Aux = %s", v, aux)) |
| } |
| for _, arg := range args { |
| x := genResult0(rr, arch, arg, false, move, pos) |
| rr.add(stmtf("%s.AddArg(%s)", v, x)) |
| } |
| |
| return v |
| } |
| |
| func split(s string) []string { |
| var r []string |
| |
| outer: |
| for s != "" { |
| d := 0 // depth of ({[< |
| var open, close byte // opening and closing markers ({[< or )}]> |
| nonsp := false // found a non-space char so far |
| for i := 0; i < len(s); i++ { |
| switch { |
| case d == 0 && s[i] == '(': |
| open, close = '(', ')' |
| d++ |
| case d == 0 && s[i] == '<': |
| open, close = '<', '>' |
| d++ |
| case d == 0 && s[i] == '[': |
| open, close = '[', ']' |
| d++ |
| case d == 0 && s[i] == '{': |
| open, close = '{', '}' |
| d++ |
| case d == 0 && (s[i] == ' ' || s[i] == '\t'): |
| if nonsp { |
| r = append(r, strings.TrimSpace(s[:i])) |
| s = s[i:] |
| continue outer |
| } |
| case d > 0 && s[i] == open: |
| d++ |
| case d > 0 && s[i] == close: |
| d-- |
| default: |
| nonsp = true |
| } |
| } |
| if d != 0 { |
| log.Fatalf("imbalanced expression: %q", s) |
| } |
| if nonsp { |
| r = append(r, strings.TrimSpace(s)) |
| } |
| break |
| } |
| return r |
| } |
| |
| // isBlock reports whether this op is a block opcode. |
| func isBlock(name string, arch arch) bool { |
| for _, b := range genericBlocks { |
| if b.name == name { |
| return true |
| } |
| } |
| for _, b := range arch.blocks { |
| if b.name == name { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func extract(val string) (op, typ, auxint, aux string, args []string) { |
| val = val[1 : len(val)-1] // remove () |
| |
| // Split val up into regions. |
| // Split by spaces/tabs, except those contained in (), {}, [], or <>. |
| s := split(val) |
| |
| // Extract restrictions and args. |
| op = s[0] |
| for _, a := range s[1:] { |
| switch a[0] { |
| case '<': |
| typ = a[1 : len(a)-1] // remove <> |
| case '[': |
| auxint = a[1 : len(a)-1] // remove [] |
| case '{': |
| aux = a[1 : len(a)-1] // remove {} |
| default: |
| args = append(args, a) |
| } |
| } |
| return |
| } |
| |
| // parseValue parses a parenthesized value from a rule. |
| // The value can be from the match or the result side. |
| // It returns the op and unparsed strings for typ, auxint, and aux restrictions and for all args. |
| // oparch is the architecture that op is located in, or "" for generic. |
| func parseValue(val string, arch arch, loc string) (op opData, oparch, typ, auxint, aux string, args []string) { |
| // Resolve the op. |
| var s string |
| s, typ, auxint, aux, args = extract(val) |
| |
| // match reports whether x is a good op to select. |
| // If strict is true, rule generation might succeed. |
| // If strict is false, rule generation has failed, |
| // but we're trying to generate a useful error. |
| // Doing strict=true then strict=false allows |
| // precise op matching while retaining good error messages. |
| match := func(x opData, strict bool, archname string) bool { |
| if x.name != s { |
| return false |
| } |
| if x.argLength != -1 && int(x.argLength) != len(args) { |
| if strict { |
| return false |
| } |
| log.Printf("%s: op %s (%s) should have %d args, has %d", loc, s, archname, x.argLength, len(args)) |
| } |
| return true |
| } |
| |
| for _, x := range genericOps { |
| if match(x, true, "generic") { |
| op = x |
| break |
| } |
| } |
| for _, x := range arch.ops { |
| if arch.name != "generic" && match(x, true, arch.name) { |
| if op.name != "" { |
| log.Fatalf("%s: matches for op %s found in both generic and %s", loc, op.name, arch.name) |
| } |
| op = x |
| oparch = arch.name |
| break |
| } |
| } |
| |
| if op.name == "" { |
| // Failed to find the op. |
| // Run through everything again with strict=false |
| // to generate useful diagnosic messages before failing. |
| for _, x := range genericOps { |
| match(x, false, "generic") |
| } |
| for _, x := range arch.ops { |
| match(x, false, arch.name) |
| } |
| log.Fatalf("%s: unknown op %s", loc, s) |
| } |
| |
| // Sanity check aux, auxint. |
| if auxint != "" { |
| switch op.aux { |
| case "Bool", "Int8", "Int16", "Int32", "Int64", "Int128", "Float32", "Float64", "SymOff", "SymValAndOff", "TypSize": |
| default: |
| log.Fatalf("%s: op %s %s can't have auxint", loc, op.name, op.aux) |
| } |
| } |
| if aux != "" { |
| switch op.aux { |
| case "String", "Sym", "SymOff", "SymValAndOff", "Typ", "TypSize", "CCop": |
| default: |
| log.Fatalf("%s: op %s %s can't have aux", loc, op.name, op.aux) |
| } |
| } |
| return |
| } |
| |
| func blockName(name string, arch arch) string { |
| for _, b := range genericBlocks { |
| if b.name == name { |
| return "Block" + name |
| } |
| } |
| return "Block" + arch.name + name |
| } |
| |
| // typeName returns the string to use to generate a type. |
| func typeName(typ string) string { |
| if typ[0] == '(' { |
| ts := strings.Split(typ[1:len(typ)-1], ",") |
| if len(ts) != 2 { |
| log.Fatalf("Tuple expect 2 arguments") |
| } |
| return "types.NewTuple(" + typeName(ts[0]) + ", " + typeName(ts[1]) + ")" |
| } |
| switch typ { |
| case "Flags", "Mem", "Void", "Int128": |
| return "types.Type" + typ |
| default: |
| return "typ." + typ |
| } |
| } |
| |
| // unbalanced reports whether there aren't the same number of ( and ) in the string. |
| func unbalanced(s string) bool { |
| balance := 0 |
| for _, c := range s { |
| if c == '(' { |
| balance++ |
| } else if c == ')' { |
| balance-- |
| } |
| } |
| return balance != 0 |
| } |
| |
| // findAllOpcode is a function to find the opcode portion of s-expressions. |
| var findAllOpcode = regexp.MustCompile(`[(](\w+[|])+\w+[)]`).FindAllStringIndex |
| |
| // excludeFromExpansion reports whether the substring s[idx[0]:idx[1]] in a rule |
| // should be disregarded as a candidate for | expansion. |
| // It uses simple syntactic checks to see whether the substring |
| // is inside an AuxInt expression or inside the && conditions. |
| func excludeFromExpansion(s string, idx []int) bool { |
| left := s[:idx[0]] |
| if strings.LastIndexByte(left, '[') > strings.LastIndexByte(left, ']') { |
| // Inside an AuxInt expression. |
| return true |
| } |
| right := s[idx[1]:] |
| if strings.Contains(left, "&&") && strings.Contains(right, "->") { |
| // Inside && conditions. |
| return true |
| } |
| return false |
| } |
| |
| // expandOr converts a rule into multiple rules by expanding | ops. |
| func expandOr(r string) []string { |
| // Find every occurrence of |-separated things. |
| // They look like MOV(B|W|L|Q|SS|SD)load or MOV(Q|L)loadidx(1|8). |
| // Generate rules selecting one case from each |-form. |
| |
| // Count width of |-forms. They must match. |
| n := 1 |
| for _, idx := range findAllOpcode(r, -1) { |
| if excludeFromExpansion(r, idx) { |
| continue |
| } |
| s := r[idx[0]:idx[1]] |
| c := strings.Count(s, "|") + 1 |
| if c == 1 { |
| continue |
| } |
| if n > 1 && n != c { |
| log.Fatalf("'|' count doesn't match in %s: both %d and %d\n", r, n, c) |
| } |
| n = c |
| } |
| if n == 1 { |
| // No |-form in this rule. |
| return []string{r} |
| } |
| // Build each new rule. |
| res := make([]string, n) |
| for i := 0; i < n; i++ { |
| buf := new(strings.Builder) |
| x := 0 |
| for _, idx := range findAllOpcode(r, -1) { |
| if excludeFromExpansion(r, idx) { |
| continue |
| } |
| buf.WriteString(r[x:idx[0]]) // write bytes we've skipped over so far |
| s := r[idx[0]+1 : idx[1]-1] // remove leading "(" and trailing ")" |
| buf.WriteString(strings.Split(s, "|")[i]) // write the op component for this rule |
| x = idx[1] // note that we've written more bytes |
| } |
| buf.WriteString(r[x:]) |
| res[i] = buf.String() |
| } |
| return res |
| } |
| |
| // commute returns all equivalent rules to r after applying all possible |
| // argument swaps to the commutable ops in r. |
| // Potentially exponential, be careful. |
| func commute(r string, arch arch) []string { |
| match, cond, result := Rule{rule: r}.parse() |
| a := commute1(match, varCount(match), arch) |
| for i, m := range a { |
| if cond != "" { |
| m += " && " + cond |
| } |
| m += " -> " + result |
| a[i] = m |
| } |
| if len(a) == 1 && normalizeWhitespace(r) != normalizeWhitespace(a[0]) { |
| fmt.Println(normalizeWhitespace(r)) |
| fmt.Println(normalizeWhitespace(a[0])) |
| log.Fatalf("commute() is not the identity for noncommuting rule") |
| } |
| if false && len(a) > 1 { |
| fmt.Println(r) |
| for _, x := range a { |
| fmt.Println(" " + x) |
| } |
| } |
| return a |
| } |
| |
| func commute1(m string, cnt map[string]int, arch arch) []string { |
| if m[0] == '<' || m[0] == '[' || m[0] == '{' || token.IsIdentifier(m) { |
| return []string{m} |
| } |
| // Split up input. |
| var prefix string |
| if i := strings.Index(m, ":"); i >= 0 && token.IsIdentifier(m[:i]) { |
| prefix = m[:i+1] |
| m = m[i+1:] |
| } |
| if m[0] != '(' || m[len(m)-1] != ')' { |
| log.Fatalf("non-compound expr in commute1: %q", m) |
| } |
| s := split(m[1 : len(m)-1]) |
| op := s[0] |
| |
| // Figure out if the op is commutative or not. |
| commutative := false |
| for _, x := range genericOps { |
| if op == x.name { |
| if x.commutative { |
| commutative = true |
| } |
| break |
| } |
| } |
| if arch.name != "generic" { |
| for _, x := range arch.ops { |
| if op == x.name { |
| if x.commutative { |
| commutative = true |
| } |
| break |
| } |
| } |
| } |
| var idx0, idx1 int |
| if commutative { |
| // Find indexes of two args we can swap. |
| for i, arg := range s { |
| if i == 0 || arg[0] == '<' || arg[0] == '[' || arg[0] == '{' { |
| continue |
| } |
| if idx0 == 0 { |
| idx0 = i |
| continue |
| } |
| if idx1 == 0 { |
| idx1 = i |
| break |
| } |
| } |
| if idx1 == 0 { |
| log.Fatalf("couldn't find first two args of commutative op %q", s[0]) |
| } |
| if cnt[s[idx0]] == 1 && cnt[s[idx1]] == 1 || s[idx0] == s[idx1] && cnt[s[idx0]] == 2 { |
| // When we have (Add x y) with no other uses of x and y in the matching rule, |
| // then we can skip the commutative match (Add y x). |
| commutative = false |
| } |
| } |
| |
| // Recursively commute arguments. |
| a := make([][]string, len(s)) |
| for i, arg := range s { |
| a[i] = commute1(arg, cnt, arch) |
| } |
| |
| // Choose all possibilities from all args. |
| r := crossProduct(a) |
| |
| // If commutative, do that again with its two args reversed. |
| if commutative { |
| a[idx0], a[idx1] = a[idx1], a[idx0] |
| r = append(r, crossProduct(a)...) |
| } |
| |
| // Construct result. |
| for i, x := range r { |
| r[i] = prefix + "(" + x + ")" |
| } |
| return r |
| } |
| |
| // varCount returns a map which counts the number of occurrences of |
| // Value variables in m. |
| func varCount(m string) map[string]int { |
| cnt := map[string]int{} |
| varCount1(m, cnt) |
| return cnt |
| } |
| |
| func varCount1(m string, cnt map[string]int) { |
| if m[0] == '<' || m[0] == '[' || m[0] == '{' { |
| return |
| } |
| if token.IsIdentifier(m) { |
| cnt[m]++ |
| return |
| } |
| // Split up input. |
| if i := strings.Index(m, ":"); i >= 0 && token.IsIdentifier(m[:i]) { |
| cnt[m[:i]]++ |
| m = m[i+1:] |
| } |
| if m[0] != '(' || m[len(m)-1] != ')' { |
| log.Fatalf("non-compound expr in commute1: %q", m) |
| } |
| s := split(m[1 : len(m)-1]) |
| for _, arg := range s[1:] { |
| varCount1(arg, cnt) |
| } |
| } |
| |
| // crossProduct returns all possible values |
| // x[0][i] + " " + x[1][j] + " " + ... + " " + x[len(x)-1][k] |
| // for all valid values of i, j, ..., k. |
| func crossProduct(x [][]string) []string { |
| if len(x) == 1 { |
| return x[0] |
| } |
| var r []string |
| for _, tail := range crossProduct(x[1:]) { |
| for _, first := range x[0] { |
| r = append(r, first+" "+tail) |
| } |
| } |
| return r |
| } |
| |
| // normalizeWhitespace replaces 2+ whitespace sequences with a single space. |
| func normalizeWhitespace(x string) string { |
| x = strings.Join(strings.Fields(x), " ") |
| x = strings.Replace(x, "( ", "(", -1) |
| x = strings.Replace(x, " )", ")", -1) |
| x = strings.Replace(x, ")->", ") ->", -1) |
| return x |
| } |