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