Showing posts with label php. Show all posts
Showing posts with label php. Show all posts

Jan 8, 2015

HHVM Extension Writing, Part IV

Hopefully you've had some time to digest the first three parts of my HHVM Extension Writing series, and you're ready to embark into the world of Resources. Not quite as exciting as objects, they're certainly less commonly used in new extensions since Objects are so much more versatile, but there were an integral part of PHP's history prior to PHP5, and are still in heavy use by such features as streams, curl, and most database connectors.

We'll be continuing with the same examples repo at https://github.com/sgolemon/hhvm-extension-writing where I've already landed a skeleton for example3 with commit 144e9698b5.

Resource

Defining a new resource type shares some similarity with Objects, except that you won't be defining anything in systemlib initially, because a resource doesn't have an API in and of itself. It's just an opaque pointer used by seemingly unrelated functions and methods. We'll define some global functions in the second half of this post when we start accessing the resource. Any wonder Objects are winning out?

As with the Object example, we'll be implementing a bit of filesystem access to demonstrate how one might use resources.
class Example3File : public SweepableResourceData {
 public:
  DECLARE_RESOURCE_ALLOCATION_NO_SWEEP(Example3File)
  CLASSNAME_IS("example3-file")
  const String& o_getClassNameHook() const override { return classnameof(); }

  Example3File(const String& filename, const String& mode) {
    m_file = fopen(filename.c_str(), mode.c_str());
    if (!m_file) {
      throw Object(SystemLib::AllocExceptionObject(
        "Unable to open file"
      ));
    }
  }

  ~Example3File() { sweep(); }
  void sweep() { close(); }

  void close() {
    if (m_file) {
      fclose(m_file);
      m_file = nullptr;
    }
  }

  bool isInvalid() const override {
    return !m_file;
  }

  FILE* m_file{nullptr};
};

The first three lines of our class are basically boilerplate. DECLARE_RESOURCE_ALLOCATION_NO_SWEEP handles some specifics about the MemoryManager, because unlike objects which are allocated from userspace, resources are allocated from C++, and a bare c++ new won't do a "smart" allocation unless told to do so by this macro. The CLASSNAME_IS macro, and the o_getClassNameHook() virtual below it, define the "resource name" as seen from PHP when you var_dump() or call get_resource_type.

As with the Object version of this example, the class destructor is automatically called when the resource variable falls out of scope, meanwhile sweep() is invoked if the variable is still "live" at the end of a request. Since in this case we want the same behavior to occur, we simple chain one through the other, and on to their actual purpose; Closing the file.

isInvalid() exists for Resource types because it's very common to invoke functions like fclose() who's purpose is to make the resource no longer usable as its normal type. HHVM's mechanism for dealing with this is very different from PHP's, but the end result is the same. So long as your class has some way of knowing that it's "dead", HHVM can detect it, and report accordingly in var_dump() and other calls.

So now that we've defined a Resource type, let's make a function to create instances and do things with them:
Resource HHVM_FUNCTION(example3_fopen, const String& filename, const String& mode) {
#ifdef NEWOBJ
  return Resource(NEWOBJ(Example3File)(filename, mode)); 
#else
  return Resource(newres<Example3File>(filename, mode));
#endif
}

void HHVM_FUNCTION(example3_fclose, const Resource& fp) {
  // By default, if fp is not of type "Example3File" and valid,
  // HHVM will throw an exception here
  auto f = fp.getTyped<Example3File>();
  f->close();
}

Variant HHVM_FUNCTION(example3_ftell, const Resource& fp) {
  // By passing "true" for "badTypeOkay", invalid resources
  // result in returning nullptr, rather than throwing an exception
  // So check the return type!
  auto f = fp.getTyped<Example3File>(true /* nullOkay */, true /* badTypeOkay */);
  if (!f) {
    raise_warning("Instance of example3-file resource expected");
    return init_null();
  }
  return (int64_t)ftell(f->m_file);
}

As you can see, allocating a resource is literally as simple as newing up a C++ class with the newres<T>() template (or if you're using an older version of HHVM, the NEWOBJ() macro) and passing it as an argument to Resource's constructor.

For getting at that C++ instance, I've presented two different, equally valid methods. The first is certainly more concise, but you may not want to throw exceptions (you've probably eschewed making an Object for a reason, after all). On the other hand, while the second form allows you to handle your error cases more explicitly, this particularly common case forced us to change our return type from int to the far less precise mixed. Take this into account when designing your APIs. Many extensions follow the latter pattern, using a simple macro to avoid excessive copypasta from one function to the next. I've added an example of that to the repo.

What's with the nullOkay arg? TL;DR version: It's a bit of legacy logic and doesn't really have a place in modern HHVM extensions. You can usually set it to the same value as you pass for badTypeOkay. Note that both of these args are false by default.

Update: In the initial version of this post, I used NEWOBJ() exclusively to allocate a new resource instance, but as @maide pointed out in IRC, that's been removed from the newest versions of HHVM and replaced with newres<T>(). We use an #ifdef to figure out which version we're dealing with because the HHVM team is lousy at updating the API version constant. ;p

The code so far is at commit 5f852d45cc.

What's next...

We've covered all the userspace data types, declaration of functions, classes, and resources. Next up, in Part V, we'll back up for a few moments and look at the build system so that we can start linking in external libraries. If you're already familiar with CMake, then you can probably skip that chapter. Part VI is TBD at the moment, but I'll probably pick up a lot of the parts I glossed over in Parts I-IV, we'll see what comes out...

Jan 7, 2015

HHVM Extension Writing, Part III

In Part I of this series, we looked at the basic building blocks of a simple HHVM extension skeleton. Part II continued with three of the five "smart" datatypes used throughout the API. But now we get to have some fun. It's time to start looking into declaring classes with methods and properties and constants (oh my!).

I'll be continuing with the same git repository at https://github.com/sgolemon/hhvm-extension-writing where we left off at the end of Part II with commit 8e82e3e416.

Declaring methods

As with functions, the bulk of your class definition is going to appear in your systemlib file, maybe something like the following in ext_example1.php.
class Example1_Greeter {
  public function greet() {
    echo "Hello {$this->name}\n";
  }

  public function __construct(protected string $name = 'Stranger') {}
}

As you should expect by now, compiling your extension with this bit of code should mean that the Example1_Greeter class is now available to all requests and may be invoked like any other class definition. Let's apply what we already know from making native functions, and see how it works with methods...

  <<__Native>>
  public function getName(): string;

  <<__Native>>
  static public function DefaultGreeting(): string;

While you're probably tempted to rush off and add an HHVM_FE() and HHVM_FUNCTION() implementation to the C++ file, you'd only be half right. These aren't functions, they're methods, and as such have a different set of macros.
const StaticString
  s_Example1_Greeter("Example1_Greeter");

String HHVM_METHOD(Example1_Greeter, getName) {
  return this_->o_get(s_name, false, s_Example1_Greet);
}

String HHVM_STATIC_METHOD(Example1_Greeter, DefaultGreeting) {
  return "Hello";
}

Meanwhile, in moduleInit(), we'll add:
  HHVM_ME(Example1_Greeter, getName);
  HHVM_STATIC_ME(Example1_Greeter, DefaultGreeting);

The code so far is at commit 335dca9573.

Similarly, properties and constants may be declared directly on the Hack definition of the class in your systemlib file. I won't bother showing it here, since you all know how to write PHP code, but I'll put an example or two in the git repo.

What's marginally more interesting, and something you can probably guess at from the coverage in Part I, is that you can declare class constants from C++, meaning that they can take on values defined in external headers or computed values. Let's add one from moduleInit().

  Native::registerClassConstant(s_Example1_Greeter.get(), s_DEFAULT_GREETING.get(), s_Hello.get());

In the examples repo, I've changed ->getName() to now use this constant, so that you can see it propagate up.

The code so far is at commit 007c314b2d.

Binding internal data

What we have so far is all well and good for simple classes, but most extension classes will need to store some opaque pointer from an external library somewhere on the class that can be easily referenced later on. For that, things start to get a little bit more complicated. To help clarify what we're doing, I'm going to wipe the slate clean by moving example1 to its own subdirectory, and starting fresh with example2.

Yeah, kinda messy changing my whole directory structure around midway, but would you rather I ran `git push --force`? Yeah, I thought not.

The code skeleton we're starting out with is at commit: 20dc4ef824 in example2/.

To illustrate something slightly less contrived than earlier examples, I'll be wrapping the POSIX FILE* object in a simple PHP class. Let's start with something basic in our systemlib, containing just a constructor:
<<__NativeData("Example2_File")>>
class Example2_File {
  <<__Native>>
  public function __construct(string $filename, string $mode): void;
}

A new user attribute has appeared! <<__NativeData("Example2_File")>> tells the runtime that this is no ordinary object. This object should be over-allocated with enough to space to handle some internal C++ object, identified by the quoted name. In practice this is usually the name of the class it goes with, but it doesn't have to be. How does this hook up to internals? That comes next, within ext_example2.cpp by adding an #include "hphp/runtime/vm/native-data.h" and the following code:
const StaticString
  s_Example2_File("Example2_File");

class Example2_File {
 public:
  Example2_File() { /* new Example2_File */ }
  Example2_File(const Example2_File&) = delete;
  Example2_File& operator=(const Example2_File& src) {
    /* clone $instanceOfExample2_File */
    throw Object(SystemLib::AllocExceptionObject(
      "Cloning Example2_File is not allowed"
    ));
  }

