Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP Internals: How does TSRMLS_FETCH Work?

How does the PHP Internals TSRMLS_FETCH macro do its job?

Per the PHP Manual

While developing extensions, build errors that contain "tsrm_ls is undefined" or errors to that effect stem from the fact that TSRMLS is undefined in the current scope, to fix this, declare the function to accept TSRMLS with the appropriate macro, if the prototype of the function in question cannot be changed, you must call TSRMLS_FETCH within the function body.

I understand that declaring the function to accept TSRMLS with the appropriate macros means using TSRMLS_C, TSRMLS_D, TSRMLS_CC, and TSRMLS_DC to either define of call a function with extra parameters/arguments.

However, if the prototype of the function in question cannot be changed, you must call TSRMLS_FETCH within the function body confuses me a bit. If I look at the php-src both here and here the TSRMLS_FETCH seems to be an empty macro.

So that leaves me with the question -- how does TSRMLS_FETCH even work? Is something else populating this macro at compile time?

like image 404
Alan Storm Avatar asked Mar 28 '18 16:03

Alan Storm


3 Answers

Firstly, I would not pay too much attention to what the manual says on PHP's internals. It is very outdated, and there's a good chance it is going to be removed from the manual in the near future. There are two websites currently dedicated to PHP's internals: PHPInternalsBook.com and PHPInternals.net (I author content for the latter). There's also a couple of good blogs to follow, including Nikita's and Julien's.

The TSRM in PHP's 5.x series was quite invasive. When wanting to access any Zend globals from within a function, the choice was between either fetching the TLS memory pointer from a function call (such as pthread_getspecific, which was relatively expensive) or propagating the TLS memory pointer through function parameters (a messy and error-prone affair, but the faster way). The TSRMLS_FETCH macro you mentioned was used for the former approach.

In PHP 7.x, propagating the TLS memory pointer (via the TSRMLS_[D|C]C? macros) has been removed completely (though their macros are still defined for backwards compatibility - they just won't do anything). The preferred way to access the TSRM's TLS now is via its static cache. This is basically just a thread local global variable used to hold the current TLS memory pointer.

Here are the relevant macros:

#define TSRMLS_CACHE _tsrm_ls_cache // the TLS global variable
#define TSRMLS_CACHE_DEFINE() TSRM_TLS void *TSRMLS_CACHE = NULL; // define it
#define TSRMLS_CACHE_UPDATE() TSRMLS_CACHE = tsrm_get_ls_cache() // update it - i.e. calls pthread_getspecific()
#define TSRMLS_CACHE_RESET()  TSRMLS_CACHE = NULL // reset it

Using the above macros does require special care to update the static cache appropriately (usually during the GINIT, and sometimes RINIT, phases of an extension). However, it is a cleaner way to provide access to the TLS memory pointer without the mess of propagating it via function parameters or the performance hit of always fetching it (via pthread_getspecific and similar).

Extra reading:

  • Native TLS (the RFC that introduced this change in PHP 7.0)
  • Threads and PHP (Julien's blog post on the TSRM)
like image 152
tpunt Avatar answered Oct 19 '22 19:10

tpunt


Take a look at older versions of that file:

#define TSRMLS_FETCH()            void ***tsrm_ls = (void ***) ts_resource_ex(0, NULL)
#define TSRMG(id, type, element)  (((type) (*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(id)])->element)
#define TSRMLS_D  void ***tsrm_ls
#define TSRMLS_DC , TSRMLS_D
#define TSRMLS_C  tsrm_ls
#define TSRMLS_CC , TSRMLS_C

It seems at some point PHP removed support for those macros but kept them empty in order to avoid having to split external code in two versions, one for the new PHP and one for the old PHP.

like image 3
Acorn Avatar answered Oct 19 '22 18:10

Acorn


This piece of code

#if ZEND_DEBUG
...
#else
#define TSRMLS_FETCH()
...
#endif

Is doing the following:

If you are not in debug mode, change each call to the macro TSRMLS_FETCH() with nothing.

In this example:

#if 0
#define TSRMLS_FETCH() printf("Bla");
#else
#define TSRMLS_FETCH()
#endif

int main(void) 
{
    TSRMLS_FETCH()
    return 0;
}

cpp demo.c (preprocessor output) returns:

int main(void) 
{
    return 0;
}
like image 1
David Ranieri Avatar answered Oct 19 '22 18:10

David Ranieri