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: the output messages for some generic functions are confusing when using -gcflags="-m -m" #58140

Closed
go101 opened this issue Jan 30, 2023 · 5 comments
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. FrozenDueToAge generics Issue is related to generics NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@go101
Copy link

go101 commented Jan 30, 2023

What version of Go are you using (go version)?

$ go version
go version go1.20rc3 linux/amd64

Does this issue reproduce with the latest release?

Yes

What did you do?

package main

type Signed interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64
}

type Unsigned interface {
	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}

type Integer interface {
	Signed | Unsigned
}

type Float interface {
	~float32 | ~float64
}

type Ordered interface {
	Integer | Float | ~string
}

func Min[T Ordered](x, y T) T {
	if x <= y {
		return x
	}
	return y
}


type ByteSeq interface{ ~string | ~[]byte }

func CommonPrefixes[X, Y ByteSeq](x X, y Y) (X, Y) {
	min := Min(len(x), len(y))
	x2, y2 := x[:min], y[:min]
	if len(x2) != len(y2) { // BCE hint
		panic("len(x2) != len(y2)")
	}
	for i := 0; i < len(x2); i++ {
		if x2[i] != y2[i] {
			return x2[:i], y2[:i]
		}
	}
	return x2, y2
}

var (
	_, _ = CommonPrefixes("", []byte{})
	_, _ = CommonPrefixes("", "")
	_, _ = CommonPrefixes([]byte{}, "")
	_, _ = CommonPrefixes([]byte{}, []byte{})
)

func Compare[X, Y ByteSeq](x X, y Y) int {
	min := Min(len(x), len(y))
	x2, y2 := x[:min], y[:min]
	if len(x2) != len(y2) {
		panic("len(x2) != len(y2)")
	}
	for i := 0; i < len(x2); i++ {
		if v := int(x2[i]) - int(y2[i]); v != 0 {
			return v
		}
	}
	
	if len(x) == len(y) {
		return 0
	}
	if len(x) < len(y) {
		return -1
	}
	return 1
}

var (
	_ = Compare("", []byte{})
	_ = Compare("", "")
	_ = Compare([]byte{}, "")
	_ = Compare([]byte{}, []byte{})
)

func main() {}
go run -gcflags="-m -m"  main.go 2>&1 | grep inline

What did you expect to see?

Consistent messages for the two generic functions, and cleaner messages.

What did you see instead?

./main.go:82:6: can inline main with cost 0 as: func() {  }
./main.go:23:6: can inline Min[go.shape.int] with cost 8 as: func(*[1]uintptr, go.shape.int, go.shape.int) go.shape.int { if x <= y { return x }; return y }
./main.go:54:6: cannot inline Compare[go.shape.[]uint8,go.shape.[]uint8]: function too complex: cost 92 exceeds budget 80
./main.go:23:6: can inline Min[int] with cost 15 as: func(int, int) int { return Min[go.shape.int](&.dict.Min[int], x, y) }
./main.go:54:6: can inline Compare[[]uint8,[]uint8] with cost 64 as: func([]byte, []byte) int { return Compare[go.shape.[]uint8,go.shape.[]uint8](&.dict.Compare[[]uint8,[]uint8], x, y) }
./main.go:54:6: cannot inline Compare[go.shape.[]uint8,go.shape.string]: function too complex: cost 92 exceeds budget 80
./main.go:54:6: can inline Compare[[]uint8,string] with cost 64 as: func([]byte, string) int { return Compare[go.shape.[]uint8,go.shape.string](&.dict.Compare[[]uint8,string], x, y) }
./main.go:54:6: cannot inline Compare[go.shape.string,go.shape.string]: function too complex: cost 92 exceeds budget 80
./main.go:54:6: can inline Compare[string,string] with cost 64 as: func(string, string) int { return Compare[go.shape.string,go.shape.string](&.dict.Compare[string,string], x, y) }
./main.go:54:6: cannot inline Compare[go.shape.string,go.shape.[]uint8]: function too complex: cost 92 exceeds budget 80
./main.go:54:6: can inline Compare[string,[]uint8] with cost 64 as: func(string, []byte) int { return Compare[go.shape.string,go.shape.[]uint8](&.dict.Compare[string,[]uint8], x, y) }
./main.go:33:6: can inline CommonPrefixes[go.shape.[]uint8,go.shape.[]uint8] with cost 73 as: func(*[2]uintptr, go.shape.[]uint8, go.shape.[]uint8) (go.shape.[]uint8, go.shape.[]uint8) { min := Min[go.shape.int](&.dict.Min[int], len(x), len(y)); x2, y2 := x[:min], y[:min]; if len(x2) != len(y2) { panic("len(x2) != len(y2)") }; for loop; return x2, y2 }
./main.go:33:6: cannot inline CommonPrefixes[[]uint8,[]uint8]: function too complex: cost 89 exceeds budget 80
./main.go:33:6: can inline CommonPrefixes[go.shape.[]uint8,go.shape.string] with cost 73 as: func(*[2]uintptr, go.shape.[]uint8, go.shape.string) (go.shape.[]uint8, go.shape.string) { min := Min[go.shape.int](&.dict.Min[int], len(x), len(y)); x2, y2 := x[:min], y[:min]; if len(x2) != len(y2) { panic("len(x2) != len(y2)") }; for loop; return x2, y2 }
./main.go:33:6: cannot inline CommonPrefixes[[]uint8,string]: function too complex: cost 89 exceeds budget 80
./main.go:33:6: can inline CommonPrefixes[go.shape.string,go.shape.string] with cost 73 as: func(*[2]uintptr, go.shape.string, go.shape.string) (go.shape.string, go.shape.string) { min := Min[go.shape.int](&.dict.Min[int], len(x), len(y)); x2, y2 := x[:min], y[:min]; if len(x2) != len(y2) { panic("len(x2) != len(y2)") }; for loop; return x2, y2 }
./main.go:33:6: cannot inline CommonPrefixes[string,string]: function too complex: cost 89 exceeds budget 80
./main.go:33:6: can inline CommonPrefixes[go.shape.string,go.shape.[]uint8] with cost 73 as: func(*[2]uintptr, go.shape.string, go.shape.[]uint8) (go.shape.string, go.shape.[]uint8) { min := Min[go.shape.int](&.dict.Min[int], len(x), len(y)); x2, y2 := x[:min], y[:min]; if len(x2) != len(y2) { panic("len(x2) != len(y2)") }; for loop; return x2, y2 }
./main.go:33:6: cannot inline CommonPrefixes[string,[]uint8]: function too complex: cost 89 exceeds budget 80