  ~Example2_File() { sweep(); }
  void sweep() {
    if (m_file) {
      fclose(m_file);
      m_file = nullptr;
    }
  }

  FILE* m_file{nullptr};
};

void HHVM_METHOD(Example2_File, __construct, const String& filename, const String& mode) {
  auto data = Native::data<Example2_File>(this_);
  if (data->m_file) {
    throw Object(SystemLib::AllocExceptionObject(
      "File is already open!"
    ));
  }
  data->m_file = fopen(filename.c_str(), mode.c_str());
  if (!data->m_file) {
    String message("Unable to open ");
    message += filename + ": errno=" + String(errno);
    throw Object(SystemLib::AllocExceptionObject(message));
  }
}

And some glue code in moduleInit() to tie both the constructor and the data class into the class definition:
  HHVM_ME(Example2_File, __construct);

  Native::registerNativeDataInfo<Example2_File>(s_Example2_File.get());

The code so far is at commit 97b3cfd49e.

We're only opening (and ultimately closing) our file at this point, but these are really important stages in an object's lifecycle, so this is worth going though slowly. When a PHP script calls $o = new Example2_File(__FILE__, "r");, the first thing the engine does is allocate space for the object. This is done by adding sizeof(ObjectData) (the standard, base object size) to the size given by any NativeDataInfo associated with it. We made that association by calling Native::registerNativeDataInfo(StringData* id);, where T is the C++ class type to allocate with the ObjectData, and id is the symbolic name we gave it in the syetemlib file using <<__NativeData("id")>>.

Next, the engine invokes the constructor, providing a pointer to the object via a hidden ObjectData* this_ property in the C++ method's signature. From here, we can get access to our private data structure by using the Native::data() accessor to jump to the correct offset from this_. At this point, we have access to a normal C++ object which just so happens to be bound to a PHP object, and we have constructor parameters as well!

From here, there are two reasons a PHP object might die. In the expected case, it runs out of references and is destructed during the course of request's runtime. In this case, our auxiliary object has its destructor called as well, so that external pointers can be cleaned up nicely. The other time a PHP object can die is when the request is shutting down. This is somewhat more exceptional since the memory manager is sweeping ALL request-local data, not necessarily in the most ideal order. It's up to your auxiliary class to deal with non-sweepable resources, but trust that the runtime will deal with resources which are. This is resolved by having a secondary psuedo-destructor called sweep();. For simple implementations like ours, we want the regular destructor and sweep to do the same time, since the only members of this C++ class are external pointers. If we have sweepable resources such as an HPHP::String however, we'd want to avoid the implicit member destruction which comes with calling ~Example2_File(). It's entirely possible that the internal state of that String is no longer valid because it was sweeped first. Hence the need for a separate sweep() function.

TL;DR? - Just have your destructor call sweep(), and deal with external pointers in sweep(). That's good 90% of the time.

You might also have noticed that I'm throwing an exception in the assignment operator. This is normally used for handling a clone, where you'd probably duplicate the FILE* handle, but I realized midway that POSIX file streams don't really have that notion, so I took the easy way out and threw a standard exception. In practice, the implementation would probably look something like:
  Example2_File& operator=(const Example2_File& src) {
    /* copy/clone class members, then return self */
    if (m_file) {
      fclose(m_file);
      m_file = nullptr;
    }
    if (src.m_file) {
      m_file = fclone(src.m_file);
    }
    return *this;
  }

But like I said, there doesn't seem to be an fclone() as such. Instead, let's add a few more methods to flesh out our class:
String HHVM_METHOD(Example2_File, read, int64_t len) {
  auto data = Native::data<Example2_File>(this_);
  String ret(len, ReserveString);
  auto slice = ret.bufferSlice();
  len = fread(slice.ptr, 1, len, data->m_file);
  return ret.setSize(len);
}

int64_t HHVM_METHOD(Example2_File, tell) {
  auto data = Native::data(this_);
  return ftell(data->m_file);
}

bool HHVM_METHOD(Example2_File, seek, int64_t pos, int64_t whence) {
  if ((whence != SEEK_SET) && (whence != SEEK_CUR) && (whence != SEEK_END)) {
    raise_warning("Invalid seek-whence");
    return false;
  }
  auto data = Native::data<Example2_File>(this_);
  return 0 == fseek(data->m_file, pos, whence);
}
The code so far is at commit df4acf359ca.

Jan 6, 2015

HHVM Extension Writing, Part II

In our last installment I walked through setting up a dev environment and creating a simple HHVM extension which exposed some constants and global scope functions. Today, we'll expand on that by delving deeper into three of the five "Smart" types: String, Array, and Variant. The other two "Smart" types will be covered in Parts III(Objects) and IV(Resources) since they require a bit more explaining.

All code in the following examples can be found at https://github.com/sgolemon/hhvm-extension-writing and we'll be starting from where Part I left off: commit ad9618ac8c.

The String class

HPHP::String resembles C++'s std::string class in many ways, but also builds in several assumptions about how PHP strings should behave, is able to be encapsulated in a Variant (mixed) object, and performs common string related tasks, such as numeric conversion.

This post is going to highlight the most common features of the String class, but you should look through the header file yourself for a more in-depth exploration.

/* Basic inspection */
class String {
 public:
  const char* c_str() const;
  int size() const;
  bool empty() const { return size() == 0; }
  int length() const ( return size(); }
  bool isNumeric() const;
  bool isInteger() const;
  bool isZero() const;
  bool toBoolean() const;
  char toByte() const;
  short toInt16() const;
  int toInt32() const;
  int64_t toInt64() const;
  double toDouble() const;
  std::string toCppString() const;

  char charAt(int pos) const;
  char operator[](int pos) const;
};

The meaning and use of these methods should all be straightforward. In practice, c_str(), size(), and empty() are going to cover 90% of your uses for reading values from the String class.

/* Creation */
class String {
 public:
  String(); // empty string
  String(const char* cstr);
  String(const std::string& cppstr);
  String(const String& hphpstr);
  String(int64_t num);
  String(double num);

  static StaticString FromCStr(const char* cstr);

  String(size_t cap, ReserveStringMode mode);
  MutableSlice bufferSlice();
  uint32_t capacity() const;
  const String& setSize(int len);
};

The constructors, as you can see, are generally built around making new runtime string values from an existing string or numeric value, and are again straight-forward to use. String::FromCStr() is a somewhat special case in that it creates a StaticString, rather than a String. While a String is cleaned up at the end of the request it was created in, StaticStrings live forever, and can even be shared between multiple requests. Because overuse of StaticString could easily lead to memory bloat, they're typically only used for defining persistent features (such as constant names/values) as seen in Part I.

The most interesting part of this API is the ReserveStringMode and MutableSlice. Ordinarily, you shouldn't save the pointer you get from String::c_str() as it can potentially change between calls, and you generally shouldn't go modifying a String unless you know you own it anyway. If you do have need to modify a string, call bufferSlice() on it. The MutableSlice structure you get back will contain a pointer to a (relatively) stable block of memory which can be populated. Here's an example:

String HHVM_FUNCTION(example1_count_preallocate) {
  /* 30 bytes: 3 per number: 'X, ' */
  String ret(30, ReserveString);
  auto slice = ret.bufferSlice();
  for (int i = 0; i < 10; ++i) {
    snprintf(slice.ptr + (i*3), 4, "%d, ", i);
  }
  /* Terminate just after the 9th digit, overwriting the ',' with a null byte */
  return ret.setSize((9*3) + 1);
}

This contrived example allocates enough space for 10 single-digit numbers, and a comma and space following them. It uses snprintf() to fill that buffer up, then it truncates it as 28 characters, since the final ', ' wasn't actually necessary. You'll find this pattern in use anywhere an API expects you to provide it with a buffer for it to fill, such as in the intl extension where it calls into ICU.

Another approach to building up a string from parts would be to use the operator+ overload which allows you to simply concatenate Strings such as in the following:

String HHVM_FUNCTION(example1_count_concatenate) {
  String ret, delimiter(", ");
  for (int i = 0; i < 10; ++i) {
    if (i > 0) {
      ret += delimiter;
    }
    ret += String(i);
  }
  return ret;
}

There are costs and benefits to both versions. The former is more efficient as it only does one allocation, as opposed to the latter which does at least 11, and far less copying around. On the other hand, the second version is far more readable and far less error prone. For the contrived example, I'd call the second version "better", but there are certainly cases where the first version is superior.
The code so far is at commit: fa82b3cd70

The Array Class

Arrays are the do-all bucket of "stuff" of the PHP language. They can behave like vectors, maps, sets, or weird hybrid hodgepodge containers without rhyme or reason. You already know how to interact with them from userspace, so let's take a look at how to interact with them from C++. As with Strings, we're only going to go into the most common API calls here, check out the header for the full story.

/* Core API */
class Array {
 public:
  static Array Create(); // array()
  static Array Create(const Variant& value); // array($value)
  static Array Create(const Variant& key, const Variant& value); // array($key => $value)

  /* Read */
  const Variant operator[](int64_t key) const;
  const Variant operator[](const String& key) const;
  const Variant operator[](const Variant& key) const;

  /* count($arr) */
  ssize_t count() const;

  /* array_key_exists($arr, $key); */
  bool exists(int64_t key) const;
  bool exists(const String& key, bool isKey = false) const;
  bool exists(const Variant& key, bool isKey = false) const;

  /* Write */
  void clear();

