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

Comments

Projects
None yet
9 participants
@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

This comment has been minimized.

Copy link
Contributor

remyoudompheng commented Jul 20, 2013

Comment 1:

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

This comment has been minimized.

Copy link
Contributor

remyoudompheng commented Jul 28, 2013

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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Contributor

remyoudompheng commented Jul 28, 2013

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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Contributor

rsc commented Jul 30, 2013

Comment 6:

Labels changed: added feature.

@robpike

This comment has been minimized.

Copy link
Contributor

robpike commented Aug 29, 2013

Comment 7:

Not for 1.2.
@robpike

This comment has been minimized.

Copy link
Contributor

robpike commented Aug 29, 2013

Comment 8:

Labels changed: removed go1.2maybe.

@rsc

This comment has been minimized.

Copy link
Contributor

rsc commented Nov 27, 2013

Comment 9:

Labels changed: added go1.3maybe.

@rsc

This comment has been minimized.

Copy link
Contributor

rsc commented Nov 27, 2013

Comment 10:

Labels changed: removed feature.

@rsc

This comment has been minimized.

Copy link
Contributor

rsc commented Dec 4, 2013

Comment 11:

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

@rsc

This comment has been minimized.

Copy link
Contributor

rsc commented Dec 4, 2013

Comment 12:

Labels changed: added repo-main.

@edsrzf edsrzf added accepted labels Dec 4, 2013

@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

This comment has been minimized.

Copy link
Member

odeke-em commented Jan 14, 2017

/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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Contributor

randall77 commented Apr 18, 2017

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

This comment has been minimized.

Copy link

gopherbot commented Sep 20, 2018

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
You can’t perform that action at this time.