summaryrefslogtreecommitdiff
path: root/vendor/github.com/mmcloughlin/avo/pass/cleanup.go
blob: d91250f3b818c8afa86705835936d183816b416c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package pass

import (
	"github.com/mmcloughlin/avo/ir"
	"github.com/mmcloughlin/avo/operand"
)

// PruneJumpToFollowingLabel removes jump instructions that target an
// immediately following label.
func PruneJumpToFollowingLabel(fn *ir.Function) error {
	for i := 0; i+1 < len(fn.Nodes); i++ {
		node := fn.Nodes[i]
		next := fn.Nodes[i+1]

		// This node is an unconditional jump.
		inst, ok := node.(*ir.Instruction)
		if !ok || !inst.IsBranch || inst.IsConditional {
			continue
		}

		target := inst.TargetLabel()
		if target == nil {
			continue
		}

		// And the jump target is the immediately following node.
		lbl, ok := next.(ir.Label)
		if !ok || lbl != *target {
			continue
		}

		// Then the jump is unnecessary and can be removed.
		fn.Nodes = deletenode(fn.Nodes, i)
		i--
	}

	return nil
}

// PruneDanglingLabels removes labels that are not referenced by any branches.
func PruneDanglingLabels(fn *ir.Function) error {
	// Count label references.
	count := map[ir.Label]int{}
	for _, n := range fn.Nodes {
		i, ok := n.(*ir.Instruction)
		if !ok || !i.IsBranch {
			continue
		}

		target := i.TargetLabel()
		if target == nil {
			continue
		}

		count[*target]++
	}

	// Look for labels with no references.
	for i := 0; i < len(fn.Nodes); i++ {
		node := fn.Nodes[i]
		lbl, ok := node.(ir.Label)
		if !ok {
			continue
		}

		if count[lbl] == 0 {
			fn.Nodes = deletenode(fn.Nodes, i)
			i--
		}
	}

	return nil
}

// PruneSelfMoves removes move instructions from one register to itself.
func PruneSelfMoves(fn *ir.Function) error {
	return removeinstructions(fn, func(i *ir.Instruction) bool {
		switch i.Opcode {
		case "MOVB", "MOVW", "MOVL", "MOVQ":
		default:
			return false
		}

		return operand.IsRegister(i.Operands[0]) && operand.IsRegister(i.Operands[1]) && i.Operands[0] == i.Operands[1]
	})
}

// removeinstructions deletes instructions from the given function which match predicate.
func removeinstructions(fn *ir.Function, predicate func(*ir.Instruction) bool) error {
	// Removal of instructions has the potential to invalidate CFG structures.
	// Clear them to prevent accidental use of stale structures after this pass.
	invalidatecfg(fn)

	for i := 0; i < len(fn.Nodes); i++ {
		n := fn.Nodes[i]

		inst, ok := n.(*ir.Instruction)
		if !ok || !predicate(inst) {
			continue
		}

		fn.Nodes = deletenode(fn.Nodes, i)
	}

	return nil
}

// deletenode deletes node i from nodes and returns the resulting slice.
func deletenode(nodes []ir.Node, i int) []ir.Node {
	n := len(nodes)
	copy(nodes[i:], nodes[i+1:])
	nodes[n-1] = nil
	return nodes[:n-1]
}

// invalidatecfg clears CFG structures.
func invalidatecfg(fn *ir.Function) {
	fn.LabelTarget = nil
	for _, i := range fn.Instructions() {
		i.Pred = nil
		i.Succ = nil
	}
}