Revision: 24285
Author: a...@chromium.org
Date: Mon Sep 29 12:59:54 2014 UTC
Log: Initial implementation of GetStackSample sampling profiler API.
The patch is based on https://codereview.chromium.org/578163002/#ps20001
made by gho...@chromium.org
LOG=N
BUG=v8:3490
R=bmeu...@chromium.org, yu...@chromium.org
Review URL: https://codereview.chromium.org/596533002
https://code.google.com/p/v8/source/detail?r=24285
Added:
/branches/bleeding_edge/test/cctest/test-sampler-api.cc
Modified:
/branches/bleeding_edge/include/v8.h
/branches/bleeding_edge/src/api.cc
/branches/bleeding_edge/src/globals.h
/branches/bleeding_edge/src/sampler.cc
/branches/bleeding_edge/src/sampler.h
/branches/bleeding_edge/src/vm-state.h
/branches/bleeding_edge/test/cctest/cctest.gyp
/branches/bleeding_edge/test/cctest/test-api.cc
=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/cctest/test-sampler-api.cc Mon Sep 29
12:59:54 2014 UTC
@@ -0,0 +1,245 @@
+// Copyright 2014 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.
+//
+// Tests the sampling API in include/v8.h
+
+#include <map>
+#include <string>
+#include "include/v8.h"
+#include "src/simulator.h"
+#include "test/cctest/cctest.h"
+
+namespace {
+
+class Sample {
+ public:
+ enum { kFramesLimit = 255 };
+
+ Sample() {}
+
+ typedef const void* const* const_iterator;
+ const_iterator begin() const { return data_.start(); }
+ const_iterator end() const { return &data_[data_.length()]; }
+
+ int size() const { return data_.length(); }
+ v8::internal::Vector<void*>& data() { return data_; }
+
+ private:
+ v8::internal::EmbeddedVector<void*, kFramesLimit> data_;
+};
+
+
+#if defined(USE_SIMULATOR)
+class SimulatorHelper {
+ public:
+ inline bool Init(v8::Isolate* isolate) {
+ simulator_ = reinterpret_cast<v8::internal::Isolate*>(isolate)
+ ->thread_local_top()
+ ->simulator_;
+ // Check if there is active simulator.
+ return simulator_ != NULL;
+ }
+
+ inline void FillRegisters(v8::RegisterState* state) {
+#if V8_TARGET_ARCH_ARM
+ state->pc = reinterpret_cast<void*>(simulator_->get_pc());
+ state->sp = reinterpret_cast<void*>(
+ simulator_->get_register(v8::internal::Simulator::sp));
+ state->fp = reinterpret_cast<void*>(
+ simulator_->get_register(v8::internal::Simulator::r11));
+#elif V8_TARGET_ARCH_ARM64
+ if (simulator_->sp() == 0 || simulator_->fp() == 0) {
+ // It's possible that the simulator is interrupted while it is
updating
+ // the sp or fp register. ARM64 simulator does this in two steps:
+ // first setting it to zero and then setting it to a new value.
+ // Bailout if sp/fp doesn't contain the new value.
+ return;
+ }
+ state->pc = reinterpret_cast<void*>(simulator_->pc());
+ state->sp = reinterpret_cast<void*>(simulator_->sp());
+ state->fp = reinterpret_cast<void*>(simulator_->fp());
+#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
+ state->pc = reinterpret_cast<void*>(simulator_->get_pc());
+ state->sp = reinterpret_cast<void*>(
+ simulator_->get_register(v8::internal::Simulator::sp));
+ state->fp = reinterpret_cast<void*>(
+ simulator_->get_register(v8::internal::Simulator::fp));
+#endif
+ }
+
+ private:
+ v8::internal::Simulator* simulator_;
+};
+#endif // USE_SIMULATOR
+
+
+class SamplingTestHelper {
+ public:
+ struct CodeEventEntry {
+ std::string name;
+ const void* code_start;
+ size_t code_len;
+ };
+ typedef std::map<const void*, CodeEventEntry> CodeEntries;
+
+ explicit SamplingTestHelper(const std::string& test_function)
+ : sample_is_taken_(false), isolate_(CcTest::isolate()) {
+ DCHECK_EQ(NULL, instance_);
+ instance_ = this;
+ v8::HandleScope scope(isolate_);
+ v8::Handle<v8::ObjectTemplate> global =
v8::ObjectTemplate::New(isolate_);
+ global->Set(v8::String::NewFromUtf8(isolate_, "CollectSample"),
+ v8::FunctionTemplate::New(isolate_, CollectSample));
+ LocalContext env(isolate_, NULL, global);
+ isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault,
+ JitCodeEventHandler);
+ v8::Script::Compile(
+ v8::String::NewFromUtf8(isolate_, test_function.c_str()))->Run();
+ }
+
+ ~SamplingTestHelper() {
+ isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault, NULL);
+ instance_ = NULL;
+ }
+
+ Sample& sample() { return sample_; }
+
+ const CodeEventEntry* FindEventEntry(const void* address) {
+ CodeEntries::const_iterator it = code_entries_.upper_bound(address);
+ if (it == code_entries_.begin()) return NULL;
+ const CodeEventEntry& entry = (--it)->second;
+ const void* code_end =
+ static_cast<const uint8_t*>(entry.code_start) + entry.code_len;
+ return address < code_end ? &entry : NULL;
+ }
+
+ private:
+ static void CollectSample(const v8::FunctionCallbackInfo<v8::Value>&
args) {
+ instance_->DoCollectSample();
+ }
+
+ static void JitCodeEventHandler(const v8::JitCodeEvent* event) {
+ instance_->DoJitCodeEventHandler(event);
+ }
+
+ // The JavaScript calls this function when on full stack depth.
+ void DoCollectSample() {
+ v8::RegisterState state;
+#if defined(USE_SIMULATOR)
+ SimulatorHelper simulator_helper;
+ if (!simulator_helper.Init(isolate_)) return;
+ simulator_helper.FillRegisters(&state);
+#else
+ state.pc = NULL;
+ state.fp = &state;
+ state.sp = &state;
+#endif
+ v8::SampleInfo info;
+ isolate_->GetStackSample(state, sample_.data().start(),
+ static_cast<size_t>(sample_.size()), &info);
+ size_t frames_count = info.frames_count;
+ CHECK_LE(frames_count, static_cast<size_t>(sample_.size()));
+ sample_.data().Truncate(static_cast<int>(frames_count));
+ sample_is_taken_ = true;
+ }
+
+ void DoJitCodeEventHandler(const v8::JitCodeEvent* event) {
+ if (sample_is_taken_) return;
+ switch (event->type) {
+ case v8::JitCodeEvent::CODE_ADDED: {
+ CodeEventEntry entry;
+ entry.name = std::string(event->name.str, event->name.len);
+ entry.code_start = event->code_start;
+ entry.code_len = event->code_len;
+ code_entries_.insert(std::make_pair(entry.code_start, entry));
+ break;
+ }
+ case v8::JitCodeEvent::CODE_MOVED: {
+ CodeEntries::iterator it = code_entries_.find(event->code_start);
+ CHECK(it != code_entries_.end());
+ code_entries_.erase(it);
+ CodeEventEntry entry;
+ entry.name = std::string(event->name.str, event->name.len);
+ entry.code_start = event->new_code_start;
+ entry.code_len = event->code_len;
+ code_entries_.insert(std::make_pair(entry.code_start, entry));
+ break;
+ }
+ case v8::JitCodeEvent::CODE_REMOVED:
+ code_entries_.erase(event->code_start);
+ break;
+ default:
+ break;
+ }
+ }
+
+ Sample sample_;
+ bool sample_is_taken_;
+ v8::Isolate* isolate_;
+ CodeEntries code_entries_;
+
+ static SamplingTestHelper* instance_;
+};
+
+SamplingTestHelper* SamplingTestHelper::instance_;
+
+} // namespace
+
+
+// A JavaScript function which takes stack depth
+// (minimum value 2) as an argument.
+// When at the bottom of the recursion,
+// the JavaScript code calls into C++ test code,
+// waiting for the sampler to take a sample.
+static const char* test_function =
+ "function func(depth) {"
+ " if (depth == 2) CollectSample();"
+ " else return func(depth - 1);"
+ "}";
+
+
+TEST(StackDepthIsConsistent) {
+ SamplingTestHelper helper(std::string(test_function) + "func(8);");
+ CHECK_EQ(8, helper.sample().size());
+}
+
+
+TEST(StackDepthDoesNotExceedMaxValue) {
+ SamplingTestHelper helper(std::string(test_function) + "func(300);");
+ CHECK_EQ(Sample::kFramesLimit, helper.sample().size());
+}
+
+
+// The captured sample should have three pc values.
+// They should fall in the range where the compiled code resides.
+// The expected stack is:
+// bottom of stack [{anon script}, outer, inner] top of stack
+// ^ ^ ^
+// sample.stack indices 2 1 0
+TEST(StackFramesConsistent) {
+ // Note: The arguments.callee stuff is there so that the
+ // functions are not optimized away.
+ const char* test_script =
+ "function test_sampler_api_inner() {"
+ " CollectSample();"
+ " return arguments.callee.toString();"
+ "}"
+ "function test_sampler_api_outer() {"
+ " return test_sampler_api_inner() + arguments.callee.toString();"
+ "}"
+ "test_sampler_api_outer();";
+
+ SamplingTestHelper helper(test_script);
+ Sample& sample = helper.sample();
+ CHECK_EQ(3, sample.size());
+
+ const SamplingTestHelper::CodeEventEntry* entry;
+ entry = helper.FindEventEntry(sample.begin()[0]);
+ CHECK_NE(NULL, entry);
+ CHECK(std::string::npos != entry->name.find("test_sampler_api_inner"));
+
+ entry = helper.FindEventEntry(sample.begin()[1]);
+ CHECK_NE(NULL, entry);
+ CHECK(std::string::npos != entry->name.find("test_sampler_api_outer"));
+}
=======================================
--- /branches/bleeding_edge/include/v8.h Mon Sep 29 12:17:31 2014 UTC
+++ /branches/bleeding_edge/include/v8.h Mon Sep 29 12:59:54 2014 UTC
@@ -1415,6 +1415,27 @@
};
+// A StateTag represents a possible state of the VM.
+enum StateTag { JS, GC, COMPILER, OTHER, EXTERNAL, IDLE };
+
+
+// A RegisterState represents the current state of registers used
+// by the sampling profiler API.
+struct RegisterState {
+ RegisterState() : pc(NULL), sp(NULL), fp(NULL) {}
+ void* pc; // Instruction pointer.
+ void* sp; // Stack pointer.
+ void* fp; // Frame pointer.
+};
+
+
+// The output structure filled up by GetStackSample API function.
+struct SampleInfo {
+ size_t frames_count;
+ StateTag vm_state;
+};
+
+
/**
* A JSON Parser.
*/
@@ -4559,6 +4580,21 @@
*/
void GetHeapStatistics(HeapStatistics* heap_statistics);
+ /**
+ * Get a call stack sample from the isolate.
+ * \param state Execution state.
+ * \param frames Caller allocated buffer to store stack frames.
+ * \param frames_limit Maximum number of frames to capture. The buffer
must
+ * be large enough to hold the number of frames.
+ * \param sample_info The sample info is filled up by the function
+ * provides number of actual captured stack frames and
+ * the current VM state.
+ * \note GetStackSample should only be called when the JS thread is
paused or
+ * interrupted. Otherwise the behavior is undefined.
+ */
+ void GetStackSample(const RegisterState& state, void** frames,
+ size_t frames_limit, SampleInfo* sample_info);
+
/**
* Adjusts the amount of registered external memory. Used to give V8 an
* indication of the amount of externally allocated memory that is kept
alive
=======================================
--- /branches/bleeding_edge/src/api.cc Mon Sep 29 12:17:31 2014 UTC
+++ /branches/bleeding_edge/src/api.cc Mon Sep 29 12:59:54 2014 UTC
@@ -40,6 +40,7 @@
#include "src/prototype.h"
#include "src/runtime/runtime.h"
#include "src/runtime-profiler.h"
+#include "src/sampler.h"
#include "src/scanner-character-streams.h"
#include "src/simulator.h"
#include "src/snapshot.h"
@@ -51,9 +52,9 @@
#define LOG_API(isolate, expr) LOG(isolate, ApiEntryCall(expr))
-#define ENTER_V8(isolate) \
- DCHECK((isolate)->IsInitialized()); \
- i::VMState<i::OTHER> __state__((isolate))
+#define ENTER_V8(isolate) \
+ DCHECK((isolate)->IsInitialized()); \
+ i::VMState<v8::OTHER> __state__((isolate))
namespace v8 {
@@ -6700,6 +6701,14 @@
heap_statistics->used_heap_size_ = heap->SizeOfObjects();
heap_statistics->heap_size_limit_ = heap->MaxReserved();
}
+
+
+void Isolate::GetStackSample(const RegisterState& state, void** frames,
+ size_t frames_limit, SampleInfo* sample_info)
{
+ i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
+ i::TickSample::GetStackSample(isolate, state, frames, frames_limit,
+ sample_info);
+}
void Isolate::SetEventLogger(LogEventCallback that) {
@@ -7199,13 +7208,13 @@
void CpuProfiler::SetIdle(bool is_idle) {
i::Isolate* isolate = reinterpret_cast<i::CpuProfiler*>(this)->isolate();
- i::StateTag state = isolate->current_vm_state();
- DCHECK(state == i::EXTERNAL || state == i::IDLE);
+ v8::StateTag state = isolate->current_vm_state();
+ DCHECK(state == v8::EXTERNAL || state == v8::IDLE);
if (isolate->js_entry_sp() != NULL) return;
if (is_idle) {
- isolate->set_current_vm_state(i::IDLE);
- } else if (state == i::IDLE) {
- isolate->set_current_vm_state(i::EXTERNAL);
+ isolate->set_current_vm_state(v8::IDLE);
+ } else if (state == v8::IDLE) {
+ isolate->set_current_vm_state(v8::EXTERNAL);
}
}
=======================================
--- /branches/bleeding_edge/src/globals.h Wed Sep 24 08:49:32 2014 UTC
+++ /branches/bleeding_edge/src/globals.h Mon Sep 29 12:59:54 2014 UTC
@@ -547,22 +547,6 @@
};
-// Logging and profiling. A StateTag represents a possible state of
-// the VM. The logger maintains a stack of these. Creating a VMState
-// object enters a state by pushing on the stack, and destroying a
-// VMState object leaves a state by popping the current state from the
-// stack.
-
-enum StateTag {
- JS,
- GC,
- COMPILER,
- OTHER,
- EXTERNAL,
- IDLE
-};
-
-
//
-----------------------------------------------------------------------------
// Macros
=======================================
--- /branches/bleeding_edge/src/sampler.cc Fri Aug 29 09:39:28 2014 UTC
+++ /branches/bleeding_edge/src/sampler.cc Mon Sep 29 12:59:54 2014 UTC
@@ -226,13 +226,13 @@
#if defined(USE_SIMULATOR)
class SimulatorHelper {
public:
- inline bool Init(Sampler* sampler, Isolate* isolate) {
+ inline bool Init(Isolate* isolate) {
simulator_ = isolate->thread_local_top()->simulator_;
// Check if there is active simulator.
return simulator_ != NULL;
}
- inline void FillRegisters(RegisterState* state) {
+ inline void FillRegisters(v8::RegisterState* state) {
#if V8_TARGET_ARCH_ARM
state->pc = reinterpret_cast<Address>(simulator_->get_pc());
state->sp = reinterpret_cast<Address>(simulator_->get_register(
@@ -241,27 +241,21 @@
Simulator::r11));
#elif V8_TARGET_ARCH_ARM64
if (simulator_->sp() == 0 || simulator_->fp() == 0) {
- // It possible that the simulator is interrupted while it is updating
+ // It's possible that the simulator is interrupted while it is
updating
// the sp or fp register. ARM64 simulator does this in two steps:
- // first setting it to zero and then setting it to the new value.
+ // first setting it to zero and then setting it to a new value.
// Bailout if sp/fp doesn't contain the new value.
return;
}
state->pc = reinterpret_cast<Address>(simulator_->pc());
state->sp = reinterpret_cast<Address>(simulator_->sp());
state->fp = reinterpret_cast<Address>(simulator_->fp());
-#elif V8_TARGET_ARCH_MIPS
+#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
state->pc = reinterpret_cast<Address>(simulator_->get_pc());
state->sp = reinterpret_cast<Address>(simulator_->get_register(
Simulator::sp));
state->fp = reinterpret_cast<Address>(simulator_->get_register(
Simulator::fp));
-#elif V8_TARGET_ARCH_MIPS64
- state->pc = reinterpret_cast<Address>(simulator_->get_pc());
- state->sp = reinterpret_cast<Address>(simulator_->get_register(
- Simulator::sp));
- state->fp = reinterpret_cast<Address>(simulator_->get_register(
- Simulator::fp));
#endif
}
@@ -353,11 +347,11 @@
Sampler* sampler = isolate->logger()->sampler();
if (sampler == NULL) return;
- RegisterState state;
+ v8::RegisterState state;
#if defined(USE_SIMULATOR)
SimulatorHelper helper;
- if (!helper.Init(sampler, isolate)) return;
+ if (!helper.Init(isolate)) return;
helper.FillRegisters(&state);
// It possible that the simulator is interrupted while it is updating
// the sp or fp register. ARM64 simulator does this in two steps:
@@ -577,20 +571,17 @@
// StackTracer implementation
//
DISABLE_ASAN void TickSample::Init(Isolate* isolate,
- const RegisterState& regs) {
+ const v8::RegisterState& regs) {
DCHECK(isolate->IsInitialized());
timestamp = base::TimeTicks::HighResolutionNow();
- pc = regs.pc;
+ pc = reinterpret_cast<Address>(regs.pc);
state = isolate->current_vm_state();
// Avoid collecting traces while doing GC.
if (state == GC) return;
Address js_entry_sp = isolate->js_entry_sp();
- if (js_entry_sp == 0) {
- // Not executing JS now.
- return;
- }
+ if (js_entry_sp == 0) return; // Not executing JS now.
ExternalCallbackScope* scope = isolate->external_callback_scope();
Address handler = Isolate::handler(isolate->thread_local_top());
@@ -603,18 +594,40 @@
} else {
// Sample potential return address value for frameless invocation of
// stubs (we'll figure out later, if this value makes sense).
- tos = Memory::Address_at(regs.sp);
+ tos = Memory::Address_at(reinterpret_cast<Address>(regs.sp));
has_external_callback = false;
}
- SafeStackFrameIterator it(isolate, regs.fp, regs.sp, js_entry_sp);
+ SafeStackFrameIterator it(isolate, reinterpret_cast<Address>(regs.fp),
+ reinterpret_cast<Address>(regs.sp),
js_entry_sp);
top_frame_type = it.top_frame_type();
- unsigned i = 0;
- while (!it.done() && i < TickSample::kMaxFramesCount) {
- stack[i++] = it.frame()->pc();
+
+ SampleInfo info;
+ GetStackSample(isolate, regs, reinterpret_cast<void**>(&stack[0]),
+ kMaxFramesCount, &info);
+ frames_count = static_cast<unsigned>(info.frames_count);
+}
+
+
+void TickSample::GetStackSample(Isolate* isolate, const v8::RegisterState&
regs,
+ void** frames, size_t frames_limit,
+ v8::SampleInfo* sample_info) {
+ DCHECK(isolate->IsInitialized());
+ sample_info->frames_count = 0;
+ sample_info->vm_state = isolate->current_vm_state();
+ if (sample_info->vm_state == GC) return;
+
+ Address js_entry_sp = isolate->js_entry_sp();
+ if (js_entry_sp == 0) return; // Not executing JS now.
+
+ SafeStackFrameIterator it(isolate, reinterpret_cast<Address>(regs.fp),
+ reinterpret_cast<Address>(regs.sp),
js_entry_sp);
+ size_t i = 0;
+ while (!it.done() && i < frames_limit) {
+ frames[i++] = it.frame()->pc();
it.Advance();
}
- frames_count = i;
+ sample_info->frames_count = i;
}
@@ -682,7 +695,7 @@
}
-void Sampler::SampleStack(const RegisterState& state) {
+void Sampler::SampleStack(const v8::RegisterState& state) {
TickSample* sample = isolate_->cpu_profiler()->StartTickSample();
TickSample sample_obj;
if (sample == NULL) sample = &sample_obj;
@@ -714,7 +727,7 @@
#if defined(USE_SIMULATOR)
SimulatorHelper helper;
- if (!helper.Init(this, isolate())) return;
+ if (!helper.Init(isolate())) return;
#endif
const DWORD kSuspendFailed = static_cast<DWORD>(-1);
@@ -725,7 +738,7 @@
memset(&context, 0, sizeof(context));
context.ContextFlags = CONTEXT_FULL;
if (GetThreadContext(profiled_thread, &context) != 0) {
- RegisterState state;
+ v8::RegisterState state;
#if defined(USE_SIMULATOR)
helper.FillRegisters(&state);
#else
=======================================
--- /branches/bleeding_edge/src/sampler.h Mon Jun 30 13:25:46 2014 UTC
+++ /branches/bleeding_edge/src/sampler.h Mon Sep 29 12:59:54 2014 UTC
@@ -5,6 +5,8 @@
#ifndef V8_SAMPLER_H_
#define V8_SAMPLER_H_
+#include "include/v8.h"
+
#include "src/base/atomicops.h"
#include "src/frames.h"
#include "src/globals.h"
@@ -21,13 +23,6 @@
// (if used for profiling) the program counter and stack pointer for
// the thread that created it.
-struct RegisterState {
- RegisterState() : pc(NULL), sp(NULL), fp(NULL) {}
- Address pc; // Instruction pointer.
- Address sp; // Stack pointer.
- Address fp; // Frame pointer.
-};
-
// TickSample captures the information collected for each sample.
struct TickSample {
TickSample()
@@ -37,7 +32,10 @@
frames_count(0),
has_external_callback(false),
top_frame_type(StackFrame::NONE) {}
- void Init(Isolate* isolate, const RegisterState& state);
+ void Init(Isolate* isolate, const v8::RegisterState& state);
+ static void GetStackSample(Isolate* isolate, const v8::RegisterState&
state,
+ void** frames, size_t frames_limit,
+ v8::SampleInfo* sample_info);
StateTag state; // The state of the VM.
Address pc; // Instruction pointer.
union {
@@ -67,7 +65,7 @@
int interval() const { return interval_; }
// Performs stack sampling.
- void SampleStack(const RegisterState& regs);
+ void SampleStack(const v8::RegisterState& regs);
// Start and stop sampler.
void Start();
=======================================
--- /branches/bleeding_edge/src/vm-state.h Tue Jun 3 08:12:43 2014 UTC
+++ /branches/bleeding_edge/src/vm-state.h Mon Sep 29 12:59:54 2014 UTC
@@ -11,6 +11,11 @@
namespace v8 {
namespace internal {
+// Logging and profiling. A StateTag represents a possible state of
+// the VM. The logger maintains a stack of these. Creating a VMState
+// object enters a state by pushing on the stack, and destroying a
+// VMState object leaves a state by popping the current state from the
+// stack.
template <StateTag Tag>
class VMState BASE_EMBEDDED {
public:
=======================================
--- /branches/bleeding_edge/test/cctest/cctest.gyp Mon Sep 29 07:29:14 2014
UTC
+++ /branches/bleeding_edge/test/cctest/cctest.gyp Mon Sep 29 12:59:54 2014
UTC
@@ -143,6 +143,7 @@
'test-regexp.cc',
'test-reloc-info.cc',
'test-representation.cc',
+ 'test-sampler-api.cc',
'test-serialize.cc',
'test-spaces.cc',
'test-strings.cc',
=======================================
--- /branches/bleeding_edge/test/cctest/test-api.cc Mon Sep 29 10:22:56
2014 UTC
+++ /branches/bleeding_edge/test/cctest/test-api.cc Mon Sep 29 12:59:54
2014 UTC
@@ -981,7 +981,7 @@
// If CPU profiler is active check that when API callback is invoked
// VMState is set to EXTERNAL.
if (isolate->cpu_profiler()->is_profiling()) {
- CHECK_EQ(i::EXTERNAL, isolate->current_vm_state());
+ CHECK_EQ(v8::EXTERNAL, isolate->current_vm_state());
CHECK(isolate->external_callback_scope());
CHECK_EQ(callback, isolate->external_callback_scope()->callback());
}
--
--
v8-dev mailing list
v8-dev@googlegroups.com
http://groups.google.com/group/v8-dev
---
You received this message because you are subscribed to the Google Groups "v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to v8-dev+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.