[flang] unit testing, better error messages

Original-commit: flang-compiler/f18@f3876008d0
Reviewed-on: https://github.com/flang-compiler/f18/pull/212
Tree-same-pre-rewrite: false
This commit is contained in:
peter klausler 2018-10-12 16:01:55 -07:00
parent 62425d6021
commit 7bda1b3243
7 changed files with 286 additions and 92 deletions

View File

@ -112,12 +112,12 @@ public:
return *this;
}
constexpr BitSet set(std::size_t x, bool value = true) {
if (value) {
bits_ |= static_cast<Word>(1) << x;
if (!value) {
return reset(x);
} else {
bits_ &= ~(static_cast<Word>(1) << x);
bits_ |= static_cast<Word>(1) << x;
return *this;
}
return *this;
}
constexpr BitSet &reset() {
bits_ = 0;

View File

@ -19,6 +19,8 @@
#include "../common/fortran.h"
#include "../common/idioms.h"
#include <map>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
@ -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<SpecificIntrinsic> 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<SpecificIntrinsic> 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<SpecificIntrinsic> 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<SpecificIntrinsic> 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<SpecificIntrinsic> 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<SpecificIntrinsic> 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<SpecificIntrinsic> 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<SpecificIntrinsic> 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<SpecificIntrinsic> 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<std::string, const IntrinsicInterface *> genericFuncs;
std::multimap<std::string, const SpecificIntrinsicInterface *> specificFuncs;
std::ostream &Dump(std::ostream &) const;
};
// Probe the configured intrinsic procedure pattern tables in search of a
@ -1095,12 +1095,12 @@ std::optional<SpecificIntrinsic> 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<SpecificIntrinsic> 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<SpecificIntrinsic> 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<SpecificIntrinsic> 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

View File

@ -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<SpecificIntrinsic> Probe(const CallCharacteristics &,
parser::ContextualMessages *messages = nullptr) const;
std::ostream &Dump(std::ostream &) const;
private:
Implementation *impl_{nullptr}; // owning pointer

View File

@ -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;

View File

@ -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;

View File

@ -204,17 +204,24 @@ public:
std::optional<ProvenanceRange> 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);
provenanceMap_.Put(ProvenanceRange{p, 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;

View File

@ -17,15 +17,122 @@
#include "../../lib/evaluate/expression.h"
#include "../../lib/evaluate/tools.h"
#include "../../lib/parser/provenance.h"
#include <initializer_list>
#include <iostream>
#include <map>
#include <string>
namespace Fortran::evaluate {
class CookedStrings {
public:
CookedStrings() {}
explicit CookedStrings(const std::initializer_list<std::string> &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<std::string, std::size_t> offsets_;
};
template<typename A> auto Const(A &&x) -> Constant<TypeOf<A>> {
return Constant<TypeOf<A>>{std::move(x)};
}
template<typename A> struct NamedArg {
std::string keyword;
A value;
};
template<typename A> static NamedArg<A> 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<typename A> TestCall &Push(A &&x) {
args.emplace_back(AsGenericExpr(std::move(x)));
keywords.push_back("");
return *this;
}
template<typename A> TestCall &Push(NamedArg<A> &&x) {
args.emplace_back(AsGenericExpr(std::move(x.value)));
keywords.push_back(x.keyword);
strings.Save(x.keyword);
return *this;
}
template<typename A, typename... As> 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<DynamicType> 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<SpecificIntrinsic> 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<std::string> keywords;
};
template<typename A> 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<SpecificIntrinsic> si{table.Probe(call, &messages)};
TEST(si.has_value());
TEST(buffer.empty());
buffer.Emit(std::cout, cooked);
using Int4 = Type<TypeCategory::Integer, 4>;
TestCall{table, "bad"}
.Push(Const(Scalar<Int4>{1}))
.DoCall(); // bad intrinsic name
TestCall{table, "abs"}
.Push(Named("a", Const(Scalar<Int4>{1})))
.DoCall(Int4::dynamicType);
TestCall{table, "abs"}.Push(Const(Scalar<Int4>{1})).DoCall(Int4::dynamicType);
TestCall{table, "abs"}
.Push(Named("bad", Const(Scalar<Int4>{1})))
.DoCall(); // bad keyword
TestCall{table, "abs"}.DoCall(); // insufficient args
TestCall{table, "abs"}
.Push(Const(Scalar<Int4>{1}))
.Push(Const(Scalar<Int4>{2}))
.DoCall(); // too many args
TestCall{table, "abs"}
.Push(Const(Scalar<Int4>{1}))
.Push(Named("a", Const(Scalar<Int4>{2})))
.DoCall();
TestCall{table, "abs"}
.Push(Named("a", Const(Scalar<Int4>{1})))
.Push(Const(Scalar<Int4>{2}))
.DoCall();
}
} // namespace Fortran::evaluate