mirror of
https://github.com/golang/go
synced 2024-11-11 21:10:21 -07:00
test: add support for code generation tests (asmcheck)
The top-level test harness is modified to support a new kind of test: "asmcheck". This is meant to replace asm_test.go as an easier and more readable way to test code generation. I've added a couple of codegen tests to get initial feedback on the syntax. I've created them under a common "codegen" subdirectory, so that it's easier to run them all with "go run run.go -v codegen". The asmcheck syntax allows to insert line comments that can specify a regular expression to match in the assembly code, for multiple architectures (the testsuite will automatically build each testfile multiple times, one per mentioned architecture). Negative matches are unsupported for now, so this cannot fully replace asm_test yet. Change-Id: Ifdbba389f01d55e63e73c99e5f5449e642101d55 Reviewed-on: https://go-review.googlesource.com/97355 Run-TryBot: Giovanni Bajo <rasky@develer.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Alberto Donizetti <alb.donizetti@gmail.com>
This commit is contained in:
parent
c7c01efd96
commit
c9438cb198
10
test/codegen/bits.go
Normal file
10
test/codegen/bits.go
Normal file
@ -0,0 +1,10 @@
|
||||
// asmcheck
|
||||
|
||||
package codegen
|
||||
|
||||
func bitcheck(a, b uint64) int {
|
||||
if a&(1<<(b&63)) != 0 { // amd64:"BTQ"
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
25
test/codegen/rotate.go
Normal file
25
test/codegen/rotate.go
Normal file
@ -0,0 +1,25 @@
|
||||
// asmcheck
|
||||
|
||||
package codegen
|
||||
|
||||
import "math"
|
||||
|
||||
func rot32(x uint32) uint32 {
|
||||
var a uint32
|
||||
a += x<<7 | x>>25 // amd64:"ROLL.*[$]7" arm:"MOVW.*@>25"
|
||||
a += x<<8 + x>>24 // amd64:`ROLL.*\$8` arm:"MOVW.*@>24"
|
||||
a += x<<9 ^ x>>23 // amd64:"ROLL.*\\$9" arm:"MOVW.*@>23"
|
||||
return a
|
||||
}
|
||||
|
||||
func rot64(x uint64) uint64 {
|
||||
var a uint64
|
||||
a += x<<7 | x>>57 // amd64:"ROL"
|
||||
a += x<<8 + x>>56 // amd64:"ROL"
|
||||
a += x<<9 ^ x>>55 // amd64:"ROL"
|
||||
return a
|
||||
}
|
||||
|
||||
func copysign(a, b float64) float64 {
|
||||
return math.Copysign(a, b)
|
||||
}
|
126
test/run.go
126
test/run.go
@ -488,7 +488,7 @@ func (t *test) run() {
|
||||
action = "rundir"
|
||||
case "cmpout":
|
||||
action = "run" // the run case already looks for <dir>/<test>.out files
|
||||
case "compile", "compiledir", "build", "builddir", "run", "buildrun", "runoutput", "rundir":
|
||||
case "compile", "compiledir", "build", "builddir", "run", "buildrun", "runoutput", "rundir", "asmcheck":
|
||||
// nothing to do
|
||||
case "errorcheckandrundir":
|
||||
wantError = false // should be no error if also will run
|
||||
@ -593,6 +593,27 @@ func (t *test) run() {
|
||||
default:
|
||||
t.err = fmt.Errorf("unimplemented action %q", action)
|
||||
|
||||
case "asmcheck":
|
||||
ops, archs := t.wantedAsmOpcodes(long)
|
||||
for _, arch := range archs {
|
||||
os.Setenv("GOOS", "linux")
|
||||
os.Setenv("GOARCH", arch)
|
||||
|
||||
cmdline := []string{"go", "build", "-gcflags", "-S"}
|
||||
cmdline = append(cmdline, flags...)
|
||||
cmdline = append(cmdline, long)
|
||||
out, err := runcmd(cmdline...)
|
||||
if err != nil {
|
||||
t.err = err
|
||||
return
|
||||
}
|
||||
t.err = t.asmCheck(string(out), long, arch, ops[arch])
|
||||
if t.err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
case "errorcheck":
|
||||
// TODO(gri) remove need for -C (disable printing of columns in error messages)
|
||||
cmdline := []string{"go", "tool", "compile", "-C", "-e", "-o", "a.o"}
|
||||
@ -1226,6 +1247,109 @@ func (t *test) wantedErrors(file, short string) (errs []wantedError) {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
rxAsmCheck = regexp.MustCompile(`//(?:\s+(\w+):((?:"(?:.+?)")|(?:` + "`" + `(?:.+?)` + "`" + `)))+`)
|
||||
)
|
||||
|
||||
type wantedAsmOpcode struct {
|
||||
line int
|
||||
opcode *regexp.Regexp
|
||||
negative bool
|
||||
found bool
|
||||
}
|
||||
|
||||
func (t *test) wantedAsmOpcodes(fn string) (map[string]map[string][]wantedAsmOpcode, []string) {
|
||||
ops := make(map[string]map[string][]wantedAsmOpcode)
|
||||
archs := make(map[string]bool)
|
||||
|
||||
src, _ := ioutil.ReadFile(fn)
|
||||
for i, line := range strings.Split(string(src), "\n") {
|
||||
matches := rxAsmCheck.FindStringSubmatch(line)
|
||||
if len(matches) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
lnum := fn + ":" + strconv.Itoa(i+1)
|
||||
for j := 1; j < len(matches); j += 2 {
|
||||
rxsrc, err := strconv.Unquote(matches[j+1])
|
||||
if err != nil {
|
||||
log.Fatalf("%s:%d: error unquoting string: %v", t.goFileName(), i+1, err)
|
||||
}
|
||||
oprx, err := regexp.Compile(rxsrc)
|
||||
if err != nil {
|
||||
log.Fatalf("%s:%d: %v", t.goFileName(), i+1, err)
|
||||
}
|
||||
arch := matches[j]
|
||||
if ops[arch] == nil {
|
||||
ops[arch] = make(map[string][]wantedAsmOpcode)
|
||||
}
|
||||
archs[arch] = true
|
||||
ops[arch][lnum] = append(ops[arch][lnum], wantedAsmOpcode{
|
||||
line: i + 1,
|
||||
opcode: oprx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var sarchs []string
|
||||
for a := range archs {
|
||||
sarchs = append(sarchs, a)
|
||||
}
|
||||
sort.Strings(sarchs)
|
||||
|
||||
return ops, sarchs
|
||||
}
|
||||
|
||||
func (t *test) asmCheck(outStr string, fn string, arch string, fullops map[string][]wantedAsmOpcode) (err error) {
|
||||
defer func() {
|
||||
if *verbose && err != nil {
|
||||
log.Printf("%s gc output:\n%s", t, outStr)
|
||||
}
|
||||
}()
|
||||
|
||||
rxLine := regexp.MustCompile(fmt.Sprintf(`\((%s:\d+)\)\s+(.*)`, regexp.QuoteMeta(fn)))
|
||||
|
||||
for _, line := range strings.Split(outStr, "\n") {
|
||||
matches := rxLine.FindStringSubmatch(line)
|
||||
if len(matches) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
ops := fullops[matches[1]]
|
||||
asm := matches[2]
|
||||
for i := range ops {
|
||||
if !ops[i].found && ops[i].opcode.FindString(asm) != "" {
|
||||
ops[i].found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var notfound []wantedAsmOpcode
|
||||
for _, ops := range fullops {
|
||||
for _, o := range ops {
|
||||
if !o.found {
|
||||
notfound = append(notfound, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(notfound) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// At least one asmcheck failed; report them
|
||||
sort.Slice(notfound, func(i, j int) bool {
|
||||
return notfound[i].line < notfound[j].line
|
||||
})
|
||||
|
||||
var errbuf bytes.Buffer
|
||||
fmt.Fprintln(&errbuf)
|
||||
for _, o := range notfound {
|
||||
fmt.Fprintf(&errbuf, "%s:%d: %s: no match for opcode: %q\n", t.goFileName(), o.line, arch, o.opcode.String())
|
||||
}
|
||||
err = errors.New(errbuf.String())
|
||||
return
|
||||
}
|
||||
|
||||
// defaultRunOutputLimit returns the number of runoutput tests that
|
||||
// can be executed in parallel.
|
||||
func defaultRunOutputLimit() int {
|
||||
|
Loading…
Reference in New Issue
Block a user