kubo39's blog

ただの雑記です。

Goのジェネリクスを試してみたらいきなりICEに遭遇した話

Goにジェネリクスが来るぞ!ということで早速試してみることにする。

適当にmasterをクローンして cd src && ./make.bash でビルド(これだけでビルドできるの楽ちん)。

$ go version
go version devel go1.18-6602c86a38 Fri Sep 17 08:20:48 2021 +0000 linux/amd64

いろいろ遊んでみたところで、以下のようなコードを書いた時にインターナルコンパイルエラー(ICE)に遭遇した。

package main

import "fmt"

func Foo[T interface{ int }, S interface{ *T }](x T) S {
    return &x
}

func main() {
    x := 42
    y := Foo(x)
    fmt.Println(*y)
}

コンパイルエラーの出力はこのような感じ。

$ ../go/bin/go build -gcflags=-G=3 foo.go
# command-line-arguments
./foo.go:7:9: internal compiler error: found illegal assignment PTR-*.shape.int_0 -> .shape.*uint8_1; 

goroutine 1 [running]:
runtime/debug.Stack()
    /home/kubo39/dev/go/go/src/runtime/debug/stack.go:24 +0x65
cmd/compile/internal/base.FatalfAt({0x0, 0x0}, {0xd0ffd4, 0x27}, {0xc00045cd60, 0x3, 0x3})
    /home/kubo39/dev/go/go/src/cmd/compile/internal/base/print.go:227 +0x154
cmd/compile/internal/base.Fatalf(...)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/base/print.go:196
cmd/compile/internal/noder.assignconvfn({0xe6c010, 0xc000463220}, 0xc00041dab0)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/transform.go:417 +0x1b2
cmd/compile/internal/noder.typecheckaste(0x98, {0xc0004631d0, 0xc000463220}, 0x0, 0xc00045ce80, {0xc000408a00, 0x1, 0xc00045ce80})
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/transform.go:484 +0x179
cmd/compile/internal/noder.transformReturn(0xc0004631d0)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/transform.go:505 +0xa8
cmd/compile/internal/noder.(*subster).node.func1({0xe6e270, 0xc000443a90})
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/stencil.go:993 +0x7cb
cmd/compile/internal/noder.(*subster).node(0xc00041db20, {0xe6e270, 0xc000443a90})
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/stencil.go:1166 +0xa5
cmd/compile/internal/noder.(*subster).list(0xc000404300, {0xc000408800, 0x1, 0xc0000be550})
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/stencil.go:1418 +0x8e
cmd/compile/internal/noder.(*irgen).genericSubst(0xc00045e000, 0xc000462ff0, 0xc000460000, {0xc000408980, 0x2, 0x2}, 0x0, 0xc00040cb28)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/stencil.go:765 +0xce6
cmd/compile/internal/noder.(*irgen).getInstantiation(0xc00045e000, 0xc000460000, {0xc000408970, 0x2, 0x2}, 0x0)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/stencil.go:643 +0x2b0
cmd/compile/internal/noder.(*irgen).stencil.func1({0xe6c718, 0xc000430480})
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/stencil.go:108 +0x2df
cmd/compile/internal/ir.Visit.func1({0xe6c718, 0xc000430480})
    /home/kubo39/dev/go/go/src/cmd/compile/internal/ir/visit.go:105 +0x30
cmd/compile/internal/ir.(*AssignStmt).doChildren(0xc000443d60, 0xc00040cb10)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/ir/node_gen.go:152 +0x82
cmd/compile/internal/ir.DoChildren(...)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/ir/visit.go:94
cmd/compile/internal/ir.Visit.func1({0xe6c330, 0xc000443d60})
    /home/kubo39/dev/go/go/src/cmd/compile/internal/ir/visit.go:106 +0x57
cmd/compile/internal/ir.doNodes({0xc000417380, 0x4, 0x0}, 0xc00040cb10)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/ir/node_gen.go:1512 +0x67
cmd/compile/internal/ir.(*Func).doChildren(0xe6d078, 0xc00040a420)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/ir/func.go:152 +0x2e
cmd/compile/internal/ir.DoChildren(...)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/ir/visit.go:94
cmd/compile/internal/ir.Visit.func1({0xe6d078, 0xc00040a420})
    /home/kubo39/dev/go/go/src/cmd/compile/internal/ir/visit.go:106 +0x57
cmd/compile/internal/ir.Visit({0xe6d078, 0xc00040a420}, 0xc0004174c0)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/ir/visit.go:108 +0xb8
cmd/compile/internal/noder.(*irgen).stencil(0xc00045e000)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/stencil.go:90 +0x238
cmd/compile/internal/noder.(*irgen).generate(0xc00045e000, {0xc00009eb40, 0x2, 0x2})
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/irgen.go:301 +0x359
cmd/compile/internal/noder.check2({0xc00009eb40, 0x2, 0x2})
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/irgen.go:93 +0x175
cmd/compile/internal/noder.LoadPackage({0xc0000b8100, 0x2, 0x0})
    /home/kubo39/dev/go/go/src/cmd/compile/internal/noder/noder.go:90 +0x335
cmd/compile/internal/gc.Main(0xd23420)
    /home/kubo39/dev/go/go/src/cmd/compile/internal/gc/main.go:190 +0xaf3
main.main()
    /home/kubo39/dev/go/go/src/cmd/compile/main.go:55 +0xdd

エラーになっているのは return文 return &x の型と関数の返り値の型 S (実態は interface { *T }) の型チェックを行った際に、 transform.assignconvfn -> transform.Assignop -> typecheck.Assignop1 で解決できないまま素通しになった結果illegal assignment扱いになっているためだが、 そもそも S interface { *T }*uint8 になっているのがおかしいようにみえる。 (どうでもいいがtransform.Assignopはなぜかtypecheck.Assignopと同じ定義になっており、transform側の定義は冗長なためここを消すcontribution chanceがある)。

サクッと直せないだろうかとてきとうにあたりをつけながらみてみたが、パッと修正箇所がみつからなかったのでとりあえずこんなことがあったくらいの話になってしまった。。

というわけでGoのジェネリクスはまだバグがありそうなので、皆さんでコーナーケース探して修正していくとよさそうです。

追記 (2021/09/21)

このパッチ で修正されました。 なんかshapeな型として*uint8くるのは別に間違ってないっぽい。。?このへんgcshapeの意味とってないのでよくわかんないですが。 なおこの変更によってtransformとtypecheckのAssignopの実装に乖離が生まれたので定義の冗長さも消えました、よかったですね(?)