在`cmd/compile/internal/gc`模块中,`esc.go`文件存在重复的sink路径用于重新分配,

ffdz8vbo  于 4个月前  发布在  Go
关注(0)|答案(4)|浏览(42)

给定以下代码:

/* 1*/ package example
/* 2*/ 
/* 3*/ var sink interface{}
/* 4*/ 
/* 5*/ func fn() {
/* 6*/ 	var x *int
/* 7*/ 	x = new(int)
/* 8*/ 	sink = x // Sink 1 at the line 8; first new(int) flows here
/* 9*/ 	x = new(int)
/*10*/ 	sink = x // Sink 2 at the line 10; second new(int) flows here
/*11*/ }

(带有行号的代码已注解,问题描述后概述了最小示例。)
执行命令 $ go tool compile -m=2 example.go
提示上的输出是(重要的位用粗体表示):

example.go:5:6: can inline fn as: func() { var x *int; x = ; x = new(int); sink = x; x = new(int); sink = x }
example.go:8:7: x escapes to heap
example.go:8:7: 	from sink (assigned to top level variable) at example.go:8:7
example.go:7:9: new(int) escapes to heap
example.go:7:9: 	from x (assigned) at example.go:7:4
**example.go:7:9: 	from x (interface-converted) at example.go:8:7
example.go:7:9: 	from sink (assigned to top level variable) at example.go:8:7**
example.go:9:9: new(int) escapes to heap
example.go:9:9: 	from x (assigned) at example.go:9:4
**example.go:9:9: 	from x (interface-converted) at example.go:8:7
example.go:9:9: 	from sink (assigned to top level variable) at example.go:8:7**
example.go:10:7: x escapes to heap
example.go:10:7: 	from sink (assigned to top level variable) at example.go:10:7

预期输出不会报告第二个汇点位于相同的位置。
这是图形构建和打印的结果。
简化示例:

package example

var sink *int

func fn() {
	var x *int
	x = new(int)
	sink = x
	x = new(int)
	sink = x
}

输出:

example.go:5:6: can inline fn as: func() { var x *int; x = ; x = new(int); sink = x; x = new(int); sink = x }
example.go:7:9: new(int) escapes to heap
**example.go:7:9: 	from x (assigned) at example.go:7:4
example.go:7:9: 	from sink (assigned to top level variable) at example.go:8:7**
example.go:9:9: new(int) escapes to heap
**example.go:9:9: 	from x (assigned) at example.go:9:4
example.go:9:9: 	from sink (assigned to top level variable) at example.go:8:7**

预期输出:

example.go:5:6: can inline fn as: func() { var x *int; x = ; x = new(int); sink = x; x = new(int); sink = x }
example.go:7:9: new(int) escapes to heap
**example.go:7:9: 	from x (assigned) at example.go:7:4
example.go:7:9: 	from sink (assigned to top level variable) at example.go:8:7**
example.go:9:9: new(int) escapes to heap
**example.go:9:9: 	from x (assigned) at example.go:9:4
example.go:9:9: 	from sink (assigned to top level variable) at example.go:10:7**

对于这个例子,我们有类似这样的东西:

  • sink 伪节点有两个源边,都来自 x 节点。
  • x 节点有两个源边,它们来自两个不同的 new(int)
sink.Flowsrc == { {dst=sink, src=x, where=line8}, {dst=sink, src=x, where=line10} }
x.Flowsrc == { {dst=x, src=new(int), where=line7 }, {dst=x, src=new(int), where=line9} }

escwalkBody 中的遍历过程中,使用第一个 sink 目标作为父节点打印了两个 new(int) 路径,所以我们得到了两个相同的路径。由于 osrcesc 变量检查用于避免重复消息,因此没有打印第二个目的地。
如果删除 osrcesc ,两个路径都会打印两次(因此,总共有4条消息而不是2条,但其中2条是正确的)。目前, osrcesc 导致了2条消息,其中一条是错误的。
仅仅检查目标节点是否位于实际起始流点之前是不够的,因为存在递归函数:

func f8(x int, y *int) *int {
	if x <= 0 {
		return y
	}
	x--
	return f8(*y, &x)
}

在这里, return y 是目标端点,它出现在跟踪的 &x 之前。
我对此没有好的想法来解决这个问题。
希望上面的见解能帮助某人解决这个问题。

zpjtge22

zpjtge221#

不知道该抄送给谁。也许可以@josharian?(这只是为了验证问题;也许这是esc.go解释器已知并接受的限制。)

hgqdbh6s

hgqdbh6s2#

@dr2chase是主要的逃逸分析Maven(据我所知);他编写了逃逸分析解释器。另一个想到的人是@cherrymui。

wlsrxk51

wlsrxk513#

在我看来,一个更深层次的问题是数据流分析是在节点级别进行的,其中所有 x 的出现都用相同的节点表示。例如,如果我删除第二个 sink=x 赋值,即

package example

var sink *int

func fn() {
var x *int
x = new(int)
sink = x
x = new(int)
_ = x
}

第二个 new(int) 不一定会逃逸。在当前的分析中,它确实会逃逸,因为 new(int) 流向 xx,在某个时刻流向堆。在原始程序中,实际上并不关心 x 在何时/何地流向堆;它只是选择一个。
为此,我认为分析需要在一个跟踪数据流顺序的层级上工作(可能是某种形式的 SSA)。自然而然地,更准确的位置报告就会跟上。

shyt4zoc

shyt4zoc4#

第二个新的(int)不一定需要转义。根据当前的分析,它确实需要转义,因为新的(int)流向x,而x在某个时刻流向堆。在原始程序中,实际上并不关心x何时/何地流向堆;它只是选择一个。

我实际上尝试过制作完全相同的内容,以便让逃逸分析识别出第二个 new(int) 是不必要的,但遇到了意外的调试输出,这让我感到疑惑。

相关问题