Here is the simplified version:

...
./main.go:54:6: can inline Compare[[]uint8,[]uint8] with cost 64 as: func([]byte, []byte) int { return Compare[go.shape.[]uint8,go.shape.[]uint8](&.dict.Compare[[]uint8,[]uint8], x, y) }
./main.go:54:6: cannot inline Compare[go.shape.[]uint8,go.shape.string]: function too complex: cost 92 exceeds budget 80
...
./main.go:33:6: cannot inline CommonPrefixes[[]uint8,[]uint8]: function too complex: cost 89 exceeds budget 80
./main.go:33:6: can inline CommonPrefixes[go.shape.[]uint8,go.shape.string] with cost 73 as: func(*[2]uintptr, go.shape.[]uint8, 
...

It looks, for each of the generic functions, there are two groups of instances: go.shape.xxx group and xxx group.
Why two groups? It is some confusing.

And for the Compare function, the xxx group can be inlined, but the go.shape.xxx group can't. For the CommonPrefixes function, it is the inverse. This makes developers unsure whether or not a generic function can be inlined or not.

@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Jan 30, 2023
@mknyszek
Copy link
Contributor

CC @golang/compiler

@mknyszek mknyszek added NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. generics Issue is related to generics labels Jan 30, 2023
@mknyszek mknyszek added this to the Backlog milestone Jan 30, 2023
@dr2chase
Copy link
Contributor

There's a "who's this for?" problem here, since if debugging a problem in the compiler, those ugly names are "the truth" and the requirement for pretty/ugly name translation is just another thing to get wrong in a situation where at least one thing is already wrong (else we would not be debugging). This complaint is however very much applicable to the output of the -json flag, since that is intended for IDE/normal-programmer consumption.

@go101
Copy link
Author

go101 commented Jan 31, 2023

I don't care much about the names. Now I just not clear whether or not the instances of the two generic functions can be inlined or not?

@mdempsky
Copy link
Member

mdempsky commented Jan 31, 2023

It looks, for each of the generic functions, there are two groups of instances: go.shape.xxx group and xxx group. Why two groups?

There are two groups because that's how cmd/compile implements generics. See https://go.googlesource.com/proposal/+/refs/heads/master/design/generics-implementation-gcshape.md for more details.

If you read the diagnostics that you posted, you'll notice that the functions are different:

./main.go:23:6: can inline Min[go.shape.int] with cost 8 as: func(*[1]uintptr, go.shape.int, go.shape.int) go.shape.int { if x <= y { return x }; return y }
./main.go:23:6: can inline Min[int] with cost 15 as: func(int, int) int { return Min[go.shape.int](&.dict.Min[int], x, y) }

Min[go.shape.int] is the internal GC-shaped instantiation, which takes an extra dictionary parameter (the *[1]uintptr-typed parameter).

Min[int] is a wrapper function with the correct user-type of just func(int, int) int, which calls Min[go.shape.int] with the appropriate dictionary argument for type argument int.

Now I just not clear whether or not the instances of the two generic functions can be inlined or not?

I'm afraid I don't know what additional information you need to answer this question. We report for each instantiation whether it was inlinable.

@go101
Copy link
Author

go101 commented Feb 1, 2023

Thanks for the explanation.

Now, I understand why the inline analysis statuses for the two functions are different.

func foo_wrapper(...) (...) {
   ...
   return foo(...)
}

If a foo call can be inlined but its inline cost is larger than the inline cost of a function call,
then it might make the inline cost of foo_wrapper too larger to be inline-able.
And on the other hand, if foo is not inline-able, then foo_wrapper might be.

Please feel free to close this issue if nothing needs to be done.

@golang golang locked and limited conversation to collaborators Feb 1, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. FrozenDueToAge generics Issue is related to generics NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

6 participants