angr 9.2.138__py3-none-manylinux2014_x86_64.whl → 9.2.140__py3-none-manylinux2014_x86_64.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 (100) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/calling_convention.py +48 -21
  3. angr/analyses/calling_convention/fact_collector.py +59 -12
  4. angr/analyses/calling_convention/utils.py +2 -2
  5. angr/analyses/cfg/cfg_base.py +13 -0
  6. angr/analyses/cfg/cfg_fast.py +23 -4
  7. angr/analyses/decompiler/ail_simplifier.py +79 -53
  8. angr/analyses/decompiler/block_simplifier.py +0 -2
  9. angr/analyses/decompiler/callsite_maker.py +80 -14
  10. angr/analyses/decompiler/clinic.py +99 -80
  11. angr/analyses/decompiler/condition_processor.py +2 -2
  12. angr/analyses/decompiler/decompiler.py +19 -7
  13. angr/analyses/decompiler/dephication/rewriting_engine.py +16 -7
  14. angr/analyses/decompiler/expression_narrower.py +1 -1
  15. angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
  16. angr/analyses/decompiler/optimization_passes/condition_constprop.py +149 -0
  17. angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +8 -7
  18. angr/analyses/decompiler/optimization_passes/deadblock_remover.py +12 -3
  19. angr/analyses/decompiler/optimization_passes/inlined_string_transformation_simplifier.py +1 -1
  20. angr/analyses/decompiler/optimization_passes/ite_region_converter.py +21 -13
  21. angr/analyses/decompiler/optimization_passes/optimization_pass.py +21 -12
  22. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +17 -9
  23. angr/analyses/decompiler/optimization_passes/return_duplicator_high.py +7 -10
  24. angr/analyses/decompiler/peephole_optimizations/eager_eval.py +12 -1
  25. angr/analyses/decompiler/peephole_optimizations/remove_redundant_conversions.py +61 -25
  26. angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts.py +50 -1
  27. angr/analyses/decompiler/presets/fast.py +2 -0
  28. angr/analyses/decompiler/presets/full.py +2 -0
  29. angr/analyses/decompiler/region_simplifiers/expr_folding.py +259 -108
  30. angr/analyses/decompiler/region_simplifiers/region_simplifier.py +28 -9
  31. angr/analyses/decompiler/ssailification/rewriting_engine.py +20 -2
  32. angr/analyses/decompiler/ssailification/traversal_engine.py +4 -3
  33. angr/analyses/decompiler/structured_codegen/c.py +10 -3
  34. angr/analyses/decompiler/structuring/dream.py +28 -19
  35. angr/analyses/decompiler/structuring/phoenix.py +253 -89
  36. angr/analyses/decompiler/structuring/recursive_structurer.py +1 -0
  37. angr/analyses/decompiler/structuring/structurer_base.py +121 -46
  38. angr/analyses/decompiler/structuring/structurer_nodes.py +6 -1
  39. angr/analyses/decompiler/utils.py +60 -1
  40. angr/analyses/deobfuscator/api_obf_finder.py +13 -5
  41. angr/analyses/deobfuscator/api_obf_type2_finder.py +166 -0
  42. angr/analyses/deobfuscator/string_obf_finder.py +105 -18
  43. angr/analyses/forward_analysis/forward_analysis.py +1 -1
  44. angr/analyses/propagator/top_checker_mixin.py +6 -6
  45. angr/analyses/reaching_definitions/__init__.py +2 -1
  46. angr/analyses/reaching_definitions/dep_graph.py +1 -12
  47. angr/analyses/reaching_definitions/engine_vex.py +36 -31
  48. angr/analyses/reaching_definitions/function_handler.py +15 -2
  49. angr/analyses/reaching_definitions/rd_state.py +1 -37
  50. angr/analyses/reaching_definitions/reaching_definitions.py +13 -24
  51. angr/analyses/s_propagator.py +129 -87
  52. angr/analyses/s_reaching_definitions/s_rda_model.py +7 -1
  53. angr/analyses/s_reaching_definitions/s_rda_view.py +2 -2
  54. angr/analyses/s_reaching_definitions/s_reaching_definitions.py +3 -1
  55. angr/analyses/stack_pointer_tracker.py +36 -22
  56. angr/analyses/typehoon/simple_solver.py +45 -7
  57. angr/analyses/typehoon/typeconsts.py +18 -5
  58. angr/analyses/variable_recovery/engine_ail.py +1 -1
  59. angr/analyses/variable_recovery/engine_base.py +62 -67
  60. angr/analyses/variable_recovery/engine_vex.py +1 -1
  61. angr/analyses/variable_recovery/irsb_scanner.py +2 -2
  62. angr/block.py +69 -107
  63. angr/callable.py +14 -7
  64. angr/calling_conventions.py +81 -10
  65. angr/distributed/__init__.py +1 -1
  66. angr/engines/__init__.py +7 -8
  67. angr/engines/engine.py +3 -138
  68. angr/engines/failure.py +2 -2
  69. angr/engines/hook.py +2 -2
  70. angr/engines/light/engine.py +5 -10
  71. angr/engines/pcode/emulate.py +2 -2
  72. angr/engines/pcode/engine.py +2 -14
  73. angr/engines/pcode/lifter.py +2 -2
  74. angr/engines/procedure.py +2 -2
  75. angr/engines/soot/engine.py +2 -2
  76. angr/engines/soot/statements/switch.py +1 -1
  77. angr/engines/successors.py +123 -17
  78. angr/engines/syscall.py +2 -2
  79. angr/engines/unicorn.py +3 -3
  80. angr/engines/vex/heavy/heavy.py +3 -15
  81. angr/engines/vex/lifter.py +2 -2
  82. angr/engines/vex/light/light.py +2 -2
  83. angr/factory.py +4 -19
  84. angr/knowledge_plugins/cfg/cfg_model.py +3 -2
  85. angr/knowledge_plugins/key_definitions/atoms.py +8 -4
  86. angr/knowledge_plugins/key_definitions/live_definitions.py +41 -103
  87. angr/knowledge_plugins/labels.py +2 -2
  88. angr/knowledge_plugins/obfuscations.py +1 -0
  89. angr/knowledge_plugins/xrefs/xref_manager.py +4 -0
  90. angr/sim_type.py +19 -17
  91. angr/state_plugins/plugin.py +19 -4
  92. angr/storage/memory_mixins/memory_mixin.py +1 -1
  93. angr/storage/memory_mixins/paged_memory/pages/multi_values.py +10 -5
  94. angr/utils/ssa/__init__.py +119 -4
  95. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/METADATA +6 -6
  96. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/RECORD +100 -98
  97. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/LICENSE +0 -0
  98. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/WHEEL +0 -0
  99. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/entry_points.txt +0 -0
  100. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- # pylint:disable=no-self-use,missing-class-docstring
