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: missing bounds check elimination for complex conditionals #72132

Closed
jub0bs opened this issue Mar 6, 2025 · 11 comments
Closed

cmd/compile: missing bounds check elimination for complex conditionals #72132

jub0bs opened this issue Mar 6, 2025 · 11 comments
Assignees
Labels
BugReport Issues describing a possible bug in the Go implementation. compiler/runtime Issues related to the Go compiler and/or runtime. FixPending Issues that have a fix which has not yet been reviewed or submitted. Performance
Milestone

Comments

@jub0bs
Copy link

jub0bs commented Mar 6, 2025

Go version

go version go1.24.1 darwin/amd64

Output of go env in your module/workspace:

AR='ar'
CC='cc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='c++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN='/Users/jcretel/go/bin'
GOCACHE='/Users/jcretel/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/jcretel/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/0k/mmhg_4vd4rxdzzxp8hr1564r0000gn/T/go-build2912752211=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='amd64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/jcretel/Desktop/oauth2/go.mod'
GOMODCACHE='/Users/jcretel/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/jcretel/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/Cellar/go/1.24.1/libexec'
GOSUMDB='sum.golang.org'
GOTELEMETRY='on'
GOTELEMETRYDIR='/Users/jcretel/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/Cellar/go/1.24.1/libexec/pkg/tool/darwin_amd64'
GOVCS=''
GOVERSION='go1.24.1'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

Run go build -gcflags '-d=ssa/check_bce/debug=1'on the following two programmes:

// int.go
package bce_int

import "cmp"

var _ = insertionSortOrdered[int] // <---------

func insertionSortOrdered[E cmp.Ordered](data []E, a, b int) {
	for i := a + 1; i < b; i++ {
		for j := i; j > a && data[j] < data[j-1]; j-- {
			// two bounds checks here when E is string, none when E is int
			data[j], data[j-1] = data[j-1], data[j]
		}
	}
}
// string.go
package bce_string

import "cmp"

var _ = insertionSortOrdered[string] // <---------

func insertionSortOrdered[E cmp.Ordered](data []E, a, b int) {
	for i := a + 1; i < b; i++ {
		for j := i; j > a && data[j] < data[j-1]; j-- {
			// two bounds checks here when E is string, none when E is int
			data[j], data[j-1] = data[j-1], data[j]
		}
	}
}

What did you see happen?

The first programme has no bounds checks on the swap line:

$ go build -gcflags '-d=ssa/check_bce/debug=1' ./bce_int
# whatever/bce_int
bce_int/int.go:10:28: Found IsInBounds
bce_int/int.go:10:38: Found IsInBounds
bce_int/int.go:8:6: Found IsInBounds

Surprisingly, the second programme has two bounds checks on the swap line:

$ go build -gcflags '-d=ssa/check_bce/debug=1' ./bce_string 
# whatever/bce_string
bce_string/string.go:10:28: Found IsInBounds
bce_string/string.go:10:38: Found IsInBounds
bce_string/string.go:12:29: Found IsInBounds # <----
bce_string/string.go:12:40: Found IsInBounds # <----
bce_string/string.go:8:6: Found IsInBounds

What did you expect to see?

Given the absence of any type switch on insertionSortOrdered's type parameter, I expected the number of bounds checks to be insensitive to the choice of type argument.

@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Mar 6, 2025
@gabyhelp gabyhelp added the BugReport Issues describing a possible bug in the Go implementation. label Mar 6, 2025
@randall77
Copy link
Contributor

I think that's just a line numbering problem. There are bounds checks in all the examples.
(Look for panicIndex calls.)

@jub0bs
Copy link
Author

jub0bs commented Mar 6, 2025

@randall77 There are bounds checks in all examples, but (unless I'm missing something) double the amount in the string version. Search for "panicindex" on godbolt:

  • 4 hits for the int version;
  • 8 hits for the string version.

@randall77
Copy link
Contributor

Ah, ok, I see it now. Somehow the bounds checks in the loop condition are not making the bounds checks in the swap line redundant.

I suspect this has something to do with the cmpstring call that's needed for string <.

Note this has nothing to do with generics, replacing E with string shows the same behavior.

@randall77 randall77 added this to the Unplanned milestone Mar 6, 2025
@randall77
Copy link
Contributor

Here's a simpler reproducer:

package p

func f(x []string, i int, b bool) {
	if b && x[i] < "foo" {
		x[i] = "bar"
	}
}

This has 2 bounds checks. Remove b && and it has only one.

@jub0bs
Copy link
Author

jub0bs commented Mar 6, 2025

@randall77 Thanks! Glad I'm not hallucinating. 😌

@JunyangShao
Copy link
Contributor

JunyangShao commented Mar 6, 2025

By looking at the discussion, this looks like WAI, closing.

Edit: 😰 I am very sorry for closing without evaluating thoroughly... Yesterday I closed this issue because I was thinking maybe the "<" comparator(if internally it was implemented as a function) is "inlined" there, so that the bounds check of string comparison appears in line 10. But @randall77's comment confirm that when collocated with other conditions, the behavior of string comparison will have issues with BCE.

Thanks for @as and @mvdan to reopen this. 👍

@jub0bs
Copy link
Author

jub0bs commented Mar 6, 2025

@JunyangShao What? No...

Edit: Even though the problem appears unrelated to generics, this issue could be re-purposed to track down those spurious bounds checks (confirmed by @randall77 in #72132 (comment)). I'm not aware of any open issue about that.

@as
Copy link
Contributor

as commented Mar 6, 2025

Please reopen the issue at this time.

@mvdan mvdan reopened this Mar 6, 2025
@seankhliao seankhliao changed the title cmd/compile: generic function incurs more bounds checks for some type arguments than for others cmd/compile: missing bounds check elimination for complex conditionals Mar 6, 2025
@JunyangShao
Copy link
Contributor

JunyangShao commented Mar 7, 2025

I printed out @randall77 's example in SSA and find out that the fact runtime.cmpstring not being considered pure might be the cause of this redundant check:
Image

Here to select the 3 different memory states for the return block caused by the 2 string comparisons, b2 is created and decouples b7 from the dominance relation from b4. We can still statically infer that whenever b2->b7 is taken, the memory state will always be v22 when entering b4. But the fact that b6 needs knowledge of the memory state along different control flow paths makes it impossible to decouple b2 from b7, because for the same condition value we cannot have two successor blocks in SSA.

Maybe some other analysis is needed in the prove pass that looks outside the dominance tree.

@JunyangShao JunyangShao added NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. and removed NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. labels Mar 7, 2025
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/656255 mentions this issue: cmd/compile: match more patterns for shortcircuit

@JunyangShao JunyangShao added FixPending Issues that have a fix which has not yet been reviewed or submitted. and removed NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Mar 10, 2025
@JunyangShao JunyangShao self-assigned this Mar 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BugReport Issues describing a possible bug in the Go implementation. compiler/runtime Issues related to the Go compiler and/or runtime. FixPending Issues that have a fix which has not yet been reviewed or submitted. Performance
Projects
None yet
Development

No branches or pull requests

7 participants