main: add -print-allocs flag that lets you print all heap allocations

This flag, if set, is a regexp for function names. If there are heap
allocations in the matching function names, these heap allocations will
be printed with an explanation why the heap allocation exists (and why
the object can't be stack allocated).
This commit is contained in:
Ayke van Laethem
2021-04-19 00:34:57 +02:00
committed by Ron Evans
parent 404b65941a
commit c466465c32
6 changed files with 225 additions and 16 deletions
+118 -1
View File
@@ -1,12 +1,129 @@
package transform_test
import (
"go/token"
"go/types"
"io/ioutil"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"testing"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/loader"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
func TestAllocs(t *testing.T) {
t.Parallel()
testTransform(t, "testdata/allocs", transform.OptimizeAllocs)
testTransform(t, "testdata/allocs", func(mod llvm.Module) {
transform.OptimizeAllocs(mod, nil, nil)
})
}
type allocsTestOutput struct {
filename string
line int
msg string
}
func (out allocsTestOutput) String() string {
return out.filename + ":" + strconv.Itoa(out.line) + ": " + out.msg
}
// Test with a Go file as input (for more accurate tests).
func TestAllocs2(t *testing.T) {
t.Parallel()
target, err := compileopts.LoadTarget("i686--linux")
if err != nil {
t.Fatal("failed to load target:", err)
}
config := &compileopts.Config{
Options: &compileopts.Options{},
Target: target,
}
compilerConfig := &compiler.Config{
Triple: config.Triple(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
Debug: true,
}
machine, err := compiler.NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
}
// Load entire program AST into memory.
lprogram, err := loader.Load(config, []string{"./testdata/allocs2.go"}, config.ClangHeaders, types.Config{
Sizes: compiler.Sizes(machine),
})
if err != nil {
t.Fatal("failed to create target machine:", err)
}
err = lprogram.Parse()
if err != nil {
t.Fatal("could not parse", err)
}
// Compile AST to IR.
program := lprogram.LoadSSA()
pkg := lprogram.MainPkg()
mod, errs := compiler.CompilePackage("allocs2.go", pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
if errs != nil {
for _, err := range errs {
t.Error(err)
}
return
}
// Run functionattrs pass, which is necessary for escape analysis.
pm := llvm.NewPassManager()
defer pm.Dispose()
pm.AddInstructionCombiningPass()
pm.AddFunctionAttrsPass()
pm.Run(mod)
// Run heap to stack transform.
var testOutputs []allocsTestOutput
transform.OptimizeAllocs(mod, regexp.MustCompile("."), func(pos token.Position, msg string) {
testOutputs = append(testOutputs, allocsTestOutput{
filename: filepath.Base(pos.Filename),
line: pos.Line,
msg: msg,
})
})
sort.Slice(testOutputs, func(i, j int) bool {
return testOutputs[i].line < testOutputs[j].line
})
testOutput := ""
for _, out := range testOutputs {
testOutput += out.String() + "\n"
}
// Load expected test output (the OUT: lines).
testInput, err := ioutil.ReadFile("./testdata/allocs2.go")
if err != nil {
t.Fatal("could not read test input:", err)
}
var expectedTestOutput string
for i, line := range strings.Split(strings.ReplaceAll(string(testInput), "\r\n", "\n"), "\n") {
if idx := strings.Index(line, " // OUT: "); idx > 0 {
msg := line[idx+len(" // OUT: "):]
expectedTestOutput += "allocs2.go:" + strconv.Itoa(i+1) + ": " + msg + "\n"
}
}
if testOutput != expectedTestOutput {
t.Errorf("output does not match expected output:\n%s", testOutput)
}
}