1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
|
// 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 = kSmiTagSize + kSmiShiftSize;
}
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-tq";
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
|