From 589f297fe8b0a16e691a61cc76a6c0f05b56a5e8 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Thu, 2 Nov 2023 14:23:19 +0900 Subject: [PATCH] wazevo(regalloc): refactors interference graph construction Signed-off-by: Takeshi Yoneda --- .../engine/wazevo/backend/backend_test.go | 36 +-- .../engine/wazevo/backend/regalloc/assign.go | 127 ++++++--- .../wazevo/backend/regalloc/assign_test.go | 2 + .../wazevo/backend/regalloc/coloring.go | 59 +--- .../wazevo/backend/regalloc/coloring_test.go | 82 +----- .../wazevo/backend/regalloc/intervals.go | 147 ---------- .../wazevo/backend/regalloc/intervals_test.go | 258 ------------------ .../wazevo/backend/regalloc/regalloc.go | 253 ++++++----------- .../wazevo/backend/regalloc/regalloc_test.go | 114 +++++--- .../fuzz/fuzz/fuzz_targets/no_diff.rs | 1 + 10 files changed, 287 insertions(+), 792 deletions(-) delete mode 100644 internal/engine/wazevo/backend/regalloc/intervals.go delete mode 100644 internal/engine/wazevo/backend/regalloc/intervals_test.go diff --git a/internal/engine/wazevo/backend/backend_test.go b/internal/engine/wazevo/backend/backend_test.go index 92b5fc8c02..7fcc6dd205 100644 --- a/internal/engine/wazevo/backend/backend_test.go +++ b/internal/engine/wazevo/backend/backend_test.go @@ -34,7 +34,7 @@ func newMachine() backend.Machine { } func TestE2E(t *testing.T) { - const verbose = false + const verbose = true type testCase struct { name string @@ -1518,33 +1518,33 @@ L1 (SSA Block: blk0): add w8, w22, w8 add w0, w23, w8 ldr s8, #8; b 8; data.f32 1.000000 - fmul s20, s0, s8 + fmul s21, s0, s8 ldr s8, #8; b 8; data.f32 2.000000 - fmul s19, s0, s8 + fmul s20, s0, s8 ldr s8, #8; b 8; data.f32 3.000000 - fmul s18, s0, s8 + fmul s19, s0, s8 ldr s8, #8; b 8; data.f32 4.000000 - fmul s17, s0, s8 + fmul s18, s0, s8 ldr s8, #8; b 8; data.f32 5.000000 - fmul s16, s0, s8 + fmul s17, s0, s8 ldr s8, #8; b 8; data.f32 6.000000 - fmul s15, s0, s8 + fmul s16, s0, s8 ldr s8, #8; b 8; data.f32 7.000000 - fmul s14, s0, s8 + fmul s15, s0, s8 ldr s8, #8; b 8; data.f32 8.000000 - fmul s13, s0, s8 + fmul s14, s0, s8 ldr s8, #8; b 8; data.f32 9.000000 - fmul s12, s0, s8 + fmul s13, s0, s8 ldr s8, #8; b 8; data.f32 10.000000 - fmul s11, s0, s8 + fmul s12, s0, s8 ldr s8, #8; b 8; data.f32 11.000000 - fmul s10, s0, s8 + fmul s11, s0, s8 ldr s8, #8; b 8; data.f32 12.000000 - fmul s9, s0, s8 + fmul s10, s0, s8 ldr s8, #8; b 8; data.f32 13.000000 + fmul s9, s0, s8 + ldr s8, #8; b 8; data.f32 14.000000 fmul s8, s0, s8 - ldr s21, #8; b 8; data.f32 14.000000 - fmul s21, s0, s21 ldr s22, #8; b 8; data.f32 15.000000 fmul s22, s0, s22 ldr s23, #8; b 8; data.f32 16.000000 @@ -1562,8 +1562,7 @@ L1 (SSA Block: blk0): fadd s24, s24, s25 fadd s23, s23, s24 fadd s22, s22, s23 - fadd s21, s21, s22 - fadd s8, s8, s21 + fadd s8, s8, s22 fadd s8, s9, s8 fadd s8, s10, s8 fadd s8, s11, s8 @@ -1575,7 +1574,8 @@ L1 (SSA Block: blk0): fadd s8, s17, s8 fadd s8, s18, s8 fadd s8, s19, s8 - fadd s0, s20, s8 + fadd s8, s20, s8 + fadd s0, s21, s8 add sp, sp, #0x10 ldr q27, [sp], #0x10 ldr q26, [sp], #0x10 diff --git a/internal/engine/wazevo/backend/regalloc/assign.go b/internal/engine/wazevo/backend/regalloc/assign.go index 851236c7b7..91565eccfd 100644 --- a/internal/engine/wazevo/backend/regalloc/assign.go +++ b/internal/engine/wazevo/backend/regalloc/assign.go @@ -2,6 +2,7 @@ package regalloc import ( "fmt" + "sort" "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" ) @@ -10,35 +11,97 @@ import ( // This is called after coloring is done. func (a *Allocator) assignRegisters(f Function) { for blk := f.ReversePostOrderBlockIteratorBegin(); blk != nil; blk = f.ReversePostOrderBlockIteratorNext() { - a.assignRegistersPerBlock(f, blk, a.vRegIDToNode) + a.assignRegistersPerBlock(f, blk) } } // assignRegistersPerBlock assigns real registers to virtual registers on each instruction in a block. -func (a *Allocator) assignRegistersPerBlock(f Function, blk Block, vRegIDToNode []*node) { +func (a *Allocator) assignRegistersPerBlock(f Function, blk Block) { if wazevoapi.RegAllocLoggingEnabled { fmt.Println("---------------------- assigning registers for block", blk.ID(), "----------------------") } - blkID := blk.ID() + info := a.blockInfoAt(blk.ID()) + a.aliveSet = resetMap(a.aliveSet) + for v := range info.liveIns { + n := a.getOrAllocateNode(v) + a.aliveSet[n] = struct{}{} + } + + if !blk.Entry() { + for _, arg := range blk.BlockParams() { + n := a.getOrAllocateNode(arg) + a.aliveSet[n] = struct{}{} + } + } + var pc programCounter for instr := blk.InstrIteratorBegin(); instr != nil; instr = blk.InstrIteratorNext() { - tree := a.blockInfos[blkID].intervalMng - a.assignRegistersPerInstr(f, pc, instr, vRegIDToNode, tree) + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("--- handling %v ---\n", instr) + for alive := range a.aliveSet { + fmt.Println("\t", alive) + } + } + + a.assignRegistersPerInstr(f, info, pc, instr) pc += pcStride } } -func (a *Allocator) assignRegistersPerInstr(f Function, pc programCounter, instr Instr, vRegIDToNode []*node, intervalMng *intervalManager) { +func (a *Allocator) collectOrderedActiveNodes(real bool) { + a.nodes1 = a.nodes1[:0] + for n := range a.aliveSet { + if real { + if n.assignedRealReg() == RealRegInvalid { + continue + } + } else { + if n.spill() || n.v.IsRealReg() { + continue + } + } + a.nodes1 = append(a.nodes1, n) + } + sort.Slice(a.nodes1, func(i, j int) bool { + return a.nodes1[i].v.ID() < a.nodes1[j].v.ID() + }) +} + +func (a *Allocator) updateAliveNodesByUse(info *blockInfo, pc programCounter, instr Instr) { + for _, use := range instr.Uses() { + n := a.vRegIDToNode[use.ID()] + v := n.v + if v.IsRealReg() { + delete(a.aliveSet, n) + } else { + if info.lastUses.Lookup(v) == pc { + if _, ok := info.liveOuts[v]; !ok { + delete(a.aliveSet, n) + } + } + } + } +} + +func (a *Allocator) updateAliveNodesByDef(info *blockInfo, instr Instr) { + for _, def := range instr.Defs() { + n := a.vRegIDToNode[def.ID()] + v := n.v + if !v.IsRealReg() && info.lastUses.Lookup(v) < 0 { + if _, ok := info.liveOuts[v]; !ok { + continue + } + } + a.aliveSet[n] = struct{}{} + } +} + +func (a *Allocator) assignRegistersPerInstr(f Function, info *blockInfo, pc programCounter, instr Instr) { if indirect := instr.IsIndirectCall(); instr.IsCall() || indirect { - intervalMng.collectActiveNodes( - // To find the all the live registers "after" call, we need to add pcDefOffset for search. - pc+pcDefOffset, - &a.nodes1, - // Only take care of non-real VRegs (e.g. VReg.IsRealReg() == false) since - // the real VRegs are already placed in the right registers at this point. - false, - ) + a.updateAliveNodesByUse(info, pc, instr) + a.updateAliveNodesByDef(info, instr) + a.collectOrderedActiveNodes(false) for _, active := range a.nodes1 { if r := active.r; a.regInfo.isCallerSaved(r) { v := active.v.SetRealReg(r) @@ -48,15 +111,7 @@ func (a *Allocator) assignRegistersPerInstr(f Function, pc programCounter, instr } if indirect { // Direct function calls do not need assignment, while indirect one needs the assignment on the function pointer. - a.assignIndirectCall(f, instr, vRegIDToNode) - } - - if wazevoapi.RegAllocValidationEnabled { - for _, def := range instr.Defs() { - if !def.IsRealReg() { - panic(fmt.Sprintf("BUG: call/indirect call instruction must define only real registers: %s", def)) - } - } + a.assignIndirectCall(f, instr) } return } else if instr.IsReturn() { @@ -72,7 +127,7 @@ func (a *Allocator) assignRegistersPerInstr(f Function, pc programCounter, instr if wazevoapi.RegAllocLoggingEnabled { fmt.Printf("%s uses %s(%d)\n", instr, u.RegType(), u.ID()) } - n := vRegIDToNode[u.ID()] + n := a.vRegIDToNode[u.ID()] if !n.spill() { instr.AssignUse(i, u.SetRealReg(n.r)) } else { @@ -91,7 +146,7 @@ func (a *Allocator) assignRegistersPerInstr(f Function, pc programCounter, instr fmt.Printf("%s defines %s(%d)\n", instr, d.RegType(), d.ID()) } - n := vRegIDToNode[d.ID()] + n := a.vRegIDToNode[d.ID()] if !n.spill() { instr.AssignDef(d.SetRealReg(n.r)) } else { @@ -102,19 +157,23 @@ func (a *Allocator) assignRegistersPerInstr(f Function, pc programCounter, instr panic("BUG: multiple def instructions must be special cased") } - a.handleSpills(f, pc, instr, usesSpills, defSpill, intervalMng) + a.handleSpills(f, info, pc, instr, usesSpills, defSpill) a.vs = usesSpills[:0] // for reuse. } func (a *Allocator) handleSpills( - f Function, pc programCounter, instr Instr, - usesSpills []VReg, defSpill VReg, intervalMng *intervalManager, + f Function, info *blockInfo, pc programCounter, instr Instr, + usesSpills []VReg, defSpill VReg, ) { _usesSpills, _defSpill := len(usesSpills) > 0, defSpill.Valid() switch { case !_usesSpills && !_defSpill: // Nothing to do. + a.updateAliveNodesByUse(info, pc, instr) + a.updateAliveNodesByDef(info, instr) case !_usesSpills && _defSpill: // Only definition is spilled. - intervalMng.collectActiveNodes(pc+pcDefOffset, &a.nodes1, true) + a.updateAliveNodesByUse(info, pc, instr) + a.updateAliveNodesByDef(info, instr) + a.collectOrderedActiveNodes(true) a.spillHandler.init(a.nodes1, instr) r, evictedNode := a.spillHandler.getUnusedOrEvictReg(defSpill.RegType(), a.regInfo) @@ -130,7 +189,8 @@ func (a *Allocator) handleSpills( f.StoreRegisterAfter(defSpill, instr) case _usesSpills: - intervalMng.collectActiveNodes(pc, &a.nodes1, true) + a.updateAliveNodesByUse(info, pc, instr) + a.collectOrderedActiveNodes(true) a.spillHandler.init(a.nodes1, instr) var evicted [3]*node @@ -174,7 +234,8 @@ func (a *Allocator) handleSpills( if !defSpill.IsRealReg() { // This case, the destination register type is different from the source registers. - intervalMng.collectActiveNodes(pc+pcDefOffset, &a.nodes1, true) + a.updateAliveNodesByDef(info, instr) + a.collectOrderedActiveNodes(true) a.spillHandler.init(a.nodes1, instr) r, evictedNode := a.spillHandler.getUnusedOrEvictReg(defSpill.RegType(), a.regInfo) if evictedNode != nil { @@ -191,7 +252,7 @@ func (a *Allocator) handleSpills( } } -func (a *Allocator) assignIndirectCall(f Function, instr Instr, vRegIDToNode []*node) { +func (a *Allocator) assignIndirectCall(f Function, instr Instr) { a.nodes1 = a.nodes1[:0] uses := instr.Uses() if wazevoapi.RegAllocValidationEnabled { @@ -218,7 +279,7 @@ func (a *Allocator) assignIndirectCall(f Function, instr Instr, vRegIDToNode []* panic(fmt.Sprintf("BUG: function pointer for indirect call must be an integer register: %s", v)) } - n := vRegIDToNode[v.ID()] + n := a.vRegIDToNode[v.ID()] if n.spill() { // If the function pointer is spilled, we need to reload it to a register. // But at this point, all the caller-saved registers are saved, we can use a callee-saved register to reload. diff --git a/internal/engine/wazevo/backend/regalloc/assign_test.go b/internal/engine/wazevo/backend/regalloc/assign_test.go index cb5285d4be..133df7421d 100644 --- a/internal/engine/wazevo/backend/regalloc/assign_test.go +++ b/internal/engine/wazevo/backend/regalloc/assign_test.go @@ -1,3 +1,5 @@ +//go:build tmp + package regalloc import ( diff --git a/internal/engine/wazevo/backend/regalloc/coloring.go b/internal/engine/wazevo/backend/regalloc/coloring.go index 00f3245d60..12a82874dc 100644 --- a/internal/engine/wazevo/backend/regalloc/coloring.go +++ b/internal/engine/wazevo/backend/regalloc/coloring.go @@ -7,52 +7,6 @@ import ( "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" ) -// buildNeighbors builds the neighbors for each node in the interference graph. -func (a *Allocator) buildNeighbors() { - allocated := a.nodePool.Allocated() - if diff := allocated - len(a.dedup); diff > 0 { - a.dedup = append(a.dedup, make([]bool, diff+1)...) - } - for i := 0; i < allocated; i++ { - n := a.nodePool.View(i) - a.buildNeighborsFor(n) - } -} - -func (a *Allocator) buildNeighborsFor(n *node) { - for _, r := range n.ranges { - // Collects all the nodes that are in the same range. - for _, neighbor := range r.nodes { - neighborID := neighbor.id - if neighbor.v.RegType() != n.v.RegType() { - continue - } - if neighbor != n && !a.dedup[neighborID] { - n.neighbors = append(n.neighbors, neighbor) - a.dedup[neighborID] = true - } - } - - // And also collects all the nodes that are in the neighbor ranges. - for _, neighborInterval := range r.neighbors { - for _, neighbor := range neighborInterval.nodes { - if neighbor.v.RegType() != n.v.RegType() { - continue - } - neighborID := neighbor.id - if neighbor != n && !a.dedup[neighborID] { - n.neighbors = append(n.neighbors, neighbor) - a.dedup[neighborID] = true - } - } - } - } - // Reset for the next iteration. - for _, neighbor := range n.neighbors { - a.dedup[neighbor.id] = false - } -} - // coloring does the graph coloring for both RegType(s). // Since the graphs are disjoint per RegType, we do it by RegType separately. func (a *Allocator) coloring() { @@ -88,11 +42,6 @@ func (a *Allocator) coloringFor(allocatable []RealReg) { numAllocatable := len(allocatable) - // Initialize the degree for each node which is defined as the number of neighbors. - for _, n := range degreeSortedNodes { - n.degree = len(n.neighbors) - } - // Sort the nodes by the current degree. sort.SliceStable(degreeSortedNodes, func(i, j int) bool { return degreeSortedNodes[i].degree < degreeSortedNodes[j].degree @@ -133,6 +82,7 @@ func (a *Allocator) coloringFor(allocatable []RealReg) { for len(popTargetQueue) > 0 { top := popTargetQueue[0] popTargetQueue = popTargetQueue[1:] + for _, neighbor := range top.neighbors { neighbor.degree-- if neighbor.degree < numAllocatable { @@ -162,6 +112,13 @@ func (a *Allocator) coloringFor(allocatable []RealReg) { // Gather already used colors. for _, neighbor := range n.neighbors { + if wazevoapi.RegAllocLoggingEnabled { + if neighbor.r == RealRegInvalid { + fmt.Println("\tneighbor: ", neighbor, " (not colored yet)") + } else { + fmt.Println("\tneighbor: ", neighbor, " (colored)", a.regInfo.RealRegName(neighbor.r)) + } + } if neighborColor := neighbor.r; neighborColor != RealRegInvalid { neighborColorsSet[neighborColor] = true } diff --git a/internal/engine/wazevo/backend/regalloc/coloring_test.go b/internal/engine/wazevo/backend/regalloc/coloring_test.go index 6f8e662a3e..d0d85d407f 100644 --- a/internal/engine/wazevo/backend/regalloc/coloring_test.go +++ b/internal/engine/wazevo/backend/regalloc/coloring_test.go @@ -1,8 +1,6 @@ package regalloc import ( - "sort" - "strconv" "testing" "github.com/tetratelabs/wazero/internal/testing/require" @@ -26,10 +24,6 @@ func TestAllocator_collectNodesByRegType(t *testing.T) { } func TestAllocator_coloringFor(t *testing.T) { - addEdge := func(n1, n2 *node) { - n1.neighbors = append(n1.neighbors, n2) - n2.neighbors = append(n2.neighbors, n1) - } for _, tc := range []struct { name string @@ -56,7 +50,7 @@ func TestAllocator_coloringFor(t *testing.T) { allocatable: []RealReg{1, 2}, links: [][]int{{1}, {0}}, // Interference, so only one can be assigned a register. - expRegs: []RealReg{1, 2}, + expRegs: []RealReg{2, 1}, }, { // 0 <- 1 -> 2 @@ -137,9 +131,10 @@ func TestAllocator_coloringFor(t *testing.T) { n1 := testNodes[i] for _, link := range links { n2 := testNodes[link] - addEdge(n1, n2) + a.maybeAddEdge(n1, n2) } } + a.finalizeEdges() a.coloringFor(tc.allocatable) var actual []string for _, n := range testNodes { @@ -235,74 +230,3 @@ func TestAllocator_assignColor(t *testing.T) { require.True(t, ok) }) } - -func TestAllocator_buildNeighbors(t *testing.T) { - a := NewAllocator(&RegisterInfo{}) - a.dedup = make([]bool, 1000) // Enough large. - - newNode := func(id int) *node { - return &node{id: id} - } - - newNodes := func(ids ...int) []*node { - var ns []*node - for _, id := range ids { - ns = append(ns, newNode(id)) - } - return ns - } - - for i, tc := range []struct { - n *node - exp []int - }{ - {n: newNode(0)}, - { - n: &node{ - ranges: []*interval{ - {nodes: newNodes(1, 2, 3)}, - {nodes: newNodes(4, 5, 1, 2, 3)}, - }, - }, - exp: []int{1, 2, 3, 4, 5}, - }, - { - n: &node{ - ranges: []*interval{ - {nodes: newNodes(1, 2, 3)}, - {nodes: newNodes(1, 2, 3)}, - {nodes: newNodes(1, 2, 3)}, - {nodes: newNodes(1, 2, 3)}, - {nodes: newNodes(4, 5, 1, 2, 3)}, - }, - }, - exp: []int{1, 2, 3, 4, 5}, - }, - { - n: &node{ - ranges: []*interval{ - {nodes: newNodes(1, 2, 3)}, - {nodes: newNodes(4), neighbors: []*interval{ - {nodes: newNodes(5, 6)}, - {nodes: newNodes(100, 200)}, - }}, - }, - }, - exp: []int{1, 2, 3, 4, 5, 6, 100, 200}, - }, - } { - tc := tc - t.Run(strconv.Itoa(i), func(t *testing.T) { - a.buildNeighborsFor(tc.n) - var collected []int - for _, nei := range tc.n.neighbors { - collected = append(collected, nei.id) - } - sort.Ints(collected) - require.Equal(t, tc.exp, collected) - for i := range a.dedup { - require.False(t, a.dedup[i]) // must be cleaned up. - } - }) - } -} diff --git a/internal/engine/wazevo/backend/regalloc/intervals.go b/internal/engine/wazevo/backend/regalloc/intervals.go deleted file mode 100644 index 6f43e4b941..0000000000 --- a/internal/engine/wazevo/backend/regalloc/intervals.go +++ /dev/null @@ -1,147 +0,0 @@ -package regalloc - -import ( - "sort" - - "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" -) - -type ( - // intervalManager manages intervals for each block. - intervalManager struct { - allocator wazevoapi.Pool[interval] - intervals map[intervalKey]*interval - sortedIntervals []*interval - collectionCur int - } - // interval represents an interval in the block, which is a range of program counters. - // Each interval has a list of nodes which are live in the interval. - interval struct { - begin, end programCounter - // nodes are nodes which are alive in this interval. - nodes []*node - // neighbors are intervals which are adjacent to this interval. - neighbors []*interval - } - // intervalKey is a key for intervalManager.intervals which consists of begin and end. - intervalKey uint64 -) - -func newIntervalManager() *intervalManager { - return &intervalManager{ - allocator: wazevoapi.NewPool[interval](resetIntervalTreeNode), - intervals: make(map[intervalKey]*interval), - } -} - -func resetIntervalTreeNode(i *interval) { - i.begin = 0 - i.end = 0 - i.nodes = i.nodes[:0] - i.neighbors = i.neighbors[:0] -} - -// intervalTreeNodeKey returns a key for intervalManager.intervals. -func intervalTreeNodeKey(begin, end programCounter) intervalKey { - return intervalKey(begin) | intervalKey(end)<<32 -} - -// insert inserts a node into the interval tree. -func (t *intervalManager) insert(n *node, begin, end programCounter) *interval { - key := intervalTreeNodeKey(begin, end) - if i, ok := t.intervals[key]; ok { - i.nodes = append(i.nodes, n) - return i - } - i := t.allocator.Allocate() - i.nodes = append(i.nodes, n) - i.begin = begin - i.end = end - t.intervals[key] = i - t.sortedIntervals = append(t.sortedIntervals, i) // Will be sorted later. - return i -} - -func (t *intervalManager) reset() { - t.allocator.Reset() - t.sortedIntervals = t.sortedIntervals[:0] - t.intervals = make(map[intervalKey]*interval) - t.collectionCur = 0 -} - -// build is called after all the intervals are inserted. This sorts the intervals, -// and builds the neighbor intervals for each interval. -func (t *intervalManager) build() { - sort.Slice(t.sortedIntervals, func(i, j int) bool { - ii, ij := t.sortedIntervals[i], t.sortedIntervals[j] - if ii.begin == ij.begin { - return ii.end < ij.end - } - return ii.begin < ij.begin - }) - - var cur int - var existingEndMax programCounter = -1 - for i, _interval := range t.sortedIntervals { - begin, end := _interval.begin, _interval.end - if begin > existingEndMax { - cur = i - existingEndMax = end - } else { - for j := cur; j < i; j++ { - existing := t.sortedIntervals[j] - if existing.end < begin { - continue - } - if existing.begin > end { - panic("BUG") - } - _interval.neighbors = append(_interval.neighbors, existing) - existing.neighbors = append(existing.neighbors, _interval) - } - if end > existingEndMax { - existingEndMax = end - } - } - } -} - -// collectActiveNodes collects nodes which are alive at pc, and the result is stored in `collected`. -// If `real` is true, only nodes which are assigned to a real register are collected. -func (t *intervalManager) collectActiveNodes(pc programCounter, collected *[]*node, real bool) { - *collected = (*collected)[:0] - - // Advance the collection cursor until the current interval's end is greater than pc. - l := len(t.sortedIntervals) - for cur := t.collectionCur; cur < l; cur++ { - curNode := t.sortedIntervals[cur] - if curNode.end < pc { - t.collectionCur++ - continue - } else { - break - } - } - - for cur := t.collectionCur; cur < l; cur++ { - curNode := t.sortedIntervals[cur] - if curNode.end < pc { - continue - } else if curNode.begin > pc { - break - } - - for _, n := range curNode.nodes { - if real { - if n.assignedRealReg() == RealRegInvalid { - continue - } - } else { - if n.spill() || n.v.IsRealReg() { - continue - } - } - *collected = append(*collected, n) - } - } -} diff --git a/internal/engine/wazevo/backend/regalloc/intervals_test.go b/internal/engine/wazevo/backend/regalloc/intervals_test.go deleted file mode 100644 index cec878c44c..0000000000 --- a/internal/engine/wazevo/backend/regalloc/intervals_test.go +++ /dev/null @@ -1,258 +0,0 @@ -package regalloc - -import ( - "fmt" - "sort" - "testing" - - "github.com/tetratelabs/wazero/internal/testing/require" -) - -func TestIntervalsManager_build(t *testing.T) { - type ( - intervalCase struct { - begin, end programCounter - } - expNeighborCase struct { - index int - neighbors []intervalCase - } - ) - - for _, tc := range []struct { - name string - intervals []intervalCase - expNeighbors []expNeighborCase - }{ - { - name: "single", - intervals: []intervalCase{{begin: 0, end: 100}}, - expNeighbors: []expNeighborCase{{index: 0}}, - }, - { - name: "disjoints", - intervals: []intervalCase{{begin: 50, end: 100}, {begin: 1, end: 2}}, - expNeighbors: []expNeighborCase{{index: 0}, {index: 1}}, - }, - { - name: "disjoints duplicate", - intervals: []intervalCase{{begin: 50, end: 100}, {begin: 1, end: 2}, {begin: 50, end: 100}, {begin: 1, end: 2}}, - expNeighbors: []expNeighborCase{{index: 0}, {index: 1}, {index: 2}, {index: 3}}, - }, - { - name: "two intersecting", - intervals: []intervalCase{ - {begin: 70, end: 200}, - {begin: 50, end: 100}, - }, - expNeighbors: []expNeighborCase{ - {index: 0, neighbors: []intervalCase{{begin: 50, end: 100}}}, - {index: 1, neighbors: []intervalCase{{begin: 70, end: 200}}}, - }, - }, - { - name: "same beginnings", - intervals: []intervalCase{ - {begin: 50, end: 200}, - {begin: 50, end: 401}, - {begin: 50, end: 201}, - {begin: 50, end: 302}, - }, - expNeighbors: []expNeighborCase{ - {index: 0, neighbors: []intervalCase{{begin: 50, end: 201}, {begin: 50, end: 302}, {begin: 50, end: 401}}}, - {index: 1, neighbors: []intervalCase{{begin: 50, end: 200}, {begin: 50, end: 201}, {begin: 50, end: 302}}}, - {index: 2, neighbors: []intervalCase{{begin: 50, end: 200}, {begin: 50, end: 302}, {begin: 50, end: 401}}}, - {index: 3, neighbors: []intervalCase{{begin: 50, end: 200}, {begin: 50, end: 201}, {begin: 50, end: 401}}}, - }, - }, - { - name: "three intersecting", - intervals: []intervalCase{ - {begin: 70, end: 200}, - {begin: 71, end: 150}, - {begin: 50, end: 100}, - }, - expNeighbors: []expNeighborCase{ - {index: 0, neighbors: []intervalCase{{begin: 50, end: 100}, {begin: 71, end: 150}}}, - {index: 1, neighbors: []intervalCase{{begin: 50, end: 100}, {begin: 70, end: 200}}}, - {index: 2, neighbors: []intervalCase{{begin: 70, end: 200}, {begin: 71, end: 150}}}, - }, - }, - { - name: "two enclosing interval", - intervals: []intervalCase{ - {begin: 50, end: 100}, - {begin: 25, end: 200}, - {begin: 40, end: 1000}, - }, - expNeighbors: []expNeighborCase{ - {index: 0, neighbors: []intervalCase{{begin: 25, end: 200}, {begin: 40, end: 1000}}}, - {index: 1, neighbors: []intervalCase{{begin: 40, end: 1000}, {begin: 50, end: 100}}}, - {index: 2, neighbors: []intervalCase{{begin: 25, end: 200}, {begin: 50, end: 100}}}, - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - manager := newIntervalManager() - for i, inter := range tc.intervals { - n := &node{id: i, r: RealReg(1)} - manager.insert(n, inter.begin, inter.end) - } - manager.build() - - for i, exp := range tc.expNeighbors { - it := tc.intervals[exp.index] - key := intervalTreeNodeKey(it.begin, it.end) - - var found []intervalCase - for _, n := range manager.intervals[key].neighbors { - found = append(found, intervalCase{begin: n.begin, end: n.end}) - } - sort.Slice(found, func(i, j int) bool { - return found[i].begin < found[j].begin - }) - require.Equal(t, exp.neighbors, found, fmt.Sprintf("case=%d", i)) - } - }) - } -} - -func TestIntervalManager_collectActiveNodes(t *testing.T) { - type ( - queryCase struct { - query programCounter - exp []int - } - intervalCase struct { - begin, end programCounter - id int - } - ) - - newQueryCase := func(s programCounter, exp ...int) queryCase { - return queryCase{query: s, exp: exp} - } - - for _, tc := range []struct { - name string - intervals []intervalCase - queryCases []queryCase - }{ - { - name: "single", - intervals: []intervalCase{{begin: 0, end: 100, id: 0}}, - queryCases: []queryCase{ - newQueryCase(0, 0), - newQueryCase(1, 0), - newQueryCase(101), - }, - }, - { - name: "single/2", - intervals: []intervalCase{{begin: 50, end: 100, id: 0}}, - queryCases: []queryCase{ - newQueryCase(48), - newQueryCase(50, 0), - newQueryCase(51, 0), - newQueryCase(101), - }, - }, - { - name: "same id for different intervals", - intervals: []intervalCase{{begin: 50, end: 100, id: 0xa}, {begin: 150, end: 200, id: 0xa}}, - queryCases: []queryCase{ - newQueryCase(0), - newQueryCase(50, 0xa), - newQueryCase(101), - newQueryCase(150, 0xa), - }, - }, - { - name: "two disjoint intervals", - intervals: []intervalCase{{begin: 50, end: 100, id: 0xa}, {begin: 150, end: 200, id: 0xb}}, - queryCases: []queryCase{ - newQueryCase(0), - newQueryCase(50, 0xa), - newQueryCase(51, 0xa), - newQueryCase(101), - newQueryCase(150, 0xb), - newQueryCase(200, 0xb), - newQueryCase(201), - }, - }, - { - name: "two intersecting intervals", - intervals: []intervalCase{{begin: 50, end: 100, id: 0xa}, {begin: 51, end: 200, id: 0xb}}, - queryCases: []queryCase{ - newQueryCase(0), - newQueryCase(1), - newQueryCase(49), - newQueryCase(50, 0xa), - newQueryCase(51, 0xa, 0xb), - newQueryCase(70, 0xa, 0xb), - newQueryCase(100, 0xa, 0xb), - newQueryCase(101, 0xb), - newQueryCase(1001), - }, - }, - { - name: "two enclosing interval", - intervals: []intervalCase{{begin: 50, end: 100, id: 0xa}, {begin: 25, end: 200, id: 0xb}, {begin: 40, end: 1000, id: 0xc}}, - queryCases: []queryCase{ - newQueryCase(24), - newQueryCase(25, 0xb), - newQueryCase(39, 0xb), - newQueryCase(40, 0xb, 0xc), - newQueryCase(49, 0xb, 0xc), - newQueryCase(50, 0xa, 0xb, 0xc), - newQueryCase(51, 0xa, 0xb, 0xc), - newQueryCase(99, 0xa, 0xb, 0xc), - newQueryCase(100, 0xa, 0xb, 0xc), - newQueryCase(101, 0xb, 0xc), - newQueryCase(200, 0xb, 0xc), - newQueryCase(201, 0xc), - newQueryCase(1000, 0xc), - newQueryCase(1001), - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - for _, onlyReal := range []bool{false, true} { - t.Run(fmt.Sprintf("onlyReal=%t", onlyReal), func(t *testing.T) { - manager := newIntervalManager() - for _, inter := range tc.intervals { - n := &node{id: inter.id, r: RealReg(1)} - manager.insert(n, inter.begin, inter.end) - key := intervalTreeNodeKey(inter.begin, inter.end) - inserted := manager.intervals[key] - - // They are ignored. - if onlyReal { - inserted.nodes = append(inserted.nodes, &node{v: VRegInvalid.SetRealReg(RealRegInvalid)}) // non-real reg should be ignored. - } else { - inserted.nodes = append(inserted.nodes, &node{v: FromRealReg(1, RegTypeInt)}) - inserted.nodes = append(inserted.nodes, &node{v: FromRealReg(1, RegTypeFloat)}) - inserted.nodes = append(inserted.nodes, &node{v: VReg(1)}) - } - } - manager.build() - for _, qc := range tc.queryCases { - t.Run(fmt.Sprintf("%d", qc.query), func(t *testing.T) { - var collected []*node - manager.collectActiveNodes(qc.query, &collected, onlyReal) - require.Equal(t, len(qc.exp), len(collected)) - var foundIDs []int - for _, n := range collected { - foundIDs = append(foundIDs, n.id) - } - sort.Slice(foundIDs, func(i, j int) bool { - return foundIDs[i] < foundIDs[j] - }) - require.Equal(t, qc.exp, foundIDs) - }) - } - }) - } - }) - } -} diff --git a/internal/engine/wazevo/backend/regalloc/regalloc.go b/internal/engine/wazevo/backend/regalloc/regalloc.go index 6fa6d087a7..6af50bc1af 100644 --- a/internal/engine/wazevo/backend/regalloc/regalloc.go +++ b/internal/engine/wazevo/backend/regalloc/regalloc.go @@ -10,7 +10,6 @@ package regalloc import ( "fmt" - "math" "sort" "strings" @@ -23,6 +22,7 @@ func NewAllocator(allocatableRegs *RegisterInfo) Allocator { regInfo: allocatableRegs, nodePool: wazevoapi.NewPool[node](resetNode), blockInfoPool: wazevoapi.NewPool[blockInfo](resetBlockInfo), + aliveSet: make(map[*node]struct{}), } for _, regs := range allocatableRegs.AllocatableRegisters { for _, r := range regs { @@ -66,9 +66,10 @@ type ( phiBlocks []Block phis []VReg + aliveSet map[*node]struct{} + // Followings are re-used during various places e.g. coloring. realRegSet [RealRegsNumMax]bool - realRegs []RealReg nodes1 []*node nodes2 []*node nodes3 []*node @@ -80,23 +81,14 @@ type ( blockInfo struct { liveOuts map[VReg]struct{} liveIns map[VReg]struct{} - defs map[VReg]programCounter lastUses VRegTable - // Pre-colored real registers can have multiple live ranges in one block. - realRegUses [vRegIDReservedForRealNum][]programCounter - realRegDefs [vRegIDReservedForRealNum][]programCounter - intervalMng *intervalManager } // node represents a VReg. node struct { - id int - v VReg - ranges []*interval + v VReg // r is the real register assigned to this node. It is either a pre-colored register or a register assigned during allocation. r RealReg - // neighbors are the nodes that this node interferes with. Such neighbors have the same RegType as this node. - neighbors []*node // copyFromReal and copyToReal are the real registers that this node copies from/to. During the allocation phase, // we try to assign the same RealReg to copyFromReal and copyToReal so that we can remove the redundant copy. copyFromReal, copyToReal RealReg @@ -104,6 +96,7 @@ type ( copyFromVReg, copyToVReg *node degree int visited bool + neighbors []*node } // programCounter represents an opaque index into the program which is used to represents a LiveInterval of a VReg. @@ -114,7 +107,6 @@ type ( func (a *Allocator) DoAllocation(f Function) { a.livenessAnalysis(f) a.buildLiveRanges(f) - a.buildNeighbors() a.coloring() a.determineCalleeSavedRealRegs(f) a.assignRegisters(f) @@ -160,10 +152,8 @@ func (a *Allocator) livenessAnalysis(f Function) { // First, we need to allocate blockInfos. var maxBlockID int for blk := f.PostOrderBlockIteratorBegin(); blk != nil; blk = f.PostOrderBlockIteratorNext() { // Order doesn't matter. - info := a.allocateBlockInfo(blk.ID()) // If this is not the entry block, we should define phi nodes, which are not defined by instructions. for _, p := range blk.BlockParams() { - info.defs[p] = 0 // Earliest definition is at the beginning of the block. a.phis = append(a.phis, p) pid := int(p.ID()) if diff := pid + 1 - len(a.phiBlocks); diff > 0 { @@ -223,21 +213,13 @@ func (a *Allocator) livenessAnalysis(f Function) { pc -= pcStride var use, def VReg for _, def = range instr.Defs() { - defID := def.ID() - pos := pc + pcDefOffset - if def.IsRealReg() { - info.realRegDefs[defID] = append(info.realRegDefs[defID], pos) - } else { - info.defs[def] = pos + if !def.IsRealReg() { delete(info.liveIns, def) } } for _, use = range instr.Uses() { pos := pc + pcUseOffset - if use.IsRealReg() { - id := use.ID() - info.realRegUses[id] = append(info.realRegUses[id], pos) - } else { + if !use.IsRealReg() { if info.lastUses.Lookup(use) < 0 { info.lastUses.Insert(use, pos) } @@ -252,10 +234,9 @@ func (a *Allocator) livenessAnalysis(f Function) { // If the destination is a phi value, and ... if def.Valid() && a.phiBlk(def.ID()) != nil { if use.Valid() && use.IsRealReg() { + info.liveIns[use] = struct{}{} // If the source is a real register, this is the beginning of the function, and // therefore we need to add the definition of the real register. - r := use.ID() - info.realRegDefs[r] = append(info.realRegDefs[r], 0) } else { // Otherwise, this is the definition of the phi value for the successor block. // So we need to make it outlive the block. @@ -314,123 +295,96 @@ func (a *Allocator) loopTreeDFS(entry Block) { } func (a *Allocator) buildLiveRanges(f Function) { - for blk := f.PostOrderBlockIteratorBegin(); blk != nil; blk = f.PostOrderBlockIteratorNext() { // Order doesn't matter. - blkID := blk.ID() - info := a.blockInfoAt(blkID) - a.buildLiveRangesForNonReals(info) - a.buildLiveRangesForReals(info) - info.intervalMng.build() + for blk := f.ReversePostOrderBlockIteratorBegin(); blk != nil; blk = f.ReversePostOrderBlockIteratorNext() { // Order doesn't matter. + a.buildLiveRangeEdges(blk) } + a.finalizeEdges() } -func (a *Allocator) buildLiveRangesForNonReals(info *blockInfo) { - ins, outs, defs := info.liveIns, info.liveOuts, info.defs - - // In order to do the deterministic allocation, we need to sort ins. - vs := a.vs[:0] - for v := range ins { - vs = append(vs, v) - } - sort.SliceStable(vs, func(i, j int) bool { - return vs[i].ID() < vs[j].ID() - }) - for _, v := range vs { - if v.IsRealReg() { - panic("BUG: real registers should not be in liveIns") - } - var begin, end programCounter - if _, ok := outs[v]; ok { - // v is live-in and live-out, so it is live-through. - begin, end = 0, math.MaxInt32 - } else { - // v is killed at killPos. - begin, end = 0, info.lastUses.Lookup(v) +func (a *Allocator) finalizeEdges() { + for i := 0; i < a.nodePool.Allocated(); i++ { + n := a.nodePool.View(i) + if n.v.IsRealReg() { + continue } - n := a.getOrAllocateNode(v) - intervalNode := info.intervalMng.insert(n, begin, end) - n.ranges = append(n.ranges, intervalNode) + n.degree = len(n.neighbors) } +} - // In order to do the deterministic allocation, we need to sort defs. - vs = vs[:0] - for v := range defs { - vs = append(vs, v) +func (a *Allocator) buildLiveRangeEdges(blk Block) { + blkID := blk.ID() + info := a.blockInfoAt(blkID) + + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("blk%d:\n%s\n", blkID, info.Format(a.regInfo)) } - sort.SliceStable(vs, func(i, j int) bool { - return vs[i].ID() < vs[j].ID() - }) - for _, v := range vs { - defPos := defs[v] + + a.aliveSet = resetMap(a.aliveSet) + a.vs = a.vs[:0] + for v := range info.liveIns { + a.vs = append(a.vs, v) + } + for _, v := range a.vs { + n := a.getOrAllocateNode(v) if v.IsRealReg() { - panic("BUG: real registers should not be in defs") + n.r = v.RealReg() } + a.aliveSet[n] = struct{}{} + } - if _, ok := ins[v]; ok { - // This case, phi value is coming in (used somewhere) but re-defined at the end. - continue + if !blk.Entry() { + for _, arg := range blk.BlockParams() { + n := a.getOrAllocateNode(arg) + a.aliveSet[n] = struct{}{} } + } - var end programCounter - if _, ok := outs[v]; ok { - // v is defined here and live-out, so it is live-through. - end = math.MaxInt32 - } else { - end = info.lastUses.Lookup(v) - if end == -1 { - // This case the defined value is not used at all. - end = defPos + var pc programCounter + for instr := blk.InstrIteratorBegin(); instr != nil; instr = blk.InstrIteratorNext() { + for _, use := range instr.Uses() { + pos := pc + pcUseOffset + if use.IsRealReg() { + delete(a.aliveSet, a.getOrAllocateNode(use)) + } else { + if info.lastUses.Lookup(use) == pos { + if _, ok := info.liveOuts[use]; !ok { + delete(a.aliveSet, a.getOrAllocateNode(use)) + } + } } } - n := a.getOrAllocateNode(v) - intervalNode := info.intervalMng.insert(n, defPos, end) - n.ranges = append(n.ranges, intervalNode) - } - // Reuse for the next block. - a.vs = vs[:0] -} + for _, def := range instr.Defs() { + n := a.getOrAllocateNode(def) + if def.IsRealReg() { + n.r = def.RealReg() + } -// buildLiveRangesForReals builds live ranges for pre-colored real registers. -func (a *Allocator) buildLiveRangesForReals(info *blockInfo) { - ds, us := info.realRegDefs, info.realRegUses + if _, ok := a.aliveSet[n]; ok { + continue + } - for i := 0; i < RealRegsNumMax; i++ { - r := RealReg(i) - // Non allocation target registers are not needed here. - if !a.allocatableSet[r] { - continue - } + for m := range a.aliveSet { + nID, mID := n.v.ID(), m.v.ID() + if nID == mID || n.v.RegType() != m.v.RegType() { + continue + } + if nID >= mID { + nID, mID = mID, nID + } + n.neighbors = append(n.neighbors, m) + m.neighbors = append(m.neighbors, n) + } - uses := us[r] - defs := ds[r] - if len(defs) != len(uses) { - // This is likely a bug of the Instr interface implementation and/or ABI around call instructions. - // E.g. call or ret instructions should specify that they use all the real registers (calling convention). - panic( - fmt.Sprintf( - "BUG: real register (%s) is defined and used, but the number of defs and uses are different: %d (defs) != %d (uses)", - a.regInfo.RealRegName(r), len(defs), len(uses), - ), - ) - } else if len(uses) == 0 { - continue - } + if !def.IsRealReg() && info.lastUses.Lookup(def) < 0 { + if _, ok := info.liveOuts[def]; !ok { + continue + } + } - sort.Slice(uses, func(i, j int) bool { - return uses[i] < uses[j] - }) - sort.Slice(defs, func(i, j int) bool { - return defs[i] < defs[j] - }) - - for i := range uses { - n := a.allocateNode() - n.r = r - n.v = FromRealReg(r, a.regInfo.RealRegType(r)) - defined, used := defs[i], uses[i] - intervalNode := info.intervalMng.insert(n, defined, used) - n.ranges = append(n.ranges, intervalNode) + a.aliveSet[n] = struct{}{} } + pc += pcStride } } @@ -448,12 +402,12 @@ func (a *Allocator) Reset() { a.nodes1 = a.nodes1[:0] a.nodes2 = a.nodes2[:0] - a.realRegs = a.realRegs[:0] for _, phi := range a.phis { a.phiBlocks[phi.ID()] = nil } a.phis = a.phis[:0] a.vs = a.vs[:0] + a.aliveSet = resetMap(a.aliveSet) } func (a *Allocator) allocateBlockInfo(blockID int) *blockInfo { @@ -492,32 +446,20 @@ func (a *Allocator) getOrAllocateNode(v VReg) (n *node) { } func resetBlockInfo(i *blockInfo) { - if i.intervalMng == nil { - i.intervalMng = newIntervalManager() - } else { - i.intervalMng.reset() - } i.liveOuts = resetMap(i.liveOuts) i.liveIns = resetMap(i.liveIns) - i.defs = resetMap(i.defs) - - for index := range i.realRegUses { - i.realRegUses[index] = i.realRegUses[index][:0] - i.realRegDefs[index] = i.realRegDefs[index][:0] - } } func resetNode(n *node) { n.r = RealRegInvalid n.v = VRegInvalid - n.ranges = n.ranges[:0] - n.neighbors = n.neighbors[:0] n.copyFromVReg = nil n.copyToVReg = nil n.copyFromReal = RealRegInvalid n.copyToReal = RealRegInvalid n.degree = 0 n.visited = false + n.neighbors = n.neighbors[:0] } func resetMap[K comparable, V any](m map[K]V) map[K]V { @@ -533,7 +475,6 @@ func resetMap[K comparable, V any](m map[K]V) map[K]V { func (a *Allocator) allocateNode() (n *node) { n = a.nodePool.Allocate() - n.id = a.nodePool.Allocated() - 1 return } @@ -542,32 +483,24 @@ func (i *blockInfo) Format(ri *RegisterInfo) string { var buf strings.Builder buf.WriteString("\tliveOuts: ") for v := range i.liveOuts { - buf.WriteString(fmt.Sprintf("%v ", v)) + if v.IsRealReg() { + buf.WriteString(fmt.Sprintf("%v ", ri.RealRegName(v.RealReg()))) + } else { + buf.WriteString(fmt.Sprintf("%v ", v)) + } } buf.WriteString("\n\tliveIns: ") for v := range i.liveIns { - buf.WriteString(fmt.Sprintf("%v ", v)) - } - buf.WriteString("\n\tdefs: ") - for v, pos := range i.defs { - buf.WriteString(fmt.Sprintf("%v@%v ", v, pos)) + if v.IsRealReg() { + buf.WriteString(fmt.Sprintf("%v ", ri.RealRegName(v.RealReg()))) + } else { + buf.WriteString(fmt.Sprintf("%v ", v)) + } } buf.WriteString("\n\tlastUses: ") i.lastUses.Range(func(v VReg, pos programCounter) { buf.WriteString(fmt.Sprintf("%v@%v ", v, pos)) }) - buf.WriteString("\n\trealRegUses: ") - for v, pos := range i.realRegUses { - if len(pos) > 0 { - buf.WriteString(fmt.Sprintf("%s@%v ", ri.RealRegName(RealReg(v)), pos)) - } - } - buf.WriteString("\n\trealRegDefs: ") - for v, pos := range i.realRegDefs { - if len(pos) > 0 { - buf.WriteString(fmt.Sprintf("%s@%v ", ri.RealRegName(RealReg(v)), pos)) - } - } return buf.String() } @@ -578,12 +511,6 @@ func (n *node) String() string { if n.r != RealRegInvalid { buf.WriteString(fmt.Sprintf(":%v", n.r)) } - // Add neighbors - buf.WriteString(" neighbors[") - for _, n := range n.neighbors { - buf.WriteString(fmt.Sprintf("v%v ", n.v.ID())) - } - buf.WriteString("]") return buf.String() } diff --git a/internal/engine/wazevo/backend/regalloc/regalloc_test.go b/internal/engine/wazevo/backend/regalloc/regalloc_test.go index 7fda48abe8..24eb9b22e0 100644 --- a/internal/engine/wazevo/backend/regalloc/regalloc_test.go +++ b/internal/engine/wazevo/backend/regalloc/regalloc_test.go @@ -2,7 +2,7 @@ package regalloc import ( "fmt" - "sort" + "strconv" "testing" "github.com/tetratelabs/wazero/internal/testing/require" @@ -70,8 +70,6 @@ func TestAllocator_livenessAnalysis(t *testing.T) { 1: pcStride + pcUseOffset, 2: pcStride*2 + pcUseOffset, }), - realRegUses: [vRegIDReservedForRealNum][]programCounter{10: {0}}, - realRegDefs: [vRegIDReservedForRealNum][]programCounter{10: {pcDefOffset + pcStride*2}}, }, }, }, @@ -121,12 +119,6 @@ func TestAllocator_livenessAnalysis(t *testing.T) { 4: pcStride + pcDefOffset, 5: pcStride + pcDefOffset, }, - realRegUses: [vRegIDReservedForRealNum][]programCounter{ - realRegID: {pcStride*2 + pcUseOffset}, - }, - realRegDefs: [vRegIDReservedForRealNum][]programCounter{ - realRegID: {pcDefOffset}, - }, }, 2: { liveIns: map[VReg]struct{}{3: {}, 4: {}, 5: {}}, @@ -181,21 +173,11 @@ func TestAllocator_livenessAnalysis(t *testing.T) { liveIns: map[VReg]struct{}{1000: {}, 1: {}}, liveOuts: map[VReg]struct{}{1000: {}}, lastUses: makeVRegTable(map[VReg]programCounter{1: pcUseOffset}), - realRegDefs: [vRegIDReservedForRealNum][]programCounter{ - realRegID: {pcDefOffset, pcStride*4 + pcDefOffset}, - realRegID2: {pcStride*2 + pcDefOffset}, - }, - realRegUses: [vRegIDReservedForRealNum][]programCounter{ - realRegID: {pcStride + pcUseOffset, pcStride*5 + pcUseOffset}, - realRegID2: {pcStride*3 + pcUseOffset}, - }, }, 2: { - liveIns: map[VReg]struct{}{1000: {}, 2: {}}, - liveOuts: map[VReg]struct{}{1000: {}}, - lastUses: makeVRegTable(map[VReg]programCounter{2: pcUseOffset}), - realRegUses: [vRegIDReservedForRealNum][]programCounter{realRegID2: {pcUseOffset}}, - realRegDefs: [vRegIDReservedForRealNum][]programCounter{}, + liveIns: map[VReg]struct{}{1000: {}, 2: {}}, + liveOuts: map[VReg]struct{}{1000: {}}, + lastUses: makeVRegTable(map[VReg]programCounter{2: pcUseOffset}), }, 3: { liveIns: map[VReg]struct{}{1000: {}}, @@ -476,31 +458,11 @@ func TestAllocator_livenessAnalysis(t *testing.T) { exp := tc.exp[blockID] initMapInInfo(exp) fmt.Printf("\n[exp for block[%d]]\n%v\n[actual for block[%d]]\n%v\n", - blockID, exp.Format(a.regInfo), blockID, actual.Format(a.regInfo)) + blockID, exp.Format(), blockID, actual.Format()) require.Equal(t, exp.liveOuts, actual.liveOuts, "live outs") require.Equal(t, exp.liveIns, actual.liveIns, "live ins") require.Equal(t, exp.defs, actual.defs, "defs") - for i := range exp.realRegUses { - _exp, _actual := exp.realRegUses[i], actual.realRegUses[i] - sort.Slice(_exp, func(i, j int) bool { - return _exp[i] < _exp[j] - }) - sort.Slice(_actual, func(i, j int) bool { - return _actual[i] < _actual[j] - }) - require.Equal(t, _exp, _actual, "real reg use[%d]", i) - } - for i := range exp.realRegDefs { - _exp, _actual := exp.realRegDefs[i], actual.realRegDefs[i] - sort.Slice(_exp, func(i, j int) bool { - return _exp[i] < _exp[j] - }) - sort.Slice(_actual, func(i, j int) bool { - return _actual[i] < _actual[j] - }) - require.Equal(t, _exp, _actual, "real defs[%d]", i) - } require.Equal(t, exp.lastUses, actual.lastUses, "last uses") }) } @@ -590,3 +552,69 @@ func TestNode_assignedRealReg(t *testing.T) { require.Equal(t, RealReg(100), (&node{r: 100}).assignedRealReg()) require.Equal(t, RealReg(200), (&node{v: VReg(1).SetRealReg(200)}).assignedRealReg()) } + +func TestAllocator_finalizeEdges(t *testing.T) { + for i, tc := range []struct { + edges [][2]nodeID + expEdgeNum int + expDegrees map[nodeID]int + expEdgeIndex map[nodeID][2]int // [Begin, End] + }{ + { + edges: [][2]nodeID{{0, 1}}, + expEdgeNum: 2, + expDegrees: map[nodeID]int{ + 0: 1, 1: 1, + 2: 0, + 3: 0, + 4: 0, + }, + expEdgeIndex: map[nodeID][2]int{ + 0: {0, 0}, + 1: {1, 1}, + 2: {0, -1}, + 3: {0, -1}, + 4: {0, -1}, + }, + }, + { + edges: [][2]nodeID{{0, 1}, {0, 2}}, + expEdgeNum: 4, + expDegrees: map[nodeID]int{ + 0: 2, 1: 1, 2: 1, + 3: 0, + 4: 0, + }, + expEdgeIndex: map[nodeID][2]int{ + 0: {0, 1}, + 1: {2, 2}, + 2: {3, 3}, + 3: {0, -1}, + 4: {0, -1}, + }, + }, + } { + t.Run(strconv.Itoa(i), func(t *testing.T) { + a := NewAllocator(&RegisterInfo{}) + for i := 0; i < 5; i++ { + a.allocateNode() + } + for _, edge := range tc.edges { + n := a.nodePool.View(int(edge[0])) + m := a.nodePool.View(int(edge[1])) + a.maybeAddEdge(n, m) + } + a.finalizeEdges() + require.Equal(t, tc.expEdgeNum, len(a.edges)) + for nID, expDegree := range tc.expDegrees { + t.Run(fmt.Sprintf("node_id=%d", nID), func(t *testing.T) { + n := a.nodePool.View(int(nID)) + require.Equal(t, expDegree, n.degree) + expEdgeIndex := tc.expEdgeIndex[nID] + require.Equal(t, expEdgeIndex[0], n.edgeIdxBegin) + require.Equal(t, expEdgeIndex[1], n.edgeIdxEnd) + }) + } + }) + } +} diff --git a/internal/integration_test/fuzz/fuzz/fuzz_targets/no_diff.rs b/internal/integration_test/fuzz/fuzz/fuzz_targets/no_diff.rs index d0f557cc98..880373cfb9 100644 --- a/internal/integration_test/fuzz/fuzz/fuzz_targets/no_diff.rs +++ b/internal/integration_test/fuzz/fuzz/fuzz_targets/no_diff.rs @@ -42,6 +42,7 @@ fn run(data: &[u8]) -> Result<()> { // Ensures that at least one function exists. config.min_funcs = 1; config.max_funcs = config.max_funcs.max(1); + config.simd_enabled = false; // Generate the random module via wasm-smith. let mut module = wasm_smith::Module::new(config.clone(), &mut u)?;