Go语言:Go 语言之 defer 的前世今生( 二 )


Go语言:Go 语言之 defer 的前世今生
本文插图
在堆上分配的 defer 我们先来讨论最简单的在堆上分配的 defer 这种形式 。 在堆上分配的原因是 defer 语句出现在了循环语句里 , 或者无法执行更高阶的编译器优化导致的 。
如果一个与 defer 出现在循环语句中 , 则可执行的次数可能无法在编译期决定;如果一个调用中 defer 由于数量过多等原因 , 不能被编译器进行开放编码 , 则也会在堆上分配 defer 。
总之 , 由于这种不确定性的存在 , 在堆上分配的 defer 需要最多的运行时支持 , 因而产生的运行时开销也最大 。
编译阶段为了使延迟语句的功能满足语言规范 , 该语句在编译的 SSA 阶段会被翻译为两个主体 , 其中第一个主体是被延迟的函数本身 , 另一个主体则是函数结束时需要执行所记录 defer 的代码块 。
state.call 调用会生成用于记录延迟调用参数的指令 , 并创建一个 deferproc 的调用指令;而后 state.exit 调用在函数返回前插入 deferreturn 调用的指令 。
1// src/cmd/compile/internal/gc/ssa.go2func (s *state) call(n *Node, k callKind) *ssa.Value {3 ...4 var call *ssa.Value5 if k == callDeferStack {6 ...7 } else {8 // 在堆上创建 defer9 argStart := Ctxt.FixedFrameSize10 // 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) // 保存参数大小 siz16 addr = s.constOffPtrSP(s.f.Config.Types.UintptrPtr, argStart+int64(Widthptr))17 s.store(types.Types[TUINTPTR], addr, closure) // 保存函数地址 fn18 stksize += 2 * int64(Widthptr)19 argStart += 2 * int64(Widthptr)20 }21 ...2223 // 创建 deferproc 调用24 switch {25 case k == callDefer:26 call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, deferproc, s.mem)27 ...28 }29 ...30 }31 ...3233 // 结束 defer 块34 if k == callDefer || k == callDeferStack {35 s.exit36 ...37 }38 ...39}40func (s *state) exit *ssa.Block {41 if s.hasdefer {42 if s.hasOpenDefers {43 ...44 } else {45 // 调用 deferreturn46 s.rtcall(Deferreturn, true, nil)47 }48 }49 ...50} 例如 , 对于一个纯粹的 defer 调用而言:
1package main23func foo {4 return5}67func main {8 defer foo9 return10} 如果我们将其强制编译为在堆上分配的形式 , 可以观察到如下的汇编代码 。 其中 defer foo被转化为了 deferproc 调用 , 并在函数返回前 , 调用了 deferreturn:
1TEXT main.foo(SB) /Users/changkun/Desktop/defer/ssa/main.go2 return3 0x104ea20 c3 RET45TEXT main.main(SB) /Users/changkun/Desktop/defer/ssa/main.go6func main {7 ...8 // 将 defer foo { ... } 转化为一个 deferproc 调用9 // 在调用 deferproc 前完成参数的准备工作 , 这个例子中没有参数10 0x104ea4d c7042400000000 MOVL $0x0, 0(SP)11 0x104ea54 488d0585290200 LEAQ go.func.*+60(SB), AX12 0x104ea5b 4889442408 MOVQ AX, 0x8(SP)13 0x104ea60 e8bb31fdff CALL runtime.deferproc(SB)14 ...15 // 函数返回指令 RET 前插入的 deferreturn 语句16 0x104ea7b 90 NOPL17 0x104ea7c e82f3afdff CALL runtime.deferreturn(SB)18 0x104ea81 488b6c2410 MOVQ 0x10(SP), BP19 0x104ea86 4883c418 ADDQ $0x18, SP20 0x104ea8a c3 RET21 // 函数的尾声22 0x104ea8b e8d084ffff CALL runtime.morestack_noctxt(SB)23 0x104ea90 eb9e JMP main.main(SB)