phpcmacrosphp-internals

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?


Solution

  • 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: