transform: modify output format from the -print-allocs flag to be the same as expected by the go coverage tool

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram
2026-02-24 14:34:13 +01:00
parent 180220a2e3
commit 9bdbe943ce
4 changed files with 62 additions and 19 deletions
+37 -1
View File
@@ -6,8 +6,10 @@ package transform
// interprocedural escape analysis.
import (
"bufio"
"fmt"
"go/token"
"os"
"regexp"
"tinygo.org/x/go-llvm"
@@ -37,6 +39,10 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, maxStackAlloc u
complex128Type := ctx.StructType([]llvm.Type{ctx.DoubleType(), ctx.DoubleType()}, false)
maxAlign := int64(targetData.ABITypeAlignment(complex128Type))
if printAllocs != nil {
fmt.Fprintln(os.Stderr, "mode: set")
}
for _, heapalloc := range getUses(allocator) {
logAllocs := printAllocs != nil && printAllocs.MatchString(heapalloc.InstructionParent().Parent().Name())
if heapalloc.Operand(0).IsAConstantInt().IsNil() {
@@ -173,5 +179,35 @@ func valueEscapesAt(value llvm.Value) llvm.Value {
// logAlloc prints a message to stderr explaining why the given object had to be
// allocated on the heap.
func logAlloc(logger func(token.Position, string), allocCall llvm.Value, reason string) {
logger(getPosition(allocCall), "object allocated on the heap: "+reason)
pos := getPosition(allocCall)
if pos.Filename == "" || pos.Line <= 0 {
logger(pos, "")
return
}
endCol := lineLengthAt(pos.Filename, pos.Line)
if endCol < 1 {
endCol = 1
}
// Only emit the coverprofile line, without position prefix.
logger(token.Position{}, fmt.Sprintf("%s:%d.1,%d.%d 1 0", pos.Filename, pos.Line, pos.Line, endCol))
}
func lineLengthAt(filename string, lineNumber int) int {
f, err := os.Open(filename)
if err != nil {
return 0
}
defer f.Close()
scanner := bufio.NewScanner(f)
line := 1
for scanner.Scan() {
if line == lineNumber {
return len(scanner.Text())
}
line++
}
return 0
}
+10 -7
View File
@@ -57,9 +57,9 @@ func TestAllocs2(t *testing.T) {
sort.Slice(testOutputs, func(i, j int) bool {
return testOutputs[i].line < testOutputs[j].line
})
testOutput := ""
testOutput := make([]string, 0)
for _, out := range testOutputs {
testOutput += out.String() + "\n"
testOutput = append(testOutput, out.String())
}
// Load expected test output (the OUT: lines).
@@ -67,15 +67,18 @@ func TestAllocs2(t *testing.T) {
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") {
var expectedTestOutput []string
for _, 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"
expectedTestOutput = append(expectedTestOutput, msg)
}
}
if testOutput != expectedTestOutput {
t.Errorf("output does not match expected output:\n%s", testOutput)
for i := range testOutput {
if !strings.HasSuffix(testOutput[i], expectedTestOutput[i]) {
t.Errorf("output does not match expected output:\n%s\n%s\n", testOutput[i], expectedTestOutput[i])
return
}
}
}
+5 -1
View File
@@ -88,7 +88,11 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
// Run TinyGo-specific interprocedural optimizations.
OptimizeAllocs(mod, config.Options.PrintAllocs, maxStackSize, func(pos token.Position, msg string) {
fmt.Fprintln(os.Stderr, pos.String()+": "+msg)
if pos.Filename != "" {
fmt.Fprintf(os.Stderr, "%s:%d:%d: %s\n", pos.Filename, pos.Line, pos.Column, msg)
} else {
fmt.Fprintln(os.Stderr, msg) // No prefix!
}
})
OptimizeStringToBytes(mod)
OptimizeStringEqual(mod)
+10 -10
View File
@@ -10,7 +10,7 @@ func main() {
derefInt(&n1)
// This should eventually be modified to not escape.
n2 := 6 // OUT: object allocated on the heap: escapes at line 14
n2 := 6 // OUT: allocs2.go:52.1,52.42 1 0
returnIntPtr(&n2)
s1 := make([]int, 3)
@@ -20,22 +20,22 @@ func main() {
readIntSlice(s2[:])
// This should also be modified to not escape.
s3 := make([]int, 3) // OUT: object allocated on the heap: escapes at line 24
s3 := make([]int, 3) // OUT: allocs2.go:51.1,51.42 1 0
returnIntSlice(s3)
useSlice(make([]int, getUnknownNumber())) // OUT: object allocated on the heap: size is not constant
useSlice(make([]int, getUnknownNumber())) // OUT: allocs2.go:48.1,48.55 1 0
s4 := make([]byte, 300) // OUT: object allocated on the heap: object size 300 exceeds maximum stack allocation size 256
s4 := make([]byte, 300) // OUT: allocs2.go:46.1,46.56 1 0
readByteSlice(s4)
s5 := make([]int, 4) // OUT: object allocated on the heap: escapes at line 32
s5 := make([]int, 4) // OUT: allocs2.go:38.1,38.56 1 0
_ = append(s5, 5)
s6 := make([]int, 3)
s7 := []int{1, 2, 3}
copySlice(s6, s7)
c1 := getComplex128() // OUT: object allocated on the heap: escapes at line 39
c1 := getComplex128() // OUT: allocs2.go:31.1,31.55 1 0
useInterface(c1)
n3 := 5
@@ -43,13 +43,13 @@ func main() {
return n3
}()
callVariadic(3, 5, 8) // OUT: object allocated on the heap: escapes at line 46
callVariadic(3, 5, 8) // OUT: allocs2.go:28.1,28.58 1 0
s8 := []int{3, 5, 8} // OUT: object allocated on the heap: escapes at line 49
s8 := []int{3, 5, 8} // OUT: allocs2.go:26.1,26.76 1 0
callVariadic(s8...)
n4 := 3 // OUT: object allocated on the heap: escapes at line 53
n5 := 7 // OUT: object allocated on the heap: escapes at line 53
n4 := 3 // OUT: allocs2.go:23.1,23.55 1 0
n5 := 7 // OUT: allocs2.go:13.1,13.42 1 0
func() {
n4 = n5
}()