// Copyright 2017 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" "testing" ) // Test that a trivial 'if' is eliminated func TestBranchElimIf(t *testing.T) { var testData = []struct { arch string intType string ok bool }{ {"arm64", "int32", true}, {"amd64", "int32", true}, {"amd64", "int8", false}, } for _, data := range testData { t.Run(data.arch+"/"+data.intType, func(t *testing.T) { c := testConfigArch(t, data.arch) boolType := c.config.Types.Bool var intType *types.Type switch data.intType { case "int32": intType = c.config.Types.Int32 case "int8": intType = c.config.Types.Int8 default: t.Fatal("invalid integer type:", data.intType) } fun := c.Fun("entry", Bloc("entry", Valu("start", OpInitMem, types.TypeMem, 0, nil), Valu("sb", OpSB, c.config.Types.Uintptr, 0, nil), Valu("const1", OpConst32, intType, 1, nil), Valu("const2", OpConst32, intType, 2, nil), Valu("addr", OpAddr, boolType.PtrTo(), 0, nil, "sb"), Valu("cond", OpLoad, boolType, 0, nil, "addr", "start"), If("cond", "b2", "b3")), Bloc("b2", Goto("b3")), Bloc("b3", Valu("phi", OpPhi, intType, 0, nil, "const1", "const2"), Valu("retstore", OpStore, types.TypeMem, 0, nil, "phi", "sb", "start"), Exit("retstore"))) CheckFunc(fun.f) branchelim(fun.f) CheckFunc(fun.f) Deadcode(fun.f) CheckFunc(fun.f) if data.ok { if len(fun.f.Blocks) != 1 { t.Fatalf("expected 1 block after branchelim and deadcode; found %d", len(fun.f.Blocks)) } if fun.values["phi"].Op != OpCondSelect { t.Fatalf("expected phi op to be CondSelect; found op %s", fun.values["phi"].Op) } if fun.values["phi"].Args[2] != fun.values["cond"] { t.Errorf("expected CondSelect condition to be %s; found %s", fun.values["cond"], fun.values["phi"].Args[2]) } if fun.blocks["entry"].Kind != BlockExit { t.Errorf("expected entry to be BlockExit; found kind %s", fun.blocks["entry"].Kind.String()) } } else { if len(fun.f.Blocks) != 3 { t.Fatalf("expected 3 block after branchelim and deadcode; found %d", len(fun.f.Blocks)) } } }) } } // Test that a trivial if/else is eliminated func TestBranchElimIfElse(t *testing.T) { for _, arch := range []string{"arm64", "amd64"} { t.Run(arch, func(t *testing.T) { c := testConfigArch(t, arch) boolType := c.config.Types.Bool intType := c.config.Types.Int32 fun := c.Fun("entry", Bloc("entry", Valu("start", OpInitMem, types.TypeMem, 0, nil), Valu("sb", OpSB, c.config.Types.Uintptr, 0, nil), Valu("const1", OpConst32, intType, 1, nil), Valu("const2", OpConst32, intType, 2, nil), Valu("addr", OpAddr, boolType.PtrTo(), 0, nil, "sb"), Valu("cond", OpLoad, boolType, 0, nil, "addr", "start"), If("cond", "b2", "b3")), Bloc("b2", Goto("b4")), Bloc("b3", Goto("b4")), Bloc("b4", Valu("phi", OpPhi, intType, 0, nil, "const1", "const2"), Valu("retstore", OpStore, types.TypeMem, 0, nil, "phi", "sb", "start"), Exit("retstore"))) CheckFunc(fun.f) branchelim(fun.f) CheckFunc(fun.f) Deadcode(fun.f) CheckFunc(fun.f) if len(fun.f.Blocks) != 1 { t.Fatalf("expected 1 block after branchelim; found %d", len(fun.f.Blocks)) } if fun.values["phi"].Op != OpCondSelect { t.Fatalf("expected phi op to be CondSelect; found op %s", fun.values["phi"].Op) } if fun.values["phi"].Args[2] != fun.values["cond"] { t.Errorf("expected CondSelect condition to be %s; found %s", fun.values["cond"], fun.values["phi"].Args[2]) } if fun.blocks["entry"].Kind != BlockExit { t.Errorf("expected entry to be BlockExit; found kind %s", fun.blocks["entry"].Kind.String()) } }) } } // Test that an if/else CFG that loops back // into itself does *not* get eliminated. func TestNoBranchElimLoop(t *testing.T) { for _, arch := range []string{"arm64", "amd64"} { t.Run(arch, func(t *testing.T) { c := testConfigArch(t, arch) boolType := c.config.Types.Bool intType := c.config.Types.Int32 // The control flow here is totally bogus, // but a dead cycle seems like the only plausible // way to arrive at a diamond CFG that is also a loop. fun := c.Fun("entry", Bloc("entry", Valu("start", OpInitMem, types.TypeMem, 0, nil), Valu("sb", OpSB, c.config.Types.Uintptr, 0, nil), Valu("const2", OpConst32, intType, 2, nil), Valu("const3", OpConst32, intType, 3, nil), Goto("b5")), Bloc("b2", Valu("addr", OpAddr, boolType.PtrTo(), 0, nil, "sb"), Valu("cond", OpLoad, boolType, 0, nil, "addr", "start"), Valu("phi", OpPhi, intType, 0, nil, "const2", "const3"), If("cond", "b3", "b4")), Bloc("b3", Goto("b2")), Bloc("b4", Goto("b2")), Bloc("b5", Exit("start"))) CheckFunc(fun.f) branchelim(fun.f) CheckFunc(fun.f) if len(fun.f.Blocks) != 5 { t.Errorf("expected 5 block after branchelim; found %d", len(fun.f.Blocks)) } if fun.values["phi"].Op != OpPhi { t.Errorf("expected phi op to be CondSelect; found op %s", fun.values["phi"].Op) } }) } }