1+ """Low-level optimization of textual assembly."""
2+
13import dataclasses
24import pathlib
35import re
46import typing
57
6- _RE_NEVER_MATCH = re .compile (r"(?!)" ) # Same as saying "not string.startswith('')".
8+ # Same as saying "not string.startswith('')":
9+ _RE_NEVER_MATCH = re .compile (r"(?!)" )
10+ # Dictionary mapping x86 branching instructions to their inverted counterparts.
11+ # If a branch cannot be inverted, the value is None:
712_X86_BRANCHES = {
813 "ja" : "jna" ,
914 "jae" : "jnae" ,
2934 "loopnz" : None ,
3035 "loopz" : None ,
3136}
37+ # Update with all of the inverted branches, too:
3238_X86_BRANCHES |= {v : k for k , v in _X86_BRANCHES .items () if v }
3339
3440
3541@dataclasses .dataclass
3642class _Block :
3743 label : str | None = None
38- noise : list [str ] = dataclasses .field (default_factory = list )
44+ header : list [str ] = dataclasses .field (default_factory = list )
3945 instructions : list [str ] = dataclasses .field (default_factory = list )
4046 target : typing .Self | None = None
4147 link : typing .Self | None = None
4248 fallthrough : bool = True
4349 hot : bool = False
4450
4551 def resolve (self ) -> typing .Self :
46- while self .link and not self .instructions :
47- self = self .link
48- return self
52+ """Find the first non-empty block reachable from this one."""
53+ block = self
54+ while block .link and not block .instructions :
55+ block = block .link
56+ return block
4957
5058
5159@dataclasses .dataclass
5260class Optimizer :
61+ """Several passes of analysis and optimization for textual assembly."""
62+
5363 path : pathlib .Path
5464 _ : dataclasses .KW_ONLY
65+ # prefix used to mangle symbols on some platforms:
5566 prefix : str = ""
56- _graph : _Block = dataclasses .field (init = False )
57- _labels : dict = dataclasses .field (init = False , default_factory = dict )
58- _alignment : typing .ClassVar [int ] = 1
59- _branches : typing .ClassVar [dict [str , str | None ]] = {}
60- _re_branch : typing .ClassVar [re .Pattern [str ]] = (
61- _RE_NEVER_MATCH # Two groups: instruction and target.
62- )
63- _re_jump : typing .ClassVar [re .Pattern [str ]] = _RE_NEVER_MATCH # One group: target.
67+ # The first block in the linked list:
68+ _root : _Block = dataclasses .field (init = False , default_factory = _Block )
69+ _labels : dict [str , _Block ] = dataclasses .field (init = False , default_factory = dict )
70+ # No groups:
71+ _re_header : typing .ClassVar [re .Pattern [str ]] = re .compile (r"\s*(?:\.|#|//|$)" )
72+ # One group (label):
6473 _re_label : typing .ClassVar [re .Pattern [str ]] = re .compile (
65- r'\s*(?P<label>[\w."$?@]+):' # One group: label.
66- )
67- _re_noise : typing .ClassVar [re .Pattern [str ]] = re .compile (
68- r"\s*(?:\.|#|//|$)" # No groups.
74+ r'\s*(?P<label>[\w."$?@]+):'
6975 )
70- _re_return : typing .ClassVar [re .Pattern [str ]] = _RE_NEVER_MATCH # No groups.
76+ # Override everything that follows in subclasses:
77+ _alignment : typing .ClassVar [int ] = 1
78+ _branches : typing .ClassVar [dict [str , str | None ]] = {}
79+ # Two groups (instruction and target):
80+ _re_branch : typing .ClassVar [re .Pattern [str ]] = _RE_NEVER_MATCH
81+ # One group (target):
82+ _re_jump : typing .ClassVar [re .Pattern [str ]] = _RE_NEVER_MATCH
83+ # No groups:
84+ _re_return : typing .ClassVar [re .Pattern [str ]] = _RE_NEVER_MATCH
7185
7286 def __post_init__ (self ) -> None :
7387 text = self ._preprocess (self .path .read_text ())
74- self . _graph = block = _Block ()
88+ block = self . _root
7589 for line in text .splitlines ():
7690 if match := self ._re_label .match (line ):
7791 block .link = block = self ._lookup_label (match ["label" ])
78- block .noise .append (line )
92+ block .header .append (line )
7993 continue
80- if self ._re_noise .match (line ):
94+ if self ._re_header .match (line ):
8195 if block .instructions :
8296 block .link = block = _Block ()
83- block .noise .append (line )
97+ block .header .append (line )
8498 continue
8599 if block .target or not block .fallthrough :
86100 block .link = block = _Block ()
@@ -95,8 +109,7 @@ def __post_init__(self) -> None:
95109 assert not block .target
96110 block .fallthrough = False
97111
98- @staticmethod
99- def _preprocess (text : str ) -> str :
112+ def _preprocess (self , text : str ) -> str :
100113 return text
101114
102115 @classmethod
@@ -122,7 +135,7 @@ def _lookup_label(self, label: str) -> _Block:
122135 return self ._labels [label ]
123136
124137 def _blocks (self ) -> typing .Generator [_Block , None , None ]:
125- block = self ._graph
138+ block : _Block | None = self ._root
126139 while block :
127140 yield block
128141 block = block .link
@@ -134,7 +147,7 @@ def _body(self) -> str:
134147 if hot != block .hot :
135148 hot = block .hot
136149 lines .append (f"# JIT: { 'HOT' if hot else 'COLD' } " .ljust (80 , "#" ))
137- lines .extend (block .noise )
150+ lines .extend (block .header )
138151 lines .extend (block .instructions )
139152 return "\n " .join (lines )
140153
@@ -150,10 +163,10 @@ def _insert_continue_label(self) -> None:
150163 if end .instructions :
151164 break
152165 align = _Block ()
153- align .noise .append (f"\t .balign\t { self ._alignment } " )
166+ align .header .append (f"\t .balign\t { self ._alignment } " )
154167 continuation = self ._lookup_label (f"{ self .prefix } _JIT_CONTINUE" )
155168 assert continuation .label
156- continuation .noise .append (f"{ continuation .label } :" )
169+ continuation .header .append (f"{ continuation .label } :" )
157170 end .link , align .link , continuation .link = align , continuation , end .link
158171
159172 def _mark_hot_blocks (self ) -> None :
@@ -168,10 +181,10 @@ def _mark_hot_blocks(self) -> None:
168181 )
169182
170183 def _invert_hot_branches (self ) -> None :
171- # Turn :
184+ # Before :
172185 # branch <hot>
173186 # jump <cold>
174- # Into :
187+ # After :
175188 # opposite-branch <cold>
176189 # jump <hot>
177190 for branch in self ._blocks ():
@@ -217,20 +230,25 @@ def _remove_redundant_jumps(self) -> None:
217230 block .instructions .pop ()
218231
219232 def run (self ) -> None :
233+ """Run this optimizer."""
220234 self ._insert_continue_label ()
221235 self ._mark_hot_blocks ()
222236 self ._invert_hot_branches ()
223237 self ._remove_redundant_jumps ()
224238 self .path .write_text (self ._body ())
225239
226240
227- class OptimizerAArch64 (Optimizer ):
241+ class OptimizerAArch64 (Optimizer ): # pylint: disable = too-few-public-methods
242+ """aarch64-apple-darwin/aarch64-pc-windows-msvc/aarch64-unknown-linux-gnu"""
243+
228244 # TODO: @diegorusso
229245 _alignment = 8
230246 _re_jump = re .compile (r"\s*b\s+(?P<target>[\w.]+)" )
231247
232248
233- class OptimizerX86 (Optimizer ):
249+ class OptimizerX86 (Optimizer ): # pylint: disable = too-few-public-methods
250+ """i686-pc-windows-msvc/x86_64-apple-darwin/x86_64-unknown-linux-gnu"""
251+
234252 _branches = _X86_BRANCHES
235253 _re_branch = re .compile (
236254 rf"\s*(?P<instruction>{ '|' .join (_X86_BRANCHES )} )\s+(?P<target>[\w.]+)"
@@ -239,10 +257,15 @@ class OptimizerX86(Optimizer):
239257 _re_return = re .compile (r"\s*ret\b" )
240258
241259
242- class OptimizerX86Windows (OptimizerX86 ):
260+ class OptimizerX8664Windows (OptimizerX86 ): # pylint: disable = too-few-public-methods
261+ """x86_64-pc-windows-msvc"""
262+
243263 def _preprocess (self , text : str ) -> str :
244264 text = super ()._preprocess (text )
245- # rex64 jumpq *__imp__JIT_CONTINUE(%rip) -> jmp _JIT_CONTINUE
265+ # Before:
266+ # rex64 jmpq *__imp__JIT_CONTINUE(%rip)
267+ # After:
268+ # jmp _JIT_CONTINUE
246269 far_indirect_jump = (
247270 rf"rex64\s+jmpq\s+\*__imp_(?P<target>{ self .prefix } _JIT_\w+)\(%rip\)"
248271 )
0 commit comments