// 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_ #include "src/base/bit-field.h" #include "src/codegen/machine-type.h" #include "src/wasm/wasm-constants.h" namespace v8 { namespace internal { template <typename T> class Signature; namespace wasm { // Type for holding simd values, defined in wasm-value.h. class Simd128; // Type lattice: Given a fixed struct type S, the following lattice // defines the subtyping relation among types: // For every two types connected by a line, the top type is a // (direct) subtype of the bottom type. // // AnyRef // / \ // / EqRef // / / \ // FuncRef ExnRef OptRef(S) // \ | / \ // I32 I64 F32 F64 NullRef Ref(S) // \ \ \ \ | / // ---------------------- Bottom --------- // Format: kind, log2Size, code, machineType, shortName, typeName // // Some of these types are from proposals that are not standardized yet: // - "ref" types per https://github.com/WebAssembly/function-references // - "optref"/"eqref" per https://github.com/WebAssembly/gc // // TODO(7748): Extend this with struct and function subtyping. // Keep up to date with funcref vs. anyref subtyping. #define FOREACH_VALUE_TYPE(V) \ V(Stmt, -1, Void, None, 'v', "<stmt>") \ 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', "s128") \ V(AnyRef, kSystemPointerSizeLog2, AnyRef, TaggedPointer, 'r', "anyref") \ V(FuncRef, kSystemPointerSizeLog2, FuncRef, TaggedPointer, 'a', "funcref") \ V(NullRef, kSystemPointerSizeLog2, NullRef, TaggedPointer, 'n', "nullref") \ V(ExnRef, kSystemPointerSizeLog2, ExnRef, TaggedPointer, 'e', "exn") \ V(Ref, kSystemPointerSizeLog2, Ref, TaggedPointer, '*', "ref") \ V(OptRef, kSystemPointerSizeLog2, OptRef, TaggedPointer, 'o', "optref") \ V(EqRef, kSystemPointerSizeLog2, EqRef, TaggedPointer, 'q', "eqref") \ V(Bottom, -1, Void, None, '*', "<bot>") class ValueType { public: enum Kind : uint8_t { #define DEF_ENUM(kind, ...) k##kind, FOREACH_VALUE_TYPE(DEF_ENUM) #undef DEF_ENUM }; constexpr bool has_immediate() const { return kind() == kRef || kind() == kOptRef; } constexpr ValueType() : bit_field_(KindField::encode(kStmt)) {} explicit constexpr ValueType(Kind kind) : bit_field_(KindField::encode(kind)) { #if V8_HAS_CXX14_CONSTEXPR DCHECK(!has_immediate()); #endif } constexpr ValueType(Kind kind, uint32_t ref_index) : bit_field_(KindField::encode(kind) | RefIndexField::encode(ref_index)) { #if V8_HAS_CXX14_CONSTEXPR DCHECK(has_immediate()); #endif } constexpr Kind kind() const { return KindField::decode(bit_field_); } constexpr uint32_t ref_index() const { #if V8_HAS_CXX14_CONSTEXPR DCHECK(has_immediate()); #endif return RefIndexField::decode(bit_field_); } constexpr int element_size_log2() const { #if V8_HAS_CXX14_CONSTEXPR DCHECK_NE(kStmt, kind()); DCHECK_NE(kBottom, kind()); #endif constexpr int kElementSizeLog2[] = { #define ELEM_SIZE_LOG2(kind, log2Size, ...) log2Size, FOREACH_VALUE_TYPE(ELEM_SIZE_LOG2) #undef ELEM_SIZE_LOG2 }; return kElementSizeLog2[kind()]; } constexpr int element_size_bytes() const { return 1 << element_size_log2(); } constexpr bool operator==(ValueType other) const { return bit_field_ == other.bit_field_; } constexpr bool operator!=(ValueType other) const { return bit_field_ != other.bit_field_; } // TODO(7748): Extend this with struct and function subtyping. // Keep up to date with funcref vs. anyref subtyping. constexpr bool IsSubTypeOf(ValueType other) const { return (*this == other) || (other.kind() == kAnyRef && IsReferenceType()) || (kind() == kNullRef && other.kind() != kRef && other.IsReferenceType()) || (other.kind() == kEqRef && (kind() == kExnRef || kind() == kOptRef || kind() == kRef)) || (kind() == kRef && other.kind() == kOptRef && ref_index() == other.ref_index()); } constexpr bool IsReferenceType() const { return kind() == kAnyRef || kind() == kFuncRef || kind() == kNullRef || kind() == kExnRef || kind() == kRef || kind() == kOptRef || kind() == kEqRef; } // TODO(7748): Extend this with struct and function subtyping. // Keep up to date with funcref vs. anyref subtyping. static ValueType CommonSubType(ValueType a, ValueType b) { if (a == b) return a; // The only sub type of any value type is {bot}. if (!a.IsReferenceType() || !b.IsReferenceType()) { return ValueType(kBottom); } if (a.IsSubTypeOf(b)) return a; if (b.IsSubTypeOf(a)) return b; // {a} and {b} are not each other's subtype. // If one of them is not nullable, their greatest subtype is bottom, // otherwise null. if (a.kind() == kRef || b.kind() == kRef) return ValueType(kBottom); return ValueType(kNullRef); } constexpr ValueTypeCode value_type_code() const { #if V8_HAS_CXX14_CONSTEXPR DCHECK_NE(kBottom, kind()); #endif constexpr ValueTypeCode kValueTypeCode[] = { #define TYPE_CODE(kind, log2Size, code, ...) kLocal##code, FOREACH_VALUE_TYPE(TYPE_CODE) #undef TYPE_CODE }; return kValueTypeCode[kind()]; } constexpr MachineType machine_type() const { #if V8_HAS_CXX14_CONSTEXPR DCHECK_NE(kBottom, kind()); #endif constexpr MachineType kMachineType[] = { #define MACH_TYPE(kind, log2Size, code, machineType, ...) \ MachineType::machineType(), FOREACH_VALUE_TYPE(MACH_TYPE) #undef MACH_TYPE }; return kMachineType[kind()]; } constexpr MachineRepresentation machine_representation() const { return machine_type().representation(); } static ValueType For(MachineType type) { switch (type.representation()) { case MachineRepresentation::kWord8: case MachineRepresentation::kWord16: case MachineRepresentation::kWord32: return ValueType(kI32); case MachineRepresentation::kWord64: return ValueType(kI64); case MachineRepresentation::kFloat32: return ValueType(kF32); case MachineRepresentation::kFloat64: return ValueType(kF64); case MachineRepresentation::kTaggedPointer: return ValueType(kAnyRef); case MachineRepresentation::kSimd128: return ValueType(kS128); default: UNREACHABLE(); } } constexpr char short_name() const { constexpr char kShortName[] = { #define SHORT_NAME(kind, log2Size, code, machineType, shortName, ...) shortName, FOREACH_VALUE_TYPE(SHORT_NAME) #undef SHORT_NAME }; return kShortName[kind()]; } constexpr const char* type_name() const { constexpr const char* kTypeName[] = { #define TYPE_NAME(kind, log2Size, code, machineType, shortName, typeName, ...) \ typeName, FOREACH_VALUE_TYPE(TYPE_NAME) #undef TYPE_NAME }; return kTypeName[kind()]; } private: using KindField = base::BitField<Kind, 0, 8>; using RefIndexField = base::BitField<uint32_t, 8, 24>; uint32_t bit_field_; }; static_assert(sizeof(ValueType) <= kUInt32Size, "ValueType is small and can be passed by value"); inline size_t hash_value(ValueType type) { return static_cast<size_t>(type.kind()); } // Output operator, useful for DCHECKS and others. inline std::ostream& operator<<(std::ostream& oss, ValueType type) { return oss << type.type_name(); } constexpr ValueType kWasmI32 = ValueType(ValueType::kI32); constexpr ValueType kWasmI64 = ValueType(ValueType::kI64); constexpr ValueType kWasmF32 = ValueType(ValueType::kF32); constexpr ValueType kWasmF64 = ValueType(ValueType::kF64); constexpr ValueType kWasmAnyRef = ValueType(ValueType::kAnyRef); constexpr ValueType kWasmEqRef = ValueType(ValueType::kEqRef); constexpr ValueType kWasmExnRef = ValueType(ValueType::kExnRef); constexpr ValueType kWasmFuncRef = ValueType(ValueType::kFuncRef); constexpr ValueType kWasmNullRef = ValueType(ValueType::kNullRef); constexpr ValueType kWasmS128 = ValueType(ValueType::kS128); constexpr ValueType kWasmStmt = ValueType(ValueType::kStmt); constexpr ValueType kWasmBottom = ValueType(ValueType::kBottom); #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>; #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, , 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. constexpr LoadType(LoadTypeValue val) // NOLINT(runtime/explicit) : val_(val) {} constexpr LoadTypeValue value() const { return val_; } constexpr unsigned size_log_2() const { return kLoadSizeLog2[val_]; } constexpr unsigned size() const { return 1 << size_log_2(); } constexpr ValueType value_type() const { return kValueType[val_]; } constexpr MachineType mem_type() const { return kMemType[val_]; } static LoadType ForValueType(ValueType type) { switch (type.kind()) { case ValueType::kI32: return kI32Load; case ValueType::kI64: return kI64Load; case ValueType::kF32: return kF32Load; case ValueType::kF64: return kF64Load; case ValueType::kS128: return kS128Load; default: UNREACHABLE(); } } private: const LoadTypeValue val_; 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(ValueType::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, , 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 convertion of the enum value to this wrapper. constexpr StoreType(StoreTypeValue val) // NOLINT(runtime/explicit) : 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 ForValueType(ValueType type) { switch (type.kind()) { case ValueType::kI32: return kI32Store; case ValueType::kI64: return kI64Store; case ValueType::kF32: return kF32Store; case ValueType::kF64: return kF64Store; case ValueType::kS128: return kS128Store; default: UNREACHABLE(); } } private: const 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(ValueType::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 }; }; } // namespace wasm } // namespace internal } // namespace v8 #endif // V8_WASM_VALUE_TYPE_H_