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
の様なハウスキーピング用のコマンドがあります(機会を見て紹介します)。
脚註
[編集]
参考文献
[編集]- “A Quick Guide to Go's Assembler”. 2021年10月22日閲覧。