go/src/cmd/compile/internal/ssa/regalloc_test.go
Keith Randall 67a782b8cc cmd/compile: test for moving spills
Test that we really do move spills down to the dominator of
all the uses.

Also add a test where go1.8 would have moved the spill out of
the loop into two exit points, but go1.9 doesn't move the spill.
This is a case where the 1.9 spill moving code does not subsume
the 1.8 spill moving code.

Maybe we resurrect moving-spills-out-of-loops CL to fix this one.
(I suspect it wouldn't be worth the effort, but would be happy
to hear evidence otherwise.)

Update #20472

Change-Id: I7dbf8d65e7f4d675d14e5ecf502887cebda35d2a
Reviewed-on: https://go-review.googlesource.com/44038
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
2017-05-24 21:36:06 +00:00

181 lines
5.1 KiB
Go

// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
import (
"cmd/compile/internal/types"
"cmd/internal/src"
"testing"
)
func TestLiveControlOps(t *testing.T) {
c := testConfig(t)
f := c.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
Valu("x", OpAMD64MOVLconst, c.config.Types.Int8, 1, nil),
Valu("y", OpAMD64MOVLconst, c.config.Types.Int8, 2, nil),
Valu("a", OpAMD64TESTB, types.TypeFlags, 0, nil, "x", "y"),
Valu("b", OpAMD64TESTB, types.TypeFlags, 0, nil, "y", "x"),
Eq("a", "if", "exit"),
),
Bloc("if",
Eq("b", "plain", "exit"),
),
Bloc("plain",
Goto("exit"),
),
Bloc("exit",
Exit("mem"),
),
)
flagalloc(f.f)
regalloc(f.f)
checkFunc(f.f)
}
// Test to make sure we don't push spills into loops.
// See issue #19595.
func TestSpillWithLoop(t *testing.T) {
c := testConfig(t)
f := c.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
Valu("ptr", OpArg, c.config.Types.Int64.PtrTo(), 0, c.Frontend().Auto(src.NoXPos, c.config.Types.Int64)),
Valu("cond", OpArg, c.config.Types.Bool, 0, c.Frontend().Auto(src.NoXPos, c.config.Types.Bool)),
Valu("ld", OpAMD64MOVQload, c.config.Types.Int64, 0, nil, "ptr", "mem"), // this value needs a spill
Goto("loop"),
),
Bloc("loop",
Valu("memphi", OpPhi, types.TypeMem, 0, nil, "mem", "call"),
Valu("call", OpAMD64CALLstatic, types.TypeMem, 0, nil, "memphi"),
Valu("test", OpAMD64CMPBconst, types.TypeFlags, 0, nil, "cond"),
Eq("test", "next", "exit"),
),
Bloc("next",
Goto("loop"),
),
Bloc("exit",
Valu("store", OpAMD64MOVQstore, types.TypeMem, 0, nil, "ptr", "ld", "call"),
Exit("store"),
),
)
regalloc(f.f)
checkFunc(f.f)
for _, v := range f.blocks["loop"].Values {
if v.Op == OpStoreReg {
t.Errorf("spill inside loop %s", v.LongString())
}
}
}
func TestSpillMove1(t *testing.T) {
c := testConfig(t)
f := c.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
Valu("x", OpArg, c.config.Types.Int64, 0, c.Frontend().Auto(src.NoXPos, c.config.Types.Int64)),
Valu("p", OpArg, c.config.Types.Int64.PtrTo(), 0, c.Frontend().Auto(src.NoXPos, c.config.Types.Int64.PtrTo())),
Valu("a", OpAMD64TESTQ, types.TypeFlags, 0, nil, "x", "x"),
Goto("loop1"),
),
Bloc("loop1",
Valu("y", OpAMD64MULQ, c.config.Types.Int64, 0, nil, "x", "x"),
Eq("a", "loop2", "exit1"),
),
Bloc("loop2",
Eq("a", "loop1", "exit2"),
),
Bloc("exit1",
// store before call, y is available in a register
Valu("mem2", OpAMD64MOVQstore, types.TypeMem, 0, nil, "p", "y", "mem"),
Valu("mem3", OpAMD64CALLstatic, types.TypeMem, 0, nil, "mem2"),
Exit("mem3"),
),
Bloc("exit2",
// store after call, y must be loaded from a spill location
Valu("mem4", OpAMD64CALLstatic, types.TypeMem, 0, nil, "mem"),
Valu("mem5", OpAMD64MOVQstore, types.TypeMem, 0, nil, "p", "y", "mem4"),
Exit("mem5"),
),
)
flagalloc(f.f)
regalloc(f.f)
checkFunc(f.f)
// Spill should be moved to exit2.
if numSpills(f.blocks["loop1"]) != 0 {
t.Errorf("spill present from loop1")
}
if numSpills(f.blocks["loop2"]) != 0 {
t.Errorf("spill present in loop2")
}
if numSpills(f.blocks["exit1"]) != 0 {
t.Errorf("spill present in exit1")
}
if numSpills(f.blocks["exit2"]) != 1 {
t.Errorf("spill missing in exit2")
}
}
func TestSpillMove2(t *testing.T) {
c := testConfig(t)
f := c.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
Valu("x", OpArg, c.config.Types.Int64, 0, c.Frontend().Auto(src.NoXPos, c.config.Types.Int64)),
Valu("p", OpArg, c.config.Types.Int64.PtrTo(), 0, c.Frontend().Auto(src.NoXPos, c.config.Types.Int64.PtrTo())),
Valu("a", OpAMD64TESTQ, types.TypeFlags, 0, nil, "x", "x"),
Goto("loop1"),
),
Bloc("loop1",
Valu("y", OpAMD64MULQ, c.config.Types.Int64, 0, nil, "x", "x"),
Eq("a", "loop2", "exit1"),
),
Bloc("loop2",
Eq("a", "loop1", "exit2"),
),
Bloc("exit1",
// store after call, y must be loaded from a spill location
Valu("mem2", OpAMD64CALLstatic, types.TypeMem, 0, nil, "mem"),
Valu("mem3", OpAMD64MOVQstore, types.TypeMem, 0, nil, "p", "y", "mem2"),
Exit("mem3"),
),
Bloc("exit2",
// store after call, y must be loaded from a spill location
Valu("mem4", OpAMD64CALLstatic, types.TypeMem, 0, nil, "mem"),
Valu("mem5", OpAMD64MOVQstore, types.TypeMem, 0, nil, "p", "y", "mem4"),
Exit("mem5"),
),
)
flagalloc(f.f)
regalloc(f.f)
checkFunc(f.f)
// There should be a spill in loop1, and nowhere else.
// TODO: resurrect moving spills out of loops? We could put spills at the start of both exit1 and exit2.
if numSpills(f.blocks["loop1"]) != 1 {
t.Errorf("spill missing from loop1")
}
if numSpills(f.blocks["loop2"]) != 0 {
t.Errorf("spill present in loop2")
}
if numSpills(f.blocks["exit1"]) != 0 {
t.Errorf("spill present in exit1")
}
if numSpills(f.blocks["exit2"]) != 0 {
t.Errorf("spill present in exit2")
}
}
func numSpills(b *Block) int {
n := 0
for _, v := range b.Values {
if v.Op == OpStoreReg {
n++
}
}
return n
}