|
4 | 4 | # ------------------------------------------------------------------------------ |
5 | 5 | # The MIT License (MIT) |
6 | 6 | # |
7 | | -# Copyright (c) 2021-2024 Aarno Labs LLC |
| 7 | +# Copyright (c) 2021-2025 Aarno Labs LLC |
8 | 8 | # |
9 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
10 | 10 | # of this software and associated documentation files (the "Software"), to deal |
|
52 | 52 | import chb.app.Function |
53 | 53 |
|
54 | 54 |
|
| 55 | +class BasicBlockFragment: |
| 56 | + """Represents a basic block fragment without ast control flow. |
| 57 | +
|
| 58 | + In ARM instructions may be predicated, e.g.: |
| 59 | +
|
| 60 | + MOVEQ R0, R1 : if condition then R0 := R1 |
| 61 | +
|
| 62 | + In the ocaml analyzer this additional control flow can be accomodated |
| 63 | + directly in CHIF without the need to create a separate basic block |
| 64 | + in the CFG (i.e. lightweight control flow). In decompilation, some of |
| 65 | + these instructions may be accomodated without explicit control flow (e.g., |
| 66 | + by the C ternary operation), but this is not always possible. |
| 67 | +
|
| 68 | + However, even when predicated instructions cannot be converted into |
| 69 | + expressions, it is not necessary to create top-level basic blocks |
| 70 | + that are subject to the CFG-to-AST conversion. A more light-weight |
| 71 | + solution is to embed the necessary control flow (notably limited |
| 72 | + to branches and instruction sequences) within the block created for |
| 73 | + the original basic block. |
| 74 | +
|
| 75 | + The basic block is partitioned into a linear sequence of BasicBlock |
| 76 | + Fragments, where each fragment can be one of the following: |
| 77 | + - a (linear) instruction sequence statement |
| 78 | + - a branch statement containing a condition and a single (if) branch |
| 79 | + - a branch statement containing a condition and a then and an else |
| 80 | + branch. |
| 81 | + In case of the branch statement either branch can have one or more |
| 82 | + instructions that have the same condition setter and the same |
| 83 | + condition. |
| 84 | + """ |
| 85 | + |
| 86 | + def __init__(self, instr: Instruction) -> None: |
| 87 | + self._linear: List[Instruction] = [] |
| 88 | + self._thenbranch: List[Instruction] = [] |
| 89 | + self._elsebranch: List[Instruction] = [] |
| 90 | + self._setter: Optional[str] = None |
| 91 | + self._condition: Optional[str] = None |
| 92 | + self.add_instr(instr) |
| 93 | + |
| 94 | + @property |
| 95 | + def condition(self) -> Optional[str]: |
| 96 | + return self._condition |
| 97 | + |
| 98 | + @property |
| 99 | + def setter(self) -> Optional[str]: |
| 100 | + return self._setter |
| 101 | + |
| 102 | + @property |
| 103 | + def is_predicated(self) -> bool: |
| 104 | + return self.condition is not None |
| 105 | + |
| 106 | + @property |
| 107 | + def is_then_only(self) -> bool: |
| 108 | + return self.is_predicated and len(self.elsebranch) == 0 |
| 109 | + |
| 110 | + @property |
| 111 | + def linear(self) -> List[Instruction]: |
| 112 | + return self._linear |
| 113 | + |
| 114 | + @property |
| 115 | + def thenbranch(self) -> List[Instruction]: |
| 116 | + return self._thenbranch |
| 117 | + |
| 118 | + @property |
| 119 | + def elsebranch(self) -> List[Instruction]: |
| 120 | + return self._elsebranch |
| 121 | + |
| 122 | + @property |
| 123 | + def is_empty(self) -> bool: |
| 124 | + return ( |
| 125 | + len(self.linear) + len(self.thenbranch) + len(self.elsebranch) == 0) |
| 126 | + |
| 127 | + def add_predicated_instr(self, instr: Instruction) -> None: |
| 128 | + if self.is_empty: |
| 129 | + self._condition = instr.get_instruction_cc() |
| 130 | + self._setter = instr.get_instruction_condition_setter() |
| 131 | + self.thenbranch.append(instr) |
| 132 | + elif self.is_predicated: |
| 133 | + if self.condition == instr.get_instruction_cc(): |
| 134 | + self.thenbranch.append(instr) |
| 135 | + else: |
| 136 | + self.elsebranch.append(instr) |
| 137 | + else: |
| 138 | + raise UF.CHBError("Cannot add predicated instruction to linear frag") |
| 139 | + |
| 140 | + def add_linear_instr(self, instr: Instruction) -> None: |
| 141 | + if self.is_empty or (not self.is_predicated): |
| 142 | + self.linear.append(instr) |
| 143 | + else: |
| 144 | + raise UF.CHBError( |
| 145 | + "Cannot add unpredicated instruction to predicate fragment") |
| 146 | + |
| 147 | + def add_instr(self, instr: Instruction) -> None: |
| 148 | + if instr.has_control_flow(): |
| 149 | + self.add_predicated_instr(instr) |
| 150 | + else: |
| 151 | + self.add_linear_instr(instr) |
| 152 | + |
| 153 | + def __str__(self) -> str: |
| 154 | + lines: List[str] = [] |
| 155 | + if self.condition: |
| 156 | + setter = " (" + self.setter + ")" if self.setter else "" |
| 157 | + lines.append("condition: " + self.condition + setter) |
| 158 | + if self.linear: |
| 159 | + lines.append("linear") |
| 160 | + for i in self.linear: |
| 161 | + lines.append(" " + str(i)) |
| 162 | + if self.thenbranch: |
| 163 | + lines.append("then:") |
| 164 | + for i in self.thenbranch: |
| 165 | + lines.append(" " + str(i)) |
| 166 | + if self.elsebranch: |
| 167 | + lines.append("else:") |
| 168 | + for i in self.elsebranch: |
| 169 | + lines.append(" " + str(i)) |
| 170 | + return "\n".join(lines) |
| 171 | + |
| 172 | + |
55 | 173 | class BasicBlock(ABC): |
56 | 174 |
|
57 | 175 | def __init__( |
58 | 176 | self, |
59 | 177 | xnode: ET.Element) -> None: |
60 | 178 | self.xnode = xnode |
| 179 | + self._partition: Dict[str, BasicBlockFragment] = {} |
| 180 | + |
| 181 | + @property |
| 182 | + def partition(self) -> Dict[str, BasicBlockFragment]: |
| 183 | + return self._partition |
61 | 184 |
|
62 | 185 | @property |
63 | 186 | def baddr(self) -> str: |
@@ -99,6 +222,55 @@ def has_return(self) -> bool: |
99 | 222 | def has_conditional_return(self) -> bool: |
100 | 223 | return self.last_instruction.is_conditional_return_instruction |
101 | 224 |
|
| 225 | + def has_control_flow(self) -> bool: |
| 226 | + """Returns true if this block contains predicated instructions that |
| 227 | + are not otherwise covered in aggregates or by other conditions. |
| 228 | +
|
| 229 | + The case of a block with a conditional return is already handled in |
| 230 | + the Cfg, so it is excluded here. |
| 231 | +
|
| 232 | + The case of a block with a conditional return and other conditional |
| 233 | + instructions is not yet handled. |
| 234 | + """ |
| 235 | + |
| 236 | + count = len([i for i in self.instructions.values() if i.has_control_flow()]) |
| 237 | + if count == 1 and self.has_conditional_return: |
| 238 | + return False |
| 239 | + |
| 240 | + return any(i.has_control_flow() for i in self.instructions.values()) |
| 241 | + |
| 242 | + def partition_control_flow(self) -> None: |
| 243 | + curblock: Optional[BasicBlockFragment] = None |
| 244 | + curaddr: Optional[str] = None |
| 245 | + |
| 246 | + for (a, i) in sorted(self.instructions.items()): |
| 247 | + if curaddr is None or curblock is None: |
| 248 | + curaddr = a |
| 249 | + curblock = BasicBlockFragment(i) |
| 250 | + else: |
| 251 | + if i.has_control_flow(): |
| 252 | + if curblock.is_predicated: |
| 253 | + if i.get_instruction_condition_setter() == curblock.setter: |
| 254 | + curblock.add_instr(i) |
| 255 | + else: |
| 256 | + self._partition[curaddr] = curblock |
| 257 | + curblock = BasicBlockFragment(i) |
| 258 | + curaddr = a |
| 259 | + else: |
| 260 | + self._partition[curaddr] = curblock |
| 261 | + curblock = BasicBlockFragment(i) |
| 262 | + curaddr = a |
| 263 | + else: |
| 264 | + if curblock.is_predicated: |
| 265 | + self._partition[curaddr] = curblock |
| 266 | + curblock = BasicBlockFragment(i) |
| 267 | + curaddr = a |
| 268 | + else: |
| 269 | + curblock.add_instr(i) |
| 270 | + |
| 271 | + if curaddr is not None and curblock is not None: |
| 272 | + self._partition[curaddr] = curblock |
| 273 | + |
102 | 274 | @property |
103 | 275 | @abstractmethod |
104 | 276 | def instructions(self) -> Mapping[str, Instruction]: |
|
0 commit comments