-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
x/tools/go/ssa/interp: phi nodes are not executed in parallel (the "swap problem") #69929
Comments
The variable y is not lost. It is translated to a phi node, that is, a node whose value depends on which incoming edge control came through to reach the current block:
The comment tells you that this value corresponds to y, and the phi notation tells you that y's value is either t0 (*root) when coming from block zero (i.e. on the first iteration), or t5 (x) on all later iterations. Unlike traditional intermediate representations, there are no local assignments in SSA, so you can't "update" y. The representation expresses the conditionality in a stateless form. This is the nature of SSA. I suggest you read some introductory material on the topic, such as the Wikipedia article. |
I hope the development team can treat community feedback with mutual respect. This is my newly added test result. By comparing “go run hello.go” and “x/tools/go/ssa/interp”, I still found differences when executing the following:
As a gopher who doesn't understand SSA, I am rather confused as to why the above two execution results are different? man.go code: package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/interp"
)
const src = `
package main
func main() {
root = &mapNode{}
insert()
println(root)
}
var root *mapNode
type mapNode struct {
Child *mapNode
}
func insert() {
var x *mapNode = root
var y *mapNode = x
for x != nil {
y = x
println(y)
x = x.Child
}
println(y)
}
`
func main() {
prog := NewProgram(map[string]string{
"main": src,
"runtime": `
package runtime
type errorString string
func (e errorString) RuntimeError() {}
func (e errorString) Error() string { return "runtime error: " + string(e) }
type Error interface {
error
RuntimeError()
}
`,
})
prog.LoadPackage("main")
prog.LoadPackage("runtime")
var ssaProg = ssa.NewProgram(prog.fset, ssa.SanityCheckFunctions)
var ssaMainPkg *ssa.Package
for name, pkg := range prog.pkgs {
ssaPkg := ssaProg.CreatePackage(pkg, []*ast.File{prog.ast[name]}, prog.infos[name], true)
if name == "main" {
ssaMainPkg = ssaPkg
}
}
ssaProg.Build()
exitCode := interp.Interpret(
ssaMainPkg, 0, &types.StdSizes{8, 8},
"main", []string{},
)
if exitCode != 0 {
fmt.Println("exitCode:", exitCode)
}
}
type Program struct {
fs map[string]string
ast map[string]*ast.File
pkgs map[string]*types.Package
infos map[string]*types.Info
fset *token.FileSet
}
func NewProgram(fs map[string]string) *Program {
return &Program{
fs: fs,
ast: make(map[string]*ast.File),
pkgs: make(map[string]*types.Package),
infos: make(map[string]*types.Info),
fset: token.NewFileSet(),
}
}
func (p *Program) LoadPackage(path string) (pkg *types.Package, f *ast.File, err error) {
if pkg, ok := p.pkgs[path]; ok {
return pkg, p.ast[path], nil
}
f, err = parser.ParseFile(p.fset, path, p.fs[path], parser.AllErrors)
if err != nil {
return nil, nil, err
}
info := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
Scopes: make(map[ast.Node]*types.Scope),
}
conf := types.Config{Importer: p}
pkg, err = conf.Check(path, p.fset, []*ast.File{f}, info)
if err != nil {
return nil, nil, err
}
p.ast[path] = f
p.pkgs[path] = pkg
p.infos[path] = info
return pkg, f, nil
}
func (p *Program) Import(path string) (*types.Package, error) {
if pkg, ok := p.pkgs[path]; ok {
return pkg, nil
}
pkg, _, err := p.LoadPackage(path)
return pkg, err
} ./hello/hello.go: package main
func main() {
root = &mapNode{}
insert()
println(root)
}
var root *mapNode
type mapNode struct {
Child *mapNode
}
func insert() {
var x *mapNode = root
var y *mapNode = x
for x != nil {
y = x
println(y)
x = x.Child
}
println(y)
} |
We know how phi works, this problem is not about losing According to SSA code above, on all iterations after Using |
Apologies for my previous comment. I mistakenly thought I was addressing someone unfamiliar with SSA basics, though even if that were so I now see how the tone of my comment came across as condescending. The subtlety in this case is that the two phis are interdependent:
The SSA builder acts as if all consecutive phis are executed in parallel, which is consistent with the interpretation that phis are really virtual instructions at the end of each predecessor block. Annotating the purpose of each value below shows that the final print(t6) is correct: it prints the value of y on entry to the final (false) loop test:
However, the ssa/interp logic gets this case wrong because it shouldn't update the environment for t5 and t6 one instruction at a time; it must wait until both phis have been evaluated. So I think this is a problem in the interpreter, not the builder, though perhaps the documentation should clarify the parallel nature of phis. |
That explains all. Thanks! |
For how to efficiently replace phi nodes with explicit copies while preserving parallel semantics, see the "swap problem" in Briggs et al's Practical Improvements to the Construction and Destruction of Static Single Assignment Form. |
Change https://go.dev/cl/621595 mentions this issue: |
Thanks for reporting the bug, with a nice reproducible test case. |
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 <matthew@golang.org> Reviewed-by: Tim King <taking@google.com> Auto-Submit: Alan Donovan <adonovan@google.com> Commit-Queue: Tim King <taking@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Go version
go1.23
Output of
go env
in your module/workspace:What did you do?
i use x/tools/go/ssa to print ssa text:
https://go.dev/play/p/dxj4fTu2M5G
What did you see happen?
the insert func ssa:
What did you expect to see?
i guess the ssa lose the
y = x
infor
loop. the test code shouldprint(root)
twice.but the ssa
print(root)
andprint(root.Child)
.The text was updated successfully, but these errors were encountered: