From c3992bd77b1e0b64c56065d9d32b8b920eb0efb2 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 25 Apr 2021 01:48:54 +0200 Subject: [PATCH] compiler: improve position information In many cases, position information is not stored in Go SSA instructions because they don't exit directly in the source code. This includes implicit type conversions, implicit returns at the end of a function, the creation of a (hidden) slice when calling a variadic function, and many other cases. I'm not sure where this information is supposed to come from, but this patch takes the value (usually) from the value the instruction refers to. This seems to work well for these implicit conversions. I've also added a few extra tests to the heap-to-stack transform pass, of which one requires this improved position information. --- compiler/compiler.go | 50 ++++++++++++++++++++++++++++++++++- transform/testdata/allocs2.go | 21 ++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index c6e577fd..e34ac978 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -964,11 +964,59 @@ func (b *builder) createFunction() { } } +// posser is an interface that's implemented by both ssa.Value and +// ssa.Instruction. It is implemented by everything that has a Pos() method, +// which is all that getPos() needs. +type posser interface { + Pos() token.Pos +} + +// getPos returns position information for a ssa.Value or ssa.Instruction. +// +// Not all instructions have position information, especially when they're +// implicit (such as implicit casts or implicit returns at the end of a +// function). In these cases, it makes sense to try a bit harder to guess what +// the position really should be. +func getPos(val posser) token.Pos { + pos := val.Pos() + if pos != token.NoPos { + // Easy: position is known. + return pos + } + + // No position information is known. + switch val := val.(type) { + case *ssa.MakeInterface: + return getPos(val.X) + case *ssa.Return: + syntax := val.Parent().Syntax() + if syntax != nil { + // non-synthetic + return syntax.End() + } + return token.NoPos + case *ssa.FieldAddr: + return getPos(val.X) + case *ssa.IndexAddr: + return getPos(val.X) + case *ssa.Slice: + return getPos(val.X) + case *ssa.Store: + return getPos(val.Addr) + case *ssa.Extract: + return getPos(val.Tuple) + default: + // This is reachable, for example with *ssa.Const, *ssa.If, and + // *ssa.Jump. They might be implemented in some way in the future. + return token.NoPos + } +} + // createInstruction builds the LLVM IR equivalent instructions for the // particular Go SSA instruction. func (b *builder) createInstruction(instr ssa.Instruction) { if b.Debug { - pos := b.program.Fset.Position(instr.Pos()) + pos := b.program.Fset.Position(getPos(instr)) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) } diff --git a/transform/testdata/allocs2.go b/transform/testdata/allocs2.go index 3e1df07d..aeddcf6b 100644 --- a/transform/testdata/allocs2.go +++ b/transform/testdata/allocs2.go @@ -24,11 +24,24 @@ func main() { readByteSlice(s4) s5 := make([]int, 4) // OUT: object allocated on the heap: escapes at line 27 - s5 = append(s5, 5) + _ = 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 34 + useInterface(c1) + + n3 := 5 // OUT: object allocated on the heap: escapes at line 39 + func() int { + return n3 + }() + + callVariadic(3, 5, 8) // OUT: object allocated on the heap: escapes at line 41 + + s8 := []int{3, 5, 8} // OUT: object allocated on the heap: escapes at line 44 + callVariadic(s8...) } func derefInt(x *int) int { @@ -56,3 +69,9 @@ func getUnknownNumber() int func copySlice(out, in []int) { copy(out, in) } + +func getComplex128() complex128 + +func useInterface(interface{}) + +func callVariadic(...int)