llc.cpp revision 353358
1//===-- llc.cpp - Implement the LLVM Native Code Generator ----------------===//
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// This is the llc code generator driver. It provides a convenient
10// command-line interface for generating native assembly-language code
11// or C code, given LLVM bitcode.
12//
13//===----------------------------------------------------------------------===//
14
15#include "llvm/ADT/STLExtras.h"
16#include "llvm/ADT/Triple.h"
17#include "llvm/Analysis/TargetLibraryInfo.h"
18#include "llvm/CodeGen/CommandFlags.inc"
19#include "llvm/CodeGen/LinkAllAsmWriterComponents.h"
20#include "llvm/CodeGen/LinkAllCodegenComponents.h"
21#include "llvm/CodeGen/MIRParser/MIRParser.h"
22#include "llvm/CodeGen/MachineFunctionPass.h"
23#include "llvm/CodeGen/MachineModuleInfo.h"
24#include "llvm/CodeGen/TargetPassConfig.h"
25#include "llvm/CodeGen/TargetSubtargetInfo.h"
26#include "llvm/IR/AutoUpgrade.h"
27#include "llvm/IR/DataLayout.h"
28#include "llvm/IR/DiagnosticInfo.h"
29#include "llvm/IR/DiagnosticPrinter.h"
30#include "llvm/IR/IRPrintingPasses.h"
31#include "llvm/IR/LLVMContext.h"
32#include "llvm/IR/LegacyPassManager.h"
33#include "llvm/IR/Module.h"
34#include "llvm/IR/RemarkStreamer.h"
35#include "llvm/IR/Verifier.h"
36#include "llvm/IRReader/IRReader.h"
37#include "llvm/MC/SubtargetFeature.h"
38#include "llvm/Pass.h"
39#include "llvm/Support/CommandLine.h"
40#include "llvm/Support/Debug.h"
41#include "llvm/Support/FileSystem.h"
42#include "llvm/Support/FormattedStream.h"
43#include "llvm/Support/Host.h"
44#include "llvm/Support/InitLLVM.h"
45#include "llvm/Support/ManagedStatic.h"
46#include "llvm/Support/PluginLoader.h"
47#include "llvm/Support/SourceMgr.h"
48#include "llvm/Support/TargetRegistry.h"
49#include "llvm/Support/TargetSelect.h"
50#include "llvm/Support/ToolOutputFile.h"
51#include "llvm/Support/WithColor.h"
52#include "llvm/Target/TargetMachine.h"
53#include "llvm/Transforms/Utils/Cloning.h"
54#include <memory>
55using namespace llvm;
56
57// General options for llc.  Other pass-specific options are specified
58// within the corresponding llc passes, and target-specific options
59// and back-end code generation options are specified with the target machine.
60//
61static cl::opt<std::string>
62InputFilename(cl::Positional, cl::desc("<input bitcode>"), cl::init("-"));
63
64static cl::opt<std::string>
65InputLanguage("x", cl::desc("Input language ('ir' or 'mir')"));
66
67static cl::opt<std::string>
68OutputFilename("o", cl::desc("Output filename"), cl::value_desc("filename"));
69
70static cl::opt<std::string>
71    SplitDwarfOutputFile("split-dwarf-output",
72                         cl::desc(".dwo output filename"),
73                         cl::value_desc("filename"));
74
75static cl::opt<unsigned>
76TimeCompilations("time-compilations", cl::Hidden, cl::init(1u),
77                 cl::value_desc("N"),
78                 cl::desc("Repeat compilation N times for timing"));
79
80static cl::opt<bool>
81NoIntegratedAssembler("no-integrated-as", cl::Hidden,
82                      cl::desc("Disable integrated assembler"));
83
84static cl::opt<bool>
85    PreserveComments("preserve-as-comments", cl::Hidden,
86                     cl::desc("Preserve Comments in outputted assembly"),
87                     cl::init(true));
88
89// Determine optimization level.
90static cl::opt<char>
91OptLevel("O",
92         cl::desc("Optimization level. [-O0, -O1, -O2, or -O3] "
93                  "(default = '-O2')"),
94         cl::Prefix,
95         cl::ZeroOrMore,
96         cl::init(' '));
97
98static cl::opt<std::string>
99TargetTriple("mtriple", cl::desc("Override target triple for module"));
100
101static cl::opt<std::string> SplitDwarfFile(
102    "split-dwarf-file",
103    cl::desc(
104        "Specify the name of the .dwo file to encode in the DWARF output"));
105
106static cl::opt<bool> NoVerify("disable-verify", cl::Hidden,
107                              cl::desc("Do not verify input module"));
108
109static cl::opt<bool> DisableSimplifyLibCalls("disable-simplify-libcalls",
110                                             cl::desc("Disable simplify-libcalls"));
111
112static cl::opt<bool> ShowMCEncoding("show-mc-encoding", cl::Hidden,
113                                    cl::desc("Show encoding in .s output"));
114
115static cl::opt<bool> EnableDwarfDirectory(
116    "enable-dwarf-directory", cl::Hidden,
117    cl::desc("Use .file directives with an explicit directory."));
118
119static cl::opt<bool> AsmVerbose("asm-verbose",
120                                cl::desc("Add comments to directives."),
121                                cl::init(true));
122
123static cl::opt<bool>
124    CompileTwice("compile-twice", cl::Hidden,
125                 cl::desc("Run everything twice, re-using the same pass "
126                          "manager and verify the result is the same."),
127                 cl::init(false));
128
129static cl::opt<bool> DiscardValueNames(
130    "discard-value-names",
131    cl::desc("Discard names from Value (other than GlobalValue)."),
132    cl::init(false), cl::Hidden);
133
134static cl::list<std::string> IncludeDirs("I", cl::desc("include search path"));
135
136static cl::opt<bool> RemarksWithHotness(
137    "pass-remarks-with-hotness",
138    cl::desc("With PGO, include profile count in optimization remarks"),
139    cl::Hidden);
140
141static cl::opt<unsigned>
142    RemarksHotnessThreshold("pass-remarks-hotness-threshold",
143                            cl::desc("Minimum profile count required for "
144                                     "an optimization remark to be output"),
145                            cl::Hidden);
146
147static cl::opt<std::string>
148    RemarksFilename("pass-remarks-output",
149                    cl::desc("Output filename for pass remarks"),
150                    cl::value_desc("filename"));
151
152static cl::opt<std::string>
153    RemarksPasses("pass-remarks-filter",
154                  cl::desc("Only record optimization remarks from passes whose "
155                           "names match the given regular expression"),
156                  cl::value_desc("regex"));
157
158static cl::opt<std::string> RemarksFormat(
159    "pass-remarks-format",
160    cl::desc("The format used for serializing remarks (default: YAML)"),
161    cl::value_desc("format"), cl::init("yaml"));
162
163namespace {
164static ManagedStatic<std::vector<std::string>> RunPassNames;
165
166struct RunPassOption {
167  void operator=(const std::string &Val) const {
168    if (Val.empty())
169      return;
170    SmallVector<StringRef, 8> PassNames;
171    StringRef(Val).split(PassNames, ',', -1, false);
172    for (auto PassName : PassNames)
173      RunPassNames->push_back(PassName);
174  }
175};
176}
177
178static RunPassOption RunPassOpt;
179
180static cl::opt<RunPassOption, true, cl::parser<std::string>> RunPass(
181    "run-pass",
182    cl::desc("Run compiler only for specified passes (comma separated list)"),
183    cl::value_desc("pass-name"), cl::ZeroOrMore, cl::location(RunPassOpt));
184
185static int compileModule(char **, LLVMContext &);
186
187static std::unique_ptr<ToolOutputFile> GetOutputStream(const char *TargetName,
188                                                       Triple::OSType OS,
189                                                       const char *ProgName) {
190  // If we don't yet have an output filename, make one.
191  if (OutputFilename.empty()) {
192    if (InputFilename == "-")
193      OutputFilename = "-";
194    else {
195      // If InputFilename ends in .bc or .ll, remove it.
196      StringRef IFN = InputFilename;
197      if (IFN.endswith(".bc") || IFN.endswith(".ll"))
198        OutputFilename = IFN.drop_back(3);
199      else if (IFN.endswith(".mir"))
200        OutputFilename = IFN.drop_back(4);
201      else
202        OutputFilename = IFN;
203
204      switch (FileType) {
205      case TargetMachine::CGFT_AssemblyFile:
206        if (TargetName[0] == 'c') {
207          if (TargetName[1] == 0)
208            OutputFilename += ".cbe.c";
209          else if (TargetName[1] == 'p' && TargetName[2] == 'p')
210            OutputFilename += ".cpp";
211          else
212            OutputFilename += ".s";
213        } else
214          OutputFilename += ".s";
215        break;
216      case TargetMachine::CGFT_ObjectFile:
217        if (OS == Triple::Win32)
218          OutputFilename += ".obj";
219        else
220          OutputFilename += ".o";
221        break;
222      case TargetMachine::CGFT_Null:
223        OutputFilename += ".null";
224        break;
225      }
226    }
227  }
228
229  // Decide if we need "binary" output.
230  bool Binary = false;
231  switch (FileType) {
232  case TargetMachine::CGFT_AssemblyFile:
233    break;
234  case TargetMachine::CGFT_ObjectFile:
235  case TargetMachine::CGFT_Null:
236    Binary = true;
237    break;
238  }
239
240  // Open the file.
241  std::error_code EC;
242  sys::fs::OpenFlags OpenFlags = sys::fs::F_None;
243  if (!Binary)
244    OpenFlags |= sys::fs::F_Text;
245  auto FDOut = llvm::make_unique<ToolOutputFile>(OutputFilename, EC, OpenFlags);
246  if (EC) {
247    WithColor::error() << EC.message() << '\n';
248    return nullptr;
249  }
250
251  return FDOut;
252}
253
254struct LLCDiagnosticHandler : public DiagnosticHandler {
255  bool *HasError;
256  LLCDiagnosticHandler(bool *HasErrorPtr) : HasError(HasErrorPtr) {}
257  bool handleDiagnostics(const DiagnosticInfo &DI) override {
258    if (DI.getSeverity() == DS_Error)
259      *HasError = true;
260
261    if (auto *Remark = dyn_cast<DiagnosticInfoOptimizationBase>(&DI))
262      if (!Remark->isEnabled())
263        return true;
264
265    DiagnosticPrinterRawOStream DP(errs());
266    errs() << LLVMContext::getDiagnosticMessagePrefix(DI.getSeverity()) << ": ";
267    DI.print(DP);
268    errs() << "\n";
269    return true;
270  }
271};
272
273static void InlineAsmDiagHandler(const SMDiagnostic &SMD, void *Context,
274                                 unsigned LocCookie) {
275  bool *HasError = static_cast<bool *>(Context);
276  if (SMD.getKind() == SourceMgr::DK_Error)
277    *HasError = true;
278
279  SMD.print(nullptr, errs());
280
281  // For testing purposes, we print the LocCookie here.
282  if (LocCookie)
283    WithColor::note() << "!srcloc = " << LocCookie << "\n";
284}
285
286// main - Entry point for the llc compiler.
287//
288int main(int argc, char **argv) {
289  InitLLVM X(argc, argv);
290
291  // Enable debug stream buffering.
292  EnableDebugBuffering = true;
293
294  LLVMContext Context;
295
296  // Initialize targets first, so that --version shows registered targets.
297  InitializeAllTargets();
298  InitializeAllTargetMCs();
299  InitializeAllAsmPrinters();
300  InitializeAllAsmParsers();
301
302  // Initialize codegen and IR passes used by llc so that the -print-after,
303  // -print-before, and -stop-after options work.
304  PassRegistry *Registry = PassRegistry::getPassRegistry();
305  initializeCore(*Registry);
306  initializeCodeGen(*Registry);
307  initializeLoopStrengthReducePass(*Registry);
308  initializeLowerIntrinsicsPass(*Registry);
309  initializeEntryExitInstrumenterPass(*Registry);
310  initializePostInlineEntryExitInstrumenterPass(*Registry);
311  initializeUnreachableBlockElimLegacyPassPass(*Registry);
312  initializeConstantHoistingLegacyPassPass(*Registry);
313  initializeScalarOpts(*Registry);
314  initializeVectorization(*Registry);
315  initializeScalarizeMaskedMemIntrinPass(*Registry);
316  initializeExpandReductionsPass(*Registry);
317  initializeHardwareLoopsPass(*Registry);
318
319  // Initialize debugging passes.
320  initializeScavengerTestPass(*Registry);
321
322  // Register the target printer for --version.
323  cl::AddExtraVersionPrinter(TargetRegistry::printRegisteredTargetsForVersion);
324
325  cl::ParseCommandLineOptions(argc, argv, "llvm system compiler\n");
326
327  Context.setDiscardValueNames(DiscardValueNames);
328
329  // Set a diagnostic handler that doesn't exit on the first error
330  bool HasError = false;
331  Context.setDiagnosticHandler(
332      llvm::make_unique<LLCDiagnosticHandler>(&HasError));
333  Context.setInlineAsmDiagnosticHandler(InlineAsmDiagHandler, &HasError);
334
335  Expected<std::unique_ptr<ToolOutputFile>> RemarksFileOrErr =
336      setupOptimizationRemarks(Context, RemarksFilename, RemarksPasses,
337                               RemarksFormat, RemarksWithHotness,
338                               RemarksHotnessThreshold);
339  if (Error E = RemarksFileOrErr.takeError()) {
340    WithColor::error(errs(), argv[0]) << toString(std::move(E)) << '\n';
341    return 1;
342  }
343  std::unique_ptr<ToolOutputFile> RemarksFile = std::move(*RemarksFileOrErr);
344
345  if (InputLanguage != "" && InputLanguage != "ir" &&
346      InputLanguage != "mir") {
347    WithColor::error(errs(), argv[0])
348        << "input language must be '', 'IR' or 'MIR'\n";
349    return 1;
350  }
351
352  // Compile the module TimeCompilations times to give better compile time
353  // metrics.
354  for (unsigned I = TimeCompilations; I; --I)
355    if (int RetVal = compileModule(argv, Context))
356      return RetVal;
357
358  if (RemarksFile)
359    RemarksFile->keep();
360  return 0;
361}
362
363static bool addPass(PassManagerBase &PM, const char *argv0,
364                    StringRef PassName, TargetPassConfig &TPC) {
365  if (PassName == "none")
366    return false;
367
368  const PassRegistry *PR = PassRegistry::getPassRegistry();
369  const PassInfo *PI = PR->getPassInfo(PassName);
370  if (!PI) {
371    WithColor::error(errs(), argv0)
372        << "run-pass " << PassName << " is not registered.\n";
373    return true;
374  }
375
376  Pass *P;
377  if (PI->getNormalCtor())
378    P = PI->getNormalCtor()();
379  else {
380    WithColor::error(errs(), argv0)
381        << "cannot create pass: " << PI->getPassName() << "\n";
382    return true;
383  }
384  std::string Banner = std::string("After ") + std::string(P->getPassName());
385  PM.add(P);
386  TPC.printAndVerify(Banner);
387
388  return false;
389}
390
391static int compileModule(char **argv, LLVMContext &Context) {
392  // Load the module to be compiled...
393  SMDiagnostic Err;
394  std::unique_ptr<Module> M;
395  std::unique_ptr<MIRParser> MIR;
396  Triple TheTriple;
397
398  bool SkipModule = MCPU == "help" ||
399                    (!MAttrs.empty() && MAttrs.front() == "help");
400
401  // If user just wants to list available options, skip module loading
402  if (!SkipModule) {
403    if (InputLanguage == "mir" ||
404        (InputLanguage == "" && StringRef(InputFilename).endswith(".mir"))) {
405      MIR = createMIRParserFromFile(InputFilename, Err, Context);
406      if (MIR)
407        M = MIR->parseIRModule();
408    } else
409      M = parseIRFile(InputFilename, Err, Context, false);
410    if (!M) {
411      Err.print(argv[0], WithColor::error(errs(), argv[0]));
412      return 1;
413    }
414
415    // If we are supposed to override the target triple, do so now.
416    if (!TargetTriple.empty())
417      M->setTargetTriple(Triple::normalize(TargetTriple));
418    TheTriple = Triple(M->getTargetTriple());
419  } else {
420    TheTriple = Triple(Triple::normalize(TargetTriple));
421  }
422
423  if (TheTriple.getTriple().empty())
424    TheTriple.setTriple(sys::getDefaultTargetTriple());
425
426  // Get the target specific parser.
427  std::string Error;
428  const Target *TheTarget = TargetRegistry::lookupTarget(MArch, TheTriple,
429                                                         Error);
430  if (!TheTarget) {
431    WithColor::error(errs(), argv[0]) << Error;
432    return 1;
433  }
434
435  std::string CPUStr = getCPUStr(), FeaturesStr = getFeaturesStr();
436
437  CodeGenOpt::Level OLvl = CodeGenOpt::Default;
438  switch (OptLevel) {
439  default:
440    WithColor::error(errs(), argv[0]) << "invalid optimization level.\n";
441    return 1;
442  case ' ': break;
443  case '0': OLvl = CodeGenOpt::None; break;
444  case '1': OLvl = CodeGenOpt::Less; break;
445  case '2': OLvl = CodeGenOpt::Default; break;
446  case '3': OLvl = CodeGenOpt::Aggressive; break;
447  }
448
449  TargetOptions Options = InitTargetOptionsFromCodeGenFlags();
450  Options.DisableIntegratedAS = NoIntegratedAssembler;
451  Options.MCOptions.ShowMCEncoding = ShowMCEncoding;
452  Options.MCOptions.MCUseDwarfDirectory = EnableDwarfDirectory;
453  Options.MCOptions.AsmVerbose = AsmVerbose;
454  Options.MCOptions.PreserveAsmComments = PreserveComments;
455  Options.MCOptions.IASSearchPaths = IncludeDirs;
456  Options.MCOptions.SplitDwarfFile = SplitDwarfFile;
457
458  std::unique_ptr<TargetMachine> Target(TheTarget->createTargetMachine(
459      TheTriple.getTriple(), CPUStr, FeaturesStr, Options, getRelocModel(),
460      getCodeModel(), OLvl));
461
462  assert(Target && "Could not allocate target machine!");
463
464  // If we don't have a module then just exit now. We do this down
465  // here since the CPU/Feature help is underneath the target machine
466  // creation.
467  if (SkipModule)
468    return 0;
469
470  assert(M && "Should have exited if we didn't have a module!");
471  if (FloatABIForCalls != FloatABI::Default)
472    Options.FloatABIType = FloatABIForCalls;
473
474  // Figure out where we are going to send the output.
475  std::unique_ptr<ToolOutputFile> Out =
476      GetOutputStream(TheTarget->getName(), TheTriple.getOS(), argv[0]);
477  if (!Out) return 1;
478
479  std::unique_ptr<ToolOutputFile> DwoOut;
480  if (!SplitDwarfOutputFile.empty()) {
481    std::error_code EC;
482    DwoOut = llvm::make_unique<ToolOutputFile>(SplitDwarfOutputFile, EC,
483                                               sys::fs::F_None);
484    if (EC) {
485      WithColor::error(errs(), argv[0]) << EC.message() << '\n';
486      return 1;
487    }
488  }
489
490  // Build up all of the passes that we want to do to the module.
491  legacy::PassManager PM;
492
493  // Add an appropriate TargetLibraryInfo pass for the module's triple.
494  TargetLibraryInfoImpl TLII(Triple(M->getTargetTriple()));
495
496  // The -disable-simplify-libcalls flag actually disables all builtin optzns.
497  if (DisableSimplifyLibCalls)
498    TLII.disableAllFunctions();
499  PM.add(new TargetLibraryInfoWrapperPass(TLII));
500
501  // Add the target data from the target machine, if it exists, or the module.
502  M->setDataLayout(Target->createDataLayout());
503
504  // This needs to be done after setting datalayout since it calls verifier
505  // to check debug info whereas verifier relies on correct datalayout.
506  UpgradeDebugInfo(*M);
507
508  // Verify module immediately to catch problems before doInitialization() is
509  // called on any passes.
510  if (!NoVerify && verifyModule(*M, &errs())) {
511    std::string Prefix =
512        (Twine(argv[0]) + Twine(": ") + Twine(InputFilename)).str();
513    WithColor::error(errs(), Prefix) << "input module is broken!\n";
514    return 1;
515  }
516
517  // Override function attributes based on CPUStr, FeaturesStr, and command line
518  // flags.
519  setFunctionAttributes(CPUStr, FeaturesStr, *M);
520
521  if (RelaxAll.getNumOccurrences() > 0 &&
522      FileType != TargetMachine::CGFT_ObjectFile)
523    WithColor::warning(errs(), argv[0])
524        << ": warning: ignoring -mc-relax-all because filetype != obj";
525
526  {
527    raw_pwrite_stream *OS = &Out->os();
528
529    // Manually do the buffering rather than using buffer_ostream,
530    // so we can memcmp the contents in CompileTwice mode
531    SmallVector<char, 0> Buffer;
532    std::unique_ptr<raw_svector_ostream> BOS;
533    if ((FileType != TargetMachine::CGFT_AssemblyFile &&
534         !Out->os().supportsSeeking()) ||
535        CompileTwice) {
536      BOS = make_unique<raw_svector_ostream>(Buffer);
537      OS = BOS.get();
538    }
539
540    const char *argv0 = argv[0];
541    LLVMTargetMachine &LLVMTM = static_cast<LLVMTargetMachine&>(*Target);
542    MachineModuleInfo *MMI = new MachineModuleInfo(&LLVMTM);
543
544    // Construct a custom pass pipeline that starts after instruction
545    // selection.
546    if (!RunPassNames->empty()) {
547      if (!MIR) {
548        WithColor::warning(errs(), argv[0])
549            << "run-pass is for .mir file only.\n";
550        return 1;
551      }
552      TargetPassConfig &TPC = *LLVMTM.createPassConfig(PM);
553      if (TPC.hasLimitedCodeGenPipeline()) {
554        WithColor::warning(errs(), argv[0])
555            << "run-pass cannot be used with "
556            << TPC.getLimitedCodeGenPipelineReason(" and ") << ".\n";
557        return 1;
558      }
559
560      TPC.setDisableVerify(NoVerify);
561      PM.add(&TPC);
562      PM.add(MMI);
563      TPC.printAndVerify("");
564      for (const std::string &RunPassName : *RunPassNames) {
565        if (addPass(PM, argv0, RunPassName, TPC))
566          return 1;
567      }
568      TPC.setInitialized();
569      PM.add(createPrintMIRPass(*OS));
570      PM.add(createFreeMachineFunctionPass());
571    } else if (Target->addPassesToEmitFile(PM, *OS,
572                                           DwoOut ? &DwoOut->os() : nullptr,
573                                           FileType, NoVerify, MMI)) {
574      WithColor::warning(errs(), argv[0])
575          << "target does not support generation of this"
576          << " file type!\n";
577      return 1;
578    }
579
580    if (MIR) {
581      assert(MMI && "Forgot to create MMI?");
582      if (MIR->parseMachineFunctions(*M, *MMI))
583        return 1;
584    }
585
586    // Before executing passes, print the final values of the LLVM options.
587    cl::PrintOptionValues();
588
589    // If requested, run the pass manager over the same module again,
590    // to catch any bugs due to persistent state in the passes. Note that
591    // opt has the same functionality, so it may be worth abstracting this out
592    // in the future.
593    SmallVector<char, 0> CompileTwiceBuffer;
594    if (CompileTwice) {
595      std::unique_ptr<Module> M2(llvm::CloneModule(*M));
596      PM.run(*M2);
597      CompileTwiceBuffer = Buffer;
598      Buffer.clear();
599    }
600
601    PM.run(*M);
602
603    auto HasError =
604        ((const LLCDiagnosticHandler *)(Context.getDiagHandlerPtr()))->HasError;
605    if (*HasError)
606      return 1;
607
608    // Compare the two outputs and make sure they're the same
609    if (CompileTwice) {
610      if (Buffer.size() != CompileTwiceBuffer.size() ||
611          (memcmp(Buffer.data(), CompileTwiceBuffer.data(), Buffer.size()) !=
612           0)) {
613        errs()
614            << "Running the pass manager twice changed the output.\n"
615               "Writing the result of the second run to the specified output\n"
616               "To generate the one-run comparison binary, just run without\n"
617               "the compile-twice option\n";
618        Out->os() << Buffer;
619        Out->keep();
620        return 1;
621      }
622    }
623
624    if (BOS) {
625      Out->os() << Buffer;
626    }
627  }
628
629  // Declare success.
630  Out->keep();
631  if (DwoOut)
632    DwoOut->keep();
633
634  return 0;
635}
636