From: peter klausler Date: Fri, 12 Oct 2018 23:01:55 +0000 (-0700) Subject: [flang] unit testing, better error messages X-Git-Tag: llvmorg-12-init~9537^2~2078 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=7bda1b3243af01b10d9ce5d8097a445ab27e182a;p=platform%2Fupstream%2Fllvm.git [flang] unit testing, better error messages Original-commit: flang-compiler/f18@f3876008d03751fff5d9490cbb1c7b7539e5c095 Reviewed-on: https://github.com/flang-compiler/f18/pull/212 Tree-same-pre-rewrite: false --- diff --git a/flang/lib/common/constexpr-bitset.h b/flang/lib/common/constexpr-bitset.h index 296028b..fca10d2 100644 --- a/flang/lib/common/constexpr-bitset.h +++ b/flang/lib/common/constexpr-bitset.h @@ -112,12 +112,12 @@ public: return *this; } constexpr BitSet set(std::size_t x, bool value = true) { - if (value) { - bits_ |= static_cast(1) << x; + if (!value) { + return reset(x); } else { - bits_ &= ~(static_cast(1) << x); + bits_ |= static_cast(1) << x; + return *this; } - return *this; } constexpr BitSet &reset() { bits_ = 0; diff --git a/flang/lib/evaluate/intrinsics.cc b/flang/lib/evaluate/intrinsics.cc index 7d049a0..0f6995f 100644 --- a/flang/lib/evaluate/intrinsics.cc +++ b/flang/lib/evaluate/intrinsics.cc @@ -19,6 +19,8 @@ #include "../common/fortran.h" #include "../common/idioms.h" #include +#include +#include #include #include @@ -62,26 +64,23 @@ static constexpr CategorySet IntrinsicType{ static constexpr CategorySet AnyType{ IntrinsicType | CategorySet{TypeCategory::Derived}}; -enum class KindCode { - none, - defaultIntegerKind, - defaultRealKind, // is also the default COMPLEX kind - doublePrecision, - defaultCharKind, - defaultLogicalKind, - any, // matches any kind value; each instance is independent - typeless, // BOZ literals are INTEGER with this kind - teamType, // TEAM_TYPE from module ISO_FORTRAN_ENV (for coarrays) - kindArg, // this argument is KIND= - effectiveKind, // for function results: same "kindArg", possibly defaulted - dimArg, // this argument is DIM= - same, // match any kind; all "same" kinds must be equal - likeMultiply, // for DOT_PRODUCT and MATMUL -}; +ENUM_CLASS(KindCode, none, defaultIntegerKind, + defaultRealKind, // is also the default COMPLEX kind + doublePrecision, defaultCharKind, defaultLogicalKind, + any, // matches any kind value; each instance is independent + typeless, // BOZ literals are INTEGER with this kind + teamType, // TEAM_TYPE from module ISO_FORTRAN_ENV (for coarrays) + kindArg, // this argument is KIND= + effectiveKind, // for function results: same "kindArg", possibly defaulted + dimArg, // this argument is DIM= + same, // match any kind; all "same" kinds must be equal + likeMultiply, // for DOT_PRODUCT and MATMUL +) struct TypePattern { CategorySet categorySet; KindCode kindCode{KindCode::none}; + std::ostream &Dump(std::ostream &) const; }; // Abbreviations for argument and result patterns in the intrinsic prototypes: @@ -138,37 +137,35 @@ static constexpr TypePattern KINDLogical{Logical, KindCode::effectiveKind}; // The default rank pattern for dummy arguments and function results is // "elemental". -enum class Rank { - elemental, // scalar, or array that conforms with other array arguments - elementalOrBOZ, // elemental, or typeless BOZ literal scalar - scalar, - vector, - shape, // INTEGER vector of known length and no negative element - matrix, - array, // not scalar, rank is known and greater than zero - known, // rank is known and can be scalar - anyOrAssumedRank, // rank can be unknown - conformable, // scalar, or array of same rank & shape as "array" argument - reduceOperation, // a pure function with constraints for REDUCE - dimReduced, // scalar if no DIM= argument, else rank(array)-1 - dimRemoved, // scalar, or rank(array)-1 - rankPlus1, // rank(known)+1 - shaped, // rank is length of SHAPE vector -}; +ENUM_CLASS(Rank, + elemental, // scalar, or array that conforms with other array arguments + elementalOrBOZ, // elemental, or typeless BOZ literal scalar + scalar, vector, + shape, // INTEGER vector of known length and no negative element + matrix, + array, // not scalar, rank is known and greater than zero + known, // rank is known and can be scalar + anyOrAssumedRank, // rank can be unknown + conformable, // scalar, or array of same rank & shape as "array" argument + reduceOperation, // a pure function with constraints for REDUCE + dimReduced, // scalar if no DIM= argument, else rank(array)-1 + dimRemoved, // scalar, or rank(array)-1 + rankPlus1, // rank(known)+1 + shaped, // rank is length of SHAPE vector +) -enum class Optionality { - required, - optional, - defaultsToSameKind, // for MatchingDefaultKIND - defaultsToDefaultForResult, // for DefaultingKIND - repeats, // for MAX/MIN and their several variants -}; +ENUM_CLASS(Optionality, required, optional, + defaultsToSameKind, // for MatchingDefaultKIND + defaultsToDefaultForResult, // for DefaultingKIND + repeats, // for MAX/MIN and their several variants +) struct IntrinsicDummyArgument { const char *keyword{nullptr}; TypePattern typePattern; Rank rank{Rank::elemental}; Optionality optionality{Optionality::required}; + std::ostream &Dump(std::ostream &) const; }; // constexpr abbreviations for popular arguments: @@ -196,6 +193,7 @@ struct IntrinsicInterface { std::optional Match(const CallCharacteristics &, const IntrinsicTypeDefaultKinds &, parser::ContextualMessages &messages) const; + std::ostream &Dump(std::ostream &) const; }; static const IntrinsicInterface genericIntrinsicFunction[]{ @@ -742,7 +740,7 @@ std::optional IntrinsicInterface::Match( if (arg.isAlternateReturn) { messages.Say( "alternate return specifier not acceptable on call to intrinsic '%s'"_err_en_US, - call.name.ToString().data()); + name); return std::nullopt; } bool found{false}; @@ -755,16 +753,16 @@ std::optional IntrinsicInterface::Match( break; } } - if (!found) { - if (arg.keyword.has_value()) { - messages.Say(*arg.keyword, - "unknown keyword argument to intrinsic '%'"_err_en_US, - call.name.ToString().data()); - } else { - messages.Say("too many actual arguments"_err_en_US); - } - return std::nullopt; + } + if (!found) { + if (arg.keyword.has_value()) { + messages.Say(*arg.keyword, + "unknown keyword argument to intrinsic '%s'"_err_en_US, name); + } else { + messages.Say( + "too many actual arguments for intrinsic '%s'"_err_en_US, name); } + return std::nullopt; } } @@ -784,7 +782,7 @@ std::optional IntrinsicInterface::Match( const ActualArgument *arg{actualForDummy[dummyArgIndex]}; if (!arg) { if (d.optionality == Optionality::required) { - messages.Say("missing '%s' argument"_err_en_US, d.keyword); + messages.Say("missing mandatory '%s=' argument"_err_en_US, d.keyword); return std::nullopt; // missing non-OPTIONAL argument } else { continue; @@ -797,10 +795,11 @@ std::optional IntrinsicInterface::Match( d.rank == Rank::elementalOrBOZ) { continue; } - messages.Say("typeless (BOZ) not allowed for '%s'"_err_en_US, d.keyword); + messages.Say( + "typeless (BOZ) not allowed for '%s=' argument"_err_en_US, d.keyword); return std::nullopt; } else if (!d.typePattern.categorySet.test(type->category)) { - messages.Say("actual argument for '%s' has bad type '%s'"_err_en_US, + messages.Say("actual argument for '%s=' has bad type '%s'"_err_en_US, d.keyword, type->Dump().data()); return std::nullopt; // argument has invalid type category } @@ -853,7 +852,7 @@ std::optional IntrinsicInterface::Match( } if (!argOk) { messages.Say( - "actual argument for '%s' has bad type or kind '%s'"_err_en_US, + "actual argument for '%s=' has bad type or kind '%s'"_err_en_US, d.keyword, type->Dump().data()); return std::nullopt; } @@ -869,7 +868,7 @@ std::optional IntrinsicInterface::Match( if (const ActualArgument * arg{actualForDummy[dummyArgIndex]}) { if (arg->isAssumedRank && d.rank != Rank::anyOrAssumedRank) { messages.Say( - "assumed-rank array cannot be used for '%s' argument"_err_en_US, + "assumed-rank array cannot be used for '%s=' argument"_err_en_US, d.keyword); return std::nullopt; } @@ -932,7 +931,7 @@ std::optional IntrinsicInterface::Match( default: CRASH_NO_CASE; } if (!argOk) { - messages.Say("'%s' argument has unacceptable rank %d"_err_en_US, + messages.Say("'%s=' argument has unacceptable rank %d"_err_en_US, d.keyword, rank); return std::nullopt; } @@ -992,7 +991,7 @@ std::optional IntrinsicInterface::Match( } } } - messages.Say("'kind' argument must be a constant scalar integer " + messages.Say("'kind=' argument must be a constant scalar integer " "whose value is a supported kind for the " "intrinsic result type"_err_en_US); return std::nullopt; @@ -1085,6 +1084,7 @@ struct IntrinsicProcTable::Implementation { IntrinsicTypeDefaultKinds defaults; std::multimap genericFuncs; std::multimap specificFuncs; + std::ostream &Dump(std::ostream &) const; }; // Probe the configured intrinsic procedure pattern tables in search of a @@ -1095,12 +1095,12 @@ std::optional IntrinsicProcTable::Implementation::Probe( if (call.isSubroutineCall) { return std::nullopt; // TODO } - bool wantMessages{messages != nullptr && messages->messages() != nullptr}; + parser::Messages *finalBuffer{messages ? messages->messages() : nullptr}; // Probe the specific intrinsic function table first. parser::Messages specificBuffer; parser::ContextualMessages specificErrors{ messages ? messages->at() : call.name, - wantMessages ? &specificBuffer : nullptr}; + finalBuffer ? &specificBuffer : nullptr}; std::string name{call.name.ToString()}; auto specificRange{specificFuncs.equal_range(name)}; for (auto iter{specificRange.first}; iter != specificRange.second; ++iter) { @@ -1116,7 +1116,7 @@ std::optional IntrinsicProcTable::Implementation::Probe( parser::Messages genericBuffer; parser::ContextualMessages genericErrors{ messages ? messages->at() : call.name, - wantMessages ? &genericBuffer : nullptr}; + finalBuffer ? &genericBuffer : nullptr}; auto genericRange{genericFuncs.equal_range(name)}; for (auto iter{genericRange.first}; iter != genericRange.second; ++iter) { if (auto specific{iter->second->Match(call, defaults, genericErrors)}) { @@ -1142,12 +1142,11 @@ std::optional IntrinsicProcTable::Implementation::Probe( } } // No match - if (wantMessages) { + if (finalBuffer) { if (genericBuffer.empty()) { - CHECK(!specificBuffer.empty()); - messages->messages()->Annex(std::move(specificBuffer)); + finalBuffer->Annex(std::move(specificBuffer)); } else { - messages->messages()->Annex(std::move(genericBuffer)); + finalBuffer->Annex(std::move(genericBuffer)); } } return std::nullopt; @@ -1176,4 +1175,65 @@ std::optional IntrinsicProcTable::Probe( std::ostream &SpecificIntrinsic::Dump(std::ostream &o) const { return o << name; } + +std::ostream &TypePattern::Dump(std::ostream &o) const { + if (categorySet == AnyType) { + o << "any type"; + } else { + const char *sep = ""; + auto set{categorySet}; + while (auto least{set.LeastElement()}) { + o << sep << EnumToString(*least); + sep = " or "; + set.reset(*least); + } + } + o << '(' << EnumToString(kindCode) << ')'; + return o; +} + +std::ostream &IntrinsicDummyArgument::Dump(std::ostream &o) const { + if (keyword) { + o << keyword << '='; + } + return typePattern.Dump(o) + << ' ' << EnumToString(rank) << ' ' << EnumToString(optionality); +} + +std::ostream &IntrinsicInterface::Dump(std::ostream &o) const { + o << name; + char sep{'('}; + for (const auto &d : dummy) { + if (d.typePattern.kindCode == KindCode::none) { + break; + } + d.Dump(o << sep); + sep = ','; + } + if (sep == '(') { + o << "()"; + } + return result.Dump(o << " -> ") << ' ' << EnumToString(rank); +} + +std::ostream &IntrinsicProcTable::Implementation::Dump(std::ostream &o) const { + o << "generic intrinsic functions:\n"; + for (const auto &iter : genericFuncs) { + iter.second->Dump(o << iter.first << ": ") << '\n'; + } + o << "specific intrinsic functions:\n"; + for (const auto &iter : specificFuncs) { + iter.second->Dump(o << iter.first << ": "); + if (const char *g{iter.second->generic}) { + o << " -> " << g; + } + o << '\n'; + } + return o; +} + +std::ostream &IntrinsicProcTable::Dump(std::ostream &o) const { + return impl_->Dump(o); +} + } // namespace Fortran::evaluate diff --git a/flang/lib/evaluate/intrinsics.h b/flang/lib/evaluate/intrinsics.h index 1c28928..8c7d5c8d 100644 --- a/flang/lib/evaluate/intrinsics.h +++ b/flang/lib/evaluate/intrinsics.h @@ -43,6 +43,7 @@ struct SpecificIntrinsic { SpecificIntrinsic(IntrinsicProcedure n, bool isElem, DynamicType dt, int r) : name{n}, isElemental{isElem}, type{dt}, rank{r} {} std::ostream &Dump(std::ostream &) const; + IntrinsicProcedure name; bool isElemental{false}; // TODO: consider using Attrs instead bool isPointer{false}; // NULL() @@ -60,6 +61,7 @@ public: static IntrinsicProcTable Configure(const IntrinsicTypeDefaultKinds &); std::optional Probe(const CallCharacteristics &, parser::ContextualMessages *messages = nullptr) const; + std::ostream &Dump(std::ostream &) const; private: Implementation *impl_{nullptr}; // owning pointer diff --git a/flang/lib/parser/char-buffer.cc b/flang/lib/parser/char-buffer.cc index 361793a..02074aa 100644 --- a/flang/lib/parser/char-buffer.cc +++ b/flang/lib/parser/char-buffer.cc @@ -41,7 +41,7 @@ void CharBuffer::Claim(std::size_t n) { } } -void CharBuffer::Put(const char *data, std::size_t n) { +std::size_t CharBuffer::Put(const char *data, std::size_t n) { std::size_t chunk; for (std::size_t at{0}; at < n; at += chunk) { char *to{FreeSpace(&chunk)}; @@ -49,9 +49,12 @@ void CharBuffer::Put(const char *data, std::size_t n) { Claim(chunk); std::memcpy(to, data + at, chunk); } + return bytes_ - n; } -void CharBuffer::Put(const std::string &str) { Put(str.data(), str.size()); } +std::size_t CharBuffer::Put(const std::string &str) { + return Put(str.data(), str.size()); +} std::string CharBuffer::Marshal() const { std::string result; diff --git a/flang/lib/parser/char-buffer.h b/flang/lib/parser/char-buffer.h index a64232c..00dab90 100644 --- a/flang/lib/parser/char-buffer.h +++ b/flang/lib/parser/char-buffer.h @@ -55,9 +55,12 @@ public: char *FreeSpace(std::size_t *); void Claim(std::size_t); - void Put(const char *data, std::size_t n); - void Put(const std::string &); - void Put(char x) { Put(&x, 1); } + + // The return value is the byte offset of the new data, + // i.e. the value of size() before the call. + std::size_t Put(const char *data, std::size_t n); + std::size_t Put(const std::string &); + std::size_t Put(char x) { return Put(&x, 1); } std::string Marshal() const; diff --git a/flang/lib/parser/provenance.h b/flang/lib/parser/provenance.h index 9278129..814ea61 100644 --- a/flang/lib/parser/provenance.h +++ b/flang/lib/parser/provenance.h @@ -204,17 +204,24 @@ public: std::optional GetProvenanceRange(CharBlock) const; - void Put(const char *data, std::size_t bytes) { buffer_.Put(data, bytes); } - void Put(char ch) { buffer_.Put(&ch, 1); } - void Put(char ch, Provenance p) { - buffer_.Put(&ch, 1); + // The result of a Put() is the offset that the new data + // will have in the eventually marshaled contiguous buffer. + std::size_t Put(const char *data, std::size_t bytes) { + return buffer_.Put(data, bytes); + } + std::size_t Put(const std::string &s) { return buffer_.Put(s); } + std::size_t Put(char ch) { return buffer_.Put(&ch, 1); } + std::size_t Put(char ch, Provenance p) { provenanceMap_.Put(ProvenanceRange{p, 1}); + return buffer_.Put(&ch, 1); } + void PutProvenance(Provenance p) { provenanceMap_.Put(ProvenanceRange{p}); } void PutProvenance(ProvenanceRange pr) { provenanceMap_.Put(pr); } void PutProvenanceMappings(const OffsetToProvenanceMappings &pm) { provenanceMap_.Put(pm); } + void Marshal(); // marshals text into one contiguous block std::string AcquireData() { return std::move(data_); } std::ostream &Dump(std::ostream &) const; diff --git a/flang/test/evaluate/intrinsics.cc b/flang/test/evaluate/intrinsics.cc index 3928440..615b27c 100644 --- a/flang/test/evaluate/intrinsics.cc +++ b/flang/test/evaluate/intrinsics.cc @@ -17,15 +17,122 @@ #include "../../lib/evaluate/expression.h" #include "../../lib/evaluate/tools.h" #include "../../lib/parser/provenance.h" +#include #include +#include #include namespace Fortran::evaluate { +class CookedStrings { +public: + CookedStrings() {} + explicit CookedStrings(const std::initializer_list &ss) { + for (const auto &s : ss) { + Save(s); + } + Marshal(); + } + void Save(const std::string &s) { + offsets_[s] = cooked_.Put(s); + cooked_.PutProvenance(cooked_.allSources().AddCompilerInsertion(s)); + } + void Marshal() { cooked_.Marshal(); } + parser::CharBlock operator()(const std::string &s) { + return {cooked_.data().data() + offsets_[s], s.size()}; + } + parser::ContextualMessages Messages(parser::Messages &buffer) { + return parser::ContextualMessages{cooked_.data(), &buffer}; + } + void Emit(std::ostream &o, const parser::Messages &messages) { + messages.Emit(o, cooked_); + } + +private: + parser::CookedSource cooked_; + std::map offsets_; +}; + template auto Const(A &&x) -> Constant> { return Constant>{std::move(x)}; } +template struct NamedArg { + std::string keyword; + A value; +}; + +template static NamedArg Named(std::string kw, A &&x) { + return {kw, std::move(x)}; +} + +struct TestCall { + TestCall(const IntrinsicProcTable &t, std::string n) : table{t}, name{n} {} + template TestCall &Push(A &&x) { + args.emplace_back(AsGenericExpr(std::move(x))); + keywords.push_back(""); + return *this; + } + template TestCall &Push(NamedArg &&x) { + args.emplace_back(AsGenericExpr(std::move(x.value))); + keywords.push_back(x.keyword); + strings.Save(x.keyword); + return *this; + } + template TestCall &Push(A &&x, As &&... xs) { + Push(std::move(x)); + return Push(std::move(xs)...); + } + void Marshal() { + strings.Save(name); + strings.Marshal(); + std::size_t j{0}; + for (auto &kw : keywords) { + if (!kw.empty()) { + args[j].keyword = strings(kw); + } + ++j; + } + } + void DoCall(std::optional resultType = std::nullopt, + int rank = 0, bool isElemental = false) { + Marshal(); + parser::CharBlock fName{strings(name)}; + std::cout << "function: " << fName.ToString(); + char sep{'('}; + for (const auto &a : args) { + std::cout << sep; + sep = ','; + a.Dump(std::cout); + } + if (sep == '(') { + std::cout << '('; + } + std::cout << ")\n"; + CallCharacteristics call{fName, args}; + auto messages{strings.Messages(buffer)}; + std::optional si{table.Probe(call, &messages)}; + if (resultType.has_value()) { + TEST(si.has_value()); + TEST(buffer.empty()); + TEST(*resultType == si->type); + MATCH(rank, si->rank); + MATCH(isElemental, si->isElemental); + } else { + TEST(!si.has_value()); + TEST(!buffer.empty() || name == "bad"); + } + strings.Emit(std::cout, buffer); + } + + const IntrinsicProcTable &table; + CookedStrings strings; + parser::Messages buffer; + Arguments args; + std::string name; + std::vector keywords; +}; + template void Push(Arguments &args, A &&x) { args.emplace_back(AsGenericExpr(std::move(x))); } @@ -45,21 +152,33 @@ void TestIntrinsics() { MATCH(4, defaults.defaultIntegerKind); MATCH(4, defaults.defaultRealKind); IntrinsicProcTable table{IntrinsicProcTable::Configure(defaults)}; + table.Dump(std::cout); - parser::CookedSource cooked; - std::string name{"abs"}; - cooked.Put(name.data(), name.size()); - cooked.PutProvenance(cooked.allSources().AddCompilerInsertion(name)); - cooked.Marshal(); - TEST(cooked.data() == name); - parser::CharBlock nameCharBlock{cooked.data().data(), name.size()}; - CallCharacteristics call{nameCharBlock, Args(Const(value::Integer<32>{1}))}; - parser::Messages buffer; - parser::ContextualMessages messages{cooked.data(), &buffer}; - std::optional si{table.Probe(call, &messages)}; - TEST(si.has_value()); - TEST(buffer.empty()); - buffer.Emit(std::cout, cooked); + using Int4 = Type; + + TestCall{table, "bad"} + .Push(Const(Scalar{1})) + .DoCall(); // bad intrinsic name + TestCall{table, "abs"} + .Push(Named("a", Const(Scalar{1}))) + .DoCall(Int4::dynamicType); + TestCall{table, "abs"}.Push(Const(Scalar{1})).DoCall(Int4::dynamicType); + TestCall{table, "abs"} + .Push(Named("bad", Const(Scalar{1}))) + .DoCall(); // bad keyword + TestCall{table, "abs"}.DoCall(); // insufficient args + TestCall{table, "abs"} + .Push(Const(Scalar{1})) + .Push(Const(Scalar{2})) + .DoCall(); // too many args + TestCall{table, "abs"} + .Push(Const(Scalar{1})) + .Push(Named("a", Const(Scalar{2}))) + .DoCall(); + TestCall{table, "abs"} + .Push(Named("a", Const(Scalar{1}))) + .Push(Const(Scalar{2})) + .DoCall(); } } // namespace Fortran::evaluate