blob: 54bb4d4fe670f2933c300d7579b8300024ddb8c5 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ART_RUNTIME_LAMBDA_SHORTY_FIELD_TYPE_H_
#define ART_RUNTIME_LAMBDA_SHORTY_FIELD_TYPE_H_
#include "base/logging.h"
#include "base/macros.h"
#include "base/value_object.h"
#include "globals.h"
#include "runtime/primitive.h"
#include <ostream>
namespace art {
namespace mirror {
class Object; // forward declaration
} // namespace mirror
namespace lambda {
struct Closure; // forward declaration
// TODO: Refactor together with primitive.h
// The short form of a field type descriptor. Corresponds to ShortyFieldType in dex specification.
// Only types usable by a field (and locals) are allowed (i.e. no void type).
// Note that arrays and objects are treated both as 'L'.
//
// This is effectively a 'char' enum-like zero-cost type-safe wrapper with extra helper functions.
struct ShortyFieldType : ValueObject {
// Use as if this was an enum class, e.g. 'ShortyFieldType::kBoolean'.
enum : char {
// Primitives (Narrow):
kBoolean = 'Z',
kByte = 'B',
kChar = 'C',
kShort = 'S',
kInt = 'I',
kFloat = 'F',
// Primitives (Wide):
kLong = 'J',
kDouble = 'D',
// Managed types:
kObject = 'L', // This can also be an array (which is otherwise '[' in a non-shorty).
kLambda = '\\',
}; // NOTE: This is an anonymous enum so we can get exhaustive switch checking from the compiler.
// Implicitly construct from the enum above. Value must be one of the enum list members above.
// Always safe to use, does not do any DCHECKs.
inline constexpr ShortyFieldType(decltype(kByte) c) : value_(c) {
}
// Default constructor. The initial value is undefined. Initialize before calling methods.
// This is very unsafe but exists as a convenience to having undefined values.
explicit ShortyFieldType() : value_(StaticCastValue(0)) {
}
// Explicitly construct from a char. Value must be one of the enum list members above.
// Conversion is potentially unsafe, so DCHECKing is performed.
explicit inline ShortyFieldType(char c) : value_(StaticCastValue(c)) {
if (kIsDebugBuild) {
// Verify at debug-time that our conversion is safe.
ShortyFieldType ignored;
DCHECK(MaybeCreate(c, &ignored)) << "unknown shorty field type '" << c << "'";
}
}
// Attempts to parse the character in 'shorty_field_type' into its strongly typed version.
// Returns false if the character was out of range of the grammar.
static bool MaybeCreate(char shorty_field_type, ShortyFieldType* out) {
DCHECK(out != nullptr);
switch (shorty_field_type) {
case kBoolean:
case kByte:
case kChar:
case kShort:
case kInt:
case kFloat:
case kLong:
case kDouble:
case kObject:
case kLambda:
*out = ShortyFieldType(static_cast<decltype(kByte)>(shorty_field_type));
return true;
default:
break;
}
return false;
}
// Convert the first type in a field type descriptor string into a shorty.
// Arrays are converted into objects.
// Does not work for 'void' types (as they are illegal in a field type descriptor).
static ShortyFieldType CreateFromFieldTypeDescriptor(const char* field_type_descriptor) {
DCHECK(field_type_descriptor != nullptr);
char c = *field_type_descriptor;
if (UNLIKELY(c == kArray)) { // Arrays are treated as object references.
c = kObject;
}
return ShortyFieldType{c}; // NOLINT [readability/braces] [4]
}
// Parse the first type in the field type descriptor string into a shorty.
// See CreateFromFieldTypeDescriptor for more details.
//
// Returns the pointer offset into the middle of the field_type_descriptor
// that would either point to the next shorty type, or to null if there are
// no more types.
//
// DCHECKs that each of the nested types is a valid shorty field type. This
// means the type descriptor must be already valid.
static const char* ParseFromFieldTypeDescriptor(const char* field_type_descriptor,
ShortyFieldType* out_type) {
DCHECK(field_type_descriptor != nullptr);
if (UNLIKELY(field_type_descriptor[0] == '\0')) {
// Handle empty strings by immediately returning null.
return nullptr;
}
// All non-empty strings must be a valid list of field type descriptors, otherwise
// the DCHECKs will kick in and the program will crash.
const char shorter_type = *field_type_descriptor;
ShortyFieldType safe_type;
bool type_set = MaybeCreate(shorter_type, &safe_type);
// Lambda that keeps skipping characters until it sees ';'.
// Stops one character -after- the ';'.
auto skip_until_semicolon = [&field_type_descriptor]() {
while (*field_type_descriptor != ';' && *field_type_descriptor != '\0') {
++field_type_descriptor;
}
DCHECK_NE(*field_type_descriptor, '\0')
<< " type descriptor terminated too early: " << field_type_descriptor;
++field_type_descriptor; // Skip the ';'
};
++field_type_descriptor;
switch (shorter_type) {
case kObject:
skip_until_semicolon();
DCHECK(type_set);
DCHECK(safe_type == kObject);
break;
case kArray:
// Strip out all of the leading [[[[[s, we don't care if it's a multi-dimensional array.
while (*field_type_descriptor == '[' && *field_type_descriptor != '\0') {
++field_type_descriptor;
}
DCHECK_NE(*field_type_descriptor, '\0')
<< " type descriptor terminated too early: " << field_type_descriptor;
// Either a primitive, object, or closure left. No more arrays.
{
// Now skip all the characters that form the array's interior-most element type
// (which itself is guaranteed not to be an array).
ShortyFieldType array_interior_type;
type_set = MaybeCreate(*field_type_descriptor, &array_interior_type);
DCHECK(type_set) << " invalid remaining type descriptor " << field_type_descriptor;
// Handle array-of-objects case like [[[[[LObject; and array-of-closures like [[[[[\Foo;
if (*field_type_descriptor == kObject || *field_type_descriptor == kLambda) {
skip_until_semicolon();
} else {
// Handle primitives which are exactly one character we can skip.
DCHECK(array_interior_type.IsPrimitive());
++field_type_descriptor;
}
}
safe_type = kObject;
type_set = true;
break;
case kLambda:
skip_until_semicolon();
DCHECK(safe_type == kLambda);
DCHECK(type_set);
break;
default:
DCHECK_NE(kVoid, shorter_type) << "cannot make a ShortyFieldType from a void type";
break;
}
DCHECK(type_set) << "invalid shorty type descriptor " << shorter_type;
*out_type = safe_type;
return type_set ? field_type_descriptor : nullptr;
}
// Explicitly convert to a char.
inline explicit operator char() const {
return value_;
}
// Is this a primitive?
inline bool IsPrimitive() const {
return IsPrimitiveNarrow() || IsPrimitiveWide();
}
// Is this a narrow primitive (i.e. can fit into 1 virtual register)?
inline bool IsPrimitiveNarrow() const {
switch (value_) {
case kBoolean:
case kByte:
case kChar:
case kShort:
case kInt:
case kFloat:
return true;
default:
return false;
}
}
// Is this a wide primitive (i.e. needs exactly 2 virtual registers)?
inline bool IsPrimitiveWide() const {
switch (value_) {
case kLong:
case kDouble:
return true;
default:
return false;
}
}
// Is this an object reference (which can also be an array)?
inline bool IsObject() const {
return value_ == kObject;
}
// Is this a lambda?
inline bool IsLambda() const {
return value_ == kLambda;
}
// Is the size of this (to store inline as a field) always known at compile-time?
inline bool IsStaticSize() const {
return !IsLambda();
}
// Get the compile-time size (to be able to store it inline as a field or on stack).
// Dynamically-sized values such as lambdas return the guaranteed lower bound.
inline size_t GetStaticSize() const {
switch (value_) {
case kBoolean:
return sizeof(bool);
case kByte:
return sizeof(uint8_t);
case kChar:
return sizeof(int16_t);
case kShort:
return sizeof(uint16_t);
case kInt:
return sizeof(int32_t);
case kLong:
return sizeof(int64_t);
case kFloat:
return sizeof(float);
case kDouble:
return sizeof(double);
case kObject:
return kObjectReferenceSize;
case kLambda:
return sizeof(void*); // Large enough to store the ArtLambdaMethod
default:
DCHECK(false) << "unknown shorty field type '" << static_cast<char>(value_) << "'";
UNREACHABLE();
}
}
// Get the number of virtual registers necessary to represent this type as a stack local.
inline size_t GetVirtualRegisterCount() const {
if (IsPrimitiveNarrow()) {
return 1;
} else if (IsPrimitiveWide()) {
return 2;
} else if (IsObject()) {
return kObjectReferenceSize / sizeof(uint32_t);
} else if (IsLambda()) {
return 2;
} else {
DCHECK(false) << "unknown shorty field type '" << static_cast<char>(value_) << "'";
UNREACHABLE();
}
}
// Count how many virtual registers would be necessary in order to store this list of shorty
// field types.
inline size_t static CountVirtualRegistersRequired(const char* shorty) {
size_t size = 0;
while (shorty != nullptr && *shorty != '\0') {
// Each argument appends to the size.
ShortyFieldType shorty_field{*shorty}; // NOLINT [readability/braces] [4]
size += shorty_field.GetVirtualRegisterCount();
++shorty;
}
return size;
}
// Implicitly convert to the anonymous nested inner type. Used for exhaustive switch detection.
inline operator decltype(kByte)() const {
return value_;
}
// Returns a read-only static string representing the enum name, useful for printing/debug only.
inline const char* ToString() const {
switch (value_) {
case kBoolean:
return "kBoolean";
case kByte:
return "kByte";
case kChar:
return "kChar";
case kShort:
return "kShort";
case kInt:
return "kInt";
case kLong:
return "kLong";
case kFloat:
return "kFloat";
case kDouble:
return "kDouble";
case kObject:
return "kObject";
case kLambda:
return "kLambda";
default:
// Undefined behavior if we get this far. Pray the compiler gods are merciful.
return "<undefined>";
}
}
private:
static constexpr const char kArray = '[';
static constexpr const char kVoid = 'V';
// Helper to statically cast anything into our nested anonymous enum type.
template <typename T>
inline static decltype(kByte) StaticCastValue(const T& anything) {
return static_cast<decltype(value_)>(anything);
}
// The only field in this struct.
decltype(kByte) value_;
};
// Print to an output stream.
inline std::ostream& operator<<(std::ostream& ostream, ShortyFieldType shorty) {
return ostream << shorty.ToString();
}
static_assert(sizeof(ShortyFieldType) == sizeof(char),
"ShortyFieldType must be lightweight just like a char");
// Compile-time trait information regarding the ShortyFieldType.
// Used by static_asserts to verify that the templates are correctly used at compile-time.
//
// For example,
// ShortyFieldTypeTraits::IsPrimitiveNarrowType<int64_t>() == true
// ShortyFieldTypeTraits::IsObjectType<mirror::Object*>() == true
struct ShortyFieldTypeTraits {
// A type guaranteed to be large enough to holds any of the shorty field types.
using MaxType = uint64_t;
// Type traits: Returns true if 'T' is a valid type that can be represented by a shorty field type.
template <typename T>
static inline constexpr bool IsType() {
return IsPrimitiveType<T>() || IsObjectType<T>() || IsLambdaType<T>();
}
// Returns true if 'T' is a primitive type (i.e. a built-in without nested references).
template <typename T>
static inline constexpr bool IsPrimitiveType() {
return IsPrimitiveNarrowType<T>() || IsPrimitiveWideType<T>();
}
// Returns true if 'T' is a primitive type that is narrow (i.e. can be stored into 1 vreg).
template <typename T>
static inline constexpr bool IsPrimitiveNarrowType() {
return IsPrimitiveNarrowTypeImpl(static_cast<T* const>(nullptr));
}
// Returns true if 'T' is a primitive type that is wide (i.e. needs 2 vregs for storage).
template <typename T>
static inline constexpr bool IsPrimitiveWideType() {
return IsPrimitiveWideTypeImpl(static_cast<T* const>(nullptr));
}
// Returns true if 'T' is an object (i.e. it is a managed GC reference).
// Note: This is equivalent to std::base_of<mirror::Object*, T>::value
template <typename T>
static inline constexpr bool IsObjectType() {
return IsObjectTypeImpl(static_cast<T* const>(nullptr));
}
// Returns true if 'T' is a lambda (i.e. it is a closure with unknown static data);
template <typename T>
static inline constexpr bool IsLambdaType() {
return IsLambdaTypeImpl(static_cast<T* const>(nullptr));
}
private:
#define IS_VALID_TYPE_SPECIALIZATION(type, name) \
static inline constexpr bool Is ## name ## TypeImpl(type* const = 0) { \
return true; \
} \
\
static_assert(sizeof(MaxType) >= sizeof(type), "MaxType too small")
IS_VALID_TYPE_SPECIALIZATION(bool, PrimitiveNarrow);
IS_VALID_TYPE_SPECIALIZATION(int8_t, PrimitiveNarrow);
IS_VALID_TYPE_SPECIALIZATION(uint8_t, PrimitiveNarrow); // Not strictly true, but close enough.
IS_VALID_TYPE_SPECIALIZATION(int16_t, PrimitiveNarrow);
IS_VALID_TYPE_SPECIALIZATION(uint16_t, PrimitiveNarrow); // Chars are unsigned.
IS_VALID_TYPE_SPECIALIZATION(int32_t, PrimitiveNarrow);
IS_VALID_TYPE_SPECIALIZATION(uint32_t, PrimitiveNarrow); // Not strictly true, but close enough.
IS_VALID_TYPE_SPECIALIZATION(float, PrimitiveNarrow);
IS_VALID_TYPE_SPECIALIZATION(int64_t, PrimitiveWide);
IS_VALID_TYPE_SPECIALIZATION(uint64_t, PrimitiveWide); // Not strictly true, but close enough.
IS_VALID_TYPE_SPECIALIZATION(double, PrimitiveWide);
IS_VALID_TYPE_SPECIALIZATION(mirror::Object*, Object);
IS_VALID_TYPE_SPECIALIZATION(Closure*, Lambda);
#undef IS_VALID_TYPE_SPECIALIZATION
#define IS_VALID_TYPE_SPECIALIZATION_IMPL(name) \
template <typename T> \
static inline constexpr bool Is ## name ## TypeImpl(T* const = 0) { \
return false; \
}
IS_VALID_TYPE_SPECIALIZATION_IMPL(PrimitiveNarrow);
IS_VALID_TYPE_SPECIALIZATION_IMPL(PrimitiveWide);
IS_VALID_TYPE_SPECIALIZATION_IMPL(Object);
IS_VALID_TYPE_SPECIALIZATION_IMPL(Lambda);
#undef IS_VALID_TYPE_SPECIALIZATION_IMPL
};
// Maps the ShortyFieldType enum into it's C++ type equivalent, into the "type" typedef.
// For example:
// ShortyFieldTypeSelectType<ShortyFieldType::kBoolean>::type => bool
// ShortyFieldTypeSelectType<ShortyFieldType::kLong>::type => int64_t
//
// Invalid enums will not have the type defined.
template <decltype(ShortyFieldType::kByte) Shorty>
struct ShortyFieldTypeSelectType {
};
// Maps the C++ type into it's ShortyFieldType enum equivalent, into the "value" constexpr.
// For example:
// ShortyFieldTypeSelectEnum<bool>::value => ShortyFieldType::kBoolean
// ShortyFieldTypeSelectEnum<int64_t>::value => ShortyFieldType::kLong
//
// Signed-ness must match for a valid select, e.g. uint64_t will not map to kLong, but int64_t will.
// Invalid types will not have the value defined (see e.g. ShortyFieldTypeTraits::IsType<T>())
template <typename T>
struct ShortyFieldTypeSelectEnum {
};
#define SHORTY_FIELD_TYPE_SELECT_IMPL(cpp_type, enum_element) \
template <> \
struct ShortyFieldTypeSelectType<ShortyFieldType::enum_element> { \
using type = cpp_type; \
}; \
\
template <> \
struct ShortyFieldTypeSelectEnum<cpp_type> { \
static constexpr const auto value = ShortyFieldType::enum_element; \
}; \
SHORTY_FIELD_TYPE_SELECT_IMPL(bool, kBoolean);
SHORTY_FIELD_TYPE_SELECT_IMPL(int8_t, kByte);
SHORTY_FIELD_TYPE_SELECT_IMPL(int16_t, kShort);
SHORTY_FIELD_TYPE_SELECT_IMPL(uint16_t, kChar);
SHORTY_FIELD_TYPE_SELECT_IMPL(int32_t, kInt);
SHORTY_FIELD_TYPE_SELECT_IMPL(float, kFloat);
SHORTY_FIELD_TYPE_SELECT_IMPL(int64_t, kLong);
SHORTY_FIELD_TYPE_SELECT_IMPL(double, kDouble);
SHORTY_FIELD_TYPE_SELECT_IMPL(mirror::Object*, kObject);
SHORTY_FIELD_TYPE_SELECT_IMPL(Closure*, kLambda);
} // namespace lambda
} // namespace art
#endif // ART_RUNTIME_LAMBDA_SHORTY_FIELD_TYPE_H_