Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/compile: missed escape analysis opportunity #5919

Open
edsrzf opened this issue Jul 19, 2013 · 16 comments
Open

cmd/compile: missed escape analysis opportunity #5919

edsrzf opened this issue Jul 19, 2013 · 16 comments
Milestone

Comments

@edsrzf
Copy link

edsrzf commented Jul 19, 2013

Escape analysis says that s escapes in this function even though it doesn't:

func f(s *string) {
    *s = *s
}

If f is called as f(&s), the compiler will (with inlining disabled) allocate s on
the heap even though it could be allocated on the stack. For types that don't contain
pointers, this doesn't happen. For example, if I change string to int, the parameter
isn't considered to escape.

A real-world example of when this might happen is when a function takes a pointer to a
string or slice and then re-slices it:

*s = (*s)[n:]
@remyoudompheng
Copy link
Contributor

Comment 1:

It's probably an oversight, if it works for *int it should also work with *string for
consistency.

@remyoudompheng
Copy link
Contributor

Comment 2:

Well, it's not that simple. The escape analysis has to keep track of when pointers at
flowing from a location to another. But it should notice that the string itself does not
escape, only the data pointed to by the string.
A special case could be added for strings, but it would not solve the general issue,
which that the level of indirection of escaping must be remembered. It's weird because I
thought rsc said it was working properly when inside a single package.

@edsrzf
Copy link
Author

edsrzf commented Jul 28, 2013

Comment 3:

Ah, I wondered if it was something like that.
A general solution would, of course, be best, but I think special cases for strings and
slices would go a long way (if they're significantly easier to add).

@remyoudompheng
Copy link
Contributor

Comment 4:

strings are special because their contents are always allocated on heap (currently) so
we don't care about what the analysis results are. But you can't special-case slices
here, they are just like ordinary pointers, the code be changed to handle them
appropriately.

@rsc
Copy link
Contributor

rsc commented Jul 30, 2013

Comment 5:

It may be okay to special case assignment of something to itself, to handle reslicing.
The change would not be safe in general:
    func f(s, t *string) {
        *t = *s
    }
In that function, s (well its internal pointer) really does escape (into t).

Labels changed: added priority-later, go1.2maybe, removed priority-triage.

Status changed to Accepted.

@rsc
Copy link
Contributor

rsc commented Jul 30, 2013

Comment 6:

Labels changed: added feature.

@robpike
Copy link
Contributor

robpike commented Aug 29, 2013

Comment 7:

Not for 1.2.

@robpike
Copy link
Contributor

robpike commented Aug 29, 2013

Comment 8:

Labels changed: removed go1.2maybe.

@rsc
Copy link
Contributor

rsc commented Nov 27, 2013

Comment 9:

Labels changed: added go1.3maybe.

@rsc
Copy link
Contributor

rsc commented Nov 27, 2013

Comment 10:

Labels changed: removed feature.

@rsc
Copy link
Contributor

rsc commented Dec 4, 2013

Comment 11:

Labels changed: added release-none, removed go1.3maybe.

@rsc
Copy link
Contributor

rsc commented Dec 4, 2013

Comment 12:

Labels changed: added repo-main.

@rsc rsc added this to the Unplanned milestone Apr 10, 2015
@rsc rsc changed the title cmd/gc: missed escape analysis opportunity cmd/compile: missed escape analysis opportunity Jun 8, 2015
@odeke-em
Copy link
Member

/cc @rsc @dr2chase @randall77, currently running https://play.golang.org/p/JU4Y50K7ne on tip Go1.8 ac05542 says otherwise, PTAL and help correct me if am mistaken.

$ cat main.go && go tool compile -l -m main.go 
package main

func main() {
	var name = "gopher"
	f(&name)

	var nameCopy string
	g(&name, &nameCopy)

	var a, b int
	a = 10
	h(&a, &b)
	i(&b)
}

func i(a *int) {
	*a = *a
}

func h(i, j *int) {
	*j = *i
}

func g(s, t *string) {
	*t = *s
}

func f(s *string) {
	*s = *s
}

main.go:28: leaking param content: s
main.go:24: leaking param content: s
main.go:24: g t does not escape
main.go:20: h i does not escape
main.go:20: h j does not escape
main.go:16: i a does not escape
main.go:5: main &name does not escape
main.go:8: main &name does not escape
main.go:8: main &nameCopy does not escape
main.go:12: main &a does not escape
main.go:12: main &b does not escape
main.go:13: main &b does not escape

$ go version
go version devel +ac05542 Wed Jan 11 01:39:54 2017 +0000 darwin/amd64

@cespare
Copy link
Contributor

cespare commented Apr 5, 2017

@odeke-em If I'm not mistaken, what's going on there is that the pointers don't escape, and strings are always heap-allocated (so nothing's printed out about them escaping).

But it seems what the original report was really concerned about was a reslicing function, and I believe that doesn't escape as of Go 1.8.

  1 package main                                                                    
  2                                                                                 
  3 //go:noinline                                                                   
  4 func f(sp *string) {                                                            
  5         *sp = (*sp)[1:]                                                         
  6 }                                                                               
  7                                                                                 
  8 //go:noinline                                                                   
  9 func g(p *[]byte) {                                                             
 10         *p = (*p)[1:]                                                           
 11 }                                                                               
 12                                                                                 
 13 //go:noinline                                                                   
 14 func h(p0, p1 *[]byte) {                                                        
 15         *p1 = *p0                                                               
 16 }                                                                               
 17                                                                                 
 18 func main() {                                                                   
 19         s := "hi"                                                               
 20         f(&s)                                                                   
 21                                                                                 
 22         b := []byte("s")                                                        
 23         g(&b)                                                                   
 24                                                                                 
 25         b0 := []byte("s0")                                                      
 26         var b1 []byte                                                           
 27         h(&b0, &b1)                                                             
 28 } 
./test.go:5: f ignoring self-assignment to *sp
./test.go:4: f sp does not escape
./test.go:10: g ignoring self-assignment to *p
./test.go:9: g p does not escape                      <--------
./test.go:14: leaking param content: p0
./test.go:14: h p1 does not escape
./test.go:25: ([]byte)("s0") escapes to heap
./test.go:20: main &s does not escape
./test.go:22: main ([]byte)("s") does not escape      <--------
./test.go:23: main &b does not escape
./test.go:27: main &b0 does not escape
./test.go:27: main &b1 does not escape

The two indicated lines are what I believe show that the reslicing function doesn't cause its underlying slice to escape.

I'm not confident enough in my reading of the -m tea leaves to close this issue, though.

@randall77
Copy link
Contributor

I think the important part of that output are these missing lines:

./test.go:5: leaking param content: sp
./test.go:9: leaking param content: p

Those are the root of the inference chain. The latter is used to derive the :22 message about the initializer of b not escaping.

I think we should still leave this bug open though. For this function:

func j(p *string) {
	*p = *p
}

we do get a leaking param content. Arguably this case is not very useful. But I would think it is easy to fix.

@gopherbot
Copy link

Change https://golang.org/cl/136496 mentions this issue: cmd/compile/internal/gc: generalize self-assignment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants