Skip to content

Commit 8dd84a4

Browse files
adonovangopherbot
authored andcommitted
go/ssa/interp: assign phi nodes in parallel
This CL causes the interpreter to assign all phi nodes of a block in parallel, as required by SSA semantics. Previously it would execute them in order like real instructions, so that earlier phis might clobber the environment values required as inputs to later phis. The phi handling is moved out of the ordinary visitInstr code and into the block-entry logic, since all phis need to be handled en bloc. Also, a test, based on the user-reported problem in the attached issue. Fixes golang/go#69929 Change-Id: I12a61286b2151e2e72b642ca336e4ae31b7fa614 Reviewed-on: https://go-review.googlesource.com/c/tools/+/621595 Reviewed-by: Matthew Dempsky <[email protected]> Reviewed-by: Tim King <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Commit-Queue: Tim King <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 12610a1 commit 8dd84a4

File tree

3 files changed

+112
-14
lines changed

3 files changed

+112
-14
lines changed

go/ssa/interp/interp.go

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ import (
4848
"fmt"
4949
"go/token"
5050
"go/types"
51+
"log"
5152
"os"
5253
"reflect"
5354
"runtime"
55+
"slices"
5456
"sync/atomic"
5557
_ "unsafe"
5658

@@ -108,6 +110,7 @@ type frame struct {
108110
result value
109111
panicking bool
110112
panic interface{}
113+
phitemps []value // temporaries for parallel phi assignment
111114
}
112115

113116
func (fr *frame) get(key ssa.Value) value {
@@ -379,12 +382,7 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation {
379382
fr.env[instr] = &closure{instr.Fn.(*ssa.Function), bindings}
380383

381384
case *ssa.Phi:
382-
for i, pred := range instr.Block().Preds {
383-
if fr.prevBlock == pred {
384-
fr.env[instr] = fr.get(instr.Edges[i])
385-
break
386-
}
387-
}
385+
log.Fatal("unreachable") // phis are processed at block entry
388386

389387
case *ssa.Select:
390388
var cases []reflect.SelectCase
@@ -589,25 +587,57 @@ func runFrame(fr *frame) {
589587
if fr.i.mode&EnableTracing != 0 {
590588
fmt.Fprintf(os.Stderr, ".%s:\n", fr.block)
591589
}
592-
block:
593-
for _, instr := range fr.block.Instrs {
590+
591+
nonPhis := executePhis(fr)
592+
for _, instr := range nonPhis {
594593
if fr.i.mode&EnableTracing != 0 {
595594
if v, ok := instr.(ssa.Value); ok {
596595
fmt.Fprintln(os.Stderr, "\t", v.Name(), "=", instr)
597596
} else {
598597
fmt.Fprintln(os.Stderr, "\t", instr)
599598
}
600599
}
601-
switch visitInstr(fr, instr) {
602-
case kReturn:
600+
if visitInstr(fr, instr) == kReturn {
603601
return
604-
case kNext:
605-
// no-op
606-
case kJump:
607-
break block
608602
}
603+
// Inv: kNext (continue) or kJump (last instr)
604+
}
605+
}
606+
}
607+
608+
// executePhis executes the phi-nodes at the start of the current
609+
// block and returns the non-phi instructions.
610+
func executePhis(fr *frame) []ssa.Instruction {
611+
firstNonPhi := -1
612+
for i, instr := range fr.block.Instrs {
613+
if _, ok := instr.(*ssa.Phi); !ok {
614+
firstNonPhi = i
615+
break
616+
}
617+
}
618+
// Inv: 0 <= firstNonPhi; every block contains a non-phi.
619+
620+
nonPhis := fr.block.Instrs[firstNonPhi:]
621+
if firstNonPhi > 0 {
622+
phis := fr.block.Instrs[:firstNonPhi]
623+
// Execute parallel assignment of phis.
624+
//
625+
// See "the swap problem" in Briggs et al's "Practical Improvements
626+
// to the Construction and Destruction of SSA Form" for discussion.
627+
predIndex := slices.Index(fr.block.Preds, fr.prevBlock)
628+
fr.phitemps = fr.phitemps[:0]
629+
for _, phi := range phis {
630+
phi := phi.(*ssa.Phi)
631+
if fr.i.mode&EnableTracing != 0 {
632+
fmt.Fprintln(os.Stderr, "\t", phi.Name(), "=", phi)
633+
}
634+
fr.phitemps = append(fr.phitemps, fr.get(phi.Edges[predIndex]))
635+
}
636+
for i, phi := range phis {
637+
fr.env[phi.(*ssa.Phi)] = fr.phitemps[i]
609638
}
610639
}
640+
return nonPhis
611641
}
612642

613643
// doRecover implements the recover() built-in.

go/ssa/interp/interp_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ var testdataTests = []string{
136136
"fixedbugs/issue52835.go",
137137
"fixedbugs/issue55086.go",
138138
"fixedbugs/issue66783.go",
139+
"fixedbugs/issue69929.go",
139140
"typeassert.go",
140141
"zeros.go",
141142
"slice2array.go",
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package main
2+
3+
// This is a regression test for a bug (#69929) in
4+
// the SSA interpreter in which it would not execute phis in parallel.
5+
//
6+
// The insert function below has interdependent phi nodes:
7+
//
8+
// entry:
9+
// t0 = *root // t0 is x or y before loop
10+
// jump test
11+
// body:
12+
// print(t5) // t5 is x at loop entry
13+
// t3 = t5.Child // t3 is x after loop
14+
// jump test
15+
// test:
16+
// t5 = phi(t0, t3) // t5 is x at loop entry
17+
// t6 = phi(t0, t5) // t6 is y at loop entry
18+
// if t5 != nil goto body else done
19+
// done:
20+
// print(t6)
21+
// return
22+
//
23+
// The two phis:
24+
//
25+
// t5 = phi(t0, t3)
26+
// t6 = phi(t0, t5)
27+
//
28+
// must be executed in parallel as if they were written in Go
29+
// as:
30+
//
31+
// t5, t6 = phi(t0, t3), phi(t0, t5)
32+
//
33+
// with the second phi node observing the original, not
34+
// updated, value of t5. (In more complex examples, the phi
35+
// nodes may be mutually recursive, breaking partial solutions
36+
// based on simple reordering of the phi instructions. See the
37+
// Briggs paper for detail.)
38+
//
39+
// The correct behavior is print(1, root); print(2, root); print(3, root).
40+
// The previous incorrect behavior had print(2, nil).
41+
42+
func main() {
43+
insert()
44+
print(3, root)
45+
}
46+
47+
var root = new(node)
48+
49+
type node struct{ child *node }
50+
51+
func insert() {
52+
x := root
53+
y := x
54+
for x != nil {
55+
y = x
56+
print(1, y)
57+
x = x.child
58+
}
59+
print(2, y)
60+
}
61+
62+
func print(order int, ptr *node) {
63+
println(order, ptr)
64+
if ptr != root {
65+
panic(ptr)
66+
}
67+
}

0 commit comments

Comments
 (0)