Heap Number encoding

- represent smis as fake heap numbers
- numbers nodes (both smi and heap numbers) reference a child node whose
  name is "value" and whose entry is the string representation of that
  number

That feature is disabled by default, and can be enabled by passing
captureNumericValue: true when calling HeapProfiler.takeHeapSnapshot

This patch slightly refactors some functions that operate on "essential
objects". We now check that the object is essential before trying to
create the entry. Otherwise, we would end up with smi objects created,
but not referenced anywhere.

Design doc:
https://docs.google.com/document/d/1Qh1zxyn0SS5wzJzitD6ecBJTdFbQkJogSMwxDRsn44o/edit

Change-Id: Ibbe6e79a54c4f9eace72bc0a0ccb622a97698e00
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2806747
Reviewed-by: Yang Guo <yangguo@chromium.org>
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Commit-Queue: Arnaud Renevier <arenevier@fb.com>
Cr-Commit-Position: refs/heads/master@{#73985}
This commit is contained in:
Arno Renevier 2021-04-14 09:35:24 -07:00 committed by Commit Bot
parent 80aaae9e74
commit 7f52e4f92d
10 changed files with 189 additions and 47 deletions

View File

@ -707,6 +707,8 @@ experimental domain HeapProfiler
# when the tracking is stopped.
optional boolean reportProgress
optional boolean treatGlobalObjectsAsRoots
# If true, numerical values are included in the snapshot
optional boolean captureNumericValue
command takeHeapSnapshot
parameters
@ -714,6 +716,8 @@ experimental domain HeapProfiler
optional boolean reportProgress
# If true, a raw snapshot without artifical roots will be generated
optional boolean treatGlobalObjectsAsRoots
# If true, numerical values are included in the snapshot
optional boolean captureNumericValue
event addHeapSnapshotChunk
parameters

View File

@ -900,7 +900,8 @@ class V8_EXPORT HeapProfiler {
const HeapSnapshot* TakeHeapSnapshot(
ActivityControl* control = nullptr,
ObjectNameResolver* global_object_name_resolver = nullptr,
bool treat_global_objects_as_roots = true);
bool treat_global_objects_as_roots = true,
bool capture_numeric_value = false);
/**
* Starts tracking of heap objects population statistics. After calling

View File

@ -9707,10 +9707,11 @@ void HeapProfiler::ClearObjectIds() {
const HeapSnapshot* HeapProfiler::TakeHeapSnapshot(
ActivityControl* control, ObjectNameResolver* resolver,
bool treat_global_objects_as_roots) {
bool treat_global_objects_as_roots, bool capture_numeric_value) {
return reinterpret_cast<const HeapSnapshot*>(
reinterpret_cast<i::HeapProfiler*>(this)->TakeSnapshot(
control, resolver, treat_global_objects_as_roots));
control, resolver, treat_global_objects_as_roots,
capture_numeric_value));
}
void HeapProfiler::StartTrackingHeapObjects(bool track_allocations) {

View File

@ -232,10 +232,12 @@ Response V8HeapProfilerAgentImpl::startTrackingHeapObjects(
}
Response V8HeapProfilerAgentImpl::stopTrackingHeapObjects(
Maybe<bool> reportProgress, Maybe<bool> treatGlobalObjectsAsRoots) {
Maybe<bool> reportProgress, Maybe<bool> treatGlobalObjectsAsRoots,
Maybe<bool> captureNumericValue) {
requestHeapStatsUpdate();
takeHeapSnapshot(std::move(reportProgress),
std::move(treatGlobalObjectsAsRoots));
std::move(treatGlobalObjectsAsRoots),
std::move(captureNumericValue));
stopTrackingHeapObjectsInternal();
return Response::Success();
}
@ -258,7 +260,8 @@ Response V8HeapProfilerAgentImpl::disable() {
}
Response V8HeapProfilerAgentImpl::takeHeapSnapshot(
Maybe<bool> reportProgress, Maybe<bool> treatGlobalObjectsAsRoots) {
Maybe<bool> reportProgress, Maybe<bool> treatGlobalObjectsAsRoots,
Maybe<bool> captureNumericValue) {
v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
if (!profiler) return Response::ServerError("Cannot access v8 heap profiler");
std::unique_ptr<HeapSnapshotProgress> progress;
@ -267,7 +270,8 @@ Response V8HeapProfilerAgentImpl::takeHeapSnapshot(
GlobalObjectNameResolver resolver(m_session);
const v8::HeapSnapshot* snapshot = profiler->TakeHeapSnapshot(
progress.get(), &resolver, treatGlobalObjectsAsRoots.fromMaybe(true));
progress.get(), &resolver, treatGlobalObjectsAsRoots.fromMaybe(true),
captureNumericValue.fromMaybe(false));
if (!snapshot) return Response::ServerError("Failed to take heap snapshot");
HeapSnapshotOutputStream stream(&m_frontend);
snapshot->Serialize(&stream);

View File

@ -34,14 +34,15 @@ class V8HeapProfilerAgentImpl : public protocol::HeapProfiler::Backend {
Response enable() override;
Response startTrackingHeapObjects(Maybe<bool> trackAllocations) override;
Response stopTrackingHeapObjects(
Maybe<bool> reportProgress,
Maybe<bool> treatGlobalObjectsAsRoots) override;
Response stopTrackingHeapObjects(Maybe<bool> reportProgress,
Maybe<bool> treatGlobalObjectsAsRoots,
Maybe<bool> captureNumericValue) override;
Response disable() override;
Response takeHeapSnapshot(Maybe<bool> reportProgress,
Maybe<bool> treatGlobalObjectsAsRoots) override;
Maybe<bool> treatGlobalObjectsAsRoots,
Maybe<bool> captureNumericValue) override;
Response getObjectByHeapObjectId(
const String16& heapSnapshotObjectId, Maybe<String16> objectGroup,

View File

@ -81,9 +81,10 @@ v8::EmbedderGraph::Node::Detachedness HeapProfiler::GetDetachedness(
HeapSnapshot* HeapProfiler::TakeSnapshot(
v8::ActivityControl* control,
v8::HeapProfiler::ObjectNameResolver* resolver,
bool treat_global_objects_as_roots) {
bool treat_global_objects_as_roots, bool capture_numeric_value) {
is_taking_snapshot_ = true;
HeapSnapshot* result = new HeapSnapshot(this, treat_global_objects_as_roots);
HeapSnapshot* result = new HeapSnapshot(this, treat_global_objects_as_roots,
capture_numeric_value);
{
HeapSnapshotGenerator generator(result, control, resolver, heap());
if (!generator.GenerateSnapshot()) {

View File

@ -33,7 +33,8 @@ class HeapProfiler : public HeapObjectAllocationTracker {
HeapSnapshot* TakeSnapshot(v8::ActivityControl* control,
v8::HeapProfiler::ObjectNameResolver* resolver,
bool treat_global_objects_as_roots);
bool treat_global_objects_as_roots,
bool capture_numeric_value);
bool StartSamplingHeapProfiler(uint64_t sample_interval, int stack_depth,
v8::HeapProfiler::SamplingFlags);

View File

@ -183,9 +183,11 @@ const char* HeapEntry::TypeAsString() const {
}
}
HeapSnapshot::HeapSnapshot(HeapProfiler* profiler, bool global_objects_as_roots)
HeapSnapshot::HeapSnapshot(HeapProfiler* profiler, bool global_objects_as_roots,
bool capture_numeric_value)
: profiler_(profiler),
treat_global_objects_as_roots_(global_objects_as_roots) {
treat_global_objects_as_roots_(global_objects_as_roots),
capture_numeric_value_(capture_numeric_value) {
// It is very important to keep objects that form a heap snapshot
// as small as possible. Check assumptions about data structure sizes.
STATIC_ASSERT(kSystemPointerSize != 4 || sizeof(HeapGraphEdge) == 12);
@ -387,8 +389,7 @@ SnapshotObjectId HeapObjectsMap::FindOrAddEntry(Address addr,
return entry_info.id;
}
entry->value = reinterpret_cast<void*>(entries_.size());
SnapshotObjectId id = next_id_;
next_id_ += kObjectIdStep;
SnapshotObjectId id = get_next_id();
entries_.push_back(EntryInfo(id, addr, size, accessed));
DCHECK(static_cast<uint32_t>(entries_.size()) > entries_map_.occupancy());
return id;
@ -553,6 +554,16 @@ HeapEntry* V8HeapExplorer::AllocateEntry(HeapThing ptr) {
return AddEntry(HeapObject::cast(Object(reinterpret_cast<Address>(ptr))));
}
HeapEntry* V8HeapExplorer::AllocateEntry(Smi smi) {
SnapshotObjectId id = heap_object_map_->get_next_id();
HeapEntry* entry =
snapshot_->AddEntry(HeapEntry::kHeapNumber, "smi number", id, 0, 0);
// XXX: Smis do not appear in CombinedHeapObjectIterator, so we need to
// extract the references here
ExtractNumberReference(entry, smi);
return entry;
}
void V8HeapExplorer::ExtractLocation(HeapEntry* entry, HeapObject object) {
if (object.IsJSFunction()) {
JSFunction func = JSFunction::cast(object);
@ -638,7 +649,7 @@ HeapEntry* V8HeapExplorer::AddEntry(HeapObject object) {
object.IsByteArray()) {
return AddEntry(object, HeapEntry::kArray, "");
} else if (object.IsHeapNumber()) {
return AddEntry(object, HeapEntry::kHeapNumber, "number");
return AddEntry(object, HeapEntry::kHeapNumber, "heap number");
}
return AddEntry(object, HeapEntry::kHidden, GetSystemEntryName(object));
}
@ -837,6 +848,10 @@ void V8HeapExplorer::ExtractReferences(HeapEntry* entry, HeapObject obj) {
ExtractEphemeronHashTableReferences(entry, EphemeronHashTable::cast(obj));
} else if (obj.IsFixedArray()) {
ExtractFixedArrayReferences(entry, FixedArray::cast(obj));
} else if (obj.IsHeapNumber()) {
if (snapshot_->capture_numeric_value()) {
ExtractNumberReference(entry, obj);
}
}
}
@ -1253,6 +1268,11 @@ class JSArrayBufferDataEntryAllocator : public HeapEntriesAllocator {
HeapEntry::kNative, "system / JSArrayBufferData",
size_);
}
HeapEntry* AllocateEntry(Smi smi) override {
DCHECK(false);
return nullptr;
}
private:
size_t size_;
V8HeapExplorer* explorer_;
@ -1298,6 +1318,30 @@ void V8HeapExplorer::ExtractFixedArrayReferences(HeapEntry* entry,
}
}
void V8HeapExplorer::ExtractNumberReference(HeapEntry* entry, Object number) {
DCHECK(number.IsNumber());
// Must be large enough to fit any double, int, or size_t.
char arr[32];
Vector<char> buffer(arr, arraysize(arr));
const char* string;
if (number.IsSmi()) {
int int_value = Smi::ToInt(number);
string = IntToCString(int_value, buffer);
} else {
double double_value = HeapNumber::cast(number).value();
string = DoubleToCString(double_value, buffer);
}
const char* name = names_->GetCopy(string);
SnapshotObjectId id = heap_object_map_->get_next_id();
HeapEntry* child_entry =
snapshot_->AddEntry(HeapEntry::kString, name, id, 0, 0);
entry->SetNamedReference(HeapGraphEdge::kInternal, "value", child_entry);
}
void V8HeapExplorer::ExtractFeedbackVectorReferences(
HeapEntry* entry, FeedbackVector feedback_vector) {
MaybeObject code = feedback_vector.maybe_optimized_code();
@ -1352,8 +1396,10 @@ void V8HeapExplorer::ExtractPropertyReferences(JSObject js_obj,
PropertyDetails details = descs.GetDetails(i);
switch (details.location()) {
case kField: {
Representation r = details.representation();
if (r.IsSmi() || r.IsDouble()) break;
if (!snapshot_->capture_numeric_value()) {
Representation r = details.representation();
if (r.IsSmi() || r.IsDouble()) break;
}
Name k = descs.GetKey(i);
FieldIndex field_index = FieldIndex::ForDescriptor(js_obj.map(), i);
@ -1483,9 +1529,15 @@ String V8HeapExplorer::GetConstructorName(JSObject object) {
}
HeapEntry* V8HeapExplorer::GetEntry(Object obj) {
return obj.IsHeapObject() ? generator_->FindOrAddEntry(
reinterpret_cast<void*>(obj.ptr()), this)
: nullptr;
if (obj.IsHeapObject()) {
return generator_->FindOrAddEntry(reinterpret_cast<void*>(obj.ptr()), this);
}
DCHECK(obj.IsSmi());
if (!snapshot_->capture_numeric_value()) {
return nullptr;
}
return generator_->FindOrAddEntry(Smi::cast(obj), this);
}
class RootsReferencesExtractor : public RootVisitor {
@ -1657,23 +1709,25 @@ void V8HeapExplorer::SetElementReference(HeapEntry* parent_entry, int index,
void V8HeapExplorer::SetInternalReference(HeapEntry* parent_entry,
const char* reference_name,
Object child_obj, int field_offset) {
HeapEntry* child_entry = GetEntry(child_obj);
if (child_entry == nullptr) return;
if (IsEssentialObject(child_obj)) {
parent_entry->SetNamedReference(HeapGraphEdge::kInternal, reference_name,
child_entry);
if (!IsEssentialObject(child_obj)) {
return;
}
HeapEntry* child_entry = GetEntry(child_obj);
DCHECK_NOT_NULL(child_entry);
parent_entry->SetNamedReference(HeapGraphEdge::kInternal, reference_name,
child_entry);
MarkVisitedField(field_offset);
}
void V8HeapExplorer::SetInternalReference(HeapEntry* parent_entry, int index,
Object child_obj, int field_offset) {
HeapEntry* child_entry = GetEntry(child_obj);
if (child_entry == nullptr) return;
if (IsEssentialObject(child_obj)) {
parent_entry->SetNamedReference(HeapGraphEdge::kInternal,
names_->GetName(index), child_entry);
if (!IsEssentialObject(child_obj)) {
return;
}
HeapEntry* child_entry = GetEntry(child_obj);
DCHECK_NOT_NULL(child_entry);
parent_entry->SetNamedReference(HeapGraphEdge::kInternal,
names_->GetName(index), child_entry);
MarkVisitedField(field_offset);
}
@ -1682,9 +1736,12 @@ void V8HeapExplorer::SetHiddenReference(HeapObject parent_obj,
Object child_obj, int field_offset) {
DCHECK_EQ(parent_entry, GetEntry(parent_obj));
DCHECK(!MapWord::IsPacked(child_obj.ptr()));
if (!IsEssentialObject(child_obj)) {
return;
}
HeapEntry* child_entry = GetEntry(child_obj);
if (child_entry != nullptr && IsEssentialObject(child_obj) &&
IsEssentialHiddenReference(parent_obj, field_offset)) {
DCHECK_NOT_NULL(child_entry);
if (IsEssentialHiddenReference(parent_obj, field_offset)) {
parent_entry->SetIndexedReference(HeapGraphEdge::kHidden, index,
child_entry);
}
@ -1693,23 +1750,25 @@ void V8HeapExplorer::SetHiddenReference(HeapObject parent_obj,
void V8HeapExplorer::SetWeakReference(HeapEntry* parent_entry,
const char* reference_name,
Object child_obj, int field_offset) {
HeapEntry* child_entry = GetEntry(child_obj);
if (child_entry == nullptr) return;
if (IsEssentialObject(child_obj)) {
parent_entry->SetNamedReference(HeapGraphEdge::kWeak, reference_name,
child_entry);
if (!IsEssentialObject(child_obj)) {
return;
}
HeapEntry* child_entry = GetEntry(child_obj);
DCHECK_NOT_NULL(child_entry);
parent_entry->SetNamedReference(HeapGraphEdge::kWeak, reference_name,
child_entry);
MarkVisitedField(field_offset);
}
void V8HeapExplorer::SetWeakReference(HeapEntry* parent_entry, int index,
Object child_obj, int field_offset) {
HeapEntry* child_entry = GetEntry(child_obj);
if (child_entry == nullptr) return;
if (IsEssentialObject(child_obj)) {
parent_entry->SetNamedReference(
HeapGraphEdge::kWeak, names_->GetFormatted("%d", index), child_entry);
if (!IsEssentialObject(child_obj)) {
return;
}
HeapEntry* child_entry = GetEntry(child_obj);
DCHECK_NOT_NULL(child_entry);
parent_entry->SetNamedReference(
HeapGraphEdge::kWeak, names_->GetFormatted("%d", index), child_entry);
MarkVisitedField(field_offset);
}
@ -1767,6 +1826,13 @@ void V8HeapExplorer::SetGcRootsReference(Root root) {
void V8HeapExplorer::SetGcSubrootReference(Root root, const char* description,
bool is_weak, Object child_obj) {
if (child_obj.IsSmi()) {
// TODO(arenevier): if we handle smis here, the snapshot gets 2 to 3 times
// slower on large heaps. According to perf, The bulk of the extra works
// happens in TemplateHashMapImpl::Probe method, when tyring to get
// names->GetFormatted("%d / %s", index, description)
return;
}
HeapEntry* child_entry = GetEntry(child_obj);
if (child_entry == nullptr) return;
const char* name = GetStrongGcSubrootName(child_obj);
@ -1944,6 +2010,7 @@ class EmbedderGraphEntriesAllocator : public HeapEntriesAllocator {
names_(snapshot_->profiler()->names()),
heap_object_map_(snapshot_->profiler()->heap_object_map()) {}
HeapEntry* AllocateEntry(HeapThing ptr) override;
HeapEntry* AllocateEntry(Smi smi) override;
private:
HeapSnapshot* snapshot_;
@ -1994,6 +2061,11 @@ HeapEntry* EmbedderGraphEntriesAllocator::AllocateEntry(HeapThing ptr) {
return heap_entry;
}
HeapEntry* EmbedderGraphEntriesAllocator::AllocateEntry(Smi smi) {
DCHECK(false);
return nullptr;
}
NativeObjectsExplorer::NativeObjectsExplorer(
HeapSnapshot* snapshot, SnapshottingProgressReportingInterface* progress)
: isolate_(

View File

@ -188,7 +188,8 @@ class HeapEntry {
// HeapSnapshotGenerator fills in a HeapSnapshot.
class HeapSnapshot {
public:
explicit HeapSnapshot(HeapProfiler* profiler, bool global_objects_as_roots);
explicit HeapSnapshot(HeapProfiler* profiler, bool global_objects_as_roots,
bool capture_numeric_value);
HeapSnapshot(const HeapSnapshot&) = delete;
HeapSnapshot& operator=(const HeapSnapshot&) = delete;
void Delete();
@ -213,6 +214,7 @@ class HeapSnapshot {
bool treat_global_objects_as_roots() const {
return treat_global_objects_as_roots_;
}
bool capture_numeric_value() const { return capture_numeric_value_; }
void AddLocation(HeapEntry* entry, int scriptId, int line, int col);
HeapEntry* AddEntry(HeapEntry::Type type,
@ -245,6 +247,7 @@ class HeapSnapshot {
std::vector<SourceLocation> locations_;
SnapshotObjectId max_snapshot_js_object_id_ = -1;
bool treat_global_objects_as_roots_;
bool capture_numeric_value_;
};
@ -277,6 +280,10 @@ class HeapObjectsMap {
SnapshotObjectId last_assigned_id() const {
return next_id_ - kObjectIdStep;
}
SnapshotObjectId get_next_id() {
next_id_ += kObjectIdStep;
return next_id_ - kObjectIdStep;
}
void StopHeapObjectsTracking();
SnapshotObjectId PushHeapObjectsStats(OutputStream* stream,
@ -322,6 +329,7 @@ class HeapEntriesAllocator {
public:
virtual ~HeapEntriesAllocator() = default;
virtual HeapEntry* AllocateEntry(HeapThing ptr) = 0;
virtual HeapEntry* AllocateEntry(Smi smi) = 0;
};
class SnapshottingProgressReportingInterface {
@ -342,6 +350,7 @@ class V8_EXPORT_PRIVATE V8HeapExplorer : public HeapEntriesAllocator {
V8HeapExplorer& operator=(const V8HeapExplorer&) = delete;
HeapEntry* AllocateEntry(HeapThing ptr) override;
HeapEntry* AllocateEntry(Smi smi) override;
int EstimateObjectsCount();
bool IterateAndExtractReferences(HeapSnapshotGenerator* generator);
void CollectGlobalObjectsTags();
@ -397,6 +406,7 @@ class V8_EXPORT_PRIVATE V8HeapExplorer : public HeapEntriesAllocator {
void ExtractJSGeneratorObjectReferences(HeapEntry* entry,
JSGeneratorObject generator);
void ExtractFixedArrayReferences(HeapEntry* entry, FixedArray array);
void ExtractNumberReference(HeapEntry* entry, Object number);
void ExtractFeedbackVectorReferences(HeapEntry* entry,
FeedbackVector feedback_vector);
void ExtractDescriptorArrayReferences(HeapEntry* entry,
@ -501,6 +511,9 @@ class HeapSnapshotGenerator : public SnapshottingProgressReportingInterface {
// The HeapEntriesMap instance is used to track a mapping between
// real heap objects and their representations in heap snapshots.
using HeapEntriesMap = std::unordered_map<HeapThing, HeapEntry*>;
// The SmiEntriesMap instance is used to track a mapping between smi and
// their representations in heap snapshots.
using SmiEntriesMap = std::unordered_map<int, HeapEntry*>;
HeapSnapshotGenerator(HeapSnapshot* snapshot,
v8::ActivityControl* control,
@ -515,16 +528,31 @@ class HeapSnapshotGenerator : public SnapshottingProgressReportingInterface {
return it != entries_map_.end() ? it->second : nullptr;
}
HeapEntry* FindEntry(Smi smi) {
auto it = smis_map_.find(smi.value());
return it != smis_map_.end() ? it->second : nullptr;
}
HeapEntry* AddEntry(HeapThing ptr, HeapEntriesAllocator* allocator) {
return entries_map_.emplace(ptr, allocator->AllocateEntry(ptr))
.first->second;
}
HeapEntry* AddEntry(Smi smi, HeapEntriesAllocator* allocator) {
return smis_map_.emplace(smi.value(), allocator->AllocateEntry(smi))
.first->second;
}
HeapEntry* FindOrAddEntry(HeapThing ptr, HeapEntriesAllocator* allocator) {
HeapEntry* entry = FindEntry(ptr);
return entry != nullptr ? entry : AddEntry(ptr, allocator);
}
HeapEntry* FindOrAddEntry(Smi smi, HeapEntriesAllocator* allocator) {
HeapEntry* entry = FindEntry(smi);
return entry != nullptr ? entry : AddEntry(smi, allocator);
}
private:
bool FillReferences();
void ProgressStep() override;
@ -537,6 +565,7 @@ class HeapSnapshotGenerator : public SnapshottingProgressReportingInterface {
NativeObjectsExplorer dom_explorer_;
// Mapping from HeapThing pointers to HeapEntry indices.
HeapEntriesMap entries_map_;
SmiEntriesMap smis_map_;
// Used during snapshot generation.
int progress_counter_;
int progress_total_;

View File

@ -508,6 +508,34 @@ TEST(HeapSnapshotHeapNumbers) {
CHECK_EQ(v8::HeapGraphNode::kHeapNumber, b->GetType());
}
TEST(HeapSnapshotHeapNumbersCaptureNumericValue) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler();
CompileRun(
"a = 1; // a is Smi\n"
"b = 2.5; // b is HeapNumber");
const v8::HeapSnapshot* snapshot =
heap_profiler->TakeHeapSnapshot(nullptr, nullptr, true, true);
CHECK(ValidateSnapshot(snapshot));
const v8::HeapGraphNode* global = GetGlobalObject(snapshot);
const v8::HeapGraphNode* a =
GetProperty(env->GetIsolate(), global, v8::HeapGraphEdge::kProperty, "a");
CHECK(a);
CHECK_EQ(1, a->GetChildrenCount());
v8::String::Utf8Value value_a(CcTest::isolate(),
a->GetChild(0)->GetToNode()->GetName());
CHECK_EQ(0, strcmp("1", *value_a));
const v8::HeapGraphNode* b =
GetProperty(env->GetIsolate(), global, v8::HeapGraphEdge::kProperty, "b");
CHECK(b);
CHECK_EQ(2, b->GetChildrenCount());
v8::String::Utf8Value value_b(CcTest::isolate(),
b->GetChild(0)->GetToNode()->GetName());
CHECK_EQ(0, strcmp("2.5", *value_b));
}
TEST(HeapSnapshotHeapBigInts) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());