diff options
Diffstat (limited to 'utils')
63 files changed, 6146 insertions, 799 deletions
diff --git a/utils/TableGen/CMakeLists.txt b/utils/TableGen/CMakeLists.txt index daf8676..ce9b66f 100644 --- a/utils/TableGen/CMakeLists.txt +++ b/utils/TableGen/CMakeLists.txt @@ -23,6 +23,8 @@ add_executable(tblgen TGValueTypes.cpp TableGen.cpp TableGenBackend.cpp + X86DisassemblerTables.cpp + X86RecognizableInstr.cpp ) target_link_libraries(tblgen LLVMSupport LLVMSystem) diff --git a/utils/TableGen/CodeEmitterGen.cpp b/utils/TableGen/CodeEmitterGen.cpp index e9f30be..7e6c769 100644 --- a/utils/TableGen/CodeEmitterGen.cpp +++ b/utils/TableGen/CodeEmitterGen.cpp @@ -61,11 +61,14 @@ void CodeEmitterGen::reverseBits(std::vector<Record*> &Insts) { // If the VarBitInit at position 'bit' matches the specified variable then // return the variable bit position. Otherwise return -1. -int CodeEmitterGen::getVariableBit(const Init *VarVal, +int CodeEmitterGen::getVariableBit(const std::string &VarName, BitsInit *BI, int bit) { if (VarBitInit *VBI = dynamic_cast<VarBitInit*>(BI->getBit(bit))) { TypedInit *TI = VBI->getVariable(); - if (TI == VarVal) return VBI->getBitNum(); + + if (VarInit *VI = dynamic_cast<VarInit*>(TI)) { + if (VI->getName() == VarName) return VBI->getBitNum(); + } } return -1; @@ -159,11 +162,11 @@ void CodeEmitterGen::run(raw_ostream &o) { if (!Vals[i].getPrefix() && !Vals[i].getValue()->isComplete()) { // Is the operand continuous? If so, we can just mask and OR it in // instead of doing it bit-by-bit, saving a lot in runtime cost. - const Init *VarVal = Vals[i].getValue(); + const std::string &VarName = Vals[i].getName(); bool gotOp = false; for (int bit = BI->getNumBits()-1; bit >= 0; ) { - int varBit = getVariableBit(VarVal, BI, bit); + int varBit = getVariableBit(VarName, BI, bit); if (varBit == -1) { --bit; @@ -173,7 +176,7 @@ void CodeEmitterGen::run(raw_ostream &o) { int N = 1; for (--bit; bit >= 0;) { - varBit = getVariableBit(VarVal, BI, bit); + varBit = getVariableBit(VarName, BI, bit); if (varBit == -1 || varBit != (beginVarBit - N)) break; ++N; --bit; @@ -185,7 +188,7 @@ void CodeEmitterGen::run(raw_ostream &o) { while (CGI.isFlatOperandNotEmitted(op)) ++op; - Case += " // op: " + Vals[i].getName() + "\n" + Case += " // op: " + VarName + "\n" + " op = getMachineOpValue(MI, MI.getOperand(" + utostr(op++) + "));\n"; gotOp = true; diff --git a/utils/TableGen/CodeEmitterGen.h b/utils/TableGen/CodeEmitterGen.h index 2dc34ba..f0b3229 100644 --- a/utils/TableGen/CodeEmitterGen.h +++ b/utils/TableGen/CodeEmitterGen.h @@ -23,7 +23,6 @@ namespace llvm { class RecordVal; class BitsInit; -struct Init; class CodeEmitterGen : public TableGenBackend { RecordKeeper &Records; @@ -36,7 +35,7 @@ private: void emitMachineOpEmitter(raw_ostream &o, const std::string &Namespace); void emitGetValueBit(raw_ostream &o, const std::string &Namespace); void reverseBits(std::vector<Record*> &Insts); - int getVariableBit(const Init *VarVal, BitsInit *BI, int bit); + int getVariableBit(const std::string &VarName, BitsInit *BI, int bit); }; } // End llvm namespace diff --git a/utils/TableGen/CodeGenDAGPatterns.cpp b/utils/TableGen/CodeGenDAGPatterns.cpp index fab41c5..cf79365 100644 --- a/utils/TableGen/CodeGenDAGPatterns.cpp +++ b/utils/TableGen/CodeGenDAGPatterns.cpp @@ -321,8 +321,7 @@ bool SDTypeConstraint::ApplyTypeConstraint(TreePatternNode *N, getOperandNum(x.SDTCisVTSmallerThanOp_Info.OtherOperandNum, N,NumResults); // It must be integer. - bool MadeChange = false; - MadeChange |= OtherNode->UpdateNodeType(MVT::iAny, TP); + bool MadeChange = OtherNode->UpdateNodeType(MVT::iAny, TP); // This code only handles nodes that have one type set. Assert here so // that we can change this if we ever need to deal with multiple value @@ -330,7 +329,7 @@ bool SDTypeConstraint::ApplyTypeConstraint(TreePatternNode *N, assert(OtherNode->getExtTypes().size() == 1 && "Node has too many types!"); if (OtherNode->hasTypeSet() && OtherNode->getTypeNum(0) <= VT) OtherNode->UpdateNodeType(MVT::Other, TP); // Throw an error. - return false; + return MadeChange; } case SDTCisOpSmallerThanOp: { TreePatternNode *BigOperand = diff --git a/utils/TableGen/CodeGenInstruction.cpp b/utils/TableGen/CodeGenInstruction.cpp index 8520d9e..c69ce96 100644 --- a/utils/TableGen/CodeGenInstruction.cpp +++ b/utils/TableGen/CodeGenInstruction.cpp @@ -18,36 +18,56 @@ using namespace llvm; static void ParseConstraint(const std::string &CStr, CodeGenInstruction *I) { - // FIXME: Only supports TIED_TO for now. + // EARLY_CLOBBER: @early $reg + std::string::size_type wpos = CStr.find_first_of(" \t"); + std::string::size_type start = CStr.find_first_not_of(" \t"); + std::string Tok = CStr.substr(start, wpos - start); + if (Tok == "@earlyclobber") { + std::string Name = CStr.substr(wpos+1); + wpos = Name.find_first_not_of(" \t"); + if (wpos == std::string::npos) + throw "Illegal format for @earlyclobber constraint: '" + CStr + "'"; + Name = Name.substr(wpos); + std::pair<unsigned,unsigned> Op = + I->ParseOperandName(Name, false); + + // Build the string for the operand + std::string OpConstraint = "(1 << TOI::EARLY_CLOBBER)"; + if (!I->OperandList[Op.first].Constraints[Op.second].empty()) + throw "Operand '" + Name + "' cannot have multiple constraints!"; + I->OperandList[Op.first].Constraints[Op.second] = OpConstraint; + return; + } + + // Only other constraint is "TIED_TO" for now. std::string::size_type pos = CStr.find_first_of('='); assert(pos != std::string::npos && "Unrecognized constraint"); - std::string::size_type start = CStr.find_first_not_of(" \t"); + start = CStr.find_first_not_of(" \t"); std::string Name = CStr.substr(start, pos - start); - + // TIED_TO: $src1 = $dst - std::string::size_type wpos = Name.find_first_of(" \t"); + wpos = Name.find_first_of(" \t"); if (wpos == std::string::npos) throw "Illegal format for tied-to constraint: '" + CStr + "'"; std::string DestOpName = Name.substr(0, wpos); std::pair<unsigned,unsigned> DestOp = I->ParseOperandName(DestOpName, false); - + Name = CStr.substr(pos+1); wpos = Name.find_first_not_of(" \t"); if (wpos == std::string::npos) throw "Illegal format for tied-to constraint: '" + CStr + "'"; - + std::pair<unsigned,unsigned> SrcOp = I->ParseOperandName(Name.substr(wpos), false); if (SrcOp > DestOp) throw "Illegal tied-to operand constraint '" + CStr + "'"; - - + + unsigned FlatOpNo = I->getFlattenedOperandNumber(SrcOp); // Build the string for the operand. std::string OpConstraint = "((" + utostr(FlatOpNo) + " << 16) | (1 << TOI::TIED_TO))"; - - + if (!I->OperandList[DestOp.first].Constraints[DestOp.second].empty()) throw "Operand '" + DestOpName + "' cannot have multiple constraints!"; I->OperandList[DestOp.first].Constraints[DestOp.second] = OpConstraint; @@ -56,20 +76,20 @@ static void ParseConstraint(const std::string &CStr, CodeGenInstruction *I) { static void ParseConstraints(const std::string &CStr, CodeGenInstruction *I) { // Make sure the constraints list for each operand is large enough to hold // constraint info, even if none is present. - for (unsigned i = 0, e = I->OperandList.size(); i != e; ++i) + for (unsigned i = 0, e = I->OperandList.size(); i != e; ++i) I->OperandList[i].Constraints.resize(I->OperandList[i].MINumOperands); - + if (CStr.empty()) return; - + const std::string delims(","); std::string::size_type bidx, eidx; - + bidx = CStr.find_first_not_of(delims); while (bidx != std::string::npos) { eidx = CStr.find_first_of(delims, bidx); if (eidx == std::string::npos) eidx = CStr.length(); - + ParseConstraint(CStr.substr(bidx, eidx - bidx), I); bidx = CStr.find_first_not_of(delims, eidx); } @@ -145,7 +165,7 @@ CodeGenInstruction::CodeGenInstruction(Record *R, const std::string &AsmStr) if (Rec->isSubClassOf("Operand")) { PrintMethod = Rec->getValueAsString("PrintMethod"); MIOpInfo = Rec->getValueAsDag("MIOperandInfo"); - + // Verify that MIOpInfo has an 'ops' root value. if (!dynamic_cast<DefInit*>(MIOpInfo->getOperator()) || dynamic_cast<DefInit*>(MIOpInfo->getOperator()) @@ -165,7 +185,7 @@ CodeGenInstruction::CodeGenInstruction(Record *R, const std::string &AsmStr) } else if (Rec->getName() == "variable_ops") { isVariadic = true; continue; - } else if (!Rec->isSubClassOf("RegisterClass") && + } else if (!Rec->isSubClassOf("RegisterClass") && Rec->getName() != "ptr_rc" && Rec->getName() != "unknown") throw "Unknown operand class '" + Rec->getName() + "' in '" + R->getName() + "' instruction!"; @@ -177,15 +197,15 @@ CodeGenInstruction::CodeGenInstruction(Record *R, const std::string &AsmStr) if (!OperandNames.insert(DI->getArgName(i)).second) throw "In instruction '" + R->getName() + "', operand #" + utostr(i) + " has the same name as a previous operand!"; - - OperandList.push_back(OperandInfo(Rec, DI->getArgName(i), PrintMethod, + + OperandList.push_back(OperandInfo(Rec, DI->getArgName(i), PrintMethod, MIOperandNo, NumOps, MIOpInfo)); MIOperandNo += NumOps; } // Parse Constraints. ParseConstraints(R->getValueAsString("Constraints"), this); - + // For backward compatibility: isTwoAddress means operand 1 is tied to // operand 0. if (isTwoAddress) { @@ -194,13 +214,13 @@ CodeGenInstruction::CodeGenInstruction(Record *R, const std::string &AsmStr) "already has constraint set!"; OperandList[1].Constraints[0] = "((0 << 16) | (1 << TOI::TIED_TO))"; } - + // Any operands with unset constraints get 0 as their constraint. for (unsigned op = 0, e = OperandList.size(); op != e; ++op) for (unsigned j = 0, e = OperandList[op].MINumOperands; j != e; ++j) if (OperandList[op].Constraints[j].empty()) OperandList[op].Constraints[j] = "0"; - + // Parse the DisableEncoding field. std::string DisableEncoding = R->getValueAsString("DisableEncoding"); while (1) { @@ -229,15 +249,15 @@ unsigned CodeGenInstruction::getOperandNamed(const std::string &Name) const { "' does not have an operand named '$" + Name + "'!"; } -std::pair<unsigned,unsigned> +std::pair<unsigned,unsigned> CodeGenInstruction::ParseOperandName(const std::string &Op, bool AllowWholeOp) { if (Op.empty() || Op[0] != '$') throw TheDef->getName() + ": Illegal operand name: '" + Op + "'"; - + std::string OpName = Op.substr(1); std::string SubOpName; - + // Check to see if this is $foo.bar. std::string::size_type DotIdx = OpName.find_first_of("."); if (DotIdx != std::string::npos) { @@ -246,7 +266,7 @@ CodeGenInstruction::ParseOperandName(const std::string &Op, throw TheDef->getName() + ": illegal empty suboperand name in '" +Op +"'"; OpName = OpName.substr(0, DotIdx); } - + unsigned OpIdx = getOperandNamed(OpName); if (SubOpName.empty()) { // If no suboperand name was specified: @@ -255,16 +275,16 @@ CodeGenInstruction::ParseOperandName(const std::string &Op, SubOpName.empty()) throw TheDef->getName() + ": Illegal to refer to" " whole operand part of complex operand '" + Op + "'"; - + // Otherwise, return the operand. return std::make_pair(OpIdx, 0U); } - + // Find the suboperand number involved. DagInit *MIOpInfo = OperandList[OpIdx].MIOperandInfo; if (MIOpInfo == 0) throw TheDef->getName() + ": unknown suboperand name in '" + Op + "'"; - + // Find the operand with the right name. for (unsigned i = 0, e = MIOpInfo->getNumArgs(); i != e; ++i) if (MIOpInfo->getArgName(i) == SubOpName) diff --git a/utils/TableGen/DAGISelEmitter.cpp b/utils/TableGen/DAGISelEmitter.cpp index 66debe2..a901fd0 100644 --- a/utils/TableGen/DAGISelEmitter.cpp +++ b/utils/TableGen/DAGISelEmitter.cpp @@ -1292,8 +1292,8 @@ public: // possible and it avoids CSE map recalculation for the node's // users, however it's tricky to use in a non-root context. // - // We also don't use if the pattern replacement is being used to - // jettison a chain result, since morphing the node in place + // We also don't use SelectNodeTo if the pattern replacement is being + // used to jettison a chain result, since morphing the node in place // would leave users of the chain dangling. // if (!isRoot || (InputHasChain && !NodeHasChain)) { diff --git a/utils/TableGen/DisassemblerEmitter.cpp b/utils/TableGen/DisassemblerEmitter.cpp index cc13125..61b9b15 100644 --- a/utils/TableGen/DisassemblerEmitter.cpp +++ b/utils/TableGen/DisassemblerEmitter.cpp @@ -10,7 +10,86 @@ #include "DisassemblerEmitter.h" #include "CodeGenTarget.h" #include "Record.h" +#include "X86DisassemblerTables.h" +#include "X86RecognizableInstr.h" using namespace llvm; +using namespace llvm::X86Disassembler; + +/// DisassemblerEmitter - Contains disassembler table emitters for various +/// architectures. + +/// X86 Disassembler Emitter +/// +/// *** IF YOU'RE HERE TO RESOLVE A "Primary decode conflict", LOOK DOWN NEAR +/// THE END OF THIS COMMENT! +/// +/// The X86 disassembler emitter is part of the X86 Disassembler, which is +/// documented in lib/Target/X86/X86Disassembler.h. +/// +/// The emitter produces the tables that the disassembler uses to translate +/// instructions. The emitter generates the following tables: +/// +/// - One table (CONTEXTS_SYM) that contains a mapping of attribute masks to +/// instruction contexts. Although for each attribute there are cases where +/// that attribute determines decoding, in the majority of cases decoding is +/// the same whether or not an attribute is present. For example, a 64-bit +/// instruction with an OPSIZE prefix and an XS prefix decodes the same way in +/// all cases as a 64-bit instruction with only OPSIZE set. (The XS prefix +/// may have effects on its execution, but does not change the instruction +/// returned.) This allows considerable space savings in other tables. +/// - Four tables (ONEBYTE_SYM, TWOBYTE_SYM, THREEBYTE38_SYM, and +/// THREEBYTE3A_SYM) contain the hierarchy that the decoder traverses while +/// decoding an instruction. At the lowest level of this hierarchy are +/// instruction UIDs, 16-bit integers that can be used to uniquely identify +/// the instruction and correspond exactly to its position in the list of +/// CodeGenInstructions for the target. +/// - One table (INSTRUCTIONS_SYM) contains information about the operands of +/// each instruction and how to decode them. +/// +/// During table generation, there may be conflicts between instructions that +/// occupy the same space in the decode tables. These conflicts are resolved as +/// follows in setTableFields() (X86DisassemblerTables.cpp) +/// +/// - If the current context is the native context for one of the instructions +/// (that is, the attributes specified for it in the LLVM tables specify +/// precisely the current context), then it has priority. +/// - If the current context isn't native for either of the instructions, then +/// the higher-priority context wins (that is, the one that is more specific). +/// That hierarchy is determined by outranks() (X86DisassemblerTables.cpp) +/// - If the current context is native for both instructions, then the table +/// emitter reports a conflict and dies. +/// +/// *** RESOLUTION FOR "Primary decode conflict"S +/// +/// If two instructions collide, typically the solution is (in order of +/// likelihood): +/// +/// (1) to filter out one of the instructions by editing filter() +/// (X86RecognizableInstr.cpp). This is the most common resolution, but +/// check the Intel manuals first to make sure that (2) and (3) are not the +/// problem. +/// (2) to fix the tables (X86.td and its subsidiaries) so the opcodes are +/// accurate. Sometimes they are not. +/// (3) to fix the tables to reflect the actual context (for example, required +/// prefixes), and possibly to add a new context by editing +/// lib/Target/X86/X86DisassemblerDecoderCommon.h. This is unlikely to be +/// the cause. +/// +/// DisassemblerEmitter.cpp contains the implementation for the emitter, +/// which simply pulls out instructions from the CodeGenTarget and pushes them +/// into X86DisassemblerTables. +/// X86DisassemblerTables.h contains the interface for the instruction tables, +/// which manage and emit the structures discussed above. +/// X86DisassemblerTables.cpp contains the implementation for the instruction +/// tables. +/// X86ModRMFilters.h contains filters that can be used to determine which +/// ModR/M values are valid for a particular instruction. These are used to +/// populate ModRMDecisions. +/// X86RecognizableInstr.h contains the interface for a single instruction, +/// which knows how to translate itself from a CodeGenInstruction and provide +/// the information necessary for integration into the tables. +/// X86RecognizableInstr.cpp contains the implementation for a single +/// instruction. void DisassemblerEmitter::run(raw_ostream &OS) { CodeGenTarget Target; @@ -25,6 +104,26 @@ void DisassemblerEmitter::run(raw_ostream &OS) { << " *===---------------------------------------------------------------" << "-------===*/\n"; + // X86 uses a custom disassembler. + if (Target.getName() == "X86") { + DisassemblerTables Tables; + + std::vector<const CodeGenInstruction*> numberedInstructions; + Target.getInstructionsByEnumValue(numberedInstructions); + + for (unsigned i = 0, e = numberedInstructions.size(); i != e; ++i) + RecognizableInstr::processInstr(Tables, *numberedInstructions[i], i); + + // FIXME: As long as we are using exceptions, might as well drop this to the + // actual conflict site. + if (Tables.hasConflicts()) + throw TGError(Target.getTargetRecord()->getLoc(), + "Primary decode conflict"); + + Tables.emit(OS); + return; + } + throw TGError(Target.getTargetRecord()->getLoc(), "Unable to generate disassembler for this target"); } diff --git a/utils/TableGen/LLVMCConfigurationEmitter.cpp b/utils/TableGen/LLVMCConfigurationEmitter.cpp index 5be9ab7..88fb6c3 100644 --- a/utils/TableGen/LLVMCConfigurationEmitter.cpp +++ b/utils/TableGen/LLVMCConfigurationEmitter.cpp @@ -17,6 +17,7 @@ #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringSet.h" + #include <algorithm> #include <cassert> #include <functional> @@ -26,6 +27,7 @@ using namespace llvm; +namespace { //===----------------------------------------------------------------------===// /// Typedefs @@ -37,18 +39,16 @@ typedef std::vector<std::string> StrVector; /// Constants // Indentation. -static const unsigned TabWidth = 4; -static const unsigned Indent1 = TabWidth*1; -static const unsigned Indent2 = TabWidth*2; -static const unsigned Indent3 = TabWidth*3; +const unsigned TabWidth = 4; +const unsigned Indent1 = TabWidth*1; +const unsigned Indent2 = TabWidth*2; +const unsigned Indent3 = TabWidth*3; // Default help string. -static const char * const DefaultHelpString = "NO HELP MESSAGE PROVIDED"; +const char * const DefaultHelpString = "NO HELP MESSAGE PROVIDED"; // Name for the "sink" option. -static const char * const SinkOptionName = "AutoGeneratedSinkOption"; - -namespace { +const char * const SinkOptionName = "AutoGeneratedSinkOption"; //===----------------------------------------------------------------------===// /// Helper functions @@ -86,26 +86,30 @@ const DagInit& InitPtrToDag(const Init* ptr) { return val; } -const std::string GetOperatorName(const DagInit* D) { - return D->getOperator()->getAsString(); +const std::string GetOperatorName(const DagInit& D) { + return D.getOperator()->getAsString(); } -const std::string GetOperatorName(const DagInit& D) { - return GetOperatorName(&D); +/// CheckBooleanConstant - Check that the provided value is a boolean constant. +void CheckBooleanConstant(const Init* I) { + const DefInit& val = dynamic_cast<const DefInit&>(*I); + const std::string& str = val.getAsString(); + + if (str != "true" && str != "false") { + throw "Incorrect boolean value: '" + str + + "': must be either 'true' or 'false'"; + } } -// checkNumberOfArguments - Ensure that the number of args in d is +// CheckNumberOfArguments - Ensure that the number of args in d is // greater than or equal to min_arguments, otherwise throw an exception. -void checkNumberOfArguments (const DagInit* d, unsigned minArgs) { - if (!d || d->getNumArgs() < minArgs) +void CheckNumberOfArguments (const DagInit& d, unsigned minArgs) { + if (d.getNumArgs() < minArgs) throw GetOperatorName(d) + ": too few arguments!"; } -void checkNumberOfArguments (const DagInit& d, unsigned minArgs) { - checkNumberOfArguments(&d, minArgs); -} -// isDagEmpty - is this DAG marked with an empty marker? -bool isDagEmpty (const DagInit* d) { +// IsDagEmpty - is this DAG marked with an empty marker? +bool IsDagEmpty (const DagInit& d) { return GetOperatorName(d) == "empty_dag_marker"; } @@ -132,8 +136,8 @@ std::string EscapeVariableName(const std::string& Var) { return ret; } -/// oneOf - Does the input string contain this character? -bool oneOf(const char* lst, char c) { +/// OneOf - Does the input string contain this character? +bool OneOf(const char* lst, char c) { while (*lst) { if (*lst++ == c) return true; @@ -142,7 +146,7 @@ bool oneOf(const char* lst, char c) { } template <class I, class S> -void checkedIncrement(I& P, I E, S ErrorString) { +void CheckedIncrement(I& P, I E, S ErrorString) { ++P; if (P == E) throw ErrorString; @@ -499,17 +503,36 @@ public: }; +template <class Handler, class FunctionObject> +Handler GetHandler(FunctionObject* Obj, const DagInit& Dag) { + const std::string& HandlerName = GetOperatorName(Dag); + return Obj->GetHandler(HandlerName); +} + +template <class FunctionObject> +void InvokeDagInitHandler(FunctionObject* Obj, Init* I) { + typedef void (FunctionObject::*Handler) (const DagInit&); + + const DagInit& Dag = InitPtrToDag(I); + Handler h = GetHandler<Handler>(Obj, Dag); + + ((Obj)->*(h))(Dag); +} + template <class FunctionObject> -void InvokeDagInitHandler(FunctionObject* Obj, Init* i) { - typedef void (FunctionObject::*Handler) (const DagInit*); +void InvokeDagInitHandler(const FunctionObject* const Obj, + const Init* I, unsigned IndentLevel, raw_ostream& O) +{ + typedef void (FunctionObject::*Handler) + (const DagInit&, unsigned IndentLevel, raw_ostream& O) const; - const DagInit& property = InitPtrToDag(i); - const std::string& property_name = GetOperatorName(property); - Handler h = Obj->GetHandler(property_name); + const DagInit& Dag = InitPtrToDag(I); + Handler h = GetHandler<Handler>(Obj, Dag); - ((Obj)->*(h))(&property); + ((Obj)->*(h))(Dag, IndentLevel, O); } + template <typename H> typename HandlerTable<H>::HandlerMap HandlerTable<H>::Handlers_; @@ -521,7 +544,7 @@ bool HandlerTable<H>::staticMembersInitialized_ = false; /// option property list. class CollectOptionProperties; typedef void (CollectOptionProperties::* CollectOptionPropertiesHandler) -(const DagInit*); +(const DagInit&); class CollectOptionProperties : public HandlerTable<CollectOptionPropertiesHandler> @@ -555,8 +578,8 @@ public: /// operator() - Just forwards to the corresponding property /// handler. - void operator() (Init* i) { - InvokeDagInitHandler(this, i); + void operator() (Init* I) { + InvokeDagInitHandler(this, I); } private: @@ -564,44 +587,44 @@ private: /// Option property handlers -- /// Methods that handle option properties such as (help) or (hidden). - void onExtern (const DagInit* d) { - checkNumberOfArguments(d, 0); + void onExtern (const DagInit& d) { + CheckNumberOfArguments(d, 0); optDesc_.setExtern(); } - void onHelp (const DagInit* d) { - checkNumberOfArguments(d, 1); - optDesc_.Help = InitPtrToString(d->getArg(0)); + void onHelp (const DagInit& d) { + CheckNumberOfArguments(d, 1); + optDesc_.Help = InitPtrToString(d.getArg(0)); } - void onHidden (const DagInit* d) { - checkNumberOfArguments(d, 0); + void onHidden (const DagInit& d) { + CheckNumberOfArguments(d, 0); optDesc_.setHidden(); } - void onReallyHidden (const DagInit* d) { - checkNumberOfArguments(d, 0); + void onReallyHidden (const DagInit& d) { + CheckNumberOfArguments(d, 0); optDesc_.setReallyHidden(); } - void onCommaSeparated (const DagInit* d) { - checkNumberOfArguments(d, 0); + void onCommaSeparated (const DagInit& d) { + CheckNumberOfArguments(d, 0); if (!optDesc_.isList()) throw "'comma_separated' is valid only on list options!"; optDesc_.setCommaSeparated(); } - void onRequired (const DagInit* d) { - checkNumberOfArguments(d, 0); + void onRequired (const DagInit& d) { + CheckNumberOfArguments(d, 0); if (optDesc_.isOneOrMore() || optDesc_.isOptional()) throw "Only one of (required), (optional) or " "(one_or_more) properties is allowed!"; optDesc_.setRequired(); } - void onInit (const DagInit* d) { - checkNumberOfArguments(d, 1); - Init* i = d->getArg(0); + void onInit (const DagInit& d) { + CheckNumberOfArguments(d, 1); + Init* i = d.getArg(0); const std::string& str = i->getAsString(); bool correct = optDesc_.isParameter() && dynamic_cast<StringInit*>(i); @@ -613,8 +636,8 @@ private: optDesc_.InitVal = i; } - void onOneOrMore (const DagInit* d) { - checkNumberOfArguments(d, 0); + void onOneOrMore (const DagInit& d) { + CheckNumberOfArguments(d, 0); if (optDesc_.isRequired() || optDesc_.isOptional()) throw "Only one of (required), (optional) or " "(one_or_more) properties is allowed!"; @@ -624,8 +647,8 @@ private: optDesc_.setOneOrMore(); } - void onOptional (const DagInit* d) { - checkNumberOfArguments(d, 0); + void onOptional (const DagInit& d) { + CheckNumberOfArguments(d, 0); if (optDesc_.isRequired() || optDesc_.isOneOrMore()) throw "Only one of (required), (optional) or " "(one_or_more) properties is allowed!"; @@ -635,9 +658,9 @@ private: optDesc_.setOptional(); } - void onMultiVal (const DagInit* d) { - checkNumberOfArguments(d, 1); - int val = InitPtrToInt(d->getArg(0)); + void onMultiVal (const DagInit& d) { + CheckNumberOfArguments(d, 1); + int val = InitPtrToInt(d.getArg(0)); if (val < 2) throw "Error in the 'multi_val' property: " "the value must be greater than 1!"; @@ -660,7 +683,7 @@ public: void operator()(const Init* i) { const DagInit& d = InitPtrToDag(i); - checkNumberOfArguments(&d, 1); + CheckNumberOfArguments(d, 1); const OptionType::OptionType Type = stringToOptionType(GetOperatorName(d)); @@ -669,14 +692,14 @@ public: OptionDescription OD(Type, Name); if (!OD.isExtern()) - checkNumberOfArguments(&d, 2); + CheckNumberOfArguments(d, 2); if (OD.isAlias()) { // Aliases store the aliased option name in the 'Help' field. OD.Help = InitPtrToString(d.getArg(1)); } else if (!OD.isExtern()) { - processOptionProperties(&d, OD); + processOptionProperties(d, OD); } OptDescs_.InsertDescription(OD); } @@ -684,12 +707,12 @@ public: private: /// processOptionProperties - Go through the list of option /// properties and call a corresponding handler for each. - static void processOptionProperties (const DagInit* d, OptionDescription& o) { - checkNumberOfArguments(d, 2); - DagInit::const_arg_iterator B = d->arg_begin(); + static void processOptionProperties (const DagInit& d, OptionDescription& o) { + CheckNumberOfArguments(d, 2); + DagInit::const_arg_iterator B = d.arg_begin(); // Skip the first argument: it's always the option name. ++B; - std::for_each(B, d->arg_end(), CollectOptionProperties(o)); + std::for_each(B, d.arg_end(), CollectOptionProperties(o)); } }; @@ -750,7 +773,7 @@ typedef std::vector<IntrusiveRefCntPtr<ToolDescription> > ToolDescriptions; class CollectToolProperties; typedef void (CollectToolProperties::* CollectToolPropertiesHandler) -(const DagInit*); +(const DagInit&); class CollectToolProperties : public HandlerTable<CollectToolPropertiesHandler> { @@ -779,8 +802,8 @@ public: } } - void operator() (Init* i) { - InvokeDagInitHandler(this, i); + void operator() (Init* I) { + InvokeDagInitHandler(this, I); } private: @@ -789,23 +812,23 @@ private: /// Functions that extract information about tool properties from /// DAG representation. - void onActions (const DagInit* d) { - checkNumberOfArguments(d, 1); - Init* Case = d->getArg(0); + void onActions (const DagInit& d) { + CheckNumberOfArguments(d, 1); + Init* Case = d.getArg(0); if (typeid(*Case) != typeid(DagInit) || - GetOperatorName(static_cast<DagInit*>(Case)) != "case") + GetOperatorName(static_cast<DagInit&>(*Case)) != "case") throw "The argument to (actions) should be a 'case' construct!"; toolDesc_.Actions = Case; } - void onCmdLine (const DagInit* d) { - checkNumberOfArguments(d, 1); - toolDesc_.CmdLine = d->getArg(0); + void onCmdLine (const DagInit& d) { + CheckNumberOfArguments(d, 1); + toolDesc_.CmdLine = d.getArg(0); } - void onInLanguage (const DagInit* d) { - checkNumberOfArguments(d, 1); - Init* arg = d->getArg(0); + void onInLanguage (const DagInit& d) { + CheckNumberOfArguments(d, 1); + Init* arg = d.getArg(0); // Find out the argument's type. if (typeid(*arg) == typeid(StringInit)) { @@ -830,23 +853,23 @@ private: } } - void onJoin (const DagInit* d) { - checkNumberOfArguments(d, 0); + void onJoin (const DagInit& d) { + CheckNumberOfArguments(d, 0); toolDesc_.setJoin(); } - void onOutLanguage (const DagInit* d) { - checkNumberOfArguments(d, 1); - toolDesc_.OutLanguage = InitPtrToString(d->getArg(0)); + void onOutLanguage (const DagInit& d) { + CheckNumberOfArguments(d, 1); + toolDesc_.OutLanguage = InitPtrToString(d.getArg(0)); } - void onOutputSuffix (const DagInit* d) { - checkNumberOfArguments(d, 1); - toolDesc_.OutputSuffix = InitPtrToString(d->getArg(0)); + void onOutputSuffix (const DagInit& d) { + CheckNumberOfArguments(d, 1); + toolDesc_.OutputSuffix = InitPtrToString(d.getArg(0)); } - void onSink (const DagInit* d) { - checkNumberOfArguments(d, 0); + void onSink (const DagInit& d) { + CheckNumberOfArguments(d, 0); toolDesc_.setSink(); } @@ -1033,12 +1056,12 @@ void WalkCase(const Init* Case, F1 TestCallback, F2 StatementCallback, throw "Case construct handler: no corresponding action " "found for the test " + Test.getAsString() + '!'; - TestCallback(&Test, IndentLevel, (i == 1)); + TestCallback(Test, IndentLevel, (i == 1)); } else { if (dynamic_cast<DagInit*>(arg) - && GetOperatorName(static_cast<DagInit*>(arg)) == "case") { + && GetOperatorName(static_cast<DagInit&>(*arg)) == "case") { // Nested 'case'. WalkCase(arg, TestCallback, StatementCallback, IndentLevel + Indent1); } @@ -1063,14 +1086,28 @@ class ExtractOptionNames { if (ActionName == "forward" || ActionName == "forward_as" || ActionName == "forward_value" || ActionName == "forward_transformed_value" || - ActionName == "switch_on" || ActionName == "parameter_equals" || + ActionName == "switch_on" || ActionName == "any_switch_on" || + ActionName == "parameter_equals" || ActionName == "element_in_list" || ActionName == "not_empty" || ActionName == "empty") { - checkNumberOfArguments(&Stmt, 1); - const std::string& Name = InitPtrToString(Stmt.getArg(0)); - OptionNames_.insert(Name); + CheckNumberOfArguments(Stmt, 1); + + Init* Arg = Stmt.getArg(0); + if (typeid(*Arg) == typeid(StringInit)) { + const std::string& Name = InitPtrToString(Arg); + OptionNames_.insert(Name); + } + else { + // It's a list. + const ListInit& List = InitPtrToList(Arg); + for (ListInit::const_iterator B = List.begin(), E = List.end(); + B != E; ++B) { + const std::string& Name = InitPtrToString(*B); + OptionNames_.insert(Name); + } + } } - else if (ActionName == "and" || ActionName == "or") { + else if (ActionName == "and" || ActionName == "or" || ActionName == "not") { for (unsigned i = 0, NumArgs = Stmt.getNumArgs(); i < NumArgs; ++i) { this->processDag(Stmt.getArg(i)); } @@ -1093,8 +1130,8 @@ public: } } - void operator()(const DagInit* Test, unsigned, bool) { - this->operator()(Test); + void operator()(const DagInit& Test, unsigned, bool) { + this->operator()(&Test); } void operator()(const Init* Statement, unsigned) { this->operator()(Statement); @@ -1125,10 +1162,10 @@ void CheckForSuperfluousOptions (const RecordVector& Edges, for (RecordVector::const_iterator B = Edges.begin(), E = Edges.end(); B != E; ++B) { const Record* Edge = *B; - DagInit* Weight = Edge->getValueAsDag("weight"); + DagInit& Weight = *Edge->getValueAsDag("weight"); - if (!isDagEmpty(Weight)) - WalkCase(Weight, ExtractOptionNames(nonSuperfluousOptions), Id()); + if (!IsDagEmpty(Weight)) + WalkCase(&Weight, ExtractOptionNames(nonSuperfluousOptions), Id()); } // Check that all options in OptDescs belong to the set of @@ -1169,7 +1206,7 @@ void EmitListTest(const ListInit& L, const char* LogicOp, if (isFirst) isFirst = false; else - O << " || "; + O << ' ' << LogicOp << ' '; Callback(InitPtrToString(*B), O); } } @@ -1217,7 +1254,7 @@ bool EmitCaseTest1ArgList(const std::string& TestName, const DagInit& d, const OptionDescriptions& OptDescs, raw_ostream& O) { - const ListInit& L = *static_cast<ListInit*>(d.getArg(0)); + const ListInit& L = InitPtrToList(d.getArg(0)); if (TestName == "any_switch_on") { EmitListTest(L, "||", EmitSwitchOn(OptDescs), O); @@ -1284,7 +1321,7 @@ bool EmitCaseTest1Arg(const std::string& TestName, const DagInit& d, const OptionDescriptions& OptDescs, raw_ostream& O) { - checkNumberOfArguments(&d, 1); + CheckNumberOfArguments(d, 1); if (typeid(*d.getArg(0)) == typeid(ListInit)) return EmitCaseTest1ArgList(TestName, d, OptDescs, O); else @@ -1297,7 +1334,7 @@ bool EmitCaseTest2Args(const std::string& TestName, unsigned IndentLevel, const OptionDescriptions& OptDescs, raw_ostream& O) { - checkNumberOfArguments(&d, 2); + CheckNumberOfArguments(d, 2); const std::string& OptName = InitPtrToString(d.getArg(0)); const std::string& OptArg = InitPtrToString(d.getArg(1)); @@ -1348,7 +1385,7 @@ void EmitLogicalOperationTest(const DagInit& d, const char* LogicOp, void EmitLogicalNot(const DagInit& d, unsigned IndentLevel, const OptionDescriptions& OptDescs, raw_ostream& O) { - checkNumberOfArguments(&d, 1); + CheckNumberOfArguments(d, 1); const DagInit& InnerTest = InitPtrToDag(d.getArg(0)); O << "! ("; EmitCaseTest(InnerTest, IndentLevel, OptDescs, O); @@ -1374,7 +1411,7 @@ void EmitCaseTest(const DagInit& d, unsigned IndentLevel, else if (EmitCaseTest2Args(TestName, d, IndentLevel, OptDescs, O)) return; else - throw TestName + ": unknown edge property!"; + throw "Unknown test '" + TestName + "' used in the 'case' construct!"; } @@ -1390,7 +1427,7 @@ public: : EmitElseIf_(EmitElseIf), OptDescs_(OptDescs), O_(O) {} - void operator()(const DagInit* Test, unsigned IndentLevel, bool FirstTest) + void operator()(const DagInit& Test, unsigned IndentLevel, bool FirstTest) { if (GetOperatorName(Test) == "default") { O_.indent(IndentLevel) << "else {\n"; @@ -1398,7 +1435,7 @@ public: else { O_.indent(IndentLevel) << ((!FirstTest && EmitElseIf_) ? "else if (" : "if ("); - EmitCaseTest(*Test, IndentLevel, OptDescs_, O_); + EmitCaseTest(Test, IndentLevel, OptDescs_, O_); O_ << ") {\n"; } } @@ -1419,7 +1456,7 @@ public: // Ignore nested 'case' DAG. if (!(dynamic_cast<const DagInit*>(Statement) && - GetOperatorName(static_cast<const DagInit*>(Statement)) == "case")) { + GetOperatorName(static_cast<const DagInit&>(*Statement)) == "case")) { if (typeid(*Statement) == typeid(ListInit)) { const ListInit& DagList = *static_cast<const ListInit*>(Statement); for (ListInit::const_iterator B = DagList.begin(), E = DagList.end(); @@ -1452,10 +1489,10 @@ void EmitCaseConstructHandler(const Init* Case, unsigned IndentLevel, EmitCaseStatementCallback<F>(Callback, O), IndentLevel); } -/// TokenizeCmdline - converts from +/// TokenizeCmdLine - converts from /// "$CALL(HookName, 'Arg1', 'Arg2')/path -arg1 -arg2" to /// ["$CALL(", "HookName", "Arg1", "Arg2", ")/path", "-arg1", "-arg2"]. -void TokenizeCmdline(const std::string& CmdLine, StrVector& Out) { +void TokenizeCmdLine(const std::string& CmdLine, StrVector& Out) { const char* Delimiters = " \t\n\v\f\r"; enum TokenizerState { Normal, SpecialCommand, InsideSpecialCommand, InsideQuotationMarks } @@ -1477,7 +1514,7 @@ void TokenizeCmdline(const std::string& CmdLine, StrVector& Out) { cur_st = SpecialCommand; break; } - if (oneOf(Delimiters, cur_ch)) { + if (OneOf(Delimiters, cur_ch)) { // Skip whitespace B = CmdLine.find_first_not_of(Delimiters, B); if (B == std::string::npos) { @@ -1492,7 +1529,7 @@ void TokenizeCmdline(const std::string& CmdLine, StrVector& Out) { case SpecialCommand: - if (oneOf(Delimiters, cur_ch)) { + if (OneOf(Delimiters, cur_ch)) { cur_st = Normal; Out.push_back(""); continue; @@ -1505,7 +1542,7 @@ void TokenizeCmdline(const std::string& CmdLine, StrVector& Out) { break; case InsideSpecialCommand: - if (oneOf(Delimiters, cur_ch)) { + if (OneOf(Delimiters, cur_ch)) { continue; } if (cur_ch == '\'') { @@ -1544,7 +1581,7 @@ SubstituteCall (StrVector::const_iterator Pos, bool IsJoin, raw_ostream& O) { const char* errorMessage = "Syntax error in $CALL invocation!"; - checkedIncrement(Pos, End, errorMessage); + CheckedIncrement(Pos, End, errorMessage); const std::string& CmdName = *Pos; if (CmdName == ")") @@ -1556,7 +1593,7 @@ SubstituteCall (StrVector::const_iterator Pos, bool firstIteration = true; while (true) { - checkedIncrement(Pos, End, errorMessage); + CheckedIncrement(Pos, End, errorMessage); const std::string& Arg = *Pos; assert(Arg.size() != 0); @@ -1591,7 +1628,7 @@ SubstituteEnv (StrVector::const_iterator Pos, StrVector::const_iterator End, raw_ostream& O) { const char* errorMessage = "Syntax error in $ENV invocation!"; - checkedIncrement(Pos, End, errorMessage); + CheckedIncrement(Pos, End, errorMessage); const std::string& EnvName = *Pos; if (EnvName == ")") @@ -1601,7 +1638,7 @@ SubstituteEnv (StrVector::const_iterator Pos, O << EnvName; O << "\"))"; - checkedIncrement(Pos, End, errorMessage); + CheckedIncrement(Pos, End, errorMessage); return Pos; } @@ -1642,7 +1679,7 @@ void EmitCmdLineVecFill(const Init* CmdLine, const std::string& ToolName, bool IsJoin, unsigned IndentLevel, raw_ostream& O) { StrVector StrVec; - TokenizeCmdline(InitPtrToString(CmdLine), StrVec); + TokenizeCmdLine(InitPtrToString(CmdLine), StrVec); if (StrVec.empty()) throw "Tool '" + ToolName + "' has empty command line!"; @@ -1786,7 +1823,8 @@ void EmitForwardOptionPropertyHandlingCode (const OptionDescription& D, /// ActionHandlingCallbackBase - Base class of EmitActionHandlersCallback and /// EmitPreprocessOptionsCallback. -struct ActionHandlingCallbackBase { +struct ActionHandlingCallbackBase +{ void onErrorDag(const DagInit& d, unsigned IndentLevel, raw_ostream& O) const @@ -1801,7 +1839,7 @@ struct ActionHandlingCallbackBase { void onWarningDag(const DagInit& d, unsigned IndentLevel, raw_ostream& O) const { - checkNumberOfArguments(&d, 1); + CheckNumberOfArguments(d, 1); O.indent(IndentLevel) << "llvm::errs() << \"" << InitPtrToString(d.getArg(0)) << "\";\n"; } @@ -1810,17 +1848,20 @@ struct ActionHandlingCallbackBase { /// EmitActionHandlersCallback - Emit code that handles actions. Used by /// EmitGenerateActionMethod() as an argument to EmitCaseConstructHandler(). + class EmitActionHandlersCallback; + typedef void (EmitActionHandlersCallback::* EmitActionHandlersCallbackHandler) (const DagInit&, unsigned, raw_ostream&) const; -class EmitActionHandlersCallback -: public ActionHandlingCallbackBase, +class EmitActionHandlersCallback : + public ActionHandlingCallbackBase, public HandlerTable<EmitActionHandlersCallbackHandler> { - const OptionDescriptions& OptDescs; typedef EmitActionHandlersCallbackHandler Handler; + const OptionDescriptions& OptDescs; + /// EmitHookInvocation - Common code for hook invocation from actions. Used by /// onAppendCmd and onOutputSuffix. void EmitHookInvocation(const std::string& Str, @@ -1828,7 +1869,7 @@ class EmitActionHandlersCallback unsigned IndentLevel, raw_ostream& O) const { StrVector Out; - TokenizeCmdline(Str, Out); + TokenizeCmdLine(Str, Out); for (StrVector::const_iterator B = Out.begin(), E = Out.end(); B != E; ++B) { @@ -1848,7 +1889,7 @@ class EmitActionHandlersCallback void onAppendCmd (const DagInit& Dag, unsigned IndentLevel, raw_ostream& O) const { - checkNumberOfArguments(&Dag, 1); + CheckNumberOfArguments(Dag, 1); this->EmitHookInvocation(InitPtrToString(Dag.getArg(0)), "vec.push_back(", ");\n", IndentLevel, O); } @@ -1856,7 +1897,7 @@ class EmitActionHandlersCallback void onForward (const DagInit& Dag, unsigned IndentLevel, raw_ostream& O) const { - checkNumberOfArguments(&Dag, 1); + CheckNumberOfArguments(Dag, 1); const std::string& Name = InitPtrToString(Dag.getArg(0)); EmitForwardOptionPropertyHandlingCode(OptDescs.FindOption(Name), IndentLevel, "", O); @@ -1865,7 +1906,7 @@ class EmitActionHandlersCallback void onForwardAs (const DagInit& Dag, unsigned IndentLevel, raw_ostream& O) const { - checkNumberOfArguments(&Dag, 2); + CheckNumberOfArguments(Dag, 2); const std::string& Name = InitPtrToString(Dag.getArg(0)); const std::string& NewName = InitPtrToString(Dag.getArg(1)); EmitForwardOptionPropertyHandlingCode(OptDescs.FindOption(Name), @@ -1875,7 +1916,7 @@ class EmitActionHandlersCallback void onForwardValue (const DagInit& Dag, unsigned IndentLevel, raw_ostream& O) const { - checkNumberOfArguments(&Dag, 1); + CheckNumberOfArguments(Dag, 1); const std::string& Name = InitPtrToString(Dag.getArg(0)); const OptionDescription& D = OptDescs.FindListOrParameter(Name); @@ -1893,7 +1934,7 @@ class EmitActionHandlersCallback void onForwardTransformedValue (const DagInit& Dag, unsigned IndentLevel, raw_ostream& O) const { - checkNumberOfArguments(&Dag, 2); + CheckNumberOfArguments(Dag, 2); const std::string& Name = InitPtrToString(Dag.getArg(0)); const std::string& Hook = InitPtrToString(Dag.getArg(1)); const OptionDescription& D = OptDescs.FindListOrParameter(Name); @@ -1906,7 +1947,7 @@ class EmitActionHandlersCallback void onOutputSuffix (const DagInit& Dag, unsigned IndentLevel, raw_ostream& O) const { - checkNumberOfArguments(&Dag, 1); + CheckNumberOfArguments(Dag, 1); this->EmitHookInvocation(InitPtrToString(Dag.getArg(0)), "output_suffix = ", ";\n", IndentLevel, O); } @@ -1949,20 +1990,16 @@ class EmitActionHandlersCallback } } - void operator()(const Init* Statement, + void operator()(const Init* I, unsigned IndentLevel, raw_ostream& O) const { - const DagInit& Dag = InitPtrToDag(Statement); - const std::string& ActionName = GetOperatorName(Dag); - Handler h = GetHandler(ActionName); - - ((this)->*(h))(Dag, IndentLevel, O); + InvokeDagInitHandler(this, I, IndentLevel, O); } }; bool IsOutFileIndexCheckRequiredStr (const Init* CmdLine) { StrVector StrVec; - TokenizeCmdline(InitPtrToString(CmdLine), StrVec); + TokenizeCmdLine(InitPtrToString(CmdLine), StrVec); for (StrVector::const_iterator I = StrVec.begin(), E = StrVec.end(); I != E; ++I) { @@ -2280,11 +2317,46 @@ void EmitOptionDefinitions (const OptionDescriptions& descs, /// EmitPreprocessOptionsCallback - Helper function passed to /// EmitCaseConstructHandler() by EmitPreprocessOptions(). -class EmitPreprocessOptionsCallback : ActionHandlingCallbackBase { + +class EmitPreprocessOptionsCallback; + +typedef void +(EmitPreprocessOptionsCallback::* EmitPreprocessOptionsCallbackHandler) +(const DagInit&, unsigned, raw_ostream&) const; + +class EmitPreprocessOptionsCallback : + public ActionHandlingCallbackBase, + public HandlerTable<EmitPreprocessOptionsCallbackHandler> +{ + typedef EmitPreprocessOptionsCallbackHandler Handler; + typedef void + (EmitPreprocessOptionsCallback::* HandlerImpl) + (const Init*, unsigned, raw_ostream&) const; + const OptionDescriptions& OptDescs_; - void onUnsetOption(Init* i, unsigned IndentLevel, raw_ostream& O) { - const std::string& OptName = InitPtrToString(i); + void onListOrDag(const DagInit& d, HandlerImpl h, + unsigned IndentLevel, raw_ostream& O) const + { + CheckNumberOfArguments(d, 1); + const Init* I = d.getArg(0); + + // If I is a list, apply h to each element. + if (typeid(*I) == typeid(ListInit)) { + const ListInit& L = *static_cast<const ListInit*>(I); + for (ListInit::const_iterator B = L.begin(), E = L.end(); B != E; ++B) + ((this)->*(h))(*B, IndentLevel, O); + } + // Otherwise, apply h to I. + else { + ((this)->*(h))(I, IndentLevel, O); + } + } + + void onUnsetOptionImpl(const Init* I, + unsigned IndentLevel, raw_ostream& O) const + { + const std::string& OptName = InitPtrToString(I); const OptionDescription& OptDesc = OptDescs_.FindOption(OptName); if (OptDesc.isSwitch()) { @@ -2301,45 +2373,93 @@ class EmitPreprocessOptionsCallback : ActionHandlingCallbackBase { } } - void processDag(const Init* I, unsigned IndentLevel, raw_ostream& O) + void onUnsetOption(const DagInit& d, + unsigned IndentLevel, raw_ostream& O) const { - const DagInit& d = InitPtrToDag(I); - const std::string& OpName = GetOperatorName(d); + this->onListOrDag(d, &EmitPreprocessOptionsCallback::onUnsetOptionImpl, + IndentLevel, O); + } + + void onSetOptionImpl(const DagInit& d, + unsigned IndentLevel, raw_ostream& O) const { + CheckNumberOfArguments(d, 2); + const std::string& OptName = InitPtrToString(d.getArg(0)); + const Init* Value = d.getArg(1); + const OptionDescription& OptDesc = OptDescs_.FindOption(OptName); - if (OpName == "warning") { - this->onWarningDag(d, IndentLevel, O); + if (OptDesc.isList()) { + const ListInit& List = InitPtrToList(Value); + + O.indent(IndentLevel) << OptDesc.GenVariableName() << ".clear();\n"; + for (ListInit::const_iterator B = List.begin(), E = List.end(); + B != E; ++B) { + O.indent(IndentLevel) << OptDesc.GenVariableName() << ".push_back(\"" + << InitPtrToString(*B) << "\");\n"; + } } - else if (OpName == "error") { - this->onWarningDag(d, IndentLevel, O); + else if (OptDesc.isSwitch()) { + CheckBooleanConstant(Value); + O.indent(IndentLevel) << OptDesc.GenVariableName() + << " = " << Value->getAsString() << ";\n"; } - else if (OpName == "unset_option") { - checkNumberOfArguments(&d, 1); - Init* I = d.getArg(0); - if (typeid(*I) == typeid(ListInit)) { - const ListInit& DagList = *static_cast<const ListInit*>(I); - for (ListInit::const_iterator B = DagList.begin(), E = DagList.end(); - B != E; ++B) - this->onUnsetOption(*B, IndentLevel, O); - } - else { - this->onUnsetOption(I, IndentLevel, O); - } + else if (OptDesc.isParameter()) { + const std::string& Str = InitPtrToString(Value); + O.indent(IndentLevel) << OptDesc.GenVariableName() + << " = \"" << Str << "\";\n"; } else { - throw "Unknown operator in the option preprocessor: '" + OpName + "'!" - "\nOnly 'warning', 'error' and 'unset_option' are allowed."; + throw "Can't apply 'set_option' to alias option -" + OptName + " !"; } } -public: + void onSetSwitch(const Init* I, + unsigned IndentLevel, raw_ostream& O) const { + const std::string& OptName = InitPtrToString(I); + const OptionDescription& OptDesc = OptDescs_.FindOption(OptName); - void operator()(const Init* I, unsigned IndentLevel, raw_ostream& O) { - this->processDag(I, IndentLevel, O); + if (OptDesc.isSwitch()) + O.indent(IndentLevel) << OptDesc.GenVariableName() << " = true;\n"; + else + throw "set_option: -" + OptName + " is not a switch option!"; + } + + void onSetOption(const DagInit& d, + unsigned IndentLevel, raw_ostream& O) const + { + CheckNumberOfArguments(d, 1); + + // Two arguments: (set_option "parameter", VALUE), where VALUE can be a + // boolean, a string or a string list. + if (d.getNumArgs() > 1) + this->onSetOptionImpl(d, IndentLevel, O); + // One argument: (set_option "switch") + // or (set_option ["switch1", "switch2", ...]) + else + this->onListOrDag(d, &EmitPreprocessOptionsCallback::onSetSwitch, + IndentLevel, O); } +public: + EmitPreprocessOptionsCallback(const OptionDescriptions& OptDescs) : OptDescs_(OptDescs) - {} + { + if (!staticMembersInitialized_) { + AddHandler("error", &EmitPreprocessOptionsCallback::onErrorDag); + AddHandler("warning", &EmitPreprocessOptionsCallback::onWarningDag); + AddHandler("unset_option", &EmitPreprocessOptionsCallback::onUnsetOption); + AddHandler("set_option", &EmitPreprocessOptionsCallback::onSetOption); + + staticMembersInitialized_ = true; + } + } + + void operator()(const Init* I, + unsigned IndentLevel, raw_ostream& O) const + { + InvokeDagInitHandler(this, I, IndentLevel, O); + } + }; /// EmitPreprocessOptions - Emit the PreprocessOptionsLocal() function. @@ -2407,7 +2527,7 @@ void IncDecWeight (const Init* i, unsigned IndentLevel, O.indent(IndentLevel) << "ret -= "; } else if (OpName == "error") { - checkNumberOfArguments(&d, 1); + CheckNumberOfArguments(d, 1); O.indent(IndentLevel) << "throw std::runtime_error(\"" << InitPtrToString(d.getArg(0)) << "\");\n"; @@ -2445,7 +2565,7 @@ void EmitEdgeClass (unsigned N, const std::string& Target, EmitCaseConstructHandler(Case, Indent2, IncDecWeight, false, OptDescs, O); O.indent(Indent2) << "return ret;\n"; - O.indent(Indent1) << "};\n\n};\n\n"; + O.indent(Indent1) << "}\n\n};\n\n"; } /// EmitEdgeClasses - Emit Edge* classes that represent graph edges. @@ -2457,10 +2577,10 @@ void EmitEdgeClasses (const RecordVector& EdgeVector, E = EdgeVector.end(); B != E; ++B) { const Record* Edge = *B; const std::string& NodeB = Edge->getValueAsString("b"); - DagInit* Weight = Edge->getValueAsDag("weight"); + DagInit& Weight = *Edge->getValueAsDag("weight"); - if (!isDagEmpty(Weight)) - EmitEdgeClass(i, NodeB, Weight, OptDescs, O); + if (!IsDagEmpty(Weight)) + EmitEdgeClass(i, NodeB, &Weight, OptDescs, O); ++i; } } @@ -2487,11 +2607,11 @@ void EmitPopulateCompilationGraph (const RecordVector& EdgeVector, const Record* Edge = *B; const std::string& NodeA = Edge->getValueAsString("a"); const std::string& NodeB = Edge->getValueAsString("b"); - DagInit* Weight = Edge->getValueAsDag("weight"); + DagInit& Weight = *Edge->getValueAsDag("weight"); O.indent(Indent1) << "G.insertEdge(\"" << NodeA << "\", "; - if (isDagEmpty(Weight)) + if (IsDagEmpty(Weight)) O << "new SimpleEdge(\"" << NodeB << "\")"; else O << "new Edge" << i << "()"; @@ -2540,7 +2660,7 @@ public: const std::string& Name = GetOperatorName(Dag); if (Name == "forward_transformed_value") { - checkNumberOfArguments(Dag, 2); + CheckNumberOfArguments(Dag, 2); const std::string& OptName = InitPtrToString(Dag.getArg(0)); const std::string& HookName = InitPtrToString(Dag.getArg(1)); const OptionDescription& D = OptDescs_.FindOption(OptName); @@ -2549,14 +2669,14 @@ public: : HookInfo::ArgHook); } else if (Name == "append_cmd" || Name == "output_suffix") { - checkNumberOfArguments(Dag, 1); + CheckNumberOfArguments(Dag, 1); this->onCmdLine(InitPtrToString(Dag.getArg(0))); } } void onCmdLine(const std::string& Cmd) { StrVector cmds; - TokenizeCmdline(Cmd, cmds); + TokenizeCmdLine(Cmd, cmds); for (StrVector::const_iterator B = cmds.begin(), E = cmds.end(); B != E; ++B) { @@ -2564,7 +2684,7 @@ public: if (cmd == "$CALL") { unsigned NumArgs = 0; - checkedIncrement(B, E, "Syntax error in $CALL invocation!"); + CheckedIncrement(B, E, "Syntax error in $CALL invocation!"); const std::string& HookName = *B; if (HookName.at(0) == ')') @@ -2789,7 +2909,6 @@ void CheckPluginData(PluginData& Data) { // Check that there are no options without side effects (specified // only in the OptionList). CheckForSuperfluousOptions(Data.Edges, Data.ToolDescs, Data.OptDescs); - } void EmitPluginCode(const PluginData& Data, raw_ostream& O) { diff --git a/utils/TableGen/Record.cpp b/utils/TableGen/Record.cpp index 53f9014..542735e 100644 --- a/utils/TableGen/Record.cpp +++ b/utils/TableGen/Record.cpp @@ -945,11 +945,13 @@ Init *TernOpInit::Fold(Record *CurRec, MultiClass *CurMultiClass) { std::string Val = RHSs->getValue(); std::string::size_type found; + std::string::size_type idx = 0; do { - found = Val.find(LHSs->getValue()); + found = Val.find(LHSs->getValue(), idx); if (found != std::string::npos) { Val.replace(found, LHSs->getValue().size(), MHSs->getValue()); } + idx = found + MHSs->getValue().size(); } while (found != std::string::npos); return new StringInit(Val); diff --git a/utils/TableGen/X86DisassemblerShared.h b/utils/TableGen/X86DisassemblerShared.h new file mode 100644 index 0000000..0417e9d --- /dev/null +++ b/utils/TableGen/X86DisassemblerShared.h @@ -0,0 +1,38 @@ +//===- X86DisassemblerShared.h - Emitter shared header ----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef X86DISASSEMBLERSHARED_H +#define X86DISASSEMBLERSHARED_H + +#include <string> +#include <string.h> + +#define INSTRUCTION_SPECIFIER_FIELDS \ + bool filtered; \ + InstructionContext insnContext; \ + std::string name; \ + \ + InstructionSpecifier() { \ + filtered = false; \ + insnContext = IC; \ + name = ""; \ + modifierType = MODIFIER_NONE; \ + modifierBase = 0; \ + memset(operands, 0, sizeof(operands)); \ + } + +#define INSTRUCTION_IDS \ + InstrUID instructionIDs[256]; + +#include "../../lib/Target/X86/Disassembler/X86DisassemblerDecoderCommon.h" + +#undef INSTRUCTION_SPECIFIER_FIELDS +#undef INSTRUCTION_IDS + +#endif diff --git a/utils/TableGen/X86DisassemblerTables.cpp b/utils/TableGen/X86DisassemblerTables.cpp new file mode 100644 index 0000000..be07031 --- /dev/null +++ b/utils/TableGen/X86DisassemblerTables.cpp @@ -0,0 +1,603 @@ +//===- X86DisassemblerTables.cpp - Disassembler tables ----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is part of the X86 Disassembler Emitter. +// It contains the implementation of the disassembler tables. +// Documentation for the disassembler emitter in general can be found in +// X86DisasemblerEmitter.h. +// +//===----------------------------------------------------------------------===// + +#include "X86DisassemblerShared.h" +#include "X86DisassemblerTables.h" + +#include "TableGenBackend.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Format.h" + +using namespace llvm; +using namespace X86Disassembler; + +/// inheritsFrom - Indicates whether all instructions in one class also belong +/// to another class. +/// +/// @param child - The class that may be the subset +/// @param parent - The class that may be the superset +/// @return - True if child is a subset of parent, false otherwise. +static inline bool inheritsFrom(InstructionContext child, + InstructionContext parent) { + if (child == parent) + return true; + + switch (parent) { + case IC: + return true; + case IC_64BIT: + return(inheritsFrom(child, IC_64BIT_REXW) || + inheritsFrom(child, IC_64BIT_OPSIZE) || + inheritsFrom(child, IC_64BIT_XD) || + inheritsFrom(child, IC_64BIT_XS)); + case IC_OPSIZE: + return(inheritsFrom(child, IC_64BIT_OPSIZE)); + case IC_XD: + return(inheritsFrom(child, IC_64BIT_XD)); + case IC_XS: + return(inheritsFrom(child, IC_64BIT_XS)); + case IC_64BIT_REXW: + return(inheritsFrom(child, IC_64BIT_REXW_XS) || + inheritsFrom(child, IC_64BIT_REXW_XD) || + inheritsFrom(child, IC_64BIT_REXW_OPSIZE)); + case IC_64BIT_OPSIZE: + return(inheritsFrom(child, IC_64BIT_REXW_OPSIZE)); + case IC_64BIT_XD: + return(inheritsFrom(child, IC_64BIT_REXW_XD)); + case IC_64BIT_XS: + return(inheritsFrom(child, IC_64BIT_REXW_XS)); + case IC_64BIT_REXW_XD: + return false; + case IC_64BIT_REXW_XS: + return false; + case IC_64BIT_REXW_OPSIZE: + return false; + default: + return false; + } +} + +/// outranks - Indicates whether, if an instruction has two different applicable +/// classes, which class should be preferred when performing decode. This +/// imposes a total ordering (ties are resolved toward "lower") +/// +/// @param upper - The class that may be preferable +/// @param lower - The class that may be less preferable +/// @return - True if upper is to be preferred, false otherwise. +static inline bool outranks(InstructionContext upper, + InstructionContext lower) { + assert(upper < IC_max); + assert(lower < IC_max); + +#define ENUM_ENTRY(n, r, d) r, + static int ranks[IC_max] = { + INSTRUCTION_CONTEXTS + }; +#undef ENUM_ENTRY + + return (ranks[upper] > ranks[lower]); +} + +/// stringForContext - Returns a string containing the name of a particular +/// InstructionContext, usually for diagnostic purposes. +/// +/// @param insnContext - The instruction class to transform to a string. +/// @return - A statically-allocated string constant that contains the +/// name of the instruction class. +static inline const char* stringForContext(InstructionContext insnContext) { + switch (insnContext) { + default: + llvm_unreachable("Unhandled instruction class"); +#define ENUM_ENTRY(n, r, d) case n: return #n; break; + INSTRUCTION_CONTEXTS +#undef ENUM_ENTRY + } + + return 0; +} + +/// stringForOperandType - Like stringForContext, but for OperandTypes. +static inline const char* stringForOperandType(OperandType type) { + switch (type) { + default: + llvm_unreachable("Unhandled type"); +#define ENUM_ENTRY(i, d) case i: return #i; + TYPES +#undef ENUM_ENTRY + } +} + +/// stringForOperandEncoding - like stringForContext, but for +/// OperandEncodings. +static inline const char* stringForOperandEncoding(OperandEncoding encoding) { + switch (encoding) { + default: + llvm_unreachable("Unhandled encoding"); +#define ENUM_ENTRY(i, d) case i: return #i; + ENCODINGS +#undef ENUM_ENTRY + } +} + +void DisassemblerTables::emitOneID(raw_ostream &o, + uint32_t &i, + InstrUID id, + bool addComma) const { + if (id) + o.indent(i * 2) << format("0x%hx", id); + else + o.indent(i * 2) << 0; + + if (addComma) + o << ", "; + else + o << " "; + + o << "/* "; + o << InstructionSpecifiers[id].name; + o << "*/"; + + o << "\n"; +} + +/// emitEmptyTable - Emits the modRMEmptyTable, which is used as a ID table by +/// all ModR/M decisions for instructions that are invalid for all possible +/// ModR/M byte values. +/// +/// @param o - The output stream on which to emit the table. +/// @param i - The indentation level for that output stream. +static void emitEmptyTable(raw_ostream &o, uint32_t &i) +{ + o.indent(i * 2) << "InstrUID modRMEmptyTable[1] = { 0 };" << "\n"; + o << "\n"; +} + +/// getDecisionType - Determines whether a ModRM decision with 255 entries can +/// be compacted by eliminating redundant information. +/// +/// @param decision - The decision to be compacted. +/// @return - The compactest available representation for the decision. +static ModRMDecisionType getDecisionType(ModRMDecision &decision) +{ + bool satisfiesOneEntry = true; + bool satisfiesSplitRM = true; + + uint16_t index; + + for (index = 0; index < 256; ++index) { + if (decision.instructionIDs[index] != decision.instructionIDs[0]) + satisfiesOneEntry = false; + + if (((index & 0xc0) == 0xc0) && + (decision.instructionIDs[index] != decision.instructionIDs[0xc0])) + satisfiesSplitRM = false; + + if (((index & 0xc0) != 0xc0) && + (decision.instructionIDs[index] != decision.instructionIDs[0x00])) + satisfiesSplitRM = false; + } + + if (satisfiesOneEntry) + return MODRM_ONEENTRY; + + if (satisfiesSplitRM) + return MODRM_SPLITRM; + + return MODRM_FULL; +} + +/// stringForDecisionType - Returns a statically-allocated string corresponding +/// to a particular decision type. +/// +/// @param dt - The decision type. +/// @return - A pointer to the statically-allocated string (e.g., +/// "MODRM_ONEENTRY" for MODRM_ONEENTRY). +static const char* stringForDecisionType(ModRMDecisionType dt) +{ +#define ENUM_ENTRY(n) case n: return #n; + switch (dt) { + default: + llvm_unreachable("Unknown decision type"); + MODRMTYPES + }; +#undef ENUM_ENTRY +} + +/// stringForModifierType - Returns a statically-allocated string corresponding +/// to an opcode modifier type. +/// +/// @param mt - The modifier type. +/// @return - A pointer to the statically-allocated string (e.g., +/// "MODIFIER_NONE" for MODIFIER_NONE). +static const char* stringForModifierType(ModifierType mt) +{ +#define ENUM_ENTRY(n) case n: return #n; + switch(mt) { + default: + llvm_unreachable("Unknown modifier type"); + MODIFIER_TYPES + }; +#undef ENUM_ENTRY +} + +DisassemblerTables::DisassemblerTables() { + unsigned i; + + for (i = 0; i < 4; i++) { + Tables[i] = new ContextDecision; + memset(Tables[i], 0, sizeof(ContextDecision)); + } + + HasConflicts = false; +} + +DisassemblerTables::~DisassemblerTables() { + unsigned i; + + for (i = 0; i < 4; i++) + delete Tables[i]; +} + +void DisassemblerTables::emitModRMDecision(raw_ostream &o1, + raw_ostream &o2, + uint32_t &i1, + uint32_t &i2, + ModRMDecision &decision) + const { + static uint64_t sTableNumber = 0; + uint64_t thisTableNumber = sTableNumber; + ModRMDecisionType dt = getDecisionType(decision); + uint16_t index; + + if (dt == MODRM_ONEENTRY && decision.instructionIDs[0] == 0) + { + o2.indent(i2) << "{ /* ModRMDecision */" << "\n"; + i2++; + + o2.indent(i2) << stringForDecisionType(dt) << "," << "\n"; + o2.indent(i2) << "modRMEmptyTable"; + + i2--; + o2.indent(i2) << "}"; + return; + } + + o1.indent(i1) << "InstrUID modRMTable" << thisTableNumber; + + switch (dt) { + default: + llvm_unreachable("Unknown decision type"); + case MODRM_ONEENTRY: + o1 << "[1]"; + break; + case MODRM_SPLITRM: + o1 << "[2]"; + break; + case MODRM_FULL: + o1 << "[256]"; + break; + } + + o1 << " = {" << "\n"; + i1++; + + switch (dt) { + default: + llvm_unreachable("Unknown decision type"); + case MODRM_ONEENTRY: + emitOneID(o1, i1, decision.instructionIDs[0], false); + break; + case MODRM_SPLITRM: + emitOneID(o1, i1, decision.instructionIDs[0x00], true); // mod = 0b00 + emitOneID(o1, i1, decision.instructionIDs[0xc0], false); // mod = 0b11 + break; + case MODRM_FULL: + for (index = 0; index < 256; ++index) + emitOneID(o1, i1, decision.instructionIDs[index], index < 255); + break; + } + + i1--; + o1.indent(i1) << "};" << "\n"; + o1 << "\n"; + + o2.indent(i2) << "{ /* struct ModRMDecision */" << "\n"; + i2++; + + o2.indent(i2) << stringForDecisionType(dt) << "," << "\n"; + o2.indent(i2) << "modRMTable" << sTableNumber << "\n"; + + i2--; + o2.indent(i2) << "}"; + + ++sTableNumber; +} + +void DisassemblerTables::emitOpcodeDecision( + raw_ostream &o1, + raw_ostream &o2, + uint32_t &i1, + uint32_t &i2, + OpcodeDecision &decision) const { + uint16_t index; + + o2.indent(i2) << "{ /* struct OpcodeDecision */" << "\n"; + i2++; + o2.indent(i2) << "{" << "\n"; + i2++; + + for (index = 0; index < 256; ++index) { + o2.indent(i2); + + o2 << "/* 0x" << format("%02hhx", index) << " */" << "\n"; + + emitModRMDecision(o1, o2, i1, i2, decision.modRMDecisions[index]); + + if (index < 255) + o2 << ","; + + o2 << "\n"; + } + + i2--; + o2.indent(i2) << "}" << "\n"; + i2--; + o2.indent(i2) << "}" << "\n"; +} + +void DisassemblerTables::emitContextDecision( + raw_ostream &o1, + raw_ostream &o2, + uint32_t &i1, + uint32_t &i2, + ContextDecision &decision, + const char* name) const { + o2.indent(i2) << "struct ContextDecision " << name << " = {" << "\n"; + i2++; + o2.indent(i2) << "{ /* opcodeDecisions */" << "\n"; + i2++; + + unsigned index; + + for (index = 0; index < IC_max; ++index) { + o2.indent(i2) << "/* "; + o2 << stringForContext((InstructionContext)index); + o2 << " */"; + o2 << "\n"; + + emitOpcodeDecision(o1, o2, i1, i2, decision.opcodeDecisions[index]); + + if (index + 1 < IC_max) + o2 << ", "; + } + + i2--; + o2.indent(i2) << "}" << "\n"; + i2--; + o2.indent(i2) << "};" << "\n"; +} + +void DisassemblerTables::emitInstructionInfo(raw_ostream &o, uint32_t &i) + const { + o.indent(i * 2) << "struct InstructionSpecifier "; + o << INSTRUCTIONS_STR << "["; + o << InstructionSpecifiers.size(); + o << "] = {" << "\n"; + + i++; + + uint16_t numInstructions = InstructionSpecifiers.size(); + uint16_t index, operandIndex; + + for (index = 0; index < numInstructions; ++index) { + o.indent(i * 2) << "{ /* " << index << " */" << "\n"; + i++; + + o.indent(i * 2) << + stringForModifierType(InstructionSpecifiers[index].modifierType); + o << "," << "\n"; + + o.indent(i * 2) << "0x"; + o << format("%02hhx", (uint16_t)InstructionSpecifiers[index].modifierBase); + o << "," << "\n"; + + o.indent(i * 2) << "{" << "\n"; + i++; + + for (operandIndex = 0; operandIndex < X86_MAX_OPERANDS; ++operandIndex) { + o.indent(i * 2) << "{ "; + o << stringForOperandEncoding(InstructionSpecifiers[index] + .operands[operandIndex] + .encoding); + o << ", "; + o << stringForOperandType(InstructionSpecifiers[index] + .operands[operandIndex] + .type); + o << " }"; + + if (operandIndex < X86_MAX_OPERANDS - 1) + o << ","; + + o << "\n"; + } + + i--; + o.indent(i * 2) << "}," << "\n"; + + o.indent(i * 2) << "\"" << InstructionSpecifiers[index].name << "\""; + o << "\n"; + + i--; + o.indent(i * 2) << "}"; + + if (index + 1 < numInstructions) + o << ","; + + o << "\n"; + } + + i--; + o.indent(i * 2) << "};" << "\n"; +} + +void DisassemblerTables::emitContextTable(raw_ostream &o, uint32_t &i) const { + uint16_t index; + + o.indent(i * 2) << "InstructionContext "; + o << CONTEXTS_STR << "[256] = {" << "\n"; + i++; + + for (index = 0; index < 256; ++index) { + o.indent(i * 2); + + if ((index & ATTR_64BIT) && (index & ATTR_REXW) && (index & ATTR_XS)) + o << "IC_64BIT_REXW_XS"; + else if ((index & ATTR_64BIT) && (index & ATTR_REXW) && (index & ATTR_XD)) + o << "IC_64BIT_REXW_XD"; + else if ((index & ATTR_64BIT) && (index & ATTR_REXW) && + (index & ATTR_OPSIZE)) + o << "IC_64BIT_REXW_OPSIZE"; + else if ((index & ATTR_64BIT) && (index & ATTR_XS)) + o << "IC_64BIT_XS"; + else if ((index & ATTR_64BIT) && (index & ATTR_XD)) + o << "IC_64BIT_XD"; + else if ((index & ATTR_64BIT) && (index & ATTR_OPSIZE)) + o << "IC_64BIT_OPSIZE"; + else if ((index & ATTR_64BIT) && (index & ATTR_REXW)) + o << "IC_64BIT_REXW"; + else if ((index & ATTR_64BIT)) + o << "IC_64BIT"; + else if (index & ATTR_XS) + o << "IC_XS"; + else if (index & ATTR_XD) + o << "IC_XD"; + else if (index & ATTR_OPSIZE) + o << "IC_OPSIZE"; + else + o << "IC"; + + if (index < 255) + o << ","; + else + o << " "; + + o << " /* " << index << " */"; + + o << "\n"; + } + + i--; + o.indent(i * 2) << "};" << "\n"; +} + +void DisassemblerTables::emitContextDecisions(raw_ostream &o1, + raw_ostream &o2, + uint32_t &i1, + uint32_t &i2) + const { + emitContextDecision(o1, o2, i1, i2, *Tables[0], ONEBYTE_STR); + emitContextDecision(o1, o2, i1, i2, *Tables[1], TWOBYTE_STR); + emitContextDecision(o1, o2, i1, i2, *Tables[2], THREEBYTE38_STR); + emitContextDecision(o1, o2, i1, i2, *Tables[3], THREEBYTE3A_STR); +} + +void DisassemblerTables::emit(raw_ostream &o) const { + uint32_t i1 = 0; + uint32_t i2 = 0; + + std::string s1; + std::string s2; + + raw_string_ostream o1(s1); + raw_string_ostream o2(s2); + + emitInstructionInfo(o, i2); + o << "\n"; + + emitContextTable(o, i2); + o << "\n"; + + emitEmptyTable(o1, i1); + emitContextDecisions(o1, o2, i1, i2); + + o << o1.str(); + o << "\n"; + o << o2.str(); + o << "\n"; + o << "\n"; +} + +void DisassemblerTables::setTableFields(ModRMDecision &decision, + const ModRMFilter &filter, + InstrUID uid, + uint8_t opcode) { + unsigned index; + + for (index = 0; index < 256; ++index) { + if (filter.accepts(index)) { + if (decision.instructionIDs[index] == uid) + continue; + + if (decision.instructionIDs[index] != 0) { + InstructionSpecifier &newInfo = + InstructionSpecifiers[uid]; + InstructionSpecifier &previousInfo = + InstructionSpecifiers[decision.instructionIDs[index]]; + + if(newInfo.filtered) + continue; // filtered instructions get lowest priority + + if(previousInfo.name == "NOOP") + continue; // special case for XCHG32ar and NOOP + + if (outranks(previousInfo.insnContext, newInfo.insnContext)) + continue; + + if (previousInfo.insnContext == newInfo.insnContext && + !previousInfo.filtered) { + errs() << "Error: Primary decode conflict: "; + errs() << newInfo.name << " would overwrite " << previousInfo.name; + errs() << "\n"; + errs() << "ModRM " << index << "\n"; + errs() << "Opcode " << (uint16_t)opcode << "\n"; + errs() << "Context " << stringForContext(newInfo.insnContext) << "\n"; + HasConflicts = true; + } + } + + decision.instructionIDs[index] = uid; + } + } +} + +void DisassemblerTables::setTableFields(OpcodeType type, + InstructionContext insnContext, + uint8_t opcode, + const ModRMFilter &filter, + InstrUID uid) { + unsigned index; + + ContextDecision &decision = *Tables[type]; + + for (index = 0; index < IC_max; ++index) { + if (inheritsFrom((InstructionContext)index, + InstructionSpecifiers[uid].insnContext)) + setTableFields(decision.opcodeDecisions[index].modRMDecisions[opcode], + filter, + uid, + opcode); + } +} diff --git a/utils/TableGen/X86DisassemblerTables.h b/utils/TableGen/X86DisassemblerTables.h new file mode 100644 index 0000000..08eba01 --- /dev/null +++ b/utils/TableGen/X86DisassemblerTables.h @@ -0,0 +1,291 @@ +//===- X86DisassemblerTables.h - Disassembler tables ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is part of the X86 Disassembler Emitter. +// It contains the interface of the disassembler tables. +// Documentation for the disassembler emitter in general can be found in +// X86DisasemblerEmitter.h. +// +//===----------------------------------------------------------------------===// + +#ifndef X86DISASSEMBLERTABLES_H +#define X86DISASSEMBLERTABLES_H + +#include "X86DisassemblerShared.h" +#include "X86ModRMFilters.h" + +#include "llvm/Support/raw_ostream.h" + +#include <vector> + +namespace llvm { + +namespace X86Disassembler { + +/// DisassemblerTables - Encapsulates all the decode tables being generated by +/// the table emitter. Contains functions to populate the tables as well as +/// to emit them as hierarchical C structures suitable for consumption by the +/// runtime. +class DisassemblerTables { +private: + /// The decoder tables. There is one for each opcode type: + /// [0] one-byte opcodes + /// [1] two-byte opcodes of the form 0f __ + /// [2] three-byte opcodes of the form 0f 38 __ + /// [3] three-byte opcodes of the form 0f 3a __ + ContextDecision* Tables[4]; + + /// The instruction information table + std::vector<InstructionSpecifier> InstructionSpecifiers; + + /// True if there are primary decode conflicts in the instruction set + bool HasConflicts; + + /// emitOneID - Emits a table entry for a single instruction entry, at the + /// innermost level of the structure hierarchy. The entry is printed out + /// in the format "nnnn, /* MNEMONIC */" where nnnn is the ID in decimal, + /// the comma is printed if addComma is true, and the menonic is the name + /// of the instruction as listed in the LLVM tables. + /// + /// @param o - The output stream to print the entry on. + /// @param i - The indentation level for o. + /// @param id - The unique ID of the instruction to print. + /// @param addComma - Whether or not to print a comma after the ID. True if + /// additional items will follow. + void emitOneID(raw_ostream &o, + uint32_t &i, + InstrUID id, + bool addComma) const; + + /// emitModRMDecision - Emits a table of entries corresponding to a single + /// ModR/M decision. Compacts the ModR/M decision if possible. ModR/M + /// decisions are printed as: + /// + /// { /* struct ModRMDecision */ + /// TYPE, + /// modRMTablennnn + /// } + /// + /// where nnnn is a unique ID for the corresponding table of IDs. + /// TYPE indicates whether the table has one entry that is the same + /// regardless of ModR/M byte, two entries - one for bytes 0x00-0xbf and one + /// for bytes 0xc0-0xff -, or 256 entries, one for each possible byte. + /// nnnn is the number of a table for looking up these values. The tables + /// are writen separately so that tables consisting entirely of zeros will + /// not be duplicated. (These all have the name modRMEmptyTable.) A table + /// is printed as: + /// + /// InstrUID modRMTablennnn[k] = { + /// nnnn, /* MNEMONIC */ + /// ... + /// nnnn /* MNEMONIC */ + /// }; + /// + /// @param o1 - The output stream to print the ID table to. + /// @param o2 - The output stream to print the decision structure to. + /// @param i1 - The indentation level to use with stream o1. + /// @param i2 - The indentation level to use with stream o2. + /// @param decision - The ModR/M decision to emit. This decision has 256 + /// entries - emitModRMDecision decides how to compact it. + void emitModRMDecision(raw_ostream &o1, + raw_ostream &o2, + uint32_t &i1, + uint32_t &i2, + ModRMDecision &decision) const; + + /// emitOpcodeDecision - Emits an OpcodeDecision and all its subsidiary ModR/M + /// decisions. An OpcodeDecision is printed as: + /// + /// { /* struct OpcodeDecision */ + /// /* 0x00 */ + /// { /* struct ModRMDecision */ + /// ... + /// } + /// ... + /// } + /// + /// where the ModRMDecision structure is printed as described in the + /// documentation for emitModRMDecision(). emitOpcodeDecision() passes on a + /// stream and indent level for the UID tables generated by + /// emitModRMDecision(), but does not use them itself. + /// + /// @param o1 - The output stream to print the ID tables generated by + /// emitModRMDecision() to. + /// @param o2 - The output stream for the decision structure itself. + /// @param i1 - The indent level to use with stream o1. + /// @param i2 - The indent level to use with stream o2. + /// @param decision - The OpcodeDecision to emit along with its subsidiary + /// structures. + void emitOpcodeDecision(raw_ostream &o1, + raw_ostream &o2, + uint32_t &i1, + uint32_t &i2, + OpcodeDecision &decision) const; + + /// emitContextDecision - Emits a ContextDecision and all its subsidiary + /// Opcode and ModRMDecisions. A ContextDecision is printed as: + /// + /// struct ContextDecision NAME = { + /// { /* OpcodeDecisions */ + /// /* IC */ + /// { /* struct OpcodeDecision */ + /// ... + /// }, + /// ... + /// } + /// } + /// + /// NAME is the name of the ContextDecision (typically one of the four names + /// ONEBYTE_SYM, TWOBYTE_SYM, THREEBYTE38_SYM, and THREEBYTE3A_SYM from + /// X86DisassemblerDecoderCommon.h). + /// IC is one of the contexts in InstructionContext. There is an opcode + /// decision for each possible context. + /// The OpcodeDecision structures are printed as described in the + /// documentation for emitOpcodeDecision. + /// + /// @param o1 - The output stream to print the ID tables generated by + /// emitModRMDecision() to. + /// @param o2 - The output stream to print the decision structure to. + /// @param i1 - The indent level to use with stream o1. + /// @param i2 - The indent level to use with stream o2. + /// @param decision - The ContextDecision to emit along with its subsidiary + /// structures. + /// @param name - The name for the ContextDecision. + void emitContextDecision(raw_ostream &o1, + raw_ostream &o2, + uint32_t &i1, + uint32_t &i2, + ContextDecision &decision, + const char* name) const; + + /// emitInstructionInfo - Prints the instruction specifier table, which has + /// one entry for each instruction, and contains name and operand + /// information. This table is printed as: + /// + /// struct InstructionSpecifier CONTEXTS_SYM[k] = { + /// { + /// /* nnnn */ + /// "MNEMONIC", + /// 0xnn, + /// { + /// { + /// ENCODING, + /// TYPE + /// }, + /// ... + /// } + /// }, + /// }; + /// + /// k is the total number of instructions. + /// nnnn is the ID of the current instruction (0-based). This table + /// includes entries for non-instructions like PHINODE. + /// 0xnn is the lowest possible opcode for the current instruction, used for + /// AddRegFrm instructions to compute the operand's value. + /// ENCODING and TYPE describe the encoding and type for a single operand. + /// + /// @param o - The output stream to which the instruction table should be + /// written. + /// @param i - The indent level for use with the stream. + void emitInstructionInfo(raw_ostream &o, uint32_t &i) const; + + /// emitContextTable - Prints the table that is used to translate from an + /// instruction attribute mask to an instruction context. This table is + /// printed as: + /// + /// InstructionContext CONTEXTS_STR[256] = { + /// IC, /* 0x00 */ + /// ... + /// }; + /// + /// IC is the context corresponding to the mask 0x00, and there are 256 + /// possible masks. + /// + /// @param o - The output stream to which the context table should be written. + /// @param i - The indent level for use with the stream. + void emitContextTable(raw_ostream &o, uint32_t &i) const; + + /// emitContextDecisions - Prints all four ContextDecision structures using + /// emitContextDecision(). + /// + /// @param o1 - The output stream to print the ID tables generated by + /// emitModRMDecision() to. + /// @param o2 - The output stream to print the decision structures to. + /// @param i1 - The indent level to use with stream o1. + /// @param i2 - The indent level to use with stream o2. + void emitContextDecisions(raw_ostream &o1, + raw_ostream &o2, + uint32_t &i1, + uint32_t &i2) const; + + /// setTableFields - Uses a ModRMFilter to set the appropriate entries in a + /// ModRMDecision to refer to a particular instruction ID. + /// + /// @param decision - The ModRMDecision to populate. + /// @param filter - The filter to use in deciding which entries to populate. + /// @param uid - The unique ID to set matching entries to. + /// @param opcode - The opcode of the instruction, for error reporting. + void setTableFields(ModRMDecision &decision, + const ModRMFilter &filter, + InstrUID uid, + uint8_t opcode); +public: + /// Constructor - Allocates space for the class decisions and clears them. + DisassemblerTables(); + + ~DisassemblerTables(); + + /// emit - Emits the instruction table, context table, and class decisions. + /// + /// @param o - The output stream to print the tables to. + void emit(raw_ostream &o) const; + + /// setTableFields - Uses the opcode type, instruction context, opcode, and a + /// ModRMFilter as criteria to set a particular set of entries in the + /// decode tables to point to a specific uid. + /// + /// @param type - The opcode type (ONEBYTE, TWOBYTE, etc.) + /// @param insnContext - The context to use (IC, IC_64BIT, etc.) + /// @param opcode - The last byte of the opcode (not counting any escape + /// or extended opcodes). + /// @param filter - The ModRMFilter that decides which ModR/M byte values + /// correspond to the desired instruction. + /// @param uid - The unique ID of the instruction. + void setTableFields(OpcodeType type, + InstructionContext insnContext, + uint8_t opcode, + const ModRMFilter &filter, + InstrUID uid); + + /// specForUID - Returns the instruction specifier for a given unique + /// instruction ID. Used when resolving collisions. + /// + /// @param uid - The unique ID of the instruction. + /// @return - A reference to the instruction specifier. + InstructionSpecifier& specForUID(InstrUID uid) { + if (uid >= InstructionSpecifiers.size()) + InstructionSpecifiers.resize(uid + 1); + + return InstructionSpecifiers[uid]; + } + + // hasConflicts - Reports whether there were primary decode conflicts + // from any instructions added to the tables. + // @return - true if there were; false otherwise. + + bool hasConflicts() { + return HasConflicts; + } +}; + +} // namespace X86Disassembler + +} // namespace llvm + +#endif diff --git a/utils/TableGen/X86ModRMFilters.h b/utils/TableGen/X86ModRMFilters.h new file mode 100644 index 0000000..45cb07a --- /dev/null +++ b/utils/TableGen/X86ModRMFilters.h @@ -0,0 +1,197 @@ +//===- X86ModRMFilters.h - Disassembler ModR/M filterss ---------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is part of the X86 Disassembler Emitter. +// It contains ModR/M filters that determine which values of the ModR/M byte +// are valid for a partiuclar instruction. +// Documentation for the disassembler emitter in general can be found in +// X86DisasemblerEmitter.h. +// +//===----------------------------------------------------------------------===// + +#ifndef X86MODRMFILTERS_H +#define X86MODRMFILTERS_H + +#include "llvm/System/DataTypes.h" + +namespace llvm { + +namespace X86Disassembler { + +/// ModRMFilter - Abstract base class for clases that recognize patterns in +/// ModR/M bytes. +class ModRMFilter { +public: + /// Destructor - Override as necessary. + virtual ~ModRMFilter() { } + + /// isDumb - Indicates whether this filter returns the same value for + /// any value of the ModR/M byte. + /// + /// @result - True if the filter returns the same value for any ModR/M + /// byte; false if not. + virtual bool isDumb() const { return false; } + + /// accepts - Indicates whether the filter accepts a particular ModR/M + /// byte value. + /// + /// @result - True if the filter accepts the ModR/M byte; false if not. + virtual bool accepts(uint8_t modRM) const = 0; +}; + +/// DumbFilter - Accepts any ModR/M byte. Used for instructions that do not +/// require a ModR/M byte or instructions where the entire ModR/M byte is used +/// for operands. +class DumbFilter : public ModRMFilter { +public: + bool isDumb() const { + return true; + } + + bool accepts(uint8_t modRM) const { + return true; + } +}; + +/// ModFilter - Filters based on the mod bits [bits 7-6] of the ModR/M byte. +/// Some instructions are classified based on whether they are 11 or anything +/// else. This filter performs that classification. +class ModFilter : public ModRMFilter { +private: + bool R; +public: + /// Constructor + /// + /// @r - True if the mod bits of the ModR/M byte must be 11; false + /// otherwise. The name r derives from the fact that the mod + /// bits indicate whether the R/M bits [bits 2-0] signify a + /// register or a memory operand. + ModFilter(bool r) : + ModRMFilter(), + R(r) { + } + + bool accepts(uint8_t modRM) const { + if (R == ((modRM & 0xc0) == 0xc0)) + return true; + else + return false; + } +}; + +/// EscapeFilter - Filters escape opcodes, which are classified in two ways. If +/// the ModR/M byte is between 0xc0 and 0xff, then there is one slot for each +/// possible value. Otherwise, there is one instruction for each value of the +/// nnn field [bits 5-3], known elsewhere as the reg field. +class EscapeFilter : public ModRMFilter { +private: + bool C0_FF; + uint8_t NNN_or_ModRM; +public: + /// Constructor + /// + /// @c0_ff - True if the ModR/M byte must fall between 0xc0 and 0xff; + /// false otherwise. + /// @nnn_or_modRM - If c0_ff is true, the required value of the entire ModR/M + /// byte. If c0_ff is false, the required value of the nnn + /// field. + EscapeFilter(bool c0_ff, uint8_t nnn_or_modRM) : + ModRMFilter(), + C0_FF(c0_ff), + NNN_or_ModRM(nnn_or_modRM) { + } + + bool accepts(uint8_t modRM) const { + if ((C0_FF && modRM >= 0xc0 && (modRM == NNN_or_ModRM)) || + (!C0_FF && modRM < 0xc0 && ((modRM & 0x38) >> 3) == NNN_or_ModRM)) + return true; + else + return false; + } +}; + +/// AddRegEscapeFilter - Some escape opcodes have one of the register operands +/// added to the ModR/M byte, meaning that a range of eight ModR/M values +/// maps to a single instruction. Such instructions require the ModR/M byte +/// to fall between 0xc0 and 0xff. +class AddRegEscapeFilter : public ModRMFilter { +private: + uint8_t ModRM; +public: + /// Constructor + /// + /// @modRM - The value of the ModR/M byte when the register operand + /// refers to the first register in the register set. + AddRegEscapeFilter(uint8_t modRM) : ModRM(modRM) { + } + + bool accepts(uint8_t modRM) const { + if (modRM >= ModRM && modRM < ModRM + 8) + return true; + else + return false; + } +}; + +/// ExtendedFilter - Extended opcodes are classified based on the value of the +/// mod field [bits 7-6] and the value of the nnn field [bits 5-3]. +class ExtendedFilter : public ModRMFilter { +private: + bool R; + uint8_t NNN; +public: + /// Constructor + /// + /// @r - True if the mod field must be set to 11; false otherwise. + /// The name is explained at ModFilter. + /// @nnn - The required value of the nnn field. + ExtendedFilter(bool r, uint8_t nnn) : + ModRMFilter(), + R(r), + NNN(nnn) { + } + + bool accepts(uint8_t modRM) const { + if (((R && ((modRM & 0xc0) == 0xc0)) || + (!R && ((modRM & 0xc0) != 0xc0))) && + (((modRM & 0x38) >> 3) == NNN)) + return true; + else + return false; + } +}; + +/// ExactFilter - The occasional extended opcode (such as VMCALL or MONITOR) +/// requires the ModR/M byte to have a specific value. +class ExactFilter : public ModRMFilter +{ +private: + uint8_t ModRM; +public: + /// Constructor + /// + /// @modRM - The required value of the full ModR/M byte. + ExactFilter(uint8_t modRM) : + ModRMFilter(), + ModRM(modRM) { + } + + bool accepts(uint8_t modRM) const { + if (ModRM == modRM) + return true; + else + return false; + } +}; + +} // namespace X86Disassembler + +} // namespace llvm + +#endif diff --git a/utils/TableGen/X86RecognizableInstr.cpp b/utils/TableGen/X86RecognizableInstr.cpp new file mode 100644 index 0000000..2b6e30d --- /dev/null +++ b/utils/TableGen/X86RecognizableInstr.cpp @@ -0,0 +1,959 @@ +//===- X86RecognizableInstr.cpp - Disassembler instruction spec --*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is part of the X86 Disassembler Emitter. +// It contains the implementation of a single recognizable instruction. +// Documentation for the disassembler emitter in general can be found in +// X86DisasemblerEmitter.h. +// +//===----------------------------------------------------------------------===// + +#include "X86DisassemblerShared.h" +#include "X86RecognizableInstr.h" +#include "X86ModRMFilters.h" + +#include "llvm/Support/ErrorHandling.h" + +#include <string> + +using namespace llvm; + +// A clone of X86 since we can't depend on something that is generated. +namespace X86Local { + enum { + Pseudo = 0, + RawFrm = 1, + AddRegFrm = 2, + MRMDestReg = 3, + MRMDestMem = 4, + MRMSrcReg = 5, + MRMSrcMem = 6, + MRM0r = 16, MRM1r = 17, MRM2r = 18, MRM3r = 19, + MRM4r = 20, MRM5r = 21, MRM6r = 22, MRM7r = 23, + MRM0m = 24, MRM1m = 25, MRM2m = 26, MRM3m = 27, + MRM4m = 28, MRM5m = 29, MRM6m = 30, MRM7m = 31, + MRMInitReg = 32 + }; + + enum { + TB = 1, + REP = 2, + D8 = 3, D9 = 4, DA = 5, DB = 6, + DC = 7, DD = 8, DE = 9, DF = 10, + XD = 11, XS = 12, + T8 = 13, TA = 14 + }; +} + +#define ONE_BYTE_EXTENSION_TABLES \ + EXTENSION_TABLE(80) \ + EXTENSION_TABLE(81) \ + EXTENSION_TABLE(82) \ + EXTENSION_TABLE(83) \ + EXTENSION_TABLE(8f) \ + EXTENSION_TABLE(c0) \ + EXTENSION_TABLE(c1) \ + EXTENSION_TABLE(c6) \ + EXTENSION_TABLE(c7) \ + EXTENSION_TABLE(d0) \ + EXTENSION_TABLE(d1) \ + EXTENSION_TABLE(d2) \ + EXTENSION_TABLE(d3) \ + EXTENSION_TABLE(f6) \ + EXTENSION_TABLE(f7) \ + EXTENSION_TABLE(fe) \ + EXTENSION_TABLE(ff) + +#define TWO_BYTE_EXTENSION_TABLES \ + EXTENSION_TABLE(00) \ + EXTENSION_TABLE(01) \ + EXTENSION_TABLE(18) \ + EXTENSION_TABLE(71) \ + EXTENSION_TABLE(72) \ + EXTENSION_TABLE(73) \ + EXTENSION_TABLE(ae) \ + EXTENSION_TABLE(b9) \ + EXTENSION_TABLE(ba) \ + EXTENSION_TABLE(c7) + +#define TWO_BYTE_FULL_EXTENSION_TABLES \ + EXTENSION_TABLE(01) + + +using namespace X86Disassembler; + +/// needsModRMForDecode - Indicates whether a particular instruction requires a +/// ModR/M byte for the instruction to be properly decoded. For example, a +/// MRMDestReg instruction needs the Mod field in the ModR/M byte to be set to +/// 0b11. +/// +/// @param form - The form of the instruction. +/// @return - true if the form implies that a ModR/M byte is required, false +/// otherwise. +static bool needsModRMForDecode(uint8_t form) { + if (form == X86Local::MRMDestReg || + form == X86Local::MRMDestMem || + form == X86Local::MRMSrcReg || + form == X86Local::MRMSrcMem || + (form >= X86Local::MRM0r && form <= X86Local::MRM7r) || + (form >= X86Local::MRM0m && form <= X86Local::MRM7m)) + return true; + else + return false; +} + +/// isRegFormat - Indicates whether a particular form requires the Mod field of +/// the ModR/M byte to be 0b11. +/// +/// @param form - The form of the instruction. +/// @return - true if the form implies that Mod must be 0b11, false +/// otherwise. +static bool isRegFormat(uint8_t form) { + if (form == X86Local::MRMDestReg || + form == X86Local::MRMSrcReg || + (form >= X86Local::MRM0r && form <= X86Local::MRM7r)) + return true; + else + return false; +} + +/// byteFromBitsInit - Extracts a value at most 8 bits in width from a BitsInit. +/// Useful for switch statements and the like. +/// +/// @param init - A reference to the BitsInit to be decoded. +/// @return - The field, with the first bit in the BitsInit as the lowest +/// order bit. +static uint8_t byteFromBitsInit(BitsInit &init) { + int width = init.getNumBits(); + + assert(width <= 8 && "Field is too large for uint8_t!"); + + int index; + uint8_t mask = 0x01; + + uint8_t ret = 0; + + for (index = 0; index < width; index++) { + if (static_cast<BitInit*>(init.getBit(index))->getValue()) + ret |= mask; + + mask <<= 1; + } + + return ret; +} + +/// byteFromRec - Extract a value at most 8 bits in with from a Record given the +/// name of the field. +/// +/// @param rec - The record from which to extract the value. +/// @param name - The name of the field in the record. +/// @return - The field, as translated by byteFromBitsInit(). +static uint8_t byteFromRec(const Record* rec, const std::string &name) { + BitsInit* bits = rec->getValueAsBitsInit(name); + return byteFromBitsInit(*bits); +} + +RecognizableInstr::RecognizableInstr(DisassemblerTables &tables, + const CodeGenInstruction &insn, + InstrUID uid) { + UID = uid; + + Rec = insn.TheDef; + Name = Rec->getName(); + Spec = &tables.specForUID(UID); + + if (!Rec->isSubClassOf("X86Inst")) { + ShouldBeEmitted = false; + return; + } + + Prefix = byteFromRec(Rec, "Prefix"); + Opcode = byteFromRec(Rec, "Opcode"); + Form = byteFromRec(Rec, "FormBits"); + SegOvr = byteFromRec(Rec, "SegOvrBits"); + + HasOpSizePrefix = Rec->getValueAsBit("hasOpSizePrefix"); + HasREX_WPrefix = Rec->getValueAsBit("hasREX_WPrefix"); + HasLockPrefix = Rec->getValueAsBit("hasLockPrefix"); + IsCodeGenOnly = Rec->getValueAsBit("isCodeGenOnly"); + + Name = Rec->getName(); + AsmString = Rec->getValueAsString("AsmString"); + + Operands = &insn.OperandList; + + IsSSE = HasOpSizePrefix && (Name.find("16") == Name.npos); + HasFROperands = false; + + ShouldBeEmitted = true; +} + +void RecognizableInstr::processInstr(DisassemblerTables &tables, + const CodeGenInstruction &insn, + InstrUID uid) +{ + RecognizableInstr recogInstr(tables, insn, uid); + + recogInstr.emitInstructionSpecifier(tables); + + if (recogInstr.shouldBeEmitted()) + recogInstr.emitDecodePath(tables); +} + +InstructionContext RecognizableInstr::insnContext() const { + InstructionContext insnContext; + + if (Name.find("64") != Name.npos || HasREX_WPrefix) { + if (HasREX_WPrefix && HasOpSizePrefix) + insnContext = IC_64BIT_REXW_OPSIZE; + else if (HasOpSizePrefix) + insnContext = IC_64BIT_OPSIZE; + else if (HasREX_WPrefix && Prefix == X86Local::XS) + insnContext = IC_64BIT_REXW_XS; + else if (HasREX_WPrefix && Prefix == X86Local::XD) + insnContext = IC_64BIT_REXW_XD; + else if (Prefix == X86Local::XD) + insnContext = IC_64BIT_XD; + else if (Prefix == X86Local::XS) + insnContext = IC_64BIT_XS; + else if (HasREX_WPrefix) + insnContext = IC_64BIT_REXW; + else + insnContext = IC_64BIT; + } else { + if (HasOpSizePrefix) + insnContext = IC_OPSIZE; + else if (Prefix == X86Local::XD) + insnContext = IC_XD; + else if (Prefix == X86Local::XS) + insnContext = IC_XS; + else + insnContext = IC; + } + + return insnContext; +} + +RecognizableInstr::filter_ret RecognizableInstr::filter() const { + // Filter out intrinsics + + if (!Rec->isSubClassOf("X86Inst")) + return FILTER_STRONG; + + if (Form == X86Local::Pseudo || + IsCodeGenOnly) + return FILTER_STRONG; + + // Filter out instructions with a LOCK prefix; + // prefer forms that do not have the prefix + if (HasLockPrefix) + return FILTER_WEAK; + + // Filter out artificial instructions + + if (Name.find("TAILJMP") != Name.npos || + Name.find("_Int") != Name.npos || + Name.find("_int") != Name.npos || + Name.find("Int_") != Name.npos || + Name.find("_NOREX") != Name.npos || + Name.find("EH_RETURN") != Name.npos || + Name.find("V_SET") != Name.npos || + Name.find("LOCK_") != Name.npos || + Name.find("WIN") != Name.npos) + return FILTER_STRONG; + + // Special cases. + + if (Name.find("PCMPISTRI") != Name.npos && Name != "PCMPISTRI") + return FILTER_WEAK; + if (Name.find("PCMPESTRI") != Name.npos && Name != "PCMPESTRI") + return FILTER_WEAK; + + if (Name.find("MOV") != Name.npos && Name.find("r0") != Name.npos) + return FILTER_WEAK; + if (Name.find("MOVZ") != Name.npos && Name.find("MOVZX") == Name.npos) + return FILTER_WEAK; + if (Name.find("Fs") != Name.npos) + return FILTER_WEAK; + if (Name == "MOVLPDrr" || + Name == "MOVLPSrr" || + Name == "PUSHFQ" || + Name == "BSF16rr" || + Name == "BSF16rm" || + Name == "BSR16rr" || + Name == "BSR16rm" || + Name == "MOVSX16rm8" || + Name == "MOVSX16rr8" || + Name == "MOVZX16rm8" || + Name == "MOVZX16rr8" || + Name == "PUSH32i16" || + Name == "PUSH64i16" || + Name == "MOVPQI2QImr" || + Name == "MOVSDmr" || + Name == "MOVSDrm" || + Name == "MOVSSmr" || + Name == "MOVSSrm" || + Name == "MMX_MOVD64rrv164" || + Name == "CRC32m16" || + Name == "MOV64ri64i32" || + Name == "CRC32r16") + return FILTER_WEAK; + + // Filter out instructions with segment override prefixes. + // They're too messy to handle now and we'll special case them if needed. + + if (SegOvr) + return FILTER_STRONG; + + // Filter out instructions that can't be printed. + + if (AsmString.size() == 0) + return FILTER_STRONG; + + // Filter out instructions with subreg operands. + + if (AsmString.find("subreg") != AsmString.npos) + return FILTER_STRONG; + + assert(Form != X86Local::MRMInitReg && + "FORMAT_MRMINITREG instruction not skipped"); + + if (HasFROperands && Name.find("MOV") != Name.npos && + ((Name.find("2") != Name.npos && Name.find("32") == Name.npos) || + (Name.find("to") != Name.npos))) + return FILTER_WEAK; + + return FILTER_NORMAL; +} + +void RecognizableInstr::handleOperand( + bool optional, + unsigned &operandIndex, + unsigned &physicalOperandIndex, + unsigned &numPhysicalOperands, + unsigned *operandMapping, + OperandEncoding (*encodingFromString)(const std::string&, bool hasOpSizePrefix)) { + if (optional) { + if (physicalOperandIndex >= numPhysicalOperands) + return; + } else { + assert(physicalOperandIndex < numPhysicalOperands); + } + + while (operandMapping[operandIndex] != operandIndex) { + Spec->operands[operandIndex].encoding = ENCODING_DUP; + Spec->operands[operandIndex].type = + (OperandType)(TYPE_DUP0 + operandMapping[operandIndex]); + ++operandIndex; + } + + const std::string &typeName = (*Operands)[operandIndex].Rec->getName(); + + Spec->operands[operandIndex].encoding = encodingFromString(typeName, + HasOpSizePrefix); + Spec->operands[operandIndex].type = typeFromString(typeName, + IsSSE, + HasREX_WPrefix, + HasOpSizePrefix); + + ++operandIndex; + ++physicalOperandIndex; +} + +void RecognizableInstr::emitInstructionSpecifier(DisassemblerTables &tables) { + Spec->name = Name; + + if (!Rec->isSubClassOf("X86Inst")) + return; + + switch (filter()) { + case FILTER_WEAK: + Spec->filtered = true; + break; + case FILTER_STRONG: + ShouldBeEmitted = false; + return; + case FILTER_NORMAL: + break; + } + + Spec->insnContext = insnContext(); + + const std::vector<CodeGenInstruction::OperandInfo> &OperandList = *Operands; + + unsigned operandIndex; + unsigned numOperands = OperandList.size(); + unsigned numPhysicalOperands = 0; + + // operandMapping maps from operands in OperandList to their originals. + // If operandMapping[i] != i, then the entry is a duplicate. + unsigned operandMapping[X86_MAX_OPERANDS]; + + bool hasFROperands = false; + + assert(numOperands < X86_MAX_OPERANDS && "X86_MAX_OPERANDS is not large enough"); + + for (operandIndex = 0; operandIndex < numOperands; ++operandIndex) { + if (OperandList[operandIndex].Constraints.size()) { + const std::string &constraint = OperandList[operandIndex].Constraints[0]; + std::string::size_type tiedToPos; + + if ((tiedToPos = constraint.find(" << 16) | (1 << TOI::TIED_TO))")) != + constraint.npos) { + tiedToPos--; + operandMapping[operandIndex] = constraint[tiedToPos] - '0'; + } else { + ++numPhysicalOperands; + operandMapping[operandIndex] = operandIndex; + } + } else { + ++numPhysicalOperands; + operandMapping[operandIndex] = operandIndex; + } + + const std::string &recName = OperandList[operandIndex].Rec->getName(); + + if (recName.find("FR") != recName.npos) + hasFROperands = true; + } + + if (hasFROperands && Name.find("MOV") != Name.npos && + ((Name.find("2") != Name.npos && Name.find("32") == Name.npos) || + (Name.find("to") != Name.npos))) + ShouldBeEmitted = false; + + if (!ShouldBeEmitted) + return; + +#define HANDLE_OPERAND(class) \ + handleOperand(false, \ + operandIndex, \ + physicalOperandIndex, \ + numPhysicalOperands, \ + operandMapping, \ + class##EncodingFromString); + +#define HANDLE_OPTIONAL(class) \ + handleOperand(true, \ + operandIndex, \ + physicalOperandIndex, \ + numPhysicalOperands, \ + operandMapping, \ + class##EncodingFromString); + + // operandIndex should always be < numOperands + operandIndex = 0; + // physicalOperandIndex should always be < numPhysicalOperands + unsigned physicalOperandIndex = 0; + + switch (Form) { + case X86Local::RawFrm: + // Operand 1 (optional) is an address or immediate. + // Operand 2 (optional) is an immediate. + assert(numPhysicalOperands <= 2 && + "Unexpected number of operands for RawFrm"); + HANDLE_OPTIONAL(relocation) + HANDLE_OPTIONAL(immediate) + break; + case X86Local::AddRegFrm: + // Operand 1 is added to the opcode. + // Operand 2 (optional) is an address. + assert(numPhysicalOperands >= 1 && numPhysicalOperands <= 2 && + "Unexpected number of operands for AddRegFrm"); + HANDLE_OPERAND(opcodeModifier) + HANDLE_OPTIONAL(relocation) + break; + case X86Local::MRMDestReg: + // Operand 1 is a register operand in the R/M field. + // Operand 2 is a register operand in the Reg/Opcode field. + // Operand 3 (optional) is an immediate. + assert(numPhysicalOperands >= 2 && numPhysicalOperands <= 3 && + "Unexpected number of operands for MRMDestRegFrm"); + HANDLE_OPERAND(rmRegister) + HANDLE_OPERAND(roRegister) + HANDLE_OPTIONAL(immediate) + break; + case X86Local::MRMDestMem: + // Operand 1 is a memory operand (possibly SIB-extended) + // Operand 2 is a register operand in the Reg/Opcode field. + // Operand 3 (optional) is an immediate. + assert(numPhysicalOperands >= 2 && numPhysicalOperands <= 3 && + "Unexpected number of operands for MRMDestMemFrm"); + HANDLE_OPERAND(memory) + HANDLE_OPERAND(roRegister) + HANDLE_OPTIONAL(immediate) + break; + case X86Local::MRMSrcReg: + // Operand 1 is a register operand in the Reg/Opcode field. + // Operand 2 is a register operand in the R/M field. + // Operand 3 (optional) is an immediate. + assert(numPhysicalOperands >= 2 && numPhysicalOperands <= 3 && + "Unexpected number of operands for MRMSrcRegFrm"); + HANDLE_OPERAND(roRegister) + HANDLE_OPERAND(rmRegister) + HANDLE_OPTIONAL(immediate) + break; + case X86Local::MRMSrcMem: + // Operand 1 is a register operand in the Reg/Opcode field. + // Operand 2 is a memory operand (possibly SIB-extended) + // Operand 3 (optional) is an immediate. + assert(numPhysicalOperands >= 2 && numPhysicalOperands <= 3 && + "Unexpected number of operands for MRMSrcMemFrm"); + HANDLE_OPERAND(roRegister) + HANDLE_OPERAND(memory) + HANDLE_OPTIONAL(immediate) + break; + case X86Local::MRM0r: + case X86Local::MRM1r: + case X86Local::MRM2r: + case X86Local::MRM3r: + case X86Local::MRM4r: + case X86Local::MRM5r: + case X86Local::MRM6r: + case X86Local::MRM7r: + // Operand 1 is a register operand in the R/M field. + // Operand 2 (optional) is an immediate or relocation. + assert(numPhysicalOperands <= 2 && + "Unexpected number of operands for MRMnRFrm"); + HANDLE_OPTIONAL(rmRegister) + HANDLE_OPTIONAL(relocation) + break; + case X86Local::MRM0m: + case X86Local::MRM1m: + case X86Local::MRM2m: + case X86Local::MRM3m: + case X86Local::MRM4m: + case X86Local::MRM5m: + case X86Local::MRM6m: + case X86Local::MRM7m: + // Operand 1 is a memory operand (possibly SIB-extended) + // Operand 2 (optional) is an immediate or relocation. + assert(numPhysicalOperands >= 1 && numPhysicalOperands <= 2 && + "Unexpected number of operands for MRMnMFrm"); + HANDLE_OPERAND(memory) + HANDLE_OPTIONAL(relocation) + break; + case X86Local::MRMInitReg: + // Ignored. + break; + } + + #undef HANDLE_OPERAND + #undef HANDLE_OPTIONAL +} + +void RecognizableInstr::emitDecodePath(DisassemblerTables &tables) const { + // Special cases where the LLVM tables are not complete + +#define EXACTCASE(class, name, lastbyte) \ + if (Name == name) { \ + tables.setTableFields(class, \ + insnContext(), \ + Opcode, \ + ExactFilter(lastbyte), \ + UID); \ + Spec->modifierBase = Opcode; \ + return; \ + } + + EXACTCASE(TWOBYTE, "MONITOR", 0xc8) + EXACTCASE(TWOBYTE, "MWAIT", 0xc9) + EXACTCASE(TWOBYTE, "SWPGS", 0xf8) + EXACTCASE(TWOBYTE, "INVEPT", 0x80) + EXACTCASE(TWOBYTE, "INVVPID", 0x81) + EXACTCASE(TWOBYTE, "VMCALL", 0xc1) + EXACTCASE(TWOBYTE, "VMLAUNCH", 0xc2) + EXACTCASE(TWOBYTE, "VMRESUME", 0xc3) + EXACTCASE(TWOBYTE, "VMXOFF", 0xc4) + + if (Name == "INVLPG") { + tables.setTableFields(TWOBYTE, + insnContext(), + Opcode, + ExtendedFilter(false, 7), + UID); + Spec->modifierBase = Opcode; + return; + } + + OpcodeType opcodeType = (OpcodeType)-1; + + ModRMFilter* filter = NULL; + uint8_t opcodeToSet = 0; + + switch (Prefix) { + // Extended two-byte opcodes can start with f2 0f, f3 0f, or 0f + case X86Local::XD: + case X86Local::XS: + case X86Local::TB: + opcodeType = TWOBYTE; + + switch (Opcode) { +#define EXTENSION_TABLE(n) case 0x##n: + TWO_BYTE_EXTENSION_TABLES +#undef EXTENSION_TABLE + switch (Form) { + default: + llvm_unreachable("Unhandled two-byte extended opcode"); + case X86Local::MRM0r: + case X86Local::MRM1r: + case X86Local::MRM2r: + case X86Local::MRM3r: + case X86Local::MRM4r: + case X86Local::MRM5r: + case X86Local::MRM6r: + case X86Local::MRM7r: + filter = new ExtendedFilter(true, Form - X86Local::MRM0r); + break; + case X86Local::MRM0m: + case X86Local::MRM1m: + case X86Local::MRM2m: + case X86Local::MRM3m: + case X86Local::MRM4m: + case X86Local::MRM5m: + case X86Local::MRM6m: + case X86Local::MRM7m: + filter = new ExtendedFilter(false, Form - X86Local::MRM0m); + break; + } // switch (Form) + break; + default: + if (needsModRMForDecode(Form)) + filter = new ModFilter(isRegFormat(Form)); + else + filter = new DumbFilter(); + + break; + } // switch (opcode) + opcodeToSet = Opcode; + break; + case X86Local::T8: + opcodeType = THREEBYTE_38; + if (needsModRMForDecode(Form)) + filter = new ModFilter(isRegFormat(Form)); + else + filter = new DumbFilter(); + opcodeToSet = Opcode; + break; + case X86Local::TA: + opcodeType = THREEBYTE_3A; + if (needsModRMForDecode(Form)) + filter = new ModFilter(isRegFormat(Form)); + else + filter = new DumbFilter(); + opcodeToSet = Opcode; + break; + case X86Local::D8: + case X86Local::D9: + case X86Local::DA: + case X86Local::DB: + case X86Local::DC: + case X86Local::DD: + case X86Local::DE: + case X86Local::DF: + assert(Opcode >= 0xc0 && "Unexpected opcode for an escape opcode"); + opcodeType = ONEBYTE; + if (Form == X86Local::AddRegFrm) { + Spec->modifierType = MODIFIER_MODRM; + Spec->modifierBase = Opcode; + filter = new AddRegEscapeFilter(Opcode); + } else { + filter = new EscapeFilter(true, Opcode); + } + opcodeToSet = 0xd8 + (Prefix - X86Local::D8); + break; + default: + opcodeType = ONEBYTE; + switch (Opcode) { +#define EXTENSION_TABLE(n) case 0x##n: + ONE_BYTE_EXTENSION_TABLES +#undef EXTENSION_TABLE + switch (Form) { + default: + llvm_unreachable("Fell through the cracks of a single-byte " + "extended opcode"); + case X86Local::MRM0r: + case X86Local::MRM1r: + case X86Local::MRM2r: + case X86Local::MRM3r: + case X86Local::MRM4r: + case X86Local::MRM5r: + case X86Local::MRM6r: + case X86Local::MRM7r: + filter = new ExtendedFilter(true, Form - X86Local::MRM0r); + break; + case X86Local::MRM0m: + case X86Local::MRM1m: + case X86Local::MRM2m: + case X86Local::MRM3m: + case X86Local::MRM4m: + case X86Local::MRM5m: + case X86Local::MRM6m: + case X86Local::MRM7m: + filter = new ExtendedFilter(false, Form - X86Local::MRM0m); + break; + } // switch (Form) + break; + case 0xd8: + case 0xd9: + case 0xda: + case 0xdb: + case 0xdc: + case 0xdd: + case 0xde: + case 0xdf: + filter = new EscapeFilter(false, Form - X86Local::MRM0m); + break; + default: + if (needsModRMForDecode(Form)) + filter = new ModFilter(isRegFormat(Form)); + else + filter = new DumbFilter(); + break; + } // switch (Opcode) + opcodeToSet = Opcode; + } // switch (Prefix) + + assert(opcodeType != (OpcodeType)-1 && + "Opcode type not set"); + assert(filter && "Filter not set"); + + if (Form == X86Local::AddRegFrm) { + if(Spec->modifierType != MODIFIER_MODRM) { + assert(opcodeToSet < 0xf9 && + "Not enough room for all ADDREG_FRM operands"); + + uint8_t currentOpcode; + + for (currentOpcode = opcodeToSet; + currentOpcode < opcodeToSet + 8; + ++currentOpcode) + tables.setTableFields(opcodeType, + insnContext(), + currentOpcode, + *filter, + UID); + + Spec->modifierType = MODIFIER_OPCODE; + Spec->modifierBase = opcodeToSet; + } else { + // modifierBase was set where MODIFIER_MODRM was set + tables.setTableFields(opcodeType, + insnContext(), + opcodeToSet, + *filter, + UID); + } + } else { + tables.setTableFields(opcodeType, + insnContext(), + opcodeToSet, + *filter, + UID); + + Spec->modifierType = MODIFIER_NONE; + Spec->modifierBase = opcodeToSet; + } + + delete filter; +} + +#define TYPE(str, type) if (s == str) return type; +OperandType RecognizableInstr::typeFromString(const std::string &s, + bool isSSE, + bool hasREX_WPrefix, + bool hasOpSizePrefix) { + if (isSSE) { + // For SSE instructions, we ignore the OpSize prefix and force operand + // sizes. + TYPE("GR16", TYPE_R16) + TYPE("GR32", TYPE_R32) + TYPE("GR64", TYPE_R64) + } + if(hasREX_WPrefix) { + // For instructions with a REX_W prefix, a declared 32-bit register encoding + // is special. + TYPE("GR32", TYPE_R32) + } + if(!hasOpSizePrefix) { + // For instructions without an OpSize prefix, a declared 16-bit register or + // immediate encoding is special. + TYPE("GR16", TYPE_R16) + TYPE("i16imm", TYPE_IMM16) + } + TYPE("i16mem", TYPE_Mv) + TYPE("i16imm", TYPE_IMMv) + TYPE("i16i8imm", TYPE_IMMv) + TYPE("GR16", TYPE_Rv) + TYPE("i32mem", TYPE_Mv) + TYPE("i32imm", TYPE_IMMv) + TYPE("i32i8imm", TYPE_IMM32) + TYPE("GR32", TYPE_Rv) + TYPE("i64mem", TYPE_Mv) + TYPE("i64i32imm", TYPE_IMM64) + TYPE("i64i8imm", TYPE_IMM64) + TYPE("GR64", TYPE_R64) + TYPE("i8mem", TYPE_M8) + TYPE("i8imm", TYPE_IMM8) + TYPE("GR8", TYPE_R8) + TYPE("VR128", TYPE_XMM128) + TYPE("f128mem", TYPE_M128) + TYPE("FR64", TYPE_XMM64) + TYPE("f64mem", TYPE_M64FP) + TYPE("FR32", TYPE_XMM32) + TYPE("f32mem", TYPE_M32FP) + TYPE("RST", TYPE_ST) + TYPE("i128mem", TYPE_M128) + TYPE("i64i32imm_pcrel", TYPE_REL64) + TYPE("i32imm_pcrel", TYPE_REL32) + TYPE("SSECC", TYPE_IMM8) + TYPE("brtarget", TYPE_RELv) + TYPE("brtarget8", TYPE_REL8) + TYPE("f80mem", TYPE_M80FP) + TYPE("lea32mem", TYPE_LEA) + TYPE("lea64_32mem", TYPE_LEA) + TYPE("lea64mem", TYPE_LEA) + TYPE("VR64", TYPE_MM64) + TYPE("i64imm", TYPE_IMMv) + TYPE("opaque32mem", TYPE_M1616) + TYPE("opaque48mem", TYPE_M1632) + TYPE("opaque80mem", TYPE_M1664) + TYPE("opaque512mem", TYPE_M512) + TYPE("SEGMENT_REG", TYPE_SEGMENTREG) + TYPE("DEBUG_REG", TYPE_DEBUGREG) + TYPE("CONTROL_REG_32", TYPE_CR32) + TYPE("CONTROL_REG_64", TYPE_CR64) + TYPE("offset8", TYPE_MOFFS8) + TYPE("offset16", TYPE_MOFFS16) + TYPE("offset32", TYPE_MOFFS32) + TYPE("offset64", TYPE_MOFFS64) + errs() << "Unhandled type string " << s << "\n"; + llvm_unreachable("Unhandled type string"); +} +#undef TYPE + +#define ENCODING(str, encoding) if (s == str) return encoding; +OperandEncoding RecognizableInstr::immediateEncodingFromString + (const std::string &s, + bool hasOpSizePrefix) { + if(!hasOpSizePrefix) { + // For instructions without an OpSize prefix, a declared 16-bit register or + // immediate encoding is special. + ENCODING("i16imm", ENCODING_IW) + } + ENCODING("i32i8imm", ENCODING_IB) + ENCODING("SSECC", ENCODING_IB) + ENCODING("i16imm", ENCODING_Iv) + ENCODING("i16i8imm", ENCODING_IB) + ENCODING("i32imm", ENCODING_Iv) + ENCODING("i64i32imm", ENCODING_ID) + ENCODING("i64i8imm", ENCODING_IB) + ENCODING("i8imm", ENCODING_IB) + errs() << "Unhandled immediate encoding " << s << "\n"; + llvm_unreachable("Unhandled immediate encoding"); +} + +OperandEncoding RecognizableInstr::rmRegisterEncodingFromString + (const std::string &s, + bool hasOpSizePrefix) { + ENCODING("GR16", ENCODING_RM) + ENCODING("GR32", ENCODING_RM) + ENCODING("GR64", ENCODING_RM) + ENCODING("GR8", ENCODING_RM) + ENCODING("VR128", ENCODING_RM) + ENCODING("FR64", ENCODING_RM) + ENCODING("FR32", ENCODING_RM) + ENCODING("VR64", ENCODING_RM) + errs() << "Unhandled R/M register encoding " << s << "\n"; + llvm_unreachable("Unhandled R/M register encoding"); +} + +OperandEncoding RecognizableInstr::roRegisterEncodingFromString + (const std::string &s, + bool hasOpSizePrefix) { + ENCODING("GR16", ENCODING_REG) + ENCODING("GR32", ENCODING_REG) + ENCODING("GR64", ENCODING_REG) + ENCODING("GR8", ENCODING_REG) + ENCODING("VR128", ENCODING_REG) + ENCODING("FR64", ENCODING_REG) + ENCODING("FR32", ENCODING_REG) + ENCODING("VR64", ENCODING_REG) + ENCODING("SEGMENT_REG", ENCODING_REG) + ENCODING("DEBUG_REG", ENCODING_REG) + ENCODING("CONTROL_REG_32", ENCODING_REG) + ENCODING("CONTROL_REG_64", ENCODING_REG) + errs() << "Unhandled reg/opcode register encoding " << s << "\n"; + llvm_unreachable("Unhandled reg/opcode register encoding"); +} + +OperandEncoding RecognizableInstr::memoryEncodingFromString + (const std::string &s, + bool hasOpSizePrefix) { + ENCODING("i16mem", ENCODING_RM) + ENCODING("i32mem", ENCODING_RM) + ENCODING("i64mem", ENCODING_RM) + ENCODING("i8mem", ENCODING_RM) + ENCODING("f128mem", ENCODING_RM) + ENCODING("f64mem", ENCODING_RM) + ENCODING("f32mem", ENCODING_RM) + ENCODING("i128mem", ENCODING_RM) + ENCODING("f80mem", ENCODING_RM) + ENCODING("lea32mem", ENCODING_RM) + ENCODING("lea64_32mem", ENCODING_RM) + ENCODING("lea64mem", ENCODING_RM) + ENCODING("opaque32mem", ENCODING_RM) + ENCODING("opaque48mem", ENCODING_RM) + ENCODING("opaque80mem", ENCODING_RM) + ENCODING("opaque512mem", ENCODING_RM) + errs() << "Unhandled memory encoding " << s << "\n"; + llvm_unreachable("Unhandled memory encoding"); +} + +OperandEncoding RecognizableInstr::relocationEncodingFromString + (const std::string &s, + bool hasOpSizePrefix) { + if(!hasOpSizePrefix) { + // For instructions without an OpSize prefix, a declared 16-bit register or + // immediate encoding is special. + ENCODING("i16imm", ENCODING_IW) + } + ENCODING("i16imm", ENCODING_Iv) + ENCODING("i16i8imm", ENCODING_IB) + ENCODING("i32imm", ENCODING_Iv) + ENCODING("i32i8imm", ENCODING_IB) + ENCODING("i64i32imm", ENCODING_ID) + ENCODING("i64i8imm", ENCODING_IB) + ENCODING("i8imm", ENCODING_IB) + ENCODING("i64i32imm_pcrel", ENCODING_ID) + ENCODING("i32imm_pcrel", ENCODING_ID) + ENCODING("brtarget", ENCODING_Iv) + ENCODING("brtarget8", ENCODING_IB) + ENCODING("i64imm", ENCODING_IO) + ENCODING("offset8", ENCODING_Ia) + ENCODING("offset16", ENCODING_Ia) + ENCODING("offset32", ENCODING_Ia) + ENCODING("offset64", ENCODING_Ia) + errs() << "Unhandled relocation encoding " << s << "\n"; + llvm_unreachable("Unhandled relocation encoding"); +} + +OperandEncoding RecognizableInstr::opcodeModifierEncodingFromString + (const std::string &s, + bool hasOpSizePrefix) { + ENCODING("RST", ENCODING_I) + ENCODING("GR32", ENCODING_Rv) + ENCODING("GR64", ENCODING_RO) + ENCODING("GR16", ENCODING_Rv) + ENCODING("GR8", ENCODING_RB) + errs() << "Unhandled opcode modifier encoding " << s << "\n"; + llvm_unreachable("Unhandled opcode modifier encoding"); +} +#undef ENCODING diff --git a/utils/TableGen/X86RecognizableInstr.h b/utils/TableGen/X86RecognizableInstr.h new file mode 100644 index 0000000..84374b0 --- /dev/null +++ b/utils/TableGen/X86RecognizableInstr.h @@ -0,0 +1,237 @@ +//===- X86RecognizableInstr.h - Disassembler instruction spec ----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is part of the X86 Disassembler Emitter. +// It contains the interface of a single recognizable instruction. +// Documentation for the disassembler emitter in general can be found in +// X86DisasemblerEmitter.h. +// +//===----------------------------------------------------------------------===// + +#ifndef X86RECOGNIZABLEINSTR_H +#define X86RECOGNIZABLEINSTR_H + +#include "X86DisassemblerTables.h" + +#include "CodeGenTarget.h" +#include "Record.h" + +#include "llvm/System/DataTypes.h" +#include "llvm/ADT/SmallVector.h" + +namespace llvm { + +namespace X86Disassembler { + +/// RecognizableInstr - Encapsulates all information required to decode a single +/// instruction, as extracted from the LLVM instruction tables. Has methods +/// to interpret the information available in the LLVM tables, and to emit the +/// instruction into DisassemblerTables. +class RecognizableInstr { +private: + /// The opcode of the instruction, as used in an MCInst + InstrUID UID; + /// The record from the .td files corresponding to this instruction + const Record* Rec; + /// The prefix field from the record + uint8_t Prefix; + /// The opcode field from the record; this is the opcode used in the Intel + /// encoding and therefore distinct from the UID + uint8_t Opcode; + /// The form field from the record + uint8_t Form; + /// The segment override field from the record + uint8_t SegOvr; + /// The hasOpSizePrefix field from the record + bool HasOpSizePrefix; + /// The hasREX_WPrefix field from the record + bool HasREX_WPrefix; + /// The hasLockPrefix field from the record + bool HasLockPrefix; + /// The isCodeGenOnly filed from the record + bool IsCodeGenOnly; + + /// The instruction name as listed in the tables + std::string Name; + /// The AT&T AsmString for the instruction + std::string AsmString; + + /// Indicates whether the instruction is SSE + bool IsSSE; + /// Indicates whether the instruction has FR operands - MOVs with FR operands + /// are typically ignored + bool HasFROperands; + /// Indicates whether the instruction should be emitted into the decode + /// tables; regardless, it will be emitted into the instruction info table + bool ShouldBeEmitted; + + /// The operands of the instruction, as listed in the CodeGenInstruction. + /// They are not one-to-one with operands listed in the MCInst; for example, + /// memory operands expand to 5 operands in the MCInst + const std::vector<CodeGenInstruction::OperandInfo>* Operands; + /// The description of the instruction that is emitted into the instruction + /// info table + InstructionSpecifier* Spec; + + /// insnContext - Returns the primary context in which the instruction is + /// valid. + /// + /// @return - The context in which the instruction is valid. + InstructionContext insnContext() const; + + enum filter_ret { + FILTER_STRONG, // instruction has no place in the instruction tables + FILTER_WEAK, // instruction may conflict, and should be eliminated if + // it does + FILTER_NORMAL // instruction should have high priority and generate an + // error if it conflcits with any other FILTER_NORMAL + // instruction + }; + + /// filter - Determines whether the instruction should be decodable. Some + /// instructions are pure intrinsics and use unencodable operands; many + /// synthetic instructions are duplicates of other instructions; other + /// instructions only differ in the logical way in which they are used, and + /// have the same decoding. Because these would cause decode conflicts, + /// they must be filtered out. + /// + /// @return - The degree of filtering to be applied (see filter_ret). + filter_ret filter() const; + + /// typeFromString - Translates an operand type from the string provided in + /// the LLVM tables to an OperandType for use in the operand specifier. + /// + /// @param s - The string, as extracted by calling Rec->getName() + /// on a CodeGenInstruction::OperandInfo. + /// @param isSSE - Indicates whether the instruction is an SSE + /// instruction. For SSE instructions, immediates are + /// fixed-size rather than being affected by the + /// mandatory OpSize prefix. + /// @param hasREX_WPrefix - Indicates whether the instruction has a REX.W + /// prefix. If it does, 32-bit register operands stay + /// 32-bit regardless of the operand size. + /// @param hasOpSizePrefix- Indicates whether the instruction has an OpSize + /// prefix. If it does not, then 16-bit register + /// operands stay 16-bit. + /// @return - The operand's type. + static OperandType typeFromString(const std::string& s, + bool isSSE, + bool hasREX_WPrefix, + bool hasOpSizePrefix); + + /// immediateEncodingFromString - Translates an immediate encoding from the + /// string provided in the LLVM tables to an OperandEncoding for use in + /// the operand specifier. + /// + /// @param s - See typeFromString(). + /// @param hasOpSizePrefix - Indicates whether the instruction has an OpSize + /// prefix. If it does not, then 16-bit immediate + /// operands stay 16-bit. + /// @return - The operand's encoding. + static OperandEncoding immediateEncodingFromString(const std::string &s, + bool hasOpSizePrefix); + + /// rmRegisterEncodingFromString - Like immediateEncodingFromString, but + /// handles operands that are in the REG field of the ModR/M byte. + static OperandEncoding rmRegisterEncodingFromString(const std::string &s, + bool hasOpSizePrefix); + + /// rmRegisterEncodingFromString - Like immediateEncodingFromString, but + /// handles operands that are in the REG field of the ModR/M byte. + static OperandEncoding roRegisterEncodingFromString(const std::string &s, + bool hasOpSizePrefix); + static OperandEncoding memoryEncodingFromString(const std::string &s, + bool hasOpSizePrefix); + static OperandEncoding relocationEncodingFromString(const std::string &s, + bool hasOpSizePrefix); + static OperandEncoding opcodeModifierEncodingFromString(const std::string &s, + bool hasOpSizePrefix); + + /// handleOperand - Converts a single operand from the LLVM table format to + /// the emitted table format, handling any duplicate operands it encounters + /// and then one non-duplicate. + /// + /// @param optional - Determines whether to assert that the + /// operand exists. + /// @param operandIndex - The index into the generated operand table. + /// Incremented by this function one or more + /// times to reflect possible duplicate + /// operands). + /// @param physicalOperandIndex - The index of the current operand into the + /// set of non-duplicate ('physical') operands. + /// Incremented by this function once. + /// @param numPhysicalOperands - The number of non-duplicate operands in the + /// instructions. + /// @param operandMapping - The operand mapping, which has an entry for + /// each operand that indicates whether it is a + /// duplicate, and of what. + void handleOperand(bool optional, + unsigned &operandIndex, + unsigned &physicalOperandIndex, + unsigned &numPhysicalOperands, + unsigned *operandMapping, + OperandEncoding (*encodingFromString) + (const std::string&, + bool hasOpSizePrefix)); + + /// shouldBeEmitted - Returns the shouldBeEmitted field. Although filter() + /// filters out many instructions, at various points in decoding we + /// determine that the instruction should not actually be decodable. In + /// particular, MMX MOV instructions aren't emitted, but they're only + /// identified during operand parsing. + /// + /// @return - true if at this point we believe the instruction should be + /// emitted; false if not. This will return false if filter() returns false + /// once emitInstructionSpecifier() has been called. + bool shouldBeEmitted() const { + return ShouldBeEmitted; + } + + /// emitInstructionSpecifier - Loads the instruction specifier for the current + /// instruction into a DisassemblerTables. + /// + /// @arg tables - The DisassemblerTables to populate with the specifier for + /// the current instruction. + void emitInstructionSpecifier(DisassemblerTables &tables); + + /// emitDecodePath - Populates the proper fields in the decode tables + /// corresponding to the decode paths for this instruction. + /// + /// @arg tables - The DisassemblerTables to populate with the decode + /// decode information for the current instruction. + void emitDecodePath(DisassemblerTables &tables) const; + + /// Constructor - Initializes a RecognizableInstr with the appropriate fields + /// from a CodeGenInstruction. + /// + /// @arg tables - The DisassemblerTables that the specifier will be added to. + /// @arg insn - The CodeGenInstruction to extract information from. + /// @arg uid - The unique ID of the current instruction. + RecognizableInstr(DisassemblerTables &tables, + const CodeGenInstruction &insn, + InstrUID uid); +public: + /// processInstr - Accepts a CodeGenInstruction and loads decode information + /// for it into a DisassemblerTables if appropriate. + /// + /// @arg tables - The DiassemblerTables to be populated with decode + /// information. + /// @arg insn - The CodeGenInstruction to be used as a source for this + /// information. + /// @uid - The unique ID of the instruction. + static void processInstr(DisassemblerTables &tables, + const CodeGenInstruction &insn, + InstrUID uid); +}; + +} // namespace X86Disassembler + +} // namespace llvm + +#endif diff --git a/utils/buildit/build_llvm b/utils/buildit/build_llvm index 4392b27..25f6554 100755 --- a/utils/buildit/build_llvm +++ b/utils/buildit/build_llvm @@ -243,7 +243,11 @@ if ! test $? == 0 ; then fi # Install Version.h -RC_ProjectSourceSubversion=`printf "%d" $LLVM_SUBMIT_SUBVERSION` +LLVM_MINOR_VERSION=`echo $LLVM_SUBMIT_SUBVERSION | sed -e 's,0*\([1-9][0-9]*\),\1,'` +if [ "x$LLVM_MINOR_VERSION" = "x" ]; then + LLVM_MINOR_VERSION=0 +fi +RC_ProjectSourceSubversion=`printf "%d" $LLVM_MINOR_VERSION` echo "#define LLVM_VERSION ${RC_ProjectSourceVersion}" > $DEST_DIR$DEST_ROOT/include/llvm/Version.h echo "#define LLVM_MINOR_VERSION ${RC_ProjectSourceSubversion}" >> $DEST_DIR$DEST_ROOT/include/llvm/Version.h diff --git a/utils/emacs/llvm-mode.el b/utils/emacs/llvm-mode.el index 55c56da..b1af853 100644 --- a/utils/emacs/llvm-mode.el +++ b/utils/emacs/llvm-mode.el @@ -122,7 +122,7 @@ ;; Associate .ll files with llvm-mode (setq auto-mode-alist - (append '(("\\.ll$" . llvm-mode) ("\\.llx$" . llvm-mode)) auto-mode-alist)) + (append '(("\\.ll$" . llvm-mode)) auto-mode-alist)) (provide 'llvm-mode) ;; end of llvm-mode.el diff --git a/utils/lit/TestFormats.py b/utils/lit/TestFormats.py index 7305c79..5dfd54a 100644 --- a/utils/lit/TestFormats.py +++ b/utils/lit/TestFormats.py @@ -9,12 +9,17 @@ class GoogleTest(object): self.test_sub_dir = str(test_sub_dir) self.test_suffix = str(test_suffix) - def getGTestTests(self, path): + def getGTestTests(self, path, litConfig): """getGTestTests(path) - [name] Return the tests available in gtest executable.""" - lines = Util.capture([path, '--gtest_list_tests']).split('\n') + try: + lines = Util.capture([path, '--gtest_list_tests']).split('\n') + except: + litConfig.error("unable to discover google-tests in %r" % path) + raise StopIteration + nested_tests = [] for ln in lines: if not ln.strip(): @@ -47,7 +52,7 @@ class GoogleTest(object): execpath = os.path.join(filepath, subfilename) # Discover the tests in this executable. - for name in self.getGTestTests(execpath): + for name in self.getGTestTests(execpath, litConfig): testPath = path_in_suite + (filename, subfilename, name) yield Test.Test(testSuite, testPath, localConfig) diff --git a/utils/lit/lit.py b/utils/lit/lit.py index 293976f..851063b 100755 --- a/utils/lit/lit.py +++ b/utils/lit/lit.py @@ -1,576 +1,5 @@ #!/usr/bin/env python -""" -lit - LLVM Integrated Tester. - -See lit.pod for more information. -""" - -import math, os, platform, random, re, sys, time, threading, traceback - -import ProgressBar -import TestRunner -import Util - -from TestingConfig import TestingConfig -import LitConfig -import Test - -# Configuration files to look for when discovering test suites. These can be -# overridden with --config-prefix. -# -# FIXME: Rename to 'config.lit', 'site.lit', and 'local.lit' ? -gConfigName = 'lit.cfg' -gSiteConfigName = 'lit.site.cfg' - -kLocalConfigName = 'lit.local.cfg' - -class TestingProgressDisplay: - def __init__(self, opts, numTests, progressBar=None): - self.opts = opts - self.numTests = numTests - self.current = None - self.lock = threading.Lock() - self.progressBar = progressBar - self.completed = 0 - - def update(self, test): - # Avoid locking overhead in quiet mode - if self.opts.quiet and not test.result.isFailure: - self.completed += 1 - return - - # Output lock. - self.lock.acquire() - try: - self.handleUpdate(test) - finally: - self.lock.release() - - def finish(self): - if self.progressBar: - self.progressBar.clear() - elif self.opts.quiet: - pass - elif self.opts.succinct: - sys.stdout.write('\n') - - def handleUpdate(self, test): - self.completed += 1 - if self.progressBar: - self.progressBar.update(float(self.completed)/self.numTests, - test.getFullName()) - - if self.opts.succinct and not test.result.isFailure: - return - - if self.progressBar: - self.progressBar.clear() - - print '%s: %s (%d of %d)' % (test.result.name, test.getFullName(), - self.completed, self.numTests) - - if test.result.isFailure and self.opts.showOutput: - print "%s TEST '%s' FAILED %s" % ('*'*20, test.getFullName(), - '*'*20) - print test.output - print "*" * 20 - - sys.stdout.flush() - -class TestProvider: - def __init__(self, tests, maxTime): - self.maxTime = maxTime - self.iter = iter(tests) - self.lock = threading.Lock() - self.startTime = time.time() - - def get(self): - # Check if we have run out of time. - if self.maxTime is not None: - if time.time() - self.startTime > self.maxTime: - return None - - # Otherwise take the next test. - self.lock.acquire() - try: - item = self.iter.next() - except StopIteration: - item = None - self.lock.release() - return item - -class Tester(threading.Thread): - def __init__(self, litConfig, provider, display): - threading.Thread.__init__(self) - self.litConfig = litConfig - self.provider = provider - self.display = display - - def run(self): - while 1: - item = self.provider.get() - if item is None: - break - self.runTest(item) - - def runTest(self, test): - result = None - startTime = time.time() - try: - result, output = test.config.test_format.execute(test, - self.litConfig) - except KeyboardInterrupt: - # This is a sad hack. Unfortunately subprocess goes - # bonkers with ctrl-c and we start forking merrily. - print '\nCtrl-C detected, goodbye.' - os.kill(0,9) - except: - if self.litConfig.debug: - raise - result = Test.UNRESOLVED - output = 'Exception during script execution:\n' - output += traceback.format_exc() - output += '\n' - elapsed = time.time() - startTime - - test.setResult(result, output, elapsed) - self.display.update(test) - -def dirContainsTestSuite(path): - cfgpath = os.path.join(path, gSiteConfigName) - if os.path.exists(cfgpath): - return cfgpath - cfgpath = os.path.join(path, gConfigName) - if os.path.exists(cfgpath): - return cfgpath - -def getTestSuite(item, litConfig, cache): - """getTestSuite(item, litConfig, cache) -> (suite, relative_path) - - Find the test suite containing @arg item. - - @retval (None, ...) - Indicates no test suite contains @arg item. - @retval (suite, relative_path) - The suite that @arg item is in, and its - relative path inside that suite. - """ - def search1(path): - # Check for a site config or a lit config. - cfgpath = dirContainsTestSuite(path) - - # If we didn't find a config file, keep looking. - if not cfgpath: - parent,base = os.path.split(path) - if parent == path: - return (None, ()) - - ts, relative = search(parent) - return (ts, relative + (base,)) - - # We found a config file, load it. - if litConfig.debug: - litConfig.note('loading suite config %r' % cfgpath) - - cfg = TestingConfig.frompath(cfgpath, None, litConfig, mustExist = True) - source_root = os.path.realpath(cfg.test_source_root or path) - exec_root = os.path.realpath(cfg.test_exec_root or path) - return Test.TestSuite(cfg.name, source_root, exec_root, cfg), () - - def search(path): - # Check for an already instantiated test suite. - res = cache.get(path) - if res is None: - cache[path] = res = search1(path) - return res - - # Canonicalize the path. - item = os.path.realpath(item) - - # Skip files and virtual components. - components = [] - while not os.path.isdir(item): - parent,base = os.path.split(item) - if parent == item: - return (None, ()) - components.append(base) - item = parent - components.reverse() - - ts, relative = search(item) - return ts, tuple(relative + tuple(components)) - -def getLocalConfig(ts, path_in_suite, litConfig, cache): - def search1(path_in_suite): - # Get the parent config. - if not path_in_suite: - parent = ts.config - else: - parent = search(path_in_suite[:-1]) - - # Load the local configuration. - source_path = ts.getSourcePath(path_in_suite) - cfgpath = os.path.join(source_path, kLocalConfigName) - if litConfig.debug: - litConfig.note('loading local config %r' % cfgpath) - return TestingConfig.frompath(cfgpath, parent, litConfig, - mustExist = False, - config = parent.clone(cfgpath)) - - def search(path_in_suite): - key = (ts, path_in_suite) - res = cache.get(key) - if res is None: - cache[key] = res = search1(path_in_suite) - return res - - return search(path_in_suite) - -def getTests(path, litConfig, testSuiteCache, localConfigCache): - # Find the test suite for this input and its relative path. - ts,path_in_suite = getTestSuite(path, litConfig, testSuiteCache) - if ts is None: - litConfig.warning('unable to find test suite for %r' % path) - return (),() - - if litConfig.debug: - litConfig.note('resolved input %r to %r::%r' % (path, ts.name, - path_in_suite)) - - return ts, getTestsInSuite(ts, path_in_suite, litConfig, - testSuiteCache, localConfigCache) - -def getTestsInSuite(ts, path_in_suite, litConfig, - testSuiteCache, localConfigCache): - # Check that the source path exists (errors here are reported by the - # caller). - source_path = ts.getSourcePath(path_in_suite) - if not os.path.exists(source_path): - return - - # Check if the user named a test directly. - if not os.path.isdir(source_path): - lc = getLocalConfig(ts, path_in_suite[:-1], litConfig, localConfigCache) - yield Test.Test(ts, path_in_suite, lc) - return - - # Otherwise we have a directory to search for tests, start by getting the - # local configuration. - lc = getLocalConfig(ts, path_in_suite, litConfig, localConfigCache) - - # Search for tests. - for res in lc.test_format.getTestsInDirectory(ts, path_in_suite, - litConfig, lc): - yield res - - # Search subdirectories. - for filename in os.listdir(source_path): - # FIXME: This doesn't belong here? - if filename in ('Output', '.svn') or filename in lc.excludes: - continue - - # Ignore non-directories. - file_sourcepath = os.path.join(source_path, filename) - if not os.path.isdir(file_sourcepath): - continue - - # Check for nested test suites, first in the execpath in case there is a - # site configuration and then in the source path. - file_execpath = ts.getExecPath(path_in_suite + (filename,)) - if dirContainsTestSuite(file_execpath): - sub_ts, subiter = getTests(file_execpath, litConfig, - testSuiteCache, localConfigCache) - elif dirContainsTestSuite(file_sourcepath): - sub_ts, subiter = getTests(file_sourcepath, litConfig, - testSuiteCache, localConfigCache) - else: - # Otherwise, continue loading from inside this test suite. - subiter = getTestsInSuite(ts, path_in_suite + (filename,), - litConfig, testSuiteCache, - localConfigCache) - sub_ts = None - - N = 0 - for res in subiter: - N += 1 - yield res - if sub_ts and not N: - litConfig.warning('test suite %r contained no tests' % sub_ts.name) - -def runTests(numThreads, litConfig, provider, display): - # If only using one testing thread, don't use threads at all; this lets us - # profile, among other things. - if numThreads == 1: - t = Tester(litConfig, provider, display) - t.run() - return - - # Otherwise spin up the testing threads and wait for them to finish. - testers = [Tester(litConfig, provider, display) - for i in range(numThreads)] - for t in testers: - t.start() - try: - for t in testers: - t.join() - except KeyboardInterrupt: - sys.exit(2) - -def main(): - global options - from optparse import OptionParser, OptionGroup - parser = OptionParser("usage: %prog [options] {file-or-path}") - - parser.add_option("-j", "--threads", dest="numThreads", metavar="N", - help="Number of testing threads", - type=int, action="store", default=None) - parser.add_option("", "--config-prefix", dest="configPrefix", - metavar="NAME", help="Prefix for 'lit' config files", - action="store", default=None) - parser.add_option("", "--param", dest="userParameters", - metavar="NAME=VAL", - help="Add 'NAME' = 'VAL' to the user defined parameters", - type=str, action="append", default=[]) - - group = OptionGroup(parser, "Output Format") - # FIXME: I find these names very confusing, although I like the - # functionality. - group.add_option("-q", "--quiet", dest="quiet", - help="Suppress no error output", - action="store_true", default=False) - group.add_option("-s", "--succinct", dest="succinct", - help="Reduce amount of output", - action="store_true", default=False) - group.add_option("-v", "--verbose", dest="showOutput", - help="Show all test output", - action="store_true", default=False) - group.add_option("", "--no-progress-bar", dest="useProgressBar", - help="Do not use curses based progress bar", - action="store_false", default=True) - parser.add_option_group(group) - - group = OptionGroup(parser, "Test Execution") - group.add_option("", "--path", dest="path", - help="Additional paths to add to testing environment", - action="append", type=str, default=[]) - group.add_option("", "--vg", dest="useValgrind", - help="Run tests under valgrind", - action="store_true", default=False) - group.add_option("", "--vg-arg", dest="valgrindArgs", metavar="ARG", - help="Specify an extra argument for valgrind", - type=str, action="append", default=[]) - group.add_option("", "--time-tests", dest="timeTests", - help="Track elapsed wall time for each test", - action="store_true", default=False) - group.add_option("", "--no-execute", dest="noExecute", - help="Don't execute any tests (assume PASS)", - action="store_true", default=False) - parser.add_option_group(group) - - group = OptionGroup(parser, "Test Selection") - group.add_option("", "--max-tests", dest="maxTests", metavar="N", - help="Maximum number of tests to run", - action="store", type=int, default=None) - group.add_option("", "--max-time", dest="maxTime", metavar="N", - help="Maximum time to spend testing (in seconds)", - action="store", type=float, default=None) - group.add_option("", "--shuffle", dest="shuffle", - help="Run tests in random order", - action="store_true", default=False) - parser.add_option_group(group) - - group = OptionGroup(parser, "Debug and Experimental Options") - group.add_option("", "--debug", dest="debug", - help="Enable debugging (for 'lit' development)", - action="store_true", default=False) - group.add_option("", "--show-suites", dest="showSuites", - help="Show discovered test suites", - action="store_true", default=False) - group.add_option("", "--no-tcl-as-sh", dest="useTclAsSh", - help="Don't run Tcl scripts using 'sh'", - action="store_false", default=True) - group.add_option("", "--repeat", dest="repeatTests", metavar="N", - help="Repeat tests N times (for timing)", - action="store", default=None, type=int) - parser.add_option_group(group) - - (opts, args) = parser.parse_args() - - if not args: - parser.error('No inputs specified') - - if opts.configPrefix is not None: - global gConfigName, gSiteConfigName - gConfigName = '%s.cfg' % opts.configPrefix - gSiteConfigName = '%s.site.cfg' % opts.configPrefix - - if opts.numThreads is None: - opts.numThreads = Util.detectCPUs() - - inputs = args - - # Create the user defined parameters. - userParams = {} - for entry in opts.userParameters: - if '=' not in entry: - name,val = entry,'' - else: - name,val = entry.split('=', 1) - userParams[name] = val - - # Create the global config object. - litConfig = LitConfig.LitConfig(progname = os.path.basename(sys.argv[0]), - path = opts.path, - quiet = opts.quiet, - useValgrind = opts.useValgrind, - valgrindArgs = opts.valgrindArgs, - useTclAsSh = opts.useTclAsSh, - noExecute = opts.noExecute, - debug = opts.debug, - isWindows = (platform.system()=='Windows'), - params = userParams) - - # Load the tests from the inputs. - tests = [] - testSuiteCache = {} - localConfigCache = {} - for input in inputs: - prev = len(tests) - tests.extend(getTests(input, litConfig, - testSuiteCache, localConfigCache)[1]) - if prev == len(tests): - litConfig.warning('input %r contained no tests' % input) - - # If there were any errors during test discovery, exit now. - if litConfig.numErrors: - print >>sys.stderr, '%d errors, exiting.' % litConfig.numErrors - sys.exit(2) - - if opts.showSuites: - suitesAndTests = dict([(ts,[]) - for ts,_ in testSuiteCache.values() - if ts]) - for t in tests: - suitesAndTests[t.suite].append(t) - - print '-- Test Suites --' - suitesAndTests = suitesAndTests.items() - suitesAndTests.sort(key = lambda (ts,_): ts.name) - for ts,ts_tests in suitesAndTests: - print ' %s - %d tests' %(ts.name, len(ts_tests)) - print ' Source Root: %s' % ts.source_root - print ' Exec Root : %s' % ts.exec_root - - # Select and order the tests. - numTotalTests = len(tests) - if opts.shuffle: - random.shuffle(tests) - else: - tests.sort(key = lambda t: t.getFullName()) - if opts.maxTests is not None: - tests = tests[:opts.maxTests] - - extra = '' - if len(tests) != numTotalTests: - extra = ' of %d' % numTotalTests - header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra, - opts.numThreads) - - if opts.repeatTests: - tests = [t.copyWithIndex(i) - for t in tests - for i in range(opts.repeatTests)] - - progressBar = None - if not opts.quiet: - if opts.succinct and opts.useProgressBar: - try: - tc = ProgressBar.TerminalController() - progressBar = ProgressBar.ProgressBar(tc, header) - except ValueError: - print header - progressBar = ProgressBar.SimpleProgressBar('Testing: ') - else: - print header - - # Don't create more threads than tests. - opts.numThreads = min(len(tests), opts.numThreads) - - startTime = time.time() - display = TestingProgressDisplay(opts, len(tests), progressBar) - provider = TestProvider(tests, opts.maxTime) - runTests(opts.numThreads, litConfig, provider, display) - display.finish() - - if not opts.quiet: - print 'Testing Time: %.2fs'%(time.time() - startTime) - - # Update results for any tests which weren't run. - for t in tests: - if t.result is None: - t.setResult(Test.UNRESOLVED, '', 0.0) - - # List test results organized by kind. - hasFailures = False - byCode = {} - for t in tests: - if t.result not in byCode: - byCode[t.result] = [] - byCode[t.result].append(t) - if t.result.isFailure: - hasFailures = True - - # FIXME: Show unresolved and (optionally) unsupported tests. - for title,code in (('Unexpected Passing Tests', Test.XPASS), - ('Failing Tests', Test.FAIL)): - elts = byCode.get(code) - if not elts: - continue - print '*'*20 - print '%s (%d):' % (title, len(elts)) - for t in elts: - print ' %s' % t.getFullName() - print - - if opts.timeTests: - # Collate, in case we repeated tests. - times = {} - for t in tests: - key = t.getFullName() - times[key] = times.get(key, 0.) + t.elapsed - - byTime = list(times.items()) - byTime.sort(key = lambda (name,elapsed): elapsed) - if byTime: - Util.printHistogram(byTime, title='Tests') - - for name,code in (('Expected Passes ', Test.PASS), - ('Expected Failures ', Test.XFAIL), - ('Unsupported Tests ', Test.UNSUPPORTED), - ('Unresolved Tests ', Test.UNRESOLVED), - ('Unexpected Passes ', Test.XPASS), - ('Unexpected Failures', Test.FAIL),): - if opts.quiet and not code.isFailure: - continue - N = len(byCode.get(code,[])) - if N: - print ' %s: %d' % (name,N) - - # If we encountered any additional errors, exit abnormally. - if litConfig.numErrors: - print >>sys.stderr, '\n%d error(s), exiting.' % litConfig.numErrors - sys.exit(2) - - # Warn about warnings. - if litConfig.numWarnings: - print >>sys.stderr, '\n%d warning(s) in tests.' % litConfig.numWarnings - - if hasFailures: - sys.exit(1) - sys.exit(0) - if __name__=='__main__': - # Bump the GIL check interval, its more important to get any one thread to a - # blocking operation (hopefully exec) than to try and unblock other threads. - import sys - sys.setcheckinterval(1000) - main() + import lit + lit.main() diff --git a/utils/lit/lit/ExampleTests.ObjDir/lit.site.cfg b/utils/lit/lit/ExampleTests.ObjDir/lit.site.cfg new file mode 100644 index 0000000..14b6e01 --- /dev/null +++ b/utils/lit/lit/ExampleTests.ObjDir/lit.site.cfg @@ -0,0 +1,15 @@ +# -*- Python -*- + +# Site specific configuration file. +# +# Typically this will be generated by the build system to automatically set +# certain configuration variables which cannot be autodetected, so that 'lit' +# can easily be used on the command line. + +import os + +# Preserve the obj_root, for use by the main lit.cfg. +config.example_obj_root = os.path.dirname(__file__) + +lit.load_config(config, os.path.join(config.test_source_root, + 'lit.cfg')) diff --git a/utils/lit/lit/ExampleTests/Clang/fsyntax-only.c b/utils/lit/lit/ExampleTests/Clang/fsyntax-only.c new file mode 100644 index 0000000..a4a064b --- /dev/null +++ b/utils/lit/lit/ExampleTests/Clang/fsyntax-only.c @@ -0,0 +1,4 @@ +// RUN: clang -fsyntax-only -Xclang -verify %s + +int f0(void) {} // expected-warning {{control reaches end of non-void function}} + diff --git a/utils/lit/lit/ExampleTests/Clang/lit.cfg b/utils/lit/lit/ExampleTests/Clang/lit.cfg new file mode 100644 index 0000000..114ac60 --- /dev/null +++ b/utils/lit/lit/ExampleTests/Clang/lit.cfg @@ -0,0 +1,80 @@ +# -*- Python -*- + +# Configuration file for the 'lit' test runner. + +# name: The name of this test suite. +config.name = 'Clang' + +# testFormat: The test format to use to interpret tests. +# +# For now we require '&&' between commands, until they get globally killed and +# the test runner updated. +config.test_format = lit.formats.ShTest(execute_external = True) + +# suffixes: A list of file extensions to treat as test files. +config.suffixes = ['.c', '.cpp', '.m', '.mm'] + +# target_triple: Used by ShTest and TclTest formats for XFAIL checks. +config.target_triple = 'foo' + +### + +# Discover the 'clang' and 'clangcc' to use. + +import os + +def inferClang(PATH): + # Determine which clang to use. + clang = os.getenv('CLANG') + + # If the user set clang in the environment, definitely use that and don't + # try to validate. + if clang: + return clang + + # Otherwise look in the path. + clang = lit.util.which('clang', PATH) + + if not clang: + lit.fatal("couldn't find 'clang' program, try setting " + "CLANG in your environment") + + return clang + +def inferClangCC(clang, PATH): + clangcc = os.getenv('CLANGCC') + + # If the user set clang in the environment, definitely use that and don't + # try to validate. + if clangcc: + return clangcc + + # Otherwise try adding -cc since we expect to be looking in a build + # directory. + if clang.endswith('.exe'): + clangccName = clang[:-4] + '-cc.exe' + else: + clangccName = clang + '-cc' + clangcc = lit.util.which(clangccName, PATH) + if not clangcc: + # Otherwise ask clang. + res = lit.util.capture([clang, '-print-prog-name=clang-cc']) + res = res.strip() + if res and os.path.exists(res): + clangcc = res + + if not clangcc: + lit.fatal("couldn't find 'clang-cc' program, try setting " + "CLANGCC in your environment") + + return clangcc + +clang = inferClang(config.environment['PATH']) +if not lit.quiet: + lit.note('using clang: %r' % clang) +config.substitutions.append( (' clang ', ' ' + clang + ' ') ) + +clang_cc = inferClangCC(clang, config.environment['PATH']) +if not lit.quiet: + lit.note('using clang-cc: %r' % clang_cc) +config.substitutions.append( (' clang-cc ', ' ' + clang_cc + ' ') ) diff --git a/utils/lit/lit/ExampleTests/LLVM.InTree/test/Bar/bar-test.ll b/utils/lit/lit/ExampleTests/LLVM.InTree/test/Bar/bar-test.ll new file mode 100644 index 0000000..3017b13 --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.InTree/test/Bar/bar-test.ll @@ -0,0 +1,3 @@ +; RUN: true +; XFAIL: * +; XTARGET: darwin diff --git a/utils/lit/lit/ExampleTests/LLVM.InTree/test/Bar/dg.exp b/utils/lit/lit/ExampleTests/LLVM.InTree/test/Bar/dg.exp new file mode 100644 index 0000000..2bda07a --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.InTree/test/Bar/dg.exp @@ -0,0 +1,6 @@ +load_lib llvm.exp + +if { [llvm_supports_target X86] } { + RunLLVMTests [lsort [glob -nocomplain $srcdir/$subdir/*.{ll}]] +} + diff --git a/utils/lit/lit/ExampleTests/LLVM.InTree/test/lit.cfg b/utils/lit/lit/ExampleTests/LLVM.InTree/test/lit.cfg new file mode 100644 index 0000000..e7ef037 --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.InTree/test/lit.cfg @@ -0,0 +1,151 @@ +# -*- Python -*- + +# Configuration file for the 'lit' test runner. + +import os + +# name: The name of this test suite. +config.name = 'LLVM' + +# testFormat: The test format to use to interpret tests. +config.test_format = lit.formats.TclTest() + +# suffixes: A list of file extensions to treat as test files, this is actually +# set by on_clone(). +config.suffixes = [] + +# test_source_root: The root path where tests are located. +config.test_source_root = os.path.dirname(__file__) + +# test_exec_root: The root path where tests should be run. +llvm_obj_root = getattr(config, 'llvm_obj_root', None) +if llvm_obj_root is not None: + config.test_exec_root = os.path.join(llvm_obj_root, 'test') + +### + +import os + +# Check that the object root is known. +if config.test_exec_root is None: + # Otherwise, we haven't loaded the site specific configuration (the user is + # probably trying to run on a test file directly, and either the site + # configuration hasn't been created by the build system, or we are in an + # out-of-tree build situation). + + # Try to detect the situation where we are using an out-of-tree build by + # looking for 'llvm-config'. + # + # FIXME: I debated (i.e., wrote and threw away) adding logic to + # automagically generate the lit.site.cfg if we are in some kind of fresh + # build situation. This means knowing how to invoke the build system + # though, and I decided it was too much magic. + + llvm_config = lit.util.which('llvm-config', config.environment['PATH']) + if not llvm_config: + lit.fatal('No site specific configuration available!') + + # Get the source and object roots. + llvm_src_root = lit.util.capture(['llvm-config', '--src-root']).strip() + llvm_obj_root = lit.util.capture(['llvm-config', '--obj-root']).strip() + + # Validate that we got a tree which points to here. + this_src_root = os.path.dirname(config.test_source_root) + if os.path.realpath(llvm_src_root) != os.path.realpath(this_src_root): + lit.fatal('No site specific configuration available!') + + # Check that the site specific configuration exists. + site_cfg = os.path.join(llvm_obj_root, 'test', 'lit.site.cfg') + if not os.path.exists(site_cfg): + lit.fatal('No site specific configuration available!') + + # Okay, that worked. Notify the user of the automagic, and reconfigure. + lit.note('using out-of-tree build at %r' % llvm_obj_root) + lit.load_config(config, site_cfg) + raise SystemExit + +### + +# Load site data from DejaGNU's site.exp. +import re +site_exp = {} +# FIXME: Implement lit.site.cfg. +for line in open(os.path.join(config.llvm_obj_root, 'test', 'site.exp')): + m = re.match('set ([^ ]+) "([^"]*)"', line) + if m: + site_exp[m.group(1)] = m.group(2) + +# Add substitutions. +for sub in ['prcontext', 'llvmgcc', 'llvmgxx', 'compile_cxx', 'compile_c', + 'link', 'shlibext', 'ocamlopt', 'llvmdsymutil', 'llvmlibsdir', + 'bugpoint_topts']: + if sub in ('llvmgcc', 'llvmgxx'): + config.substitutions.append(('%' + sub, + site_exp[sub] + ' -emit-llvm -w')) + else: + config.substitutions.append(('%' + sub, site_exp[sub])) + +excludes = [] + +# Provide target_triple for use in XFAIL and XTARGET. +config.target_triple = site_exp['target_triplet'] + +# Provide llvm_supports_target for use in local configs. +targets = set(site_exp["TARGETS_TO_BUILD"].split()) +def llvm_supports_target(name): + return name in targets + +langs = set(site_exp['llvmgcc_langs'].split(',')) +def llvm_gcc_supports(name): + return name in langs + +# Provide on_clone hook for reading 'dg.exp'. +import os +simpleLibData = re.compile(r"""load_lib llvm.exp + +RunLLVMTests \[lsort \[glob -nocomplain \$srcdir/\$subdir/\*\.(.*)\]\]""", + re.MULTILINE) +conditionalLibData = re.compile(r"""load_lib llvm.exp + +if.*\[ ?(llvm[^ ]*) ([^ ]*) ?\].*{ + *RunLLVMTests \[lsort \[glob -nocomplain \$srcdir/\$subdir/\*\.(.*)\]\] +\}""", re.MULTILINE) +def on_clone(parent, cfg, for_path): + def addSuffixes(match): + if match[0] == '{' and match[-1] == '}': + cfg.suffixes = ['.' + s for s in match[1:-1].split(',')] + else: + cfg.suffixes = ['.' + match] + + libPath = os.path.join(os.path.dirname(for_path), + 'dg.exp') + if not os.path.exists(libPath): + cfg.unsupported = True + return + + # Reset unsupported, in case we inherited it. + cfg.unsupported = False + lib = open(libPath).read().strip() + + # Check for a simple library. + m = simpleLibData.match(lib) + if m: + addSuffixes(m.group(1)) + return + + # Check for a conditional test set. + m = conditionalLibData.match(lib) + if m: + funcname,arg,match = m.groups() + addSuffixes(match) + + func = globals().get(funcname) + if not func: + lit.error('unsupported predicate %r' % funcname) + elif not func(arg): + cfg.unsupported = True + return + # Otherwise, give up. + lit.error('unable to understand %r:\n%s' % (libPath, lib)) + +config.on_clone = on_clone diff --git a/utils/lit/lit/ExampleTests/LLVM.InTree/test/lit.site.cfg b/utils/lit/lit/ExampleTests/LLVM.InTree/test/lit.site.cfg new file mode 100644 index 0000000..3bfee54 --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.InTree/test/lit.site.cfg @@ -0,0 +1,10 @@ +# -*- Python -*- + +## Autogenerated by Makefile ## +# Do not edit! + +# Preserve some key paths for use by main LLVM test suite config. +config.llvm_obj_root = os.path.dirname(os.path.dirname(__file__)) + +# Let the main config do the real work. +lit.load_config(config, os.path.join(config.llvm_obj_root, 'test/lit.cfg')) diff --git a/utils/lit/lit/ExampleTests/LLVM.InTree/test/site.exp b/utils/lit/lit/ExampleTests/LLVM.InTree/test/site.exp new file mode 100644 index 0000000..1d9c743 --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.InTree/test/site.exp @@ -0,0 +1,30 @@ +## these variables are automatically generated by make ## +# Do not edit here. If you wish to override these values +# edit the last section +set target_triplet "x86_64-apple-darwin10" +set TARGETS_TO_BUILD "X86 Sparc PowerPC Alpha ARM Mips CellSPU PIC16 XCore MSP430 SystemZ Blackfin CBackend MSIL CppBackend" +set llvmgcc_langs "c,c++,objc,obj-c++" +set llvmgcc_version "4.2.1" +set prcontext "/usr/bin/tclsh8.4 /Volumes/Data/ddunbar/llvm/test/Scripts/prcontext.tcl" +set llvmtoolsdir "/Users/ddunbar/llvm.obj.64/Debug/bin" +set llvmlibsdir "/Users/ddunbar/llvm.obj.64/Debug/lib" +set srcroot "/Volumes/Data/ddunbar/llvm" +set objroot "/Volumes/Data/ddunbar/llvm.obj.64" +set srcdir "/Volumes/Data/ddunbar/llvm/test" +set objdir "/Volumes/Data/ddunbar/llvm.obj.64/test" +set gccpath "/usr/bin/gcc -arch x86_64" +set gxxpath "/usr/bin/g++ -arch x86_64" +set compile_c " /usr/bin/gcc -arch x86_64 -I/Users/ddunbar/llvm.obj.64/include -I/Users/ddunbar/llvm.obj.64/test -I/Volumes/Data/ddunbar/llvm.obj.64/include -I/Volumes/Data/ddunbar/llvm/include -I/Volumes/Data/ddunbar/llvm/test -D_DEBUG -D_GNU_SOURCE -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -m64 -pedantic -Wno-long-long -Wall -W -Wno-unused-parameter -Wwrite-strings -c " +set compile_cxx " /usr/bin/g++ -arch x86_64 -I/Users/ddunbar/llvm.obj.64/include -I/Users/ddunbar/llvm.obj.64/test -I/Volumes/Data/ddunbar/llvm.obj.64/include -I/Volumes/Data/ddunbar/llvm/include -I/Volumes/Data/ddunbar/llvm/test -D_DEBUG -D_GNU_SOURCE -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -g -fno-exceptions -fno-common -Woverloaded-virtual -m64 -pedantic -Wno-long-long -Wall -W -Wno-unused-parameter -Wwrite-strings -c " +set link " /usr/bin/g++ -arch x86_64 -I/Users/ddunbar/llvm.obj.64/include -I/Users/ddunbar/llvm.obj.64/test -I/Volumes/Data/ddunbar/llvm.obj.64/include -I/Volumes/Data/ddunbar/llvm/include -I/Volumes/Data/ddunbar/llvm/test -D_DEBUG -D_GNU_SOURCE -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -g -fno-exceptions -fno-common -Woverloaded-virtual -m64 -pedantic -Wno-long-long -Wall -W -Wno-unused-parameter -Wwrite-strings -g -L/Users/ddunbar/llvm.obj.64/Debug/lib -L/Volumes/Data/ddunbar/llvm.obj.64/Debug/lib " +set llvmgcc "/Users/ddunbar/llvm-gcc/install/bin/llvm-gcc -m64 " +set llvmgxx "/Users/ddunbar/llvm-gcc/install/bin/llvm-gcc -m64 " +set llvmgccmajvers "4" +set bugpoint_topts "-gcc-tool-args -m64" +set shlibext ".dylib" +set ocamlopt "/sw/bin/ocamlopt -cc \"g++ -Wall -D_FILE_OFFSET_BITS=64 -D_REENTRANT\" -I /Users/ddunbar/llvm.obj.64/Debug/lib/ocaml" +set valgrind "" +set grep "/usr/bin/grep" +set gas "/usr/bin/as" +set llvmdsymutil "dsymutil" +## All variables above are generated by configure. Do Not Edit ## diff --git a/utils/lit/lit/ExampleTests/LLVM.OutOfTree/lit.local.cfg b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/lit.local.cfg new file mode 100644 index 0000000..80d0c7e --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/lit.local.cfg @@ -0,0 +1 @@ +config.excludes = ['src'] diff --git a/utils/lit/lit/ExampleTests/LLVM.OutOfTree/obj/test/Foo/lit.local.cfg b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/obj/test/Foo/lit.local.cfg new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/obj/test/Foo/lit.local.cfg diff --git a/utils/lit/lit/ExampleTests/LLVM.OutOfTree/obj/test/lit.site.cfg b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/obj/test/lit.site.cfg new file mode 100644 index 0000000..bdcc35e --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/obj/test/lit.site.cfg @@ -0,0 +1,11 @@ +# -*- Python -*- + +## Autogenerated by Makefile ## +# Do not edit! + +# Preserve some key paths for use by main LLVM test suite config. +config.llvm_obj_root = os.path.dirname(os.path.dirname(__file__)) + +# Let the main config do the real work. +lit.load_config(config, os.path.join(config.llvm_obj_root, + '../src/test/lit.cfg')) diff --git a/utils/lit/lit/ExampleTests/LLVM.OutOfTree/obj/test/site.exp b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/obj/test/site.exp new file mode 100644 index 0000000..1d9c743 --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/obj/test/site.exp @@ -0,0 +1,30 @@ +## these variables are automatically generated by make ## +# Do not edit here. If you wish to override these values +# edit the last section +set target_triplet "x86_64-apple-darwin10" +set TARGETS_TO_BUILD "X86 Sparc PowerPC Alpha ARM Mips CellSPU PIC16 XCore MSP430 SystemZ Blackfin CBackend MSIL CppBackend" +set llvmgcc_langs "c,c++,objc,obj-c++" +set llvmgcc_version "4.2.1" +set prcontext "/usr/bin/tclsh8.4 /Volumes/Data/ddunbar/llvm/test/Scripts/prcontext.tcl" +set llvmtoolsdir "/Users/ddunbar/llvm.obj.64/Debug/bin" +set llvmlibsdir "/Users/ddunbar/llvm.obj.64/Debug/lib" +set srcroot "/Volumes/Data/ddunbar/llvm" +set objroot "/Volumes/Data/ddunbar/llvm.obj.64" +set srcdir "/Volumes/Data/ddunbar/llvm/test" +set objdir "/Volumes/Data/ddunbar/llvm.obj.64/test" +set gccpath "/usr/bin/gcc -arch x86_64" +set gxxpath "/usr/bin/g++ -arch x86_64" +set compile_c " /usr/bin/gcc -arch x86_64 -I/Users/ddunbar/llvm.obj.64/include -I/Users/ddunbar/llvm.obj.64/test -I/Volumes/Data/ddunbar/llvm.obj.64/include -I/Volumes/Data/ddunbar/llvm/include -I/Volumes/Data/ddunbar/llvm/test -D_DEBUG -D_GNU_SOURCE -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -m64 -pedantic -Wno-long-long -Wall -W -Wno-unused-parameter -Wwrite-strings -c " +set compile_cxx " /usr/bin/g++ -arch x86_64 -I/Users/ddunbar/llvm.obj.64/include -I/Users/ddunbar/llvm.obj.64/test -I/Volumes/Data/ddunbar/llvm.obj.64/include -I/Volumes/Data/ddunbar/llvm/include -I/Volumes/Data/ddunbar/llvm/test -D_DEBUG -D_GNU_SOURCE -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -g -fno-exceptions -fno-common -Woverloaded-virtual -m64 -pedantic -Wno-long-long -Wall -W -Wno-unused-parameter -Wwrite-strings -c " +set link " /usr/bin/g++ -arch x86_64 -I/Users/ddunbar/llvm.obj.64/include -I/Users/ddunbar/llvm.obj.64/test -I/Volumes/Data/ddunbar/llvm.obj.64/include -I/Volumes/Data/ddunbar/llvm/include -I/Volumes/Data/ddunbar/llvm/test -D_DEBUG -D_GNU_SOURCE -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -g -fno-exceptions -fno-common -Woverloaded-virtual -m64 -pedantic -Wno-long-long -Wall -W -Wno-unused-parameter -Wwrite-strings -g -L/Users/ddunbar/llvm.obj.64/Debug/lib -L/Volumes/Data/ddunbar/llvm.obj.64/Debug/lib " +set llvmgcc "/Users/ddunbar/llvm-gcc/install/bin/llvm-gcc -m64 " +set llvmgxx "/Users/ddunbar/llvm-gcc/install/bin/llvm-gcc -m64 " +set llvmgccmajvers "4" +set bugpoint_topts "-gcc-tool-args -m64" +set shlibext ".dylib" +set ocamlopt "/sw/bin/ocamlopt -cc \"g++ -Wall -D_FILE_OFFSET_BITS=64 -D_REENTRANT\" -I /Users/ddunbar/llvm.obj.64/Debug/lib/ocaml" +set valgrind "" +set grep "/usr/bin/grep" +set gas "/usr/bin/as" +set llvmdsymutil "dsymutil" +## All variables above are generated by configure. Do Not Edit ## diff --git a/utils/lit/lit/ExampleTests/LLVM.OutOfTree/src/test/Foo/data.txt b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/src/test/Foo/data.txt new file mode 100644 index 0000000..45b983b --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/src/test/Foo/data.txt @@ -0,0 +1 @@ +hi diff --git a/utils/lit/lit/ExampleTests/LLVM.OutOfTree/src/test/Foo/dg.exp b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/src/test/Foo/dg.exp new file mode 100644 index 0000000..2bda07a --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/src/test/Foo/dg.exp @@ -0,0 +1,6 @@ +load_lib llvm.exp + +if { [llvm_supports_target X86] } { + RunLLVMTests [lsort [glob -nocomplain $srcdir/$subdir/*.{ll}]] +} + diff --git a/utils/lit/lit/ExampleTests/LLVM.OutOfTree/src/test/Foo/pct-S.ll b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/src/test/Foo/pct-S.ll new file mode 100644 index 0000000..4e8a582 --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/src/test/Foo/pct-S.ll @@ -0,0 +1 @@ +; RUN: grep "hi" %S/data.txt
\ No newline at end of file diff --git a/utils/lit/lit/ExampleTests/LLVM.OutOfTree/src/test/lit.cfg b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/src/test/lit.cfg new file mode 100644 index 0000000..e7ef037 --- /dev/null +++ b/utils/lit/lit/ExampleTests/LLVM.OutOfTree/src/test/lit.cfg @@ -0,0 +1,151 @@ +# -*- Python -*- + +# Configuration file for the 'lit' test runner. + +import os + +# name: The name of this test suite. +config.name = 'LLVM' + +# testFormat: The test format to use to interpret tests. +config.test_format = lit.formats.TclTest() + +# suffixes: A list of file extensions to treat as test files, this is actually +# set by on_clone(). +config.suffixes = [] + +# test_source_root: The root path where tests are located. +config.test_source_root = os.path.dirname(__file__) + +# test_exec_root: The root path where tests should be run. +llvm_obj_root = getattr(config, 'llvm_obj_root', None) +if llvm_obj_root is not None: + config.test_exec_root = os.path.join(llvm_obj_root, 'test') + +### + +import os + +# Check that the object root is known. +if config.test_exec_root is None: + # Otherwise, we haven't loaded the site specific configuration (the user is + # probably trying to run on a test file directly, and either the site + # configuration hasn't been created by the build system, or we are in an + # out-of-tree build situation). + + # Try to detect the situation where we are using an out-of-tree build by + # looking for 'llvm-config'. + # + # FIXME: I debated (i.e., wrote and threw away) adding logic to + # automagically generate the lit.site.cfg if we are in some kind of fresh + # build situation. This means knowing how to invoke the build system + # though, and I decided it was too much magic. + + llvm_config = lit.util.which('llvm-config', config.environment['PATH']) + if not llvm_config: + lit.fatal('No site specific configuration available!') + + # Get the source and object roots. + llvm_src_root = lit.util.capture(['llvm-config', '--src-root']).strip() + llvm_obj_root = lit.util.capture(['llvm-config', '--obj-root']).strip() + + # Validate that we got a tree which points to here. + this_src_root = os.path.dirname(config.test_source_root) + if os.path.realpath(llvm_src_root) != os.path.realpath(this_src_root): + lit.fatal('No site specific configuration available!') + + # Check that the site specific configuration exists. + site_cfg = os.path.join(llvm_obj_root, 'test', 'lit.site.cfg') + if not os.path.exists(site_cfg): + lit.fatal('No site specific configuration available!') + + # Okay, that worked. Notify the user of the automagic, and reconfigure. + lit.note('using out-of-tree build at %r' % llvm_obj_root) + lit.load_config(config, site_cfg) + raise SystemExit + +### + +# Load site data from DejaGNU's site.exp. +import re +site_exp = {} +# FIXME: Implement lit.site.cfg. +for line in open(os.path.join(config.llvm_obj_root, 'test', 'site.exp')): + m = re.match('set ([^ ]+) "([^"]*)"', line) + if m: + site_exp[m.group(1)] = m.group(2) + +# Add substitutions. +for sub in ['prcontext', 'llvmgcc', 'llvmgxx', 'compile_cxx', 'compile_c', + 'link', 'shlibext', 'ocamlopt', 'llvmdsymutil', 'llvmlibsdir', + 'bugpoint_topts']: + if sub in ('llvmgcc', 'llvmgxx'): + config.substitutions.append(('%' + sub, + site_exp[sub] + ' -emit-llvm -w')) + else: + config.substitutions.append(('%' + sub, site_exp[sub])) + +excludes = [] + +# Provide target_triple for use in XFAIL and XTARGET. +config.target_triple = site_exp['target_triplet'] + +# Provide llvm_supports_target for use in local configs. +targets = set(site_exp["TARGETS_TO_BUILD"].split()) +def llvm_supports_target(name): + return name in targets + +langs = set(site_exp['llvmgcc_langs'].split(',')) +def llvm_gcc_supports(name): + return name in langs + +# Provide on_clone hook for reading 'dg.exp'. +import os +simpleLibData = re.compile(r"""load_lib llvm.exp + +RunLLVMTests \[lsort \[glob -nocomplain \$srcdir/\$subdir/\*\.(.*)\]\]""", + re.MULTILINE) +conditionalLibData = re.compile(r"""load_lib llvm.exp + +if.*\[ ?(llvm[^ ]*) ([^ ]*) ?\].*{ + *RunLLVMTests \[lsort \[glob -nocomplain \$srcdir/\$subdir/\*\.(.*)\]\] +\}""", re.MULTILINE) +def on_clone(parent, cfg, for_path): + def addSuffixes(match): + if match[0] == '{' and match[-1] == '}': + cfg.suffixes = ['.' + s for s in match[1:-1].split(',')] + else: + cfg.suffixes = ['.' + match] + + libPath = os.path.join(os.path.dirname(for_path), + 'dg.exp') + if not os.path.exists(libPath): + cfg.unsupported = True + return + + # Reset unsupported, in case we inherited it. + cfg.unsupported = False + lib = open(libPath).read().strip() + + # Check for a simple library. + m = simpleLibData.match(lib) + if m: + addSuffixes(m.group(1)) + return + + # Check for a conditional test set. + m = conditionalLibData.match(lib) + if m: + funcname,arg,match = m.groups() + addSuffixes(match) + + func = globals().get(funcname) + if not func: + lit.error('unsupported predicate %r' % funcname) + elif not func(arg): + cfg.unsupported = True + return + # Otherwise, give up. + lit.error('unable to understand %r:\n%s' % (libPath, lib)) + +config.on_clone = on_clone diff --git a/utils/lit/lit/ExampleTests/ShExternal/lit.local.cfg b/utils/lit/lit/ExampleTests/ShExternal/lit.local.cfg new file mode 100644 index 0000000..1061da6 --- /dev/null +++ b/utils/lit/lit/ExampleTests/ShExternal/lit.local.cfg @@ -0,0 +1,6 @@ +# -*- Python -*- + +config.test_format = lit.formats.ShTest(execute_external = True) + +config.suffixes = ['.c'] + diff --git a/utils/lit/lit/ExampleTests/ShInternal/lit.local.cfg b/utils/lit/lit/ExampleTests/ShInternal/lit.local.cfg new file mode 100644 index 0000000..448eaa4 --- /dev/null +++ b/utils/lit/lit/ExampleTests/ShInternal/lit.local.cfg @@ -0,0 +1,6 @@ +# -*- Python -*- + +config.test_format = lit.formats.ShTest(execute_external = False) + +config.suffixes = ['.c'] + diff --git a/utils/lit/lit/ExampleTests/TclTest/lit.local.cfg b/utils/lit/lit/ExampleTests/TclTest/lit.local.cfg new file mode 100644 index 0000000..6a37129 --- /dev/null +++ b/utils/lit/lit/ExampleTests/TclTest/lit.local.cfg @@ -0,0 +1,5 @@ +# -*- Python -*- + +config.test_format = lit.formats.TclTest() + +config.suffixes = ['.ll'] diff --git a/utils/lit/lit/ExampleTests/TclTest/stderr-pipe.ll b/utils/lit/lit/ExampleTests/TclTest/stderr-pipe.ll new file mode 100644 index 0000000..6c55fe8 --- /dev/null +++ b/utils/lit/lit/ExampleTests/TclTest/stderr-pipe.ll @@ -0,0 +1 @@ +; RUN: gcc -### > /dev/null |& grep {gcc version} diff --git a/utils/lit/lit/ExampleTests/TclTest/tcl-redir-1.ll b/utils/lit/lit/ExampleTests/TclTest/tcl-redir-1.ll new file mode 100644 index 0000000..61240ba --- /dev/null +++ b/utils/lit/lit/ExampleTests/TclTest/tcl-redir-1.ll @@ -0,0 +1,7 @@ +; RUN: echo 'hi' > %t.1 | echo 'hello' > %t.2 +; RUN: not grep 'hi' %t.1 +; RUN: grep 'hello' %t.2 + + + + diff --git a/utils/lit/lit/ExampleTests/fail.c b/utils/lit/lit/ExampleTests/fail.c new file mode 100644 index 0000000..84db41a --- /dev/null +++ b/utils/lit/lit/ExampleTests/fail.c @@ -0,0 +1,2 @@ +// RUN: echo 'I am some stdout' +// RUN: false diff --git a/utils/lit/lit/ExampleTests/lit.cfg b/utils/lit/lit/ExampleTests/lit.cfg new file mode 100644 index 0000000..dbd574f --- /dev/null +++ b/utils/lit/lit/ExampleTests/lit.cfg @@ -0,0 +1,23 @@ +# -*- Python -*- + +# Configuration file for the 'lit' test runner. + +# name: The name of this test suite. +config.name = 'Examples' + +# suffixes: A list of file extensions to treat as test files. +config.suffixes = ['.c', '.cpp', '.m', '.mm', '.ll'] + +# testFormat: The test format to use to interpret tests. +config.test_format = lit.formats.ShTest() + +# test_source_root: The path where tests are located (default is the test suite +# root). +config.test_source_root = None + +# test_exec_root: The path where tests are located (default is the test suite +# root). +config.test_exec_root = None + +# target_triple: Used by ShTest and TclTest formats for XFAIL checks. +config.target_triple = 'foo' diff --git a/utils/lit/lit/ExampleTests/pass.c b/utils/lit/lit/ExampleTests/pass.c new file mode 100644 index 0000000..5c1031c --- /dev/null +++ b/utils/lit/lit/ExampleTests/pass.c @@ -0,0 +1 @@ +// RUN: true diff --git a/utils/lit/lit/ExampleTests/xfail.c b/utils/lit/lit/ExampleTests/xfail.c new file mode 100644 index 0000000..b36cd99 --- /dev/null +++ b/utils/lit/lit/ExampleTests/xfail.c @@ -0,0 +1,2 @@ +// RUN: false +// XFAIL: * diff --git a/utils/lit/lit/ExampleTests/xpass.c b/utils/lit/lit/ExampleTests/xpass.c new file mode 100644 index 0000000..ad84990 --- /dev/null +++ b/utils/lit/lit/ExampleTests/xpass.c @@ -0,0 +1,2 @@ +// RUN: true +// XFAIL diff --git a/utils/lit/lit/LitConfig.py b/utils/lit/lit/LitConfig.py new file mode 100644 index 0000000..0e0a493 --- /dev/null +++ b/utils/lit/lit/LitConfig.py @@ -0,0 +1,95 @@ +class LitConfig: + """LitConfig - Configuration data for a 'lit' test runner instance, shared + across all tests. + + The LitConfig object is also used to communicate with client configuration + files, it is always passed in as the global variable 'lit' so that + configuration files can access common functionality and internal components + easily. + """ + + # Provide access to built-in formats. + import LitFormats as formats + + # Provide access to built-in utility functions. + import Util as util + + def __init__(self, progname, path, quiet, + useValgrind, valgrindArgs, + useTclAsSh, + noExecute, debug, isWindows, + params): + # The name of the test runner. + self.progname = progname + # The items to add to the PATH environment variable. + self.path = list(map(str, path)) + self.quiet = bool(quiet) + self.useValgrind = bool(useValgrind) + self.valgrindArgs = list(valgrindArgs) + self.useTclAsSh = bool(useTclAsSh) + self.noExecute = noExecute + self.debug = debug + self.isWindows = bool(isWindows) + self.params = dict(params) + self.bashPath = None + + self.numErrors = 0 + self.numWarnings = 0 + + def load_config(self, config, path): + """load_config(config, path) - Load a config object from an alternate + path.""" + from TestingConfig import TestingConfig + return TestingConfig.frompath(path, config.parent, self, + mustExist = True, + config = config) + + def getBashPath(self): + """getBashPath - Get the path to 'bash'""" + import os, Util + + if self.bashPath is not None: + return self.bashPath + + self.bashPath = Util.which('bash', os.pathsep.join(self.path)) + if self.bashPath is None: + # Check some known paths. + for path in ('/bin/bash', '/usr/bin/bash'): + if os.path.exists(path): + self.bashPath = path + break + + if self.bashPath is None: + self.warning("Unable to find 'bash', running Tcl tests internally.") + self.bashPath = '' + + return self.bashPath + + def _write_message(self, kind, message): + import inspect, os, sys + + # Get the file/line where this message was generated. + f = inspect.currentframe() + # Step out of _write_message, and then out of wrapper. + f = f.f_back.f_back + file,line,_,_,_ = inspect.getframeinfo(f) + location = '%s:%d' % (os.path.basename(file), line) + + print >>sys.stderr, '%s: %s: %s: %s' % (self.progname, location, + kind, message) + + def note(self, message): + self._write_message('note', message) + + def warning(self, message): + self._write_message('warning', message) + self.numWarnings += 1 + + def error(self, message): + self._write_message('error', message) + self.numErrors += 1 + + def fatal(self, message): + import sys + self._write_message('fatal', message) + sys.exit(2) diff --git a/utils/lit/lit/LitFormats.py b/utils/lit/lit/LitFormats.py new file mode 100644 index 0000000..270f087 --- /dev/null +++ b/utils/lit/lit/LitFormats.py @@ -0,0 +1,3 @@ +from TestFormats import GoogleTest, ShTest, TclTest +from TestFormats import SyntaxCheckTest, OneCommandPerFileTest + diff --git a/utils/lit/lit/ProgressBar.py b/utils/lit/lit/ProgressBar.py new file mode 100644 index 0000000..85c95f5 --- /dev/null +++ b/utils/lit/lit/ProgressBar.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python + +# Source: http://code.activestate.com/recipes/475116/, with +# modifications by Daniel Dunbar. + +import sys, re, time + +class TerminalController: + """ + A class that can be used to portably generate formatted output to + a terminal. + + `TerminalController` defines a set of instance variables whose + values are initialized to the control sequence necessary to + perform a given action. These can be simply included in normal + output to the terminal: + + >>> term = TerminalController() + >>> print 'This is '+term.GREEN+'green'+term.NORMAL + + Alternatively, the `render()` method can used, which replaces + '${action}' with the string required to perform 'action': + + >>> term = TerminalController() + >>> print term.render('This is ${GREEN}green${NORMAL}') + + If the terminal doesn't support a given action, then the value of + the corresponding instance variable will be set to ''. As a + result, the above code will still work on terminals that do not + support color, except that their output will not be colored. + Also, this means that you can test whether the terminal supports a + given action by simply testing the truth value of the + corresponding instance variable: + + >>> term = TerminalController() + >>> if term.CLEAR_SCREEN: + ... print 'This terminal supports clearning the screen.' + + Finally, if the width and height of the terminal are known, then + they will be stored in the `COLS` and `LINES` attributes. + """ + # Cursor movement: + BOL = '' #: Move the cursor to the beginning of the line + UP = '' #: Move the cursor up one line + DOWN = '' #: Move the cursor down one line + LEFT = '' #: Move the cursor left one char + RIGHT = '' #: Move the cursor right one char + + # Deletion: + CLEAR_SCREEN = '' #: Clear the screen and move to home position + CLEAR_EOL = '' #: Clear to the end of the line. + CLEAR_BOL = '' #: Clear to the beginning of the line. + CLEAR_EOS = '' #: Clear to the end of the screen + + # Output modes: + BOLD = '' #: Turn on bold mode + BLINK = '' #: Turn on blink mode + DIM = '' #: Turn on half-bright mode + REVERSE = '' #: Turn on reverse-video mode + NORMAL = '' #: Turn off all modes + + # Cursor display: + HIDE_CURSOR = '' #: Make the cursor invisible + SHOW_CURSOR = '' #: Make the cursor visible + + # Terminal size: + COLS = None #: Width of the terminal (None for unknown) + LINES = None #: Height of the terminal (None for unknown) + + # Foreground colors: + BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = '' + + # Background colors: + BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = '' + BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = '' + + _STRING_CAPABILITIES = """ + BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1 + CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold + BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0 + HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split() + _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split() + _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split() + + def __init__(self, term_stream=sys.stdout): + """ + Create a `TerminalController` and initialize its attributes + with appropriate values for the current terminal. + `term_stream` is the stream that will be used for terminal + output; if this stream is not a tty, then the terminal is + assumed to be a dumb terminal (i.e., have no capabilities). + """ + # Curses isn't available on all platforms + try: import curses + except: return + + # If the stream isn't a tty, then assume it has no capabilities. + if not term_stream.isatty(): return + + # Check the terminal type. If we fail, then assume that the + # terminal has no capabilities. + try: curses.setupterm() + except: return + + # Look up numeric capabilities. + self.COLS = curses.tigetnum('cols') + self.LINES = curses.tigetnum('lines') + + # Look up string capabilities. + for capability in self._STRING_CAPABILITIES: + (attrib, cap_name) = capability.split('=') + setattr(self, attrib, self._tigetstr(cap_name) or '') + + # Colors + set_fg = self._tigetstr('setf') + if set_fg: + for i,color in zip(range(len(self._COLORS)), self._COLORS): + setattr(self, color, curses.tparm(set_fg, i) or '') + set_fg_ansi = self._tigetstr('setaf') + if set_fg_ansi: + for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): + setattr(self, color, curses.tparm(set_fg_ansi, i) or '') + set_bg = self._tigetstr('setb') + if set_bg: + for i,color in zip(range(len(self._COLORS)), self._COLORS): + setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '') + set_bg_ansi = self._tigetstr('setab') + if set_bg_ansi: + for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): + setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '') + + def _tigetstr(self, cap_name): + # String capabilities can include "delays" of the form "$<2>". + # For any modern terminal, we should be able to just ignore + # these, so strip them out. + import curses + cap = curses.tigetstr(cap_name) or '' + return re.sub(r'\$<\d+>[/*]?', '', cap) + + def render(self, template): + """ + Replace each $-substitutions in the given template string with + the corresponding terminal control string (if it's defined) or + '' (if it's not). + """ + return re.sub(r'\$\$|\${\w+}', self._render_sub, template) + + def _render_sub(self, match): + s = match.group() + if s == '$$': return s + else: return getattr(self, s[2:-1]) + +####################################################################### +# Example use case: progress bar +####################################################################### + +class SimpleProgressBar: + """ + A simple progress bar which doesn't need any terminal support. + + This prints out a progress bar like: + 'Header: 0 .. 10.. 20.. ...' + """ + + def __init__(self, header): + self.header = header + self.atIndex = None + + def update(self, percent, message): + if self.atIndex is None: + sys.stdout.write(self.header) + self.atIndex = 0 + + next = int(percent*50) + if next == self.atIndex: + return + + for i in range(self.atIndex, next): + idx = i % 5 + if idx == 0: + sys.stdout.write('%-2d' % (i*2)) + elif idx == 1: + pass # Skip second char + elif idx < 4: + sys.stdout.write('.') + else: + sys.stdout.write(' ') + sys.stdout.flush() + self.atIndex = next + + def clear(self): + if self.atIndex is not None: + sys.stdout.write('\n') + sys.stdout.flush() + self.atIndex = None + +class ProgressBar: + """ + A 3-line progress bar, which looks like:: + + Header + 20% [===========----------------------------------] + progress message + + The progress bar is colored, if the terminal supports color + output; and adjusts to the width of the terminal. + """ + BAR = '%s${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}%s\n' + HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n' + + def __init__(self, term, header, useETA=True): + self.term = term + if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL): + raise ValueError("Terminal isn't capable enough -- you " + "should use a simpler progress dispaly.") + self.width = self.term.COLS or 75 + self.bar = term.render(self.BAR) + self.header = self.term.render(self.HEADER % header.center(self.width)) + self.cleared = 1 #: true if we haven't drawn the bar yet. + self.useETA = useETA + if self.useETA: + self.startTime = time.time() + self.update(0, '') + + def update(self, percent, message): + if self.cleared: + sys.stdout.write(self.header) + self.cleared = 0 + prefix = '%3d%% ' % (percent*100,) + suffix = '' + if self.useETA: + elapsed = time.time() - self.startTime + if percent > .0001 and elapsed > 1: + total = elapsed / percent + eta = int(total - elapsed) + h = eta//3600. + m = (eta//60) % 60 + s = eta % 60 + suffix = ' ETA: %02d:%02d:%02d'%(h,m,s) + barWidth = self.width - len(prefix) - len(suffix) - 2 + n = int(barWidth*percent) + if len(message) < self.width: + message = message + ' '*(self.width - len(message)) + else: + message = '... ' + message[-(self.width-4):] + sys.stdout.write( + self.term.BOL + self.term.UP + self.term.CLEAR_EOL + + (self.bar % (prefix, '='*n, '-'*(barWidth-n), suffix)) + + self.term.CLEAR_EOL + message) + + def clear(self): + if not self.cleared: + sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL + + self.term.UP + self.term.CLEAR_EOL + + self.term.UP + self.term.CLEAR_EOL) + self.cleared = 1 + +def test(): + import time + tc = TerminalController() + p = ProgressBar(tc, 'Tests') + for i in range(101): + p.update(i/100., str(i)) + time.sleep(.3) + +if __name__=='__main__': + test() diff --git a/utils/lit/lit/ShCommands.py b/utils/lit/lit/ShCommands.py new file mode 100644 index 0000000..4550437 --- /dev/null +++ b/utils/lit/lit/ShCommands.py @@ -0,0 +1,85 @@ +class Command: + def __init__(self, args, redirects): + self.args = list(args) + self.redirects = list(redirects) + + def __repr__(self): + return 'Command(%r, %r)' % (self.args, self.redirects) + + def __cmp__(self, other): + if not isinstance(other, Command): + return -1 + + return cmp((self.args, self.redirects), + (other.args, other.redirects)) + + def toShell(self, file): + for arg in self.args: + if "'" not in arg: + quoted = "'%s'" % arg + elif '"' not in arg and '$' not in arg: + quoted = '"%s"' % arg + else: + raise NotImplementedError,'Unable to quote %r' % arg + print >>file, quoted, + + # For debugging / validation. + import ShUtil + dequoted = list(ShUtil.ShLexer(quoted).lex()) + if dequoted != [arg]: + raise NotImplementedError,'Unable to quote %r' % arg + + for r in self.redirects: + if len(r[0]) == 1: + print >>file, "%s '%s'" % (r[0][0], r[1]), + else: + print >>file, "%s%s '%s'" % (r[0][1], r[0][0], r[1]), + +class Pipeline: + def __init__(self, commands, negate=False, pipe_err=False): + self.commands = commands + self.negate = negate + self.pipe_err = pipe_err + + def __repr__(self): + return 'Pipeline(%r, %r, %r)' % (self.commands, self.negate, + self.pipe_err) + + def __cmp__(self, other): + if not isinstance(other, Pipeline): + return -1 + + return cmp((self.commands, self.negate, self.pipe_err), + (other.commands, other.negate, self.pipe_err)) + + def toShell(self, file, pipefail=False): + if pipefail != self.pipe_err: + raise ValueError,'Inconsistent "pipefail" attribute!' + if self.negate: + print >>file, '!', + for cmd in self.commands: + cmd.toShell(file) + if cmd is not self.commands[-1]: + print >>file, '|\n ', + +class Seq: + def __init__(self, lhs, op, rhs): + assert op in (';', '&', '||', '&&') + self.op = op + self.lhs = lhs + self.rhs = rhs + + def __repr__(self): + return 'Seq(%r, %r, %r)' % (self.lhs, self.op, self.rhs) + + def __cmp__(self, other): + if not isinstance(other, Seq): + return -1 + + return cmp((self.lhs, self.op, self.rhs), + (other.lhs, other.op, other.rhs)) + + def toShell(self, file, pipefail=False): + self.lhs.toShell(file, pipefail) + print >>file, ' %s\n' % self.op + self.rhs.toShell(file, pipefail) diff --git a/utils/lit/lit/ShUtil.py b/utils/lit/lit/ShUtil.py new file mode 100644 index 0000000..c4bbb3d --- /dev/null +++ b/utils/lit/lit/ShUtil.py @@ -0,0 +1,346 @@ +import itertools + +import Util +from ShCommands import Command, Pipeline, Seq + +class ShLexer: + def __init__(self, data, win32Escapes = False): + self.data = data + self.pos = 0 + self.end = len(data) + self.win32Escapes = win32Escapes + + def eat(self): + c = self.data[self.pos] + self.pos += 1 + return c + + def look(self): + return self.data[self.pos] + + def maybe_eat(self, c): + """ + maybe_eat(c) - Consume the character c if it is the next character, + returning True if a character was consumed. """ + if self.data[self.pos] == c: + self.pos += 1 + return True + return False + + def lex_arg_fast(self, c): + # Get the leading whitespace free section. + chunk = self.data[self.pos - 1:].split(None, 1)[0] + + # If it has special characters, the fast path failed. + if ('|' in chunk or '&' in chunk or + '<' in chunk or '>' in chunk or + "'" in chunk or '"' in chunk or + '\\' in chunk): + return None + + self.pos = self.pos - 1 + len(chunk) + return chunk + + def lex_arg_slow(self, c): + if c in "'\"": + str = self.lex_arg_quoted(c) + else: + str = c + while self.pos != self.end: + c = self.look() + if c.isspace() or c in "|&": + break + elif c in '><': + # This is an annoying case; we treat '2>' as a single token so + # we don't have to track whitespace tokens. + + # If the parse string isn't an integer, do the usual thing. + if not str.isdigit(): + break + + # Otherwise, lex the operator and convert to a redirection + # token. + num = int(str) + tok = self.lex_one_token() + assert isinstance(tok, tuple) and len(tok) == 1 + return (tok[0], num) + elif c == '"': + self.eat() + str += self.lex_arg_quoted('"')
+ elif not self.win32Escapes and c == '\\': + # Outside of a string, '\\' escapes everything. + self.eat() + if self.pos == self.end: + Util.warning("escape at end of quoted argument in: %r" % + self.data) + return str + str += self.eat() + else: + str += self.eat() + return str + + def lex_arg_quoted(self, delim): + str = '' + while self.pos != self.end: + c = self.eat() + if c == delim: + return str + elif c == '\\' and delim == '"': + # Inside a '"' quoted string, '\\' only escapes the quote + # character and backslash, otherwise it is preserved. + if self.pos == self.end: + Util.warning("escape at end of quoted argument in: %r" % + self.data) + return str + c = self.eat() + if c == '"': # + str += '"' + elif c == '\\': + str += '\\' + else: + str += '\\' + c + else: + str += c + Util.warning("missing quote character in %r" % self.data) + return str + + def lex_arg_checked(self, c): + pos = self.pos + res = self.lex_arg_fast(c) + end = self.pos + + self.pos = pos + reference = self.lex_arg_slow(c) + if res is not None: + if res != reference: + raise ValueError,"Fast path failure: %r != %r" % (res, reference) + if self.pos != end: + raise ValueError,"Fast path failure: %r != %r" % (self.pos, end) + return reference + + def lex_arg(self, c): + return self.lex_arg_fast(c) or self.lex_arg_slow(c) + + def lex_one_token(self): + """ + lex_one_token - Lex a single 'sh' token. """ + + c = self.eat() + if c in ';!': + return (c,) + if c == '|': + if self.maybe_eat('|'): + return ('||',) + return (c,) + if c == '&': + if self.maybe_eat('&'): + return ('&&',) + if self.maybe_eat('>'): + return ('&>',) + return (c,) + if c == '>': + if self.maybe_eat('&'): + return ('>&',) + if self.maybe_eat('>'): + return ('>>',) + return (c,) + if c == '<': + if self.maybe_eat('&'): + return ('<&',) + if self.maybe_eat('>'): + return ('<<',) + return (c,) + + return self.lex_arg(c) + + def lex(self): + while self.pos != self.end: + if self.look().isspace(): + self.eat() + else: + yield self.lex_one_token() + +### + +class ShParser: + def __init__(self, data, win32Escapes = False): + self.data = data + self.tokens = ShLexer(data, win32Escapes = win32Escapes).lex() + + def lex(self): + try: + return self.tokens.next() + except StopIteration: + return None + + def look(self): + next = self.lex() + if next is not None: + self.tokens = itertools.chain([next], self.tokens) + return next + + def parse_command(self): + tok = self.lex() + if not tok: + raise ValueError,"empty command!" + if isinstance(tok, tuple): + raise ValueError,"syntax error near unexpected token %r" % tok[0] + + args = [tok] + redirects = [] + while 1: + tok = self.look() + + # EOF? + if tok is None: + break + + # If this is an argument, just add it to the current command. + if isinstance(tok, str): + args.append(self.lex()) + continue + + # Otherwise see if it is a terminator. + assert isinstance(tok, tuple) + if tok[0] in ('|',';','&','||','&&'): + break + + # Otherwise it must be a redirection. + op = self.lex() + arg = self.lex() + if not arg: + raise ValueError,"syntax error near token %r" % op[0] + redirects.append((op, arg)) + + return Command(args, redirects) + + def parse_pipeline(self): + negate = False + if self.look() == ('!',): + self.lex() + negate = True + + commands = [self.parse_command()] + while self.look() == ('|',): + self.lex() + commands.append(self.parse_command()) + return Pipeline(commands, negate) + + def parse(self): + lhs = self.parse_pipeline() + + while self.look(): + operator = self.lex() + assert isinstance(operator, tuple) and len(operator) == 1 + + if not self.look(): + raise ValueError, "missing argument to operator %r" % operator[0] + + # FIXME: Operator precedence!! + lhs = Seq(lhs, operator[0], self.parse_pipeline()) + + return lhs + +### + +import unittest + +class TestShLexer(unittest.TestCase): + def lex(self, str, *args, **kwargs): + return list(ShLexer(str, *args, **kwargs).lex()) + + def test_basic(self): + self.assertEqual(self.lex('a|b>c&d<e'), + ['a', ('|',), 'b', ('>',), 'c', ('&',), 'd', + ('<',), 'e']) + + def test_redirection_tokens(self): + self.assertEqual(self.lex('a2>c'), + ['a2', ('>',), 'c']) + self.assertEqual(self.lex('a 2>c'), + ['a', ('>',2), 'c']) + + def test_quoting(self): + self.assertEqual(self.lex(""" 'a' """), + ['a']) + self.assertEqual(self.lex(""" "hello\\"world" """), + ['hello"world']) + self.assertEqual(self.lex(""" "hello\\'world" """), + ["hello\\'world"]) + self.assertEqual(self.lex(""" "hello\\\\world" """), + ["hello\\world"]) + self.assertEqual(self.lex(""" he"llo wo"rld """), + ["hello world"]) + self.assertEqual(self.lex(""" a\\ b a\\\\b """), + ["a b", "a\\b"]) + self.assertEqual(self.lex(""" "" "" """), + ["", ""]) + self.assertEqual(self.lex(""" a\\ b """, win32Escapes = True), + ['a\\', 'b']) + +class TestShParse(unittest.TestCase): + def parse(self, str): + return ShParser(str).parse() + + def test_basic(self): + self.assertEqual(self.parse('echo hello'), + Pipeline([Command(['echo', 'hello'], [])], False)) + self.assertEqual(self.parse('echo ""'), + Pipeline([Command(['echo', ''], [])], False)) + + def test_redirection(self): + self.assertEqual(self.parse('echo hello > c'), + Pipeline([Command(['echo', 'hello'], + [((('>'),), 'c')])], False)) + self.assertEqual(self.parse('echo hello > c >> d'), + Pipeline([Command(['echo', 'hello'], [(('>',), 'c'), + (('>>',), 'd')])], False)) + self.assertEqual(self.parse('a 2>&1'), + Pipeline([Command(['a'], [(('>&',2), '1')])], False)) + + def test_pipeline(self): + self.assertEqual(self.parse('a | b'), + Pipeline([Command(['a'], []), + Command(['b'], [])], + False)) + + self.assertEqual(self.parse('a | b | c'), + Pipeline([Command(['a'], []), + Command(['b'], []), + Command(['c'], [])], + False)) + + self.assertEqual(self.parse('! a'), + Pipeline([Command(['a'], [])], + True)) + + def test_list(self): + self.assertEqual(self.parse('a ; b'), + Seq(Pipeline([Command(['a'], [])], False), + ';', + Pipeline([Command(['b'], [])], False))) + + self.assertEqual(self.parse('a & b'), + Seq(Pipeline([Command(['a'], [])], False), + '&', + Pipeline([Command(['b'], [])], False))) + + self.assertEqual(self.parse('a && b'), + Seq(Pipeline([Command(['a'], [])], False), + '&&', + Pipeline([Command(['b'], [])], False))) + + self.assertEqual(self.parse('a || b'), + Seq(Pipeline([Command(['a'], [])], False), + '||', + Pipeline([Command(['b'], [])], False))) + + self.assertEqual(self.parse('a && b || c'), + Seq(Seq(Pipeline([Command(['a'], [])], False), + '&&', + Pipeline([Command(['b'], [])], False)), + '||', + Pipeline([Command(['c'], [])], False))) + +if __name__ == '__main__': + unittest.main() diff --git a/utils/lit/lit/TclUtil.py b/utils/lit/lit/TclUtil.py new file mode 100644 index 0000000..4a3f345 --- /dev/null +++ b/utils/lit/lit/TclUtil.py @@ -0,0 +1,322 @@ +import itertools + +from ShCommands import Command, Pipeline + +def tcl_preprocess(data): + # Tcl has a preprocessing step to replace escaped newlines. + i = data.find('\\\n') + if i == -1: + return data + + # Replace '\\\n' and subsequent whitespace by a single space. + n = len(data) + str = data[:i] + i += 2 + while i < n and data[i] in ' \t': + i += 1 + return str + ' ' + data[i:] + +class TclLexer: + """TclLexer - Lex a string into "words", following the Tcl syntax.""" + + def __init__(self, data): + self.data = tcl_preprocess(data) + self.pos = 0 + self.end = len(self.data) + + def at_end(self): + return self.pos == self.end + + def eat(self): + c = self.data[self.pos] + self.pos += 1 + return c + + def look(self): + return self.data[self.pos] + + def maybe_eat(self, c): + """ + maybe_eat(c) - Consume the character c if it is the next character, + returning True if a character was consumed. """ + if self.data[self.pos] == c: + self.pos += 1 + return True + return False + + def escape(self, c): + if c == 'a': + return '\x07' + elif c == 'b': + return '\x08' + elif c == 'f': + return '\x0c' + elif c == 'n': + return '\n' + elif c == 'r': + return '\r' + elif c == 't': + return '\t' + elif c == 'v': + return '\x0b' + elif c in 'uxo': + raise ValueError,'Invalid quoted character %r' % c + else: + return c + + def lex_braced(self): + # Lex until whitespace or end of string, the opening brace has already + # been consumed. + + str = '' + while 1: + if self.at_end(): + raise ValueError,"Unterminated '{' quoted word" + + c = self.eat() + if c == '}': + break + elif c == '{': + str += '{' + self.lex_braced() + '}' + elif c == '\\' and self.look() in '{}': + str += self.eat() + else: + str += c + + return str + + def lex_quoted(self): + str = '' + + while 1: + if self.at_end(): + raise ValueError,"Unterminated '\"' quoted word" + + c = self.eat() + if c == '"': + break + elif c == '\\': + if self.at_end(): + raise ValueError,'Missing quoted character' + + str += self.escape(self.eat()) + else: + str += c + + return str + + def lex_unquoted(self, process_all=False): + # Lex until whitespace or end of string. + str = '' + while not self.at_end(): + if not process_all: + if self.look().isspace() or self.look() == ';': + break + + c = self.eat() + if c == '\\': + if self.at_end(): + raise ValueError,'Missing quoted character' + + str += self.escape(self.eat()) + elif c == '[': + raise NotImplementedError, ('Command substitution is ' + 'not supported') + elif c == '$' and not self.at_end() and (self.look().isalpha() or + self.look() == '{'): + raise NotImplementedError, ('Variable substitution is ' + 'not supported') + else: + str += c + + return str + + def lex_one_token(self): + if self.maybe_eat('"'): + return self.lex_quoted() + elif self.maybe_eat('{'): + # Check for argument substitution. + if not self.maybe_eat('*'): + return self.lex_braced() + + if not self.maybe_eat('}'): + return '*' + self.lex_braced() + + if self.at_end() or self.look().isspace(): + return '*' + + raise NotImplementedError, "Argument substitution is unsupported" + else: + return self.lex_unquoted() + + def lex(self): + while not self.at_end(): + c = self.look() + if c in ' \t': + self.eat() + elif c in ';\n': + self.eat() + yield (';',) + else: + yield self.lex_one_token() + +class TclExecCommand: + kRedirectPrefixes1 = ('<', '>') + kRedirectPrefixes2 = ('<@', '<<', '2>', '>&', '>>', '>@') + kRedirectPrefixes3 = ('2>@', '2>>', '>>&', '>&@') + kRedirectPrefixes4 = ('2>@1',) + + def __init__(self, args): + self.args = iter(args) + + def lex(self): + try: + return self.args.next() + except StopIteration: + return None + + def look(self): + next = self.lex() + if next is not None: + self.args = itertools.chain([next], self.args) + return next + + def parse_redirect(self, tok, length): + if len(tok) == length: + arg = self.lex() + if arg is None: + raise ValueError,'Missing argument to %r redirection' % tok + else: + tok,arg = tok[:length],tok[length:] + + if tok[0] == '2': + op = (tok[1:],2) + else: + op = (tok,) + return (op, arg) + + def parse_pipeline(self): + if self.look() is None: + raise ValueError,"Expected at least one argument to exec" + + commands = [Command([],[])] + while 1: + arg = self.lex() + if arg is None: + break + elif arg == '|': + commands.append(Command([],[])) + elif arg == '|&': + # Write this as a redirect of stderr; it must come first because + # stdout may have already been redirected. + commands[-1].redirects.insert(0, (('>&',2),'1')) + commands.append(Command([],[])) + elif arg[:4] in TclExecCommand.kRedirectPrefixes4: + commands[-1].redirects.append(self.parse_redirect(arg, 4)) + elif arg[:3] in TclExecCommand.kRedirectPrefixes3: + commands[-1].redirects.append(self.parse_redirect(arg, 3)) + elif arg[:2] in TclExecCommand.kRedirectPrefixes2: + commands[-1].redirects.append(self.parse_redirect(arg, 2)) + elif arg[:1] in TclExecCommand.kRedirectPrefixes1: + commands[-1].redirects.append(self.parse_redirect(arg, 1)) + else: + commands[-1].args.append(arg) + + return Pipeline(commands, False, pipe_err=True) + + def parse(self): + ignoreStderr = False + keepNewline = False + + # Parse arguments. + while 1: + next = self.look() + if not isinstance(next, str) or next[0] != '-': + break + + if next == '--': + self.lex() + break + elif next == '-ignorestderr': + ignoreStderr = True + elif next == '-keepnewline': + keepNewline = True + else: + raise ValueError,"Invalid exec argument %r" % next + + return (ignoreStderr, keepNewline, self.parse_pipeline()) + +### + +import unittest + +class TestTclLexer(unittest.TestCase): + def lex(self, str, *args, **kwargs): + return list(TclLexer(str, *args, **kwargs).lex()) + + def test_preprocess(self): + self.assertEqual(tcl_preprocess('a b'), 'a b') + self.assertEqual(tcl_preprocess('a\\\nb c'), 'a b c') + + def test_unquoted(self): + self.assertEqual(self.lex('a b c'), + ['a', 'b', 'c']) + self.assertEqual(self.lex(r'a\nb\tc\ '), + ['a\nb\tc ']) + self.assertEqual(self.lex(r'a \\\$b c $\\'), + ['a', r'\$b', 'c', '$\\']) + + def test_braced(self): + self.assertEqual(self.lex('a {b c} {}'), + ['a', 'b c', '']) + self.assertEqual(self.lex(r'a {b {c\n}}'), + ['a', 'b {c\\n}']) + self.assertEqual(self.lex(r'a {b\{}'), + ['a', 'b{']) + self.assertEqual(self.lex(r'{*}'), ['*']) + self.assertEqual(self.lex(r'{*} a'), ['*', 'a']) + self.assertEqual(self.lex(r'{*} a'), ['*', 'a']) + self.assertEqual(self.lex('{a\\\n b}'), + ['a b']) + + def test_quoted(self): + self.assertEqual(self.lex('a "b c"'), + ['a', 'b c']) + + def test_terminators(self): + self.assertEqual(self.lex('a\nb'), + ['a', (';',), 'b']) + self.assertEqual(self.lex('a;b'), + ['a', (';',), 'b']) + self.assertEqual(self.lex('a ; b'), + ['a', (';',), 'b']) + +class TestTclExecCommand(unittest.TestCase): + def parse(self, str): + return TclExecCommand(list(TclLexer(str).lex())).parse() + + def test_basic(self): + self.assertEqual(self.parse('echo hello'), + (False, False, + Pipeline([Command(['echo', 'hello'], [])], + False, True))) + self.assertEqual(self.parse('echo hello | grep hello'), + (False, False, + Pipeline([Command(['echo', 'hello'], []), + Command(['grep', 'hello'], [])], + False, True))) + + def test_redirect(self): + self.assertEqual(self.parse('echo hello > a >b >>c 2> d |& e'), + (False, False, + Pipeline([Command(['echo', 'hello'], + [(('>&',2),'1'), + (('>',),'a'), + (('>',),'b'), + (('>>',),'c'), + (('>',2),'d')]), + Command(['e'], [])], + False, True))) + +if __name__ == '__main__': + unittest.main() diff --git a/utils/lit/lit/Test.py b/utils/lit/lit/Test.py new file mode 100644 index 0000000..1f6556b --- /dev/null +++ b/utils/lit/lit/Test.py @@ -0,0 +1,79 @@ +import os + +# Test results. + +class TestResult: + def __init__(self, name, isFailure): + self.name = name + self.isFailure = isFailure + +PASS = TestResult('PASS', False) +XFAIL = TestResult('XFAIL', False) +FAIL = TestResult('FAIL', True) +XPASS = TestResult('XPASS', True) +UNRESOLVED = TestResult('UNRESOLVED', True) +UNSUPPORTED = TestResult('UNSUPPORTED', False) + +# Test classes. + +class TestFormat: + """TestFormat - Test information provider.""" + + def __init__(self, name): + self.name = name + +class TestSuite: + """TestSuite - Information on a group of tests. + + A test suite groups together a set of logically related tests. + """ + + def __init__(self, name, source_root, exec_root, config): + self.name = name + self.source_root = source_root + self.exec_root = exec_root + # The test suite configuration. + self.config = config + + def getSourcePath(self, components): + return os.path.join(self.source_root, *components) + + def getExecPath(self, components): + return os.path.join(self.exec_root, *components) + +class Test: + """Test - Information on a single test instance.""" + + def __init__(self, suite, path_in_suite, config): + self.suite = suite + self.path_in_suite = path_in_suite + self.config = config + # The test result code, once complete. + self.result = None + # Any additional output from the test, once complete. + self.output = None + # The wall time to execute this test, if timing and once complete. + self.elapsed = None + # The repeat index of this test, or None. + self.index = None + + def copyWithIndex(self, index): + import copy + res = copy.copy(self) + res.index = index + return res + + def setResult(self, result, output, elapsed): + assert self.result is None, "Test result already set!" + self.result = result + self.output = output + self.elapsed = elapsed + + def getFullName(self): + return self.suite.config.name + '::' + '/'.join(self.path_in_suite) + + def getSourcePath(self): + return self.suite.getSourcePath(self.path_in_suite) + + def getExecPath(self): + return self.suite.getExecPath(self.path_in_suite) diff --git a/utils/lit/lit/TestFormats.py b/utils/lit/lit/TestFormats.py new file mode 100644 index 0000000..5dfd54a --- /dev/null +++ b/utils/lit/lit/TestFormats.py @@ -0,0 +1,189 @@ +import os + +import Test +import TestRunner +import Util + +class GoogleTest(object): + def __init__(self, test_sub_dir, test_suffix): + self.test_sub_dir = str(test_sub_dir) + self.test_suffix = str(test_suffix) + + def getGTestTests(self, path, litConfig): + """getGTestTests(path) - [name] + + Return the tests available in gtest executable.""" + + try: + lines = Util.capture([path, '--gtest_list_tests']).split('\n') + except: + litConfig.error("unable to discover google-tests in %r" % path) + raise StopIteration + + nested_tests = [] + for ln in lines: + if not ln.strip(): + continue + + prefix = '' + index = 0 + while ln[index*2:index*2+2] == ' ': + index += 1 + while len(nested_tests) > index: + nested_tests.pop() + + ln = ln[index*2:] + if ln.endswith('.'): + nested_tests.append(ln) + else: + yield ''.join(nested_tests) + ln + + def getTestsInDirectory(self, testSuite, path_in_suite, + litConfig, localConfig): + source_path = testSuite.getSourcePath(path_in_suite) + for filename in os.listdir(source_path): + # Check for the one subdirectory (build directory) tests will be in. + if filename != self.test_sub_dir: + continue + + filepath = os.path.join(source_path, filename) + for subfilename in os.listdir(filepath): + if subfilename.endswith(self.test_suffix): + execpath = os.path.join(filepath, subfilename) + + # Discover the tests in this executable. + for name in self.getGTestTests(execpath, litConfig): + testPath = path_in_suite + (filename, subfilename, name) + yield Test.Test(testSuite, testPath, localConfig) + + def execute(self, test, litConfig): + testPath,testName = os.path.split(test.getSourcePath()) + while not os.path.exists(testPath): + # Handle GTest parametrized and typed tests, whose name includes + # some '/'s. + testPath, namePrefix = os.path.split(testPath) + testName = os.path.join(namePrefix, testName) + + cmd = [testPath, '--gtest_filter=' + testName] + out, err, exitCode = TestRunner.executeCommand(cmd) + + if not exitCode: + return Test.PASS,'' + + return Test.FAIL, out + err + +### + +class FileBasedTest(object): + def getTestsInDirectory(self, testSuite, path_in_suite, + litConfig, localConfig): + source_path = testSuite.getSourcePath(path_in_suite) + for filename in os.listdir(source_path): + filepath = os.path.join(source_path, filename) + if not os.path.isdir(filepath): + base,ext = os.path.splitext(filename) + if ext in localConfig.suffixes: + yield Test.Test(testSuite, path_in_suite + (filename,), + localConfig) + +class ShTest(FileBasedTest): + def __init__(self, execute_external = False): + self.execute_external = execute_external + + def execute(self, test, litConfig): + return TestRunner.executeShTest(test, litConfig, + self.execute_external) + +class TclTest(FileBasedTest): + def execute(self, test, litConfig): + return TestRunner.executeTclTest(test, litConfig) + +### + +import re +import tempfile + +class OneCommandPerFileTest: + # FIXME: Refactor into generic test for running some command on a directory + # of inputs. + + def __init__(self, command, dir, recursive=False, + pattern=".*", useTempInput=False): + if isinstance(command, str): + self.command = [command] + else: + self.command = list(command) + self.dir = str(dir) + self.recursive = bool(recursive) + self.pattern = re.compile(pattern) + self.useTempInput = useTempInput + + def getTestsInDirectory(self, testSuite, path_in_suite, + litConfig, localConfig): + for dirname,subdirs,filenames in os.walk(self.dir): + if not self.recursive: + subdirs[:] = [] + + subdirs[:] = [d for d in subdirs + if (d != '.svn' and + d not in localConfig.excludes)] + + for filename in filenames: + if (not self.pattern.match(filename) or + filename in localConfig.excludes): + continue + + path = os.path.join(dirname,filename) + suffix = path[len(self.dir):] + if suffix.startswith(os.sep): + suffix = suffix[1:] + test = Test.Test(testSuite, + path_in_suite + tuple(suffix.split(os.sep)), + localConfig) + # FIXME: Hack? + test.source_path = path + yield test + + def createTempInput(self, tmp, test): + abstract + + def execute(self, test, litConfig): + if test.config.unsupported: + return (Test.UNSUPPORTED, 'Test is unsupported') + + cmd = list(self.command) + + # If using temp input, create a temporary file and hand it to the + # subclass. + if self.useTempInput: + tmp = tempfile.NamedTemporaryFile(suffix='.cpp') + self.createTempInput(tmp, test) + tmp.flush() + cmd.append(tmp.name) + else: + cmd.append(test.source_path) + + out, err, exitCode = TestRunner.executeCommand(cmd) + + diags = out + err + if not exitCode and not diags.strip(): + return Test.PASS,'' + + # Try to include some useful information. + report = """Command: %s\n""" % ' '.join(["'%s'" % a + for a in cmd]) + if self.useTempInput: + report += """Temporary File: %s\n""" % tmp.name + report += "--\n%s--\n""" % open(tmp.name).read() + report += """Output:\n--\n%s--""" % diags + + return Test.FAIL, report + +class SyntaxCheckTest(OneCommandPerFileTest): + def __init__(self, compiler, dir, extra_cxx_args=[], *args, **kwargs): + cmd = [compiler, '-x', 'c++', '-fsyntax-only'] + extra_cxx_args + OneCommandPerFileTest.__init__(self, cmd, dir, + useTempInput=1, *args, **kwargs) + + def createTempInput(self, tmp, test): + print >>tmp, '#include "%s"' % test.source_path diff --git a/utils/lit/lit/TestRunner.py b/utils/lit/lit/TestRunner.py new file mode 100644 index 0000000..20fbc6c --- /dev/null +++ b/utils/lit/lit/TestRunner.py @@ -0,0 +1,517 @@ +import os, signal, subprocess, sys +import StringIO + +import ShUtil +import Test +import Util + +import platform +import tempfile + +class InternalShellError(Exception): + def __init__(self, command, message): + self.command = command + self.message = message + +# Don't use close_fds on Windows. +kUseCloseFDs = platform.system() != 'Windows' + +# Use temporary files to replace /dev/null on Windows. +kAvoidDevNull = platform.system() == 'Windows' + +def executeCommand(command, cwd=None, env=None): + p = subprocess.Popen(command, cwd=cwd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env) + out,err = p.communicate() + exitCode = p.wait() + + # Detect Ctrl-C in subprocess. + if exitCode == -signal.SIGINT: + raise KeyboardInterrupt + + return out, err, exitCode + +def executeShCmd(cmd, cfg, cwd, results): + if isinstance(cmd, ShUtil.Seq): + if cmd.op == ';': + res = executeShCmd(cmd.lhs, cfg, cwd, results) + return executeShCmd(cmd.rhs, cfg, cwd, results) + + if cmd.op == '&': + raise NotImplementedError,"unsupported test command: '&'" + + if cmd.op == '||': + res = executeShCmd(cmd.lhs, cfg, cwd, results) + if res != 0: + res = executeShCmd(cmd.rhs, cfg, cwd, results) + return res + if cmd.op == '&&': + res = executeShCmd(cmd.lhs, cfg, cwd, results) + if res is None: + return res + + if res == 0: + res = executeShCmd(cmd.rhs, cfg, cwd, results) + return res + + raise ValueError,'Unknown shell command: %r' % cmd.op + + assert isinstance(cmd, ShUtil.Pipeline) + procs = [] + input = subprocess.PIPE + stderrTempFiles = [] + # To avoid deadlock, we use a single stderr stream for piped + # output. This is null until we have seen some output using + # stderr. + for i,j in enumerate(cmd.commands): + # Apply the redirections, we use (N,) as a sentinal to indicate stdin, + # stdout, stderr for N equal to 0, 1, or 2 respectively. Redirects to or + # from a file are represented with a list [file, mode, file-object] + # where file-object is initially None. + redirects = [(0,), (1,), (2,)] + for r in j.redirects: + if r[0] == ('>',2): + redirects[2] = [r[1], 'w', None] + elif r[0] == ('>>',2): + redirects[2] = [r[1], 'a', None] + elif r[0] == ('>&',2) and r[1] in '012': + redirects[2] = redirects[int(r[1])] + elif r[0] == ('>&',) or r[0] == ('&>',): + redirects[1] = redirects[2] = [r[1], 'w', None] + elif r[0] == ('>',): + redirects[1] = [r[1], 'w', None] + elif r[0] == ('>>',): + redirects[1] = [r[1], 'a', None] + elif r[0] == ('<',): + redirects[0] = [r[1], 'r', None] + else: + raise NotImplementedError,"Unsupported redirect: %r" % (r,) + + # Map from the final redirections to something subprocess can handle. + final_redirects = [] + for index,r in enumerate(redirects): + if r == (0,): + result = input + elif r == (1,): + if index == 0: + raise NotImplementedError,"Unsupported redirect for stdin" + elif index == 1: + result = subprocess.PIPE + else: + result = subprocess.STDOUT + elif r == (2,): + if index != 2: + raise NotImplementedError,"Unsupported redirect on stdout" + result = subprocess.PIPE + else: + if r[2] is None: + if kAvoidDevNull and r[0] == '/dev/null': + r[2] = tempfile.TemporaryFile(mode=r[1]) + else: + r[2] = open(r[0], r[1]) + # Workaround a Win32 and/or subprocess bug when appending. + if r[1] == 'a': + r[2].seek(0, 2) + result = r[2] + final_redirects.append(result) + + stdin, stdout, stderr = final_redirects + + # If stderr wants to come from stdout, but stdout isn't a pipe, then put + # stderr on a pipe and treat it as stdout. + if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE): + stderr = subprocess.PIPE + stderrIsStdout = True + else: + stderrIsStdout = False + + # Don't allow stderr on a PIPE except for the last + # process, this could deadlock. + # + # FIXME: This is slow, but so is deadlock. + if stderr == subprocess.PIPE and j != cmd.commands[-1]: + stderr = tempfile.TemporaryFile(mode='w+b') + stderrTempFiles.append((i, stderr)) + + # Resolve the executable path ourselves. + args = list(j.args) + args[0] = Util.which(args[0], cfg.environment['PATH']) + if not args[0]: + raise InternalShellError(j, '%r: command not found' % j.args[0]) + + procs.append(subprocess.Popen(args, cwd=cwd, + stdin = stdin, + stdout = stdout, + stderr = stderr, + env = cfg.environment, + close_fds = kUseCloseFDs)) + + # Immediately close stdin for any process taking stdin from us. + if stdin == subprocess.PIPE: + procs[-1].stdin.close() + procs[-1].stdin = None + + # Update the current stdin source. + if stdout == subprocess.PIPE: + input = procs[-1].stdout + elif stderrIsStdout: + input = procs[-1].stderr + else: + input = subprocess.PIPE + + # FIXME: There is probably still deadlock potential here. Yawn. + procData = [None] * len(procs) + procData[-1] = procs[-1].communicate() + + for i in range(len(procs) - 1): + if procs[i].stdout is not None: + out = procs[i].stdout.read() + else: + out = '' + if procs[i].stderr is not None: + err = procs[i].stderr.read() + else: + err = '' + procData[i] = (out,err) + + # Read stderr out of the temp files. + for i,f in stderrTempFiles: + f.seek(0, 0) + procData[i] = (procData[i][0], f.read()) + + exitCode = None + for i,(out,err) in enumerate(procData): + res = procs[i].wait() + # Detect Ctrl-C in subprocess. + if res == -signal.SIGINT: + raise KeyboardInterrupt + + results.append((cmd.commands[i], out, err, res)) + if cmd.pipe_err: + # Python treats the exit code as a signed char. + if res < 0: + exitCode = min(exitCode, res) + else: + exitCode = max(exitCode, res) + else: + exitCode = res + + if cmd.negate: + exitCode = not exitCode + + return exitCode + +def executeScriptInternal(test, litConfig, tmpBase, commands, cwd): + ln = ' &&\n'.join(commands) + try: + cmd = ShUtil.ShParser(ln, litConfig.isWindows).parse() + except: + return (Test.FAIL, "shell parser error on: %r" % ln) + + results = [] + try: + exitCode = executeShCmd(cmd, test.config, cwd, results) + except InternalShellError,e: + out = '' + err = e.message + exitCode = 255 + + out = err = '' + for i,(cmd, cmd_out,cmd_err,res) in enumerate(results): + out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args)) + out += 'Command %d Result: %r\n' % (i, res) + out += 'Command %d Output:\n%s\n\n' % (i, cmd_out) + out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err) + + return out, err, exitCode + +def executeTclScriptInternal(test, litConfig, tmpBase, commands, cwd): + import TclUtil + cmds = [] + for ln in commands: + # Given the unfortunate way LLVM's test are written, the line gets + # backslash substitution done twice. + ln = TclUtil.TclLexer(ln).lex_unquoted(process_all = True) + + try: + tokens = list(TclUtil.TclLexer(ln).lex()) + except: + return (Test.FAIL, "Tcl lexer error on: %r" % ln) + + # Validate there are no control tokens. + for t in tokens: + if not isinstance(t, str): + return (Test.FAIL, + "Invalid test line: %r containing %r" % (ln, t)) + + try: + cmds.append(TclUtil.TclExecCommand(tokens).parse_pipeline()) + except: + return (Test.FAIL, "Tcl 'exec' parse error on: %r" % ln) + + cmd = cmds[0] + for c in cmds[1:]: + cmd = ShUtil.Seq(cmd, '&&', c) + + # FIXME: This is lame, we shouldn't need bash. See PR5240. + bashPath = litConfig.getBashPath() + if litConfig.useTclAsSh and bashPath: + script = tmpBase + '.script' + + # Write script file + f = open(script,'w') + print >>f, 'set -o pipefail' + cmd.toShell(f, pipefail = True) + f.close() + + if 0: + print >>sys.stdout, cmd + print >>sys.stdout, open(script).read() + print >>sys.stdout + return '', '', 0 + + command = [litConfig.getBashPath(), script] + out,err,exitCode = executeCommand(command, cwd=cwd, + env=test.config.environment) + + # Tcl commands fail on standard error output. + if err: + exitCode = 1 + out = 'Command has output on stderr!\n\n' + out + + return out,err,exitCode + else: + results = [] + try: + exitCode = executeShCmd(cmd, test.config, cwd, results) + except InternalShellError,e: + results.append((e.command, '', e.message + '\n', 255)) + exitCode = 255 + + out = err = '' + + # Tcl commands fail on standard error output. + if [True for _,_,err,res in results if err]: + exitCode = 1 + out += 'Command has output on stderr!\n\n' + + for i,(cmd, cmd_out, cmd_err, res) in enumerate(results): + out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args)) + out += 'Command %d Result: %r\n' % (i, res) + out += 'Command %d Output:\n%s\n\n' % (i, cmd_out) + out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err) + + return out, err, exitCode + +def executeScript(test, litConfig, tmpBase, commands, cwd): + script = tmpBase + '.script' + if litConfig.isWindows: + script += '.bat' + + # Write script file + f = open(script,'w') + if litConfig.isWindows: + f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands)) + else: + f.write(' &&\n'.join(commands)) + f.write('\n') + f.close() + + if litConfig.isWindows: + command = ['cmd','/c', script] + else: + command = ['/bin/sh', script] + if litConfig.useValgrind: + # FIXME: Running valgrind on sh is overkill. We probably could just + # run on clang with no real loss. + valgrindArgs = ['valgrind', '-q', + '--tool=memcheck', '--trace-children=yes', + '--error-exitcode=123'] + valgrindArgs.extend(litConfig.valgrindArgs) + + command = valgrindArgs + command + + return executeCommand(command, cwd=cwd, env=test.config.environment) + +def isExpectedFail(xfails, xtargets, target_triple): + # Check if any xfail matches this target. + for item in xfails: + if item == '*' or item in target_triple: + break + else: + return False + + # If so, see if it is expected to pass on this target. + # + # FIXME: Rename XTARGET to something that makes sense, like XPASS. + for item in xtargets: + if item == '*' or item in target_triple: + return False + + return True + +def parseIntegratedTestScript(test): + """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test + script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET' + information. The RUN lines also will have variable substitution performed. + """ + + # Get the temporary location, this is always relative to the test suite + # root, not test source root. + # + # FIXME: This should not be here? + sourcepath = test.getSourcePath() + execpath = test.getExecPath() + execdir,execbase = os.path.split(execpath) + tmpBase = os.path.join(execdir, 'Output', execbase) + if test.index is not None: + tmpBase += '_%d' % test.index + + # We use #_MARKER_# to hide %% while we do the other substitutions. + substitutions = [('%%', '#_MARKER_#')] + substitutions.extend(test.config.substitutions) + substitutions.extend([('%s', sourcepath), + ('%S', os.path.dirname(sourcepath)), + ('%p', os.path.dirname(sourcepath)), + ('%t', tmpBase + '.tmp'), + # FIXME: Remove this once we kill DejaGNU. + ('%abs_tmp', tmpBase + '.tmp'), + ('#_MARKER_#', '%')]) + + # Collect the test lines from the script. + script = [] + xfails = [] + xtargets = [] + for ln in open(sourcepath): + if 'RUN:' in ln: + # Isolate the command to run. + index = ln.index('RUN:') + ln = ln[index+4:] + + # Trim trailing whitespace. + ln = ln.rstrip() + + # Collapse lines with trailing '\\'. + if script and script[-1][-1] == '\\': + script[-1] = script[-1][:-1] + ln + else: + script.append(ln) + elif 'XFAIL:' in ln: + items = ln[ln.index('XFAIL:') + 6:].split(',') + xfails.extend([s.strip() for s in items]) + elif 'XTARGET:' in ln: + items = ln[ln.index('XTARGET:') + 8:].split(',') + xtargets.extend([s.strip() for s in items]) + elif 'END.' in ln: + # Check for END. lines. + if ln[ln.index('END.'):].strip() == 'END.': + break + + # Apply substitutions to the script. + def processLine(ln): + # Apply substitutions + for a,b in substitutions: + ln = ln.replace(a,b) + + # Strip the trailing newline and any extra whitespace. + return ln.strip() + script = map(processLine, script) + + # Verify the script contains a run line. + if not script: + return (Test.UNRESOLVED, "Test has no run line!") + + if script[-1][-1] == '\\': + return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')") + + isXFail = isExpectedFail(xfails, xtargets, test.suite.config.target_triple) + return script,isXFail,tmpBase,execdir + +def formatTestOutput(status, out, err, exitCode, script): + output = StringIO.StringIO() + print >>output, "Script:" + print >>output, "--" + print >>output, '\n'.join(script) + print >>output, "--" + print >>output, "Exit Code: %r" % exitCode + print >>output, "Command Output (stdout):" + print >>output, "--" + output.write(out) + print >>output, "--" + print >>output, "Command Output (stderr):" + print >>output, "--" + output.write(err) + print >>output, "--" + return (status, output.getvalue()) + +def executeTclTest(test, litConfig): + if test.config.unsupported: + return (Test.UNSUPPORTED, 'Test is unsupported') + + res = parseIntegratedTestScript(test) + if len(res) == 2: + return res + + script, isXFail, tmpBase, execdir = res + + if litConfig.noExecute: + return (Test.PASS, '') + + # Create the output directory if it does not already exist. + Util.mkdir_p(os.path.dirname(tmpBase)) + + res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir) + if len(res) == 2: + return res + + out,err,exitCode = res + if isXFail: + ok = exitCode != 0 + status = (Test.XPASS, Test.XFAIL)[ok] + else: + ok = exitCode == 0 + status = (Test.FAIL, Test.PASS)[ok] + + if ok: + return (status,'') + + return formatTestOutput(status, out, err, exitCode, script) + +def executeShTest(test, litConfig, useExternalSh): + if test.config.unsupported: + return (Test.UNSUPPORTED, 'Test is unsupported') + + res = parseIntegratedTestScript(test) + if len(res) == 2: + return res + + script, isXFail, tmpBase, execdir = res + + if litConfig.noExecute: + return (Test.PASS, '') + + # Create the output directory if it does not already exist. + Util.mkdir_p(os.path.dirname(tmpBase)) + + if useExternalSh: + res = executeScript(test, litConfig, tmpBase, script, execdir) + else: + res = executeScriptInternal(test, litConfig, tmpBase, script, execdir) + if len(res) == 2: + return res + + out,err,exitCode = res + if isXFail: + ok = exitCode != 0 + status = (Test.XPASS, Test.XFAIL)[ok] + else: + ok = exitCode == 0 + status = (Test.FAIL, Test.PASS)[ok] + + if ok: + return (status,'') + + return formatTestOutput(status, out, err, exitCode, script) diff --git a/utils/lit/lit/TestingConfig.py b/utils/lit/lit/TestingConfig.py new file mode 100644 index 0000000..1f5067c --- /dev/null +++ b/utils/lit/lit/TestingConfig.py @@ -0,0 +1,97 @@ +import os + +class TestingConfig: + """" + TestingConfig - Information on the tests inside a suite. + """ + + @staticmethod + def frompath(path, parent, litConfig, mustExist, config = None): + if config is None: + # Set the environment based on the command line arguments. + environment = { + 'PATH' : os.pathsep.join(litConfig.path + + [os.environ.get('PATH','')]), + 'PATHEXT' : os.environ.get('PATHEXT',''), + 'SYSTEMROOT' : os.environ.get('SYSTEMROOT',''), + 'LLVM_DISABLE_CRT_DEBUG' : '1', + } + + config = TestingConfig(parent, + name = '<unnamed>', + suffixes = set(), + test_format = None, + environment = environment, + substitutions = [], + unsupported = False, + on_clone = None, + test_exec_root = None, + test_source_root = None, + excludes = []) + + if os.path.exists(path): + # FIXME: Improve detection and error reporting of errors in the + # config file. + f = open(path) + cfg_globals = dict(globals()) + cfg_globals['config'] = config + cfg_globals['lit'] = litConfig + cfg_globals['__file__'] = path + try: + exec f in cfg_globals + except SystemExit,status: + # We allow normal system exit inside a config file to just + # return control without error. + if status.args: + raise + f.close() + elif mustExist: + litConfig.fatal('unable to load config from %r ' % path) + + config.finish(litConfig) + return config + + def __init__(self, parent, name, suffixes, test_format, + environment, substitutions, unsupported, on_clone, + test_exec_root, test_source_root, excludes): + self.parent = parent + self.name = str(name) + self.suffixes = set(suffixes) + self.test_format = test_format + self.environment = dict(environment) + self.substitutions = list(substitutions) + self.unsupported = unsupported + self.on_clone = on_clone + self.test_exec_root = test_exec_root + self.test_source_root = test_source_root + self.excludes = set(excludes) + + def clone(self, path): + # FIXME: Chain implementations? + # + # FIXME: Allow extra parameters? + cfg = TestingConfig(self, self.name, self.suffixes, self.test_format, + self.environment, self.substitutions, + self.unsupported, self.on_clone, + self.test_exec_root, self.test_source_root, + self.excludes) + if cfg.on_clone: + cfg.on_clone(self, cfg, path) + return cfg + + def finish(self, litConfig): + """finish() - Finish this config object, after loading is complete.""" + + self.name = str(self.name) + self.suffixes = set(self.suffixes) + self.environment = dict(self.environment) + self.substitutions = list(self.substitutions) + if self.test_exec_root is not None: + # FIXME: This should really only be suite in test suite config + # files. Should we distinguish them? + self.test_exec_root = str(self.test_exec_root) + if self.test_source_root is not None: + # FIXME: This should really only be suite in test suite config + # files. Should we distinguish them? + self.test_source_root = str(self.test_source_root) + self.excludes = set(self.excludes) diff --git a/utils/lit/lit/Util.py b/utils/lit/lit/Util.py new file mode 100644 index 0000000..66c5e46 --- /dev/null +++ b/utils/lit/lit/Util.py @@ -0,0 +1,124 @@ +import os, sys + +def detectCPUs(): + """ + Detects the number of CPUs on a system. Cribbed from pp. + """ + # Linux, Unix and MacOS: + if hasattr(os, "sysconf"): + if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"): + # Linux & Unix: + ncpus = os.sysconf("SC_NPROCESSORS_ONLN") + if isinstance(ncpus, int) and ncpus > 0: + return ncpus + else: # OSX: + return int(os.popen2("sysctl -n hw.ncpu")[1].read()) + # Windows: + if os.environ.has_key("NUMBER_OF_PROCESSORS"): + ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]) + if ncpus > 0: + return ncpus + return 1 # Default + +def mkdir_p(path): + """mkdir_p(path) - Make the "path" directory, if it does not exist; this + will also make directories for any missing parent directories.""" + import errno + + if not path or os.path.exists(path): + return + + parent = os.path.dirname(path) + if parent != path: + mkdir_p(parent) + + try: + os.mkdir(path) + except OSError,e: + # Ignore EEXIST, which may occur during a race condition. + if e.errno != errno.EEXIST: + raise + +def capture(args): + import subprocess + """capture(command) - Run the given command (or argv list) in a shell and + return the standard output.""" + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out,_ = p.communicate() + return out + +def which(command, paths = None): + """which(command, [paths]) - Look up the given command in the paths string + (or the PATH environment variable, if unspecified).""" + + if paths is None: + paths = os.environ.get('PATH','') + + # Check for absolute match first. + if os.path.exists(command): + return command + + # Would be nice if Python had a lib function for this. + if not paths: + paths = os.defpath + + # Get suffixes to search. + pathext = os.environ.get('PATHEXT', '').split(os.pathsep) + + # Search the paths... + for path in paths.split(os.pathsep): + for ext in pathext: + p = os.path.join(path, command + ext) + if os.path.exists(p): + return p + + return None + +def printHistogram(items, title = 'Items'): + import itertools, math + + items.sort(key = lambda (_,v): v) + + maxValue = max([v for _,v in items]) + + # Select first "nice" bar height that produces more than 10 bars. + power = int(math.ceil(math.log(maxValue, 10))) + for inc in itertools.cycle((5, 2, 2.5, 1)): + barH = inc * 10**power + N = int(math.ceil(maxValue / barH)) + if N > 10: + break + elif inc == 1: + power -= 1 + + histo = [set() for i in range(N)] + for name,v in items: + bin = min(int(N * v/maxValue), N-1) + histo[bin].add(name) + + barW = 40 + hr = '-' * (barW + 34) + print '\nSlowest %s:' % title + print hr + for name,value in items[-20:]: + print '%.2fs: %s' % (value, name) + print '\n%s Times:' % title + print hr + pDigits = int(math.ceil(math.log(maxValue, 10))) + pfDigits = max(0, 3-pDigits) + if pfDigits: + pDigits += pfDigits + 1 + cDigits = int(math.ceil(math.log(len(items), 10))) + print "[%s] :: [%s] :: [%s]" % ('Range'.center((pDigits+1)*2 + 3), + 'Percentage'.center(barW), + 'Count'.center(cDigits*2 + 1)) + print hr + for i,row in enumerate(histo): + pct = float(len(row)) / len(items) + w = int(barW * pct) + print "[%*.*fs,%*.*fs)" % (pDigits, pfDigits, i*barH, + pDigits, pfDigits, (i+1)*barH), + print ":: [%s%s] :: [%*d/%*d]" % ('*'*w, ' '*(barW-w), + cDigits, len(row), + cDigits, len(items)) + diff --git a/utils/lit/lit/__init__.py b/utils/lit/lit/__init__.py new file mode 100644 index 0000000..0102602 --- /dev/null +++ b/utils/lit/lit/__init__.py @@ -0,0 +1,10 @@ +"""'lit' Testing Tool""" + +from lit import main + +__author__ = 'Daniel Dunbar' +__email__ = 'daniel@zuster.org' +__versioninfo__ = (0, 1, 0) +__version__ = '.'.join(map(str, __versioninfo__)) + +__all__ = [] diff --git a/utils/lit/lit/lit.py b/utils/lit/lit/lit.py new file mode 100755 index 0000000..f1f19c4 --- /dev/null +++ b/utils/lit/lit/lit.py @@ -0,0 +1,579 @@ +#!/usr/bin/env python + +""" +lit - LLVM Integrated Tester. + +See lit.pod for more information. +""" + +import math, os, platform, random, re, sys, time, threading, traceback + +import ProgressBar +import TestRunner +import Util + +from TestingConfig import TestingConfig +import LitConfig +import Test + +# Configuration files to look for when discovering test suites. These can be +# overridden with --config-prefix. +# +# FIXME: Rename to 'config.lit', 'site.lit', and 'local.lit' ? +gConfigName = 'lit.cfg' +gSiteConfigName = 'lit.site.cfg' + +kLocalConfigName = 'lit.local.cfg' + +class TestingProgressDisplay: + def __init__(self, opts, numTests, progressBar=None): + self.opts = opts + self.numTests = numTests + self.current = None + self.lock = threading.Lock() + self.progressBar = progressBar + self.completed = 0 + + def update(self, test): + # Avoid locking overhead in quiet mode + if self.opts.quiet and not test.result.isFailure: + self.completed += 1 + return + + # Output lock. + self.lock.acquire() + try: + self.handleUpdate(test) + finally: + self.lock.release() + + def finish(self): + if self.progressBar: + self.progressBar.clear() + elif self.opts.quiet: + pass + elif self.opts.succinct: + sys.stdout.write('\n') + + def handleUpdate(self, test): + self.completed += 1 + if self.progressBar: + self.progressBar.update(float(self.completed)/self.numTests, + test.getFullName()) + + if self.opts.succinct and not test.result.isFailure: + return + + if self.progressBar: + self.progressBar.clear() + + print '%s: %s (%d of %d)' % (test.result.name, test.getFullName(), + self.completed, self.numTests) + + if test.result.isFailure and self.opts.showOutput: + print "%s TEST '%s' FAILED %s" % ('*'*20, test.getFullName(), + '*'*20) + print test.output + print "*" * 20 + + sys.stdout.flush() + +class TestProvider: + def __init__(self, tests, maxTime): + self.maxTime = maxTime + self.iter = iter(tests) + self.lock = threading.Lock() + self.startTime = time.time() + + def get(self): + # Check if we have run out of time. + if self.maxTime is not None: + if time.time() - self.startTime > self.maxTime: + return None + + # Otherwise take the next test. + self.lock.acquire() + try: + item = self.iter.next() + except StopIteration: + item = None + self.lock.release() + return item + +class Tester(threading.Thread): + def __init__(self, litConfig, provider, display): + threading.Thread.__init__(self) + self.litConfig = litConfig + self.provider = provider + self.display = display + + def run(self): + while 1: + item = self.provider.get() + if item is None: + break + self.runTest(item) + + def runTest(self, test): + result = None + startTime = time.time() + try: + result, output = test.config.test_format.execute(test, + self.litConfig) + except KeyboardInterrupt: + # This is a sad hack. Unfortunately subprocess goes + # bonkers with ctrl-c and we start forking merrily. + print '\nCtrl-C detected, goodbye.' + os.kill(0,9) + except: + if self.litConfig.debug: + raise + result = Test.UNRESOLVED + output = 'Exception during script execution:\n' + output += traceback.format_exc() + output += '\n' + elapsed = time.time() - startTime + + test.setResult(result, output, elapsed) + self.display.update(test) + +def dirContainsTestSuite(path): + cfgpath = os.path.join(path, gSiteConfigName) + if os.path.exists(cfgpath): + return cfgpath + cfgpath = os.path.join(path, gConfigName) + if os.path.exists(cfgpath): + return cfgpath + +def getTestSuite(item, litConfig, cache): + """getTestSuite(item, litConfig, cache) -> (suite, relative_path) + + Find the test suite containing @arg item. + + @retval (None, ...) - Indicates no test suite contains @arg item. + @retval (suite, relative_path) - The suite that @arg item is in, and its + relative path inside that suite. + """ + def search1(path): + # Check for a site config or a lit config. + cfgpath = dirContainsTestSuite(path) + + # If we didn't find a config file, keep looking. + if not cfgpath: + parent,base = os.path.split(path) + if parent == path: + return (None, ()) + + ts, relative = search(parent) + return (ts, relative + (base,)) + + # We found a config file, load it. + if litConfig.debug: + litConfig.note('loading suite config %r' % cfgpath) + + cfg = TestingConfig.frompath(cfgpath, None, litConfig, mustExist = True) + source_root = os.path.realpath(cfg.test_source_root or path) + exec_root = os.path.realpath(cfg.test_exec_root or path) + return Test.TestSuite(cfg.name, source_root, exec_root, cfg), () + + def search(path): + # Check for an already instantiated test suite. + res = cache.get(path) + if res is None: + cache[path] = res = search1(path) + return res + + # Canonicalize the path. + item = os.path.realpath(item) + + # Skip files and virtual components. + components = [] + while not os.path.isdir(item): + parent,base = os.path.split(item) + if parent == item: + return (None, ()) + components.append(base) + item = parent + components.reverse() + + ts, relative = search(item) + return ts, tuple(relative + tuple(components)) + +def getLocalConfig(ts, path_in_suite, litConfig, cache): + def search1(path_in_suite): + # Get the parent config. + if not path_in_suite: + parent = ts.config + else: + parent = search(path_in_suite[:-1]) + + # Load the local configuration. + source_path = ts.getSourcePath(path_in_suite) + cfgpath = os.path.join(source_path, kLocalConfigName) + if litConfig.debug: + litConfig.note('loading local config %r' % cfgpath) + return TestingConfig.frompath(cfgpath, parent, litConfig, + mustExist = False, + config = parent.clone(cfgpath)) + + def search(path_in_suite): + key = (ts, path_in_suite) + res = cache.get(key) + if res is None: + cache[key] = res = search1(path_in_suite) + return res + + return search(path_in_suite) + +def getTests(path, litConfig, testSuiteCache, localConfigCache): + # Find the test suite for this input and its relative path. + ts,path_in_suite = getTestSuite(path, litConfig, testSuiteCache) + if ts is None: + litConfig.warning('unable to find test suite for %r' % path) + return (),() + + if litConfig.debug: + litConfig.note('resolved input %r to %r::%r' % (path, ts.name, + path_in_suite)) + + return ts, getTestsInSuite(ts, path_in_suite, litConfig, + testSuiteCache, localConfigCache) + +def getTestsInSuite(ts, path_in_suite, litConfig, + testSuiteCache, localConfigCache): + # Check that the source path exists (errors here are reported by the + # caller). + source_path = ts.getSourcePath(path_in_suite) + if not os.path.exists(source_path): + return + + # Check if the user named a test directly. + if not os.path.isdir(source_path): + lc = getLocalConfig(ts, path_in_suite[:-1], litConfig, localConfigCache) + yield Test.Test(ts, path_in_suite, lc) + return + + # Otherwise we have a directory to search for tests, start by getting the + # local configuration. + lc = getLocalConfig(ts, path_in_suite, litConfig, localConfigCache) + + # Search for tests. + for res in lc.test_format.getTestsInDirectory(ts, path_in_suite, + litConfig, lc): + yield res + + # Search subdirectories. + for filename in os.listdir(source_path): + # FIXME: This doesn't belong here? + if filename in ('Output', '.svn') or filename in lc.excludes: + continue + + # Ignore non-directories. + file_sourcepath = os.path.join(source_path, filename) + if not os.path.isdir(file_sourcepath): + continue + + # Check for nested test suites, first in the execpath in case there is a + # site configuration and then in the source path. + file_execpath = ts.getExecPath(path_in_suite + (filename,)) + if dirContainsTestSuite(file_execpath): + sub_ts, subiter = getTests(file_execpath, litConfig, + testSuiteCache, localConfigCache) + elif dirContainsTestSuite(file_sourcepath): + sub_ts, subiter = getTests(file_sourcepath, litConfig, + testSuiteCache, localConfigCache) + else: + # Otherwise, continue loading from inside this test suite. + subiter = getTestsInSuite(ts, path_in_suite + (filename,), + litConfig, testSuiteCache, + localConfigCache) + sub_ts = None + + N = 0 + for res in subiter: + N += 1 + yield res + if sub_ts and not N: + litConfig.warning('test suite %r contained no tests' % sub_ts.name) + +def runTests(numThreads, litConfig, provider, display): + # If only using one testing thread, don't use threads at all; this lets us + # profile, among other things. + if numThreads == 1: + t = Tester(litConfig, provider, display) + t.run() + return + + # Otherwise spin up the testing threads and wait for them to finish. + testers = [Tester(litConfig, provider, display) + for i in range(numThreads)] + for t in testers: + t.start() + try: + for t in testers: + t.join() + except KeyboardInterrupt: + sys.exit(2) + +def main(): + # Bump the GIL check interval, its more important to get any one thread to a + # blocking operation (hopefully exec) than to try and unblock other threads. + # + # FIXME: This is a hack. + import sys + sys.setcheckinterval(1000) + + global options + from optparse import OptionParser, OptionGroup + parser = OptionParser("usage: %prog [options] {file-or-path}") + + parser.add_option("-j", "--threads", dest="numThreads", metavar="N", + help="Number of testing threads", + type=int, action="store", default=None) + parser.add_option("", "--config-prefix", dest="configPrefix", + metavar="NAME", help="Prefix for 'lit' config files", + action="store", default=None) + parser.add_option("", "--param", dest="userParameters", + metavar="NAME=VAL", + help="Add 'NAME' = 'VAL' to the user defined parameters", + type=str, action="append", default=[]) + + group = OptionGroup(parser, "Output Format") + # FIXME: I find these names very confusing, although I like the + # functionality. + group.add_option("-q", "--quiet", dest="quiet", + help="Suppress no error output", + action="store_true", default=False) + group.add_option("-s", "--succinct", dest="succinct", + help="Reduce amount of output", + action="store_true", default=False) + group.add_option("-v", "--verbose", dest="showOutput", + help="Show all test output", + action="store_true", default=False) + group.add_option("", "--no-progress-bar", dest="useProgressBar", + help="Do not use curses based progress bar", + action="store_false", default=True) + parser.add_option_group(group) + + group = OptionGroup(parser, "Test Execution") + group.add_option("", "--path", dest="path", + help="Additional paths to add to testing environment", + action="append", type=str, default=[]) + group.add_option("", "--vg", dest="useValgrind", + help="Run tests under valgrind", + action="store_true", default=False) + group.add_option("", "--vg-arg", dest="valgrindArgs", metavar="ARG", + help="Specify an extra argument for valgrind", + type=str, action="append", default=[]) + group.add_option("", "--time-tests", dest="timeTests", + help="Track elapsed wall time for each test", + action="store_true", default=False) + group.add_option("", "--no-execute", dest="noExecute", + help="Don't execute any tests (assume PASS)", + action="store_true", default=False) + parser.add_option_group(group) + + group = OptionGroup(parser, "Test Selection") + group.add_option("", "--max-tests", dest="maxTests", metavar="N", + help="Maximum number of tests to run", + action="store", type=int, default=None) + group.add_option("", "--max-time", dest="maxTime", metavar="N", + help="Maximum time to spend testing (in seconds)", + action="store", type=float, default=None) + group.add_option("", "--shuffle", dest="shuffle", + help="Run tests in random order", + action="store_true", default=False) + parser.add_option_group(group) + + group = OptionGroup(parser, "Debug and Experimental Options") + group.add_option("", "--debug", dest="debug", + help="Enable debugging (for 'lit' development)", + action="store_true", default=False) + group.add_option("", "--show-suites", dest="showSuites", + help="Show discovered test suites", + action="store_true", default=False) + group.add_option("", "--no-tcl-as-sh", dest="useTclAsSh", + help="Don't run Tcl scripts using 'sh'", + action="store_false", default=True) + group.add_option("", "--repeat", dest="repeatTests", metavar="N", + help="Repeat tests N times (for timing)", + action="store", default=None, type=int) + parser.add_option_group(group) + + (opts, args) = parser.parse_args() + + if not args: + parser.error('No inputs specified') + + if opts.configPrefix is not None: + global gConfigName, gSiteConfigName + gConfigName = '%s.cfg' % opts.configPrefix + gSiteConfigName = '%s.site.cfg' % opts.configPrefix + + if opts.numThreads is None: + opts.numThreads = Util.detectCPUs() + + inputs = args + + # Create the user defined parameters. + userParams = {} + for entry in opts.userParameters: + if '=' not in entry: + name,val = entry,'' + else: + name,val = entry.split('=', 1) + userParams[name] = val + + # Create the global config object. + litConfig = LitConfig.LitConfig(progname = os.path.basename(sys.argv[0]), + path = opts.path, + quiet = opts.quiet, + useValgrind = opts.useValgrind, + valgrindArgs = opts.valgrindArgs, + useTclAsSh = opts.useTclAsSh, + noExecute = opts.noExecute, + debug = opts.debug, + isWindows = (platform.system()=='Windows'), + params = userParams) + + # Load the tests from the inputs. + tests = [] + testSuiteCache = {} + localConfigCache = {} + for input in inputs: + prev = len(tests) + tests.extend(getTests(input, litConfig, + testSuiteCache, localConfigCache)[1]) + if prev == len(tests): + litConfig.warning('input %r contained no tests' % input) + + # If there were any errors during test discovery, exit now. + if litConfig.numErrors: + print >>sys.stderr, '%d errors, exiting.' % litConfig.numErrors + sys.exit(2) + + if opts.showSuites: + suitesAndTests = dict([(ts,[]) + for ts,_ in testSuiteCache.values() + if ts]) + for t in tests: + suitesAndTests[t.suite].append(t) + + print '-- Test Suites --' + suitesAndTests = suitesAndTests.items() + suitesAndTests.sort(key = lambda (ts,_): ts.name) + for ts,ts_tests in suitesAndTests: + print ' %s - %d tests' %(ts.name, len(ts_tests)) + print ' Source Root: %s' % ts.source_root + print ' Exec Root : %s' % ts.exec_root + + # Select and order the tests. + numTotalTests = len(tests) + if opts.shuffle: + random.shuffle(tests) + else: + tests.sort(key = lambda t: t.getFullName()) + if opts.maxTests is not None: + tests = tests[:opts.maxTests] + + extra = '' + if len(tests) != numTotalTests: + extra = ' of %d' % numTotalTests + header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra, + opts.numThreads) + + if opts.repeatTests: + tests = [t.copyWithIndex(i) + for t in tests + for i in range(opts.repeatTests)] + + progressBar = None + if not opts.quiet: + if opts.succinct and opts.useProgressBar: + try: + tc = ProgressBar.TerminalController() + progressBar = ProgressBar.ProgressBar(tc, header) + except ValueError: + print header + progressBar = ProgressBar.SimpleProgressBar('Testing: ') + else: + print header + + # Don't create more threads than tests. + opts.numThreads = min(len(tests), opts.numThreads) + + startTime = time.time() + display = TestingProgressDisplay(opts, len(tests), progressBar) + provider = TestProvider(tests, opts.maxTime) + runTests(opts.numThreads, litConfig, provider, display) + display.finish() + + if not opts.quiet: + print 'Testing Time: %.2fs'%(time.time() - startTime) + + # Update results for any tests which weren't run. + for t in tests: + if t.result is None: + t.setResult(Test.UNRESOLVED, '', 0.0) + + # List test results organized by kind. + hasFailures = False + byCode = {} + for t in tests: + if t.result not in byCode: + byCode[t.result] = [] + byCode[t.result].append(t) + if t.result.isFailure: + hasFailures = True + + # FIXME: Show unresolved and (optionally) unsupported tests. + for title,code in (('Unexpected Passing Tests', Test.XPASS), + ('Failing Tests', Test.FAIL)): + elts = byCode.get(code) + if not elts: + continue + print '*'*20 + print '%s (%d):' % (title, len(elts)) + for t in elts: + print ' %s' % t.getFullName() + print + + if opts.timeTests: + # Collate, in case we repeated tests. + times = {} + for t in tests: + key = t.getFullName() + times[key] = times.get(key, 0.) + t.elapsed + + byTime = list(times.items()) + byTime.sort(key = lambda (name,elapsed): elapsed) + if byTime: + Util.printHistogram(byTime, title='Tests') + + for name,code in (('Expected Passes ', Test.PASS), + ('Expected Failures ', Test.XFAIL), + ('Unsupported Tests ', Test.UNSUPPORTED), + ('Unresolved Tests ', Test.UNRESOLVED), + ('Unexpected Passes ', Test.XPASS), + ('Unexpected Failures', Test.FAIL),): + if opts.quiet and not code.isFailure: + continue + N = len(byCode.get(code,[])) + if N: + print ' %s: %d' % (name,N) + + # If we encountered any additional errors, exit abnormally. + if litConfig.numErrors: + print >>sys.stderr, '\n%d error(s), exiting.' % litConfig.numErrors + sys.exit(2) + + # Warn about warnings. + if litConfig.numWarnings: + print >>sys.stderr, '\n%d warning(s) in tests.' % litConfig.numWarnings + + if hasFailures: + sys.exit(1) + sys.exit(0) + +if __name__=='__main__': + main() diff --git a/utils/lit/setup.py b/utils/lit/setup.py new file mode 100644 index 0000000..e6ae3d8 --- /dev/null +++ b/utils/lit/setup.py @@ -0,0 +1,69 @@ +import lit + +# FIXME: Support distutils? +from setuptools import setup, find_packages +setup( + name = "Lit", + version = lit.__version__, + + author = lit.__author__, + author_email = lit.__email__, + url = 'http://llvm.org', + license = 'BSD', + + description = "A Software Testing Tool", + keywords = 'test C++ automatic discovery', + long_description = """\ +Lit ++++ + +About +===== + +Lit is a portable tool for executing LLVM and Clang style test suites, +summarizing their results, and providing indication of failures. Lit is designed +to be a lightweight testing tool with as simple a user interface as possible. + + +Features +======== + + * Portable! + * Flexible test discovery. + * Parallel test execution. + * Support for multiple test formats and test suite designs. + + +Documentation +============= + +The offical Lit documentation is in the man page, available online in the `LLVM +Command Guide http://llvm.org/cmds/lit.html`_. + + +Source +====== + +The Lit source is available as part of LLVM, in the `LLVM SVN repository +<http://llvm.org/svn/llvm-project/llvm/trunk/utils/lit`_. +""", + + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: University of Illinois/NCSA Open Source License', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Progamming Language :: Python', + 'Topic :: Software Development :: Testing', + ], + + zip_safe = False, + packages = find_packages(), + entry_points = { + 'console_scripts': [ + 'lit = lit:main', + ], + } +) diff --git a/utils/llvmdo b/utils/llvmdo index 26f2183..4a7e05a 100755 --- a/utils/llvmdo +++ b/utils/llvmdo @@ -112,7 +112,6 @@ files_to_match="\ -o -name *.intro \ -o -name *.l \ -o -name *.ll \ - -o -name *.llx \ -o -name *.lst \ -o -name *.m4 \ -o -name *.pod \ diff --git a/utils/unittest/googletest/gtest.cc b/utils/unittest/googletest/gtest.cc index e46e90a..b5a654f 100644 --- a/utils/unittest/googletest/gtest.cc +++ b/utils/unittest/googletest/gtest.cc @@ -532,7 +532,7 @@ TypeId GetTestTypeId() { // The value of GetTestTypeId() as seen from within the Google Test // library. This is solely for testing GetTestTypeId(). -extern const TypeId kTestTypeIdInGoogleTest = GetTestTypeId(); +const TypeId kTestTypeIdInGoogleTest = GetTestTypeId(); // This predicate-formatter checks that 'results' contains a test part // failure of the given type and that the failure message contains the diff --git a/utils/unittest/googletest/include/gtest/gtest-param-test.h b/utils/unittest/googletest/include/gtest/gtest-param-test.h index 2d63237..0cf05dc 100644 --- a/utils/unittest/googletest/include/gtest/gtest-param-test.h +++ b/utils/unittest/googletest/include/gtest/gtest-param-test.h @@ -155,7 +155,6 @@ INSTANTIATE_TEST_CASE_P(AnotherInstantiationName, FooTest, ValuesIn(pets)); #include <gtest/internal/gtest-internal.h> #include <gtest/internal/gtest-param-util.h> -#include <gtest/internal/gtest-param-util-generated.h> namespace testing { @@ -289,6 +288,12 @@ internal::ParamGenerator<typename Container::value_type> ValuesIn( return ValuesIn(container.begin(), container.end()); } +} // namespace testing + +#include <gtest/internal/gtest-param-util-generated.h> + +namespace testing { + // Values() allows generating tests from explicitly specified list of // parameters. // diff --git a/utils/unittest/googletest/tempfile.tmp b/utils/unittest/googletest/tempfile.tmp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/utils/unittest/googletest/tempfile.tmp |