1// Copyright 2019 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/flags/flags.h"
6#include "src/torque/implementation-visitor.h"
7#include "src/torque/type-oracle.h"
8
9namespace v8 {
10namespace internal {
11namespace torque {
12
13constexpr char kTqObjectOverrideDecls[] =
14    R"(  std::vector<std::unique_ptr<ObjectProperty>> GetProperties(
15      d::MemoryAccessor accessor) const override;
16  const char* GetName() const override;
17  void Visit(TqObjectVisitor* visitor) const override;
18  bool IsSuperclassOf(const TqObject* other) const override;
19)";
20
21constexpr char kObjectClassListDefinition[] = R"(
22const d::ClassList kObjectClassList {
23  sizeof(kObjectClassNames) / sizeof(const char*),
24  kObjectClassNames,
25};
26)";
27
28namespace {
29enum TypeStorage {
30  kAsStoredInHeap,
31  kUncompressed,
32};
33
34// An iterator for use in ValueTypeFieldsRange.
35class ValueTypeFieldIterator {
36 public:
37  ValueTypeFieldIterator(const Type* type, size_t index)
38      : type_(type), index_(index) {}
39  struct Result {
40    NameAndType name_and_type;
41    SourcePosition pos;
42    size_t offset_bytes;
43    int num_bits;
44    int shift_bits;
45  };
46  const Result operator*() const {
47    if (auto struct_type = type_->StructSupertype()) {
48      const auto& field = (*struct_type)->fields()[index_];
49      return {field.name_and_type, field.pos, *field.offset, 0, 0};
50    }
51    const Type* type = type_;
52    int bitfield_start_offset = 0;
53    if (const auto type_wrapped_in_smi =
54            Type::MatchUnaryGeneric(type_, TypeOracle::GetSmiTaggedGeneric())) {
55      type = *type_wrapped_in_smi;
56      bitfield_start_offset = TargetArchitecture::SmiTagAndShiftSize();
57    }
58    if (const BitFieldStructType* bit_field_struct_type =
59            BitFieldStructType::DynamicCast(type)) {
60      const auto& field = bit_field_struct_type->fields()[index_];
61      return {field.name_and_type, field.pos, 0, field.num_bits,
62              field.offset + bitfield_start_offset};
63    }
64    UNREACHABLE();
65  }
66  ValueTypeFieldIterator& operator++() {
67    ++index_;
68    return *this;
69  }
70  bool operator==(const ValueTypeFieldIterator& other) const {
71    return type_ == other.type_ && index_ == other.index_;
72  }
73  bool operator!=(const ValueTypeFieldIterator& other) const {
74    return !(*this == other);
75  }
76
77 private:
78  const Type* type_;
79  size_t index_;
80};
81
82// A way to iterate over the fields of structs or bitfield structs. For other
83// types, the iterators returned from begin() and end() are immediately equal.
84class ValueTypeFieldsRange {
85 public:
86  explicit ValueTypeFieldsRange(const Type* type) : type_(type) {}
87  ValueTypeFieldIterator begin() { return {type_, 0}; }
88  ValueTypeFieldIterator end() {
89    size_t index = 0;
90    base::Optional<const StructType*> struct_type = type_->StructSupertype();
91    if (struct_type && *struct_type != TypeOracle::GetFloat64OrHoleType()) {
92      index = (*struct_type)->fields().size();
93    }
94    const Type* type = type_;
95    if (const auto type_wrapped_in_smi =
96            Type::MatchUnaryGeneric(type_, TypeOracle::GetSmiTaggedGeneric())) {
97      type = *type_wrapped_in_smi;
98    }
99    if (const BitFieldStructType* bit_field_struct_type =
100            BitFieldStructType::DynamicCast(type)) {
101      index = bit_field_struct_type->fields().size();
102    }
103    return {type_, index};
104  }
105
106 private:
107  const Type* type_;
108};
109
110// A convenient way to keep track of several different ways that we might need
111// to represent a field's type in the generated C++.
112class DebugFieldType {
113 public:
114  explicit DebugFieldType(const Field& field)
115      : name_and_type_(field.name_and_type), pos_(field.pos) {}
116  DebugFieldType(const NameAndType& name_and_type, const SourcePosition& pos)
117      : name_and_type_(name_and_type), pos_(pos) {}
118
119  bool IsTagged() const {
120    return name_and_type_.type->IsSubtypeOf(TypeOracle::GetTaggedType());
121  }
122
123  // Returns the type that should be used for this field's value within code
124  // that is compiled as part of the debug helper library. In particular, this
125  // simplifies any tagged type to a plain uintptr_t because the debug helper
126  // compiles without most of the V8 runtime code.
127  std::string GetValueType(TypeStorage storage) const {
128    if (IsTagged()) {
129      return storage == kAsStoredInHeap ? "i::Tagged_t" : "uintptr_t";
130    }
131
132    // We can't emit a useful error at this point if the constexpr type name is
133    // wrong, but we can include a comment that might be helpful.
134    return GetOriginalType(storage) +
135           " /*Failing? Ensure constexpr type name is correct, and the "
136           "necessary #include is in any .tq file*/";
137  }
138
139  // Returns the type that should be used to represent a field's type to
140  // debugging tools that have full V8 symbols. The types returned from this
141  // method are resolveable in the v8::internal namespace and may refer to
142  // object types that are not included in the compilation of the debug helper
143  // library.
144  std::string GetOriginalType(TypeStorage storage) const {
145    if (name_and_type_.type->StructSupertype()) {
146      // There's no meaningful type we could use here, because the V8 symbols
147      // don't have any definition of a C++ struct matching this struct type.
148      return "";
149    }
150    if (IsTagged()) {
151      if (storage == kAsStoredInHeap &&
152          TargetArchitecture::ArePointersCompressed()) {
153        return "v8::internal::TaggedValue";
154      }
155      base::Optional<const ClassType*> field_class_type =
156          name_and_type_.type->ClassSupertype();
157      return "v8::internal::" +
158             (field_class_type.has_value()
159                  ? (*field_class_type)->GetGeneratedTNodeTypeName()
160                  : "Object");
161    }
162    return name_and_type_.type->GetConstexprGeneratedTypeName();
163  }
164
165  // Returns a C++ expression that evaluates to a string (type `const char*`)
166  // containing the name of the field's type. The types returned from this
167  // method are resolveable in the v8::internal namespace and may refer to
168  // object types that are not included in the compilation of the debug helper
169  // library.
170  std::string GetTypeString(TypeStorage storage) const {
171    if (IsTagged() || name_and_type_.type->IsStructType()) {
172      // Wrap up the original type in a string literal.
173      return "\"" + GetOriginalType(storage) + "\"";
174    }
175
176    // We require constexpr type names to be resolvable in the v8::internal
177    // namespace, according to the contract in debug-helper.h. In order to
178    // verify at compile time that constexpr type names are resolvable, we use
179    // the type name as a dummy template parameter to a function that just
180    // returns its parameter.
181    return "CheckTypeName<" + GetValueType(storage) + ">(\"" +
182           GetOriginalType(storage) + "\")";
183  }
184
185  // Returns the field's size in bytes.
186  size_t GetSize() const {
187    auto opt_size = SizeOf(name_and_type_.type);
188    if (!opt_size.has_value()) {
189      Error("Size required for type ", name_and_type_.type->ToString())
190          .Position(pos_);
191      return 0;
192    }
193    return std::get<0>(*opt_size);
194  }
195
196  // Returns the name of the function for getting this field's address.
197  std::string GetAddressGetter() {
198    return "Get" + CamelifyString(name_and_type_.name) + "Address";
199  }
200
201 private:
202  NameAndType name_and_type_;
203  SourcePosition pos_;
204};
205
206// Emits a function to get the address of a field within a class, based on the
207// member variable {address_}, which is a tagged pointer. Example
208// implementation:
209//
210// uintptr_t TqFixedArray::GetObjectsAddress() const {
211//   return address_ - i::kHeapObjectTag + 16;
212// }
213void GenerateFieldAddressAccessor(const Field& field,
214                                  const std::string& class_name,
215                                  std::ostream& h_contents,
216                                  std::ostream& cc_contents) {
217  DebugFieldType debug_field_type(field);
218
219  const std::string address_getter = debug_field_type.GetAddressGetter();
220
221  h_contents << "  uintptr_t " << address_getter << "() const;\n";
222  cc_contents << "\nuintptr_t Tq" << class_name << "::" << address_getter
223              << "() const {\n";
224  cc_contents << "  return address_ - i::kHeapObjectTag + " << *field.offset
225              << ";\n";
226  cc_contents << "}\n";
227}
228
229// Emits a function to get the value of a field, or the value from an indexed
230// position within an array field, based on the member variable {address_},
231// which is a tagged pointer, and the parameter {accessor}, a function pointer
232// that allows for fetching memory from the debuggee. The returned result
233// includes both a "validity", indicating whether the memory could be fetched,
234// and the fetched value. If the field contains tagged data, then these
235// functions call EnsureDecompressed to expand compressed data. Example:
236//
237// Value<uintptr_t> TqMap::GetPrototypeValue(d::MemoryAccessor accessor) const {
238//   i::Tagged_t value{};
239//   d::MemoryAccessResult validity = accessor(
240//       GetPrototypeAddress(),
241//       reinterpret_cast<uint8_t*>(&value),
242//       sizeof(value));
243//   return {validity, EnsureDecompressed(value, address_)};
244// }
245//
246// For array fields, an offset parameter is included. Example:
247//
248// Value<uintptr_t> TqFixedArray::GetObjectsValue(d::MemoryAccessor accessor,
249//                                                size_t offset) const {
250//   i::Tagged_t value{};
251//   d::MemoryAccessResult validity = accessor(
252//       GetObjectsAddress() + offset * sizeof(value),
253//       reinterpret_cast<uint8_t*>(&value),
254//       sizeof(value));
255//   return {validity, EnsureDecompressed(value, address_)};
256// }
257void GenerateFieldValueAccessor(const Field& field,
258                                const std::string& class_name,
259                                std::ostream& h_contents,
260                                std::ostream& cc_contents) {
261  // Currently not implemented for struct fields.
262  if (field.name_and_type.type->StructSupertype()) return;
263
264  DebugFieldType debug_field_type(field);
265
266  const std::string address_getter = debug_field_type.GetAddressGetter();
267  const std::string field_getter =
268      "Get" + CamelifyString(field.name_and_type.name) + "Value";
269
270  std::string index_param;
271  std::string index_offset;
272  if (field.index) {
273    index_param = ", size_t offset";
274    index_offset = " + offset * sizeof(value)";
275  }
276
277  std::string field_value_type = debug_field_type.GetValueType(kUncompressed);
278  h_contents << "  Value<" << field_value_type << "> " << field_getter
279             << "(d::MemoryAccessor accessor " << index_param << ") const;\n";
280  cc_contents << "\nValue<" << field_value_type << "> Tq" << class_name
281              << "::" << field_getter << "(d::MemoryAccessor accessor"
282              << index_param << ") const {\n";
283  cc_contents << "  " << debug_field_type.GetValueType(kAsStoredInHeap)
284              << " value{};\n";
285  cc_contents << "  d::MemoryAccessResult validity = accessor("
286              << address_getter << "()" << index_offset
287              << ", reinterpret_cast<uint8_t*>(&value), sizeof(value));\n";
288#ifdef V8_MAP_PACKING
289  if (field_getter == "GetMapValue") {
290    cc_contents << "  value = i::MapWord::Unpack(value);\n";
291  }
292#endif
293  cc_contents << "  return {validity, "
294              << (debug_field_type.IsTagged()
295                      ? "EnsureDecompressed(value, address_)"
296                      : "value")
297              << "};\n";
298  cc_contents << "}\n";
299}
300
301// Emits a portion of the member function GetProperties that is responsible for
302// adding data about the current field to a result vector called "result".
303// Example output:
304//
305// std::vector<std::unique_ptr<StructProperty>> prototype_struct_field_list;
306// result.push_back(std::make_unique<ObjectProperty>(
307//     "prototype",                                     // Field name
308//     "v8::internal::HeapObject",                      // Field type
309//     "v8::internal::HeapObject",                      // Decompressed type
310//     GetPrototypeAddress(),                           // Field address
311//     1,                                               // Number of values
312//     8,                                               // Size of value
313//     std::move(prototype_struct_field_list),          // Struct fields
314//     d::PropertyKind::kSingle));                      // Field kind
315//
316// In builds with pointer compression enabled, the field type for tagged values
317// is "v8::internal::TaggedValue" (a four-byte class) and the decompressed type
318// is a normal Object subclass that describes the expanded eight-byte type.
319//
320// If the field is an array, then its length is fetched from the debuggee. This
321// could fail if the debuggee has incomplete memory, so the "validity" from that
322// fetch is used to determine the result PropertyKind, which will say whether
323// the array's length is known.
324//
325// If the field's type is a struct, then a local variable is created and filled
326// with descriptions of each of the struct's fields. The type and decompressed
327// type in the ObjectProperty are set to the empty string, to indicate to the
328// caller that the struct fields vector should be used instead.
329//
330// The following example is an array of structs, so it uses both of the optional
331// components described above:
332//
333// std::vector<std::unique_ptr<StructProperty>> descriptors_struct_field_list;
334// descriptors_struct_field_list.push_back(std::make_unique<StructProperty>(
335//     "key",                                // Struct field name
336//     "v8::internal::PrimitiveHeapObject",  // Struct field type
337//     "v8::internal::PrimitiveHeapObject",  // Struct field decompressed type
338//     0,                                    // Byte offset within struct data
339//     0,                                    // Bitfield size (0=not a bitfield)
340//     0));                                  // Bitfield shift
341// // The line above is repeated for other struct fields. Omitted here.
342// // Fetch the slice.
343// auto indexed_field_slice_descriptors =
344//     TqDebugFieldSliceDescriptorArrayDescriptors(accessor, address_);
345// if (indexed_field_slice_descriptors.validity == d::MemoryAccessResult::kOk) {
346//   result.push_back(std::make_unique<ObjectProperty>(
347//     "descriptors",                                 // Field name
348//     "",                                            // Field type
349//     "",                                            // Decompressed type
350//     address_ - i::kHeapObjectTag +
351//     std::get<1>(indexed_field_slice_descriptors.value), // Field address
352//     std::get<2>(indexed_field_slice_descriptors.value), // Number of values
353//     12,                                            // Size of value
354//     std::move(descriptors_struct_field_list),      // Struct fields
355//     GetArrayKind(indexed_field_slice_descriptors.validity)));  // Field kind
356// }
357void GenerateGetPropsChunkForField(const Field& field,
358                                   std::ostream& get_props_impl,
359                                   std::string class_name) {
360  DebugFieldType debug_field_type(field);
361
362  // If the current field is a struct or bitfield struct, create a vector
363  // describing its fields. Otherwise this vector will be empty.
364  std::string struct_field_list =
365      field.name_and_type.name + "_struct_field_list";
366  get_props_impl << "  std::vector<std::unique_ptr<StructProperty>> "
367                 << struct_field_list << ";\n";
368  for (const auto& struct_field :
369       ValueTypeFieldsRange(field.name_and_type.type)) {
370    DebugFieldType struct_field_type(struct_field.name_and_type,
371                                     struct_field.pos);
372    get_props_impl << "  " << struct_field_list
373                   << ".push_back(std::make_unique<StructProperty>(\""
374                   << struct_field.name_and_type.name << "\", "
375                   << struct_field_type.GetTypeString(kAsStoredInHeap) << ", "
376                   << struct_field_type.GetTypeString(kUncompressed) << ", "
377                   << struct_field.offset_bytes << ", " << struct_field.num_bits
378                   << ", " << struct_field.shift_bits << "));\n";
379  }
380  struct_field_list = "std::move(" + struct_field_list + ")";
381
382  // The number of values and property kind for non-indexed properties:
383  std::string count_value = "1";
384  std::string property_kind = "d::PropertyKind::kSingle";
385
386  // If the field is indexed, emit a fetch of the array length, and change
387  // count_value and property_kind to be the correct values for an array.
388  if (field.index) {
389    std::string indexed_field_slice =
390        "indexed_field_slice_" + field.name_and_type.name;
391    get_props_impl << "  auto " << indexed_field_slice << " = "
392                   << "TqDebugFieldSlice" << class_name
393                   << CamelifyString(field.name_and_type.name)
394                   << "(accessor, address_);\n";
395    std::string validity = indexed_field_slice + ".validity";
396    std::string value = indexed_field_slice + ".value";
397    property_kind = "GetArrayKind(" + validity + ")";
398
399    get_props_impl << "  if (" << validity
400                   << " == d::MemoryAccessResult::kOk) {\n"
401                   << "    result.push_back(std::make_unique<ObjectProperty>(\""
402                   << field.name_and_type.name << "\", "
403                   << debug_field_type.GetTypeString(kAsStoredInHeap) << ", "
404                   << debug_field_type.GetTypeString(kUncompressed) << ", "
405                   << "address_ - i::kHeapObjectTag + std::get<1>(" << value
406                   << "), "
407                   << "std::get<2>(" << value << ")"
408                   << ", " << debug_field_type.GetSize() << ", "
409                   << struct_field_list << ", " << property_kind << "));\n"
410                   << "  }\n";
411    return;
412  }
413  get_props_impl << "  result.push_back(std::make_unique<ObjectProperty>(\""
414                 << field.name_and_type.name << "\", "
415                 << debug_field_type.GetTypeString(kAsStoredInHeap) << ", "
416                 << debug_field_type.GetTypeString(kUncompressed) << ", "
417                 << debug_field_type.GetAddressGetter() << "(), " << count_value
418                 << ", " << debug_field_type.GetSize() << ", "
419                 << struct_field_list << ", " << property_kind << "));\n";
420}
421
422// For any Torque-defined class Foo, this function generates a class TqFoo which
423// allows for convenient inspection of objects of type Foo in a crash dump or
424// time travel session (where we can't just run the object printer). The
425// generated class looks something like this:
426//
427// class TqFoo : public TqParentOfFoo {
428//  public:
429//   // {address} is an uncompressed tagged pointer.
430//   inline TqFoo(uintptr_t address) : TqParentOfFoo(address) {}
431//
432//   // Creates and returns a list of this object's properties.
433//   std::vector<std::unique_ptr<ObjectProperty>> GetProperties(
434//       d::MemoryAccessor accessor) const override;
435//
436//   // Returns the name of this class, "v8::internal::Foo".
437//   const char* GetName() const override;
438//
439//   // Visitor pattern; implementation just calls visitor->VisitFoo(this).
440//   void Visit(TqObjectVisitor* visitor) const override;
441//
442//   // Returns whether Foo is a superclass of the other object's type.
443//   bool IsSuperclassOf(const TqObject* other) const override;
444//
445//   // Field accessors omitted here (see other comments above).
446// };
447//
448// Four output streams are written:
449//
450// h_contents:  A header file which gets the class definition above.
451// cc_contents: A cc file which gets implementations of that class's members.
452// visitor:     A stream that is accumulating the definition of the class
453//              TqObjectVisitor. Each class Foo gets its own virtual method
454//              VisitFoo in TqObjectVisitor.
455// class_names: A stream that is accumulating a list of strings including fully-
456//              qualified names for every Torque-defined class type.
457void GenerateClassDebugReader(const ClassType& type, std::ostream& h_contents,
458                              std::ostream& cc_contents, std::ostream& visitor,
459                              std::ostream& class_names,
460                              std::unordered_set<const ClassType*>* done) {
461  // Make sure each class only gets generated once.
462  if (!done->insert(&type).second) return;
463  const ClassType* super_type = type.GetSuperClass();
464
465  // We must emit the classes in dependency order. If the super class hasn't
466  // been emitted yet, go handle it first.
467  if (super_type != nullptr) {
468    GenerateClassDebugReader(*super_type, h_contents, cc_contents, visitor,
469                             class_names, done);
470  }
471
472  // Classes with undefined layout don't grant any particular value here and may
473  // not correspond with actual C++ classes, so skip them.
474  if (type.HasUndefinedLayout()) return;
475
476  const std::string name = type.name();
477  const std::string super_name =
478      super_type == nullptr ? "Object" : super_type->name();
479  h_contents << "\nclass Tq" << name << " : public Tq" << super_name << " {\n";
480  h_contents << " public:\n";
481  h_contents << "  inline Tq" << name << "(uintptr_t address) : Tq"
482             << super_name << "(address) {}\n";
483  h_contents << kTqObjectOverrideDecls;
484
485  cc_contents << "\nconst char* Tq" << name << "::GetName() const {\n";
486  cc_contents << "  return \"v8::internal::" << name << "\";\n";
487  cc_contents << "}\n";
488
489  cc_contents << "\nvoid Tq" << name
490              << "::Visit(TqObjectVisitor* visitor) const {\n";
491  cc_contents << "  visitor->Visit" << name << "(this);\n";
492  cc_contents << "}\n";
493
494  cc_contents << "\nbool Tq" << name
495              << "::IsSuperclassOf(const TqObject* other) const {\n";
496  cc_contents
497      << "  return GetName() != other->GetName() && dynamic_cast<const Tq"
498      << name << "*>(other) != nullptr;\n";
499  cc_contents << "}\n";
500
501  // By default, the visitor method for this class just calls the visitor method
502  // for this class's parent. This allows custom visitors to only override a few
503  // classes they care about without needing to know about the entire hierarchy.
504  visitor << "  virtual void Visit" << name << "(const Tq" << name
505          << "* object) {\n";
506  visitor << "    Visit" << super_name << "(object);\n";
507  visitor << "  }\n";
508
509  class_names << "  \"v8::internal::" << name << "\",\n";
510
511  std::stringstream get_props_impl;
512
513  for (const Field& field : type.fields()) {
514    if (field.name_and_type.type == TypeOracle::GetVoidType()) continue;
515    if (field.offset.has_value()) {
516      GenerateFieldAddressAccessor(field, name, h_contents, cc_contents);
517      GenerateFieldValueAccessor(field, name, h_contents, cc_contents);
518    }
519    GenerateGetPropsChunkForField(field, get_props_impl, name);
520  }
521
522  h_contents << "};\n";
523
524  cc_contents << "\nstd::vector<std::unique_ptr<ObjectProperty>> Tq" << name
525              << "::GetProperties(d::MemoryAccessor accessor) const {\n";
526  // Start by getting the fields from the parent class.
527  cc_contents << "  std::vector<std::unique_ptr<ObjectProperty>> result = Tq"
528              << super_name << "::GetProperties(accessor);\n";
529  // Then add the fields from this class.
530  cc_contents << get_props_impl.str();
531  cc_contents << "  return result;\n";
532  cc_contents << "}\n";
533}
534}  // namespace
535
536void ImplementationVisitor::GenerateClassDebugReaders(
537    const std::string& output_directory) {
538  const std::string file_name = "class-debug-readers";
539  std::stringstream h_contents;
540  std::stringstream cc_contents;
541  h_contents << "// Provides the ability to read object properties in\n";
542  h_contents << "// postmortem or remote scenarios, where the debuggee's\n";
543  h_contents << "// memory is not part of the current process's address\n";
544  h_contents << "// space and must be read using a callback function.\n\n";
545  {
546    IncludeGuardScope include_guard(h_contents, file_name + ".h");
547
548    h_contents << "#include <cstdint>\n";
549    h_contents << "#include <vector>\n";
550    h_contents
551        << "\n#include \"tools/debug_helper/debug-helper-internal.h\"\n\n";
552
553    const char* kWingdiWorkaround =
554        "// Unset a wingdi.h macro that causes conflicts.\n"
555        "#ifdef GetBValue\n"
556        "#undef GetBValue\n"
557        "#endif\n\n";
558
559    h_contents << kWingdiWorkaround;
560
561    cc_contents << "#include \"torque-generated/" << file_name << ".h\"\n\n";
562    cc_contents << "#include \"src/objects/all-objects-inl.h\"\n";
563    cc_contents << "#include \"torque-generated/debug-macros.h\"\n\n";
564    cc_contents << kWingdiWorkaround;
565    cc_contents << "namespace i = v8::internal;\n\n";
566
567    NamespaceScope h_namespaces(h_contents,
568                                {"v8", "internal", "debug_helper_internal"});
569    NamespaceScope cc_namespaces(cc_contents,
570                                 {"v8", "internal", "debug_helper_internal"});
571
572    std::stringstream visitor;
573    visitor << "\nclass TqObjectVisitor {\n";
574    visitor << " public:\n";
575    visitor << "  virtual void VisitObject(const TqObject* object) {}\n";
576
577    std::stringstream class_names;
578
579    std::unordered_set<const ClassType*> done;
580    for (const ClassType* type : TypeOracle::GetClasses()) {
581      GenerateClassDebugReader(*type, h_contents, cc_contents, visitor,
582                               class_names, &done);
583    }
584
585    visitor << "};\n";
586    h_contents << visitor.str();
587
588    cc_contents << "\nconst char* kObjectClassNames[] {\n";
589    cc_contents << class_names.str();
590    cc_contents << "};\n";
591    cc_contents << kObjectClassListDefinition;
592  }
593  WriteFile(output_directory + "/" + file_name + ".h", h_contents.str());
594  WriteFile(output_directory + "/" + file_name + ".cc", cc_contents.str());
595}
596
597}  // namespace torque
598}  // namespace internal
599}  // namespace v8
600