Original: https://studygolang.com/articles/19815
October 15, 2016
Last week, I was in dotGo , to attend a meeting of the best Go, and transatlantic Gopher meet. I quickly made a brief speech on the use of the tool chain available tools to check code generation. This article gives Gopher who could not attend the meeting to go over the speech content. Slide the go-talks can also be found.
In this article, we will use the following procedure:
package main
import "fmt"
func main() { sum := 1 + 1 fmt.Printf("sum: %v\n", sum) }
Toolchain
go build a command for the user encompasses a lot of things. However, if you need it, it also provides more detailed information about what it is doing. -x
Go build output is a way to make what is called a tag. If you want to see what assembly tool chain is that they are in a sequence where and what kind of mark using what words to use -x
.
$ go build -x
WORK=/var/folders/00/1b8h8000h01000cxqpysvccm005d21/T/go-build190726544
mkdir -p $WORK/hello/_obj/
mkdir -p $WORK/hello/_obj/exe/
cd /Users/jbd/src/hello
/Users/jbd/go/pkg/tool/darwin_amd64/compile -o $WORK/hello.a -trimpath $WORK -p main -complete -buildid d934a5702088e0fe5c931a55ff26bec87b80cbdc -D _/Users/jbd/src/hello -I $WORK -pack ./hello.go
cd .
/Users/jbd/go/pkg/tool/darwin_amd64/link -o $WORK/hello/_obj/exe/a.out -L $WORK -extld=clang -buildmode=exe -buildid=d934a5702088e0fe5c931a55ff26bec87b80cbdc $WORK/hello.a
mv $WORK/hello/_obj/exe/a.out hello
Compilation middle
Go in front, the system generates real-specific assembly, there is an intermediate stage of compilation. Go get compiler file number, and generating intermediate commands added to obj
the packet to generate machine code. If you are interested in what the compiler generated at this stage, -S
you can let the compiler will output dump it.
Go intermediate line of assembler code to be understood that the cost generally is a good reference. Or for example when you want to use a more optimal functional equivalent of replacing a compilation Go function it is also a good reference.
Here you will see the output of main.main.
$ go build -gcflags="-S"
# hello
"".main t=1 size=179 args=0x0 locals=0x60
0x0000 00000 (/Users/jbd/src/hello/hello.go:5) TEXT "".main(SB), $96-0
0x0000 00000 (/Users/jbd/src/hello/hello.go:5) MOVQ (TLS), CX
0x0009 00009 (/Users/jbd/src/hello/hello.go:5) CMPQ SP, 16(CX)
0x000d 00013 (/Users/jbd/src/hello/hello.go:5) JLS 169
0x0013 00019 (/Users/jbd/src/hello/hello.go:5) SUBQ $96, SP
0x0017 00023 (/Users/jbd/src/hello/hello.go:5) MOVQ BP, 88(SP)
0x001c 00028 (/Users/jbd/src/hello/hello.go:5) LEAQ 88(SP), BP
0x0021 00033 (/Users/jbd/src/hello/hello.go:5) FUNCDATA $0, gclocals · 69c1753bd5f81501d95132d08af04464(SB)
0x0021 00033 (/Users/jbd/src/hello/hello.go:5) FUNCDATA $1, gclocals · e226d4ae4a7cad8835311c6a4683c14f(SB)
0x0021 00033 (/Users/jbd/src/hello/hello.go:7) MOVQ $2, "".autotmp_1+64(SP)
0x002a 00042 (/Users/jbd/src/hello/hello.go:7) MOVQ $0, "".autotmp_0+72(SP)
0x0033 00051 (/Users/jbd/src/hello/hello.go:7) MOVQ $0, "".autotmp_0+80(SP)
0x003c 00060 (/Users/jbd/src/hello/hello.go:7) LEAQ type.int(SB), AX
0x0043 00067 (/Users/jbd/src/hello/hello.go:7) MOVQ AX, (SP)
0x0047 00071 (/Users/jbd/src/hello/hello.go:7) LEAQ "".autotmp_1+64(SP), AX
0x004c 00076 (/Users/jbd/src/hello/hello.go:7) MOVQ AX, 8(SP)
0x0051 00081 (/Users/jbd/src/hello/hello.go:7) PCDATA $0, $1
0x0051 00081 (/Users/jbd/src/hello/hello.go:7) CALL runtime.convT2E(SB)
0x0056 00086 (/Users/jbd/src/hello/hello.go:7) MOVQ 16(SP), AX
0x005b 00091 (/Users/jbd/src/hello/hello.go:7) MOVQ 24(SP), CX
0x0060 00096 (/Users/jbd/src/hello/hello.go:7) MOVQ AX, "".autotmp_0+72(SP)
0x0065 00101 (/Users/jbd/src/hello/hello.go:7) MOVQ CX, "".autotmp_0+80(SP)
0x006a 00106 (/Users/jbd/src/hello/hello.go:7) LEAQ Go.string."sum: %v\n"(SB), AX
0x0071 00113 (/Users/jbd/src/hello/hello.go:7) MOVQ AX, (SP)
0x0075 00117 (/Users/jbd/src/hello/hello.go:7) MOVQ $8, 8(SP)
0x007e 00126 (/Users/jbd/src/hello/hello.go:7) LEAQ "".autotmp_0+72(SP), AX
0x0083 00131 (/Users/jbd/src/hello/hello.go:7) MOVQ AX, 16(SP)
0x0088 00136 (/Users/jbd/src/hello/hello.go:7) MOVQ $1, 24(SP)
0x0091 00145 (/Users/jbd/src/hello/hello.go:7) MOVQ $1, 32(SP)
0x009a 00154 (/Users/jbd/src/hello/hello.go:7) PCDATA $0, $1
0x009a 00154 (/Users/jbd/src/hello/hello.go:7) CALL fmt.Printf(SB)
0x009f 00159 (/Users/jbd/src/hello/hello.go:8) MOVQ 88(SP), BP
0x00a4 00164 (/Users/jbd/src/hello/hello.go:8) ADDQ $96, SP
0x00a8 00168 (/Users/jbd/src/hello/hello.go:8) RET
0x00a9 00169 (/Users/jbd/src/hello/hello.go:8) NOP
0x00a9 00169 (/Users/jbd/src/hello/hello.go:5) PCDATA $0, $-1
0x00a9 00169 (/Users/jbd/src/hello/hello.go:5) CALL runtime.morestack_noctxt(SB)
0x00ae 00174 (/Users/jbd/src/hello/hello.go:5) JMP 0
...
If you want to learn more about the concept of middle compiled and why is it important in Go, I highly recommend from this year GopherCon of Rob Pike at The Go Assembler of Design at The .
Disassembler
As I mentioned, the -S
only action in the middle of the compilation. It represents a real machine can be used in the final workpiece. You can use a disassembler to check what's inside. The use of binary or library go tool objdump
. You may also want to use -s
to pay attention to the symbolic name. In this example, I will be main.main dump. Here is darwin/amd64
true generated assembly.
$ go tool objdump -s main.main hello
TEXT main.main(SB) /Users/jbd/src/hello/hello.go
hello.go:5 0x2040 65488b0c25a0080000 GS MOVQ GS:0x8a0, CX
hello.go:5 0x2049 483b6110 CMPQ 0x10(CX), SP
hello.go:5 0x204d 0f8696000000 JBE 0x20e9
hello.go:5 0x2053 4883ec60 SUBQ $0x60, SP
hello.go:5 0x2057 48896c2458 MOVQ BP, 0x58(SP)
hello.go:5 0x205c 488d6c2458 LEAQ 0x58(SP), BP
hello.go:7 0x2061 48c744244002000000 MOVQ $0x2, 0x40(SP)
hello.go:7 0x206a 48c744244800000000 MOVQ $0x0, 0x48(SP)
hello.go:7 0x2073 48c744245000000000 MOVQ $0x0, 0x50(SP)
hello.go:7 0x207c 488d053d4d0800 LEAQ 0x84d3d(IP), AX
...
Symbol table
Sometimes, you just need to check all of the symbol table and not understand the code segment or data segment. Similar generic nm tool, Go circulated a list nm tool allows you to work in a signed note on the table and the size of. Or if you want to see what the inside of a binary library Go is derived what, this is a very handy tool.
$ go tool nm hello
...
f4760 B __cgo_init
f4768 B __cgo_notify_runtime_init_done
f4770 B __cgo_thread_start
4fb70 T __rt0_amd64_darwin
4e220 T _gosave
4fb90 T _main
ad1e0 R _masks
4fd00 T _nanotime
4e480 T _setg_gcc
ad2e0 R _shifts
624a0 T errors.(*errorString).Error
62400 T errors.New
52470 T fmt.(*buffer).WriteRune
...
optimization
SSA back-end and new contributions together contributed a team SSA pass all the tools to visualize. Set the value of the environment variable GOSSAFUNC is a function name go build and run the command. Ssa.html will produce a document showing every step of the compiler to optimize your code elapsed.
$ GOSSAFUNC=main Go build && open ssa.html
Here is a visualization of the results of all pass main function of the application.
Go compiler can also be labeled inline and escape analysis. If you -m=2
mark to the compiler, it will output optimization and labeling on these two aspects. Here we see the net/context
inline operation and escape analysis package related.
$ go build -gcflags="-m" golang.org/x/net/context
# golang.org/x/net/context
../golang.org/x/net/context/context.go:140: can inline Background as: func() Context { return background }
../golang.org/x/net/context/context.go:149: can inline TODO as: func() Context { return todo }
../golang.org/x/net/context/go17.go:32: cannot inline WithCancel: non-leaf function
../golang.org/x/net/context/go17.go:46: cannot inline WithDeadline: non-leaf function
../golang.org/x/net/context/go17.go:61: cannot inline WithTimeout: non-leaf function
../golang.org/x/net/context/go17.go:62: inlining call to time.Time.Add method(time.Time) func(time.Duration) time.Time { time.t · 2.sec += int64(time.d · 3 / time.Duration(1000000000)); var time.nsec · 4 int32; time.nsec · 4 = <N>; time.nsec · 4 = time.t · 2.nsec + int32(time.d · 3 % time.Duration(1000000000)); if time.nsec · 4 >= int32(1000000000) { time.t · 2.sec++; time.nsec · 4 -= int32(1000000000) } else { if time.nsec · 4 < int32(0) { time.t · 2.sec--; time.nsec · 4 += int32(1000000000) } }; time.t · 2.nsec = time.nsec · 4; return time.t · 2 }
../golang.org/x/net/context/go17.go:70: cannot inline WithValue: non-leaf function
../golang.org/x/net/context/context.go:141: background escapes to heap
../golang.org/x/net/context/context.go:141: from ~r0 (return) at ../golang.org/x/net/context/context.go:140
../golang.org/x/net/context/context.go:150: todo escapes to heap
../golang.org/x/net/context/context.go:150: from ~r0 (return) at ../golang.org/x/net/context/context.go:149
../golang.org/x/net/context/go17.go:33: parent escapes to heap
../golang.org/x/net/context/go17.go:33: from parent (passed to function[unknown]) at ../golang.org/x/net/context/go17.go:33
../golang.org/x/net/context/go17.go:32: leaking param: parent
../golang.org/x/net/context/go17.go:32: from parent (interface-converted) at ../golang.org/x/net/context/go17.go:33
../golang.org/x/net/context/go17.go:32: from parent (passed to function[unknown]) at ../golang.org/x/net/context/go17.go:33
../golang.org/x/net/context/go17.go:47: parent escapes to heap
../golang.org/x/net/context/go17.go:47: from parent (passed to function[unknown]) at ../golang.org/x/net/context/go17.go:47
../golang.org/x/net/context/go17.go:46: leaking param: parent
../golang.org/x/net/context/go17.go:46: from parent (interface-converted) at ../golang.org/x/net/context/go17.go:47
../golang.org/x/net/context/go17.go:46: from parent (passed to function[unknown]) at ../golang.org/x/net/context/go17.go:47
../golang.org/x/net/context/go17.go:46: leaking param: deadline
../golang.org/x/net/context/go17.go:46: from deadline (passed to function[unknown]) at ../golang.org/x/net/context/go17.go:46
../golang.org/x/net/context/go17.go:48: ctx escapes to heap
../golang.org/x/net/context/go17.go:48: from ~r2 (return) at ../golang.org/x/net/context/go17.go:46
../golang.org/x/net/context/go17.go:61: leaking param: parent
../golang.org/x/net/context/go17.go:61: from parent (passed to function[unknown]) at ../golang.org/x/net/context/go17.go:61
../golang.org/x/net/context/go17.go:71: parent escapes to heap
../golang.org/x/net/context/go17.go:71: from parent (passed to function[unknown]) at ../golang.org/x/net/context/go17.go:71
../golang.org/x/net/context/go17.go:70: leaking param: parent
../golang.org/x/net/context/go17.go:70: from parent (interface-converted) at ../golang.org/x/net/context/go17.go:71
../golang.org/x/net/context/go17.go:70: from parent (passed to function[unknown]) at ../golang.org/x/net/context/go17.go:71
../golang.org/x/net/context/go17.go:70: leaking param: key
../golang.org/x/net/context/go17.go:70: from key (passed to function[unknown]) at ../golang.org/x/net/context/go17.go:70
../golang.org/x/net/context/go17.go:70: leaking param: val
../golang.org/x/net/context/go17.go:70: from val (passed to function[unknown]) at ../golang.org/x/net/context/go17.go:70
../golang.org/x/net/context/go17.go:71: context.WithValue(parent, key, val) escapes to heap
../golang.org/x/net/context/go17.go:71: from ~r3 (return) at ../golang.org/x/net/context/go17.go:70
<autogenerated>:1: leaking param: .this
<autogenerated>:1: from .this.Deadline() (receiver in indirect call) at <autogenerated>:1
<autogenerated>:2: leaking param: .this
<autogenerated>:2: from .this.Done() (receiver in indirect call) at <autogenerated>:2
<autogenerated>:3: leaking param: .this
<autogenerated>:3: from .this.Err() (receiver in indirect call) at <autogenerated>:3
<autogenerated>:4: leaking param: key
<autogenerated>:4: from .this.Value(key) (parameter to indirect call) at <autogenerated>:4
<autogenerated>:4: leaking param: .this
<autogenerated>:4: from .this.Value(key) (receiver in indirect call) at <autogenerated>:4
You can use the -m
view comes with no reason, not so verbose output, but David Chase said that although -m=2
not perfect, but it is often useful.
It is worth mentioning that you often need to disable optimizations to get an easier view on what happened, because the optimization may modify the sequence of operations, increase the code, remove the code or the code conversion. Optimization is turned on, the output of the line of code optimization Go correspondence will be more difficult, performance testing will be more difficult, because optimization can bring more than one change. You can -N
disable optimization, through -l
to disable inlining.
$ go build -gcflags="-l -N"
Once the optimization is disabled, you will not be affected debugging code changes, performance testing will not be affected by more than one change.
Lexer
If you work in the lexer, the compiler provides a flag lexer debugging while checking the source.
$ go build -gcflags="-x"
# hello
lex: PACKAGE
lex: ident main
lex: implicit semi
lex: IMPORT
lex: string literal
lex: implicit semi
lex: FUNC
lex: ident main
./hello.go:5 lex: TOKEN '('
./hello.go:5 lex: TOKEN ')'
./hello.go:5 lex: TOKEN '{'
lex: ident sum
./hello.go:6 lex: TOKEN COLAS
lex: integer literal
./hello.go:6 lex: TOKEN '+'
lex: integer literal
lex: implicit semi
lex: ident fmt
./hello.go:7 lex: TOKEN '.'
lex: ident Printf
./hello.go:7 lex: TOKEN '('
lex: string literal
./hello.go:7 lex: TOKEN ','
lex: ident sum
./hello.go:7 lex: TOKEN ')'
lex: implicit semi
./hello.go:8 lex: TOKEN '}'
lex: implicit semi