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

View as plain text