// 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/obj/x86" "fmt" "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 G register is never reloaded from spill (spill of G is okay) // See #25504 func TestNoGetgLoadReg(t *testing.T) { /* Original: func fff3(i int) *g { gee := getg() if i == 0 { fff() } return gee // here } */ c := testConfigARM64(t) f := c.Fun("b1", Bloc("b1", Valu("v1", OpInitMem, types.TypeMem, 0, nil), Valu("v6", OpArg, c.config.Types.Int64, 0, c.Temp(c.config.Types.Int64)), Valu("v8", OpGetG, c.config.Types.Int64.PtrTo(), 0, nil, "v1"), Valu("v11", OpARM64CMPconst, types.TypeFlags, 0, nil, "v6"), Eq("v11", "b2", "b4"), ), Bloc("b4", Goto("b3"), ), Bloc("b3", Valu("v14", OpPhi, types.TypeMem, 0, nil, "v1", "v12"), Valu("sb", OpSB, c.config.Types.Uintptr, 0, nil), Valu("v16", OpARM64MOVDstore, types.TypeMem, 0, nil, "v8", "sb", "v14"), Exit("v16"), ), Bloc("b2", Valu("v12", OpARM64CALLstatic, types.TypeMem, 0, AuxCallLSym("_"), "v1"), Goto("b3"), ), ) regalloc(f.f) checkFunc(f.f) // Double-check that we never restore to the G register. Regalloc should catch it, but check again anyway. r := f.f.RegAlloc for _, b := range f.blocks { for _, v := range b.Values { if v.Op == OpLoadReg && r[v.ID].String() == "g" { t.Errorf("Saw OpLoadReg targeting g register: %s", v.LongString()) } } } } // 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.Temp(c.config.Types.Int64)), Valu("cond", OpArg, c.config.Types.Bool, 0, c.Temp(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, AuxCallLSym("_"), "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.Temp(c.config.Types.Int64)), Valu("p", OpArg, c.config.Types.Int64.PtrTo(), 0, c.Temp(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, AuxCallLSym("_"), "mem2"), Exit("mem3"), ), Bloc("exit2", // store after call, y must be loaded from a spill location Valu("mem4", OpAMD64CALLstatic, types.TypeMem, 0, AuxCallLSym("_"), "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.Temp(c.config.Types.Int64)), Valu("p", OpArg, c.config.Types.Int64.PtrTo(), 0, c.Temp(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, AuxCallLSym("_"), "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, AuxCallLSym("_"), "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 TestClobbersArg0(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.Temp(c.config.Types.Int64.PtrTo())), Valu("dst", OpArg, c.config.Types.Int64.PtrTo().PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo().PtrTo())), Valu("zero", OpAMD64LoweredZeroLoop, types.TypeMem, 256, nil, "ptr", "mem"), Valu("store", OpAMD64MOVQstore, types.TypeMem, 0, nil, "dst", "ptr", "zero"), Exit("store"))) flagalloc(f.f) regalloc(f.f) checkFunc(f.f) // LoweredZeroLoop clobbers its argument, so there must be a copy of "ptr" somewhere // so we still have that value available at "store". if n := numCopies(f.blocks["entry"]); n != 1 { fmt.Printf("%s\n", f.f.String()) t.Errorf("got %d copies, want 1", n) } } func TestClobbersArg1(t *testing.T) { c := testConfig(t) f := c.Fun("entry", Bloc("entry", Valu("mem", OpInitMem, types.TypeMem, 0, nil), Valu("src", OpArg, c.config.Types.Int64.PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo())), Valu("dst", OpArg, c.config.Types.Int64.PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo())), Valu("use1", OpArg, c.config.Types.Int64.PtrTo().PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo().PtrTo())), Valu("use2", OpArg, c.config.Types.Int64.PtrTo().PtrTo(), 0, c.Temp(c.config.Types.Int64.PtrTo().PtrTo())), Valu("move", OpAMD64LoweredMoveLoop, types.TypeMem, 256, nil, "dst", "src", "mem"), Valu("store1", OpAMD64MOVQstore, types.TypeMem, 0, nil, "use1", "src", "move"), Valu("store2", OpAMD64MOVQstore, types.TypeMem, 0, nil, "use2", "dst", "store1"), Exit("store2"))) flagalloc(f.f) regalloc(f.f) checkFunc(f.f) // LoweredMoveLoop clobbers its arguments, so there must be a copy of "src" and "dst" somewhere // so we still have that value available at the stores. if n := numCopies(f.blocks["entry"]); n != 2 { fmt.Printf("%s\n", f.f.String()) t.Errorf("got %d copies, want 2", n) } } func TestNoRematerializeDeadConstant(t *testing.T) { c := testConfigARM64(t) f := c.Fun("b1", Bloc("b1", Valu("mem", OpInitMem, types.TypeMem, 0, nil), Valu("addr", OpArg, c.config.Types.Int32.PtrTo(), 0, c.Temp(c.config.Types.Int32.PtrTo())), Valu("const", OpARM64MOVDconst, c.config.Types.Int32, -1, nil), // Original constant Valu("cmp", OpARM64CMPconst, types.TypeFlags, 0, nil, "const"), Goto("b2"), ), Bloc("b2", Valu("phi_mem", OpPhi, types.TypeMem, 0, nil, "mem", "callmem"), Eq("cmp", "b6", "b3"), ), Bloc("b3", Valu("call", OpARM64CALLstatic, types.TypeMem, 0, AuxCallLSym("_"), "phi_mem"), Valu("callmem", OpSelectN, types.TypeMem, 0, nil, "call"), Eq("cmp", "b5", "b4"), ), Bloc("b4", // A block where we don't really need to rematerialize the constant -1 Goto("b2"), ), Bloc("b5", Valu("user", OpAMD64MOVQstore, types.TypeMem, 0, nil, "addr", "const", "callmem"), Exit("user"), ), Bloc("b6", Exit("phi_mem"), ), ) regalloc(f.f) checkFunc(f.f) // Check that in block b4, there's no dead rematerialization of the constant -1 for _, v := range f.blocks["b4"].Values { if v.Op == OpARM64MOVDconst && v.AuxInt == -1 { t.Errorf("constant -1 rematerialized in loop block b4: %s", v.LongString()) } } } func numSpills(b *Block) int { return numOps(b, OpStoreReg) } func numCopies(b *Block) int { return numOps(b, OpCopy) } func numOps(b *Block, op Op) int { n := 0 for _, v := range b.Values { if v.Op == op { n++ } } return n } func TestRematerializeableRegCompatible(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.Int32, 1, nil), Valu("a", OpAMD64POR, c.config.Types.Float32, 0, nil, "x", "x"), Valu("res", OpMakeResult, types.NewResults([]*types.Type{c.config.Types.Float32, types.TypeMem}), 0, nil, "a", "mem"), Ret("res"), ), ) regalloc(f.f) checkFunc(f.f) moveFound := false for _, v := range f.f.Blocks[0].Values { if v.Op == OpCopy && x86.REG_X0 <= v.Reg() && v.Reg() <= x86.REG_X31 { moveFound = true } } if !moveFound { t.Errorf("Expects an Copy to be issued, but got: %+v", f.f) } } func TestPreload(t *testing.T) { c := testConfig(t) // amd64 has 13 general registers. We use 1 for ptr and 12 for x0-11. // They all contain live values at the end of the entry block. f := c.Fun("entry", Bloc("entry", Valu("ptr", OpArgIntReg, c.config.Types.Int8.PtrTo(), 0, &AuxNameOffset{Name: c.Temp(c.config.Types.Int8.PtrTo()), Offset: 0}), Valu("mem", OpInitMem, types.TypeMem, 0, nil), Valu("x0", OpAMD64MOVBload, c.config.Types.Int8, 0, nil, "ptr", "mem"), Valu("x1", OpAMD64MOVBload, c.config.Types.Int8, 1, nil, "ptr", "mem"), Valu("x2", OpAMD64MOVBload, c.config.Types.Int8, 2, nil, "ptr", "mem"), Valu("x3", OpAMD64MOVBload, c.config.Types.Int8, 3, nil, "ptr", "mem"), Valu("x4", OpAMD64MOVBload, c.config.Types.Int8, 4, nil, "ptr", "mem"), Valu("x5", OpAMD64MOVBload, c.config.Types.Int8, 5, nil, "ptr", "mem"), Valu("x6", OpAMD64MOVBload, c.config.Types.Int8, 6, nil, "ptr", "mem"), Valu("x7", OpAMD64MOVBload, c.config.Types.Int8, 7, nil, "ptr", "mem"), Valu("x8", OpAMD64MOVBload, c.config.Types.Int8, 8, nil, "ptr", "mem"), Valu("x9", OpAMD64MOVBload, c.config.Types.Int8, 9, nil, "ptr", "mem"), Valu("x10", OpAMD64MOVBload, c.config.Types.Int8, 10, nil, "ptr", "mem"), Valu("x11", OpAMD64MOVBload, c.config.Types.Int8, 11, nil, "ptr", "mem"), Valu("init", OpAMD64MOVQconst, c.config.Types.Int64, 0, nil), Goto("loopHead"), ), Bloc("loopHead", Valu("phi", OpPhi, c.config.Types.Int64, 0, nil, "init", "next"), Valu("test", OpAMD64CMPQconst, types.TypeFlags, 10, nil, "phi"), Lt("test", "loopBody", "exit"), ), Bloc("loopBody", Valu("next", OpAMD64ADDQconst, c.config.Types.Int64, 1, nil, "phi"), Goto("loopHead"), ), Bloc("exit", Valu("m0", OpAMD64MOVBstore, types.TypeMem, 0, nil, "ptr", "x0", "mem"), Valu("m1", OpAMD64MOVBstore, types.TypeMem, 1, nil, "ptr", "x1", "m0"), Valu("m2", OpAMD64MOVBstore, types.TypeMem, 2, nil, "ptr", "x2", "m1"), Valu("m3", OpAMD64MOVBstore, types.TypeMem, 3, nil, "ptr", "x3", "m2"), Valu("m4", OpAMD64MOVBstore, types.TypeMem, 4, nil, "ptr", "x4", "m3"), Valu("m5", OpAMD64MOVBstore, types.TypeMem, 5, nil, "ptr", "x5", "m4"), Valu("m6", OpAMD64MOVBstore, types.TypeMem, 6, nil, "ptr", "x6", "m5"), Valu("m7", OpAMD64MOVBstore, types.TypeMem, 7, nil, "ptr", "x7", "m6"), Valu("m8", OpAMD64MOVBstore, types.TypeMem, 8, nil, "ptr", "x8", "m7"), Valu("m9", OpAMD64MOVBstore, types.TypeMem, 9, nil, "ptr", "x9", "m8"), Valu("m10", OpAMD64MOVBstore, types.TypeMem, 10, nil, "ptr", "x10", "m9"), Valu("m11", OpAMD64MOVBstore, types.TypeMem, 11, nil, "ptr", "x11", "m10"), Ret("m11"), ), ) f.f.Blocks[1].Likely = BranchLikely regalloc(f.f) checkFunc(f.f) v := f.values["phi"] loc := f.f.RegAlloc[v.ID] if _, ok := loc.(*Register); !ok { t.Errorf("Expects to use a register for phi, but got: %s\n%v", loc, f.f) } } // TestStartRegsDrop tests dropping physical register bits from startRegs preventing // unnecessary OpLoadReg on edges, such as edge right->merge in this example. // // entry -> left (no pressure) -> merge (startRegs updated) -> exit // -> right (pressure) -> func TestStartRegsDrop(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.Temp(c.config.Types.Int64)), // Non-phi values that will be originally in registers and get into startRegs[merge] Valu("u0", OpAMD64MOVQload, c.config.Types.Int64, 100, nil, "ptr", "mem"), Valu("u1", OpAMD64MOVQload, c.config.Types.Int64, 108, nil, "ptr", "mem"), Valu("u2", OpAMD64MOVQload, c.config.Types.Int64, 116, nil, "ptr", "mem"), Valu("u3", OpAMD64MOVQload, c.config.Types.Int64, 124, nil, "ptr", "mem"), Valu("u4", OpAMD64MOVQload, c.config.Types.Int64, 132, nil, "ptr", "mem"), Valu("u5", OpAMD64MOVQload, c.config.Types.Int64, 140, nil, "ptr", "mem"), Valu("u6", OpAMD64MOVQload, c.config.Types.Int64, 148, nil, "ptr", "mem"), Valu("u7", OpAMD64MOVQload, c.config.Types.Int64, 156, nil, "ptr", "mem"), // Some phi value Valu("v0", OpAMD64MOVQload, c.config.Types.Int64, 0, nil, "ptr", "mem"), // Branch Valu("cond", OpAMD64MOVLconst, c.config.Types.Int32, 1, nil), Valu("test", OpAMD64TESTL, types.TypeFlags, 0, nil, "cond", "cond"), Eq("test", "left", "right"), ), Bloc("left", // No register pressure - values stay in their original registers // This is the "primary" predecessor of merge block (less spill live) Goto("merge"), ), Bloc("right", // Create some register pressure. We want to make difference in this block's endRegs, // so that at shuffle stage, the updated startRegs of merge block will be measurable. Valu("r0", OpAMD64MOVQload, c.config.Types.Int64, 200, nil, "ptr", "mem"), Valu("r1", OpAMD64MOVQload, c.config.Types.Int64, 208, nil, "ptr", "mem"), Valu("r2", OpAMD64MOVQload, c.config.Types.Int64, 216, nil, "ptr", "mem"), Valu("r3", OpAMD64MOVQload, c.config.Types.Int64, 224, nil, "ptr", "mem"), Valu("r4", OpAMD64MOVQload, c.config.Types.Int64, 232, nil, "ptr", "mem"), Valu("r5", OpAMD64MOVQload, c.config.Types.Int64, 240, nil, "ptr", "mem"), Valu("r6", OpAMD64MOVQload, c.config.Types.Int64, 248, nil, "ptr", "mem"), Valu("r7", OpAMD64MOVQload, c.config.Types.Int64, 256, nil, "ptr", "mem"), Valu("r8", OpAMD64MOVQload, c.config.Types.Int64, 264, nil, "ptr", "mem"), Valu("r9", OpAMD64MOVQload, c.config.Types.Int64, 272, nil, "ptr", "mem"), Valu("r10", OpAMD64MOVQload, c.config.Types.Int64, 280, nil, "ptr", "mem"), Valu("r11", OpAMD64MOVQload, c.config.Types.Int64, 288, nil, "ptr", "mem"), Valu("r12", OpAMD64MOVQload, c.config.Types.Int64, 296, nil, "ptr", "mem"), Valu("r13", OpAMD64MOVQload, c.config.Types.Int64, 304, nil, "ptr", "mem"), Valu("r14", OpAMD64MOVQload, c.config.Types.Int64, 312, nil, "ptr", "mem"), Valu("r15", OpAMD64MOVQload, c.config.Types.Int64, 320, nil, "ptr", "mem"), Valu("sum0", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r0", "r1"), Valu("sum1", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r2", "r3"), Valu("sum2", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r4", "r5"), Valu("sum3", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r6", "r7"), Valu("sum4", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r8", "r9"), Valu("sum5", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r10", "r11"), Valu("sum6", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r12", "r13"), Valu("sum7", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "r14", "r15"), Valu("store0", OpAMD64MOVQstore, types.TypeMem, 400, nil, "ptr", "sum0", "mem"), Valu("store1", OpAMD64MOVQstore, types.TypeMem, 408, nil, "ptr", "sum1", "store0"), Valu("store2", OpAMD64MOVQstore, types.TypeMem, 416, nil, "ptr", "sum2", "store1"), Valu("store3", OpAMD64MOVQstore, types.TypeMem, 424, nil, "ptr", "sum3", "store2"), Valu("store4", OpAMD64MOVQstore, types.TypeMem, 432, nil, "ptr", "sum4", "store3"), Valu("store5", OpAMD64MOVQstore, types.TypeMem, 440, nil, "ptr", "sum5", "store4"), Valu("store6", OpAMD64MOVQstore, types.TypeMem, 448, nil, "ptr", "sum6", "store5"), Valu("store7", OpAMD64MOVQstore, types.TypeMem, 456, nil, "ptr", "sum7", "store6"), Goto("merge"), ), Bloc("merge", // One phi (for v0) Valu("p0", OpPhi, c.config.Types.Int64, 0, nil, "v0", "v0"), // New values to evict the most distant uses in the end of this block Valu("n0", OpAMD64MOVQload, c.config.Types.Int64, 500, nil, "ptr", "mem"), Valu("n1", OpAMD64MOVQload, c.config.Types.Int64, 508, nil, "ptr", "mem"), Valu("n2", OpAMD64MOVQload, c.config.Types.Int64, 516, nil, "ptr", "mem"), Valu("n3", OpAMD64MOVQload, c.config.Types.Int64, 524, nil, "ptr", "mem"), Valu("n4", OpAMD64MOVQload, c.config.Types.Int64, 532, nil, "ptr", "mem"), Valu("n5", OpAMD64MOVQload, c.config.Types.Int64, 540, nil, "ptr", "mem"), Valu("n6", OpAMD64MOVQload, c.config.Types.Int64, 548, nil, "ptr", "mem"), Valu("n7", OpAMD64MOVQload, c.config.Types.Int64, 556, nil, "ptr", "mem"), Valu("n8", OpAMD64MOVQload, c.config.Types.Int64, 564, nil, "ptr", "mem"), Valu("n9", OpAMD64MOVQload, c.config.Types.Int64, 572, nil, "ptr", "mem"), Valu("n10", OpAMD64MOVQload, c.config.Types.Int64, 580, nil, "ptr", "mem"), Valu("n11", OpAMD64MOVQload, c.config.Types.Int64, 588, nil, "ptr", "mem"), Valu("n12", OpAMD64MOVQload, c.config.Types.Int64, 596, nil, "ptr", "mem"), Valu("n13", OpAMD64MOVQload, c.config.Types.Int64, 604, nil, "ptr", "mem"), // Normal uses before we start to evict / clean up startRegs mask Valu("a0", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n0", "n1"), Valu("a1", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n2", "n3"), Valu("a2", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n4", "n5"), Valu("a3", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n6", "n7"), Valu("a4", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n8", "n9"), Valu("a5", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n10", "n11"), Valu("a6", OpAMD64ADDQ, c.config.Types.Int64, 0, nil, "n12", "n13"), Valu("s0", OpAMD64MOVQstore, types.TypeMem, 0, nil, "ptr", "a0", "mem"), Valu("s1", OpAMD64MOVQstore, types.TypeMem, 8, nil, "ptr", "a1", "s0"), Valu("s2", OpAMD64MOVQstore, types.TypeMem, 16, nil, "ptr", "a2", "s1"), Valu("s3", OpAMD64MOVQstore, types.TypeMem, 24, nil, "ptr", "a3", "s2"), Valu("s4", OpAMD64MOVQstore, types.TypeMem, 32, nil, "ptr", "a4", "s3"), Valu("s5", OpAMD64MOVQstore, types.TypeMem, 40, nil, "ptr", "a5", "s4"), Valu("s6", OpAMD64MOVQstore, types.TypeMem, 48, nil, "ptr", "a6", "s5"), // The distant uses - to be evicted and cleaned up from the startRegs mask Valu("t0", OpAMD64MOVQstore, types.TypeMem, 100, nil, "ptr", "u0", "s6"), Valu("t1", OpAMD64MOVQstore, types.TypeMem, 108, nil, "ptr", "u1", "t0"), Valu("t2", OpAMD64MOVQstore, types.TypeMem, 116, nil, "ptr", "u2", "t1"), Valu("t3", OpAMD64MOVQstore, types.TypeMem, 124, nil, "ptr", "u3", "t2"), Valu("t4", OpAMD64MOVQstore, types.TypeMem, 132, nil, "ptr", "u4", "t3"), Valu("t5", OpAMD64MOVQstore, types.TypeMem, 140, nil, "ptr", "u5", "t4"), Valu("t6", OpAMD64MOVQstore, types.TypeMem, 148, nil, "ptr", "u6", "t5"), Valu("t7", OpAMD64MOVQstore, types.TypeMem, 156, nil, "ptr", "u7", "t6"), Exit("t7"), ), ) regalloc(f.f) checkFunc(f.f) leftLoadCount := numOps(f.blocks["left"], OpLoadReg) rightLoadCount := numOps(f.blocks["right"], OpLoadReg) t.Logf("OpLoadReg count in left: %d, right: %d", leftLoadCount, rightLoadCount) // Without startRegs mask cleanup we would have some dead LoadRegs added by shuffle if rightLoadCount > 8 { t.Errorf("expected <= 8 OpLoadReg in right block (got %d)", rightLoadCount) } }