blob: 8a0575cc57d44af002339f665bd96795f2a97f2c [file] [log] [blame]
// Copyright 2019 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.
#include "src/flags/flags.h"
#include "src/torque/implementation-visitor.h"
#include "src/torque/type-oracle.h"
namespace v8 {
namespace internal {
namespace torque {
constexpr char kTqObjectOverrideDecls[] =
R"( std::vector<std::unique_ptr<ObjectProperty>> GetProperties(
d::MemoryAccessor accessor) const override;
const char* GetName() const override;
void Visit(TqObjectVisitor* visitor) const override;
bool IsSuperclassOf(const TqObject* other) const override;
)";
constexpr char kObjectClassListDefinition[] = R"(
const d::ClassList kObjectClassList {
sizeof(kObjectClassNames) / sizeof(const char*),
kObjectClassNames,
};
)";
namespace {
enum TypeStorage {
kAsStoredInHeap,
kUncompressed,
};
// An iterator for use in ValueTypeFieldsRange.
class ValueTypeFieldIterator {
public:
ValueTypeFieldIterator(const Type* type, size_t index)
: type_(type), index_(index) {}
struct Result {
NameAndType name_and_type;
SourcePosition pos;
size_t offset_bytes;
int num_bits;
int shift_bits;
};
const Result operator*() const {
if (auto struct_type = type_->StructSupertype()) {
const auto& field = (*struct_type)->fields()[index_];
return {field.name_and_type, field.pos, *field.offset, 0, 0};
}
const Type* type = type_;
int bitfield_start_offset = 0;
if (const auto type_wrapped_in_smi =
Type::MatchUnaryGeneric(type_, TypeOracle::GetSmiTaggedGeneric())) {
type = *type_wrapped_in_smi;
bitfield_start_offset = TargetArchitecture::SmiTagAndShiftSize();
}
if (const BitFieldStructType* bit_field_struct_type =
BitFieldStructType::DynamicCast(type)) {
const auto& field = bit_field_struct_type->fields()[index_];
return {field.name_and_type, field.pos, 0, field.num_bits,
field.offset + bitfield_start_offset};
}
UNREACHABLE();
}
ValueTypeFieldIterator& operator++() {
++index_;
return *this;
}
bool operator==(const ValueTypeFieldIterator& other) const {
return type_ == other.type_ && index_ == other.index_;
}
bool operator!=(const ValueTypeFieldIterator& other) const {
return !(*this == other);
}
private:
const Type* type_;
size_t index_;
};
// A way to iterate over the fields of structs or bitfield structs. For other
// types, the iterators returned from begin() and end() are immediately equal.
class ValueTypeFieldsRange {
public:
explicit ValueTypeFieldsRange(const Type* type) : type_(type) {}
ValueTypeFieldIterator begin() { return {type_, 0}; }
ValueTypeFieldIterator end() {
size_t index = 0;
base::Optional<const StructType*> struct_type = type_->StructSupertype();
if (struct_type && *struct_type != TypeOracle::GetFloat64OrHoleType()) {
index = (*struct_type)->fields().size();
}
const Type* type = type_;
if (const auto type_wrapped_in_smi =
Type::MatchUnaryGeneric(type_, TypeOracle::GetSmiTaggedGeneric())) {
type = *type_wrapped_in_smi;
}
if (const BitFieldStructType* bit_field_struct_type =
BitFieldStructType::DynamicCast(type)) {
index = bit_field_struct_type->fields().size();
}
return {type_, index};
}
private:
const Type* type_;
};
// A convenient way to keep track of several different ways that we might need
// to represent a field's type in the generated C++.
class DebugFieldType {
public:
explicit DebugFieldType(const Field& field)
: name_and_type_(field.name_and_type), pos_(field.pos) {}
DebugFieldType(const NameAndType& name_and_type, const SourcePosition& pos)
: name_and_type_(name_and_type), pos_(pos) {}
bool IsTagged() const {
return name_and_type_.type->IsSubtypeOf(TypeOracle::GetTaggedType());
}
// Returns the type that should be used for this field's value within code
// that is compiled as part of the debug helper library. In particular, this
// simplifies any tagged type to a plain uintptr_t because the debug helper
// compiles without most of the V8 runtime code.
std::string GetValueType(TypeStorage storage) const {
if (IsTagged()) {
return storage == kAsStoredInHeap ? "i::Tagged_t" : "uintptr_t";
}
// We can't emit a useful error at this point if the constexpr type name is
// wrong, but we can include a comment that might be helpful.
return GetOriginalType(storage) +
" /*Failing? Ensure constexpr type name is correct, and the "
"necessary #include is in any .tq file*/";
}
// Returns the type that should be used to represent a field's type to
// debugging tools that have full V8 symbols. The types returned from this
// method are resolveable in the v8::internal namespace and may refer to
// object types that are not included in the compilation of the debug helper
// library.
std::string GetOriginalType(TypeStorage storage) const {
if (name_and_type_.type->StructSupertype()) {
// There's no meaningful type we could use here, because the V8 symbols
// don't have any definition of a C++ struct matching this struct type.
return "";
}
if (IsTagged()) {
if (storage == kAsStoredInHeap &&
TargetArchitecture::ArePointersCompressed()) {
return "v8::internal::TaggedValue";
}
base::Optional<const ClassType*> field_class_type =
name_and_type_.type->ClassSupertype();
return "v8::internal::" +
(field_class_type.has_value()
? (*field_class_type)->GetGeneratedTNodeTypeName()
: "Object");
}
return name_and_type_.type->GetConstexprGeneratedTypeName();
}
// Returns a C++ expression that evaluates to a string (type `const char*`)
// containing the name of the field's type. The types returned from this
// method are resolveable in the v8::internal namespace and may refer to
// object types that are not included in the compilation of the debug helper
// library.
std::string GetTypeString(TypeStorage storage) const {
if (IsTagged() || name_and_type_.type->IsStructType()) {
// Wrap up the original type in a string literal.
return "\"" + GetOriginalType(storage) + "\"";
}
// We require constexpr type names to be resolvable in the v8::internal
// namespace, according to the contract in debug-helper.h. In order to
// verify at compile time that constexpr type names are resolvable, we use
// the type name as a dummy template parameter to a function that just
// returns its parameter.
return "CheckTypeName<" + GetValueType(storage) + ">(\"" +
GetOriginalType(storage) + "\")";
}
// Returns the field's size in bytes.
size_t GetSize() const {
auto opt_size = SizeOf(name_and_type_.type);
if (!opt_size.has_value()) {
Error("Size required for type ", name_and_type_.type->ToString())
.Position(pos_);
return 0;
}
return std::get<0>(*opt_size);
}
// Returns the name of the function for getting this field's address.
std::string GetAddressGetter() {
return "Get" + CamelifyString(name_and_type_.name) + "Address";
}
private:
NameAndType name_and_type_;
SourcePosition pos_;
};
// Emits a function to get the address of a field within a class, based on the
// member variable {address_}, which is a tagged pointer. Example
// implementation:
//
// uintptr_t TqFixedArray::GetObjectsAddress() const {
// return address_ - i::kHeapObjectTag + 16;
// }
void GenerateFieldAddressAccessor(const Field& field,
const std::string& class_name,
std::ostream& h_contents,
std::ostream& cc_contents) {
DebugFieldType debug_field_type(field);
const std::string address_getter = debug_field_type.GetAddressGetter();
h_contents << " uintptr_t " << address_getter << "() const;\n";
cc_contents << "\nuintptr_t Tq" << class_name << "::" << address_getter
<< "() const {\n";
cc_contents << " return address_ - i::kHeapObjectTag + " << *field.offset
<< ";\n";
cc_contents << "}\n";
}
// Emits a function to get the value of a field, or the value from an indexed
// position within an array field, based on the member variable {address_},
// which is a tagged pointer, and the parameter {accessor}, a function pointer
// that allows for fetching memory from the debuggee. The returned result
// includes both a "validity", indicating whether the memory could be fetched,
// and the fetched value. If the field contains tagged data, then these
// functions call EnsureDecompressed to expand compressed data. Example:
//
// Value<uintptr_t> TqMap::GetPrototypeValue(d::MemoryAccessor accessor) const {
// i::Tagged_t value{};
// d::MemoryAccessResult validity = accessor(
// GetPrototypeAddress(),
// reinterpret_cast<uint8_t*>(&value),
// sizeof(value));
// return {validity, EnsureDecompressed(value, address_)};
// }
//
// For array fields, an offset parameter is included. Example:
//
// Value<uintptr_t> TqFixedArray::GetObjectsValue(d::MemoryAccessor accessor,
// size_t offset) const {
// i::Tagged_t value{};
// d::MemoryAccessResult validity = accessor(
// GetObjectsAddress() + offset * sizeof(value),
// reinterpret_cast<uint8_t*>(&value),
// sizeof(value));
// return {validity, EnsureDecompressed(value, address_)};
// }
void GenerateFieldValueAccessor(const Field& field,
const std::string& class_name,
std::ostream& h_contents,
std::ostream& cc_contents) {
// Currently not implemented for struct fields.
if (field.name_and_type.type->StructSupertype()) return;
DebugFieldType debug_field_type(field);
const std::string address_getter = debug_field_type.GetAddressGetter();
const std::string field_getter =
"Get" + CamelifyString(field.name_and_type.name) + "Value";
std::string index_param;
std::string index_offset;
if (field.index) {
index_param = ", size_t offset";
index_offset = " + offset * sizeof(value)";
}
std::string field_value_type = debug_field_type.GetValueType(kUncompressed);
h_contents << " Value<" << field_value_type << "> " << field_getter
<< "(d::MemoryAccessor accessor " << index_param << ") const;\n";
cc_contents << "\nValue<" << field_value_type << "> Tq" << class_name
<< "::" << field_getter << "(d::MemoryAccessor accessor"
<< index_param << ") const {\n";
cc_contents << " " << debug_field_type.GetValueType(kAsStoredInHeap)
<< " value{};\n";
cc_contents << " d::MemoryAccessResult validity = accessor("
<< address_getter << "()" << index_offset
<< ", reinterpret_cast<uint8_t*>(&value), sizeof(value));\n";
cc_contents << " return {validity, "
<< (debug_field_type.IsTagged()
? "EnsureDecompressed(value, address_)"
: "value")
<< "};\n";
cc_contents << "}\n";
}
// Emits a portion of the member function GetProperties that is responsible for
// adding data about the current field to a result vector called "result".
// Example output:
//
// std::vector<std::unique_ptr<StructProperty>> prototype_struct_field_list;
// result.push_back(std::make_unique<ObjectProperty>(
// "prototype", // Field name
// "v8::internal::HeapObject", // Field type
// "v8::internal::HeapObject", // Decompressed type
// GetPrototypeAddress(), // Field address
// 1, // Number of values
// 8, // Size of value
// std::move(prototype_struct_field_list), // Struct fields
// d::PropertyKind::kSingle)); // Field kind
//
// In builds with pointer compression enabled, the field type for tagged values
// is "v8::internal::TaggedValue" (a four-byte class) and the decompressed type
// is a normal Object subclass that describes the expanded eight-byte type.
//
// If the field is an array, then its length is fetched from the debuggee. This
// could fail if the debuggee has incomplete memory, so the "validity" from that
// fetch is used to determine the result PropertyKind, which will say whether
// the array's length is known.
//
// If the field's type is a struct, then a local variable is created and filled
// with descriptions of each of the struct's fields. The type and decompressed
// type in the ObjectProperty are set to the empty string, to indicate to the
// caller that the struct fields vector should be used instead.
//
// The following example is an array of structs, so it uses both of the optional
// components described above:
//
// std::vector<std::unique_ptr<StructProperty>> descriptors_struct_field_list;
// descriptors_struct_field_list.push_back(std::make_unique<StructProperty>(
// "key", // Struct field name
// "v8::internal::PrimitiveHeapObject", // Struct field type
// "v8::internal::PrimitiveHeapObject", // Struct field decompressed type
// 0, // Byte offset within struct data
// 0, // Bitfield size (0=not a bitfield)
// 0)); // Bitfield shift
// // The line above is repeated for other struct fields. Omitted here.
// Value<uint16_t> indexed_field_count =
// GetNumberOfAllDescriptorsValue(accessor); // Fetch the array length.
// result.push_back(std::make_unique<ObjectProperty>(
// "descriptors", // Field name
// "", // Field type
// "", // Decompressed type
// GetDescriptorsAddress(), // Field address
// indexed_field_count.value, // Number of values
// 24, // Size of value
// std::move(descriptors_struct_field_list), // Struct fields
// GetArrayKind(indexed_field_count.validity))); // Field kind
void GenerateGetPropsChunkForField(const Field& field,
base::Optional<NameAndType> array_length,
std::ostream& get_props_impl) {
DebugFieldType debug_field_type(field);
// If the current field is a struct or bitfield struct, create a vector
// describing its fields. Otherwise this vector will be empty.
std::string struct_field_list =
field.name_and_type.name + "_struct_field_list";
get_props_impl << " std::vector<std::unique_ptr<StructProperty>> "
<< struct_field_list << ";\n";
for (const auto& struct_field :
ValueTypeFieldsRange(field.name_and_type.type)) {
DebugFieldType struct_field_type(struct_field.name_and_type,
struct_field.pos);
get_props_impl << " " << struct_field_list
<< ".push_back(std::make_unique<StructProperty>(\""
<< struct_field.name_and_type.name << "\", "
<< struct_field_type.GetTypeString(kAsStoredInHeap) << ", "
<< struct_field_type.GetTypeString(kUncompressed) << ", "
<< struct_field.offset_bytes << ", " << struct_field.num_bits
<< ", " << struct_field.shift_bits << "));\n";
}
struct_field_list = "std::move(" + struct_field_list + ")";
// The number of values and property kind for non-indexed properties:
std::string count_value = "1";
std::string property_kind = "d::PropertyKind::kSingle";
// If the field is indexed, emit a fetch of the array length, and change
// count_value and property_kind to be the correct values for an array.
if (array_length) {
const Type* index_type = array_length->type;
std::string index_type_name;
if (index_type == TypeOracle::GetSmiType()) {
index_type_name = "uintptr_t";
count_value =
"i::PlatformSmiTagging::SmiToInt(indexed_field_count.value)";
} else if (!index_type->IsSubtypeOf(TypeOracle::GetTaggedType())) {
index_type_name = index_type->GetConstexprGeneratedTypeName();
count_value = "indexed_field_count.value";
} else {
Error("Unsupported index type: ", index_type);
return;
}
get_props_impl << " Value<" << index_type_name
<< "> indexed_field_count = Get"
<< CamelifyString(array_length->name)
<< "Value(accessor);\n";
property_kind = "GetArrayKind(indexed_field_count.validity)";
}
get_props_impl << " result.push_back(std::make_unique<ObjectProperty>(\""
<< field.name_and_type.name << "\", "
<< debug_field_type.GetTypeString(kAsStoredInHeap) << ", "
<< debug_field_type.GetTypeString(kUncompressed) << ", "
<< debug_field_type.GetAddressGetter() << "(), " << count_value
<< ", " << debug_field_type.GetSize() << ", "
<< struct_field_list << ", " << property_kind << "));\n";
}
// For any Torque-defined class Foo, this function generates a class TqFoo which
// allows for convenient inspection of objects of type Foo in a crash dump or
// time travel session (where we can't just run the object printer). The
// generated class looks something like this:
//
// class TqFoo : public TqParentOfFoo {
// public:
// // {address} is an uncompressed tagged pointer.
// inline TqFoo(uintptr_t address) : TqParentOfFoo(address) {}
//
// // Creates and returns a list of this object's properties.
// std::vector<std::unique_ptr<ObjectProperty>> GetProperties(
// d::MemoryAccessor accessor) const override;
//
// // Returns the name of this class, "v8::internal::Foo".
// const char* GetName() const override;
//
// // Visitor pattern; implementation just calls visitor->VisitFoo(this).
// void Visit(TqObjectVisitor* visitor) const override;
//
// // Returns whether Foo is a superclass of the other object's type.
// bool IsSuperclassOf(const TqObject* other) const override;
//
// // Field accessors omitted here (see other comments above).
// };
//
// Four output streams are written:
//
// h_contents: A header file which gets the class definition above.
// cc_contents: A cc file which gets implementations of that class's members.
// visitor: A stream that is accumulating the definition of the class
// TqObjectVisitor. Each class Foo gets its own virtual method
// VisitFoo in TqObjectVisitor.
// class_names: A stream that is accumulating a list of strings including fully-
// qualified names for every Torque-defined class type.
void GenerateClassDebugReader(const ClassType& type, std::ostream& h_contents,
std::ostream& cc_contents, std::ostream& visitor,
std::ostream& class_names,
std::unordered_set<const ClassType*>* done) {
// Make sure each class only gets generated once.
if (!done->insert(&type).second) return;
const ClassType* super_type = type.GetSuperClass();
// We must emit the classes in dependency order. If the super class hasn't
// been emitted yet, go handle it first.
if (super_type != nullptr) {
GenerateClassDebugReader(*super_type, h_contents, cc_contents, visitor,
class_names, done);
}
// Classes with undefined layout don't grant any particular value here and may
// not correspond with actual C++ classes, so skip them.
if (type.HasUndefinedLayout()) return;
const std::string name = type.name();
const std::string super_name =
super_type == nullptr ? "Object" : super_type->name();
h_contents << "\nclass Tq" << name << " : public Tq" << super_name << " {\n";
h_contents << " public:\n";
h_contents << " inline Tq" << name << "(uintptr_t address) : Tq"
<< super_name << "(address) {}\n";
h_contents << kTqObjectOverrideDecls;
cc_contents << "\nconst char* Tq" << name << "::GetName() const {\n";
cc_contents << " return \"v8::internal::" << name << "\";\n";
cc_contents << "}\n";
cc_contents << "\nvoid Tq" << name
<< "::Visit(TqObjectVisitor* visitor) const {\n";
cc_contents << " visitor->Visit" << name << "(this);\n";
cc_contents << "}\n";
cc_contents << "\nbool Tq" << name
<< "::IsSuperclassOf(const TqObject* other) const {\n";
cc_contents
<< " return GetName() != other->GetName() && dynamic_cast<const Tq"
<< name << "*>(other) != nullptr;\n";
cc_contents << "}\n";
// By default, the visitor method for this class just calls the visitor method
// for this class's parent. This allows custom visitors to only override a few
// classes they care about without needing to know about the entire hierarchy.
visitor << " virtual void Visit" << name << "(const Tq" << name
<< "* object) {\n";
visitor << " Visit" << super_name << "(object);\n";
visitor << " }\n";
class_names << " \"v8::internal::" << name << "\",\n";
std::stringstream get_props_impl;
for (const Field& field : type.fields()) {
if (field.name_and_type.type == TypeOracle::GetVoidType()) continue;
if (!field.offset.has_value()) {
// Fields with dynamic offset are currently unsupported.
continue;
}
GenerateFieldAddressAccessor(field, name, h_contents, cc_contents);
GenerateFieldValueAccessor(field, name, h_contents, cc_contents);
base::Optional<NameAndType> array_length;
if (field.index) {
array_length = ExtractSimpleFieldArraySize(type, *field.index);
if (!array_length) {
// Unsupported complex array length, skipping this field.
continue;
}
}
GenerateGetPropsChunkForField(field, array_length, get_props_impl);
}
h_contents << "};\n";
cc_contents << "\nstd::vector<std::unique_ptr<ObjectProperty>> Tq" << name
<< "::GetProperties(d::MemoryAccessor accessor) const {\n";
// Start by getting the fields from the parent class.
cc_contents << " std::vector<std::unique_ptr<ObjectProperty>> result = Tq"
<< super_name << "::GetProperties(accessor);\n";
// Then add the fields from this class.
cc_contents << get_props_impl.str();
cc_contents << " return result;\n";
cc_contents << "}\n";
}
} // namespace
void ImplementationVisitor::GenerateClassDebugReaders(
const std::string& output_directory) {
const std::string file_name = "class-debug-readers";
std::stringstream h_contents;
std::stringstream cc_contents;
h_contents << "// Provides the ability to read object properties in\n";
h_contents << "// postmortem or remote scenarios, where the debuggee's\n";
h_contents << "// memory is not part of the current process's address\n";
h_contents << "// space and must be read using a callback function.\n\n";
{
IncludeGuardScope include_guard(h_contents, file_name + ".h");
h_contents << "#include <cstdint>\n";
h_contents << "#include <vector>\n";
h_contents
<< "\n#include \"tools/debug_helper/debug-helper-internal.h\"\n\n";
h_contents << "// Unset a windgi.h macro that causes conflicts.\n";
h_contents << "#ifdef GetBValue\n";
h_contents << "#undef GetBValue\n";
h_contents << "#endif\n\n";
for (const std::string& include_path : GlobalContext::CppIncludes()) {
cc_contents << "#include " << StringLiteralQuote(include_path) << "\n";
}
cc_contents << "#include \"torque-generated/" << file_name << ".h\"\n";
cc_contents << "#include \"include/v8-internal.h\"\n\n";
cc_contents << "namespace i = v8::internal;\n\n";
NamespaceScope h_namespaces(h_contents,
{"v8", "internal", "debug_helper_internal"});
NamespaceScope cc_namespaces(cc_contents,
{"v8", "internal", "debug_helper_internal"});
std::stringstream visitor;
visitor << "\nclass TqObjectVisitor {\n";
visitor << " public:\n";
visitor << " virtual void VisitObject(const TqObject* object) {}\n";
std::stringstream class_names;
std::unordered_set<const ClassType*> done;
for (const ClassType* type : TypeOracle::GetClasses()) {
GenerateClassDebugReader(*type, h_contents, cc_contents, visitor,
class_names, &done);
}
visitor << "};\n";
h_contents << visitor.str();
cc_contents << "\nconst char* kObjectClassNames[] {\n";
cc_contents << class_names.str();
cc_contents << "};\n";
cc_contents << kObjectClassListDefinition;
}
WriteFile(output_directory + "/" + file_name + ".h", h_contents.str());
WriteFile(output_directory + "/" + file_name + ".cc", cc_contents.str());
}
} // namespace torque
} // namespace internal
} // namespace v8