dart-sdk/runtime/vm/class_id.h
Tess Strickland dfc1b6b16b [vm] Store the number of used inputs in SubtypeTestCaches.
Previously, SlowTypeTestStub would use the incoming instance and
destination type to determine which `SubtypeNTestCacheStub` to call,
where `N` is the number of inputs checked for each entry. The code
for generating `InstanceOf` instructions performs similar work,
inlining some checks and then falling back to checking and updating
an appropriate `SubtypeNTestCacheStub` if the inlined checks fail.

Meanwhile, the runtime would use all available information to
create a new entry when needed, depending on the `SubtypeNTestCacheStub`
called at runtime to ignore the parts of the entry it didn't need.

To illustrate a situation where the runtime might use more
information to create an entry than needed by the stub, consider
the following class header:

```
  class C<T> extends D
```

If no subtype of `D` requires knowing the specific type arguments for
an instance to determine whether that instance is an instance of `D`,
then the compiler can generate a `Subtype1TestCacheStub` call, where
only the class id of the instance is checked.

However, in the runtime, when updating the cache, the runtime will
create an entry that includes the specific type arguments used to
instantiate `C` when the initial cache miss occurred, as the runtime
does not know how many inputs the code that checks that SubtypeTestCache
uses. If the cache has grown to the point that it is hash-based, that
means the runtime uses the instance type arguments of the first cache
miss to determine the correct place to create a new entry.

When the stub only performs a linear search over the cache contents,
this works, as every entry is checked until a matching entry is found,
and thus the stub can ignore unneeded information. However, if a cache
is hash-based, this doesn't work: if the first miss was an instance
of `C<int>`, then the runtime includes all that information in a new
entry. This means the stub has to calculate the instance type
arguments, which it did not have to do before, and if a subsequent
check uses different instance type arguments (e.g., an instance of
`C<String>`), the result may still be a cache miss, and the runtime
adds another entry which has the same information as the previous
entry as far as the stub was originally concerned.

Thus, to limit itself to considering only the inputs used by the
appropriate `SubtypeNTestCacheStub`, the runtime needs to be told how
many inputs are used. This CL adds an additional field to
`SubtypeTestCache`s which contains the number of used inputs, and
changes the places where `SubtypeTestCache`s are allocated in the
flow graph compiler and the `DRT_TypeCheck` runtime entry to pass the
appropriate number of inputs. To ensure that the runtime and stubs
agree on how many inputs should be used, the `SlowTypeTestStub` now
uses the value of this field to determine which `SubtypeNTestCacheStub`
to call.

TEST=vm/cc/STC_{Linear,Hash}Lookup

Bug: https://github.com/dart-lang/sdk/issues/48345
Change-Id: Ie16f2d12c4c5eae856b668f28178818203662d1c
Cq-Include-Trybots: luci.dart.try:vm-aot-linux-debug-simriscv64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-release-x64-try,vm-aot-linux-product-x64-try,vm-aot-linux-release-simarm64-try,vm-aot-linux-release-simarm_x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-aot-dwarf-linux-product-x64-try,vm-linux-release-ia32-try,vm-linux-debug-x64c-try,vm-linux-debug-x64-try,vm-linux-debug-simriscv64-try,vm-linux-release-simarm64-try,vm-linux-release-simarm-try,vm-mac-release-arm64-try,vm-mac-release-x64-try,vm-tsan-linux-release-x64-try,vm-reload-rollback-linux-release-x64-try,vm-reload-linux-release-x64-try,vm-ffi-qemu-linux-release-arm-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/310180
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
2023-06-21 15:24:30 +00:00

514 lines
25 KiB
C++

// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#ifndef RUNTIME_VM_CLASS_ID_H_
#define RUNTIME_VM_CLASS_ID_H_
// This header defines the list of VM implementation classes and their ids.
//
// Note: we assume that all builds of Dart VM use exactly the same class ids
// for these classes.
#include "platform/assert.h"
#include "vm/globals.h"
namespace dart {
// Large enough to contain the class-id part of the object header. See
// UntaggedObject. Signed to be comparable to intptr_t.
typedef int32_t ClassIdTagType;
static constexpr intptr_t kClassIdTagMax = (1 << 20) - 1;
// Classes that are not subclasses of Instance and only handled by the VM,
// but do not require any special handling other than being a predefined class.
#define CLASS_LIST_INTERNAL_ONLY(V) \
V(Class) \
V(PatchClass) \
V(Function) \
V(TypeParameters) \
V(ClosureData) \
V(FfiTrampolineData) \
V(Field) \
V(Script) \
V(Library) \
V(Namespace) \
V(KernelProgramInfo) \
V(WeakSerializationReference) \
V(WeakArray) \
V(Code) \
V(Instructions) \
V(InstructionsSection) \
V(InstructionsTable) \
V(ObjectPool) \
V(PcDescriptors) \
V(CodeSourceMap) \
V(CompressedStackMaps) \
V(LocalVarDescriptors) \
V(ExceptionHandlers) \
V(Context) \
V(ContextScope) \
V(Sentinel) \
V(SingleTargetCache) \
V(UnlinkedCall) \
V(MonomorphicSmiableCall) \
V(CallSiteData) \
V(ICData) \
V(MegamorphicCache) \
V(SubtypeTestCache) \
V(LoadingUnit) \
V(Error) \
V(ApiError) \
V(LanguageError) \
V(UnhandledException) \
V(UnwindError)
// Classes that are subclasses of Instance and neither part of a specific cid
// grouping like strings, arrays, etc. nor require special handling outside of
// being a predefined class.
#define CLASS_LIST_INSTANCE_SINGLETONS(V) \
V(Instance) \
V(LibraryPrefix) \
V(TypeArguments) \
V(AbstractType) \
V(Type) \
V(FunctionType) \
V(RecordType) \
V(TypeParameter) \
V(FinalizerBase) \
V(Finalizer) \
V(NativeFinalizer) \
V(FinalizerEntry) \
V(Closure) \
V(Number) \
V(Integer) \
V(Smi) \
V(Mint) \
V(Double) \
V(Bool) \
V(Float32x4) \
V(Int32x4) \
V(Float64x2) \
V(Record) \
V(TypedDataBase) \
V(TypedData) \
V(ExternalTypedData) \
V(TypedDataView) \
V(Pointer) \
V(DynamicLibrary) \
V(Capability) \
V(ReceivePort) \
V(SendPort) \
V(StackTrace) \
V(SuspendState) \
V(RegExp) \
V(WeakProperty) \
V(WeakReference) \
V(MirrorReference) \
V(FutureOr) \
V(UserTag) \
V(TransferableTypedData)
#define CLASS_LIST_NO_OBJECT_NOR_STRING_NOR_ARRAY_NOR_MAP(V) \
CLASS_LIST_INTERNAL_ONLY(V) CLASS_LIST_INSTANCE_SINGLETONS(V)
#define CLASS_LIST_MAPS(V) \
V(Map) \
V(ConstMap)
#define CLASS_LIST_SETS(V) \
V(Set) \
V(ConstSet)
#define CLASS_LIST_FIXED_LENGTH_ARRAYS(V) \
V(Array) \
V(ImmutableArray)
#define CLASS_LIST_ARRAYS(V) \
CLASS_LIST_FIXED_LENGTH_ARRAYS(V) \
V(GrowableObjectArray)
#define CLASS_LIST_STRINGS(V) \
V(String) \
V(OneByteString) \
V(TwoByteString) \
V(ExternalOneByteString) \
V(ExternalTwoByteString)
#define CLASS_LIST_TYPED_DATA(V) \
V(Int8Array) \
V(Uint8Array) \
V(Uint8ClampedArray) \
V(Int16Array) \
V(Uint16Array) \
V(Int32Array) \
V(Uint32Array) \
V(Int64Array) \
V(Uint64Array) \
V(Float32Array) \
V(Float64Array) \
V(Float32x4Array) \
V(Int32x4Array) \
V(Float64x2Array)
#define CLASS_LIST_FFI_NUMERIC_FIXED_SIZE(V) \
V(Int8) \
V(Int16) \
V(Int32) \
V(Int64) \
V(Uint8) \
V(Uint16) \
V(Uint32) \
V(Uint64) \
V(Float) \
V(Double)
#define CLASS_LIST_FFI_TYPE_MARKER(V) \
CLASS_LIST_FFI_NUMERIC_FIXED_SIZE(V) \
V(Void) \
V(Handle) \
V(Bool)
#define CLASS_LIST_FFI(V) \
V(NativeFunction) \
CLASS_LIST_FFI_TYPE_MARKER(V) \
V(NativeType) \
V(Struct)
#define DART_CLASS_LIST_TYPED_DATA(V) \
V(Int8) \
V(Uint8) \
V(Uint8Clamped) \
V(Int16) \
V(Uint16) \
V(Int32) \
V(Uint32) \
V(Int64) \
V(Uint64) \
V(Float32) \
V(Float64) \
V(Float32x4) \
V(Int32x4) \
V(Float64x2)
#define CLASS_LIST_FOR_HANDLES(V) \
CLASS_LIST_NO_OBJECT_NOR_STRING_NOR_ARRAY_NOR_MAP(V) \
V(Map) \
V(Set) \
V(Array) \
V(GrowableObjectArray) \
V(String)
#define CLASS_LIST_NO_OBJECT(V) \
CLASS_LIST_NO_OBJECT_NOR_STRING_NOR_ARRAY_NOR_MAP(V) \
CLASS_LIST_MAPS(V) \
CLASS_LIST_SETS(V) \
CLASS_LIST_ARRAYS(V) \
CLASS_LIST_STRINGS(V)
#define CLASS_LIST(V) \
V(Object) \
CLASS_LIST_NO_OBJECT(V)
enum ClassId : intptr_t {
// Illegal class id.
kIllegalCid = 0,
// Pseudo class id for native pointers, the heap should never see an
// object with this class id.
kNativePointer,
// The following entries describes classes for pseudo-objects in the heap
// that should never be reachable from live objects. Free list elements
// maintain the free list for old space, and forwarding corpses are used to
// implement one-way become.
kFreeListElement,
kForwardingCorpse,
// List of Ids for predefined classes.
#define DEFINE_OBJECT_KIND(clazz) k##clazz##Cid,
CLASS_LIST(DEFINE_OBJECT_KIND)
#undef DEFINE_OBJECT_KIND
// clang-format off
#define DEFINE_OBJECT_KIND(clazz) kFfi##clazz##Cid,
CLASS_LIST_FFI(DEFINE_OBJECT_KIND)
#undef DEFINE_OBJECT_KIND
#define DEFINE_OBJECT_KIND(clazz) \
kTypedData##clazz##Cid, \
kTypedData##clazz##ViewCid, \
kExternalTypedData##clazz##Cid, \
kUnmodifiableTypedData##clazz##ViewCid,
CLASS_LIST_TYPED_DATA(DEFINE_OBJECT_KIND)
#undef DEFINE_OBJECT_KIND
kByteDataViewCid,
kUnmodifiableByteDataViewCid,
kByteBufferCid,
// clang-format on
// The following entries do not describe a predefined class, but instead
// are class indexes for pre-allocated instances (Null, dynamic, void, Never).
kNullCid,
kDynamicCid,
kVoidCid,
kNeverCid,
kNumPredefinedCids,
};
// Keep these in sync with the cid numbering above.
const int kTypedDataCidRemainderInternal = 0;
const int kTypedDataCidRemainderView = 1;
const int kTypedDataCidRemainderExternal = 2;
const int kTypedDataCidRemainderUnmodifiable = 3;
// Class Id predicates.
bool IsInternalOnlyClassId(intptr_t index);
bool IsErrorClassId(intptr_t index);
bool IsNumberClassId(intptr_t index);
bool IsIntegerClassId(intptr_t index);
bool IsStringClassId(intptr_t index);
bool IsOneByteStringClassId(intptr_t index);
bool IsExternalStringClassId(intptr_t index);
bool IsBuiltinListClassId(intptr_t index);
bool IsTypeClassId(intptr_t index);
bool IsTypedDataBaseClassId(intptr_t index);
bool IsTypedDataClassId(intptr_t index);
bool IsTypedDataViewClassId(intptr_t index);
bool IsExternalTypedDataClassId(intptr_t index);
bool IsFfiPointerClassId(intptr_t index);
bool IsFfiTypeClassId(intptr_t index);
bool IsFfiDynamicLibraryClassId(intptr_t index);
bool IsInternalVMdefinedClassId(intptr_t index);
bool IsImplicitFieldClassId(intptr_t index);
// Should be used for looping over non-Object internal-only cids.
constexpr intptr_t kFirstInternalOnlyCid = kClassCid;
constexpr intptr_t kLastInternalOnlyCid = kUnwindErrorCid;
// Use the currently surrounding cids to check that no new classes have been
// added to the beginning or end of CLASS_LIST_INTERNAL_ONLY without adjusting
// the above definitions.
COMPILE_ASSERT(kFirstInternalOnlyCid == kObjectCid + 1);
COMPILE_ASSERT(kInstanceCid == kLastInternalOnlyCid + 1);
// Returns true for any class id that either does not correspond to a real
// class, like kIllegalCid or kForwardingCorpse, or that is internal to the VM
// and should not be exposed directly to user code.
inline bool IsInternalOnlyClassId(intptr_t index) {
// Fix the condition below if these become non-contiguous.
COMPILE_ASSERT(kIllegalCid + 1 == kNativePointer &&
kIllegalCid + 2 == kFreeListElement &&
kIllegalCid + 3 == kForwardingCorpse &&
kIllegalCid + 4 == kObjectCid &&
kIllegalCid + 5 == kFirstInternalOnlyCid);
return index <= kLastInternalOnlyCid;
}
// Make sure this function is updated when new Error types are added.
static const ClassId kFirstErrorCid = kErrorCid;
static const ClassId kLastErrorCid = kUnwindErrorCid;
COMPILE_ASSERT(kFirstErrorCid == kErrorCid &&
kApiErrorCid == kFirstErrorCid + 1 &&
kLanguageErrorCid == kFirstErrorCid + 2 &&
kUnhandledExceptionCid == kFirstErrorCid + 3 &&
kUnwindErrorCid == kFirstErrorCid + 4 &&
kLastErrorCid == kUnwindErrorCid &&
// Change if needed for detecting a new error added at the end.
kLastInternalOnlyCid == kLastErrorCid);
inline bool IsErrorClassId(intptr_t index) {
return (index >= kFirstErrorCid && index <= kLastErrorCid);
}
inline bool IsConcreteTypeClassId(intptr_t index) {
// Make sure to update when new AbstractType subclasses are added.
COMPILE_ASSERT(kFunctionTypeCid == kTypeCid + 1 &&
kRecordTypeCid == kTypeCid + 2 &&
kTypeParameterCid == kTypeCid + 3);
return (index >= kTypeCid && index <= kTypeParameterCid);
}
inline bool IsNumberClassId(intptr_t index) {
// Make sure this function is updated when new Number types are added.
COMPILE_ASSERT(kIntegerCid == kNumberCid + 1 && kSmiCid == kNumberCid + 2 &&
kMintCid == kNumberCid + 3 && kDoubleCid == kNumberCid + 4);
return (index >= kNumberCid && index <= kDoubleCid);
}
inline bool IsIntegerClassId(intptr_t index) {
// Make sure this function is updated when new Integer types are added.
COMPILE_ASSERT(kSmiCid == kIntegerCid + 1 && kMintCid == kIntegerCid + 2);
return (index >= kIntegerCid && index <= kMintCid);
}
// Make sure this check is updated when new StringCid types are added.
COMPILE_ASSERT(kOneByteStringCid == kStringCid + 1 &&
kTwoByteStringCid == kStringCid + 2 &&
kExternalOneByteStringCid == kStringCid + 3 &&
kExternalTwoByteStringCid == kStringCid + 4);
inline bool IsStringClassId(intptr_t index) {
return (index >= kStringCid && index <= kExternalTwoByteStringCid);
}
inline bool IsOneByteStringClassId(intptr_t index) {
return (index == kOneByteStringCid || index == kExternalOneByteStringCid);
}
inline bool IsExternalStringClassId(intptr_t index) {
return (index == kExternalOneByteStringCid ||
index == kExternalTwoByteStringCid);
}
inline bool IsArrayClassId(intptr_t index) {
COMPILE_ASSERT(kImmutableArrayCid == kArrayCid + 1);
COMPILE_ASSERT(kGrowableObjectArrayCid == kArrayCid + 2);
return (index >= kArrayCid && index <= kGrowableObjectArrayCid);
}
inline bool IsBuiltinListClassId(intptr_t index) {
// Make sure this function is updated when new builtin List types are added.
return (IsArrayClassId(index) || IsTypedDataBaseClassId(index) ||
(index == kByteBufferCid));
}
inline bool IsTypeClassId(intptr_t index) {
// Only Type, FunctionType and RecordType can be encountered as instance
// types at runtime.
return index == kTypeCid || index == kFunctionTypeCid ||
index == kRecordTypeCid;
}
inline bool IsTypedDataBaseClassId(intptr_t index) {
// Make sure this is updated when new TypedData types are added.
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 4 == kTypedDataUint8ArrayCid);
return index >= kTypedDataInt8ArrayCid && index < kByteDataViewCid;
}
inline bool IsTypedDataClassId(intptr_t index) {
// Make sure this is updated when new TypedData types are added.
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 4 == kTypedDataUint8ArrayCid);
return IsTypedDataBaseClassId(index) && ((index - kTypedDataInt8ArrayCid) %
4) == kTypedDataCidRemainderInternal;
}
inline bool IsTypedDataViewClassId(intptr_t index) {
// Make sure this is updated when new TypedData types are added.
COMPILE_ASSERT(kTypedDataInt8ArrayViewCid + 4 == kTypedDataUint8ArrayViewCid);
const bool is_byte_data_view = index == kByteDataViewCid;
return is_byte_data_view ||
(IsTypedDataBaseClassId(index) &&
((index - kTypedDataInt8ArrayCid) % 4) == kTypedDataCidRemainderView);
}
inline bool IsExternalTypedDataClassId(intptr_t index) {
// Make sure this is updated when new TypedData types are added.
COMPILE_ASSERT(kExternalTypedDataInt8ArrayCid + 4 ==
kExternalTypedDataUint8ArrayCid);
return IsTypedDataBaseClassId(index) && ((index - kTypedDataInt8ArrayCid) %
4) == kTypedDataCidRemainderExternal;
}
inline bool IsUnmodifiableTypedDataViewClassId(intptr_t index) {
// Make sure this is updated when new TypedData types are added.
COMPILE_ASSERT(kExternalTypedDataInt8ArrayCid + 4 ==
kExternalTypedDataUint8ArrayCid);
const bool is_byte_data_view = index == kUnmodifiableByteDataViewCid;
return is_byte_data_view || (IsTypedDataBaseClassId(index) &&
((index - kTypedDataInt8ArrayCid) % 4) ==
kTypedDataCidRemainderUnmodifiable);
}
inline bool ShouldHaveImmutabilityBitSet(intptr_t index) {
return IsUnmodifiableTypedDataViewClassId(index) || IsStringClassId(index) ||
index == kMintCid || index == kNeverCid || index == kSentinelCid ||
index == kStackTraceCid || index == kDoubleCid ||
index == kFloat32x4Cid || index == kFloat64x2Cid ||
index == kInt32x4Cid || index == kSendPortCid ||
index == kCapabilityCid || index == kRegExpCid || index == kBoolCid ||
index == kNullCid;
}
inline bool IsFfiTypeClassId(intptr_t index) {
switch (index) {
case kPointerCid:
case kFfiNativeFunctionCid:
#define CASE_FFI_CID(name) case kFfi##name##Cid:
CLASS_LIST_FFI_TYPE_MARKER(CASE_FFI_CID)
#undef CASE_FFI_CID
return true;
default:
return false;
}
UNREACHABLE();
}
inline bool IsFfiPredefinedClassId(classid_t class_id) {
switch (class_id) {
case kPointerCid:
case kDynamicLibraryCid:
#define CASE_FFI_CID(name) case kFfi##name##Cid:
CLASS_LIST_FFI(CASE_FFI_CID)
#undef CASE_FFI_CID
return true;
default:
return false;
}
UNREACHABLE();
}
inline bool IsFfiPointerClassId(intptr_t index) {
return index == kPointerCid;
}
inline bool IsFfiDynamicLibraryClassId(intptr_t index) {
return index == kDynamicLibraryCid;
}
inline bool IsInternalVMdefinedClassId(intptr_t index) {
return ((index < kNumPredefinedCids) && !IsImplicitFieldClassId(index));
}
// This is a set of classes that are not Dart classes whose representation
// is defined by the VM but are used in the VM code by computing the
// implicit field offsets of the various fields in the dart object.
inline bool IsImplicitFieldClassId(intptr_t index) {
return index == kByteBufferCid;
}
// Make sure the following checks are updated when adding new TypedData types.
// Ensure that each typed data type comes in internal/view/external variants
// next to each other.
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 1 == kTypedDataInt8ArrayViewCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 2 == kExternalTypedDataInt8ArrayCid);
// Ensure the order of the typed data members in 3-step.
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 1 * 4 == kTypedDataUint8ArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 2 * 4 ==
kTypedDataUint8ClampedArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 3 * 4 == kTypedDataInt16ArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 4 * 4 == kTypedDataUint16ArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 5 * 4 == kTypedDataInt32ArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 6 * 4 == kTypedDataUint32ArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 7 * 4 == kTypedDataInt64ArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 8 * 4 == kTypedDataUint64ArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 9 * 4 == kTypedDataFloat32ArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 10 * 4 == kTypedDataFloat64ArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 11 * 4 == kTypedDataFloat32x4ArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 12 * 4 == kTypedDataInt32x4ArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 13 * 4 == kTypedDataFloat64x2ArrayCid);
COMPILE_ASSERT(kTypedDataInt8ArrayCid + 14 * 4 == kByteDataViewCid);
COMPILE_ASSERT(kByteBufferCid + 1 == kNullCid);
} // namespace dart
#endif // RUNTIME_VM_CLASS_ID_H_