1
+ # pylint:disable=no-self-use,too-many-boolean-expressions
2
2
  from __future__ import annotations
3
3
  from ailment.expression import BinaryOp, Const, Convert
4
4
 
@@ -7,6 +7,10 @@ from .utils import get_expr_shift_left_amount
7
7
 
8
8
 
9
9
  class RemoveRedundantShifts(PeepholeOptimizationExprBase):
10
+ """
11
+ Remove redundant bitshift operations.
12
+ """
13
+
10
14
  __slots__ = ()
11
15
 
12
16
  NAME = "Remove redundant bitshifts"
@@ -43,4 +47,49 @@ class RemoveRedundantShifts(PeepholeOptimizationExprBase):
43
47
  if expr.op in {"Shl", "Shr", "Sar"} and isinstance(expr.operands[1], Const) and expr.operands[1].value == 0:
44
48
  return expr.operands[0]
45
49
 
50
+ mask_hi32bits = 0xFFFFFFFF_00000000
51
+ exp_32bits = 0x1_00000000
52
+ if (
53
+ expr.op == "Shr"
54
+ and isinstance(expr.operands[1], Const)
55
+ and expr.operands[1].value == 32
56
+ and isinstance(expr.operands[0], BinaryOp)
57
+ and expr.operands[0].op == "Or"
58
+ ):
59
+ op0, op1 = expr.operands[0].operands
60
+ if (
61
+ isinstance(op1, Convert)
62
+ and op1.from_bits == 32
63
+ and op1.to_bits == 64
64
+ and op1.from_type == Convert.TYPE_INT
65
+ and op1.to_type == Convert.TYPE_INT
66
+ and isinstance(op0, BinaryOp)
67
+ and op0.op == "And"
68
+ ):
69
+ # (expr<64-bits> & 0xffffffff_00000000) | Conv(32->64, expr<32-bits>)) >> 32 ==> expr<64-bits> >> 32
70
+ inner_op0, inner_op1 = op0.operands
71
+ if isinstance(inner_op1, Const) and inner_op1.value == mask_hi32bits:
72
+ if (
73
+ isinstance(inner_op0, BinaryOp)
74
+ and isinstance(inner_op0.operands[1], Const)
75
+ and inner_op0.operands[1].value == exp_32bits
76
+ ):
77
+ return inner_op0.operands[0]
78
+ return BinaryOp(expr.idx, "Shr", [inner_op0, expr.operands[1]], expr.signed, **expr.tags)
79
+ return BinaryOp(expr.idx, "Shr", [op0, expr.operands[1]], expr.signed, **expr.tags)
80
+
81
+ for op0, op1 in [expr.operands[0].operands, expr.operands[0].operands[::-1]]:
82
+ # ((v11 & 0xffff_ffff | 10.0 * 0x1_00000000) >> 32) ==> 10.0
83
+ if (
84
+ isinstance(op0, BinaryOp)
85
+ and op0.op == "And"
86
+ and isinstance(op0.operands[1], Const)
87
+ and op0.operands[1].value == 0xFFFF_FFFF
88
+ and isinstance(op1, BinaryOp)
89
+ and op1.op == "Mul"
90
+ and isinstance(op1.operands[1], Const)
91
+ and op1.operands[1].value == 0x1_0000_0000
92
+ ):
93
+ return op1.operands[0]
94
+
46
95
  return None
