diff --git a/llvm/docs/CommandGuide/FileCheck.rst b/llvm/docs/CommandGuide/FileCheck.rst --- a/llvm/docs/CommandGuide/FileCheck.rst +++ b/llvm/docs/CommandGuide/FileCheck.rst @@ -580,8 +580,8 @@ * ``%`` is an optional scanf-style matching format specifier to indicate what number format to match (e.g. hex number). Currently accepted - format specifier are ``%u``, ``%x`` and ``%X``. If absent, the format - specifier defaults to ``%u``. + format specifier are ``%u``, ``%d``, ``%x`` and ``%X``. If absent, the + format specifier defaults to ``%u``. * ``NUMVAR`` is the name of the numeric variable to define to the matching value. diff --git a/llvm/include/llvm/Support/FileCheck.h b/llvm/include/llvm/Support/FileCheck.h --- a/llvm/include/llvm/Support/FileCheck.h +++ b/llvm/include/llvm/Support/FileCheck.h @@ -40,10 +40,14 @@ // Numeric expression handling code. //===----------------------------------------------------------------------===// +class FileCheckNumExprVal; + /// Bitfield representing the format a numeric expression value should be /// printed into for matching. Used to represent both explicit format /// specifiers as well as implicit format from using numeric variables. struct FileCheckNumExprFmt { + /// Value is a signed integer. + unsigned Signed : 1; /// Value should be printed as hex number. unsigned Hex : 1; /// Value should be printed using upper case letters, only used for hex @@ -70,22 +74,98 @@ /// Return the string representation of \p Value in the format represented by /// this instance. - std::string getMatchingString(uint64_t Value) const; + llvm::Optional + getMatchingString(FileCheckNumExprVal &Value) const; /// Return the value corresponding to string representation \p StrVal - /// according to the matching format represented by this instance or nothing - /// if \p StrVal does not correspond to a valid and representable value. - llvm::Optional valueFromStringRepr(StringRef StrVal) const; + /// according to the matching format represented by this instance or an + /// invalid value if \p StrVal does not correspond to a valid and + /// representable value. + FileCheckNumExprVal valueFromStringRepr(StringRef StrVal) const; }; /// Initializer for numeric expression without format. -const FileCheckNumExprFmt FmtNone = {0, 0, 0, 0}; +const FileCheckNumExprFmt FmtNone = {0, 0, 0, 0, 0}; /// Initializer for numeric expression matched as unsigned value. -const FileCheckNumExprFmt FmtUnsigned = {0, 0, 1, 0}; +const FileCheckNumExprFmt FmtUnsigned = {0, 0, 0, 1, 0}; +/// Initializer for numeric expression matched as signed value. +const FileCheckNumExprFmt FmtSigned = {1, 0, 0, 1, 0}; /// Initializer for numeric expression matched as lower case hex value. -const FileCheckNumExprFmt FmtLowHex = {1, 0, 1, 0}; +const FileCheckNumExprFmt FmtLowHex = {0, 1, 0, 1, 0}; /// Initializer for numeric expression matched as capital case hex value. -const FileCheckNumExprFmt FmtCapHex = {1, 1, 1, 0}; +const FileCheckNumExprFmt FmtCapHex = {0, 1, 1, 1, 0}; + +/// Class representing a numeric value. +class FileCheckNumExprVal { +private: + union { + int64_t SignedValue; + uint64_t UnsignedValue; + }; + + /// Whether value is signed (and thus is stored in SignedValue) or not (in + /// which case it is stored in UnsignedValue). + bool Signed; + + /// Whether this holds an actual value. Examples of where this would be + /// false: + /// - underflow or overflow of one of the binary operation in the expression; + /// - unset variable's value. + bool Valid; + +public: + /// Constructor for an invalid value. + FileCheckNumExprVal() : Valid(false) {} + + /// Constructor for a signed value. + explicit FileCheckNumExprVal(int64_t Val) + : SignedValue(Val), Signed(true), Valid(true) {} + + /// Constructor for an unsigned value. + explicit FileCheckNumExprVal(uint64_t Val) + : UnsignedValue(Val), Signed(false), Valid(true) {} + + /// Define equality to be true only if both values are valid and they have + /// the same signedness and corresponding value. Tentative bit is ignored to + /// allow the evaluation of a numeric expression using variables with + /// tentative value to compare equal to a matched value. + bool operator==(const FileCheckNumExprVal &other); + bool operator!=(const FileCheckNumExprVal &other) { + return !(*this == other); + } + + bool isSigned() const { return Signed; } + + bool isValid() const { return Valid; } + + /// Return the signed value. Must only be called if value is signed in the + /// first place. + int64_t getSignedValue() const { + assert(Signed); + return SignedValue; + } + + /// Return the unsigned value. Must only be called if value is unsigned in + /// the first place. + uint64_t getUnsignedValue() const { + assert(!Signed); + return UnsignedValue; + } + + /// Convert value to a signed value, or mark value invalid if not possible + /// (original value was not within range for a signed integer). + void convertSigned(); + + /// Convert value to an unsigned value, or mark value invalid if not possible + /// (original value was not within range for an unsigned integer). + void convertUnsigned(); + + /// Return an invalid value in case of underflow or overflow. + friend FileCheckNumExprVal operator+(const FileCheckNumExprVal &lhs, + const FileCheckNumExprVal &rhs); + friend FileCheckNumExprVal operator-(const FileCheckNumExprVal &lhs, + const FileCheckNumExprVal &rhs); +}; /// Base class representing the AST of a given numeric expression. class FileCheckNumExprAST { @@ -94,7 +174,7 @@ /// Evaluates and \returns the value of the expression represented by this /// AST. - virtual llvm::Optional eval() const = 0; + virtual FileCheckNumExprVal eval() const = 0; /// \returns implicit format of this AST, FmtConflict if implicit formats of /// the AST's components conflict and Fmt none if the AST has no implicit @@ -112,15 +192,18 @@ class FileCheckNumExprLiteral : public FileCheckNumExprAST { private: /// Actual value of the literal. - uint64_t Value; + FileCheckNumExprVal Value; public: + /// Constructor for a signed literal. + FileCheckNumExprLiteral(int64_t Val) : Value(Val) {} + /// Constructor for an unsigned literal. FileCheckNumExprLiteral(uint64_t Val) : Value(Val) {} /// Evaluates and returns the value of the expression represented by this /// AST. Therefore, \returns the literal's value. - llvm::Optional eval() const { return Value; } + FileCheckNumExprVal eval() const { return Value; } /// Return implicit format of this AST, therefore FmtNone. FileCheckNumExprFmt getImplicitFmt() const { return FmtNone; } @@ -169,8 +252,8 @@ /// NumExpr points to a FileCheckNumExpr with a null AST. FileCheckNumExpr *NumExpr; - /// Value of numeric variable, if defined, or None otherwise. - llvm::Optional Value; + /// Value of numeric variable. + FileCheckNumExprVal Value; /// Line number where this variable is defined. Used to determine whether a /// variable is defined on the same line as a given use. @@ -181,12 +264,13 @@ /// the numeric expression represented by NumExpr. FileCheckNumericVariable(StringRef Name, FileCheckNumExpr *NumExpr, unsigned DefLineNumber) - : Name(Name), NumExpr(NumExpr), Value(llvm::None), - DefLineNumber(DefLineNumber) {} + : Name(Name), NumExpr(NumExpr), DefLineNumber(DefLineNumber) { + Value = FileCheckNumExprVal(); + } /// Constructor for numeric variable \p Name with a known \p Value at parse /// time (e.g. the @LINE numeric variable). - explicit FileCheckNumericVariable(StringRef Name, uint64_t Value) + FileCheckNumericVariable(StringRef Name, FileCheckNumExprVal &Value) : Name(Name), NumExpr(nullptr), Value(Value) {} /// \returns name of that numeric variable. @@ -198,7 +282,7 @@ /// Evaluates and returns the value of the expression represented by this /// AST. Therefore, \returns this variable's value or the value of its /// associated numeric expression, if any. - llvm::Optional eval() const; + FileCheckNumExprVal eval() const; /// \returns whether this variable's value is known at match time, when /// performing the substitutions. @@ -212,7 +296,7 @@ /// Sets value of this numeric variable if not defined. \returns whether the /// variable was already defined. - bool setValue(uint64_t Value); + bool setValue(FileCheckNumExprVal Value); /// Clears value of this numeric variable. \returns whether the variable was /// already undefined. @@ -223,7 +307,8 @@ }; /// Type of functions evaluating a given binary operation. -using binop_eval_t = uint64_t (*)(uint64_t, uint64_t); +using binop_eval_t = FileCheckNumExprVal (*)(const FileCheckNumExprVal &, + const FileCheckNumExprVal &); /// Class representing a single binary operation in the AST of a numeric /// expression. @@ -247,7 +332,7 @@ /// Evaluates and \returns the value of the binary operation represented by /// this AST. Uses EvalBinop to perform the binary operation on the values of /// recursively evaluating the left and right operands. - llvm::Optional eval() const; + FileCheckNumExprVal eval() const; /// \returns implicit format of this AST, FmtConflict if implicit formats of /// the AST's components conflict and Fmt none if the AST has no implicit diff --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -25,7 +25,8 @@ using namespace llvm; bool FileCheckNumExprFmt::operator==(const FileCheckNumExprFmt &other) { - return Valid == other.Valid && Hex == other.Hex && Cap == other.Cap; + return Valid == other.Valid && Conflict == other.Conflict && + Signed == other.Signed && Hex == other.Hex && Cap == other.Cap; } StringRef FileCheckNumExprFmt::getWildcardRegex() const { @@ -35,27 +36,162 @@ return StringRef("[[:digit:]A-F]+"); else return StringRef("[[:digit:]a-f]+"); - } else + } else if (Signed) + return StringRef("-?[[:digit:]]+"); + else return StringRef("[[:digit:]]+"); } -std::string FileCheckNumExprFmt::getMatchingString(uint64_t Value) const { - assert(Valid && !Conflict && "Trying to match value with invalid format"); +llvm::Optional +FileCheckNumExprFmt::getMatchingString(FileCheckNumExprVal &Value) const { + if (Signed) + Value.convertSigned(); + else + Value.convertUnsigned(); + + assert(!Conflict); + // Conversion error (e.g. negative value converted to unsigned). + if (!Valid) + return llvm::None; + if (Hex) - return utohexstr(Value, !Cap); + return utohexstr(Value.getUnsignedValue(), !Cap); + else if (Signed) + return itostr(Value.getSignedValue()); else - return utostr(Value); + return utostr(Value.getUnsignedValue()); } -llvm::Optional +FileCheckNumExprVal FileCheckNumExprFmt::valueFromStringRepr(StringRef StrVal) const { unsigned Radix = Hex ? 16 : 10; - uint64_t Value; + if (Signed) { + int64_t SignedValue; - if (StrVal.getAsInteger(Radix, Value)) - return llvm::None; + if (StrVal.getAsInteger(Radix, SignedValue)) + return FileCheckNumExprVal(); + + return FileCheckNumExprVal(SignedValue); + } else { + uint64_t UnsignedValue; - return Value; + if (StrVal.getAsInteger(Radix, UnsignedValue)) + return FileCheckNumExprVal(); + + return FileCheckNumExprVal(UnsignedValue); + } +} + +bool FileCheckNumExprVal::operator==(const FileCheckNumExprVal &other) { + if (!Valid || !other.Valid) + return false; + + if (Signed) + return SignedValue == other.SignedValue; + else + return UnsignedValue == other.UnsignedValue; +} + +void FileCheckNumExprVal::convertSigned() { + if (!Valid || Signed) + return; + + if (UnsignedValue > std::numeric_limits::max()) { + Valid = false; + return; + } + + SignedValue = UnsignedValue; + Signed = true; +} + +void FileCheckNumExprVal::convertUnsigned() { + if (!Valid || !Signed) + return; + + if (SignedValue < 0) { + Valid = false; + return; + } + + UnsignedValue = SignedValue; + Signed = false; + return; +} + +llvm::FileCheckNumExprVal llvm:: +operator+(const llvm::FileCheckNumExprVal &Op1, + const llvm::FileCheckNumExprVal &Op2) { + // Operands must be valid. + if (!Op1.Valid || !Op2.Valid) + return FileCheckNumExprVal(); + + // Operands must have same sign. + if (Op1.Signed != Op2.Signed) + return FileCheckNumExprVal(); + + if (Op1.Signed) { + int64_t Val1 = Op1.SignedValue; + int64_t Val2 = Op2.SignedValue; + + // Op1 + Op2 > max int64_t. + if (Val1 > 0 && Val2 > 0 && + Val1 > (std::numeric_limits::max() - Val2)) + return FileCheckNumExprVal(); + + // Op1 + Op2 < min int64_t. + if (Val1 < 0 && Val2 < 0 && + Val1 < (std::numeric_limits::min() - Val2)) + return FileCheckNumExprVal(); + + return FileCheckNumExprVal(Val1 + Val2); + } else { + uint64_t Val1 = Op1.UnsignedValue; + uint64_t Val2 = Op2.UnsignedValue; + + // Op1 + Op2 > max uint64_t. + if (Val1 > std::numeric_limits::max() - Val2) + return FileCheckNumExprVal(); + + return FileCheckNumExprVal(Val1 + Val2); + } +} + +llvm::FileCheckNumExprVal llvm:: +operator-(const llvm::FileCheckNumExprVal &Op1, + const llvm::FileCheckNumExprVal &Op2) { + // Operands must be valid. + if (!Op1.Valid || !Op2.Valid) + return FileCheckNumExprVal(); + + // Operands must have same sign. + if (Op1.Signed != Op2.Signed) + return FileCheckNumExprVal(); + + if (Op1.Signed) { + int64_t Val1 = Op1.SignedValue; + int64_t Val2 = Op2.SignedValue; + + // Op1 - Op2 > max int64_t. + if (Val1 > 0 && Val2 < 0 && + Val1 > (std::numeric_limits::max() + Val2)) + return FileCheckNumExprVal(); + + // Op1 - Op2 < min int64_t. + if (Val1 < 0 && Val2 > 0 && + Val1 < (std::numeric_limits::min() + Val2)) + return FileCheckNumExprVal(); + + return FileCheckNumExprVal(Val1 - Val2); + } else { + uint64_t Val1 = Op1.UnsignedValue; + uint64_t Val2 = Op2.UnsignedValue; + + // Op1 < Op2. + if (Val1 < Val2) + return FileCheckNumExprVal(); + return FileCheckNumExprVal(Val1 - Val2); + } } FileCheckNumExpr::FileCheckNumExpr(std::shared_ptr AST, @@ -67,18 +203,18 @@ this->Fmt = FmtUnsigned; } -llvm::Optional FileCheckNumericVariable::eval() const { - if (Value) +FileCheckNumExprVal FileCheckNumericVariable::eval() const { + if (Value.isValid()) return Value; if (NumExpr == nullptr || NumExpr->getAST() == nullptr) - return llvm::None; + return FileCheckNumExprVal(); return NumExpr->getAST()->eval(); } bool FileCheckNumericVariable::isMatchTimeKnown() const { - if (Value) + if (Value.isValid()) return true; return NumExpr != nullptr && NumExpr->getAST() != nullptr; @@ -93,34 +229,40 @@ void FileCheckNumericVariable::appendUndefVarNames( std::vector &UndefVarNames) const { - if (!Value) + if (!Value.isValid()) UndefVarNames.emplace_back(Name); } -bool FileCheckNumericVariable::setValue(uint64_t NewValue) { - if (Value) +bool FileCheckNumericVariable::setValue(FileCheckNumExprVal NewValue) { + if (Value.isValid()) return true; + assert(NewValue.isValid() && "Setting invalid value"); Value = NewValue; return false; } bool FileCheckNumericVariable::clearValue() { - if (!Value) + if (!Value.isValid()) return true; - Value = llvm::None; + Value = FileCheckNumExprVal(); NumExpr = nullptr; return false; } -llvm::Optional FileCheckASTBinop::eval() const { - llvm::Optional LeftOp = this->LeftOp->eval(); - llvm::Optional RightOp = this->RightOp->eval(); +FileCheckNumExprVal FileCheckASTBinop::eval() const { + FileCheckNumExprVal LeftValue = LeftOp->eval(); + FileCheckNumExprVal RightValue = RightOp->eval(); - // Uses undefined variable. - if (!LeftOp || !RightOp) - return llvm::None; + // Integer promotion. + if (!LeftValue.isSigned() || !RightValue.isSigned()) { + LeftValue.convertUnsigned(); + RightValue.convertUnsigned(); + } + + if (!LeftValue.isValid() || !RightValue.isValid()) + return FileCheckNumExprVal(); - return EvalBinop(*LeftOp, *RightOp); + return EvalBinop(LeftValue, RightValue); } FileCheckNumExprFmt FileCheckASTBinop::getImplicitFmt() const { @@ -134,8 +276,6 @@ return Fmt; } -/// Append names of undefined variables used in any of the operands of this -/// binary operation. void FileCheckASTBinop::appendUndefVarNames( std::vector &UndefVarNames) const { LeftOp->appendUndefVarNames(UndefVarNames); @@ -146,11 +286,12 @@ if (IsNumExpr) { assert(NumExpr->getAST() != nullptr && "Substituting empty numeric expression"); - llvm::Optional EvaluatedValue = NumExpr->getAST()->eval(); - if (!EvaluatedValue) + FileCheckNumExprVal EvaluatedValue = NumExpr->getAST()->eval(); + if (!EvaluatedValue.isValid()) return llvm::None; + FileCheckNumExprFmt Fmt = NumExpr->getEffectiveFmt(); - return Fmt.getMatchingString(*EvaluatedValue); + return Fmt.getMatchingString(EvaluatedValue); } // Look up the value and escape it so that we can put it into the regex. @@ -309,18 +450,19 @@ if (AO != LegacyLiteral && AO != Any) return nullptr; unsigned Radix = (AO == LegacyLiteral) ? 10 : 0; - uint64_t LiteralValue; - if (Expr.consumeInteger(Radix, LiteralValue)) - return nullptr; - return std::make_shared(LiteralValue); -} - -static uint64_t add(uint64_t LeftOp, uint64_t RightOp) { - return LeftOp + RightOp; -} - -static uint64_t sub(uint64_t LeftOp, uint64_t RightOp) { - return LeftOp - RightOp; + int64_t SignedLiteralValue; + uint64_t UnsignedLiteralValue; + StringRef SaveExpr = Expr; + // Accept both signed and unsigned literal. + if (AO != LegacyLiteral && !Expr.consumeInteger(Radix, SignedLiteralValue)) { + return std::make_shared(SignedLiteralValue); + } else { + Expr = SaveExpr; + if (!Expr.consumeInteger(Radix, UnsignedLiteralValue)) { + return std::make_shared(UnsignedLiteralValue); + } else + return nullptr; + } } std::shared_ptr FileCheckPattern::parseFileCheckBinop( @@ -337,10 +479,10 @@ binop_eval_t EvalBinop; switch (Operator) { case '+': - EvalBinop = add; + EvalBinop = operator+; break; case '-': - EvalBinop = sub; + EvalBinop = operator-; break; default: SM.PrintMessage(OpLoc, SourceMgr::DK_Error, @@ -401,6 +543,9 @@ case 'u': ExplicitFmt = FmtUnsigned; break; + case 'd': + ExplicitFmt = FmtSigned; + break; case 'x': ExplicitFmt = FmtLowHex; break; @@ -505,8 +650,9 @@ // Create fake @LINE pseudo variable definition. StringRef LinePseudo = "@LINE"; uint64_t LineNumber64 = LineNumber; + auto LineNumberVal = FileCheckNumExprVal(LineNumber64); std::shared_ptr LinePseudoVar = - std::make_shared(LinePseudo, LineNumber64); + std::make_shared(LinePseudo, LineNumberVal); Context->GlobalNumericVariableTable[LinePseudo] = LinePseudoVar; if (!(Req.NoCanonicalizeWhiteSpace && Req.MatchFullLines)) @@ -818,8 +964,13 @@ for (const auto &Substitution : Substitutions) { // Substitute and check for failure (e.g. use of undefined variable). llvm::Optional Value = Substitution.getResult(); - if (!Value) + if (!Value) { + SM.PrintMessage( + SMLoc::getFromPointer(Substitution.getFromString().data()), + SourceMgr::DK_Error, + "Unable to substitute variable or numeric expression"); return StringRef::npos; + } // Plop it into the regex at the adjusted offset. TmpStr.insert(TmpStr.begin() + Substitution.getIndex() + InsertOffset, @@ -858,12 +1009,12 @@ assert(DefinedNumericVariable->getNumExpr() != nullptr); FileCheckNumExprFmt Fmt = DefinedNumericVariable->getNumExpr()->getEffectiveFmt(); - llvm::Optional Value = Fmt.valueFromStringRepr(MatchedValue); - if (!Value) { + FileCheckNumExprVal Value = Fmt.valueFromStringRepr(MatchedValue); + if (!Value.isValid()) { SM.PrintMessage(SMLoc::getFromPointer(MatchedValue.data()), SourceMgr::DK_Error, "Unable to represent numeric value"); } - if (DefinedNumericVariable->setValue(*Value)) + if (DefinedNumericVariable->setValue(Value)) assert(false && "Numeric variable redefined"); } @@ -1964,15 +2115,15 @@ ErrorFound = true; continue; } - llvm::Optional Value = NumExpr->getAST()->eval(); - if (!Value) { + FileCheckNumExprVal Value = NumExpr->getAST()->eval(); + if (!Value.isValid()) { SM.PrintMessage(SMLoc::getFromPointer(CmdlineDefExpr.data()), SourceMgr::DK_Error, "unable to represent numeric value"); ErrorFound = true; continue; } - DefinedNumericVariable->setValue(*Value); + DefinedNumericVariable->setValue(Value); // Record this variable definition. GlobalNumericVariableTable[DefinedNumericVariable->getName()] = diff --git a/llvm/test/FileCheck/numeric-expression.txt b/llvm/test/FileCheck/numeric-expression.txt --- a/llvm/test/FileCheck/numeric-expression.txt +++ b/llvm/test/FileCheck/numeric-expression.txt @@ -20,25 +20,27 @@ ; Numeric variable definition with explicit matching format DEF FMT +-30 c D ; CHECK-LABEL: DEF FMT -; CHECK-NEXT: [[#%x,VAR2:]] -; CHECK-NEXT: [[#%X,VAR3:]] +; CHECK-NEXT: [[#%d,VAR2:]] +; CHECK-NEXT: [[#%x,VAR3:]] +; CHECK-NEXT: [[#%X,VAR4:]] ; Numeric variable definition with explicit matching format in alternate spacing DEF FMT ALT SPC -c -c -c -c -c +-30 +-30 +-30 +-30 +-30 ; CHECK-LABEL: DEF FMT ALT SPC -; CHECK-NEXT: [[#%x, VAR2a:]] -; CHECK-NEXT: [[# %x, VAR2b:]] -; CHECK-NEXT: [[# %x , VAR2c:]] -; CHECK-NEXT: [[# %x , VAR2d :]] -; CHECK-NEXT: [[# %x , VAR2e : ]] +; CHECK-NEXT: [[#%d, VAR2a:]] +; CHECK-NEXT: [[# %d, VAR2b:]] +; CHECK-NEXT: [[# %d , VAR2c:]] +; CHECK-NEXT: [[# %d , VAR2d :]] +; CHECK-NEXT: [[# %d , VAR2e : ]] ; Numeric expressions in explicit matching format and default matching rule using ; variables defined on other lines @@ -46,26 +48,36 @@ 11 12 10 +-30 +-29 +-31 c d b 1a +1a D E C 1B +1B ; CHECK-LABEL: USE DEF FMT IMPL MATCH ; CHECK-NEXT: [[#%u,VAR1]] ; CHECK-NEXT: [[#%u,VAR1+1]] ; CHECK-NEXT: [[#%u,VAR1-1]] -; CHECK-NEXT: [[#%x,VAR2]] -; CHECK-NEXT: [[#%x,VAR2+1]] -; CHECK-NEXT: [[#%x,VAR2-1]] -; CHECK-NEXT: [[#%x,VAR2+14]] -; CHECK-NEXT: [[#%X,VAR3]] -; CHECK-NEXT: [[#%X,VAR3+1]] -; CHECK-NEXT: [[#%X,VAR3-1]] -; CHECK-NEXT: [[#%X,VAR3+14]] +; CHECK-NEXT: [[#%d,VAR2]] +; CHECK-NEXT: [[#%d,VAR2+1]] +; CHECK-NEXT: [[#%d,VAR2-1]] +; CHECK-NEXT: [[#%x,VAR3]] +; CHECK-NEXT: [[#%x,VAR3+1]] +; CHECK-NEXT: [[#%x,VAR3-1]] +; CHECK-NEXT: [[#%x,VAR3+0xe]] +; CHECK-NEXT: [[#%x,VAR3+0xE]] +; CHECK-NEXT: [[#%X,VAR4]] +; CHECK-NEXT: [[#%X,VAR4+1]] +; CHECK-NEXT: [[#%X,VAR4-1]] +; CHECK-NEXT: [[#%X,VAR4+0xe]] +; CHECK-NEXT: [[#%X,VAR4+0xE]] ; Numeric expressions in explicit matching format and default matching rule using ; variables defined on other lines in alternate spacing @@ -108,14 +120,19 @@ 11 12 10 +-30 +-29 +-31 c d b 1a +1a D E C 1B +1B ; CHECK-LABEL: USE IMPL FMT IMPL MATCH ; CHECK-NEXT: [[#VAR1]] ; CHECK-NEXT: [[#VAR1+1]] @@ -123,17 +140,22 @@ ; CHECK-NEXT: [[#VAR2]] ; CHECK-NEXT: [[#VAR2+1]] ; CHECK-NEXT: [[#VAR2-1]] -; CHECK-NEXT: [[#VAR2+14]] ; CHECK-NEXT: [[#VAR3]] ; CHECK-NEXT: [[#VAR3+1]] ; CHECK-NEXT: [[#VAR3-1]] -; CHECK-NEXT: [[#VAR3+14]] +; CHECK-NEXT: [[#VAR3+0xe]] +; CHECK-NEXT: [[#VAR3+0xE]] +; CHECK-NEXT: [[#VAR4]] +; CHECK-NEXT: [[#VAR4+1]] +; CHECK-NEXT: [[#VAR4-1]] +; CHECK-NEXT: [[#VAR4+0xe]] +; CHECK-NEXT: [[#VAR4+0xE]] ; Explicit format override implicit format conflicts VAR USE IMPL OVERRIDE FMT CONFLICT 23 ; CHECK-LABEL: VAR USE IMPL OVERRIDE FMT CONFLICT -; CHECK-NEXT: [[# %u, VAR1 + VAR2]] +; CHECK-NEXT: [[# %u, VAR1 + VAR3]] ; Numeric expressions using variables defined on the command-line and an ; immediate interpreted as an unsigned value @@ -149,12 +171,16 @@ b B 12 +12 +13 13 ; CHECK-LABEL: USE CONV FMT IMPL MATCH ; CHECK-NEXT: [[# %x, VAR1]] ; CHECK-NEXT: [[# %X, VAR1]] -; CHECK-NEXT: [[# %u, VAR2]] ; CHECK-NEXT: [[# %u, VAR3]] +; CHECK-NEXT: [[# %d, VAR3]] +; CHECK-NEXT: [[# %u, VAR4]] +; CHECK-NEXT: [[# %d, VAR4]] ; Numeric variable definition with unsupported matching format ; RUN: not FileCheck -check-prefixes ERR,INVALID-FMT-SPEC1 -input-file %s %s 2>&1 \ @@ -272,3 +298,29 @@ ; FMT-CONFLICT-MSG: numeric-expression.txt:[[#@LINE-1]]:25: error: variables with conflicting format specifier: need an explicit one ; FMT-CONFLICT-MSG-NEXT: {{F}}MT-CONFLICT-NEXT: {{\[\[#VAR1 \+ VAR2\]\]}} ; FMT-CONFLICT-MSG-NEXT: {{^ \^$}} + + +; Numeric expression with overflow +; RUN: not FileCheck -check-prefix OVERFLOW -input-file %s %s 2>&1 \ +; RUN: | FileCheck -check-prefix OVERFLOW-MSG %s + +OVERFLOW +BIGVAR=10000000000000000 +; OVERFLOW-LABEL: OVERFLOW +; OVERFLOW-NEXT: BIGVAR: [[#BIGVAR:0x8000000000000000+0x8000000000000000]] +; OVERFLOW-MSG: numeric-expression.txt:[[#@LINE-1]]:29: error: Unable to substitute variable or numeric expression +; OVERFLOW-MSG-NEXT: {{O}}VERFLOW-NEXT: BIGVAR: {{\[\[#BIGVAR:0x8000000000000000\+0x8000000000000000\]\]}} +; OVERFLOW-MSG-NEXT: {{^ \^$}} + + +; Numeric expression with underflow +; RUN: not FileCheck -check-prefix UNDERFLOW -input-file %s %s 2>&1 \ +; RUN: | FileCheck -check-prefix UNDERFLOW-MSG %s + +UNDERFLOW +TINYVAR=-10000000000000000 +; UNDERFLOW-LABEL: UNDERFLOW +; UNDERFLOW-NEXT: TINYVAR: [[#%d,TINYVAR:-0x8000000000000000-0x8000000000000000]] +; UNDERFLOW-MSG: numeric-expression.txt:[[#@LINE-1]]:31: error: Unable to substitute variable or numeric expression +; UNDERFLOW-MSG-NEXT: {{U}}NDERFLOW-NEXT: TINYVAR: {{\[\[#%d,TINYVAR:-0x8000000000000000-0x8000000000000000\]\]}} +; UNDERFLOW-MSG-NEXT: {{^ \^$}} diff --git a/llvm/unittests/Support/FileCheckTest.cpp b/llvm/unittests/Support/FileCheckTest.cpp --- a/llvm/unittests/Support/FileCheckTest.cpp +++ b/llvm/unittests/Support/FileCheckTest.cpp @@ -19,41 +19,58 @@ auto NumVarExpr = FileCheckNumExpr(nullptr, FmtUnsigned); FileCheckNumericVariable FooVar = FileCheckNumericVariable("FOO", &NumVarExpr, 1); - llvm::Optional Value = FooVar.eval(); - EXPECT_FALSE(Value); + FileCheckNumExprVal Value = FooVar.eval(); + EXPECT_FALSE(Value.isValid()); EXPECT_TRUE(FooVar.clearValue()); - EXPECT_FALSE(FooVar.setValue((uint64_t)42)); + FileCheckNumExprVal LifeAnswer = FileCheckNumExprVal((uint64_t)42); + FooVar.setValue(LifeAnswer); // Defined variable: getValue returns value set, setValue fails. Value = FooVar.eval(); - EXPECT_TRUE(Value); - EXPECT_EQ((uint64_t)42, *Value); - EXPECT_TRUE(FooVar.setValue((uint64_t)43)); + EXPECT_TRUE(Value.isValid()); + EXPECT_EQ((uint64_t)42, Value.getUnsignedValue()); + FileCheckNumExprVal FourThree = FileCheckNumExprVal((uint64_t)43); + EXPECT_TRUE(FooVar.setValue(FourThree)); // Clearing variable: getValue fails, clearValue again fails. EXPECT_FALSE(FooVar.clearValue()); Value = FooVar.eval(); - EXPECT_FALSE(Value); + EXPECT_FALSE(Value.isValid()); EXPECT_TRUE(FooVar.clearValue()); } -uint64_t doAdd(uint64_t OpL, uint64_t OpR) { return OpL + OpR; } +FileCheckNumExprVal doAdd(const FileCheckNumExprVal &Op1, + const FileCheckNumExprVal &Op2) { + if (Op1.isSigned()) { + int64_t Val1 = Op1.getSignedValue(); + int64_t Val2 = Op2.getSignedValue(); + + return FileCheckNumExprVal(Val1 + Val2); + } else { + uint64_t Val1 = Op1.getUnsignedValue(); + uint64_t Val2 = Op2.getUnsignedValue(); + + return FileCheckNumExprVal(Val1 + Val2); + } +} TEST_F(FileCheckTest, BinopEvalUndef) { auto FooNumExpr = FileCheckNumExpr(nullptr, FmtUnsigned); auto FooVar = std::make_shared("FOO", &FooNumExpr, 1); - FooVar->setValue((uint64_t)42); + FileCheckNumExprVal LifeAnswer = FileCheckNumExprVal((uint64_t)42); + FooVar->setValue(LifeAnswer); auto BarNumExpr = FileCheckNumExpr(nullptr, FmtUnsigned); auto BarVar = std::make_shared("BAR", &BarNumExpr, 2); - BarVar->setValue((uint64_t)18); + FileCheckNumExprVal OneEight = FileCheckNumExprVal((uint64_t)18); + BarVar->setValue(OneEight); auto Binop = new FileCheckASTBinop(doAdd, FooVar, BarVar); // Defined variable: eval returns right value, no undef variable returned. - llvm::Optional Value = Binop->eval(); - EXPECT_TRUE(Value); - EXPECT_EQ((uint64_t)60, *Value); + FileCheckNumExprVal Value = Binop->eval(); + EXPECT_TRUE(Value.isValid()); + EXPECT_EQ((uint64_t)60, Value.getUnsignedValue()); std::vector UndefVarNames; Binop->appendUndefVarNames(UndefVarNames); EXPECT_TRUE(UndefVarNames.empty()); @@ -61,7 +78,7 @@ // 1 undefined variable: eval fails, undef variable returned. FooVar->clearValue(); Value = Binop->eval(); - EXPECT_FALSE(Value); + EXPECT_FALSE(Value.isValid()); Binop->appendUndefVarNames(UndefVarNames); EXPECT_EQ(1U, UndefVarNames.size()); EXPECT_EQ("FOO", UndefVarNames[0]); @@ -69,7 +86,7 @@ // 2 undefined variables: eval fails, undef variables returned. BarVar->clearValue(); Value = Binop->eval(); - EXPECT_FALSE(Value); + EXPECT_FALSE(Value.isValid()); UndefVarNames.clear(); Binop->appendUndefVarNames(UndefVarNames); EXPECT_EQ(2U, UndefVarNames.size()); @@ -326,7 +343,8 @@ // Substitution of defined numeric variable returns the right value. auto NumVarExpr = FileCheckNumExpr(nullptr, FmtUnsigned); auto NumVar = std::make_shared("N", &NumVarExpr, 1); - NumVar->setValue(42); + FileCheckNumExprVal LifeAnswer = FileCheckNumExprVal((uint64_t)42); + NumVar->setValue(LifeAnswer); FileCheckNumExpr NumExpr = FileCheckNumExpr(NumVar, FmtUnsigned); Substitution = FileCheckPatternSubstitution(&Context, "N", &NumExpr, 12); llvm::Optional Value = Substitution.getResult(); @@ -369,9 +387,10 @@ // Undef var in numeric expression substitution with defined variable is // empty. + FileCheckNumExprVal LifeAnswer = FileCheckNumExprVal((uint64_t)42); auto LineVar = - std::make_shared("@LINE", (uint64_t)42); - auto Zero = std::make_shared(0); + std::make_shared("@LINE", LifeAnswer); + auto Zero = std::make_shared((uint64_t)0); auto Binop = std::make_shared(doAdd, LineVar, Zero); FileCheckNumExpr NumExpr = FileCheckNumExpr(Binop, FmtUnsigned); Substitution = FileCheckPatternSubstitution(&Context, "@LINE", &NumExpr, 12); @@ -457,9 +476,9 @@ EXPECT_TRUE(LocalVar); EXPECT_EQ(*LocalVar, "FOO"); EXPECT_TRUE(NumExpr); - llvm::Optional NumExprVal = NumExpr->getAST()->eval(); - EXPECT_TRUE(NumExprVal); - EXPECT_EQ(*NumExprVal, 18U); + FileCheckNumExprVal NumExprVal = NumExpr->getAST()->eval(); + EXPECT_TRUE(NumExprVal.isValid()); + EXPECT_EQ(NumExprVal.getSignedValue(), 18U); EXPECT_TRUE(EmptyVar); EXPECT_EQ(*EmptyVar, ""); EXPECT_FALSE(UnknownVar); @@ -469,7 +488,7 @@ LocalVar = Cxt.getPatternVarValue(LocalVarStr); EXPECT_FALSE(LocalVar); // Check eval fails even if we kept a pointer to the numeric expression. - EXPECT_FALSE(NumExpr->getAST()->eval()); + EXPECT_FALSE(NumExpr->getAST()->eval().isValid()); P = FileCheckPattern(Check::CheckPlain, &Cxt, 2); NumExpr = P.parseNumericExpression(LocalNumVarRef, DefinedNumericVariable, false /*Legacy*/, SM); @@ -492,8 +511,8 @@ false /*Legacy*/, SM); EXPECT_TRUE(NumExpr); NumExprVal = NumExpr->getAST()->eval(); - EXPECT_TRUE(NumExprVal); - EXPECT_EQ(*NumExprVal, 36U); + EXPECT_TRUE(NumExprVal.isValid()); + EXPECT_EQ(NumExprVal.getSignedValue(), 36U); // Clear local variables and check global variables remain defined. Cxt.clearLocalVars(); @@ -504,7 +523,7 @@ false /*Legacy*/, SM); EXPECT_TRUE(NumExpr); NumExprVal = NumExpr->getAST()->eval(); - EXPECT_TRUE(NumExprVal); - EXPECT_EQ(*NumExprVal, 36U); + EXPECT_TRUE(NumExprVal.isValid()); + EXPECT_EQ(NumExprVal.getSignedValue(), 36U); } } // namespace