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