mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
transform: follow aggregate returned alloc aliases
Track whether an allocation reaches a callee return separately from the
instruction where it escapes. The extra result state is needed because LLVM's
returned parameter attribute only covers scalar returns: a slice helper returns
its data pointer inside a {ptr, len, cap} aggregate, so the alias is only
visible by walking insertvalue and ret uses in the callee.
This lets OptimizeAllocs keep the backing array for returnIntSlice(s) on the
stack when the returned slice is ignored, matching gc's escape decision for the
same pattern. The golden output still keeps the escaping returned-slice case on
the heap.
This commit is contained in:
+56
-25
@@ -162,21 +162,41 @@ func FormatAllocCover(pos token.Position) string {
|
||||
// valueEscapesAt returns the instruction where the given value may escape and a
|
||||
// nil llvm.Value if it definitely doesn't. The value must be an instruction.
|
||||
func valueEscapesAt(value llvm.Value) llvm.Value {
|
||||
return valueEscapesAtImpl(value, false, nil)
|
||||
return valueEscapesAtImpl(value, false, nil).escapeAt
|
||||
}
|
||||
|
||||
func valueEscapesAtImpl(value llvm.Value, allowReturn bool, visiting map[llvm.Value]struct{}) llvm.Value {
|
||||
type escapeResult struct {
|
||||
escapeAt llvm.Value
|
||||
|
||||
// returned is separate from escapeAt because values can flow to a return
|
||||
// through aggregate operations. LLVM can mark a scalar returned parameter,
|
||||
// but a slice data pointer returned inside {ptr, len, cap} is only visible
|
||||
// after walking insertvalue/ret uses in the callee.
|
||||
returned bool
|
||||
}
|
||||
|
||||
func (r *escapeResult) merge(other escapeResult) bool {
|
||||
if !other.escapeAt.IsNil() {
|
||||
r.escapeAt = other.escapeAt
|
||||
return false
|
||||
}
|
||||
r.returned = r.returned || other.returned
|
||||
return true
|
||||
}
|
||||
|
||||
func valueEscapesAtImpl(value llvm.Value, allowReturn bool, visiting map[llvm.Value]struct{}) escapeResult {
|
||||
if visiting == nil {
|
||||
visiting = make(map[llvm.Value]struct{})
|
||||
}
|
||||
if _, ok := visiting[value]; ok {
|
||||
// Recursive call graph while following returned parameters. Treat this
|
||||
// as escaping to keep the analysis conservative and bounded.
|
||||
return value
|
||||
return escapeResult{escapeAt: value}
|
||||
}
|
||||
visiting[value] = struct{}{}
|
||||
defer delete(visiting, value)
|
||||
|
||||
var result escapeResult
|
||||
uses := getUses(value)
|
||||
for _, use := range uses {
|
||||
if use.IsAInstruction().IsNil() {
|
||||
@@ -184,13 +204,23 @@ func valueEscapesAtImpl(value llvm.Value, allowReturn bool, visiting map[llvm.Va
|
||||
}
|
||||
switch use.InstructionOpcode() {
|
||||
case llvm.GetElementPtr:
|
||||
if at := valueEscapesAtImpl(use, allowReturn, visiting); !at.IsNil() {
|
||||
return at
|
||||
if !result.merge(valueEscapesAtImpl(use, allowReturn, visiting)) {
|
||||
return result
|
||||
}
|
||||
case llvm.BitCast:
|
||||
// A bitcast escapes if the casted-to value escapes.
|
||||
if at := valueEscapesAtImpl(use, allowReturn, visiting); !at.IsNil() {
|
||||
return at
|
||||
if !result.merge(valueEscapesAtImpl(use, allowReturn, visiting)) {
|
||||
return result
|
||||
}
|
||||
case llvm.InsertValue:
|
||||
if !result.merge(valueEscapesAtImpl(use, allowReturn, visiting)) {
|
||||
return result
|
||||
}
|
||||
case llvm.ExtractValue:
|
||||
if use.Type().TypeKind() == llvm.PointerTypeKind {
|
||||
if !result.merge(valueEscapesAtImpl(use, allowReturn, visiting)) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
case llvm.Load:
|
||||
// Load does not escape.
|
||||
@@ -198,41 +228,42 @@ func valueEscapesAtImpl(value llvm.Value, allowReturn bool, visiting map[llvm.Va
|
||||
// Store only escapes when the value is stored to, not when the
|
||||
// value is stored into another value.
|
||||
if use.Operand(0) == value {
|
||||
return use
|
||||
return escapeResult{escapeAt: use}
|
||||
}
|
||||
case llvm.Call:
|
||||
if at := callValueEscapesAt(use, value, allowReturn, visiting); !at.IsNil() {
|
||||
return at
|
||||
if !result.merge(callValueEscapesAt(use, value, allowReturn, visiting)) {
|
||||
return result
|
||||
}
|
||||
case llvm.ICmp:
|
||||
// Comparing pointers don't let the pointer escape.
|
||||
// This is often a compiler-inserted nil check.
|
||||
case llvm.Ret:
|
||||
if !allowReturn || use.Operand(0) != value {
|
||||
return use
|
||||
return escapeResult{escapeAt: use}
|
||||
}
|
||||
result.returned = true
|
||||
default:
|
||||
// Unknown instruction, might escape.
|
||||
return use
|
||||
return escapeResult{escapeAt: use}
|
||||
}
|
||||
}
|
||||
|
||||
// Checked all uses, and none let the pointer value escape.
|
||||
return llvm.Value{}
|
||||
return result
|
||||
}
|
||||
|
||||
// callValueEscapesAt returns whether value escapes through this call. It also
|
||||
// handles calls that return value unchanged, as long as the called function does
|
||||
// not otherwise capture the parameter and the returned alias does not escape.
|
||||
func callValueEscapesAt(call, value llvm.Value, allowReturn bool, visiting map[llvm.Value]struct{}) llvm.Value {
|
||||
func callValueEscapesAt(call, value llvm.Value, allowReturn bool, visiting map[llvm.Value]struct{}) escapeResult {
|
||||
called := call.CalledValue()
|
||||
if called.IsAFunction().IsNil() {
|
||||
return call
|
||||
return escapeResult{escapeAt: call}
|
||||
}
|
||||
kindNoCapture := llvm.AttributeKindID("nocapture")
|
||||
kindReturned := llvm.AttributeKindID("returned")
|
||||
matched := false
|
||||
returned := false
|
||||
var result escapeResult
|
||||
for i := 0; i < called.ParamsCount(); i++ {
|
||||
if call.Operand(i) != value {
|
||||
continue
|
||||
@@ -242,30 +273,30 @@ func callValueEscapesAt(call, value llvm.Value, allowReturn bool, visiting map[l
|
||||
nocapture := !called.GetEnumAttributeAtIndex(index, kindNoCapture).IsNil()
|
||||
returnedParam := !called.GetEnumAttributeAtIndex(index, kindReturned).IsNil()
|
||||
if returnedParam {
|
||||
returned = true
|
||||
result.returned = true
|
||||
}
|
||||
if nocapture {
|
||||
continue
|
||||
}
|
||||
if !returnedParam || called.IsDeclaration() {
|
||||
return call
|
||||
if called.IsDeclaration() {
|
||||
return escapeResult{escapeAt: call}
|
||||
}
|
||||
if at := valueEscapesAtImpl(called.Param(i), true, visiting); !at.IsNil() {
|
||||
return at
|
||||
if !result.merge(valueEscapesAtImpl(called.Param(i), true, visiting)) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
for i := called.ParamsCount(); i < call.OperandsCount(); i++ {
|
||||
if call.Operand(i) == value {
|
||||
return call
|
||||
return escapeResult{escapeAt: call}
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return llvm.Value{}
|
||||
return escapeResult{}
|
||||
}
|
||||
if returned {
|
||||
if result.returned {
|
||||
return valueEscapesAtImpl(call, allowReturn, visiting)
|
||||
}
|
||||
return llvm.Value{}
|
||||
return escapeResult{}
|
||||
}
|
||||
|
||||
func lineLengthAt(filename string, lineNumber int) int {
|
||||
|
||||
Vendored
-1
@@ -1,4 +1,3 @@
|
||||
testdata/allocs2.go:21.1,21.22 1 0
|
||||
testdata/allocs2.go:24.1,24.43 1 0
|
||||
testdata/allocs2.go:26.1,26.25 1 0
|
||||
testdata/allocs2.go:29.1,29.22 1 0
|
||||
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
testdata/allocs2.go:21:12: object allocated on the heap: escapes at line 22
|
||||
testdata/allocs2.go:24:15: object allocated on the heap: size is not constant
|
||||
testdata/allocs2.go:26:12: object allocated on the heap: object size 300 exceeds maximum stack allocation size 256
|
||||
testdata/allocs2.go:29:12: object allocated on the heap: escapes at line 30
|
||||
@@ -9,4 +8,4 @@ testdata/allocs2.go:49:2: object allocated on the heap: escapes at line 51
|
||||
testdata/allocs2.go:50:2: object allocated on the heap: escapes at line 51
|
||||
testdata/allocs2.go:107:11: object allocated on the heap: escapes at line 108
|
||||
testdata/allocs2.go:114:2: object allocated on the heap: escapes at line 117
|
||||
testdata/allocs2.go:128:2: object allocated on the heap: escapes at line 130
|
||||
testdata/allocs2.go:128:2: object allocated on the heap: escapes at unknown line
|
||||
|
||||
Reference in New Issue
Block a user