Plan 9とGo言語のブログ

主にPlan 9やGo言語の日々気づいたことを書きます。

Go on Plan 9ではまったこと

この記事は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.sruntime.aeshashbodyで刺さっている。

余談だけれど、AESENCっていう命令があるのね。

他OSでコンパイルしたバイナリを動かす

Linuxなど、他のOSでGOOS, GOARCHPlan 9へ切り替えてビルドしたバイナリを動かしてみた。

$ GOOS=plan9 GOARCH=386 go build -o hello hello.go

ソースによって異なるけど、invalid opcodeエラーで落ちた。根本的にまずいくさい。

クラッシュさせるコードを調べた

invalid opcodeというメッセージは/sys/src/9/pc/trap.cexcnameにあるものだと思う。だとするとCPUによって発生している割り込みなので、ソースがどうこう言うよりは奇妙な命令を踏み抜いているだけのようにみえる。

問題のスタックトレース

package main

import "fmt"

func main() {
    fmt.Println("hello")
}

これをplan9/386用にコンパイルして実行。

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.cfloatmove()が生成部分だと思う。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環境変数はいらない。