@@ -21,6 +21,7 @@ from angr.analyses.decompiler.optimization_passes import (
21
21
  CallStatementRewriter,
22
22
  DeadblockRemover,
23
23
  SwitchReusedEntryRewriter,
24
+ ConditionConstantPropagation,
24
25
  )
25
26
 
26
27
 
@@ -47,6 +48,7 @@ preset_fast = DecompilationPreset(
47
48
  FlipBooleanCmp,
48
49
  InlinedStringTransformationSimplifier,
49
50
  CallStatementRewriter,
51
+ ConditionConstantPropagation,
50
52
  ],
51
53
  )
52
54
 
@@ -26,6 +26,7 @@ from angr.analyses.decompiler.optimization_passes import (
26
26
  InlinedStringTransformationSimplifier,
27
27
  CallStatementRewriter,
28
28
  SwitchReusedEntryRewriter,
29
+ ConditionConstantPropagation,
29
30
  )
30
31
 
31
32
 
@@ -57,6 +58,7 @@ preset_full = DecompilationPreset(
57
58
  InlinedStringTransformationSimplifier,
58
59
  CallStatementRewriter,
59
60
  SwitchReusedEntryRewriter,
61
+ ConditionConstantPropagation,
60
62
  ],
61
63
  )
62
64
 
@@ -1,15 +1,16 @@
1
- # pylint:disable=missing-class-docstring,unused-argument
1
+ # pylint:disable=missing-class-docstring,unused-argument,consider-using-dict-items
2
2
  from __future__ import annotations
3
3
  from collections import defaultdict
4
- from typing import Any, TYPE_CHECKING
5
4
  from collections.abc import Iterable
5
+ from typing import Any, TYPE_CHECKING
6
6
 
7
7
  import ailment
8
8
  from ailment import Expression, Block, AILBlockWalker
9
- from ailment.expression import ITE
10
- from ailment.statement import Statement, Assignment, Call
9
+ from ailment.expression import ITE, Load
10
+ from ailment.statement import Statement, Assignment, Call, Return
11
11
 
12
12
  from angr.utils.ail import is_phi_assignment
13
+ from angr.utils.ssa import VVarUsesCollector
13
14
  from angr.analyses.decompiler.sequence_walker import SequenceWalker
