angr 9.2.130__py3-none-macosx_11_0_arm64.whl → 9.2.131__py3-none-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of angr might be problematic. Click here for more details.

Files changed (36) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/decompiler/clinic.py +283 -4
  3. angr/analyses/decompiler/optimization_passes/__init__.py +0 -3
  4. angr/analyses/decompiler/peephole_optimizations/__init__.py +5 -1
  5. angr/analyses/decompiler/peephole_optimizations/a_mul_const_sub_a.py +34 -0
  6. angr/analyses/decompiler/peephole_optimizations/a_shl_const_sub_a.py +3 -1
  7. angr/analyses/decompiler/peephole_optimizations/bswap.py +10 -6
  8. angr/analyses/decompiler/peephole_optimizations/eager_eval.py +100 -19
  9. angr/analyses/decompiler/peephole_optimizations/remove_noop_conversions.py +15 -0
  10. angr/analyses/decompiler/peephole_optimizations/remove_redundant_conversions.py +42 -3
  11. angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts.py +4 -2
  12. angr/analyses/decompiler/peephole_optimizations/rol_ror.py +37 -10
  13. angr/analyses/decompiler/peephole_optimizations/shl_to_mul.py +25 -0
  14. angr/analyses/decompiler/peephole_optimizations/utils.py +18 -0
  15. angr/analyses/decompiler/presets/fast.py +0 -2
  16. angr/analyses/decompiler/presets/full.py +0 -2
  17. angr/analyses/decompiler/ssailification/rewriting_engine.py +2 -2
  18. angr/analyses/decompiler/structured_codegen/c.py +74 -13
  19. angr/analyses/decompiler/structuring/phoenix.py +14 -5
  20. angr/analyses/s_propagator.py +20 -2
  21. angr/analyses/typehoon/simple_solver.py +9 -2
  22. angr/analyses/typehoon/typehoon.py +4 -1
  23. angr/analyses/variable_recovery/engine_ail.py +15 -15
  24. angr/analyses/variable_recovery/engine_base.py +3 -0
  25. angr/engines/light/engine.py +3 -3
  26. angr/engines/vex/claripy/irop.py +13 -2
  27. angr/lib/angr_native.dylib +0 -0
  28. angr/utils/bits.py +5 -0
  29. angr/utils/formatting.py +4 -1
  30. {angr-9.2.130.dist-info → angr-9.2.131.dist-info}/METADATA +6 -6
  31. {angr-9.2.130.dist-info → angr-9.2.131.dist-info}/RECORD +35 -33
  32. angr/analyses/decompiler/optimization_passes/multi_simplifier.py +0 -223
  33. {angr-9.2.130.dist-info → angr-9.2.131.dist-info}/LICENSE +0 -0
  34. {angr-9.2.130.dist-info → angr-9.2.131.dist-info}/WHEEL +0 -0
  35. {angr-9.2.130.dist-info → angr-9.2.131.dist-info}/entry_points.txt +0 -0
  36. {angr-9.2.130.dist-info → angr-9.2.131.dist-info}/top_level.txt +0 -0
angr/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
  # pylint: disable=wrong-import-position
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "9.2.130"
5
+ __version__ = "9.2.131"
6
6
 
7
7
  if bytes is str:
