[golang] スコープ外で参照される可能性のある変数はヒープにエスケープされる

# 目的
スコープ外で参照される可能性のある変数はヒープにエスケープされ、ローカル変数として完結する変数はヒープにエスケープされないことを理解する。

# 注意
- 今回検証する値は、サイズを小さくしています。サイズが大きい場合、どんな状況でもヒープに配置されるので、今回の検証とは異なる結果になります。
- 今回はintスライスを返り値として扱っていますが、int では再現できません。
    - intスライスが返り値の場合、intスライス構造体のコピーが返り、intスライス要素はヒープにエスケープされます。int スライス(構造体)は、内部にintスライス要素へのポインタを持っています。
    - intが返り値の場合は、完全なコピーが返るので、ヒープにエスケープされる可能性はないです。詳しくは、下記の(4)の例を見てください。
    - 返り値として、intではなくて`&int`（intのアドレス）を使った場合、intそのものがヒープにエスケープされる必要性があるので、intスライスと同じような検証結果になるでしょう。


# 環境
```
$ go version
go version go1.23.2 linux/amd64
```

# 検証
## (1)
まず大前提として、`go build -gcflags "-m=2" .`の出力結果は、その関数のみに焦点を当てたものであり、呼び出し元でインライン展開されることは全く考慮していない。
`./main.go:6:10: make([]int, 1) does not escape`を見ると、`f1()`で作成されたintスライスはヒープではなくてスタックに配置されることがわかる。
`./main.go:10:13: make([]int, 1) escapes to heap:`を見ると、`f2()`で作成されたintスライスはヒープに配置されることがわかる。このintスライスが、このスタックフレーム以外でも使用される可能性があるからである。ただし、先ほども言ったように、呼び出し元でインライン展開されない前提である。
``` main.go
package main

func main() {}

func f1() {
	_ = make([]int, 1)
}

func f2() []int {
	res := make([]int, 1)
	return res
}

```
```
$ go build -gcflags "-m=2" .
# gopractice
./main.go:3:6: can inline main with cost 0 as: func() {  }
./main.go:5:6: can inline f1 with cost 4 as: func() { _ = make([]int, 1) }
./main.go:9:6: can inline f2 with cost 8 as: func() []int { res := make([]int, 1); return res }
./main.go:6:10: make([]int, 1) does not escape
./main.go:10:13: make([]int, 1) escapes to heap:
./main.go:10:13:   flow: res = &{storage for make([]int, 1)}:
./main.go:10:13:     from make([]int, 1) (spill) at ./main.go:10:13
./main.go:10:13:     from res := make([]int, 1) (assign) at ./main.go:10:6
./main.go:10:13:   flow: ~r0 = res:
./main.go:10:13:     from return res (return) at ./main.go:11:2
./main.go:10:13: make([]int, 1) escapes to heap
```
```
$ go test -bench=. -benchmem
今回はやらない
```
```
$ go build -o main_binary main.go
$ objdump -d main_binary > objdump.txt
0000000000466be0 <main.main>:
  466be0:	c3                   	ret
  466be1:	cc                   	int3
  466be2:	cc                   	int3
  466be3:	cc                   	int3
  466be4:	cc                   	int3
  466be5:	cc                   	int3
  466be6:	cc                   	int3
  466be7:	cc                   	int3
  466be8:	cc                   	int3
  466be9:	cc                   	int3
  466bea:	cc                   	int3
  466beb:	cc                   	int3
  466bec:	cc                   	int3
  466bed:	cc                   	int3
  466bee:	cc                   	int3
  466bef:	cc                   	int3
  466bf0:	cc                   	int3
  466bf1:	cc                   	int3
  466bf2:	cc                   	int3
  466bf3:	cc                   	int3
  466bf4:	cc                   	int3
  466bf5:	cc                   	int3
  466bf6:	cc                   	int3
  466bf7:	cc                   	int3
  466bf8:	cc                   	int3
  466bf9:	cc                   	int3
  466bfa:	cc                   	int3
  466bfb:	cc                   	int3
  466bfc:	cc                   	int3
  466bfd:	cc                   	int3
  466bfe:	cc                   	int3
  466bff:	cc                   	int3
```

