-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add new observer API #5857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new observer API #5857
Changes from 53 commits
6a73552
45a8540
ecfca90
1e7c59d
01308fd
dc74744
23b7d37
1553336
947c955
fe6b447
3f617d1
a3e0139
e5e7bce
e7badd9
71bbb1f
0b13ca6
9837e2b
ef1bcdc
f6edb80
c66d659
a6faa3b
5f24f65
9e9bfc9
b5b2762
2958a20
95bcf05
1e3b6be
1445b3b
228807a
fc73942
b4731c5
c76e8f5
bb429b0
c57324d
e3cdb9f
791c921
0f78895
bf56f27
b8e1408
7792c18
f0da73f
3f93dc8
00a038b
1d2f9f6
d22d421
96d1040
639a09e
971824b
f9e10a0
e4a6c45
58a075f
1698665
6181716
63dba3d
1ca066b
85083ff
6d5faf5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/* | ||
+----------------------------------------------------------------------+ | ||
| Zend Engine | | ||
+----------------------------------------------------------------------+ | ||
| Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | | ||
+----------------------------------------------------------------------+ | ||
| This source file is subject to version 2.00 of the Zend license, | | ||
| that is bundled with this package in the file LICENSE, and is | | ||
| available through the world-wide-web at the following url: | | ||
| http://www.zend.com/license/2_00.txt. | | ||
| If you did not receive a copy of the Zend license and are unable to | | ||
| obtain it through the world-wide-web, please send a note to | | ||
| [email protected] so we can mail you a copy immediately. | | ||
+----------------------------------------------------------------------+ | ||
| Authors: Levi Morrison <[email protected]> | | ||
| Sammy Kaye Powers <[email protected]> | | ||
+----------------------------------------------------------------------+ | ||
*/ | ||
|
||
#include "zend_observer.h" | ||
|
||
#include "zend_extensions.h" | ||
#include "zend_llist.h" | ||
#include "zend_vm.h" | ||
|
||
zend_llist zend_observers_fcall_list; | ||
int zend_observer_fcall_op_array_extension = -1; | ||
|
||
ZEND_TLS zend_arena *fcall_handlers_arena = NULL; | ||
|
||
ZEND_API extern inline void zend_observer_maybe_fcall_call_begin( | ||
zend_execute_data *execute_data); | ||
ZEND_API extern inline void zend_observer_maybe_fcall_call_end( | ||
zend_execute_data *execute_data, | ||
zval *return_value); | ||
|
||
// Call during minit/startup ONLY | ||
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init) { | ||
/* We don't want to get an extension handle unless an ext installs an observer */ | ||
if (!ZEND_OBSERVER_ENABLED) { | ||
zend_observer_fcall_op_array_extension = | ||
zend_get_op_array_extension_handle(); | ||
|
||
/* ZEND_CALL_TRAMPOLINE has SPEC(OBSERVER) but zend_init_call_trampoline_op() | ||
* is called before any extensions have registered as an observer. So we | ||
* adjust the offset to the observed handler when we know we need to observe. */ | ||
ZEND_VM_SET_OPCODE_HANDLER(&EG(call_trampoline_op)); | ||
|
||
/* ZEND_HANDLE_EXCEPTION also has SPEC(OBSERVER) and no observer extensions | ||
* exist when zend_init_exception_op() is called. */ | ||
ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)); | ||
ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)+1); | ||
ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)+2); | ||
} | ||
zend_llist_add_element(&zend_observers_fcall_list, &init); | ||
} | ||
|
||
// Called by engine before MINITs | ||
ZEND_API void zend_observer_startup(void) { | ||
zend_llist_init(&zend_observers_fcall_list, sizeof(zend_observer_fcall_init), NULL, 1); | ||
} | ||
|
||
ZEND_API void zend_observer_activate(void) { | ||
/* todo: how to size the arena? */ | ||
if (ZEND_OBSERVER_ENABLED) { | ||
fcall_handlers_arena = zend_arena_create(1024); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dstogov Any recommendations on sizing the arena? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (N * 4096) should be friendly for Zend MM. |
||
|
||
ZEND_API void zend_observer_deactivate(void) { | ||
if (fcall_handlers_arena) { | ||
zend_arena_destroy(fcall_handlers_arena); | ||
} | ||
} | ||
|
||
ZEND_API void zend_observer_shutdown(void) { | ||
zend_llist_destroy(&zend_observers_fcall_list); | ||
} | ||
|
||
ZEND_API void zend_observer_fcall_install(zend_function *function) { | ||
zend_llist_element *element; | ||
zend_llist *list = &zend_observers_fcall_list; | ||
zend_op_array *op_array = &function->op_array; | ||
|
||
if (fcall_handlers_arena == NULL) { | ||
return; | ||
} | ||
|
||
ZEND_ASSERT(function->type != ZEND_INTERNAL_FUNCTION); | ||
|
||
zend_llist handlers_list; | ||
zend_llist_init(&handlers_list, sizeof(zend_observer_fcall), NULL, 0); | ||
for (element = list->head; element; element = element->next) { | ||
zend_observer_fcall_init init; | ||
memcpy(&init, element->data, sizeof init); | ||
zend_observer_fcall handlers = init(function); | ||
if (handlers.begin || handlers.end) { | ||
zend_llist_add_element(&handlers_list, &handlers); | ||
} | ||
} | ||
|
||
ZEND_ASSERT(RUN_TIME_CACHE(op_array)); | ||
void *ext; | ||
if (handlers_list.count) { | ||
size_t size = sizeof(zend_observer_fcall_cache) + (handlers_list.count - 1) * sizeof(zend_observer_fcall); | ||
zend_observer_fcall_cache *cache = zend_arena_alloc(&fcall_handlers_arena, size); | ||
zend_observer_fcall *handler = cache->handlers; | ||
for (element = handlers_list.head; element; element = element->next) { | ||
memcpy(handler++, element->data, sizeof *handler); | ||
} | ||
cache->end = handler; | ||
ext = cache; | ||
} else { | ||
ext = ZEND_OBSERVER_NOT_OBSERVED; | ||
} | ||
|
||
ZEND_OBSERVER_HANDLERS(op_array) = ext; | ||
zend_llist_destroy(&handlers_list); | ||
} | ||
|
||
void zend_observe_fcall_begin( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do the definitions of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see a reason to export them at the moment. We can always export them later if there is a need to. That being said, we could probably remove |
||
zend_observer_fcall_cache *cache, | ||
zend_execute_data *execute_data) | ||
{ | ||
zend_observer_fcall *handler, *end = cache->end; | ||
for (handler = cache->handlers; handler != end; ++handler) { | ||
if (handler->begin) { | ||
handler->begin(execute_data); | ||
} | ||
} | ||
} | ||
|
||
void zend_observer_fcall_call_end_helper( | ||
zend_execute_data *execute_data, | ||
zval *return_value) | ||
{ | ||
zend_function *func = execute_data->func; | ||
ZEND_ASSUME(ZEND_OBSERVABLE_FN(func->common.fn_flags)); | ||
void *observer_handlers = ZEND_OBSERVER_HANDLERS(&func->op_array); | ||
// TODO: Fix exceptions from generators | ||
// ZEND_ASSERT(observer_handlers); | ||
if (observer_handlers && observer_handlers != ZEND_OBSERVER_NOT_OBSERVED) { | ||
zend_observer_fcall_cache *cache = observer_handlers; | ||
zend_observe_fcall_end(cache, execute_data, return_value); | ||
} | ||
} | ||
|
||
void zend_observe_fcall_end( | ||
zend_observer_fcall_cache *cache, | ||
zend_execute_data *execute_data, | ||
zval *return_value) | ||
{ | ||
zend_observer_fcall *handler = cache->end, *end = cache->handlers; | ||
while (handler-- != end) { | ||
if (handler->end) { | ||
handler->end(execute_data, return_value); | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/* | ||
+----------------------------------------------------------------------+ | ||
| Zend Engine | | ||
+----------------------------------------------------------------------+ | ||
| Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | | ||
+----------------------------------------------------------------------+ | ||
| This source file is subject to version 2.00 of the Zend license, | | ||
| that is bundled with this package in the file LICENSE, and is | | ||
| available through the world-wide-web at the following url: | | ||
| http://www.zend.com/license/2_00.txt. | | ||
| If you did not receive a copy of the Zend license and are unable to | | ||
| obtain it through the world-wide-web, please send a note to | | ||
| [email protected] so we can mail you a copy immediately. | | ||
+----------------------------------------------------------------------+ | ||
| Authors: Levi Morrison <[email protected]> | | ||
| Sammy Kaye Powers <[email protected]> | | ||
+----------------------------------------------------------------------+ | ||
*/ | ||
|
||
#ifndef ZEND_OBSERVER_H | ||
#define ZEND_OBSERVER_H | ||
|
||
#include "zend.h" | ||
#include "zend_compile.h" | ||
|
||
BEGIN_EXTERN_C() | ||
|
||
extern ZEND_API int zend_observer_fcall_op_array_extension; | ||
|
||
#define ZEND_OBSERVER_ENABLED (zend_observer_fcall_op_array_extension != -1) | ||
|
||
#define ZEND_OBSERVER_HANDLERS(op_array) \ | ||
ZEND_OP_ARRAY_EXTENSION(op_array, zend_observer_fcall_op_array_extension) | ||
|
||
#define ZEND_OBSERVER_NOT_OBSERVED ((void *) 2) | ||
|
||
#define ZEND_OBSERVABLE_FN(fn_flags) \ | ||
(ZEND_OBSERVER_ENABLED && \ | ||
!(fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_FAKE_CLOSURE))) | ||
|
||
struct zend_observer_fcall { | ||
void (*begin)(zend_execute_data *execute_data); | ||
void (*end)(zend_execute_data *execute_data, zval *retval); | ||
}; | ||
typedef struct zend_observer_fcall zend_observer_fcall; | ||
|
||
struct zend_observer_fcall_cache { | ||
// points after the last handler | ||
zend_observer_fcall *end; | ||
// a variadic array using "struct hack" | ||
zend_observer_fcall handlers[1]; | ||
}; | ||
typedef struct zend_observer_fcall_cache zend_observer_fcall_cache; | ||
|
||
/* If the fn should not be observed then return {NULL, NULL} */ | ||
typedef zend_observer_fcall(*zend_observer_fcall_init)(zend_function *func); | ||
|
||
// Call during minit/startup ONLY | ||
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init); | ||
|
||
ZEND_API void zend_observer_startup(void); // Called by engine before MINITs | ||
ZEND_API void zend_observer_activate(void); | ||
ZEND_API void zend_observer_deactivate(void); | ||
ZEND_API void zend_observer_shutdown(void); | ||
|
||
ZEND_API void zend_observer_fcall_install(zend_function *function); | ||
|
||
ZEND_API void zend_observe_fcall_begin( | ||
zend_observer_fcall_cache *cache, | ||
zend_execute_data *execute_data); | ||
|
||
ZEND_API void zend_observe_fcall_end( | ||
zend_observer_fcall_cache *cache, | ||
zend_execute_data *execute_data, | ||
zval *return_value); | ||
|
||
ZEND_API void zend_observer_fcall_call_end_helper( | ||
zend_execute_data *execute_data, | ||
zval *return_value); | ||
|
||
ZEND_API zend_always_inline void zend_observer_maybe_fcall_call_begin( | ||
zend_execute_data *execute_data) | ||
{ | ||
zend_op_array *op_array = (zend_op_array *)execute_data->func; | ||
if (ZEND_OBSERVABLE_FN(op_array->fn_flags) && | ||
!(op_array->fn_flags & ZEND_ACC_GENERATOR)) { | ||
void *observer_handlers = ZEND_OBSERVER_HANDLERS(&EX(func)->op_array); | ||
if (!observer_handlers) { | ||
zend_observer_fcall_install((zend_function *)&EX(func)->op_array); | ||
observer_handlers = ZEND_OBSERVER_HANDLERS(&EX(func)->op_array); | ||
} | ||
ZEND_ASSERT(observer_handlers); | ||
if (observer_handlers != ZEND_OBSERVER_NOT_OBSERVED) { | ||
zend_observe_fcall_begin(observer_handlers, execute_data); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the body here get extracted into a helper like I did for The original motivation of the helper was to reduce code size without hurting performance much; the main conditional check would inline but if it's true then the helper body isn't inlined, so it won't hurt code size. Now that these are mostly done in spec handlers, we are probably fine with more aggressive inlining, I would guess? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know what would be better so I don't have any strong opinions on this one. |
||
} | ||
} | ||
|
||
ZEND_API zend_always_inline void zend_observer_maybe_fcall_call_end( | ||
zend_execute_data *execute_data, | ||
zval *return_value) | ||
{ | ||
zend_function *func = execute_data->func; | ||
if (ZEND_OBSERVABLE_FN(func->common.fn_flags)) { | ||
zend_observer_fcall_call_end_helper(execute_data, return_value); | ||
} | ||
} | ||
|
||
END_EXTERN_C() | ||
|
||
#endif /* ZEND_OBSERVER_H */ |
Uh oh!
There was an error while loading. Please reload this page.