When we call a PHP function, it may return a value. However, we may not always use it. In this case, whatever the function does for returning that value is a waste of CPU cycles. In some rare cases, to improve performance, we may want to deduce whether the return value will be used.
Unfortunately, there’s no way to do that in userland PHP, but in a PHP extension, that’s quite easy. There’s a macro defined in zend.h.
We can see that the return value is considered used if at least one of the following conditions is met.
- There’s no previous execute data.
- The function which called this function is not defined in userland PHP.
- The result type stored in the
oplineof previous execute data is not
Well, the global scope has no previous execute data. You can
return from global scope so that another script who
includes this script will get the return value. For example:
USED_RET() in global scope will always get true. However, that doesn’t concern us, because the native functions we implement in a PHP extension is never in global scope.
func property of previous execute data stores the pointer to the
zend_function from which the current function is being invoked. For example, we define two native functions in an extension like this:
Then, run the following script, and you will get “2 4 1” as output.
Let’s take a look at zend_compile.c:
So it’s clear that if the previous scope is in a function defined in PHP, the
type property yields
ZEND_USER_FUNCTION. If in
ZEND_EVAL_CODE. If in a native function,
ZEND_USER_CODE() macro is used to detect whether a function type is either
ZEND_EVAL_CODE. If so, that function is considered userland PHP.
USED_RET() macro will always yield true if the function which invoked the current function is not defined in userland PHP. Why? Because there’s no way to check whether a native function(like
bar() in our example) will use the return value or not.
Finally, we check whether return value is used by checking the
result_type property of the
opline from the calling scope. You can understand
opline as “line of opcode”, which is the current line of opcode executed from the calling scope.
result_type can be one of the following values:
If the value of
IS_UNUSED, we are certain that the return value of the called function will not be used.
As said in the previous section, we can use
USED_RET() when the return value of our native function is not mandatory, and may cause performance overhead. Some built-in functions of PHP already take advantage of that macro, for example, functions which manipulate the internal pointer of a
zend_array, such as
reset(). If the user just want to do something to the pointer without fetching the corresponding value of the array, the function won’t do the fetch & return job.
For example, in array.c:
This macro is available in all current versions of PHP 7. Feel free to use it in your extension, if necessary.
You can’t do that in userland PHP, not without the help of a native function. Write a function in your extension like this, and it’s ready to use:
Just that simple :) Now we can test it with a PHP script.