pcode-graph 1.0.0__py3-none-any.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.
@@ -0,0 +1,185 @@
1
+ from pcode_graph.checker import check_graph
2
+ from pcode_graph.graph import CDG, Edge, EdgeKinds, NodeIndex, OpCodes, NodeKinds
3
+
4
+ from loguru import logger
5
+ from pathlib import Path
6
+
7
+
8
+ class GraphSimplifier:
9
+
10
+ def __init__(
11
+ self,
12
+ graph: CDG,
13
+ ) -> None:
14
+ self.graph = graph
15
+
16
+ def _update_proxy(self):
17
+ self._proxy = self.graph.compute_edge_proxy()
18
+
19
+ def _remove_node(self, index: NodeIndex):
20
+ def remap_index(n: NodeIndex):
21
+ assert n != index
22
+ return n - int(n > index)
23
+
24
+ # logger.debug(f"Remove {self.graph.nodes[index]} at {index}")
25
+
26
+ # Remap edges
27
+ proxy = self._proxy[index]
28
+
29
+ control_succs = proxy.get_control_successors()
30
+ assert (
31
+ len(control_succs) <= 1
32
+ ), "More than one control successor to the node to remove"
33
+
34
+ data_preds = proxy.get_data_predecessors()
35
+
36
+ # Convert to set to handle particular case of Phi node
37
+ assert len(data_preds) <= 1 or (
38
+ self.graph.nodes[index].kind == NodeKinds.Phi and len(set(data_preds)) == 1
39
+ ), "More than one data predecessor to the node to remove"
40
+
41
+ kept_edges: list[Edge] = []
42
+
43
+ def remap_edge(
44
+ kind: EdgeKinds, source: NodeIndex, dest: NodeIndex, operand: int | None
45
+ ):
46
+ # logger.debug(
47
+ # f"Remap {self.graph.dump_edge(Edge(kind=kind, source_node=source, destination_node=dest, operand_number=operand))}"
48
+ # )
49
+ edge = Edge(
50
+ source_node=remap_index(source),
51
+ destination_node=remap_index(dest),
52
+ kind=kind,
53
+ operand_number=operand,
54
+ )
55
+ kept_edges.append(edge)
56
+
57
+ for e in self.graph.edges:
58
+ if e.source_node == index:
59
+ if e.kind == EdgeKinds.Data:
60
+ remap_edge(
61
+ e.kind,
62
+ data_preds[0],
63
+ e.destination_node,
64
+ e.operand_number,
65
+ )
66
+ elif e.destination_node == index:
67
+ if e.kind == EdgeKinds.Control:
68
+ remap_edge(
69
+ e.kind,
70
+ e.source_node,
71
+ control_succs[0],
72
+ e.operand_number,
73
+ )
74
+ else:
75
+ remap_edge(e.kind, e.source_node, e.destination_node, e.operand_number)
76
+
77
+ # Patch the graph
78
+ self.graph.edges = kept_edges
79
+ self.graph.nodes.pop(index)
80
+
81
+ self._graph_changed = True
82
+ self._update_proxy()
83
+
84
+ # self.debug_helper()
85
+
86
+ def _remove_edge(self, edge: Edge):
87
+
88
+ self.graph.remove_edge(edge)
89
+ self._graph_changed = True
90
+ self._update_proxy()
91
+
92
+ def debug_helper(self):
93
+ try:
94
+ self.rounds += 1
95
+ except AttributeError:
96
+ self.rounds = 0
97
+ path = Path(f"round{self.rounds}.md")
98
+ path.write_text(str(self.graph))
99
+ logger.debug(f"Written {path}")
100
+ check_graph(self.graph)
101
+
102
+ def process(self):
103
+
104
+ self._graph_changed = True
105
+ self._update_proxy()
106
+
107
+ while self._graph_changed:
108
+ self._graph_changed = False
109
+
110
+ # Remove useless phi nodes
111
+ for index, node in enumerate(self.graph.nodes):
112
+
113
+ if node.kind == NodeKinds.Phi:
114
+
115
+ phi_data_preds = set(self._proxy[index].get_data_predecessors())
116
+ if len(phi_data_preds) > 1:
117
+ # Keep this Phi node
118
+ continue
119
+
120
+ self._remove_node(index)
121
+ break
122
+
123
+ elif node.opcode == OpCodes.COPY:
124
+
125
+ ctrl_preds = set(self._proxy[index].get_control_predecessors())
126
+ ctrl_succs = self._proxy[index].get_control_successors()
127
+
128
+ remove = True
129
+ if ctrl_preds and ctrl_succs:
130
+ (ctrl_succ,) = ctrl_succs
131
+
132
+ remove = False
133
+ # Remove copies that dominates their successor...
134
+ if self._proxy[ctrl_succ].get_control_predecessors() == [index]:
135
+ remove = True
136
+ elif len(ctrl_preds) == 1:
137
+ # ...or are dominated by their unique predecessor
138
+ (ctrl_pred,) = ctrl_preds
139
+ if self._proxy[ctrl_pred].get_control_successors() == [
140
+ index
141
+ ]:
142
+ remove = True
143
+
144
+ if remove:
145
+ self._remove_node(index)
146
+ break
147
+
148
+ elif node.kind == NodeKinds.Constant:
149
+ if not self._proxy[index].get_data_successors():
150
+ # Remove constants let orphan by other simplifications
151
+ self._remove_node(index)
152
+ break
153
+
154
+ elif node.opcode in {
155
+ OpCodes.INT_ZEXT,
156
+ OpCodes.INT_SEXT,
157
+ }:
158
+ self._remove_node(index)
159
+ break
160
+
161
+ elif node.opcode == OpCodes.SUBPIECE:
162
+
163
+ operands: list[Edge] = self._proxy[index].get_inputs_edges()
164
+
165
+ assert len(operands) == 2
166
+ num_trunc_bytes = self.graph.nodes[operands[1].source_node]
167
+ assert num_trunc_bytes.kind == NodeKinds.Constant
168
+ if num_trunc_bytes.value == 0:
169
+ self._remove_edge(operands[1])
170
+ self._remove_node(index)
171
+ break
172
+
173
+ elif node.opcode == OpCodes.INT_MULT:
174
+ # Remove mulitiplication by one
175
+ operands: list[Edge] = self._proxy[index].get_inputs_edges()
176
+ for op_edge in operands:
177
+ o = op_edge.source_node
178
+ op = self.graph.nodes[o]
179
+ if op.kind == NodeKinds.Constant and op.value == 1:
180
+ self._remove_edge(op_edge)
181
+ self._remove_node(index)
182
+ break
183
+ else:
184
+ continue
185
+ break
@@ -0,0 +1,30 @@
1
+ import torch
2
+
3
+ class BinaryConverter:
4
+ """Helper to convert between scalar tensor and tensor of bits"""
5
+
6
+ def __init__(self, num_bits: int) -> None:
7
+ self.num_bits = num_bits
8
+ self._min_value = -(2 ** (num_bits - 1))
9
+ self._max_value = 2 ** (num_bits - 1) - 1
10
+ self._mask = 2 ** torch.arange(num_bits - 1, -1, -1, dtype=torch.long)
11
+
12
+ def value_to_bits(self, value: int) -> torch.Tensor:
13
+ """Converts the given signed value to bits"""
14
+
15
+ if value < self._min_value:
16
+ value = self._min_value
17
+ elif value > self._max_value:
18
+ value = self._max_value
19
+
20
+ return (
21
+ torch.tensor(value, dtype=torch.long)
22
+ .unsqueeze(-1)
23
+ .bitwise_and(self._mask)
24
+ .ne(0)
25
+ .float()
26
+ )
27
+
28
+ def bits_to_value(self, logits: torch.Tensor) -> torch.Tensor:
29
+ bits = (logits > 0).to(torch.long)
30
+ return torch.sum(self._mask * bits, -1)
@@ -0,0 +1,235 @@
1
+ from dataclasses import dataclass
2
+ from typing import Iterator, NamedTuple
3
+ from loguru import logger
4
+ import pypcode
5
+ from pcode_graph.arch import Arch
6
+
7
+
8
+ @dataclass(slots=True)
9
+ class ChunkOps:
10
+ """Result of lifting a piece of binary."""
11
+
12
+ num_instructions: int
13
+ address: int
14
+ operations: list[pypcode.PcodeOp]
15
+
16
+
17
+ @dataclass
18
+ class Instruction:
19
+ """Result of disassembling."""
20
+
21
+ address: int
22
+ length: int
23
+ mnem: str
24
+ body: str
25
+
26
+ def __str__(self) -> str:
27
+ return self.dump(True)
28
+
29
+ def dump(self, print_address: bool):
30
+ s = ""
31
+ if print_address:
32
+ s += f"{self.address:05x} "
33
+ s += self.mnem
34
+ if self.body:
35
+ s += " " + self.body
36
+ return s
37
+
38
+
39
+ @dataclass(slots=True)
40
+ class LiftStats:
41
+ total_bytes: int = 0
42
+ lifted_bytes: int = 0
43
+ skipped_bytes: int = 0
44
+ truncated_bytes: int = 0
45
+ num_instructions: int = 0
46
+ resyncs: int = 0
47
+ translate_calls: int = 0
48
+
49
+ @property
50
+ def coverage(self) -> float:
51
+ return self.lifted_bytes / self.total_bytes if self.total_bytes else 1.0
52
+
53
+
54
+ class ArchParams(NamedTuple):
55
+ #: Resynchronisation step, in bytes, after an undecodable instruction.
56
+ #: Must divide every valid instruction address, otherwise recovery lands
57
+ #: mid-instruction.
58
+ alignment: int
59
+ #: Longest single instruction encoding, in bytes.
60
+ max_instruction_size: int
61
+
62
+
63
+ ARCH_PARAMETERS: dict[Arch, ArchParams] = {
64
+ Arch.x86_32: ArchParams(1, 15),
65
+ Arch.x86_64: ArchParams(1, 15),
66
+ Arch.arm_32: ArchParams(4, 4),
67
+ Arch.arm_64: ArchParams(4, 4),
68
+ Arch.mips_32: ArchParams(4, 4),
69
+ Arch.mips_64: ArchParams(4, 4),
70
+ }
71
+
72
+
73
+ def create_pcode_context(arch: Arch) -> pypcode.Context:
74
+ match arch:
75
+ case Arch.arm_32:
76
+ return pypcode.Context("ARM:LE:32:v8")
77
+ case Arch.arm_64:
78
+ return pypcode.Context("AARCH64:LE:64:v8A")
79
+ # return pypcode.Context("AARCH64:LE:64:AppleSilicon")
80
+ case Arch.x86_32:
81
+ return pypcode.Context("x86:LE:32:default")
82
+ case Arch.x86_64:
83
+ return pypcode.Context("x86:LE:64:default")
84
+ case Arch.mips_32:
85
+ return pypcode.Context("MIPS:BE:32:default")
86
+ case Arch.mips_64:
87
+ return pypcode.Context("MIPS:BE:64:default")
88
+ case _:
89
+ raise ValueError(f"Unknown architecture {arch}")
90
+
91
+
92
+ def get_pcode_context_arch(context: pypcode.Context) -> Arch:
93
+ size = int(context.language.size)
94
+ processor = context.language.processor
95
+ match processor:
96
+ case "ARM":
97
+ if size == 32:
98
+ return Arch.arm_32
99
+ case "AARCH64":
100
+ if size == 64:
101
+ return Arch.arm_64
102
+ case "MIPS":
103
+ match size:
104
+ case 32:
105
+ return Arch.mips_32
106
+ case 64:
107
+ return Arch.mips_64
108
+ case "x86":
109
+ match size:
110
+ case 32:
111
+ return Arch.x86_32
112
+ case 64:
113
+ return Arch.x86_64
114
+
115
+ raise ValueError(f"Unsupported context with {processor=} and {size=}")
116
+
117
+
118
+ class Translator:
119
+
120
+ def __init__(self, arch_context: pypcode.Context | Arch):
121
+ if isinstance(arch_context, Arch):
122
+ self.arch = arch_context
123
+ self.context = create_pcode_context(self.arch)
124
+ else:
125
+ self.context = arch_context
126
+ self.arch = get_pcode_context_arch(arch_context)
127
+
128
+ self.alignment, self.max_instr_size = ARCH_PARAMETERS[self.arch]
129
+
130
+ def translate(self, data: bytes, base_address: int) -> list[pypcode.PcodeOp]:
131
+ """Translates a piece of code into P-Code operations."""
132
+
133
+ ops = []
134
+ for chunk in self.iter_operations(data, base_address):
135
+ ops += chunk.operations
136
+ return ops
137
+
138
+ def iter_operations(
139
+ self,
140
+ data: bytes,
141
+ base_address: int,
142
+ window: int = 4096,
143
+ max_instructions: int = 1024,
144
+ stats: LiftStats | None = None,
145
+ ) -> Iterator[ChunkOps]:
146
+ """Translates a piece of code into P-Code, handling errors and avoiding
147
+ out-of-memory for big sections.
148
+
149
+ Each ChunkOps covers a contiguous range: undecodable bytes are skipped,
150
+ ``instruction_alignment`` at a time. ``window`` and ``max_instructions``
151
+ both bound the memory of a single call.
152
+ """
153
+
154
+ assert window > 1 and max_instructions > 1
155
+ if stats is None:
156
+ stats = LiftStats()
157
+ stats.total_bytes += len(data)
158
+
159
+ offset = 0
160
+ padded_data = data
161
+ if self.arch in {Arch.mips_32, Arch.mips_64}:
162
+ # Avoid delay slot exception
163
+ padded_data = data + b"\x00\x00\x00\x00"
164
+
165
+ while offset < len(data):
166
+ window_base = base_address + offset
167
+
168
+ try:
169
+ stats.translate_calls += 1
170
+ ops = self.context.translate(
171
+ padded_data,
172
+ window_base,
173
+ max_instructions=max_instructions,
174
+ offset=offset,
175
+ max_bytes=window,
176
+ ).ops
177
+ except (IndexError, pypcode.BadDataError, pypcode.UnimplError, pypcode.LowlevelError):
178
+ ops = []
179
+
180
+ chunk: list[pypcode.PcodeOp] = []
181
+ num_instructions = 0
182
+ consumed_bytes = 0
183
+ truncated = False
184
+
185
+ for op in ops:
186
+ if op.opcode == pypcode.OpCode.IMARK:
187
+ # A single IMARK may cover several instructions (grouped delay slots)
188
+ # Do not assume the varnodes are sorted.
189
+ end = max(i.offset + i.size for i in op.inputs) - base_address
190
+
191
+ # pypcode silently zero-pads past the end of the buffer
192
+ # and reports the instruction's full length, so the
193
+ # last instruction of a section can be entirely
194
+ # fabricated. Any instruction consuming a padding byte
195
+ # necessarily ends past `size`, which makes this test
196
+ # both sufficient and exact.
197
+ if end > len(data):
198
+ truncated = True
199
+ break
200
+
201
+ num_instructions += len(op.inputs)
202
+ consumed_bytes = max(consumed_bytes, end - offset)
203
+
204
+ chunk.append(op)
205
+
206
+ if consumed_bytes > 0:
207
+ stats.lifted_bytes += consumed_bytes
208
+ stats.num_instructions += num_instructions
209
+ yield ChunkOps(num_instructions, window_base, chunk)
210
+ offset += consumed_bytes
211
+
212
+ if truncated:
213
+ stats.truncated_bytes += len(data) - offset
214
+ return
215
+
216
+ if consumed_bytes == 0:
217
+ # Nothing decodable at `offset`: resynchronise.
218
+ stats.skipped_bytes += min(self.alignment, len(data) - offset)
219
+ stats.resyncs += 1
220
+ offset += self.alignment
221
+
222
+ def disassemble(self, code: bytes, base_address: int) -> Iterator[Instruction]:
223
+ data = self.context.disassemble(code, base_address=base_address)
224
+ for i in data.instructions:
225
+ yield Instruction(i.addr.offset, i.length, i.mnem, i.body)
226
+
227
+ def dump_asm(
228
+ self,
229
+ code: bytes,
230
+ base_address: int,
231
+ print_address: bool = True,
232
+ ) -> str:
233
+ return "\n".join(
234
+ i.dump(print_address) for i in self.disassemble(code, base_address)
235
+ )
pcode_graph/utils.py ADDED
@@ -0,0 +1,15 @@
1
+ def unsigned_to_signed(value: int, size_bytes: int) -> int:
2
+ """Converts the given unsigned value into a signed one."""
3
+
4
+ assert size_bytes > 0
5
+
6
+ mask = 1 << (size_bytes * 8 - 1)
7
+ return (value ^ mask) - mask
8
+
9
+
10
+ def signed_to_unsigned(value: int, size_bytes: int) -> int:
11
+ """Converts the given signed value into an unsigned one."""
12
+
13
+ return value + (value < 0) * (2 ** (size_bytes * 8))
14
+
15
+
pcode_graph/view.py ADDED
@@ -0,0 +1,65 @@
1
+ from pathlib import Path
2
+ from loguru import logger
3
+ from pyvis.network import Network
4
+
5
+ from pcode_graph.graph import CDG, EdgeKinds, NodeKinds
6
+ from pcode_graph.pcode import JUMP_OPCODES
7
+
8
+
9
+ def draw_graph(
10
+ graph: CDG,
11
+ out_file: Path,
12
+ open_browser: bool = False,
13
+ print_ids: bool = False,
14
+ ):
15
+ """Creates a HTML representation of the given graph."""
16
+
17
+ parent_dir = out_file.parent
18
+ parent_dir.mkdir(parents=True, exist_ok=True)
19
+
20
+ network = Network(width="1200", height="1200", directed=True)
21
+
22
+ # Create nodes
23
+ for index, node in enumerate(graph.nodes):
24
+ style: dict
25
+
26
+ match node.kind:
27
+ case NodeKinds.InputRegister | NodeKinds.ReadMemory:
28
+ style = dict(color="#c0d0ff") # Light Blue
29
+ case NodeKinds.OutputRegister | NodeKinds.WrittenMemory:
30
+ style = dict(color="#7070da") # Blue
31
+ case NodeKinds.Constant:
32
+ style = dict(color="#ff8080") # Red
33
+ case NodeKinds.Begin | NodeKinds.End | NodeKinds.External:
34
+ style = dict(color="#70d660", shape="box")
35
+ case NodeKinds.Operation | NodeKinds.Phi:
36
+ if node.opcode in JUMP_OPCODES:
37
+ # Jumps in light Green
38
+ style = dict(color="#ab2a", shape="box")
39
+ else:
40
+ # Other operations in Grey
41
+ style = dict(color="#d1c5c5", shape="box")
42
+ case _:
43
+ raise ValueError(node.kind)
44
+
45
+ label = str(node)
46
+ if print_ids:
47
+ label += " " + str(index)
48
+ style["label"] = label
49
+ network.add_node(index, **style)
50
+
51
+ # Create edges
52
+ for edge in graph.edges:
53
+ network.add_edge(
54
+ edge.source_node,
55
+ edge.destination_node,
56
+ style={"dashes": edge.kind == EdgeKinds.Control},
57
+ label=str(edge.operand_number) if edge.operand_number is not None else "",
58
+ color="#a1a5c5" if edge.kind == EdgeKinds.Data else "#70d660",
59
+ )
60
+
61
+ # Layout
62
+ network.force_atlas_2based(spring_length=75)
63
+
64
+ network.write_html(out_file.as_posix(), open_browser=open_browser)
65
+ logger.info(f"Written {out_file}")
pcode_graph/visitor.py ADDED
@@ -0,0 +1,103 @@
1
+ from typing import Callable
2
+
3
+ from pcode_graph.pcode import OpCodes
4
+
5
+
6
+ class PcodeVisitor:
7
+ """A hierarchical visitor to P-Code operations."""
8
+
9
+ def __init__(self) -> None:
10
+ self._pcode_callbacks: dict[OpCodes | None, Callable] = {
11
+ opcode: getattr(self, "visit_" + opcode.name.lower()) for opcode in OpCodes
12
+ }
13
+ self._pcode_callbacks[None] = self.visit_nop
14
+
15
+ def visit(self, opcode: OpCodes | None, *args, **kwargs):
16
+ "Visitor main entry point."
17
+ return self._pcode_callbacks[opcode](*args, **kwargs)
18
+
19
+ def visit_nop(self, *args, **kwargs):
20
+ return self.visit_default(*args, **kwargs)
21
+
22
+ def visit_binary(self, *args, **kwargs):
23
+ "A binary logical or arithmetical operation."
24
+ return self.visit_default(*args, **kwargs)
25
+
26
+ def visit_unary(self, *args, **kwargs):
27
+ "An unary logical or arithmetical operation."
28
+ return self.visit_default(*args, **kwargs)
29
+
30
+ def visit_jump(self, *args, **kwargs):
31
+ "Any operation impacting the usual instruction flow."
32
+ return self.visit_default(*args, **kwargs)
33
+
34
+ def visit_default(self, *args, **kwargs):
35
+ "Catch-all callback"
36
+ raise NotImplementedError
37
+
38
+ # fmt: off
39
+ def visit_invalid(self, *args, **kwargs): return self.visit_default(*args, **kwargs)
40
+ def visit_bool_and(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
41
+ def visit_bool_negate(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
42
+ def visit_bool_or(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
43
+ def visit_bool_xor(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
44
+ def visit_branch(self, *args, **kwargs): return self.visit_jump(*args, **kwargs)
45
+ def visit_branchind(self, *args, **kwargs): return self.visit_jump(*args, **kwargs)
46
+ def visit_call(self, *args, **kwargs): return self.visit_branch(*args, **kwargs)
47
+ def visit_callind(self, *args, **kwargs): return self.visit_branchind(*args, **kwargs)
48
+ def visit_callother(self, *args, **kwargs): return self.visit_default(*args, **kwargs) # Note: callother is not a implemented as a branching instruction but is followed by one
49
+ def visit_cbranch(self, *args, **kwargs): return self.visit_jump(*args, **kwargs)
50
+ def visit_copy(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
51
+ def visit_float_abs(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
52
+ def visit_float_add(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
53
+ def visit_float_ceil(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
54
+ def visit_float_div(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
55
+ def visit_float_equal(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
56
+ def visit_float_float2float(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
57
+ def visit_float_floor(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
58
+ def visit_float_int2float(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
59
+ def visit_float_less(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
60
+ def visit_float_lessequal(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
61
+ def visit_float_mult(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
62
+ def visit_float_nan(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
63
+ def visit_float_neg(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
64
+ def visit_float_notequal(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
65
+ def visit_float_round(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
66
+ def visit_float_sqrt(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
67
+ def visit_float_sub(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
68
+ def visit_float_trunc(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
69
+ def visit_int_2comp(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
70
+ def visit_int_add(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
71
+ def visit_int_and(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
72
+ def visit_int_carry(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
73
+ def visit_int_div(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
74
+ def visit_int_equal(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
75
+ def visit_int_left(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
76
+ def visit_int_less(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
77
+ def visit_int_lessequal(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
78
+ def visit_int_mult(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
79
+ def visit_int_negate(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
80
+ def visit_int_notequal(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
81
+ def visit_int_or(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
82
+ def visit_int_rem(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
83
+ def visit_int_right(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
84
+ def visit_int_sborrow(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
85
+ def visit_int_scarry(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
86
+ def visit_int_sdiv(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
87
+ def visit_int_sext(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
88
+ def visit_int_sless(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
89
+ def visit_int_slessequal(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
90
+ def visit_int_srem(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
91
+ def visit_int_sright(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
92
+ def visit_int_sub(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
93
+ def visit_int_xor(self, *args, **kwargs): return self.visit_binary(*args, **kwargs)
94
+ def visit_int_zext(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
95
+ def visit_load(self, *args, **kwargs): return self.visit_default(*args, **kwargs)
96
+ def visit_lzcount(self, *args, **kwargs): return self.visit_unary(*args, **kwargs)
97
+ def visit_piece(self, *args, **kwargs): return self.visit_default(*args, **kwargs)
98
+ def visit_popcount(self, *args, **kwargs): return self.visit_default(*args, **kwargs)
99
+ def visit_return(self, *args, **kwargs): return self.visit_branchind(*args, **kwargs)
100
+ def visit_segmentop(self, *args, **kwargs): return self.visit_default(*args, **kwargs)
101
+ def visit_store(self, *args, **kwargs): return self.visit_default(*args, **kwargs)
102
+ def visit_subpiece(self, *args, **kwargs): return self.visit_default(*args, **kwargs)
103
+ # fmt: on