Page 16 - 《软件学报》2024年第6期
P. 16
2592 软件学报 2024 年第 35 卷第 6 期
令, 因此指令列表中只包含涉及存储指针的 store 指令和可能引起控制流变化的 cmp 和 br 指令. 其中指令 store
addr dst , addr 代表将 addr 存入 addr ds 所指向的内存区域.
t
(World) W ::= (GS,GH,GG)
(Goroutine set) GS ::= (G 1 ,G 2 ,...,G n )
(Go heap) GH ::= {addr}
(Go global) GG ::= {addr}
(Goroutine) G ::= (C,S) (1)
(Code) C ::= [instr]
(Goroutine stack) S ::= [addr lo ,addr hi )
(Memory address) addr ::= n (unsigned nums)
(Instruction) instr ::= store addr dst , addr
| br addr | cmp
def
code( instr ) = C instr ∈ C
def
codeG(C 0 ) = G G·C == C 0 (2)
def
codeS (C) = S 0 S 0 == codeG(C).S
S = codeS (code(s l ))
2.2 违反 Go 逃逸不变式的判定规则
为便于描述, 首先引入一些辅助函数, 定义见公式 (2). 其中 code(instr) 用于获取指令 instr 所在的指令序列 C ,
C 的 C 的
codeG( C ) 用于获取执行指令序列 Goroutine, codeS( C ) 用于获取执行指令序列 Goroutine 栈.
为了后面讨论违反逃逸不变式的判定规则以及栈对象的生命期, 接下来定义两个概念.
定义 1 (不合法的 store 指令). 若一条 store 指令 s l : store addr dst , addr 的内存访问违反了 Go 逃逸不变式, 则将
其记为 illegal(s l ) .
定义 2 (栈对象所在的栈帧深度). 若 addr 为某栈对象地址, 则 fd(addr) 代表 addr 指向的栈对象所在的栈帧深
度. 处于栈顶的函数栈帧深度为 1, 其余函数的栈帧深度沿着栈上的调用链依次加 1. 越靠近栈顶的栈帧, 其栈帧深
度越小.
根据第 1.1 节所述的两条 Go 逃逸不变式, 可得违反 Go 逃逸不变式的情况为:
• 违反逃逸不变式 1: 栈对象指针被堆对象获取.
• 违反逃逸不变式 2: 栈对象指针被生命期更长的对象获取.
针对违反逃逸不变式 1 的情况, 其表现为堆对象指向了栈对象, 栈对象地址在某处被存入堆对象中. 因此, 针
对指令 s l : store addr dst , addr , 可用公式 (3) 来判断是否违反了逃逸不变式 1, 即 addr dst 是堆地址且 addr 是当前执行
指令 s l 所在的 Goroutine 栈中的地址时, 指令 s l 会因引起逃逸不变式的违例而视为不合法.
S = codeS (code(s l )) addr ∈ S∧addr dst ∈ GH
(3)
illegal(s l )
针对违反逃逸不变式 2 的情况, 比当前栈对象生命期更长的对象可能有多种情况, 包括堆对象、全局对象、
其他 Goroutine 的栈对象, 以及当前 Goroutine 的栈对象. 前 3 种情况都认为是当前 Goroutine 栈之外的对象. 下面
分别进行讨论.
• 将栈对象地址写入当前 Goroutine 栈之外的违例情况. 除了公式 (3) 讨论过的堆对象外, 所有全局对象都可
认为比当前栈对象生命期更长. 此时表现为全局对象指向了栈对象, 栈对象地址在某处被存入全局变量中. 因此,
针对指令 s l : store addr dst , addr, 可用公式 (4) 来判断是否将栈地址存入全局变量中.
addr ∈ S∧addr dst ∈ GG
(4)
illegal(s l )
由于不同 Goroutine 的执行受 Go 运行调度的影响可能相互交叠, 分处在不同 Goroutine 栈中的栈对象生命期
可能存在交叠, 也可能存在不确定的一先一后, 因此将当前 Goroutine 中的一个栈对象的地址存到另一个 Goroutine
栈中也是不安全的. 为此, 针对指令 s l : store addr dst , addr, 可用公式 (5) 来判断是否将当前 Goroutine 中的栈地址存
入了另一个 Goroutine 栈中.