Source file src/cmd/compile/internal/ssacompile/regalloc_test.go

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssacompile
     6  
     7  import (
     8  	"fmt"
     9  	"testing"
    10  
    11  	"cmd/compile/internal/ssa"
    12  	"cmd/compile/internal/ssa/ssabase"
    13  	"cmd/compile/internal/ssa/ssaop"
    14  	"cmd/compile/internal/types"
    15  	"cmd/internal/obj/x86"
    16  )
    17  
    18  func TestLiveControlOps(t *testing.T) {
    19  	c := testConfig(t)
    20  	f := c.Fun("entry",
    21  		Bloc("entry",
    22  			Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
    23  			Valu("x", ssaop.OpAMD64MOVLconst, c.config.Types.Int8, 1, nil),
    24  			Valu("y", ssaop.OpAMD64MOVLconst, c.config.Types.Int8, 2, nil),
    25  			Valu("a", ssaop.OpAMD64TESTB, types.TypeFlags, 0, nil, "x", "y"),
    26  			Valu("b", ssaop.OpAMD64TESTB, types.TypeFlags, 0, nil, "y", "x"),
    27  			Eq("a", "if", "exit"),
    28  		),
    29  		Bloc("if",
    30  			Eq("b", "plain", "exit"),
    31  		),
    32  		Bloc("plain",
    33  			Goto("exit"),
    34  		),
    35  		Bloc("exit",
    36  			Exit("mem"),
    37  		),
    38  	)
    39  	flagalloc(f.f)
    40  	regalloc(f.f)
    41  	checkFunc(f.f)
    42  }
    43  
    44  // Test to make sure G register is never reloaded from spill (spill of G is okay)
    45  // See #25504
    46  func TestNoGetgLoadReg(t *testing.T) {
    47  	/*
    48  		Original:
    49  		func fff3(i int) *g {
    50  			gee := getg()
    51  			if i == 0 {
    52  				fff()
    53  			}
    54  			return gee // here
    55  		}
    56  	*/
    57  	c := testConfigARM64(t)
    58  	f := c.Fun("b1",
    59  		Bloc("b1",
    60  			Valu("v1", ssaop.OpInitMem, types.TypeMem, 0, nil),
    61  			Valu("v6", ssaop.OpArg, c.config.Types.Int64, 0, c.Temp(c.config.Types.Int64)),
    62  			Valu("v8", ssaop.OpGetG, c.config.Types.Int64.PtrTo(), 0, nil, "v1"),
    63  			Valu("v11", ssaop.OpARM64CMPconst, types.TypeFlags, 0, nil, "v6"),
    64  			Eq("v11", "b2", "b4"),
    65  		),
    66  		Bloc("b4",
    67  			Goto("b3"),
    68  		),
    69  		Bloc("b3",
    70  			Valu("v14", ssaop.OpPhi, types.TypeMem, 0, nil, "v1", "v12"),
    71  			Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
    72  			Valu("v16", ssaop.OpARM64MOVDstore, types.TypeMem, 0, nil, "v8", "sb", "v14"),
    73  			Exit("v16"),
    74  		),
    75  		Bloc("b2",
    76  			Valu("v12", ssaop.OpARM64CALLstatic, types.TypeMem, 0, AuxCallLSym("_"), "v1"),
    77  			Goto("b3"),
    78  		),
    79  	)
    80  	regalloc(f.f)
    81  	checkFunc(f.f)
    82  	// Double-check that we never restore to the G register. Regalloc should catch it, but check again anyway.
    83  	r := f.f.RegAlloc
    84  	for _, b := range f.blocks {
    85  		for _, v := range b.Values {
    86  			if v.Op == ssaop.OpLoadReg && r[v.ID].String() == "g" {
    87  				t.Errorf("Saw OpLoadReg targeting g register: %s", v.LongString())
    88  			}
    89  		}
    90  	}
    91  }
    92  
    93  // Test to make sure we don't push spills into loops.
    94  // See issue #19595.
    95  func TestSpillWithLoop(t *testing.T) {
    96  	c := testConfig(t)
    97  	f := c.Fun("entry",
    98  		Bloc("entry",
    99  			Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
   100  			Valu("ptr", ssaop.OpArg, c.config.Types.Int64.PtrTo(), 0, c.Temp(c.config.Types.Int64)),
   101  			Valu("cond", ssaop.OpArg, c.config.Types.Bool, 0, c.Temp(c.config.Types.Bool)),
   102  			Valu("ld", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 0, nil, "ptr", "mem"), // this value needs a spill
   103  			Goto("loop"),
   104  		),
   105  		Bloc("loop",
   106  			Valu("memphi", ssaop.OpPhi, types.TypeMem, 0, nil, "mem", "call"),
   107  			Valu("call", ssaop.OpAMD64CALLstatic, types.TypeMem, 0, AuxCallLSym("_"), "memphi"),
   108  			Valu("test", ssaop.OpAMD64CMPBconst, types.TypeFlags, 0, nil, "cond"),
   109  			Eq("test", "next", "exit"),
   110  		),
   111  		Bloc("next",
   112  			Goto("loop"),
   113  		),
   114  		Bloc("exit",
   115  			Valu("store", ssaop.OpAMD64MOVQstore, types.TypeMem, 0, nil, "ptr", "ld", "call"),
   116  			Exit("store"),
   117  		),
   118  	)
   119  	regalloc(f.f)
   120  	checkFunc(f.f)
   121  	for _, v := range f.blocks["loop"].Values {
   122  		if v.Op == ssaop.OpStoreReg {
   123  			t.Errorf("spill inside loop %s", v.LongString())
   124  		}
   125  	}
   126  }
   127  
   128  func TestSpillMove1(t *testing.T) {
   129  	c := testConfig(t)
   130  	f := c.Fun("entry",
   131  		Bloc("entry",
   132  			Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
   133  			Valu("x", ssaop.OpArg, c.config.Types.Int64, 0, c.Temp(c.config.Types.Int64)),
   134  			Valu("p", ssaop.OpArg, c.config.Types.Int64.PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo())),
   135  			Valu("a", ssaop.OpAMD64TESTQ, types.TypeFlags, 0, nil, "x", "x"),
   136  			Goto("loop1"),
   137  		),
   138  		Bloc("loop1",
   139  			Valu("y", ssaop.OpAMD64MULQ, c.config.Types.Int64, 0, nil, "x", "x"),
   140  			Eq("a", "loop2", "exit1"),
   141  		),
   142  		Bloc("loop2",
   143  			Eq("a", "loop1", "exit2"),
   144  		),
   145  		Bloc("exit1",
   146  			// store before call, y is available in a register
   147  			Valu("mem2", ssaop.OpAMD64MOVQstore, types.TypeMem, 0, nil, "p", "y", "mem"),
   148  			Valu("mem3", ssaop.OpAMD64CALLstatic, types.TypeMem, 0, AuxCallLSym("_"), "mem2"),
   149  			Exit("mem3"),
   150  		),
   151  		Bloc("exit2",
   152  			// store after call, y must be loaded from a spill location
   153  			Valu("mem4", ssaop.OpAMD64CALLstatic, types.TypeMem, 0, AuxCallLSym("_"), "mem"),
   154  			Valu("mem5", ssaop.OpAMD64MOVQstore, types.TypeMem, 0, nil, "p", "y", "mem4"),
   155  			Exit("mem5"),
   156  		),
   157  	)
   158  	flagalloc(f.f)
   159  	regalloc(f.f)
   160  	checkFunc(f.f)
   161  	// Spill should be moved to exit2.
   162  	if numSpills(f.blocks["loop1"]) != 0 {
   163  		t.Errorf("spill present from loop1")
   164  	}
   165  	if numSpills(f.blocks["loop2"]) != 0 {
   166  		t.Errorf("spill present in loop2")
   167  	}
   168  	if numSpills(f.blocks["exit1"]) != 0 {
   169  		t.Errorf("spill present in exit1")
   170  	}
   171  	if numSpills(f.blocks["exit2"]) != 1 {
   172  		t.Errorf("spill missing in exit2")
   173  	}
   174  
   175  }
   176  
   177  func TestSpillMove2(t *testing.T) {
   178  	c := testConfig(t)
   179  	f := c.Fun("entry",
   180  		Bloc("entry",
   181  			Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
   182  			Valu("x", ssaop.OpArg, c.config.Types.Int64, 0, c.Temp(c.config.Types.Int64)),
   183  			Valu("p", ssaop.OpArg, c.config.Types.Int64.PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo())),
   184  			Valu("a", ssaop.OpAMD64TESTQ, types.TypeFlags, 0, nil, "x", "x"),
   185  			Goto("loop1"),
   186  		),
   187  		Bloc("loop1",
   188  			Valu("y", ssaop.OpAMD64MULQ, c.config.Types.Int64, 0, nil, "x", "x"),
   189  			Eq("a", "loop2", "exit1"),
   190  		),
   191  		Bloc("loop2",
   192  			Eq("a", "loop1", "exit2"),
   193  		),
   194  		Bloc("exit1",
   195  			// store after call, y must be loaded from a spill location
   196  			Valu("mem2", ssaop.OpAMD64CALLstatic, types.TypeMem, 0, AuxCallLSym("_"), "mem"),
   197  			Valu("mem3", ssaop.OpAMD64MOVQstore, types.TypeMem, 0, nil, "p", "y", "mem2"),
   198  			Exit("mem3"),
   199  		),
   200  		Bloc("exit2",
   201  			// store after call, y must be loaded from a spill location
   202  			Valu("mem4", ssaop.OpAMD64CALLstatic, types.TypeMem, 0, AuxCallLSym("_"), "mem"),
   203  			Valu("mem5", ssaop.OpAMD64MOVQstore, types.TypeMem, 0, nil, "p", "y", "mem4"),
   204  			Exit("mem5"),
   205  		),
   206  	)
   207  	flagalloc(f.f)
   208  	regalloc(f.f)
   209  	checkFunc(f.f)
   210  	// There should be a spill in loop1, and nowhere else.
   211  	// TODO: resurrect moving spills out of loops? We could put spills at the start of both exit1 and exit2.
   212  	if numSpills(f.blocks["loop1"]) != 1 {
   213  		t.Errorf("spill missing from loop1")
   214  	}
   215  	if numSpills(f.blocks["loop2"]) != 0 {
   216  		t.Errorf("spill present in loop2")
   217  	}
   218  	if numSpills(f.blocks["exit1"]) != 0 {
   219  		t.Errorf("spill present in exit1")
   220  	}
   221  	if numSpills(f.blocks["exit2"]) != 0 {
   222  		t.Errorf("spill present in exit2")
   223  	}
   224  
   225  }
   226  
   227  func TestClobbersArg0(t *testing.T) {
   228  	c := testConfig(t)
   229  	f := c.Fun("entry",
   230  		Bloc("entry",
   231  			Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
   232  			Valu("ptr", ssaop.OpArg, c.config.Types.Int64.PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo())),
   233  			Valu("dst", ssaop.OpArg, c.config.Types.Int64.PtrTo().PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo().PtrTo())),
   234  			Valu("zero", ssaop.OpAMD64LoweredZeroLoop, types.TypeMem, 256, nil, "ptr", "mem"),
   235  			Valu("store", ssaop.OpAMD64MOVQstore, types.TypeMem, 0, nil, "dst", "ptr", "zero"),
   236  			Exit("store")))
   237  	flagalloc(f.f)
   238  	regalloc(f.f)
   239  	checkFunc(f.f)
   240  	// LoweredZeroLoop clobbers its argument, so there must be a copy of "ptr" somewhere
   241  	// so we still have that value available at "store".
   242  	if n := numCopies(f.blocks["entry"]); n != 1 {
   243  		fmt.Printf("%s\n", f.f.String())
   244  		t.Errorf("got %d copies, want 1", n)
   245  	}
   246  }
   247  
   248  func TestClobbersArg1(t *testing.T) {
   249  	c := testConfig(t)
   250  	f := c.Fun("entry",
   251  		Bloc("entry",
   252  			Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
   253  			Valu("src", ssaop.OpArg, c.config.Types.Int64.PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo())),
   254  			Valu("dst", ssaop.OpArg, c.config.Types.Int64.PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo())),
   255  			Valu("use1", ssaop.OpArg, c.config.Types.Int64.PtrTo().PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo().PtrTo())),
   256  			Valu("use2", ssaop.OpArg, c.config.Types.Int64.PtrTo().PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo().PtrTo())),
   257  			Valu("move", ssaop.OpAMD64LoweredMoveLoop, types.TypeMem, 256, nil, "dst", "src", "mem"),
   258  			Valu("store1", ssaop.OpAMD64MOVQstore, types.TypeMem, 0, nil, "use1", "src", "move"),
   259  			Valu("store2", ssaop.OpAMD64MOVQstore, types.TypeMem, 0, nil, "use2", "dst", "store1"),
   260  			Exit("store2")))
   261  	flagalloc(f.f)
   262  	regalloc(f.f)
   263  	checkFunc(f.f)
   264  	// LoweredMoveLoop clobbers its arguments, so there must be a copy of "src" and "dst" somewhere
   265  	// so we still have that value available at the stores.
   266  	if n := numCopies(f.blocks["entry"]); n != 2 {
   267  		fmt.Printf("%s\n", f.f.String())
   268  		t.Errorf("got %d copies, want 2", n)
   269  	}
   270  }
   271  
   272  func TestNoRematerializeDeadConstant(t *testing.T) {
   273  	c := testConfigARM64(t)
   274  	f := c.Fun("b1",
   275  		Bloc("b1",
   276  			Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
   277  			Valu("addr", ssaop.OpArg, c.config.Types.Int32.PtrTo(), 0, c.Temp(c.config.Types.Int32.PtrTo())),
   278  			Valu("const", ssaop.OpARM64MOVDconst, c.config.Types.Int32, -1, nil), // Original constant
   279  			Valu("cmp", ssaop.OpARM64CMPconst, types.TypeFlags, 0, nil, "const"),
   280  			Goto("b2"),
   281  		),
   282  		Bloc("b2",
   283  			Valu("phi_mem", ssaop.OpPhi, types.TypeMem, 0, nil, "mem", "callmem"),
   284  			Eq("cmp", "b6", "b3"),
   285  		),
   286  		Bloc("b3",
   287  			Valu("call", ssaop.OpARM64CALLstatic, types.TypeMem, 0, AuxCallLSym("_"), "phi_mem"),
   288  			Valu("callmem", ssaop.OpSelectN, types.TypeMem, 0, nil, "call"),
   289  			Eq("cmp", "b5", "b4"),
   290  		),
   291  		Bloc("b4", // A block where we don't really need to rematerialize the constant -1
   292  			Goto("b2"),
   293  		),
   294  		Bloc("b5",
   295  			Valu("user", ssaop.OpAMD64MOVQstore, types.TypeMem, 0, nil, "addr", "const", "callmem"),
   296  			Exit("user"),
   297  		),
   298  		Bloc("b6",
   299  			Exit("phi_mem"),
   300  		),
   301  	)
   302  
   303  	regalloc(f.f)
   304  	checkFunc(f.f)
   305  
   306  	// Check that in block b4, there's no dead rematerialization of the constant -1
   307  	for _, v := range f.blocks["b4"].Values {
   308  		if v.Op == ssaop.OpARM64MOVDconst && v.AuxInt == -1 {
   309  			t.Errorf("constant -1 rematerialized in loop block b4: %s", v.LongString())
   310  		}
   311  	}
   312  }
   313  
   314  func numSpills(b *ssa.Block) int {
   315  	return numOps(b, ssaop.OpStoreReg)
   316  }
   317  func numCopies(b *ssa.Block) int {
   318  	return numOps(b, ssaop.OpCopy)
   319  }
   320  func numOps(b *ssa.Block, op ssaop.Op) int {
   321  	n := 0
   322  	for _, v := range b.Values {
   323  		if v.Op == op {
   324  			n++
   325  		}
   326  	}
   327  	return n
   328  }
   329  
   330  func TestRematerializeableRegCompatible(t *testing.T) {
   331  	c := testConfig(t)
   332  	f := c.Fun("entry",
   333  		Bloc("entry",
   334  			Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
   335  			Valu("x", ssaop.OpAMD64MOVLconst, c.config.Types.Int32, 1, nil),
   336  			Valu("a", ssaop.OpAMD64POR, c.config.Types.Float32, 0, nil, "x", "x"),
   337  			Valu("res", ssaop.OpMakeResult, types.NewResults([]*types.Type{c.config.Types.Float32, types.TypeMem}), 0, nil, "a", "mem"),
   338  			Ret("res"),
   339  		),
   340  	)
   341  	regalloc(f.f)
   342  	checkFunc(f.f)
   343  	moveFound := false
   344  	for _, v := range f.f.Blocks[0].Values {
   345  		if v.Op == ssaop.OpCopy && x86.REG_X0 <= v.Reg() && v.Reg() <= x86.REG_X31 {
   346  			moveFound = true
   347  		}
   348  	}
   349  	if !moveFound {
   350  		t.Errorf("Expects an Copy to be issued, but got: %+v", f.f)
   351  	}
   352  }
   353  
   354  func TestPreload(t *testing.T) {
   355  	c := testConfig(t)
   356  	// amd64 has 13 general registers. We use 1 for ptr and 12 for x0-11.
   357  	// They all contain live values at the end of the entry block.
   358  	f := c.Fun("entry",
   359  		Bloc("entry",
   360  			Valu("ptr", ssaop.OpArgIntReg, c.config.Types.Int8.PtrTo(), 0, &ssa.AuxNameOffset{Name: c.Temp(c.config.Types.Int8.PtrTo()), Offset: 0}),
   361  			Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
   362  			Valu("x0", ssaop.OpAMD64MOVBload, c.config.Types.Int8, 0, nil, "ptr", "mem"),
   363  			Valu("x1", ssaop.OpAMD64MOVBload, c.config.Types.Int8, 1, nil, "ptr", "mem"),
   364  			Valu("x2", ssaop.OpAMD64MOVBload, c.config.Types.Int8, 2, nil, "ptr", "mem"),
   365  			Valu("x3", ssaop.OpAMD64MOVBload, c.config.Types.Int8, 3, nil, "ptr", "mem"),
   366  			Valu("x4", ssaop.OpAMD64MOVBload, c.config.Types.Int8, 4, nil, "ptr", "mem"),
   367  			Valu("x5", ssaop.OpAMD64MOVBload, c.config.Types.Int8, 5, nil, "ptr", "mem"),
   368  			Valu("x6", ssaop.OpAMD64MOVBload, c.config.Types.Int8, 6, nil, "ptr", "mem"),
   369  			Valu("x7", ssaop.OpAMD64MOVBload, c.config.Types.Int8, 7, nil, "ptr", "mem"),
   370  			Valu("x8", ssaop.OpAMD64MOVBload, c.config.Types.Int8, 8, nil, "ptr", "mem"),
   371  			Valu("x9", ssaop.OpAMD64MOVBload, c.config.Types.Int8, 9, nil, "ptr", "mem"),
   372  			Valu("x10", ssaop.OpAMD64MOVBload, c.config.Types.Int8, 10, nil, "ptr", "mem"),
   373  			Valu("x11", ssaop.OpAMD64MOVBload, c.config.Types.Int8, 11, nil, "ptr", "mem"),
   374  			Valu("init", ssaop.OpAMD64MOVQconst, c.config.Types.Int64, 0, nil),
   375  			Goto("loopHead"),
   376  		),
   377  		Bloc("loopHead",
   378  			Valu("phi", ssaop.OpPhi, c.config.Types.Int64, 0, nil, "init", "next"),
   379  			Valu("test", ssaop.OpAMD64CMPQconst, types.TypeFlags, 10, nil, "phi"),
   380  			Lt("test", "loopBody", "exit"),
   381  		),
   382  		Bloc("loopBody",
   383  			Valu("next", ssaop.OpAMD64ADDQconst, c.config.Types.Int64, 1, nil, "phi"),
   384  			Goto("loopHead"),
   385  		),
   386  		Bloc("exit",
   387  			Valu("m0", ssaop.OpAMD64MOVBstore, types.TypeMem, 0, nil, "ptr", "x0", "mem"),
   388  			Valu("m1", ssaop.OpAMD64MOVBstore, types.TypeMem, 1, nil, "ptr", "x1", "m0"),
   389  			Valu("m2", ssaop.OpAMD64MOVBstore, types.TypeMem, 2, nil, "ptr", "x2", "m1"),
   390  			Valu("m3", ssaop.OpAMD64MOVBstore, types.TypeMem, 3, nil, "ptr", "x3", "m2"),
   391  			Valu("m4", ssaop.OpAMD64MOVBstore, types.TypeMem, 4, nil, "ptr", "x4", "m3"),
   392  			Valu("m5", ssaop.OpAMD64MOVBstore, types.TypeMem, 5, nil, "ptr", "x5", "m4"),
   393  			Valu("m6", ssaop.OpAMD64MOVBstore, types.TypeMem, 6, nil, "ptr", "x6", "m5"),
   394  			Valu("m7", ssaop.OpAMD64MOVBstore, types.TypeMem, 7, nil, "ptr", "x7", "m6"),
   395  			Valu("m8", ssaop.OpAMD64MOVBstore, types.TypeMem, 8, nil, "ptr", "x8", "m7"),
   396  			Valu("m9", ssaop.OpAMD64MOVBstore, types.TypeMem, 9, nil, "ptr", "x9", "m8"),
   397  			Valu("m10", ssaop.OpAMD64MOVBstore, types.TypeMem, 10, nil, "ptr", "x10", "m9"),
   398  			Valu("m11", ssaop.OpAMD64MOVBstore, types.TypeMem, 11, nil, "ptr", "x11", "m10"),
   399  			Ret("m11"),
   400  		),
   401  	)
   402  	f.f.Blocks[1].Likely = ssa.BranchLikely
   403  	regalloc(f.f)
   404  	checkFunc(f.f)
   405  
   406  	v := f.values["phi"]
   407  	loc := f.f.RegAlloc[v.ID]
   408  	if _, ok := loc.(*ssabase.Register); !ok {
   409  		t.Errorf("Expects to use a register for phi, but got: %s\n%v", loc, f.f)
   410  	}
   411  }
   412  
   413  // TestStartRegsDrop tests dropping physical register bits from startRegs preventing
   414  // unnecessary OpLoadReg on edges, such as edge right->merge in this example.
   415  //
   416  //	entry -> left (no pressure)  -> merge (startRegs updated) -> exit
   417  //	      -> right (pressure)   ->
   418  func TestStartRegsDrop(t *testing.T) {
   419  	c := testConfig(t)
   420  
   421  	f := c.Fun("entry",
   422  		Bloc("entry",
   423  			Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
   424  			Valu("ptr", ssaop.OpArg, c.config.Types.Int64.PtrTo(), 0, c.Temp(c.config.Types.Int64)),
   425  			// Non-phi values that will be originally in registers and get into startRegs[merge]
   426  			Valu("u0", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 100, nil, "ptr", "mem"),
   427  			Valu("u1", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 108, nil, "ptr", "mem"),
   428  			Valu("u2", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 116, nil, "ptr", "mem"),
   429  			Valu("u3", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 124, nil, "ptr", "mem"),
   430  			Valu("u4", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 132, nil, "ptr", "mem"),
   431  			Valu("u5", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 140, nil, "ptr", "mem"),
   432  			Valu("u6", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 148, nil, "ptr", "mem"),
   433  			Valu("u7", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 156, nil, "ptr", "mem"),
   434  			// Some phi value
   435  			Valu("v0", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 0, nil, "ptr", "mem"),
   436  			// Branch
   437  			Valu("cond", ssaop.OpAMD64MOVLconst, c.config.Types.Int32, 1, nil),
   438  			Valu("test", ssaop.OpAMD64TESTL, types.TypeFlags, 0, nil, "cond", "cond"),
   439  			Eq("test", "left", "right"),
   440  		),
   441  		Bloc("left",
   442  			// No register pressure - values stay in their original registers
   443  			// This is the "primary" predecessor of merge block (less spill live)
   444  			Goto("merge"),
   445  		),
   446  		Bloc("right",
   447  			// Create some register pressure. We want to make difference in this block's endRegs,
   448  			// so that at shuffle stage, the updated startRegs of merge block will be measurable.
   449  			Valu("r0", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 200, nil, "ptr", "mem"),
   450  			Valu("r1", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 208, nil, "ptr", "mem"),
   451  			Valu("r2", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 216, nil, "ptr", "mem"),
   452  			Valu("r3", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 224, nil, "ptr", "mem"),
   453  			Valu("r4", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 232, nil, "ptr", "mem"),
   454  			Valu("r5", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 240, nil, "ptr", "mem"),
   455  			Valu("r6", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 248, nil, "ptr", "mem"),
   456  			Valu("r7", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 256, nil, "ptr", "mem"),
   457  			Valu("r8", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 264, nil, "ptr", "mem"),
   458  			Valu("r9", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 272, nil, "ptr", "mem"),
   459  			Valu("r10", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 280, nil, "ptr", "mem"),
   460  			Valu("r11", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 288, nil, "ptr", "mem"),
   461  			Valu("r12", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 296, nil, "ptr", "mem"),
   462  			Valu("r13", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 304, nil, "ptr", "mem"),
   463  			Valu("r14", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 312, nil, "ptr", "mem"),
   464  			Valu("r15", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 320, nil, "ptr", "mem"),
   465  			Valu("sum0", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r0", "r1"),
   466  			Valu("sum1", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r2", "r3"),
   467  			Valu("sum2", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r4", "r5"),
   468  			Valu("sum3", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r6", "r7"),
   469  			Valu("sum4", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r8", "r9"),
   470  			Valu("sum5", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r10", "r11"),
   471  			Valu("sum6", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r12", "r13"),
   472  			Valu("sum7", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r14", "r15"),
   473  			Valu("store0", ssaop.OpAMD64MOVQstore, types.TypeMem, 400, nil, "ptr", "sum0", "mem"),
   474  			Valu("store1", ssaop.OpAMD64MOVQstore, types.TypeMem, 408, nil, "ptr", "sum1", "store0"),
   475  			Valu("store2", ssaop.OpAMD64MOVQstore, types.TypeMem, 416, nil, "ptr", "sum2", "store1"),
   476  			Valu("store3", ssaop.OpAMD64MOVQstore, types.TypeMem, 424, nil, "ptr", "sum3", "store2"),
   477  			Valu("store4", ssaop.OpAMD64MOVQstore, types.TypeMem, 432, nil, "ptr", "sum4", "store3"),
   478  			Valu("store5", ssaop.OpAMD64MOVQstore, types.TypeMem, 440, nil, "ptr", "sum5", "store4"),
   479  			Valu("store6", ssaop.OpAMD64MOVQstore, types.TypeMem, 448, nil, "ptr", "sum6", "store5"),
   480  			Valu("store7", ssaop.OpAMD64MOVQstore, types.TypeMem, 456, nil, "ptr", "sum7", "store6"),
   481  			Goto("merge"),
   482  		),
   483  		Bloc("merge",
   484  			// One phi (for v0)
   485  			Valu("p0", ssaop.OpPhi, c.config.Types.Int64, 0, nil, "v0", "v0"),
   486  			// New values to evict the most distant uses in the end of this block
   487  			Valu("n0", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 500, nil, "ptr", "mem"),
   488  			Valu("n1", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 508, nil, "ptr", "mem"),
   489  			Valu("n2", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 516, nil, "ptr", "mem"),
   490  			Valu("n3", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 524, nil, "ptr", "mem"),
   491  			Valu("n4", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 532, nil, "ptr", "mem"),
   492  			Valu("n5", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 540, nil, "ptr", "mem"),
   493  			Valu("n6", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 548, nil, "ptr", "mem"),
   494  			Valu("n7", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 556, nil, "ptr", "mem"),
   495  			Valu("n8", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 564, nil, "ptr", "mem"),
   496  			Valu("n9", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 572, nil, "ptr", "mem"),
   497  			Valu("n10", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 580, nil, "ptr", "mem"),
   498  			Valu("n11", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 588, nil, "ptr", "mem"),
   499  			Valu("n12", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 596, nil, "ptr", "mem"),
   500  			Valu("n13", ssaop.OpAMD64MOVQload, c.config.Types.Int64, 604, nil, "ptr", "mem"),
   501  			// Normal uses before we start to evict / clean up startRegs mask
   502  			Valu("a0", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n0", "n1"),
   503  			Valu("a1", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n2", "n3"),
   504  			Valu("a2", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n4", "n5"),
   505  			Valu("a3", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n6", "n7"),
   506  			Valu("a4", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n8", "n9"),
   507  			Valu("a5", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n10", "n11"),
   508  			Valu("a6", ssaop.OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n12", "n13"),
   509  			Valu("s0", ssaop.OpAMD64MOVQstore, types.TypeMem, 0, nil, "ptr", "a0", "mem"),
   510  			Valu("s1", ssaop.OpAMD64MOVQstore, types.TypeMem, 8, nil, "ptr", "a1", "s0"),
   511  			Valu("s2", ssaop.OpAMD64MOVQstore, types.TypeMem, 16, nil, "ptr", "a2", "s1"),
   512  			Valu("s3", ssaop.OpAMD64MOVQstore, types.TypeMem, 24, nil, "ptr", "a3", "s2"),
   513  			Valu("s4", ssaop.OpAMD64MOVQstore, types.TypeMem, 32, nil, "ptr", "a4", "s3"),
   514  			Valu("s5", ssaop.OpAMD64MOVQstore, types.TypeMem, 40, nil, "ptr", "a5", "s4"),
   515  			Valu("s6", ssaop.OpAMD64MOVQstore, types.TypeMem, 48, nil, "ptr", "a6", "s5"),
   516  			// The distant uses - to be evicted and cleaned up from the startRegs mask
   517  			Valu("t0", ssaop.OpAMD64MOVQstore, types.TypeMem, 100, nil, "ptr", "u0", "s6"),
   518  			Valu("t1", ssaop.OpAMD64MOVQstore, types.TypeMem, 108, nil, "ptr", "u1", "t0"),
   519  			Valu("t2", ssaop.OpAMD64MOVQstore, types.TypeMem, 116, nil, "ptr", "u2", "t1"),
   520  			Valu("t3", ssaop.OpAMD64MOVQstore, types.TypeMem, 124, nil, "ptr", "u3", "t2"),
   521  			Valu("t4", ssaop.OpAMD64MOVQstore, types.TypeMem, 132, nil, "ptr", "u4", "t3"),
   522  			Valu("t5", ssaop.OpAMD64MOVQstore, types.TypeMem, 140, nil, "ptr", "u5", "t4"),
   523  			Valu("t6", ssaop.OpAMD64MOVQstore, types.TypeMem, 148, nil, "ptr", "u6", "t5"),
   524  			Valu("t7", ssaop.OpAMD64MOVQstore, types.TypeMem, 156, nil, "ptr", "u7", "t6"),
   525  			Exit("t7"),
   526  		),
   527  	)
   528  
   529  	regalloc(f.f)
   530  	checkFunc(f.f)
   531  
   532  	leftLoadCount := numOps(f.blocks["left"], ssaop.OpLoadReg)
   533  	rightLoadCount := numOps(f.blocks["right"], ssaop.OpLoadReg)
   534  	t.Logf("OpLoadReg count in left: %d, right: %d", leftLoadCount, rightLoadCount)
   535  
   536  	// Without startRegs mask cleanup we would have some dead LoadRegs added by shuffle
   537  	if rightLoadCount > 8 {
   538  		t.Errorf("expected <= 8 OpLoadReg in right block (got %d)", rightLoadCount)
   539  	}
   540  }
   541  

View as plain text