延遲語句 defer 在最早期的 Go 語言設計中並不存在,後來才單獨增加了這一特性,由 Robert Griesemer 完成語言規範的編寫 [Griesemer, 2009], 並由 Ken Thompson 完成最早期的實現 [Thompson, 2009],兩人合作完成這一語言特性。
defer 的語義表明,它會在函數返回、產生恐慌或者 runtime.Goexit 時被調用。直覺上看,defer 應該由編譯器直接將需要的函數調用插入到該調用的地方,似乎是一個編譯期特性,不應該存在運行時性能問題,非常類似於 C++ 的 RAII 範式(當離開資源的作用域時,自動執行析構函數)。但實際情況是,由於 defer 並沒有與其依賴資源掛鈎,也允許在條件、循環語句中出現,從而不再是一個作用域相關的概念,這就是使得 defer 的語義變得相對複雜。在一些複雜情況下,無法在編譯期決定存在多少個 defer 調用。
例如,在一個執行次數不確定的 for 循環中,defer 的執行次數是隨機的:
1func randomDefers() {
2 rand.Seed(time.Now().UnixNano())
3 for rand.Intn(100) > 42 {
4 defer func() {
5 println("changkun.de/golang")
6 }()
7 }
8}
因而 defer 並不是免費的午餐,在一個複雜的調用中,當無法直接確定需要的產生的延遲調用的數量時,延遲語句將導致運行性能的下降。本文我們來討論 defer 的實現本質及其對症下藥的相關性能優化手段。
1// src/cmd/compile/internal/gc/ssa.go
2func buildssa(fn *Node, worker int) *ssa.Func {
3 var s state
4 ...
5 s.stmtList(fn.Nbody)
6 ...
7}
8func (s *state) stmtList(l Nodes) {
9 for _, n := range l.Slice() { s.stmt(n) }
10}
1// src/cmd/compile/internal/gc/ssa.go
2func (s *state) stmt(n *Node) {
3 ...
4 switch n.Op {
5 case ODEFER:
6 // 開放編碼式 defer
7 if s.hasOpenDefers {
8 s.openDeferRecord(n.Left)
9 } else {
10 // 堆上分配的 defer
11 d := callDefer
12 if n.Esc == EscNever {
13 // 棧上分配的 defer
14 d = callDeferStack
15 }
16 s.call(n.Left, d)
17 }
18 case ...
19 }
20 ...
21}
1// src/cmd/compile/internal/gc/ssa.go
2func (s *state) call(n *Node, k callKind) *ssa.Value {
3 ...
4 var call *ssa.Value
5 if k == callDeferStack {
6 ...
7 } else {
8 // 在堆上創建 defer
9 argStart := Ctxt.FixedFrameSize()
10 // Defer 參數
11 if k != callNormal {
12 // 記錄 deferproc 的參數
13 argsize := s.constInt32(types.Types[TUINT32], int32(stksize))
14 addr := s.constOffPtrSP(s.f.Config.Types.UInt32Ptr, argStart)
15 s.store(types.Types[TUINT32], addr, argsize) // 保存參數大小 siz
16 addr = s.constOffPtrSP(s.f.Config.Types.UintptrPtr, argStart+int64(Widthptr))
17 s.store(types.Types[TUINTPTR], addr, closure) // 保存函數地址 fn
18 stksize += 2 * int64(Widthptr)
19 argStart += 2 * int64(Widthptr)
20 }
21 ...
22
23 // 創建 deferproc 調用
24 switch {
25 case k == callDefer:
26 call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, deferproc, s.mem())
27 ...
28 }
29 ...
30 }
31 ...
32
33 // 結束 defer 塊
34 if k == callDefer || k == callDeferStack {
35 s.exit()
36 ...
37 }
38 ...
39}
40func (s *state) exit() *ssa.Block {
41 if s.hasdefer {
42 if s.hasOpenDefers {
43 ...
44 } else {
45 // 調用 deferreturn
46 s.rtcall(Deferreturn, true, nil)
47 }
48 }
49 ...
50}
1package main
2
3func foo() {
4 return
5}
6
7func main() {
8 defer foo()
9 return
10}
1TEXT main.foo(SB) /Users/changkun/Desktop/defer/ssa/main.go
2 return
3 0x104ea20 c3 RET
4
5TEXT main.main(SB) /Users/changkun/Desktop/defer/ssa/main.go
6func main() {
7 ...
8 // 將 defer foo() { ... }() 轉化為一個 deferproc 調用
9 // 在調用 deferproc 前完成參數的準備工作,這個例子中沒有參數
10 0x104ea4d c7042400000000 MOVL $0x0, 0(SP)
11 0x104ea54 488d0585290200 LEAQ go.func.*+60(SB), AX
12 0x104ea5b 4889442408 MOVQ AX, 0x8(SP)
13 0x104ea60 e8bb31fdff CALL runtime.deferproc(SB)
14 ...
15 // 函數返回指令 RET 前插入的 deferreturn 語句
16 0x104ea7b 90 NOPL
17 0x104ea7c e82f3afdff CALL runtime.deferreturn(SB)
18 0x104ea81 488b6c2410 MOVQ 0x10(SP), BP
19 0x104ea86 4883c418 ADDQ $0x18, SP
20 0x104ea8a c3 RET
21 // 函數的尾聲
22 0x104ea8b e8d084ffff CALL runtime.morestack_noctxt(SB)
23 0x104ea90 eb9e JMP main.main(SB)
1// src/runtime/panic.go
2type _defer struct {
3 siz int32
4 heap bool
5 sp uintptr
6 pc uintptr
7 fn *funcval
8 link *_defer
9 ...
10}
11// src/runtime/runtime2.go
12type g struct {
13 ...
14 _defer *_defer
15 ...
16}
1//go:nosplit
2func deferproc(siz int32, fn *funcval) {
3 ...
4 sp := getcallersp()
5 argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
6 callerpc := getcallerpc()
7
8 d := newdefer(siz)
9 d.fn = fn
10 d.pc = callerpc
11 d.sp = sp
12
13 // 將參數保存到 _defer 記錄中
14 switch siz {
15 case 0: // 什麼也不做
16 case sys.PtrSize:
17 *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
18 default:
19 memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
20 }
21
22 return0()
23}
1// src/runtime/runtime2.go
2type p struct {
3 ...
4 // 不同大小的本地 defer 池
5 deferpool [5][]*_defer
6 deferpoolbuf [5][32]*_defer
7 ...
8}
9type schedt struct {
10 ...
11 // 不同大小的全局 defer 池
12 deferlock mutex
13 deferpool [5]*_defer
14 ...
15}
1// src/runtime/panic.go
2
3//go:nosplit
4func newdefer(siz int32) *_defer {
5 var d *_defer
6 sc := deferclass(uintptr(siz))
7 gp := getg()
8 // 檢查 defer 參數的大小是否從 p 的 deferpool 直接分配
9 if sc < uintptr(len(p{}.deferpool)) {
10 pp := gp.m.p.ptr()
11
12 // 如果 p 本地無法分配,則從全局池中獲取一半 defer,來填充 P 的本地資源池
13 if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil {
14 // 出於性能考慮,如果發生棧的增長,則會調用 morestack,
15 // 進一步降低 defer 的性能。因此切換到系統棧上執行,進而不會發生棧的增長。
16 systemstack(func() {
17 lock(&sched.deferlock)
18 for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {
19 d := sched.deferpool[sc]
20 sched.deferpool[sc] = d.link
21 d.link = nil
22 pp.deferpool[sc] = append(pp.deferpool[sc], d)
23 }
24 unlock(&sched.deferlock)
25 })
26 }
27
28 // 從 P 本地進行分配
29 if n := len(pp.deferpool[sc]); n > 0 {
30 d = pp.deferpool[sc][n-1]
31 pp.deferpool[sc][n-1] = nil
32 pp.deferpool[sc] = pp.deferpool[sc][:n-1]
33 }
34 }
35 // 沒有可用的緩存,直接從堆上分配新的 defer 和 args
36 if d == nil {
37 systemstack(func() {
38 total := roundupsize(totaldefersize(uintptr(siz)))
39 d = (*_defer)(mallocgc(total, deferType, true))
40 })
41 }
42 // 將 _defer 實例添加到 Goroutine 的 _defer 鍊表上。
43 d.siz = siz
44 d.heap = true
45 d.link = gp._defer
46 gp._defer = d
47 return d
48}
1// src/runtime/panic.go
2
3//go:nosplit
4func deferreturn(arg0 uintptr) {
5 gp := getg()
6 d := gp._defer
7 if d == nil {
8 return
9 }
10 // 確定 defer 的調用方是不是當前 deferreturn 的調用方
11 sp := getcallersp()
12 if d.sp != sp {
13 return
14 }
15 ...
16
17 // 將參數複製出 _defer 記錄外
18 switch d.siz {
19 case 0: // 什麼也不做
20 case sys.PtrSize:
21 *(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
22 default:
23 memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
24 }
25 // 獲得被延遲的調用 fn 的入口地址,並隨後立即將 _defer 釋放掉
26 fn := d.fn
27 d.fn = nil
28 gp._defer = d.link
29 freedefer(d)
30
31 // 調用,並跳轉到下一個 defer
32 jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
33}
1// src/runtime/asm_amd64.s
2
3// func jmpdefer(fv *funcval, argp uintptr)
4TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16
5 MOVQ fv+0(FP), DX // DX = fn
6 MOVQ argp+8(FP), BX // 調用方 SP
7 LEAQ -8(BX), SP // CALL 後的調用方 SP
8 MOVQ -8(SP), BP // 恢復 BP,好像 deferreturn 返回
9 SUBQ $5, (SP) // 再次返回到 CALL
10 MOVQ 0(DX), BX // BX = DX
11 JMP BX // 最後才運行被 defer 的函數
1// src/runtime/panic.go
2
3//go:nosplit
4func freedefer(d *_defer) {
5 ...
6 sc := deferclass(uintptr(d.siz))
7 if sc >= uintptr(len(p{}.deferpool)) {
8 return
9 }
10 pp := getg().m.p.ptr()
11 // 如果 P 本地池已滿,則將一半資源放入全局池,同樣也是出於性能考慮
12 // 操作會切換到系統棧上執行。
13 if len(pp.deferpool[sc]) == cap(pp.deferpool[sc]) {
14 systemstack(func() {
15 var first, last *_defer
16 for len(pp.deferpool[sc]) > cap(pp.deferpool[sc])/2 {
17 n := len(pp.deferpool[sc])
18 d := pp.deferpool[sc][n-1]
19 pp.deferpool[sc][n-1] = nil
20 pp.deferpool[sc] = pp.deferpool[sc][:n-1]
21 if first == nil {
22 first = d
23 } else {
24 last.link = d
25 }
26 last = d
27 }
28 lock(&sched.deferlock)
29 last.link = sched.deferpool[sc]
30 sched.deferpool[sc] = first
31 unlock(&sched.deferlock)
32 })
33 }
34
35 // 恢復 _defer 的零值,即 *d = _defer{}
36 d.siz = 0
37 ...
38 d.sp = 0
39 d.pc = 0
40 d.framepc = 0
41 ...
42 d.link = nil
43
44 // 放入 P 本地資源池
45 pp.deferpool[sc] = append(pp.deferpool[sc], d)
46}
1// src/cmd/compile/internal/gc/ssa.go
2func (s *state) call(n *Node, k callKind) *ssa.Value {
3 ...
4 var call *ssa.Value
5 if k == callDeferStack {
6 // 直接在棧上創建 defer 記錄
7 t := deferstruct(stksize) // 從編譯器角度構造 _defer 結構
8 d := tempAt(n.Pos, s.curfn, t)
9
10 s.vars[&memVar] = s.newValue1A(ssa.OpVarDef, types.TypeMem, d, s.mem())
11 addr := s.addr(d, false)
12
13 // 在棧上預留記錄 _defer 的各個欄位的空間
14 s.store(types.Types[TUINT32],
15 s.newValue1I(ssa.OpOffPtr, types.Types[TUINT32].PtrTo(), t.FieldOff(0), addr),
16 s.constInt32(types.Types[TUINT32], int32(stksize)))
17 s.store(closure.Type,
18 s.newValue1I(ssa.OpOffPtr, closure.Type.PtrTo(), t.FieldOff(6), addr),
19 closure)
20
21 // 記錄參與 defer 調用的函數參數
22 ft := fn.Type
23 off := t.FieldOff(12)
24 args := n.Rlist.Slice()
25
26 // 調用 deferprocStack,以 _defer 記錄的指針作為參數傳遞
27 arg0 := s.constOffPtrSP(types.Types[TUINTPTR], Ctxt.FixedFrameSize())
28 s.store(types.Types[TUINTPTR], arg0, addr)
29 call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, deferprocStack, s.mem())
30 ...
31 } else { ... }
32
33 // 函數尾聲與堆上分配的棧一樣,調用 deferreturn
34 if k == callDefer || k == callDeferStack {
35 ...
36 s.exit()
37 }
38 ...
39 }
1// src/runtime/panic.go
2
3//go:nosplit
4func deferprocStack(d *_defer) {
5 gp := getg()
6 // 注意,siz 和 fn 已經在編譯階段完成設置,這裡只初始化了其他欄位
7 d.started = false
8 d.heap = false // 可見此時 defer 被標記為不在堆上分配
9 d.openDefer = false
10 d.sp = getcallersp()
11 d.pc = getcallerpc()
12 ...
13 // 儘管在棧上進行分配,仍然需要將多個 _defer 記錄通過鍊表進行串聯,
14 // 以便在 deferreturn 中找到被延遲的函數的入口地址:
15 // d.link = gp._defer
16 // gp._defer = d
17 *(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer))
18 *(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d))
19 return0()
20 }
1// src/runtime/panic.go
2func freedefer(d *_defer) {
3 if !d.heap { return }
4 ...
5}
1func call() { func() {}() }
2func callDefer() { defer func() {}() }
3func BenchmarkDefer(b *testing.B) {
4 for i := 0; i < b.N; i++ {
5 call() // 第二次運行時替換為 callDefer
6 }
7}
1ame old time/op new time/op delta
2Defer-12 1.24ns ± 1% 2.23ns ± 1% +80.06% (p=0.000 n=10+9)
1$ go build -gcflags "-l" -ldflags=-compressdwarf=false -o main.out main.go
2$ go tool objdump -S main.out > main.s
1var mu sync.Mutex
2func callDefer() {
3 mu.Lock()
4 defer mu.Unlock()
5}
1TEXT main.callDefer(SB) /Users/changkun/Desktop/defer/main.go
2func callDefer() {
3 ...
4 mu.Lock()
5 0x105794a 488d05071f0a00 LEAQ main.mu(SB), AX
6 0x1057951 48890424 MOVQ AX, 0(SP)
7 0x1057955 e8f6f8ffff CALL sync.(*Mutex).Lock(SB)
8 defer mu.Unlock()
9 0x105795a 488d057f110200 LEAQ go.func.*+1064(SB), AX
10 0x1057961 4889442418 MOVQ AX, 0x18(SP)
11 0x1057966 488d05eb1e0a00 LEAQ main.mu(SB), AX
12 0x105796d 4889442410 MOVQ AX, 0x10(SP)
13}
14 0x1057972 c644240f00 MOVB $0x0, 0xf(SP)
15 0x1057977 488b442410 MOVQ 0x10(SP), AX
16 0x105797c 48890424 MOVQ AX, 0(SP)
17 0x1057980 e8ebfbffff CALL sync.(*Mutex).Unlock(SB)
18 0x1057985 488b6c2420 MOVQ 0x20(SP), BP
19 0x105798a 4883c428 ADDQ $0x28, SP
20 0x105798e c3 RET
21 ...
1// src/cmd/compile/internal/gc/ssa.go
2const maxOpenDefers = 8
3func walkstmt(n *Node) *Node {
4 ...
5 switch n.Op {
6 case ODEFER:
7 Curfn.Func.SetHasDefer(true)
8 Curfn.Func.numDefers++
9 // 超過 8 個 defer 時,禁用對 defer 進行開放編碼
10 if Curfn.Func.numDefers > maxOpenDefers {
11 Curfn.Func.SetOpenCodedDeferDisallowed(true)
12 }
13 // 存在循環語句中的 defer,禁用對 defer 進行開放編碼。
14 // 是否有 defer 發生在循環語句內,會在 SSA 之前的逃逸分析中進行判斷,
15 // 逃逸分析會檢查是否存在循環(loopDepth):
16 // if where.Op == ODEFER && e.loopDepth == 1 {
17 // where.Esc = EscNever
18 // ...
19 // }
20 if n.Esc != EscNever {
21 Curfn.Func.SetOpenCodedDeferDisallowed(true)
22 }
23 case ...
24 }
25 ...
26}
27
28func buildssa(fn *Node, worker int) *ssa.Func {
29 ...
30 var s state
31 ...
32 s.hasdefer = fn.Func.HasDefer()
33 ...
34 // 可以對 defer 進行開放編碼的條件
35 s.hasOpenDefers = Debug['N'] == 0 && s.hasdefer && !s.curfn.Func.OpenCodedDeferDisallowed()
36 if s.hasOpenDefers &&
37 s.curfn.Func.numReturns*s.curfn.Func.numDefers > 15 {
38 s.hasOpenDefers = false
39 }
40 ...
41}
1if rand.Intn(100) < 42 {
2 defer fmt.Println("meaning-of-life")
3}
1defer f1(a1)
2if cond {
3 defer f2(a2)
4}
5...
1deferBits = 0 // 初始值 00000000
2deferBits |= 1 << 0 // 遇到第一個 defer,設置為 00000001
3_f1 = f1
4_a1 = a1
5if cond {
6 // 如果第二個 defer 被設置,則設置為 00000011,否則依然為 00000001
7 deferBits |= 1 << 1
8 _f2 = f2
9 _a2 = a2
10}
1exit:
2// 按順序倒序檢查延遲比特。如果第二個 defer 被設置,則
3// 00000011 & 00000010 == 00000010,即延遲比特不為零,應該調用 f2。
4// 如果第二個 defer 沒有被設置,則
5// 00000001 & 00000010 == 00000000,即延遲比特為零,不應該調用 f2。
6if deferBits & 1 << 1 != 0 { // 00000011 & 00000010 != 0
7 deferBits &^= 1<<1 // 00000001
8 _f2(_a2)
9}
10// 同理,由於 00000001 & 00000001 == 00000001,因此延遲比特不為零,應該調用 f1
11if deferBits && 1 << 0 != 0 {
12 deferBits &^= 1<<0
13 _f1(_a1)
14}
1// src/cmd/compile/internal/gc/ssa.go
2func buildssa(fn *Node, worker int) *ssa.Func {
3 ...
4 if s.hasOpenDefers {
5 // 創建 deferBits 臨時變量
6 deferBitsTemp := tempAt(src.NoXPos, s.curfn, types.Types[TUINT8])
7 s.deferBitsTemp = deferBitsTemp
8 // deferBits 被設計為 8 位二進位,因此可以被開放編碼的 defer 數量不能超過 8 個
9 // 此處還將起始 deferBits 設置為零
10 startDeferBits := s.entryNewValue0(ssa.OpConst8, types.Types[TUINT8])
11 s.vars[&deferBitsVar] = startDeferBits
12 s.deferBitsAddr = s.addr(deferBitsTemp, false)
13 s.store(types.Types[TUINT8], s.deferBitsAddr, startDeferBits)
14 ...
15 }
16 ...
17 s.stmtList(fn.Nbody) // 調用 s.stmt
18 ...
19}
1// src/cmd/compile/internal/gc/ssa.go
2func (s *state) stmt(n *Node) {
3 ...
4 switch n.Op {
5 case ODEFER:
6 // 開放編碼式 defer
7 if s.hasOpenDefers {
8 s.openDeferRecord(n.Left)
9 } else { ... }
10 case ...
11 }
12 ...
13}
14
15// 存儲一個 defer 調用的相關信息,例如所在的語法樹結點、被延遲的調用、參數等等
16type openDeferInfo struct {
17 n *Node
18 closure *ssa.Value
19 closureNode *Node
20 ...
21 argVals []*ssa.Value
22 argNodes []*Node
23}
24func (s *state) openDeferRecord(n *Node) {
25 ...
26 var args []*ssa.Value
27 var argNodes []*Node
28
29 // 記錄與 defer 相關的入口地址與參數信息
30 opendefer := &openDeferInfo{n: n}
31 fn := n.Left
32 // 記錄函數入口地址
33 if n.Op == OCALLFUNC {
34 closureVal := s.expr(fn)
35 closure := s.openDeferSave(nil, fn.Type, closureVal)
36 opendefer.closureNode = closure.Aux.(*Node)
37 if !(fn.Op == ONAME && fn.Class() == PFUNC) {
38 opendefer.closure = closure
39 }
40 } else {
41 ...
42 }
43 // 記錄需要立即求值的的參數
44 for _, argn := range n.Rlist.Slice() {
45 var v *ssa.Value
46 if canSSAType(argn.Type) {
47 v = s.openDeferSave(nil, argn.Type, s.expr(argn))
48 } else {
49 v = s.openDeferSave(argn, argn.Type, nil)
50 }
51 args = append(args, v)
52 argNodes = append(argNodes, v.Aux.(*Node))
53 }
54 opendefer.argVals = args
55 opendefer.argNodes = argNodes
56
57 // 每多出現一個 defer,len(defers) 會增加,進而
58 // 延遲比特 deferBits |= 1<<len(defers) 被設置在不同的位上
59 index := len(s.openDefers)
60 s.openDefers = append(s.openDefers, opendefer)
61 bitvalue := s.constInt8(types.Types[TUINT8], 1<<uint(index))
62 newDeferBits := s.newValue2(ssa.OpOr8, types.Types[TUINT8], s.variable(&deferBitsVar, types.Types[TUINT8]), bitvalue)
63 s.vars[&deferBitsVar] = newDeferBits
64 s.store(types.Types[TUINT8], s.deferBitsAddr, newDeferBits)
65 }
1// src/cmd/compile/internal/gc/ssa.go
2func (s *state) exit() *ssa.Block {
3 if s.hasdefer {
4 if s.hasOpenDefers {
5 ...
6 s.openDeferExit()
7 } else {
8 ...
9 }
10 }
11 ...
12}
13
14func (s *state) openDeferExit() {
15 deferExit := s.f.NewBlock(ssa.BlockPlain)
16 s.endBlock().AddEdgeTo(deferExit)
17 s.startBlock(deferExit)
18 s.lastDeferExit = deferExit
19 s.lastDeferCount = len(s.openDefers)
20 zeroval := s.constInt8(types.Types[TUINT8], 0)
21 // 倒序檢查 defer
22 for i := len(s.openDefers) - 1; i >= 0; i-- {
23 r := s.openDefers[i]
24 bCond := s.f.NewBlock(ssa.BlockPlain)
25 bEnd := s.f.NewBlock(ssa.BlockPlain)
26
27 // 檢查 deferBits
28 deferBits := s.variable(&deferBitsVar, types.Types[TUINT8])
29 // 創建 if deferBits & 1 << len(defer) != 0 { ... }
30 bitval := s.constInt8(types.Types[TUINT8], 1<<uint(i))
31 andval := s.newValue2(ssa.OpAnd8, types.Types[TUINT8], deferBits, bitval)
32 eqVal := s.newValue2(ssa.OpEq8, types.Types[TBOOL], andval, zeroval)
33 b := s.endBlock()
34 b.Kind = ssa.BlockIf
35 b.SetControl(eqVal)
36 b.AddEdgeTo(bEnd)
37 b.AddEdgeTo(bCond)
38 bCond.AddEdgeTo(bEnd)
39 s.startBlock(bCond)
40
41 // 如果創建的條件分支被觸發,則清空當前的延遲比特: deferBits &^= 1 << len(defers)
42 nbitval := s.newValue1(ssa.OpCom8, types.Types[TUINT8], bitval)
43 maskedval := s.newValue2(ssa.OpAnd8, types.Types[TUINT8], deferBits, nbitval)
44 s.store(types.Types[TUINT8], s.deferBitsAddr, maskedval)
45 s.vars[&deferBitsVar] = maskedval
46
47 // 處理被延遲的函數調用,取出保存的入口地址、參數信息
48 argStart := Ctxt.FixedFrameSize()
49 fn := r.n.Left
50 stksize := fn.Type.ArgWidth()
51 ...
52 for j, argAddrVal := range r.argVals {
53 f := getParam(r.n, j)
54 pt := types.NewPtr(f.Type)
55 addr := s.constOffPtrSP(pt, argStart+f.Offset)
56 if !canSSAType(f.Type) {
57 s.move(f.Type, addr, argAddrVal)
58 } else {
59 argVal := s.load(f.Type, argAddrVal)
60 s.storeType(f.Type, addr, argVal, 0, false)
61 }
62 }
63 // 調用
64 var call *ssa.Value
65 ...
66 call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, fn.Sym.Linksym(), s.mem())
67 call.AuxInt = stksize
68 s.vars[&memVar] = call
69 ...
70 s.endBlock()
71 s.startBlock(bEnd)
72 }
73}
[Scales, 2019] Dan Scales, Keith Randall, and Austin Clements. Proposal: Low-cost defers through inline code, and extra funcdata to manage the panic case. Sep, 2019. https://go.googlesource.com/proposal/+/refs/heads/master/design/34481-opencoded-defers.md