[flang] Support operator== for expressions.

Original-commit: flang-compiler/f18@42013513e9
Reviewed-on: https://github.com/flang-compiler/f18/pull/251
This commit is contained in:
peter klausler 2019-01-04 14:05:53 -08:00
parent b45b098d0f
commit 2ad9986698
17 changed files with 413 additions and 186 deletions

View File

@ -58,6 +58,8 @@ public:
A *operator->() { return p_; }
const A *operator->() const { return p_; }
bool operator==(const Indirection &that) const { return *p_ == *that.p_; }
template<typename... ARGS> static Indirection Make(ARGS &&... args) {
return {new A(std::forward<ARGS>(args)...)};
}
@ -106,6 +108,8 @@ public:
A *operator->() { return p_; }
const A *operator->() const { return p_; }
bool operator==(const Indirection &that) const { return *p_ == *that.p_; }
template<typename... ARGS> static Indirection Make(ARGS &&... args) {
return {new A(std::forward<ARGS>(args)...)};
}
@ -149,6 +153,11 @@ public:
p_ = p;
}
bool operator==(const OwningPointer &that) const {
return (p_ == nullptr && that.p_ == nullptr) ||
(p_ != nullptr && that.p_ != nullptr && *p_ == *that.p_);
}
private:
A *p_{nullptr};
};

View File

