2015-02-13 14:40:36 -05:00
|
|
|
// Copyright 2011 The Go Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
package gc
|
|
|
|
|
|
|
|
|
|
import (
|
cmd/compile: factor out Pkg, Sym, and Type into package types
- created new package cmd/compile/internal/types
- moved Pkg, Sym, Type to new package
- to break cycles, for now we need the (ugly) types/utils.go
file which contains a handful of functions that must be installed
early by the gc frontend
- to break cycles, for now we need two functions to convert between
*gc.Node and *types.Node (the latter is a dummy type)
- adjusted the gc's code to use the new package and the conversion
functions as needed
- made several Pkg, Sym, and Type methods functions as needed
- renamed constructors typ, typPtr, typArray, etc. to types.New,
types.NewPtr, types.NewArray, etc.
Passes toolstash-check -all.
Change-Id: I8adfa5e85c731645d0a7fd2030375ed6ebf54b72
Reviewed-on: https://go-review.googlesource.com/39855
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
2017-04-04 17:54:02 -07:00
|
|
|
"cmd/compile/internal/types"
|
2015-02-13 14:40:36 -05:00
|
|
|
"fmt"
|
|
|
|
|
)
|
|
|
|
|
|
2016-03-09 20:29:21 -08:00
|
|
|
func escapes(all []*Node) {
|
2019-07-25 12:40:02 -07:00
|
|
|
visitBottomUp(all, escapeFuncs)
|
2015-02-24 12:14:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
EscFuncUnknown = 0 + iota
|
|
|
|
|
EscFuncPlanned
|
|
|
|
|
EscFuncStarted
|
|
|
|
|
EscFuncTagged
|
|
|
|
|
)
|
|
|
|
|
|
cmd/internal/gc: improve flow of input params to output params
This includes the following information in the per-function summary:
outK = paramJ encoded in outK bits for paramJ
outK = *paramJ encoded in outK bits for paramJ
heap = paramJ EscHeap
heap = *paramJ EscContentEscapes
Note that (currently) if the address of a parameter is taken and
returned, necessarily a heap allocation occurred to contain that
reference, and the heap can never refer to stack, therefore the
parameter and everything downstream from it escapes to the heap.
The per-function summary information now has a tuneable number of bits
(2 is probably noticeably better than 1, 3 is likely overkill, but it
is now easy to check and the -m debugging output includes information
that allows you to figure out if more would be better.)
A new test was added to check pointer flow through struct-typed and
*struct-typed parameters and returns; some of these are sensitive to
the number of summary bits, and ought to yield better results with a
more competent escape analysis algorithm. Another new test checks
(some) correctness with array parameters, results, and operations.
The old analysis inferred a piece of plan9 runtime was non-escaping by
counteracting overconservative analysis with buggy analysis; with the
bug fixed, the result was too conservative (and it's not easy to fix
in this framework) so the source code was tweaked to get the desired
result. A test was added against the discovered bug.
The escape analysis was further improved splitting the "level" into
3 parts, one tracking the conventional "level" and the other two
computing the highest-level-suffix-from-copy, which is used to
generally model the cancelling effect of indirection applied to
address-of.
With the improved escape analysis enabled, it was necessary to
modify one of the runtime tests because it now attempts to allocate
too much on the (small, fixed-size) G0 (system) stack and this
failed the test.
Compiling src/std after touching src/runtime/*.go with -m logging
turned on shows 420 fewer heap allocation sites (10538 vs 10968).
Profiling allocations in src/html/template with
for i in {1..5} ;
do go tool 6g -memprofile=mastx.${i}.prof -memprofilerate=1 *.go;
go tool pprof -alloc_objects -text mastx.${i}.prof ;
done
showed a 15% reduction in allocations performed by the compiler.
Update #3753
Update #4720
Fixes #10466
Change-Id: I0fd97d5f5ac527b45f49e2218d158a6e89951432
Reviewed-on: https://go-review.googlesource.com/8202
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
2015-03-26 16:36:15 -04:00
|
|
|
func min8(a, b int8) int8 {
|
|
|
|
|
if a < b {
|
|
|
|
|
return a
|
|
|
|
|
}
|
|
|
|
|
return b
|
|
|
|
|
}
|
2015-02-13 14:40:36 -05:00
|
|
|
|
cmd/internal/gc: improve flow of input params to output params
This includes the following information in the per-function summary:
outK = paramJ encoded in outK bits for paramJ
outK = *paramJ encoded in outK bits for paramJ
heap = paramJ EscHeap
heap = *paramJ EscContentEscapes
Note that (currently) if the address of a parameter is taken and
returned, necessarily a heap allocation occurred to contain that
reference, and the heap can never refer to stack, therefore the
parameter and everything downstream from it escapes to the heap.
The per-function summary information now has a tuneable number of bits
(2 is probably noticeably better than 1, 3 is likely overkill, but it
is now easy to check and the -m debugging output includes information
that allows you to figure out if more would be better.)
A new test was added to check pointer flow through struct-typed and
*struct-typed parameters and returns; some of these are sensitive to
the number of summary bits, and ought to yield better results with a
more competent escape analysis algorithm. Another new test checks
(some) correctness with array parameters, results, and operations.
The old analysis inferred a piece of plan9 runtime was non-escaping by
counteracting overconservative analysis with buggy analysis; with the
bug fixed, the result was too conservative (and it's not easy to fix
in this framework) so the source code was tweaked to get the desired
result. A test was added against the discovered bug.
The escape analysis was further improved splitting the "level" into
3 parts, one tracking the conventional "level" and the other two
computing the highest-level-suffix-from-copy, which is used to
generally model the cancelling effect of indirection applied to
address-of.
With the improved escape analysis enabled, it was necessary to
modify one of the runtime tests because it now attempts to allocate
too much on the (small, fixed-size) G0 (system) stack and this
failed the test.
Compiling src/std after touching src/runtime/*.go with -m logging
turned on shows 420 fewer heap allocation sites (10538 vs 10968).
Profiling allocations in src/html/template with
for i in {1..5} ;
do go tool 6g -memprofile=mastx.${i}.prof -memprofilerate=1 *.go;
go tool pprof -alloc_objects -text mastx.${i}.prof ;
done
showed a 15% reduction in allocations performed by the compiler.
Update #3753
Update #4720
Fixes #10466
Change-Id: I0fd97d5f5ac527b45f49e2218d158a6e89951432
Reviewed-on: https://go-review.googlesource.com/8202
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
2015-03-26 16:36:15 -04:00
|
|
|
func max8(a, b int8) int8 {
|
|
|
|
|
if a > b {
|
|
|
|
|
return a
|
2015-02-13 14:40:36 -05:00
|
|
|
}
|
cmd/internal/gc: improve flow of input params to output params
This includes the following information in the per-function summary:
outK = paramJ encoded in outK bits for paramJ
outK = *paramJ encoded in outK bits for paramJ
heap = paramJ EscHeap
heap = *paramJ EscContentEscapes
Note that (currently) if the address of a parameter is taken and
returned, necessarily a heap allocation occurred to contain that
reference, and the heap can never refer to stack, therefore the
parameter and everything downstream from it escapes to the heap.
The per-function summary information now has a tuneable number of bits
(2 is probably noticeably better than 1, 3 is likely overkill, but it
is now easy to check and the -m debugging output includes information
that allows you to figure out if more would be better.)
A new test was added to check pointer flow through struct-typed and
*struct-typed parameters and returns; some of these are sensitive to
the number of summary bits, and ought to yield better results with a
more competent escape analysis algorithm. Another new test checks
(some) correctness with array parameters, results, and operations.
The old analysis inferred a piece of plan9 runtime was non-escaping by
counteracting overconservative analysis with buggy analysis; with the
bug fixed, the result was too conservative (and it's not easy to fix
in this framework) so the source code was tweaked to get the desired
result. A test was added against the discovered bug.
The escape analysis was further improved splitting the "level" into
3 parts, one tracking the conventional "level" and the other two
computing the highest-level-suffix-from-copy, which is used to
generally model the cancelling effect of indirection applied to
address-of.
With the improved escape analysis enabled, it was necessary to
modify one of the runtime tests because it now attempts to allocate
too much on the (small, fixed-size) G0 (system) stack and this
failed the test.
Compiling src/std after touching src/runtime/*.go with -m logging
turned on shows 420 fewer heap allocation sites (10538 vs 10968).
Profiling allocations in src/html/template with
for i in {1..5} ;
do go tool 6g -memprofile=mastx.${i}.prof -memprofilerate=1 *.go;
go tool pprof -alloc_objects -text mastx.${i}.prof ;
done
showed a 15% reduction in allocations performed by the compiler.
Update #3753
Update #4720
Fixes #10466
Change-Id: I0fd97d5f5ac527b45f49e2218d158a6e89951432
Reviewed-on: https://go-review.googlesource.com/8202
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
2015-03-26 16:36:15 -04:00
|
|
|
return b
|
|
|
|
|
}
|
2015-02-13 14:40:36 -05:00
|
|
|
|
cmd/internal/gc: improve flow of input params to output params
This includes the following information in the per-function summary:
outK = paramJ encoded in outK bits for paramJ
outK = *paramJ encoded in outK bits for paramJ
heap = paramJ EscHeap
heap = *paramJ EscContentEscapes
Note that (currently) if the address of a parameter is taken and
returned, necessarily a heap allocation occurred to contain that
reference, and the heap can never refer to stack, therefore the
parameter and everything downstream from it escapes to the heap.
The per-function summary information now has a tuneable number of bits
(2 is probably noticeably better than 1, 3 is likely overkill, but it
is now easy to check and the -m debugging output includes information
that allows you to figure out if more would be better.)
A new test was added to check pointer flow through struct-typed and
*struct-typed parameters and returns; some of these are sensitive to
the number of summary bits, and ought to yield better results with a
more competent escape analysis algorithm. Another new test checks
(some) correctness with array parameters, results, and operations.
The old analysis inferred a piece of plan9 runtime was non-escaping by
counteracting overconservative analysis with buggy analysis; with the
bug fixed, the result was too conservative (and it's not easy to fix
in this framework) so the source code was tweaked to get the desired
result. A test was added against the discovered bug.
The escape analysis was further improved splitting the "level" into
3 parts, one tracking the conventional "level" and the other two
computing the highest-level-suffix-from-copy, which is used to
generally model the cancelling effect of indirection applied to
address-of.
With the improved escape analysis enabled, it was necessary to
modify one of the runtime tests because it now attempts to allocate
too much on the (small, fixed-size) G0 (system) stack and this
failed the test.
Compiling src/std after touching src/runtime/*.go with -m logging
turned on shows 420 fewer heap allocation sites (10538 vs 10968).
Profiling allocations in src/html/template with
for i in {1..5} ;
do go tool 6g -memprofile=mastx.${i}.prof -memprofilerate=1 *.go;
go tool pprof -alloc_objects -text mastx.${i}.prof ;
done
showed a 15% reduction in allocations performed by the compiler.
Update #3753
Update #4720
Fixes #10466
Change-Id: I0fd97d5f5ac527b45f49e2218d158a6e89951432
Reviewed-on: https://go-review.googlesource.com/8202
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
2015-03-26 16:36:15 -04:00
|
|
|
const (
|
2019-09-26 17:21:50 -07:00
|
|
|
EscUnknown = iota
|
|
|
|
|
EscNone // Does not escape to heap, result, or parameters.
|
|
|
|
|
EscHeap // Reachable from the heap
|
|
|
|
|
EscNever // By construction will not escape.
|
cmd/internal/gc: improve flow of input params to output params
This includes the following information in the per-function summary:
outK = paramJ encoded in outK bits for paramJ
outK = *paramJ encoded in outK bits for paramJ
heap = paramJ EscHeap
heap = *paramJ EscContentEscapes
Note that (currently) if the address of a parameter is taken and
returned, necessarily a heap allocation occurred to contain that
reference, and the heap can never refer to stack, therefore the
parameter and everything downstream from it escapes to the heap.
The per-function summary information now has a tuneable number of bits
(2 is probably noticeably better than 1, 3 is likely overkill, but it
is now easy to check and the -m debugging output includes information
that allows you to figure out if more would be better.)
A new test was added to check pointer flow through struct-typed and
*struct-typed parameters and returns; some of these are sensitive to
the number of summary bits, and ought to yield better results with a
more competent escape analysis algorithm. Another new test checks
(some) correctness with array parameters, results, and operations.
The old analysis inferred a piece of plan9 runtime was non-escaping by
counteracting overconservative analysis with buggy analysis; with the
bug fixed, the result was too conservative (and it's not easy to fix
in this framework) so the source code was tweaked to get the desired
result. A test was added against the discovered bug.
The escape analysis was further improved splitting the "level" into
3 parts, one tracking the conventional "level" and the other two
computing the highest-level-suffix-from-copy, which is used to
generally model the cancelling effect of indirection applied to
address-of.
With the improved escape analysis enabled, it was necessary to
modify one of the runtime tests because it now attempts to allocate
too much on the (small, fixed-size) G0 (system) stack and this
failed the test.
Compiling src/std after touching src/runtime/*.go with -m logging
turned on shows 420 fewer heap allocation sites (10538 vs 10968).
Profiling allocations in src/html/template with
for i in {1..5} ;
do go tool 6g -memprofile=mastx.${i}.prof -memprofilerate=1 *.go;
go tool pprof -alloc_objects -text mastx.${i}.prof ;
done
showed a 15% reduction in allocations performed by the compiler.
Update #3753
Update #4720
Fixes #10466
Change-Id: I0fd97d5f5ac527b45f49e2218d158a6e89951432
Reviewed-on: https://go-review.googlesource.com/8202
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
2015-03-26 16:36:15 -04:00
|
|
|
)
|
|
|
|
|
|
2015-06-03 14:16:01 -04:00
|
|
|
// funcSym returns fn.Func.Nname.Sym if no nils are encountered along the way.
|
cmd/compile: factor out Pkg, Sym, and Type into package types
- created new package cmd/compile/internal/types
- moved Pkg, Sym, Type to new package
- to break cycles, for now we need the (ugly) types/utils.go
file which contains a handful of functions that must be installed
early by the gc frontend
- to break cycles, for now we need two functions to convert between
*gc.Node and *types.Node (the latter is a dummy type)
- adjusted the gc's code to use the new package and the conversion
functions as needed
- made several Pkg, Sym, and Type methods functions as needed
- renamed constructors typ, typPtr, typArray, etc. to types.New,
types.NewPtr, types.NewArray, etc.
Passes toolstash-check -all.
Change-Id: I8adfa5e85c731645d0a7fd2030375ed6ebf54b72
Reviewed-on: https://go-review.googlesource.com/39855
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
2017-04-04 17:54:02 -07:00
|
|
|
func funcSym(fn *Node) *types.Sym {
|
2015-05-27 10:42:55 -04:00
|
|
|
if fn == nil || fn.Func.Nname == nil {
|
2015-05-15 12:19:07 -04:00
|
|
|
return nil
|
|
|
|
|
}
|
2015-05-27 10:42:55 -04:00
|
|
|
return fn.Func.Nname.Sym
|
2015-05-15 12:19:07 -04:00
|
|
|
}
|
|
|
|
|
|
2016-10-12 11:34:47 +09:00
|
|
|
// Mark labels that have no backjumps to them as not increasing e.loopdepth.
|
|
|
|
|
// Walk hasn't generated (goto|label).Left.Sym.Label yet, so we'll cheat
|
2016-03-01 23:21:55 +00:00
|
|
|
// and set it to one of the following two. Then in esc we'll clear it again.
|
2016-10-26 17:14:35 -07:00
|
|
|
var (
|
|
|
|
|
looping Node
|
|
|
|
|
nonlooping Node
|
|
|
|
|
)
|
2015-02-13 14:40:36 -05:00
|
|
|
|
2019-04-02 10:40:12 -07:00
|
|
|
func isSliceSelfAssign(dst, src *Node) bool {
|
2018-09-04 23:14:53 +03:00
|
|
|
// Detect the following special case.
|
|
|
|
|
//
|
|
|
|
|
// func (b *Buffer) Foo() {
|
|
|
|
|
// n, m := ...
|
|
|
|
|
// b.buf = b.buf[n:m]
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// This assignment is a no-op for escape analysis,
|
|
|
|
|
// it does not store any new pointers into b that were not already there.
|
|
|
|
|
// However, without this special case b will escape, because we assign to OIND/ODOTPTR.
|
|
|
|
|
// Here we assume that the statement will not contain calls,
|
|
|
|
|
// that is, that order will move any calls to init.
|
|
|
|
|
// Otherwise base ONAME value could change between the moments
|
|
|
|
|
// when we evaluate it for dst and for src.
|
|
|
|
|
|
|
|
|
|
// dst is ONAME dereference.
|
2018-11-18 08:34:38 -08:00
|
|
|
if dst.Op != ODEREF && dst.Op != ODOTPTR || dst.Left.Op != ONAME {
|
2018-09-04 23:14:53 +03:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
// src is a slice operation.
|
|
|
|
|
switch src.Op {
|
|
|
|
|
case OSLICE, OSLICE3, OSLICESTR:
|
|
|
|
|
// OK.
|
|
|
|
|
case OSLICEARR, OSLICE3ARR:
|
|
|
|
|
// Since arrays are embedded into containing object,
|
|
|
|
|
// slice of non-pointer array will introduce a new pointer into b that was not already there
|
|
|
|
|
// (pointer to b itself). After such assignment, if b contents escape,
|
|
|
|
|
// b escapes as well. If we ignore such OSLICEARR, we will conclude
|
|
|
|
|
// that b does not escape when b contents do.
|
|
|
|
|
//
|
|
|
|
|
// Pointer to an array is OK since it's not stored inside b directly.
|
|
|
|
|
// For slicing an array (not pointer to array), there is an implicit OADDR.
|
|
|
|
|
// We check that to determine non-pointer array slicing.
|
|
|
|
|
if src.Left.Op == OADDR {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
// slice is applied to ONAME dereference.
|
2018-11-18 08:34:38 -08:00
|
|
|
if src.Left.Op != ODEREF && src.Left.Op != ODOTPTR || src.Left.Left.Op != ONAME {
|
2018-09-04 23:14:53 +03:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
// dst and src reference the same base ONAME.
|
|
|
|
|
return dst.Left == src.Left.Left
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-27 19:32:17 +03:00
|
|
|
// isSelfAssign reports whether assignment from src to dst can
|
|
|
|
|
// be ignored by the escape analysis as it's effectively a self-assignment.
|
2019-04-02 10:40:12 -07:00
|
|
|
func isSelfAssign(dst, src *Node) bool {
|
|
|
|
|
if isSliceSelfAssign(dst, src) {
|
2018-09-19 15:53:03 +03:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-04 23:14:53 +03:00
|
|
|
// Detect trivial assignments that assign back to the same object.
|
|
|
|
|
//
|
|
|
|
|
// It covers these cases:
|
|
|
|
|
// val.x = val.y
|
|
|
|
|
// val.x[i] = val.y[j]
|
|
|
|
|
// val.x1.x2 = val.x1.y2
|
|
|
|
|
// ... etc
|
|
|
|
|
//
|
|
|
|
|
// These assignments do not change assigned object lifetime.
|
|
|
|
|
|
2018-07-27 19:32:17 +03:00
|
|
|
if dst == nil || src == nil || dst.Op != src.Op {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch dst.Op {
|
|
|
|
|
case ODOT, ODOTPTR:
|
|
|
|
|
// Safe trailing accessors that are permitted to differ.
|
|
|
|
|
case OINDEX:
|
2019-04-02 10:40:12 -07:00
|
|
|
if mayAffectMemory(dst.Right) || mayAffectMemory(src.Right) {
|
2018-07-27 19:32:17 +03:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The expression prefix must be both "safe" and identical.
|
|
|
|
|
return samesafeexpr(dst.Left, src.Left)
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-06 20:59:12 -04:00
|
|
|
// mayAffectMemory reports whether evaluation of n may affect the program's
|
|
|
|
|
// memory state. If the expression can't affect memory state, then it can be
|
|
|
|
|
// safely ignored by the escape analysis.
|
2019-04-02 10:40:12 -07:00
|
|
|
func mayAffectMemory(n *Node) bool {
|
2020-06-06 20:59:12 -04:00
|
|
|
// We may want to use a list of "memory safe" ops instead of generally
|
|
|
|
|
// "side-effect free", which would include all calls and other ops that can
|
|
|
|
|
// allocate or change global state. For now, it's safer to start with the latter.
|
2018-07-27 19:32:17 +03:00
|
|
|
//
|
|
|
|
|
// We're ignoring things like division by zero, index out of range,
|
|
|
|
|
// and nil pointer dereference here.
|
|
|
|
|
switch n.Op {
|
|
|
|
|
case ONAME, OCLOSUREVAR, OLITERAL:
|
|
|
|
|
return false
|
2018-09-05 18:49:52 +03:00
|
|
|
|
|
|
|
|
// Left+Right group.
|
|
|
|
|
case OINDEX, OADD, OSUB, OOR, OXOR, OMUL, OLSH, ORSH, OAND, OANDNOT, ODIV, OMOD:
|
2019-04-02 10:40:12 -07:00
|
|
|
return mayAffectMemory(n.Left) || mayAffectMemory(n.Right)
|
2018-09-05 18:49:52 +03:00
|
|
|
|
|
|
|
|
// Left group.
|
2018-11-18 08:34:38 -08:00
|
|
|
case ODOT, ODOTPTR, ODEREF, OCONVNOP, OCONV, OLEN, OCAP,
|
|
|
|
|
ONOT, OBITNOT, OPLUS, ONEG, OALIGNOF, OOFFSETOF, OSIZEOF:
|
2019-04-02 10:40:12 -07:00
|
|
|
return mayAffectMemory(n.Left)
|
2018-09-05 18:49:52 +03:00
|
|
|
|
2018-07-27 19:32:17 +03:00
|
|
|
default:
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-01 12:03:27 +02:00
|
|
|
// heapAllocReason returns the reason the given Node must be heap
|
|
|
|
|
// allocated, or the empty string if it doesn't.
|
|
|
|
|
func heapAllocReason(n *Node) string {
|
2019-11-07 12:32:30 -08:00
|
|
|
if n.Type == nil {
|
2020-10-01 12:03:27 +02:00
|
|
|
return ""
|
2019-11-07 12:32:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parameters are always passed via the stack.
|
|
|
|
|
if n.Op == ONAME && (n.Class() == PPARAM || n.Class() == PPARAMOUT) {
|
2020-10-01 12:03:27 +02:00
|
|
|
return ""
|
2019-11-07 12:32:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n.Type.Width > maxStackVarSize {
|
2020-10-01 12:03:27 +02:00
|
|
|
return "too large for stack"
|
2019-11-07 12:32:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (n.Op == ONEW || n.Op == OPTRLIT) && n.Type.Elem().Width >= maxImplicitStackVarSize {
|
2020-10-01 12:03:27 +02:00
|
|
|
return "too large for stack"
|
2019-11-07 12:32:30 -08:00
|
|
|
}
|
|
|
|
|
|
2020-07-27 12:08:56 +07:00
|
|
|
if n.Op == OCLOSURE && closureType(n).Size() >= maxImplicitStackVarSize {
|
2020-10-01 12:03:27 +02:00
|
|
|
return "too large for stack"
|
2020-07-27 12:08:56 +07:00
|
|
|
}
|
|
|
|
|
if n.Op == OCALLPART && partialCallType(n).Size() >= maxImplicitStackVarSize {
|
2020-10-01 12:03:27 +02:00
|
|
|
return "too large for stack"
|
2020-07-27 12:08:56 +07:00
|
|
|
}
|
|
|
|
|
|
2020-10-01 12:03:27 +02:00
|
|
|
if n.Op == OMAKESLICE {
|
|
|
|
|
r := n.Right
|
|
|
|
|
if r == nil {
|
|
|
|
|
r = n.Left
|
|
|
|
|
}
|
|
|
|
|
if !smallintconst(r) {
|
|
|
|
|
return "non-constant size"
|
|
|
|
|
}
|
2020-10-12 15:02:59 +02:00
|
|
|
if t := n.Type; t.Elem().Width != 0 && r.Int64Val() >= maxImplicitStackVarSize/t.Elem().Width {
|
2020-10-01 12:03:27 +02:00
|
|
|
return "too large for stack"
|
|
|
|
|
}
|
2019-11-07 12:32:30 -08:00
|
|
|
}
|
|
|
|
|
|
2020-10-01 12:03:27 +02:00
|
|
|
return ""
|
2019-04-02 10:40:12 -07:00
|
|
|
}
|
|
|
|
|
|
2017-04-27 06:29:07 -07:00
|
|
|
// addrescapes tags node n as having had its address taken
|
|
|
|
|
// by "increasing" the "value" of n.Esc to EscHeap.
|
|
|
|
|
// Storage is allocated as necessary to allow the address
|
|
|
|
|
// to be taken.
|
|
|
|
|
func addrescapes(n *Node) {
|
|
|
|
|
switch n.Op {
|
|
|
|
|
default:
|
|
|
|
|
// Unexpected Op, probably due to a previous type error. Ignore.
|
|
|
|
|
|
2018-11-18 08:34:38 -08:00
|
|
|
case ODEREF, ODOTPTR:
|
2017-04-27 06:29:07 -07:00
|
|
|
// Nothing to do.
|
|
|
|
|
|
|
|
|
|
case ONAME:
|
2018-03-08 21:00:36 +00:00
|
|
|
if n == nodfp {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-27 06:29:07 -07:00
|
|
|
// if this is a tmpname (PAUTO), it was tagged by tmpname as not escaping.
|
|
|
|
|
// on PPARAM it means something different.
|
|
|
|
|
if n.Class() == PAUTO && n.Esc == EscNever {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If a closure reference escapes, mark the outer variable as escaping.
|
cmd/compile: move some ONAME-specific flags from Node to Name
The IsClosureVar, IsOutputParamHeapAddr, Assigned, Addrtaken,
InlFormal, and InlLocal flags are only interesting for ONAME nodes, so
it's better to set these flags on Name.flags instead of Node.flags.
Two caveats though:
1. Previously, we would set Assigned and Addrtaken on the entire
expression tree involved in an assignment or addressing operation.
However, the rest of the compiler only actually cares about knowing
whether the underlying ONAME (if any) was assigned/addressed.
2. This actually requires bumping Name.flags from bitset8 to bitset16,
whereas it doesn't allow shrinking Node.flags any. However, Name has
some trailing padding bytes, so expanding Name.flags doesn't cost any
memory.
Passes toolstash-check.
Change-Id: I7775d713566a38d5b9723360b1659b79391744c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/200898
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-10-12 16:21:55 -07:00
|
|
|
if n.Name.IsClosureVar() {
|
2017-04-27 06:29:07 -07:00
|
|
|
addrescapes(n.Name.Defn)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n.Class() != PPARAM && n.Class() != PPARAMOUT && n.Class() != PAUTO {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This is a plain parameter or local variable that needs to move to the heap,
|
|
|
|
|
// but possibly for the function outside the one we're compiling.
|
|
|
|
|
// That is, if we have:
|
|
|
|
|
//
|
|
|
|
|
// func f(x int) {
|
|
|
|
|
// func() {
|
|
|
|
|
// global = &x
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// then we're analyzing the inner closure but we need to move x to the
|
|
|
|
|
// heap in f, not in the inner closure. Flip over to f before calling moveToHeap.
|
|
|
|
|
oldfn := Curfn
|
|
|
|
|
Curfn = n.Name.Curfn
|
|
|
|
|
if Curfn.Func.Closure != nil && Curfn.Op == OCLOSURE {
|
|
|
|
|
Curfn = Curfn.Func.Closure
|
|
|
|
|
}
|
|
|
|
|
ln := lineno
|
|
|
|
|
lineno = Curfn.Pos
|
|
|
|
|
moveToHeap(n)
|
|
|
|
|
Curfn = oldfn
|
|
|
|
|
lineno = ln
|
|
|
|
|
|
|
|
|
|
// ODOTPTR has already been introduced,
|
|
|
|
|
// so these are the non-pointer ODOT and OINDEX.
|
|
|
|
|
// In &x[0], if x is a slice, then x does not
|
|
|
|
|
// escape--the pointer inside x does, but that
|
|
|
|
|
// is always a heap pointer anyway.
|
|
|
|
|
case ODOT, OINDEX, OPAREN, OCONVNOP:
|
|
|
|
|
if !n.Left.Type.IsSlice() {
|
|
|
|
|
addrescapes(n.Left)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// moveToHeap records the parameter or local variable n as moved to the heap.
|
|
|
|
|
func moveToHeap(n *Node) {
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.r != 0 {
|
2017-04-27 06:29:07 -07:00
|
|
|
Dump("MOVE", n)
|
|
|
|
|
}
|
|
|
|
|
if compiling_runtime {
|
2019-10-14 12:16:52 -07:00
|
|
|
yyerror("%v escapes to heap, not allowed in runtime", n)
|
2017-04-27 06:29:07 -07:00
|
|
|
}
|
|
|
|
|
if n.Class() == PAUTOHEAP {
|
|
|
|
|
Dump("n", n)
|
|
|
|
|
Fatalf("double move to heap")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Allocate a local stack variable to hold the pointer to the heap copy.
|
|
|
|
|
// temp will add it to the function declaration list automatically.
|
|
|
|
|
heapaddr := temp(types.NewPtr(n.Type))
|
|
|
|
|
heapaddr.Sym = lookup("&" + n.Sym.Name)
|
|
|
|
|
heapaddr.Orig.Sym = heapaddr.Sym
|
2017-07-09 17:03:45 +02:00
|
|
|
heapaddr.Pos = n.Pos
|
2017-04-27 06:29:07 -07:00
|
|
|
|
|
|
|
|
// Unset AutoTemp to persist the &foo variable name through SSA to
|
|
|
|
|
// liveness analysis.
|
|
|
|
|
// TODO(mdempsky/drchase): Cleaner solution?
|
|
|
|
|
heapaddr.Name.SetAutoTemp(false)
|
|
|
|
|
|
|
|
|
|
// Parameters have a local stack copy used at function start/end
|
|
|
|
|
// in addition to the copy in the heap that may live longer than
|
|
|
|
|
// the function.
|
|
|
|
|
if n.Class() == PPARAM || n.Class() == PPARAMOUT {
|
|
|
|
|
if n.Xoffset == BADWIDTH {
|
|
|
|
|
Fatalf("addrescapes before param assignment")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We rewrite n below to be a heap variable (indirection of heapaddr).
|
|
|
|
|
// Preserve a copy so we can still write code referring to the original,
|
|
|
|
|
// and substitute that copy into the function declaration list
|
|
|
|
|
// so that analyses of the local (on-stack) variables use it.
|
|
|
|
|
stackcopy := newname(n.Sym)
|
|
|
|
|
stackcopy.Type = n.Type
|
|
|
|
|
stackcopy.Xoffset = n.Xoffset
|
|
|
|
|
stackcopy.SetClass(n.Class())
|
|
|
|
|
stackcopy.Name.Param.Heapaddr = heapaddr
|
|
|
|
|
if n.Class() == PPARAMOUT {
|
|
|
|
|
// Make sure the pointer to the heap copy is kept live throughout the function.
|
|
|
|
|
// The function could panic at any point, and then a defer could recover.
|
|
|
|
|
// Thus, we need the pointer to the heap copy always available so the
|
|
|
|
|
// post-deferreturn code can copy the return value back to the stack.
|
|
|
|
|
// See issue 16095.
|
cmd/compile: move some ONAME-specific flags from Node to Name
The IsClosureVar, IsOutputParamHeapAddr, Assigned, Addrtaken,
InlFormal, and InlLocal flags are only interesting for ONAME nodes, so
it's better to set these flags on Name.flags instead of Node.flags.
Two caveats though:
1. Previously, we would set Assigned and Addrtaken on the entire
expression tree involved in an assignment or addressing operation.
However, the rest of the compiler only actually cares about knowing
whether the underlying ONAME (if any) was assigned/addressed.
2. This actually requires bumping Name.flags from bitset8 to bitset16,
whereas it doesn't allow shrinking Node.flags any. However, Name has
some trailing padding bytes, so expanding Name.flags doesn't cost any
memory.
Passes toolstash-check.
Change-Id: I7775d713566a38d5b9723360b1659b79391744c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/200898
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-10-12 16:21:55 -07:00
|
|
|
heapaddr.Name.SetIsOutputParamHeapAddr(true)
|
2017-04-27 06:29:07 -07:00
|
|
|
}
|
|
|
|
|
n.Name.Param.Stackcopy = stackcopy
|
|
|
|
|
|
|
|
|
|
// Substitute the stackcopy into the function variable list so that
|
|
|
|
|
// liveness and other analyses use the underlying stack slot
|
|
|
|
|
// and not the now-pseudo-variable n.
|
|
|
|
|
found := false
|
|
|
|
|
for i, d := range Curfn.Func.Dcl {
|
|
|
|
|
if d == n {
|
|
|
|
|
Curfn.Func.Dcl[i] = stackcopy
|
|
|
|
|
found = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
// Parameters are before locals, so can stop early.
|
|
|
|
|
// This limits the search even in functions with many local variables.
|
|
|
|
|
if d.Class() == PAUTO {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !found {
|
|
|
|
|
Fatalf("cannot find %v in local variable list", n)
|
|
|
|
|
}
|
|
|
|
|
Curfn.Func.Dcl = append(Curfn.Func.Dcl, n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Modify n in place so that uses of n now mean indirection of the heapaddr.
|
|
|
|
|
n.SetClass(PAUTOHEAP)
|
|
|
|
|
n.Xoffset = 0
|
|
|
|
|
n.Name.Param.Heapaddr = heapaddr
|
|
|
|
|
n.Esc = EscHeap
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m != 0 {
|
2019-09-12 10:18:03 -07:00
|
|
|
Warnl(n.Pos, "moved to heap: %v", n)
|
2017-04-27 06:29:07 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
cmd/compile: recognize Syscall-like functions for liveness analysis
Consider this code:
func f(*int)
func g() {
p := new(int)
f(p)
}
where f is an assembly function.
In general liveness analysis assumes that during the call to f, p is dead
in this frame. If f has retained p, p will be found alive in f's frame and keep
the new(int) from being garbage collected. This is all correct and works.
We use the Go func declaration for f to give the assembly function
liveness information (the arguments are assumed live for the entire call).
Now consider this code:
func h1() {
p := new(int)
syscall.Syscall(1, 2, 3, uintptr(unsafe.Pointer(p)))
}
Here syscall.Syscall is taking the place of f, but because its arguments
are uintptr, the liveness analysis and the garbage collector ignore them.
Since p is no longer live in h once the call starts, if the garbage collector
scans the stack while the system call is blocked, it will find no reference
to the new(int) and reclaim it. If the kernel is going to write to *p once
the call finishes, reclaiming the memory is a mistake.
We can't change the arguments or the liveness information for
syscall.Syscall itself, both for compatibility and because sometimes the
arguments really are integers, and the garbage collector will get quite upset
if it finds an integer where it expects a pointer. The problem is that
these arguments are fundamentally untyped.
The solution we have taken in the syscall package's wrappers in past
releases is to insert a call to a dummy function named "use", to make
it look like the argument is live during the call to syscall.Syscall:
func h2() {
p := new(int)
syscall.Syscall(1, 2, 3, uintptr(unsafe.Pointer(p)))
use(unsafe.Pointer(p))
}
Keeping p alive during the call means that if the garbage collector
scans the stack during the system call now, it will find the reference to p.
Unfortunately, this approach is not available to users outside syscall,
because 'use' is unexported, and people also have to realize they need
to use it and do so. There is much existing code using syscall.Syscall
without a 'use'-like function. That code will fail very occasionally in
mysterious ways (see #13372).
This CL fixes all that existing code by making the compiler do the right
thing automatically, without any code modifications. That is, it takes h1
above, which is incorrect code today, and makes it correct code.
Specifically, if the compiler sees a foreign func definition (one
without a body) that has uintptr arguments, it marks those arguments
as "unsafe uintptrs". If it later sees the function being called
with uintptr(unsafe.Pointer(x)) as an argument, it arranges to mark x
as having escaped, and it makes sure to hold x in a live temporary
variable until the call returns, so that the garbage collector cannot
reclaim whatever heap memory x points to.
For now I am leaving the explicit calls to use in package syscall,
but they can be removed early in a future cycle (likely Go 1.7).
The rule has no effect on escape analysis, only on liveness analysis.
Fixes #13372.
Change-Id: I2addb83f70d08db08c64d394f9d06ff0a063c500
Reviewed-on: https://go-review.googlesource.com/18584
Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-01-13 00:46:28 -05:00
|
|
|
// This special tag is applied to uintptr variables
|
|
|
|
|
// that we believe may hold unsafe.Pointers for
|
|
|
|
|
// calls into assembly functions.
|
2018-03-22 11:18:24 -07:00
|
|
|
const unsafeUintptrTag = "unsafe-uintptr"
|
cmd/compile: recognize Syscall-like functions for liveness analysis
Consider this code:
func f(*int)
func g() {
p := new(int)
f(p)
}
where f is an assembly function.
In general liveness analysis assumes that during the call to f, p is dead
in this frame. If f has retained p, p will be found alive in f's frame and keep
the new(int) from being garbage collected. This is all correct and works.
We use the Go func declaration for f to give the assembly function
liveness information (the arguments are assumed live for the entire call).
Now consider this code:
func h1() {
p := new(int)
syscall.Syscall(1, 2, 3, uintptr(unsafe.Pointer(p)))
}
Here syscall.Syscall is taking the place of f, but because its arguments
are uintptr, the liveness analysis and the garbage collector ignore them.
Since p is no longer live in h once the call starts, if the garbage collector
scans the stack while the system call is blocked, it will find no reference
to the new(int) and reclaim it. If the kernel is going to write to *p once
the call finishes, reclaiming the memory is a mistake.
We can't change the arguments or the liveness information for
syscall.Syscall itself, both for compatibility and because sometimes the
arguments really are integers, and the garbage collector will get quite upset
if it finds an integer where it expects a pointer. The problem is that
these arguments are fundamentally untyped.
The solution we have taken in the syscall package's wrappers in past
releases is to insert a call to a dummy function named "use", to make
it look like the argument is live during the call to syscall.Syscall:
func h2() {
p := new(int)
syscall.Syscall(1, 2, 3, uintptr(unsafe.Pointer(p)))
use(unsafe.Pointer(p))
}
Keeping p alive during the call means that if the garbage collector
scans the stack during the system call now, it will find the reference to p.
Unfortunately, this approach is not available to users outside syscall,
because 'use' is unexported, and people also have to realize they need
to use it and do so. There is much existing code using syscall.Syscall
without a 'use'-like function. That code will fail very occasionally in
mysterious ways (see #13372).
This CL fixes all that existing code by making the compiler do the right
thing automatically, without any code modifications. That is, it takes h1
above, which is incorrect code today, and makes it correct code.
Specifically, if the compiler sees a foreign func definition (one
without a body) that has uintptr arguments, it marks those arguments
as "unsafe uintptrs". If it later sees the function being called
with uintptr(unsafe.Pointer(x)) as an argument, it arranges to mark x
as having escaped, and it makes sure to hold x in a live temporary
variable until the call returns, so that the garbage collector cannot
reclaim whatever heap memory x points to.
For now I am leaving the explicit calls to use in package syscall,
but they can be removed early in a future cycle (likely Go 1.7).
The rule has no effect on escape analysis, only on liveness analysis.
Fixes #13372.
Change-Id: I2addb83f70d08db08c64d394f9d06ff0a063c500
Reviewed-on: https://go-review.googlesource.com/18584
Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-01-13 00:46:28 -05:00
|
|
|
|
2016-06-28 14:19:27 -07:00
|
|
|
// This special tag is applied to uintptr parameters of functions
|
|
|
|
|
// marked go:uintptrescapes.
|
|
|
|
|
const uintptrEscapesTag = "uintptr-escapes"
|
|
|
|
|
|
2019-09-04 15:16:25 -07:00
|
|
|
func (e *Escape) paramTag(fn *Node, narg int, f *types.Field) string {
|
2019-09-04 15:12:08 -07:00
|
|
|
name := func() string {
|
|
|
|
|
if f.Sym != nil {
|
|
|
|
|
return f.Sym.Name
|
2015-02-13 14:40:36 -05:00
|
|
|
}
|
2019-09-04 15:12:08 -07:00
|
|
|
return fmt.Sprintf("arg#%d", narg)
|
|
|
|
|
}
|
2015-02-13 14:40:36 -05:00
|
|
|
|
2019-09-04 15:12:08 -07:00
|
|
|
if fn.Nbody.Len() == 0 {
|
cmd/compile: recognize Syscall-like functions for liveness analysis
Consider this code:
func f(*int)
func g() {
p := new(int)
f(p)
}
where f is an assembly function.
In general liveness analysis assumes that during the call to f, p is dead
in this frame. If f has retained p, p will be found alive in f's frame and keep
the new(int) from being garbage collected. This is all correct and works.
We use the Go func declaration for f to give the assembly function
liveness information (the arguments are assumed live for the entire call).
Now consider this code:
func h1() {
p := new(int)
syscall.Syscall(1, 2, 3, uintptr(unsafe.Pointer(p)))
}
Here syscall.Syscall is taking the place of f, but because its arguments
are uintptr, the liveness analysis and the garbage collector ignore them.
Since p is no longer live in h once the call starts, if the garbage collector
scans the stack while the system call is blocked, it will find no reference
to the new(int) and reclaim it. If the kernel is going to write to *p once
the call finishes, reclaiming the memory is a mistake.
We can't change the arguments or the liveness information for
syscall.Syscall itself, both for compatibility and because sometimes the
arguments really are integers, and the garbage collector will get quite upset
if it finds an integer where it expects a pointer. The problem is that
these arguments are fundamentally untyped.
The solution we have taken in the syscall package's wrappers in past
releases is to insert a call to a dummy function named "use", to make
it look like the argument is live during the call to syscall.Syscall:
func h2() {
p := new(int)
syscall.Syscall(1, 2, 3, uintptr(unsafe.Pointer(p)))
use(unsafe.Pointer(p))
}
Keeping p alive during the call means that if the garbage collector
scans the stack during the system call now, it will find the reference to p.
Unfortunately, this approach is not available to users outside syscall,
because 'use' is unexported, and people also have to realize they need
to use it and do so. There is much existing code using syscall.Syscall
without a 'use'-like function. That code will fail very occasionally in
mysterious ways (see #13372).
This CL fixes all that existing code by making the compiler do the right
thing automatically, without any code modifications. That is, it takes h1
above, which is incorrect code today, and makes it correct code.
Specifically, if the compiler sees a foreign func definition (one
without a body) that has uintptr arguments, it marks those arguments
as "unsafe uintptrs". If it later sees the function being called
with uintptr(unsafe.Pointer(x)) as an argument, it arranges to mark x
as having escaped, and it makes sure to hold x in a live temporary
variable until the call returns, so that the garbage collector cannot
reclaim whatever heap memory x points to.
For now I am leaving the explicit calls to use in package syscall,
but they can be removed early in a future cycle (likely Go 1.7).
The rule has no effect on escape analysis, only on liveness analysis.
Fixes #13372.
Change-Id: I2addb83f70d08db08c64d394f9d06ff0a063c500
Reviewed-on: https://go-review.googlesource.com/18584
Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-01-13 00:46:28 -05:00
|
|
|
// Assume that uintptr arguments must be held live across the call.
|
|
|
|
|
// This is most important for syscall.Syscall.
|
|
|
|
|
// See golang.org/issue/13372.
|
|
|
|
|
// This really doesn't have much to do with escape analysis per se,
|
|
|
|
|
// but we are reusing the ability to annotate an individual function
|
|
|
|
|
// argument and pass those annotations along to importing code.
|
2020-09-09 12:09:26 +07:00
|
|
|
if f.Type.IsUintptr() {
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m != 0 {
|
2019-09-12 10:18:03 -07:00
|
|
|
Warnl(f.Pos, "assuming %v is unsafe uintptr", name())
|
cmd/compile: recognize Syscall-like functions for liveness analysis
Consider this code:
func f(*int)
func g() {
p := new(int)
f(p)
}
where f is an assembly function.
In general liveness analysis assumes that during the call to f, p is dead
in this frame. If f has retained p, p will be found alive in f's frame and keep
the new(int) from being garbage collected. This is all correct and works.
We use the Go func declaration for f to give the assembly function
liveness information (the arguments are assumed live for the entire call).
Now consider this code:
func h1() {
p := new(int)
syscall.Syscall(1, 2, 3, uintptr(unsafe.Pointer(p)))
}
Here syscall.Syscall is taking the place of f, but because its arguments
are uintptr, the liveness analysis and the garbage collector ignore them.
Since p is no longer live in h once the call starts, if the garbage collector
scans the stack while the system call is blocked, it will find no reference
to the new(int) and reclaim it. If the kernel is going to write to *p once
the call finishes, reclaiming the memory is a mistake.
We can't change the arguments or the liveness information for
syscall.Syscall itself, both for compatibility and because sometimes the
arguments really are integers, and the garbage collector will get quite upset
if it finds an integer where it expects a pointer. The problem is that
these arguments are fundamentally untyped.
The solution we have taken in the syscall package's wrappers in past
releases is to insert a call to a dummy function named "use", to make
it look like the argument is live during the call to syscall.Syscall:
func h2() {
p := new(int)
syscall.Syscall(1, 2, 3, uintptr(unsafe.Pointer(p)))
use(unsafe.Pointer(p))
}
Keeping p alive during the call means that if the garbage collector
scans the stack during the system call now, it will find the reference to p.
Unfortunately, this approach is not available to users outside syscall,
because 'use' is unexported, and people also have to realize they need
to use it and do so. There is much existing code using syscall.Syscall
without a 'use'-like function. That code will fail very occasionally in
mysterious ways (see #13372).
This CL fixes all that existing code by making the compiler do the right
thing automatically, without any code modifications. That is, it takes h1
above, which is incorrect code today, and makes it correct code.
Specifically, if the compiler sees a foreign func definition (one
without a body) that has uintptr arguments, it marks those arguments
as "unsafe uintptrs". If it later sees the function being called
with uintptr(unsafe.Pointer(x)) as an argument, it arranges to mark x
as having escaped, and it makes sure to hold x in a live temporary
variable until the call returns, so that the garbage collector cannot
reclaim whatever heap memory x points to.
For now I am leaving the explicit calls to use in package syscall,
but they can be removed early in a future cycle (likely Go 1.7).
The rule has no effect on escape analysis, only on liveness analysis.
Fixes #13372.
Change-Id: I2addb83f70d08db08c64d394f9d06ff0a063c500
Reviewed-on: https://go-review.googlesource.com/18584
Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-01-13 00:46:28 -05:00
|
|
|
}
|
2019-09-04 15:12:08 -07:00
|
|
|
return unsafeUintptrTag
|
cmd/compile: recognize Syscall-like functions for liveness analysis
Consider this code:
func f(*int)
func g() {
p := new(int)
f(p)
}
where f is an assembly function.
In general liveness analysis assumes that during the call to f, p is dead
in this frame. If f has retained p, p will be found alive in f's frame and keep
the new(int) from being garbage collected. This is all correct and works.
We use the Go func declaration for f to give the assembly function
liveness information (the arguments are assumed live for the entire call).
Now consider this code:
func h1() {
p := new(int)
syscall.Syscall(1, 2, 3, uintptr(unsafe.Pointer(p)))
}
Here syscall.Syscall is taking the place of f, but because its arguments
are uintptr, the liveness analysis and the garbage collector ignore them.
Since p is no longer live in h once the call starts, if the garbage collector
scans the stack while the system call is blocked, it will find no reference
to the new(int) and reclaim it. If the kernel is going to write to *p once
the call finishes, reclaiming the memory is a mistake.
We can't change the arguments or the liveness information for
syscall.Syscall itself, both for compatibility and because sometimes the
arguments really are integers, and the garbage collector will get quite upset
if it finds an integer where it expects a pointer. The problem is that
these arguments are fundamentally untyped.
The solution we have taken in the syscall package's wrappers in past
releases is to insert a call to a dummy function named "use", to make
it look like the argument is live during the call to syscall.Syscall:
func h2() {
p := new(int)
syscall.Syscall(1, 2, 3, uintptr(unsafe.Pointer(p)))
use(unsafe.Pointer(p))
}
Keeping p alive during the call means that if the garbage collector
scans the stack during the system call now, it will find the reference to p.
Unfortunately, this approach is not available to users outside syscall,
because 'use' is unexported, and people also have to realize they need
to use it and do so. There is much existing code using syscall.Syscall
without a 'use'-like function. That code will fail very occasionally in
mysterious ways (see #13372).
This CL fixes all that existing code by making the compiler do the right
thing automatically, without any code modifications. That is, it takes h1
above, which is incorrect code today, and makes it correct code.
Specifically, if the compiler sees a foreign func definition (one
without a body) that has uintptr arguments, it marks those arguments
as "unsafe uintptrs". If it later sees the function being called
with uintptr(unsafe.Pointer(x)) as an argument, it arranges to mark x
as having escaped, and it makes sure to hold x in a live temporary
variable until the call returns, so that the garbage collector cannot
reclaim whatever heap memory x points to.
For now I am leaving the explicit calls to use in package syscall,
but they can be removed early in a future cycle (likely Go 1.7).
The rule has no effect on escape analysis, only on liveness analysis.
Fixes #13372.
Change-Id: I2addb83f70d08db08c64d394f9d06ff0a063c500
Reviewed-on: https://go-review.googlesource.com/18584
Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-01-13 00:46:28 -05:00
|
|
|
}
|
|
|
|
|
|
2020-08-22 14:07:30 -07:00
|
|
|
if !f.Type.HasPointers() { // don't bother tagging for scalars
|
2019-09-04 15:12:08 -07:00
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-26 15:55:58 -07:00
|
|
|
var esc EscLeaks
|
|
|
|
|
|
2019-09-04 15:12:08 -07:00
|
|
|
// External functions are assumed unsafe, unless
|
|
|
|
|
// //go:noescape is given before the declaration.
|
2019-10-08 14:21:17 -07:00
|
|
|
if fn.Func.Pragma&Noescape != 0 {
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m != 0 && f.Sym != nil {
|
2019-09-12 10:18:03 -07:00
|
|
|
Warnl(f.Pos, "%v does not escape", name())
|
2019-09-04 15:16:25 -07:00
|
|
|
}
|
2019-09-26 15:55:58 -07:00
|
|
|
} else {
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m != 0 && f.Sym != nil {
|
2019-09-26 15:55:58 -07:00
|
|
|
Warnl(f.Pos, "leaking param: %v", name())
|
|
|
|
|
}
|
|
|
|
|
esc.AddHeap(0)
|
2019-09-04 15:12:08 -07:00
|
|
|
}
|
2019-09-04 15:16:25 -07:00
|
|
|
|
2019-09-26 15:55:58 -07:00
|
|
|
return esc.Encode()
|
2015-02-13 14:40:36 -05:00
|
|
|
}
|
|
|
|
|
|
2016-10-12 11:34:47 +09:00
|
|
|
if fn.Func.Pragma&UintptrEscapes != 0 {
|
2020-09-09 12:09:26 +07:00
|
|
|
if f.Type.IsUintptr() {
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m != 0 {
|
2019-09-12 10:18:03 -07:00
|
|
|
Warnl(f.Pos, "marking %v as escaping uintptr", name())
|
2016-06-28 14:19:27 -07:00
|
|
|
}
|
2019-09-04 15:12:08 -07:00
|
|
|
return uintptrEscapesTag
|
|
|
|
|
}
|
2020-09-09 12:09:26 +07:00
|
|
|
if f.IsDDD() && f.Type.Elem().IsUintptr() {
|
2019-09-04 15:12:08 -07:00
|
|
|
// final argument is ...uintptr.
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m != 0 {
|
2019-09-12 10:18:03 -07:00
|
|
|
Warnl(f.Pos, "marking %v as escaping ...uintptr", name())
|
2016-06-28 14:19:27 -07:00
|
|
|
}
|
2019-09-04 15:12:08 -07:00
|
|
|
return uintptrEscapesTag
|
2016-06-28 14:19:27 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 14:07:30 -07:00
|
|
|
if !f.Type.HasPointers() { // don't bother tagging for scalars
|
2019-09-04 15:12:08 -07:00
|
|
|
return ""
|
|
|
|
|
}
|
2018-03-07 15:11:54 -08:00
|
|
|
|
2019-09-04 15:12:08 -07:00
|
|
|
// Unnamed parameters are unused and therefore do not escape.
|
|
|
|
|
if f.Sym == nil || f.Sym.IsBlank() {
|
2019-09-26 15:55:58 -07:00
|
|
|
var esc EscLeaks
|
|
|
|
|
return esc.Encode()
|
2017-03-24 09:00:17 -07:00
|
|
|
}
|
2019-09-04 15:12:08 -07:00
|
|
|
|
|
|
|
|
n := asNode(f.Nname)
|
2019-09-04 15:16:25 -07:00
|
|
|
loc := e.oldLoc(n)
|
2019-09-26 15:55:58 -07:00
|
|
|
esc := loc.paramEsc
|
|
|
|
|
esc.Optimize()
|
2019-09-04 15:16:25 -07:00
|
|
|
|
2020-10-19 11:31:10 +02:00
|
|
|
if Debug.m != 0 && !loc.escapes {
|
2019-09-26 17:21:50 -07:00
|
|
|
if esc.Empty() {
|
|
|
|
|
Warnl(f.Pos, "%v does not escape", name())
|
|
|
|
|
}
|
2019-09-26 15:55:58 -07:00
|
|
|
if x := esc.Heap(); x >= 0 {
|
|
|
|
|
if x == 0 {
|
|
|
|
|
Warnl(f.Pos, "leaking param: %v", name())
|
|
|
|
|
} else {
|
|
|
|
|
// TODO(mdempsky): Mention level=x like below?
|
2019-09-12 10:18:03 -07:00
|
|
|
Warnl(f.Pos, "leaking param content: %v", name())
|
2019-09-04 15:16:25 -07:00
|
|
|
}
|
2019-09-26 15:55:58 -07:00
|
|
|
}
|
|
|
|
|
for i := 0; i < numEscResults; i++ {
|
|
|
|
|
if x := esc.Result(i); x >= 0 {
|
|
|
|
|
res := fn.Type.Results().Field(i).Sym
|
|
|
|
|
Warnl(f.Pos, "leaking param: %v to result %v level=%d", name(), res, x)
|
2019-09-04 15:16:25 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-26 15:55:58 -07:00
|
|
|
return esc.Encode()
|
2015-02-13 14:40:36 -05:00
|
|
|
}
|