blob: 6e2313ca7b72989155234e25c392d023fde95b88 [file] [log] [blame]
Douglas Gregor32110df2009-05-20 00:16:32 +00001<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
2<html> <head>
3<title>Precompiled Headers (PCH)</title>
4</head>
5
6<body>
7
8<!--#include virtual="../menu.html.incl"-->
9
10<div id="content">
11
12<h1>Precompiled Headers</h1>
13
14 <p>This document describes the design and implementation of Clang's
15 precompiled headers (PCH). If you are interested in the end-user
16 view, please see the <a
17 href="UsersManual.html#precompiledheaders">User's Manual</a>.</p>
18
Douglas Gregor923cb232009-06-03 18:35:59 +000019 <p><b>Table of Contents</b></p>
20 <ul>
21 <li><a href="#usage">Using Precompiled Headers with
22 <tt>clang-cc</tt></a></li>
23 <li><a href="#philosophy">Design Philosophy</a></li>
24 <li><a href="#contents">Precompiled Header Contents</a>
25 <ul>
26 <li><a href="#metadata">Metadata Block</a></li>
27 <li><a href="#sourcemgr">Source Manager Block</a></li>
28 <li><a href="#preprocessor">Preprocessor Block</a></li>
29 <li><a href="#types">Types Block</a></li>
30 <li><a href="#decls">Declarations Block</a></li>
31 <li><a href="#stmt">Statements and Expressions</a></li>
32 <li><a href="#idtable">Identifier Table Block</a></li>
33 <li><a href="#method-pool">Method Pool Block</a></li>
34 </ul>
35 </li>
Douglas Gregor4c0397f2009-06-03 21:55:35 +000036 <li><a href="#tendrils">Precompiled Header Integration
37 Points</a></li>
Douglas Gregor0084ead2009-06-03 21:41:31 +000038</ul>
Douglas Gregor923cb232009-06-03 18:35:59 +000039
40<h2 id="usage">Using Precompiled Headers with <tt>clang-cc</tt></h2>
Douglas Gregor32110df2009-05-20 00:16:32 +000041
42<p>The low-level Clang compiler, <tt>clang-cc</tt>, supports two command
43line options for generating and using PCH files.<p>
44
45<p>To generate PCH files using <tt>clang-cc</tt>, use the option
46<b><tt>-emit-pch</tt></b>:
47
48<pre> $ clang-cc test.h -emit-pch -o test.h.pch </pre>
49
50<p>This option is transparently used by <tt>clang</tt> when generating
51PCH files. The resulting PCH file contains the serialized form of the
52compiler's internal representation after it has completed parsing and
53semantic analysis. The PCH file can then be used as a prefix header
54with the <b><tt>-include-pch</tt></b> option:</p>
55
56<pre>
57 $ clang-cc -include-pch test.h.pch test.c -o test.s
58</pre>
59
Douglas Gregor923cb232009-06-03 18:35:59 +000060<h2 id="philosophy">Design Philosophy</h2>
Douglas Gregor32110df2009-05-20 00:16:32 +000061
62<p>Precompiled headers are meant to improve overall compile times for
63 projects, so the design of precompiled headers is entirely driven by
64 performance concerns. The use case for precompiled headers is
65 relatively simple: when there is a common set of headers that is
66 included in nearly every source file in the project, we
67 <i>precompile</i> that bundle of headers into a single precompiled
68 header (PCH file). Then, when compiling the source files in the
69 project, we load the PCH file first (as a prefix header), which acts
70 as a stand-in for that bundle of headers.</p>
71
72<p>A precompiled header implementation improves performance when:</p>
73<ul>
74 <li>Loading the PCH file is significantly faster than re-parsing the
75 bundle of headers stored within the PCH file. Thus, a precompiled
76 header design attempts to minimize the cost of reading the PCH
77 file. Ideally, this cost should not vary with the size of the
78 precompiled header file.</li>
79
80 <li>The cost of generating the PCH file initially is not so large
81 that it counters the per-source-file performance improvement due to
82 eliminating the need to parse the bundled headers in the first
83 place. This is particularly important on multi-core systems, because
84 PCH file generation serializes the build when all compilations
85 require the PCH file to be up-to-date.</li>
86</ul>
Douglas Gregor2cc390e2009-06-02 22:08:07 +000087
88<p>Clang's precompiled headers are designed with a compact on-disk
89representation, which minimizes both PCH creation time and the time
90required to initially load the PCH file. The PCH file itself contains
91a serialized representation of Clang's abstract syntax trees and
92supporting data structures, stored using the same compressed bitstream
93as <a href="http://llvm.org/docs/BitCodeFormat.html">LLVM's bitcode
94file format</a>.</p>
95
96<p>Clang's precompiled headers are loaded "lazily" from disk. When a
97PCH file is initially loaded, Clang reads only a small amount of data
98from the PCH file to establish where certain important data structures
99are stored. The amount of data read in this initial load is
100independent of the size of the PCH file, such that a larger PCH file
101does not lead to longer PCH load times. The actual header data in the
102PCH file--macros, functions, variables, types, etc.--is loaded only
103when it is referenced from the user's code, at which point only that
104entity (and those entities it depends on) are deserialized from the
105PCH file. With this approach, the cost of using a precompiled header
106for a translation unit is proportional to the amount of code actually
107used from the header, rather than being proportional to the size of
Douglas Gregor4c0397f2009-06-03 21:55:35 +0000108the header itself.</p>
109
110<p>When given the <code>-print-stats</code> option, Clang produces
111statistics describing how much of the precompiled header was actually
112loaded from disk. For a simple "Hello, World!" program that includes
113the Apple <code>Cocoa.h</code> header (which is built as a precompiled
114header), this option illustrates how little of the actual precompiled
115header is required:</p>
116
117<pre>
118*** PCH Statistics:
119 933 stat cache hits
120 4 stat cache misses
121 895/39981 source location entries read (2.238563%)
122 19/15315 types read (0.124061%)
123 20/82685 declarations read (0.024188%)
124 154/58070 identifiers read (0.265197%)
125 0/7260 selectors read (0.000000%)
126 0/30842 statements read (0.000000%)
127 4/8400 macros read (0.047619%)
128 1/4995 lexical declcontexts read (0.020020%)
129 0/4413 visible declcontexts read (0.000000%)
130 0/7230 method pool entries read (0.000000%)
131 0 method pool misses
132</pre>
133
134<p>For this small program, only a tiny fraction of the source
135locations, types, declarations, identifiers, and macros were actually
136deserialized from the precompiled header. These statistics can be
137useful to determine whether the precompiled header implementation can
138be improved by making more of the implementation lazy.</p>
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000139
Douglas Gregor923cb232009-06-03 18:35:59 +0000140<h2 id="contents">Precompiled Header Contents</h2>
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000141
142<img src="PCHLayout.png" align="right" alt="Precompiled header layout">
143
144<p>Clang's precompiled headers are organized into several different
145blocks, each of which contains the serialized representation of a part
146of Clang's internal representation. Each of the blocks corresponds to
147either a block or a record within <a
148 href="http://llvm.org/docs/BitCodeFormat.html">LLVM's bitstream
149format</a>. The contents of each of these logical blocks are described
150below.</p>
151
Douglas Gregor4c0397f2009-06-03 21:55:35 +0000152<p>For a given precompiled header, the <a
153href="http://llvm.org/cmds/llvm-bcanalyzer.html"><code>llvm-bcanalyzer</code></a>
154utility can be used to examine the actual structure of the bitstream
155for the precompiled header. This information can be used both to help
156understand the structure of the precompiled header and to isolate
157areas where precompiled headers can still be optimized, e.g., through
158the introduction of abbreviations.</p>
159
Douglas Gregor923cb232009-06-03 18:35:59 +0000160<h3 id="metadata">Metadata Block</h3>
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000161
162<p>The metadata block contains several records that provide
163information about how the precompiled header was built. This metadata
164is primarily used to validate the use of a precompiled header. For
Douglas Gregorfe3f2232009-06-03 18:26:16 +0000165example, a precompiled header built for a 32-bit x86 target cannot be used
166when compiling for a 64-bit x86 target. The metadata block contains
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000167information about:</p>
168
169<dl>
170 <dt>Language options</dt>
171 <dd>Describes the particular language dialect used to compile the
172PCH file, including major options (e.g., Objective-C support) and more
173minor options (e.g., support for "//" comments). The contents of this
174record correspond to the <code>LangOptions</code> class.</dd>
Douglas Gregor32110df2009-05-20 00:16:32 +0000175
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000176 <dt>Target architecture</dt>
177 <dd>The target triple that describes the architecture, platform, and
178ABI for which the PCH file was generated, e.g.,
179<code>i386-apple-darwin9</code>.</dd>
180
181 <dt>PCH version</dt>
182 <dd>The major and minor version numbers of the precompiled header
183format. Changes in the minor version number should not affect backward
184compatibility, while changes in the major version number imply that a
185newer compiler cannot read an older precompiled header (and
186vice-versa).</dd>
187
188 <dt>Original file name</dt>
189 <dd>The full path of the header that was used to generate the
Douglas Gregor5accbb92009-06-03 16:06:22 +0000190precompiled header.</dd>
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000191
192 <dt>Predefines buffer</dt>
193 <dd>Although not explicitly stored as part of the metadata, the
194predefines buffer is used in the validation of the precompiled header.
195The predefines buffer itself contains code generated by the compiler
196to initialize the preprocessor state according to the current target,
197platform, and command-line options. For example, the predefines buffer
198will contain "<code>#define __STDC__ 1</code>" when we are compiling C
199without Microsoft extensions. The predefines buffer itself is stored
200within the <a href="#sourcemgr">source manager block</a>, but its
Douglas Gregor5accbb92009-06-03 16:06:22 +0000201contents are verified along with the rest of the metadata.</dd>
202
203</dl>
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000204
Douglas Gregor923cb232009-06-03 18:35:59 +0000205<h3 id="sourcemgr">Source Manager Block</h3>
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000206
207<p>The source manager block contains the serialized representation of
208Clang's <a
209 href="InternalsManual.html#SourceLocation">SourceManager</a> class,
210which handles the mapping from source locations (as represented in
211Clang's abstract syntax tree) into actual column/line positions within
212a source file or macro instantiation. The precompiled header's
213representation of the source manager also includes information about
214all of the headers that were (transitively) included when building the
215precompiled header.</p>
216
217<p>The bulk of the source manager block is dedicated to information
218about the various files, buffers, and macro instantiations into which
219a source location can refer. Each of these is referenced by a numeric
220"file ID", which is a unique number (allocated starting at 1) stored
221in the source location. Clang serializes the information for each kind
222of file ID, along with an index that maps file IDs to the position
223within the PCH file where the information about that file ID is
224stored. The data associated with a file ID is loaded only when
225required by the front end, e.g., to emit a diagnostic that includes a
226macro instantiation history inside the header itself.</p>
227
228<p>The source manager block also contains information about all of the
229headers that were included when building the precompiled header. This
230includes information about the controlling macro for the header (e.g.,
231when the preprocessor identified that the contents of the header
232dependent on a macro like <code>LLVM_CLANG_SOURCEMANAGER_H</code>)
233along with a cached version of the results of the <code>stat()</code>
234system calls performed when building the precompiled header. The
235latter is particularly useful in reducing system time when searching
236for include files.</p>
237
Douglas Gregor923cb232009-06-03 18:35:59 +0000238<h3 id="preprocessor">Preprocessor Block</h3>
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000239
240<p>The preprocessor block contains the serialized representation of
241the preprocessor. Specifically, it contains all of the macros that
242have been defined by the end of the header used to build the
243precompiled header, along with the token sequences that comprise each
244macro. The macro definitions are only read from the PCH file when the
245name of the macro first occurs in the program. This lazy loading of
246macro definitions is trigged by lookups into the <a
247 href="#idtable">identifier table</a>.</p>
248
Douglas Gregor923cb232009-06-03 18:35:59 +0000249<h3 id="types">Types Block</h3>
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000250
251<p>The types block contains the serialized representation of all of
252the types referenced in the translation unit. Each Clang type node
253(<code>PointerType</code>, <code>FunctionProtoType</code>, etc.) has a
254corresponding record type in the PCH file. When types are deserialized
255from the precompiled header, the data within the record is used to
256reconstruct the appropriate type node using the AST context.</p>
257
258<p>Each type has a unique type ID, which is an integer that uniquely
259identifies that type. Type ID 0 represents the NULL type, type IDs
260less than <code>NUM_PREDEF_TYPE_IDS</code> represent predefined types
261(<code>void</code>, <code>float</code>, etc.), while other
262"user-defined" type IDs are assigned consecutively from
263<code>NUM_PREDEF_TYPE_IDS</code> upward as the types are encountered.
264The PCH file has an associated mapping from the user-defined types
265block to the location within the types block where the serialized
266representation of that type resides, enabling lazy deserialization of
267types. When a type is referenced from within the PCH file, that
268reference is encoded using the type ID shifted left by 3 bits. The
269lower three bits are used to represent the <code>const</code>,
270<code>volatile</code>, and <code>restrict</code> qualifiers, as in
271Clang's <a
272 href="http://clang.llvm.org/docs/InternalsManual.html#Type">QualType</a>
273class.</p>
274
Douglas Gregor923cb232009-06-03 18:35:59 +0000275<h3 id="decls">Declarations Block</h3>
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000276
277<p>The declarations block contains the serialized representation of
278all of the declarations referenced in the translation unit. Each Clang
279declaration node (<code>VarDecl</code>, <code>FunctionDecl</code>,
280etc.) has a corresponding record type in the PCH file. When
281declarations are deserialized from the precompiled header, the data
282within the record is used to build and populate a new instance of the
283corresponding <code>Decl</code> node. As with types, each declaration
284node has a numeric ID that is used to refer to that declaration within
285the PCH file. In addition, a lookup table provides a mapping from that
286numeric ID to the offset within the precompiled header where that
287declaration is described.</p>
288
289<p>Declarations in Clang's abstract syntax trees are stored
290hierarchically. At the top of the hierarchy is the translation unit
291(<code>TranslationUnitDecl</code>), which contains all of the
292declarations in the translation unit. These declarations---such as
293functions or struct types---may also contain other declarations inside
294them, and so on. Within Clang, each declaration is stored within a <a
295href="http://clang.llvm.org/docs/InternalsManual.html#DeclContext">declaration
296context</a>, as represented by the <code>DeclContext</code> class.
297Declaration contexts provide the mechanism to perform name lookup
298within a given declaration (e.g., find the member named <code>x</code>
299in a structure) and iterate over the declarations stored within a
300context (e.g., iterate over all of the fields of a structure for
301structure layout).</p>
302
303<p>In Clang's precompiled header format, deserializing a declaration
304that is a <code>DeclContext</code> is a separate operation from
305deserializing all of the declarations stored within that declaration
306context. Therefore, Clang will deserialize the translation unit
307declaration without deserializing the declarations within that
308translation unit. When required, the declarations stored within a
309declaration context will be serialized. There are two representations
310of the declarations within a declaration context, which correspond to
311the name-lookup and iteration behavior described above:</p>
312
313<ul>
314 <li>When the front end performs name lookup to find a name
315 <code>x</code> within a given declaration context (for example,
316 during semantic analysis of the expression <code>p-&gt;x</code>,
317 where <code>p</code>'s type is defined in the precompiled header),
318 Clang deserializes a hash table mapping from the names within that
319 declaration context to the declaration IDs that represent each
320 visible declaration with that name. The entire hash table is
321 deserialized at this point (into the <code>llvm::DenseMap</code>
322 stored within each <code>DeclContext</code> object), but the actual
323 declarations are not yet deserialized. In a second step, those
324 declarations with the name <code>x</code> will be deserialized and
325 will be used as the result of name lookup.</li>
326
327 <li>When the front end performs iteration over all of the
328 declarations within a declaration context, all of those declarations
329 are immediately de-serialized. For large declaration contexts (e.g.,
330 the translation unit), this operation is expensive; however, large
331 declaration contexts are not traversed in normal compilation, since
332 such a traversal is unnecessary. However, it is common for the code
333 generator and semantic analysis to traverse declaration contexts for
334 structs, classes, unions, and enumerations, although those contexts
335 contain relatively few declarations in the common case.</li>
336</ul>
337
Douglas Gregor923cb232009-06-03 18:35:59 +0000338<h3 id="stmt">Statements and Expressions</h3>
Douglas Gregor5accbb92009-06-03 16:06:22 +0000339
340<p>Statements and expressions are stored in the precompiled header in
341both the <a href="#types">types</a> and the <a
342 href="#decls">declarations</a> blocks, because every statement or
343expression will be associated with either a type or declaration. The
344actual statement and expression records are stored immediately
345following the declaration or type that owns the statement or
346expression. For example, the statement representing the body of a
347function will be stored directly following the declaration of the
348function.</p>
349
350<p>As with types and declarations, each statement and expression kind
351in Clang's abstract syntax tree (<code>ForStmt</code>,
352<code>CallExpr</code>, etc.) has a corresponding record type in the
353precompiled header, which contains the serialized representation of
Douglas Gregorfe3f2232009-06-03 18:26:16 +0000354that statement or expression. Each substatement or subexpression
355within an expression is stored as a separate record (which keeps most
356records to a fixed size). Within the precompiled header, the
357subexpressions of an expression are stored prior to the expression
358that owns those expression, using a form of <a
359href="http://en.wikipedia.org/wiki/Reverse_Polish_notation">Reverse
360Polish Notation</a>. For example, an expression <code>3 - 4 + 5</code>
361would be represented as follows:</p>
362
363<table border="1">
364 <tr><td><code>IntegerLiteral(3)</code></td></tr>
365 <tr><td><code>IntegerLiteral(4)</code></td></tr>
366 <tr><td><code>BinaryOperator(-)</code></td></tr>
367 <tr><td><code>IntegerLiteral(5)</code></td></tr>
368 <tr><td><code>BinaryOperator(+)</code></td></tr>
369 <tr><td>STOP</td></tr>
370</table>
371
372<p>When reading this representation, Clang evaluates each expression
373record it encounters, builds the appropriate abstract synax tree node,
374and then pushes that expression on to a stack. When a record contains <i>N</i>
375subexpressions--<code>BinaryOperator</code> has two of them--those
376expressions are popped from the top of the stack. The special STOP
377code indicates that we have reached the end of a serialized expression
378or statement; other expression or statement records may follow, but
379they are part of a different expression.</p>
Douglas Gregor5accbb92009-06-03 16:06:22 +0000380
Douglas Gregor923cb232009-06-03 18:35:59 +0000381<h3 id="idtable">Identifier Table Block</h3>
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000382
383<p>The identifier table block contains an on-disk hash table that maps
384each identifier mentioned within the precompiled header to the
385serialized representation of the identifier's information (e.g, the
386<code>IdentifierInfo</code> structure). The serialized representation
387contains:</p>
388
389<ul>
390 <li>The actual identifier string.</li>
391 <li>Flags that describe whether this identifier is the name of a
392 built-in, a poisoned identifier, an extension token, or a
393 macro.</li>
394 <li>If the identifier names a macro, the offset of the macro
395 definition within the <a href="#preprocessor">preprocessor
396 block</a>.</li>
397 <li>If the identifier names one or more declarations visible from
398 translation unit scope, the <a href="#decls">declaration IDs</a> of these
399 declarations.</li>
400</ul>
401
402<p>When a precompiled header is loaded, the precompiled header
403mechanism introduces itself into the identifier table as an external
404lookup source. Thus, when the user program refers to an identifier
405that has not yet been seen, Clang will perform a lookup into the
Douglas Gregor5accbb92009-06-03 16:06:22 +0000406identifier table. If an identifier is found, its contents---macro definitions, flags, top-level declarations, etc.---will be deserialized, at which point the corresponding <code>IdentifierInfo</code> structure will have the same contents it would have after parsing the headers in the precompiled header.</p>
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000407
Douglas Gregor5accbb92009-06-03 16:06:22 +0000408<p>Within the PCH file, the identifiers used to name declarations are represented with an integral value. A separate table provides a mapping from this integral value (the identifier ID) to the location within the on-disk
Douglas Gregor2cc390e2009-06-02 22:08:07 +0000409hash table where that identifier is stored. This mapping is used when
410deserializing the name of a declaration, the identifier of a token, or
411any other construct in the PCH file that refers to a name.</p>
412
Douglas Gregor923cb232009-06-03 18:35:59 +0000413<h3 id="method-pool">Method Pool Block</h3>
Douglas Gregor5accbb92009-06-03 16:06:22 +0000414
415<p>The method pool block is represented as an on-disk hash table that
416serves two purposes: it provides a mapping from the names of
417Objective-C selectors to the set of Objective-C instance and class
418methods that have that particular selector (which is required for
419semantic analysis in Objective-C) and also stores all of the selectors
420used by entities within the precompiled header. The design of the
421method pool is similar to that of the <a href="#idtable">identifier
422table</a>: the first time a particular selector is formed during the
423compilation of the program, Clang will search in the on-disk hash
424table of selectors; if found, Clang will read the Objective-C methods
425associated with that selector into the appropriate front-end data
426structure (<code>Sema::InstanceMethodPool</code> and
427<code>Sema::FactoryMethodPool</code> for instance and class methods,
428respectively).</p>
429
430<p>As with identifiers, selectors are represented by numeric values
431within the PCH file. A separate index maps these numeric selector
432values to the offset of the selector within the on-disk hash table,
433and will be used when de-serializing an Objective-C method declaration
434(or other Objective-C construct) that refers to the selector.</p>
435
Douglas Gregor0084ead2009-06-03 21:41:31 +0000436<h2 id="tendrils">Precompiled Header Integration Points</h2>
437
438<p>The "lazy" deserialization behavior of precompiled headers requires
439their integration into several completely different submodules of
440Clang. For example, lazily deserializing the declarations during name
441lookup requires that the name-lookup routines be able to query the
442precompiled header to find entities within the PCH file.</p>
443
444<p>For each Clang data structure that requires direct interaction with
445the precompiled header logic, there is an abstract class that provides
446the interface between the two modules. The <code>PCHReader</code>
447class, which handles the loading of a precompiled header, inherits
448from all of these abstract classes to provide lazy deserialization of
449Clang's data structures. <code>PCHReader</code> implements the
450following abstract classes:</p>
451
452<dl>
453 <dt><code>StatSysCallCache</code></dt>
454 <dd>This abstract interface is associated with the
455 <code>FileManager</code> class, and is used whenever the file
456 manager is going to perform a <code>stat()</code> system call.</dd>
457
458 <dt><code>ExternalSLocEntrySource</code></dt>
459 <dd>This abstract interface is associated with the
460 <code>SourceManager</code> class, and is used whenever the
461 <a href="#sourcemgr">source manager</a> needs to load the details
462 of a file, buffer, or macro instantiation.</dd>
463
464 <dt><code>IdentifierInfoLookup</code></dt>
465 <dd>This abstract interface is associated with the
466 <code>IdentifierTable</code> class, and is used whenever the
467 program source refers to an identifier that has not yet been seen.
468 In this case, the precompiled header implementation searches for
469 this identifier within its <a href="#idtable">identifier table</a>
470 to load any top-level declarations or macros associated with that
471 identifier.</dd>
472
473 <dt><code>ExternalASTSource</code></dt>
474 <dd>This abstract interface is associated with the
475 <code>ASTContext</code> class, and is used whenever the abstract
476 syntax tree nodes need to loaded from the precompiled header. It
477 provides the ability to de-serialize declarations and types
478 identified by their numeric values, read the bodies of functions
479 when required, and read the declarations stored within a
480 declaration context (either for iteration or for name lookup).</dd>
481
482 <dt><code>ExternalSemaSource</code></dt>
483 <dd>This abstract interface is associated with the <code>Sema</code>
484 class, and is used whenever semantic analysis needs to read
485 information from the <a href="#methodpool">global method
486 pool</a>.</dd>
487</dl>
488
Douglas Gregor32110df2009-05-20 00:16:32 +0000489</div>
490
Douglas Gregor4c0397f2009-06-03 21:55:35 +0000491</body>
Douglas Gregor32110df2009-05-20 00:16:32 +0000492</html>