  /* $arr[$key] = $v; */
  void set(int64_t key, const Variant& v);
  void set(const String& key, const Variant& v, bool isKey = false);
  void set(const Variant& key, const Variant& v, bool isKey = false);

  void prepend(const Variant& v); // array_unshift($v);
  Variant dequeue();              // array_shift($v);
  void append(const Variant& v);  // array_push($v); aka => $arr[] = $v;
  Variant pop();                  // array_pop($v);

  /* $arr[$key] =& $v; */
  void setRef(int64_t key, const Variant& v);
  void setRef(const String& key, const Variant& v, bool isKey = false);
  void setRef(const Variant& key, const Variant& v, bool isKey = false);

  /* $arr[] =& $v; */
  void appendRef(Variant& v);

  /* unset($arr[$key]); */
  void remove(int64_t key);
  void remove(const String& key, bool isKey = false);
  void remove(const Variant& key);
};

As you can see, the Array APIs mirror PHP's userspace API very closely, down to the read API using square-bracket notation just like PHP code. Let's write a couple new methods dealing with arrays as arguments and return values.
const StaticString
  s_name("name"),
  s_hello("hello"),
  s_Stranger("Stranger");

void HHVM_FUNCTION(example1_greet_options, const Array& options) {
  String name(s_Stranger);
  if (options.exists(s_name)) {
    name = options[s_name].toString();
  }
  bool hello = true;
  if (options.exists(s_hello)) {
    hello = options[s_hello].toBoolean();
  }
  g_context->write(greet ? "Hello " : "Goodbyte ");
  g_context->write(name);
  g_context->write("\n");
}

Array HHVM_FUNCTION(example1_greet_make_options, const String& name, bool hello) {
  Array ret = Array::Create();
  if (!name.empty()) {
    ret.set(s_name, name);
  }
  ret.set(s_hello, hello);
  return ret;
}

Pretty similar syntax to writing PHP code, yeah?

The code so far is at commit: 3966bb1da1

The Variant Class

The last "smart" class doesn't represent a single PHP type, rather it represents all types in a sort of meta-container which knows what it's holding, and knows how to convert between the concrete types. Variant is useful when you need to accept and/or return multiple possible types. For a start, let's list out the core API. Remember that there are far more methods than I'll cover here, and you can find the reset in the header file.

/* Creation/Assignment */
class Variant {
 public:
  Variant();
  Variant(bool bval);
  Variant(int64_t lval);
  Variant(double dval);
  Variant(const String& strval);
  Variant(const char* cstrval);
  Variant(const Array& arrval);
  Variant(const Resource& resval);
  Variant(const Object& objval);
  Variant(const Variant& val);

  template Variant &operator=(const T &v);
}

These APIs together mean that a Variant may be initialized or assigned from any other variable type supported by userspace code. This becomes especially powerful when looking at Variant return types.
Variant HHVM_FUNCTION(example1_password, const String& guess) {
  if (guess.same(s_secret)) {
    return "Password accepted: A winner is you!";
  }
  return false;
}

These seemingly incompatible return types (const char* and bool) work because they are implicitly constructed into a Variant instance. Explicit types are generally preferred, because the IR can make better assumptions during optimization, but sometimes you just want your return values to be adaptable like that.

/* Introspection and Unboxing */
class Variant {
 public:
  bool isNull() const;
  bool isBoolean() const;
  bool isInteger() const;
  bool isDouble() const;
  bool isNumeric(bool checkString = false) const;
  bool isString() const;
  bool isArray() const;
  bool isResource() const;
  bool isObject() const;

  bool toBoolean() const;
  int64_t toInt64() const;
  double toDouble() const;
  DataType toNumeric(int64_t &ival, double &dval, bool checkString = false) const;
  String toString() const;
  Array toArray() const;
  Resource toResource() const;
  Object toObject() const;
}

These APIs allow pulling a concrete data type out of a Variant so they can be operated on directly. Note that the to*() APIs will convert the type if necessary, even if the is*() call returned false, but that not all conversions make sense. Let's make a contrived example by implementing a simplistic var_dump():

<<__Native>>
function example1_var_dump(mixed $value): void;

void HHVM_FUNCTION(example1_var_dump, const Variant &value) {
  if (value.isNull()) {
    g_context->write("null\n");
    return;
  }
  if (value.isBoolean()) {
    g_context->write("bool(");
    g_context->write(value.toBoolean() ? "true" : "false");
    g_context->write(")\n");
    return;
  }
  if (value.isInteger()) {
    g_context->write("int(");
    g_context->write(String(value.toInt64()));
    g_context->write(")\n");
    return;
  }
  // etc...
}

The code so far is at commit: 8e82e3e416

What's next...

We'll continue in the next installment by exploring Objects. These get a bit more complicated with the introduction of visibility, properties, constants, inheritance, and internal data structures.

HHVM Extension Writing, Part I

I've written a number of blogposts and even one book over the years on writing extensions for PHP, but very little documentation is available for writing HHVM extensions.  This is kinda sad since I built a good portion of the latter's API. Let's fix that, starting with this article.

All the code in this post (and its followups) will be found at https://github.com/sgolemon/hhvm-extension-writing.

Setting up a build environment

The first thing you need to do is get all the dependencies in place.  I'm going to start from a clean install of Ubuntu 14.04 LTS and use the prebuilt HHVM binaries for Ubuntu. Other distros should work (with varying degrees of success), but sorting them all out is beyond the scope of this blog entry.

First, let's trust HHVM's package repo and pull in its package list. Then we can install the hhvm-dev package to pull in the binary along with all needed headers.

$ wget -O - http://dl.hhvm.com/conf/hhvm.gpg.key | \
  sudo apt-key add -
$ echo deb http://dl.hhvm.com/ubuntu trusty main | \
  sudo tee /etc/apt/sources.list.d/hhvm.list
$ sudo apt-get update

$ sudo apt-get install hhvm-dev

Creating an extension skeleton

The most basic, no-nothing extension imaginable requires two files. A C++ source file to declare itself, and a config.cmake file to describe what's being built. Let's start with the build file, which is a simple, single line:

HHVM_EXTENSION(example1 ext_example1.cpp)

This macro declares a new extension named "example1" with a single source file named "ext_example1.cpp". If we had multiple source files, we'd delimit them with a space (HHVM_EXTENSION(example1 ext_example1.cpp ex1lib.cpp utilex1.cpp etc.cpp))

The source file has a little more boilerplate, but fortunately it's also just a handful of lines:

#include "hphp/runtime/base/base-includes.h"

namespace HPHP {

class Example1Extension : public Extension {
 public:
  Example1Extension(): Extension("example1", "1.0") {}
} s_example1_extension;

HHVM_GET_MODULE(example1);

} // namespace HPHP

All we're doing here is exposing a specialization of the "Extension" class which gives itself the name "example1". It doesn't do anything more than declare itself into the runtime environment. Those familiar with PHP extension development can think of this as the zend_module_entry struct, with all callbacks and the function table set to NULL.

The code so far is at commit: 214e2e7be6

Building an extension and testing it out

To build an extension, first run hphpize to generate a CMakeLists.txt file, then cmake . to generate a Makefile from that. Finally, issue make to actually build it. You should see output like the following:

$ hphpize
** hphpize complete, now run 'cmake . && make` to build
$ cmake .
-- Configuring for HHVM API version 20140829
-- Configuring done
-- Generating done
-- Build files have been written to: /home/username/hhvm-ext-writing
$ make
Scanning dependencies of target example1
[100%] Building CXX object CMakeFiles/example1.dir/ext_example1.cpp.o
[100%] Built target example1

Now we're ready to load it into our runtime. Start by creating a simple test file:
<?php
var_dump(extension_loaded('example1.php'));
Then fire up hhvm: hhvm -d extension_dir=. -d hhvm.extensions[]=example1.so tests/loaded.php and you should see bool(true).

Adding functionality

The simplest way to add functionality is to write some Hack code. You could write straight PHP code, but you'll see in a few moments why Hack is preferable for extension systemlibs. Let's introduce a new file: ext_example1.php and link it into our project:
<?hh

function example1_hello() {
  echo "Hello World\n";
}

Then load it in during the moduleInit() (aka MINIT) phase:
class Example1Extension : public Extension {
 public:
  Example1Extension(): Extension("example1", "1.0") {}
  void moduleInit() override {
    loadSystemlib();
  }
} s_example1_extension;
And finally, add the following to your config.cmake file to embed it into the .so, where HHVM can load it from at runtime.
HHVM_EXTENSION(example1 ext_example1.cpp)
HHVM_SYSTEMLIB(example1 ext_example1.php)

Rebuild your extension according to the instructions above, then try it out:
$ hhvm -d extension_dir=. -d hhvm.extensions[]=example1.so tests/hello.php
Hello World

The code so far is at commit: 54782f157d

Bridging the gap

If all you wanted to do was write PHP code implementations you could create a normal library for that. Extensions are for bridging PHP-script into native code, so let's do that. Make a new entry in your systemlib file using some hack specific syntax:
<<__Native>>
function example1_greet(string $name, bool $hello = true): void;

The <<__Native>> UserAttribute tells HHVM that this is the declaration for an internal function. The hack types tell the runtime what C++ type to pair them with, and the usual rules for default arguments apply.

To pair it with an internal implementation, we'll add the following to ext_example1.cpp:

void HHVM_FUNCTION(example1_greet, const String& name, bool hello) {
  g_context->write(hello ? "Hello " : "Goodbye ");
  g_context->write(name);
  g_context->write("\n");
}


And link it to the systemlib by adding HHVM_FE(example1_greet); to moduleInit().