14
15
  from angr.analyses.decompiler.structuring.structurer_nodes import (
15
16
  ConditionNode,
@@ -20,8 +21,6 @@ from angr.analyses.decompiler.structuring.structurer_nodes import (
20
21
  )
21
22
 
22
23
  if TYPE_CHECKING:
23
- from angr.sim_variable import SimVariable
24
- from angr.knowledge_plugins.variables.variable_manager import VariableManagerInternal
25
24
  from ailment.expression import MultiStatementExpression
26
25
 
27
26
 
@@ -30,6 +29,10 @@ class LocationBase:
30
29
 
31
30
 
32
31
  class StatementLocation(LocationBase):
32
+ """
33
+ Describes the location of a statement.
34
+ """
35
+
33
36
  __slots__ = (
34
37
  "block_addr",
35
38
  "block_idx",
@@ -60,6 +63,10 @@ class StatementLocation(LocationBase):
60
63
 
61
64
 
62
65
  class ExpressionLocation(LocationBase):
66
+ """
67
+ Describes the location of an expression.
68
+ """
69
+
63
70
  __slots__ = (
64
71
  "block_addr",
65
72
  "block_idx",
@@ -93,6 +100,10 @@ class ExpressionLocation(LocationBase):
93
100
 
94
101
 
95
102
  class ConditionLocation(LocationBase):
103
+ """
104
+ Describes the location of a condition.
105
+ """
106
+
96
107
  __slots__ = (
97
108
  "case_idx",
98
109
  "node_addr",
@@ -117,6 +128,10 @@ class ConditionLocation(LocationBase):
117
128
 
118
129
 
119
130
  class ConditionalBreakLocation(LocationBase):
131
+ """
132
+ Describes the location of a conditional break.
133
+ """
134
+
120
135
  __slots__ = ("node_addr",)
121
136
 
122
137
  def __init__(self, node_addr):
@@ -177,18 +192,18 @@ class ExpressionUseFinder(AILBlockWalker):
177
192
 
178
193
  def __init__(self):
179
194
  super().__init__()
180
- self.uses: defaultdict[SimVariable, set[tuple[Expression, ExpressionLocation | None]]] = defaultdict(set)
195
+ self.uses: defaultdict[int, set[tuple[Expression, ExpressionLocation | None]]] = defaultdict(set)
181
196
  self.has_load = False
182
197
 
183
198
  def _handle_expr(
184
199
  self, expr_idx: int, expr: Expression, stmt_idx: int, stmt: Statement | None, block: Block | None
185
200
  ) -> Any:
186
- if isinstance(expr, ailment.Expr.VirtualVariable) and expr.was_reg and expr.variable is not None:
201
+ if isinstance(expr, ailment.Expr.VirtualVariable) and expr.was_reg:
187
202
  if not (isinstance(stmt, ailment.Stmt.Assignment) and stmt.dst is expr):
188
203
  if block is not None:
189
- self.uses[expr.variable].add((expr, ExpressionLocation(block.addr, block.idx, stmt_idx, expr_idx)))
204
+ self.uses[expr.varid].add((expr, ExpressionLocation(block.addr, block.idx, stmt_idx, expr_idx)))
190
205
  else:
191
- self.uses[expr.variable].add((expr, None))
206
+ self.uses[expr.varid].add((expr, None))
192
207
  return None
193
208
  return super()._handle_expr(expr_idx, expr, stmt_idx, stmt, block)
194
209
 
@@ -202,7 +217,7 @@ class ExpressionCounter(SequenceWalker):
202
217
  Find all expressions that are assigned once and only used once.
203
218
  """
204
219
 
205
- def __init__(self, node, variable_manager):
220
+ def __init__(self, node):
206
221
  handlers = {
207
222
  ConditionalBreakNode: self._handle_ConditionalBreak,
208
223
  ConditionNode: self._handle_Condition,
@@ -215,20 +230,12 @@ class ExpressionCounter(SequenceWalker):
215
230
  # the current assignment depends on, StatementLocation of the assignment statement, a Boolean variable that
216
231
  # indicates if ExpressionUseFinder has succeeded or not)
217
232
  self.assignments: defaultdict[Any, set[tuple]] = defaultdict(set)
218
- self.uses: dict[SimVariable, set[tuple[Expression, LocationBase | None]]] = {}
219
- self._variable_manager: VariableManagerInternal = variable_manager
233
+ self.uses: dict[int, set[tuple[Expression, LocationBase | None]]] = {}
220
234
 
221
235
  super().__init__(handlers)
222
236
  self.walk(node)
223
237
 
224
- def _u(self, v) -> SimVariable | None:
225
- """
226
- Get unified variable for a given variable.
227
- """
228
-
229
- return self._variable_manager.unified_variable(v)
230
-
231
- def _handle_Statement(self, idx: int, stmt: ailment.Stmt, node: ailment.Block | LoopNode):
238
+ def _handle_Statement(self, idx: int, stmt: Statement, node: ailment.Block | LoopNode):
232
239
  if isinstance(stmt, ailment.Stmt.Assignment):
233
240
  if is_phi_assignment(stmt):
234
241
  return
@@ -237,72 +244,65 @@ class ExpressionCounter(SequenceWalker):
237
244
  and stmt.dst.was_reg
238
245
  and stmt.dst.variable is not None
239
246
  ):
240
- u = self._u(stmt.dst.variable)
241
- if u is not None:
242
- # dependency
243
- dependency_finder = ExpressionUseFinder()
244
- dependency_finder.walk_expression(stmt.src)
245
- dependencies = tuple({self._u(v) for v in dependency_finder.uses})
246
- self.assignments[u].add(
247
- (
248
- stmt.src,
249
- dependencies,
250
- StatementLocation(node.addr, node.idx if isinstance(node, ailment.Block) else None, idx),
251
- dependency_finder.has_load,
252
- )
247
+ # dependency
248
+ dependency_finder = ExpressionUseFinder()
249
+ dependency_finder.walk_expression(stmt.src)
250
+ dependencies = tuple(dependency_finder.uses)
251
+ self.assignments[stmt.dst.varid].add(
252
+ (
253
+ stmt.src,
254
+ dependencies,
255
+ StatementLocation(node.addr, node.idx if isinstance(node, ailment.Block) else None, idx),
256
+ dependency_finder.has_load,
253
257
  )
258
+ )
254
259
  if (
255
260
  isinstance(stmt, ailment.Stmt.Call)
256
261
  and isinstance(stmt.ret_expr, ailment.Expr.VirtualVariable)
257
262
  and stmt.ret_expr.was_reg
258
263
  and stmt.ret_expr.variable is not None
259
264
  ):
260
- u = self._u(stmt.ret_expr.variable)
261
- if u is not None:
262
- dependency_finder = ExpressionUseFinder()
263
- dependency_finder.walk_expression(stmt)
264
- dependencies = tuple({self._u(v) for v in dependency_finder.uses})
265
- self.assignments[u].add(
266
- (
267
- stmt,
268
- dependencies,
269
- StatementLocation(node.addr, node.idx if isinstance(node, ailment.Block) else None, idx),
270
- dependency_finder.has_load,
271
- )
265
+ dependency_finder = ExpressionUseFinder()
266
+ dependency_finder.walk_expression(stmt)
267
+ dependencies = tuple(dependency_finder.uses)
268
+ self.assignments[stmt.ret_expr.varid].add(
269
+ (
270
+ stmt,
271
+ dependencies,
272
+ StatementLocation(node.addr, node.idx if isinstance(node, ailment.Block) else None, idx),
273
+ dependency_finder.has_load,
272
274
  )
275
+ )
273
276
 
274
277
  def _handle_Block(self, node: ailment.Block, **kwargs):
275
- # find assignments
278
+ # find assignments and uses of variables
279
+ use_finder = ExpressionUseFinder()
276
280
  for idx, stmt in enumerate(node.statements):
277
281
  self._handle_Statement(idx, stmt, node)
282
+ if not is_phi_assignment(stmt):
283
+ use_finder.walk_statement(stmt)
278
284
 
279
- # walk the block and find uses of variables
280
- use_finder = ExpressionUseFinder()
281
- use_finder.walk(node)
282
-
283
- for v, content in use_finder.uses.items():
284
- u = self._u(v)
285
- if u is not None:
286
- if u not in self.uses:
287
- self.uses[u] = set()
288
- self.uses[u] |= content
285
+ for varid, content in use_finder.uses.items():
286
+ if varid not in self.uses:
287
+ self.uses[varid] = set()
288
+ self.uses[varid] |= content
289
289
 
290
- def _collect_assignments(self, expr: ailment.Expr, node) -> None:
290
+ def _collect_assignments(self, expr: Expression, node) -> None:
291
291
  finder = MultiStatementExpressionAssignmentFinder(self._handle_Statement)
292
292
  finder.walk_expression(expr, None, None, node)
293
293
 
294
- def _collect_uses(self, expr: Expression, loc: LocationBase):
294
+ def _collect_uses(self, expr: Expression | Statement, loc: LocationBase):
295
295
  use_finder = ExpressionUseFinder()
296
- use_finder.walk_expression(expr, stmt_idx=-1)
296
+ if isinstance(expr, Statement):
297
+ use_finder.walk_statement(expr)
298
+ else:
299
+ use_finder.walk_expression(expr, stmt_idx=-1)
297
300
 
298
- for var, uses in use_finder.uses.items():
299
- u = self._u(var)
300
- if u is None:
301
- continue
301
+ for varid, uses in use_finder.uses.items():
302
302
  for use in uses:
303
- if u not in self.uses:
304
- self.uses[u] = set()
305
- self.uses[u].add((use[0], loc))
303
+ if varid not in self.uses:
304
+ self.uses[varid] = set()
305
+ self.uses[varid].add((use[0], loc))
306
306
 
307
307
  def _handle_ConditionalBreak(self, node: ConditionalBreakNode, **kwargs):
308
308
  # collect uses on the condition expression
@@ -338,22 +338,185 @@ class ExpressionCounter(SequenceWalker):
338
338
  return super()._handle_SwitchCase(node, **kwargs)
339
339
 
340
340
 
341
- class ExpressionReplacer(AILBlockWalker):
342
- def __init__(self, assignments: dict, uses: dict, variable_manager):
341
+ class ExpressionSpotter(VVarUsesCollector):
342
+ """
343
+ ExpressionSpotter collects uses of vvars and existence of Call expressions.
344
+ """
345
+
346
+ def __init__(self):
343
347
  super().__init__()
348
+ self.has_calls: bool = False
349
+ self.has_loads: bool = False
350
+
351
+ def _handle_CallExpr(self, expr_idx: int, expr: Call, stmt_idx: int, stmt: Statement, block: Block | None):
352
+ self.has_calls = True
353
+ return super()._handle_CallExpr(expr_idx, expr, stmt_idx, stmt, block)
354
+
355
+ def _handle_Load(self, expr_idx: int, expr: Load, stmt_idx: int, stmt: Statement, block: Block | None):
356
+ self.has_loads = True
357
+ return super()._handle_Load(expr_idx, expr, stmt_idx, stmt, block)
358
+
359
+
360
+ class InterferenceChecker(SequenceWalker):
361
+ """
362
+ Detect for every pair of definition (assignment) - use if there is anything that may interfere with the definition.
363
+
364
+ Interferences may be caused by:
365
+
366
+ - another call
367
+ - function return
368
+ - store statements
369
+ - load expressions
370
+ - Condition and CascadingCondition nodes
371
+ """
372
+
373
+ def __init__(self, assignments: dict[int, Any], uses: dict[int, Any], node):
374
+ handlers = {
375
+ ailment.Block: self._handle_Block,
376
+ ConditionNode: self._handle_Condition,
377
+ ConditionalBreakNode: self._handle_ConditionalBreak,
378
+ SwitchCaseNode: self._handle_SwitchCase,
379
+ }
380
+
381
+ super().__init__(handlers, update_seqnode_in_place=False, force_forward_scan=True)
344
382
  self._assignments = assignments
345
383
  self._uses = uses
346
- self._variable_manager: VariableManagerInternal = variable_manager
384
+ self._assignment_interferences: dict[int, list] = {}
385
+ self.interfered_assignments: set[int] = set()
386
+ self.walk(node)
347
387
 
348
- def _u(self, v) -> SimVariable | None:
349
- """
350
- Get unified variable for a given variable.
351
- """
352
- return self._variable_manager.unified_variable(v)
388
+ def _after_spotting(self, obj, spotter: ExpressionSpotter) -> None:
389
+ if spotter.has_calls or spotter.has_loads:
390
+ # mark all existing assignments as interfered
391
+ for vid in list(self._assignment_interferences):
392
+ self._assignment_interferences[vid].append(obj)
393
+ if set(self._assignment_interferences).intersection(spotter.vvars):
394
+ for used_vvar_id in spotter.vvars:
395
+ if used_vvar_id in self._assignment_interferences:
396
+ if self._assignment_interferences[used_vvar_id]:
397
+ self.interfered_assignments.add(used_vvar_id)
398
+ del self._assignment_interferences[used_vvar_id]
353
399
 
354
- def _handle_MultiStatementExpression(
400
+ def _handle_Block(self, node: ailment.Block, **kwargs):
401
+ for stmt in node.statements:
402
+
403
+ # deal with uses
404
+ spotter = ExpressionSpotter()
405
+ # special case: we process the call arguments first, then the call itself. this is to allow more expression
406
+ # folding opportunities.
407
+ the_call = None
408
+ if isinstance(stmt, Assignment) and isinstance(stmt.src, ailment.Stmt.Call):
409
+ the_call = stmt.src
410
+ elif isinstance(stmt, ailment.Stmt.Call):
411
+ the_call = stmt
412
+ if the_call is not None:
413
+ spotter.walk_expression(the_call.target)
414
+ if the_call.args:
415
+ for arg in the_call.args:
416
+ spotter.walk_expression(arg)
417
+ self._after_spotting(the_call, spotter)
418
+ spotter.walk_statement(stmt)
419
+ self._after_spotting(stmt, spotter)
420
+
421
+ if isinstance(stmt, Return):
422
+ # mark all existing assignments as interfered
423
+ for vid in self._assignment_interferences:
424
+ self._assignment_interferences[vid].append(stmt)
425
+
426
+ if isinstance(stmt, ailment.Stmt.Store):
427
+ # mark all existing assignments as interfered
428
+ for vid in self._assignment_interferences:
429
+ self._assignment_interferences[vid].append(stmt)
430
+
431
+ if isinstance(stmt, ailment.Stmt.Call):
432
+ # mark all existing assignments as interfered
433
+ for vid in self._assignment_interferences:
434
+ self._assignment_interferences[vid].append(stmt)
435
+
436
+ # deal with defs
437
+ if (
438
+ isinstance(stmt, ailment.Stmt.Assignment)
439
+ and isinstance(stmt.dst, ailment.Expr.VirtualVariable)
440
+ and stmt.dst.was_reg
441
+ and stmt.dst.varid in self._assignments
442
+ ):
443
+ # we found this def
444
+ self._assignment_interferences[stmt.dst.varid] = []
445
+
446
+ if (
447
+ isinstance(stmt, ailment.Stmt.Call)
448
+ and isinstance(stmt.ret_expr, ailment.Expr.VirtualVariable)
449
+ and stmt.ret_expr.was_reg
450
+ and stmt.ret_expr.variable is not None
451
+ and stmt.ret_expr.varid in self._assignments
452
+ ):
453
+ # we found this def
454
+ self._assignment_interferences[stmt.ret_expr.varid] = []
455
+
456
+ def _handle_ConditionalBreak(self, node: ConditionalBreakNode, **kwargs):
457
+ spotter = ExpressionSpotter()
458
+ spotter.walk_expression(node.condition)
459
+ self._after_spotting(node, spotter)
460
+ return super()._handle_ConditionalBreak(node, **kwargs)
461
+
462
+ def _handle_Condition(self, node: ConditionNode, **kwargs):
463
+ spotter = ExpressionSpotter()
464
+ spotter.walk_expression(node.condition)
465
+ self._after_spotting(node, spotter)
466
+
467
+ # mark all existing assignments as interfered
468
+ for vid in self._assignment_interferences:
469
+ self._assignment_interferences[vid].append(node)
470
+
471
+ return super()._handle_Condition(node, **kwargs)
472
+
473
+ def _handle_CascadingCondition(self, node: CascadingConditionNode, **kwargs):
474
+ spotter = ExpressionSpotter()
475
+ for cond, _ in node.condition_and_nodes: # pylint:disable=consider-using-enumerate
476
+ spotter.walk_expression(cond)
477
+ self._after_spotting(node, spotter)
478
+
479
+ # mark all existing assignments as interfered
480
+ for vid in self._assignment_interferences:
481
+ self._assignment_interferences[vid].append(node)
482
+
483
+ return super()._handle_CascadingCondition(node, **kwargs)
484
+
485
+ def _handle_Loop(self, node: LoopNode, **kwargs):
486
+ spotter = ExpressionSpotter()
487
+
488
+ # iterator
489
+ if node.iterator is not None:
490
+ spotter.walk_statement(node.iterator)
491
+
492
+ # initializer
493
+ if node.initializer is not None:
494
+ spotter.walk_statement(node.initializer)
495
+
496
+ # condition
497
+ if node.condition is not None:
498
+ spotter.walk_expression(node.condition)
499
+
500
+ self._after_spotting(node, spotter)
501
+
502
+ return super()._handle_Loop(node, **kwargs)
503
+
504
+ def _handle_SwitchCase(self, node: SwitchCaseNode, **kwargs):
505
+ spotter = ExpressionSpotter()
506
+ spotter.walk_expression(node.switch_expr)
507
+ self._after_spotting(node, spotter)
508
+ return super()._handle_SwitchCase(node, **kwargs)
509
+
510
+
511
+ class ExpressionReplacer(AILBlockWalker):
512
+ def __init__(self, assignments: dict[int, Any], uses: dict[int, Any]):
513
+ super().__init__()
514
+ self._assignments = assignments
515
+ self._uses = uses
516
+
517
+ def _handle_MultiStatementExpression( # type: ignore
355
518
  self, expr_idx, expr: MultiStatementExpression, stmt_idx: int, stmt: Statement, block: Block | None
356
- ):
519
+ ) -> Expression | None:
357
520
  changed = False
358
521
  new_statements = []
359
522
  for idx, stmt_ in enumerate(expr.stmts):
@@ -386,7 +549,7 @@ class ExpressionReplacer(AILBlockWalker):
386
549
  if changed:
387
550
  if not new_statements:
388
551
  # it is no longer a multi-statement expression
389
- return new_expr
552
+ return new_expr # type: ignore
390
553
  expr_ = expr.copy()
391
554
  expr_.expr = new_expr
392
555
  expr_.stmts = new_statements
@@ -424,16 +587,14 @@ class ExpressionReplacer(AILBlockWalker):
424
587
  def _handle_expr(
425
588
  self, expr_idx: int, expr: Expression, stmt_idx: int, stmt: Statement | None, block: Block | None
426
589
  ) -> Any:
427
- if isinstance(expr, ailment.Expr.VirtualVariable) and expr.was_reg and expr.variable is not None:
428
- unified_var = self._u(expr.variable)
429
- if unified_var in self._uses:
430
- replace_with, _ = self._assignments[unified_var]
431
- return replace_with
590
+ if isinstance(expr, ailment.Expr.VirtualVariable) and expr.was_reg and expr.varid in self._uses:
591
+ replace_with, _ = self._assignments[expr.varid]
592
+ return replace_with
432
593
  return super()._handle_expr(expr_idx, expr, stmt_idx, stmt, block)
433
594
 
434
595
 
435
596
  class ExpressionFolder(SequenceWalker):
436
- def __init__(self, assignments: dict, uses: dict, node, variable_manager):
597
+ def __init__(self, assignments: dict[int, Any], uses: dict[int, Any], node):
437
598
  handlers = {
438
599
  ailment.Block: self._handle_Block,
439
600
  ConditionNode: self._handle_Condition,
@@ -444,15 +605,8 @@ class ExpressionFolder(SequenceWalker):
444
605
  super().__init__(handlers)
445
606
  self._assignments = assignments
446
607
  self._uses = uses
447
- self._variable_manager = variable_manager
448
608
  self.walk(node)
449
609
 
450
- def _u(self, v) -> SimVariable | None:
451
- """
452
- Get unified variable for a given variable.
453
- """
454
- return self._variable_manager.unified_variable(v)
455
-
456
610
  def _handle_Block(self, node: ailment.Block, **kwargs):
457
611
  # Walk the block to remove each assignment and replace uses of each variable
458
612
  new_stmts = []
@@ -461,45 +615,42 @@ class ExpressionFolder(SequenceWalker):
461
615
  isinstance(stmt, ailment.Stmt.Assignment)
462
616
  and isinstance(stmt.dst, ailment.Expr.VirtualVariable)
463
617
  and stmt.dst.was_reg
464
- and stmt.dst.variable is not None
618
+ and stmt.dst.varid in self._assignments
465
619
  ):
466
- unified_var = self._u(stmt.dst.variable)
467
- if unified_var in self._assignments:
468
- # remove this statement
469
- continue
620
+ # remove this statement
621
+ continue
470
622
  if (
471
623
  isinstance(stmt, ailment.Stmt.Call)
472
624
  and isinstance(stmt.ret_expr, ailment.Expr.VirtualVariable)
473
625
  and stmt.ret_expr.was_reg
474
626
  and stmt.ret_expr.variable is not None
627
+ and stmt.ret_expr.varid in self._assignments
475
628
  ):
476
- unified_var = self._u(stmt.ret_expr.variable)
477
- if unified_var in self._assignments:
478
- # remove this statement
479
- continue
629
+ # remove this statement
630
+ continue
480
631
  new_stmts.append(stmt)
481
632
  node.statements = new_stmts
482
633
 
483
634
  # Walk the block to replace the use of each variable
484
- replacer = ExpressionReplacer(self._assignments, self._uses, self._variable_manager)
635
+ replacer = ExpressionReplacer(self._assignments, self._uses)
485
636
  replacer.walk(node)
486
637
 
487
638
  def _handle_ConditionalBreak(self, node: ConditionalBreakNode, **kwargs):
488
- replacer = ExpressionReplacer(self._assignments, self._uses, self._variable_manager)
639
+ replacer = ExpressionReplacer(self._assignments, self._uses)
489
640
  r = replacer.walk_expression(node.condition)
490
641
  if r is not None and r is not node.condition:
491
642
  node.condition = r
492
643
  return super()._handle_ConditionalBreak(node, **kwargs)
493
644
 
494
645
  def _handle_Condition(self, node: ConditionNode, **kwargs):
495
- replacer = ExpressionReplacer(self._assignments, self._uses, self._variable_manager)
646
+ replacer = ExpressionReplacer(self._assignments, self._uses)
496
647
  r = replacer.walk_expression(node.condition)
497
648
  if r is not None and r is not node.condition:
498
649
  node.condition = r
499
650
  return super()._handle_Condition(node, **kwargs)
500
651
 
501
652
  def _handle_CascadingCondition(self, node: CascadingConditionNode, **kwargs):
502
- replacer = ExpressionReplacer(self._assignments, self._uses, self._variable_manager)
653
+ replacer = ExpressionReplacer(self._assignments, self._uses)
503
654
  for idx in range(len(node.condition_and_nodes)): # pylint:disable=consider-using-enumerate
504
655
  cond, _ = node.condition_and_nodes[idx]
505
656
  r = replacer.walk_expression(cond)
@@ -508,17 +659,17 @@ class ExpressionFolder(SequenceWalker):
508
659
  return super()._handle_CascadingCondition(node, **kwargs)
509
660
 
510
661
  def _handle_Loop(self, node: LoopNode, **kwargs):
511
- replacer = ExpressionReplacer(self._assignments, self._uses, self._variable_manager)
662
+ replacer = ExpressionReplacer(self._assignments, self._uses)
512
663
 
513
664
  # iterator
514
665
  if node.iterator is not None:
515
- r = replacer.walk_expression(node.iterator)
666
+ r = replacer.walk_statement(node.iterator)
516
667
  if r is not None and r is not node.iterator:
517
668
  node.iterator = r
518
669
 
519
670
  # initializer
520
671
  if node.initializer is not None:
521
- r = replacer.walk_expression(node.initializer)
672
+ r = replacer.walk_statement(node.initializer)
522
673
  if r is not None and r is not node.initializer:
523
674
  node.initializer = r
524
675
 
@@ -531,7 +682,7 @@ class ExpressionFolder(SequenceWalker):
531
682
  return super()._handle_Loop(node, **kwargs)
532
683
 
533
684
  def _handle_SwitchCase(self, node: SwitchCaseNode, **kwargs):
534
- replacer = ExpressionReplacer(self._assignments, self._uses, self._variable_manager)
685
+ replacer = ExpressionReplacer(self._assignments, self._uses)
535
686
 
536
687
  r = replacer.walk_expression(node.switch_expr)
537
688
  if r is not None and r is not node.switch_expr: