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
|
import logging
import pprint
from typing import Dict, List, NamedTuple
from .rv_colors import palette
from .shared_utils import InstrDict, instr_dict_2_extensions
pp = pprint.PrettyPrinter(indent=2)
logging.basicConfig(level=logging.INFO, format="%(levelname)s:: %(message)s")
class RectangleDimensions(NamedTuple):
x: float
y: float
w: float
h: float
class InstrRectangle(NamedTuple):
dims: RectangleDimensions
extension: str
label: str
InstrDimsDict = Dict[str, RectangleDimensions]
def encoding_to_rect(encoding: str) -> RectangleDimensions:
"""Convert a binary encoding string to rectangle dimensions."""
def calculate_size(free_bits: int, tick: float) -> float:
"""Calculate size based on number of free bits and tick value."""
return 2**free_bits * tick
instr_length = len(encoding)
# starting position
x = 0
y = 0
x_tick = 1 / (2 ** (0.5 * instr_length))
y_tick = 1 / (2 ** (0.5 * instr_length))
x_free_bits = 0
y_free_bits = 0
even = encoding[0::2]
odd = encoding[1::2]
# Process bits from least significant to most significant
for i, bit in enumerate(encoding):
if bit == "1":
offset = 0.5 / (2 ** int(i / 2))
if i % 2 == 0:
y += offset
else:
x += offset
elif bit == "0":
pass
# position not adjusted on 0
x_free_bits = odd.count("-")
y_free_bits = even.count("-")
x_size = calculate_size(x_free_bits, x_tick)
y_size = calculate_size(y_free_bits, y_tick)
# If we came here, encoding can be visualized with a single rectangle
rectangle = RectangleDimensions(x=x, y=y, w=x_size, h=y_size)
return rectangle
FIGSIZE = 128
def plot_image(
instr_dict: InstrDict,
instr_dims_dict: InstrDimsDict,
extension_sizes: Dict[str, float],
) -> None:
"""Plot the instruction rectangles using matplotlib."""
from matplotlib import patches
from matplotlib import pyplot as plt
def get_readable_font_color(bg_hex: str) -> str:
"""Determine readable font color based on background color."""
def hex_to_rgb(hex_color: str) -> tuple[int, int, int]:
"""Convert hex color string to RGB tuple."""
hex_color = hex_color.lstrip("#")
r = int(hex_color[0:2], 16)
g = int(hex_color[2:4], 16)
b = int(hex_color[4:6], 16)
return (r, g, b)
r, g, b = hex_to_rgb(bg_hex)
luminance = 0.299 * r + 0.587 * g + 0.114 * b
return "#000000" if luminance > 186 else "#FFFFFF"
def plot_with_matplotlib(
rectangles: list[InstrRectangle],
colors: list[str],
hatches: list[str],
extensions: list[str],
) -> None:
"""Plot rectangles with matplotlib using specified styles."""
_, ax = plt.subplots(figsize=(FIGSIZE, FIGSIZE), facecolor="none") # type: ignore
ax.set_facecolor("none") # type: ignore
linewidth = FIGSIZE / 100
for dims, ext, label in rectangles:
x, y, w, h = dims
ext_idx = extensions.index(ext)
color = colors[ext_idx]
hatch = hatches[ext_idx]
rect = patches.Rectangle(
(x, y),
w,
h,
linewidth=linewidth,
edgecolor="black",
facecolor=color,
hatch=hatch,
alpha=1.0,
)
ax.add_patch(rect)
if w >= h:
base_dim = w
rotation = 0
else:
base_dim = h
rotation = 90
# Scale font size based on base dimension and label length
n_chars = len(label)
font_size = (
base_dim / n_chars * 90 * FIGSIZE
) # Adjust scaling factor as needed
if font_size > 1:
fontdict = {
"fontsize": font_size,
"color": get_readable_font_color(color),
"family": "DejaVu Sans Mono",
}
ax.text( # type: ignore
x + w / 2,
y + h / 2,
label,
ha="center",
va="center",
fontdict=fontdict,
rotation=rotation,
)
plt.axis("off") # type: ignore
plt.tight_layout() # type: ignore
plt.savefig("inst.svg", format="svg") # type: ignore
plt.show() # type: ignore
extensions: List[str] = sorted(
extension_sizes.keys(), key=lambda k: extension_sizes[k], reverse=True
)
rectangles: List[InstrRectangle] = []
for instr in instr_dict:
dims = instr_dims_dict[instr]
rectangles.append(
InstrRectangle(
dims=dims,
extension=instr_dict[instr]["extension"][0],
label=instr.replace("_", "."),
)
)
# sort rectangles so that small ones are in the foreground
# An overlap occurs e.g. for pseudo ops, and these should be on top of the encoding it reuses
rectangles = sorted(rectangles, key=lambda x: x.dims.w * x.dims.h, reverse=True)
colors, hatches = generate_styles(extensions)
plot_with_matplotlib(rectangles, colors, hatches, extensions)
def generate_styles(extensions: list[str]) -> tuple[list[str], list[str]]:
"""Generate color and hatch styles for extensions."""
n_colors = len(palette)
colors = [""] * len(extensions)
hatches = [""] * len(extensions)
hatch_options = ["", "/", "\\", "|", "-", "+", "x", ".", "*"]
color_options = list(palette.values())
for i in range(len(extensions)):
colors[i] = color_options[i % n_colors]
hatches[i] = hatch_options[int(i / n_colors) % len(hatch_options)]
return colors, hatches
def defragment_encodings(
encodings: list[str], length: int = 32, offset: int = 0
) -> list[str]:
"""Defragment a list of binary encodings by reordering bits."""
# determine bit position which has the most fixed bits
fixed_encodings = ["0", "1"]
fixed_bits = [0] * length
fixed_encoding_indeces: Dict[str, List[int]] = {
value: [] for value in fixed_encodings
}
for index, encoding in enumerate(encodings):
for position, value in enumerate(encoding):
if position > offset:
if value != "-":
fixed_bits[position] += 1
# find bit position with most fixed bits, starting with the LSB to favor the opcode field
max_fixed_bits = max(fixed_bits)
if max_fixed_bits == 0:
# fully defragemented
return encodings
max_fixed_position = len(fixed_bits) - 1 - fixed_bits[::-1].index(max_fixed_bits)
# move bit position with the most fixed bits to the front
for index, encoding in enumerate(encodings):
encodings[index] = (
encoding[0:offset]
+ encoding[max_fixed_position]
+ encoding[offset:max_fixed_position]
+ encoding[max_fixed_position + 1 :]
)
if encoding[max_fixed_position] in fixed_encodings:
fixed_encoding_indeces[encoding[max_fixed_position]].append(index)
else:
# No more fixed bits in this encoding
pass
if offset < length:
# continue to defragement starting from the next offset
offset = offset + 1
# separate encodings
sep_encodings: Dict[str, List[str]] = {}
for fixed_encoding in fixed_encodings:
sep_encodings[fixed_encoding] = [
encodings[i] for i in fixed_encoding_indeces[fixed_encoding]
]
sep_encodings[fixed_encoding] = defragment_encodings(
sep_encodings[fixed_encoding], length=length, offset=offset
)
# join encodings
for new_index, orig_index in enumerate(
fixed_encoding_indeces[fixed_encoding]
):
encodings[orig_index] = sep_encodings[fixed_encoding][new_index]
return encodings
def defragment_encoding_dict(instr_dict: InstrDict) -> InstrDict:
"""Apply defragmentation to the encoding dictionary."""
encodings = [instr["encoding"] for instr in instr_dict.values()]
encodings_defragemented = defragment_encodings(encodings, length=32, offset=0)
for index, instr in enumerate(instr_dict):
instr_dict[instr]["encoding"] = encodings_defragemented[index]
return instr_dict
def make_svg(instr_dict: InstrDict) -> None:
"""Generate an SVG image from instruction encodings."""
extensions = instr_dict_2_extensions(instr_dict)
extension_size: Dict[str, float] = {}
instr_dict = defragment_encoding_dict(instr_dict)
instr_dims_dict: InstrDimsDict = {}
for ext in extensions:
extension_size[ext] = 0
for instr in instr_dict:
dims = encoding_to_rect(instr_dict[instr]["encoding"])
extension_size[instr_dict[instr]["extension"][0]] += dims.h * dims.w
instr_dims_dict[instr] = dims
plot_image(instr_dict, instr_dims_dict, extension_size)
|