この記事はTumblrで公開されていました
Go 1.3.1をビルド
Plan 9(386)にGo 1.3.1をインストールしてみた。通常どおりsrc/all.rcを実行したのだけれど、cmd/8cと出力された後にinvalid opcodeエラーで落ちる。そのため、インストールできない。
Go 1.3.0をビルド
1.3.1と同じエラーだった。stack traceをみると、これも1.3.1と同じで、src/pkg/runtime/asm_386.sのruntime.aeshashbodyで刺さっている。
余談だけれど、AESENCっていう命令があるのね。
他OSでコンパイルしたバイナリを動かす
Linuxなど、他のOSでGOOS, GOARCHをPlan 9へ切り替えてビルドしたバイナリを動かしてみた。
$ GOOS=plan9 GOARCH=386 go build -o hello hello.go
ソースによって異なるけど、invalid opcodeエラーで落ちた。根本的にまずいくさい。
クラッシュさせるコードを調べた
invalid opcodeというメッセージは/sys/src/9/pc/trap.cのexcnameにあるものだと思う。だとするとCPUによって発生している割り込みなので、ソースがどうこう言うよりは奇妙な命令を踏み抜いているだけのようにみえる。
問題のスタックトレース
package main import "fmt" func main() { fmt.Println("hello") }
cpu% ./hello sys: trap: invalid opcode pc=0x0003ebab PC=0x3ebab goroutine 1 [running, locked to thread]: math.init·1() /Users/lufia/go/src/pkg/math/pow10.go:34 +0x1b fp=0x10231f38 sp=0x10231f34 math.init() /Users/lufia/go/src/pkg/math/unsafe.go:21 +0x41 fp=0x10231f3c sp=0x10231f38 reflect.init() /Users/lufia/go/src/pkg/reflect/value.go:2718 +0x47 fp=0x10231f5c sp=0x10231f3c fmt.init() /Users/lufia/go/src/pkg/fmt/scan.go:1169 +0x4c fp=0x10231f94 sp=0x10231f5c main.init() /tmp/h.go:7 +0x41 fp=0x10231f98 sp=0x10231f94 runtime.main() /Users/lufia/go/src/pkg/runtime/proc.c:272 +0xd5 fp=0x10231fd0 sp=0x10231f98 runtime.goexit() /Users/lufia/go/src/pkg/runtime/proc.c:1771 fp=0x10231fd4 sp=0x10231fd0
src/pkg/math/pow10.go:34は何かというと
func init() { pow10tab[0] = 1.0e0 // これが34行目 pow10tab[1] = 1.0e1 for i := 2; i < len(pow10tab); i++ { m := i / 2 pow10tab[i] = pow10tab[m] * pow10tab[i-m] } }
helloバイナリファイルをダンプしてみた。
$ go tool objdump hello | grep pow10.go:34 pow10.go:34 0x3ebab f20f100570e30b00 REPNE MOVSD_XMM 0xbe370(IP), X0 pow10.go:34 0x3ebb3 f20f1105a03a1000 REPNE MOVSD_XMM X0, 0x103aa0(IP)
PCのアドレスから察するに、
REPNE MOVSD_XMM 0xbe370(IP), X0
が踏み抜いた様子。
アセンブリで再現させてみた。
TEXT main·main+0(SB),$16-0 MOVSD $1.0,X0 RET TEXT main·init+0(SB),$16-0 RET
これをLinuxで実行させると落ちないが、Plan 9(386)では落ちる。次に、MOVSDが悪いのかX0(XMM0)が悪いのか、どちらなのかを調べるためPlan 9アセンブラで以下のコードをアセンブルしてみた。
TEXT main+0(SB), 0, $0 MOVSD $1.0, X0 RET
このコードは、6aではアセンブルできる。8aの場合はX0がsyntax errorとなる。
誰がMOVSDを生成しているのか
build.golang.orgではplan9/386のステータスが正常にもかかわらず動かないことが不思議。致命的に間違っているのかと考え、invalid opcodeエラーが発生していた行はfloat64型の変数にfloat64な値を代入する行だったのでそこを中心にコード生成処理を読んだ。
コード生成
Go配布物の、src/cmd/8g/gsubr.cのfloatmove()
が生成部分だと思う。float64を代入する処理の途中でuse_sseという変数があって、これによりfloatmove_sse()
かfloatmove_387()
か分岐する。
floatmove_387()
の場合、代入先がメモリかそれ以外かで命令が変わる。代入先がメモリの場合はAFMOVDP、それ以外ならAFMOVD命令。floatmove_sse()
の場合、メモリかどうかにかかわらずAMOVSD命令が使われる。推測すると、plan9/386の場合はfloatmove_387()
を使うべきだけれどなぜかfloatmove_sse()
のほうが使われてしまってinvalid opcodeエラーになっている様子。
use_sseとは
use_sseフラグはsrc/cmd/gc/lex.cで切り替えられている。
GOARCH | GO386 | use_sse |
---|---|---|
386 | 387 | 0 |
386 | sse2 | 1 |
GO386環境変数ってなんだと思ったけど、Installing Go from sourceにきちんと書かれていた。
解決方法
invalid opcodeエラーを解決するには、上記ドキュメントの通りGO386環境変数に387を指定してあげなければならない。
$ cd $GOROOT/src $ GOOS=plan9 GOARCH=386 GO386=387 ./make.bash
これでビルドすればよい。go build
のときはGO386環境変数はいらない。