Commit c9438cb1 authored by Giovanni Bajo's avatar Giovanni Bajo

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: default avatarKeith Randall <khr@golang.org>
Reviewed-by: default avatarAlberto Donizetti <alb.donizetti@gmail.com>
parent c7c01efd
// asmcheck
package codegen
func bitcheck(a, b uint64) int {
if a&(1<<(b&63)) != 0 { // amd64:"BTQ"
return 1
}
return -1
}
// 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)
}
......@@ -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 {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment