Go/Goのプログラムがどんなアセンブリにコンパイルされるか?

< Go

Goのプログラムがどんなアセンブリにコンパイルされるか? 編集

Goは簡素化された構文とキャッシュの使用により、非常に高速にコンパイルされますが、(中間コードではなく)ネイティブな機械語を生成します。

では、実際にどのようなコードが生成されるかを検証してみましょう。

Hello, World を逆アセンブル 編集

hello.go
package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}

上のような単純で完全なプログラム hello.go を用意します。

%  go tool compile -o hello.o hello.go
hello.oにコンパイル結果が出力されますが、これは。
% file fibo.o
fibo.o: current ar archive

ar(1) のアーカイブです。

% ar tv hello.o
rw-r--r--       0/0            92 Jan  1 09:00 1970 __.PKGDEF
rw-r--r--       0/0          6447 Jan  1 09:00 1970 _go_.o

展開してみても

% ar xv hello.o
x - __.PKGDEF
x - _go_.o
% file __.PKGDEF _go_.o
__.PKGDEF: data
_go_.o:    data
と、素性はわかりませんが
go tool objdump という Go の逆アセンブラーは、この形式を理解できます(-S はソースも併せて表示するオプションです)。
% go tool objdump -S hello.o > hello.objdump
hello.objdump
  0x14b9                440f117c2440            MOVUPS X15, 0x40(SP)
  0x14bf                488d0500000000          LEAQ 0(IP), AX          [3:7]R_PCREL:type.string
  0x14c6                4889442440              MOVQ AX, 0x40(SP)
  0x14cb                488d0500000000          LEAQ 0(IP), AX          [3:7]R_PCREL:""..stmp_0<1>
  0x14d2                4889442448              MOVQ AX, 0x48(SP)
        return Fprintln(os.Stdout, a...)
  0x14d7                488b0500000000          MOVQ 0(IP), AX          [3:7]R_PCREL:os.Stdout
  0x14de                488d0d00000000          LEAQ 0(IP), CX          [3:7]R_PCREL:go.itab.*os.File,io.Writer
  0x14e5                48890c24                MOVQ CX, 0(SP)
  0x14e9                4889442408              MOVQ AX, 0x8(SP)
  0x14ee                488d442440              LEAQ 0x40(SP), AX
  0x14f3                4889442410              MOVQ AX, 0x10(SP)
  0x14f8                48c744241801000000      MOVQ $0x1, 0x18(SP)
  0x1501                48c744242001000000      MOVQ $0x1, 0x20(SP)
  0x150a                e800000000              CALL 0x150f             [1:5]R_CALL:fmt.Fprintln
}
  0x150f                488b6c2450              MOVQ 0x50(SP), BP
  0x1514                4883c458                ADDQ $0x58, SP
  0x1518                c3                      RET
func main() {
  0x1519                e800000000              CALL 0x151e             [1:5]R_CALL:runtime.morestack_noctxt
  0x151e                e975ffffff              JMP "".main(SB)

TEXT os.(*File).close(SB) gofile..<autogenerated>

  0x16de                488b442408              MOVQ 0x8(SP), AX
  0x16e3                488b00                  MOVQ 0(AX), AX
  0x16e6                4889442408              MOVQ AX, 0x8(SP)
  0x16eb                450f57ff                XORPS X15, X15
  0x16ef                440f117c2410            MOVUPS X15, 0x10(SP)
  0x16f5                e900000000              JMP 0x16fa              [1:5]R_CALL:os.(*file).close
ターゲットは AMD64 なのですが、あまり見慣れないニーモニックだと思います。
これは、plan9 のアセンブラフォーマットでIntelともGasとも違います。
レジスタは8086の頃にあったものならば8086当時の名前で、AMD64で追加されたレジスタはRnnの形式で表示されます。
演算や転送の幅は SUBQ や MOVL のようにオペレーションの末尾の文字で指定されます。

よく見ると、ソースの fmt.Println("Hello, World") が、fmt.Fprintln(os.Stdout,) に置き換えられておりインライン展開が行われていることがわかります。

Goはコンパイラーですが、コンパイラー自体がGoで書かれ、ソースコードとともに配布されており、他にも

go tool nm

go tool pack

の様なハウスキーピング用のコマンドがあります(機会を見て紹介します)。

脚註 編集


参考文献 編集