Commit 1a815e44 authored by Seth Brenith's avatar Seth Brenith Committed by Commit Bot

[tools][torque]Improve postmortem API behavior on strings

This change adds the indexed field for the characters in the definition
of sequential string types, and introduces support for recognizing the
various specific string types in v8_debug_helper. In an attempt to
avoid duplicating info about string instance types, it also refactors
String::Get so that StringShape (a simple class usable by postmortem
tools) can dispatch using a class that defines behaviors for each
concrete type.

Bug: v8:9376
Change-Id: Id0653040f6decddc004c73f8fe93d2187828c2c6
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1735795
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Reviewed-by: 's avatarMichael Stanton <mvstanton@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63352}
parent a9ab7912
......@@ -56,6 +56,8 @@ type uint16 extends uint31
type int8 extends int16 generates 'TNode<Int8T>' constexpr 'int8_t';
type uint8 extends uint16
generates 'TNode<Uint8T>' constexpr 'uint8_t';
type char8 extends int8 constexpr 'char';
type char16 extends uint16 constexpr 'char16_t';
type int64 generates 'TNode<Int64T>' constexpr 'int64_t';
type intptr generates 'TNode<IntPtrT>' constexpr 'intptr_t';
type uintptr generates 'TNode<UintPtrT>' constexpr 'uintptr_t';
......@@ -136,9 +138,11 @@ extern class SeqString extends String {
}
@generateCppClass
extern class SeqOneByteString extends SeqString {
chars[length]: char8;
}
@generateCppClass
extern class SeqTwoByteString extends SeqString {
chars[length]: char16;
}
@generateCppClass
......
......@@ -137,6 +137,65 @@ STATIC_ASSERT((kExternalStringTag | kTwoByteStringTag) ==
STATIC_ASSERT(v8::String::TWO_BYTE_ENCODING == kTwoByteStringTag);
template <typename TDispatcher, typename TResult, typename... TArgs>
inline TResult StringShape::DispatchToSpecificTypeWithoutCast(TArgs&&... args) {
switch (full_representation_tag()) {
case kSeqStringTag | kOneByteStringTag:
return TDispatcher::HandleSeqOneByteString(std::forward<TArgs>(args)...);
case kSeqStringTag | kTwoByteStringTag:
return TDispatcher::HandleSeqTwoByteString(std::forward<TArgs>(args)...);
case kConsStringTag | kOneByteStringTag:
case kConsStringTag | kTwoByteStringTag:
return TDispatcher::HandleConsString(std::forward<TArgs>(args)...);
case kExternalStringTag | kOneByteStringTag:
return TDispatcher::HandleExternalOneByteString(
std::forward<TArgs>(args)...);
case kExternalStringTag | kTwoByteStringTag:
return TDispatcher::HandleExternalTwoByteString(
std::forward<TArgs>(args)...);
case kSlicedStringTag | kOneByteStringTag:
case kSlicedStringTag | kTwoByteStringTag:
return TDispatcher::HandleSlicedString(std::forward<TArgs>(args)...);
case kThinStringTag | kOneByteStringTag:
case kThinStringTag | kTwoByteStringTag:
return TDispatcher::HandleThinString(std::forward<TArgs>(args)...);
default:
return TDispatcher::HandleInvalidString(std::forward<TArgs>(args)...);
}
}
// All concrete subclasses of String (leaves of the inheritance tree).
#define STRING_CLASS_TYPES(V) \
V(SeqOneByteString) \
V(SeqTwoByteString) \
V(ConsString) \
V(ExternalOneByteString) \
V(ExternalTwoByteString) \
V(SlicedString) \
V(ThinString)
template <typename TDispatcher, typename TResult, typename... TArgs>
inline TResult StringShape::DispatchToSpecificType(String str,
TArgs&&... args) {
class CastingDispatcher : public AllStatic {
public:
#define DEFINE_METHOD(Type) \
static inline TResult Handle##Type(String str, TArgs&&... args) { \
return TDispatcher::Handle##Type(Type::cast(str), \
std::forward<TArgs>(args)...); \
}
STRING_CLASS_TYPES(DEFINE_METHOD)
#undef DEFINE_METHOD
static inline TResult HandleInvalidString(String str, TArgs&&... args) {
return TDispatcher::HandleInvalidString(str,
std::forward<TArgs>(args)...);
}
};
return DispatchToSpecificTypeWithoutCast<CastingDispatcher, TResult>(
str, std::forward<TArgs>(args)...);
}
DEF_GETTER(String, IsOneByteRepresentation, bool) {
uint32_t type = map(isolate).instance_type();
return (type & kStringEncodingMask) == kOneByteStringTag;
......@@ -340,29 +399,22 @@ Handle<String> String::Flatten(Isolate* isolate, Handle<String> string,
uint16_t String::Get(int index) {
DCHECK(index >= 0 && index < length());
switch (StringShape(*this).full_representation_tag()) {
case kSeqStringTag | kOneByteStringTag:
return SeqOneByteString::cast(*this).Get(index);
case kSeqStringTag | kTwoByteStringTag:
return SeqTwoByteString::cast(*this).Get(index);
case kConsStringTag | kOneByteStringTag:
case kConsStringTag | kTwoByteStringTag:
return ConsString::cast(*this).Get(index);
case kExternalStringTag | kOneByteStringTag:
return ExternalOneByteString::cast(*this).Get(index);
case kExternalStringTag | kTwoByteStringTag:
return ExternalTwoByteString::cast(*this).Get(index);
case kSlicedStringTag | kOneByteStringTag:
case kSlicedStringTag | kTwoByteStringTag:
return SlicedString::cast(*this).Get(index);
case kThinStringTag | kOneByteStringTag:
case kThinStringTag | kTwoByteStringTag:
return ThinString::cast(*this).Get(index);
default:
break;
class StringGetDispatcher : public AllStatic {
public:
#define DEFINE_METHOD(Type) \
static inline uint16_t Handle##Type(Type str, int index) { \
return str.Get(index); \
}
STRING_CLASS_TYPES(DEFINE_METHOD)
#undef DEFINE_METHOD
static inline uint16_t HandleInvalidString(String str, int index) {
UNREACHABLE();
}
};
UNREACHABLE();
return StringShape(*this)
.DispatchToSpecificType<StringGetDispatcher, uint16_t>(*this, index);
}
void String::Set(int index, uint16_t value) {
......
......@@ -61,6 +61,13 @@ class StringShape {
inline void invalidate() {}
#endif
// Run different behavior for each concrete string class type, as defined by
// the dispatcher.
template <typename TDispatcher, typename TResult, typename... TArgs>
inline TResult DispatchToSpecificTypeWithoutCast(TArgs&&... args);
template <typename TDispatcher, typename TResult, typename... TArgs>
inline TResult DispatchToSpecificType(String str, TArgs&&... args);
private:
uint32_t type_;
#ifdef DEBUG
......
......@@ -87,16 +87,32 @@ void GenerateClassDebugReader(const ClassType& type, std::ostream& h_contents,
std::string indexed_field_info;
if (field.index) {
if ((*field.index)->name_and_type.type != TypeOracle::GetSmiType()) {
Error("Non-SMI values are not (yet) supported as indexes.");
const Type* index_type = (*field.index)->name_and_type.type;
std::string index_type_name;
std::string index_value;
if (index_type == TypeOracle::GetSmiType()) {
index_type_name = "uintptr_t";
index_value =
"i::PlatformSmiTagging::SmiToInt(indexed_field_count.value)";
} else if (!index_type->IsSubtypeOf(TypeOracle::GetTaggedType())) {
const Type* constexpr_index = index_type->ConstexprVersion();
if (constexpr_index == nullptr) {
Error("Type '", index_type->ToString(),
"' requires a constexpr representation");
continue;
}
index_type_name = constexpr_index->GetGeneratedTypeName();
index_value = "indexed_field_count.value";
} else {
Error("Unsupported index type: ", index_type);
continue;
}
get_props_impl << " Value<uintptr_t> indexed_field_count = Get"
get_props_impl << " Value<" << index_type_name
<< "> indexed_field_count = Get"
<< CamelifyString((*field.index)->name_and_type.name)
<< "Value(accessor);\n";
indexed_field_info =
", i::PlatformSmiTagging::SmiToInt(indexed_field_count.value), "
"GetArrayKind(indexed_field_count.validity)";
", " + index_value + ", GetArrayKind(indexed_field_count.validity)";
}
get_props_impl
<< " result.push_back(v8::base::make_unique<ObjectProperty>(\""
......
......@@ -2738,15 +2738,31 @@ class FieldOffsetsGenerator {
public:
explicit FieldOffsetsGenerator(const ClassType* type) : type_(type) {}
virtual void WriteField(const Field& f) = 0;
virtual void WriteField(const Field& f, const std::string& size_string) = 0;
virtual void WriteMarker(const std::string& marker) = 0;
virtual void BeginPrivateOffsets() = 0;
virtual ~FieldOffsetsGenerator() { CHECK(is_finished_); }
void RecordOffsetFor(const Field& f) {
CHECK(!is_finished_);
UpdateSection(f);
WriteField(f);
// We don't know statically how much space an indexed field takes, so report
// it as zero.
std::string size_string = "0";
if (!f.index.has_value()) {
size_t field_size;
std::tie(field_size, size_string) = f.GetFieldSizeInformation();
}
WriteField(f, size_string);
// Offsets for anything after an indexed field are likely to cause
// confusion, because the indexed field itself takes up a variable amount of
// space. We could not emit them at all, but that might allow an inherited
// kSize to be accessible (and wrong), so we emit them as private.
if (f.index.has_value()) {
BeginPrivateOffsets();
}
}
void Finish() {
......@@ -2827,17 +2843,16 @@ class MacroFieldOffsetsGenerator : public FieldOffsetsGenerator {
out_ << "TORQUE_GENERATED_" << CapifyStringWithUnderscores(type_->name())
<< "_FIELDS(V) \\\n";
}
virtual void WriteField(const Field& f) {
size_t field_size;
std::string size_string;
std::string machine_type;
std::tie(field_size, size_string) = f.GetFieldSizeInformation();
void WriteField(const Field& f, const std::string& size_string) override {
out_ << "V(k" << CamelifyString(f.name_and_type.name) << "Offset, "
<< size_string << ") \\\n";
}
virtual void WriteMarker(const std::string& marker) {
void WriteMarker(const std::string& marker) override {
out_ << "V(" << marker << ", 0) \\\n";
}
void BeginPrivateOffsets() override {
// Can't do anything meaningful here in the macro generator.
}
private:
std::ostream& out_;
......@@ -2950,11 +2965,7 @@ class ClassFieldOffsetGenerator : public FieldOffsetsGenerator {
: FieldOffsetsGenerator(type),
hdr_(header),
previous_field_end_("P::kHeaderSize") {}
virtual void WriteField(const Field& f) {
size_t field_size;
std::string size_string;
std::string machine_type;
std::tie(field_size, size_string) = f.GetFieldSizeInformation();
void WriteField(const Field& f, const std::string& size_string) override {
std::string field = "k" + CamelifyString(f.name_and_type.name) + "Offset";
std::string field_end = field + "End";
hdr_ << " static constexpr int " << field << " = " << previous_field_end_
......@@ -2963,10 +2974,15 @@ class ClassFieldOffsetGenerator : public FieldOffsetsGenerator {
<< size_string << " - 1;\n";
previous_field_end_ = field_end + " + 1";
}
virtual void WriteMarker(const std::string& marker) {
void WriteMarker(const std::string& marker) override {
hdr_ << " static constexpr int " << marker << " = " << previous_field_end_
<< ";\n";
}
void BeginPrivateOffsets() override {
// The following section must re-establish public mode (currently done by
// GenerateClassConstructors).
hdr_ << " private:\n";
}
private:
std::ostream& hdr_;
......@@ -3022,7 +3038,7 @@ void CppClassGenerator::GenerateClass() {
hdr_ << " static_assert(std::is_same<" << super_->name() << ", P>::value,\n"
<< " \"Pass in " << super_->name()
<< " as second template parameter for " << gen_name_ << ".\");\n";
hdr_ << "public: \n";
hdr_ << " public: \n";
hdr_ << " using Super = P;\n";
for (const Field& f : type_->fields()) {
GenerateFieldAccessor(f);
......@@ -3074,7 +3090,7 @@ void CppClassGenerator::GenerateClassCasts() {
}
void CppClassGenerator::GenerateClassConstructors() {
hdr_ << "public:\n";
hdr_ << " public:\n";
hdr_ << " template <class DAlias = D>\n";
hdr_ << " constexpr " << gen_name_ << "() : P() {\n";
hdr_ << " static_assert(std::is_base_of<" << gen_name_ << ", \n";
......
......@@ -168,9 +168,9 @@ bool IsKeywordLikeName(const std::string& s) {
// naming convention and are those exempt from the normal type convention.
bool IsMachineType(const std::string& s) {
static const char* const machine_types[]{
"void", "never", "int8", "uint8", "int16", "uint16",
"int31", "uint31", "int32", "uint32", "int64", "intptr",
"uintptr", "float32", "float64", "bool", "string", "bint"};
"void", "never", "int8", "uint8", "int16", "uint16", "int31",
"uint31", "int32", "uint32", "int64", "intptr", "uintptr", "float32",
"float64", "bool", "string", "bint", "char8", "char16"};
return std::find(std::begin(machine_types), std::end(machine_types), s) !=
std::end(machine_types);
......
......@@ -79,7 +79,7 @@ TEST(GetObjectProperties) {
CHECK(props->type == std::string("v8::internal::Smi"));
CHECK_EQ(props->num_properties, 0);
v = CompileRun("[\"a\", \"b\"]");
v = CompileRun("[\"a\", \"bc\"]");
o = v8::Utils::OpenHandle(*v);
props = d::GetObjectProperties(o->ptr(), &ReadMemory, roots);
CHECK(props->type_check_result == d::TypeCheckResult::kUsedMap);
......@@ -144,11 +144,17 @@ TEST(GetObjectProperties) {
props->properties[2]->address + sizeof(i::Tagged_t));
props = d::GetObjectProperties(second_string_address, &ReadMemory, roots);
CHECK(props->type_check_result == d::TypeCheckResult::kUsedMap);
CHECK(props->type == std::string("v8::internal::String"));
CHECK_EQ(props->num_properties, 3);
CHECK(props->type == std::string("v8::internal::SeqOneByteString"));
CHECK_EQ(props->num_properties, 4);
CheckProp(*props->properties[0], "v8::internal::Map", "map");
CheckProp(*props->properties[1], "uint32_t", "hash_field");
CheckProp(*props->properties[2], "int32_t", "length", 1);
CheckProp(*props->properties[2], "int32_t", "length", 2);
CheckProp(*props->properties[3], "char", "chars",
d::PropertyKind::kArrayOfKnownSize, 2);
CHECK_EQ(
strncmp("bc",
reinterpret_cast<const char*>(props->properties[3]->address), 2),
0);
// Read the second string again, using a type hint instead of the map. All of
// its properties should match what we read last time.
......@@ -170,7 +176,7 @@ TEST(GetObjectProperties) {
*reinterpret_cast<i::Tagged_t*>(props->properties[0]->address));
CheckProp(*props2->properties[1], "uint32_t", "hash_field",
*reinterpret_cast<int32_t*>(props->properties[1]->address));
CheckProp(*props2->properties[2], "int32_t", "length", 1);
CheckProp(*props2->properties[2], "int32_t", "length", 2);
}
// Try a weak reference.
......@@ -179,13 +185,13 @@ TEST(GetObjectProperties) {
std::string weak_ref_prefix = "weak ref to ";
CHECK(weak_ref_prefix + props->brief == props2->brief);
CHECK(props2->type_check_result == d::TypeCheckResult::kUsedMap);
CHECK(props2->type == std::string("v8::internal::String"));
CHECK_EQ(props2->num_properties, 3);
CHECK(props2->type == std::string("v8::internal::SeqOneByteString"));
CHECK_EQ(props2->num_properties, 4);
CheckProp(*props2->properties[0], "v8::internal::Map", "map",
*reinterpret_cast<i::Tagged_t*>(props->properties[0]->address));
CheckProp(*props2->properties[1], "uint32_t", "hash_field",
*reinterpret_cast<i::Tagged_t*>(props->properties[1]->address));
CheckProp(*props2->properties[2], "int32_t", "length", 1);
CheckProp(*props2->properties[2], "int32_t", "length", 2);
}
} // namespace internal
......
......@@ -8,6 +8,7 @@
#include "heap-constants.h"
#include "include/v8-internal.h"
#include "src/common/ptr-compr-inl.h"
#include "src/objects/string-inl.h"
#include "torque-generated/class-debug-readers-tq.h"
namespace i = v8::internal;
......@@ -141,71 +142,117 @@ std::string AppendAddressAndType(const std::string& brief, uintptr_t address,
: brief + " (" + brief_stream.str() + ")";
}
struct TypeNameAndProps {
TypeNameAndProps(d::TypeCheckResult type_check_result, std::string&& type,
std::vector<std::unique_ptr<ObjectProperty>>&& properties)
: type_check_result(type_check_result),
type_name(std::move(type)),
props(std::move(properties)) {}
explicit TypeNameAndProps(d::TypeCheckResult type_check_result)
: type_check_result(type_check_result) {}
d::TypeCheckResult type_check_result;
std::string type_name;
std::vector<std::unique_ptr<ObjectProperty>> props;
};
TypeNameAndProps GetTypeNameAndPropsByHint(uintptr_t address,
d::MemoryAccessor accessor,
std::string type_hint_string) {
#define TYPE_NAME_CASE(ClassName, ...) \
if (type_hint_string == "v8::internal::" #ClassName) { \
return {d::TypeCheckResult::kUsedTypeHint, #ClassName, \
Tq##ClassName(address).GetProperties(accessor)}; \
}
TQ_INSTANCE_TYPES_SINGLE(TYPE_NAME_CASE)
TQ_INSTANCE_TYPES_RANGE(TYPE_NAME_CASE)
#undef TYPE_NAME_CASE
return TypeNameAndProps(d::TypeCheckResult::kUnknownTypeHint);
}
TypeNameAndProps GetTypeNameAndPropsForString(uintptr_t address,
d::MemoryAccessor accessor,
i::InstanceType type) {
class StringGetDispatcher : public i::AllStatic {
public:
#define DEFINE_METHOD(ClassName) \
static inline TypeNameAndProps Handle##ClassName( \
uintptr_t address, d::MemoryAccessor accessor) { \
return {d::TypeCheckResult::kUsedMap, #ClassName, \
Tq##ClassName(address).GetProperties(accessor)}; \
}
STRING_CLASS_TYPES(DEFINE_METHOD)
#undef DEFINE_METHOD
static inline TypeNameAndProps HandleInvalidString(
uintptr_t address, d::MemoryAccessor accessor) {
return TypeNameAndProps(d::TypeCheckResult::kUnknownInstanceType);
}
};
return i::StringShape(type)
.DispatchToSpecificTypeWithoutCast<StringGetDispatcher, TypeNameAndProps>(
address, accessor);
}
std::unique_ptr<ObjectPropertiesResult> GetHeapObjectProperties(
uintptr_t address, d::MemoryAccessor accessor, Value<i::InstanceType> type,
const char* type_hint, std::string brief) {
std::vector<std::unique_ptr<ObjectProperty>> props;
std::string type_name;
d::TypeCheckResult type_check_result = d::TypeCheckResult::kUsedMap;
TypeNameAndProps tnp(d::TypeCheckResult::kUsedMap);
if (type.validity == d::MemoryAccessResult::kOk) {
// Dispatch to the appropriate method for each instance type. After calling
// the generated method to fetch properties, we can add custom properties.
switch (type.value) {
#define INSTANCE_TYPE_CASE(ClassName, INSTANCE_TYPE) \
case i::INSTANCE_TYPE: \
type_name = #ClassName; \
props = Tq##ClassName(address).GetProperties(accessor); \
#define INSTANCE_TYPE_CASE(ClassName, INSTANCE_TYPE) \
case i::INSTANCE_TYPE: \
tnp.type_name = #ClassName; \
tnp.props = Tq##ClassName(address).GetProperties(accessor); \
break;
TQ_INSTANCE_TYPES_SINGLE(INSTANCE_TYPE_CASE)
#undef INSTANCE_TYPE_CASE
default:
// Special case: concrete subtypes of String are not included in the
// main instance type list because they use the low bits of the instance
// type enum as flags.
if (type.value <= i::LAST_STRING_TYPE) {
tnp = GetTypeNameAndPropsForString(address, accessor, type.value);
break;
}
#define INSTANCE_RANGE_CASE(ClassName, FIRST_TYPE, LAST_TYPE) \
if (type.value >= i::FIRST_TYPE && type.value <= i::LAST_TYPE) { \
type_name = #ClassName; \
props = Tq##ClassName(address).GetProperties(accessor); \
tnp.type_name = #ClassName; \
tnp.props = Tq##ClassName(address).GetProperties(accessor); \
break; \
}
TQ_INSTANCE_TYPES_RANGE(INSTANCE_RANGE_CASE)
#undef INSTANCE_RANGE_CASE
type_check_result = d::TypeCheckResult::kUnknownInstanceType;
tnp.type_check_result = d::TypeCheckResult::kUnknownInstanceType;
break;
}
} else if (type_hint != nullptr) {
// Try to use the provided type hint, since the real instance type is
// unavailable.
std::string type_hint_string(type_hint);
type_check_result = d::TypeCheckResult::kUsedTypeHint;
#define TYPE_NAME_CASE(ClassName, ...) \
if (type_hint_string == "v8::internal::" #ClassName) { \
type_name = #ClassName; \
props = Tq##ClassName(address).GetProperties(accessor); \
} else
TQ_INSTANCE_TYPES_SINGLE(TYPE_NAME_CASE)
TQ_INSTANCE_TYPES_RANGE(TYPE_NAME_CASE)
/*else*/ {
type_check_result = d::TypeCheckResult::kUnknownTypeHint;
}
#undef TYPE_NAME_CASE
tnp = GetTypeNameAndPropsByHint(address, accessor, type_hint);
} else {
// TODO(v8:9376): Use known maps here. If known map is just a guess (because
// root pointers weren't provided), then create a synthetic property with
// the more specific type. Then the caller could presumably ask us again
// with the type hint we provided. Otherwise, just go ahead and use it to
// generate properties.
type_check_result =
tnp.type_check_result =
type.validity == d::MemoryAccessResult::kAddressNotValid
? d::TypeCheckResult::kMapPointerInvalid
: d::TypeCheckResult::kMapPointerValidButInaccessible;
}
if (type_name.empty()) {
type_name = "Object";
if (tnp.type_name.empty()) {
tnp.type_name = "Object";
}
// TODO(v8:9376): Many object types need additional data that is not included
......@@ -215,10 +262,11 @@ std::unique_ptr<ObjectPropertiesResult> GetHeapObjectProperties(
// is available, we should instead represent those properties (and any out-of-
// object properties) using their JavaScript property names.
brief = AppendAddressAndType(brief, address, type_name.c_str());
brief = AppendAddressAndType(brief, address, tnp.type_name.c_str());
return v8::base::make_unique<ObjectPropertiesResult>(
type_check_result, brief, "v8::internal::" + type_name, std::move(props));
tnp.type_check_result, brief, "v8::internal::" + tnp.type_name,
std::move(tnp.props));
}
#undef STRUCT_INSTANCE_TYPE_ADAPTER
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment