1 | \section{Introduction} |
---|
2 | |
---|
3 | The problem of branch displacement optimisation, also known as jump encoding, is |
---|
4 | a well-known problem in assembler design~\cite{Hyde2006}. It is caused by the |
---|
5 | fact that in many architecture sets, the encoding (and therefore size) of some |
---|
6 | instructions depends on the distance to their operand (the instruction 'span'). |
---|
7 | The branch displacement optimisation problem consists of encoding these |
---|
8 | span-dependent instructions in such a way that the resulting program is as |
---|
9 | small as possible. |
---|
10 | |
---|
11 | This problem is the subject of the present paper. After introducing the problem |
---|
12 | in more detail, we will discuss the solutions used by other compilers, present |
---|
13 | the algorithm we use in the CerCo assembler, and discuss its verification, |
---|
14 | that is the proofs of termination and correctness using the Matita proof |
---|
15 | assistant~\cite{Asperti2007}. |
---|
16 | |
---|
17 | The research presented in this paper has been executed within the CerCo project |
---|
18 | which aims at formally verifying a C compiler with cost annotations. The |
---|
19 | target architecture for this project is the MCS-51, whose instruction set |
---|
20 | contains span-dependent instructions. Furthermore, its maximum addressable |
---|
21 | memory size is very small (64 Kb), which makes it important to generate |
---|
22 | programs that are as small as possible. |
---|
23 | |
---|
24 | With this optimisation, however, comes increased complexity and hence |
---|
25 | increased possibility for error. We must make sure that the branch instructions |
---|
26 | are encoded correctly, otherwise the assembled program will behave |
---|
27 | unpredictably. |
---|
28 | |
---|
29 | \section{The branch displacement optimisation problem} |
---|
30 | |
---|
31 | In most modern instruction sets that have them, the only span-dependent |
---|
32 | instructions are branch instructions. Taking the ubiquitous x86-64 instruction |
---|
33 | set as an example, we find that it contains eleven different forms of the |
---|
34 | unconditional branch instruction, all with different ranges, instruction sizes |
---|
35 | and semantics (only six are valid in 64-bit mode, for example). Some examples |
---|
36 | are shown in Figure~\ref{f:x86jumps} (see also~\cite{IntelDev}). |
---|
37 | |
---|
38 | \begin{figure}[h] |
---|
39 | \begin{center} |
---|
40 | \begin{tabular}{|l|l|l|} |
---|
41 | \hline |
---|
42 | Instruction & Size (bytes) & Displacement range \\ |
---|
43 | \hline |
---|
44 | Short jump & 2 & -128 to 127 bytes \\ |
---|
45 | Relative near jump & 5 & $-2^{32}$ to $2^{32}-1$ bytes \\ |
---|
46 | Absolute near jump & 6 & one segment (64-bit address) \\ |
---|
47 | Far jump & 8 & entire memory (indirect jump) \\ |
---|
48 | \hline |
---|
49 | \end{tabular} |
---|
50 | \end{center} |
---|
51 | \caption{List of x86 branch instructions} |
---|
52 | \label{f:x86jumps} |
---|
53 | \end{figure} |
---|
54 | |
---|
55 | The chosen target architecture of the CerCo project is the Intel MCS-51, which |
---|
56 | features three types of branch instructions (or jump instructions; the two terms |
---|
57 | are used interchangeably), as shown in Figure~\ref{f:mcs51jumps}. |
---|
58 | |
---|
59 | \begin{figure}[h] |
---|
60 | \begin{center} |
---|
61 | \begin{tabular}{|l|l|l|l|} |
---|
62 | \hline |
---|
63 | Instruction & Size & Execution time & Displacement range \\ |
---|
64 | & (bytes) & (cycles) & \\ |
---|
65 | \hline |
---|
66 | SJMP (`short jump') & 2 & 2 & -128 to 127 bytes \\ |
---|
67 | AJMP (`absolute jump') & 2 & 2 & one segment (11-bit address) \\ |
---|
68 | LJMP (`long jump') & 3 & 3 & entire memory \\ |
---|
69 | \hline |
---|
70 | \end{tabular} |
---|
71 | \end{center} |
---|
72 | \caption{List of MCS-51 branch instructions} |
---|
73 | \label{f:mcs51jumps} |
---|
74 | \end{figure} |
---|
75 | |
---|
76 | Conditional branch instructions are only available in short form, which |
---|
77 | means that a conditional branch outside the short address range has to be |
---|
78 | encoded using three branch instructions (for instructions whose logical |
---|
79 | negation is available, it can be done with two branch instructions, but for |
---|
80 | some instructions this is not available); the call instruction is |
---|
81 | only available in absolute and long forms. |
---|
82 | |
---|
83 | Note that even though the MCS-51 architecture is much less advanced and simpler |
---|
84 | than the x86-64 architecture, the basic types of branch instruction |
---|
85 | remain the same: a short jump with a limited range, an intra-segment jump and a |
---|
86 | jump that can reach the entire available memory. |
---|
87 | |
---|
88 | Generally, in code fed to the assembler as input, the only |
---|
89 | difference between branch instructions is semantics, not span. This |
---|
90 | means that a distinction is made between an unconditional branch and the |
---|
91 | several kinds of conditional branch, but not between their short, absolute or |
---|
92 | long variants. |
---|
93 | |
---|
94 | The algorithm used by the assembler to encode these branch instructions into |
---|
95 | the different machine instructions is known as the {\em branch displacement |
---|
96 | algorithm}. The optimisation problem consists of finding as small an encoding as |
---|
97 | possible, thus minimising program length and execution time. |
---|
98 | |
---|
99 | This problem is known to be NP-complete~\cite{Robertson1979,Szymanski1978}, |
---|
100 | which could make finding an optimal solution very time-consuming. |
---|
101 | |
---|
102 | The canonical solution, as shown by Szymanski~\cite{Szymanski1978} or more |
---|
103 | recently by Dickson~\cite{Dickson2008} for the x86 instruction set, is to use a |
---|
104 | fixed point algorithm that starts with the shortest possible encoding (all |
---|
105 | branch instruction encoded as short jumps, which is likely not a correct |
---|
106 | solution) and then iterates over the source to re-encode those branch |
---|
107 | instructions whose target is outside their range. |
---|
108 | |
---|
109 | \subsection*{Adding absolute jumps} |
---|
110 | |
---|
111 | In both papers mentioned above, the encoding of a jump is only dependent on the |
---|
112 | distance between the jump and its target: below a certain value a short jump |
---|
113 | can be used; above this value the jump must be encoded as a long jump. |
---|
114 | |
---|
115 | Here, termination of the smallest fixed point algorithm is easy to prove. All |
---|
116 | branch instructions start out encoded as short jumps, which means that the |
---|
117 | distance between any branch instruction and its target is as short as possible. |
---|
118 | If, in this situation, there is a branch instruction $b$ whose span is not |
---|
119 | within the range for a short jump, we can be sure that we can never reach a |
---|
120 | situation where the span of $j$ is so small that it can be encoded as a short |
---|
121 | jump. This argument continues to hold throughout the subsequent iterations of |
---|
122 | the algorithm: short jumps can change into long jumps, but not \emph{vice versa}, |
---|
123 | as spans only increase. Hence, the algorithm either terminates early when a fixed |
---|
124 | point is reached or when all short jumps have been changed into long jumps. |
---|
125 | |
---|
126 | Also, we can be certain that we have reached an optimal solution: a short jump |
---|
127 | is only changed into a long jump if it is absolutely necessary. |
---|
128 | |
---|
129 | However, neither of these claims (termination nor optimality) hold when we add |
---|
130 | the absolute jump, as with absolute jumps, the encoding of a branch |
---|
131 | instruction no longer depends only on the distance between the branch |
---|
132 | instruction and its target: an absolute jump is possible when they |
---|
133 | are in the same segment (for the MCS-51, this means that the first 5 |
---|
134 | bytes of their addresses have to be equal). It is therefore entirely possible |
---|
135 | for two branch instructions with the same span to be encoded in different ways |
---|
136 | (absolute if the branch instruction and its target are in the same segment, |
---|
137 | long if this is not the case). |
---|
138 | |
---|
139 | \begin{figure}[t] |
---|
140 | \begin{subfigure}[b]{.45\linewidth} |
---|
141 | \begin{alltt} |
---|
142 | jmp X |
---|
143 | \ldots |
---|
144 | L\(\sb{0}\): \ldots |
---|
145 | % Start of new segment if |
---|
146 | % jmp X is encoded as short |
---|
147 | \ldots |
---|
148 | jmp L\(\sb{0}\) |
---|
149 | \end{alltt} |
---|
150 | \caption{Example of a program where a long jump becomes absolute} |
---|
151 | \label{f:term_example} |
---|
152 | \end{subfigure} |
---|
153 | \hfill |
---|
154 | \begin{subfigure}[b]{.45\linewidth} |
---|
155 | \begin{alltt} |
---|
156 | L\(\sb{0}\): jmp X |
---|
157 | X: \ldots |
---|
158 | \ldots |
---|
159 | L\(\sb{1}\): \ldots |
---|
160 | % Start of new segment if |
---|
161 | % jmp X is encoded as short |
---|
162 | \ldots |
---|
163 | jmp L\(\sb{1}\) |
---|
164 | \ldots |
---|
165 | jmp L\(\sb{1}\) |
---|
166 | \ldots |
---|
167 | jmp L\(\sb{1}\) |
---|
168 | \ldots |
---|
169 | \end{alltt} |
---|
170 | \caption{Example of a program where the fixed-point algorithm is not optimal} |
---|
171 | \label{f:opt_example} |
---|
172 | \end{subfigure} |
---|
173 | \end{figure} |
---|
174 | |
---|
175 | This invalidates our earlier termination argument: a branch instruction, once encoded |
---|
176 | as a long jump, can be re-encoded during a later iteration as an absolute jump. |
---|
177 | Consider the program shown in Figure~\ref{f:term_example}. At the start of the |
---|
178 | first iteration, both the branch to {\tt X} and the branch to $\mathtt{L}_{0}$ |
---|
179 | are encoded as small jumps. Let us assume that in this case, the placement of |
---|
180 | $\mathtt{L}_{0}$ and the branch to it are such that $\mathtt{L}_{0}$ is just |
---|
181 | outside the segment that contains this branch. Let us also assume that the |
---|
182 | distance between $\mathtt{L}_{0}$ and the branch to it is too large for the |
---|
183 | branch instruction to be encoded as a short jump. |
---|
184 | |
---|
185 | All this means that in the second iteration, the branch to $\mathtt{L}_{0}$ will |
---|
186 | be encoded as a long jump. If we assume that the branch to {\tt X} is encoded as |
---|
187 | a long jump as well, the size of the branch instruction will increase and |
---|
188 | $\mathtt{L}_{0}$ will be `propelled' into the same segment as its branch |
---|
189 | instruction, because every subsequent instruction will move one byte forward. |
---|
190 | Hence, in the third iteration, the branch to $\mathtt{L}_{0}$ can be encoded as |
---|
191 | an absolute jump. At first glance, there is nothing that prevents us from |
---|
192 | constructing a configuration where two branch instructions interact in such a |
---|
193 | way as to iterate indefinitely between long and absolute encodings. |
---|
194 | |
---|
195 | This situation mirrors the explanation by Szymanski~\cite{Szymanski1978} of why |
---|
196 | the branch displacement optimisation problem is NP-complete. In this explanation, |
---|
197 | a condition for NP-completeness is the fact that programs be allowed to contain |
---|
198 | {\em pathological} jumps. These are branch instructions that can normally not be |
---|
199 | encoded as a short(er) jump, but gain this property when some other branch |
---|
200 | instructions are encoded as a long(er) jump. This is exactly what happens in |
---|
201 | Figure~\ref{f:term_example}. By encoding the first branch instruction as a long |
---|
202 | jump, another branch instruction switches from long to absolute (which is |
---|
203 | shorter). |
---|
204 | |
---|
205 | In addition, our previous optimality argument no longer holds. Consider the program |
---|
206 | shown in Figure~\ref{f:opt_example}. Suppose that the distance between |
---|
207 | $\mathtt{L}_{0}$ and $\mathtt{L}_{1}$ is such that if {\tt jmp X} is encoded |
---|
208 | as a short jump, there is a segment border just after $\mathtt{L}_{1}$. Let |
---|
209 | us also assume that the three branches to $\mathtt{L}_{1}$ are all in the same |
---|
210 | segment, but far enough away from $\mathtt{L}_{1}$ that they cannot be encoded |
---|
211 | as short jumps. |
---|
212 | |
---|
213 | Then, if {\tt jmp X} were to be encoded as a short jump, which is clearly |
---|
214 | possible, all of the branches to $\mathtt{L}_{1}$ would have to be encoded as |
---|
215 | long jumps. However, if {\tt jmp X} were to be encoded as a long jump, and |
---|
216 | therefore increase in size, $\mathtt{L}_{1}$ would be `propelled' across the |
---|
217 | segment border, so that the three branches to $\mathtt{L}_{1}$ could be encoded |
---|
218 | as absolute jumps. Depending on the relative sizes of long and absolute jumps, |
---|
219 | this solution might actually be smaller than the one reached by the smallest |
---|
220 | fixed point algorithm. |
---|