What is the meaning of 'leak/leaking param' in Golang Escape Analysis

3.5k views Asked by At
func main() {
        i1 := 1
    A1(&i1)
}

func A1(i1 *int) *int {
    return i1
}

And the result of escape analysis is

./main.go:18:9: parameter i1 leaks to \~r1 with derefs=0:
./main.go:18:9:   flow: \~r1 = i1:
./main.go:18:9:     from return i1 (return) at ./main.go:19:2
./main.go:18:9: leaking param: i1 to result \~r1 level=0

Whats the meaning of parameter i1 leaks to \~r1 with derefs=0 and leaking param: i1 to result \~r1 level=0


First I try to Google golang escape leaking, the most relevant result is in the comment of escape-analysis-shows-channel-as-leaking-param

"Why would you think that?" It's reasonable to assume that leaking is bad and related to its stem leak. I am struggling to think of an example context where leaking is a good thing, e.g leaking bucket, leaking gas tank, taking a leak, leaking capacitor, leaky boat, leaky abstraction. It may be obvious to high performance go experts, but for the rest of us it would be helpful to link to docs and provide brief clarification of what leaking param refers to

It is the same question that i want to ask, but no more replies after this.


Then I try to read the source code where print these result.

In compile/internal/escape/leaks.go, i found comment

// An leaks represents a set of assignment flows from a parameter

// to the heap or to any of its function's (first numEscResults)

// result parameters.

But i can't understand this, is there any official document to represent it.


Besides, in source code I find one more question. If result parameters after numEscResults(7) will escape to heap in runtime?

func main() {
    i1, i2, i3, i4, i5, i6, i7, i8, i9 := 1, 1, 1, 1, 1, 1, 1, 1, 1
    A1(&i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8, &i9)
    return
}

func A1(i1, i2, i3, i4, i5, i6, i7, i8, i9 *int) (*int, *int, *int, *int, *int, *int, *int, *int, *int) {
    return i1, i2, i3, i4, i5, i6, i7, i8, i9
}
...some duplicate output
./main.go:16:13: leaking param: i2 to result ~r10 level=0
./main.go:16:17: leaking param: i3 to result ~r11 level=0
./main.go:16:21: leaking param: i4 to result ~r12 level=0
./main.go:16:25: leaking param: i5 to result ~r13 level=0
./main.go:16:29: leaking param: i6 to result ~r14 level=0
./main.go:16:33: leaking param: i7 to result ~r15 level=0
./main.go:16:37: leaking param: i8
./main.go:16:41: leaking param: i9
./main.go:8:30: i8 escapes to heap:
./main.go:8:30:   flow: {heap} = &i8:
./main.go:8:30:     from &i8 (address-of) at ./main.go:9:40
./main.go:8:30:     from A1(&i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8, &i9) (call parameter) at ./main.go:9:4
./main.go:8:34: i9 escapes to heap:
./main.go:8:34:   flow: {heap} = &i9:
./main.go:8:34:     from &i9 (address-of) at ./main.go:9:45
./main.go:8:34:     from A1(&i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8, &i9) (call parameter) at ./main.go:9:4
./main.go:8:30: moved to heap: i8
./main.go:8:34: moved to heap: i9
1

There are 1 answers

0
Wagner Riffel On BEST ANSWER

Whats the meaning of parameter i1 leaks to ~r1 with derefs=0 and leaking param: i1 to result ~r1 level=0

TLDL: If you're hunting down allocations, ignore leaking param and look for "moved to heap" pieces.

A "leaking param" means that this function somehow keeps its parameter alive after it returns, this doesn't mean it's being moved to heap, in fact most of "leaking params" are allocated on the stack.

"r1" refers to function's return value, it starts at 0, so "r1" refer to the second return value. (which doesn't match with OP provided sample code, should be r0), in case of the first snippet it's leaking "i1" because "r0 = i1", so the 0'th function return value is "i1" thus "i1" must be kept alive after return, "leaking" to the caller

The piece that comes before the "leaking param" in compiler output is because OP is using '-m -m', which prints the data flow graph.

For deref, from the comment in cmd/compile/internal/escape/escape.go:

[...] The number of dereference operations minus the number of addressing operations is recorded as the edge's weight (termed "derefs").

"level" is not described in current comments and it's been a while since I was familiar with gc source code, as far as I can tell it's the level of memory indirections, an indirect (*) operation increments, address-of (&) decrements, thus this function

func A1(a **int) *int {
    p := &a
    return **p
}

should give a leaking param a with level=1.

Besides, in source code I find one more question. If result parameters after numEscResults(7) will escape to heap in runtime?

Yes, all results (read, returns) after the 7'th that depends on leaking params will be moved to heap, I don't know exactly the reason for 7, but I can guess from experience with gc source code it's a value that doesn't slow down compilation too much, yet preserve the optimization for most functions