1193323Sed//===- ExecutionDriver.cpp - Allow execution of LLVM program --------------===//
2193323Sed//
3193323Sed//                     The LLVM Compiler Infrastructure
4193323Sed//
5193323Sed// This file is distributed under the University of Illinois Open Source
6193323Sed// License. See LICENSE.TXT for details.
7193323Sed//
8193323Sed//===----------------------------------------------------------------------===//
9193323Sed//
10193323Sed// This file contains code used to execute the program utilizing one of the
11193323Sed// various ways of running LLVM bitcode.
12193323Sed//
13193323Sed//===----------------------------------------------------------------------===//
14193323Sed
15193323Sed#include "BugDriver.h"
16193323Sed#include "ToolRunner.h"
17193323Sed#include "llvm/Support/CommandLine.h"
18193323Sed#include "llvm/Support/Debug.h"
19193323Sed#include "llvm/Support/FileUtilities.h"
20193323Sed#include "llvm/Support/SystemUtils.h"
21198090Srdivacky#include "llvm/Support/raw_ostream.h"
22193323Sed#include <fstream>
23193323Sed
24193323Sedusing namespace llvm;
25193323Sed
26193323Sednamespace {
27193323Sed  // OutputType - Allow the user to specify the way code should be run, to test
28193323Sed  // for miscompilation.
29193323Sed  //
30193323Sed  enum OutputType {
31234353Sdim    AutoPick, RunLLI, RunJIT, RunLLC, RunLLCIA, LLC_Safe, CompileCustom, Custom
32193323Sed  };
33193323Sed
34193323Sed  cl::opt<double>
35193323Sed  AbsTolerance("abs-tolerance", cl::desc("Absolute error tolerated"),
36193323Sed               cl::init(0.0));
37193323Sed  cl::opt<double>
38193323Sed  RelTolerance("rel-tolerance", cl::desc("Relative error tolerated"),
39193323Sed               cl::init(0.0));
40193323Sed
41193323Sed  cl::opt<OutputType>
42193323Sed  InterpreterSel(cl::desc("Specify the \"test\" i.e. suspect back-end:"),
43193323Sed                 cl::values(clEnumValN(AutoPick, "auto", "Use best guess"),
44193323Sed                            clEnumValN(RunLLI, "run-int",
45193323Sed                                       "Execute with the interpreter"),
46193323Sed                            clEnumValN(RunJIT, "run-jit", "Execute with JIT"),
47193323Sed                            clEnumValN(RunLLC, "run-llc", "Compile with LLC"),
48205218Srdivacky                            clEnumValN(RunLLCIA, "run-llc-ia",
49205218Srdivacky                                  "Compile with LLC with integrated assembler"),
50193323Sed                            clEnumValN(LLC_Safe, "llc-safe", "Use LLC for all"),
51218885Sdim                            clEnumValN(CompileCustom, "compile-custom",
52218885Sdim                            "Use -compile-command to define a command to "
53218885Sdim                            "compile the bitcode. Useful to avoid linking."),
54193323Sed                            clEnumValN(Custom, "run-custom",
55193323Sed                            "Use -exec-command to define a command to execute "
56193323Sed                            "the bitcode. Useful for cross-compilation."),
57193323Sed                            clEnumValEnd),
58193323Sed                 cl::init(AutoPick));
59193323Sed
60193323Sed  cl::opt<OutputType>
61193323Sed  SafeInterpreterSel(cl::desc("Specify \"safe\" i.e. known-good backend:"),
62198090Srdivacky              cl::values(clEnumValN(AutoPick, "safe-auto", "Use best guess"),
63198090Srdivacky                         clEnumValN(RunLLC, "safe-run-llc", "Compile with LLC"),
64198090Srdivacky                         clEnumValN(Custom, "safe-run-custom",
65198090Srdivacky                         "Use -exec-command to define a command to execute "
66198090Srdivacky                         "the bitcode. Useful for cross-compilation."),
67198090Srdivacky                         clEnumValEnd),
68193323Sed                     cl::init(AutoPick));
69193323Sed
70193323Sed  cl::opt<std::string>
71193323Sed  SafeInterpreterPath("safe-path",
72198090Srdivacky                   cl::desc("Specify the path to the \"safe\" backend program"),
73198090Srdivacky                   cl::init(""));
74193323Sed
75193323Sed  cl::opt<bool>
76193323Sed  AppendProgramExitCode("append-exit-code",
77193323Sed      cl::desc("Append the exit code to the output so it gets diff'd too"),
78193323Sed      cl::init(false));
79193323Sed
80193323Sed  cl::opt<std::string>
81193323Sed  InputFile("input", cl::init("/dev/null"),
82193323Sed            cl::desc("Filename to pipe in as stdin (default: /dev/null)"));
83193323Sed
84193323Sed  cl::list<std::string>
85193323Sed  AdditionalSOs("additional-so",
86193323Sed                cl::desc("Additional shared objects to load "
87193323Sed                         "into executing programs"));
88193323Sed
89193323Sed  cl::list<std::string>
90218885Sdim  AdditionalLinkerArgs("Xlinker",
91193323Sed      cl::desc("Additional arguments to pass to the linker"));
92193323Sed
93193323Sed  cl::opt<std::string>
94218885Sdim  CustomCompileCommand("compile-command", cl::init("llc"),
95218885Sdim      cl::desc("Command to compile the bitcode (use with -compile-custom) "
96218885Sdim               "(default: llc)"));
97218885Sdim
98218885Sdim  cl::opt<std::string>
99193323Sed  CustomExecCommand("exec-command", cl::init("simulate"),
100193323Sed      cl::desc("Command to execute the bitcode (use with -run-custom) "
101193323Sed               "(default: simulate)"));
102193323Sed}
103193323Sed
104193323Sednamespace llvm {
105193323Sed  // Anything specified after the --args option are taken as arguments to the
106193323Sed  // program being debugged.
107193323Sed  cl::list<std::string>
108193323Sed  InputArgv("args", cl::Positional, cl::desc("<program arguments>..."),
109193323Sed            cl::ZeroOrMore, cl::PositionalEatsArgs);
110198090Srdivacky
111198090Srdivacky  cl::opt<std::string>
112198090Srdivacky  OutputPrefix("output-prefix", cl::init("bugpoint"),
113198090Srdivacky            cl::desc("Prefix to use for outputs (default: 'bugpoint')"));
114193323Sed}
115193323Sed
116193323Sednamespace {
117193323Sed  cl::list<std::string>
118193323Sed  ToolArgv("tool-args", cl::Positional, cl::desc("<tool arguments>..."),
119193323Sed           cl::ZeroOrMore, cl::PositionalEatsArgs);
120193323Sed
121193323Sed  cl::list<std::string>
122193323Sed  SafeToolArgv("safe-tool-args", cl::Positional,
123193323Sed               cl::desc("<safe-tool arguments>..."),
124193323Sed               cl::ZeroOrMore, cl::PositionalEatsArgs);
125193323Sed
126208599Srdivacky  cl::opt<std::string>
127218885Sdim  GCCBinary("gcc", cl::init("gcc"),
128208599Srdivacky              cl::desc("The gcc binary to use. (default 'gcc')"));
129208599Srdivacky
130193323Sed  cl::list<std::string>
131193323Sed  GCCToolArgv("gcc-tool-args", cl::Positional,
132193323Sed              cl::desc("<gcc-tool arguments>..."),
133193323Sed              cl::ZeroOrMore, cl::PositionalEatsArgs);
134193323Sed}
135193323Sed
136193323Sed//===----------------------------------------------------------------------===//
137193323Sed// BugDriver method implementation
138193323Sed//
139193323Sed
140193323Sed/// initializeExecutionEnvironment - This method is used to set up the
141193323Sed/// environment for executing LLVM programs.
142193323Sed///
143193323Sedbool BugDriver::initializeExecutionEnvironment() {
144198090Srdivacky  outs() << "Initializing execution environment: ";
145193323Sed
146193323Sed  // Create an instance of the AbstractInterpreter interface as specified on
147193323Sed  // the command line
148193323Sed  SafeInterpreter = 0;
149193323Sed  std::string Message;
150193323Sed
151193323Sed  switch (InterpreterSel) {
152193323Sed  case AutoPick:
153193323Sed    if (!Interpreter) {
154193323Sed      InterpreterSel = RunJIT;
155193323Sed      Interpreter = AbstractInterpreter::createJIT(getToolName(), Message,
156193323Sed                                                   &ToolArgv);
157193323Sed    }
158193323Sed    if (!Interpreter) {
159193323Sed      InterpreterSel = RunLLC;
160193323Sed      Interpreter = AbstractInterpreter::createLLC(getToolName(), Message,
161218885Sdim                                                   GCCBinary, &ToolArgv,
162208599Srdivacky                                                   &GCCToolArgv);
163193323Sed    }
164193323Sed    if (!Interpreter) {
165193323Sed      InterpreterSel = RunLLI;
166193323Sed      Interpreter = AbstractInterpreter::createLLI(getToolName(), Message,
167193323Sed                                                   &ToolArgv);
168193323Sed    }
169193323Sed    if (!Interpreter) {
170193323Sed      InterpreterSel = AutoPick;
171193323Sed      Message = "Sorry, I can't automatically select an interpreter!\n";
172193323Sed    }
173193323Sed    break;
174193323Sed  case RunLLI:
175193323Sed    Interpreter = AbstractInterpreter::createLLI(getToolName(), Message,
176193323Sed                                                 &ToolArgv);
177193323Sed    break;
178193323Sed  case RunLLC:
179205218Srdivacky  case RunLLCIA:
180193323Sed  case LLC_Safe:
181193323Sed    Interpreter = AbstractInterpreter::createLLC(getToolName(), Message,
182218885Sdim                                                 GCCBinary, &ToolArgv,
183208599Srdivacky                                                 &GCCToolArgv,
184205218Srdivacky                                                 InterpreterSel == RunLLCIA);
185193323Sed    break;
186193323Sed  case RunJIT:
187193323Sed    Interpreter = AbstractInterpreter::createJIT(getToolName(), Message,
188193323Sed                                                 &ToolArgv);
189193323Sed    break;
190218885Sdim  case CompileCustom:
191218885Sdim    Interpreter =
192218885Sdim      AbstractInterpreter::createCustomCompiler(Message, CustomCompileCommand);
193218885Sdim    break;
194193323Sed  case Custom:
195218885Sdim    Interpreter =
196218885Sdim      AbstractInterpreter::createCustomExecutor(Message, CustomExecCommand);
197193323Sed    break;
198193323Sed  }
199193323Sed  if (!Interpreter)
200198090Srdivacky    errs() << Message;
201193323Sed  else // Display informational messages on stdout instead of stderr
202198090Srdivacky    outs() << Message;
203193323Sed
204193323Sed  std::string Path = SafeInterpreterPath;
205193323Sed  if (Path.empty())
206193323Sed    Path = getToolName();
207193323Sed  std::vector<std::string> SafeToolArgs = SafeToolArgv;
208193323Sed  switch (SafeInterpreterSel) {
209193323Sed  case AutoPick:
210193323Sed    // In "llc-safe" mode, default to using LLC as the "safe" backend.
211193323Sed    if (!SafeInterpreter &&
212193323Sed        InterpreterSel == LLC_Safe) {
213193323Sed      SafeInterpreterSel = RunLLC;
214193323Sed      SafeToolArgs.push_back("--relocation-model=pic");
215198090Srdivacky      SafeInterpreter = AbstractInterpreter::createLLC(Path.c_str(), Message,
216218885Sdim                                                       GCCBinary,
217193323Sed                                                       &SafeToolArgs,
218193323Sed                                                       &GCCToolArgv);
219193323Sed    }
220193323Sed
221193323Sed    if (!SafeInterpreter &&
222193323Sed        InterpreterSel != RunLLC &&
223193323Sed        InterpreterSel != RunJIT) {
224193323Sed      SafeInterpreterSel = RunLLC;
225193323Sed      SafeToolArgs.push_back("--relocation-model=pic");
226198090Srdivacky      SafeInterpreter = AbstractInterpreter::createLLC(Path.c_str(), Message,
227218885Sdim                                                       GCCBinary,
228193323Sed                                                       &SafeToolArgs,
229193323Sed                                                       &GCCToolArgv);
230193323Sed    }
231193323Sed    if (!SafeInterpreter) {
232193323Sed      SafeInterpreterSel = AutoPick;
233249423Sdim      Message = "Sorry, I can't automatically select a safe interpreter!\n";
234193323Sed    }
235193323Sed    break;
236193323Sed  case RunLLC:
237205218Srdivacky  case RunLLCIA:
238193323Sed    SafeToolArgs.push_back("--relocation-model=pic");
239198090Srdivacky    SafeInterpreter = AbstractInterpreter::createLLC(Path.c_str(), Message,
240208599Srdivacky                                                     GCCBinary, &SafeToolArgs,
241205218Srdivacky                                                     &GCCToolArgv,
242205218Srdivacky                                                SafeInterpreterSel == RunLLCIA);
243193323Sed    break;
244193323Sed  case Custom:
245218885Sdim    SafeInterpreter =
246218885Sdim      AbstractInterpreter::createCustomExecutor(Message, CustomExecCommand);
247193323Sed    break;
248193323Sed  default:
249193323Sed    Message = "Sorry, this back-end is not supported by bugpoint as the "
250193323Sed              "\"safe\" backend right now!\n";
251193323Sed    break;
252193323Sed  }
253198090Srdivacky  if (!SafeInterpreter) { outs() << Message << "\nExiting.\n"; exit(1); }
254218885Sdim
255208599Srdivacky  gcc = GCC::create(Message, GCCBinary, &GCCToolArgv);
256198090Srdivacky  if (!gcc) { outs() << Message << "\nExiting.\n"; exit(1); }
257193323Sed
258193323Sed  // If there was an error creating the selected interpreter, quit with error.
259193323Sed  return Interpreter == 0;
260193323Sed}
261193323Sed
262207618Srdivacky/// compileProgram - Try to compile the specified module, returning false and
263207618Srdivacky/// setting Error if an error occurs.  This is used for code generation
264207618Srdivacky/// crash testing.
265193323Sed///
266212793Sdimvoid BugDriver::compileProgram(Module *M, std::string *Error) const {
267193323Sed  // Emit the program to a bitcode file...
268263508Sdim  SmallString<128> BitcodeFile;
269263508Sdim  int BitcodeFD;
270263508Sdim  error_code EC = sys::fs::createUniqueFile(
271263508Sdim      OutputPrefix + "-test-program-%%%%%%%.bc", BitcodeFD, BitcodeFile);
272263508Sdim  if (EC) {
273263508Sdim    errs() << ToolName << ": Error making unique filename: " << EC.message()
274198090Srdivacky           << "\n";
275193323Sed    exit(1);
276193323Sed  }
277263508Sdim  if (writeProgramToFile(BitcodeFile.str(), BitcodeFD, M)) {
278263508Sdim    errs() << ToolName << ": Error emitting bitcode to file '" << BitcodeFile
279263508Sdim           << "'!\n";
280193323Sed    exit(1);
281193323Sed  }
282193323Sed
283207618Srdivacky  // Remove the temporary bitcode file when we are done.
284221337Sdim  FileRemover BitcodeFileRemover(BitcodeFile.str(), !SaveTemps);
285193323Sed
286193323Sed  // Actually compile the program!
287208599Srdivacky  Interpreter->compileProgram(BitcodeFile.str(), Error, Timeout, MemoryLimit);
288193323Sed}
289193323Sed
290193323Sed
291193323Sed/// executeProgram - This method runs "Program", capturing the output of the
292193323Sed/// program to a file, returning the filename of the file.  A recommended
293193323Sed/// filename may be optionally specified.
294193323Sed///
295212793Sdimstd::string BugDriver::executeProgram(const Module *Program,
296212793Sdim                                      std::string OutputFile,
297193323Sed                                      std::string BitcodeFile,
298193323Sed                                      const std::string &SharedObj,
299193323Sed                                      AbstractInterpreter *AI,
300212793Sdim                                      std::string *Error) const {
301193323Sed  if (AI == 0) AI = Interpreter;
302193323Sed  assert(AI && "Interpreter should have been created already!");
303193323Sed  bool CreatedBitcode = false;
304193323Sed  if (BitcodeFile.empty()) {
305193323Sed    // Emit the program to a bitcode file...
306263508Sdim    SmallString<128> UniqueFilename;
307263508Sdim    int UniqueFD;
308263508Sdim    error_code EC = sys::fs::createUniqueFile(
309263508Sdim        OutputPrefix + "-test-program-%%%%%%%.bc", UniqueFD, UniqueFilename);
310263508Sdim    if (EC) {
311198090Srdivacky      errs() << ToolName << ": Error making unique filename: "
312263508Sdim             << EC.message() << "!\n";
313193323Sed      exit(1);
314193323Sed    }
315263508Sdim    BitcodeFile = UniqueFilename.str();
316193323Sed
317263508Sdim    if (writeProgramToFile(BitcodeFile, UniqueFD, Program)) {
318198090Srdivacky      errs() << ToolName << ": Error emitting bitcode to file '"
319198090Srdivacky             << BitcodeFile << "'!\n";
320193323Sed      exit(1);
321193323Sed    }
322193323Sed    CreatedBitcode = true;
323193323Sed  }
324193323Sed
325193323Sed  // Remove the temporary bitcode file when we are done.
326263508Sdim  std::string BitcodePath(BitcodeFile);
327263508Sdim  FileRemover BitcodeFileRemover(BitcodePath,
328221337Sdim    CreatedBitcode && !SaveTemps);
329193323Sed
330263508Sdim  if (OutputFile.empty()) OutputFile = OutputPrefix + "-execution-output-%%%%%%%";
331193323Sed
332193323Sed  // Check to see if this is a valid output filename...
333263508Sdim  SmallString<128> UniqueFile;
334263508Sdim  error_code EC = sys::fs::createUniqueFile(OutputFile, UniqueFile);
335263508Sdim  if (EC) {
336198090Srdivacky    errs() << ToolName << ": Error making unique filename: "
337263508Sdim           << EC.message() << "\n";
338193323Sed    exit(1);
339193323Sed  }
340263508Sdim  OutputFile = UniqueFile.str();
341193323Sed
342193323Sed  // Figure out which shared objects to run, if any.
343193323Sed  std::vector<std::string> SharedObjs(AdditionalSOs);
344193323Sed  if (!SharedObj.empty())
345193323Sed    SharedObjs.push_back(SharedObj);
346193323Sed
347207618Srdivacky  int RetVal = AI->ExecuteProgram(BitcodeFile, InputArgv, InputFile, OutputFile,
348207618Srdivacky                                  Error, AdditionalLinkerArgs, SharedObjs,
349193323Sed                                  Timeout, MemoryLimit);
350207618Srdivacky  if (!Error->empty())
351207618Srdivacky    return OutputFile;
352193323Sed
353193323Sed  if (RetVal == -1) {
354198090Srdivacky    errs() << "<timeout>";
355193323Sed    static bool FirstTimeout = true;
356193323Sed    if (FirstTimeout) {
357198090Srdivacky      outs() << "\n"
358193323Sed "*** Program execution timed out!  This mechanism is designed to handle\n"
359193323Sed "    programs stuck in infinite loops gracefully.  The -timeout option\n"
360193323Sed "    can be used to change the timeout threshold or disable it completely\n"
361193323Sed "    (with -timeout=0).  This message is only displayed once.\n";
362193323Sed      FirstTimeout = false;
363193323Sed    }
364193323Sed  }
365193323Sed
366193323Sed  if (AppendProgramExitCode) {
367193323Sed    std::ofstream outFile(OutputFile.c_str(), std::ios_base::app);
368193323Sed    outFile << "exit " << RetVal << '\n';
369193323Sed    outFile.close();
370193323Sed  }
371193323Sed
372193323Sed  // Return the filename we captured the output to.
373193323Sed  return OutputFile;
374193323Sed}
375193323Sed
376193323Sed/// executeProgramSafely - Used to create reference output with the "safe"
377193323Sed/// backend, if reference output is not provided.
378193323Sed///
379212793Sdimstd::string BugDriver::executeProgramSafely(const Module *Program,
380212793Sdim                                            std::string OutputFile,
381212793Sdim                                            std::string *Error) const {
382212793Sdim  return executeProgram(Program, OutputFile, "", "", SafeInterpreter, Error);
383193323Sed}
384193323Sed
385207618Srdivackystd::string BugDriver::compileSharedObject(const std::string &BitcodeFile,
386207618Srdivacky                                           std::string &Error) {
387193323Sed  assert(Interpreter && "Interpreter should have been created already!");
388263508Sdim  std::string OutputFile;
389193323Sed
390193323Sed  // Using the known-good backend.
391207618Srdivacky  GCC::FileType FT = SafeInterpreter->OutputCode(BitcodeFile, OutputFile,
392207618Srdivacky                                                 Error);
393207618Srdivacky  if (!Error.empty())
394207618Srdivacky    return "";
395193323Sed
396193323Sed  std::string SharedObjectFile;
397263508Sdim  bool Failure = gcc->MakeSharedObject(OutputFile, FT, SharedObjectFile,
398207618Srdivacky                                       AdditionalLinkerArgs, Error);
399207618Srdivacky  if (!Error.empty())
400207618Srdivacky    return "";
401207618Srdivacky  if (Failure)
402193323Sed    exit(1);
403193323Sed
404193323Sed  // Remove the intermediate C file
405263508Sdim  sys::fs::remove(OutputFile);
406193323Sed
407193323Sed  return "./" + SharedObjectFile;
408193323Sed}
409193323Sed
410193323Sed/// createReferenceFile - calls compileProgram and then records the output
411218885Sdim/// into ReferenceOutputFile. Returns true if reference file created, false
412193323Sed/// otherwise. Note: initializeExecutionEnvironment should be called BEFORE
413193323Sed/// this function.
414193323Sed///
415193323Sedbool BugDriver::createReferenceFile(Module *M, const std::string &Filename) {
416207618Srdivacky  std::string Error;
417207618Srdivacky  compileProgram(Program, &Error);
418207618Srdivacky  if (!Error.empty())
419193323Sed    return false;
420207618Srdivacky
421212793Sdim  ReferenceOutputFile = executeProgramSafely(Program, Filename, &Error);
422207618Srdivacky  if (!Error.empty()) {
423207618Srdivacky    errs() << Error;
424193323Sed    if (Interpreter != SafeInterpreter) {
425198090Srdivacky      errs() << "*** There is a bug running the \"safe\" backend.  Either"
426234353Sdim             << " debug it (for example with the -run-jit bugpoint option,"
427234353Sdim             << " if JIT is being used as the \"safe\" backend), or fix the"
428198090Srdivacky             << " error some other way.\n";
429193323Sed    }
430193323Sed    return false;
431193323Sed  }
432207618Srdivacky  outs() << "\nReference output is: " << ReferenceOutputFile << "\n\n";
433193323Sed  return true;
434193323Sed}
435193323Sed
436193323Sed/// diffProgram - This method executes the specified module and diffs the
437193323Sed/// output against the file specified by ReferenceOutputFile.  If the output
438207618Srdivacky/// is different, 1 is returned.  If there is a problem with the code
439223013Sdim/// generator (e.g., llc crashes), this will set ErrMsg.
440193323Sed///
441212793Sdimbool BugDriver::diffProgram(const Module *Program,
442212793Sdim                            const std::string &BitcodeFile,
443193323Sed                            const std::string &SharedObject,
444207618Srdivacky                            bool RemoveBitcode,
445212793Sdim                            std::string *ErrMsg) const {
446193323Sed  // Execute the program, generating an output file...
447263508Sdim  std::string Output(
448263508Sdim      executeProgram(Program, "", BitcodeFile, SharedObject, 0, ErrMsg));
449207618Srdivacky  if (!ErrMsg->empty())
450207618Srdivacky    return false;
451193323Sed
452193323Sed  std::string Error;
453193323Sed  bool FilesDifferent = false;
454263508Sdim  if (int Diff = DiffFilesWithTolerance(ReferenceOutputFile,
455263508Sdim                                        Output,
456193323Sed                                        AbsTolerance, RelTolerance, &Error)) {
457193323Sed    if (Diff == 2) {
458198090Srdivacky      errs() << "While diffing output: " << Error << '\n';
459193323Sed      exit(1);
460193323Sed    }
461193323Sed    FilesDifferent = true;
462193323Sed  }
463198090Srdivacky  else {
464198090Srdivacky    // Remove the generated output if there are no differences.
465263508Sdim    sys::fs::remove(Output);
466198090Srdivacky  }
467193323Sed
468193323Sed  // Remove the bitcode file if we are supposed to.
469193323Sed  if (RemoveBitcode)
470263508Sdim    sys::fs::remove(BitcodeFile);
471193323Sed  return FilesDifferent;
472193323Sed}
473193323Sed
474193323Sedbool BugDriver::isExecutingJIT() {
475193323Sed  return InterpreterSel == RunJIT;
476193323Sed}
477193323Sed
478