Skip to content

Commit 32a84c9

Browse files
committed
cmd/compile: fix live variable computation for deferreturn
Taking the live variable set from the last return point is problematic. See golang#40629 for details, but there may not be a return point, or it may be before the final defer. Additionally, keeping track of the last call as a *Value doesn't quite work. If it is dead-code eliminated, the storage for the Value is reused for some other random instruction. Its live variable information, if it is available at all, is wrong. Instead, just mark all the open-defer argument slots as live throughout the function. (They are already zero-initialized.) Fixes golang#40629 Change-Id: Ie456c7db3082d0de57eaa5234a0f32525a1cce13 Reviewed-on: https://go-review.googlesource.com/c/go/+/247522 Run-TryBot: Keith Randall <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Dan Scales <[email protected]>
1 parent 02a7b4b commit 32a84c9

File tree

4 files changed

+99
-118
lines changed

4 files changed

+99
-118
lines changed

src/cmd/compile/internal/gc/plive.go

Lines changed: 27 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -140,24 +140,14 @@ type Liveness struct {
140140
regMaps []liveRegMask
141141

142142
cache progeffectscache
143-
144-
// These are only populated if open-coded defers are being used.
145-
// List of vars/stack slots storing defer args
146-
openDeferVars []openDeferVarInfo
147-
// Map from defer arg OpVarDef to the block where the OpVarDef occurs.
148-
openDeferVardefToBlockMap map[*Node]*ssa.Block
149-
// Map of blocks that cannot reach a return or exit (panic)
150-
nonReturnBlocks map[*ssa.Block]bool
151-
}
152-
153-
type openDeferVarInfo struct {
154-
n *Node // Var/stack slot storing a defer arg
155-
varsIndex int // Index of variable in lv.vars
156143
}
157144

158145
// LivenessMap maps from *ssa.Value to LivenessIndex.
159146
type LivenessMap struct {
160147
vals map[ssa.ID]LivenessIndex
148+
// The set of live, pointer-containing variables at the deferreturn
149+
// call (only set when open-coded defers are used).
150+
deferreturn LivenessIndex
161151
}
162152

163153
func (m *LivenessMap) reset() {
@@ -168,6 +158,7 @@ func (m *LivenessMap) reset() {
168158
delete(m.vals, k)
169159
}
170160
}
161+
m.deferreturn = LivenessInvalid
171162
}
172163

173164
func (m *LivenessMap) set(v *ssa.Value, i LivenessIndex) {
@@ -542,7 +533,7 @@ func newliveness(fn *Node, f *ssa.Func, vars []*Node, idx map[*Node]int32, stkpt
542533
if cap(lc.be) >= f.NumBlocks() {
543534
lv.be = lc.be[:f.NumBlocks()]
544535
}
545-
lv.livenessMap = LivenessMap{lc.livenessMap.vals}
536+
lv.livenessMap = LivenessMap{vals: lc.livenessMap.vals, deferreturn: LivenessInvalid}
546537
lc.livenessMap.vals = nil
547538
}
548539
if lv.be == nil {
@@ -893,58 +884,12 @@ func (lv *Liveness) hasStackMap(v *ssa.Value) bool {
893884
func (lv *Liveness) prologue() {
894885
lv.initcache()
895886

896-
if lv.fn.Func.HasDefer() && !lv.fn.Func.OpenCodedDeferDisallowed() {
897-
lv.openDeferVardefToBlockMap = make(map[*Node]*ssa.Block)
898-
for i, n := range lv.vars {
899-
if n.Name.OpenDeferSlot() {
900-
lv.openDeferVars = append(lv.openDeferVars, openDeferVarInfo{n: n, varsIndex: i})
901-
}
902-
}
903-
904-
// Find any blocks that cannot reach a return or a BlockExit
905-
// (panic) -- these must be because of an infinite loop.
906-
reachesRet := make(map[ssa.ID]bool)
907-
blockList := make([]*ssa.Block, 0, 256)
908-
909-
for _, b := range lv.f.Blocks {
910-
if b.Kind == ssa.BlockRet || b.Kind == ssa.BlockRetJmp || b.Kind == ssa.BlockExit {
911-
blockList = append(blockList, b)
912-
}
913-
}
914-
915-
for len(blockList) > 0 {
916-
b := blockList[0]
917-
blockList = blockList[1:]
918-
if reachesRet[b.ID] {
919-
continue
920-
}
921-
reachesRet[b.ID] = true
922-
for _, e := range b.Preds {
923-
blockList = append(blockList, e.Block())
924-
}
925-
}
926-
927-
lv.nonReturnBlocks = make(map[*ssa.Block]bool)
928-
for _, b := range lv.f.Blocks {
929-
if !reachesRet[b.ID] {
930-
lv.nonReturnBlocks[b] = true
931-
//fmt.Println("No reach ret", lv.f.Name, b.ID, b.Kind)
932-
}
933-
}
934-
}
935-
936887
for _, b := range lv.f.Blocks {
937888
be := lv.blockEffects(b)
938889

939890
// Walk the block instructions backward and update the block
940891
// effects with the each prog effects.
941892
for j := len(b.Values) - 1; j >= 0; j-- {
942-
if b.Values[j].Op == ssa.OpVarDef {
943-
n := b.Values[j].Aux.(*Node)
944-
if n.Name.OpenDeferSlot() {
945-
lv.openDeferVardefToBlockMap[n] = b
946-
}
947-
}
948893
pos, e := lv.valueEffects(b.Values[j])
949894
regUevar, regKill := lv.regEffects(b.Values[j])
950895
if e&varkill != 0 {
@@ -961,20 +906,6 @@ func (lv *Liveness) prologue() {
961906
}
962907
}
963908

964-
// markDeferVarsLive marks each variable storing an open-coded defer arg as
965-
// specially live in block b if the variable definition dominates block b.
966-
func (lv *Liveness) markDeferVarsLive(b *ssa.Block, newliveout *varRegVec) {
967-
// Only force computation of dominators if we have a block where we need
968-
// to specially mark defer args live.
969-
sdom := lv.f.Sdom()
970-
for _, info := range lv.openDeferVars {
971-
defB := lv.openDeferVardefToBlockMap[info.n]
972-
if sdom.IsAncestorEq(defB, b) {
973-
newliveout.vars.Set(int32(info.varsIndex))
974-
}
975-
}
976-
}
977-
978909
// Solve the liveness dataflow equations.
979910
func (lv *Liveness) solve() {
980911
// These temporary bitvectors exist to avoid successive allocations and
@@ -1018,23 +949,6 @@ func (lv *Liveness) solve() {
1018949
}
1019950
}
1020951

1021-
if lv.fn.Func.HasDefer() && !lv.fn.Func.OpenCodedDeferDisallowed() &&
1022-
(b.Kind == ssa.BlockExit || lv.nonReturnBlocks[b]) {
1023-
// Open-coded defer args slots must be live
1024-
// everywhere in a function, since a panic can
1025-
// occur (almost) anywhere. Force all appropriate
1026-
// defer arg slots to be live in BlockExit (panic)
1027-
// blocks and in blocks that do not reach a return
1028-
// (because of infinite loop).
1029-
//
1030-
// We are assuming that the defer exit code at
1031-
// BlockReturn/BlockReturnJmp accesses all of the
1032-
// defer args (with pointers), and so keeps them
1033-
// live. This analysis may have to be adjusted if
1034-
// that changes (because of optimizations).
1035-
lv.markDeferVarsLive(b, &newliveout)
1036-
}
1037-
1038952
if !be.liveout.Eq(newliveout) {
1039953
change = true
1040954
be.liveout.Copy(newliveout)
@@ -1087,6 +1001,17 @@ func (lv *Liveness) epilogue() {
10871001
n.Name.SetNeedzero(true)
10881002
livedefer.Set(int32(i))
10891003
}
1004+
if n.Name.OpenDeferSlot() {
1005+
// Open-coded defer args slots must be live
1006+
// everywhere in a function, since a panic can
1007+
// occur (almost) anywhere. Because it is live
1008+
// everywhere, it must be zeroed on entry.
1009+
livedefer.Set(int32(i))
1010+
// It was already marked as Needzero when created.
1011+
if !n.Name.Needzero() {
1012+
Fatalf("all pointer-containing defer arg slots should have Needzero set")
1013+
}
1014+
}
10901015
}
10911016
}
10921017

@@ -1188,6 +1113,17 @@ func (lv *Liveness) epilogue() {
11881113
lv.compact(b)
11891114
}
11901115

1116+
// If we have an open-coded deferreturn call, make a liveness map for it.
1117+
if lv.fn.Func.OpenCodedDeferDisallowed() {
1118+
lv.livenessMap.deferreturn = LivenessInvalid
1119+
} else {
1120+
lv.livenessMap.deferreturn = LivenessIndex{
1121+
stackMapIndex: lv.stackMapSet.add(livedefer),
1122+
regMapIndex: 0, // entry regMap, containing no live registers
1123+
isUnsafePoint: false,
1124+
}
1125+
}
1126+
11911127
// Done compacting. Throw out the stack map set.
11921128
lv.stackMaps = lv.stackMapSet.extractUniqe()
11931129
lv.stackMapSet = bvecSet{}

src/cmd/compile/internal/gc/ssa.go

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4318,12 +4318,6 @@ func (s *state) openDeferExit() {
43184318
}
43194319
}
43204320

4321-
if i == len(s.openDefers)-1 {
4322-
// Record the call of the first defer. This will be used
4323-
// to set liveness info for the deferreturn (which is also
4324-
// used for any location that causes a runtime panic)
4325-
s.f.LastDeferExit = call
4326-
}
43274321
s.endBlock()
43284322
s.startBlock(bEnd)
43294323
}
@@ -5807,11 +5801,6 @@ type SSAGenState struct {
58075801

58085802
// wasm: The number of values on the WebAssembly stack. This is only used as a safeguard.
58095803
OnWasmStackSkipped int
5810-
5811-
// Liveness index for the first function call in the final defer exit code
5812-
// path that we generated. All defer functions and args should be live at
5813-
// this point. This will be used to set the liveness for the deferreturn.
5814-
lastDeferLiveness LivenessIndex
58155804
}
58165805

58175806
// Prog appends a new Prog.
@@ -6056,12 +6045,6 @@ func genssa(f *ssa.Func, pp *Progs) {
60566045
// instruction.
60576046
s.pp.nextLive = s.livenessMap.Get(v)
60586047

6059-
// Remember the liveness index of the first defer call of
6060-
// the last defer exit
6061-
if v.Block.Func.LastDeferExit != nil && v == v.Block.Func.LastDeferExit {
6062-
s.lastDeferLiveness = s.pp.nextLive
6063-
}
6064-
60656048
// Special case for first line in function; move it to the start.
60666049
if firstPos != src.NoXPos {
60676050
s.SetPos(firstPos)
@@ -6122,7 +6105,7 @@ func genssa(f *ssa.Func, pp *Progs) {
61226105
// When doing open-coded defers, generate a disconnected call to
61236106
// deferreturn and a return. This will be used to during panic
61246107
// recovery to unwind the stack and return back to the runtime.
6125-
s.pp.nextLive = s.lastDeferLiveness
6108+
s.pp.nextLive = s.livenessMap.deferreturn
61266109
gencallret(pp, Deferreturn)
61276110
}
61286111

src/cmd/compile/internal/ssa/func.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,8 @@ type Func struct {
3333
Blocks []*Block // unordered set of all basic blocks (note: not indexable by ID)
3434
Entry *Block // the entry basic block
3535

36-
// If we are using open-coded defers, this is the first call to a deferred
37-
// function in the final defer exit sequence that we generated. This call
38-
// should be after all defer statements, and will have all args, etc. of
39-
// all defer calls as live. The liveness info of this call will be used
40-
// for the deferreturn/ret segment generated for functions with open-coded
41-
// defers.
42-
LastDeferExit *Value
43-
bid idAlloc // block ID allocator
44-
vid idAlloc // value ID allocator
36+
bid idAlloc // block ID allocator
37+
vid idAlloc // value ID allocator
4538

4639
// Given an environment variable used for debug hash match,
4740
// what file (if any) receives the yes/no logging?

test/fixedbugs/issue40629.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// run
2+
3+
// Copyright 2020 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package main
8+
9+
import "fmt"
10+
11+
const N = 40
12+
13+
func main() {
14+
var x [N]int // stack-allocated memory
15+
for i := range x {
16+
x[i] = 0x999
17+
}
18+
19+
// This defer checks to see if x is uncorrupted.
20+
defer func(p *[N]int) {
21+
recover()
22+
for i := range p {
23+
if p[i] != 0x999 {
24+
for j := range p {
25+
fmt.Printf("p[%d]=0x%x\n", j, p[j])
26+
}
27+
panic("corrupted stack variable")
28+
}
29+
}
30+
}(&x)
31+
32+
// This defer starts a new goroutine, which will (hopefully)
33+
// overwrite x on the garbage stack.
34+
defer func() {
35+
c := make(chan bool)
36+
go func() {
37+
useStack(1000)
38+
c <- true
39+
}()
40+
<-c
41+
42+
}()
43+
44+
// This defer causes a stack copy.
45+
// The old stack is now garbage.
46+
defer func() {
47+
useStack(1000)
48+
}()
49+
50+
// Trigger a segfault.
51+
*g = 0
52+
53+
// Make the return statement unreachable.
54+
// That makes the stack map at the deferreturn call empty.
55+
// In particular, the argument to the first defer is not
56+
// marked as a pointer, so it doesn't get adjusted
57+
// during the stack copy.
58+
for {
59+
}
60+
}
61+
62+
var g *int64
63+
64+
func useStack(n int) {
65+
if n == 0 {
66+
return
67+
}
68+
useStack(n - 1)
69+
}

0 commit comments

Comments
 (0)