xref: /llvm-project-15.0.7/lld/wasm/Driver.cpp (revision 3a22c3cc)
1 //===- Driver.cpp ---------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "lld/Common/Driver.h"
10 #include "Config.h"
11 #include "InputChunks.h"
12 #include "InputGlobal.h"
13 #include "MarkLive.h"
14 #include "SymbolTable.h"
15 #include "Writer.h"
16 #include "lld/Common/Args.h"
17 #include "lld/Common/ErrorHandler.h"
18 #include "lld/Common/Memory.h"
19 #include "lld/Common/Strings.h"
20 #include "lld/Common/Threads.h"
21 #include "lld/Common/Version.h"
22 #include "llvm/ADT/Twine.h"
23 #include "llvm/Object/Wasm.h"
24 #include "llvm/Option/Arg.h"
25 #include "llvm/Option/ArgList.h"
26 #include "llvm/Support/CommandLine.h"
27 #include "llvm/Support/Path.h"
28 #include "llvm/Support/Process.h"
29 #include "llvm/Support/TargetSelect.h"
30 
31 #define DEBUG_TYPE "lld"
32 
33 using namespace llvm;
34 using namespace llvm::object;
35 using namespace llvm::sys;
36 using namespace llvm::wasm;
37 
38 using namespace lld;
39 using namespace lld::wasm;
40 
41 Configuration *lld::wasm::Config;
42 
43 namespace {
44 
45 // Create enum with OPT_xxx values for each option in Options.td
46 enum {
47   OPT_INVALID = 0,
48 #define OPTION(_1, _2, ID, _4, _5, _6, _7, _8, _9, _10, _11, _12) OPT_##ID,
49 #include "Options.inc"
50 #undef OPTION
51 };
52 
53 // This function is called on startup. We need this for LTO since
54 // LTO calls LLVM functions to compile bitcode files to native code.
55 // Technically this can be delayed until we read bitcode files, but
56 // we don't bother to do lazily because the initialization is fast.
57 static void initLLVM() {
58   InitializeAllTargets();
59   InitializeAllTargetMCs();
60   InitializeAllAsmPrinters();
61   InitializeAllAsmParsers();
62 }
63 
64 class LinkerDriver {
65 public:
66   void link(ArrayRef<const char *> ArgsArr);
67 
68 private:
69   void createFiles(opt::InputArgList &Args);
70   void addFile(StringRef Path);
71   void addLibrary(StringRef Name);
72 
73   // True if we are in --whole-archive and --no-whole-archive.
74   bool InWholeArchive = false;
75 
76   std::vector<InputFile *> Files;
77 };
78 } // anonymous namespace
79 
80 bool lld::wasm::link(ArrayRef<const char *> Args, bool CanExitEarly,
81                      raw_ostream &Error) {
82   errorHandler().LogName = args::getFilenameWithoutExe(Args[0]);
83   errorHandler().ErrorOS = &Error;
84   errorHandler().ColorDiagnostics = Error.has_colors();
85   errorHandler().ErrorLimitExceededMsg =
86       "too many errors emitted, stopping now (use "
87       "-error-limit=0 to see all errors)";
88 
89   Config = make<Configuration>();
90   Symtab = make<SymbolTable>();
91 
92   initLLVM();
93   LinkerDriver().link(Args);
94 
95   // Exit immediately if we don't need to return to the caller.
96   // This saves time because the overhead of calling destructors
97   // for all globally-allocated objects is not negligible.
98   if (CanExitEarly)
99     exitLld(errorCount() ? 1 : 0);
100 
101   freeArena();
102   return !errorCount();
103 }
104 
105 // Create prefix string literals used in Options.td
106 #define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE;
107 #include "Options.inc"
108 #undef PREFIX
109 
110 // Create table mapping all options defined in Options.td
111 static const opt::OptTable::Info OptInfo[] = {
112 #define OPTION(X1, X2, ID, KIND, GROUP, ALIAS, X7, X8, X9, X10, X11, X12)      \
113   {X1, X2, X10,         X11,         OPT_##ID, opt::Option::KIND##Class,       \
114    X9, X8, OPT_##GROUP, OPT_##ALIAS, X7,       X12},
115 #include "Options.inc"
116 #undef OPTION
117 };
118 
119 namespace {
120 class WasmOptTable : public llvm::opt::OptTable {
121 public:
122   WasmOptTable() : OptTable(OptInfo) {}
123   opt::InputArgList parse(ArrayRef<const char *> Argv);
124 };
125 } // namespace
126 
127 // Set color diagnostics according to -color-diagnostics={auto,always,never}
128 // or -no-color-diagnostics flags.
129 static void handleColorDiagnostics(opt::InputArgList &Args) {
130   auto *Arg = Args.getLastArg(OPT_color_diagnostics, OPT_color_diagnostics_eq,
131                               OPT_no_color_diagnostics);
132   if (!Arg)
133     return;
134   if (Arg->getOption().getID() == OPT_color_diagnostics) {
135     errorHandler().ColorDiagnostics = true;
136   } else if (Arg->getOption().getID() == OPT_no_color_diagnostics) {
137     errorHandler().ColorDiagnostics = false;
138   } else {
139     StringRef S = Arg->getValue();
140     if (S == "always")
141       errorHandler().ColorDiagnostics = true;
142     else if (S == "never")
143       errorHandler().ColorDiagnostics = false;
144     else if (S != "auto")
145       error("unknown option: --color-diagnostics=" + S);
146   }
147 }
148 
149 // Find a file by concatenating given paths.
150 static Optional<std::string> findFile(StringRef Path1, const Twine &Path2) {
151   SmallString<128> S;
152   path::append(S, Path1, Path2);
153   if (fs::exists(S))
154     return S.str().str();
155   return None;
156 }
157 
158 opt::InputArgList WasmOptTable::parse(ArrayRef<const char *> Argv) {
159   SmallVector<const char *, 256> Vec(Argv.data(), Argv.data() + Argv.size());
160 
161   unsigned MissingIndex;
162   unsigned MissingCount;
163 
164   // Expand response files (arguments in the form of @<filename>)
165   cl::ExpandResponseFiles(Saver, cl::TokenizeGNUCommandLine, Vec);
166 
167   opt::InputArgList Args = this->ParseArgs(Vec, MissingIndex, MissingCount);
168 
169   handleColorDiagnostics(Args);
170   for (auto *Arg : Args.filtered(OPT_UNKNOWN))
171     error("unknown argument: " + Arg->getSpelling());
172   return Args;
173 }
174 
175 // Currently we allow a ".imports" to live alongside a library. This can
176 // be used to specify a list of symbols which can be undefined at link
177 // time (imported from the environment.  For example libc.a include an
178 // import file that lists the syscall functions it relies on at runtime.
179 // In the long run this information would be better stored as a symbol
180 // attribute/flag in the object file itself.
181 // See: https://github.com/WebAssembly/tool-conventions/issues/35
182 static void readImportFile(StringRef Filename) {
183   if (Optional<MemoryBufferRef> Buf = readFile(Filename))
184     for (StringRef Sym : args::getLines(*Buf))
185       Config->AllowUndefinedSymbols.insert(Sym);
186 }
187 
188 // Returns slices of MB by parsing MB as an archive file.
189 // Each slice consists of a member file in the archive.
190 std::vector<MemoryBufferRef> static getArchiveMembers(MemoryBufferRef MB) {
191   std::unique_ptr<Archive> File =
192       CHECK(Archive::create(MB),
193             MB.getBufferIdentifier() + ": failed to parse archive");
194 
195   std::vector<MemoryBufferRef> V;
196   Error Err = Error::success();
197   for (const ErrorOr<Archive::Child> &COrErr : File->children(Err)) {
198     Archive::Child C =
199         CHECK(COrErr, MB.getBufferIdentifier() +
200                           ": could not get the child of the archive");
201     MemoryBufferRef MBRef =
202         CHECK(C.getMemoryBufferRef(),
203               MB.getBufferIdentifier() +
204                   ": could not get the buffer for a child of the archive");
205     V.push_back(MBRef);
206   }
207   if (Err)
208     fatal(MB.getBufferIdentifier() +
209           ": Archive::children failed: " + toString(std::move(Err)));
210 
211   // Take ownership of memory buffers created for members of thin archives.
212   for (std::unique_ptr<MemoryBuffer> &MB : File->takeThinBuffers())
213     make<std::unique_ptr<MemoryBuffer>>(std::move(MB));
214 
215   return V;
216 }
217 
218 void LinkerDriver::addFile(StringRef Path) {
219   Optional<MemoryBufferRef> Buffer = readFile(Path);
220   if (!Buffer.hasValue())
221     return;
222   MemoryBufferRef MBRef = *Buffer;
223 
224   switch (identify_magic(MBRef.getBuffer())) {
225   case file_magic::archive: {
226     // Handle -whole-archive.
227     if (InWholeArchive) {
228       for (MemoryBufferRef &M : getArchiveMembers(MBRef))
229         Files.push_back(createObjectFile(M));
230       return;
231     }
232 
233     SmallString<128> ImportFile = Path;
234     path::replace_extension(ImportFile, ".imports");
235     if (fs::exists(ImportFile))
236       readImportFile(ImportFile.str());
237 
238     Files.push_back(make<ArchiveFile>(MBRef));
239     return;
240   }
241   case file_magic::bitcode:
242   case file_magic::wasm_object:
243     Files.push_back(createObjectFile(MBRef));
244     break;
245   default:
246     error("unknown file type: " + MBRef.getBufferIdentifier());
247   }
248 }
249 
250 // Add a given library by searching it from input search paths.
251 void LinkerDriver::addLibrary(StringRef Name) {
252   for (StringRef Dir : Config->SearchPaths) {
253     if (Optional<std::string> S = findFile(Dir, "lib" + Name + ".a")) {
254       addFile(*S);
255       return;
256     }
257   }
258 
259   error("unable to find library -l" + Name);
260 }
261 
262 void LinkerDriver::createFiles(opt::InputArgList &Args) {
263   for (auto *Arg : Args) {
264     switch (Arg->getOption().getUnaliasedOption().getID()) {
265     case OPT_l:
266       addLibrary(Arg->getValue());
267       break;
268     case OPT_INPUT:
269       addFile(Arg->getValue());
270       break;
271     case OPT_whole_archive:
272       InWholeArchive = true;
273       break;
274     case OPT_no_whole_archive:
275       InWholeArchive = false;
276       break;
277     }
278   }
279 }
280 
281 static StringRef getEntry(opt::InputArgList &Args, StringRef Default) {
282   auto *Arg = Args.getLastArg(OPT_entry, OPT_no_entry);
283   if (!Arg)
284     return Default;
285   if (Arg->getOption().getID() == OPT_no_entry)
286     return "";
287   return Arg->getValue();
288 }
289 
290 // Some Config members do not directly correspond to any particular
291 // command line options, but computed based on other Config values.
292 // This function initialize such members. See Config.h for the details
293 // of these values.
294 static void setConfigs(opt::InputArgList &Args) {
295   Config->AllowUndefined = Args.hasArg(OPT_allow_undefined);
296   Config->CheckFeatures =
297       Args.hasFlag(OPT_check_features, OPT_no_check_features, true);
298   Config->CompressRelocations = Args.hasArg(OPT_compress_relocations);
299   Config->Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, true);
300   Config->DisableVerify = Args.hasArg(OPT_disable_verify);
301   Config->Entry = getEntry(Args, Args.hasArg(OPT_relocatable) ? "" : "_start");
302   Config->ExportAll = Args.hasArg(OPT_export_all);
303   Config->ExportDynamic = Args.hasFlag(OPT_export_dynamic,
304       OPT_no_export_dynamic, false);
305   Config->ExportTable = Args.hasArg(OPT_export_table);
306   errorHandler().FatalWarnings =
307       Args.hasFlag(OPT_fatal_warnings, OPT_no_fatal_warnings, false);
308   Config->ImportMemory = Args.hasArg(OPT_import_memory);
309   Config->SharedMemory = Args.hasArg(OPT_shared_memory);
310   Config->ImportTable = Args.hasArg(OPT_import_table);
311   Config->LTOO = args::getInteger(Args, OPT_lto_O, 2);
312   Config->LTOPartitions = args::getInteger(Args, OPT_lto_partitions, 1);
313   Config->Optimize = args::getInteger(Args, OPT_O, 0);
314   Config->OutputFile = Args.getLastArgValue(OPT_o);
315   Config->Relocatable = Args.hasArg(OPT_relocatable);
316   Config->GcSections =
317       Args.hasFlag(OPT_gc_sections, OPT_no_gc_sections, !Config->Relocatable);
318   Config->MergeDataSegments =
319       Args.hasFlag(OPT_merge_data_segments, OPT_no_merge_data_segments,
320                    !Config->Relocatable);
321   Config->Pie = Args.hasFlag(OPT_pie, OPT_no_pie, false);
322   Config->PrintGcSections =
323       Args.hasFlag(OPT_print_gc_sections, OPT_no_print_gc_sections, false);
324   Config->SaveTemps = Args.hasArg(OPT_save_temps);
325   Config->SearchPaths = args::getStrings(Args, OPT_L);
326   Config->Shared = Args.hasArg(OPT_shared);
327   Config->StripAll = Args.hasArg(OPT_strip_all);
328   Config->StripDebug = Args.hasArg(OPT_strip_debug);
329   Config->StackFirst = Args.hasArg(OPT_stack_first);
330   Config->Trace = Args.hasArg(OPT_trace);
331   Config->ThinLTOCacheDir = Args.getLastArgValue(OPT_thinlto_cache_dir);
332   Config->ThinLTOCachePolicy = CHECK(
333       parseCachePruningPolicy(Args.getLastArgValue(OPT_thinlto_cache_policy)),
334       "--thinlto-cache-policy: invalid cache policy");
335   Config->ThinLTOJobs = args::getInteger(Args, OPT_thinlto_jobs, -1u);
336   errorHandler().Verbose = Args.hasArg(OPT_verbose);
337   LLVM_DEBUG(errorHandler().Verbose = true);
338   ThreadsEnabled = Args.hasFlag(OPT_threads, OPT_no_threads, true);
339 
340   Config->InitialMemory = args::getInteger(Args, OPT_initial_memory, 0);
341   Config->GlobalBase = args::getInteger(Args, OPT_global_base, 1024);
342   Config->MaxMemory = args::getInteger(Args, OPT_max_memory, 0);
343   Config->ZStackSize =
344       args::getZOptionValue(Args, OPT_z, "stack-size", WasmPageSize);
345 
346   if (auto *Arg = Args.getLastArg(OPT_features)) {
347     Config->Features =
348         llvm::Optional<std::vector<std::string>>(std::vector<std::string>());
349     for (StringRef S : Arg->getValues())
350       Config->Features->push_back(S);
351   }
352 }
353 
354 // Some command line options or some combinations of them are not allowed.
355 // This function checks for such errors.
356 static void checkOptions(opt::InputArgList &Args) {
357   if (!Config->StripDebug && !Config->StripAll && Config->CompressRelocations)
358     error("--compress-relocations is incompatible with output debug"
359           " information. Please pass --strip-debug or --strip-all");
360 
361   if (Config->LTOO > 3)
362     error("invalid optimization level for LTO: " + Twine(Config->LTOO));
363   if (Config->LTOPartitions == 0)
364     error("--lto-partitions: number of threads must be > 0");
365   if (Config->ThinLTOJobs == 0)
366     error("--thinlto-jobs: number of threads must be > 0");
367 
368   if (Config->Pie && Config->Shared)
369     error("-shared and -pie may not be used together");
370 
371   if (Config->OutputFile.empty())
372     error("no output file specified");
373 
374   if (Config->ImportTable && Config->ExportTable)
375     error("--import-table and --export-table may not be used together");
376 
377   if (Config->Relocatable) {
378     if (!Config->Entry.empty())
379       error("entry point specified for relocatable output file");
380     if (Config->GcSections)
381       error("-r and --gc-sections may not be used together");
382     if (Config->CompressRelocations)
383       error("-r -and --compress-relocations may not be used together");
384     if (Args.hasArg(OPT_undefined))
385       error("-r -and --undefined may not be used together");
386     if (Config->Pie)
387       error("-r and -pie may not be used together");
388   }
389 }
390 
391 // Force Sym to be entered in the output. Used for -u or equivalent.
392 static Symbol *handleUndefined(StringRef Name) {
393   Symbol *Sym = Symtab->find(Name);
394   if (!Sym)
395     return nullptr;
396 
397   // Since symbol S may not be used inside the program, LTO may
398   // eliminate it. Mark the symbol as "used" to prevent it.
399   Sym->IsUsedInRegularObj = true;
400 
401   if (auto *LazySym = dyn_cast<LazySymbol>(Sym))
402     LazySym->fetch();
403 
404   return Sym;
405 }
406 
407 static UndefinedGlobal *
408 createUndefinedGlobal(StringRef Name, llvm::wasm::WasmGlobalType *Type) {
409   auto *Sym =
410       cast<UndefinedGlobal>(Symtab->addUndefinedGlobal(Name, Name,
411                                                        DefaultModule, 0,
412                                                        nullptr, Type));
413   Config->AllowUndefinedSymbols.insert(Sym->getName());
414   Sym->IsUsedInRegularObj = true;
415   return Sym;
416 }
417 
418 // Create ABI-defined synthetic symbols
419 static void createSyntheticSymbols() {
420   static WasmSignature NullSignature = {{}, {}};
421   static llvm::wasm::WasmGlobalType GlobalTypeI32 = {WASM_TYPE_I32, false};
422   static llvm::wasm::WasmGlobalType MutableGlobalTypeI32 = {WASM_TYPE_I32,
423                                                             true};
424 
425   if (!Config->Relocatable)
426     WasmSym::CallCtors = Symtab->addSyntheticFunction(
427         "__wasm_call_ctors", WASM_SYMBOL_VISIBILITY_HIDDEN,
428         make<SyntheticFunction>(NullSignature, "__wasm_call_ctors"));
429 
430   // The __stack_pointer is imported in the shared library case, and exported
431   // in the non-shared (executable) case.
432   if (Config->Shared) {
433     WasmSym::StackPointer =
434         createUndefinedGlobal("__stack_pointer", &MutableGlobalTypeI32);
435   } else {
436     llvm::wasm::WasmGlobal Global;
437     Global.Type = {WASM_TYPE_I32, true};
438     Global.InitExpr.Value.Int32 = 0;
439     Global.InitExpr.Opcode = WASM_OPCODE_I32_CONST;
440     Global.SymbolName = "__stack_pointer";
441     auto *StackPointer = make<InputGlobal>(Global, nullptr);
442     StackPointer->Live = true;
443     // For non-PIC code
444     // TODO(sbc): Remove WASM_SYMBOL_VISIBILITY_HIDDEN when the mutable global
445     // spec proposal is implemented in all major browsers.
446     // See: https://github.com/WebAssembly/mutable-global
447     WasmSym::StackPointer = Symtab->addSyntheticGlobal(
448         "__stack_pointer", WASM_SYMBOL_VISIBILITY_HIDDEN, StackPointer);
449     WasmSym::HeapBase = Symtab->addSyntheticDataSymbol("__heap_base", 0);
450     WasmSym::DataEnd = Symtab->addSyntheticDataSymbol("__data_end", 0);
451 
452     // These two synthetic symbols exist purely for the embedder so we always
453     // want to export them.
454     WasmSym::HeapBase->ForceExport = true;
455     WasmSym::DataEnd->ForceExport = true;
456   }
457 
458   if (Config->Pic) {
459     // For PIC code, we import two global variables (__memory_base and
460     // __table_base) from the environment and use these as the offset at
461     // which to load our static data and function table.
462     // See:
463     // https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md
464     WasmSym::MemoryBase =
465         createUndefinedGlobal("__memory_base", &GlobalTypeI32);
466     WasmSym::TableBase = createUndefinedGlobal("__table_base", &GlobalTypeI32);
467     WasmSym::MemoryBase->markLive();
468     WasmSym::TableBase->markLive();
469   }
470 
471   WasmSym::DsoHandle = Symtab->addSyntheticDataSymbol(
472       "__dso_handle", WASM_SYMBOL_VISIBILITY_HIDDEN);
473 }
474 
475 void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
476   WasmOptTable Parser;
477   opt::InputArgList Args = Parser.parse(ArgsArr.slice(1));
478 
479   // Handle --help
480   if (Args.hasArg(OPT_help)) {
481     Parser.PrintHelp(outs(),
482                      (std::string(ArgsArr[0]) + " [options] file...").c_str(),
483                      "LLVM Linker", false);
484     return;
485   }
486 
487   // Handle --version
488   if (Args.hasArg(OPT_version) || Args.hasArg(OPT_v)) {
489     outs() << getLLDVersion() << "\n";
490     return;
491   }
492 
493   // Parse and evaluate -mllvm options.
494   std::vector<const char *> V;
495   V.push_back("wasm-ld (LLVM option parsing)");
496   for (auto *Arg : Args.filtered(OPT_mllvm))
497     V.push_back(Arg->getValue());
498   cl::ParseCommandLineOptions(V.size(), V.data());
499 
500   errorHandler().ErrorLimit = args::getInteger(Args, OPT_error_limit, 20);
501 
502   setConfigs(Args);
503   checkOptions(Args);
504 
505   if (auto *Arg = Args.getLastArg(OPT_allow_undefined_file))
506     readImportFile(Arg->getValue());
507 
508   if (!Args.hasArg(OPT_INPUT)) {
509     error("no input files");
510     return;
511   }
512 
513   Config->Pic = Config->Pie || Config->Shared;
514 
515   if (Config->Pic) {
516     if (Config->ExportTable)
517       error("-shared/-pie is incompatible with --export-table");
518     Config->ImportTable = true;
519   }
520 
521   if (Config->Shared) {
522     Config->ExportDynamic = true;
523     Config->AllowUndefined = true;
524   }
525 
526   // Handle --trace-symbol.
527   for (auto *Arg : Args.filtered(OPT_trace_symbol))
528     Symtab->trace(Arg->getValue());
529 
530   if (!Config->Relocatable)
531     createSyntheticSymbols();
532 
533   createFiles(Args);
534   if (errorCount())
535     return;
536 
537   // Add all files to the symbol table. This will add almost all
538   // symbols that we need to the symbol table.
539   for (InputFile *F : Files)
540     Symtab->addFile(F);
541   if (errorCount())
542     return;
543 
544   // Handle the `--undefined <sym>` options.
545   for (auto *Arg : Args.filtered(OPT_undefined))
546     handleUndefined(Arg->getValue());
547 
548   Symbol *EntrySym = nullptr;
549   if (!Config->Relocatable) {
550     if (!Config->Shared && !Config->Entry.empty()) {
551       EntrySym = handleUndefined(Config->Entry);
552       if (EntrySym && EntrySym->isDefined())
553         EntrySym->ForceExport = true;
554       else
555         error("entry symbol not defined (pass --no-entry to supress): " +
556               Config->Entry);
557     }
558   }
559 
560   if (errorCount())
561     return;
562 
563   // Handle the `--export <sym>` options
564   // This works like --undefined but also exports the symbol if its found
565   for (auto *Arg : Args.filtered(OPT_export))
566     handleUndefined(Arg->getValue());
567 
568   // Do link-time optimization if given files are LLVM bitcode files.
569   // This compiles bitcode files into real object files.
570   Symtab->addCombinedLTOObject();
571   if (errorCount())
572     return;
573 
574   // Resolve any variant symbols that were created due to signature
575   // mismatchs.
576   Symtab->handleSymbolVariants();
577   if (errorCount())
578     return;
579 
580   for (auto *Arg : Args.filtered(OPT_export)) {
581     Symbol *Sym = Symtab->find(Arg->getValue());
582     if (Sym && Sym->isDefined())
583       Sym->ForceExport = true;
584     else if (!Config->AllowUndefined)
585       error(Twine("symbol exported via --export not found: ") +
586             Arg->getValue());
587   }
588 
589   if (!Config->Relocatable) {
590     // Add synthetic dummies for weak undefined functions.  Must happen
591     // after LTO otherwise functions may not yet have signatures.
592     Symtab->handleWeakUndefines();
593 
594     // Make sure we have resolved all symbols.
595     if (!Config->AllowUndefined)
596       Symtab->reportRemainingUndefines();
597   }
598 
599   if (EntrySym)
600     EntrySym->setHidden(false);
601 
602   if (errorCount())
603     return;
604 
605   // Do size optimizations: garbage collection
606   markLive();
607 
608   // Write the result to the file.
609   writeResult();
610 }
611