diff --git a/test/LLVMC/HookWithArguments.td b/test/LLVMC/HookWithArguments.td new file mode 100644 index 00000000000..0f7c3797cb4 --- /dev/null +++ b/test/LLVMC/HookWithArguments.td @@ -0,0 +1,16 @@ +// Check that hooks with arguments work. +// RUN: tblgen -I $srcroot/include --gen-llvmc %s -o %t +// RUN: grep {Hook(const char\\* Arg0, const char\\* Arg1, const char\\* Arg2);} %t | count 1 +// RUN: grep "/path" %t | count 1 +// RUN: grep "VARIABLE" %t | count 1 +// RUN: grep "/2path" %t | count 1 + +include "llvm/CompilerDriver/Common.td" + +def dummy_tool : Tool<[ +(cmd_line "$CALL(Hook, 'Arg1', 'Arg2', 'Arg3 Arg3Cont')/path arg1 $ENV(VARIABLE)/2path arg2 $INFILE"), +(in_language "dummy"), +(out_language "dummy") +]>; + +def DummyGraph : CompilationGraph<[SimpleEdge<"root", "dummy_tool">]>; diff --git a/tools/llvmc/doc/LLVMC-Reference.rst b/tools/llvmc/doc/LLVMC-Reference.rst index d70b4a55988..9eb927c1658 100644 --- a/tools/llvmc/doc/LLVMC-Reference.rst +++ b/tools/llvmc/doc/LLVMC-Reference.rst @@ -560,16 +560,21 @@ Hooks and environment variables ------------------------------- Normally, LLVMC executes programs from the system ``PATH``. Sometimes, -this is not sufficient: for example, we may want to specify tool names -in the configuration file. This can be achieved via the mechanism of -hooks - to write your own hooks, just add their definitions to the -``PluginMain.cpp`` or drop a ``.cpp`` file into the -``$LLVMC_DIR/driver`` directory. Hooks should live in the ``hooks`` -namespace and have the signature ``std::string hooks::MyHookName -(void)``. They can be used from the ``cmd_line`` tool property:: +this is not sufficient: for example, we may want to specify tool paths +or names in the configuration file. This can be easily achieved via +the hooks mechanism. To write your own hooks, just add their +definitions to the ``PluginMain.cpp`` or drop a ``.cpp`` file into the +your plugin directory. Hooks should live in the ``hooks`` namespace +and have the signature ``const char* hooks::MyHookName ([const char* +Arg0 [ const char* Arg2 [, ...]]])``. They can be used from the +``cmd_line`` tool property:: (cmd_line "$CALL(MyHook)/path/to/file -o $CALL(AnotherHook)") +To pass arguments to hooks, use the following syntax:: + + (cmd_line "$CALL(MyHook, 'Arg1', 'Arg2', 'Arg # 3')/path/to/file -o1 -o2") + It is also possible to use environment variables in the same manner:: (cmd_line "$ENV(VAR1)/path/to/file -o $ENV(VAR2)") diff --git a/utils/TableGen/LLVMCConfigurationEmitter.cpp b/utils/TableGen/LLVMCConfigurationEmitter.cpp index c97d25785cc..3de4d37f25e 100644 --- a/utils/TableGen/LLVMCConfigurationEmitter.cpp +++ b/utils/TableGen/LLVMCConfigurationEmitter.cpp @@ -118,6 +118,15 @@ std::string EscapeVariableName(const std::string& Var) { return ret; } +/// oneOf - Does the input string contain this character? +bool oneOf(const char* lst, char c) { + while (*lst) { + if (*lst++ == c) + return true; + } + return false; +} + //===----------------------------------------------------------------------===// /// Back-end specific code @@ -1041,39 +1050,157 @@ void EmitCaseConstructHandler(const Init* Dag, const char* IndentLevel, } } +/// TokenizeCmdline - converts from "$CALL(HookName, 'Arg1', 'Arg2')/path" to +/// ["$CALL(", "HookName", "Arg1", "Arg2", ")/path"] . +/// Helper function used by EmitCmdLineVecFill and. +void TokenizeCmdline(const std::string& CmdLine, StrVector& Out) { + const char* Delimiters = " \t\n\v\f\r"; + enum TokenizerState + { Normal, SpecialCommand, InsideSpecialCommand, InsideQuotationMarks } + cur_st = Normal; + Out.push_back(""); + + std::string::size_type B = CmdLine.find_first_not_of(Delimiters), + E = CmdLine.size(); + if (B == std::string::npos) + throw "Empty command-line string!"; + for (; B != E; ++B) { + char cur_ch = CmdLine[B]; + + switch (cur_st) { + case Normal: + if (cur_ch == '$') { + cur_st = SpecialCommand; + break; + } + if (oneOf(Delimiters, cur_ch)) { + // Skip whitespace + B = CmdLine.find_first_not_of(Delimiters, B); + if (B == std::string::npos) { + B = E-1; + continue; + } + --B; + Out.push_back(""); + continue; + } + break; + + + case SpecialCommand: + if (oneOf(Delimiters, cur_ch)) { + cur_st = Normal; + Out.push_back(""); + continue; + } + if (cur_ch == '(') { + Out.push_back(""); + cur_st = InsideSpecialCommand; + continue; + } + break; + + case InsideSpecialCommand: + if (oneOf(Delimiters, cur_ch)) { + continue; + } + if (cur_ch == '\'') { + cur_st = InsideQuotationMarks; + Out.push_back(""); + continue; + } + if (cur_ch == ')') { + cur_st = Normal; + Out.push_back(""); + } + if (cur_ch == ',') { + continue; + } + + break; + + case InsideQuotationMarks: + if (cur_ch == '\'') { + cur_st = InsideSpecialCommand; + continue; + } + break; + } + + Out.back().push_back(cur_ch); + } +} + +template +void checkedIncrement(I& P, I E, S ErrorString) { + ++P; + if (P == E) + throw ErrorString; +} + /// SubstituteSpecialCommands - Perform string substitution for $CALL /// and $ENV. Helper function used by EmitCmdLineVecFill(). -std::string SubstituteSpecialCommands(const std::string& cmd) { - size_t cparen = cmd.find(")"); - std::string ret; +StrVector::const_iterator SubstituteSpecialCommands +(StrVector::const_iterator Pos, StrVector::const_iterator End, std::ostream& O) +{ - if (cmd.find("$CALL(") == 0) { - if (cmd.size() == 6) + const std::string& cmd = *Pos; + + if (cmd == "$CALL") { + checkedIncrement(Pos, End, "Syntax error in $CALL invocation!"); + const std::string& CmdName = *Pos; + + if (CmdName == ")") throw std::string("$CALL invocation: empty argument list!"); - ret += "hooks::"; - ret += std::string(cmd.begin() + 6, cmd.begin() + cparen); - ret += "()"; - } - else if (cmd.find("$ENV(") == 0) { - if (cmd.size() == 5) - throw std::string("$ENV invocation: empty argument list!"); + O << "hooks::"; + O << CmdName << "("; - ret += "checkCString(std::getenv(\""; - ret += std::string(cmd.begin() + 5, cmd.begin() + cparen); - ret += "\"))"; + + bool firstIteration = true; + while (true) { + checkedIncrement(Pos, End, "Syntax error in $CALL invocation!"); + const std::string& Arg = *Pos; + assert(Arg.size() != 0); + + if (Arg[0] == ')') + break; + + if (firstIteration) + firstIteration = false; + else + O << ", "; + + O << '"' << Arg << '"'; + } + + O << ')'; + + } + else if (cmd == "$ENV") { + checkedIncrement(Pos, End, "Syntax error in $ENV invocation!"); + const std::string& EnvName = *Pos; + + if (EnvName == ")") + throw "$ENV invocation: empty argument list!"; + + O << "checkCString(std::getenv(\""; + O << EnvName; + O << "\"))"; + + checkedIncrement(Pos, End, "Syntax error in $ENV invocation!"); } else { throw "Unknown special command: " + cmd; } - if (cmd.begin() + cparen + 1 != cmd.end()) { - ret += " + std::string(\""; - ret += (cmd.c_str() + cparen + 1); - ret += "\")"; - } + const std::string& Leftover = *Pos; + assert(Leftover.at(0) == ')'); + if (Leftover.size() != 1) + O << " + std::string(\"" << (Leftover.c_str() + 1) << "\")"; + O << ')'; - return ret; + return Pos; } /// EmitCmdLineVecFill - Emit code that fills in the command line @@ -1082,14 +1209,28 @@ void EmitCmdLineVecFill(const Init* CmdLine, const std::string& ToolName, bool IsJoin, const char* IndentLevel, std::ostream& O) { StrVector StrVec; - SplitString(InitPtrToString(CmdLine), StrVec); + TokenizeCmdline(InitPtrToString(CmdLine), StrVec); + if (StrVec.empty()) throw "Tool " + ToolName + " has empty command line!"; - StrVector::const_iterator I = StrVec.begin(); - ++I; - for (StrVector::const_iterator E = StrVec.end(); I != E; ++I) { + StrVector::const_iterator I = StrVec.begin(), E = StrVec.end(); + + // If there is a hook invocation on the place of the first command, skip it. + if (StrVec[0][0] == '$') { + while (I != E && (*I)[0] != ')' ) + ++I; + + // Skip the ')' symbol. + ++I; + } + else { + ++I; + } + + for (; I != E; ++I) { const std::string& cmd = *I; + // std::cerr << cmd; O << IndentLevel; if (cmd.at(0) == '$') { if (cmd == "$INFILE") { @@ -1105,7 +1246,8 @@ void EmitCmdLineVecFill(const Init* CmdLine, const std::string& ToolName, O << "vec.push_back(out_file);\n"; } else { - O << "vec.push_back(" << SubstituteSpecialCommands(cmd); + O << "vec.push_back("; + I = SubstituteSpecialCommands(I, E, O); O << ");\n"; } } @@ -1113,10 +1255,13 @@ void EmitCmdLineVecFill(const Init* CmdLine, const std::string& ToolName, O << "vec.push_back(\"" << cmd << "\");\n"; } } - O << IndentLevel << "cmd = " - << ((StrVec[0][0] == '$') ? SubstituteSpecialCommands(StrVec[0]) - : "\"" + StrVec[0] + "\"") - << ";\n"; + O << IndentLevel << "cmd = "; + + if (StrVec[0][0] == '$') + SubstituteSpecialCommands(StrVec.begin(), StrVec.end(), O); + else + O << '"' << StrVec[0] << '"'; + O << ";\n"; } /// EmitCmdLineVecFillCallback - A function object wrapper around @@ -1650,22 +1795,39 @@ void EmitPopulateCompilationGraph (const RecordVector& EdgeVector, /// $CALL(HookName) in the provided command line string. Helper /// function used by FillInHookNames(). class ExtractHookNames { - llvm::StringSet<>& HookNames_; + llvm::StringMap& HookNames_; public: - ExtractHookNames(llvm::StringSet<>& HookNames) - : HookNames_(HookNames_) {} + ExtractHookNames(llvm::StringMap& HookNames) + : HookNames_(HookNames) {} void operator()(const Init* CmdLine) { StrVector cmds; - llvm::SplitString(InitPtrToString(CmdLine), cmds); + TokenizeCmdline(InitPtrToString(CmdLine), cmds); for (StrVector::const_iterator B = cmds.begin(), E = cmds.end(); B != E; ++B) { const std::string& cmd = *B; - if (cmd.find("$CALL(") == 0) { - if (cmd.size() == 6) - throw std::string("$CALL invocation: empty argument list!"); - HookNames_.insert(std::string(cmd.begin() + 6, - cmd.begin() + cmd.find(")"))); + + if (cmd == "$CALL") { + unsigned NumArgs = 0; + checkedIncrement(B, E, "Syntax error in $CALL invocation!"); + const std::string& HookName = *B; + + + if (HookName.at(0) == ')') + throw "$CALL invoked with no arguments!"; + + while (++B != E && B->at(0) != ')') { + ++NumArgs; + } + + StringMap::const_iterator H = HookNames_.find(HookName); + + if (H != HookNames_.end() && H->second != NumArgs) + throw "Overloading of hooks is not allowed. Overloaded hook: " + + HookName; + else + HookNames_[HookName] = NumArgs; + } } } @@ -1674,7 +1836,7 @@ public: /// FillInHookNames - Actually extract the hook names from all command /// line strings. Helper function used by EmitHookDeclarations(). void FillInHookNames(const ToolDescriptions& ToolDescs, - llvm::StringSet<>& HookNames) + llvm::StringMap& HookNames) { // For all command lines: for (ToolDescriptions::const_iterator B = ToolDescs.begin(), @@ -1695,16 +1857,23 @@ void FillInHookNames(const ToolDescriptions& ToolDescs, /// property records and emit hook function declaration for each /// instance of $CALL(HookName). void EmitHookDeclarations(const ToolDescriptions& ToolDescs, std::ostream& O) { - llvm::StringSet<> HookNames; + llvm::StringMap HookNames; + FillInHookNames(ToolDescs, HookNames); if (HookNames.empty()) return; O << "namespace hooks {\n"; - for (StringSet<>::const_iterator B = HookNames.begin(), E = HookNames.end(); - B != E; ++B) - O << Indent1 << "std::string " << B->first() << "();\n"; + for (StringMap::const_iterator B = HookNames.begin(), + E = HookNames.end(); B != E; ++B) { + O << Indent1 << "const char* " << B->first() << "("; + for (unsigned i = 0, j = B->second; i < j; ++i) { + O << "const char* Arg" << i << (i+1 == j ? "" : ", "); + } + + O <<");\n"; + } O << "}\n\n"; }