From 9bdbe943ce334e99f7e7c395a2c22c1dccb029df Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 24 Feb 2026 14:34:13 +0100 Subject: [PATCH] transform: modify output format from the -print-allocs flag to be the same as expected by the go coverage tool Signed-off-by: deadprogram --- transform/allocs.go | 38 ++++++++++++++++++++++++++++++++++- transform/allocs_test.go | 17 +++++++++------- transform/optimizer.go | 6 +++++- transform/testdata/allocs2.go | 20 +++++++++--------- 4 files changed, 62 insertions(+), 19 deletions(-) diff --git a/transform/allocs.go b/transform/allocs.go index 870faa5b7..d9c7e4709 100644 --- a/transform/allocs.go +++ b/transform/allocs.go @@ -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 } diff --git a/transform/allocs_test.go b/transform/allocs_test.go index 7f7ff5b75..df39605b1 100644 --- a/transform/allocs_test.go +++ b/transform/allocs_test.go @@ -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 + } } } diff --git a/transform/optimizer.go b/transform/optimizer.go index 54f9762bc..120975431 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -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) diff --git a/transform/testdata/allocs2.go b/transform/testdata/allocs2.go index 9fcebb212..2b0306db1 100644 --- a/transform/testdata/allocs2.go +++ b/transform/testdata/allocs2.go @@ -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 }()