-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Detect and reroll unrolled do-while loops #76
base: master
Are you sure you want to change the base?
Changes from all commits
0de7b12
9d10258
d394e9b
ad167f7
8d99b31
24f5e2f
e35518b
db72018
9bf11da
b3b8e22
09e85cb
0a1156b
03a8ab0
0b22145
c937919
b4ed8e0
75ce71f
1997ed4
7919a49
f637685
2d58683
8a5bf38
2fe2c7d
24e6066
3517860
57f6697
f4aa9e6
2768028
49fbcfa
c359b3b
e4ce1f1
8073b65
ce9ba4e
cc67261
0ec795e
03b6d42
90c8f2c
4379021
a2ed466
fb23b5d
774d467
914479e
ea1593f
75a6c19
5849d59
4fdaadc
25e798f
2f9dcd0
66083c3
85e9d55
b595ce8
ec17645
ef59716
fc7fff9
d63b4af
0df65fc
c89f8a0
f9b25ed
587e770
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,138 @@ | ||||||||||||||
from typing import Dict, List, Optional, Tuple, Union | ||||||||||||||
|
||||||||||||||
from .flow_graph import ( | ||||||||||||||
BasicNode, | ||||||||||||||
ConditionalNode, | ||||||||||||||
FlowGraph, | ||||||||||||||
Node, | ||||||||||||||
compute_relations, | ||||||||||||||
) | ||||||||||||||
from .parse_instruction import Instruction | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
def replace_node_references( | ||||||||||||||
flow_graph: FlowGraph, replace_this: Node, with_this: Node | ||||||||||||||
) -> None: | ||||||||||||||
for node_to_modify in flow_graph.nodes: | ||||||||||||||
node_to_modify.replace_any_children(replace_this, with_this) | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
def remove_node(flow_graph: FlowGraph, to_delete: Node, new_child: Node) -> None: | ||||||||||||||
flow_graph.nodes.remove(to_delete) | ||||||||||||||
replace_node_references(flow_graph, to_delete, new_child) | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
def replace_node(flow_graph: FlowGraph, replace_this: Node, with_this: Node) -> None: | ||||||||||||||
replacement_index = flow_graph.nodes.index(replace_this) | ||||||||||||||
flow_graph.nodes[replacement_index] = with_this | ||||||||||||||
replace_node_references(flow_graph, replace_this, with_this) | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
PatternGraph = Dict[int, Union[int, Tuple[int, int]]] | ||||||||||||||
|
||||||||||||||
IDO_O2_SIMPLE_LOOP: PatternGraph = { | ||||||||||||||
0: (1, 7), | ||||||||||||||
1: (2, 5), | ||||||||||||||
2: 3, | ||||||||||||||
3: (4, 3), | ||||||||||||||
4: (5, 7), | ||||||||||||||
5: 6, | ||||||||||||||
6: (7, 6), | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
def detect_pattern( | ||||||||||||||
pattern: PatternGraph, flow_graph: FlowGraph, start: Node | ||||||||||||||
) -> Optional[Tuple[Node, ...]]: | ||||||||||||||
indices = [node.block.index for node in flow_graph.nodes] | ||||||||||||||
assert sorted(indices) == indices, "FlowGraphs should be sorted" | ||||||||||||||
|
||||||||||||||
offset = start.block.index | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Right now this can happen when |
||||||||||||||
for label in pattern.keys(): | ||||||||||||||
try: | ||||||||||||||
node = flow_graph.nodes[label + offset] | ||||||||||||||
target = pattern[label] | ||||||||||||||
if isinstance(target, int): | ||||||||||||||
if not ( | ||||||||||||||
isinstance(node, BasicNode) | ||||||||||||||
and node.successor is flow_graph.nodes[offset + target] | ||||||||||||||
): | ||||||||||||||
return None | ||||||||||||||
else: | ||||||||||||||
(fallthrough, conditional) = target | ||||||||||||||
if not ( | ||||||||||||||
isinstance(node, ConditionalNode) | ||||||||||||||
and node.conditional_edge is flow_graph.nodes[offset + conditional] | ||||||||||||||
and node.fallthrough_edge is flow_graph.nodes[offset + fallthrough] | ||||||||||||||
): | ||||||||||||||
return None | ||||||||||||||
except IndexError: | ||||||||||||||
return None | ||||||||||||||
|
||||||||||||||
# TODO: Check that the subgraph is self-contained except for the entry | ||||||||||||||
# and exit nodes, which themselves should be somehow "designated" using an enum. | ||||||||||||||
Comment on lines
+72
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the following check is sufficient, without requiring you to explicitly mark an exit node in the pattern? (I think an exit node is any node that is not in # pattern_nodes could be the `tuple(flow_graph.nodes[i] for i in sorted(all_nodes_in_pattern))` return value I suggest later
for node in pattern_nodes:
if node is not start and not all(p in pattern_nodes for p in node.parents):
return None |
||||||||||||||
|
||||||||||||||
all_nodes_in_pattern = ( | ||||||||||||||
{offset + label for label in pattern.keys()} | ||||||||||||||
| {offset + label[0] for label in pattern.values() if isinstance(label, tuple)} | ||||||||||||||
| {offset + label[1] for label in pattern.values() if isinstance(label, tuple)} | ||||||||||||||
) | ||||||||||||||
return tuple( | ||||||||||||||
node | ||||||||||||||
for i, node in enumerate(flow_graph.nodes) | ||||||||||||||
if node is not start and i in all_nodes_in_pattern | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think If you include |
||||||||||||||
) | ||||||||||||||
Comment on lines
+80
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
def remove_and_replace_nodes(flow_graph: FlowGraph, nodes: Tuple[Node, ...]) -> None: | ||||||||||||||
(node_1, node_2, node_3, node_4, node_5, node_6, node_7) = nodes | ||||||||||||||
new_node_1 = BasicNode(node_1.block, node_1.emit_goto, node_2) | ||||||||||||||
replace_node(flow_graph, node_1, new_node_1) | ||||||||||||||
remove_node(flow_graph, node_4, node_7) | ||||||||||||||
remove_node(flow_graph, node_5, node_7) | ||||||||||||||
remove_node(flow_graph, node_6, node_7) # TODO: assert didn't execute anything?. | ||||||||||||||
Comment on lines
+87
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is only used once, and looks pretty specific to the exact structure: I think it'd be easier if the body were just inlined directly in |
||||||||||||||
|
||||||||||||||
|
||||||||||||||
def reroll_loop(flow_graph: FlowGraph, start: ConditionalNode) -> bool: | ||||||||||||||
nodes = detect_pattern(IDO_O2_SIMPLE_LOOP, flow_graph, start) | ||||||||||||||
if nodes is None: | ||||||||||||||
return False | ||||||||||||||
(node_1, node_2, node_3, node_4, node_5, node_6, node_7) = nodes | ||||||||||||||
|
||||||||||||||
def modify_node_1_instructions(instructions: List[Instruction]) -> bool: | ||||||||||||||
# First, we check that the node has the instructions we | ||||||||||||||
# think it has. | ||||||||||||||
branches = [instr for instr in instructions if instr.is_branch_instruction()] | ||||||||||||||
if len(branches) != 1: | ||||||||||||||
return False | ||||||||||||||
andi_instrs = [instr for instr in instructions if instr.mnemonic == "andi"] | ||||||||||||||
if len(andi_instrs) != 1: | ||||||||||||||
return False | ||||||||||||||
# We are now free to modify the instructions, as we have verified | ||||||||||||||
# that this node fits the criteria. | ||||||||||||||
instructions.remove(branches[0]) | ||||||||||||||
andi = andi_instrs[0] | ||||||||||||||
move = Instruction.derived("move", [andi.args[0], andi.args[1]], andi) | ||||||||||||||
instructions[instructions.index(andi)] = move | ||||||||||||||
return True | ||||||||||||||
|
||||||||||||||
if not modify_node_1_instructions(node_1.block.instructions): | ||||||||||||||
return False | ||||||||||||||
|
||||||||||||||
remove_and_replace_nodes(flow_graph, nodes) | ||||||||||||||
|
||||||||||||||
return True | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
def reroll_loops(flow_graph: FlowGraph) -> FlowGraph: | ||||||||||||||
changed: bool = True | ||||||||||||||
while changed: | ||||||||||||||
changed = False | ||||||||||||||
for node in flow_graph.nodes: | ||||||||||||||
if not isinstance(node, ConditionalNode): | ||||||||||||||
continue | ||||||||||||||
changed = reroll_loop(flow_graph, node) | ||||||||||||||
if changed: | ||||||||||||||
compute_relations(flow_graph.nodes) | ||||||||||||||
break | ||||||||||||||
return flow_graph | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function modifies |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
--no-reroll |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
s32 test(s8 *arg0, s32 arg1) { | ||
s32 temp_a3; | ||
s32 temp_v0; | ||
s32 temp_v0_2; | ||
void *temp_v1; | ||
s8 *phi_v1; | ||
s32 phi_v0; | ||
void *phi_v1_2; | ||
s32 phi_v0_2; | ||
s32 phi_return; | ||
s32 phi_v0_3; | ||
|
||
phi_return = 0; | ||
if (arg1 > 0) { | ||
temp_a3 = arg1 & 3; | ||
phi_v0_3 = 0; | ||
if (temp_a3 != 0) { | ||
phi_v1 = arg0; | ||
phi_v0 = 0; | ||
do { | ||
temp_v0 = phi_v0 + 1; | ||
*phi_v1 = (u8)0; | ||
phi_v1 += 1; | ||
phi_v0 = temp_v0; | ||
} while (temp_a3 != temp_v0); | ||
phi_return = temp_v0; | ||
phi_v0_3 = temp_v0; | ||
if (temp_v0 != arg1) { | ||
block_5: | ||
phi_v1_2 = arg0 + phi_v0_3; | ||
phi_v0_2 = phi_v0_3; | ||
do { | ||
temp_v0_2 = phi_v0_2 + 4; | ||
phi_v1_2->unk1 = (u8)0; | ||
phi_v1_2->unk2 = (u8)0; | ||
phi_v1_2->unk3 = (u8)0; | ||
temp_v1 = phi_v1_2 + 4; | ||
temp_v1->unk-4 = (u8)0; | ||
phi_v1_2 = temp_v1; | ||
phi_v0_2 = temp_v0_2; | ||
phi_return = temp_v0_2; | ||
} while (temp_v0_2 != arg1); | ||
} | ||
} else { | ||
goto block_5; | ||
} | ||
} | ||
return phi_return; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
.set noat # allow manual use of $at | ||
.set noreorder # don't insert nops after branches | ||
|
||
|
||
glabel test | ||
/* 000090 00400090 18A00012 */ blez $a1, .L004000DC | ||
/* 000094 00400094 00001025 */ move $v0, $zero | ||
/* 000098 00400098 30A70003 */ andi $a3, $a1, 3 | ||
/* 00009C 0040009C 10E00007 */ beqz $a3, .L004000BC | ||
/* 0000A0 004000A0 00E03025 */ move $a2, $a3 | ||
/* 0000A4 004000A4 00801821 */ move $v1, $a0 | ||
.L004000A8: | ||
/* 0000A8 004000A8 24420001 */ addiu $v0, $v0, 1 | ||
/* 0000AC 004000AC A0600000 */ sb $zero, ($v1) | ||
/* 0000B0 004000B0 14C2FFFD */ bne $a2, $v0, .L004000A8 | ||
/* 0000B4 004000B4 24630001 */ addiu $v1, $v1, 1 | ||
/* 0000B8 004000B8 10450008 */ beq $v0, $a1, .L004000DC | ||
.L004000BC: | ||
/* 0000BC 004000BC 00821821 */ addu $v1, $a0, $v0 | ||
.L004000C0: | ||
/* 0000C0 004000C0 24420004 */ addiu $v0, $v0, 4 | ||
/* 0000C4 004000C4 A0600001 */ sb $zero, 1($v1) | ||
/* 0000C8 004000C8 A0600002 */ sb $zero, 2($v1) | ||
/* 0000CC 004000CC A0600003 */ sb $zero, 3($v1) | ||
/* 0000D0 004000D0 24630004 */ addiu $v1, $v1, 4 | ||
/* 0000D4 004000D4 1445FFFA */ bne $v0, $a1, .L004000C0 | ||
/* 0000D8 004000D8 A060FFFC */ sb $zero, -4($v1) | ||
.L004000DC: | ||
/* 0000DC 004000DC 03E00008 */ jr $ra | ||
/* 0000E0 004000E0 00000000 */ nop | ||
|
||
/* 0000E4 004000E4 00000000 */ nop | ||
/* 0000E8 004000E8 00000000 */ nop | ||
/* 0000EC 004000EC 00000000 */ nop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At first glance, I think the
break
should becontinue
(if it's OK to have missing edges) orreturn False
(if it's not OK) -- otherwise, right now the behavior seems dependent on the order of the children in the list?I think the fix here might actually be to iterate over
set(children)
instead. Ifn
isn't inincoming_edges[s]
, then I think that meanss.parents
was not constructed correctly? (Sorry that this might be a pre-existing bug)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does that mean
n.children()
might have repeats? That makes sense actually, and is probably why my extremely old version of this PR didn't need this fix (I changedchildren()
to aSet
.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wait, I'm wrong, my bad!
n.children()
shouldn't have repeats: the implementations for that fn inSwitchNode
&ConditionalNode
explicitly deduplicate the children.I can take a look and figure out what's going on here.