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
func main() {
  0x14f1                493b6610                CMPQ 0x10(R14), SP      [2:2]R_USEIFACE:type.string [2:2]R_USEIFACE:type.*os.File
  0x14f5                7656                    JBE 0x154d
  0x14f7                4883ec40                SUBQ $0x40, SP
  0x14fb                48896c2438              MOVQ BP, 0x38(SP)
  0x1500                488d6c2438              LEAQ 0x38(SP), BP
    fmt.Println("Hello, World")
  0x1505                440f117c2428            MOVUPS X15, 0x28(SP)
  0x150b                488d1500000000          LEAQ 0(IP), DX          [3:7]R_PCREL:type.string
  0x1512                4889542428              MOVQ DX, 0x28(SP)
  0x1517                488d1500000000          LEAQ 0(IP), DX          [3:7]R_PCREL:main..stmp_0<1>
  0x151e                4889542430              MOVQ DX, 0x30(SP)
        return Fprintln(os.Stdout, a...)
  0x1523                488b1d00000000          MOVQ 0(IP), BX          [3:7]R_PCREL:os.Stdout
  0x152a                488d0500000000          LEAQ 0(IP), AX          [3:7]R_PCREL:go.itab.*os.File,io.Writer
  0x1531                488d4c2428              LEAQ 0x28(SP), CX
  0x1536                bf01000000              MOVL $0x1, DI
  0x153b                4889fe                  MOVQ DI, SI
  0x153e                e800000000              CALL 0x1543             [1:5]R_CALL:fmt.Fprintln
}
  0x1543                488b6c2438              MOVQ 0x38(SP), BP
  0x1548                4883c440                ADDQ $0x40, SP
  0x154c                c3                      RET
func main() {
  0x154d                0f1f4000                NOPL 0(AX)
  0x1551                e800000000              CALL 0x1556             [1:5]R_CALL:runtime.morestack_noctxt
  0x1556                eb99                    JMP main.main(SB)
ターゲットは AMD64 なのですが、あまり見慣れないニーモニックだと思います。
これは、Plan9 のアセンブラフォーマットでIntelともAT&Tとも違います。
レジスタは8086の頃にあったものならば8086当時の名前で、AMD64で追加されたレジスタはRnnの形式で表示されます。
演算や転送の幅は SUBQ や MOVL のようにオペレーションの末尾の文字で指定されます。

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

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

go tool nm

go tool pack

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

脚註

編集


参考文献

編集