From c8ba0a0acd30f0b56d08a3a0947f68ac01a40730 Mon Sep 17 00:00:00 2001 From: Hans Wennborg Date: Thu, 19 Sep 2013 20:32:16 +0000 Subject: [PATCH] clang-cl: implement /fallback mode When this flag is enabled, clang-cl falls back to cl.exe if it cannot compile the code itself for some reason. The idea is to use this to help build projects that almost compile with clang-cl, except for some files that can then be built with the fallback mechanism. Differential Revision: http://llvm-reviews.chandlerc.com/D1711 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@191034 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Driver/CLCompatOptions.td | 2 + include/clang/Driver/Job.h | 37 ++++++++++-- include/clang/Driver/Tool.h | 3 +- lib/Driver/Job.cpp | 37 ++++++++++++ lib/Driver/Tools.cpp | 77 ++++++++++++++++++++++++- lib/Driver/Tools.h | 24 +++++++- test/Driver/cl-fallback.c | 22 +++++++ 7 files changed, 193 insertions(+), 9 deletions(-) create mode 100644 test/Driver/cl-fallback.c diff --git a/include/clang/Driver/CLCompatOptions.td b/include/clang/Driver/CLCompatOptions.td index 1f73971a70..c912accd25 100644 --- a/include/clang/Driver/CLCompatOptions.td +++ b/include/clang/Driver/CLCompatOptions.td @@ -110,6 +110,8 @@ def _SLASH_Zs : CLFlag<"Zs">, HelpText<"Syntax-check only">, def _SLASH_M_Group : OptionGroup<"">, Group; +def _SLASH_fallback : CLCompileFlag<"fallback">, + HelpText<"Fall back to cl.exe if clang-cl fails to compile">; def _SLASH_Fe : CLJoined<"Fe">, HelpText<"Set output executable file or directory (ends in / or \\)">, MetaVarName<"">; diff --git a/include/clang/Driver/Job.h b/include/clang/Driver/Job.h index 905ddb934c..1dd49a7923 100644 --- a/include/clang/Driver/Job.h +++ b/include/clang/Driver/Job.h @@ -11,6 +11,7 @@ #define CLANG_DRIVER_JOB_H_ #include "clang/Basic/LLVM.h" +#include "llvm/ADT/OwningPtr.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Option/Option.h" @@ -31,6 +32,7 @@ class Job { public: enum JobClass { CommandClass, + FallbackCommandClass, JobListClass }; @@ -54,8 +56,8 @@ public: bool Quote, bool CrashReport = false) const = 0; }; - /// Command - An executable path/name and argument vector to - /// execute. +/// Command - An executable path/name and argument vector to +/// execute. class Command : public Job { /// Source - The action which caused the creation of this job. const Action &Source; @@ -77,8 +79,8 @@ public: virtual void Print(llvm::raw_ostream &OS, const char *Terminator, bool Quote, bool CrashReport = false) const; - int Execute(const StringRef **Redirects, std::string *ErrMsg, - bool *ExecutionFailed) const; + virtual int Execute(const StringRef **Redirects, std::string *ErrMsg, + bool *ExecutionFailed) const; /// getSource - Return the Action which caused the creation of this job. const Action &getSource() const { return Source; } @@ -89,11 +91,34 @@ public: const llvm::opt::ArgStringList &getArguments() const { return Arguments; } static bool classof(const Job *J) { - return J->getKind() == CommandClass; + return J->getKind() == CommandClass || + J->getKind() == FallbackCommandClass; } }; - /// JobList - A sequence of jobs to perform. +/// Like Command, but with a fallback which is executed in case +/// the primary command crashes. +class FallbackCommand : public Command { +public: + FallbackCommand(const Action &Source_, const Tool &Creator_, + const char *Executable_, const ArgStringList &Arguments_, + Command *Fallback_); + + virtual void Print(llvm::raw_ostream &OS, const char *Terminator, + bool Quote, bool CrashReport = false) const; + + virtual int Execute(const StringRef **Redirects, std::string *ErrMsg, + bool *ExecutionFailed) const; + + static bool classof(const Job *J) { + return J->getKind() == FallbackCommandClass; + } + +private: + OwningPtr Fallback; +}; + +/// JobList - A sequence of jobs to perform. class JobList : public Job { public: typedef SmallVector list_type; diff --git a/include/clang/Driver/Tool.h b/include/clang/Driver/Tool.h index 4159ecabf6..015dcf513e 100644 --- a/include/clang/Driver/Tool.h +++ b/include/clang/Driver/Tool.h @@ -63,7 +63,8 @@ public: virtual bool hasGoodDiagnostics() const { return false; } /// ConstructJob - Construct jobs to perform the action \p JA, - /// writing to \p Output and with \p Inputs. + /// writing to \p Output and with \p Inputs, and add the jobs to + /// \p C. /// /// \param TCArgs - The argument list for this toolchain, with any /// tool chain specific translations applied. diff --git a/lib/Driver/Job.cpp b/lib/Driver/Job.cpp index 7df46d348c..ee68e6f14d 100644 --- a/lib/Driver/Job.cpp +++ b/lib/Driver/Job.cpp @@ -126,6 +126,43 @@ int Command::Execute(const StringRef **Redirects, std::string *ErrMsg, /*memoryLimit*/ 0, ErrMsg, ExecutionFailed); } +FallbackCommand::FallbackCommand(const Action &Source_, const Tool &Creator_, + const char *Executable_, + const ArgStringList &Arguments_, + Command *Fallback_) + : Command(Source_, Creator_, Executable_, Arguments_), Fallback(Fallback_) { +} + +void FallbackCommand::Print(raw_ostream &OS, const char *Terminator, + bool Quote, bool CrashReport) const { + Command::Print(OS, "", Quote, CrashReport); + OS << " ||"; + Fallback->Print(OS, Terminator, Quote, CrashReport); +} + +static bool ShouldFallback(int ExitCode) { + // FIXME: We really just want to fall back for internal errors, such + // as when some symbol cannot be mangled, when we should be able to + // parse something but can't, etc. + return ExitCode != 0; +} + +int FallbackCommand::Execute(const StringRef **Redirects, std::string *ErrMsg, + bool *ExecutionFailed) const { + int PrimaryStatus = Command::Execute(Redirects, ErrMsg, ExecutionFailed); + if (!ShouldFallback(PrimaryStatus)) + return PrimaryStatus; + + // Clear ExecutionFailed and ErrMsg before falling back. + if (ErrMsg) + ErrMsg->clear(); + if (ExecutionFailed) + *ExecutionFailed = false; + + int SecondaryStatus = Fallback->Execute(Redirects, ErrMsg, ExecutionFailed); + return SecondaryStatus; +} + JobList::JobList() : Job(JobListClass) {} JobList::~JobList() { diff --git a/lib/Driver/Tools.cpp b/lib/Driver/Tools.cpp index da27a941a1..a9d3acc2da 100644 --- a/lib/Driver/Tools.cpp +++ b/lib/Driver/Tools.cpp @@ -3565,7 +3565,15 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, } // Finally add the compile command to the compilation. - C.addCommand(new Command(JA, *this, Exec, CmdArgs)); + if (Args.hasArg(options::OPT__SLASH_fallback)) { + tools::visualstudio::Compile CL(getToolChain()); + Command *CLCommand = CL.GetCommand(C, JA, Output, Inputs, Args, + LinkingOutput); + C.addCommand(new FallbackCommand(JA, *this, Exec, CmdArgs, CLCommand)); + } else { + C.addCommand(new Command(JA, *this, Exec, CmdArgs)); + } + // Handle the debug info splitting at object creation time if we're // creating an object. @@ -6638,3 +6646,70 @@ void visualstudio::Link::ConstructJob(Compilation &C, const JobAction &JA, Args.MakeArgString(getToolChain().GetProgramPath("link.exe")); C.addCommand(new Command(JA, *this, Exec, CmdArgs)); } + +void visualstudio::Compile::ConstructJob(Compilation &C, const JobAction &JA, + const InputInfo &Output, + const InputInfoList &Inputs, + const ArgList &Args, + const char *LinkingOutput) const { + C.addCommand(GetCommand(C, JA, Output, Inputs, Args, LinkingOutput)); +} + +Command *visualstudio::Compile::GetCommand(Compilation &C, const JobAction &JA, + const InputInfo &Output, + const InputInfoList &Inputs, + const ArgList &Args, + const char *LinkingOutput) const { + ArgStringList CmdArgs; + CmdArgs.push_back("/c"); // Compile only. + CmdArgs.push_back("/W0"); // No warnings. + + // The goal is to be able to invoke this tool correctly based on + // any flag accepted by clang-cl. + + // These are spelled the same way in clang and cl.exe,. + Args.AddAllArgs(CmdArgs, options::OPT_D, options::OPT_U); + Args.AddAllArgs(CmdArgs, options::OPT_I); + Args.AddLastArg(CmdArgs, options::OPT_O, options::OPT_O0); + + // Flags for which clang-cl have an alias. + // FIXME: How can we ensure this stays in sync with relevant clang-cl options? + + if (Arg *A = Args.getLastArg(options::OPT_frtti, options::OPT_fno_rtti)) + CmdArgs.push_back(A->getOption().getID() == options::OPT_frtti ? "/GR" + : "/GR-"); + if (Args.hasArg(options::OPT_fsyntax_only)) + CmdArgs.push_back("/Zs"); + + // Flags that can simply be passed through. + Args.AddAllArgs(CmdArgs, options::OPT__SLASH_LD); + Args.AddAllArgs(CmdArgs, options::OPT__SLASH_LDd); + + // The order of these flags is relevant, so pick the last one. + if (Arg *A = Args.getLastArg(options::OPT__SLASH_MD, options::OPT__SLASH_MDd, + options::OPT__SLASH_MT, options::OPT__SLASH_MTd)) + A->render(Args, CmdArgs); + + + // Input filename. + assert(Inputs.size() == 1); + const InputInfo &II = Inputs[0]; + assert(II.getType() == types::TY_C || II.getType() == types::TY_CXX); + CmdArgs.push_back(II.getType() == types::TY_C ? "/Tc" : "/Tp"); + if (II.isFilename()) + CmdArgs.push_back(II.getFilename()); + else + II.getInputArg().renderAsInput(Args, CmdArgs); + + // Output filename. + assert(Output.getType() == types::TY_Object); + const char *Fo = Args.MakeArgString(std::string("/Fo") + + Output.getFilename()); + CmdArgs.push_back(Fo); + + // FIXME: If we've put clang-cl as cl.exe on the path, we have a problem. + const char *Exec = + Args.MakeArgString(getToolChain().GetProgramPath("cl.exe")); + + return new Command(JA, *this, Exec, CmdArgs); +} diff --git a/lib/Driver/Tools.h b/lib/Driver/Tools.h index 5c0fc447d1..d6fddd9c04 100644 --- a/lib/Driver/Tools.h +++ b/lib/Driver/Tools.h @@ -21,6 +21,7 @@ namespace clang { class ObjCRuntime; namespace driver { + class Command; class Driver; namespace toolchains { @@ -590,7 +591,7 @@ namespace dragonfly { /// Visual studio tools. namespace visualstudio { - class LLVM_LIBRARY_VISIBILITY Link : public Tool { + class LLVM_LIBRARY_VISIBILITY Link : public Tool { public: Link(const ToolChain &TC) : Tool("visualstudio::Link", "linker", TC) {} @@ -603,6 +604,27 @@ namespace visualstudio { const llvm::opt::ArgList &TCArgs, const char *LinkingOutput) const; }; + + class LLVM_LIBRARY_VISIBILITY Compile : public Tool { + public: + Compile(const ToolChain &TC) : Tool("visualstudio::Compile", "compiler", TC) {} + + virtual bool hasIntegratedAssembler() const { return true; } + virtual bool hasIntegratedCPP() const { return true; } + virtual bool isLinkJob() const { return false; } + + virtual void ConstructJob(Compilation &C, const JobAction &JA, + const InputInfo &Output, + const InputInfoList &Inputs, + const llvm::opt::ArgList &TCArgs, + const char *LinkingOutput) const; + + Command *GetCommand(Compilation &C, const JobAction &JA, + const InputInfo &Output, + const InputInfoList &Inputs, + const llvm::opt::ArgList &TCArgs, + const char *LinkingOutput) const; + }; } // end namespace visualstudio } // end namespace toolchains diff --git a/test/Driver/cl-fallback.c b/test/Driver/cl-fallback.c new file mode 100644 index 0000000000..ce7e245adf --- /dev/null +++ b/test/Driver/cl-fallback.c @@ -0,0 +1,22 @@ +// Don't attempt slash switches on msys bash. +// REQUIRES: shell-preserves-root + +// Note: %s must be preceded by --, otherwise it may be interpreted as a +// command-line option, e.g. on Mac where %s is commonly under /Users. + +// RUN: %clang_cl /fallback /Dfoo=bar /Ubaz /Ifoo /O0 /Ox /GR /GR- /LD /LDd \ +// RUN: /MD /MDd /MTd /MT -### -- %s 2>&1 | FileCheck %s +// CHECK: || +// CHECK: cl.exe +// CHECK: "/c" +// CHECK: "/W0" +// CHECK: "-D" "foo=bar" +// CHECK: "-U" "baz" +// CHECK: "-I" "foo" +// CHECK: "-O3" +// CHECK: "/GR-" +// CHECK: "/LD" +// CHECK: "/LDd" +// CHECK: "/MT" +// CHECK: "/Tc" "{{.*cl-fallback.c}}" +// CHECK: "/Fo{{.*cl-fallback.*.obj}}" -- 2.50.1