| // 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 | |
| } |