stronger type system for rds::Link
[hiphop-php.git] / hphp / runtime / ext / objprof / ext_heapgraph.cpp
blob4b757c734952e8f19f9b1a501d91ff5ec1979d75
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/extension.h"
19 #include "hphp/runtime/base/builtin-functions.h"
20 #include <folly/Format.h>
22 * Heapgraph Extension
23 * What is it?
24 * Set of methods to wrap around HHVM's heap graph implementation
26 * How does it work?
27 * Create a heap graph in HHVM and uses it as a Resource with a
28 * set of functions that can operate on it.
30 * How do I use it?
31 * Call heapgraph_create, and then any of the other heapgraph
32 * function
35 #include <array>
36 #include <set>
37 #include <unordered_map>
38 #include <vector>
39 #include <boost/dynamic_bitset.hpp>
41 #include "hphp/runtime/base/array-init.h"
42 #include "hphp/runtime/base/collections.h"
43 #include "hphp/runtime/base/memory-manager-defs.h"
44 #include "hphp/runtime/ext/datetime/ext_datetime.h"
45 #include "hphp/runtime/ext/simplexml/ext_simplexml.h"
46 #include "hphp/runtime/ext/std/ext_std_closure.h"
47 #include "hphp/runtime/vm/class.h"
48 #include "hphp/runtime/vm/unit.h"
49 #include "hphp/util/alloc.h"
50 #include "hphp/runtime/base/heap-graph.h"
51 #include "hphp/runtime/base/heap-algorithms.h"
52 #include "hphp/runtime/base/container-functions.h"
53 #include "hphp/runtime/base/rds.h"
54 #include "hphp/runtime/base/zend-string.h"
55 #include "hphp/runtime/vm/vm-regs.h"
57 namespace HPHP {
59 struct PhpStack;
60 struct CppStack;
62 namespace {
64 TRACE_SET_MOD(heapgraph);
66 ///////////////////////////////////////////////////////////////////////////////
68 const StaticString
69 s_nodes("nodes"),
70 s_edges("edges"),
71 s_roots("roots"),
72 s_root_nodes("root_nodes"),
73 s_root_path("root_path"),
74 s_exact("exact"),
75 s_format("format"),
77 // Node description
78 s_kind("kind"),
79 s_size("size"),
80 s_index("index"),
81 s_class("class"),
82 s_func("func"),
83 s_local("local"),
84 s_prop("prop"),
85 s_Root("Root"),
86 s_type("type"),
88 // Edge description
89 s_from("from"),
90 s_to("to"),
91 s_key("key"),
92 s_value("value"),
93 s_offset("offset");
97 ///////////////////////////////////////////////////////////////////////////////
98 // CONTEXT OBJECTS
100 // Extra information about a HeapGraph::Node.
101 union CapturedNode {
102 CapturedNode() : static_local() {}
103 CapturedNode(const CapturedNode& other) {
104 memcpy(this, &other, sizeof other);
106 rds::StaticLocal static_local; // only for rds::StaticLocalData
107 rds::SPropCache sprop_cache; // only for HPHP::SPropCache
108 struct {
109 HeaderKind kind;
110 const Class* cls;
111 } heap_object; // only for non-roots
114 // Extra information about a HeapGraph::Ptr
115 struct CapturedPtr {
116 enum { Key, Value, Property, Offset } index_kind;
117 uint32_t index; // location of ptr within it's from node
120 struct HeapGraphContext : SweepableResourceData {
121 explicit HeapGraphContext(const HeapGraph& hg) : hg(hg) {}
122 explicit HeapGraphContext(HeapGraph&& hg) : hg(std::move(hg)) {}
123 ~HeapGraphContext() {}
125 bool isInvalid() const override {
126 return false;
129 CLASSNAME_IS("HeapGraphContext")
130 DECLARE_RESOURCE_ALLOCATION(HeapGraphContext)
132 // overriding ResourceData
133 const String& o_getClassNameHook() const override { return classnameof(); }
135 std::vector<CapturedNode> cnodes;
136 std::vector<CapturedPtr> cptrs;
137 const HeapGraph hg;
140 IMPLEMENT_RESOURCE_ALLOCATION(HeapGraphContext)
142 namespace {
144 using HeapGraphContextPtr = req::ptr<HeapGraphContext>;
145 static HeapGraphContextPtr get_valid_heapgraph_context_resource(
146 const Resource& resource,
147 const char* func_name
149 auto hgcontext = dyn_cast_or_null<HeapGraphContext>(resource);
150 if (hgcontext == nullptr || hgcontext->isInvalid()) {
151 raise_warning(
152 "%s(): supplied resource is not a valid HeapGraph Context resource",
153 func_name + 2
155 return nullptr;
157 return hgcontext;
160 ///////////////////////////////////////////////////////////////////////////////
161 // TRAVERSAL FUNCTIONS
163 static std::array<const StringData*, 3> edge_kind_strs{};
164 static const char* edge_kind_cstrs[] = {
165 "Ptr:Counted", "Ptr:Ambiguous", "Ptr:Weak",
168 const StringData* edgeKindName(HeapGraph::PtrKind kind) {
169 auto s = edge_kind_strs[(int)kind];
170 if (!s) {
171 s = makeStaticString(edge_kind_cstrs[(int)kind]);
172 edge_kind_strs[(int)kind] = s;
174 return s;
175 static_assert(HeapGraph::NumPtrKinds == 3, "");
176 static_assert(HeapGraph::Counted == 0, "");
177 static_assert(HeapGraph::Ambiguous == 1, "");
178 static_assert(HeapGraph::Weak == 2, "");
181 // return metadata about this pointer, in the form of a CapturedPtr
182 CapturedPtr getEdgeInfo(const HeapGraph& g, int ptr) {
183 // Try to drill down and resolve the edge name
184 assertx(g.ptrs[ptr].from != -1 && g.ptrs[ptr].to != -1);
185 auto& edge = g.ptrs[ptr];
186 auto& from = g.nodes[edge.from];
187 int prop_offset = edge.offset;
188 if (!from.is_root) {
189 auto from_hdr = from.h;
191 // get the actual ObjectData*. This deals with object kinds that
192 // have data before the object: AFWH, Native, and Closure. Compute
193 // prop_offset relative to the inner ObjectData.
194 const ObjectData* from_obj{nullptr};
195 if (from_hdr->kind() == HeaderKind::AsyncFuncFrame) {
196 from_obj = asyncFuncWH(from_hdr);
197 prop_offset = edge.offset - (uintptr_t(from_obj) - uintptr_t(from_hdr));
198 } else if (from_hdr->kind() == HeaderKind::NativeData) {
199 from_obj = Native::obj(static_cast<const NativeNode*>(from_hdr));
200 prop_offset = edge.offset - (uintptr_t(from_obj) - uintptr_t(from_hdr));
201 } else if (from_hdr->kind() == HeaderKind::ClosureHdr) {
202 from_obj = closureObj(from_hdr);
203 prop_offset = edge.offset - (uintptr_t(from_obj) - uintptr_t(from_hdr));
204 } else if (isObjectKind(from_hdr->kind())) {
205 from_obj = static_cast<const ObjectData*>(from_hdr);
206 prop_offset = edge.offset;
209 switch (from_hdr->kind()) {
210 // Known generalized cases that don't really need pointer kind
211 case HeaderKind::Mixed:
212 case HeaderKind::Dict:
213 case HeaderKind::Keyset: {
214 if (edge.offset >= sizeof(MixedArray)) {
215 using Elm = MixedArray::Elm;
216 auto elm_offset = edge.offset - sizeof(MixedArray);
217 uint32_t index = elm_offset / sizeof(Elm);
218 if (index < static_cast<const MixedArray*>(from_hdr)->iterLimit()) {
219 auto field = elm_offset - index * sizeof(Elm);
220 if (field == Elm::keyOff()) {
221 return {CapturedPtr::Key, index};
223 if (field == Elm::dataOff() + offsetof(TypedValue, m_data)) {
224 return {CapturedPtr::Value, index};
228 break;
231 case HeaderKind::Packed:
232 case HeaderKind::VecArray: {
233 if (edge.offset >= sizeof(ArrayData)) {
234 auto elm_offset = edge.offset - sizeof(ArrayData);
235 uint32_t index = elm_offset / sizeof(TypedValue);
236 if (index < static_cast<const ArrayData*>(from_hdr)->size()) {
237 return {CapturedPtr::Value, index};
240 break;
243 case HeaderKind::Apc: {
244 if (edge.offset >= sizeof(APCLocalArray)) {
245 auto elm_offset = edge.offset - sizeof(APCLocalArray);
246 uint32_t index = elm_offset / sizeof(TypedValue);
247 if (index < static_cast<const APCLocalArray*>(from_hdr)->size()) {
248 return {CapturedPtr::Value, index};
251 break;
254 case HeaderKind::Pair: {
255 if (edge.offset >= c_Pair::dataOffset()) {
256 auto elm_offset = edge.offset - c_Pair::dataOffset();
257 uint32_t index = elm_offset / sizeof(TypedValue);
258 if (index < 2) {
259 return {CapturedPtr::Value, index};
262 break;
265 case HeaderKind::AwaitAllWH:
266 case HeaderKind::WaitHandle:
267 case HeaderKind::Ref:
268 break;
270 // cases that have explicit pointer name
271 case HeaderKind::AsyncFuncFrame:
272 case HeaderKind::ClosureHdr:
273 case HeaderKind::Closure:
274 // the class of a c_Closure describes the captured variables
275 case HeaderKind::NativeData:
276 case HeaderKind::Object: {
277 auto cls = from_obj->getVMClass();
278 FTRACE(5, "HG: Getting connection name for class {} at {}\n",
279 from_obj->getClassName().data(), from_obj);
280 if (prop_offset >= sizeof(ObjectData)) {
281 uint32_t index = (prop_offset - sizeof(ObjectData)) /
282 sizeof(TypedValue);
283 if (index < cls->numDeclProperties()) {
284 return {CapturedPtr::Property, index};
286 } else {
287 // edge_offset > 0 && prop_offset < 0 means nativedata fields
289 break;
292 case HeaderKind::NativeObject:
293 case HeaderKind::AsyncFuncWH:
294 case HeaderKind::Vector:
295 case HeaderKind::ImmVector:
296 case HeaderKind::Set:
297 case HeaderKind::ImmSet:
298 case HeaderKind::Map:
299 case HeaderKind::ImmMap:
300 case HeaderKind::Empty:
301 case HeaderKind::Globals:
302 case HeaderKind::String:
303 case HeaderKind::Resource:
304 case HeaderKind::BigMalloc:
305 case HeaderKind::Cpp:
306 case HeaderKind::SmallMalloc:
307 case HeaderKind::Free:
308 case HeaderKind::BigObj:
309 case HeaderKind::Hole:
310 case HeaderKind::Slab:
311 // just provide raw prop_offset
312 break;
316 return {CapturedPtr::Offset, uint32_t(prop_offset)};
319 void heapgraphCallback(Array fields, const Variant& callback) {
320 VMRegAnchor _;
321 auto params = make_packed_array(fields);
322 vm_call_user_func(callback, params);
325 void heapgraphCallback(Array fields, Array fields2, const Variant& callback) {
326 VMRegAnchor _;
327 auto params = make_packed_array(fields, fields2);
328 vm_call_user_func(callback, params);
331 static bool isStaticLocal(const HeapGraph::Node& node) {
332 return node.tyindex == type_scan::getIndexForScan<rds::StaticLocalData>() &&
333 type_scan::hasNonConservative();
336 static bool isStaticProp(const HeapGraph::Node& node) {
337 return node.tyindex == type_scan::getIndexForScan<StaticPropData>() &&
338 type_scan::hasNonConservative();
341 static const StringData* header_name_strs[NumHeaderKinds];
343 Array createPhpNode(HeapGraphContextPtr hgptr, int index) {
344 const auto& node = hgptr->hg.nodes[index];
345 const auto& cnode = hgptr->cnodes[index];
347 const StringData* kind_str;
348 if (!node.is_root) {
349 auto k = int(cnode.heap_object.kind);
350 kind_str = header_name_strs[k];
351 if (!kind_str) {
352 kind_str = makeStaticString(header_names[k]);
353 header_name_strs[k] = kind_str;
355 } else {
356 kind_str = s_Root.get(); // fake HeaderKind "Root"
359 auto node_arr = make_map_array(
360 s_index, VarNR(index),
361 s_kind, VarNR(kind_str),
362 s_size, VarNR(int64_t(node.size))
364 if (type_scan::hasNonConservative()) {
365 auto ty = node.tyindex;
366 if (ty > type_scan::kIndexUnknownNoPtrs) {
367 auto type = type_scan::getName(ty);
368 node_arr.set(s_type,
369 make_tv<KindOfPersistentString>(makeStaticString(type)));
372 if (!node.is_root) {
373 if (auto cls = cnode.heap_object.cls) {
374 node_arr.set(s_class, make_tv<KindOfPersistentString>(cls->name()));
376 } else if (isStaticLocal(node)) {
377 node_arr.set(s_local,
378 make_tv<KindOfPersistentString>(cnode.static_local.name));
379 auto func_id = cnode.static_local.funcId;
380 if (func_id != InvalidFuncId) {
381 auto func = Func::fromFuncId(cnode.static_local.funcId);
382 node_arr.set(s_func, make_tv<KindOfPersistentString>(func->name()));
383 if (auto cls = func->cls()) {
384 node_arr.set(s_class, make_tv<KindOfPersistentString>(cls->name()));
387 } else if (isStaticProp(node)) {
388 if (auto cls = cnode.sprop_cache.cls) {
389 auto& sprop = cls->staticProperties()[cnode.sprop_cache.slot];
390 node_arr.set(s_class, make_tv<KindOfPersistentString>(cls->name()));
391 node_arr.set(s_prop, make_tv<KindOfPersistentString>(sprop.name));
394 return node_arr;
397 Array createPhpEdge(HeapGraphContextPtr hgptr, int index) {
398 const auto& ptr = hgptr->hg.ptrs[index];
399 const auto& cptr = hgptr->cptrs[index];
400 const auto& cfrom = hgptr->cnodes[ptr.from];
402 auto ptr_arr = make_map_array(
403 s_index, VarNR(index),
404 s_kind, VarNR(edgeKindName(ptr.ptr_kind)),
405 s_from, VarNR(ptr.from),
406 s_to, VarNR(ptr.to)
408 switch (cptr.index_kind) {
409 case CapturedPtr::Key:
410 ptr_arr.set(s_key, make_tv<KindOfInt64>(cptr.index));
411 break;
412 case CapturedPtr::Value:
413 ptr_arr.set(s_value, make_tv<KindOfInt64>(cptr.index));
414 break;
415 case CapturedPtr::Property: {
416 auto& prop = cfrom.heap_object.cls->declProperties()[cptr.index];
417 ptr_arr.set(s_prop, make_tv<KindOfPersistentString>(prop.name));
418 break;
420 case CapturedPtr::Offset:
421 if (cptr.index) ptr_arr.set(s_offset, make_tv<KindOfInt64>(cptr.index));
422 break;
425 return ptr_arr;
428 std::vector<int> toBoundIntVector(const Array& arr, int64_t max) {
429 std::vector<int> result;
430 result.reserve(arr.size());
431 for (ArrayIter iter(arr); iter; ++iter) {
432 auto index = iter.second().toInt64(); // Cannot re-enter.
433 if (index < 0 || index >= max) {
434 continue;
437 result.push_back(index);
439 return result;
442 ///////////////////////////////////////////////////////////////////////////////
443 // Exports
445 Resource HHVM_FUNCTION(heapgraph_create, void) {
446 HeapGraph hg = makeHeapGraph();
447 std::vector<CapturedNode> cnodes;
448 std::vector<CapturedPtr> cptrs;
450 // Copy edges into captured edges
451 // Capturing edges first because after capturing nodes we nullify the header
452 cptrs.reserve(hg.ptrs.size());
453 for (int i = 0; i < hg.ptrs.size(); ++i) {
454 auto new_ptr = getEdgeInfo(hg, i); // edge name
455 cptrs.push_back(new_ptr);
458 // Copy useful information from heap into cnodes
459 cnodes.resize(hg.nodes.size());
460 for (size_t i = 0, n = hg.nodes.size(); i < n; ++i) {
461 auto& node = hg.nodes[i];
462 auto& cnode = cnodes[i];
463 if (!node.is_root) {
464 auto obj = innerObj(node.h);
465 cnode.heap_object.kind = node.h->kind();
466 cnode.heap_object.cls = obj ? obj->getVMClass() : nullptr;
467 } else if (isStaticLocal(node)) {
468 rds::Handle handle = rds::ptrToHandle<rds::Mode::Any>(node.ptr);
469 auto sym = rds::reverseLink(handle);
470 if (sym) {
471 cnode.static_local = boost::get<rds::StaticLocal>(sym.value());
472 } else {
473 cnode.static_local = {InvalidFuncId, nullptr};
475 } else if (isStaticProp(node)) {
476 rds::Handle handle = rds::ptrToHandle<rds::Mode::Any>(node.ptr);
477 auto sym = rds::reverseLink(handle);
478 if (sym) {
479 cnode.sprop_cache = boost::get<rds::SPropCache>(sym.value());
480 } else {
481 cnode.sprop_cache = {nullptr, 0};
485 // Nullify the pointers to be safe since this is a captured heap
486 node.h = nullptr;
489 auto hgcontext = req::make<HeapGraphContext>(std::move(hg));
490 std::swap(hgcontext->cnodes, cnodes);
491 std::swap(hgcontext->cptrs, cptrs);
492 return Resource(hgcontext);
495 void HHVM_FUNCTION(heapgraph_foreach_node,
496 const Resource& resource,
497 const Variant& callback
499 auto hgptr = get_valid_heapgraph_context_resource(resource, __FUNCTION__);
500 if (!hgptr || callback.isNull()) return;
501 for (int i = 0; i < hgptr->hg.nodes.size(); i++) {
502 auto phpnode = createPhpNode(hgptr, i);
503 heapgraphCallback(phpnode, callback);
507 void HHVM_FUNCTION(heapgraph_foreach_edge,
508 const Resource& resource,
509 const Variant& callback
511 auto hgptr = get_valid_heapgraph_context_resource(resource, __FUNCTION__);
512 if (!hgptr || callback.isNull()) return;
513 for (int i = 0; i < hgptr->hg.ptrs.size(); i++) {
514 auto phpedge = createPhpEdge(hgptr, i);
515 heapgraphCallback(phpedge, callback);
519 void HHVM_FUNCTION(heapgraph_foreach_root,
520 const Resource& resource,
521 const Variant& callback
523 auto hgptr = get_valid_heapgraph_context_resource(resource, __FUNCTION__);
524 if (!hgptr || callback.isNull()) return;
525 for (int i = 0, n = hgptr->hg.root_ptrs.size(); i < n; ++i) {
526 auto phpedge = createPhpEdge(hgptr, hgptr->hg.root_ptrs[i]);
527 heapgraphCallback(phpedge, callback);
531 void HHVM_FUNCTION(heapgraph_foreach_root_node,
532 const Resource& resource,
533 const Variant& callback
535 auto hgptr = get_valid_heapgraph_context_resource(resource, __FUNCTION__);
536 if (!hgptr || callback.isNull()) return;
537 for (auto n : hgptr->hg.root_nodes) {
538 auto phpnode = createPhpNode(hgptr, n);
539 heapgraphCallback(phpnode, callback);
543 void HHVM_FUNCTION(heapgraph_dfs_nodes,
544 const Resource& resource,
545 const Array& roots_arr,
546 const Array& skips_arr,
547 const Variant& callback
549 auto hgptr = get_valid_heapgraph_context_resource(resource, __FUNCTION__);
550 if (!hgptr || callback.isNull()) return;
551 auto max = hgptr->hg.nodes.size();
552 auto roots = toBoundIntVector(roots_arr, max);
553 auto skips = toBoundIntVector(skips_arr, max);
554 dfs_nodes(hgptr->hg, roots, skips, [&](int n) {
555 auto phpnode = createPhpNode(hgptr, n);
556 heapgraphCallback(phpnode, callback);
560 void HHVM_FUNCTION(heapgraph_dfs_edges,
561 const Resource& resource,
562 const Array& roots_arr,
563 const Array& skips_arr,
564 const Variant& callback
566 auto hgptr = get_valid_heapgraph_context_resource(resource, __FUNCTION__);
567 if (!hgptr || callback.isNull()) return;
568 auto max = hgptr->hg.ptrs.size();
569 auto roots = toBoundIntVector(roots_arr, max);
570 auto skips = toBoundIntVector(skips_arr, max);
571 dfs_ptrs(hgptr->hg, roots, skips, [&](int n, int p) {
572 auto phpnode = createPhpNode(hgptr, n);
573 auto phpedge = createPhpEdge(hgptr, p);
574 heapgraphCallback(phpedge, phpnode, callback);
578 Array HHVM_FUNCTION(heapgraph_edge, const Resource& resource, int64_t index) {
579 auto hgptr = get_valid_heapgraph_context_resource(resource, __FUNCTION__);
580 if (!hgptr) return empty_array();
581 if (size_t(index) >= hgptr->hg.ptrs.size()) return empty_array();
582 return createPhpEdge(hgptr, index);
585 Array HHVM_FUNCTION(heapgraph_node, const Resource& resource, int64_t index) {
586 auto hgptr = get_valid_heapgraph_context_resource(resource, __FUNCTION__);
587 if (!hgptr) return empty_array();
588 if (size_t(index) >= hgptr->hg.nodes.size()) return empty_array();
589 return createPhpNode(hgptr, index);
592 Array HHVM_FUNCTION(heapgraph_node_out_edges,
593 const Resource& resource,
594 int64_t index
596 auto hgptr = get_valid_heapgraph_context_resource(resource, __FUNCTION__);
597 if (!hgptr) return empty_array();
598 if (size_t(index) >= hgptr->hg.nodes.size()) return empty_array();
599 size_t num_edges{0};
600 hgptr->hg.eachOutPtr(index, [&](int) { num_edges++; });
601 PackedArrayInit result(num_edges);
602 hgptr->hg.eachOutPtr(index, [&](int ptr) {
603 result.append(createPhpEdge(hgptr, ptr));
605 return result.toArray();
608 Array HHVM_FUNCTION(heapgraph_node_in_edges,
609 const Resource& resource,
610 int64_t index
612 auto hgptr = get_valid_heapgraph_context_resource(resource, __FUNCTION__);
613 if (!hgptr) return empty_array();
614 if (size_t(index) >= hgptr->hg.nodes.size()) return empty_array();
615 size_t num_edges{0};
616 hgptr->hg.eachInPtr(index, [&](int) { num_edges++; });
617 PackedArrayInit result(num_edges);
618 hgptr->hg.eachInPtr(index, [&](int ptr) {
619 result.append(createPhpEdge(hgptr, ptr));
621 return result.toArray();
624 Array HHVM_FUNCTION(heapgraph_stats, const Resource& resource) {
625 auto hgptr = get_valid_heapgraph_context_resource(resource, __FUNCTION__);
626 if (!hgptr) return empty_array();
627 auto result = make_map_array(
628 s_nodes, VarNR(int64_t(hgptr->hg.nodes.size())),
629 s_edges, VarNR(int64_t(hgptr->hg.ptrs.size())),
630 s_roots, VarNR(int64_t(hgptr->hg.root_ptrs.size())),
631 s_root_nodes, VarNR(int64_t(hgptr->hg.root_nodes.size())),
632 s_exact, VarNR(type_scan::hasNonConservative() ? 1 : 0)
634 return result;
637 ///////////////////////////////////////////////////////////////////////////////
638 // Extension
642 struct heapgraphExtension final : Extension {
643 heapgraphExtension() : Extension("heapgraph", "1.0") { }
645 void moduleInit() override {
646 HHVM_FALIAS(HH\\heapgraph_create, heapgraph_create);
647 HHVM_FALIAS(HH\\heapgraph_stats, heapgraph_stats);
648 HHVM_FALIAS(HH\\heapgraph_foreach_node, heapgraph_foreach_node);
649 HHVM_FALIAS(HH\\heapgraph_foreach_edge, heapgraph_foreach_edge);
650 HHVM_FALIAS(HH\\heapgraph_foreach_root, heapgraph_foreach_root);
651 HHVM_FALIAS(HH\\heapgraph_foreach_root_node, heapgraph_foreach_root_node);
652 HHVM_FALIAS(HH\\heapgraph_edge, heapgraph_edge);
653 HHVM_FALIAS(HH\\heapgraph_node, heapgraph_node);
654 HHVM_FALIAS(HH\\heapgraph_node_out_edges, heapgraph_node_out_edges);
655 HHVM_FALIAS(HH\\heapgraph_node_in_edges, heapgraph_node_in_edges);
656 HHVM_FALIAS(HH\\heapgraph_dfs_nodes, heapgraph_dfs_nodes);
657 HHVM_FALIAS(HH\\heapgraph_dfs_edges, heapgraph_dfs_edges);
659 loadSystemlib();
661 } s_heapgraph_extension;
664 ///////////////////////////////////////////////////////////////////////////////