blob: ad09975a6da2d05b12d6eaf8e62c165b04c05279 [file] [log] [blame]
// 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
}