551 lines
15 KiB
Go
551 lines
15 KiB
Go
package regalloc
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
)
|
|
|
|
func TestAllocator_livenessAnalysis(t *testing.T) {
|
|
realReg, realReg2 := FromRealReg(50, RegTypeInt), FromRealReg(100, RegTypeInt)
|
|
const phiVReg = 12345
|
|
for _, tc := range []struct {
|
|
name string
|
|
setup func() Function
|
|
exp map[int]*blockInfo
|
|
}{
|
|
{
|
|
name: "single block",
|
|
setup: func() Function {
|
|
return newMockFunction(
|
|
newMockBlock(0,
|
|
newMockInstr().def(1),
|
|
newMockInstr().use(1).def(2),
|
|
).entry(),
|
|
)
|
|
},
|
|
exp: map[int]*blockInfo{
|
|
0: {
|
|
defs: map[VReg]programCounter{2: pcDefOffset + pcStride, 1: pcDefOffset},
|
|
lastUses: map[VReg]programCounter{1: pcStride + pcUseOffset},
|
|
kills: map[VReg]programCounter{1: pcStride + pcUseOffset},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "straight",
|
|
// b0 -> b1 -> b2
|
|
setup: func() Function {
|
|
b0 := newMockBlock(0,
|
|
newMockInstr().def(1000, 1, 2),
|
|
newMockInstr().use(1000),
|
|
newMockInstr().use(1, 2).def(3),
|
|
).entry()
|
|
b1 := newMockBlock(1,
|
|
newMockInstr().def(realReg),
|
|
newMockInstr().use(3).def(4, 5),
|
|
newMockInstr().use(realReg),
|
|
)
|
|
b2 := newMockBlock(2,
|
|
newMockInstr().use(3, 4, 5),
|
|
)
|
|
b2.addPred(b1)
|
|
b1.addPred(b0)
|
|
return newMockFunction(b0, b1, b2)
|
|
},
|
|
exp: map[int]*blockInfo{
|
|
0: {
|
|
defs: map[VReg]programCounter{
|
|
1000: pcDefOffset,
|
|
1: pcDefOffset,
|
|
2: pcDefOffset,
|
|
3: pcStride*2 + pcDefOffset,
|
|
},
|
|
lastUses: map[VReg]programCounter{
|
|
1000: pcStride + pcUseOffset,
|
|
1: pcStride*2 + pcUseOffset,
|
|
2: pcStride*2 + pcUseOffset,
|
|
},
|
|
liveOuts: map[VReg]struct{}{3: {}},
|
|
kills: map[VReg]programCounter{
|
|
1000: pcStride + pcUseOffset,
|
|
1: pcStride*2 + pcUseOffset,
|
|
2: pcStride*2 + pcUseOffset,
|
|
},
|
|
},
|
|
1: {
|
|
liveIns: map[VReg]struct{}{3: {}},
|
|
liveOuts: map[VReg]struct{}{3: {}, 4: {}, 5: {}},
|
|
lastUses: map[VReg]programCounter{
|
|
3: pcStride + pcUseOffset,
|
|
},
|
|
defs: map[VReg]programCounter{
|
|
4: pcStride + pcDefOffset,
|
|
5: pcStride + pcDefOffset,
|
|
},
|
|
realRegUses: map[VReg][]programCounter{
|
|
realReg: {pcStride*2 + pcUseOffset},
|
|
},
|
|
realRegDefs: map[VReg][]programCounter{
|
|
realReg: {pcDefOffset},
|
|
},
|
|
},
|
|
2: {
|
|
liveIns: map[VReg]struct{}{3: {}, 4: {}, 5: {}},
|
|
lastUses: map[VReg]programCounter{3: pcUseOffset, 4: pcUseOffset, 5: pcUseOffset},
|
|
kills: map[VReg]programCounter{3: pcUseOffset, 4: pcUseOffset, 5: pcUseOffset},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "diamond",
|
|
// 0 v1000<-, v1<-, v2<-
|
|
// / \
|
|
// 1 2
|
|
// \ /
|
|
// 3
|
|
setup: func() Function {
|
|
b0 := newMockBlock(0,
|
|
newMockInstr().def(1000),
|
|
newMockInstr().def(1),
|
|
newMockInstr().def(2),
|
|
).entry()
|
|
b1 := newMockBlock(1,
|
|
newMockInstr().def(realReg).use(1),
|
|
newMockInstr().use(realReg),
|
|
newMockInstr().def(realReg2),
|
|
newMockInstr().use(realReg2),
|
|
newMockInstr().def(realReg),
|
|
newMockInstr().use(realReg),
|
|
)
|
|
b2 := newMockBlock(2,
|
|
newMockInstr().use(2, realReg2),
|
|
)
|
|
b3 := newMockBlock(3,
|
|
newMockInstr().use(1000),
|
|
)
|
|
b3.addPred(b1)
|
|
b3.addPred(b2)
|
|
b1.addPred(b0)
|
|
b2.addPred(b0)
|
|
return newMockFunction(b0, b1, b2, b3)
|
|
},
|
|
exp: map[int]*blockInfo{
|
|
0: {
|
|
defs: map[VReg]programCounter{
|
|
1000: pcDefOffset,
|
|
1: pcStride + pcDefOffset,
|
|
2: pcStride*2 + pcDefOffset,
|
|
},
|
|
liveOuts: map[VReg]struct{}{1000: {}, 1: {}, 2: {}},
|
|
},
|
|
1: {
|
|
liveIns: map[VReg]struct{}{1000: {}, 1: {}},
|
|
liveOuts: map[VReg]struct{}{1000: {}},
|
|
lastUses: map[VReg]programCounter{1: pcUseOffset},
|
|
kills: map[VReg]programCounter{1: pcUseOffset},
|
|
realRegDefs: map[VReg][]programCounter{
|
|
realReg: {pcDefOffset, pcStride*4 + pcDefOffset},
|
|
realReg2: {pcStride*2 + pcDefOffset},
|
|
},
|
|
realRegUses: map[VReg][]programCounter{
|
|
realReg: {pcStride + pcUseOffset, pcStride*5 + pcUseOffset},
|
|
realReg2: {pcStride*3 + pcUseOffset},
|
|
},
|
|
},
|
|
2: {
|
|
liveIns: map[VReg]struct{}{1000: {}, 2: {}},
|
|
liveOuts: map[VReg]struct{}{1000: {}},
|
|
lastUses: map[VReg]programCounter{2: pcUseOffset},
|
|
kills: map[VReg]programCounter{2: pcUseOffset},
|
|
realRegUses: map[VReg][]programCounter{realReg2: {pcUseOffset}},
|
|
realRegDefs: map[VReg][]programCounter{realReg2: {0}},
|
|
},
|
|
3: {
|
|
liveIns: map[VReg]struct{}{1000: {}},
|
|
lastUses: map[VReg]programCounter{1000: pcUseOffset},
|
|
kills: map[VReg]programCounter{1000: pcUseOffset},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "phis",
|
|
// 0
|
|
// / \
|
|
// 1 \
|
|
// | |
|
|
// 2 3
|
|
// \ /
|
|
// 4 use v5 (phi node) defined at both 1 and 3.
|
|
setup: func() Function {
|
|
b0 := newMockBlock(0,
|
|
newMockInstr().def(1000, 2000, 3000),
|
|
).entry()
|
|
b1 := newMockBlock(1,
|
|
newMockInstr().def(phiVReg).use(2000),
|
|
)
|
|
b2 := newMockBlock(2)
|
|
b3 := newMockBlock(3,
|
|
newMockInstr().def(phiVReg).use(1000),
|
|
)
|
|
b4 := newMockBlock(
|
|
4, newMockInstr().use(phiVReg, 3000),
|
|
)
|
|
b4.addPred(b2)
|
|
b4.addPred(b3)
|
|
b3.addPred(b0)
|
|
b2.addPred(b1)
|
|
b1.addPred(b0)
|
|
return newMockFunction(b0, b1, b2, b3, b4)
|
|
},
|
|
exp: map[int]*blockInfo{
|
|
0: {
|
|
defs: map[VReg]programCounter{1000: pcDefOffset, 2000: pcDefOffset, 3000: pcDefOffset},
|
|
liveOuts: map[VReg]struct{}{1000: {}, 2000: {}, 3000: {}},
|
|
},
|
|
1: {
|
|
liveIns: map[VReg]struct{}{2000: {}, 3000: {}},
|
|
liveOuts: map[VReg]struct{}{phiVReg: {}, 3000: {}},
|
|
defs: map[VReg]programCounter{phiVReg: pcDefOffset},
|
|
lastUses: map[VReg]programCounter{2000: pcUseOffset},
|
|
kills: map[VReg]programCounter{2000: pcUseOffset},
|
|
},
|
|
2: {
|
|
liveIns: map[VReg]struct{}{phiVReg: {}, 3000: {}},
|
|
liveOuts: map[VReg]struct{}{phiVReg: {}, 3000: {}},
|
|
},
|
|
3: {
|
|
liveIns: map[VReg]struct{}{1000: {}, 3000: {}},
|
|
liveOuts: map[VReg]struct{}{phiVReg: {}, 3000: {}},
|
|
defs: map[VReg]programCounter{phiVReg: pcDefOffset},
|
|
lastUses: map[VReg]programCounter{1000: pcUseOffset},
|
|
kills: map[VReg]programCounter{1000: pcUseOffset},
|
|
},
|
|
4: {
|
|
liveIns: map[VReg]struct{}{phiVReg: {}, 3000: {}},
|
|
lastUses: map[VReg]programCounter{phiVReg: pcUseOffset, 3000: pcUseOffset},
|
|
kills: map[VReg]programCounter{phiVReg: pcUseOffset, 3000: pcUseOffset},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "loop",
|
|
// 0 -> 1 -> 2
|
|
// ^ |
|
|
// | v
|
|
// 4 <- 3 -> 5
|
|
setup: func() Function {
|
|
b0 := newMockBlock(0,
|
|
newMockInstr().def(1),
|
|
newMockInstr().def(phiVReg).use(1),
|
|
).entry()
|
|
b1 := newMockBlock(1,
|
|
newMockInstr().def(9999),
|
|
)
|
|
b2 := newMockBlock(2,
|
|
newMockInstr().def(100).use(phiVReg, 9999),
|
|
)
|
|
b3 := newMockBlock(3,
|
|
newMockInstr().def(54321),
|
|
newMockInstr().use(100),
|
|
)
|
|
b4 := newMockBlock(4,
|
|
newMockInstr().def(phiVReg).use(54321),
|
|
)
|
|
b5 := newMockBlock(
|
|
4, newMockInstr().use(54321),
|
|
)
|
|
b1.addPred(b0)
|
|
b1.addPred(b4)
|
|
b2.addPred(b1)
|
|
b3.addPred(b2)
|
|
b4.addPred(b3)
|
|
b5.addPred(b3)
|
|
return newMockFunction(b0, b1, b2, b3, b4, b5)
|
|
},
|
|
exp: map[int]*blockInfo{
|
|
0: {
|
|
liveIns: map[VReg]struct{}{},
|
|
liveOuts: map[VReg]struct{}{
|
|
phiVReg: {},
|
|
},
|
|
defs: map[VReg]programCounter{
|
|
1: pcDefOffset,
|
|
phiVReg: pcStride + pcDefOffset,
|
|
},
|
|
lastUses: map[VReg]programCounter{
|
|
1: pcStride + pcUseOffset,
|
|
},
|
|
kills: map[VReg]programCounter{
|
|
1: pcStride + pcUseOffset,
|
|
},
|
|
},
|
|
1: {
|
|
liveIns: map[VReg]struct{}{phiVReg: {}},
|
|
liveOuts: map[VReg]struct{}{phiVReg: {}, 9999: {}},
|
|
defs: map[VReg]programCounter{9999: pcDefOffset},
|
|
lastUses: map[VReg]programCounter{},
|
|
kills: map[VReg]programCounter{},
|
|
},
|
|
2: {
|
|
liveIns: map[VReg]struct{}{phiVReg: {}, 9999: {}},
|
|
liveOuts: map[VReg]struct{}{100: {}},
|
|
defs: map[VReg]programCounter{100: pcDefOffset},
|
|
lastUses: map[VReg]programCounter{phiVReg: pcUseOffset, 9999: pcUseOffset},
|
|
kills: map[VReg]programCounter{phiVReg: pcUseOffset, 9999: pcUseOffset},
|
|
},
|
|
3: {
|
|
liveIns: map[VReg]struct{}{100: {}},
|
|
liveOuts: map[VReg]struct{}{54321: {}},
|
|
defs: map[VReg]programCounter{54321: pcDefOffset},
|
|
lastUses: map[VReg]programCounter{100: pcStride + pcUseOffset},
|
|
kills: map[VReg]programCounter{100: pcStride + pcUseOffset},
|
|
},
|
|
4: {
|
|
liveIns: map[VReg]struct{}{54321: {}},
|
|
liveOuts: map[VReg]struct{}{phiVReg: {}},
|
|
defs: map[VReg]programCounter{phiVReg: pcDefOffset},
|
|
lastUses: map[VReg]programCounter{54321: pcUseOffset},
|
|
kills: map[VReg]programCounter{54321: pcUseOffset},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// -----+
|
|
// v |
|
|
// 0 -> 1 -> 2 -> 3 -> 4
|
|
// ^ |
|
|
// +----+
|
|
name: "Fig. 9.2 in paper",
|
|
setup: func() Function {
|
|
b0 := newMockBlock(0, newMockInstr().def(99999)).entry()
|
|
b1 := newMockBlock(1, newMockInstr().use(99999))
|
|
b2 := newMockBlock(2)
|
|
b3 := newMockBlock(3)
|
|
b4 := newMockBlock(4)
|
|
b1.addPred(b0)
|
|
b1.addPred(b2)
|
|
b2.addPred(b1)
|
|
b2.addPred(b3)
|
|
b3.addPred(b2)
|
|
b4.addPred(b3)
|
|
return newMockFunction(b0, b1, b2, b3, b4)
|
|
},
|
|
exp: map[int]*blockInfo{
|
|
0: {
|
|
defs: map[VReg]programCounter{99999: pcDefOffset},
|
|
liveOuts: map[VReg]struct{}{99999: {}},
|
|
},
|
|
1: {
|
|
liveIns: map[VReg]struct{}{99999: {}},
|
|
liveOuts: map[VReg]struct{}{99999: {}},
|
|
lastUses: map[VReg]programCounter{99999: pcUseOffset},
|
|
},
|
|
2: {
|
|
liveIns: map[VReg]struct{}{99999: {}},
|
|
liveOuts: map[VReg]struct{}{99999: {}},
|
|
},
|
|
3: {
|
|
liveIns: map[VReg]struct{}{99999: {}},
|
|
liveOuts: map[VReg]struct{}{99999: {}},
|
|
},
|
|
4: {},
|
|
},
|
|
},
|
|
// 2
|
|
// ^ +----+
|
|
// | v |
|
|
// 0 -> 1 -> 3 -> 4 -> 5 -> 6 -> 9
|
|
// ^ | ^ |
|
|
// | v | |
|
|
// | 7 -> 8 ---+ |
|
|
// | ^ | |
|
|
// | +----+ |
|
|
// +------------------------+
|
|
{
|
|
name: "Fig. 9.1 in paper",
|
|
setup: func() Function {
|
|
b0 := newMockBlock(0).entry()
|
|
b1 := newMockBlock(1)
|
|
b2 := newMockBlock(2)
|
|
b3 := newMockBlock(3,
|
|
newMockInstr().def(100),
|
|
)
|
|
b4 := newMockBlock(4)
|
|
b5 := newMockBlock(5,
|
|
newMockInstr().use(100),
|
|
)
|
|
b6 := newMockBlock(6)
|
|
b7 := newMockBlock(7)
|
|
b8 := newMockBlock(8)
|
|
b9 := newMockBlock(9)
|
|
|
|
b1.addPred(b0)
|
|
b1.addPred(b9)
|
|
|
|
b2.addPred(b1)
|
|
|
|
b3.addPred(b1)
|
|
|
|
b4.addPred(b3)
|
|
|
|
b5.addPred(b4)
|
|
b5.addPred(b6)
|
|
b5.addPred(b8)
|
|
|
|
b6.addPred(b5)
|
|
|
|
b7.addPred(b3)
|
|
b7.addPred(b8)
|
|
|
|
b8.addPred(b7)
|
|
|
|
b9.addPred(b6)
|
|
return newMockFunction(b0, b1, b2, b3, b4, b7, b8, b5, b6, b9)
|
|
},
|
|
exp: map[int]*blockInfo{
|
|
0: {},
|
|
1: {},
|
|
2: {},
|
|
3: {
|
|
liveOuts: map[VReg]struct{}{100: {}},
|
|
defs: map[VReg]programCounter{100: pcDefOffset},
|
|
},
|
|
4: {
|
|
liveIns: map[VReg]struct{}{100: {}},
|
|
liveOuts: map[VReg]struct{}{100: {}},
|
|
},
|
|
5: {
|
|
liveIns: map[VReg]struct{}{100: {}},
|
|
liveOuts: map[VReg]struct{}{100: {}},
|
|
lastUses: map[VReg]programCounter{100: pcUseOffset},
|
|
},
|
|
6: {
|
|
liveIns: map[VReg]struct{}{100: {}},
|
|
liveOuts: map[VReg]struct{}{100: {}},
|
|
},
|
|
7: {
|
|
liveIns: map[VReg]struct{}{100: {}},
|
|
liveOuts: map[VReg]struct{}{100: {}},
|
|
},
|
|
8: {
|
|
liveIns: map[VReg]struct{}{100: {}},
|
|
liveOuts: map[VReg]struct{}{100: {}},
|
|
},
|
|
9: {},
|
|
},
|
|
},
|
|
} {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
f := tc.setup()
|
|
a := NewAllocator(&RegisterInfo{})
|
|
a.livenessAnalysis(f)
|
|
for blockID := range a.blockInfos {
|
|
actual := &a.blockInfos[blockID]
|
|
exp := tc.exp[blockID]
|
|
initMapInInfo(exp)
|
|
require.Equal(t, exp, actual, "\n[exp for block[%d]]\n%s\n[actual for block[%d]]\n%s", blockID, exp, blockID, actual)
|
|
}
|
|
|
|
// Sanity check: buildLiveRanges should not panic.
|
|
a.buildLiveRanges(f)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAllocator_livenessAnalysis_copy(t *testing.T) {
|
|
f := newMockFunction(
|
|
newMockBlock(0,
|
|
newMockInstr().def(1),
|
|
newMockInstr().use(1).def(2).asCopy(),
|
|
).entry(),
|
|
)
|
|
a := NewAllocator(&RegisterInfo{})
|
|
a.livenessAnalysis(f)
|
|
|
|
n1, n2 := a.getOrAllocateNode(1), a.getOrAllocateNode(2)
|
|
require.Equal(t, n2, n1.copyToVReg)
|
|
require.Equal(t, n2.copyFromVReg, n1)
|
|
require.Nil(t, n1.copyFromVReg)
|
|
require.Nil(t, n2.copyToVReg)
|
|
}
|
|
|
|
func TestAllocator_recordCopyRelation(t *testing.T) {
|
|
t.Run("real/real", func(t *testing.T) {
|
|
// Just ensure that it doesn't panic.
|
|
a := NewAllocator(&RegisterInfo{})
|
|
a.recordCopyRelation(FromRealReg(1, RegTypeInt), FromRealReg(2, RegTypeInt))
|
|
})
|
|
t.Run("read/virtual", func(t *testing.T) {
|
|
a := NewAllocator(&RegisterInfo{})
|
|
v100, r := VReg(100), FromRealReg(1, RegTypeInt)
|
|
a.recordCopyRelation(v100, r)
|
|
|
|
n := a.vRegIDToNode[100]
|
|
require.Nil(t, n.copyFromVReg)
|
|
require.Nil(t, n.copyToVReg)
|
|
require.Equal(t, RealRegInvalid, n.copyToReal)
|
|
require.Equal(t, r.RealReg(), n.copyFromReal)
|
|
})
|
|
t.Run("virtual/read", func(t *testing.T) {
|
|
a := NewAllocator(&RegisterInfo{})
|
|
v100, r := VReg(100), FromRealReg(1, RegTypeInt)
|
|
a.recordCopyRelation(r, v100)
|
|
|
|
n := a.vRegIDToNode[100]
|
|
require.Nil(t, n.copyFromVReg)
|
|
require.Nil(t, n.copyToVReg)
|
|
require.Equal(t, RealRegInvalid, n.copyFromReal)
|
|
require.Equal(t, r.RealReg(), n.copyToReal)
|
|
})
|
|
t.Run("virtual/virtual", func(t *testing.T) {
|
|
a := NewAllocator(&RegisterInfo{})
|
|
v100, v200 := VReg(100), VReg(200)
|
|
a.recordCopyRelation(v200, v100)
|
|
|
|
n100, n200 := a.vRegIDToNode[100], a.vRegIDToNode[200]
|
|
require.Nil(t, n100.copyFromVReg)
|
|
require.Nil(t, n200.copyToVReg)
|
|
require.Equal(t, n200, n100.copyToVReg)
|
|
require.Equal(t, n200.copyFromVReg, n100)
|
|
require.Equal(t, RealRegInvalid, n100.copyFromReal)
|
|
require.Equal(t, RealRegInvalid, n100.copyToReal)
|
|
require.Equal(t, RealRegInvalid, n200.copyFromReal)
|
|
require.Equal(t, RealRegInvalid, n200.copyToReal)
|
|
})
|
|
}
|
|
|
|
func initMapInInfo(info *blockInfo) {
|
|
if info.intervalTree == nil {
|
|
info.intervalTree = newIntervalTree()
|
|
}
|
|
if info.liveIns == nil {
|
|
info.liveIns = make(map[VReg]struct{})
|
|
}
|
|
if info.liveOuts == nil {
|
|
info.liveOuts = make(map[VReg]struct{})
|
|
}
|
|
if info.defs == nil {
|
|
info.defs = make(map[VReg]programCounter)
|
|
}
|
|
if info.kills == nil {
|
|
info.kills = make(map[VReg]programCounter)
|
|
}
|
|
if info.lastUses == nil {
|
|
info.lastUses = make(map[VReg]programCounter)
|
|
}
|
|
if info.realRegUses == nil {
|
|
info.realRegUses = make(map[VReg][]programCounter)
|
|
}
|
|
if info.realRegDefs == nil {
|
|
info.realRegDefs = make(map[VReg][]programCounter)
|
|
}
|
|
}
|
|
|
|
func TestNode_assignedRealReg(t *testing.T) {
|
|
require.Equal(t, RealRegInvalid, (&node{}).assignedRealReg())
|
|
require.Equal(t, RealReg(100), (&node{r: 100}).assignedRealReg())
|
|
require.Equal(t, RealReg(200), (&node{v: VReg(1).SetRealReg(200)}).assignedRealReg())
|
|
}
|