| // 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 |