Move vmfp, vmsp, and vmpc into RDS
[hiphop-php.git] / hphp / runtime / vm / event-hook.cpp
blob1fd458b36c11b74702375fa052075090312ed114
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/event-hook.h"
18 #include "hphp/runtime/base/types.h"
19 #include "hphp/runtime/vm/func.h"
20 #include "hphp/runtime/vm/jit/mc-generator.h"
21 #include "hphp/runtime/vm/jit/translator-inline.h"
22 #include "hphp/runtime/base/builtin-functions.h"
23 #include "hphp/runtime/base/complex-types.h"
24 #include "hphp/runtime/ext/ext_function.h"
25 #include "hphp/runtime/vm/runtime.h"
26 #include "hphp/runtime/vm/vm-regs.h"
27 #include "hphp/runtime/base/thread-info.h"
28 #include "hphp/runtime/ext/ext_xenon.h"
29 #include "hphp/runtime/ext/asio/asio_session.h"
31 namespace HPHP {
33 static StaticString s_args("args");
34 static StaticString s_enter("enter");
35 static StaticString s_exit("exit");
36 static StaticString s_exception("exception");
37 static StaticString s_name("name");
38 static StaticString s_return("return");
40 // implemented in runtime/ext/ext_hotprofiler.cpp
41 extern void begin_profiler_frame(Profiler *p, const char *symbol);
42 extern void end_profiler_frame(Profiler *p, const char *symbol);
44 void EventHook::Enable() {
45 ThreadInfo::s_threadInfo->m_reqInjectionData.setEventHookFlag();
48 void EventHook::Disable() {
49 ThreadInfo::s_threadInfo->m_reqInjectionData.clearEventHookFlag();
52 void EventHook::EnableAsync() {
53 ThreadInfo::s_threadInfo->m_reqInjectionData.setAsyncEventHookFlag();
56 void EventHook::DisableAsync() {
57 ThreadInfo::s_threadInfo->m_reqInjectionData.clearAsyncEventHookFlag();
60 void EventHook::EnableIntercept() {
61 ThreadInfo::s_threadInfo->m_reqInjectionData.setInterceptFlag();
64 void EventHook::DisableIntercept() {
65 ThreadInfo::s_threadInfo->m_reqInjectionData.clearInterceptFlag();
68 ssize_t EventHook::CheckSurprise() {
69 ThreadInfo* info = ThreadInfo::s_threadInfo.getNoCheck();
70 return check_request_surprise(info);
73 ssize_t EventHook::GetConditionFlags() {
74 return RDS::header()->conditionFlags.load();
77 class ExecutingSetprofileCallbackGuard {
78 public:
79 ExecutingSetprofileCallbackGuard() {
80 g_context->m_executingSetprofileCallback = true;
83 ~ExecutingSetprofileCallbackGuard() {
84 g_context->m_executingSetprofileCallback = false;
88 namespace {
90 bool shouldRunUserProfiler(const Func* func) {
91 // Don't do anything if we are running the profiling function itself
92 // or if we haven't set up a profiler.
93 if (g_context->m_executingSetprofileCallback ||
94 g_context->m_setprofileCallback.isNull()) {
95 return false;
97 // Don't profile 86ctor, since its an implementation detail,
98 // and we dont guarantee to call it
99 if (func->cls() && func == func->cls()->getCtor() &&
100 Func::isSpecial(func->name())) {
101 return false;
103 return true;
106 void runUserProfilerOnFunctionEnter(const ActRec* ar) {
107 VMRegAnchor _;
108 ExecutingSetprofileCallbackGuard guard;
110 Array params;
111 params.append(s_enter);
112 params.append(VarNR(ar->func()->fullName()));
114 Array frameinfo;
115 frameinfo.set(s_args, hhvm_get_frame_args(ar, 0));
116 params.append(frameinfo);
118 vm_call_user_func(g_context->m_setprofileCallback, params);
121 void runUserProfilerOnFunctionExit(const ActRec* ar, const TypedValue* retval,
122 ObjectData* exception) {
123 VMRegAnchor _;
124 ExecutingSetprofileCallbackGuard guard;
126 Array params;
127 params.append(s_exit);
128 params.append(VarNR(ar->func()->fullName()));
130 Array frameinfo;
131 if (retval) {
132 frameinfo.set(s_return, tvAsCVarRef(retval));
133 } else if (exception) {
134 frameinfo.set(s_exception, exception);
136 params.append(frameinfo);
138 vm_call_user_func(g_context->m_setprofileCallback, params);
143 static Array get_frame_args_with_ref(const ActRec* ar) {
144 int numParams = ar->func()->numParams();
145 int numArgs = ar->numArgs();
147 PackedArrayInit retArray(numArgs);
149 auto local = reinterpret_cast<TypedValue*>(
150 uintptr_t(ar) - sizeof(TypedValue)
152 for (int i = 0; i < numArgs; ++i) {
153 if (i < numParams) {
154 // This corresponds to one of the function's formal parameters, so it's
155 // on the stack.
156 retArray.appendWithRef(tvAsCVarRef(local));
157 --local;
158 } else {
159 // This is not a formal parameter, so it's in the ExtraArgs.
160 retArray.appendWithRef(tvAsCVarRef(ar->getExtraArg(i - numParams)));
164 return retArray.toArray();
167 bool EventHook::RunInterceptHandler(ActRec* ar) {
168 const Func* func = ar->func();
169 if (LIKELY(func->maybeIntercepted() == 0)) return true;
171 // Intercept only original generator / async function calls, not resumption.
172 if (ar->resumed()) return true;
174 Variant* h = get_intercept_handler(func->fullNameStr(),
175 &func->maybeIntercepted());
176 if (!h) return true;
179 * In production mode, only functions that we have assumed can be
180 * intercepted during static analysis should actually be
181 * intercepted.
183 if (RuntimeOption::RepoAuthoritative &&
184 !RuntimeOption::EvalJitEnableRenameFunction) {
185 if (!(func->attrs() & AttrInterceptable)) {
186 raise_error("fb_intercept was used on a non-interceptable function (%s) "
187 "in RepoAuthoritative mode", func->fullName()->data());
191 VMRegAnchor _;
193 PC savePc = vmpc();
195 Variant doneFlag = true;
196 Variant called_on;
198 if (ar->hasThis()) {
199 called_on = Variant(ar->getThis());
200 } else if (ar->hasClass()) {
201 // For static methods, give handler the name of called class
202 called_on = Variant(const_cast<StringData*>(ar->getClass()->name()));
204 Variant intArgs =
205 PackedArrayInit(5)
206 .append(VarNR(ar->func()->fullName()))
207 .append(called_on)
208 .append(get_frame_args_with_ref(ar))
209 .append(h->asCArrRef()[1])
210 .appendRef(doneFlag)
211 .toArray();
213 Variant ret = vm_call_user_func(h->asCArrRef()[0], intArgs);
214 if (doneFlag.toBoolean()) {
215 Offset pcOff;
216 ActRec* outer = g_context->getPrevVMState(ar, &pcOff);
218 frame_free_locals_inl_no_hook<true>(ar, ar->func()->numLocals());
219 Stack& stack = vmStack();
220 stack.top() = (Cell*)(ar + 1);
221 cellDup(*ret.asCell(), *stack.allocTV());
223 vmfp() = outer;
224 vmpc() = outer ? outer->func()->unit()->at(pcOff) : nullptr;
226 return false;
228 vmfp() = ar;
229 vmpc() = savePc;
231 return true;
234 const char* EventHook::GetFunctionNameForProfiler(const Func* func,
235 int funcType) {
236 const char* name;
237 switch (funcType) {
238 case EventHook::NormalFunc:
239 name = func->fullName()->data();
240 if (name[0] == '\0') {
241 // We're evaling some code for internal purposes, most
242 // likely getting the default value for a function parameter
243 name = "{internal}";
245 break;
246 case EventHook::PseudoMain:
247 name = makeStaticString(
248 std::string("run_init::") + func->unit()->filepath()->data())
249 ->data();
250 break;
251 case EventHook::Eval:
252 name = "_";
253 break;
254 default:
255 not_reached();
257 return name;
260 void EventHook::onFunctionEnter(const ActRec* ar, int funcType, ssize_t flags) {
261 // Xenon
262 if (flags & RequestInjectionData::XenonSignalFlag) {
263 Xenon::getInstance().log(true);
266 // User profiler
267 if (flags & RequestInjectionData::EventHookFlag) {
268 if (shouldRunUserProfiler(ar->func())) {
269 runUserProfilerOnFunctionEnter(ar);
271 #ifdef HOTPROFILER
272 Profiler* profiler = ThreadInfo::s_threadInfo->m_profiler;
273 if (profiler != nullptr) {
274 begin_profiler_frame(profiler,
275 GetFunctionNameForProfiler(ar->func(), funcType));
277 #endif
281 void EventHook::onFunctionExit(const ActRec* ar, const TypedValue* retval,
282 const Fault* fault, ssize_t flags) {
283 // Xenon
284 if (flags & RequestInjectionData::XenonSignalFlag) {
285 Xenon::getInstance().log(false);
288 // User profiler
290 // Inlined calls normally skip the function enter and exit events. If we
291 // side exit in an inlined callee, we want to make sure to skip the exit
292 // event to avoid unbalancing the call stack.
293 if ((flags & RequestInjectionData::EventHookFlag) &&
294 (JIT::TCA)ar->m_savedRip != JIT::tx->uniqueStubs.retInlHelper) {
295 #ifdef HOTPROFILER
296 Profiler* profiler = ThreadInfo::s_threadInfo->m_profiler;
297 if (profiler != nullptr) {
298 // NB: we don't have a function type flag to match what we got in
299 // onFunctionEnter. That's okay, though... we tolerate this in
300 // TraceProfiler.
301 end_profiler_frame(profiler,
302 GetFunctionNameForProfiler(ar->func(), NormalFunc));
304 #endif
306 if (shouldRunUserProfiler(ar->func())) {
307 if (ThreadInfo::s_threadInfo->m_pendingException != nullptr) {
308 // Avoid running PHP code when exception from destructor is pending.
309 // TODO(#2329497) will not happen once CheckSurprise is used
310 } else if (!fault) {
311 runUserProfilerOnFunctionExit(ar, retval, nullptr);
312 } else if (fault->m_faultType == Fault::Type::UserException) {
313 runUserProfilerOnFunctionExit(ar, retval, fault->m_userException);
314 } else {
315 // Avoid running PHP code when unwinding C++ exception.
321 bool EventHook::onFunctionCall(const ActRec* ar, int funcType) {
322 ssize_t flags = CheckSurprise();
323 if (flags & RequestInjectionData::InterceptFlag &&
324 !RunInterceptHandler(const_cast<ActRec*>(ar))) {
325 return false;
327 onFunctionEnter(ar, funcType, flags);
328 return true;
331 void EventHook::onFunctionResume(const ActRec* ar) {
332 ssize_t flags = CheckSurprise();
333 onFunctionEnter(ar, EventHook::NormalFunc, flags);
336 void EventHook::onFunctionSuspend(const ActRec* ar, bool suspendingResumed) {
337 ssize_t flags = CheckSurprise();
338 onFunctionExit(ar, nullptr, nullptr, flags);
340 // Async profiler
341 if ((flags & RequestInjectionData::AsyncEventHookFlag) &&
342 ar->func()->isAsyncFunction()) {
343 assert(ar->resumed());
344 auto afwh = frame_afwh(ar);
345 auto session = AsioSession::Get();
346 // Blocking await @ eager execution => AsyncFunctionWaitHandle created.
347 if (!suspendingResumed && session->hasOnAsyncFunctionCreateCallback()) {
348 session->onAsyncFunctionCreate(afwh, afwh->getChild());
350 // Blocking await @ resumed execution => AsyncFunctionWaitHandle awaiting.
351 if (suspendingResumed && session->hasOnAsyncFunctionAwaitCallback()) {
352 session->onAsyncFunctionAwait(afwh, afwh->getChild());
357 void EventHook::onFunctionReturn(ActRec* ar, const TypedValue& retval) {
358 // Null out $this for the exiting function, it has been decref'd so it's
359 // garbage.
360 ar->setThisOrClassAllowNull(nullptr);
362 // The locals are already gone. Mark them as decref'd so that if this hook
363 // fails and unwinder kicks in, it won't try to decref them again.
364 ar->setLocalsDecRefd();
366 ssize_t flags = CheckSurprise();
367 onFunctionExit(ar, &retval, nullptr, flags);
369 // Async profiler
370 if ((flags & RequestInjectionData::AsyncEventHookFlag) &&
371 ar->func()->isAsyncFunction() && ar->resumed()) {
372 auto session = AsioSession::Get();
373 // Return @ resumed execution => AsyncFunctionWaitHandle succeeded.
374 if (session->hasOnAsyncFunctionSuccessCallback()) {
375 auto afwh = frame_afwh(ar);
376 session->onAsyncFunctionSuccess(afwh, cellAsCVarRef(retval));
381 void EventHook::onFunctionUnwind(const ActRec* ar, const Fault& fault) {
382 // TODO(#2329497) can't CheckSurprise() yet, unwinder unable to replace fault
383 ssize_t flags = GetConditionFlags();
384 onFunctionExit(ar, nullptr, &fault, flags);
387 } // namespace HPHP