Misc improvements.
[matthijs/projects/internship.git] / Report / Main / Problems / Challenges.tex
1 \section{Challenges and Solutions}
2 This section will describe the challenges faced during each of the tasks and the
3 solutions found for both the task itself and the challenges.
4
5 \subsection{What is MontiumC?}
6 A critical question popped up during at the beginning of my internship: What is
7 MontiumC? Previously, there was no real specification of MontiumC. There was
8 documentation about the functions that could be used, some examples and a lot of
9 personal knowledge in the heads of the Recore employees, but ultimately MontiumC
10 was "whatever the compiler eats".
11
12 To be able to create a proper set of transformations, the constraints on the
13 input and output of that transformation process should be properly specified.
14 This entails two parts: Specifying the MontiumC language, and specifying the
15 Montium IR constraints, which is is the input to the backend.
16
17 Specifying Montium IR was relatively easy, since it is defined directly by the
18 backend. The MontiumC specification is slightly more complex. There are two
19 different angles to it: What does the compiler support, and what do we want the
20 compiler to support.
21
22 \subsubsection{What is supported?}
23 One angle for looking at MontiumC is seeing what the compiler can currently
24 compile and turn that into a formal specification. This approach works fine to
25 set a baseline for the compiler: A point to start improving the transformations
26 from. Apart from that, this initial specification is also useful in reviewing
27 existing MontiumC code and verifying existing transformations.
28
29 Existing MontiumC code is of course supported by the compiler (since it has been
30 tweaked until it worked). However, the existing code might not fully work within
31 the produced specification. This can happen in particular when existing code
32 makes use of corner cases in the compiler that have been missed in (or left out
33 of) the specification, because they will not work reliably in all cases.
34
35 The best way to detect these cases is making the compiler check its input using
36 an the specification. This way, any code operating outside of the specification
37 can be detected automatically. Writing such checks has not happened yet, mainly
38 because the impact of the new hardware on MontiumC is not quite clear yet.
39
40 Existing transformations, on the other hand, might miss a few corner cases. When
41 writing the specification, a particular feature might appear supported by the
42 compiler. On closer inspection, the transformation passes might miss some corner
43 cases, which either need to be fixed or put into the specification.
44
45 The best way to detect these cases is writing a lot of structured testing code,
46 which uses (combinations of) the features that are supported according to the
47 specification. This way, corner cases exposed by the testing code can be
48 detected automatically. A framework for this testing has been set up and
49 partially filled with small tests.
50
51 Building this initial specification did pose a number of challenges. Since
52 simply trying all possible C features to see if they are accepted by the
53 MontiumC compiler and thus valid MontiumC is a lengthy process and only useful
54 in a limited way. A more constructive way would be to examine the compiler
55 components to see what transformations are applied and from that derive the
56 specification for valid MontiumC. However, most of these components are not very
57 transparent. The Clang frontend supports a lot of C constructs and is not a
58 trivial piece of code. It also has support for Objective C and partially C++,
59 which makes it harder to see which code paths are actually used for compiling
60 MontiumC. This issue is only partially solved, meaning that there might still be
61 incorrect or missing cases in the specification.
62
63 Another problem is the complexity of the C language. Although individual
64 features can be described and supported with relative ease, combining features
65 can easily lead to complex constructs which are hard to transform into supported
66 Montium IR. This became more of an issue after adding features to the base
67 specification of MontiumC, though.
68
69 \subsubsection{What is wanted?}
70 A completely different angle of looking at this, is from the requirements point
71 of view. What do we want MontiumC to support? This angle is even harder than the
72 previous one, since there are a lot of levels of requirements. Ideally, MontiumC
73 would not exist and our compiler would support the C language fully. However,
74 this would require a very complicated compiler (both frontend and backend).
75
76 Not only that, it is simply not possible to map all valid C programs to the
77 Montium hardware. "Regular" architectures don't suffer from this problem (as
78 much), since most instruction sets are not fundamentally different from the
79 features supported by C (or other imperative languages). This means that
80 anything is mappable, but with a simple compiler this will not result in the
81 most efficient code. In the Montium case, a lot of things simply cannot be
82 mapped on the hardware at all.
83
84 Considering that our ideal is not reachable (by far), every feature in our
85 wanted MontiumC should be evaluated thoroughly for feasibility, both in hardware
86 and in the compiler. In practice, this meant that new language features would be
87 informally expressed and discussed, and only added to the specification after
88 being succesfully implemented. This is conforming to the incremental development
89 of MontiumC that was envisioned at the outset of its development.
90
91 \subsection{Familiarizing with LLVM}
92 Since the frontend heavily relies on the LLVM project for doing it's work, one
93 of the first challenges was to get myself familiar with LLVM. There are two main
94 aspects to this: Getting to know my way around the LLVM codebase and getting to
95 know the LLVM community.
96
97 Since LLVM has a pretty large amount of documentation, I spent most of my first
98 weeks with reading tutorials and documents. Since there was already a (very
99 preliminary) version of the clang-based frontend, I also had some code to play
100 with.
101
102 During this period, it was not completely clear what the frontend should
103 be doing and what transformations should be written. To prevent circular
104 dependencies in my tasks (I can't write any code before I know what needs to be
105 written, but I can't really tell what's needed until I know how the code works,
106 which I can't effectively learn without actively working with it, etc.) I
107 started out with adapting the loop unrolling pass in LLVM to be better suited to
108 the Montium architecture. Eventually, this code didn't turn out to be
109 immediately useful (it's still not included currently), but it proved very
110 insightful as to how the LLVM framework is built and what its possibilities are.
111
112 Additionally, during my working with the code in this internship I also produced
113 a number of patches for LLVM, containing bugfixes, some cleanup and
114 documentation improvements. Since the best way to integrate with any open source
115 project seems to be contributing code, I was giving commit access to the LLVM
116 tree not long thereafter. This access has proved very useful during the rest of
117 the internship, since it was now a a lot easier to make (simple) changes to the
118 LLVM framework to better suit the needs of Recore.
119
120 A major challenge during my internship was to find the balance between doing
121 work specifically for Recore, and doing work that is useful for LLVM in general.
122 Any tasks that are required by the Montium frontend and the LLVM changes they
123 directly need are obvious. However, usually when making changes to the main LLVM
124 code, just changing enough for Recore is not engough for LLVM. Since the LLVM
125 code must work on any program, not just MontiumC programs, extra changes are
126 required (see also parapgrah \ref{StayingGeneric}. This is also an issue of
127 building up credit within the LLVM community: The more you contribute to LLVM,
128 the more influence you have when things need changing. 
129
130 Lastly, this is also a matter of efficiency: If I have been working
131 with a particular piece of code intensively, it is more efficient for me to fix
132 a bug in that code than most others, even though the particular bug does not
133 interfere with the MontiumC frontend. In the end, I think I managed to find a
134 decent balance, though it might have been tipped slighly in favour of the LLVM
135 project.
136
137 \subsection{Coding}
138 This section says something about the challenges encountered while actually
139 writing code, if I can think of enough things to say here. So far, haven't
140 thought of anything particularly interesting yet.
141
142 \subsection{Working together}
143 Since the compiler plays a fairly central role in the development process at
144 Recore, I have been cooperating with a number of different people, in different
145 areas. On one end, the compiler is directly used by the DSP engineers, so a lot
146 of the requirements and wishes for the compiler come from them. Also, they are
147 often providing bug reports and other feedback, which ensures regular contact.
148
149 On the other end, I have been in contact with the developer of the backend very
150 intensively, since most changes made to either component needed changes in the
151 other one as well. Compiler changes also require hardware support, so working
152 with the hardware developers was not uncommon either. In practice, most of this
153 communication went through the backend developer, except for the design
154 discussion concerning the new Montium hardware design (also see section
155 \ref{Pipelining} below).
156
157 In addition, discussions regarding design issues at various levels often happen
158 out in the open, which easily invites people with an opinion about something to
159 chime in, without having to gather people around for every discussion that
160 happens. This allows for very quick feedback on ideas from people in all areas
161 in an efficient way.
162
163 \subsection{Staying generic}
164 \label{StayingGeneric}
165 The toughest challenge by far was to fit the features and changes required by
166 Recore into code that is maintained by the LLVM project. The main problem here
167 is clash of goals: The LLVM project aims for code that performs well for their
168 supported architectures, while Recore aims for code that performs well (or more
169 often, can compile MontiumC at all) for the Montium architecture.
170
171 In general, these problems mainly occur because MontiumC and in particular
172 MontiumIR poses a number of constraints that are not present in other
173 architectures. This means that some transformations present in LLVM will violate
174 these constraints (which would result in better code on most other
175 architectures) resulting in miscompiled or uncompilable code.
176
177 In some cases, these extra constraints can be formulated in a generic way, such
178 that the LLVM code can handle architectures with or without the constraint.
179 In a lot of cases, however, the constraint is so Montium specific that changing
180 LLVM to support it is not feasible. In a few cases, this meant that the backend
181 was changed to support more features of the LLVM IR. In other cases, a
182 Recore-specific transformations was added to solve these problems. In a few more
183 cases, the problems are still unresolved, effectively resulting in additional
184 constraints on the MontiumC language.
185
186 \subsection{New hardware design}
187 \label{Pipelining}
188 I've also been involved for a bit with the instruction scheduling algorithm
189 required for the new (pipelined) hardware design and the hardware design itself.
190 Even though this is completely outside of the area of my assignment, the initial
191 prototype of that scheduler was created by someone else using LLVM. Because of
192 my experience with LLVM, I have been assisting him with that.  Initially mostly
193 helping out with hints on LLVM coding, but later also with thinking about the
194 scheduler and hardware design.
195
196 I will not go into much detail about the new hardware and its scheduler here,
197 but I will highlight the most important challenges and tradeoffs.
198
199 \subsubsection{Tradeoffs}
200 In general, the most important tradeoff seems to be between flexibility and
201 everything else (code size, performance, complexity, hardware area). This
202 flexibility is expressed in which instructions are possible, which connections
203 are present, how big register files are, etc.
204
205 An important reason to be flexible is for programmability. If the hardware is
206 regular, making a compiler that produces optimal code gets a lot easier.
207 On the other hand, the compiler also limits flexibility. If the hardware
208 has flexibility that the compiler will never use, it's better to save
209 area and complexity by making the hardware less flexible. Exactly for this
210 reason, it is important to develop hardware and supporting software in parallel,
211 instead of using the hardware first, software later approach used with the
212 initial Montium (TODO: Is this true?). This allows for a much better balanced
213 and usable design, without any unused extras.
214
215 \subsubsection{Inner loop pipelining}
216 When trying to improve runtime performance, the main focus is on
217 optimizing loops, and inner loops (loops that contain no other loops) in
218 particular. Since the inner loop is executed the most often, it is the most
219 efficient to optimize the inner loop. Also, the inner loop is also the piece of
220 code that can most optimally use the parellel processing power of the Montium,
221 because it can be software pipelined. 
222
223 Software pipelining means that the compiler will emit code that performs
224 operations that belong in different iterations of the original loop during the
225 same cycle. Since data dependencies within a loop body usually severely limit
226 the amount of operations that can be done in parallel, pipelining allows the
227 second (and further) iteration to start well before the first iteration is done.
228 This is done by dividing the loop body in a number of stages, that would
229 normally be executed sequentially. These stages are then executed in parallel,
230 but for different iterations (ie, run stage 2 of iteration i, while running
231 stage 1 of iteration i+1).
232
233 This approach allows a loop to be ran a lot faster than executing a
234 single iteration at a time. However, since the instructions for the
235 first and last few iterations (the prologue and epilogue) are distinctly
236 different from the loop "kernel", the number of instructions needed for
237 a pipelined loop can easily increase a lot.
238
239 However, all pipelined loops share a very distinct structure (first
240 stage 1, then stage 1+2, then stage 1+2+3, etc, then all stages at the
241 same time, similar for the epilogue). Also, every instruction in the
242 prologue and epilogue are a strict subset of the instructions in the
243 kernel. By adding some hardware support for exactly this structure, the
244 code size increase for the prologue and epilogue can be effectively
245 reduced to a fixed number of instructions (which take the number of stages as a
246 parameter and uses preloaded instructions with explicit stage annotation).
247
248 The tradeoff here is that this hardware is only usable specifically for these
249 inner loops, any other code will leave this extra hardware unused. However,
250 since pipelined inner loops appear to be very common, this should not be a
251 problem at all.
252
253 \subsubsection{Code compression}
254 Another very important tradeoff concerns codesize. In the old hardware, a lot of
255 flexibility in the original code was achieved by using inline functions (since
256 the hardware has only very limited support for code reuse through function
257 calls). This resulted in a lot of code duplication, which was compensated for by
258 using two level configuration registers to be able to reuse (partial)
259 instructions nonetheless (which will still need a lot of sequencer instructions,
260 but those will be a lot smaller than the full instruction).
261
262 On the new hardware, however, function calls are more powerful, which should
263 lead to a lot less code duplication. For this reason, putting every instruction
264 in configuration registers might actually take more space instead of less. It
265 should be noted that, the configuration registers of the old Montium are
266 effectively a compiler controlled cache that is mandatory and static
267 (instructions must be in the cache and the cache cannot be modified at runtime).
268 By lifting these limitations, we get a cache that is a lot more flexible.
269 However, since it is compiler controlled (as opposed to letting the hardware
270 decide what ends up in the cache), the performance of the code remains very
271 predictable (unlike traditional caches). Additionally, this has the advantage
272 that the size of the cache is no longer an upper bound on the program size, but
273 only the instruction memory is (which can be a lot bigger).
274
275 The tradeoff here is that the sequencer instructions will get a lot bigger,
276 since they need to contain a full instruction word (which would be preloaded
277 into the CRs in the old design) which can be up to a few hundred bits long.
278 Since not all sequencer instructions need to be this long (executing out of the
279 cache should be a lot shorter), different instruction lengths should be
280 supported. Also, some smart compression techniques should also be applied to
281 those long instruction words, though that can only be done once there is some
282 more application code to gather statistics about.