8
8
  raise Exception(
@@ -18,6 +18,7 @@ from angr.knowledge_plugins.functions import Function
18
18
  from angr.knowledge_plugins.cfg.memory_data import MemoryDataSort
19
19
  from angr.codenode import BlockNode
20
20
  from angr.utils import timethis
21
+ from angr.utils.graph import GraphUtils
21
22
  from angr.calling_conventions import SimRegArg, SimStackArg, SimFunctionArgument
22
23
  from angr.sim_type import (
23
24
  SimTypeChar,
@@ -115,6 +116,7 @@ class Clinic(Analysis):
115
116
  desired_variables: set[str] | None = None,
116
117
  force_loop_single_exit: bool = True,
117
118
  complete_successors: bool = False,
119
+ unsound_fix_abnormal_switches: bool = True,
118
120
  ):
119
121
  if not func.normalized and mode == ClinicMode.DECOMPILE:
120
122
  raise ValueError("Decompilation must work on normalized function graphs.")
@@ -146,6 +148,7 @@ class Clinic(Analysis):
146
148
  self._must_struct = must_struct
147
149
  self._reset_variable_names = reset_variable_names
148
150
  self._rewrite_ites_to_diamonds = rewrite_ites_to_diamonds
151
+ self._unsound_fix_abnormal_switches = unsound_fix_abnormal_switches
149
152
  self.reaching_definitions: ReachingDefinitionsAnalysis | None = None
150
153
  self._cache = cache
151
154
  self._mode = mode
@@ -265,6 +268,8 @@ class Clinic(Analysis):
265
268
  def _decompilation_fixups(self, ail_graph):
266
269
  is_pcode_arch = ":" in self.project.arch.name
267
270
 
271
+ # _fix_abnormal_switch_case_heads may re-lift from VEX blocks, so it should be placed as high up as possible
272
+ self._fix_abnormal_switch_case_heads(ail_graph)
268
273
  if self._rewrite_ites_to_diamonds:
269
274
  self._rewrite_ite_expressions(ail_graph)
270
275
  self._remove_redundant_jump_blocks(ail_graph)
@@ -941,7 +946,7 @@ class Clinic(Analysis):
941
946
 
942
947
  def _convert(self, block_node):
943
948
  """
944
- Convert a VEX block to an AIL block.
949
+ Convert a BlockNode to an AIL block.
945
950
 
946
951
  :param block_node: A BlockNode instance.
947
952
  :return: A converted AIL block.
@@ -955,13 +960,16 @@ class Clinic(Analysis):
955
960
  return ailment.Block(block_node.addr, 0, statements=[])
956
961
 
957
962
  block = self.project.factory.block(block_node.addr, block_node.size, cross_insn_opt=False)
963
+ return self._convert_vex(block)
964
+
965
+ def _convert_vex(self, block):
958
966
  if block.vex.jumpkind not in {"Ijk_Call", "Ijk_Boring", "Ijk_Ret"} and not block.vex.jumpkind.startswith(
959
967
  "Ijk_Sys"
960
968
  ):
961
969
  # we don't support lifting this block. use a dummy block instead
962
970
  dirty_expr = ailment.Expr.DirtyExpression(
963
971
  self._ail_manager.next_atom,
964
- f"Unsupported jumpkind {block.vex.jumpkind} at address {block_node.addr}",
972
+ f"Unsupported jumpkind {block.vex.jumpkind} at address {block.addr}",
965
973
  [],
966
974
  bits=0,
967
975
  )
@@ -969,10 +977,10 @@ class Clinic(Analysis):
969
977
  ailment.Stmt.DirtyStatement(
970
978
  self._ail_manager.next_atom(),
971
979
  dirty_expr,
972
- ins_addr=block_node.addr,
980
+ ins_addr=block.addr,
973
981
  )
974
982
  ]
975
- return ailment.Block(block_node.addr, block_node.size, statements=statements)
983
+ return ailment.Block(block.addr, block.size, statements=statements)
976
984
 
977
985
  return ailment.IRSBConverter.convert(block.vex, self._ail_manager)
978
986
 
@@ -2055,6 +2063,277 @@ class Clinic(Analysis):
2055
2063
 
2056
2064
  return end_block_ail.addr
2057
2065
 
2066
+ def _fix_abnormal_switch_case_heads(self, ail_graph: networkx.DiGraph) -> None:
2067
+ """
2068
+ Detect the existence of switch-case heads whose indirect jump node has more than one predecessor, and attempt
2069
+ to fix those cases by altering the graph.
2070
+ """
2071
+
2072
+ if self._cfg is None:
2073
+ return
2074
+
2075
+ if not self._cfg.jump_tables:
2076
+ return
2077
+
2078
+ node_dict: defaultdict[int, list[ailment.Block]] = defaultdict(list)
2079
+ for node in ail_graph:
2080
+ node_dict[node.addr].append(node)
2081
+
2082
+ candidates = []
2083
+ for block_addr in self._cfg.jump_tables:
2084
+ block_nodes = node_dict[block_addr]
2085
+ for block_node in block_nodes:
2086
+ if ail_graph.in_degree[block_node] > 1:
2087
+ # found it
2088
+ candidates.append(block_node)
2089
+
2090
+ if not candidates:
2091
+ return
2092
+
2093
+ sorted_nodes = GraphUtils.quasi_topological_sort_nodes(ail_graph)
2094
+ node_to_rank = {node: rank for rank, node in enumerate(sorted_nodes)}
2095
+ for candidate in candidates:
2096
+ # determine the "intended" switch-case head using topological order
2097
+ preds = list(ail_graph.predecessors(candidate))
2098
+ preds = sorted(preds, key=lambda n_: node_to_rank[n_])
2099
+ intended_head = preds[0]
2100
+ other_heads = preds[1:]
2101
+
2102
+ # now here is the tricky part. there are two cases:
2103
+ # Case 1: the intended head and the other heads share the same suffix (of instructions)
2104
+ # Example:
2105
+ # ; binary 736cb27201273f6c4f83da362c9595b50d12333362e02bc7a77dd327cc6b045a
2106
+ # 0041DA97 mov ecx, [esp+2Ch+var_18] ; this is the intended head
2107
+ # 0041DA9B mov ecx, [ecx]
2108
+ # 0041DA9D cmp ecx, 9
2109
+ # 0041DAA0 jbe loc_41D5A8
2110
+ #
2111
+ # 0041D599 mov ecx, [ecx] ; this is the other head
2112
+ # 0041D59B mov [esp+2Ch+var_10], eax
2113
+ # 0041D59F cmp ecx, 9
2114
+ # 0041D5A2 ja loc_41DAA6 ; fallthrough to 0x41d5a8
2115
+ # given the overlap of two instructions at the end of both blocks, we will alter the second block to remove
2116
+ # the overlapped instructions and add an unconditional jump so that it jumps to 0x41da9d.
2117
+ # this is the most common case created by jump threading optimization in compilers. it's easy to handle.
2118
+
2119
+ # Case 2: the intended head and the other heads do not share the same suffix of instructions. in this case,
2120
+ # we cannot reliably convert the blocks into a properly structured switch-case construct. we will alter the
2121
+ # last instruction of all other heads to jump to the cmp instruction in the intended head, but do not remove
2122
+ # any other instructions in these other heads. this is unsound, but is the best we can do in this case.
2123
+
2124
+ overlaps = [self._get_overlapping_suffix_instructions(intended_head, head) for head in other_heads]
2125
+ if overlaps and (overlap := min(overlaps)) > 0:
2126
+ # Case 1
2127
+ self._fix_abnormal_switch_case_heads_case1(ail_graph, candidate, intended_head, other_heads, overlap)
2128
+ else:
2129
+ if self._unsound_fix_abnormal_switches:
2130
+ # Case 2
2131
+ l.warning("Switch-case at %#x has multiple head nodes but cannot be fixed soundly.", candidate.addr)
2132
+ # find the comparison instruction in the intended head
2133
+ comparison_stmt = None
2134
+ if "cc_op" in self.project.arch.registers:
2135
+ comparison_stmt = next(
2136
+ iter(
2137
+ stmt
2138
+ for stmt in intended_head.statements
2139
+ if isinstance(stmt, ailment.Stmt.Assignment)
2140
+ and isinstance(stmt.dst, ailment.Expr.Register)
2141
+ and stmt.dst.reg_offset == self.project.arch.registers["cc_op"][0]
2142
+ ),
2143
+ None,
2144
+ )
2145
+ if comparison_stmt is not None:
2146
+ intended_head_block = self.project.factory.block(
2147
+ intended_head.addr, size=intended_head.original_size
2148
+ )
2149
+ cmp_rpos = len(
2150
+ intended_head_block.instruction_addrs
2151
+ ) - intended_head_block.instruction_addrs.index(comparison_stmt.ins_addr)
2152
+ else:
2153
+ cmp_rpos = 2
2154
+ self._fix_abnormal_switch_case_heads_case2(
2155
+ ail_graph,
2156
+ candidate,
2157
+ intended_head,
2158
+ other_heads,
2159
+ intended_head_split_insns=cmp_rpos,
2160
+ other_head_split_insns=0,
2161
+ )
2162
+
2163
+ def _get_overlapping_suffix_instructions(self, ailblock_0: ailment.Block, ailblock_1: ailment.Block) -> int:
2164
+ # we first compare their ending conditional jumps
2165
+ if not self._get_overlapping_suffix_instructions_compare_conditional_jumps(ailblock_0, ailblock_1):
2166
+ return 0
2167
+
2168
+ # we re-lift the blocks and compare the instructions
2169
+ block_0 = self.project.factory.block(ailblock_0.addr, size=ailblock_0.original_size)
2170
+ block_1 = self.project.factory.block(ailblock_1.addr, size=ailblock_1.original_size)
2171
+
2172
+ i0 = len(block_0.capstone.insns) - 2
2173
+ i1 = len(block_1.capstone.insns) - 2
2174
+ overlap = 1
2175
+ while i0 >= 0 and i1 >= 0:
2176
+ same = self._get_overlapping_suffix_instructions_compare_instructions(
2177
+ block_0.capstone.insns[i0], block_1.capstone.insns[i1]
2178
+ )
2179
+ if not same:
2180
+ break
2181
+ overlap += 1
2182
+ i0 -= 1
2183
+ i1 -= 1
2184
+
2185
+ return overlap
2186
+
2187
+ @staticmethod
2188
+ def _get_overlapping_suffix_instructions_compare_instructions(insn_0, insn_1) -> bool:
2189
+ return insn_0.mnemonic == insn_1.mnemonic and insn_0.op_str == insn_1.op_str
2190
+
2191
+ @staticmethod
2192
+ def _get_overlapping_suffix_instructions_compare_conditional_jumps(
2193
+ ailblock_0: ailment.Block, ailblock_1: ailment.Block
2194
+ ) -> bool:
2195
+ # TODO: The logic here is naive and highly customized to the only example I can access. Expand this method
2196
+ # later to handle more cases if needed.
2197
+ if len(ailblock_0.statements) == 0 or len(ailblock_1.statements) == 0:
2198
+ return False
2199
+
2200
+ # 12 | 0x41d5a2 | t17 = (t4 <= 0x9<32>)
2201
+ # 13 | 0x41d5a2 | t16 = Conv(1->32, t17)
2202
+ # 14 | 0x41d5a2 | t14 = t16
2203
+ # 15 | 0x41d5a2 | t18 = Conv(32->1, t14)
2204
+ # 16 | 0x41d5a2 | t9 = t18
2205
+ # 17 | 0x41d5a2 | if (t9) { Goto 0x41d5a8<32> } else { Goto 0x41daa6<32> }
2206
+
2207
+ last_stmt_0 = ailblock_0.statements[-1]
2208
+ last_stmt_1 = ailblock_1.statements[-1]
2209
+ if not (isinstance(last_stmt_0, ailment.Stmt.ConditionalJump) and last_stmt_0.likes(last_stmt_1)):
2210
+ return False
2211
+
2212
+ last_cmp_stmt_0 = next(
2213
+ iter(
2214
+ stmt
2215
+ for stmt in reversed(ailblock_0.statements)
2216
+ if isinstance(stmt, ailment.Stmt.Assignment)
2217
+ and isinstance(stmt.src, ailment.Expr.BinaryOp)
2218
+ and stmt.src.op in ailment.Expr.BinaryOp.COMPARISON_NEGATION
2219
+ and isinstance(stmt.src.operands[1], ailment.Expr.Const)
2220
+ and stmt.ins_addr == last_stmt_0.ins_addr
2221
+ ),
2222
+ None,
2223
+ )
2224
+ last_cmp_stmt_1 = next(
2225
+ iter(
2226
+ stmt
2227
+ for stmt in reversed(ailblock_1.statements)
2228
+ if isinstance(stmt, ailment.Stmt.Assignment)
2229
+ and isinstance(stmt.src, ailment.Expr.BinaryOp)
2230
+ and stmt.src.op in ailment.Expr.BinaryOp.COMPARISON_NEGATION
2231
+ and isinstance(stmt.src.operands[1], ailment.Expr.Const)
2232
+ and stmt.ins_addr == last_stmt_1.ins_addr
2233
+ ),
2234
+ None,
2235
+ )
2236
+ return (
2237
+ last_cmp_stmt_0 is not None
2238
+ and last_cmp_stmt_1 is not None
2239
+ and last_cmp_stmt_0.src.op == last_cmp_stmt_1.src.op
2240
+ and last_cmp_stmt_0.src.operands[1].likes(last_cmp_stmt_1.src.operands[1])
2241
+ )
2242
+
2243
+ def _fix_abnormal_switch_case_heads_case1(
2244
+ self,
2245
+ ail_graph: networkx.DiGraph,
2246
+ indirect_jump_node: ailment.Block,
2247
+ intended_head: ailment.Block,
2248
+ other_heads: list[ailment.Block],
2249
+ overlap: int,
2250
+ ) -> None:
2251
+ self._fix_abnormal_switch_case_heads_case2(
2252
+ ail_graph,
2253
+ indirect_jump_node,
2254
+ intended_head,
2255
+ other_heads,
2256
+ intended_head_split_insns=overlap,
2257
+ other_head_split_insns=overlap,
2258
+ )
2259
+
2260
+ def _fix_abnormal_switch_case_heads_case2(
2261
+ self,
2262
+ ail_graph: networkx.DiGraph,
2263
+ indirect_jump_node: ailment.Block,
2264
+ intended_head: ailment.Block,
2265
+ other_heads: list[ailment.Block],
2266
+ intended_head_split_insns: int = 1,
2267
+ other_head_split_insns: int = 0,
2268
+ ) -> None:
2269
+
2270
+ # split the intended head into two
2271
+ intended_head_block = self.project.factory.block(intended_head.addr, size=intended_head.original_size)
2272
+ split_ins_addr = intended_head_block.instruction_addrs[-intended_head_split_insns]
2273
+ intended_head_block_0 = self.project.factory.block(intended_head.addr, size=split_ins_addr - intended_head.addr)
2274
+ intended_head_block_1 = self.project.factory.block(
2275
+ split_ins_addr, size=intended_head.addr + intended_head.original_size - split_ins_addr
2276
+ )
2277
+ intended_head_0 = self._convert_vex(intended_head_block_0)
2278
+ intended_head_1 = self._convert_vex(intended_head_block_1)
2279
+
2280
+ # adjust the graph accordingly
2281
+ preds = list(ail_graph.predecessors(intended_head))
2282
+ succs = list(ail_graph.successors(intended_head))
2283
+ ail_graph.remove_node(intended_head)
2284
+ ail_graph.add_edge(intended_head_0, intended_head_1)
2285
+ for pred in preds:
2286
+ if pred is intended_head:
2287
+ ail_graph.add_edge(intended_head_1, intended_head_0)
2288
+ else:
2289
+ ail_graph.add_edge(pred, intended_head_0)
2290
+ for succ in succs:
2291
+ if succ is intended_head:
2292
+ ail_graph.add_edge(intended_head_1, intended_head_0)
2293
+ else:
2294
+ ail_graph.add_edge(intended_head_1, succ)
2295
+
2296
+ # split other heads
2297
+ for o in other_heads:
2298
+ if other_head_split_insns > 0:
2299
+ o_block = self.project.factory.block(o.addr, size=o.original_size)
2300
+ o_split_addr = o_block.instruction_addrs[-other_head_split_insns]
2301
+ new_o_block = self.project.factory.block(o.addr, size=o_split_addr - o.addr)
2302
+ new_head = self._convert_vex(new_o_block)
2303
+ else:
2304
+ new_head = o
2305
+
2306
+ if (
2307
+ new_head.statements
2308
+ and isinstance(new_head.statements[-1], ailment.Stmt.Jump)
2309
+ and isinstance(new_head.statements[-1].target, ailment.Expr.Const)
2310
+ ):
2311
+ # update the jump target
2312
+ new_head.statements[-1] = ailment.Stmt.Jump(
2313
+ new_head.statements[-1].idx,
2314
+ ailment.Expr.Const(None, None, intended_head_1.addr, self.project.arch.bits),
2315
+ target_idx=intended_head_1.idx,
2316
+ **new_head.statements[-1].tags,
2317
+ )
2318
+
2319
+ # adjust the graph accordingly
2320
+ preds = list(ail_graph.predecessors(o))
2321
+ succs = list(ail_graph.successors(o))
2322
+ ail_graph.remove_node(o)
2323
+ for pred in preds:
2324
+ if pred is o:
2325
+ ail_graph.add_edge(new_head, new_head)
2326
+ else:
2327
+ ail_graph.add_edge(pred, new_head)
2328
+ for succ in succs:
2329
+ if succ is o:
2330
+ ail_graph.add_edge(new_head, new_head)
2331
+ elif succ is indirect_jump_node:
2332
+ ail_graph.add_edge(new_head, intended_head_1)
2333
+ else:
2334
+ # it should be going to the default node. ignore it
2335
+ pass
2336
+
2058
2337
  @staticmethod
2059
2338
  def _remove_redundant_jump_blocks(ail_graph):
2060
2339
  def first_conditional_jump(block: ailment.Block) -> ailment.Stmt.ConditionalJump | None:
@@ -11,7 +11,6 @@ from .expr_op_swapper import ExprOpSwapper
11
11
  from .ite_region_converter import ITERegionConverter
12
12
  from .ite_expr_converter import ITEExprConverter
13
13
  from .lowered_switch_simplifier import LoweredSwitchSimplifier
14
- from .multi_simplifier import MultiSimplifier
15
14
  from .div_simplifier import DivSimplifier
16
15
  from .mod_simplifier import ModSimplifier
17
16
  from .return_duplicator_low import ReturnDuplicatorLow
@@ -45,7 +44,6 @@ ALL_OPTIMIZATION_PASSES = [
45
44
  WinStackCanarySimplifier,
46
45
  BasePointerSaveSimplifier,
47
46
  DivSimplifier,
48
- MultiSimplifier,
49
47
  ModSimplifier,
50
48
  ConstantDereferencesSimplifier,
51
49
  RetAddrSaveSimplifier,
@@ -116,7 +114,6 @@ __all__ = (
116
114
  "ITERegionConverter",
117
115
  "ITEExprConverter",
118
116
  "LoweredSwitchSimplifier",
119
- "MultiSimplifier",
120
117
  "DivSimplifier",
121
118
  "ModSimplifier",
122
119
  "ReturnDuplicatorLow",
@@ -13,6 +13,7 @@ from .const_mull_a_shift import ConstMullAShift
13
13
  from .extended_byte_and_mask import ExtendedByteAndMask
14
14
  from .remove_empty_if_body import RemoveEmptyIfBody
15
15
  from .remove_redundant_ite_branch import RemoveRedundantITEBranches
16
+ from .shl_to_mul import ShlToMul
16
17
  from .single_bit_xor import SingleBitXor
17
18
  from .a_sub_a_sub_n import ASubASubN
18
19
  from .conv_a_sub0_shr_and import ConvASub0ShrAnd
@@ -45,13 +46,15 @@ from .inlined_strcpy_consolidation import InlinedStrcpyConsolidation
45
46
  from .inlined_wstrcpy import InlinedWstrcpy
46
47
  from .cmpord_rewriter import CmpORDRewriter
47
48
  from .coalesce_adjacent_shrs import CoalesceAdjacentShiftRights
48
-
49
+ from .a_mul_const_sub_a import AMulConstSubA
49
50
  from .base import PeepholeOptimizationExprBase, PeepholeOptimizationStmtBase, PeepholeOptimizationMultiStmtBase
50
51
 
52
+
51
53
  ALL_PEEPHOLE_OPTS: list[type[PeepholeOptimizationExprBase]] = [
52
54
  ADivConstAddAMulNDivConst,
53
55
  AMulConstDivShrConst,
54
56
  AShlConstSubA,
57
+ AMulConstSubA,
55
58
  ASubADiv,
56
59
  ASubADivConstMulConst,
57
60
  ARMCmpF,
@@ -94,6 +97,7 @@ ALL_PEEPHOLE_OPTS: list[type[PeepholeOptimizationExprBase]] = [
94
97
  InlinedWstrcpy,
95
98
  CmpORDRewriter,
96
99
  CoalesceAdjacentShiftRights,
100
+ ShlToMul,
97
101
  ]
98
102
 
99
103
  MULTI_STMT_OPTS: list[type[PeepholeOptimizationMultiStmtBase]] = [
@@ -0,0 +1,34 @@
1
+ # pylint:disable=missing-class-docstring,no-self-use,too-many-boolean-expressions
2
+ from __future__ import annotations
3
+ from ailment.expression import BinaryOp, Const
4
+
5
+ from .base import PeepholeOptimizationExprBase
6
+
7
+
8
+ class AMulConstSubA(PeepholeOptimizationExprBase):
9
+ __slots__ = ()
10
+
11
+ NAME = "a * N - a => a * (N - 1)"
12
+ expr_classes = (BinaryOp,)
13
+
14
+ def optimize(self, expr: BinaryOp, **kwargs):
15
+ if (
16
+ expr.op == "Sub"
17
+ and len(expr.operands) == 2
18
+ and isinstance(expr.operands[0], BinaryOp)
19
+ and expr.operands[0].op == "Mul"
20
+ and isinstance(expr.operands[0].operands[1], Const)
21
+ and expr.signed == expr.operands[0].signed
22
+ ):
23
+ a = expr.operands[1]
24
+ if expr.operands[0].operands[0].likes(a):
25
+ N = expr.operands[0].operands[1].value
26
+ return BinaryOp(
27
+ expr.idx,
28
+ "Mul",
29
+ [a, Const(None, None, N - 1, expr.bits, **expr.operands[0].operands[1].tags)],
30
+ expr.signed,
31
+ **expr.tags,
32
+ )
33
+
34
+ return None
@@ -1,3 +1,4 @@
1
+ # pylint:disable=missing-class-docstring,no-self-use
1
2
  from __future__ import annotations
2
3
  from ailment.expression import BinaryOp, Const
3
4
 
@@ -17,6 +18,7 @@ class AShlConstSubA(PeepholeOptimizationExprBase):
17
18
  and isinstance(expr.operands[0], BinaryOp)
18
19
  and expr.operands[0].op == "Shl"
19
20
  and isinstance(expr.operands[0].operands[1], Const)
21
+ and expr.signed == expr.operands[0].signed
20
22
  ):
21
23
  a = expr.operands[1]
22
24
  if expr.operands[0].operands[0].likes(a):
@@ -25,7 +27,7 @@ class AShlConstSubA(PeepholeOptimizationExprBase):
25
27
  expr.idx,
26
28
  "Mul",
27
29
  [a, Const(None, None, 2**N - 1, expr.bits, **expr.operands[0].operands[1].tags)],
28
- False,
30
+ expr.signed,
29
31
  **expr.tags,
30
32
  )
31
33
 
@@ -1,8 +1,10 @@
1
+ # pylint:disable=missing-class-docstring,no-self-use
1
2
  from __future__ import annotations
2
3
  from ailment.expression import BinaryOp, Const, Expression, Convert
3
4
  from ailment.statement import Call
4
5
 
5
6
  from .base import PeepholeOptimizationExprBase
7
+ from .utils import get_expr_shift_left_amount
6
8
 
7
9
 
8
10
  class Bswap(PeepholeOptimizationExprBase):
@@ -69,19 +71,21 @@ class Bswap(PeepholeOptimizationExprBase):
69
71
  cores = set()
70
72
  for piece in or_pieces:
71
73
  if isinstance(piece, BinaryOp):
72
- if piece.op == "Shl" and isinstance(piece.operands[1], Const):
74
+ if piece.op in {"Shl", "Mul"} and isinstance(piece.operands[1], Const):
73
75
  cores.add(piece.operands[0])
74
- shifts.add(("<<", piece.operands[1].value, 0xFFFFFFFF))
76
+ shift_amount = get_expr_shift_left_amount(piece)
77
+ shifts.add(("<<", shift_amount, 0xFFFFFFFF))
75
78
  elif piece.op == "And" and isinstance(piece.operands[1], Const):
76
79
  and_amount = piece.operands[1].value
77
80
  and_core = piece.operands[0]
78
81
  if (
79
82
  isinstance(and_core, BinaryOp)
80
- and and_core.op == "Shl"
83
+ and and_core.op in {"Shl", "Mul"}
81
84
  and isinstance(and_core.operands[1], Const)
82
85
  ):
83
86
  cores.add(and_core.operands[0])
84
- shifts.add(("<<", and_core.operands[1].value, and_amount))
87
+ shift_amount = get_expr_shift_left_amount(and_core)
88
+ shifts.add(("<<", shift_amount, and_amount))
85
89
  elif (
86
90
  isinstance(and_core, BinaryOp)
87
91
  and and_core.op == "Shr"
@@ -112,9 +116,9 @@ class Bswap(PeepholeOptimizationExprBase):
112
116
  if (
113
117
  (
114
118
  isinstance(inner_first, BinaryOp)
115
- and inner_first.op == "Shl"
119
+ and inner_first.op in {"Shl", "Mul"}
116
120
  and isinstance(inner_first.operands[1], Const)
117
- and inner_first.operands[1].value == 8
121
+ and get_expr_shift_left_amount(inner_first) == 8
118
122
  )
119
123
  and (
120
124
  isinstance(inner_second, BinaryOp)