## (2)
前の(1)の例と違って、今回は、`main()`が呼び出し元である。
先ほども言ったように、`go build -gcflags "-m=2" .`は、その関数しか見ておらず、インライン展開を考慮していないため、`f2()`ではヒープにエスケープするという結果になっている。
しかし、実際は、呼び出し元でインライン展開されているので、`f2()`のintスライスはヒープにエスケープしない。その証拠に、ベンチマークテストで`0 allocs/op`であり、さらには、`objdump -d main_binary`の出力結果に`call   45e560 <runtime.makeslice>`のような出力がない。また、`./main.go:5:4: make([]int, 1) does not escape`は、`f2()`がインライン展開された後の挙動を示しており、intスライスがエスケープしないことがわかる。
``` main.go
package main

func main() {
	f1()
	f2()
}

func f1() {
	_ = make([]int, 1)
}

func f2() []int {
	res := make([]int, 1)
	return res
}
```
``` main_test.go
package main

import "testing"

func Benchmark(b *testing.B) {
	for i := 0; i < b.N; i++ {
		main()
	}
}
```
```
$ go build -gcflags "-m=2" .
# gopractice
./main.go:8:6: can inline f1 with cost 4 as: func() { _ = make([]int, 1) }
./main.go:12:6: can inline f2 with cost 8 as: func() []int { res := make([]int, 1); return res }
./main.go:3:6: can inline main with cost 16 as: func() { f1(); f2() }
./main.go:4:4: inlining call to f1
./main.go:5:4: inlining call to f2
./main.go:4:4: make([]int, 1) does not escape
./main.go:5:4: make([]int, 1) does not escape
./main.go:9:10: make([]int, 1) does not escape
./main.go:13:13: make([]int, 1) escapes to heap:
./main.go:13:13:   flow: res = &{storage for make([]int, 1)}:
./main.go:13:13:     from make([]int, 1) (spill) at ./main.go:13:13
./main.go:13:13:     from res := make([]int, 1) (assign) at ./main.go:13:6
./main.go:13:13:   flow: ~r0 = res:
./main.go:13:13:     from return res (return) at ./main.go:14:2
./main.go:13:13: make([]int, 1) escapes to heap
```
```
$ go test -bench=. -benchmem
goos: linux
goarch: amd64
pkg: gopractice
cpu: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Benchmark-2     1000000000               0.3703 ns/op          0 B/op          0 allocs/op
PASS
ok      gopractice      0.417s
```
```
$ go build -o main_binary main.go
$ objdump -d main_binary > objdump.txt
0000000000466be0 <main.main>:
  466be0:	c3                   	ret
  466be1:	cc                   	int3
  466be2:	cc                   	int3
  466be3:	cc                   	int3
  466be4:	cc                   	int3
  466be5:	cc                   	int3
  466be6:	cc                   	int3
  466be7:	cc                   	int3
  466be8:	cc                   	int3
  466be9:	cc                   	int3
  466bea:	cc                   	int3
  466beb:	cc                   	int3
  466bec:	cc                   	int3
  466bed:	cc                   	int3
  466bee:	cc                   	int3
  466bef:	cc                   	int3
  466bf0:	cc                   	int3
  466bf1:	cc                   	int3
  466bf2:	cc                   	int3
  466bf3:	cc                   	int3
  466bf4:	cc                   	int3
  466bf5:	cc                   	int3
  466bf6:	cc                   	int3
  466bf7:	cc                   	int3
  466bf8:	cc                   	int3
  466bf9:	cc                   	int3
  466bfa:	cc                   	int3
  466bfb:	cc                   	int3
  466bfc:	cc                   	int3
  466bfd:	cc                   	int3
  466bfe:	cc                   	int3
  466bff:	cc                   	int3
```

## (3)
`f2()`を`//go:noinline`にすると、呼び出し元でインライン展開されない。
このとき、`f2()`のintスライスは、ヒープにエスケープされる。その証拠に、ベンチマークテストで`1 allocs/op`となっており、さらに、`call   45e560 <runtime.makeslice>`が出力されている。
``` main.go
package main

func main() {
	f1()
	f2()
}

func f1() {
	_ = make([]int, 1)
}

//go:noinline
func f2() []int {
	res := make([]int, 1)
	return res
}
```
``` main_test.go
package main

import "testing"

func Benchmark(b *testing.B) {
	for i := 0; i < b.N; i++ {
		main()
	}
}
```
```
$ go build -gcflags "-m=2" .
# gopractice
./main.go:8:6: can inline f1 with cost 4 as: func() { _ = make([]int, 1) }
./main.go:13:6: cannot inline f2: marked go:noinline
./main.go:3:6: can inline main with cost 65 as: func() { f1(); f2() }
./main.go:4:4: inlining call to f1
./main.go:14:13: make([]int, 1) escapes to heap:
./main.go:14:13:   flow: res = &{storage for make([]int, 1)}:
./main.go:14:13:     from make([]int, 1) (spill) at ./main.go:14:13
./main.go:14:13:     from res := make([]int, 1) (assign) at ./main.go:14:6
./main.go:14:13:   flow: ~r0 = res:
./main.go:14:13:     from return res (return) at ./main.go:15:2
./main.go:14:13: make([]int, 1) escapes to heap
./main.go:4:4: make([]int, 1) does not escape
./main.go:9:10: make([]int, 1) does not escape
```
```
$ go test -bench=. -benchmem
goos: linux
goarch: amd64
pkg: gopractice
cpu: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Benchmark-2     59310711                17.13 ns/op            8 B/op          1 allocs/op
PASS
ok      gopractice      1.043s
```
```
$ go build -o main_binary main.go
$ objdump -d main_binary > objdump.txt
0000000000466be0 <main.main>:
  466be0:	49 3b 66 10          	cmp    0x10(%r14),%rsp
  466be4:	76 0b                	jbe    466bf1 <main.main+0x11>
  466be6:	55                   	push   %rbp
  466be7:	48 89 e5             	mov    %rsp,%rbp
  466bea:	e8 11 00 00 00       	call   466c00 <main.f2>
  466bef:	5d                   	pop    %rbp
  466bf0:	c3                   	ret
  466bf1:	e8 2a b1 ff ff       	call   461d20 <runtime.morestack_noctxt.abi0>
  466bf6:	eb e8                	jmp    466be0 <main.main>
  466bf8:	cc                   	int3
  466bf9:	cc                   	int3
  466bfa:	cc                   	int3
  466bfb:	cc                   	int3
  466bfc:	cc                   	int3
  466bfd:	cc                   	int3
  466bfe:	cc                   	int3
  466bff:	cc                   	int3

0000000000466c00 <main.f2>:
  466c00:	49 3b 66 10          	cmp    0x10(%r14),%rsp
  466c04:	76 2d                	jbe    466c33 <main.f2+0x33>
  466c06:	55                   	push   %rbp
  466c07:	48 89 e5             	mov    %rsp,%rbp
  466c0a:	48 83 ec 18          	sub    $0x18,%rsp
  466c0e:	48 8d 05 eb 67 00 00 	lea    0x67eb(%rip),%rax        # 46d400 <type:*+0x5400>
  466c15:	bb 01 00 00 00       	mov    $0x1,%ebx
  466c1a:	48 89 d9             	mov    %rbx,%rcx
  466c1d:	0f 1f 00             	nopl   (%rax)
  466c20:	e8 3b 79 ff ff       	call   45e560 <runtime.makeslice>
  466c25:	bb 01 00 00 00       	mov    $0x1,%ebx
  466c2a:	48 89 d9             	mov    %rbx,%rcx
  466c2d:	48 83 c4 18          	add    $0x18,%rsp
  466c31:	5d                   	pop    %rbp
  466c32:	c3                   	ret
  466c33:	e8 e8 b0 ff ff       	call   461d20 <runtime.morestack_noctxt.abi0>
  466c38:	eb c6                	jmp    466c00 <main.f2>
  466c3a:	cc                   	int3
  466c3b:	cc                   	int3
  466c3c:	cc                   	int3
  466c3d:	cc                   	int3
  466c3e:	cc                   	int3
  466c3f:	cc                   	int3
```

