pass: remove redundant jumps and dangling labels (#81)
In jump-table-like constructs, the natural way of writing the code can sometimes produce redundant jumps or labels. Therefore some basic cleanup steps have been proposed. This diff adds two transforms: 1. Remove unconditional jumps to a label immediately following. 2. Remove labels with no references at all. Fixes #75
This commit is contained in:
committed by
GitHub
parent
57c23b967e
commit
2e7d06bc7a
@@ -5,6 +5,73 @@ import (
|
||||
"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 {
|
||||
@@ -32,14 +99,20 @@ func removeinstructions(fn *ir.Function, predicate func(*ir.Instruction) bool) e
|
||||
continue
|
||||
}
|
||||
|
||||
copy(fn.Nodes[i:], fn.Nodes[i+1:])
|
||||
fn.Nodes[len(fn.Nodes)-1] = nil
|
||||
fn.Nodes = fn.Nodes[:len(fn.Nodes)-1]
|
||||
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
|
||||
|
||||
@@ -42,3 +42,45 @@ func TestPruneSelfMoves(t *testing.T) {
|
||||
t.Fatal("unexpected result from self-move pruning")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPruneJumpToFollowingLabel(t *testing.T) {
|
||||
// Construct a function containing a jump to following.
|
||||
ctx := build.NewContext()
|
||||
ctx.Function("add")
|
||||
ctx.XORQ(reg.RAX, reg.RAX)
|
||||
ctx.JMP(operand.LabelRef("next"))
|
||||
ctx.Label("next")
|
||||
ctx.XORQ(reg.RAX, reg.RAX)
|
||||
|
||||
// Build the function with the PruneJumpToFollowingLabel pass.
|
||||
fn := BuildFunction(t, ctx, pass.PruneJumpToFollowingLabel)
|
||||
|
||||
// Confirm no JMP instruction remains.
|
||||
for _, i := range fn.Instructions() {
|
||||
if i.Opcode == "JMP" {
|
||||
t.Fatal("JMP instruction not removed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPruneDanglingLabels(t *testing.T) {
|
||||
// Construct a function containing an unreferenced label.
|
||||
ctx := build.NewContext()
|
||||
ctx.Function("add")
|
||||
ctx.XORQ(reg.RAX, reg.RAX)
|
||||
ctx.JMP(operand.LabelRef("referenced"))
|
||||
ctx.XORQ(reg.RAX, reg.RAX)
|
||||
ctx.Label("dangling")
|
||||
ctx.XORQ(reg.RAX, reg.RAX)
|
||||
ctx.Label("referenced")
|
||||
ctx.XORQ(reg.RAX, reg.RAX)
|
||||
|
||||
// Build the function with the PruneDanglingLabels pass.
|
||||
fn := BuildFunction(t, ctx, pass.PruneDanglingLabels)
|
||||
|
||||
// Confirm the only label remaining is "referenced".
|
||||
expect := []ir.Label{"referenced"}
|
||||
if !reflect.DeepEqual(expect, fn.Labels()) {
|
||||
t.Fatal("expected dangling label to be removed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
// Compile pass compiles an avo file. Upon successful completion the avo file
|
||||
// may be printed to Go assembly.
|
||||
var Compile = Concat(
|
||||
FunctionPass(PruneJumpToFollowingLabel),
|
||||
FunctionPass(PruneDanglingLabels),
|
||||
FunctionPass(LabelTarget),
|
||||
FunctionPass(CFG),
|
||||
FunctionPass(Liveness),
|
||||
|
||||
Reference in New Issue
Block a user