As you can see, internal functions are declared with the HHVM_FUNCTION() macro where the first arg is the name of the function, as exposed to userspace, and the remaining map to the userspace functions argument signature. The argument types map according the following table:

Hack type C++ type (argument) C++ type (return type)
voidN/Avoid
boolboolbool
intint64_tint64_t
floatdoubledouble
stringconst String&String
arrayconst Array&Array
resourceconst Resource&Resource
object
const Object&Object
ClassNameconst Object&Object
mixedconst Variant&Variant
mixed&VRefParamN/A


Since this is Hack syntax, you may declare the types as soft (with an @) or nullable (with a question mark), but since these types are not limited to a primitive, they need to be represented internally as the more generic const Variant& for arguments or Variant or return types (essentially, mixed).

Reference arguments use the VRefParam type noted above. An example of which can be seen below:

<<__Native>>
function example1_life(mixed &$meaning): void;
void HHVM_FUNCTION(example1_life, VRefParam meaning) {
  meaning = 42;
}

The code so far is at commit: df16aca35e

Constants

Constants, like any other bit of PHP, may be declared in the systemlib file, or if they depend on some native value (such as a define from an external library), they may be declare in moduleInit() using the Native::registerConstant() template as with the following:
const StaticString s_EXAMPLE1_YEAR("EXAMPLE1_YEAR");

