From 8f072d626e894e7adc35cb062bd090a9f137766d Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 24 Nov 2024 08:42:33 -0500 Subject: [PATCH] Distinguish kinds of conditional jumps... In dotio, make "jump true" branches be bold. --- control_flow/bb.py | 27 ++++++++++++++++++++------- control_flow/cfg.py | 15 ++++++++++++--- control_flow/dotio.py | 12 +++++++----- control_flow/graph.py | 34 ++++++++++++++++++++++------------ 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/control_flow/bb.py b/control_flow/bb.py index 4882a79..32c567f 100644 --- a/control_flow/bb.py +++ b/control_flow/bb.py @@ -15,7 +15,9 @@ BB_EXIT, BB_FINALLY, BB_FOR, - BB_JUMP_CONDITIONAL, + BB_JUMP_BACKWARD_IF_FALSE, + BB_JUMP_FORWARD_IF_FALSE, + BB_JUMP_FORWARD_IF_TRUE, BB_JUMP_TO_FALLTHROUGH, BB_JUMP_UNCONDITIONAL, BB_LOOP, @@ -252,15 +254,21 @@ def __init__(self, version=PYTHON_VERSION_TRIPLE, is_pypy=IS_PYPY): else: self.EXCEPT_INSTRUCTIONS.add(opcode.opmap["RAISE_VARARGS"]) - self.JUMP_CONDITIONAL = set() + self.JUMP_IF_FALSE = set() for opname in ( - "POP_JUMP_IF_FALSE", - "POP_JUMP_IF_TRUE", "JUMP_IF_FALSE_OR_POP", + "POP_JUMP_IF_FALSE", + ): + if opname in opcode.opmap: + self.JUMP_IF_FALSE.add(opcode.opmap[opname]) + + self.JUMP_IF_TRUE = set() + for opname in ( "JUMP_IF_TRUE_OR_POP", + "POP_JUMP_IF_TRUE", ): if opname in opcode.opmap: - self.JUMP_CONDITIONAL.add(opcode.opmap[opname]) + self.JUMP_IF_TRUE.add(opcode.opmap[opname]) self.NOFOLLOW_INSTRUCTIONS = { opcode.opmap["RETURN_VALUE"], @@ -465,8 +473,13 @@ def basic_blocks( last_line_number = inst.starts_line # Add block flags for certain classes of instructions - if op in bb.JUMP_CONDITIONAL: - flags.add(BB_JUMP_CONDITIONAL) + if op in bb.JUMP_IF_FALSE: + jump_type = BB_JUMP_FORWARD_IF_FALSE if inst.argval > inst.offset else BB_JUMP_BACKWARD_IF_FALSE + flags.add(jump_type) + + if op in bb.JUMP_IF_TRUE: + jump_type = BB_JUMP_FORWARD_IF_TRUE if inst.argval > inst.offset else BB_JUMP_BACKWARD_IF_FALSE + flags.add(jump_type) if op in bb.POP_BLOCK_INSTRUCTIONS: flags.add(BB_POP_BLOCK) diff --git a/control_flow/cfg.py b/control_flow/cfg.py index a85c3bb..b431030 100644 --- a/control_flow/cfg.py +++ b/control_flow/cfg.py @@ -10,7 +10,10 @@ TreeGraph, jump_flags, BB_JOIN_POINT, - BB_JUMP_CONDITIONAL, + BB_JUMP_BACKWARD_IF_FALSE, + BB_JUMP_BACKWARD_IF_TRUE, + BB_JUMP_FORWARD_IF_FALSE, + BB_JUMP_FORWARD_IF_TRUE, BB_LOOP, BB_NOFOLLOW, BB_ENTRY, @@ -184,8 +187,14 @@ def add_edge(source_node, dest_node, edge_kind: str) -> Edge: if jump_index > block.start_offset: if BB_LOOP in block.flags: edge_kind = "for-finish" - elif BB_JUMP_CONDITIONAL in self.block_nodes[block].flags: - edge_kind = "forward-conditional" + elif BB_JUMP_BACKWARD_IF_FALSE in self.block_nodes[block].flags: + edge_kind = "jump-backward-if-false" + elif BB_JUMP_BACKWARD_IF_TRUE in self.block_nodes[block].flags: + edge_kind = "jump-backward-if-true" + elif BB_JUMP_FORWARD_IF_FALSE in self.block_nodes[block].flags: + edge_kind = "jump-forward-if-false" + elif BB_JUMP_FORWARD_IF_TRUE in self.block_nodes[block].flags: + edge_kind = "jump-forward-if-true" else: edge_kind = "forward" else: diff --git a/control_flow/dotio.py b/control_flow/dotio.py index 11b8f1f..922c54c 100644 --- a/control_flow/dotio.py +++ b/control_flow/dotio.py @@ -53,7 +53,7 @@ MAX_COLOR_LEVELS: Final = len(BB_LEVEL_BACKGROUNDS) - 1 -flags_prefix: Final = "flags=" +flags_prefix: Final = "flags={" FEL: Final = len(flags_prefix) NODE_TEXT_WIDTH = 26 + FEL @@ -238,7 +238,9 @@ def add_edge(self, edge, exit_node: BasicBlock, edge_seen): style = '[style="invis"]' else: style = '[style="dashed"]' - elif edge.kind == "forward-conditional": + elif edge.kind in ("jump-backward-if-true", "jump-forward-if-true"): + style = '[style="dotted,bold"]' + elif edge.kind in ("jump-backward-if-false", "jump-forward-if-false"): style = '[style="dotted"]' nid1 = self.node_ids[edge.source] @@ -255,7 +257,7 @@ def add_edge(self, edge, exit_node: BasicBlock, edge_seen): edge_port, ) - def node_repr(self, node, align, is_exit, is_dominator_format: bool): + def node_repr(self, node, align, is_exit): jump_text = "" reach_offset_text = "" flag_text = "" @@ -270,7 +272,7 @@ def node_repr(self, node, align, is_exit, is_dominator_format: bool): format_flags_with_width( node.flags, NODE_TEXT_WIDTH - FEL, - align + (" " * (len("flags="))), + align + (" " * (len(flags_prefix))), ), ) else: @@ -332,7 +334,7 @@ def add_node( node.number, level, align, - self.node_repr(node.bb, align, is_exit, is_dominator_format), + self.node_repr(node.bb, align, is_exit), align, ) self.buffer += " block_%d %s%s;\n" % (node.number, style, label) diff --git a/control_flow/graph.py b/control_flow/graph.py index 66d49a6..41b8839 100644 --- a/control_flow/graph.py +++ b/control_flow/graph.py @@ -67,8 +67,11 @@ BB_EXIT = 13 # Has a conditional jump of some sort. This would be -# found in "if", and "while" constructs. -BB_JUMP_CONDITIONAL = 14 +# found in "if" constructs. +BB_JUMP_FORWARD_IF_FALSE = 14 +BB_JUMP_FORWARD_IF_TRUE = 15 +BB_JUMP_BACKWARD_IF_FALSE = 16 +BB_JUMP_BACKWARD_IF_TRUE = 17 # Jumps to what would be the fallthough. # If there were optimization, this instruction would be removed. @@ -78,17 +81,17 @@ # We mostly use it in drawing graphs to make # sure the jump arrow points straight down. -BB_JUMP_TO_FALLTHROUGH = 15 +BB_JUMP_TO_FALLTHROUGH = 18 # The beginning of the basic block is a join. -BB_JOIN_POINT = 16 +BB_JOIN_POINT = 19 # Basic block ends in a return or an raise that is not inside # a "try" block. -BB_RETURN = 17 +BB_RETURN = 20 # Unreachable block -BB_DEAD_CODE = 17 +BB_DEAD_CODE = 21 FLAG2NAME = { BB_ENTRY: "entry", @@ -102,7 +105,10 @@ BB_EXCEPT: "except", BB_JOIN_POINT: "join block", BB_JUMP_UNCONDITIONAL: "unconditional", - BB_JUMP_CONDITIONAL: "conditional jump", + BB_JUMP_BACKWARD_IF_FALSE: "jump backward if false", + BB_JUMP_BACKWARD_IF_TRUE: "jump backward if true", + BB_JUMP_FORWARD_IF_FALSE: "jump forward if false", + BB_JUMP_FORWARD_IF_TRUE: "jump forward if true", BB_JUMP_TO_FALLTHROUGH: "jump to fallthough", BB_FOR: "for", BB_FINALLY: "finally", @@ -165,7 +171,7 @@ def format_flags_with_width(flags, max_width, newline): pass pass - return result + (" " * remain) + return result + "}" + (" " * remain) class Node: @@ -268,10 +274,14 @@ def is_conditional_jump(self) -> bool: """Return True is edge is attached to a conditional jump instruction at its source. """ - return ( - BB_JUMP_CONDITIONAL in self.source.flags - and self.dest.bb.start_offset in self.source.bb.jump_offsets - ) + return { + BB_JUMP_FORWARD_IF_TRUE, + BB_JUMP_FORWARD_IF_FALSE, + BB_JUMP_BACKWARD_IF_TRUE, + BB_JUMP_BACKWARD_IF_FALSE, + }.intersection( + self.source.flags + ) and self.dest.bb.start_offset in self.source.bb.jump_offsets class DiGraph: