github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/src/cmd/compile/internal/ssa/gen/rulegen.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build gen
     6  
     7  // This program generates Go code that applies rewrite rules to a Value.
     8  // The generated code implements a function of type func (v *Value) bool
     9  // which returns true iff if did something.
    10  // Ideas stolen from Swift: http://www.hpl.hp.com/techreports/Compaq-DEC/WRL-2000-2.html
    11  
    12  package main
    13  
    14  import (
    15  	"bufio"
    16  	"bytes"
    17  	"flag"
    18  	"fmt"
    19  	"go/format"
    20  	"io"
    21  	"io/ioutil"
    22  	"log"
    23  	"os"
    24  	"regexp"
    25  	"sort"
    26  	"strings"
    27  )
    28  
    29  // rule syntax:
    30  //  sexpr [&& extra conditions] -> [@block] sexpr
    31  //
    32  // sexpr are s-expressions (lisp-like parenthesized groupings)
    33  // sexpr ::= (opcode sexpr*)
    34  //         | variable
    35  //         | <type>
    36  //         | [auxint]
    37  //         | {aux}
    38  //
    39  // aux      ::= variable | {code}
    40  // type     ::= variable | {code}
    41  // variable ::= some token
    42  // opcode   ::= one of the opcodes from ../op.go (without the Op prefix)
    43  
    44  // extra conditions is just a chunk of Go that evaluates to a boolean. It may use
    45  // variables declared in the matching sexpr. The variable "v" is predefined to be
    46  // the value matched by the entire rule.
    47  
    48  // If multiple rules match, the first one in file order is selected.
    49  
    50  var (
    51  	genLog = flag.Bool("log", false, "generate code that logs; for debugging only")
    52  )
    53  
    54  type Rule struct {
    55  	rule   string
    56  	lineno int
    57  }
    58  
    59  func (r Rule) String() string {
    60  	return fmt.Sprintf("rule %q at line %d", r.rule, r.lineno)
    61  }
    62  
    63  // parse returns the matching part of the rule, additional conditions, and the result.
    64  func (r Rule) parse() (match, cond, result string) {
    65  	s := strings.Split(r.rule, "->")
    66  	if len(s) != 2 {
    67  		log.Fatalf("no arrow in %s", r)
    68  	}
    69  	match = strings.TrimSpace(s[0])
    70  	result = strings.TrimSpace(s[1])
    71  	cond = ""
    72  	if i := strings.Index(match, "&&"); i >= 0 {
    73  		cond = strings.TrimSpace(match[i+2:])
    74  		match = strings.TrimSpace(match[:i])
    75  	}
    76  	return match, cond, result
    77  }
    78  
    79  func genRules(arch arch) {
    80  	// Open input file.
    81  	text, err := os.Open(arch.name + ".rules")
    82  	if err != nil {
    83  		log.Fatalf("can't read rule file: %v", err)
    84  	}
    85  
    86  	// oprules contains a list of rules for each block and opcode
    87  	blockrules := map[string][]Rule{}
    88  	oprules := map[string][]Rule{}
    89  
    90  	// read rule file
    91  	scanner := bufio.NewScanner(text)
    92  	rule := ""
    93  	var lineno int
    94  	for scanner.Scan() {
    95  		lineno++
    96  		line := scanner.Text()
    97  		if i := strings.Index(line, "//"); i >= 0 {
    98  			// Remove comments. Note that this isn't string safe, so
    99  			// it will truncate lines with // inside strings. Oh well.
   100  			line = line[:i]
   101  		}
   102  		rule += " " + line
   103  		rule = strings.TrimSpace(rule)
   104  		if rule == "" {
   105  			continue
   106  		}
   107  		if !strings.Contains(rule, "->") {
   108  			continue
   109  		}
   110  		if strings.HasSuffix(rule, "->") {
   111  			continue
   112  		}
   113  		if unbalanced(rule) {
   114  			continue
   115  		}
   116  		op := strings.Split(rule, " ")[0][1:]
   117  		if op[len(op)-1] == ')' {
   118  			op = op[:len(op)-1] // rule has only opcode, e.g. (ConstNil) -> ...
   119  		}
   120  		if isBlock(op, arch) {
   121  			blockrules[op] = append(blockrules[op], Rule{rule: rule, lineno: lineno})
   122  		} else {
   123  			oprules[op] = append(oprules[op], Rule{rule: rule, lineno: lineno})
   124  		}
   125  		rule = ""
   126  	}
   127  	if err := scanner.Err(); err != nil {
   128  		log.Fatalf("scanner failed: %v\n", err)
   129  	}
   130  	if unbalanced(rule) {
   131  		log.Fatalf("unbalanced rule at line %d: %v\n", lineno, rule)
   132  	}
   133  
   134  	// Order all the ops.
   135  	var ops []string
   136  	for op := range oprules {
   137  		ops = append(ops, op)
   138  	}
   139  	sort.Strings(ops)
   140  
   141  	// Start output buffer, write header.
   142  	w := new(bytes.Buffer)
   143  	fmt.Fprintf(w, "// autogenerated from gen/%s.rules: do not edit!\n", arch.name)
   144  	fmt.Fprintln(w, "// generated with: cd gen; go run *.go")
   145  	fmt.Fprintln(w)
   146  	fmt.Fprintln(w, "package ssa")
   147  	if *genLog {
   148  		fmt.Fprintln(w, "import \"fmt\"")
   149  	}
   150  	fmt.Fprintln(w, "import \"math\"")
   151  	fmt.Fprintln(w, "var _ = math.MinInt8 // in case not otherwise used")
   152  
   153  	// Main rewrite routine is a switch on v.Op.
   154  	fmt.Fprintf(w, "func rewriteValue%s(v *Value, config *Config) bool {\n", arch.name)
   155  	fmt.Fprintf(w, "switch v.Op {\n")
   156  	for _, op := range ops {
   157  		fmt.Fprintf(w, "case %s:\n", opName(op, arch))
   158  		fmt.Fprintf(w, "return rewriteValue%s_%s(v, config)\n", arch.name, opName(op, arch))
   159  	}
   160  	fmt.Fprintf(w, "}\n")
   161  	fmt.Fprintf(w, "return false\n")
   162  	fmt.Fprintf(w, "}\n")
   163  
   164  	// Generate a routine per op. Note that we don't make one giant routine
   165  	// because it is too big for some compilers.
   166  	for _, op := range ops {
   167  		fmt.Fprintf(w, "func rewriteValue%s_%s(v *Value, config *Config) bool {\n", arch.name, opName(op, arch))
   168  		fmt.Fprintln(w, "b := v.Block")
   169  		fmt.Fprintln(w, "_ = b")
   170  		for _, rule := range oprules[op] {
   171  			match, cond, result := rule.parse()
   172  			fmt.Fprintf(w, "// match: %s\n", match)
   173  			fmt.Fprintf(w, "// cond: %s\n", cond)
   174  			fmt.Fprintf(w, "// result: %s\n", result)
   175  
   176  			fmt.Fprintf(w, "for {\n")
   177  			genMatch(w, arch, match)
   178  
   179  			if cond != "" {
   180  				fmt.Fprintf(w, "if !(%s) {\nbreak\n}\n", cond)
   181  			}
   182  
   183  			genResult(w, arch, result)
   184  			if *genLog {
   185  				fmt.Fprintf(w, "fmt.Println(\"rewrite %s.rules:%d\")\n", arch.name, rule.lineno)
   186  			}
   187  			fmt.Fprintf(w, "return true\n")
   188  
   189  			fmt.Fprintf(w, "}\n")
   190  		}
   191  		fmt.Fprintf(w, "return false\n")
   192  		fmt.Fprintf(w, "}\n")
   193  	}
   194  
   195  	// Generate block rewrite function. There are only a few block types
   196  	// so we can make this one function with a switch.
   197  	fmt.Fprintf(w, "func rewriteBlock%s(b *Block) bool {\n", arch.name)
   198  	fmt.Fprintf(w, "switch b.Kind {\n")
   199  	ops = nil
   200  	for op := range blockrules {
   201  		ops = append(ops, op)
   202  	}
   203  	sort.Strings(ops)
   204  	for _, op := range ops {
   205  		fmt.Fprintf(w, "case %s:\n", blockName(op, arch))
   206  		for _, rule := range blockrules[op] {
   207  			match, cond, result := rule.parse()
   208  			fmt.Fprintf(w, "// match: %s\n", match)
   209  			fmt.Fprintf(w, "// cond: %s\n", cond)
   210  			fmt.Fprintf(w, "// result: %s\n", result)
   211  
   212  			fmt.Fprintf(w, "for {\n")
   213  
   214  			s := split(match[1 : len(match)-1]) // remove parens, then split
   215  
   216  			// check match of control value
   217  			if s[1] != "nil" {
   218  				fmt.Fprintf(w, "v := b.Control\n")
   219  				if strings.Contains(s[1], "(") {
   220  					genMatch0(w, arch, s[1], "v", map[string]struct{}{}, false)
   221  				} else {
   222  					fmt.Fprintf(w, "%s := b.Control\n", s[1])
   223  				}
   224  			}
   225  
   226  			// assign successor names
   227  			succs := s[2:]
   228  			for i, a := range succs {
   229  				if a != "_" {
   230  					fmt.Fprintf(w, "%s := b.Succs[%d]\n", a, i)
   231  				}
   232  			}
   233  
   234  			if cond != "" {
   235  				fmt.Fprintf(w, "if !(%s) {\nbreak\n}\n", cond)
   236  			}
   237  
   238  			// Rule matches. Generate result.
   239  			t := split(result[1 : len(result)-1]) // remove parens, then split
   240  			newsuccs := t[2:]
   241  
   242  			// Check if newsuccs is the same set as succs.
   243  			m := map[string]bool{}
   244  			for _, succ := range succs {
   245  				if m[succ] {
   246  					log.Fatalf("can't have a repeat successor name %s in %s", succ, rule)
   247  				}
   248  				m[succ] = true
   249  			}
   250  			for _, succ := range newsuccs {
   251  				if !m[succ] {
   252  					log.Fatalf("unknown successor %s in %s", succ, rule)
   253  				}
   254  				delete(m, succ)
   255  			}
   256  			if len(m) != 0 {
   257  				log.Fatalf("unmatched successors %v in %s", m, rule)
   258  			}
   259  
   260  			// Modify predecessor lists for no-longer-reachable blocks
   261  			for succ := range m {
   262  				fmt.Fprintf(w, "b.Func.removePredecessor(b, %s)\n", succ)
   263  			}
   264  
   265  			fmt.Fprintf(w, "b.Kind = %s\n", blockName(t[0], arch))
   266  			if t[1] == "nil" {
   267  				fmt.Fprintf(w, "b.SetControl(nil)\n")
   268  			} else {
   269  				fmt.Fprintf(w, "b.SetControl(%s)\n", genResult0(w, arch, t[1], new(int), false, false))
   270  			}
   271  			if len(newsuccs) < len(succs) {
   272  				fmt.Fprintf(w, "b.Succs = b.Succs[:%d]\n", len(newsuccs))
   273  			}
   274  			for i, a := range newsuccs {
   275  				fmt.Fprintf(w, "b.Succs[%d] = %s\n", i, a)
   276  			}
   277  			// Update branch prediction
   278  			switch {
   279  			case len(newsuccs) != 2:
   280  				fmt.Fprintln(w, "b.Likely = BranchUnknown")
   281  			case newsuccs[0] == succs[0] && newsuccs[1] == succs[1]:
   282  				// unchanged
   283  			case newsuccs[0] == succs[1] && newsuccs[1] == succs[0]:
   284  				// flipped
   285  				fmt.Fprintln(w, "b.Likely *= -1")
   286  			default:
   287  				// unknown
   288  				fmt.Fprintln(w, "b.Likely = BranchUnknown")
   289  			}
   290  
   291  			if *genLog {
   292  				fmt.Fprintf(w, "fmt.Println(\"rewrite %s.rules:%d\")\n", arch.name, rule.lineno)
   293  			}
   294  			fmt.Fprintf(w, "return true\n")
   295  
   296  			fmt.Fprintf(w, "}\n")
   297  		}
   298  	}
   299  	fmt.Fprintf(w, "}\n")
   300  	fmt.Fprintf(w, "return false\n")
   301  	fmt.Fprintf(w, "}\n")
   302  
   303  	// gofmt result
   304  	b := w.Bytes()
   305  	src, err := format.Source(b)
   306  	if err != nil {
   307  		fmt.Printf("%s\n", b)
   308  		panic(err)
   309  	}
   310  
   311  	// Write to file
   312  	err = ioutil.WriteFile("../rewrite"+arch.name+".go", src, 0666)
   313  	if err != nil {
   314  		log.Fatalf("can't write output: %v\n", err)
   315  	}
   316  }
   317  
   318  func genMatch(w io.Writer, arch arch, match string) {
   319  	genMatch0(w, arch, match, "v", map[string]struct{}{}, true)
   320  }
   321  
   322  func genMatch0(w io.Writer, arch arch, match, v string, m map[string]struct{}, top bool) {
   323  	if match[0] != '(' || match[len(match)-1] != ')' {
   324  		panic("non-compound expr in genMatch0: " + match)
   325  	}
   326  
   327  	// split body up into regions. Split by spaces/tabs, except those
   328  	// contained in () or {}.
   329  	s := split(match[1 : len(match)-1]) // remove parens, then split
   330  
   331  	// check op
   332  	if !top {
   333  		fmt.Fprintf(w, "if %s.Op != %s {\nbreak\n}\n", v, opName(s[0], arch))
   334  	}
   335  
   336  	// check type/aux/args
   337  	argnum := 0
   338  	for _, a := range s[1:] {
   339  		if a[0] == '<' {
   340  			// type restriction
   341  			t := a[1 : len(a)-1] // remove <>
   342  			if !isVariable(t) {
   343  				// code. We must match the results of this code.
   344  				fmt.Fprintf(w, "if %s.Type != %s {\nbreak\n}\n", v, t)
   345  			} else {
   346  				// variable
   347  				if _, ok := m[t]; ok {
   348  					// must match previous variable
   349  					fmt.Fprintf(w, "if %s.Type != %s {\nbreak\n}\n", v, t)
   350  				} else {
   351  					m[t] = struct{}{}
   352  					fmt.Fprintf(w, "%s := %s.Type\n", t, v)
   353  				}
   354  			}
   355  		} else if a[0] == '[' {
   356  			// auxint restriction
   357  			x := a[1 : len(a)-1] // remove []
   358  			if !isVariable(x) {
   359  				// code
   360  				fmt.Fprintf(w, "if %s.AuxInt != %s {\nbreak\n}\n", v, x)
   361  			} else {
   362  				// variable
   363  				if _, ok := m[x]; ok {
   364  					fmt.Fprintf(w, "if %s.AuxInt != %s {\nbreak\n}\n", v, x)
   365  				} else {
   366  					m[x] = struct{}{}
   367  					fmt.Fprintf(w, "%s := %s.AuxInt\n", x, v)
   368  				}
   369  			}
   370  		} else if a[0] == '{' {
   371  			// auxint restriction
   372  			x := a[1 : len(a)-1] // remove {}
   373  			if !isVariable(x) {
   374  				// code
   375  				fmt.Fprintf(w, "if %s.Aux != %s {\nbreak\n}\n", v, x)
   376  			} else {
   377  				// variable
   378  				if _, ok := m[x]; ok {
   379  					fmt.Fprintf(w, "if %s.Aux != %s {\nbreak\n}\n", v, x)
   380  				} else {
   381  					m[x] = struct{}{}
   382  					fmt.Fprintf(w, "%s := %s.Aux\n", x, v)
   383  				}
   384  			}
   385  		} else if a == "_" {
   386  			argnum++
   387  		} else if !strings.Contains(a, "(") {
   388  			// leaf variable
   389  			if _, ok := m[a]; ok {
   390  				// variable already has a definition. Check whether
   391  				// the old definition and the new definition match.
   392  				// For example, (add x x).  Equality is just pointer equality
   393  				// on Values (so cse is important to do before lowering).
   394  				fmt.Fprintf(w, "if %s != %s.Args[%d] {\nbreak\n}\n", a, v, argnum)
   395  			} else {
   396  				// remember that this variable references the given value
   397  				m[a] = struct{}{}
   398  				fmt.Fprintf(w, "%s := %s.Args[%d]\n", a, v, argnum)
   399  			}
   400  			argnum++
   401  		} else {
   402  			// compound sexpr
   403  			var argname string
   404  			colon := strings.Index(a, ":")
   405  			openparen := strings.Index(a, "(")
   406  			if colon >= 0 && openparen >= 0 && colon < openparen {
   407  				// rule-specified name
   408  				argname = a[:colon]
   409  				a = a[colon+1:]
   410  			} else {
   411  				// autogenerated name
   412  				argname = fmt.Sprintf("%s_%d", v, argnum)
   413  			}
   414  			fmt.Fprintf(w, "%s := %s.Args[%d]\n", argname, v, argnum)
   415  			genMatch0(w, arch, a, argname, m, false)
   416  			argnum++
   417  		}
   418  	}
   419  
   420  	variableLength := false
   421  	for _, op := range genericOps {
   422  		if op.name == s[0] && op.argLength == -1 {
   423  			variableLength = true
   424  			break
   425  		}
   426  	}
   427  	for _, op := range arch.ops {
   428  		if op.name == s[0] && op.argLength == -1 {
   429  			variableLength = true
   430  			break
   431  		}
   432  	}
   433  	if variableLength {
   434  		fmt.Fprintf(w, "if len(%s.Args) != %d {\nbreak\n}\n", v, argnum)
   435  	}
   436  }
   437  
   438  func genResult(w io.Writer, arch arch, result string) {
   439  	move := false
   440  	if result[0] == '@' {
   441  		// parse @block directive
   442  		s := strings.SplitN(result[1:], " ", 2)
   443  		fmt.Fprintf(w, "b = %s\n", s[0])
   444  		result = s[1]
   445  		move = true
   446  	}
   447  	genResult0(w, arch, result, new(int), true, move)
   448  }
   449  func genResult0(w io.Writer, arch arch, result string, alloc *int, top, move bool) string {
   450  	// TODO: when generating a constant result, use f.constVal to avoid
   451  	// introducing copies just to clean them up again.
   452  	if result[0] != '(' {
   453  		// variable
   454  		if top {
   455  			// It in not safe in general to move a variable between blocks
   456  			// (and particularly not a phi node).
   457  			// Introduce a copy.
   458  			fmt.Fprintf(w, "v.reset(OpCopy)\n")
   459  			fmt.Fprintf(w, "v.Type = %s.Type\n", result)
   460  			fmt.Fprintf(w, "v.AddArg(%s)\n", result)
   461  		}
   462  		return result
   463  	}
   464  
   465  	s := split(result[1 : len(result)-1]) // remove parens, then split
   466  
   467  	// Find the type of the variable.
   468  	var opType string
   469  	var typeOverride bool
   470  	for _, a := range s[1:] {
   471  		if a[0] == '<' {
   472  			// type restriction
   473  			opType = a[1 : len(a)-1] // remove <>
   474  			typeOverride = true
   475  			break
   476  		}
   477  	}
   478  	if opType == "" {
   479  		// find default type, if any
   480  		for _, op := range arch.ops {
   481  			if op.name == s[0] && op.typ != "" {
   482  				opType = typeName(op.typ)
   483  				break
   484  			}
   485  		}
   486  	}
   487  	if opType == "" {
   488  		for _, op := range genericOps {
   489  			if op.name == s[0] && op.typ != "" {
   490  				opType = typeName(op.typ)
   491  				break
   492  			}
   493  		}
   494  	}
   495  	var v string
   496  	if top && !move {
   497  		v = "v"
   498  		fmt.Fprintf(w, "v.reset(%s)\n", opName(s[0], arch))
   499  		if typeOverride {
   500  			fmt.Fprintf(w, "v.Type = %s\n", opType)
   501  		}
   502  	} else {
   503  		if opType == "" {
   504  			log.Fatalf("sub-expression %s (op=%s) must have a type", result, s[0])
   505  		}
   506  		v = fmt.Sprintf("v%d", *alloc)
   507  		*alloc++
   508  		fmt.Fprintf(w, "%s := b.NewValue0(v.Line, %s, %s)\n", v, opName(s[0], arch), opType)
   509  		if move && top {
   510  			// Rewrite original into a copy
   511  			fmt.Fprintf(w, "v.reset(OpCopy)\n")
   512  			fmt.Fprintf(w, "v.AddArg(%s)\n", v)
   513  		}
   514  	}
   515  	for _, a := range s[1:] {
   516  		if a[0] == '<' {
   517  			// type restriction, handled above
   518  		} else if a[0] == '[' {
   519  			// auxint restriction
   520  			x := a[1 : len(a)-1] // remove []
   521  			fmt.Fprintf(w, "%s.AuxInt = %s\n", v, x)
   522  		} else if a[0] == '{' {
   523  			// aux restriction
   524  			x := a[1 : len(a)-1] // remove {}
   525  			fmt.Fprintf(w, "%s.Aux = %s\n", v, x)
   526  		} else {
   527  			// regular argument (sexpr or variable)
   528  			x := genResult0(w, arch, a, alloc, false, move)
   529  			fmt.Fprintf(w, "%s.AddArg(%s)\n", v, x)
   530  		}
   531  	}
   532  
   533  	return v
   534  }
   535  
   536  func split(s string) []string {
   537  	var r []string
   538  
   539  outer:
   540  	for s != "" {
   541  		d := 0               // depth of ({[<
   542  		var open, close byte // opening and closing markers ({[< or )}]>
   543  		nonsp := false       // found a non-space char so far
   544  		for i := 0; i < len(s); i++ {
   545  			switch {
   546  			case d == 0 && s[i] == '(':
   547  				open, close = '(', ')'
   548  				d++
   549  			case d == 0 && s[i] == '<':
   550  				open, close = '<', '>'
   551  				d++
   552  			case d == 0 && s[i] == '[':
   553  				open, close = '[', ']'
   554  				d++
   555  			case d == 0 && s[i] == '{':
   556  				open, close = '{', '}'
   557  				d++
   558  			case d == 0 && (s[i] == ' ' || s[i] == '\t'):
   559  				if nonsp {
   560  					r = append(r, strings.TrimSpace(s[:i]))
   561  					s = s[i:]
   562  					continue outer
   563  				}
   564  			case d > 0 && s[i] == open:
   565  				d++
   566  			case d > 0 && s[i] == close:
   567  				d--
   568  			default:
   569  				nonsp = true
   570  			}
   571  		}
   572  		if d != 0 {
   573  			panic("imbalanced expression: " + s)
   574  		}
   575  		if nonsp {
   576  			r = append(r, strings.TrimSpace(s))
   577  		}
   578  		break
   579  	}
   580  	return r
   581  }
   582  
   583  // isBlock returns true if this op is a block opcode.
   584  func isBlock(name string, arch arch) bool {
   585  	for _, b := range genericBlocks {
   586  		if b.name == name {
   587  			return true
   588  		}
   589  	}
   590  	for _, b := range arch.blocks {
   591  		if b.name == name {
   592  			return true
   593  		}
   594  	}
   595  	return false
   596  }
   597  
   598  // opName converts from an op name specified in a rule file to an Op enum.
   599  // if the name matches a generic op, returns "Op" plus the specified name.
   600  // Otherwise, returns "Op" plus arch name plus op name.
   601  func opName(name string, arch arch) string {
   602  	for _, op := range genericOps {
   603  		if op.name == name {
   604  			return "Op" + name
   605  		}
   606  	}
   607  	return "Op" + arch.name + name
   608  }
   609  
   610  func blockName(name string, arch arch) string {
   611  	for _, b := range genericBlocks {
   612  		if b.name == name {
   613  			return "Block" + name
   614  		}
   615  	}
   616  	return "Block" + arch.name + name
   617  }
   618  
   619  // typeName returns the string to use to generate a type.
   620  func typeName(typ string) string {
   621  	switch typ {
   622  	case "Flags", "Mem", "Void", "Int128":
   623  		return "Type" + typ
   624  	default:
   625  		return "config.fe.Type" + typ + "()"
   626  	}
   627  }
   628  
   629  // unbalanced returns true if there aren't the same number of ( and ) in the string.
   630  func unbalanced(s string) bool {
   631  	var left, right int
   632  	for _, c := range s {
   633  		if c == '(' {
   634  			left++
   635  		}
   636  		if c == ')' {
   637  			right++
   638  		}
   639  	}
   640  	return left != right
   641  }
   642  
   643  // isVariable reports whether s is a single Go alphanumeric identifier.
   644  func isVariable(s string) bool {
   645  	b, err := regexp.MatchString("^[A-Za-z_][A-Za-z_0-9]*$", s)
   646  	if err != nil {
   647  		panic("bad variable regexp")
   648  	}
   649  	return b
   650  }