class Example1Extension: public Extension {
 public:
  Example1Extension(): Extension("example1", "1.0") {}
  void moduleInit() override {
    Native::registerConstant<KindOfInt64>(s_EXAMPLE1_YEAR.get(), 2015);
  }
} s_example1_extension;
The use of this function should be mostly obvious, in that it takes the name of a constant as a StringData* (which comes from a StaticString's .get() accessor), and a value appropriate to the constant's type. The type, in turn, is given as the function's template parameter and is one of the DataType enum values. The kinds correspond roughly to the basic PHP data types.
DataTypeC++ type
KindOfNullN/A
KindOfBooleanbool
KindOfInt64int64_t
KindOfDoubledouble
KindOfStaticStringStringData*
The code so far is at commit: ad9618ac8c

What's next...

In the next part of this series, we'll look at the String, Array, and Variant types. Part III will continue with Objects, then Resources in Part IV.

Dec 30, 2008

2008 Lookback

It's been a helluva year... No, seriously, it's been... unique.


Last January, I started off the year with a presentation at geekSessions-1.3, along with some associated "hanging-out" in San Francisco (a city I normally avoid, because I dislike large crowds of people). I also got a brand new insulin pump to replace the seven year old beast who's LCD display had finally died. The pump itself didn't die (kudos to Medtronic for that), just the display. I've enjoyed my new "pocket pancreas" about as much as the old one, though of course the incremental improvements to its design haven't gone unappreciated.


February was a fairly quiet month for me professionally. A mixup in some of my medications (not related to the new pump), left me really distracted and irritable. The medications have been sorted, but I got to like the fear I induced in my coworker's eyes to the point that I keep up the show for appearances sake. :)

March was a flurry of activity getting things sorted at home and at work so that I could take a much needed month off in April. This month would ultimately turn into a mere three weeks since I refuse to relax, but it was time well spent anyway. During March I met two new friends who I've since come to appreciate deeply, and in April I got to spend a lot of time with my grandma; Something I do far too little of.


May saw the launch of Yahoo! Search Monkey move into full swing, bringing a lot of time and effort by the entire Search team along with it. As part of the launch, we held a developer event in Sunnyvale, and I learned that if you blog long and hard enough, random strangers will eventually show you naked pictures of themselves.


June was a hard month to handle. My wife of more than 10 years got herself a 1 bedroom apartment and moved out. We're both feeling the repercussions of that, and while reconciliation is a possibility, we really haven't fixed any of the things were wrong with our relationship. See, we're just as F-ed up as a straight couple, but with twice the estrogen! It was also at this time that my grandfather's cancer took a turn for the worse and he found himself spending a great deal of time in ICU. His children and grandchildren did their best to avoid feeling useless as we waited outside him room, not sure if he was going to pull out of it...


In July, my grandfather was feeling good enough that we threw a small party for my cousin's graduation, and brought him along for what we all knew was going to be his last summer. There was a lot of smiling outdoors and crying in the bathroom. During this party my grandfather pulled me aside and gave me a gift that... I'm not even sure he understood how much it meant to me... My sister took photos, and we all doted a bit more than we should have, but we built a new memory around him that we can carry with us.


Having managed some pretty downer months so far this summer, I took a week at the end of August to take a real vacation to south Utah where I went hiking with four complete strangers I'd met over the internet. Hrmmm... desert... strangers... dangerous climbs? Not the most sane thing I've ever done, but I made some really good friendships that'll last for years to come, and if all goes well, I'll be taking another trip with them this coming June, back to Zion National Park.


September saw ZendCon08 and PHP Appalachia, the latter of which spawned far too many anecdotes about the rowdiness of the PHP community. Where should I start? The semi-naked toxic hot tub? The jagged piece of glass that Chris tried to kill Ben with but got stuck in Cal's foot instead? The fire department we bribed with a cute blonde? Nonono, what happens in Pigeon Forge, stays in Pigeon Forge...


In October, my grandfather fell ill again, holding on just long enough to say good-bye to his children and grandchildren. Sadly his great-grandchildren aren't likely to remember him very well, but thankfully I know that my last words to him were, "I love you", and at least he went quickly. We can all hope for so much when out time eventually comes.


November led to another rowdy conference get together. This time it was php|works in Atlanta, GA, and I dunno about you, but Marco really needs to stop letting me near the open bar. I'm not a frequent drinker, so my tolerance is really very low, and I do... unconventional things when I've had my ninth vodka/cranberry... Doesn't hurt that the bartender was very generous... Oh well, at least I didn't throw up in the taxi on the way to Cal's going-away party. ((Note: I didn't say who did... assuming anyone did... which I didn't say either...))


And oh yeah... the election... biggest sack full of mixed emotions in a long time... Okay, sure, let me get it out, YAY! Obama won. I have HUGE hopes for this guy, and given what I've seen of his transition process, I think he might just stand a chance of living up to his hype. YES. WE. CAN. There, now that that's out of my system, I can go back to being really dissapointed in the One Million californians who voted in favor of Proposition 8. I can go back to slamming my face into the desk whenever I hear the FALSE argument that equal marriage rights will lead to sodomy in the streets and pedophilia in the classrooms. I can go back to being disappointed by my fellow Christians who call homosexuality as significant a threat to humanity as global warming and a threat to world peace. That's not just any Christian saying that by the way, that's the ever-lovin' POPE.


And then here it is: December. My wife and I are talking about getting back together, though I'm not sure we're making progress... I'm doing my best to focus back on my work. I've gotten back into rock climbing, and am eagerly awaiting the snow season to start in earnest. I got up to my dad's for another Christmas with the family, strained by not having gramps around, but I can tell my dad's trying to step up and fill the patriarchal void that's been left. I've spent the past week fighting off a cold and baking cookies. Tomorrow I'll head back into the office and get some work done before having friends over for a new year's eve bash. On thursday, it'll all start over again...


Happy new year.

May 15, 2008

I have officially arrived

I was at the Search Monkey Developer Launch tonight to answer questions and give demos to the attendees. The event itself was a nice success, but that's not what I'm writing about. What I'm writing about is one particular attendee. Apologies to this guy if I embarrass him, but he had to know I'd say something.


After getting through the initial queue of developers asking about Search Monkey, I was approach by a tall young man:

<guy> Hey, you're Sara Golemon, right?
<me> Yeah...
<guy> Nice to meet you, I really like your blog entries about how PHP works,
and I bought your book!
<me> Oh fantastic! Are you enjoying it?
<guy> Yeah, it's really heavy, and I haven't had a chance to use a lot,
but I was experimenting with writing my own libssh2 wrapper...

By this point, I'm suitably blushing. I'm easily swayed by compliments thanks to my lack of self-esteem. Then he takes a turn...

<guy> I almost emailed this to you, it's a photo my girlfriend took of me without warning...
Let me just find it in my camera
* guy shows photo of himself lying in a water-filled bathtub, naked, covering his... bits
with a copy of my book

I... um... well... okay... I can see why you wouldn't email that out of the blue.... Now, I try to think of myself as a fairly progressive, hard-to-phase sort. I can tell a dirty joke with the best of 'em, but this just... left me giggly for the next hour. Like, um...wow... okay... Thanks for sharing...


Is this a sign? Is it a measure of notoriety when strangers show you naked photos of themselves at random tech gatherings?

Jan 19, 2008

Understanding Opcodes

A blog reader (I have readers???) recently shared his wishlist, "I'm trying to figure out how to show the opcodes like you have in your post...". I promised that I'd throw something together, so here it is:


Slow down, wtf is an "Opcode"?

Short answer: It's the compiled form of a PHP script, similar in principle to Java bytecode or .NET's MSIL. For example, say you've got the following bit of PHP script:

<?php
echo "Hello World";
$a = 1 + 1;
echo $a;

PHP (and it's actual compiler/executor component, the Zend Engine) are going to go through a multi-stage process:

  1. Scanning (a.k.a. Lexing) - The human readable source code is turned into tokens.
  2. Parsing - Groups of tokens are collected into simple, meaningful expressions.
  3. Compilation - Expressions are translated into instruction (opcodes)
  4. Execution - Opcode stacks are processed (one opcode at a time) to perform the scripted tasks.
Side note: Opcode caches (like APC), let the engine perform the first three of these steps, then store that compiled form so that the next time a given script is used, it can use the stored version without having to redo those steps only to come to the same result.

Er... okay... can you elaborate a little? What's lexing? I thought superman put him in jail...

That's Lex Luthor you nit-wit! The most expedient way to explain lexing is by example. Take a look at the manual page for token_get_all(), this gem is actually a wrapper around the Zend Engine's own language scanner. Play around with it a bit, and you'll notice that plugging the short script above into it will produce:

Array
(
[0] => Array
(
[0] => 367
[1] => <?php
)
[1] => Array
(
[0] => 316
[1] => echo
)
[2] => Array
(
[0] => 370
[1] =>
)
[3] => Array
(
[0] => 315
[1] => "Hello World"
)
[4] => ;
[5] => Array
(
[0] => 370
[1] =>
)
[6] => =
[7] => Array
(
[0] => 370
[1] =>
)
[8] => Array
(
[0] => 305
[1] => 1
)
[9] => Array
(
[0] => 370
[1] =>
)
[10] => +
[11] => Array
(
[0] => 370
[1] =>
)
[12] => Array
(
[0] => 305
[1] => 1
)
[13] => ;
[14] => Array
(
[0] => 370
[1] =>
)
[15] => Array
(
[0] => 316
[1] => echo
)
[16] => Array
(
[0] => 370
[1] =>
)
[17] => ;
)

In the array returned by token_get_all(), you have two types of tokens: Single character non-label characters are returned as just that. The character that was found in the source file at that point. Everything else, from labels, to language constructs, to multi-character operators (like >>, +=, etc...) are returned as an array containing two elements: The token ID (which corresponds to T_* constants -- e.g. T_ECHO, T_STRING, T_VARIABLE, etc...), and the actual text which that token came from. What the engine actually gets is slightly more detailed than what you see in the output from token_get_all(), but not by much...

Okay, tokenization just breaks the script into bite-size pieces, how does parsing work then?

The first thing the parser does is throw away all whitespace (Unlike some other P* language...). From the reduced set of tokens, the engine looks for irreducible expressions. How many expressions do you see in the example above? Did you say three? WRONG There are three statements, but one of those statements is made of two distinct expressions. In the case of $a = 1 + 1; the first expression is the addition, followed by the assignment to the variable as a second, distinct expression. All together our expression list is:

  1. echo a constant string
  2. add two numbers together
  3. store the result of the prior expression to a variable
  4. echo a variable

Hey! That's starting to sound familiar! Did I see that kind of description before?

Oh, you must mean my post about strings (plug). That's correct, because these expressions are exactly the pieces which go into making up oplines! Given the expression list we've just reached, the resulting opcodes look something like:

  • ZEND_ECHO 'Hello World'
  • ZEND_ADD ~0 1 1
  • ZEND_ASSIGN !0 ~0
  • ZEND_ECHO !0

What happened to $a? What's the difference between ~0 and !0?

Short answer: !0 is $a


So here's the deal.... oplines have five principle parts:

  • Opcode - Numeric identifier which distinguishes what the opline will do. This is what coresponds to ZEND_ECHO, ZEND_ADD, etc...
  • Result Node - Most opcodes perform "non-terminal" actions. That is; after executing there's some result which can be consumed as an input to a later opline. The result node identifies what temporary location to place the result of the operation in.
  • Op1 Node - One of two inputs to the given opcode. An input may be a constant zval, a reference to a previous result node, a simple variable (CV), or in some cases a "special" data element, such as a class definition. Note that an opcode may use both, one, or neither input node. (Some even use more, see ZEND_OPDATA)
  • Op2 Node - Ditto
  • Extended Value - Simple integer value used to differentiate specific behaviors of an overloaded opcode.

So obviously the nodes are the most complicated parts of an opline, here's the important parts of what they look like:

  • op_type - One of IS_CONST, IS_TMP_VAR, IS_VAR, IS_UNUSED, or IS_CV
  • u - A union of the following elements (the one which is used depends on the value of op_type):
    • constant (IS_CONST) - zval value. This node results which you include a literal value in your script, such as the 'Hello World' or 1 values in the example above.
    • var (IS_VAR or IS_TMP_VAR or IS_CV) - Integer value corresponding to a temporary slot in a lookup table used by the engine.

Now let's look at the difference between those optypes, particularly with respect to u.var:

  • IS_TMP_VAR - These ephemeral values are strictly for use by non-assignment non-terminal expressions. They don't support any refcounting because they're guaranteed not to be shared by any other variable. These are denoted in the examples I use on this site (and in VLD output) as tilde characters (~)
  • IS_VAR - Usually the result of a ZEND_FETCH(_DIM|_OBJ)?_(R|W|RW), or one of the assignment opcodes (which are technically non-terminal expressions since they can be used as inputs to other expressions. Since these are tied to real variables, they have to respect reference counting and are passed about at an extra degree of indirection. They're stored in the same table though. These are denoted by the string symbol ($)
  • IS_CV - "CV" stands for "Compiled Variables". These are basicly cached hash lookups for fetching simple variables from the local symbol table. Once a variable is actually looked up at runtime, it's stored at an extra level of indirection in an even faster lookup table using an index into a vector. That's what the number in this node denotes. These types of nodes are distinguished by a bang (!)

Boggle... You...so lost me there...

Yeah, that explanation sort of got away from me didn't it? What can I clear up?


All I really want to know is how to translate some source code into an opcode..list...thingy...

Heh, okay... first off, that "opcode list thingy" is called an op_array, and you can generate those really easily using one of two PECL packages. You can use my parsekit package, which is useful for programmatic analysis of script compilation, but frankly... it's not what you're looking for and there's not much call for scripts analyzing other scripts anyway. I recommend Derick's VLD (Vulcan Logic Disasembler) which is what'll actually generate the kinds of opcode lists you'll see me use in blog posts.


Once you've got it installed (it installs like any other PECL extension), you can run it with a command like the following:

php -d vld.active=1 -d vld.execute=0 -f yourscript.php

Then sit back and watch the opcodes fly! Important note: Using -r with command line code may not work due to a quirk of the way the engire parses files in older versions of PHP (and with older versions of VLD). Be sure to put your script on disk and reference it using -f if -r doesn't work for you.


Holy schnikies! That's a lot of opcodes! How can I tell what they all do?

Take a look at Zend/zend_vm_def.h in your PHP source tree. In here you'll find a meta-definition of every single opcode used by the engine. Side note: It's used as a source for zend_vm_gen.php which generates the actual code file zend_vm_execute.h. How's that for chicken and egg? Every version of PHP since 5.1.0 has required PHP be already built in order to build it!

Jul 26, 2007

Fun with Unicode

I caught a blog entry by Alexey Zakhlestin via Planet-PHP today which asked the question "Why aren't unicode math symbols supported by programming languages?". The obvious answer, of course, is that they work just fine with the symbols they have and there's no need to mess with a good thing (it doesn't help that typing these symbols is a pita on your average terminal).

Being a whimsical sort, I decided that actually implementing his request would be more fun than simply pish-poshing it. I'm not suggesting this be part of PHP6 (I still don't personally think it's a good idea), but it's a fun exercise and good for a conversation starter... Here's just a few of the things I can do now:

<?php
var_dump(¼, ½ ¾);
// float(0.25)
// float(0.5)
// float(0.75)

var_dump(1 ≤ 2, 2 ≯ 3, 5 ≠ 6);
// bool(true)
// bool(true)
// bool(true)

var_dump(3 × 4, 15 ÷ 5);
// int(12)
// int(3)

var_dump(1 « 3);
// int(8)

/* Your font may be too small,
* but that's a skull and crossbones
*/
☠('aka die/exit');

P.S. - Just FYI, I am aware that the patch referenced above has flaws... The problems are fixable, I just didn't bother fixing 'em because this isn't a serious patch anyway.... Best spend that time on something worthwhile...


P.P.S. - Since the original posting of this article, PHP6-Unicode has been... well, essentially scrapped. So the patch itself is no longer relevant.


Index: Zend/zend_language_scanner.l
===================================================================
RCS file: /repository/ZendEngine2/zend_language_scanner.l,v
retrieving revision 1.167
diff -u -p -r1.167 zend_language_scanner.l
--- Zend/zend_language_scanner.l 12 Jul 2007 09:23:48 -0000 1.167
+++ Zend/zend_language_scanner.l 27 Jul 2007 05:05:47 -0000
@@ -402,6 +402,109 @@ ZEND_API int zend_copy_scanner_string(zv
return 1;
}

+/* Used by {LABEL} for converting unicode operator symbols */
+static inline int zend_scan_unicode_operator(zval *zendlval, char *str, zend_uint str_len, UConverter *conv, int *oplen, int have_equal TSRMLS_DC)
+{
+ int ret = 0;
+
+ switch (Z_USTRVAL_P(zendlval)[0]) {
+ case 0x2260: /* NOT EQUAL TO */
+ ret = T_IS_NOT_EQUAL;
+ break;
+
+ case 0x2264: /* LESS-THAN OR EQUAL TO */
+ case 0x226F: /* NOT GREATER-THAN */
+ ret = T_IS_SMALLER_OR_EQUAL;
+ break;
+
+ case 0x2265: /* GREATER-THAN OR EQUAL TO */
+ case 0x226E: /* NOT LESS-THAN */
+ ret = T_IS_GREATER_OR_EQUAL;
+ break;
+
+ case 0x00AB: /* LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */
+ case 0x226A: /* MUCH LESS THAN */
+ ret = have_equal ? T_SL_EQUAL : T_SL;
+ break;
+
+ case 0x00BB: /* RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */
+ case 0x226B: /* MUCH MORE THAN */
+ ret = have_equal ? T_SR_EQUAL : T_SR;
+ break;
+
+ case 0x2270: /* NEITHER LESS-THAN NOR EQUAL TO */
+ ret = '>';
+ break;
+
+ case 0x2271: /* NEITHER GREATER-THAN NOR EQUAL TO */
+ ret = '<';
+ break;
+
+ case 0x2276: /* LESS-THAN OR GREATER-THAN */
+ case 0x2277: /* GREATER-THAN OR LESS-THAN */
+ ret = T_IS_NOT_EQUAL;
+ break;
+
+ case 0x2278: /* NEITHER LESS-THAN NOR GREATER-THAN */
+ case 0x2279: /* NEITHER GREATER-THAN NOR LESS-THAN */
+ ret = T_IS_EQUAL;
+ break;
+
+ case 0x00D7: /* MULTIPLICATION SIGN */
+ ret = have_equal ? T_MUL_EQUAL : '*';
+ break;
+
+ case 0x00F7: /* DIVISION SIGN */
+ ret = have_equal ? T_DIV_EQUAL : '/';
+ break;
+
+ case 0x00BC: /* VULGAR FRACTION ONE QUARTER */
+ zval_dtor(zendlval);
+ ZVAL_DOUBLE(zendlval, 0.25);
+ ret = T_DNUMBER;
+ break;
+
+ case 0x00BD: /* VULGAR FRACTION ONE HALF */
+ zval_dtor(zendlval);
+ ZVAL_DOUBLE(zendlval, 0.5);
+ ret = T_DNUMBER;
+ break;
+
+ case 0x00BE: /* VULGAR FRACTION THREE QUARTERS */
+ zval_dtor(zendlval);
+ ZVAL_DOUBLE(zendlval, 0.75);
+ ret = T_DNUMBER;
+ break;
+
+ case 0x2620: /* SKULL AND CROSSBONES */
+ ret = T_EXIT;
+ break;
+ }
+
+ if (ret) {
+ /* How much of the input do we need to consume for this codepoint in this character set? */
+ UChar buffer[2], *buffer_ptr = buffer;
+ UErrorCode status = U_ZERO_ERROR;
+ const char *str_ptr = str;
+
+ ucnv_toUnicode(conv, &buffer_ptr, buffer + 1, &str_ptr, str + str_len, NULL, TRUE, &status);
+ if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {
+ /* Shouldn't happen... */
+ ret = 0;
+ } else {
+ *oplen = (str_ptr - str);
+ }
+ }
+
+ if (ret && ret != T_DNUMBER) {
+ zval_dtor(zendlval);
+ ZVAL_NULL(zendlval);
+ }
+
+
+ return ret;
+}
+
static inline int zend_check_and_normalize_identifier(zval *zendlval)
{
UChar *norm;
@@ -2217,13 +2320,47 @@ HEREDOC_CHARS ("{"*([^$\n\r\\{]|("
return T_ENCAPSED_AND_WHITESPACE;
}

-{LABEL} {
- if (!zend_copy_scanner_string(zendlval, yytext, yyleng, UG(unicode)?IS_UNICODE:IS_STRING, SCNG(output_conv) TSRMLS_CC)) {
- return 0;
+{LABEL}"="? {
+ zend_bool have_equal = 0;
+
+ if (yytext[yyleng-1] == '=') {
+ have_equal = 1;
}
- if (UG(unicode) && !zend_check_and_normalize_identifier(zendlval)) {
+
+ if (!zend_copy_scanner_string(zendlval, yytext, yyleng - have_equal, UG(unicode)?IS_UNICODE:IS_STRING, SCNG(output_conv) TSRMLS_CC)) {
return 0;
}
+
+ if (UG(unicode)) {
+ UChar *norm;
+ int norm_len;
+
+ if (!zend_is_valid_identifier(Z_USTRVAL_P(zendlval), Z_USTRLEN_P(zendlval))) {
+ int oplen = 0, ret = zend_scan_unicode_operator(zendlval, yytext, yyleng, SCNG(output_conv), &oplen, have_equal TSRMLS_CC);
+
+ if (ret) {
+ yyless(oplen + have_equal);
+ return ret;
+ }
+
+ zval_dtor(zendlval);
+ zend_error(E_COMPILE_WARNING, "Invalid identifier syntax: %r", Z_USTRVAL_P(zendlval));
+ return 0;
+ }
+
+ if (zend_normalize_identifier(&norm, &norm_len, Z_USTRVAL_P(zendlval), Z_USTRLEN_P(zendlval), 0) == FAILURE) {
+ zend_error(E_COMPILE_WARNING, "Could not normalize identifier: %r", Z_USTRVAL_P(zendlval));
+ efree(Z_USTRVAL_P(zendlval));
+ return 0;
+ }
+
+ if (norm != Z_USTRVAL_P(zendlval)) {
+ efree(Z_USTRVAL_P(zendlval));
+ ZVAL_UNICODEL(zendlval, norm, norm_len, 0);
+ }
+ }
+ yyless(yyleng - have_equal);
+
return T_STRING;
}

May 19, 2007

create_function() is not your friend, buddy

While browsing through PHP Developer today, I came across this blog entry: "My new best friend" extolling the virtues of create_function(). Let me tell you why create_function() is not my best friend...

First, despite the disclaimer in the mentioned blog entry, it is as bad as its kissing-cousin eval(). Let's take a look at what create_function() actually does by translating it into userland code:

function create_function($args, $code)
{
static $id = 0;
eval("function __lambda_func($args) { $code }");
while (!runkit_function_rename('__lambda_func', "\0lambda_" . (++$id)));
return "\0lambda_$id";
}

I'll let you contemplate on that awhile....You should be noticing the following sets of problems:

  • Prone to critical abuse by user-supplied code
  • Skips opcode cache optimizations

You should also be thinking about the practical issues with it:

  • Code lives inside quoted strings which means awkward escaping of embedded quotes
  • Encourages not using comments (evil)
  • 100% blind to reflection or PHPDoc style documentation generation
  • I'm sure you can come up with a couple more...

"If eval() is the answer, then you're asking the wrong question"

Jan 26, 2007

You're being lied to

If you're among the crowd who have migrated an OOP based application from PHP4 to PHP5, then I'm sure you've heard the expression "Objects are copied by reference by default in PHP5". Whoever told you that, was lying.

Now, to be fair, it's an innocent lie, since objects do behave in a reference-like manner, but references are NOT what they are. Let's start with a simple illustration proving that they aren't references:

<?php
$a = new stdClass;
$b = $a;
$a->foo = 'bar';
var_dump($b);
/* Notice at this point, that $a and $b are,
* indeed sharing the same object instance.
* This is their reference-like behavior at work.
*/

$a = 'baz';
var_dump($b);

/* Notice now, that $b is still that original object.
* Had it been an actual reference with $a,
* it would have changed to a simple string as well.
*/
?>

What's going on here? Well, the answer is easiest to explain by explaining what the underlying structure of objects are. In PHP5, a variable containing an object identifies the instance by storing a simple numeric value. When an action is going to be performed on an object, that numeric value is used with a lookup table to retreive the actual instance. In PHP4, by contrast, a variable containing an array identifies that object by carrying around the actual properties table itself. What this means in practice is that when you assign (not by reference) a PHP5 object to a new variable, that integer handle is copied into the new variable, but it still points at the same instance, because it's still the same number. Assigning a PHP4 object however, means copying all the properties, effectively generating a new instance, since changes to one will not effect the other.

To put this another way, PHP4 objects are basically Arrays with functions associated with them, PHP5 objects are basicly Resources (a la MySQL result handles, or file pointers) again with functions loosely associated to them. Consider the following code in PHP4 (or any version):

<?php
$fp = fopen('foo.txt', 'r');
$otherVar = $fp;
fwrite($fp, "One\n");
fwrite($otherVar, "Two\n");
fclose($fp);

/* This fails, because the file is closed */
fwrite($otherVar, "Three\n");
?>

You'd fully expect data to be written to the same file, as though you'd used $fp everywhere, rather than interchanging the variables right? Well, PHP5 objects are the same. The instance itself isn't duplicated when you assign to a new variable, just the unique identifier.


I'm lying to you also

"Copying" a variable doesn't exactly mean copying. Take the following code block:

<?php
$a = 'foo';
$b = $a;
$a = 'bar';
?>

Now, you know PHP well enough to know that by the end of this code block, the value of $b will still be 'foo'. What you may not know, is that the original copy of 'foo' that was in $a, was never actually duplicated.

To understand what PHP is doing, you need to understand the internal structure of the variable and how it relates to userspace visible variable names ('a' and 'b' in this case). First off, the actual contents of a variable (known as a zval) consists of four parts: type (e.g. NULL, Boolean, Integer, Float, String, Array, Resource, Object), a specific value (e.g. 123, 3.1415926535, etc...), is_ref - a flag indicating if the value is a reference or not, and refcount which tells how many times this value is being shared.

What you think of as a variable (e.g. $x) is actually just a label, that label ('x' in this case) is used as a lookup to find the zval which conatins the actual value. These are just like keys in an associative array, in fact, the mechanisms are identical.

With me so far? Good. Now, when you first create a variable (e.g. $x = 123;, PHP allocates a new zval for it, stores the specific value, and associates the label with the value:

  'x' => zval ( type       => IS_LONG,
value.lval => 123,
is_ref => 0,
refcount => 1 )

So far, refcount is 1 since the zval value is only being referenced by one label. If we now put this value into a full-reference set using $y =& $x;, the same zval is reused. It's simply associated with a new label and it's reference counters are adjusted properly.

  'x' => zval ( type       => IS_LONG,
| value.lval => 123,
| is_ref => 1,
| refcount => 2 )
'y' /

This way, when you later change the value of $x, $y appears to change as well because it's looking at the same internal value. But what if we hadn't done a reference assignment, what if we'd done a normal assignment: $y = $x;, surprisingly, the result would be almost the same.

  'x' => zval ( type       => IS_LONG,
| value.lval => 123,
| is_ref => 0,
| refcount => 2 )
'y' /

Again, the original zval associated with $x is reused, the only difference this time is that is_ref is not set to 1. This is known as a copy-on-write reference set (as opposed to the full-reference set described above). This 0 flag tells the engine that if anyone tries to change this value (regardless of which label they use to reach it), any other references to it should be left alone. Here's what happens if we take that current state and do $x = 456;

  'y' => zval ( type       => IS_LONG,
value.lval => 123,
is_ref => 0,
refcount => 1 )
'x' => zval ( type => IS_LONG,
value.lval => 456,
is_ref => 0,
refcount => 1 )

$x has been disassociated from the original zval (thus dropping its refcount back to 1), and new zval has been created for it.


Why referencing when you don't have to is a bad idea.

Let's consider one more situation, take a look at this code block:

<?php
$a = 'foo';
$b = $a;
$c = &$a;
?>

At the first instruction, a single zval is created, associated to a single label:

  'a' => zval ( type => IS_STRING, value.str.val = 'foo', is_ref = 0, refcount = 1 )

At the second intstruction, that zval is associated to a second label, so far so good:

  'a' => zval ( type          => IS_STRING,
| value.str.val => 'foo',
| is_ref => 0,
| refcount => 2 )
'b' /

At the third intstruction, however, we run into problems. Since this zval is already tied up in a copy-on-write reference set which include $b, that zval can't be simply promoted to is_ref==1. Doing so would drag $b into $a and $c's full-reference set, and that would be wrong. In order to resolve this, the engine is forced to duplicate that zval into two identical copies, from which it can begin to shuffle around reference flags and counts:

  'b' => zval ( type          => IS_STRING,
value.str.val =>'foo',
is_ref => 0,
refcount => 1 )
'a' => zval ( type => IS_STRING,
| value.str.val => 'foo',
| is_ref => 1,
| refcount => 2 )
'c' /

Now you've got two copies of the same literal value, so you're wasting memory for the storage, and processing time required to actually make the duplication. Since a LOT of events lead to copy-on-write uses (including simply passing an argument to a function), this sort of forced duplication actually happens very commonly when you start involving actual references.


The moral of the story

Assigning values by references when you don't need to (in order to later modify the original value through a different label) is NOT a case of you outsmarting the silly engine and gaining speed and performance. It's the opposite, it's you TRYING to outsmart the engine and failing, because the engine is already doing a better job than you think.

How does this reflect on objects? They're not special. They're not different from other variables. They are not pretty snowflakes. In this code block:

<?php
$a = new stdClass;
$b = $a;
?>

The labels are still placed into copy-on-write reference sets. What's important, is that even when a duplication does occur, (A) only that unique integer is copied (which is cheap), and (B) the duplicated integer still points to the same place. Hence you get reference-like behavior, but not an actual reference by default.

Hungry for more? Check out my coverage of the zval.

Dec 28, 2006

PHP-2006: A look back

Well, Davey Shafik started us off with his year-end wrapup so I'll follow suit with mine. The thoughts below are mine and mildly influenced by alcohol. They represent a foggy review of how I experienced the year through the imperfect recollection of mailing list archives.

January began with releases of 4.4.2 and 5.1.2. Version 5.1.2 was especially close to my heart since it was the first version to ship an extension of mine not only bundled, but enabled by default. I've had my hands in most of the PHP runtime, but this was the first time I could point at a standard extension and say that it was basicly my work (Note: Mike Wallner did a fair bit of work adding to the number of hash algorithms supported, don't let me discount his efforts). Tim Starling wrote to ask why PHP4 refcounts are 16bit, and what he could do about getting that counter increased in future versions of PHP4. Since such a change would break binary compatability and since the PHP4 branch is already quite dead, the request was ultimately left alone with an admonishment to "not do that".

A few other requests were broached or continued, such as support for Friend Classes, Named Arguments, and Naming Arguments (The last of which would eventually be implemented). It was also this month in which Rasmus suggested adding JSON to the standard distribution, this quietly morphed into votes for including filter; Both were eventually linked in. James Crane had the idea that Array Literals in PHP could use sprucing up, meanwhile Sean Coates planted the seed for what would become the PHP6 Unicode Progress tracker.

February came in fairly quiet, mostly wrapping up the topics from January. Appearantly Steph didn't like the quiet and decided to stir up the hornet's nest with some four letter words cleverly disguised under the heading of True Labeled Breaks. For those who have blocked out 2005, this was easily one of the longest threads of that year and this resumption promised to be just as bad. By March, this thread would finnally be brought to a halt as the functionality was slipped quietly into the engine. I did not see that coming...

Amidst this unexpected addition, others were busily pulling things out, with Andi slashing away at safe_mode in HEAD, and Marcus integrating support for function deprecation into the 5.2 branch.

March came in like a lion with Marcus poking the list about Late Static Binding with less argument about Why, and a healthy focus on How. Johannes Schlueter made a very pragmatic and uncontroversial suggestion to change the internal symbol prefix applied to methods in order to distinguish them from functions. Pierre ran with some PDM recommendations shouting Adieu register_globals and Adieu a la magie. Sebastian Bergmann was frustrated with trying to apply streams filters to include and require, until I pointed him at the php://filter wrapper. Though this met his need, he made a strong case for being able to set an automatic filter to be applied to all streams. I promised to do this after streams in HEAD had been cleaned up, and while I've gotten streams where I want them, I still havn't fullfilled his feature request... Shame on me.

All this time, the GOTO debate was still raging and similar types of language altering features were being put on the table. Finally, Zeev decided he'd had enough calling for people to "Give the language a rest". This managed to have a decent effect which Rasmus soon channeled into his call for performance geeks to narrow the performance gap between 4.4 and 5.1. Towards the end of the month, I suggested adding an open_basedir_for_include directive. While there was some interrest for this, Ilia quickly convinced me that my approach to the problem was flawed and wouldn't give any real benefit.

April showered the internals list with Round 2 of the Late Static Binding discussion, and Thomas Boutell's anouncement that he'd be turning over primary development of the GD library to the PHP project, spearheaded by Pierre. I made some more noise complaining of RETURN_RT_STRING() (and family)'s leakage of memory under certain not-uncommon conditions, while Nuno brought the infamous Coverity Report to attention.

Richard Lynch shared his WTF concerning the oft misunderstood tsrm_ls parameter in PHP's sources. Sadly, I hadn't written my summary of the topic yet, but although noone gave him a detailed description (It's a bit long to go into in an email), several helpful links were supplied. Round about the middle of the month, Rasmus announced PHP's eminent participation in the Google Summer of Code project (wait, don't you work for Yahoo!?). After signing up as a mentor (though I never actually mented -- is that a word?), I gathered up my materials and flew off to php|tek 2006 in Orlando.

May opened to the initial planning rounds for PHP 5.2.0, and the resumption of the ifsetor() request, now dressed up as coalesce(). By the way, why havn't we embraced this feature request yet? About a week into the month, Ilia branched the PHP_5 tree leaving room for the earnest development of 5.2.

William Candillon wrote in to request a PHP version of C's #line macro, but was drowned out by the roar of Derick's plea to "Stop breaking our apps for the sake of OO". Once the din of that had quieted down a little, Jason Garber decided to ask about making it possible to mark properties as read only. In case anyone was curious, none of these proposals gained footing.

June now, and my book is finally released bringing with it an invitation to appear on php|architect's Pro::PHP podcast. Marcus Boerger suggested that array indices could benefit from implicit __toString() calls, but he was eventually shot down. Dimitry's suggestion however, which provided for automatic module global registration via the module entry, did receive a warm welcome and you'll see it if you look inside PHP 5.2.0.

Clearly this was the month of bright ideas, because Nuno Lopes tossed in gcc branch prediction. It got a warm initial reception from the engine folk including Zeev stating "I actually like how it makes the code more readable hinting which branches are rare.". Andi agreed that it was a nice idea, but brought in the sobering reality that it didn't really do much for performance and could potentially bring unexpected results if applied heavily.

July was a busy month for me as I changed jobs for the first time in over six years. I'm loving the new job by the way. Fantastic coworkers and....interresting challenges.... Laupretre François thought it'd be a good idea to extend include_path to support stream wrappers. Those of us who know what trouble include_path already causes for performance and security were quick to nix that particular idea; Short version: No.

Marcus popped in mid-month with an implementation of the #line directive suggested back in May. Still no love from the internals community at large though; Short version: no #line for you. Dmitry committed a large patch to the Zend Engine to change how non-persistent memory is allocated and freed within a request. The good news is that emallocs are now faster, the bad news is that a hack I'd made in PHP5.1 for manipulating the non-persistent memory pool no longer worked...Grrrr.....

On July 27th, Jani said Good-Bye.

August picked up Mike Wallner's July post decrying the state of OO strictness building up in PHP. This maelstrom took up most of the first week with no real conclusion (at least, not a satisfactory one). A different Mike made some more noise later in the month asking why accessing non-existant functions/methods has to be so darned fatal. The good news is that they aren't so much anymore. Yay for E_RECOVERABLE_ERROR.

September brought a landslide of movement in the PHP6 function migration/review process. There was some degree of question as to whether unicode.semantics should be SYSTEM, PERDIR, or USER. Noone really considered the latter as a possibility as it wrecks way too many assumptions, but the SYSTEM/PERDIR debate raged for awhile with the idealists wanting PERDIR support to aid migration, and the realists clinging to the maintainable simplicity of SYSTEM. In the end, SYSTEM won.

October was a relatively slow month, leading up to conference season such as it was. Midmonth sometime I tossed out the idea of allowing open_basedir to be tightened (but not loosened) during runtime and it was green-lighted surprisingly quietly. Ilia got a little frustrated with the mounting delays holding back the release of PHP 5.2.0 which was already way behind schedule. Tragicly, this plea made the delay last even longer. Finally at the end of the month, he rolled final.

November was a big conference month for me. First I crashed the nearby ZendCon, then turned right around and flew off to Germany for the International PHP Conference. Sean poked the namespaces topic with a sharp stick and managed to generate a week's worth of noise resulting in no actual commitments by any capable/interrested parties. No sooner had that comotion died down than did Antony Dovgal bemoan a regression in fgets()'s behavior which I introduced in HEAD. I still think it's a silly behavior for a userspace function, but BCs are BCs, so the regression's been reverted.

December's most exciting event was a debaucle over the backward/forward compatability of serialize given the additional escaping concerns that processing unicode strings imposes. Ilia brought forward a proposal to finally remove the unnecessary COM/Sockets/MHash extensions from the distribution bundle. After some light debate it was decided to keep COM around and only nix Sockets and MHash as of PHP6 (possibly 5.3 if such a version comes about). Wietse Venema brought back the concept of introducing a taint mode for PHP. The topic is still being discussed, but things don't look good for Wietse...

And now here we are, at year's end. PHP6's unicode function migration process has passed the 50% mark and a preview release is sure to come once we handle a couple more extensions. PHP 5.2 is grabbing hold amongst the serious PHP shops, and even web hosters are trickling away from PHP4. There's still a huge, bright future ahead for this language, together we can make it happen.

¡Feliz Año Nuevo!

Nov 5, 2006

Don't worry, I slept last Thursday

On this, my first foray to Europe, indeed my first real trip outside the US (Those couple hours in Tijuana don't count), I'm faced with one undeniable, inexcapable fact. Jet lag sucks.

It doesn't help that all last week I was staying up past my normal bedtime partying with the attendees of ZendCon06, but I think my real mistake was trying to outsmart my own circadian rhythm. See, I figured "I've got this long flight across the atlantic, it'll go faster if I can fall asleep at some point." Seems reasonable so far. How to ensure sleep? Why, stay up all night before the flight. Brilliant! But wait, what if I can't fall asleep during the flight?

Sometime sunday morning, my plane lands in Frankfurt and I wander, zombie-like, through passport control, baggage claim, and customs somehow managing to board the right shuttle to reach the conference hotel. Based on advices from battle-hardened globetrotters, I was planning to put in a one hour power nap, then go for a walk to "reset" my internal clock. Unfortunately the sixty-plus hour run of consciousness had other plans and by the time I awoke, the sun had set.

Finally this morning (Monday), I managed to put in that walk, touring a nearby suburb which reminded me somewhat spookily of the setting from "Shaun of the Dead" (more zombie tie-ins). Five euros and three liter bottles of diet coke later and I'm almost coherent enough to....what the hell was I gonna say? Sorry, my brain has been cutting out a lot the past few days...

Guten Morgen!

Jun 18, 2006

How long is a piece of string

Sunday morning I was asked by an IRC regular: "Where does the engine parse quoted strings?". Being a sunday morning, I began to launch into a sermon on the distinction between CONSTANT_ENCAPSED_STRING and the problems which befall a single-pass compiler when you start to introduce interpolation. Not what he asked precisely, but an important component in answering his question. Unfortunately, at the time I was busy watching the Brasil-Australia game so I didn't go into the kind of detail I would have. Now, some 12 hours later, since Angela is off buying toe-socks in Santa Cruz, I'll bore anyone with little enough life to read my blog by explaining the pitfalls of using PHP's string interpolation without using an optimizer.

To start things off, let's take a page from my earlier discourse on Compiled Variables and look at the opcodes generated by a few simple PHP scripts:

<?php
echo "This is a constant string";
?>

Yields the nice, simple opcode:

ECHO            'This is a constant string'

No problem... Exactly what you'd expect... Now let's complicate the expressions a little:

<?php
echo "This is an interpolated $string";
?>

Yields the surprisingly messy instruction set:

INIT STRING  ~0
ADD_STRING ~0 ~0 'This'
ADD_STRING ~0 ~0 ' '
ADD_STRING ~0 ~0 'is'
ADD_STRING ~0 ~0 ' '
ADD_STRING ~0 ~0 'an'
ADD_STRING ~0 ~0 ' '
ADD_STRING ~0 ~0 'interpolated'
ADD_STRING ~0 ~0 ' '
ADD_VAR ~0 ~0 !0
ECHO ~0

Where !0 represents the compiled variable named $string. Looking at these opcodes: INIT_STRING allocates an IS_STRING variable of one byte (to hold the terminating NULL). Then it's realloc'd to five bytes by the first ADD_STRING ('This' plus the terminating NULL). Next it's realloc'd to six bytes in order to add a space, then again to eight bytes for 'is', then nine to add a space, and so on until the temporary string has the contents of the interpolated variable copied into its contents before being used by the echo statement and finally discarded. Now let's rewrite that line to avoid interpolation and use concatenation instead:

<?php
echo "This is a concatenated " . $string;
?>

Which yields the significantly shorter and simpler set of ops:

CONCAT       ~0 'This is a concatenated ' !0
ECHO ~0

A vast improvement already, but this version still creates a temporary IS_STRING variable to hold the combined string contents meaning that data is duplicated when it's being used in a const context anyway. Now let's try out this oft-overlooked use of the echo statement:

<?php
echo "This is a stacked echo " , $string;
?>

Look close, there is a meaningful difference from the last one. This time we're using a comma rather than a dot between the operands. If you don't know what the comma is doing there, ask the manual then check back here. Here's the resulting opcodes:

ECHO            'This is a stacked echo '
ECHO !0

Same number of opcodes, but this time no temporary variables are being created so there's no duplication and no pointless copying (unless of course $string wasn't of type IS_STRING, in which case it does have to be converted for output, but don't get picky now). Think this is bad? Consider the average heredoc string which spans several lines of prepared output embedding perhaps a handful of variables along the way. Here's one of several such blocks found in run-tests.php within the PHP distribution source tree:


<?php
echo <<NO_PCRE_ERROR

+-----------------------------------------------------------+
| ! ERROR ! |
| The test-suite requires that you have pcre extension |
| enabled. To enable this extension either compile your PHP |
| with --with-pcre-regex or if you've compiled pcre as a |
| shared module load it via php.ini. |
+-----------------------------------------------------------+

NO_PCRE_ERROR;
?>

Notice that we're not even embedding variables to be interpolated here, yet does this come out to a simple, single opcode? Nope, because the rules necessary to catch a heredoc's end token demand the same careful examination as double-quoted variable substitution and you wind up (in this case) with SEVENTY-EIGHT opcodes! One INIT_STRING, 76 ADD_STRINGs. and a final ECHO. That means a malloc, 76 reallocs, and a free which will be executed every time that code snippet comes along. Even the original contents take up more memory because they're stored in 76 distinct zval/IS_STRING structures.

Why does this happen? Because there are about a dozen ways that a variable can be hidden inside an interpolated string. Similarly, when looking for a heredoc end-token, the token can be an arbitrary length, containing any of the label characters, and may or may not sit on a line by itself. Put simply, it's too difficult to encompass in one regular expression.

The engine could perform a second-pass during compilation, however the time saved reassembling these strings will typically be about the same amount of time spent actually processing them during runtime (if one assumes that each instance will execute exactly once). Rather than complicate the build process (potentially slowing down overall run-times in the process), the compiler leaves this optimization step to opcode caches which can achieve exponentially greater advantage cleaning up this mess then caching the results and reusing the faster, leaner versions on all subsequent runs.

If you're using APC, you'll find just such an optimizer built in, but not enabled by default. To turn it on, you'll need to set apc.optimization=on in your php.ini. In addition to stitching these run-on opcodes back together, it'll also add run-time speed-ups like pre-resolving persistent constants to their actual values, folding static scalar expressions (like 1 + 1) to their fixed results (e.g. 2), and simpler stuff like avoiding the use of JMP when the target is the next opcode, or boolean casts when the original expression is known to be a boolean value. (It should be noted that these speed-ups also break some of the runtime-manipulation features of runkit, but that was stuff you....probably should have been doing anyway)

Can't use an optimizer because your webhost doesn't know how to set php.ini options? You can still avoid 90% of the INIT_STRING/ADD_STRING dilema by simply using single quotes and concatenation (or commas when dealing with echo statements). It's a simple trick and one which shouldn't harm maintainability too much, but on a large, complicated script, you just might see an extra request or two per second.