-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathbasic_block.py
400 lines (313 loc) · 12.1 KB
/
basic_block.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
import dis
from typing import Tuple, Dict, List
from dataclasses import dataclass, replace, field
from numba_rvsdg.core.utils import _next_inst_offset
from numba_rvsdg.core.datastructures import block_names
@dataclass(frozen=True)
class BasicBlock:
"""Basic building block of an SCFG graph.
The BasicBlock class represents an atomic basic block in an SCFG.
Attributes
----------
name: str
The corresponding name for this block.
_jump_targets: Tuple[str]
Jump targets (branch destinations) for this block.
backedges: Tuple[bool]
Indicates if the Jump target at the particular index
is a backedge or not.
"""
name: str
_jump_targets: Tuple[str] = tuple()
backedges: Tuple[bool] = field(init=False)
def __post_init__(self):
backedges = tuple([False] * len(self._jump_targets))
object.__setattr__(self, "backedges", backedges)
@property
def is_exiting(self) -> bool:
"""Indicates whether this block is an exiting block, i.e.,
it does not have any jump targets.
Returns
-------
is_exiting: bool
True if the current block is an exiting block, False if it
isn't.
"""
return not self.jump_targets
@property
def fallthrough(self) -> bool:
"""Indicates whether this block has a single fallthrough jump target.
Returns
-------
fallthrough: bool
True if the current block is a fallthorough block, False if it
isn't.
"""
return len(self._jump_targets) == 1
@property
def jump_targets(self) -> Tuple[str]:
"""Retrieves the jump targets for this block,
excluding any jump targets that are also backedges.
Returns
-------
jump_targets: Tuple[str]
Tuple of jump targets of this block, (exludes backedges).
Ordered according to their position.
"""
acc = []
for idx, j in enumerate(self._jump_targets):
if not self.backedges[idx]:
acc.append(j)
return tuple(acc)
def declare_backedge(self, target: str):
"""Declare one of the jump targets as a backedge of this block.
Parameters
----------
target: str
The jump target that is to be declared as a backedge.
"""
assert target in self._jump_targets
idx = self._jump_targets.index(target)
current_backedges = list(self.backedges)
current_backedges[idx] = True
object.__setattr__(self, "backedges", tuple(current_backedges))
def change_jump_targets(self, jump_targets: Tuple):
"""Changes jump targets of this block by the given tuple.
This method replaces the jump targets of the current BasicBlock.
The provided jump targets must be in the same order as their
intended original replacements.
Note that replacing jump targets will not replace the backedge
tuple, so replacement for any jump targets that is declared as
a backedge needs to be updated separately using replace_backedges
Parameters
----------
jump_targets: Tuple
The new jump target tuple. Must be ordered.
"""
is_backedge = {}
new_backedges = []
for idx, i in enumerate(self.backedges):
is_backedge[self._jump_targets[idx]] = i
for i in jump_targets:
new_backedges.append(is_backedge.get(i, False))
object.__setattr__(self, "_jump_targets", jump_targets)
object.__setattr__(self, "backedges", new_backedges)
@dataclass(frozen=True)
class PythonBytecodeBlock(BasicBlock):
"""The PythonBytecodeBlock class is a subclass of the BasicBlock that
represents basic blocks with Python bytecode.
Attributes
----------
begin: int
The starting bytecode offset.
end: int
The bytecode offset immediately after the last bytecode of the block.
"""
begin: int = None
end: int = None
bcmap: dis.Bytecode = None
def get_instructions(
self, bcmap: Dict[int, dis.Instruction]
) -> List[dis.Instruction]:
"""Retrieves a list of `dis.Instruction` objects corresponding to
the instructions within the bytecode block.
In this method, The bcmap parameter is a dictionary mapping bytecode
offsets to `dis.Instruction` objects. This method iterates over the
bytecode offsets within the begin and end range, retrieves the
corresponding `dis.Instruction` objects from bcmap, and returns a list
of these instructions.
Parameters
----------
bcmap: Dict[int, dis.Instruction]
Dictionary mapping bytecode offsets to dis.Instruction objects.
Return
------
out: List[dis.Instruction]
The requested instructions according to bcmap between begin and
end offsets.
"""
begin = self.begin
end = self.end
it = begin
out = []
while it < end:
# Python 3.11 hack: account for gaps in the bytecode sequence
try:
out.append(bcmap[it])
except KeyError:
pass
finally:
it = _next_inst_offset(it)
return out
@dataclass(frozen=True)
class SyntheticBlock(BasicBlock):
"""The SyntheticBlock represents a artificially added block in a
structured control flow graph (SCFG).
"""
@dataclass(frozen=True)
class SyntheticExit(SyntheticBlock):
"""The SyntheticExit class represents a artificially added exit block
in a structured control flow graph (SCFG).
"""
@dataclass(frozen=True)
class SyntheticReturn(SyntheticBlock):
"""The SyntheticReturn class represents a artificially added return block
in a structured control flow graph (SCFG).
"""
@dataclass(frozen=True)
class SyntheticTail(SyntheticBlock):
"""The SyntheticTail class represents a artificially added tail block
in a structured control flow graph (SCFG).
"""
@dataclass(frozen=True)
class SyntheticFill(SyntheticBlock):
"""The SyntheticFill class represents a artificially added fill block
in a structured control flow graph (SCFG).
"""
@dataclass(frozen=True)
class SyntheticAssignment(SyntheticBlock):
"""The SyntheticAssignment class represents a artificially added
assignment block in a structured control flow graph (SCFG).
This block is responsible for giving variables their values,
once the respective block is executed.
Attributes
----------
variable_assignment: dict
A dictionary representing the variable assignments. It maps
the variable name to the value that is is assigned when
the block is executed.
"""
variable_assignment: dict = None
@dataclass(frozen=True)
class SyntheticBranch(SyntheticBlock):
"""The SyntheticBranch class represents a artificially added branch block
in a structured control flow graph (SCFG).
Attributes
----------
variable: str
The variable on the basis of which branching will happen when the
current block is executed.
branch_value_table: dict
The value table maps variable values to the repective jump target
to be executed on the basis of that value.
"""
variable: str = None
branch_value_table: dict = None
def replace_jump_targets(self, jump_targets: Tuple) -> "BasicBlock":
"""Replaces jump targets of this block by the given tuple.
This method replaces the jump targets of the current BasicBlock.
The provided jump targets must be in the same order as their
intended original replacements. Additionally also updates the
branch value table of the branch block.
Note that replacing jump targets will not replace the backedge
tuple, so replacement for any jump targets that is declared as
a backedge needs to be updated separately using replace_backedges.
Parameters
----------
jump_targets: Tuple
The new jump target tuple. Must be ordered.
Returns
-------
basic_block: BasicBlock
The resulting BasicBlock.
"""
old_branch_value_table = self.branch_value_table
new_branch_value_table = {}
for target in self._jump_targets:
if target not in jump_targets:
# ASSUMPTION: only one jump_target is being updated
diff = set(jump_targets).difference(self._jump_targets)
assert len(diff) == 1
new_target = next(iter(diff))
for k, v in old_branch_value_table.items():
if v == target:
new_branch_value_table[k] = new_target
else:
# copy all old values
for k, v in old_branch_value_table.items():
if v == target:
new_branch_value_table[k] = v
return replace(
self,
_jump_targets=jump_targets,
branch_value_table=new_branch_value_table,
)
@dataclass(frozen=True)
class SyntheticHead(SyntheticBranch):
"""The SyntheticHead class represents a artificially added head block
in a structured control flow graph (SCFG).
"""
@dataclass(frozen=True)
class SyntheticExitingLatch(SyntheticBranch):
"""The SyntheticExitingLatch class represents a artificially added
exiting latch block in a structured control flow graph (SCFG).
"""
@dataclass(frozen=True)
class SyntheticExitBranch(SyntheticBranch):
"""The SyntheticExitBranch class represents a artificially added
exit branch block in a structured control flow graph (SCFG).
"""
@dataclass(frozen=True)
class RegionBlock(BasicBlock):
"""The RegionBlock is a BasicBlock that represents a region in a
structured control flow graph (SCFG) object.
Attributes
----------
kind: str
The kind of region. Can be 'head', 'tail', 'branch',
'loop' or 'meta' strings.
parent_region: "RegionBlock"
The parent region of this region as per the SCFG.
header: str
The header node of the region.
subregion: "SCFG"
The subgraph as an independent SCFG. Note that in case
of subregions the exiting node may point to blocks outside
of the current SCFG object.
exiting: str
The exiting node of the region.
"""
kind: str = None
parent_region: "RegionBlock" = None
header: str = None
subregion: "SCFG" = None # noqa
exiting: str = None
def replace_header(self, new_header):
"""This method performs a inplace replacement of the header block.
Parameters
----------
new_header: str
The new header block of the region represented by the RegionBlock.
"""
object.__setattr__(self, "header", new_header)
def replace_exiting(self, new_exiting):
"""This method performs a inplace replacement of the header block.
Parameters
----------
new_exiting: str
The new exiting block of the region represented by the RegionBlock.
"""
object.__setattr__(self, "exiting", new_exiting)
def replace_parent(self, new_parent):
"""This method performs a inplace replacement of the parent region
block.
Parameters
----------
new_exiting: str
The new exiting block of the region represented by the RegionBlock.
"""
object.__setattr__(self, "parent", new_parent)
block_type_names = {
block_names.BASIC: BasicBlock,
block_names.PYTHON_BYTECODE: PythonBytecodeBlock,
block_names.SYNTH_HEAD: SyntheticHead,
block_names.SYNTH_BRANCH: SyntheticBranch,
block_names.SYNTH_TAIL: SyntheticTail,
block_names.SYNTH_EXIT: SyntheticExit,
block_names.SYNTH_ASSIGN: SyntheticAssignment,
block_names.SYNTH_RETURN: SyntheticReturn,
block_names.SYNTH_EXIT_LATCH: SyntheticExitingLatch,
block_names.SYNTH_EXIT_BRANCH: SyntheticExitBranch,
block_names.SYNTH_FILL: SyntheticFill,
block_names.REGION: RegionBlock,
}