## (4)
`res`がスライスでなくてintだった場合は、`return int`によって、完全なコピーが返るので、ヒープにエスケープされることはないです。
``` main.go
package main

func main() {
	f1()
	f2()
}

func f1() {
	_ = make([]int, 1)
}

//go:noinline
func f2() int {
	res := 123456
	return res
}

```
``` main_test.go
package main

import "testing"

func Benchmark(b *testing.B) {
	for i := 0; i < b.N; i++ {
		main()
	}
}
```
```
$ go build -gcflags "-m=2" .
# gopractice
./main.go:8:6: can inline f1 with cost 4 as: func() { _ = make([]int, 1) }
./main.go:13:6: cannot inline f2: marked go:noinline
./main.go:3:6: can inline main with cost 65 as: func() { f1(); f2() }
./main.go:4:4: inlining call to f1
./main.go:4:4: make([]int, 1) does not escape
./main.go:9:10: make([]int, 1) does not escape
```
```
$ go test -bench=. -benchmem
goos: linux
goarch: amd64
pkg: gopractice
cpu: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Benchmark-2     794488742                1.481 ns/op           0 B/op          0 allocs/op
PASS
ok      gopractice      1.336s
```
```
$ go build -o main_binary main.go
$ objdump -d main_binary > objdump.txt
0000000000466be0 <main.main>:
  466be0:	49 3b 66 10          	cmp    0x10(%r14),%rsp
  466be4:	76 0b                	jbe    466bf1 <main.main+0x11>
  466be6:	55                   	push   %rbp
  466be7:	48 89 e5             	mov    %rsp,%rbp
  466bea:	e8 11 00 00 00       	call   466c00 <main.f2>
  466bef:	5d                   	pop    %rbp
  466bf0:	c3                   	ret
  466bf1:	e8 2a b1 ff ff       	call   461d20 <runtime.morestack_noctxt.abi0>
  466bf6:	eb e8                	jmp    466be0 <main.main>
  466bf8:	cc                   	int3
  466bf9:	cc                   	int3
  466bfa:	cc                   	int3
  466bfb:	cc                   	int3
  466bfc:	cc                   	int3
  466bfd:	cc                   	int3
  466bfe:	cc                   	int3
  466bff:	cc                   	int3

0000000000466c00 <main.f2>:
  466c00:	b8 40 e2 01 00       	mov    $0x1e240,%eax
  466c05:	c3                   	ret
  466c06:	cc                   	int3
  466c07:	cc                   	int3
  466c08:	cc                   	int3
  466c09:	cc                   	int3
  466c0a:	cc                   	int3
  466c0b:	cc                   	int3
  466c0c:	cc                   	int3
  466c0d:	cc                   	int3
  466c0e:	cc                   	int3
  466c0f:	cc                   	int3
  466c10:	cc                   	int3
  466c11:	cc                   	int3
  466c12:	cc                   	int3
  466c13:	cc                   	int3
  466c14:	cc                   	int3
  466c15:	cc                   	int3
  466c16:	cc                   	int3
  466c17:	cc                   	int3
  466c18:	cc                   	int3
  466c19:	cc                   	int3
  466c1a:	cc                   	int3
  466c1b:	cc                   	int3
  466c1c:	cc                   	int3
  466c1d:	cc                   	int3
  466c1e:	cc                   	int3
  466c1f:	cc                   	int3
```

# 参考

https://qiita.com/roba4coding/items/9def61ff8657aff4f23d

> sliceは内部にデータへのポインタを持っており、sliceそのものをポインタで渡すのは冗長であると言われる。他人のコードを見てもsliceのポインタを戻り値で返すケースはあまり見かけない。引数でsliceのポインタを受け取ることはあるけれど。

https://www.amazon.co.jp/Go%E8%A8%80%E8%AA%9E-100Tips-%E3%81%82%E3%82%8A%E3%81%8C%E3%81%A1%E3%81%AA%E3%83%9F%E3%82%B9%E3%82%92%E6%8A%8A%E6%8F%A1%E3%81%97%E3%80%81%E5%AE%9F%E8%A3%85%E3%82%92%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%99%E3%82%8B-impress-gear/dp/4295017531

https://please-sleep.cou929.nu/golang-array-and-slice-note.html