golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.55k stars 17.6k forks source link

cmd/compile: Increasing complexity when inlining #69925

Open DragonDev1906 opened 2 hours ago

DragonDev1906 commented 2 hours ago

Go version

go version go1.23.1 linux/amd64

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/xxx/.cache/go-build'
GOENV='/home/xxx/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/xxx/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/xxx/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/lib/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/lib/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.23.1'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/xxx/.config/go/telemetry'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/xxx/go.mod'
GOWORK='/xxx/go.work'
CGO_CFLAGS='-I/snap/ego-dev/current/opt/ego/include'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-L/snap/ego-dev/current/opt/ego/lib -L/usr/lib/openssl-1.1'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2403704166=/tmp/go-build -gno-record-gcc-switches'

What did you do?

With the code below the following inlining decisions are made/shown:

[godbolt](https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:go,selection:(endColumn:14,endLineNumber:19,positionColumn:14,positionLineNumber:19,selectionStartColumn:14,selectionStartLineNumber:19,startColumn:14,startLineNumber:19),source:'package+main%0A%0Atype+BasicWallet+struct+%7B%0A%09Addr+%5B20%5Dbyte%0A%7D%0A%0Afunc+(w+*BasicWallet)+Address()+%5B20%5Dbyte+%7B%0A%09return+w.Addr%0A%7D%0Afunc+(w+*BasicWallet)+Address2()+%5B20%5Dbyte+%7B%0A%09return+w.Address()%0A%7D%0Afunc+(w+*BasicWallet)+Address3()+%5B20%5Dbyte+%7B%0A%09return+w.Address2()%0A%7D%0A%0Afunc+main()+%7B%0A%09x+:%3D+BasicWallet%7B%7D%0A%09x.Address3()%0A%7D%0A'),l:'5',n:'1',o:'Go+source+%231',t:'0')),k:50,l:'4',m:100,n:'0',o:'',s:0,t:'0'),(g:!((g:!((h:compiler,i:(compiler:gl1221,filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'0',intel:'0',libraryCode:'0',trim:'1',verboseDemangling:'0'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:3,lang:go,libs:!(),options:'-m%3D2',overrides:!((name:edition,value:'2021')),selection:(endColumn:41,endLineNumber:37,positionColumn:41,positionLineNumber:37,selectionStartColumn:41,selectionStartLineNumber:37,startColumn:41,startLineNumber:37),source:1),l:'5',n:'0',o:'+x86-64+gc+1.22.1+(Editor+%231)',t:'0')),k:50,l:'4',m:67.54807692307693,n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compilerName:'rustc+1.78.0',editorid:1,fontScale:14,fontUsePx:'0',j:3,wrap:'1'),l:'5',n:'0',o:'Output+of+x86-64+gc+1.22.1+(Compiler+%233)',t:'0')),header:(),l:'4',m:32.45192307692308,n:'0',o:'',s:0,t:'0')),k:50,l:'3',n:'0',o:'',t:'0')),l:'2',m:100,n:'0',o:'',t:'0')),version:4)

can inline (*BasicWallet).Address with cost 3 as: method(*BasicWallet) func() common.Address { return w.Addr }
can inline (*BasicWallet).Address2 with cost 6 as: method(*BasicWallet) func() common.Address { return (*BasicWallet).Address(w) }
can inline (*BasicWallet).Address3 with cost 9 as: method(*BasicWallet) func() common.Address { return (*BasicWallet).Address2(w) }

Although this works, it reduces the chance of something getting inlined, as it increases the complexity of the function something is inlined into, even though the resulting bytecode will/should be identical for all 3 methods (otherwise there would be no inlining).

What did you see happen?

(see above)

What did you expect to see?

Correct me if I'm wrong, but shouldn't these be something like the following, as that's actually what inlining does:

can inline (*BasicWallet).Address with cost 3 as: method(*BasicWallet) func() common.Address { return w.Addr }
can inline (*BasicWallet).Address2 with cost 3 as: method(*BasicWallet) func() common.Address { return w.Addr }
can inline (*BasicWallet).Address3 with cost 3 as: method(*BasicWallet) func() common.Address { return w.Addr }

Since inlining effectively reduces the effort/size of a function, these should probably not increase in complexity.

gabyhelp commented 2 hours ago

Related Issues and Documentation

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

mateusz834 commented 1 hour ago

Also same thing happens with field embeding, i once noticed that binary.NativeEndian has bigger inlining cost than binary.LittleEndian/binary.BigEndian, even though it looks like this:

https://github.com/golang/go/blob/ef3e1dae2f151ddca4ba50ed8b9a98381d7e9158/src/encoding/binary/native_endian_little.go#L9-L14