blob: f16f6c1b7da6cc6d4f50b66d0887e661922c8b84 [file] [log] [blame]
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_WASM_VALUE_TYPE_H_
#define V8_WASM_VALUE_TYPE_H_
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif // !V8_ENABLE_WEBASSEMBLY
#include <optional>
#include "src/base/bit-field.h"
#include "src/base/template-utils.h"
#include "src/codegen/machine-type.h"
#include "src/codegen/signature.h"
#include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-limits.h"
namespace v8 {
namespace internal {
// Type for holding simd values, defined in simd128.h.
class Simd128;
class Zone;
namespace wasm {
// Format: kind, log2Size, code, machineType, shortName, typeName
#define FOREACH_NUMERIC_VALUE_TYPE(V) \
V(I32, 2, I32, Int32, 'i', "i32") \
V(I64, 3, I64, Int64, 'l', "i64") \
V(F32, 2, F32, Float32, 'f', "f32") \
V(F64, 3, F64, Float64, 'd', "f64") \
V(S128, 4, S128, Simd128, 's', "v128") \
V(I8, 0, I8, Int8, 'b', "i8") \
V(I16, 1, I16, Int16, 'h', "i16") \
V(F16, 1, F16, Float16, 'p', "f16")
#define FOREACH_VALUE_TYPE(V) \
V(Void, -1, Void, None, 'v', "<void>") \
FOREACH_NUMERIC_VALUE_TYPE(V) \
V(Ref, kTaggedSizeLog2, Ref, AnyTagged, 'r', "ref") \
V(RefNull, kTaggedSizeLog2, RefNull, AnyTagged, 'n', "ref null") \
V(Top, -1, Void, None, '\\', "<top>") \
V(Bottom, -1, Void, None, '*', "<bot>")
constexpr int kMaxValueTypeSize = 16; // bytes
struct TypeIndex {
uint32_t index;
// We intentionally don't define comparison operators here, because
// different subclasses must not be compared to each other.
static constexpr uint32_t kInvalid = ~0u;
constexpr bool valid() const { return index != kInvalid; }
size_t hash_value() const { return index; }
};
struct ModuleTypeIndex : public TypeIndex {
inline static constexpr ModuleTypeIndex Invalid();
// Can't use "=default" because the base class doesn't have operator<=>.
bool operator==(ModuleTypeIndex other) const { return index == other.index; }
auto operator<=>(ModuleTypeIndex other) const {
return index <=> other.index;
}
};
ASSERT_TRIVIALLY_COPYABLE(ModuleTypeIndex);
constexpr ModuleTypeIndex ModuleTypeIndex::Invalid() {
return ModuleTypeIndex{ModuleTypeIndex::kInvalid};
}
struct CanonicalTypeIndex : public TypeIndex {
inline static constexpr CanonicalTypeIndex Invalid();
bool operator==(CanonicalTypeIndex other) const {
return index == other.index;
}
auto operator<=>(CanonicalTypeIndex other) const {
return index <=> other.index;
}
};
ASSERT_TRIVIALLY_COPYABLE(CanonicalTypeIndex);
constexpr CanonicalTypeIndex CanonicalTypeIndex::Invalid() {
return CanonicalTypeIndex{CanonicalTypeIndex::kInvalid};
}
inline std::ostream& operator<<(std::ostream& oss, TypeIndex index) {
return oss << index.index;
}
enum Nullability : bool { kNonNullable = false, kNullable = true };
enum Exactness : bool { kAnySubtype, kExact };
static constexpr bool kNotShared = false;
enum ValueKind : uint8_t {
#define DEF_ENUM(kind, ...) k##kind,
FOREACH_VALUE_TYPE(DEF_ENUM)
#undef DEF_ENUM
};
////////////////////////////////////////////////////////////////////////////////
// ValueType implementation.
//
// We represent ValueTypes as bit fields. We store a mix of spec-level
// information and additional caches that are useful for efficient algorithms
// (mostly of subtyping). The key design consideration is that we need to
// express many different variations of attributes, checks, switches; so we
// want a ValueType representation that efficiently supports many different
// kinds of queries and classifications.
// The usage of the bit field is as follows:
// bits 0-1: TypeKind: numeric, indexed ref, generic ref, or sentinel. The bits
// are chosen such that they can also be interpreted as:
// bit 0: is it a reference of some sort?
// bit 1: if it's a reference: does it have an index?
// if it isn't a reference: is it an internal sentinel?
//
// bit 2: if it's a reference, is it nullable? Otherwise: unused.
// bit 3: if it's a reference, is it an exact reference? Otherwise: unused.
//
// For indexed (aka user defined) types, the next two fields cache information
// that could also be looked up in the module's definition of the type:
// bit 4: is it a shared type? Note that we consider numeric types as shared,
// i.e. "shared" means "safe to use in a shared function".
// bits 5-7: RefTypeKind: if it's a reference (indexed or generic), what
// category of type (struct/array/function/continuation) is it?
// Non-reference types store the value "kOther".
//
// bits 8-27: For indexed types, the index. For other types, the StandardType.
// The "ValueType" subclass stores module-specific indices, the
// "CanonicalValueType" stores canonicalized indices.
// For non-indexed types, we also define their respective
// "NumericKind" or "GenericKind", which in addition to the right
// StandardType include the other relevant lower bits.
//
// With these assignments, it turns out that "HeapType" is effectively a
// subset of "ValueType", where bits 0-1 are known to *not* be "numeric",
// and bits 2-3 (which are ValueType-specific) are ignored.
namespace value_type_impl {
using IsRefField = base::BitField<bool, 0, 1>;
using HasIndexOrSentinelField = IsRefField::Next<bool, 1>;
} // namespace value_type_impl
// Shorthands for the "extra_bits" definitions below. All of these must be
// subsets of kGenericKindMask below!
#define FUNC value_type_impl::RefTypeKindField::encode(RefTypeKind::kFunction)
#define STRUCT value_type_impl::RefTypeKindField::encode(RefTypeKind::kStruct)
#define ARRAY value_type_impl::RefTypeKindField::encode(RefTypeKind::kArray)
#define CONT value_type_impl::RefTypeKindField::encode(RefTypeKind::kCont)
#define REF value_type_impl::IsRefField::encode(true)
#define SENTINEL value_type_impl::HasIndexOrSentinelField::encode(true)
// Abstract ref types that can occur in wire bytes.
// Format: kName, ValueTypeCode, extra_bits, printable_name
#define FOREACH_NONE_TYPE(V) /* force 80 cols */ \
V(NoCont, NoCont, REF | CONT, "nocont") \
V(NoExn, NoExn, REF, "noexn") \
V(NoExtern, NoExtern, REF, "noextern") \
V(NoFunc, NoFunc, REF | FUNC, "nofunc") \
V(None, None, REF, "none")
#define FOREACH_ABSTRACT_TYPE(V) /* force 80 cols */ \
FOREACH_NONE_TYPE(V) \
/* Established (non-proposal) types. */ \
V(Func, FuncRef, REF | FUNC, "func") \
V(Any, AnyRef, REF, "any") \
V(Eq, EqRef, REF, "eq") \
V(I31, I31Ref, REF, "i31") \
V(Struct, StructRef, REF | STRUCT, "struct") \
V(Array, ArrayRef, REF | ARRAY, "array") \
V(Extern, ExternRef, REF, "extern") \
V(Exn, ExnRef, REF, "exn") \
/* WasmFX aka Core Stack Switching. */ \
V(Cont, ContRef, REF | CONT, "cont") \
/* Stringref proposal. */ \
V(String, StringRef, REF, "string") \
V(StringViewWtf8, StringViewWtf8, REF, "stringview_wtf8") \
V(StringViewWtf16, StringViewWtf16, REF, "stringview_wtf16") \
V(StringViewIter, StringViewIter, REF, "stringview_iter")
// Types that don't fit into any of the other groups.
#define FOREACH_INTERNAL_TYPE(V) /* force 80 cols */ \
V(Void, Void, SENTINEL, "<void>") \
V(Top, Void, SENTINEL, "<top>") \
V(Bottom, Void, SENTINEL, "<bot>") \
V(ExternString, Void, REF, "<extern_string>")
#define FOREACH_GENERIC_TYPE(V) /* force 80 cols */ \
FOREACH_INTERNAL_TYPE(V) \
FOREACH_ABSTRACT_TYPE(V)
// 2 bits to coarsely classify ValueTypes.
enum class TypeKind : uint8_t {
kNumeric = 0b00, // i32, etc. FOREACH_NUMERIC_VALUE_TYPE above.
kSentinel = SENTINEL, // top, bottom, void. Have "SENTINEL" above.
kAbstractRef = REF, // anyref, eqref, funcref, etc. Have "REF" above.
kIndexedRef = REF | SENTINEL,
};
enum class RefTypeKind : uint8_t {
kOther = 0,
kFunction = 1,
kStruct = 2,
kArray = 3,
kCont = 4,
kLastValue = kCont,
};
namespace value_type_impl {
using TypeKindField = base::BitField<TypeKind, 0, 2>;
static_assert(TypeKindField::kMask ==
(IsRefField::kMask | HasIndexOrSentinelField::kMask));
// For reference types, we classify the type of reference.
// Non-reference types don't use these bits.
using IsNullableField = TypeKindField::Next<Nullability, 1>;
using IsExactField = IsNullableField::Next<Exactness, 1>;
// For reference types, we cache some information about the referenced type.
// Non-reference types don't use these bits.
using IsSharedField = IsExactField::Next<bool, 1>;
using RefTypeKindField = IsSharedField::Next<RefTypeKind, 3>;
static_assert(RefTypeKindField::is_valid(RefTypeKind::kLastValue));
// Stores the index if {has_index()}, or the {StandardType} otherwise.
using PayloadField = RefTypeKindField::Next<uint32_t, 20>;
// Reserved for future use.
using ReservedField = PayloadField::Next<uint32_t, 4>;
static_assert(ReservedField::kShift + ReservedField::kSize == 32);
// Useful for HeapTypes, whose "shared" bit is orthogonal to their kind.
static constexpr uint32_t kGenericKindMask =
PayloadField::kMask | RefTypeKindField::kMask | TypeKindField::kMask;
// Useful for numeric types which are always considered "shared".
static constexpr uint32_t kNumericKindMask =
kGenericKindMask | IsSharedField::kMask;
static constexpr uint32_t kNumericExtraBits = IsSharedField::encode(true);
#define COUNT(...) +1
static constexpr uint32_t kNumberOfGenericKinds = 0 FOREACH_GENERIC_TYPE(COUNT);
static constexpr uint32_t kNumberOfNumericKinds =
0 FOREACH_NUMERIC_VALUE_TYPE(COUNT);
#undef COUNT
static constexpr uint32_t kNumberOfStandardTypes =
kNumberOfGenericKinds + kNumberOfNumericKinds;
} // namespace value_type_impl
// We define one enum with all non-indexed types, in a compact encoding
// space. These enum values will equal the contents of the {PayloadField} for
// the respective ValueTypes.
enum class StandardType : uint8_t {
#define DEF_ENUM(kind, ...) k##kind,
FOREACH_GENERIC_TYPE(DEF_ENUM) FOREACH_NUMERIC_VALUE_TYPE(DEF_ENUM)
#undef DEF_ENUM
};
// We additionally define separate enums for generic and numeric types, so that
// we can write exhaustive switch statements. These enums' values include
// additional relevant bits in the {bit_field_}s of the respective HeapTypes/
// ValueTypes, so that certain checks can be expressed as bit field comparisons.
enum class GenericKind : uint32_t {
#define DEF_ENUM(kind, code, extra_bits, ...) \
k##kind = value_type_impl::PayloadField::encode( \
static_cast<uint8_t>(StandardType::k##kind)) + \
(extra_bits),
FOREACH_GENERIC_TYPE(DEF_ENUM)
#undef DEF_ENUM
};
enum class NumericKind : uint32_t {
#define DEF_ENUM(kind, ...) \
k##kind = value_type_impl::PayloadField::encode( \
static_cast<uint8_t>(StandardType::k##kind)) + \
value_type_impl::kNumericExtraBits,
FOREACH_NUMERIC_VALUE_TYPE(DEF_ENUM)
#undef DEF_ENUM
};
#undef FUNC
#undef STRUCT
#undef ARRAY
#undef CONT
#undef REF
#undef SENTINEL
namespace value_type_impl {
// Useful for dense lookup tables.
constexpr uint32_t ToZeroBasedIndex(NumericKind kind) {
// Inside {StandardType}, generic types come first.
static_assert(static_cast<uint32_t>(StandardType::kI32) >=
kNumberOfGenericKinds);
uint32_t raw = PayloadField::decode(static_cast<uint32_t>(kind));
DCHECK_GE(raw, kNumberOfGenericKinds);
// As an additional safety net, as long as we happen to have exactly 8
// numeric types, we can conveniently apply a mask here. If we need to
// accommodate a different number of numeric kinds in the future, we should
// consider adding bounds checks at use sites.
static_assert(kNumberOfNumericKinds == 8);
return (raw - kNumberOfGenericKinds) & 0x7;
}
} // namespace value_type_impl
constexpr bool IsNullKind(GenericKind kind) {
switch (kind) {
#define NULLTYPE(name, ...) \
case GenericKind::k##name: \
return true;
FOREACH_NONE_TYPE(NULLTYPE)
#undef NULLTYPE
default:
break;
}
return false;
}
// {ValueTypeBase} shouldn't be used directly; code should be using one of
// the subclasses. To enforce this, the public interface is limited to
// type index agnostic getters.
class ValueTypeBase {
public:
static const int kNumIndexBits = value_type_impl::PayloadField::kSize;
static const int kLastUsedBit = value_type_impl::PayloadField::kLastUsedBit;
static const uint32_t kIsRefBit = value_type_impl::IsRefField::kMask;
static const uint32_t kIsNullableBit =
value_type_impl::IsNullableField::kMask;
static const uint32_t kHasIndexBit =
value_type_impl::HasIndexOrSentinelField::kMask;
static const uint32_t kRefKindBits = value_type_impl::RefTypeKindField::kMask;
static const uint32_t kRefKindShift =
value_type_impl::RefTypeKindField::kShift;
static const uint32_t kIndexBits = value_type_impl::PayloadField::kMask;
static const uint32_t kIndexShift = value_type_impl::PayloadField::kShift;
constexpr ValueTypeBase()
: ValueTypeBase(GenericKind::kVoid, kNonNullable, Exactness::kAnySubtype,
false) {}
// This is specifically for the needs of the decoder: sometimes we need to
// create the ValueType instance when we still only know the type index.
// Once we know more about the referenced type, this function updates those
// bits to their correct values.
void Populate(bool shared, RefTypeKind kind) {
uint32_t bits = value_type_impl::IsSharedField::update(bit_field_, shared);
bit_field_ = value_type_impl::RefTypeKindField::update(bits, kind);
}
constexpr TypeKind type_kind() const {
return value_type_impl::TypeKindField::decode(bit_field_);
}
constexpr bool has_index() const {
return type_kind() == TypeKind::kIndexedRef;
}
constexpr bool is_sentinel() const {
return type_kind() == TypeKind::kSentinel;
}
constexpr bool is_numeric() const {
return type_kind() == TypeKind::kNumeric;
}
constexpr bool is_abstract_ref() const {
return type_kind() == TypeKind::kAbstractRef;
}
// Any kind of reference: abstract or indexed.
constexpr bool is_ref() const {
return value_type_impl::IsRefField::decode(bit_field_);
}
// Any GenericKind: abstract reference or internal sentinel.
constexpr bool is_generic() const {
TypeKind kind = type_kind();
return kind == TypeKind::kAbstractRef || kind == TypeKind::kSentinel;
}
constexpr Nullability nullability() const {
DCHECK(is_ref());
return value_type_impl::IsNullableField::decode(bit_field_);
}
constexpr bool is_nullable() const {
return is_ref() && static_cast<bool>(nullability());
}
constexpr bool is_non_nullable() const {
return is_ref() && !static_cast<bool>(nullability());
}
constexpr bool is_exact() const {
return value_type_impl::IsExactField::decode(bit_field_);
}
constexpr bool is_shared() const {
return value_type_impl::IsSharedField::decode(bit_field_);
}
constexpr RefTypeKind ref_type_kind() const {
return value_type_impl::RefTypeKindField::decode(bit_field_);
}
constexpr StandardType standard_type() const {
DCHECK(!has_index());
return static_cast<StandardType>(
value_type_impl::PayloadField::decode(bit_field_));
}
constexpr NumericKind numeric_kind() const {
DCHECK(is_numeric());
return static_cast<NumericKind>(bit_field_ &
value_type_impl::kNumericKindMask);
}
constexpr GenericKind generic_kind() const {
DCHECK(is_generic());
return static_cast<GenericKind>(bit_field_ &
value_type_impl::kGenericKindMask);
}
constexpr bool is_bottom() const {
uint32_t bits = bit_field_ & value_type_impl::kGenericKindMask;
return bits == static_cast<uint32_t>(GenericKind::kBottom);
}
constexpr bool is_top() const {
uint32_t bits = bit_field_ & value_type_impl::kGenericKindMask;
return bits == static_cast<uint32_t>(GenericKind::kTop);
}
constexpr bool is_void() const {
uint32_t bits = bit_field_ & value_type_impl::kGenericKindMask;
return bits == static_cast<uint32_t>(GenericKind::kVoid);
}
constexpr bool is_string_view() const {
uint32_t bits = bit_field_ & value_type_impl::kGenericKindMask;
return bits == static_cast<uint32_t>(GenericKind::kStringViewWtf8) ||
bits == static_cast<uint32_t>(GenericKind::kStringViewWtf16) ||
bits == static_cast<uint32_t>(GenericKind::kStringViewIter);
}
constexpr bool is_packed() const {
return bit_field_ == static_cast<uint32_t>(NumericKind::kI8) ||
bit_field_ == static_cast<uint32_t>(NumericKind::kI16) ||
bit_field_ == static_cast<uint32_t>(NumericKind::kF16);
}
constexpr bool is_reference_to(GenericKind type) const {
return is_abstract_ref() && generic_kind() == type;
}
constexpr bool is_defaultable() const {
return is_numeric() || is_nullable();
}
constexpr bool is_uninhabited() const {
if (is_bottom()) return true;
if (is_defaultable()) return false; // Includes all nullable refs.
if (has_index()) return false;
// TODO(jkummerow): Update for exact types.
// Non-nullable references to null types are uninhabitable.
return IsNullKind(generic_kind());
}
constexpr bool use_wasm_null() const {
DCHECK(is_ref());
// TODO(jkummerow): Consider calling {wasm::IsSubtypeOf}.
if (has_index()) return true;
GenericKind ref = generic_kind();
if (ref == GenericKind::kExtern) return false;
if (ref == GenericKind::kExternString) return false;
if (ref == GenericKind::kNoExtern) return false;
return true;
}
constexpr int value_kind_size_log2() const {
DCHECK(!is_sentinel()); // Caller's responsibility.
if (is_ref()) return kTaggedSizeLog2;
constexpr uint8_t kValueKindSizeLog2[] = {
#define VALUE_KIND_SIZE_LOG2(kind, log2Size, ...) log2Size,
FOREACH_NUMERIC_VALUE_TYPE(VALUE_KIND_SIZE_LOG2)
#undef VALUE_KIND_SIZE_LOG2
};
uint32_t index = value_type_impl::ToZeroBasedIndex(numeric_kind());
return kValueKindSizeLog2[index];
}
constexpr int value_kind_size() const {
DCHECK(!is_sentinel()); // Caller's responsibility.
if (is_ref()) return kTaggedSize;
constexpr uint8_t kValueKindSize[] = {
#define ELEM_SIZE_LOG2(kind, log2Size, ...) (1 << log2Size),
FOREACH_NUMERIC_VALUE_TYPE(ELEM_SIZE_LOG2)
#undef ELEM_SIZE_LOG2
};
uint32_t index = value_type_impl::ToZeroBasedIndex(numeric_kind());
return kValueKindSize[index];
}
constexpr int value_kind_full_size() const {
if (is_ref()) {
// Uncompressed pointer size.
return kSystemPointerSize;
}
return value_kind_size();
}
/*************************** Machine-type related ***************************/
constexpr MachineType machine_type() const {
// TODO(jkummerow): Do any callers ever pass a sentinel here?
if (is_sentinel()) return MachineType::None();
if (is_ref()) return MachineType::AnyTagged();
constexpr MachineType kMachineType[] = {
#define MACH_TYPE(kind, log2Size, code, machineType, ...) \
MachineType::machineType(),
FOREACH_NUMERIC_VALUE_TYPE(MACH_TYPE)
#undef MACH_TYPE
};
uint32_t index = value_type_impl::ToZeroBasedIndex(numeric_kind());
return kMachineType[index];
}
constexpr MachineRepresentation machine_representation() const {
return machine_type().representation();
}
/********************************* Encoding *********************************/
// Returns the first byte of this type's representation in the wasm binary
// format. Prioritizes shorthand encodings, e.g. kFuncRefCode.
ValueTypeCode value_type_code() const {
if (is_numeric()) return value_type_code_numeric();
if (is_ref() && encoding_needs_heap_type()) {
return is_nullable() ? kRefNullCode : kRefCode;
}
DCHECK(!has_index()); // Handled by {encoding_needs_heap_type()}.
return value_type_code_generic();
}
// Returns true iff the heap type is needed to encode this type in the wasm
// binary format, taking into account available type shorthands.
constexpr bool encoding_needs_heap_type() const {
if (has_index()) return true;
if (!is_abstract_ref()) return false;
return !is_nullable() || is_shared();
}
constexpr bool encoding_needs_shared() const {
return is_abstract_ref() && is_shared();
}
V8_EXPORT_PRIVATE ValueTypeCode value_type_code_numeric() const;
V8_EXPORT_PRIVATE ValueTypeCode value_type_code_generic() const;
/****************************** Pretty-printing *****************************/
constexpr char short_name() const {
DCHECK(!is_sentinel()); // Caller's responsibility.
if (is_ref()) {
return is_nullable() ? 'n' : 'r';
}
constexpr const char kNumericShortName[] = {
#define SHORT_NAME(kind, log2, code, mtype, shortName, ...) shortName,
FOREACH_NUMERIC_VALUE_TYPE(SHORT_NAME)
#undef SHORT_NAME
};
uint32_t index = value_type_impl::ToZeroBasedIndex(numeric_kind());
return kNumericShortName[index];
}
V8_EXPORT_PRIVATE std::string generic_heaptype_name() const;
V8_EXPORT_PRIVATE std::string name() const;
constexpr bool is_valid() const {
static_assert(static_cast<int>(StandardType::kI32) >
static_cast<int>(StandardType::kAny));
uint32_t payload = value_type_impl::PayloadField::decode(bit_field_);
if (is_numeric()) {
// For numeric types, everything outside the payload is fixed.
if ((bit_field_ & ~value_type_impl::PayloadField::kMask) !=
value_type_impl::kNumericExtraBits) {
return false;
}
return payload >= value_type_impl::kNumberOfGenericKinds &&
payload < value_type_impl::kNumberOfStandardTypes;
}
if (!has_index()) {
// Generic types must be part of the predefined set.
if (payload >= value_type_impl::kNumberOfGenericKinds) return false;
}
// Both generic and indexed ref types must leave the reserved bits free.
return value_type_impl::ReservedField::decode(bit_field_) == 0;
}
constexpr uint32_t raw_bit_field() const { return bit_field_; }
size_t hash_value() const { return bit_field_; }
/************************* Incremental transition ***************************/
// The following methods are deprecated. Their usage should be replaced.
constexpr bool is_reference() const { return is_ref(); }
constexpr bool is_object_reference() const { return is_ref(); }
static constexpr ValueTypeBase Primitive(ValueKind kind) {
switch (kind) {
case kI32:
return ValueTypeBase(NumericKind::kI32);
case kI64:
return ValueTypeBase(NumericKind::kI64);
case kF32:
return ValueTypeBase(NumericKind::kF32);
case kF64:
return ValueTypeBase(NumericKind::kF64);
case kS128:
return ValueTypeBase(NumericKind::kS128);
case kI8:
return ValueTypeBase(NumericKind::kI8);
case kI16:
return ValueTypeBase(NumericKind::kI16);
case kF16:
return ValueTypeBase(NumericKind::kF16);
case kVoid:
return ValueTypeBase(GenericKind::kVoid, kNonNullable,
Exactness::kAnySubtype, false);
case kRef:
case kRefNull:
case kTop:
case kBottom:
UNREACHABLE();
}
}
constexpr ValueKind kind() const {
if (is_numeric()) {
switch (numeric_kind()) {
case NumericKind::kI32:
return kI32;
case NumericKind::kI64:
return kI64;
case NumericKind::kF32:
return kF32;
case NumericKind::kF64:
return kF64;
case NumericKind::kS128:
return kS128;
case NumericKind::kI8:
return kI8;
case NumericKind::kI16:
return kI16;
case NumericKind::kF16:
return kF16;
}
UNREACHABLE();
}
if (is_top()) return kTop;
if (is_bottom()) return kBottom;
if (is_void()) return kVoid;
return is_nullable() ? kRefNull : kRef;
}
constexpr uint32_t raw_heap_representation(bool distinguish_shared) const;
protected:
friend class CanonicalValueType;
friend class HeapType;
friend class IndependentValueType;
friend class ValueType;
explicit constexpr ValueTypeBase(uint32_t bit_field) : bit_field_(bit_field) {
DCHECK(is_valid());
}
explicit constexpr ValueTypeBase(NumericKind kind)
: bit_field_(static_cast<uint32_t>(kind)) {
DCHECK(is_numeric());
}
explicit constexpr ValueTypeBase(GenericKind kind, Nullability nullable,
Exactness exact, bool is_shared)
: bit_field_(static_cast<uint32_t>(kind) |
value_type_impl::IsNullableField::encode(nullable) |
value_type_impl::IsExactField::encode(exact) |
value_type_impl::IsSharedField::encode(is_shared)) {
DCHECK(is_generic());
}
explicit constexpr ValueTypeBase(TypeIndex index, Nullability nullable,
Exactness exact, bool shared,
RefTypeKind ref_type_kind)
: bit_field_(
value_type_impl::TypeKindField::encode(TypeKind::kIndexedRef) |
value_type_impl::IsNullableField::encode(nullable) |
value_type_impl::IsExactField::encode(exact) |
value_type_impl::IsSharedField::encode(shared) |
value_type_impl::RefTypeKindField::encode(ref_type_kind) |
value_type_impl::PayloadField::encode(index.index)) {
// We shouldn't need this, but experience has shown that having an extra
// "safety net" here is valuable every now and then.
CHECK(value_type_impl::PayloadField::is_valid(index.index));
}
constexpr TypeIndex raw_index() const {
DCHECK(has_index());
return TypeIndex(value_type_impl::PayloadField::decode(bit_field_));
}
uint32_t bit_field_;
};
ASSERT_TRIVIALLY_COPYABLE(ValueTypeBase);
// A HeapType is like a ValueType where {is_numeric()} is known to be false
// and the values of {is_nullable()} and {is_exact()} are ignored.
// Uses module-specific type indices.
class HeapType : public ValueTypeBase {
public:
static constexpr HeapType Generic(GenericKind kind, bool shared) {
return HeapType{
ValueTypeBase(kind, kNullable, Exactness::kAnySubtype, shared)};
}
static constexpr HeapType Index(ModuleTypeIndex index, bool shared,
RefTypeKind kind) {
return HeapType{
ValueTypeBase(index, kNullable, Exactness::kAnySubtype, shared, kind)};
}
static constexpr HeapType FromBits(uint32_t bits) {
HeapType type{ValueTypeBase(bits)};
DCHECK(!type.is_numeric());
return type;
}
static constexpr HeapType from_code(uint8_t code, bool is_shared) {
constexpr uint8_t kFirst = ValueTypeCode::kFirstHeapTypeCode;
constexpr uint8_t kLast = ValueTypeCode::kLastHeapTypeCode;
if (code < kFirst || code > kLast) {
return Generic(GenericKind::kBottom, false);
}
constexpr size_t kNumCases = kLast - kFirst + 1;
constexpr std::array<GenericKind, kNumCases> kLookupTable =
base::make_array<kNumCases>([](size_t i) {
switch (i) {
#define CASE(name, code, ...) \
case (k##code##Code - kFirst): \
static_assert(k##code##Code >= kFirst && k##code##Code <= kLast); \
return GenericKind::k##name;
FOREACH_ABSTRACT_TYPE(CASE)
#undef CASE
default:
return GenericKind::kBottom;
}
});
return Generic(kLookupTable[code - kFirst], is_shared);
}
int32_t code() const {
if (has_index()) return static_cast<int32_t>(ref_index().index);
// Value type codes represent the first byte of the LEB128 encoding. To get
// the int32 represented by a code, we sign-extend it from 7 to 32 bits.
return 0xFFFF'FF80 | value_type_code_generic();
}
constexpr ModuleTypeIndex ref_index() const {
return ModuleTypeIndex{raw_index()};
}
constexpr bool operator==(HeapType other) const {
constexpr uint32_t kMask = ~value_type_impl::IsRefField::kMask &
~value_type_impl::IsNullableField::kMask &
~value_type_impl::IsExactField::kMask;
return (bit_field_ & kMask) == (other.bit_field_ & kMask);
}
/****************************** Pretty-printing *****************************/
std::string name() const {
if (has_index()) return std::to_string(raw_index().index);
return generic_heaptype_name();
}
/************************* Incremental transition ***************************/
// The following methods are deprecated. Their usage should be replaced.
enum Representation : uint32_t {
kFunc = kV8MaxWasmTypes, // shorthand: c
kEq, // shorthand: q
kI31, // shorthand: j
kStruct, // shorthand: o
kArray, // shorthand: g
kAny, //
kExtern, // shorthand: a.
kExternString, // Internal type for optimization purposes.
// Subtype of extern.
// Used by the js-builtin-strings proposal.
kExn, //
kString, // shorthand: w.
kStringViewWtf8, // shorthand: x.
kStringViewWtf16, // shorthand: y.
kStringViewIter, // shorthand: z.
kNone, //
kNoFunc, //
kNoExtern, //
kNoExn, //
kCont, // shorthand: k.
kContShared,
kNoCont, // bottom continuation type
kFuncShared,
kEqShared,
kI31Shared,
kStructShared,
kArrayShared,
kAnyShared,
kExternShared,
kExternStringShared,
kExnShared,
kStringShared,
kStringViewWtf8Shared,
kStringViewWtf16Shared,
kStringViewIterShared,
kNoneShared,
kNoFuncShared,
kNoExternShared,
kNoExnShared,
kNoContShared,
// This value is an internal type (not part of the Wasm spec) that
// is the common supertype across all type hierarchies. It should never
// appear in validated Wasm programs, but is used to signify that we don't
// have any information about a particular value and to prevent bugs in our
// typed optimizations, see crbug.com/361652141. Note: kTop is the neutral
// element wrt. to intersection (whereas kBottom is for union), and kBottom
// is indicating unreachable code, which might be used for subsequent
// optimizations, e.g., DCE.
kTop,
// This value is used to represent failures in the parsing of heap types and
// does not correspond to a Wasm heap type. It has to be last in this list.
kBottom
};
constexpr Representation representation() const {
return static_cast<Representation>(raw_heap_representation(true));
}
constexpr bool is_index() const { return has_index(); }
};
// Deprecated.
constexpr uint32_t ValueTypeBase::raw_heap_representation(
bool distinguish_shared) const {
DCHECK(!is_numeric());
if (has_index()) return raw_index().index;
switch (generic_kind()) {
case GenericKind::kTop:
return HeapType::kTop;
case GenericKind::kBottom:
return HeapType::kBottom;
#define CASE(name, ...) \
case GenericKind::k##name: \
return distinguish_shared && is_shared() ? HeapType::k##name##Shared \
: HeapType::k##name;
FOREACH_ABSTRACT_TYPE(CASE)
#undef CASE
case GenericKind::kExternString:
return distinguish_shared && is_shared() ? HeapType::kExternStringShared
: HeapType::kExternString;
case GenericKind::kVoid:
UNREACHABLE();
}
}
class CanonicalValueType;
// Uses module-specific type indices.
class ValueType : public ValueTypeBase {
public:
static constexpr ValueType Primitive(NumericKind kind) {
return ValueType{ValueTypeBase(kind)};
}
static constexpr ValueType Generic(GenericKind kind, Nullability nullable,
bool shared) {
return ValueType{
ValueTypeBase(kind, nullable, Exactness::kAnySubtype, shared)};
}
static constexpr ValueType Ref(ModuleTypeIndex index, bool shared,
RefTypeKind kind) {
return ValueType{ValueTypeBase(index, kNonNullable, Exactness::kAnySubtype,
shared, kind)};
}
static constexpr ValueType Ref(HeapType type) {
return ValueType{type}.AsNonNull();
}
static constexpr ValueType RefNull(ModuleTypeIndex index, bool shared,
RefTypeKind kind) {
return ValueType{
ValueTypeBase(index, kNullable, Exactness::kAnySubtype, shared, kind)};
}
static constexpr ValueType RefNull(HeapType type) {
return ValueType{type}.AsNullable(kNullable);
}
static constexpr ValueType RefMaybeNull(ModuleTypeIndex index,
Nullability nullable, bool shared,
RefTypeKind kind) {
return ValueType{
ValueTypeBase(index, nullable, Exactness::kAnySubtype, shared, kind)};
}
static constexpr ValueType RefMaybeNull(HeapType type, Nullability nullable) {
return ValueType{type}.AsNullable(nullable);
}
static constexpr ValueType FromRawBitField(uint32_t bits) {
return ValueType{ValueTypeBase(bits)};
}
constexpr ValueType AsNonNull() const { return AsNullable(kNonNullable); }
constexpr ValueType AsNullable(Nullability nullable = kNullable) const {
DCHECK(!is_numeric());
return ValueType{ValueTypeBase(
value_type_impl::IsNullableField::update(raw_bit_field(), nullable))};
}
constexpr ValueType AsNonShared() const {
if (!is_ref()) return *this;
return ValueType{ValueTypeBase(
value_type_impl::IsSharedField::update(raw_bit_field(), false))};
}
constexpr ValueType Unpacked() const {
if (bit_field_ == static_cast<uint32_t>(NumericKind::kI8) ||
bit_field_ == static_cast<uint32_t>(NumericKind::kI16)) {
return Primitive(NumericKind::kI32);
}
if (bit_field_ == static_cast<uint32_t>(NumericKind::kF16)) {
return Primitive(NumericKind::kF32);
}
return *this;
}
constexpr CanonicalValueType Canonicalize(CanonicalTypeIndex index) const;
// For incremental transition:
static constexpr ValueType Primitive(ValueKind kind) {
return ValueType{ValueTypeBase::Primitive(kind)};
}
// For incremental transition:
constexpr HeapType::Representation heap_representation() const {
return static_cast<HeapType::Representation>(raw_heap_representation(true));
}
// For incremental transition:
constexpr bool is_reference_to(HeapType::Representation repr) const {
return is_ref() && heap_representation() == repr;
}
// Un-hide the superclass method (which is here to stay):
constexpr bool is_reference_to(GenericKind kind) const {
return ValueTypeBase::is_reference_to(kind);
}
static ValueType For(MachineType type) {
switch (type.representation()) {
case MachineRepresentation::kWord8:
case MachineRepresentation::kWord16:
case MachineRepresentation::kWord32:
return Primitive(NumericKind::kI32);
case MachineRepresentation::kWord64:
return Primitive(NumericKind::kI64);
case MachineRepresentation::kFloat32:
return Primitive(NumericKind::kF32);
case MachineRepresentation::kFloat64:
return Primitive(NumericKind::kF64);
case MachineRepresentation::kTaggedPointer:
return Generic(GenericKind::kAny, kNullable, kNotShared);
case MachineRepresentation::kSimd128:
return Primitive(NumericKind::kS128);
default:
UNREACHABLE();
}
}
constexpr bool operator==(ValueType other) const {
return bit_field_ == other.bit_field_;
}
constexpr HeapType heap_type() const {
DCHECK(!is_numeric());
return HeapType{*this};
}
constexpr ModuleTypeIndex ref_index() const {
return ModuleTypeIndex{raw_index()};
}
};
ASSERT_TRIVIALLY_COPYABLE(ValueType);
// Uses canonicalized type indices.
class CanonicalValueType : public ValueTypeBase {
public:
static constexpr CanonicalValueType Primitive(NumericKind kind) {
return CanonicalValueType{ValueTypeBase(kind)};
}
static constexpr CanonicalValueType Ref(CanonicalTypeIndex index, bool shared,
RefTypeKind kind) {
return CanonicalValueType{ValueTypeBase(
index, kNonNullable, Exactness::kAnySubtype, shared, kind)};
}
static constexpr CanonicalValueType RefNull(CanonicalTypeIndex index,
bool shared, RefTypeKind kind) {
return CanonicalValueType{
ValueTypeBase(index, kNullable, Exactness::kAnySubtype, shared, kind)};
}
// Technically this should take a "CanonicalHeapType", but we don't have
// that class because there's not enough use case for it.
static constexpr CanonicalValueType RefMaybeNull(CanonicalValueType type,
Nullability nullable) {
return CanonicalValueType{
ValueTypeBase(value_type_impl::IsNullableField::update(
type.raw_bit_field(), nullable))};
}
static constexpr CanonicalValueType FromRawBitField(uint32_t bits) {
return CanonicalValueType{ValueTypeBase(bits)};
}
constexpr CanonicalTypeIndex ref_index() const {
return CanonicalTypeIndex{raw_index()};
}
constexpr bool operator==(CanonicalValueType other) const {
return bit_field_ == other.bit_field_;
}
constexpr bool IsFunctionType() const {
return ref_type_kind() == RefTypeKind::kFunction;
}
// For incremental transition.
constexpr HeapType::Representation heap_representation() const {
return static_cast<HeapType::Representation>(raw_heap_representation(true));
}
// For incremental transition.
constexpr HeapType::Representation heap_representation_non_shared() const {
return static_cast<HeapType::Representation>(
raw_heap_representation(false));
}
// For incremental transition:
constexpr bool is_reference_to(HeapType::Representation repr) const {
return is_ref() && heap_representation() == repr;
}
};
ASSERT_TRIVIALLY_COPYABLE(CanonicalValueType);
constexpr CanonicalValueType ValueType::Canonicalize(
CanonicalTypeIndex index) const {
DCHECK(has_index());
return CanonicalValueType{ValueTypeBase(
value_type_impl::PayloadField::update(raw_bit_field(), index.index))};
}
// Non-indexed types. We use them for global constants; they have the
// specific characteristic that they can be compared and implicitly converted
// to both HeapTypes/ValueTypes and CanonicalValueTypes.
class IndependentValueType : public ValueTypeBase {
public:
explicit constexpr IndependentValueType(NumericKind kind)
: ValueTypeBase(kind) {}
constexpr operator ValueType() const { return ValueType{*this}; }
constexpr operator CanonicalValueType() const {
return CanonicalValueType{*this};
}
// Implicit conversions aren't enough when the IndependentValueType is the
// left-hand side.
constexpr bool operator==(ValueType b) const {
return raw_bit_field() == b.raw_bit_field();
}
constexpr bool operator==(CanonicalValueType b) const {
return raw_bit_field() == b.raw_bit_field();
}
protected:
explicit constexpr IndependentValueType(GenericKind kind,
Nullability nullable, bool shared)
: ValueTypeBase(kind, nullable, Exactness::kAnySubtype, shared) {}
};
class IndependentHeapType : public IndependentValueType {
public:
explicit constexpr IndependentHeapType(GenericKind kind,
Nullability nullable = kNullable,
bool shared = false)
: IndependentValueType(kind, nullable, shared) {}
constexpr IndependentHeapType AsNonNull() const {
return IndependentHeapType(generic_kind(), kNonNullable, is_shared());
}
constexpr HeapType heap_type() const { return HeapType{*this}; }
constexpr operator HeapType() const { return HeapType{*this}; }
};
// TODO(jkummerow): See how many of these ValueKind-based helpers we can
// replace with ValueType methods.
constexpr bool is_reference(ValueKind kind) {
return kind == kRef || kind == kRefNull;
}
constexpr bool is_object_reference(ValueKind kind) {
return kind == kRef || kind == kRefNull;
}
constexpr int value_kind_size_log2(ValueKind kind) {
constexpr int8_t kValueKindSizeLog2[] = {
#define VALUE_KIND_SIZE_LOG2(kind, log2Size, ...) log2Size,
FOREACH_VALUE_TYPE(VALUE_KIND_SIZE_LOG2)
#undef VALUE_KIND_SIZE_LOG2
};
int size_log_2 = kValueKindSizeLog2[kind];
DCHECK_LE(0, size_log_2);
return size_log_2;
}
constexpr int value_kind_size(ValueKind kind) {
constexpr int8_t kElementSize[] = {
#define ELEM_SIZE_LOG2(kind, log2Size, ...) \
log2Size == -1 ? -1 : (1 << std::max(0, log2Size)),
FOREACH_VALUE_TYPE(ELEM_SIZE_LOG2)
#undef ELEM_SIZE_LOG2
};
int size = kElementSize[kind];
DCHECK_LT(0, size);
return size;
}
constexpr int value_kind_full_size(ValueKind kind) {
if (is_reference(kind)) {
// Uncompressed pointer size.
return kSystemPointerSize;
}
return value_kind_size(kind);
}
constexpr const char* name(ValueKind kind) {
constexpr const char* kKindName[] = {
#define KIND_NAME(kind, log2Size, code, machineType, shortName, kindName, ...) \
kindName,
FOREACH_VALUE_TYPE(KIND_NAME)
#undef TYPE_NAME
};
return kKindName[kind];
}
// Output operator, useful for DCHECKS and others.
inline std::ostream& operator<<(std::ostream& oss, ValueKind kind) {
return oss << name(kind);
}
constexpr MachineType machine_type(ValueKind kind) {
DCHECK_NE(kBottom, kind);
constexpr MachineType kMachineType[] = {
#define MACH_TYPE(kind, log2Size, code, machineType, ...) \
MachineType::machineType(),
FOREACH_VALUE_TYPE(MACH_TYPE)
#undef MACH_TYPE
};
return kMachineType[kind];
}
constexpr bool is_packed(ValueKind kind) {
return kind == kI8 || kind == kI16 || kind == kF16;
}
constexpr ValueKind unpacked(ValueKind kind) {
return is_packed(kind) ? (kind == kF16 ? kF32 : kI32) : kind;
}
static_assert(sizeof(ValueTypeBase) <= kUInt32Size,
"ValueType is small and can be passed by value");
static_assert(ValueTypeBase::kLastUsedBit < kSmiValueSize,
"ValueType has space to be encoded in a Smi");
// Output operator, useful for DCHECKS and others.
inline std::ostream& operator<<(std::ostream& oss, ValueType type) {
return oss << type.name() << " (raw=0x" << std::hex << type.raw_bit_field()
<< ")" << std::dec;
}
// Precomputed primitive types.
constexpr IndependentValueType kWasmI32{NumericKind::kI32};
constexpr IndependentValueType kWasmI64{NumericKind::kI64};
constexpr IndependentValueType kWasmF32{NumericKind::kF32};
constexpr IndependentValueType kWasmF64{NumericKind::kF64};
constexpr IndependentValueType kWasmS128{NumericKind::kS128};
constexpr IndependentValueType kWasmI8{NumericKind::kI8};
constexpr IndependentValueType kWasmI16{NumericKind::kI16};
constexpr IndependentValueType kWasmF16{NumericKind::kF16};
constexpr IndependentHeapType kWasmVoid{GenericKind::kVoid, kNonNullable};
// The abstract top type (super type of all other types).
constexpr IndependentHeapType kWasmTop{GenericKind::kTop};
constexpr IndependentHeapType kWasmBottom{GenericKind::kBottom, kNonNullable};
// Established reference-type and wasm-gc proposal shorthands.
constexpr IndependentHeapType kWasmFuncRef{GenericKind::kFunc};
constexpr IndependentHeapType kWasmAnyRef{GenericKind::kAny};
constexpr IndependentHeapType kWasmExternRef{GenericKind::kExtern};
constexpr IndependentHeapType kWasmRefExtern{GenericKind::kExtern,
kNonNullable};
constexpr IndependentHeapType kWasmExnRef{GenericKind::kExn};
constexpr IndependentHeapType kWasmEqRef{GenericKind::kEq};
constexpr IndependentHeapType kWasmI31Ref{GenericKind::kI31};
constexpr IndependentHeapType kWasmRefI31{GenericKind::kI31, kNonNullable};
constexpr IndependentHeapType kWasmStructRef{GenericKind::kStruct};
constexpr IndependentHeapType kWasmArrayRef{GenericKind::kArray};
constexpr IndependentHeapType kWasmStringRef{GenericKind::kString};
constexpr IndependentHeapType kWasmRefString{GenericKind::kString,
kNonNullable};
constexpr IndependentHeapType kWasmRefNullExternString{
GenericKind::kExternString};
constexpr IndependentHeapType kWasmRefExternString{GenericKind::kExternString,
kNonNullable};
constexpr IndependentHeapType kWasmStringViewWtf8{GenericKind::kStringViewWtf8,
kNonNullable};
constexpr IndependentHeapType kWasmStringViewWtf16{
GenericKind::kStringViewWtf16, kNonNullable};
constexpr IndependentHeapType kWasmStringViewIter{GenericKind::kStringViewIter,
kNonNullable};
constexpr IndependentHeapType kWasmNullRef{GenericKind::kNone};
constexpr IndependentHeapType kWasmNullExternRef{GenericKind::kNoExtern};
constexpr IndependentHeapType kWasmNullExnRef{GenericKind::kNoExn};
constexpr IndependentHeapType kWasmNullFuncRef{GenericKind::kNoFunc};
constexpr IndependentHeapType kWasmContRef{GenericKind::kCont};
constexpr IndependentHeapType kWasmNullContRef{GenericKind::kNoCont};
#define FOREACH_WASMVALUE_CTYPES(V) \
V(kI32, int32_t) \
V(kI64, int64_t) \
V(kF32, float) \
V(kF64, double) \
V(kS128, Simd128)
using FunctionSig = Signature<ValueType>;
class CanonicalSig : public Signature<CanonicalValueType> {
public:
CanonicalSig(size_t return_count, size_t parameter_count,
const CanonicalValueType* reps)
: Signature<CanonicalValueType>(return_count, parameter_count, reps) {}
class Builder : public SignatureBuilder<CanonicalSig, CanonicalValueType> {
public:
Builder(Zone* zone, size_t return_count, size_t parameter_count)
: SignatureBuilder<CanonicalSig, CanonicalValueType>(zone, return_count,
parameter_count) {}
CanonicalSig* Get() const;
};
uint64_t signature_hash() const { return signature_hash_; }
private:
uint64_t signature_hash_;
};
// This is the special case where comparing module-specific to canonical
// signatures is safe: when they only contain numerical types.
V8_EXPORT_PRIVATE bool EquivalentNumericSig(const CanonicalSig* a,
const FunctionSig* b);
#define FOREACH_LOAD_TYPE(V) \
V(I32, , Int32) \
V(I32, 8S, Int8) \
V(I32, 8U, Uint8) \
V(I32, 16S, Int16) \
V(I32, 16U, Uint16) \
V(I64, , Int64) \
V(I64, 8S, Int8) \
V(I64, 8U, Uint8) \
V(I64, 16S, Int16) \
V(I64, 16U, Uint16) \
V(I64, 32S, Int32) \
V(I64, 32U, Uint32) \
V(F32, F16, Float16) \
V(F32, , Float32) \
V(F64, , Float64) \
V(S128, , Simd128)
class LoadType {
public:
enum LoadTypeValue : uint8_t {
#define DEF_ENUM(type, suffix, ...) k##type##Load##suffix,
FOREACH_LOAD_TYPE(DEF_ENUM)
#undef DEF_ENUM
};
// Allow implicit conversion of the enum value to this wrapper.
// NOLINTNEXTLINE(runtime/explicit)
constexpr LoadType(LoadTypeValue val) : val_(val) {}
constexpr LoadTypeValue value() const { return val_; }
constexpr uint8_t size_log_2() const { return kLoadSizeLog2[val_]; }
constexpr uint8_t size() const { return kLoadSize[val_]; }
constexpr ValueType value_type() const { return kValueType[val_]; }
constexpr MachineType mem_type() const { return kMemType[val_]; }
static LoadType ForValueKind(ValueKind kind, bool is_signed = false) {
switch (kind) {
case kI32:
return kI32Load;
case kI64:
return kI64Load;
case kF32:
return kF32Load;
case kF64:
return kF64Load;
case kS128:
return kS128Load;
case kI8:
return is_signed ? kI32Load8S : kI32Load8U;
case kI16:
return is_signed ? kI32Load16S : kI32Load16U;
case kF16:
return kF32LoadF16;
default:
UNREACHABLE();
}
}
private:
LoadTypeValue val_;
static constexpr uint8_t kLoadSize[] = {
// MSVC wants a static_cast here.
#define LOAD_SIZE(_, __, memtype) \
static_cast<uint8_t>( \
ElementSizeInBytes(MachineType::memtype().representation())),
FOREACH_LOAD_TYPE(LOAD_SIZE)
#undef LOAD_SIZE
};
static constexpr uint8_t kLoadSizeLog2[] = {
// MSVC wants a static_cast here.
#define LOAD_SIZE(_, __, memtype) \
static_cast<uint8_t>( \
ElementSizeLog2Of(MachineType::memtype().representation())),
FOREACH_LOAD_TYPE(LOAD_SIZE)
#undef LOAD_SIZE
};
static constexpr ValueType kValueType[] = {
#define VALUE_TYPE(type, ...) ValueType::Primitive(k##type),
FOREACH_LOAD_TYPE(VALUE_TYPE)
#undef VALUE_TYPE
};
static constexpr MachineType kMemType[] = {
#define MEMTYPE(_, __, memtype) MachineType::memtype(),
FOREACH_LOAD_TYPE(MEMTYPE)
#undef MEMTYPE
};
};
#define FOREACH_STORE_TYPE(V) \
V(I32, , Word32) \
V(I32, 8, Word8) \
V(I32, 16, Word16) \
V(I64, , Word64) \
V(I64, 8, Word8) \
V(I64, 16, Word16) \
V(I64, 32, Word32) \
V(F32, F16, Float16) \
V(F32, , Float32) \
V(F64, , Float64) \
V(S128, , Simd128)
class StoreType {
public:
enum StoreTypeValue : uint8_t {
#define DEF_ENUM(type, suffix, ...) k##type##Store##suffix,
FOREACH_STORE_TYPE(DEF_ENUM)
#undef DEF_ENUM
};
// Allow implicit conversion of the enum value to this wrapper.
// NOLINTNEXTLINE(runtime/explicit)
constexpr StoreType(StoreTypeValue val) : val_(val) {}
constexpr StoreTypeValue value() const { return val_; }
constexpr unsigned size_log_2() const { return kStoreSizeLog2[val_]; }
constexpr unsigned size() const { return 1 << size_log_2(); }
constexpr ValueType value_type() const { return kValueType[val_]; }
constexpr MachineRepresentation mem_rep() const { return kMemRep[val_]; }
static StoreType ForValueKind(ValueKind kind) {
switch (kind) {
case kI32:
return kI32Store;
case kI64:
return kI64Store;
case kF32:
return kF32Store;
case kF64:
return kF64Store;
case kS128:
return kS128Store;
case kI8:
return kI32Store8;
case kI16:
return kI32Store16;
case kF16:
return kF32StoreF16;
default:
UNREACHABLE();
}
}
private:
StoreTypeValue val_;
static constexpr uint8_t kStoreSizeLog2[] = {
// MSVC wants a static_cast here.
#define STORE_SIZE(_, __, memrep) \
static_cast<uint8_t>(ElementSizeLog2Of(MachineRepresentation::k##memrep)),
FOREACH_STORE_TYPE(STORE_SIZE)
#undef STORE_SIZE
};
static constexpr ValueType kValueType[] = {
#define VALUE_TYPE(type, ...) ValueType::Primitive(k##type),
FOREACH_STORE_TYPE(VALUE_TYPE)
#undef VALUE_TYPE
};
static constexpr MachineRepresentation kMemRep[] = {
#define MEMREP(_, __, memrep) MachineRepresentation::k##memrep,
FOREACH_STORE_TYPE(MEMREP)
#undef MEMREP
};
};
std::optional<wasm::ValueKind> WasmReturnTypeFromSignature(
const CanonicalSig* wasm_signature);
// Lowers a signature for 32 bit platforms by replacing i64 parameters and
// returns with two i32s each.
V8_EXPORT_PRIVATE const wasm::FunctionSig* GetI32Sig(
Zone* zone, const wasm::FunctionSig* sig);
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_VALUE_TYPE_H_