
Also add unit tests. This is the first of several transformation (optimization/lowering) passes that I'd like to move to the new transform package. This separates the compiler from the optimizer. Also, it finally adds unit tests for the compiler, not just end-to-end compilation tests. This should improve robustness and should make it easier to change these transformation passes in the future. While the heap-to-stack transform is relatively simple, other passes are much more complex. Adding unit tests not only helps robustness over time, but also doubles as documentation as to what these transformation passes do exactly.
82 строки
2,2 КиБ
Go
82 строки
2,2 КиБ
Go
package transform
|
|
|
|
// This file defines some helper functions for testing transforms.
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
// testTransform runs a transformation pass on an input file (pathPrefix+".ll")
|
|
// and checks whether it matches the expected output (pathPrefix+".out.ll"). The
|
|
// output is compared with a fuzzy match that ignores some irrelevant lines such
|
|
// as empty lines.
|
|
func testTransform(t *testing.T, pathPrefix string, transform func(mod llvm.Module)) {
|
|
// Read the input IR.
|
|
ctx := llvm.NewContext()
|
|
buf, err := llvm.NewMemoryBufferFromFile(pathPrefix + ".ll")
|
|
os.Stat(pathPrefix + ".ll") // make sure this file is tracked by `go test` caching
|
|
if err != nil {
|
|
t.Fatalf("could not read file %s: %v", pathPrefix+".ll", err)
|
|
}
|
|
mod, err := ctx.ParseIR(buf)
|
|
if err != nil {
|
|
t.Fatalf("could not load module:\n%v", err)
|
|
}
|
|
|
|
// Perform the transform.
|
|
transform(mod)
|
|
|
|
// Read the expected output IR.
|
|
out, err := ioutil.ReadFile(pathPrefix + ".out.ll")
|
|
if err != nil {
|
|
t.Fatalf("could not read output file %s: %v", pathPrefix+".out.ll", err)
|
|
}
|
|
|
|
// See whether the transform output matches with the expected output IR.
|
|
expected := string(out)
|
|
actual := mod.String()
|
|
if !fuzzyEqualIR(expected, actual) {
|
|
t.Logf("output does not match expected output:\n%s", actual)
|
|
t.Fail()
|
|
}
|
|
}
|
|
|
|
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
|
|
// equal. That means, only relevant lines are compared (excluding comments
|
|
// etc.).
|
|
func fuzzyEqualIR(s1, s2 string) bool {
|
|
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
|
|
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
|
|
if len(lines1) != len(lines2) {
|
|
return false
|
|
}
|
|
for i, line := range lines1 {
|
|
if line != lines2[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// filterIrrelevantIRLines removes lines from the input slice of strings that
|
|
// are not relevant in comparing IR. For example, empty lines and comments are
|
|
// stripped out.
|
|
func filterIrrelevantIRLines(lines []string) []string {
|
|
var out []string
|
|
for _, line := range lines {
|
|
if line == "" || line[0] == ';' {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(line, "source_filename = ") {
|
|
continue
|
|
}
|
|
out = append(out, line)
|
|
}
|
|
return out
|
|
}
|