@ -26,7 +26,15 @@
// a packaged value of a specific type if it is present and returns a pointer
// thereto; otherwise, it returns a null pointer. It's analogous to
// std::get_if<>() but it accepts a reference argument and is recursive.
// The type parameter cannot be omitted.
// The target type parameter cannot be omitted.
//
// Be advised: If the target type parameter is not const-qualified, but the
// isolated value is const-qualified, the result of Unwrap<> will be a
// pointer to a const-qualified value.
//
// Further: const-qualified alternatives in instances of non-const-qualified
// variants will not be returned from Unwrap if the target type is not
// const-qualified.
//
// UnwrapCopy<>() is a variation of Unwrap<>() that returns an optional copy
// of the value if one is present with the desired type.
@ -48,8 +56,7 @@ template<typename A, typename B> auto Unwrap(B &x) -> Constify<A, B> * {
}
// Prototypes of specializations, to enable mutual recursion
template<typename A, typename B>
auto Unwrap(B *) -> Constify<A, std::remove_pointer<B>> *;
template<typename A, typename B> auto Unwrap(B *p) -> Constify<A, B> *;
template<typename A, typename B>
auto Unwrap(const std::unique_ptr<B> &) -> Constify<A, B> *;
template<typename A, typename B>
@ -69,8 +76,7 @@ template<typename A, typename B>
auto Unwrap(const CountedReference<B> &) -> Constify<A, B> *;
// Implementations of specializations
template<typename A, typename B>
auto Unwrap(B *p) -> Constify<A, std::remove_pointer<B>> * {
template<typename A, typename B> auto Unwrap(B *p) -> Constify<A, B> * {
if (p != nullptr) {
return Unwrap<A>(*p);
} else {
@ -115,12 +121,22 @@ auto Unwrap(const std::optional<B> &x) -> Constify<A, B> * {
}
template<typename A, typename... Bs> A *Unwrap(std::variant<Bs...> &u) {
return std::visit([](auto &x) { return Unwrap<A>(x); }, u);
return std::visit(
[](auto &x) -> A * {
using Ty = std::decay_t<decltype(Unwrap<A>(x))>;
if constexpr (!std::is_const_v<std::remove_pointer_t<Ty>> ||
std::is_const_v<A>) {
return Unwrap<A>(x);
}
return nullptr;
},
u);
}
template<typename A, typename... Bs>
auto Unwrap(const std::variant<Bs...> &u) -> std::add_const_t<A> * {
return std::visit([](const auto &x) { return Unwrap<A>(x); }, u);
return std::visit(
[](const auto &x) -> std::add_const_t<A> * { return Unwrap<A>(x); }, u);
}
template<typename A, typename B, bool COPY>

View File

@ -14,6 +14,7 @@
#include "call.h"
#include "expression.h"
#include "../semantics/symbol.h"
namespace Fortran::evaluate {
@ -23,6 +24,11 @@ std::optional<DynamicType> ActualArgument::GetType() const {
int ActualArgument::Rank() const { return value->Rank(); }
bool ActualArgument::operator==(const ActualArgument &that) const {
return keyword == that.keyword &&
isAlternateReturn == that.isAlternateReturn && value == that.value;
}
std::ostream &ActualArgument::AsFortran(std::ostream &o) const {
if (keyword.has_value()) {
o << keyword->ToString() << '=';
@ -41,10 +47,57 @@ std::optional<int> ActualArgument::VectorSize() const {
return std::nullopt;
}
bool SpecificIntrinsic::operator==(const SpecificIntrinsic &that) const {
return name == that.name && type == that.type && rank == that.rank &&
attrs == that.attrs;
}
std::ostream &SpecificIntrinsic::AsFortran(std::ostream &o) const {
return o << name;
}
std::optional<DynamicType> ProcedureDesignator::GetType() const {
if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&u)}) {
return intrinsic->type;
}
if (const Symbol * symbol{GetSymbol()}) {
return GetSymbolType(symbol);
}
return std::nullopt;
}
int ProcedureDesignator::Rank() const {
if (const Symbol * symbol{GetSymbol()}) {
return symbol->Rank();
}
if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&u)}) {
return intrinsic->rank;
}
CHECK(!"ProcedureDesignator::Rank(): no case");
return 0;
}
bool ProcedureDesignator::IsElemental() const {
if (const Symbol * symbol{GetSymbol()}) {
return symbol->attrs().test(semantics::Attr::ELEMENTAL);
}
if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&u)}) {
return intrinsic->attrs.test(semantics::Attr::ELEMENTAL);
}
CHECK(!"ProcedureDesignator::IsElemental(): no case");
return 0;
}
const Symbol *ProcedureDesignator::GetSymbol() const {
return std::visit(
common::visitors{
[](const Symbol *sym) { return sym; },
[](const Component &c) { return &c.GetLastSymbol(); },
[](const auto &) -> const Symbol * { return nullptr; },
},
u);
}
std::ostream &ProcedureRef::AsFortran(std::ostream &o) const {
proc_.AsFortran(o);
char separator{'('};

View File

@ -37,6 +37,7 @@ struct ActualArgument {
std::optional<DynamicType> GetType() const;
int Rank() const;
bool operator==(const ActualArgument &) const;
std::ostream &AsFortran(std::ostream &) const;
std::optional<int> VectorSize() const;
@ -64,6 +65,7 @@ struct SpecificIntrinsic {
SpecificIntrinsic(IntrinsicProcedure n, std::optional<DynamicType> &&dt,
int r, semantics::Attrs a)
: name{n}, type{std::move(dt)}, rank{r}, attrs{a} {}
bool operator==(const SpecificIntrinsic &) const;
std::ostream &AsFortran(std::ostream &) const;
IntrinsicProcedure name;

View File

@ -19,9 +19,14 @@
#include "../common/fortran.h"
#include "../common/idioms.h"
#include "../common/indirection.h"
#include "../parser/char-block.h"
#include "../parser/message.h"
#include <cinttypes>
namespace Fortran::semantics {
class DerivedTypeSpec;
}
namespace Fortran::evaluate {
using common::RelationalOperator;
@ -145,7 +150,7 @@ using HostUnsignedInt =
// need for std::monostate as a default constituent in a std::variant<>.
// - There are full copy and move semantics for construction and assignment.
// - Discriminated unions have a std::variant<> member "u" and support
// explicit copy and move constructors.
// explicit copy and move constructors as well as comparison for equality.
#define DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(t) \
t(const t &) = default; \
t(t &&) = default; \
@ -161,7 +166,8 @@ using HostUnsignedInt =
template<typename _A> explicit t(const _A &x) : u{x} {} \
template<typename _A> \
explicit t(std::enable_if_t<!std::is_reference_v<_A>, _A> &&x) \
: u(std::move(x)) {}
: u(std::move(x)) {} \
bool operator==(const t &that) const { return u == that.u; }
// Force availability of copy construction and assignment
template<typename A> using CopyableIndirection = common::Indirection<A, true>;
@ -174,19 +180,21 @@ struct FoldingContext {
explicit FoldingContext(const parser::ContextualMessages &m,
Rounding round = defaultRounding, bool flush = false)
: messages{m}, rounding{round}, flushDenormalsToZero{flush} {}
FoldingContext(const parser::ContextualMessages &m, const FoldingContext &c)
: messages{m}, rounding{c.rounding}, flushDenormalsToZero{
c.flushDenormalsToZero} {}
// For narrowed contexts
FoldingContext(const FoldingContext &c, const parser::ContextualMessages &m)
: messages{m}, rounding{c.rounding}, flushDenormalsToZero{
c.flushDenormalsToZero} {}
FoldingContext(const FoldingContext &that)
: messages{that.messages}, rounding{that.rounding},
flushDenormalsToZero{that.flushDenormalsToZero}, pdtInstance{
that.pdtInstance} {}
FoldingContext(
const FoldingContext &that, const parser::ContextualMessages &m)
: messages{m}, rounding{that.rounding},
flushDenormalsToZero{that.flushDenormalsToZero}, pdtInstance{
that.pdtInstance} {}
parser::ContextualMessages messages;
Rounding rounding{defaultRounding};
bool flushDenormalsToZero{false};
bool bigEndian{false};
const semantics::DerivedTypeSpec *pdtInstance{nullptr};
};
void RealFlagWarnings(FoldingContext &, const RealFlags &, const char *op);

View File

@ -32,6 +32,10 @@ public:
constexpr Complex &operator=(const Complex &) = default;
constexpr Complex &operator=(Complex &&) = default;
constexpr bool operator==(const Complex &that) const {
return re_ == that.re_ && im_ == that.im_;
}
constexpr const Part &REAL() const { return re_; }
constexpr const Part &AIMAG() const { return im_; }
constexpr Complex CONJG() const { return {re_, im_.Negate()}; }

View File

@ -191,7 +191,7 @@ template<typename T> DynamicType ArrayConstructor<T>::GetType() const {
template<typename A>
std::optional<DynamicType> ExpressionBase<A>::GetType() const {
if constexpr (IsSpecificIntrinsicType<Result>) {
if constexpr (IsLengthlessIntrinsicType<Result>) {
return Result::GetType();
} else {
return std::visit(
@ -200,7 +200,7 @@ std::optional<DynamicType> ExpressionBase<A>::GetType() const {
BOZLiteralConstant>) {
return x.GetType();
}
return std::nullopt; // typeless -> no type
return std::nullopt; // typeless really means "no type"
},
derived().u);
}
@ -219,6 +219,31 @@ template<typename A> int ExpressionBase<A>::Rank() const {
derived().u);
}
// Equality testing for classes without EVALUATE_UNION_CLASS_BOILERPLATE()
template<typename V, typename O>
bool ImpliedDo<V, O>::operator==(const ImpliedDo<V, O> &that) const {
return controlVariableName == that.controlVariableName &&
lower == that.lower && upper == that.upper && stride == that.stride &&
values == that.values;
}
template<typename R>
bool ArrayConstructorValues<R>::operator==(
const ArrayConstructorValues<R> &that) const {
return values == that.values;
}
template<typename R>
bool ArrayConstructor<R>::operator==(const ArrayConstructor<R> &that) const {
return *static_cast<const ArrayConstructorValues<R> *>(this) == that &&
result == that.result && typeParameterValues == that.typeParameterValues;
}
bool GenericExprWrapper::operator==(const GenericExprWrapper &that) const {
return v == that.v;
}
// Template instantiations to resolve the "extern template" declarations
// that appear in expression.h.
@ -229,6 +254,7 @@ FOR_EACH_REAL_KIND(template struct Relational)
FOR_EACH_CHARACTER_KIND(template struct Relational)
template struct Relational<SomeType>;
FOR_EACH_TYPE_AND_KIND(template class ExpressionBase)
FOR_EACH_SPECIFIC_TYPE(template struct ArrayConstructor)
}
// For reclamation of analyzed expressions to which owning pointers have

View File

@ -19,7 +19,8 @@
// Expressions are the sole owners of their constituents; i.e., there is no
// context-independent hash table or sharing of common subexpressions, and
// thus these are trees, not DAGs. Both deep copy and move semantics are
// supported for expression construction.
// supported for expression construction. Expressions may be compared
// for equality.
#include "common.h"
#include "type.h"
@ -184,6 +185,10 @@ public:
}
}
bool operator==(const Operation &that) const {
return operand_ == that.operand_;
}
std::ostream &AsFortran(std::ostream &) const;
protected:
@ -387,6 +392,7 @@ template<typename VALUES, typename OPERAND> struct ImpliedDo {
using Operand = OPERAND;
using Result = ResultType<Values>;
static_assert(Operand::category == TypeCategory::Integer);
bool operator==(const ImpliedDo &) const;
parser::CharBlock controlVariableName;
CopyableIndirection<Expr<Operand>> lower, upper, stride;
CopyableIndirection<Values> values;
@ -406,6 +412,7 @@ template<typename RESULT> struct ArrayConstructorValues {
using Result = RESULT;
CLASS_BOILERPLATE(ArrayConstructorValues)
template<typename A> void Push(A &&x) { values.emplace_back(std::move(x)); }
bool operator==(const ArrayConstructorValues &) const;
std::vector<ArrayConstructorValue<Result>> values;
};
@ -416,20 +423,20 @@ struct ArrayConstructor : public ArrayConstructorValues<RESULT> {
DynamicType GetType() const;
static constexpr int Rank() { return 1; }
Expr<SubscriptInteger> LEN() const;
bool operator==(const ArrayConstructor &) const;
std::ostream &AsFortran(std::ostream &) const;
Result result;
std::vector<Expr<SubscriptInteger>> typeParameterValues;
};
// Per-category expression representations
// Expression representations for each type category.
template<int KIND>
class Expr<Type<TypeCategory::Integer, KIND>>
: public ExpressionBase<Type<TypeCategory::Integer, KIND>> {
public:
using Result = Type<TypeCategory::Integer, KIND>;
// TODO: R916 type-param-inquiry
EVALUATE_UNION_CLASS_BOILERPLATE(Expr)
explicit Expr(const Scalar<Result> &x) : u{Constant<Result>{x}} {}
@ -444,7 +451,7 @@ private:
Add<Result>, Subtract<Result>, Multiply<Result>, Divide<Result>,
Power<Result>, Extremum<Result>>;
using Others = std::variant<Constant<Result>, ArrayConstructor<Result>,
Designator<Result>, FunctionRef<Result>>;
TypeParamInquiry<KIND>, Designator<Result>, FunctionRef<Result>>;
public:
common::CombineVariants<Operations, Conversions, Others> u;
@ -664,10 +671,12 @@ public:
// from parse tree nodes.
struct GenericExprWrapper {
GenericExprWrapper(Expr<SomeType> &&x) : v{std::move(x)} {}
bool operator==(const GenericExprWrapper &) const;
Expr<SomeType> v;
};
FOR_EACH_CATEGORY_TYPE(extern template class Expr)
FOR_EACH_TYPE_AND_KIND(extern template class ExpressionBase)
FOR_EACH_SPECIFIC_TYPE(extern template struct ArrayConstructor)
}
#endif // FORTRAN_EVALUATE_EXPRESSION_H_

View File

@ -180,6 +180,10 @@ public:
constexpr Integer &operator=(const Integer &) = default;
constexpr bool operator==(const Integer &that) const {
return CompareUnsigned(that) == Ordering::Equal;
}
// Left-justified mask (e.g., MASKL(1) has only its sign bit set)
static constexpr Integer MASKL(int places) {
if (places <= 0) {

View File

@ -28,6 +28,10 @@ public:
constexpr Logical(bool truth) : word_{-std::uint64_t{truth}} {}
constexpr Logical &operator=(const Logical &) = default;
template<int B> constexpr bool operator==(const Logical<B> &that) const {
return IsTrue() == that.IsTrue();
}
// For static expression evaluation, all the bits will have the same value.
constexpr bool IsTrue() const { return word_.BTEST(0); }

View File

@ -59,6 +59,10 @@ public:
constexpr Real &operator=(const Real &) = default;
constexpr Real &operator=(Real &&) = default;
constexpr bool operator==(const Real &that) const {
return word_ == that.word_;
}
// TODO ANINT, CEILING, FLOOR, DIM, MAX, MIN, DPROD, FRACTION
// HUGE, INT/NINT, MAXEXPONENT, MINEXPONENT, NEAREST, OUT_OF_RANGE,
// PRECISION, HUGE, TINY, RRSPACING/SPACING, SCALE, SET_EXPONENT, SIGN

View File

@ -24,16 +24,23 @@ using namespace std::literals::string_literals;
namespace Fortran::evaluate {
std::optional<DynamicType> GetSymbolType(const semantics::Symbol &symbol) {
if (const auto *type{symbol.GetType()}) {
if (const auto *intrinsic{type->AsIntrinsic()}) {
TypeCategory category{intrinsic->category()};
int kind{intrinsic->kind()};
if (IsValidKindOfIntrinsicType(category, kind)) {
return DynamicType{category, kind};
bool DynamicType::operator==(const DynamicType &that) const {
return category == that.category && kind == that.kind &&
derived == that.derived && descriptor == that.descriptor;
}
std::optional<DynamicType> GetSymbolType(const semantics::Symbol *symbol) {
if (symbol != nullptr) {
if (const auto *type{symbol->GetType()}) {
if (const auto *intrinsic{type->AsIntrinsic()}) {
TypeCategory category{intrinsic->category()};
int kind{intrinsic->kind()};
if (IsValidKindOfIntrinsicType(category, kind)) {
return {DynamicType{category, kind}};
}
} else if (const auto *derived{type->AsDerived()}) {
return {DynamicType{TypeCategory::Derived, 0, derived}};
}
} else if (const auto *derived{type->AsDerived()}) {
return DynamicType{TypeCategory::Derived, 0, derived};
}
}
return std::nullopt;
@ -91,6 +98,11 @@ DynamicType DynamicType::ResultTypeForMultiply(const DynamicType &that) const {
return *this;
}
bool SomeKind<TypeCategory::Derived>::operator==(
const SomeKind<TypeCategory::Derived> &that) const {
return spec_ == that.spec_ && descriptor_ == that.descriptor_;
}
std::string SomeDerived::AsFortran() const {
return "TYPE("s + spec().name().ToString() + ')';
}

View File

@ -18,7 +18,7 @@
// These definitions map Fortran's intrinsic types, characterized by byte
// sizes encoded in KIND type parameter values, to their value representation
// types in the evaluation library, which are parameterized in terms of
// total bit width and real precision. Instances of these class templates
// total bit width and real precision. Instances of the Type class template
// are suitable for use as template parameters to instantiate other class
// templates, like expressions, over the supported types and kinds.
@ -44,33 +44,33 @@ namespace Fortran::evaluate {
using common::TypeCategory;
// DynamicType is suitable for use as the result type for
// GetType() functions and member functions.
struct DynamicType {
bool operator==(const DynamicType &that) const {
return category == that.category && kind == that.kind &&
derived == that.derived;
}
bool operator==(const DynamicType &that) const;
std::string AsFortran() const;
DynamicType ResultTypeForMultiply(const DynamicType &) const;
TypeCategory category;
int kind{0};
int kind{0}; // set only for intrinsic types
const semantics::DerivedTypeSpec *derived{nullptr};
// TODO pmk: descriptor for character length
// TODO pmk: derived type kind parameters and descriptor for lengths
const semantics::Symbol *descriptor{nullptr};
};
std::optional<DynamicType> GetSymbolType(const semantics::Symbol &);
// Result will be missing when a symbol is absent or
// has an erroneous type, e.g., REAL(KIND=666).
std::optional<DynamicType> GetSymbolType(const semantics::Symbol *);
// Specific intrinsic types are represented by specializations of
// this class template Type<CATEGORY, KIND>.
template<TypeCategory CATEGORY, int KIND = 0> class Type;
template<TypeCategory CATEGORY, int KIND> struct TypeBase {
static constexpr DynamicType dynamicType{CATEGORY, KIND};
static constexpr DynamicType GetType() { return {dynamicType}; }
template<TypeCategory CATEGORY, int KIND = 0> struct TypeBase {
static constexpr TypeCategory category{CATEGORY};
static constexpr int kind{KIND};
static std::string AsFortran() { return dynamicType.AsFortran(); }
constexpr bool operator==(const TypeBase &) const { return true; }
static constexpr DynamicType GetType() { return {category, kind}; }
static std::string AsFortran() { return GetType().AsFortran(); }
};
template<int KIND>
@ -225,11 +225,20 @@ using FloatingTypes = common::CombineTuples<RealTypes, ComplexTypes>;
using NumericTypes = common::CombineTuples<IntegerTypes, FloatingTypes>;
using RelationalTypes = common::CombineTuples<NumericTypes, CharacterTypes>;
using AllIntrinsicTypes = common::CombineTuples<RelationalTypes, LogicalTypes>;
using LengthlessIntrinsicTypes =
common::CombineTuples<NumericTypes, LogicalTypes>;
// Predicate: does a type represent a specific intrinsic type?
template<typename T>
constexpr bool IsSpecificIntrinsicType{common::HasMember<T, AllIntrinsicTypes>};
// Predicate: is a type an intrinsic type that is completely characterized
// by its category and kind parameter value, or might it have a derived type
// &/or a length type parameter?
template<typename T>
constexpr bool IsLengthlessIntrinsicType{
common::HasMember<T, LengthlessIntrinsicTypes>};
// When Scalar<T> is S, then TypeOf<S> is T.
// TypeOf is implemented by scanning all supported types for a match
// with Type<T>::Scalar.
@ -251,6 +260,7 @@ template<typename CONST> using TypeOf = typename TypeOfHelper<CONST>::type;
// Represents a type of any supported kind within a particular category.
template<TypeCategory CATEGORY> struct SomeKind {
static constexpr TypeCategory category{CATEGORY};
constexpr bool operator==(const SomeKind &) const { return true; }
};
template<> class SomeKind<TypeCategory::Derived> {
@ -258,14 +268,21 @@ public:
static constexpr TypeCategory category{TypeCategory::Derived};
CLASS_BOILERPLATE(SomeKind)
explicit SomeKind(const semantics::DerivedTypeSpec &s) : spec_{&s} {}
explicit SomeKind(const semantics::DerivedTypeSpec &dts,
const semantics::Symbol *sym = nullptr)
: spec_{&dts}, descriptor_{sym} {}
DynamicType GetType() const { return DynamicType{category, 0, spec_}; }
DynamicType GetType() const {
return DynamicType{category, 0, spec_, descriptor_};
}
const semantics::DerivedTypeSpec &spec() const { return *spec_; }
const semantics::Symbol *descriptor() const { return descriptor_; }
bool operator==(const SomeKind &) const;
std::string AsFortran() const;
private:
const semantics::DerivedTypeSpec *spec_;
const semantics::Symbol *descriptor_{nullptr};
};
using SomeInteger = SomeKind<TypeCategory::Integer>;
@ -280,36 +297,37 @@ using SomeCategory = std::tuple<SomeInteger, SomeReal, SomeComplex,
SomeCharacter, SomeLogical, SomeDerived>;
struct SomeType {};
// For "[extern] template class", &c. boilerplate
// For generating "[extern] template class", &c. boilerplate
#define EXPAND_FOR_EACH_INTEGER_KIND(M, P) \
M(P, 1) M(P, 2) M(P, 4) M(P, 8) M(P, 16)
#define EXPAND_FOR_EACH_REAL_KIND(M, P) \
M(P, 2) M(P, 3) M(P, 4) M(P, 8) M(P, 10) M(P, 16)
#define EXPAND_FOR_EACH_COMPLEX_KIND(M, P) EXPAND_FOR_EACH_REAL_KIND(M, P)
#define EXPAND_FOR_EACH_CHARACTER_KIND(M, P) M(P, 1) M(P, 2) M(P, 4)
#define EXPAND_FOR_EACH_LOGICAL_KIND(M, P) M(P, 1) M(P, 2) M(P, 4) M(P, 8)
#define TEMPLATE_INSTANTIATION(P, ARG) P<ARG>;
#define FOR_EACH_INTEGER_KIND_HELP(PREFIX, K) \
PREFIX<Type<TypeCategory::Integer, K>>;
#define FOR_EACH_REAL_KIND_HELP(PREFIX, K) PREFIX<Type<TypeCategory::Real, K>>;
#define FOR_EACH_COMPLEX_KIND_HELP(PREFIX, K) \
PREFIX<Type<TypeCategory::Complex, K>>;
#define FOR_EACH_CHARACTER_KIND_HELP(PREFIX, K) \
PREFIX<Type<TypeCategory::Character, K>>;
#define FOR_EACH_LOGICAL_KIND_HELP(PREFIX, K) \
PREFIX<Type<TypeCategory::Logical, K>>;
#define FOR_EACH_INTEGER_KIND(PREFIX) \
PREFIX<Type<TypeCategory::Integer, 1>>; \
PREFIX<Type<TypeCategory::Integer, 2>>; \
PREFIX<Type<TypeCategory::Integer, 4>>; \
PREFIX<Type<TypeCategory::Integer, 8>>; \
PREFIX<Type<TypeCategory::Integer, 16>>;
EXPAND_FOR_EACH_INTEGER_KIND(FOR_EACH_INTEGER_KIND_HELP, PREFIX)
#define FOR_EACH_REAL_KIND(PREFIX) \
PREFIX<Type<TypeCategory::Real, 2>>; \
PREFIX<Type<TypeCategory::Real, 3>>; \
PREFIX<Type<TypeCategory::Real, 4>>; \
PREFIX<Type<TypeCategory::Real, 8>>; \
PREFIX<Type<TypeCategory::Real, 10>>; \
PREFIX<Type<TypeCategory::Real, 16>>;
EXPAND_FOR_EACH_REAL_KIND(FOR_EACH_REAL_KIND_HELP, PREFIX)
#define FOR_EACH_COMPLEX_KIND(PREFIX) \
PREFIX<Type<TypeCategory::Complex, 2>>; \
PREFIX<Type<TypeCategory::Complex, 3>>; \
PREFIX<Type<TypeCategory::Complex, 4>>; \
PREFIX<Type<TypeCategory::Complex, 8>>; \
PREFIX<Type<TypeCategory::Complex, 10>>; \
PREFIX<Type<TypeCategory::Complex, 16>>;
EXPAND_FOR_EACH_COMPLEX_KIND(FOR_EACH_COMPLEX_KIND_HELP, PREFIX)
#define FOR_EACH_CHARACTER_KIND(PREFIX) \
PREFIX<Type<TypeCategory::Character, 1>>; \
PREFIX<Type<TypeCategory::Character, 2>>; \
PREFIX<Type<TypeCategory::Character, 4>>;
EXPAND_FOR_EACH_CHARACTER_KIND(FOR_EACH_CHARACTER_KIND_HELP, PREFIX)
#define FOR_EACH_LOGICAL_KIND(PREFIX) \
PREFIX<Type<TypeCategory::Logical, 1>>; \
PREFIX<Type<TypeCategory::Logical, 2>>; \
PREFIX<Type<TypeCategory::Logical, 4>>; \
PREFIX<Type<TypeCategory::Logical, 8>>;
EXPAND_FOR_EACH_LOGICAL_KIND(FOR_EACH_LOGICAL_KIND_HELP, PREFIX)
#define FOR_EACH_INTRINSIC_KIND(PREFIX) \
FOR_EACH_INTEGER_KIND(PREFIX) \
FOR_EACH_REAL_KIND(PREFIX) \
@ -325,9 +343,10 @@ struct SomeType {};
PREFIX<SomeComplex>; \
PREFIX<SomeCharacter>; \
PREFIX<SomeLogical>; \
PREFIX<SomeDerived>; \
PREFIX<SomeType>;
#define FOR_EACH_TYPE_AND_KIND(PREFIX) \
FOR_EACH_SPECIFIC_TYPE(PREFIX) \
FOR_EACH_INTRINSIC_KIND(PREFIX) \
FOR_EACH_CATEGORY_TYPE(PREFIX)
// Wraps a constant scalar value of a specific intrinsic type
@ -348,6 +367,7 @@ template<typename T> struct Constant {
constexpr DynamicType GetType() const { return Result::GetType(); }
int Rank() const { return 0; }
bool operator==(const Constant &that) const { return value == that.value; }
std::ostream &AsFortran(std::ostream &) const;
Value value;

View File

@ -27,12 +27,6 @@ using namespace Fortran::parser::literals;
namespace Fortran::evaluate {
int GetSymbolRank(const Symbol &symbol) { return symbol.Rank(); }
const parser::CharBlock &GetSymbolName(const Symbol &symbol) {
return symbol.name();
}
// Constructors, accessors, mutators
Triplet::Triplet(std::optional<Expr<SubscriptInteger>> &&l,
@ -78,6 +72,22 @@ CoarrayRef::CoarrayRef(std::vector<const Symbol *> &&c,
CHECK(!base_.empty());
}
std::optional<Expr<SomeInteger>> CoarrayRef::stat() const {
if (stat_.has_value()) {
return {**stat_};
} else {
return std::nullopt;
}
}
std::optional<Expr<SomeInteger>> CoarrayRef::team() const {
if (team_.has_value()) {
return {**team_};
} else {
return std::nullopt;
}
}
CoarrayRef &CoarrayRef::set_stat(Expr<SomeInteger> &&v) {
CHECK(IsVariable(v));
stat_ = CopyableIndirection<Expr<SomeInteger>>::Make(std::move(v));
@ -196,22 +206,30 @@ std::optional<Expr<SomeCharacter>> Substring::Fold(FoldingContext &context) {
// Variable formatting
std::ostream &Emit(std::ostream &o, const Symbol &symbol) {
return o << symbol.name().ToString();
}
std::ostream &Emit(std::ostream &o, const IntrinsicProcedure &p) {
return o << p;
}
std::ostream &Emit(std::ostream &o, const std::string &lit) {
return o << parser::QuoteCharacterLiteral(lit);
}
std::ostream &Emit(std::ostream &o, const std::u16string &lit) {
return o << parser::QuoteCharacterLiteral(lit);
}
std::ostream &Emit(std::ostream &o, const std::u32string &lit) {
return o << parser::QuoteCharacterLiteral(lit);
}
template<typename A> std::ostream &Emit(std::ostream &o, const A &x) {
return x.AsFortran(o);
}
template<> std::ostream &Emit(std::ostream &o, const std::string &lit) {
return o << parser::QuoteCharacterLiteral(lit);
}
template<> std::ostream &Emit(std::ostream &o, const std::u16string &lit) {
return o << parser::QuoteCharacterLiteral(lit);
}
template<> std::ostream &Emit(std::ostream &o, const std::u32string &lit) {
return o << parser::QuoteCharacterLiteral(lit);
}
template<typename A>
std::ostream &Emit(std::ostream &o, const A *p, const char *kw = nullptr) {
if (p != nullptr) {
@ -257,21 +275,18 @@ std::ostream &Emit(std::ostream &o, const std::variant<A...> &u) {
return o;
}
template<> std::ostream &Emit(std::ostream &o, const Symbol &symbol) {
return o << symbol.name().ToString();
}
template<> std::ostream &Emit(std::ostream &o, const IntrinsicProcedure &p) {
return o << p;
}
std::ostream &BaseObject::AsFortran(std::ostream &o) const {
return Emit(o, u);
}
template<int KIND>
std::ostream &TypeParamInquiry<KIND>::AsFortran(std::ostream &o) const {
return Emit(o, u) << '%' << parameter.ToString();
}
std::ostream &Component::AsFortran(std::ostream &o) const {
base_->AsFortran(o);
return Emit(o << '%', symbol_);
return Emit(o << '%', *symbol_);
}
std::ostream &Triplet::AsFortran(std::ostream &o) const {
@ -368,6 +383,7 @@ Expr<SubscriptInteger> BaseObject::LEN() const {
Expr<SubscriptInteger> Component::LEN() const {
return SymbolLEN(GetLastSymbol());
}
Expr<SubscriptInteger> ArrayRef::LEN() const {
return std::visit(
common::visitors{
@ -376,9 +392,11 @@ Expr<SubscriptInteger> ArrayRef::LEN() const {
},
u);
}
Expr<SubscriptInteger> CoarrayRef::LEN() const {
return SymbolLEN(*base_.back());
}
Expr<SubscriptInteger> DataRef::LEN() const {
return std::visit(
common::visitors{
@ -387,11 +405,13 @@ Expr<SubscriptInteger> DataRef::LEN() const {
},
u);
}
Expr<SubscriptInteger> Substring::LEN() const {
return AsExpr(
Extremum<SubscriptInteger>{AsExpr(Constant<SubscriptInteger>{0}),
upper() - lower() + AsExpr(Constant<SubscriptInteger>{1})});
}
template<typename T> Expr<SubscriptInteger> Designator<T>::LEN() const {
if constexpr (Result::category == TypeCategory::Character) {
return std::visit(
@ -406,6 +426,7 @@ template<typename T> Expr<SubscriptInteger> Designator<T>::LEN() const {
return AsExpr(Constant<SubscriptInteger>{0});
}
}
Expr<SubscriptInteger> ProcedureDesignator::LEN() const {
return std::visit(
common::visitors{
@ -505,27 +526,6 @@ template<typename T> int Designator<T>::Rank() const {
},
u);
}
int ProcedureDesignator::Rank() const {
if (const Symbol * symbol{GetSymbol()}) {
return symbol->Rank();
}
if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&u)}) {
return intrinsic->rank;
}
CHECK(!"ProcedureDesignator::Rank(): no case");
return 0;
}
bool ProcedureDesignator::IsElemental() const {
if (const Symbol * symbol{GetSymbol()}) {
return symbol->attrs().test(semantics::Attr::ELEMENTAL);
}
if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&u)}) {
return intrinsic->attrs.test(semantics::Attr::ELEMENTAL);
}
CHECK(!"ProcedureDesignator::IsElemental(): no case");
return 0;
}
// GetBaseObject(), GetFirstSymbol(), & GetLastSymbol()
const Symbol &Component::GetFirstSymbol() const {
@ -626,37 +626,53 @@ template<typename T> const Symbol *Designator<T>::GetLastSymbol() const {
u);
}
const Symbol *ProcedureDesignator::GetSymbol() const {
return std::visit(
common::visitors{
[](const Symbol *sym) { return sym; },
[](const Component &c) { return &c.GetLastSymbol(); },
[](const auto &) -> const Symbol * { return nullptr; },
},
u);
}
template<typename T> std::optional<DynamicType> Designator<T>::GetType() const {
if constexpr (std::is_same_v<Result, SomeDerived>) {
if (const Symbol * symbol{GetLastSymbol()}) {
return GetSymbolType(*symbol);
} else {
return std::nullopt;
}
} else {
if constexpr (IsLengthlessIntrinsicType<Result>) {
return {Result::GetType()};
} else if (const Symbol * symbol{GetLastSymbol()}) {
return GetSymbolType(symbol);
} else {
return std::nullopt;
}
}
std::optional<DynamicType> ProcedureDesignator::GetType() const {
if (const Symbol * symbol{GetSymbol()}) {
return {GetSymbolType(*symbol)};
}
if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&u)}) {
return {intrinsic->type};
}
return std::nullopt;
// Equality testing
bool BaseObject::operator==(const BaseObject &that) const {
return u == that.u;
}
bool Component::operator==(const Component &that) const {
return base_ == that.base_ && symbol_ == that.symbol_;
}
template<int KIND>
bool TypeParamInquiry<KIND>::operator==(
const TypeParamInquiry<KIND> &that) const {
return parameter == that.parameter && u == that.u;
}
bool Triplet::operator==(const Triplet &that) const {
return lower_ == that.lower_ && upper_ == that.upper_ &&
stride_ == that.stride_;
}
bool ArrayRef::operator==(const ArrayRef &that) const {
return u == that.u && subscript == that.subscript;
}
bool CoarrayRef::operator==(const CoarrayRef &that) const {
return base_ == that.base_ && subscript_ == that.subscript_ &&
cosubscript_ == that.cosubscript_ && stat_ == that.stat_ &&
team_ == that.team_ && teamIsTeamNumber_ == that.teamIsTeamNumber_;
}
bool Substring::operator==(const Substring &that) const {
return parent_ == that.parent_ && lower_ == that.lower_ &&
upper_ == that.upper_;
}
bool ComplexPart::operator==(const ComplexPart &that) const {
return part_ == that.part_ && complex_ == that.complex_;
}
bool ProcedureRef::operator==(const ProcedureRef &that) const {
return proc_ == that.proc_ && arguments_ == that.arguments_;
}
EXPAND_FOR_EACH_INTEGER_KIND(
TEMPLATE_INSTANTIATION, template struct TypeParamInquiry)
FOR_EACH_SPECIFIC_TYPE(template class Designator)
}

View File

@ -27,6 +27,7 @@
#include "type.h"
#include "../common/idioms.h"
#include "../common/template.h"
#include "../parser/char-block.h"
#include <optional>
#include <ostream>
#include <variant>
@ -52,6 +53,7 @@ struct BaseObject {
explicit BaseObject(StaticDataObject::Pointer &&p) : u{std::move(p)} {}
int Rank() const;
Expr<SubscriptInteger> LEN() const;
bool operator==(const BaseObject &) const;
std::ostream &AsFortran(std::ostream &) const;
std::variant<const Symbol *, StaticDataObject::Pointer> u;
};
@ -61,6 +63,8 @@ struct BaseObject {
// that isn't explicit in the document). Pointer and allocatable components
// are not explicitly indirected in this representation (TODO: yet?)
// Complex components (%RE, %IM) are isolated below in ComplexPart.
// (Type parameter inquiries look like component references but are distinct
// constructs and not represented by this class.)
class Component {
public:
CLASS_BOILERPLATE(Component)
@ -75,6 +79,7 @@ public:
const Symbol &GetFirstSymbol() const;
const Symbol &GetLastSymbol() const { return *symbol_; }
Expr<SubscriptInteger> LEN() const;
bool operator==(const Component &) const;
std::ostream &AsFortran(std::ostream &) const;
private:
@ -82,6 +87,35 @@ private:
const Symbol *symbol_;
};
using SymbolOrComponent = std::variant<const Symbol *, Component>;
// R916 type-param-inquiry
// N.B. x%LEN for CHARACTER is rewritten in semantics to LEN(x), which is
// then handled via LEN() member functions in the various classes.
// x%KIND for intrinsic types is similarly rewritten in semantics to
// KIND(x), which is then folded to a constant value.
// "Bare" type parameter references within a derived type definition do
// not have base objects here.
template<int KIND> struct TypeParamInquiry {
using Result = Type<TypeCategory::Integer, KIND>;
CLASS_BOILERPLATE(TypeParamInquiry)
TypeParamInquiry(const Symbol &symbol, parser::CharBlock p)
: u{&symbol}, parameter{std::move(p)} {}
TypeParamInquiry(Component &&component, parser::CharBlock p)
: u{component}, parameter{p} {}
TypeParamInquiry(SymbolOrComponent &&x, parser::CharBlock p)
: u{x}, parameter{p} {}
explicit TypeParamInquiry(parser::CharBlock p) : parameter{p} {}
static constexpr int Rank() { return 0; } // always scalar
bool operator==(const TypeParamInquiry &) const;
std::ostream &AsFortran(std::ostream &) const;
SymbolOrComponent u{nullptr};
parser::CharBlock parameter;
};
EXPAND_FOR_EACH_INTEGER_KIND(
TEMPLATE_INSTANTIATION, extern template struct TypeParamInquiry)
// R921 subscript-triplet
class Triplet {
public:
@ -93,6 +127,7 @@ public:
std::optional<Expr<SubscriptInteger>> lower() const;
std::optional<Expr<SubscriptInteger>> upper() const;
std::optional<Expr<SubscriptInteger>> stride() const;
bool operator==(const Triplet &) const;
std::ostream &AsFortran(std::ostream &) const;
private:
@ -125,9 +160,10 @@ struct ArrayRef {
const Symbol &GetFirstSymbol() const;
const Symbol &GetLastSymbol() const;
Expr<SubscriptInteger> LEN() const;
bool operator==(const ArrayRef &) const;
std::ostream &AsFortran(std::ostream &) const;
std::variant<const Symbol *, Component> u;
SymbolOrComponent u;
std::vector<Subscript> subscript;
};
@ -144,15 +180,28 @@ public:
CoarrayRef(std::vector<const Symbol *> &&,
std::vector<Expr<SubscriptInteger>> &&,
std::vector<Expr<SubscriptInteger>> &&);
const std::vector<const Symbol *> &base() const { return base_; }
const std::vector<Expr<SubscriptInteger>> &subscript() const {
return subscript_;
}
const std::vector<Expr<SubscriptInteger>> &cosubscript() const {
return cosubscript_;
}
// These integral expressions for STAT= and TEAM= must be variables
// (i.e., Designator or pointer-valued FunctionRef).
std::optional<Expr<SomeInteger>> stat() const;
CoarrayRef &set_stat(Expr<SomeInteger> &&);
std::optional<Expr<SomeInteger>> team() const;
bool teamIsTeamNumber() const { return teamIsTeamNumber_; }
CoarrayRef &set_team(Expr<SomeInteger> &&, bool isTeamNumber = false);
int Rank() const;
const Symbol &GetFirstSymbol() const { return *base_.front(); }
const Symbol &GetLastSymbol() const { return *base_.back(); }
Expr<SubscriptInteger> LEN() const;
bool operator==(const CoarrayRef &) const;
std::ostream &AsFortran(std::ostream &) const;
private:
@ -187,10 +236,10 @@ struct DataRef {
class Substring {
public:
CLASS_BOILERPLATE(Substring)
Substring(DataRef &&parent, std::optional<Expr<SubscriptInteger>> &&first,
std::optional<Expr<SubscriptInteger>> &&last)
Substring(DataRef &&parent, std::optional<Expr<SubscriptInteger>> &&lower,
std::optional<Expr<SubscriptInteger>> &&upper)
: parent_{std::move(parent)} {
SetBounds(first, last);
SetBounds(lower, upper);
}
Substring(StaticDataObject::Pointer &&parent,
std::optional<Expr<SubscriptInteger>> &&lower,
@ -202,9 +251,13 @@ public:
Expr<SubscriptInteger> lower() const;
Expr<SubscriptInteger> upper() const;
int Rank() const;
template<typename A> const A *GetParentIf() const {
return std::get_if<A>(&parent_);
}
BaseObject GetBaseObject() const;
const Symbol *GetLastSymbol() const;
Expr<SubscriptInteger> LEN() const;
bool operator==(const Substring &) const;
std::ostream &AsFortran(std::ostream &) const;
std::optional<Expr<SomeCharacter>> Fold(FoldingContext &);
@ -229,6 +282,7 @@ public:
int Rank() const;
const Symbol &GetFirstSymbol() const { return complex_.GetFirstSymbol(); }
const Symbol &GetLastSymbol() const { return complex_.GetLastSymbol(); }
bool operator==(const ComplexPart &) const;
std::ostream &AsFortran(std::ostream &) const;
private:
@ -238,20 +292,20 @@ private:
// R901 designator is the most general data reference object, apart from
// calls to pointer-valued functions. Its variant holds everything that
// a DataRef can, and possibly either a substring reference or a complex
// part (%RE/%IM) reference.
template<typename A> class Designator {
// a DataRef can, and possibly also a substring reference or a
// complex component (%RE/%IM) reference.
template<typename T> class Designator {
using DataRefs = decltype(DataRef::u);
using MaybeSubstring =
std::conditional_t<A::category == TypeCategory::Character,
std::conditional_t<T::category == TypeCategory::Character,
std::variant<Substring>, std::variant<>>;
using MaybeComplexPart = std::conditional_t<A::category == TypeCategory::Real,
using MaybeComplexPart = std::conditional_t<T::category == TypeCategory::Real,
std::variant<ComplexPart>, std::variant<>>;
using Variant =
common::CombineVariants<DataRefs, MaybeSubstring, MaybeComplexPart>;
public:
using Result = A;
using Result = T;
static_assert(IsSpecificIntrinsicType<Result> ||
std::is_same_v<Result, SomeKind<TypeCategory::Derived>>);
EVALUATE_UNION_CLASS_BOILERPLATE(Designator)
@ -283,6 +337,7 @@ public:
Expr<SubscriptInteger> LEN() const;
int Rank() const { return proc_.Rank(); }
bool IsElemental() const { return proc_.IsElemental(); }
bool operator==(const ProcedureRef &) const;
std::ostream &AsFortran(std::ostream &) const;
protected:
@ -292,23 +347,12 @@ protected:
template<typename A> struct FunctionRef : public ProcedureRef {
using Result = A;
static_assert(IsSpecificIntrinsicType<Result> ||
std::is_same_v<Result, SomeKind<TypeCategory::Derived>>);
CLASS_BOILERPLATE(FunctionRef)
FunctionRef(ProcedureRef &&pr) : ProcedureRef{std::move(pr)} {}
FunctionRef(ProcedureDesignator &&p, ActualArguments &&a)
: ProcedureRef{std::move(p), std::move(a)} {}
std::optional<DynamicType> GetType() const {
if constexpr (std::is_same_v<Result, SomeDerived>) {
if (const Symbol * symbol{proc_.GetSymbol()}) {
return GetSymbolType(*symbol);
}
} else {
return Result::GetType();
}
return std::nullopt;
}
std::optional<DynamicType> GetType() const { return proc_.GetType(); }
std::optional<Constant<Result>> Fold(FoldingContext &); // for intrinsics
};

View File

@ -287,7 +287,7 @@ MaybeExpr TypedWrapper(DynamicType &&dyType, WRAPPED &&x) {
// Wraps a data reference in a typed Designator<>.
static MaybeExpr Designate(DataRef &&dataRef) {
const Symbol &symbol{dataRef.GetLastSymbol()};
if (std::optional<DynamicType> dyType{GetSymbolType(symbol)}) {
if (std::optional<DynamicType> dyType{GetSymbolType(&symbol)}) {
return TypedWrapper<Designator, DataRef>(
std::move(*dyType), std::move(dataRef));
}
@ -764,7 +764,7 @@ MaybeExpr AnalyzeExpr(
std::optional<Expr<SubscriptInteger>> last{
GetSubstringBound(context, std::get<1>(range.t))};
const Symbol &symbol{checked->GetLastSymbol()};
if (std::optional<DynamicType> dynamicType{GetSymbolType(symbol)}) {
if (std::optional<DynamicType> dynamicType{GetSymbolType(&symbol)}) {
if (dynamicType->category == TypeCategory::Character) {
return WrapperHelper<TypeCategory::Character, Designator,
Substring>(dynamicType->kind,

View File

@ -156,8 +156,8 @@ void TestIntrinsics() {
.DoCall(); // bad intrinsic name
TestCall{table, "abs"}
.Push(Named("a", Const(Scalar<Int4>{})))
.DoCall(Int4::dynamicType);
TestCall{table, "abs"}.Push(Const(Scalar<Int4>{})).DoCall(Int4::dynamicType);
.DoCall(Int4::GetType());
TestCall{table, "abs"}.Push(Const(Scalar<Int4>{})).DoCall(Int4::GetType());
TestCall{table, "abs"}
.Push(Named("bad", Const(Scalar<Int4>{})))
.DoCall(); // bad keyword
@ -174,21 +174,17 @@ void TestIntrinsics() {
.Push(Named("a", Const(Scalar<Int4>{})))
.Push(Const(Scalar<Int4>{}))
.DoCall();
TestCall{table, "abs"}.Push(Const(Scalar<Int1>{})).DoCall(Int1::dynamicType);
TestCall{table, "abs"}.Push(Const(Scalar<Int4>{})).DoCall(Int4::dynamicType);
TestCall{table, "abs"}.Push(Const(Scalar<Int8>{})).DoCall(Int8::dynamicType);
TestCall{table, "abs"}
.Push(Const(Scalar<Real4>{}))
.DoCall(Real4::dynamicType);
TestCall{table, "abs"}
.Push(Const(Scalar<Real8>{}))
.DoCall(Real8::dynamicType);
TestCall{table, "abs"}.Push(Const(Scalar<Int1>{})).DoCall(Int1::GetType());
TestCall{table, "abs"}.Push(Const(Scalar<Int4>{})).DoCall(Int4::GetType());
TestCall{table, "abs"}.Push(Const(Scalar<Int8>{})).DoCall(Int8::GetType());
TestCall{table, "abs"}.Push(Const(Scalar<Real4>{})).DoCall(Real4::GetType());
TestCall{table, "abs"}.Push(Const(Scalar<Real8>{})).DoCall(Real8::GetType());
TestCall{table, "abs"}
.Push(Const(Scalar<Complex4>{}))
.DoCall(Real4::dynamicType);
.DoCall(Real4::GetType());
TestCall{table, "abs"}
.Push(Const(Scalar<Complex8>{}))
.DoCall(Real8::dynamicType);
.DoCall(Real8::GetType());
TestCall{table, "abs"}.Push(Const(Scalar<Char>{})).DoCall();
TestCall{table, "abs"}.Push(Const(Scalar<Log4>{})).DoCall();
@ -202,10 +198,10 @@ void TestIntrinsics() {
amin0Call.Push(Const(Scalar<Int4>{}));
amin1Call.Push(Const(Scalar<Int4>{}));
}
maxCall.DoCall(Real4::dynamicType);
maxCall.DoCall(Real4::GetType());
max0Call.DoCall();
max1Call.DoCall(Int4::dynamicType);
amin0Call.DoCall(Real4::dynamicType);
max1Call.DoCall(Int4::GetType());
amin0Call.DoCall(Real4::GetType());
amin1Call.DoCall();
// TODO: test other intrinsics