Skip to content

Commit 41f46a1

Browse files
committed
Implement stackless internal function calls
1 parent ad1fbde commit 41f46a1

28 files changed

+1931
-662
lines changed

Zend/Optimizer/dce.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ static inline bool may_have_side_effects(
167167
case ZEND_DO_FCALL_BY_NAME:
168168
case ZEND_DO_ICALL:
169169
case ZEND_DO_UCALL:
170+
case ZEND_FRAMELESS_ICALL_0:
171+
case ZEND_FRAMELESS_ICALL_1:
172+
case ZEND_FRAMELESS_ICALL_2:
173+
case ZEND_FRAMELESS_ICALL_3:
170174
/* For now assume all calls have side effects */
171175
return 1;
172176
case ZEND_RECV:

Zend/zend_API.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ ZEND_API bool ZEND_FASTCALL zend_parse_arg_class(zval *arg, zend_class_entry **p
467467
/* }}} */
468468

469469
static ZEND_COLD bool zend_null_arg_deprecated(const char *fallback_type, uint32_t arg_num) {
470-
zend_function *func = EG(current_execute_data)->func;
470+
zend_function *func = zend_active_function();
471471
ZEND_ASSERT(arg_num > 0);
472472
uint32_t arg_offset = arg_num - 1;
473473
if (arg_offset >= func->common.num_args) {
@@ -476,6 +476,7 @@ static ZEND_COLD bool zend_null_arg_deprecated(const char *fallback_type, uint32
476476
}
477477

478478
zend_arg_info *arg_info = &func->common.arg_info[arg_offset];
479+
479480
zend_string *func_name = get_active_function_or_method_name();
480481
const char *arg_name = get_active_function_arg_name(arg_num);
481482

@@ -2821,6 +2822,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
28212822
internal_function->scope = scope;
28222823
internal_function->prototype = NULL;
28232824
internal_function->attributes = NULL;
2825+
internal_function->frameless_function_infos = ptr->frameless_function_infos;
28242826
if (EG(active)) { // at run-time: this ought to only happen if registered with dl() or somehow temporarily at runtime
28252827
ZEND_MAP_PTR_INIT(internal_function->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size()));
28262828
} else {

Zend/zend_API.h

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
#include "zend_variables.h"
2929
#include "zend_execute.h"
3030
#include "zend_type_info.h"
31-
31+
#include "zend_frameless_function.h"
3232

3333
BEGIN_EXTERN_C()
3434

@@ -38,6 +38,7 @@ typedef struct _zend_function_entry {
3838
const struct _zend_internal_arg_info *arg_info;
3939
uint32_t num_args;
4040
uint32_t flags;
41+
const zend_frameless_function_info *frameless_function_infos;
4142
} zend_function_entry;
4243

4344
typedef struct _zend_fcall_info {
@@ -73,29 +74,30 @@ typedef struct _zend_fcall_info_cache {
7374
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(zif_##name)
7475
#define ZEND_METHOD(classname, name) ZEND_NAMED_FUNCTION(zim_##classname##_##name)
7576

76-
#define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags },
77+
#define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags, NULL },
7778

78-
#define ZEND_RAW_FENTRY(zend_name, name, arg_info, flags) { zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags },
79+
#define ZEND_RAW_FENTRY(zend_name, name, arg_info, flags, frameless_function_infos) { zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags, frameless_function_infos },
7980

8081
/* Same as ZEND_NAMED_FE */
81-
#define ZEND_RAW_NAMED_FE(zend_name, name, arg_info) ZEND_RAW_FENTRY(#zend_name, name, arg_info, 0)
82+
#define ZEND_RAW_NAMED_FE(zend_name, name, arg_info) ZEND_RAW_FENTRY(#zend_name, name, arg_info, 0, NULL)
8283

83-
#define ZEND_NAMED_FE(zend_name, name, arg_info) ZEND_RAW_FENTRY(#zend_name, name, arg_info, 0)
84-
#define ZEND_FE(name, arg_info) ZEND_RAW_FENTRY(#name, zif_##name, arg_info, 0)
85-
#define ZEND_DEP_FE(name, arg_info) ZEND_RAW_FENTRY(#name, zif_##name, arg_info, ZEND_ACC_DEPRECATED)
86-
#define ZEND_FALIAS(name, alias, arg_info) ZEND_RAW_FENTRY(#name, zif_##alias, arg_info, 0)
87-
#define ZEND_DEP_FALIAS(name, alias, arg_info) ZEND_RAW_FENTRY(#name, zif_##alias, arg_info, ZEND_ACC_DEPRECATED)
84+
#define ZEND_NAMED_FE(zend_name, name, arg_info) ZEND_RAW_FENTRY(#zend_name, name, arg_info, 0, NULL)
85+
#define ZEND_FE(name, arg_info) ZEND_RAW_FENTRY(#name, zif_##name, arg_info, 0, NULL)
86+
#define ZEND_DEP_FE(name, arg_info) ZEND_RAW_FENTRY(#name, zif_##name, arg_info, ZEND_ACC_DEPRECATED, NULL)
87+
#define ZEND_FALIAS(name, alias, arg_info) ZEND_RAW_FENTRY(#name, zif_##alias, arg_info, 0, NULL)
88+
#define ZEND_DEP_FALIAS(name, alias, arg_info) ZEND_RAW_FENTRY(#name, zif_##alias, arg_info, ZEND_ACC_DEPRECATED, NULL)
8889
#define ZEND_NAMED_ME(zend_name, name, arg_info, flags) ZEND_FENTRY(zend_name, name, arg_info, flags)
89-
#define ZEND_ME(classname, name, arg_info, flags) ZEND_RAW_FENTRY(#name, zim_##classname##_##name, arg_info, flags)
90-
#define ZEND_DEP_ME(classname, name, arg_info, flags) ZEND_RAW_FENTRY(#name, zim_##classname##_##name, arg_info, flags | ZEND_ACC_DEPRECATED)
91-
#define ZEND_ABSTRACT_ME(classname, name, arg_info) ZEND_RAW_FENTRY(#name, NULL, arg_info, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT)
92-
#define ZEND_ABSTRACT_ME_WITH_FLAGS(classname, name, arg_info, flags) ZEND_RAW_FENTRY(#name, NULL, arg_info, flags)
93-
#define ZEND_MALIAS(classname, name, alias, arg_info, flags) ZEND_RAW_FENTRY(#name, zim_##classname##_##alias, arg_info, flags)
94-
#define ZEND_ME_MAPPING(name, func_name, arg_info, flags) ZEND_RAW_FENTRY(#name, zif_##func_name, arg_info, flags)
90+
#define ZEND_ME(classname, name, arg_info, flags) ZEND_RAW_FENTRY(#name, zim_##classname##_##name, arg_info, flags, NULL)
91+
#define ZEND_DEP_ME(classname, name, arg_info, flags) ZEND_RAW_FENTRY(#name, zim_##classname##_##name, arg_info, flags | ZEND_ACC_DEPRECATED, NULL)
92+
#define ZEND_ABSTRACT_ME(classname, name, arg_info) ZEND_RAW_FENTRY(#name, NULL, arg_info, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT, NULL)
93+
#define ZEND_ABSTRACT_ME_WITH_FLAGS(classname, name, arg_info, flags) ZEND_RAW_FENTRY(#name, NULL, arg_info, flags, NULL)
94+
#define ZEND_MALIAS(classname, name, alias, arg_info, flags) ZEND_RAW_FENTRY(#name, zim_##classname##_##alias, arg_info, flags, NULL)
95+
#define ZEND_ME_MAPPING(name, func_name, arg_info, flags) ZEND_RAW_FENTRY(#name, zif_##func_name, arg_info, flags, NULL)
96+
#define ZEND_DIRECT_FE(name, arg_info, frameless_function_infos) { #name, zif_##name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), 0, frameless_function_infos },
9597

96-
#define ZEND_NS_FENTRY(ns, zend_name, name, arg_info, flags) ZEND_RAW_FENTRY(ZEND_NS_NAME(ns, #zend_name), name, arg_info, flags)
98+
#define ZEND_NS_FENTRY(ns, zend_name, name, arg_info, flags) ZEND_RAW_FENTRY(ZEND_NS_NAME(ns, #zend_name), name, arg_info, flags, NULL)
9799

98-
#define ZEND_NS_RAW_FENTRY(ns, zend_name, name, arg_info, flags) ZEND_RAW_FENTRY(ZEND_NS_NAME(ns, zend_name), name, arg_info, flags)
100+
#define ZEND_NS_RAW_FENTRY(ns, zend_name, name, arg_info, flags) ZEND_RAW_FENTRY(ZEND_NS_NAME(ns, zend_name), name, arg_info, flags, NULL)
99101
/**
100102
* Note that if you are asserting that a function is compile-time evaluable, you are asserting that
101103
*
@@ -106,7 +108,7 @@ typedef struct _zend_fcall_info_cache {
106108
* 4. The function will not take an unreasonable amount of time or memory to compute on code that may be seen in practice.
107109
* (e.g. str_repeat is special cased to check the length instead of using this)
108110
*/
109-
#define ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(name, arg_info) ZEND_RAW_FENTRY(#name, zif_##name, arg_info, ZEND_ACC_COMPILE_TIME_EVAL)
111+
#define ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(name, arg_info) ZEND_RAW_FENTRY(#name, zif_##name, arg_info, ZEND_ACC_COMPILE_TIME_EVAL, NULL)
110112

111113
/* Same as ZEND_NS_NAMED_FE */
112114
#define ZEND_NS_RAW_NAMED_FE(ns, zend_name, name, arg_info) ZEND_NS_RAW_FENTRY(ns, #zend_name, name, arg_info, 0)
@@ -117,7 +119,7 @@ typedef struct _zend_fcall_info_cache {
117119
#define ZEND_NS_FALIAS(ns, name, alias, arg_info) ZEND_NS_RAW_FENTRY(ns, #name, zif_##alias, arg_info, 0)
118120
#define ZEND_NS_DEP_FALIAS(ns, name, alias, arg_info) ZEND_NS_RAW_FENTRY(ns, #name, zif_##alias, arg_info, ZEND_ACC_DEPRECATED)
119121

120-
#define ZEND_FE_END { NULL, NULL, NULL, 0, 0 }
122+
#define ZEND_FE_END { NULL, NULL, NULL, 0, 0, NULL }
121123

122124
#define _ZEND_ARG_INFO_FLAGS(pass_by_ref, is_variadic, is_tentative) \
123125
(((pass_by_ref) << _ZEND_SEND_MODE_SHIFT) | ((is_variadic) ? _ZEND_IS_VARIADIC_BIT : 0) | ((is_tentative) ? _ZEND_IS_TENTATIVE_BIT : 0))

Zend/zend_builtin_functions.c

Lines changed: 101 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -981,18 +981,10 @@ ZEND_FUNCTION(property_exists)
981981
}
982982
/* }}} */
983983

984-
static inline void class_exists_impl(INTERNAL_FUNCTION_PARAMETERS, int flags, int skip_flags) /* {{{ */
984+
static inline void _class_exists_impl(zval *return_value, zend_string *name, bool autoload, int flags, int skip_flags) /* {{{ */
985985
{
986-
zend_string *name;
987986
zend_string *lcname;
988987
zend_class_entry *ce;
989-
bool autoload = 1;
990-
991-
ZEND_PARSE_PARAMETERS_START(1, 2)
992-
Z_PARAM_STR(name)
993-
Z_PARAM_OPTIONAL
994-
Z_PARAM_BOOL(autoload)
995-
ZEND_PARSE_PARAMETERS_END();
996988

997989
if (ZSTR_HAS_CE_CACHE(name)) {
998990
ce = ZSTR_GET_CE_CACHE(name);
@@ -1024,13 +1016,39 @@ static inline void class_exists_impl(INTERNAL_FUNCTION_PARAMETERS, int flags, in
10241016
}
10251017
/* {{{ */
10261018

1019+
static inline void class_exists_impl(INTERNAL_FUNCTION_PARAMETERS, int flags, int skip_flags) /* {{{ */
1020+
{
1021+
zend_string *name;
1022+
bool autoload = true;
1023+
1024+
ZEND_PARSE_PARAMETERS_START(1, 2)
1025+
Z_PARAM_STR(name)
1026+
Z_PARAM_OPTIONAL
1027+
Z_PARAM_BOOL(autoload)
1028+
ZEND_PARSE_PARAMETERS_END();
1029+
1030+
_class_exists_impl(return_value, name, autoload, flags, skip_flags);
1031+
}
1032+
10271033
/* {{{ Checks if the class exists */
10281034
ZEND_FUNCTION(class_exists)
10291035
{
10301036
class_exists_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_LINKED, ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT);
10311037
}
10321038
/* }}} */
10331039

1040+
ZEND_FRAMELESS_FUNCTION(class_exists, 1)
1041+
{
1042+
zend_string *name;
1043+
1044+
if (!zend_parse_arg_str(arg1, &name, /* null_check */ false, 1)) {
1045+
zend_wrong_parameter_type_error(1, Z_EXPECTED_STRING, arg1);
1046+
RETURN_THROWS();
1047+
}
1048+
1049+
_class_exists_impl(return_value, name, /* autoload */ true, ZEND_ACC_LINKED, ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT);
1050+
}
1051+
10341052
/* {{{ Checks if the class exists */
10351053
ZEND_FUNCTION(interface_exists)
10361054
{
@@ -1730,7 +1748,7 @@ ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int
17301748
zend_string *filename;
17311749
zend_string *include_filename = NULL;
17321750
zval tmp;
1733-
HashTable *stack_frame;
1751+
HashTable *stack_frame, *prev_stack_frame = NULL;
17341752

17351753
array_init(return_value);
17361754

@@ -1782,6 +1800,78 @@ ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int
17821800
prev = zend_generator_check_placeholder_frame(prev);
17831801
}
17841802

1803+
/* For frameless calls we add an additional frame for the call itself. */
1804+
if (ZEND_USER_CODE(call->func->type)) {
1805+
const zend_op *opline = call->opline;
1806+
const char **names;
1807+
int arity;
1808+
switch (opline->opcode) {
1809+
case ZEND_FRAMELESS_ICALL_0:
1810+
names = zend_frameless_function_0_names;
1811+
arity = 0;
1812+
break;
1813+
case ZEND_FRAMELESS_ICALL_1:
1814+
names = zend_frameless_function_1_names;
1815+
arity = 1;
1816+
break;
1817+
case ZEND_FRAMELESS_ICALL_2:
1818+
names = zend_frameless_function_2_names;
1819+
arity = 2;
1820+
break;
1821+
case ZEND_FRAMELESS_ICALL_3:;
1822+
names = zend_frameless_function_3_names;
1823+
arity = 3;
1824+
break;
1825+
default:
1826+
goto not_frameless_call;
1827+
}
1828+
stack_frame = zend_new_array(8);
1829+
zend_hash_real_init_mixed(stack_frame);
1830+
const char *name = names[opline->extended_value];
1831+
ZVAL_STRINGL(&tmp, name, strlen(name));
1832+
_zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FUNCTION), &tmp, 1);
1833+
/* Steal file and line from the previous frame. */
1834+
if (prev_stack_frame) {
1835+
zval *file = zend_hash_find(prev_stack_frame, ZSTR_KNOWN(ZEND_STR_FILE));
1836+
if (file) {
1837+
Z_TRY_ADDREF_P(file);
1838+
_zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FILE), file, 1);
1839+
zend_hash_del(prev_stack_frame, ZSTR_KNOWN(ZEND_STR_FILE));
1840+
}
1841+
zval *line = zend_hash_find(prev_stack_frame, ZSTR_KNOWN(ZEND_STR_LINE));
1842+
if (line) {
1843+
Z_TRY_ADDREF_P(file);
1844+
_zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_LINE), line, 1);
1845+
zend_hash_del(prev_stack_frame, ZSTR_KNOWN(ZEND_STR_LINE));
1846+
}
1847+
}
1848+
if ((options & DEBUG_BACKTRACE_IGNORE_ARGS) == 0) {
1849+
HashTable *args = zend_new_array(8);
1850+
zend_hash_real_init_mixed(args);
1851+
if (arity >= 1) {
1852+
zval *arg = zend_get_zval_ptr(opline, opline->op1_type, &opline->op1, call);
1853+
Z_TRY_ADDREF_P(arg);
1854+
zend_hash_next_index_insert_new(args, arg);
1855+
}
1856+
if (arity >= 2) {
1857+
zval *arg = zend_get_zval_ptr(opline, opline->op2_type, &opline->op2, call);
1858+
Z_TRY_ADDREF_P(arg);
1859+
zend_hash_next_index_insert_new(args, arg);
1860+
}
1861+
if (arity >= 3) {
1862+
const zend_op *op_data = opline + 1;
1863+
zval *arg = zend_get_zval_ptr(op_data, op_data->op1_type, &op_data->op1, call);
1864+
Z_TRY_ADDREF_P(arg);
1865+
zend_hash_next_index_insert_new(args, arg);
1866+
}
1867+
ZVAL_ARR(&tmp, args);
1868+
_zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_ARGS), &tmp, 1);
1869+
}
1870+
ZVAL_ARR(&tmp, stack_frame);
1871+
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
1872+
}
1873+
not_frameless_call:
1874+
17851875
/* We use _zend_hash_append*() and the array must be preallocated */
17861876
stack_frame = zend_new_array(8);
17871877
zend_hash_real_init_mixed(stack_frame);
@@ -1926,6 +2016,7 @@ ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int
19262016
ZVAL_ARR(&tmp, stack_frame);
19272017
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
19282018
frameno++;
2019+
prev_stack_frame = stack_frame;
19292020

19302021
skip_frame:
19312022
if (UNEXPECTED(ZEND_CALL_KIND(call) == ZEND_CALL_TOP_FUNCTION)

Zend/zend_builtin_functions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@
2020
#ifndef ZEND_BUILTIN_FUNCTIONS_H
2121
#define ZEND_BUILTIN_FUNCTIONS_H
2222

23+
#include "zend_frameless_function.h"
24+
2325
zend_result zend_startup_builtin_functions(void);
2426

2527
BEGIN_EXTERN_C()
2628
ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int options, int limit);
29+
30+
extern ZEND_FRAMELESS_FUNCTION(class_exists, 1);
2731
END_EXTERN_C()
2832

2933
#endif /* ZEND_BUILTIN_FUNCTIONS_H */

Zend/zend_builtin_functions.stub.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ function method_exists($object_or_class, string $method): bool {}
7171
/** @param object|string $object_or_class */
7272
function property_exists($object_or_class, string $property): bool {}
7373

74+
/**
75+
* @frameless-function {"arity": 1}
76+
*/
7477
function class_exists(string $class, bool $autoload = true): bool {}
7578

7679
function interface_exists(string $interface, bool $autoload = true): bool {}

Zend/zend_builtin_functions_arginfo.h

Lines changed: 7 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)