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: miscompilation of generic code; "unreachable method called. linker bug?" fatal error at runtime #48047

Closed
jasdel opened this issue Aug 29, 2021 · 6 comments
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Milestone

Comments

@jasdel
Copy link
Contributor

jasdel commented Aug 29, 2021

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

$ go version
go version devel go1.18-4d3cc847745 Thu Aug 12 20:22:27 2021 +0000 linux/amd64

Does this issue reproduce with the latest release?

Using latest of dev.typeparams branch, via gotip download dev.typeparams

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/jasdel/.cache/go-build"
GOENV="/home/jasdel/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS="-gcflags=-G=3"
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/jasdel/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/jasdel/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/jasdel/sdk/gotip"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/jasdel/sdk/gotip/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="devel go1.18-4d3cc847745 Thu Aug 12 20:22:27 2021 +0000"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/home/jasdel/go/src/jasdel/typPar/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build490389398=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Ran across this bug by mistake while experimenting with generics. This example results in a runtime panic linker error, because a method is unreachable. The panic seems to be triggered by using a generic type for a member instead of the intended type parameter for the member type.

In the following example, A generic struct's field member is of type B[T2] instead of T1. When A is initialized with BImpl as the type parameter a runtime panic occurs when a.field.Work() method is called in A.Work. I'd expect B[T2] to be valid, even if odd, and compile/run since it is the same type as T1.

package main

import "fmt"

type A[T1 B[T2], T2 any] struct {
	// This `B[T2]` was accidentally used instead of T1. The application compiles as
	// expected, but results in a runtime linker error when `A.Work` calls
	// `a.field.Work()`.
	// 
	// If this member's type is `T1` there is no error.
	field B[T2]
}

func (a *A[T1, T2]) Work() T2 {
	//   fatal error: unreachable method called. linker bug?
	return a.field.Work()
}

type B[T any] interface {
	Work() T
}

type BImpl[T any] struct{}

func (b *BImpl[T]) Work() (v T) {
	return v
}

func main() {
	s := &A[*BImpl[string], string]{
		field: &BImpl[string]{},
	}

	fmt.Println(s.Work())
}

What did you expect to see?

I'd expect the program to run successful, or emit a compile time error if this use case is not intended to be supported. This should not result in a runtime failure.

What did you see instead?

The program compiles, but fails at runtime with a linker error.

$ GOFLAGS=-gcflags=-G=3 gotip run .
fatal error: unreachable method called. linker bug?

goroutine 1 [running]:
runtime.throw({0x499773, 0x5247e0})
	/home/jasdel/sdk/gotip/src/runtime/panic.go:965 +0x71 fp=0xc000086f08 sp=0xc000086ed8 pc=0x42edb1
runtime.unreachableMethod()
	/home/jasdel/sdk/gotip/src/runtime/iface.go:525 +0x25 fp=0xc000086f28 sp=0xc000086f08 pc=0x4099e5
main.(*A[.shape.*uint8,.shape.string]).Work(...)
	/home/jasdel/go/src/jasdel/typPar/main.go:14
main.main()
	/home/jasdel/go/src/jasdel/typPar/main.go:32 +0x3d fp=0xc000086f80 sp=0xc000086f28 pc=0x47d23d
runtime.main()
	/home/jasdel/sdk/gotip/src/runtime/proc.go:255 +0x227 fp=0xc000086fe0 sp=0xc000086f80 pc=0x431467
runtime.goexit()
	/home/jasdel/sdk/gotip/src/runtime/asm_amd64.s:1439 +0x1 fp=0xc000086fe8 sp=0xc000086fe0 pc=0x459b21
exit status 2
@mdempsky
Copy link
Member

FYI, you don't need to set -gcflags=-G=3 any more at tip.

@mdempsky
Copy link
Member

/cc @danscales @randall77

Correctly prints "\n" when built and run with GOEXPERIMENT=unified.

@mdempsky mdempsky changed the title Unexpected runtime unreachable method linker error when using generic type params. cmd/compile: miscompilation of generic code; "unreachable method called. linker bug?" fatal error at runtime Aug 29, 2021
@mdempsky mdempsky added the NeedsFix The path to resolution is known, but the work has not been done. label Aug 29, 2021
@mdempsky mdempsky added this to the Go1.18 milestone Aug 29, 2021
@korzhao
Copy link
Contributor

korzhao commented Aug 30, 2021

Hope it helps. I add noinline,it works.

//go:noinline
func (a *A[T1, T2]) Work() T2 {
	return a.field.Work()
}

@danscales
Copy link
Contributor

We've run into this before. This is related to shapes and dictionaries, and the aggressive "dead-method" elimination by the linker. We need to mark the actual types that are may be stored in an interface (as opposed to just the shape types) and we need to mark the methods of the interface that have definitely been called. This is more of a problem with shapes/dictionaries, since most method calls become interface calls. We are already doing those extra mark calls in stencil.go - MarkUsedIfaceMethodIndex and MarkTypeUsedInInterface.

Hope it helps. I add noinline,it works.

@korzhao Thanks, that is actually a helpful observation!

Our problem is that these Mark calls take a symbol which they should be attached to, and the mark calls do not apply if that symbol is also dead-code eliminated. Because methods can be eliminated by inlining, we are currently using the dictionaries as the symbol. But dictionaries can also be eliminated by inlining, so we need to rethink which symbol we attach these Mark calls to.
In this case, the one dictionary used to call A[..., .shape.string].Work with string type arg is eliminated when A[...].Work() is inlined in main(). That means our call to mark that the Work method of B[string] is definitely called doesn't survive. So, we need to figure out a slightly different strategy.

@randall77

@gopherbot
Copy link

Change https://golang.org/cl/349169 mentions this issue: cmd/compile: keep methods on generic types from being deadcode eliminated

@gopherbot
Copy link

Change https://golang.org/cl/357836 mentions this issue: cmd/compile: revert most of CL 349169

gopherbot pushed a commit that referenced this issue Oct 25, 2021
The method of using references to dictionaries to hold methods
live during linker deadcode elimination wasn't working very well.
I implemented a new scheme in the CL below this, so this CL strips
out the old method.

The new method has the added benefit of having 0 runtime overhead
(unlike the stuff we're ripping out here, which does have a small overhead).

Update #48047

Change-Id: I68ac57119792d53c58f1480f407de6ab2bb53211
Reviewed-on: https://go-review.googlesource.com/c/go/+/357836
Trust: Keith Randall <khr@golang.org>
Trust: Dan Scales <danscales@google.com>
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Dan Scales <danscales@google.com>
@golang golang locked and limited conversation to collaborators Jun 23, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

No branches or pull requests

6 participants