text: /*
     +----------------------------------------------------------------------+
     | Copyright (c) The PHP Group                                          |
     +----------------------------------------------------------------------+
     | This source file is subject to version 3.01 of the PHP license,      |
     | that is bundled with this package in the file LICENSE, and is        |
     | available through the world-wide-web at the following url:           |
     | http://www.php.net/license/3_01.txt                                  |
     | If you did not receive a copy of the PHP license and are unable to   |
     | obtain it through the world-wide-web, please send a note to          |
     | license@php.net so we can mail you a copy immediately.               |
     +----------------------------------------------------------------------+
     | Author: Dmitry Stogov <dmitry@zend.com>                              |
     +----------------------------------------------------------------------+
  */
  
  #ifdef HAVE_CONFIG_H
  # include "config.h"
  #endif
  
  #include "php.h"
  #include "php_ffi.h"
  #include "ext/standard/info.h"
  #include "php_scandir.h"
  #include "zend_exceptions.h"
  #include "zend_interfaces.h"
  #include "zend_closures.h"
  #include "main/SAPI.h"
  #include "ffi_arginfo.h"
  
  #include <ffi.h>
  
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  
  #ifdef HAVE_GLOB
  #ifdef PHP_WIN32
  #include "win32/glob.h"
  #else
  #include <glob.h>
  #endif
  #endif
  
  ZEND_DECLARE_MODULE_GLOBALS(ffi)
  
  typedef enum _zend_ffi_tag_kind {
  	ZEND_FFI_TAG_ENUM,
  	ZEND_FFI_TAG_STRUCT,
  	ZEND_FFI_TAG_UNION
  } zend_ffi_tag_kind;
  
  static const char *zend_ffi_tag_kind_name[3] = {"enum", "struct", "union"};
  
  
  typedef struct _zend_ffi_tag {
  	zend_ffi_tag_kind      kind;
  	zend_ffi_type         *type;
  } zend_ffi_tag;
  
  typedef enum _zend_ffi_type_kind {
  	ZEND_FFI_TYPE_VOID,
  	ZEND_FFI_TYPE_FLOAT,
  	ZEND_FFI_TYPE_DOUBLE,
  #ifdef HAVE_LONG_DOUBLE
  	ZEND_FFI_TYPE_LONGDOUBLE,
  #endif
  	ZEND_FFI_TYPE_UINT8,
  	ZEND_FFI_TYPE_SINT8,
  	ZEND_FFI_TYPE_UINT16,
  	ZEND_FFI_TYPE_SINT16,
  	ZEND_FFI_TYPE_UINT32,
  	ZEND_FFI_TYPE_SINT32,
  	ZEND_FFI_TYPE_UINT64,
  	ZEND_FFI_TYPE_SINT64,
  	ZEND_FFI_TYPE_ENUM,
  	ZEND_FFI_TYPE_BOOL,
  	ZEND_FFI_TYPE_CHAR,
  	ZEND_FFI_TYPE_POINTER,
  	ZEND_FFI_TYPE_FUNC,
  	ZEND_FFI_TYPE_ARRAY,
  	ZEND_FFI_TYPE_STRUCT,
  } zend_ffi_type_kind;
  
  typedef enum _zend_ffi_flags {
  	ZEND_FFI_FLAG_CONST      = (1 << 0),
  	ZEND_FFI_FLAG_OWNED      = (1 << 1),
  	ZEND_FFI_FLAG_PERSISTENT = (1 << 2),
  } zend_ffi_flags;
  
  struct _zend_ffi_type {
  	zend_ffi_type_kind     kind;
  	size_t                 size;
  	uint32_t               align;
  	uint32_t               attr;
  	union {
  		struct {
  			zend_string        *tag_name;
  			zend_ffi_type_kind  kind;
  		} enumeration;
  		struct {
  			zend_ffi_type *type;
  			zend_long      length;
  		} array;
  		struct {
  			zend_ffi_type *type;
  		} pointer;
  		struct {
  			zend_string   *tag_name;
  			HashTable      fields;
  		} record;
  		struct {
  			zend_ffi_type *ret_type;
  			HashTable     *args;
  			ffi_abi        abi;
  		} func;
  	};
  };
  
  typedef struct _zend_ffi_field {
  	size_t                 offset;
  	zend_bool              is_const;
  	zend_bool              is_nested; /* part of nested anonymous struct */
  	uint8_t                first_bit;
  	uint8_t                bits;
  	zend_ffi_type         *type;
  } zend_ffi_field;
  
  typedef enum _zend_ffi_symbol_kind {
  	ZEND_FFI_SYM_TYPE,
  	ZEND_FFI_SYM_CONST,
  	ZEND_FFI_SYM_VAR,
  	ZEND_FFI_SYM_FUNC
  } zend_ffi_symbol_kind;
  
  typedef struct _zend_ffi_symbol {
  	zend_ffi_symbol_kind   kind;
  	zend_bool              is_const;
  	zend_ffi_type         *type;
  	union {
  		void *addr;
  		int64_t value;
  	};
  } zend_ffi_symbol;
  
  typedef struct _zend_ffi_scope {
  	HashTable             *symbols;
  	HashTable             *tags;
  } zend_ffi_scope;
  
  typedef struct _zend_ffi {
  	zend_object            std;
  	DL_HANDLE              lib;
  	HashTable             *symbols;
  	HashTable             *tags;
  	zend_bool              persistent;
  } zend_ffi;
  
  #define ZEND_FFI_TYPE_OWNED        (1<<0)
  
  #define ZEND_FFI_TYPE(t) \
  	((zend_ffi_type*)(((uintptr_t)(t)) & ~ZEND_FFI_TYPE_OWNED))
  
  #define ZEND_FFI_TYPE_IS_OWNED(t) \
  	(((uintptr_t)(t)) & ZEND_FFI_TYPE_OWNED)
  
  #define ZEND_FFI_TYPE_MAKE_OWNED(t) \
  	((zend_ffi_type*)(((uintptr_t)(t)) | ZEND_FFI_TYPE_OWNED))
  
  #define ZEND_FFI_SIZEOF_ARG \
  	MAX(FFI_SIZEOF_ARG, sizeof(double))
  
  typedef struct _zend_ffi_cdata {
  	zend_object            std;
  	zend_ffi_type         *type;
  	void                  *ptr;
  	void                  *ptr_holder;
  	zend_ffi_flags         flags;
  } zend_ffi_cdata;
  
  typedef struct _zend_ffi_ctype {
  	zend_object            std;
  	zend_ffi_type         *type;
  } zend_ffi_ctype;
  
  static zend_class_entry *zend_ffi_exception_ce;
  static zend_class_entry *zend_ffi_parser_exception_ce;
  static zend_class_entry *zend_ffi_ce;
  static zend_class_entry *zend_ffi_cdata_ce;
  static zend_class_entry *zend_ffi_ctype_ce;
  
  static zend_object_handlers zend_ffi_handlers;
  static zend_object_handlers zend_ffi_cdata_handlers;
  static zend_object_handlers zend_ffi_cdata_value_handlers;
  static zend_object_handlers zend_ffi_cdata_free_handlers;
  static zend_object_handlers zend_ffi_ctype_handlers;
  
  static zend_internal_function zend_ffi_new_fn;
  static zend_internal_function zend_ffi_cast_fn;
  static zend_internal_function zend_ffi_type_fn;
  
  /* forward declarations */
  static void _zend_ffi_type_dtor(zend_ffi_type *type);
  static void zend_ffi_finalize_type(zend_ffi_dcl *dcl);
  static int zend_ffi_is_same_type(zend_ffi_type *type1, zend_ffi_type *type2);
  static zend_ffi_type *zend_ffi_remember_type(zend_ffi_type *type);
  static char *zend_ffi_parse_directives(const char *filename, char *code_pos, char **scope_name, char **lib, zend_bool preload);
  static ZEND_FUNCTION(ffi_trampoline);
  static ZEND_COLD void zend_ffi_return_unsupported(zend_ffi_type *type);
  static ZEND_COLD void zend_ffi_pass_unsupported(zend_ffi_type *type);
  static ZEND_COLD void zend_ffi_assign_incompatible(zval *arg, zend_ffi_type *type);
  
  #if FFI_CLOSURES
  static void *zend_ffi_create_callback(zend_ffi_type *type, zval *value);
  #endif
  
  static zend_always_inline void zend_ffi_type_dtor(zend_ffi_type *type) /* {{{ */
  {
  	if (UNEXPECTED(ZEND_FFI_TYPE_IS_OWNED(type))) {
  		_zend_ffi_type_dtor(type);
  		return;
  	}
  }
  /* }}} */
  
  static zend_always_inline void zend_ffi_object_init(zend_object *object, zend_class_entry *ce) /* {{{ */
  {
  	GC_SET_REFCOUNT(object, 1);
  	GC_TYPE_INFO(object) = IS_OBJECT | ((GC_COLLECTABLE | IS_OBJ_DESTRUCTOR_CALLED) << GC_FLAGS_SHIFT);
  	object->ce = ce;
  	object->properties = NULL;
  	zend_objects_store_put(object);
  }
  /* }}} */
  
  static zend_object *zend_ffi_cdata_new(zend_class_entry *class_type) /* {{{ */
  {
  	zend_ffi_cdata *cdata;
  
  	cdata = emalloc(sizeof(zend_ffi_cdata));
  
  	zend_ffi_object_init(&cdata->std, class_type);
  	cdata->std.handlers = &zend_ffi_cdata_handlers;
  
  	cdata->type = NULL;
  	cdata->ptr = NULL;
  	cdata->flags = 0;
  
  	return &cdata->std;
  }
  /* }}} */
  
  static int zend_ffi_is_compatible_type(zend_ffi_type *dst_type, zend_ffi_type *src_type) /* {{{ */
  {
  	while (1) {
  		if (dst_type == src_type) {
  			return 1;
  		} else if (dst_type->kind == src_type->kind) {
  			if (dst_type->kind < ZEND_FFI_TYPE_POINTER) {
  				return 1;
  			} else if (dst_type->kind == ZEND_FFI_TYPE_POINTER) {
  				dst_type = ZEND_FFI_TYPE(dst_type->pointer.type);
  				src_type = ZEND_FFI_TYPE(src_type->pointer.type);
  				if (dst_type->kind == ZEND_FFI_TYPE_VOID ||
  				    src_type->kind == ZEND_FFI_TYPE_VOID) {
  				    return 1;
  				}
  			} else if (dst_type->kind == ZEND_FFI_TYPE_ARRAY &&
  			           (dst_type->array.length == src_type->array.length ||
  			            dst_type->array.length == 0)) {
  				dst_type = ZEND_FFI_TYPE(dst_type->array.type);
  				src_type = ZEND_FFI_TYPE(src_type->array.type);
  			} else {
  				break;
  			}
  		} else if (dst_type->kind == ZEND_FFI_TYPE_POINTER &&
  		           src_type->kind == ZEND_FFI_TYPE_ARRAY) {
  			dst_type = ZEND_FFI_TYPE(dst_type->pointer.type);
  			src_type = ZEND_FFI_TYPE(src_type->array.type);
  			if (dst_type->kind == ZEND_FFI_TYPE_VOID) {
  			    return 1;
  			}
  		} else {
  			break;
  		}
  	}
  	return 0;
  }
  /* }}} */
  
  static ffi_type *zend_ffi_make_fake_struct_type(zend_ffi_type *type) /* {{{ */
  {
  	ffi_type *t = emalloc(sizeof(ffi_type) + sizeof(ffi_type*) * (zend_hash_num_elements(&type->record.fields) + 1));
  	int i;
  	zend_ffi_field *field;
  
  	t->size = type->size;
  	t->alignment = type->align;
  	t->type = FFI_TYPE_STRUCT;
  	t->elements = (ffi_type**)(t + 1);
  	i = 0;
  	ZEND_HASH_FOREACH_PTR(&type->record.fields, field) {
  		switch (ZEND_FFI_TYPE(field->type)->kind) {
  			case ZEND_FFI_TYPE_FLOAT:
  				t->elements[i] = &ffi_type_float;
  				break;
  			case ZEND_FFI_TYPE_DOUBLE:
  				t->elements[i] = &ffi_type_double;
  				break;
  #ifndef PHP_WIN32
  			case ZEND_FFI_TYPE_LONGDOUBLE:
  				t->elements[i] = &ffi_type_longdouble;
  				break;
  #endif
  			case ZEND_FFI_TYPE_SINT8:
  			case ZEND_FFI_TYPE_UINT8:
  			case ZEND_FFI_TYPE_BOOL:
  			case ZEND_FFI_TYPE_CHAR:
  				t->elements[i] = &ffi_type_uint8;
  				break;
  			case ZEND_FFI_TYPE_SINT16:
  			case ZEND_FFI_TYPE_UINT16:
  				t->elements[i] = &ffi_type_uint16;
  				break;
  			case ZEND_FFI_TYPE_SINT32:
  			case ZEND_FFI_TYPE_UINT32:
  				t->elements[i] = &ffi_type_uint32;
  				break;
  			case ZEND_FFI_TYPE_SINT64:
  			case ZEND_FFI_TYPE_UINT64:
  				t->elements[i] = &ffi_type_uint64;
  				break;
  			case ZEND_FFI_TYPE_POINTER:
  				t->elements[i] = &ffi_type_pointer;
  				break;
  			default:
  				efree(t);
  				return NULL;
  			}
  		i++;
  	} ZEND_HASH_FOREACH_END();
  	t->elements[i] = NULL;
  	return t;
  }
  /* }}} */
  
  static ffi_type *zend_ffi_get_type(zend_ffi_type *type) /* {{{ */
  {
  	zend_ffi_type_kind kind = type->kind;
  
  again:
      switch (kind) {
  		case ZEND_FFI_TYPE_FLOAT:
  			return &ffi_type_float;
  		case ZEND_FFI_TYPE_DOUBLE:
  			return &ffi_type_double;
  #ifndef PHP_WIN32
  		case ZEND_FFI_TYPE_LONGDOUBLE:
  			return &ffi_type_longdouble;
  #endif
  		case ZEND_FFI_TYPE_UINT8:
  			return &ffi_type_uint8;
  		case ZEND_FFI_TYPE_SINT8:
  			return &ffi_type_sint8;
  		case ZEND_FFI_TYPE_UINT16:
  			return &ffi_type_uint16;
  		case ZEND_FFI_TYPE_SINT16:
  			return &ffi_type_sint16;
  		case ZEND_FFI_TYPE_UINT32:
  			return &ffi_type_uint32;
  		case ZEND_FFI_TYPE_SINT32:
  			return &ffi_type_sint32;
  		case ZEND_FFI_TYPE_UINT64:
  			return &ffi_type_uint64;
  		case ZEND_FFI_TYPE_SINT64:
  			return &ffi_type_sint64;
  		case ZEND_FFI_TYPE_POINTER:
  			return &ffi_type_pointer;
  		case ZEND_FFI_TYPE_VOID:
  			return &ffi_type_void;
  		case ZEND_FFI_TYPE_BOOL:
  			return &ffi_type_uint8;
  		case ZEND_FFI_TYPE_CHAR:
  			return &ffi_type_sint8;
  		case ZEND_FFI_TYPE_ENUM:
  			kind = type->enumeration.kind;
  			goto again;
  		case ZEND_FFI_TYPE_STRUCT:
  			if (!(type->attr & ZEND_FFI_ATTR_UNION)) {
  				ffi_type *t = zend_ffi_make_fake_struct_type(type);
  				return t;
  			}
  			break;
  		default:
  			break;
  	}
  	return NULL;
  }
  /* }}} */
  
  static zend_never_inline zend_ffi_cdata *zend_ffi_cdata_to_zval_slow(void *ptr, zend_ffi_type *type, zend_ffi_flags flags) /* {{{ */
  {
  	zend_ffi_cdata *cdata = emalloc(sizeof(zend_ffi_cdata));
  
  	zend_ffi_object_init(&cdata->std, zend_ffi_cdata_ce);
  	cdata->std.handlers =
  		(type->kind < ZEND_FFI_TYPE_POINTER) ?
  		&zend_ffi_cdata_value_handlers :
  		&zend_ffi_cdata_handlers;
  	cdata->type = type;
  	cdata->flags = flags;
  	cdata->ptr = ptr;
  	return cdata;
  }
  /* }}} */
  
  static zend_never_inline zend_ffi_cdata *zend_ffi_cdata_to_zval_slow_ptr(void *ptr, zend_ffi_type *type, zend_ffi_flags flags) /* {{{ */
  {
  	zend_ffi_cdata *cdata = emalloc(sizeof(zend_ffi_cdata));
  
  	zend_ffi_object_init(&cdata->std, zend_ffi_cdata_ce);
  	cdata->std.handlers = &zend_ffi_cdata_handlers;
  	cdata->type = type;
  	cdata->flags = flags;
  	cdata->ptr = (void*)&cdata->ptr_holder;
  	*(void**)cdata->ptr = *(void**)ptr;
  	return cdata;
  }
  /* }}} */
  
  static zend_never_inline zend_ffi_cdata *zend_ffi_cdata_to_zval_slow_ret(void *ptr, zend_ffi_type *type, zend_ffi_flags flags) /* {{{ */
  {
  	zend_ffi_cdata *cdata = emalloc(sizeof(zend_ffi_cdata));
  
  	zend_ffi_object_init(&cdata->std, zend_ffi_cdata_ce);
  	cdata->std.handlers =
  		(type->kind < ZEND_FFI_TYPE_POINTER) ?
  		&zend_ffi_cdata_value_handlers :
  		&zend_ffi_cdata_handlers;
  	cdata->type = type;
  	cdata->flags = flags;
  	if (type->kind == ZEND_FFI_TYPE_POINTER) {
  		cdata->ptr = (void*)&cdata->ptr_holder;
  		*(void**)cdata->ptr = *(void**)ptr;
  	} else if (type->kind == ZEND_FFI_TYPE_STRUCT) {
  		cdata->ptr = emalloc(type->size);
  		cdata->flags |= ZEND_FFI_FLAG_OWNED;
  		memcpy(cdata->ptr, ptr, type->size);
  	} else {
  		cdata->ptr = ptr;
  	}
  	return cdata;
  }
  /* }}} */
  
  static zend_always_inline void zend_ffi_cdata_to_zval(zend_ffi_cdata *cdata, void *ptr, zend_ffi_type *type, int read_type, zval *rv, zend_ffi_flags flags, zend_bool is_ret) /* {{{ */
  {
  	if (read_type == BP_VAR_R) {
  		zend_ffi_type_kind kind = type->kind;
  
  again:
  	    switch (kind) {
  			case ZEND_FFI_TYPE_FLOAT:
  				ZVAL_DOUBLE(rv, *(float*)ptr);
  				return;
  			case ZEND_FFI_TYPE_DOUBLE:
  				ZVAL_DOUBLE(rv, *(double*)ptr);
  				return;
  #ifdef HAVE_LONG_DOUBLE
  			case ZEND_FFI_TYPE_LONGDOUBLE:
  				ZVAL_DOUBLE(rv, *(long double*)ptr);
  				return;
  #endif
  			case ZEND_FFI_TYPE_UINT8:
  				ZVAL_LONG(rv, *(uint8_t*)ptr);
  				return;
  			case ZEND_FFI_TYPE_SINT8:
  				ZVAL_LONG(rv, *(int8_t*)ptr);
  				return;
  			case ZEND_FFI_TYPE_UINT16:
  				ZVAL_LONG(rv, *(uint16_t*)ptr);
  				return;
  			case ZEND_FFI_TYPE_SINT16:
  				ZVAL_LONG(rv, *(int16_t*)ptr);
  				return;
  			case ZEND_FFI_TYPE_UINT32:
  				ZVAL_LONG(rv, *(uint32_t*)ptr);
  				return;
  			case ZEND_FFI_TYPE_SINT32:
  				ZVAL_LONG(rv, *(int32_t*)ptr);
  				return;
  			case ZEND_FFI_TYPE_UINT64:
  				ZVAL_LONG(rv, *(uint64_t*)ptr);
  				return;
  			case ZEND_FFI_TYPE_SINT64:
  				ZVAL_LONG(rv, *(int64_t*)ptr);
  				return;
  			case ZEND_FFI_TYPE_BOOL:
  				ZVAL_BOOL(rv, *(uint8_t*)ptr);
  				return;
  			case ZEND_FFI_TYPE_CHAR:
  				ZVAL_INTERNED_STR(rv, ZSTR_CHAR(*(unsigned char*)ptr));
  				return;
  			case ZEND_FFI_TYPE_ENUM:
  				kind = type->enumeration.kind;
  				goto again;
  			case ZEND_FFI_TYPE_POINTER:
  				if (*(void**)ptr == NULL) {
  					ZVAL_NULL(rv);
  					return;
  				} else if ((type->attr & ZEND_FFI_ATTR_CONST) && ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) {
  					ZVAL_STRING(rv, *(char**)ptr);
  					return;
  				}
  				if (!cdata) {
  					if (is_ret) {
  						cdata = zend_ffi_cdata_to_zval_slow_ret(ptr, type, flags);
  					} else {
  						cdata = zend_ffi_cdata_to_zval_slow_ptr(ptr, type, flags);
  					}
  				} else {
  					GC_ADDREF(&cdata->std);
  				}
  				ZVAL_OBJ(rv, &cdata->std);
  				return;
  			default:
  				break;
  		}
  	}
  
  	if (!cdata) {
  		if (is_ret) {
  			cdata = zend_ffi_cdata_to_zval_slow_ret(ptr, type, flags);
  		} else {
  			cdata = zend_ffi_cdata_to_zval_slow(ptr, type, flags);
  		}
  	} else {
  		GC_ADDREF(&cdata->std);
  	}
  	ZVAL_OBJ(rv, &cdata->std);
  }
  /* }}} */
  
  static uint64_t zend_ffi_bit_field_read(void *ptr, zend_ffi_field *field) /* {{{ */
  {
  	size_t bit = field->first_bit;
  	size_t last_bit = bit + field->bits - 1;
  	uint8_t *p = (uint8_t *) ptr + bit / 8;
  	uint8_t *last_p = (uint8_t *) ptr + last_bit / 8;
  	size_t pos = bit % 8;
  	size_t insert_pos = 0;
  	uint8_t mask;
  	uint64_t val = 0;
  
  	/* Bitfield fits into a single byte */
  	if (p == last_p) {
  		mask = (1U << field->bits) - 1U;
  		return (*p >> pos) & mask;
  	}
  
  	/* Read partial prefix byte */
  	if (pos != 0) {
  		size_t num_bits = 8 - pos;
  		mask = ((1U << num_bits) - 1U) << pos;
  		val = (*p++ >> pos) & mask;
  		insert_pos += num_bits;
  	}
  
  	/* Read full bytes */
  	while (p < last_p) {
  		val |= *p++ << insert_pos;
  		insert_pos += 8;
  	}
  
  	/* Read partial suffix byte */
  	if (p == last_p) {
  		size_t num_bits = last_bit % 8 + 1;
  		mask = (1U << num_bits) - 1U;
  		val |= (*p & mask) << insert_pos;
  	}
  
  	return val;
  }
  
  static void zend_ffi_bit_field_to_zval(void *ptr, zend_ffi_field *field, zval *rv) /* {{{ */
  {
  	uint64_t val = zend_ffi_bit_field_read(ptr, field);
  	if (ZEND_FFI_TYPE(field->type)->kind == ZEND_FFI_TYPE_CHAR
  	 || ZEND_FFI_TYPE(field->type)->kind == ZEND_FFI_TYPE_SINT8
  	 || ZEND_FFI_TYPE(field->type)->kind == ZEND_FFI_TYPE_SINT16
  	 || ZEND_FFI_TYPE(field->type)->kind == ZEND_FFI_TYPE_SINT32
  	 || ZEND_FFI_TYPE(field->type)->kind == ZEND_FFI_TYPE_SINT64) {
  		/* Sign extend */
  		uint64_t shift = 64 - (field->bits % 64);
  		if (shift != 0) {
  			val = (int64_t)(val << shift) >> shift;
  		}
  	}
  	ZVAL_LONG(rv, val);
  }
  /* }}} */
  
  static int zend_ffi_zval_to_bit_field(void *ptr, zend_ffi_field *field, zval *value) /* {{{ */
  {
  	uint64_t val = zval_get_long(value);
  	size_t bit = field->first_bit;
  	size_t last_bit = bit + field->bits - 1;
  	uint8_t *p = (uint8_t *) ptr + bit / 8;
  	uint8_t *last_p = (uint8_t *) ptr + last_bit / 8;
  	size_t pos = bit % 8;
  	uint8_t mask;
  
  	/* Bitfield fits into a single byte */
  	if (p == last_p) {
  		mask = ((1U << field->bits) - 1U) << pos;
  		*p = (*p & ~mask) | ((val << pos) & mask);
  		return SUCCESS;
  	}
  
  	/* Write partial prefix byte */
  	if (pos != 0) {
  		size_t num_bits = 8 - pos;
  		mask = ((1U << num_bits) - 1U) << pos;
  		*p = (*p & ~mask) | ((val << pos) & mask);
  		p++;
  		val >>= num_bits;
  	}
  
  	/* Write full bytes */
  	while (p < last_p) {
  		*p++ = val;
  		val >>= 8;
  	}
  
  	/* Write partial suffix byte */
  	if (p == last_p) {
  		size_t num_bits = last_bit % 8 + 1;
  		mask = (1U << num_bits) - 1U;
  		*p = (*p & ~mask) | (val & mask);
  	}
  
  	return SUCCESS;
  }
  /* }}} */
  
  static zend_always_inline int zend_ffi_zval_to_cdata(void *ptr, zend_ffi_type *type, zval *value) /* {{{ */
  {
  	zend_long lval;
  	double dval;
  	zend_string *tmp_str;
  	zend_string *str;
  	zend_ffi_type_kind kind = type->kind;
  
  again:
      switch (kind) {
  		case ZEND_FFI_TYPE_FLOAT:
  			dval = zval_get_double(value);
  			*(float*)ptr = dval;
  			break;
  		case ZEND_FFI_TYPE_DOUBLE:
  			dval = zval_get_double(value);
  			*(double*)ptr = dval;
  			break;
  #ifdef HAVE_LONG_DOUBLE
  		case ZEND_FFI_TYPE_LONGDOUBLE:
  			dval = zval_get_double(value);
  			*(long double*)ptr = dval;
  			break;
  #endif
  		case ZEND_FFI_TYPE_UINT8:
  			lval = zval_get_long(value);
  			*(uint8_t*)ptr = lval;
  			break;
  		case ZEND_FFI_TYPE_SINT8:
  			lval = zval_get_long(value);
  			*(int8_t*)ptr = lval;
  			break;
  		case ZEND_FFI_TYPE_UINT16:
  			lval = zval_get_long(value);
  			*(uint16_t*)ptr = lval;
  			break;
  		case ZEND_FFI_TYPE_SINT16:
  			lval = zval_get_long(value);
  			*(int16_t*)ptr = lval;
  			break;
  		case ZEND_FFI_TYPE_UINT32:
  			lval = zval_get_long(value);
  			*(uint32_t*)ptr = lval;
  			break;
  		case ZEND_FFI_TYPE_SINT32:
  			lval = zval_get_long(value);
  			*(int32_t*)ptr = lval;
  			break;
  		case ZEND_FFI_TYPE_UINT64:
  			lval = zval_get_long(value);
  			*(uint64_t*)ptr = lval;
  			break;
  		case ZEND_FFI_TYPE_SINT64:
  			lval = zval_get_long(value);
  			*(int64_t*)ptr = lval;
  			break;
  		case ZEND_FFI_TYPE_BOOL:
  			*(uint8_t*)ptr = zend_is_true(value);
  			break;
  		case ZEND_FFI_TYPE_CHAR:
  			str = zval_get_tmp_string(value, &tmp_str);
  			if (ZSTR_LEN(str) == 1) {
  				*(char*)ptr = ZSTR_VAL(str)[0];
  			} else {
  				zend_ffi_assign_incompatible(value, type);
  				return FAILURE;
  			}
  			zend_tmp_string_release(tmp_str);
  			break;
  		case ZEND_FFI_TYPE_ENUM:
  			kind = type->enumeration.kind;
  			goto again;
  		case ZEND_FFI_TYPE_POINTER:
  			if (Z_TYPE_P(value) == IS_NULL) {
  				*(void**)ptr = NULL;
  				break;
  			} else if (Z_TYPE_P(value) == IS_OBJECT && Z_OBJCE_P(value) == zend_ffi_cdata_ce) {
  				zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(value);
  
  				if (zend_ffi_is_compatible_type(type, ZEND_FFI_TYPE(cdata->type))) {
  					if (ZEND_FFI_TYPE(cdata->type)->kind == ZEND_FFI_TYPE_POINTER) {
  						*(void**)ptr = *(void**)cdata->ptr;
  					} else {
  						if (cdata->flags & ZEND_FFI_FLAG_OWNED) {
  							zend_throw_error(zend_ffi_exception_ce, "Attempt to perform assign of owned C pointer");
  							return FAILURE;
  						}
  						*(void**)ptr = cdata->ptr;
  					}
  					return SUCCESS;
  				/* Allow transparent assignment of not-owned CData to compatible pointers */
  				} else if (ZEND_FFI_TYPE(cdata->type)->kind != ZEND_FFI_TYPE_POINTER
  				 && zend_ffi_is_compatible_type(ZEND_FFI_TYPE(type->pointer.type), ZEND_FFI_TYPE(cdata->type))) {
  					if (cdata->flags & ZEND_FFI_FLAG_OWNED) {
  						zend_throw_error(zend_ffi_exception_ce, "Attempt to perform assign pointer to owned C data");
  						return FAILURE;
  					}
  					*(void**)ptr = cdata->ptr;
  					return SUCCESS;
  				}
  #if FFI_CLOSURES
  			} else if (ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_FUNC) {
  				void *callback = zend_ffi_create_callback(ZEND_FFI_TYPE(type->pointer.type), value);
  
  				if (callback) {
  					*(void**)ptr = callback;
  					break;
  				} else {
  					return FAILURE;
  				}
  #endif
  			}
  			zend_ffi_assign_incompatible(value, type);
  			return FAILURE;
  		case ZEND_FFI_TYPE_STRUCT:
  		case ZEND_FFI_TYPE_ARRAY:
  		default:
  			if (Z_TYPE_P(value) == IS_OBJECT && Z_OBJCE_P(value) == zend_ffi_cdata_ce) {
  				zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(value);
  				if (zend_ffi_is_compatible_type(type, ZEND_FFI_TYPE(cdata->type)) &&
  				    type->size == ZEND_FFI_TYPE(cdata->type)->size) {
  					memcpy(ptr, cdata->ptr, type->size);
  					return SUCCESS;
  				}
  			}
  			zend_ffi_assign_incompatible(value, type);
  			return FAILURE;
  	}
  	return SUCCESS;
  }
  /* }}} */
  
  #if defined(ZEND_WIN32) && (defined(HAVE_FFI_FASTCALL) || defined(HAVE_FFI_STDCALL) || defined(HAVE_FFI_VECTORCALL_PARTIAL))
  static size_t zend_ffi_arg_size(zend_ffi_type *type) /* {{{ */
  {
  	zend_ffi_type *arg_type;
  	size_t arg_size = 0;
  
  	ZEND_HASH_FOREACH_PTR(type->func.args, arg_type) {
  		arg_size += MAX(ZEND_FFI_TYPE(arg_type)->size, sizeof(size_t));
  	} ZEND_HASH_FOREACH_END();
  	return arg_size;
  }
  /* }}} */
  #endif
  
  static zend_always_inline zend_string *zend_ffi_mangled_func_name(zend_string *name, zend_ffi_type *type) /* {{{ */
  {
  #ifdef ZEND_WIN32
  	switch (type->func.abi) {
  # ifdef HAVE_FFI_FASTCALL
  		case FFI_FASTCALL:
  			return strpprintf(0, "@%s@%zu", ZSTR_VAL(name), zend_ffi_arg_size(type));
  # endif
  # ifdef HAVE_FFI_STDCALL
  		case FFI_STDCALL:
  			return strpprintf(0, "_%s@%zu", ZSTR_VAL(name), zend_ffi_arg_size(type));
  # endif
  # ifdef HAVE_FFI_VECTORCALL_PARTIAL
  		case FFI_VECTORCALL_PARTIAL:
  			return strpprintf(0, "%s@@%zu", ZSTR_VAL(name), zend_ffi_arg_size(type));
  # endif
  	}
  #endif
  	return zend_string_copy(name);
  }
  /* }}} */
  
  #if FFI_CLOSURES
  typedef struct _zend_ffi_callback_data {
  	zend_fcall_info_cache  fcc;
  	zend_ffi_type         *type;
  	void                  *code;
  	void                  *callback;
  	ffi_cif                cif;
  	uint32_t               arg_count;
  	ffi_type              *ret_type;
  	ffi_type              *arg_types[0];
  } zend_ffi_callback_data;
  
  static void zend_ffi_callback_hash_dtor(zval *zv) /* {{{ */
  {
  	zend_ffi_callback_data *callback_data = Z_PTR_P(zv);
  
  	ffi_closure_free(callback_data->callback);
  	if (callback_data->fcc.function_handler->common.fn_flags & ZEND_ACC_CLOSURE) {
  		OBJ_RELEASE(ZEND_CLOSURE_OBJECT(callback_data->fcc.function_handler));
  	}
  	efree(callback_data);
  }
  /* }}} */
  
  static void zend_ffi_callback_trampoline(ffi_cif* cif, void* ret, void** args, void* data) /* {{{ */
  {
  	zend_ffi_callback_data *callback_data = (zend_ffi_callback_data*)data;
  	zend_fcall_info fci;
  	zend_ffi_type *ret_type;
  	zval retval;
  	ALLOCA_FLAG(use_heap)
  
  	fci.size = sizeof(zend_fcall_info);
  	ZVAL_UNDEF(&fci.function_name);
  	fci.retval = &retval;
  	fci.params = do_alloca(sizeof(zval) *callback_data->arg_count, use_heap);
  	fci.object = NULL;
  	fci.no_separation = 1;
  	fci.param_count = callback_data->arg_count;
  
  	if (callback_data->type->func.args) {
  		int n = 0;
  		zend_ffi_type *arg_type;
  
  		ZEND_HASH_FOREACH_PTR(callback_data->type->func.args, arg_type) {
  			arg_type = ZEND_FFI_TYPE(arg_type);
  			zend_ffi_cdata_to_zval(NULL, args[n], arg_type, BP_VAR_R, &fci.params[n], (zend_ffi_flags)(arg_type->attr & ZEND_FFI_ATTR_CONST), 0);
  			n++;
  		} ZEND_HASH_FOREACH_END();
  	}
  
  	ZVAL_UNDEF(&retval);
  	if (zend_call_function(&fci, &callback_data->fcc) != SUCCESS) {
  		zend_throw_error(zend_ffi_exception_ce, "Cannot call callback");
  	}
  
  	if (callback_data->arg_count) {
  		int n = 0;
  
  		for (n = 0; n < callback_data->arg_count; n++) {
  			zval_ptr_dtor(&fci.params[n]);
  		}
  	}
  	free_alloca(fci.params, use_heap);
  
  	ret_type = ZEND_FFI_TYPE(callback_data->type->func.ret_type);
  	if (ret_type->kind != ZEND_FFI_TYPE_VOID) {
  		zend_ffi_zval_to_cdata(ret, ret_type, &retval);
  	}
  }
  /* }}} */
  
  static void *zend_ffi_create_callback(zend_ffi_type *type, zval *value) /* {{{ */
  {
  	zend_fcall_info_cache fcc;
  	char *error = NULL;
  	uint32_t arg_count;
  	void *code;
  	void *callback;
  	zend_ffi_callback_data *callback_data;
  
  	if (type->attr & ZEND_FFI_ATTR_VARIADIC) {
  		zend_throw_error(zend_ffi_exception_ce, "Variadic function closures are not supported");
  		return NULL;
  	}
  
  	if (!zend_is_callable_ex(value, NULL, 0, NULL, &fcc, &error)) {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to assign an invalid callback, %s", error);
  		return NULL;
  	}
  
  	arg_count = type->func.args ? zend_hash_num_elements(type->func.args) : 0;
  	if (arg_count < fcc.function_handler->common.required_num_args) {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to assign an invalid callback, insufficient number of arguments");
  		return NULL;
  	}
  
  	callback = ffi_closure_alloc(sizeof(ffi_closure), &code);
  	if (!callback) {
  		zend_throw_error(zend_ffi_exception_ce, "Cannot allocate callback");
  		return NULL;
  	}
  
  	callback_data = emalloc(sizeof(zend_ffi_callback_data) + sizeof(ffi_type*) * arg_count);
  	memcpy(&callback_data->fcc, &fcc, sizeof(zend_fcall_info_cache));
  	callback_data->type = type;
  	callback_data->callback = callback;
  	callback_data->code = code;
  	callback_data->arg_count = arg_count;
  
  	if (type->func.args) {
  		int n = 0;
  		zend_ffi_type *arg_type;
  
  		ZEND_HASH_FOREACH_PTR(type->func.args, arg_type) {
  			arg_type = ZEND_FFI_TYPE(arg_type);
  			callback_data->arg_types[n] = zend_ffi_get_type(arg_type);
  			if (!callback_data->arg_types[n]) {
  				zend_ffi_pass_unsupported(arg_type);
  				efree(callback_data);
  				ffi_closure_free(callback);
  				return NULL;
  			}
  			n++;
  		} ZEND_HASH_FOREACH_END();
  	}
  	callback_data->ret_type = zend_ffi_get_type(ZEND_FFI_TYPE(type->func.ret_type));
  	if (!callback_data->ret_type) {
  		zend_ffi_return_unsupported(type->func.ret_type);
  		efree(callback_data);
  		ffi_closure_free(callback);
  		return NULL;
  	}
  
  	if (ffi_prep_cif(&callback_data->cif, type->func.abi, callback_data->arg_count, callback_data->ret_type, callback_data->arg_types) != FFI_OK) {
  		zend_throw_error(zend_ffi_exception_ce, "Cannot prepare callback CIF");
  		efree(callback_data);
  		ffi_closure_free(callback);
  		return NULL;
  	}
  
  	if (ffi_prep_closure_loc(callback, &callback_data->cif, zend_ffi_callback_trampoline, callback_data, code) != FFI_OK) {
  		zend_throw_error(zend_ffi_exception_ce, "Cannot prepare callback");
  		efree(callback_data);
  		ffi_closure_free(callback);
  		return NULL;
  	}
  
  	if (!FFI_G(callbacks)) {
  		FFI_G(callbacks) = emalloc(sizeof(HashTable));
  		zend_hash_init(FFI_G(callbacks), 0, NULL, zend_ffi_callback_hash_dtor, 0);
  	}
  	zend_hash_next_index_insert_ptr(FFI_G(callbacks), callback_data);
  
  	if (fcc.function_handler->common.fn_flags & ZEND_ACC_CLOSURE) {
  		GC_ADDREF(ZEND_CLOSURE_OBJECT(fcc.function_handler));
  	}
  
  	return code;
  }
  /* }}} */
  #endif
  
  static zval *zend_ffi_cdata_get(zend_object *obj, zend_string *member, int read_type, void **cache_slot, zval *rv) /* {{{ */
  {
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)obj;
  	zend_ffi_type  *type = ZEND_FFI_TYPE(cdata->type);
  
  #if 0
  	if (UNEXPECTED(!cdata->ptr)) {
  		zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  		return &EG(uninitialized_zval);
  	}
  #endif
  
  	if (UNEXPECTED(!zend_string_equals_literal(member, "cdata"))) {
  		zend_throw_error(zend_ffi_exception_ce, "only 'cdata' property may be read");
  		return &EG(uninitialized_zval);;
  	}
  
  	zend_ffi_cdata_to_zval(cdata, cdata->ptr, type, BP_VAR_R, rv, 0, 0);
  	return rv;
  }
  /* }}} */
  
  static zval *zend_ffi_cdata_set(zend_object *obj, zend_string *member, zval *value, void **cache_slot) /* {{{ */
  {
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)obj;
  	zend_ffi_type  *type = ZEND_FFI_TYPE(cdata->type);
  
  #if 0
  	if (UNEXPECTED(!cdata->ptr)) {
  		zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  		return &EG(uninitialized_zval);;
  	}
  #endif
  
  	if (UNEXPECTED(!zend_string_equals_literal(member, "cdata"))) {
  		zend_throw_error(zend_ffi_exception_ce, "only 'cdata' property may be set");
  		return &EG(uninitialized_zval);;
  	}
  
  	zend_ffi_zval_to_cdata(cdata->ptr, type, value);
  
  	return value;
  }
  /* }}} */
  
  static int zend_ffi_cdata_cast_object(zend_object *readobj, zval *writeobj, int type) /* {{{ */
  {
  	if (type == IS_STRING) {
  		zend_ffi_cdata *cdata = (zend_ffi_cdata*)readobj;
  		zend_ffi_type  *ctype = ZEND_FFI_TYPE(cdata->type);
  		void           *ptr = cdata->ptr;
  		zend_ffi_type_kind kind = ctype->kind;
  
  again:
  	    switch (kind) {
  			case ZEND_FFI_TYPE_FLOAT:
  				ZVAL_DOUBLE(writeobj, *(float*)ptr);
  				break;
  			case ZEND_FFI_TYPE_DOUBLE:
  				ZVAL_DOUBLE(writeobj, *(double*)ptr);
  				break;
  #ifdef HAVE_LONG_DOUBLE
  			case ZEND_FFI_TYPE_LONGDOUBLE:
  				ZVAL_DOUBLE(writeobj, *(long double*)ptr);
  				break;
  #endif
  			case ZEND_FFI_TYPE_UINT8:
  				ZVAL_LONG(writeobj, *(uint8_t*)ptr);
  				break;
  			case ZEND_FFI_TYPE_SINT8:
  				ZVAL_LONG(writeobj, *(int8_t*)ptr);
  				break;
  			case ZEND_FFI_TYPE_UINT16:
  				ZVAL_LONG(writeobj, *(uint16_t*)ptr);
  				break;
  			case ZEND_FFI_TYPE_SINT16:
  				ZVAL_LONG(writeobj, *(int16_t*)ptr);
  				break;
  			case ZEND_FFI_TYPE_UINT32:
  				ZVAL_LONG(writeobj, *(uint32_t*)ptr);
  				break;
  			case ZEND_FFI_TYPE_SINT32:
  				ZVAL_LONG(writeobj, *(int32_t*)ptr);
  				break;
  			case ZEND_FFI_TYPE_UINT64:
  				ZVAL_LONG(writeobj, *(uint64_t*)ptr);
  				break;
  			case ZEND_FFI_TYPE_SINT64:
  				ZVAL_LONG(writeobj, *(int64_t*)ptr);
  				break;
  			case ZEND_FFI_TYPE_BOOL:
  				ZVAL_BOOL(writeobj, *(uint8_t*)ptr);
  				break;
  			case ZEND_FFI_TYPE_CHAR:
  				ZVAL_INTERNED_STR(writeobj, ZSTR_CHAR(*(unsigned char*)ptr));
  				return SUCCESS;
  			case ZEND_FFI_TYPE_ENUM:
  				kind = ctype->enumeration.kind;
  				goto again;
  			case ZEND_FFI_TYPE_POINTER:
  				if (*(void**)ptr == NULL) {
  					ZVAL_NULL(writeobj);
  					break;
  				} else if ((ctype->attr & ZEND_FFI_ATTR_CONST) && ZEND_FFI_TYPE(ctype->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) {
  					ZVAL_STRING(writeobj, *(char**)ptr);
  					return SUCCESS;
  				}
  				return FAILURE;
  			default:
  				return FAILURE;
  		}
  		convert_to_string(writeobj);
  		return SUCCESS;
  	}
  
  	return FAILURE;
  }
  /* }}} */
  
  static zval *zend_ffi_cdata_read_field(zend_object *obj, zend_string *field_name, int read_type, void **cache_slot, zval *rv) /* {{{ */
  {
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)obj;
  	zend_ffi_type  *type = ZEND_FFI_TYPE(cdata->type);
  	void           *ptr = cdata->ptr;
  	zend_ffi_field *field;
  
  	if (cache_slot && *cache_slot == type) {
  		field = *(cache_slot + 1);
  	} else {
  		if (UNEXPECTED(type->kind != ZEND_FFI_TYPE_STRUCT)) {
  			if (type->kind == ZEND_FFI_TYPE_POINTER) {
  				/* transparently dereference the pointer */
  				if (UNEXPECTED(!ptr)) {
  					zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  					return &EG(uninitialized_zval);
  				}
  				ptr = (void*)(*(char**)ptr);
  				if (UNEXPECTED(!ptr)) {
  					zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  					return &EG(uninitialized_zval);
  				}
  				type = ZEND_FFI_TYPE(type->pointer.type);
  			}
  			if (UNEXPECTED(type->kind != ZEND_FFI_TYPE_STRUCT)) {
  				zend_throw_error(zend_ffi_exception_ce, "Attempt to read field '%s' of non C struct/union", ZSTR_VAL(field_name));
  				return &EG(uninitialized_zval);
  			}
  		}
  
  		field = zend_hash_find_ptr(&type->record.fields, field_name);
  		if (UNEXPECTED(!field)) {
  			zend_throw_error(zend_ffi_exception_ce, "Attempt to read undefined field '%s' of C struct/union", ZSTR_VAL(field_name));
  			return &EG(uninitialized_zval);
  		}
  
  		if (cache_slot) {
  			*cache_slot = type;
  			*(cache_slot + 1) = field;
  		}
  	}
  
  #if 0
  	if (UNEXPECTED(!ptr)) {
  		zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  		return &EG(uninitialized_zval);
  	}
  #endif
  
  	if (EXPECTED(!field->bits)) {
  		zend_ffi_type *field_type = field->type;
  
  		if (ZEND_FFI_TYPE_IS_OWNED(field_type)) {
  			field_type = ZEND_FFI_TYPE(field_type);
  			if (!(field_type->attr & ZEND_FFI_ATTR_STORED)
  			 && field_type->kind == ZEND_FFI_TYPE_POINTER) {
  				field->type = field_type = zend_ffi_remember_type(field_type);
  			}
  		}
  		ptr = (void*)(((char*)ptr) + field->offset);
  		zend_ffi_cdata_to_zval(NULL, ptr, field_type, read_type, rv, (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)field->is_const, 0);
  	} else {
  		zend_ffi_bit_field_to_zval(ptr, field, rv);
  	}
  
  	return rv;
  }
  /* }}} */
  
  static zval *zend_ffi_cdata_write_field(zend_object *obj, zend_string *field_name, zval *value, void **cache_slot) /* {{{ */
  {
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)obj;
  	zend_ffi_type  *type = ZEND_FFI_TYPE(cdata->type);
  	void           *ptr = cdata->ptr;
  	zend_ffi_field *field;
  
  	if (cache_slot && *cache_slot == type) {
  		field = *(cache_slot + 1);
  	} else {
  		if (UNEXPECTED(type->kind != ZEND_FFI_TYPE_STRUCT)) {
  			if (type->kind == ZEND_FFI_TYPE_POINTER) {
  				/* transparently dereference the pointer */
  				if (UNEXPECTED(!ptr)) {
  					zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  					return value;
  				}
  				ptr = (void*)(*(char**)ptr);
  				if (UNEXPECTED(!ptr)) {
  					zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  					return value;
  				}
  				type = ZEND_FFI_TYPE(type->pointer.type);
  			}
  			if (UNEXPECTED(type->kind != ZEND_FFI_TYPE_STRUCT)) {
  				zend_throw_error(zend_ffi_exception_ce, "Attempt to assign field '%s' of non C struct/union", ZSTR_VAL(field_name));
  				return value;
  			}
  		}
  
  		field = zend_hash_find_ptr(&type->record.fields, field_name);
  		if (UNEXPECTED(!field)) {
  			zend_throw_error(zend_ffi_exception_ce, "Attempt to assign undefined field '%s' of C struct/union", ZSTR_VAL(field_name));
  			return value;
  		}
  
  		if (cache_slot) {
  			*cache_slot = type;
  			*(cache_slot + 1) = field;
  		}
  	}
  
  #if 0
  	if (UNEXPECTED(!ptr)) {
  		zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  		return value;
  	}
  #endif
  
  	if (UNEXPECTED(cdata->flags & ZEND_FFI_FLAG_CONST)) {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to assign read-only location");
  		return value;
  	} else if (UNEXPECTED(field->is_const)) {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to assign read-only field '%s'", ZSTR_VAL(field_name));
  		return value;
  	}
  
  	if (EXPECTED(!field->bits)) {
  		ptr = (void*)(((char*)ptr) + field->offset);
  		zend_ffi_zval_to_cdata(ptr, ZEND_FFI_TYPE(field->type), value);
  	} else {
  		zend_ffi_zval_to_bit_field(ptr, field, value);
  	}
  	return value;
  }
  /* }}} */
  
  static zval *zend_ffi_cdata_read_dim(zend_object *obj, zval *offset, int read_type, zval *rv) /* {{{ */
  {
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)obj;
  	zend_ffi_type  *type = ZEND_FFI_TYPE(cdata->type);
  	zend_long       dim = zval_get_long(offset);
  	zend_ffi_type  *dim_type;
  	void           *ptr;
  	zend_ffi_flags  is_const;
  
  	if (EXPECTED(type->kind == ZEND_FFI_TYPE_ARRAY)) {
  		if (UNEXPECTED((zend_ulong)(dim) >= (zend_ulong)type->array.length)
  		 && (UNEXPECTED(dim < 0) || UNEXPECTED(type->array.length != 0))) {
  			zend_throw_error(zend_ffi_exception_ce, "C array index out of bounds");
  			return &EG(uninitialized_zval);
  		}
  
  		is_const = (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)(type->attr & ZEND_FFI_ATTR_CONST);
  
  		dim_type = type->array.type;
  		if (ZEND_FFI_TYPE_IS_OWNED(dim_type)) {
  			dim_type = ZEND_FFI_TYPE(dim_type);
  			if (!(dim_type->attr & ZEND_FFI_ATTR_STORED)
  			 && dim_type->kind == ZEND_FFI_TYPE_POINTER) {
  				type->array.type = dim_type = zend_ffi_remember_type(dim_type);
  			}
  		}
  #if 0
  		if (UNEXPECTED(!cdata->ptr)) {
  			zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  			return &EG(uninitialized_zval);
  		}
  #endif
  		ptr = (void*)(((char*)cdata->ptr) + dim_type->size * dim);
  	} else if (EXPECTED(type->kind == ZEND_FFI_TYPE_POINTER)) {
  		is_const = (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)(type->attr & ZEND_FFI_ATTR_CONST);
  		dim_type = type->pointer.type;
  		if (ZEND_FFI_TYPE_IS_OWNED(dim_type)) {
  			dim_type = ZEND_FFI_TYPE(dim_type);
  			if (!(dim_type->attr & ZEND_FFI_ATTR_STORED)
  			 && dim_type->kind == ZEND_FFI_TYPE_POINTER) {
  				type->pointer.type = dim_type = zend_ffi_remember_type(dim_type);
  			}
  		}
  		if (UNEXPECTED(!cdata->ptr)) {
  			zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  			return &EG(uninitialized_zval);
  		}
  		ptr = (void*)((*(char**)cdata->ptr) + dim_type->size * dim);
  	} else {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to read element of non C array");
  		return &EG(uninitialized_zval);
  	}
  
  	zend_ffi_cdata_to_zval(NULL, ptr, dim_type, read_type, rv, is_const, 0);
  	return rv;
  }
  /* }}} */
  
  static void zend_ffi_cdata_write_dim(zend_object *obj, zval *offset, zval *value) /* {{{ */
  {
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)obj;
  	zend_ffi_type  *type = ZEND_FFI_TYPE(cdata->type);
  	zend_long       dim;
  	void           *ptr;
  	zend_ffi_flags  is_const;
  
  	if (offset == NULL) {
  		zend_throw_error(zend_ffi_exception_ce, "Cannot add next element to object of type FFI\\CData");
  		return;
  	}
  
  	dim = zval_get_long(offset);
  	if (EXPECTED(type->kind == ZEND_FFI_TYPE_ARRAY)) {
  		if (UNEXPECTED((zend_ulong)(dim) >= (zend_ulong)type->array.length)
  		 && (UNEXPECTED(dim < 0) || UNEXPECTED(type->array.length != 0))) {
  			zend_throw_error(zend_ffi_exception_ce, "C array index out of bounds");
  			return;
  		}
  
  		is_const = (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)(type->attr & ZEND_FFI_ATTR_CONST);
  		type = ZEND_FFI_TYPE(type->array.type);
  #if 0
  		if (UNEXPECTED(!cdata->ptr)) {
  			zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  			return;
  		}
  #endif
  		ptr = (void*)(((char*)cdata->ptr) + type->size * dim);
  	} else if (EXPECTED(type->kind == ZEND_FFI_TYPE_POINTER)) {
  		is_const = (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)(type->attr & ZEND_FFI_ATTR_CONST);
  		type = ZEND_FFI_TYPE(type->pointer.type);
  		if (UNEXPECTED(!cdata->ptr)) {
  			zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  			return;
  		}
  		ptr = (void*)((*(char**)cdata->ptr) + type->size * dim);
  	} else {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to assign element of non C array");
  		return;
  	}
  
  	if (UNEXPECTED(is_const)) {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to assign read-only location");
  		return;
  	}
  
  	zend_ffi_zval_to_cdata(ptr, type, value);
  }
  /* }}} */
  
  #define MAX_TYPE_NAME_LEN 256
  
  typedef struct _zend_ffi_ctype_name_buf {
  	char *start;
  	char *end;
  	char buf[MAX_TYPE_NAME_LEN];
  } zend_ffi_ctype_name_buf;
  
  static int zend_ffi_ctype_name_prepend(zend_ffi_ctype_name_buf *buf, const char *str, size_t len) /* {{{ */
  {
  	buf->start -= len;
  	if (buf->start < buf->buf) {
  		return 0;
  	}
  	memcpy(buf->start, str, len);
  	return 1;
  }
  /* }}} */
  
  static int zend_ffi_ctype_name_append(zend_ffi_ctype_name_buf *buf, const char *str, size_t len) /* {{{ */
  {
  	if (buf->end + len > buf->buf + MAX_TYPE_NAME_LEN) {
  		return 0;
  	}
  	memcpy(buf->end, str, len);
  	buf->end += len;
  	return 1;
  }
  /* }}} */
  
  static int zend_ffi_ctype_name(zend_ffi_ctype_name_buf *buf, const zend_ffi_type *type) /* {{{ */
  {
  	const char *name = NULL;
  	int is_ptr = 0;
  
  	while (1) {
  		switch (type->kind) {
  			case ZEND_FFI_TYPE_VOID:
  				name = "void";
  				break;
  			case ZEND_FFI_TYPE_FLOAT:
  				name = "float";
  				break;
  			case ZEND_FFI_TYPE_DOUBLE:
  				name = "double";
  				break;
  #ifdef HAVE_LONG_DOUBLE
  			case ZEND_FFI_TYPE_LONGDOUBLE:
  				name = "long double";
  				break;
  #endif
  			case ZEND_FFI_TYPE_UINT8:
  				name = "uint8_t";
  				break;
  			case ZEND_FFI_TYPE_SINT8:
  				name = "int8_t";
  				break;
  			case ZEND_FFI_TYPE_UINT16:
  				name = "uint16_t";
  				break;
  			case ZEND_FFI_TYPE_SINT16:
  				name = "int16_t";
  				break;
  			case ZEND_FFI_TYPE_UINT32:
  				name = "uint32_t";
  				break;
  			case ZEND_FFI_TYPE_SINT32:
  				name = "int32_t";
  				break;
  			case ZEND_FFI_TYPE_UINT64:
  				name = "uint64_t";
  				break;
  			case ZEND_FFI_TYPE_SINT64:
  				name = "int64_t";
  				break;
  			case ZEND_FFI_TYPE_ENUM:
  				if (type->enumeration.tag_name) {
  					zend_ffi_ctype_name_prepend(buf, ZSTR_VAL(type->enumeration.tag_name), ZSTR_LEN(type->enumeration.tag_name));
  				} else {
  					zend_ffi_ctype_name_prepend(buf, "<anonymous>", sizeof("<anonymous>")-1);
  				}
  				name = "enum ";
  				break;
  			case ZEND_FFI_TYPE_BOOL:
  				name = "bool";
  				break;
  			case ZEND_FFI_TYPE_CHAR:
  				name = "char";
  				break;
  			case ZEND_FFI_TYPE_POINTER:
  				if (!zend_ffi_ctype_name_prepend(buf, "*", 1)) {
  					return 0;
  				}
  				is_ptr = 1;
  				type = ZEND_FFI_TYPE(type->pointer.type);
  				break;
  			case ZEND_FFI_TYPE_FUNC:
  				if (is_ptr) {
  					is_ptr = 0;
  					if (!zend_ffi_ctype_name_prepend(buf, "(", 1)
  					 || !zend_ffi_ctype_name_append(buf, ")", 1)) {
  						return 0;
  					}
  				}
  				if (!zend_ffi_ctype_name_append(buf, "(", 1)
  				 || !zend_ffi_ctype_name_append(buf, ")", 1)) {
  					return 0;
  				}
  				type = ZEND_FFI_TYPE(type->func.ret_type);
  				break;
  			case ZEND_FFI_TYPE_ARRAY:
  				if (is_ptr) {
  					is_ptr = 0;
  					if (!zend_ffi_ctype_name_prepend(buf, "(", 1)
  					 || !zend_ffi_ctype_name_append(buf, ")", 1)) {
  						return 0;
  					}
  				}
  				if (!zend_ffi_ctype_name_append(buf, "[", 1)) {
  					return 0;
  				}
  				if (type->attr & ZEND_FFI_ATTR_VLA) {
  					if (!zend_ffi_ctype_name_append(buf, "*", 1)) {
  						return 0;
  					}
  				} else if (!(type->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY)) {
  					char str[MAX_LENGTH_OF_LONG + 1];
  					char *s = zend_print_long_to_buf(str + sizeof(str) - 1, type->array.length);
  
  					if (!zend_ffi_ctype_name_append(buf, s, strlen(s))) {
  						return 0;
  					}
  				}
  				if (!zend_ffi_ctype_name_append(buf, "]", 1)) {
  					return 0;
  				}
  				type = ZEND_FFI_TYPE(type->array.type);
  				break;
  			case ZEND_FFI_TYPE_STRUCT:
  				if (type->attr & ZEND_FFI_ATTR_UNION) {
  					if (type->record.tag_name) {
  						zend_ffi_ctype_name_prepend(buf, ZSTR_VAL(type->record.tag_name), ZSTR_LEN(type->record.tag_name));
  					} else {
  						zend_ffi_ctype_name_prepend(buf, "<anonymous>", sizeof("<anonymous>")-1);
  					}
  					name = "union ";
  				} else {
  					if (type->record.tag_name) {
  						zend_ffi_ctype_name_prepend(buf, ZSTR_VAL(type->record.tag_name), ZSTR_LEN(type->record.tag_name));
  					} else {
  						zend_ffi_ctype_name_prepend(buf, "<anonymous>", sizeof("<anonymous>")-1);
  					}
  					name = "struct ";
  				}
  				break;
  			default:
  				ZEND_ASSERT(0);
  		}
  		if (name) {
  			break;
  		}
  	}
  
  //	if (buf->start != buf->end && *buf->start != '[') {
  //		if (!zend_ffi_ctype_name_prepend(buf, " ", 1)) {
  //			return 0;
  //		}
  //	}
  	return zend_ffi_ctype_name_prepend(buf, name, strlen(name));
  }
  /* }}} */
  
  static ZEND_COLD void zend_ffi_return_unsupported(zend_ffi_type *type) /* {{{ */
  {
  	type = ZEND_FFI_TYPE(type);
  	if (type->kind == ZEND_FFI_TYPE_STRUCT) {
  		zend_throw_error(zend_ffi_exception_ce, "FFI return struct/union is not implemented");
  	} else if (type->kind == ZEND_FFI_TYPE_ARRAY) {
  		zend_throw_error(zend_ffi_exception_ce, "FFI return array is not implemented");
  	} else {
  		zend_throw_error(zend_ffi_exception_ce, "FFI internal error. Unsupported return type");
  	}
  }
  /* }}} */
  
  static ZEND_COLD void zend_ffi_pass_unsupported(zend_ffi_type *type) /* {{{ */
  {
  	type = ZEND_FFI_TYPE(type);
  	if (type->kind == ZEND_FFI_TYPE_STRUCT) {
  		zend_throw_error(zend_ffi_exception_ce, "FFI passing struct/union is not implemented");
  	} else if (type->kind == ZEND_FFI_TYPE_ARRAY) {
  		zend_throw_error(zend_ffi_exception_ce, "FFI passing array is not implemented");
  	} else {
  		zend_throw_error(zend_ffi_exception_ce, "FFI internal error. Unsupported parameter type");
  	}
  }
  /* }}} */
  
  static ZEND_COLD void zend_ffi_pass_incompatible(zval *arg, zend_ffi_type *type, uint32_t n, zend_execute_data *execute_data) /* {{{ */
  {
  	zend_ffi_ctype_name_buf buf1, buf2;
  
  	buf1.start = buf1.end = buf1.buf + ((MAX_TYPE_NAME_LEN * 3) / 4);
  	if (!zend_ffi_ctype_name(&buf1, type)) {
  		zend_throw_error(zend_ffi_exception_ce, "Passing incompatible argument %d of C function '%s'", n + 1, ZSTR_VAL(EX(func)->internal_function.function_name));
  	} else {
  		*buf1.end = 0;
  		if (Z_TYPE_P(arg) == IS_OBJECT && Z_OBJCE_P(arg) == zend_ffi_cdata_ce) {
  			zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(arg);
  
  			type = ZEND_FFI_TYPE(cdata->type);
  			buf2.start = buf2.end = buf2.buf + ((MAX_TYPE_NAME_LEN * 3) / 4);
  			if (!zend_ffi_ctype_name(&buf2, type)) {
  				zend_throw_error(zend_ffi_exception_ce, "Passing incompatible argument %d of C function '%s', expecting '%s'", n + 1, ZSTR_VAL(EX(func)->internal_function.function_name), buf1.start);
  			} else {
  				*buf2.end = 0;
  				zend_throw_error(zend_ffi_exception_ce, "Passing incompatible argument %d of C function '%s', expecting '%s', found '%s'", n + 1, ZSTR_VAL(EX(func)->internal_function.function_name), buf1.start, buf2.start);
  			}
  		} else {
  			zend_throw_error(zend_ffi_exception_ce, "Passing incompatible argument %d of C function '%s', expecting '%s', found PHP '%s'", n + 1, ZSTR_VAL(EX(func)->internal_function.function_name), buf1.start, zend_get_type_by_const(Z_TYPE_P(arg)));
  		}
  	}
  }
  /* }}} */
  
  static ZEND_COLD void zend_ffi_assign_incompatible(zval *arg, zend_ffi_type *type) /* {{{ */
  {
  	zend_ffi_ctype_name_buf buf1, buf2;
  
  	buf1.start = buf1.end = buf1.buf + ((MAX_TYPE_NAME_LEN * 3) / 4);
  	if (!zend_ffi_ctype_name(&buf1, type)) {
  		zend_throw_error(zend_ffi_exception_ce, "Incompatible types when assigning");
  	} else {
  		*buf1.end = 0;
  		if (Z_TYPE_P(arg) == IS_OBJECT && Z_OBJCE_P(arg) == zend_ffi_cdata_ce) {
  			zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(arg);
  
  			type = ZEND_FFI_TYPE(cdata->type);
  			buf2.start = buf2.end = buf2.buf + ((MAX_TYPE_NAME_LEN * 3) / 4);
  			if (!zend_ffi_ctype_name(&buf2, type)) {
  				zend_throw_error(zend_ffi_exception_ce, "Incompatible types when assigning to type '%s'", buf1.start);
  			} else {
  				*buf2.end = 0;
  				zend_throw_error(zend_ffi_exception_ce, "Incompatible types when assigning to type '%s' from type '%s'", buf1.start, buf2.start);
  			}
  		} else {
  			zend_throw_error(zend_ffi_exception_ce, "Incompatible types when assigning to type '%s' from PHP '%s'", buf1.start, zend_get_type_by_const(Z_TYPE_P(arg)));
  		}
  	}
  }
  /* }}} */
  
  static zend_string *zend_ffi_get_class_name(zend_string *prefix, const zend_ffi_type *type) /* {{{ */
  {
  	zend_ffi_ctype_name_buf buf;
  
  	buf.start = buf.end = buf.buf + ((MAX_TYPE_NAME_LEN * 3) / 4);
  	if (!zend_ffi_ctype_name(&buf, type)) {
  		return zend_string_copy(prefix);
  	} else {
  		zend_string *name = zend_string_alloc(ZSTR_LEN(prefix) + 1 + buf.end - buf.start, 0);
  		memcpy(ZSTR_VAL(name), ZSTR_VAL(prefix), ZSTR_LEN(prefix));
  		ZSTR_VAL(name)[ZSTR_LEN(prefix)] = ':';
  		memcpy(ZSTR_VAL(name) + ZSTR_LEN(prefix) + 1, buf.start, buf.end - buf.start);
  		ZSTR_VAL(name)[ZSTR_LEN(name)] = 0;
  		return name;
  	}
  }
  /* }}} */
  
  static zend_string *zend_ffi_cdata_get_class_name(const zend_object *zobj) /* {{{ */
  {
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)zobj;
  
  	return zend_ffi_get_class_name(zobj->ce->name, ZEND_FFI_TYPE(cdata->type));
  }
  /* }}} */
  
  static int zend_ffi_cdata_compare_objects(zval *o1, zval *o2) /* {{{ */
  {
  	if (Z_TYPE_P(o1) == IS_OBJECT && Z_OBJCE_P(o1) == zend_ffi_cdata_ce &&
  	    Z_TYPE_P(o2) == IS_OBJECT && Z_OBJCE_P(o2) == zend_ffi_cdata_ce) {
  		zend_ffi_cdata *cdata1 = (zend_ffi_cdata*)Z_OBJ_P(o1);
  		zend_ffi_cdata *cdata2 = (zend_ffi_cdata*)Z_OBJ_P(o2);
  		zend_ffi_type *type1 = ZEND_FFI_TYPE(cdata1->type);
  		zend_ffi_type *type2 = ZEND_FFI_TYPE(cdata2->type);
  
  		if (type1->kind == ZEND_FFI_TYPE_POINTER && type2->kind == ZEND_FFI_TYPE_POINTER) {
  			void *ptr1 = *(void**)cdata1->ptr;
  			void *ptr2 = *(void**)cdata2->ptr;
  
  			if (!ptr1 || !ptr2) {
  				zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  				return 0;
  			}
  			return ptr1 == ptr2 ? 0 : (ptr1 < ptr2 ? -1 : 1);
  		}
  	}
  	zend_throw_error(zend_ffi_exception_ce, "Comparison of incompatible C types");
  	return 0;
  }
  /* }}} */
  
  static int zend_ffi_cdata_count_elements(zend_object *obj, zend_long *count) /* {{{ */
  {
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)obj;
  	zend_ffi_type  *type = ZEND_FFI_TYPE(cdata->type);
  
  	if (type->kind != ZEND_FFI_TYPE_ARRAY) {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to count() on non C array");
  		return FAILURE;
  	} else {
  		*count = type->array.length;
  		return SUCCESS;
  	}
  }
  /* }}} */
  
  static zend_object* zend_ffi_add(zend_ffi_cdata *base_cdata, zend_ffi_type *base_type, zend_long offset) /* {{{ */
  {
  	char *ptr;
  	zend_ffi_type *ptr_type;
  	zend_ffi_cdata *cdata =
  		(zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce);
  
  	if (base_type->kind == ZEND_FFI_TYPE_POINTER) {
  		if (ZEND_FFI_TYPE_IS_OWNED(base_cdata->type)) {
  			if (!(base_type->attr & ZEND_FFI_ATTR_STORED)) {
  				if (GC_REFCOUNT(&base_cdata->std) == 1) {
  					/* transfer type ownership */
  					base_cdata->type = base_type;
  					base_type = ZEND_FFI_TYPE_MAKE_OWNED(base_type);
  				} else {
  					base_cdata->type = base_type = zend_ffi_remember_type(base_type);
  				}
  			}
  		}
  		cdata->type = base_type;
  		ptr = (char*)(*(void**)base_cdata->ptr);
  		ptr_type = ZEND_FFI_TYPE(base_type)->pointer.type;
  	} else {
  		zend_ffi_type *new_type = emalloc(sizeof(zend_ffi_type));
  
  		new_type->kind = ZEND_FFI_TYPE_POINTER;
  		new_type->attr = 0;
  		new_type->size = sizeof(void*);
  		new_type->align = _Alignof(void*);
  
  		ptr_type = base_type->array.type;
  		if (ZEND_FFI_TYPE_IS_OWNED(ptr_type)) {
  			ptr_type = ZEND_FFI_TYPE(ptr_type);
  			if (!(ptr_type->attr & ZEND_FFI_ATTR_STORED)) {
  				if (GC_REFCOUNT(&base_cdata->std) == 1) {
  					/* transfer type ownership */
  					base_type->array.type = ptr_type;
  					ptr_type = ZEND_FFI_TYPE_MAKE_OWNED(ptr_type);
  				} else {
  					base_type->array.type = ptr_type = zend_ffi_remember_type(ptr_type);
  				}
  			}
  		}
  		new_type->pointer.type = ptr_type;
  
  		cdata->type = ZEND_FFI_TYPE_MAKE_OWNED(new_type);
  		ptr = (char*)base_cdata->ptr;
  	}
  	cdata->ptr = &cdata->ptr_holder;
  	cdata->ptr_holder = ptr +
  		(ptrdiff_t) (offset * ZEND_FFI_TYPE(ptr_type)->size);
  	cdata->flags = base_cdata->flags & ZEND_FFI_FLAG_CONST;
  	return &cdata->std;
  }
  /* }}} */
  
  static int zend_ffi_cdata_do_operation(zend_uchar opcode, zval *result, zval *op1, zval *op2) /* {{{ */
  {
  	zend_long offset;
  
  	ZVAL_DEREF(op1);
  	ZVAL_DEREF(op2);
  	if (Z_TYPE_P(op1) == IS_OBJECT && Z_OBJCE_P(op1) == zend_ffi_cdata_ce) {
  		zend_ffi_cdata *cdata1 = (zend_ffi_cdata*)Z_OBJ_P(op1);
  		zend_ffi_type *type1 = ZEND_FFI_TYPE(cdata1->type);
  
  		if (type1->kind == ZEND_FFI_TYPE_POINTER || type1->kind == ZEND_FFI_TYPE_ARRAY) {
  			if (opcode == ZEND_ADD) {
  				offset = zval_get_long(op2);
  				ZVAL_OBJ(result, zend_ffi_add(cdata1, type1, offset));
  				if (result == op1) {
  					OBJ_RELEASE(&cdata1->std);
  				}
  				return SUCCESS;
  			} else if (opcode == ZEND_SUB) {
  				if (Z_TYPE_P(op2) == IS_OBJECT && Z_OBJCE_P(op2) == zend_ffi_cdata_ce) {
  					zend_ffi_cdata *cdata2 = (zend_ffi_cdata*)Z_OBJ_P(op2);
  					zend_ffi_type *type2 = ZEND_FFI_TYPE(cdata2->type);
  
  					if (type2->kind == ZEND_FFI_TYPE_POINTER || type2->kind == ZEND_FFI_TYPE_ARRAY) {
  						zend_ffi_type *t1, *t2;
  						char *p1, *p2;
  
  						if (type1->kind == ZEND_FFI_TYPE_POINTER) {
  							t1 = ZEND_FFI_TYPE(type1->pointer.type);
  							p1 = (char*)(*(void**)cdata1->ptr);
  						} else {
  							t1 = ZEND_FFI_TYPE(type1->array.type);
  							p1 = cdata1->ptr;
  						}
  						if (type2->kind == ZEND_FFI_TYPE_POINTER) {
  							t2 = ZEND_FFI_TYPE(type2->pointer.type);
  							p2 = (char*)(*(void**)cdata2->ptr);
  						} else {
  							t2 = ZEND_FFI_TYPE(type2->array.type);
  							p2 = cdata2->ptr;
  						}
  						if (zend_ffi_is_same_type(t1, t2)) {
  							ZVAL_LONG(result,
  								(zend_long)(p1 - p2) / (zend_long)t1->size);
  							return SUCCESS;
  						}
  					}
  				}
  				offset = zval_get_long(op2);
  				ZVAL_OBJ(result, zend_ffi_add(cdata1, type1, -offset));
  				if (result == op1) {
  					OBJ_RELEASE(&cdata1->std);
  				}
  				return SUCCESS;
  			}
  		}
  	} else if (Z_TYPE_P(op2) == IS_OBJECT && Z_OBJCE_P(op2) == zend_ffi_cdata_ce) {
  		zend_ffi_cdata *cdata2 = (zend_ffi_cdata*)Z_OBJ_P(op2);
  		zend_ffi_type *type2 = ZEND_FFI_TYPE(cdata2->type);
  
  		if (type2->kind == ZEND_FFI_TYPE_POINTER || type2->kind == ZEND_FFI_TYPE_ARRAY) {
  			if (opcode == ZEND_ADD) {
  				offset = zval_get_long(op1);
  				ZVAL_OBJ(result, zend_ffi_add(cdata2, type2, offset));
  				return SUCCESS;
  			}
  		}
  	}
  
  	return FAILURE;
  }
  /* }}} */
  
  typedef struct _zend_ffi_cdata_iterator {
  	zend_object_iterator it;
  	zend_long key;
  	zval value;
  	zend_bool by_ref;
  } zend_ffi_cdata_iterator;
  
  static void zend_ffi_cdata_it_dtor(zend_object_iterator *iter) /* {{{ */
  {
  	zval_ptr_dtor(&((zend_ffi_cdata_iterator*)iter)->value);
  	zval_ptr_dtor(&iter->data);
  }
  /* }}} */
  
  static int zend_ffi_cdata_it_valid(zend_object_iterator *it) /* {{{ */
  {
  	zend_ffi_cdata_iterator *iter = (zend_ffi_cdata_iterator*)it;
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ(iter->it.data);
  	zend_ffi_type  *type = ZEND_FFI_TYPE(cdata->type);
  
  	return (iter->key >= 0 && iter->key < type->array.length) ? SUCCESS : FAILURE;
  }
  /* }}} */
  
  static zval *zend_ffi_cdata_it_get_current_data(zend_object_iterator *it) /* {{{ */
  {
  	zend_ffi_cdata_iterator *iter = (zend_ffi_cdata_iterator*)it;
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ(iter->it.data);
  	zend_ffi_type  *type = ZEND_FFI_TYPE(cdata->type);
  	zend_ffi_type  *dim_type;
  	void *ptr;
  
  	if (!cdata->ptr) {
  		zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  		return &EG(uninitialized_zval);
  	}
  	dim_type = type->array.type;
  	if (ZEND_FFI_TYPE_IS_OWNED(dim_type)) {
  		dim_type = ZEND_FFI_TYPE(dim_type);
  		if (!(dim_type->attr & ZEND_FFI_ATTR_STORED)
  		 && dim_type->kind == ZEND_FFI_TYPE_POINTER) {
  			type->array.type = dim_type = zend_ffi_remember_type(dim_type);
  		}
  	}
  	ptr = (void*)((char*)cdata->ptr + dim_type->size * iter->it.index);
  
  	zval_ptr_dtor(&iter->value);
  	zend_ffi_cdata_to_zval(NULL, ptr, dim_type, iter->by_ref ? BP_VAR_RW : BP_VAR_R, &iter->value, (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)(type->attr & ZEND_FFI_ATTR_CONST), 0);
  	return &iter->value;
  }
  /* }}} */
  
  static void zend_ffi_cdata_it_get_current_key(zend_object_iterator *it, zval *key) /* {{{ */
  {
  	zend_ffi_cdata_iterator *iter = (zend_ffi_cdata_iterator*)it;
  	ZVAL_LONG(key, iter->key);
  }
  /* }}} */
  
  static void zend_ffi_cdata_it_move_forward(zend_object_iterator *it) /* {{{ */
  {
  	zend_ffi_cdata_iterator *iter = (zend_ffi_cdata_iterator*)it;
  	iter->key++;
  }
  /* }}} */
  
  static void zend_ffi_cdata_it_rewind(zend_object_iterator *it) /* {{{ */
  {
  	zend_ffi_cdata_iterator *iter = (zend_ffi_cdata_iterator*)it;
  	iter->key = 0;
  }
  /* }}} */
  
  static const zend_object_iterator_funcs zend_ffi_cdata_it_funcs = {
  	zend_ffi_cdata_it_dtor,
  	zend_ffi_cdata_it_valid,
  	zend_ffi_cdata_it_get_current_data,
  	zend_ffi_cdata_it_get_current_key,
  	zend_ffi_cdata_it_move_forward,
  	zend_ffi_cdata_it_rewind,
  	NULL
  };
  
  static zend_object_iterator *zend_ffi_cdata_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
  {
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(object);
  	zend_ffi_type  *type = ZEND_FFI_TYPE(cdata->type);
  	zend_ffi_cdata_iterator *iter;
  
  	if (type->kind != ZEND_FFI_TYPE_ARRAY) {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to iterate on non C array");
  		return NULL;
  	}
  
  	iter = emalloc(sizeof(zend_ffi_cdata_iterator));
  
  	zend_iterator_init(&iter->it);
  
  	Z_ADDREF_P(object);
  	ZVAL_OBJ(&iter->it.data, Z_OBJ_P(object));
  	iter->it.funcs = &zend_ffi_cdata_it_funcs;
  	iter->key = 0;
  	iter->by_ref = by_ref;
  	ZVAL_UNDEF(&iter->value);
  
  	return &iter->it;
  }
  /* }}} */
  
  static HashTable *zend_ffi_cdata_get_debug_info(zend_object *obj, int *is_temp) /* {{{ */
  {
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)obj;
  	zend_ffi_type  *type = ZEND_FFI_TYPE(cdata->type);
  	void           *ptr = cdata->ptr;
  	HashTable      *ht = NULL;
  	zend_string    *key;
  	zend_ffi_field *f;
  	zend_long       n;
  	zval            tmp;
  
  	if (!cdata->ptr) {
  		zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  		return NULL;
  	}
  
      switch (type->kind) {
  		case ZEND_FFI_TYPE_BOOL:
  		case ZEND_FFI_TYPE_CHAR:
  		case ZEND_FFI_TYPE_ENUM:
  		case ZEND_FFI_TYPE_FLOAT:
  		case ZEND_FFI_TYPE_DOUBLE:
  #ifdef HAVE_LONG_DOUBLE
  		case ZEND_FFI_TYPE_LONGDOUBLE:
  #endif
  		case ZEND_FFI_TYPE_UINT8:
  		case ZEND_FFI_TYPE_SINT8:
  		case ZEND_FFI_TYPE_UINT16:
  		case ZEND_FFI_TYPE_SINT16:
  		case ZEND_FFI_TYPE_UINT32:
  		case ZEND_FFI_TYPE_SINT32:
  		case ZEND_FFI_TYPE_UINT64:
  		case ZEND_FFI_TYPE_SINT64:
  			zend_ffi_cdata_to_zval(cdata, ptr, type, BP_VAR_R, &tmp, ZEND_FFI_FLAG_CONST, 0);
  			ht = zend_new_array(1);
  			zend_hash_str_add(ht, "cdata", sizeof("cdata")-1, &tmp);
  			*is_temp = 1;
  			return ht;
  			break;
  		case ZEND_FFI_TYPE_POINTER:
  			if (*(void**)ptr == NULL) {
  				ZVAL_NULL(&tmp);
  				ht = zend_new_array(1);
  				zend_hash_index_add_new(ht, 0, &tmp);
  				*is_temp = 1;
  				return ht;
  			} else if (ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_VOID) {
  				ZVAL_LONG(&tmp, (uintptr_t)*(void**)ptr);
  				ht = zend_new_array(1);
  				zend_hash_index_add_new(ht, 0, &tmp);
  				*is_temp = 1;
  				return ht;
  			} else {
  				zend_ffi_cdata_to_zval(NULL, *(void**)ptr, ZEND_FFI_TYPE(type->pointer.type), BP_VAR_R, &tmp, ZEND_FFI_FLAG_CONST, 0);
  				ht = zend_new_array(1);
  				zend_hash_index_add_new(ht, 0, &tmp);
  				*is_temp = 1;
  				return ht;
  			}
  			break;
  		case ZEND_FFI_TYPE_STRUCT:
  			ht = zend_new_array(zend_hash_num_elements(&type->record.fields));
  			ZEND_HASH_FOREACH_STR_KEY_PTR(&type->record.fields, key, f) {
  				if (key) {
  					if (!f->bits) {
  						void *f_ptr = (void*)(((char*)ptr) + f->offset);
  						zend_ffi_cdata_to_zval(NULL, f_ptr, ZEND_FFI_TYPE(f->type), BP_VAR_R, &tmp, ZEND_FFI_FLAG_CONST, 0);
  						zend_hash_add(ht, key, &tmp);
  					} else {
  						zend_ffi_bit_field_to_zval(ptr, f, &tmp);
  						zend_hash_add(ht, key, &tmp);
  					}
  				}
  			} ZEND_HASH_FOREACH_END();
  			*is_temp = 1;
  			return ht;
  		case ZEND_FFI_TYPE_ARRAY:
  			ht = zend_new_array(type->array.length);
  			for (n = 0; n < type->array.length; n++) {
  				zend_ffi_cdata_to_zval(NULL, ptr, ZEND_FFI_TYPE(type->array.type), BP_VAR_R, &tmp, ZEND_FFI_FLAG_CONST, 0);
  				zend_hash_index_add(ht, n, &tmp);
  				ptr = (void*)(((char*)ptr) + ZEND_FFI_TYPE(type->array.type)->size);
  			}
  			*is_temp = 1;
  			return ht;
  		case ZEND_FFI_TYPE_FUNC:
  			ht = zend_new_array(0);
  			// TODO: function name ???
  			*is_temp = 1;
  			return ht;
  			break;
  		default:
  			ZEND_ASSERT(0);
  			break;
  	}
  	return NULL;
  }
  /* }}} */
  
  static int zend_ffi_cdata_get_closure(zend_object *obj, zend_class_entry **ce_ptr, zend_function **fptr_ptr, zend_object **obj_ptr, zend_bool check_only) /* {{{ */
  {
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)obj;
  	zend_ffi_type  *type = ZEND_FFI_TYPE(cdata->type);
  	zend_function  *func;
  
  	if (type->kind != ZEND_FFI_TYPE_POINTER) {
  		if (!check_only) {
  			zend_throw_error(zend_ffi_exception_ce, "Attempt to call non C function pointer");
  		}
  		return FAILURE;
  	}
  	type = ZEND_FFI_TYPE(type->pointer.type);
  	if (type->kind != ZEND_FFI_TYPE_FUNC) {
  		if (!check_only) {
  			zend_throw_error(zend_ffi_exception_ce, "Attempt to call non C function pointer");
  		}
  		return FAILURE;
  	}
  	if (!cdata->ptr) {
  		if (!check_only) {
  			zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  		}
  		return FAILURE;
  	}
  
  	if (EXPECTED(EG(trampoline).common.function_name == NULL)) {
  		func = &EG(trampoline);
  	} else {
  		func = ecalloc(sizeof(zend_internal_function), 1);
  	}
  	func->type = ZEND_INTERNAL_FUNCTION;
  	func->common.arg_flags[0] = 0;
  	func->common.arg_flags[1] = 0;
  	func->common.arg_flags[2] = 0;
  	func->common.fn_flags = ZEND_ACC_CALL_VIA_TRAMPOLINE;
  	func->common.function_name = ZSTR_KNOWN(ZEND_STR_MAGIC_INVOKE);
  	/* set to 0 to avoid arg_info[] allocation, because all values are passed by value anyway */
  	func->common.num_args = 0;
  	func->common.required_num_args = type->func.args ? zend_hash_num_elements(type->func.args) : 0;
  	func->common.scope = NULL;
  	func->common.prototype = NULL;
  	func->common.arg_info = NULL;
  	func->internal_function.handler = ZEND_FN(ffi_trampoline);
  	func->internal_function.module = NULL;
  
  	func->internal_function.reserved[0] = type;
  	func->internal_function.reserved[1] = *(void**)cdata->ptr;
  
  	*ce_ptr = NULL;
  	*fptr_ptr= func;
  	*obj_ptr = NULL;
  
  	return SUCCESS;
  }
  /* }}} */
  
  static zend_object *zend_ffi_ctype_new(zend_class_entry *class_type) /* {{{ */
  {
  	zend_ffi_ctype *ctype;
  
  	ctype = emalloc(sizeof(zend_ffi_ctype));
  
  	zend_ffi_object_init(&ctype->std, class_type);
  	ctype->std.handlers = &zend_ffi_ctype_handlers;
  
  	ctype->type = NULL;
  
  	return &ctype->std;
  }
  /* }}} */
  
  static void zend_ffi_ctype_free_obj(zend_object *object) /* {{{ */
  {
  	zend_ffi_ctype *ctype = (zend_ffi_ctype*)object;
  
  	zend_ffi_type_dtor(ctype->type);
  }
  /* }}} */
  
  static int zend_ffi_is_same_type(zend_ffi_type *type1, zend_ffi_type *type2) /* {{{ */
  {
  	while (1) {
  		if (type1 == type2) {
  			return 1;
  		} else if (type1->kind == type2->kind) {
  			if (type1->kind < ZEND_FFI_TYPE_POINTER) {
  				return 1;
  			} else if (type1->kind == ZEND_FFI_TYPE_POINTER) {
  				type1 = ZEND_FFI_TYPE(type1->pointer.type);
  				type2 = ZEND_FFI_TYPE(type2->pointer.type);
  				if (type1->kind == ZEND_FFI_TYPE_VOID ||
  				    type2->kind == ZEND_FFI_TYPE_VOID) {
  				    return 1;
  				}
  			} else if (type1->kind == ZEND_FFI_TYPE_ARRAY &&
  			           type1->array.length == type2->array.length) {
  				type1 = ZEND_FFI_TYPE(type1->array.type);
  				type2 = ZEND_FFI_TYPE(type2->array.type);
  			} else {
  				break;
  			}
  		} else {
  			break;
  		}
  	}
  	return 0;
  }
  /* }}} */
  
  static zend_string *zend_ffi_ctype_get_class_name(const zend_object *zobj) /* {{{ */
  {
  	zend_ffi_ctype *ctype = (zend_ffi_ctype*)zobj;
  
  	return zend_ffi_get_class_name(zobj->ce->name, ZEND_FFI_TYPE(ctype->type));
  }
  /* }}} */
  
  static int zend_ffi_ctype_compare_objects(zval *o1, zval *o2) /* {{{ */
  {
  	if (Z_TYPE_P(o1) == IS_OBJECT && Z_OBJCE_P(o1) == zend_ffi_ctype_ce &&
  	    Z_TYPE_P(o2) == IS_OBJECT && Z_OBJCE_P(o2) == zend_ffi_ctype_ce) {
  		zend_ffi_ctype *ctype1 = (zend_ffi_ctype*)Z_OBJ_P(o1);
  		zend_ffi_ctype *ctype2 = (zend_ffi_ctype*)Z_OBJ_P(o2);
  		zend_ffi_type *type1 = ZEND_FFI_TYPE(ctype1->type);
  		zend_ffi_type *type2 = ZEND_FFI_TYPE(ctype2->type);
  
  		if (zend_ffi_is_same_type(type1, type2)) {
  			return 0;
  		} else {
  			return 1;
  		}
  	}
  	zend_throw_error(zend_ffi_exception_ce, "Comparison of incompatible C types");
  	return 0;
  }
  /* }}} */
  
  static HashTable *zend_ffi_ctype_get_debug_info(zend_object *obj, int *is_temp) /* {{{ */
  {
  	return NULL;
  }
  /* }}} */
  
  static zend_object *zend_ffi_new(zend_class_entry *class_type) /* {{{ */
  {
  	zend_ffi *ffi;
  
  	ffi = emalloc(sizeof(zend_ffi));
  
  	zend_ffi_object_init(&ffi->std, class_type);
  	ffi->std.handlers = &zend_ffi_handlers;
  
  	ffi->lib = NULL;
  	ffi->symbols = NULL;
  	ffi->tags = NULL;
  	ffi->persistent = 0;
  
  	return &ffi->std;
  }
  /* }}} */
  
  static void _zend_ffi_type_dtor(zend_ffi_type *type) /* {{{ */
  {
  	type = ZEND_FFI_TYPE(type);
  
  	switch (type->kind) {
  		case ZEND_FFI_TYPE_ENUM:
  			if (type->enumeration.tag_name) {
  				zend_string_release(type->enumeration.tag_name);
  			}
  			break;
  		case ZEND_FFI_TYPE_STRUCT:
  			if (type->record.tag_name) {
  				zend_string_release(type->record.tag_name);
  			}
  			zend_hash_destroy(&type->record.fields);
  			break;
  		case ZEND_FFI_TYPE_POINTER:
  			zend_ffi_type_dtor(type->pointer.type);
  			break;
  		case ZEND_FFI_TYPE_ARRAY:
  			zend_ffi_type_dtor(type->array.type);
  			break;
  		case ZEND_FFI_TYPE_FUNC:
  			if (type->func.args) {
  				zend_hash_destroy(type->func.args);
  				pefree(type->func.args, type->attr & ZEND_FFI_ATTR_PERSISTENT);
  			}
  			zend_ffi_type_dtor(type->func.ret_type);
  			break;
  		default:
  			break;
  	}
  	pefree(type, type->attr & ZEND_FFI_ATTR_PERSISTENT);
  }
  /* }}} */
  
  static void zend_ffi_type_hash_dtor(zval *zv) /* {{{ */
  {
  	zend_ffi_type *type = Z_PTR_P(zv);
  	zend_ffi_type_dtor(type);
  }
  /* }}} */
  
  static void zend_ffi_field_hash_dtor(zval *zv) /* {{{ */
  {
  	zend_ffi_field *field = Z_PTR_P(zv);
  	zend_ffi_type_dtor(field->type);
  	efree(field);
  }
  /* }}} */
  
  static void zend_ffi_field_hash_persistent_dtor(zval *zv) /* {{{ */
  {
  	zend_ffi_field *field = Z_PTR_P(zv);
  	zend_ffi_type_dtor(field->type);
  	free(field);
  }
  /* }}} */
  
  static void zend_ffi_symbol_hash_dtor(zval *zv) /* {{{ */
  {
  	zend_ffi_symbol *sym = Z_PTR_P(zv);
  	zend_ffi_type_dtor(sym->type);
  	efree(sym);
  }
  /* }}} */
  
  static void zend_ffi_symbol_hash_persistent_dtor(zval *zv) /* {{{ */
  {
  	zend_ffi_symbol *sym = Z_PTR_P(zv);
  	zend_ffi_type_dtor(sym->type);
  	free(sym);
  }
  /* }}} */
  
  static void zend_ffi_tag_hash_dtor(zval *zv) /* {{{ */
  {
  	zend_ffi_tag *tag = Z_PTR_P(zv);
  	zend_ffi_type_dtor(tag->type);
  	efree(tag);
  }
  /* }}} */
  
  static void zend_ffi_tag_hash_persistent_dtor(zval *zv) /* {{{ */
  {
  	zend_ffi_tag *tag = Z_PTR_P(zv);
  	zend_ffi_type_dtor(tag->type);
  	free(tag);
  }
  /* }}} */
  
  static void zend_ffi_cdata_dtor(zend_ffi_cdata *cdata) /* {{{ */
  {
  	zend_ffi_type_dtor(cdata->type);
  	if (cdata->flags & ZEND_FFI_FLAG_OWNED) {
  		if (cdata->ptr != (void*)&cdata->ptr_holder) {
  			pefree(cdata->ptr, cdata->flags & ZEND_FFI_FLAG_PERSISTENT);
  		} else {
  			pefree(cdata->ptr_holder, cdata->flags & ZEND_FFI_FLAG_PERSISTENT);
  		}
  	}
  }
  /* }}} */
  
  static void zend_ffi_scope_hash_dtor(zval *zv) /* {{{ */
  {
  	zend_ffi_scope *scope = Z_PTR_P(zv);
  	if (scope->symbols) {
  		zend_hash_destroy(scope->symbols);
  		free(scope->symbols);
  	}
  	if (scope->tags) {
  		zend_hash_destroy(scope->tags);
  		free(scope->tags);
  	}
  	free(scope);
  }
  /* }}} */
  
  static void zend_ffi_free_obj(zend_object *object) /* {{{ */
  {
  	zend_ffi *ffi = (zend_ffi*)object;
  
  	if (ffi->persistent) {
  		return;
  	}
  
  	if (ffi->lib) {
  		DL_UNLOAD(ffi->lib);
  		ffi->lib = NULL;
  	}
  
  	if (ffi->symbols) {
  		zend_hash_destroy(ffi->symbols);
  		efree(ffi->symbols);
  	}
  
  	if (ffi->tags) {
  		zend_hash_destroy(ffi->tags);
  		efree(ffi->tags);
  	}
  }
  /* }}} */
  
  static void zend_ffi_cdata_free_obj(zend_object *object) /* {{{ */
  {
  	zend_ffi_cdata *cdata = (zend_ffi_cdata*)object;
  
  	zend_ffi_cdata_dtor(cdata);
  }
  /* }}} */
  
  static zend_object *zend_ffi_cdata_clone_obj(zend_object *obj) /* {{{ */
  {
  	zend_ffi_cdata *old_cdata = (zend_ffi_cdata*)obj;
  	zend_ffi_type *type = ZEND_FFI_TYPE(old_cdata->type);
  	zend_ffi_cdata *new_cdata;
  
  	new_cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce);
  	if (type->kind < ZEND_FFI_TYPE_POINTER) {
  		new_cdata->std.handlers = &zend_ffi_cdata_value_handlers;
  	}
  	new_cdata->type = type;
  	new_cdata->ptr = emalloc(type->size);
  	memcpy(new_cdata->ptr, old_cdata->ptr, type->size);
  	new_cdata->flags |= ZEND_FFI_FLAG_OWNED;
  
  	return &new_cdata->std;
  }
  /* }}} */
  
  static zval *zend_ffi_read_var(zend_object *obj, zend_string *var_name, int read_type, void **cache_slot, zval *rv) /* {{{ */
  {
  	zend_ffi        *ffi = (zend_ffi*)obj;
  	zend_ffi_symbol *sym = NULL;
  
  	if (ffi->symbols) {
  		sym = zend_hash_find_ptr(ffi->symbols, var_name);
  		if (sym && sym->kind != ZEND_FFI_SYM_VAR && sym->kind != ZEND_FFI_SYM_CONST) {
  			sym = NULL;
  		}
  	}
  	if (!sym) {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to read undefined C variable '%s'", ZSTR_VAL(var_name));
  		return &EG(uninitialized_zval);
  	}
  
  	if (sym->kind == ZEND_FFI_SYM_VAR) {
  		zend_ffi_cdata_to_zval(NULL, sym->addr, ZEND_FFI_TYPE(sym->type), read_type, rv, (zend_ffi_flags)sym->is_const, 0);
  	} else {
  		ZVAL_LONG(rv, sym->value);
  	}
  
  	return rv;
  }
  /* }}} */
  
  static zval *zend_ffi_write_var(zend_object *obj, zend_string *var_name, zval *value, void **cache_slot) /* {{{ */
  {
  	zend_ffi        *ffi = (zend_ffi*)obj;
  	zend_ffi_symbol *sym = NULL;
  
  	if (ffi->symbols) {
  		sym = zend_hash_find_ptr(ffi->symbols, var_name);
  		if (sym && sym->kind != ZEND_FFI_SYM_VAR) {
  			sym = NULL;
  		}
  	}
  	if (!sym) {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to assign undefined C variable '%s'", ZSTR_VAL(var_name));
  		return value;
  	}
  
  	if (sym->is_const) {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to assign read-only C variable '%s'", ZSTR_VAL(var_name));
  		return value;
  	}
  
  	zend_ffi_zval_to_cdata(sym->addr, ZEND_FFI_TYPE(sym->type), value);
  	return value;
  }
  /* }}} */
  
  static int zend_ffi_pass_arg(zval *arg, zend_ffi_type *type, ffi_type **pass_type, void **arg_values, uint32_t n, zend_execute_data *execute_data) /* {{{ */
  {
  	zend_long lval;
  	double dval;
  	zend_string *str, *tmp_str;
  	zend_ffi_type_kind kind = type->kind;
  
  	ZVAL_DEREF(arg);
  
  again:
      switch (kind) {
  		case ZEND_FFI_TYPE_FLOAT:
  			dval = zval_get_double(arg);
  			*pass_type = &ffi_type_float;
  			*(float*)arg_values[n] = (float)dval;
  			break;
  		case ZEND_FFI_TYPE_DOUBLE:
  			dval = zval_get_double(arg);
  			*pass_type = &ffi_type_double;
  			*(double*)arg_values[n] = dval;
  			break;
  #ifdef HAVE_LONG_DOUBLE
  		case ZEND_FFI_TYPE_LONGDOUBLE:
  			dval = zval_get_double(arg);
  			*pass_type = &ffi_type_double;
  			*(long double*)arg_values[n] = (long double)dval;
  			break;
  #endif
  		case ZEND_FFI_TYPE_UINT8:
  			lval = zval_get_long(arg);
  			*pass_type = &ffi_type_uint8;
  			*(uint8_t*)arg_values[n] = (uint8_t)lval;
  			break;
  		case ZEND_FFI_TYPE_SINT8:
  			lval = zval_get_long(arg);
  			*pass_type = &ffi_type_sint8;
  			*(int8_t*)arg_values[n] = (int8_t)lval;
  			break;
  		case ZEND_FFI_TYPE_UINT16:
  			lval = zval_get_long(arg);
  			*pass_type = &ffi_type_uint16;
  			*(uint16_t*)arg_values[n] = (uint16_t)lval;
  			break;
  		case ZEND_FFI_TYPE_SINT16:
  			lval = zval_get_long(arg);
  			*pass_type = &ffi_type_sint16;
  			*(int16_t*)arg_values[n] = (int16_t)lval;
  			break;
  		case ZEND_FFI_TYPE_UINT32:
  			lval = zval_get_long(arg);
  			*pass_type = &ffi_type_uint32;
  			*(uint32_t*)arg_values[n] = (uint32_t)lval;
  			break;
  		case ZEND_FFI_TYPE_SINT32:
  			lval = zval_get_long(arg);
  			*pass_type = &ffi_type_sint32;
  			*(int32_t*)arg_values[n] = (int32_t)lval;
  			break;
  		case ZEND_FFI_TYPE_UINT64:
  			lval = zval_get_long(arg);
  			*pass_type = &ffi_type_uint64;
  			*(uint64_t*)arg_values[n] = (uint64_t)lval;
  			break;
  		case ZEND_FFI_TYPE_SINT64:
  			lval = zval_get_long(arg);
  			*pass_type = &ffi_type_sint64;
  			*(int64_t*)arg_values[n] = (int64_t)lval;
  			break;
  		case ZEND_FFI_TYPE_POINTER:
  			*pass_type = &ffi_type_pointer;
  			if (Z_TYPE_P(arg) == IS_NULL) {
  				*(void**)arg_values[n] = NULL;
  				return SUCCESS;
  			} else if (Z_TYPE_P(arg) == IS_STRING
  			        && ((ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_CHAR)
  			         || (ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_VOID))) {
  				*(void**)arg_values[n] = Z_STRVAL_P(arg);
  				return SUCCESS;
  			} else if (Z_TYPE_P(arg) == IS_OBJECT && Z_OBJCE_P(arg) == zend_ffi_cdata_ce) {
  				zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(arg);
  
  				if (zend_ffi_is_compatible_type(type, ZEND_FFI_TYPE(cdata->type))) {
  					if (ZEND_FFI_TYPE(cdata->type)->kind == ZEND_FFI_TYPE_POINTER) {
  						if (!cdata->ptr) {
  							zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  							return FAILURE;
  						}
  						*(void**)arg_values[n] = *(void**)cdata->ptr;
  					} else {
  						*(void**)arg_values[n] = cdata->ptr;
  					}
  					return SUCCESS;
  				}
  #if FFI_CLOSURES
  			} else if (ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_FUNC) {
  				void *callback = zend_ffi_create_callback(ZEND_FFI_TYPE(type->pointer.type), arg);
  
  				if (callback) {
  					*(void**)arg_values[n] = callback;
  					break;
  				} else {
  					return FAILURE;
  				}
  #endif
  			}
  			zend_ffi_pass_incompatible(arg, type, n, execute_data);
  			return FAILURE;
  		case ZEND_FFI_TYPE_BOOL:
  			*pass_type = &ffi_type_uint8;
  			*(uint8_t*)arg_values[n] = zend_is_true(arg);
  			break;
  		case ZEND_FFI_TYPE_CHAR:
  			str = zval_get_tmp_string(arg, &tmp_str);
  			*pass_type = &ffi_type_sint8;
  			*(char*)arg_values[n] = ZSTR_VAL(str)[0];
  			if (ZSTR_LEN(str) != 1) {
  				zend_ffi_pass_incompatible(arg, type, n, execute_data);
  			}
  			zend_tmp_string_release(tmp_str);
  			break;
  		case ZEND_FFI_TYPE_ENUM:
  			kind = type->enumeration.kind;
  			goto again;
  		case ZEND_FFI_TYPE_STRUCT:
  			if (!(type->attr & ZEND_FFI_ATTR_UNION)
  			 && Z_TYPE_P(arg) == IS_OBJECT && Z_OBJCE_P(arg) == zend_ffi_cdata_ce) {
  				zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(arg);
  
  				if (zend_ffi_is_compatible_type(type, ZEND_FFI_TYPE(cdata->type))) {
  				    /* Create a fake structure type */
  					ffi_type *t = zend_ffi_make_fake_struct_type(type);
  					if (t) {
  						*pass_type = t;
  						arg_values[n] = cdata->ptr;
  						break;
  					}
  				}
  			}
  			zend_ffi_pass_incompatible(arg, type, n, execute_data);
  			return FAILURE;
  		default:
  			zend_ffi_pass_unsupported(type);
  			return FAILURE;
  	}
  	return SUCCESS;
  }
  /* }}} */
  
  static int zend_ffi_pass_var_arg(zval *arg, ffi_type **pass_type, void **arg_values, uint32_t n, zend_execute_data *execute_data) /* {{{ */
  {
  	ZVAL_DEREF(arg);
  	switch (Z_TYPE_P(arg)) {
  		case IS_NULL:
  			*pass_type = &ffi_type_pointer;
  			*(void**)arg_values[n] = NULL;
  			break;
  		case IS_FALSE:
  			*pass_type = &ffi_type_uint8;
  			*(uint8_t*)arg_values[n] = 0;
  			break;
  		case IS_TRUE:
  			*pass_type = &ffi_type_uint8;
  			*(uint8_t*)arg_values[n] = 1;
  			break;
  		case IS_LONG:
  			if (sizeof(zend_long) == 4) {
  				*pass_type = &ffi_type_sint32;
  				*(int32_t*)arg_values[n] = Z_LVAL_P(arg);
  			} else {
  				*pass_type = &ffi_type_sint64;
  				*(int64_t*)arg_values[n] = Z_LVAL_P(arg);
  			}
  			break;
  		case IS_DOUBLE:
  			*pass_type = &ffi_type_double;
  			*(double*)arg_values[n] = Z_DVAL_P(arg);
  			break;
  		case IS_STRING:
  			*pass_type = &ffi_type_pointer;
  			*(char**)arg_values[n] = Z_STRVAL_P(arg);
  			break;
  		case IS_OBJECT:
  			if (Z_OBJCE_P(arg) == zend_ffi_cdata_ce) {
  				zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(arg);
  				zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type);
  
  				return zend_ffi_pass_arg(arg, type, pass_type, arg_values, n, execute_data);
  			}
  			/* break missing intentionally */
  		default:
  			zend_throw_error(zend_ffi_exception_ce, "Unsupported argument type");
  			return FAILURE;
  	}
  	return SUCCESS;
  }
  /* }}} */
  
  static ZEND_FUNCTION(ffi_trampoline) /* {{{ */
  {
  	zend_ffi_type *type = EX(func)->internal_function.reserved[0];
  	void *addr = EX(func)->internal_function.reserved[1];
  	ffi_cif cif;
  	ffi_type *ret_type = NULL;
  	ffi_type **arg_types = NULL;
  	void **arg_values = NULL;
  	uint32_t n, arg_count;
  	ffi_arg ret;
  	zend_ffi_type *arg_type;
  	ALLOCA_FLAG(arg_types_use_heap = 0)
  	ALLOCA_FLAG(arg_values_use_heap = 0)
  
  	ZEND_ASSERT(type->kind == ZEND_FFI_TYPE_FUNC);
  	arg_count = type->func.args ? zend_hash_num_elements(type->func.args) : 0;
  	if (type->attr & ZEND_FFI_ATTR_VARIADIC) {
  		if (arg_count > EX_NUM_ARGS()) {
  			zend_throw_error(zend_ffi_exception_ce, "Incorrect number of arguments for C function '%s', expecting at least %d parameter%s", ZSTR_VAL(EX(func)->internal_function.function_name), arg_count, (arg_count != 1) ? "s" : "");
  			goto exit;
  		}
  		if (EX_NUM_ARGS()) {
  			arg_types = do_alloca(
  				sizeof(ffi_type*) * EX_NUM_ARGS(), arg_types_use_heap);
  			arg_values = do_alloca(
  				(sizeof(void*) + ZEND_FFI_SIZEOF_ARG) * EX_NUM_ARGS(), arg_values_use_heap);
  			n = 0;
  			if (type->func.args) {
  				ZEND_HASH_FOREACH_PTR(type->func.args, arg_type) {
  					arg_type = ZEND_FFI_TYPE(arg_type);
  					arg_values[n] = ((char*)arg_values) + (sizeof(void*) * EX_NUM_ARGS()) + (ZEND_FFI_SIZEOF_ARG * n);
  					if (zend_ffi_pass_arg(EX_VAR_NUM(n), arg_type, &arg_types[n], arg_values, n, execute_data) != SUCCESS) {
  						free_alloca(arg_types, arg_types_use_heap);
  						free_alloca(arg_values, arg_values_use_heap);
  						goto exit;
  					}
  					n++;
  				} ZEND_HASH_FOREACH_END();
  			}
  			for (; n < EX_NUM_ARGS(); n++) {
  				arg_values[n] = ((char*)arg_values) + (sizeof(void*) * EX_NUM_ARGS()) + (ZEND_FFI_SIZEOF_ARG * n);
  				if (zend_ffi_pass_var_arg(EX_VAR_NUM(n), &arg_types[n], arg_values, n, execute_data) != SUCCESS) {
  					free_alloca(arg_types, arg_types_use_heap);
  					free_alloca(arg_values, arg_values_use_heap);
  					goto exit;
  				}
  			}
  		}
  		ret_type = zend_ffi_get_type(ZEND_FFI_TYPE(type->func.ret_type));
  		if (!ret_type) {
  			zend_ffi_return_unsupported(type->func.ret_type);
  			free_alloca(arg_types, arg_types_use_heap);
  			free_alloca(arg_values, arg_values_use_heap);
  			goto exit;
  		}
  		if (ffi_prep_cif_var(&cif, type->func.abi, arg_count, EX_NUM_ARGS(), ret_type, arg_types) != FFI_OK) {
  			zend_throw_error(zend_ffi_exception_ce, "Cannot prepare callback CIF");
  			free_alloca(arg_types, arg_types_use_heap);
  			free_alloca(arg_values, arg_values_use_heap);
  			goto exit;
  		}
  	} else {
  		if (arg_count != EX_NUM_ARGS()) {
  			zend_throw_error(zend_ffi_exception_ce, "Incorrect number of arguments for C function '%s', expecting exactly %d parameter%s", ZSTR_VAL(EX(func)->internal_function.function_name), arg_count, (arg_count != 1) ? "s" : "");
  			goto exit;
  		}
  		if (EX_NUM_ARGS()) {
  			arg_types = do_alloca(
  				(sizeof(ffi_type*) + sizeof(ffi_type)) * EX_NUM_ARGS(), arg_types_use_heap);
  			arg_values = do_alloca(
  				(sizeof(void*) + ZEND_FFI_SIZEOF_ARG) * EX_NUM_ARGS(), arg_values_use_heap);
  			n = 0;
  			if (type->func.args) {
  				ZEND_HASH_FOREACH_PTR(type->func.args, arg_type) {
  					arg_type = ZEND_FFI_TYPE(arg_type);
  					arg_values[n] = ((char*)arg_values) + (sizeof(void*) * EX_NUM_ARGS()) + (ZEND_FFI_SIZEOF_ARG * n);
  					if (zend_ffi_pass_arg(EX_VAR_NUM(n), arg_type, &arg_types[n], arg_values, n, execute_data) != SUCCESS) {
  						free_alloca(arg_types, arg_types_use_heap);
  						free_alloca(arg_values, arg_values_use_heap);
  						goto exit;
  					}
  					n++;
  				} ZEND_HASH_FOREACH_END();
  			}
  		}
  		ret_type = zend_ffi_get_type(ZEND_FFI_TYPE(type->func.ret_type));
  		if (!ret_type) {
  			zend_ffi_return_unsupported(type->func.ret_type);
  			free_alloca(arg_types, arg_types_use_heap);
  			free_alloca(arg_values, arg_values_use_heap);
  			goto exit;
  		}
  		if (ffi_prep_cif(&cif, type->func.abi, arg_count, ret_type, arg_types) != FFI_OK) {
  			zend_throw_error(zend_ffi_exception_ce, "Cannot prepare callback CIF");
  			free_alloca(arg_types, arg_types_use_heap);
  			free_alloca(arg_values, arg_values_use_heap);
  			goto exit;
  		}
  	}
  
  	ffi_call(&cif, addr, &ret, arg_values);
  
  	for (n = 0; n < arg_count; n++) {
  		if (arg_types[n]->type == FFI_TYPE_STRUCT) {
  			efree(arg_types[n]);
  		}
  	}
  	if (ret_type->type == FFI_TYPE_STRUCT) {
  		efree(ret_type);
  	}
  
  	if (EX_NUM_ARGS()) {
  		free_alloca(arg_types, arg_types_use_heap);
  		free_alloca(arg_values, arg_values_use_heap);
  	}
  
  	zend_ffi_cdata_to_zval(NULL, (void*)&ret, ZEND_FFI_TYPE(type->func.ret_type), BP_VAR_R, return_value, 0, 1);
  
  exit:
  	zend_string_release(EX(func)->common.function_name);
  	if (EX(func)->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) {
  		zend_free_trampoline(EX(func));
  		EX(func) = NULL;
  	}
  }
  /* }}} */
  
  static zend_function *zend_ffi_get_func(zend_object **obj, zend_string *name, const zval *key) /* {{{ */
  {
  	zend_ffi        *ffi = (zend_ffi*)*obj;
  	zend_ffi_symbol *sym = NULL;
  	zend_function   *func;
  	zend_ffi_type   *type;
  
  	if (ZSTR_LEN(name) == sizeof("new") -1
  	 && (ZSTR_VAL(name)[0] == 'n' || ZSTR_VAL(name)[0] == 'N')
  	 && (ZSTR_VAL(name)[1] == 'e' || ZSTR_VAL(name)[1] == 'E')
  	 && (ZSTR_VAL(name)[2] == 'w' || ZSTR_VAL(name)[2] == 'W')) {
  		return (zend_function*)&zend_ffi_new_fn;
  	} else if (ZSTR_LEN(name) == sizeof("cast") -1
  	 && (ZSTR_VAL(name)[0] == 'c' || ZSTR_VAL(name)[0] == 'C')
  	 && (ZSTR_VAL(name)[1] == 'a' || ZSTR_VAL(name)[1] == 'A')
  	 && (ZSTR_VAL(name)[2] == 's' || ZSTR_VAL(name)[2] == 'S')
  	 && (ZSTR_VAL(name)[3] == 't' || ZSTR_VAL(name)[3] == 'T')) {
  		return (zend_function*)&zend_ffi_cast_fn;
  	} else if (ZSTR_LEN(name) == sizeof("type") -1
  	 && (ZSTR_VAL(name)[0] == 't' || ZSTR_VAL(name)[0] == 'T')
  	 && (ZSTR_VAL(name)[1] == 'y' || ZSTR_VAL(name)[1] == 'Y')
  	 && (ZSTR_VAL(name)[2] == 'p' || ZSTR_VAL(name)[2] == 'P')
  	 && (ZSTR_VAL(name)[3] == 'e' || ZSTR_VAL(name)[3] == 'E')) {
  		return (zend_function*)&zend_ffi_type_fn;
  	}
  
  	if (ffi->symbols) {
  		sym = zend_hash_find_ptr(ffi->symbols, name);
  		if (sym && sym->kind != ZEND_FFI_SYM_FUNC) {
  			sym = NULL;
  		}
  	}
  	if (!sym) {
  		zend_throw_error(zend_ffi_exception_ce, "Attempt to call undefined C function '%s'", ZSTR_VAL(name));
  		return NULL;
  	}
  
  	type = ZEND_FFI_TYPE(sym->type);
  	ZEND_ASSERT(type->kind == ZEND_FFI_TYPE_FUNC);
  
  	if (EXPECTED(EG(trampoline).common.function_name == NULL)) {
  		func = &EG(trampoline);
  	} else {
  		func = ecalloc(sizeof(zend_internal_function), 1);
  	}
  	func->common.type = ZEND_INTERNAL_FUNCTION;
  	func->common.arg_flags[0] = 0;
  	func->common.arg_flags[1] = 0;
  	func->common.arg_flags[2] = 0;
  	func->common.fn_flags = ZEND_ACC_CALL_VIA_TRAMPOLINE;
  	func->common.function_name = zend_string_copy(name);
  	/* set to 0 to avoid arg_info[] allocation, because all values are passed by value anyway */
  	func->common.num_args = 0;
  	func->common.required_num_args = type->func.args ? zend_hash_num_elements(type->func.args) : 0;
  	func->common.scope = NULL;
  	func->common.prototype = NULL;
  	func->common.arg_info = NULL;
  	func->internal_function.handler = ZEND_FN(ffi_trampoline);
  	func->internal_function.module = NULL;
  
  	func->internal_function.reserved[0] = type;
  	func->internal_function.reserved[1] = sym->addr;
  
  	return func;
  }
  /* }}} */
  
  static zend_never_inline int zend_ffi_disabled(void) /* {{{ */
  {
  	zend_throw_error(zend_ffi_exception_ce, "FFI API is restricted by \"ffi.enable\" configuration directive");
  	return 0;
  }
  /* }}} */
  
  static zend_always_inline int zend_ffi_validate_api_restriction(zend_execute_data *execute_data) /* {{{ */
  {
  	if (EXPECTED(FFI_G(restriction) > ZEND_FFI_ENABLED)) {
  		ZEND_ASSERT(FFI_G(restriction) == ZEND_FFI_PRELOAD);
  		if (FFI_G(is_cli)
  		 || (execute_data->prev_execute_data
  		  && (execute_data->prev_execute_data->func->common.fn_flags & ZEND_ACC_PRELOADED))
  		 || (CG(compiler_options) & ZEND_COMPILE_PRELOAD)) {
  			return 1;
  		}
  	} else if (EXPECTED(FFI_G(restriction) == ZEND_FFI_ENABLED)) {
  		return 1;
  	}
  	return zend_ffi_disabled();
  }
  /* }}} */
  
  #define ZEND_FFI_VALIDATE_API_RESTRICTION() do { \
  		if (UNEXPECTED(!zend_ffi_validate_api_restriction(execute_data))) { \
  			RETURN_THROWS(); \
  		} \
  	} while (0)
  
  ZEND_METHOD(FFI, cdef) /* {{{ */
  {
  	zend_string *code = NULL;
  	zend_string *lib = NULL;
  	zend_ffi *ffi = NULL;
  	DL_HANDLE handle = NULL;
  	void *addr;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(0, 2)
  		Z_PARAM_OPTIONAL
  		Z_PARAM_STR(code)
  		Z_PARAM_STR(lib)
  	ZEND_PARSE_PARAMETERS_END();
  
  	if (lib) {
  		handle = DL_LOAD(ZSTR_VAL(lib));
  		if (!handle) {
  			zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s'", ZSTR_VAL(lib));
  			RETURN_THROWS();
  		}
  #ifdef RTLD_DEFAULT
  	} else if (1) {
  		// TODO: this might need to be disabled or protected ???
  		handle = RTLD_DEFAULT;
  #endif
  	}
  
  	FFI_G(symbols) = NULL;
  	FFI_G(tags) = NULL;
  
  	if (code) {
  		/* Parse C definitions */
  		FFI_G(default_type_attr) = ZEND_FFI_ATTR_STORED;
  
  		if (zend_ffi_parse_decl(ZSTR_VAL(code), ZSTR_LEN(code)) != SUCCESS) {
  			if (FFI_G(symbols)) {
  				zend_hash_destroy(FFI_G(symbols));
  				efree(FFI_G(symbols));
  				FFI_G(symbols) = NULL;
  			}
  			if (FFI_G(tags)) {
  				zend_hash_destroy(FFI_G(tags));
  				efree(FFI_G(tags));
  				FFI_G(tags) = NULL;
  			}
  			return;
  		}
  
  		if (FFI_G(symbols)) {
  			zend_string *name;
  			zend_ffi_symbol *sym;
  
  			ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(symbols), name, sym) {
  				if (sym->kind == ZEND_FFI_SYM_VAR) {
  					addr = DL_FETCH_SYMBOL(handle, ZSTR_VAL(name));
  					if (!addr) {
  						zend_throw_error(zend_ffi_exception_ce, "Failed resolving C variable '%s'", ZSTR_VAL(name));
  					}
  					sym->addr = addr;
  				} else if (sym->kind == ZEND_FFI_SYM_FUNC) {
  					zend_string *mangled_name = zend_ffi_mangled_func_name(name, ZEND_FFI_TYPE(sym->type));
  
  					addr = DL_FETCH_SYMBOL(handle, ZSTR_VAL(mangled_name));
  					zend_string_release(mangled_name);
  					if (!addr) {
  						zend_throw_error(zend_ffi_exception_ce, "Failed resolving C function '%s'", ZSTR_VAL(name));
  					}
  					sym->addr = addr;
  				}
  			} ZEND_HASH_FOREACH_END();
  		}
  	}
  
  	ffi = (zend_ffi*)zend_ffi_new(zend_ffi_ce);
  	ffi->lib = handle;
  	ffi->symbols = FFI_G(symbols);
  	ffi->tags = FFI_G(tags);
  
  	FFI_G(symbols) = NULL;
  	FFI_G(tags) = NULL;
  
  	RETURN_OBJ(&ffi->std);
  }
  /* }}} */
  
  static int zend_ffi_same_types(zend_ffi_type *old, zend_ffi_type *type) /* {{{ */
  {
  	if (old == type) {
  		return 1;
  	}
  
  	if (old->kind != type->kind
  	 || old->size != type->size
  	 || old->align != type->align
  	 || old->attr != type->attr) {
  		return 0;
  	}
  
  	switch (old->kind) {
  		case ZEND_FFI_TYPE_ENUM:
  			return old->enumeration.kind == type->enumeration.kind;
  		case ZEND_FFI_TYPE_ARRAY:
  			return old->array.length == type->array.length
  			 &&	zend_ffi_same_types(ZEND_FFI_TYPE(old->array.type), ZEND_FFI_TYPE(type->array.type));
  		case ZEND_FFI_TYPE_POINTER:
  			return zend_ffi_same_types(ZEND_FFI_TYPE(old->pointer.type), ZEND_FFI_TYPE(type->pointer.type));
  		case ZEND_FFI_TYPE_STRUCT:
  			if (zend_hash_num_elements(&old->record.fields) != zend_hash_num_elements(&type->record.fields)) {
  				return 0;
  			} else {
  				zend_ffi_field *old_field, *field;
  				zend_string *key;
  				Bucket *b = type->record.fields.arData;
  
  				ZEND_HASH_FOREACH_STR_KEY_PTR(&old->record.fields, key, old_field) {
  					while (Z_TYPE(b->val) == IS_UNDEF) {
  						b++;
  					}
  					if (key) {
  						if (!b->key
  						 || !zend_string_equals(key, b->key)) {
  							return 0;
  						}
  					} else if (b->key) {
  						return 0;
  					}
  					field = Z_PTR(b->val);
  					if (old_field->offset != field->offset
  					 || old_field->is_const != field->is_const
  					 || old_field->is_nested != field->is_nested
  					 || old_field->first_bit != field->first_bit
  					 || old_field->bits != field->bits
  					 || !zend_ffi_same_types(ZEND_FFI_TYPE(old_field->type), ZEND_FFI_TYPE(field->type))) {
  						return 0;
  					}
  					b++;
  				} ZEND_HASH_FOREACH_END();
  			}
  			break;
  		case ZEND_FFI_TYPE_FUNC:
  			if (old->func.abi != type->func.abi
  			 || ((old->func.args ? zend_hash_num_elements(old->func.args) : 0) != (type->func.args ? zend_hash_num_elements(type->func.args) : 0))
  			 || !zend_ffi_same_types(ZEND_FFI_TYPE(old->func.ret_type), ZEND_FFI_TYPE(type->func.ret_type))) {
  				return 0;
  			} else if (old->func.args) {
  				zend_ffi_type *arg_type;
  				Bucket *b = type->func.args->arData;
  
  				ZEND_HASH_FOREACH_PTR(old->func.args, arg_type) {
  					while (Z_TYPE(b->val) == IS_UNDEF) {
  						b++;
  					}
  					if (!zend_ffi_same_types(ZEND_FFI_TYPE(arg_type), ZEND_FFI_TYPE(Z_PTR(b->val)))) {
  						return 0;
  					}
  					b++;
  				} ZEND_HASH_FOREACH_END();
  			}
  			break;
  		default:
  			break;
  	}
  
  	return 1;
  }
  /* }}} */
  
  static int zend_ffi_same_symbols(zend_ffi_symbol *old, zend_ffi_symbol *sym) /* {{{ */
  {
  	if (old->kind != sym->kind || old->is_const != sym->is_const) {
  		return 0;
  	}
  
  	if (old->kind == ZEND_FFI_SYM_CONST) {
  		if (old->value != sym->value) {
  			return 0;
  		}
  	}
  
  	return zend_ffi_same_types(ZEND_FFI_TYPE(old->type), ZEND_FFI_TYPE(sym->type));
  }
  /* }}} */
  
  static int zend_ffi_same_tags(zend_ffi_tag *old, zend_ffi_tag *tag) /* {{{ */
  {
  	if (old->kind != tag->kind) {
  		return 0;
  	}
  
  	return zend_ffi_same_types(ZEND_FFI_TYPE(old->type), ZEND_FFI_TYPE(tag->type));
  }
  /* }}} */
  
  static int zend_ffi_subst_old_type(zend_ffi_type **dcl, zend_ffi_type *old, zend_ffi_type *type) /* {{{ */
  {
  	zend_ffi_type *dcl_type;
  	zend_ffi_field *field;
  
  	if (ZEND_FFI_TYPE(*dcl) == type) {
  		*dcl = old;
  		return 1;
  	}
  	dcl_type = *dcl;
  	switch (dcl_type->kind) {
  		case ZEND_FFI_TYPE_POINTER:
  			return zend_ffi_subst_old_type(&dcl_type->pointer.type, old, type);
  		case ZEND_FFI_TYPE_ARRAY:
  			return zend_ffi_subst_old_type(&dcl_type->array.type, old, type);
  		case ZEND_FFI_TYPE_FUNC:
  			if (zend_ffi_subst_old_type(&dcl_type->func.ret_type, old, type)) {
  				return 1;
  			}
  			if (dcl_type->func.args) {
  				zval *zv;
  
  				ZEND_HASH_FOREACH_VAL(dcl_type->func.args, zv) {
  					if (zend_ffi_subst_old_type((zend_ffi_type**)&Z_PTR_P(zv), old, type)) {
  						return 1;
  					}
  				} ZEND_HASH_FOREACH_END();
  			}
  			break;
  		case ZEND_FFI_TYPE_STRUCT:
  			ZEND_HASH_FOREACH_PTR(&dcl_type->record.fields, field) {
  				if (zend_ffi_subst_old_type(&field->type, old, type)) {
  					return 1;
  				}
  			} ZEND_HASH_FOREACH_END();
  			break;
  		default:
  			break;
  	}
  	return 0;
  } /* }}} */
  
  static void zend_ffi_cleanup_type(zend_ffi_type *old, zend_ffi_type *type) /* {{{ */
  {
  	zend_ffi_symbol *sym;
  	zend_ffi_tag *tag;
  
  	if (FFI_G(symbols)) {
  		ZEND_HASH_FOREACH_PTR(FFI_G(symbols), sym) {
  			zend_ffi_subst_old_type(&sym->type, old, type);
  		} ZEND_HASH_FOREACH_END();
  	}
  	if (FFI_G(tags)) {
  		ZEND_HASH_FOREACH_PTR(FFI_G(tags), tag) {
  			zend_ffi_subst_old_type(&tag->type, old, type);
  		} ZEND_HASH_FOREACH_END();
  	}
  }
  /* }}} */
  
  static zend_ffi_type *zend_ffi_remember_type(zend_ffi_type *type) /* {{{ */
  {
  	if (!FFI_G(weak_types)) {
  		FFI_G(weak_types) = emalloc(sizeof(HashTable));
  		zend_hash_init(FFI_G(weak_types), 0, NULL, zend_ffi_type_hash_dtor, 0);
  	}
  	// TODO: avoid dups ???
  	type->attr |= ZEND_FFI_ATTR_STORED;
  	zend_hash_next_index_insert_ptr(FFI_G(weak_types), ZEND_FFI_TYPE_MAKE_OWNED(type));
  	return type;
  }
  /* }}} */
  
  static zend_ffi *zend_ffi_load(const char *filename, zend_bool preload) /* {{{ */
  {
  	struct stat buf;
  	int fd;
  	char *code, *code_pos, *scope_name, *lib;
  	size_t code_size, scope_name_len;
  	zend_ffi *ffi;
  	DL_HANDLE handle = NULL;
  	zend_ffi_scope *scope = NULL;
  	zend_string *name;
  	zend_ffi_symbol *sym;
  	zend_ffi_tag *tag;
  	void *addr;
  
  	if (stat(filename, &buf) != 0) {
  		if (preload) {
  			zend_error(E_WARNING, "FFI: failed pre-loading '%s', file doesn't exist", filename);
  		} else {
  			zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', file doesn't exist", filename);
  		}
  		return NULL;
  	}
  
  	if ((buf.st_mode & S_IFMT) != S_IFREG) {
  		if (preload) {
  			zend_error(E_WARNING, "FFI: failed pre-loading '%s', not a regular file", filename);
  		} else {
  			zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', not a regular file", filename);
  		}
  		return NULL;
  	}
  
  	code_size = buf.st_size;
  	code = emalloc(code_size + 1);
  	fd = open(filename, O_RDONLY, 0);
  	if (fd < 0 || read(fd, code, code_size) != code_size) {
  		if (preload) {
  			zend_error(E_WARNING, "FFI: Failed pre-loading '%s', cannot read_file", filename);
          } else {
  			zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', cannot read_file", filename);
  		}
  		efree(code);
  		close(fd);
  		return NULL;
  	}
  	close(fd);
  	code[code_size] = 0;
  
  	FFI_G(symbols) = NULL;
  	FFI_G(tags) = NULL;
  	FFI_G(persistent) = preload;
  	FFI_G(default_type_attr) = preload ?
  		ZEND_FFI_ATTR_STORED | ZEND_FFI_ATTR_PERSISTENT :
  		ZEND_FFI_ATTR_STORED;
  
  	scope_name = NULL;
  	scope_name_len = 0;
  	lib = NULL;
  	code_pos = zend_ffi_parse_directives(filename, code, &scope_name, &lib, preload);
  	if (!code_pos) {
  		efree(code);
  		FFI_G(persistent) = 0;
  		return NULL;
  	}
  	code_size -= code_pos - code;
  
  	if (zend_ffi_parse_decl(code_pos, code_size) != SUCCESS) {
  		if (preload) {
  			zend_error(E_WARNING, "FFI: failed pre-loading '%s'", filename);
  		} else {
  			zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s'", filename);
  		}
  		goto cleanup;
  	}
  
  	if (lib) {
  		handle = DL_LOAD(lib);
  		if (!handle) {
  			if (preload) {
  				zend_error(E_WARNING, "FFI: Failed pre-loading '%s'", lib);
  			} else {
  				zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s'", lib);
  			}
  			goto cleanup;
  		}
  #ifdef RTLD_DEFAULT
  	} else if (1) {
  		// TODO: this might need to be disabled or protected ???
  		handle = RTLD_DEFAULT;
  #endif
  	}
  
  	if (preload) {
  		if (!scope_name) {
  			scope_name = "C";
  		}
  		scope_name_len = strlen(scope_name);
  		if (FFI_G(scopes)) {
  			scope = zend_hash_str_find_ptr(FFI_G(scopes), scope_name, scope_name_len);
  		}
  	}
  
  	if (FFI_G(symbols)) {
  		ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(symbols), name, sym) {
  			if (sym->kind == ZEND_FFI_SYM_VAR) {
  				addr = DL_FETCH_SYMBOL(handle, ZSTR_VAL(name));
  				if (!addr) {
  					if (preload) {
  						zend_error(E_WARNING, "FFI: failed pre-loading '%s', cannot resolve C variable '%s'", filename, ZSTR_VAL(name));
  					} else {
  						zend_throw_error(zend_ffi_exception_ce, "Failed resolving C variable '%s'", ZSTR_VAL(name));
  					}
  					if (lib) {
  						DL_UNLOAD(handle);
  					}
  					goto cleanup;
  				}
  				sym->addr = addr;
  			} else if (sym->kind == ZEND_FFI_SYM_FUNC) {
  				zend_string *mangled_name = zend_ffi_mangled_func_name(name, ZEND_FFI_TYPE(sym->type));
  
  				addr = DL_FETCH_SYMBOL(handle, ZSTR_VAL(mangled_name));
  				zend_string_release(mangled_name);
  				if (!addr) {
  					if (preload) {
  						zend_error(E_WARNING, "failed pre-loading '%s', cannot resolve C function '%s'", filename, ZSTR_VAL(name));
  					} else {
  						zend_throw_error(zend_ffi_exception_ce, "Failed resolving C function '%s'", ZSTR_VAL(name));
  					}
  					if (lib) {
  						DL_UNLOAD(handle);
  					}
  					goto cleanup;
  				}
  				sym->addr = addr;
  			}
  			if (scope && scope->symbols) {
  				zend_ffi_symbol *old_sym = zend_hash_find_ptr(scope->symbols, name);
  
  				if (old_sym) {
  					if (zend_ffi_same_symbols(old_sym, sym)) {
  						if (ZEND_FFI_TYPE_IS_OWNED(sym->type)
  						 && ZEND_FFI_TYPE(old_sym->type) != ZEND_FFI_TYPE(sym->type)) {
  							zend_ffi_type *type = ZEND_FFI_TYPE(sym->type);
  							zend_ffi_cleanup_type(ZEND_FFI_TYPE(old_sym->type), ZEND_FFI_TYPE(type));
  							zend_ffi_type_dtor(type);
  						}
  					} else {
  						zend_error(E_WARNING, "FFI: failed pre-loading '%s', redefinition of '%s'", filename, ZSTR_VAL(name));
  						if (lib) {
  							DL_UNLOAD(handle);
  						}
  						goto cleanup;
  					}
  				}
  			}
  		} ZEND_HASH_FOREACH_END();
  	}
  
  	if (preload) {
  		if (scope && scope->tags && FFI_G(tags)) {
  			ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(tags), name, tag) {
  				zend_ffi_tag *old_tag = zend_hash_find_ptr(scope->tags, name);
  
  				if (old_tag) {
  					if (zend_ffi_same_tags(old_tag, tag)) {
  						if (ZEND_FFI_TYPE_IS_OWNED(tag->type)
  						 && ZEND_FFI_TYPE(old_tag->type) != ZEND_FFI_TYPE(tag->type)) {
  							zend_ffi_type *type = ZEND_FFI_TYPE(tag->type);
  							zend_ffi_cleanup_type(ZEND_FFI_TYPE(old_tag->type), ZEND_FFI_TYPE(type));
  							zend_ffi_type_dtor(type);
  						}
  					} else {
  						zend_error(E_WARNING, "FFI: failed pre-loading '%s', redefinition of '%s %s'", filename, zend_ffi_tag_kind_name[tag->kind], ZSTR_VAL(name));
  						if (lib) {
  							DL_UNLOAD(handle);
  						}
  						goto cleanup;
  					}
  				}
  			} ZEND_HASH_FOREACH_END();
  		}
  
  		if (!scope) {
  			scope = malloc(sizeof(zend_ffi_scope));
  			scope->symbols = FFI_G(symbols);
  			scope->tags = FFI_G(tags);
  
  			if (!FFI_G(scopes)) {
  				FFI_G(scopes) = malloc(sizeof(HashTable));
  				zend_hash_init(FFI_G(scopes), 0, NULL, zend_ffi_scope_hash_dtor, 1);
  			}
  
  			zend_hash_str_add_ptr(FFI_G(scopes), scope_name, scope_name_len, scope);
  		} else {
  			if (FFI_G(symbols)) {
  				if (!scope->symbols) {
  					scope->symbols = FFI_G(symbols);
  					FFI_G(symbols) = NULL;
  				} else {
  					ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(symbols), name, sym) {
  						if (!zend_hash_add_ptr(scope->symbols, name, sym)) {
  							zend_ffi_type_dtor(sym->type);
  							free(sym);
  						}
  					} ZEND_HASH_FOREACH_END();
  					FFI_G(symbols)->pDestructor = NULL;
  					zend_hash_destroy(FFI_G(symbols));
  				}
  			}
  			if (FFI_G(tags)) {
  				if (!scope->tags) {
  					scope->tags = FFI_G(tags);
  					FFI_G(tags) = NULL;
  				} else {
  					ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(tags), name, tag) {
  						if (!zend_hash_add_ptr(scope->tags, name, tag)) {
  							zend_ffi_type_dtor(tag->type);
  							free(tag);
  						}
  					} ZEND_HASH_FOREACH_END();
  					FFI_G(tags)->pDestructor = NULL;
  					zend_hash_destroy(FFI_G(tags));
  				}
  			}
  		}
  
  		if (EG(objects_store).object_buckets) {
  			ffi = (zend_ffi*)zend_ffi_new(zend_ffi_ce);
  		} else {
  			ffi = ecalloc(1, sizeof(zend_ffi));
  		}
  		ffi->symbols = scope->symbols;
  		ffi->tags = scope->tags;
  		ffi->persistent = 1;
  	} else {
  		ffi = (zend_ffi*)zend_ffi_new(zend_ffi_ce);
  		ffi->lib = handle;
  		ffi->symbols = FFI_G(symbols);
  		ffi->tags = FFI_G(tags);
  	}
  
  	efree(code);
  	FFI_G(symbols) = NULL;
  	FFI_G(tags) = NULL;
  	FFI_G(persistent) = 0;
  
  	return ffi;
  
  cleanup:
  	efree(code);
  	if (FFI_G(symbols)) {
  		zend_hash_destroy(FFI_G(symbols));
  		pefree(FFI_G(symbols), preload);
  		FFI_G(symbols) = NULL;
  	}
  	if (FFI_G(tags)) {
  		zend_hash_destroy(FFI_G(tags));
  		pefree(FFI_G(tags), preload);
  		FFI_G(tags) = NULL;
  	}
  	FFI_G(persistent) = 0;
  	return NULL;
  }
  /* }}} */
  
  ZEND_METHOD(FFI, load) /* {{{ */
  {
  	zend_string *fn;
  	zend_ffi *ffi;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(1, 1)
  		Z_PARAM_STR(fn)
  	ZEND_PARSE_PARAMETERS_END();
  
  	if (CG(compiler_options) & ZEND_COMPILE_PRELOAD_IN_CHILD) {
  		zend_throw_error(zend_ffi_exception_ce, "FFI::load() doesn't work in conjunction with \"opcache.preload_user\". Use \"ffi.preload\" instead.");
  		RETURN_THROWS();
  	}
  
  	ffi = zend_ffi_load(ZSTR_VAL(fn), (CG(compiler_options) & ZEND_COMPILE_PRELOAD) != 0);
  
  	if (ffi) {
  		RETURN_OBJ(&ffi->std);
  	}
  }
  /* }}} */
  
  ZEND_METHOD(FFI, scope) /* {{{ */
  {
  	zend_string *scope_name;
  	zend_ffi_scope *scope = NULL;
  	zend_ffi *ffi;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(1, 1)
  		Z_PARAM_STR(scope_name)
  	ZEND_PARSE_PARAMETERS_END();
  
  	if (FFI_G(scopes)) {
  		scope = zend_hash_find_ptr(FFI_G(scopes), scope_name);
  	}
  
  	if (!scope) {
  		zend_throw_error(zend_ffi_exception_ce, "Failed loading scope '%s'", ZSTR_VAL(scope_name));
  		RETURN_THROWS();
  	}
  
  	ffi = (zend_ffi*)zend_ffi_new(zend_ffi_ce);
  
  	ffi->symbols = scope->symbols;
  	ffi->tags = scope->tags;
  	ffi->persistent = 1;
  
  	RETURN_OBJ(&ffi->std);
  }
  /* }}} */
  
  static void zend_ffi_cleanup_dcl(zend_ffi_dcl *dcl) /* {{{ */
  {
  	if (dcl) {
  		zend_ffi_type_dtor(dcl->type);
  		dcl->type = NULL;
  	}
  }
  /* }}} */
  
  static void zend_ffi_throw_parser_error(const char *format, ...) /* {{{ */
  {
  	va_list va;
  	char *message = NULL;
  
  	va_start(va, format);
  	zend_vspprintf(&message, 0, format, va);
  
  	if (EG(current_execute_data)) {
  		zend_throw_exception(zend_ffi_parser_exception_ce, message, 0);
  	} else {
  		zend_error(E_WARNING, "FFI Parser: %s", message);
  	}
  
  	efree(message);
  	va_end(va);
  }
  /* }}} */
  
  static int zend_ffi_validate_vla(zend_ffi_type *type) /* {{{ */
  {
  	if (!FFI_G(allow_vla) && (type->attr & ZEND_FFI_ATTR_VLA)) {
  		zend_ffi_throw_parser_error("'[*]' not allowed in other than function prototype scope at line %d", FFI_G(line));
  		return FAILURE;
  	}
  	return SUCCESS;
  }
  /* }}} */
  
  static int zend_ffi_validate_incomplete_type(zend_ffi_type *type, zend_bool allow_incomplete_tag, zend_bool allow_incomplete_array) /* {{{ */
  {
  	if (!allow_incomplete_tag && (type->attr & ZEND_FFI_ATTR_INCOMPLETE_TAG)) {
  		if (FFI_G(tags)) {
  			zend_string *key;
  			zend_ffi_tag *tag;
  
  			ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(tags), key, tag) {
  				if (ZEND_FFI_TYPE(tag->type) == type) {
  					if (type->kind == ZEND_FFI_TYPE_ENUM) {
  						zend_ffi_throw_parser_error("incomplete 'enum %s' at line %d", ZSTR_VAL(key), FFI_G(line));
  					} else if (type->attr & ZEND_FFI_ATTR_UNION) {
  						zend_ffi_throw_parser_error("incomplete 'union %s' at line %d", ZSTR_VAL(key), FFI_G(line));
  					} else {
  						zend_ffi_throw_parser_error("incomplete 'struct %s' at line %d", ZSTR_VAL(key), FFI_G(line));
  					}
  					return FAILURE;
  				}
  			} ZEND_HASH_FOREACH_END();
  		}
  		if (FFI_G(symbols)) {
  			zend_string *key;
  			zend_ffi_symbol *sym;
  
  			ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(symbols), key, sym) {
  				if (type == ZEND_FFI_TYPE(sym->type)) {
  					zend_ffi_throw_parser_error("incomplete C type '%s' at line %d", ZSTR_VAL(key), FFI_G(line));
  					return FAILURE;
  				}
  			} ZEND_HASH_FOREACH_END();
  		}
  		zend_ffi_throw_parser_error("incomplete type at line %d", FFI_G(line));
  		return FAILURE;
  	} else if (!allow_incomplete_array && type->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY) {
  		zend_ffi_throw_parser_error("'[]' not allowed at line %d", FFI_G(line));
  		return FAILURE;
  	} else if (!FFI_G(allow_vla) && (type->attr & ZEND_FFI_ATTR_VLA)) {
  		zend_ffi_throw_parser_error("'[*]' not allowed in other than function prototype scope at line %d", FFI_G(line));
  		return FAILURE;
  	}
  	return SUCCESS;
  }
  /* }}} */
  
  static int zend_ffi_validate_type(zend_ffi_type *type, zend_bool allow_incomplete_tag, zend_bool allow_incomplete_array) /* {{{ */
  {
  	if (type->kind == ZEND_FFI_TYPE_VOID) {
  		zend_ffi_throw_parser_error("'void' type is not allowed at line %d", FFI_G(line));
  		return FAILURE;
  	}
  	return zend_ffi_validate_incomplete_type(type, allow_incomplete_tag, allow_incomplete_array);
  }
  /* }}} */
  
  static int zend_ffi_validate_var_type(zend_ffi_type *type, zend_bool allow_incomplete_array) /* {{{ */
  {
  	if (type->kind == ZEND_FFI_TYPE_FUNC) {
  		zend_ffi_throw_parser_error("'function' type is not allowed at line %d", FFI_G(line));
  		return FAILURE;
  	}
  	return zend_ffi_validate_type(type, 0, allow_incomplete_array);
  }
  /* }}} */
  
  void zend_ffi_validate_type_name(zend_ffi_dcl *dcl) /* {{{ */
  {
  	zend_ffi_finalize_type(dcl);
  	if (zend_ffi_validate_var_type(ZEND_FFI_TYPE(dcl->type), 0) != SUCCESS) {
  		zend_ffi_cleanup_dcl(dcl);
  		LONGJMP(FFI_G(bailout), FAILURE);
  	}
  }
  /* }}} */
  
  static int zend_ffi_subst_type(zend_ffi_type **dcl, zend_ffi_type *type) /* {{{ */
  {
  	zend_ffi_type *dcl_type;
  	zend_ffi_field *field;
  
  	if (*dcl == type) {
  		*dcl = ZEND_FFI_TYPE_MAKE_OWNED(type);
  		return 1;
  	}
  	dcl_type = *dcl;
  	switch (dcl_type->kind) {
  		case ZEND_FFI_TYPE_POINTER:
  			return zend_ffi_subst_type(&dcl_type->pointer.type, type);
  		case ZEND_FFI_TYPE_ARRAY:
  			return zend_ffi_subst_type(&dcl_type->array.type, type);
  		case ZEND_FFI_TYPE_FUNC:
  			if (zend_ffi_subst_type(&dcl_type->func.ret_type, type)) {
  				return 1;
  			}
  			if (dcl_type->func.args) {
  				zval *zv;
  
  				ZEND_HASH_FOREACH_VAL(dcl_type->func.args, zv) {
  					if (zend_ffi_subst_type((zend_ffi_type**)&Z_PTR_P(zv), type)) {
  						return 1;
  					}
  				} ZEND_HASH_FOREACH_END();
  			}
  			break;
  		case ZEND_FFI_TYPE_STRUCT:
  			ZEND_HASH_FOREACH_PTR(&dcl_type->record.fields, field) {
  				if (zend_ffi_subst_type(&field->type, type)) {
  					return 1;
  				}
  			} ZEND_HASH_FOREACH_END();
  			break;
  		default:
  			break;
  	}
  	return 0;
  } /* }}} */
  
  static void zend_ffi_tags_cleanup(zend_ffi_dcl *dcl) /* {{{ */
  {
  	zend_ffi_tag *tag;
  	ZEND_HASH_FOREACH_PTR(FFI_G(tags), tag) {
  		if (ZEND_FFI_TYPE_IS_OWNED(tag->type)) {
  			zend_ffi_type *type = ZEND_FFI_TYPE(tag->type);
  			zend_ffi_subst_type(&dcl->type, type);
  			tag->type = type;
  		}
  	} ZEND_HASH_FOREACH_END();
  	zend_hash_destroy(FFI_G(tags));
  	efree(FFI_G(tags));
  }
  /* }}} */
  
  ZEND_METHOD(FFI, new) /* {{{ */
  {
  	zend_string *type_def = NULL;
  	zval *ztype = NULL;
  	zend_ffi_type *type, *type_ptr;
  	zend_ffi_cdata *cdata;
  	void *ptr;
  	zend_bool owned = 1;
  	zend_bool persistent = 0;
  	zend_bool is_const = 0;
  	zend_ffi_flags flags = ZEND_FFI_FLAG_OWNED;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(1, 3)
  		if (Z_TYPE_P(EX_VAR_NUM(0)) == IS_STRING) {
  			Z_PARAM_STR(type_def)
  		} else {
  			Z_PARAM_OBJECT_OF_CLASS(ztype, zend_ffi_ctype_ce)
  		}
  		Z_PARAM_OPTIONAL
  		Z_PARAM_BOOL(owned)
  		Z_PARAM_BOOL(persistent)
  	ZEND_PARSE_PARAMETERS_END();
  
  	if (!owned) {
  		flags &= ~ZEND_FFI_FLAG_OWNED;
  	}
  
  	if (persistent) {
  		flags |= ZEND_FFI_FLAG_PERSISTENT;
  	}
  
  	if (type_def) {
  		zend_ffi_dcl dcl = ZEND_FFI_ATTR_INIT;
  
  		if (Z_TYPE(EX(This)) == IS_OBJECT) {
  			zend_ffi *ffi = (zend_ffi*)Z_OBJ(EX(This));
  			FFI_G(symbols) = ffi->symbols;
  			FFI_G(tags) = ffi->tags;
  		} else {
  			FFI_G(symbols) = NULL;
  			FFI_G(tags) = NULL;
  		}
  
  		FFI_G(default_type_attr) = 0;
  
  		if (zend_ffi_parse_type(ZSTR_VAL(type_def), ZSTR_LEN(type_def), &dcl) != SUCCESS) {
  			zend_ffi_type_dtor(dcl.type);
  			if (Z_TYPE(EX(This)) != IS_OBJECT) {
  				if (FFI_G(tags)) {
  					zend_hash_destroy(FFI_G(tags));
  					efree(FFI_G(tags));
  					FFI_G(tags) = NULL;
  				}
  				if (FFI_G(symbols)) {
  					zend_hash_destroy(FFI_G(symbols));
  					efree(FFI_G(symbols));
  					FFI_G(symbols) = NULL;
  				}
  			}
  			return;
  		}
  
  		type = ZEND_FFI_TYPE(dcl.type);
  		if (dcl.attr & ZEND_FFI_ATTR_CONST) {
  			is_const = 1;
  		}
  
  		if (Z_TYPE(EX(This)) != IS_OBJECT) {
  			if (FFI_G(tags)) {
  				zend_ffi_tags_cleanup(&dcl);
  			}
  			if (FFI_G(symbols)) {
  				zend_hash_destroy(FFI_G(symbols));
  				efree(FFI_G(symbols));
  				FFI_G(symbols) = NULL;
  			}
  		}
  		FFI_G(symbols) = NULL;
  		FFI_G(tags) = NULL;
  
  		type_ptr = dcl.type;
  	} else {
  		zend_ffi_ctype *ctype = (zend_ffi_ctype*)Z_OBJ_P(ztype);
  
  		type_ptr = type = ctype->type;
  		if (ZEND_FFI_TYPE_IS_OWNED(type)) {
  			type = ZEND_FFI_TYPE(type);
  			if (!(type->attr & ZEND_FFI_ATTR_STORED)) {
  				if (GC_REFCOUNT(&ctype->std) == 1) {
  					/* transfer type ownership */
  					ctype->type = type;
  				} else {
  					ctype->type = type_ptr = type = zend_ffi_remember_type(type);
  				}
  			}
  		}
  	}
  
  	ptr = pemalloc(type->size, flags & ZEND_FFI_FLAG_PERSISTENT);
  	memset(ptr, 0, type->size);
  
  	cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce);
  	if (type->kind < ZEND_FFI_TYPE_POINTER) {
  		cdata->std.handlers = &zend_ffi_cdata_value_handlers;
  	}
  	cdata->type = type_ptr;
  	cdata->ptr = ptr;
  	cdata->flags = flags;
  	if (is_const) {
  		cdata->flags |= ZEND_FFI_FLAG_CONST;
  	}
  
  	RETURN_OBJ(&cdata->std);
  }
  /* }}} */
  
  ZEND_METHOD(FFI, free) /* {{{ */
  {
  	zval *zv;
  	zend_ffi_cdata *cdata;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(1, 1)
  		Z_PARAM_OBJECT_OF_CLASS_EX2(zv, zend_ffi_cdata_ce, 0, 1, 0);
  	ZEND_PARSE_PARAMETERS_END();
  
  	cdata = (zend_ffi_cdata*)Z_OBJ_P(zv);
  
  	if (ZEND_FFI_TYPE(cdata->type)->kind == ZEND_FFI_TYPE_POINTER) {
  		if (!cdata->ptr) {
  			zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference");
  			RETURN_THROWS();
  		}
  		if (cdata->ptr != (void*)&cdata->ptr_holder) {
  			pefree(*(void**)cdata->ptr, cdata->flags & ZEND_FFI_FLAG_PERSISTENT);
  		} else {
  			pefree(cdata->ptr_holder, (cdata->flags & ZEND_FFI_FLAG_PERSISTENT) || !is_zend_ptr(cdata->ptr_holder));
  		}
  		*(void**)cdata->ptr = NULL;
  	} else if (!(cdata->flags & ZEND_FFI_FLAG_OWNED)) {
  		pefree(cdata->ptr, cdata->flags & ZEND_FFI_FLAG_PERSISTENT);
  		cdata->ptr = NULL;
  		cdata->flags &= ~(ZEND_FFI_FLAG_OWNED|ZEND_FFI_FLAG_PERSISTENT);
  		cdata->std.handlers = &zend_ffi_cdata_free_handlers;
  	} else {
  		zend_throw_error(zend_ffi_exception_ce, "free() non a C pointer");
  	}
  }
  /* }}} */
  
  ZEND_METHOD(FFI, cast) /* {{{ */
  {
  	zend_string *type_def = NULL;
  	zval *ztype = NULL;
  	zend_ffi_type *old_type, *type, *type_ptr;
  	zend_ffi_cdata *old_cdata, *cdata;
  	zend_bool is_const = 0;
  	zval *zv, *arg;
  	void *ptr;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(2, 2)
  		if (Z_TYPE_P(EX_VAR_NUM(0)) == IS_STRING) {
  			Z_PARAM_STR(type_def)
  		} else {
  			Z_PARAM_OBJECT_OF_CLASS(ztype, zend_ffi_ctype_ce)
  		}
  		Z_PARAM_ZVAL(zv);
  	ZEND_PARSE_PARAMETERS_END();
  
  	arg = zv;
  	ZVAL_DEREF(zv);
  
  	if (type_def) {
  		zend_ffi_dcl dcl = ZEND_FFI_ATTR_INIT;
  
  		if (Z_TYPE(EX(This)) == IS_OBJECT) {
  			zend_ffi *ffi = (zend_ffi*)Z_OBJ(EX(This));
  			FFI_G(symbols) = ffi->symbols;
  			FFI_G(tags) = ffi->tags;
  		} else {
  			FFI_G(symbols) = NULL;
  			FFI_G(tags) = NULL;
  		}
  
  		FFI_G(default_type_attr) = 0;
  
  		if (zend_ffi_parse_type(ZSTR_VAL(type_def), ZSTR_LEN(type_def), &dcl) != SUCCESS) {
  			zend_ffi_type_dtor(dcl.type);
  			if (Z_TYPE(EX(This)) != IS_OBJECT) {
  				if (FFI_G(tags)) {
  					zend_hash_destroy(FFI_G(tags));
  					efree(FFI_G(tags));
  					FFI_G(tags) = NULL;
  				}
  				if (FFI_G(symbols)) {
  					zend_hash_destroy(FFI_G(symbols));
  					efree(FFI_G(symbols));
  					FFI_G(symbols) = NULL;
  				}
  			}
  			return;
  		}
  
  		type = ZEND_FFI_TYPE(dcl.type);
  		if (dcl.attr & ZEND_FFI_ATTR_CONST) {
  			is_const = 1;
  		}
  
  		if (Z_TYPE(EX(This)) != IS_OBJECT) {
  			if (FFI_G(tags)) {
  				zend_ffi_tags_cleanup(&dcl);
  			}
  			if (FFI_G(symbols)) {
  				zend_hash_destroy(FFI_G(symbols));
  				efree(FFI_G(symbols));
  				FFI_G(symbols) = NULL;
  			}
  		}
  		FFI_G(symbols) = NULL;
  		FFI_G(tags) = NULL;
  
  		type_ptr = dcl.type;
  	} else {
  		zend_ffi_ctype *ctype = (zend_ffi_ctype*)Z_OBJ_P(ztype);
  
  		type_ptr = type = ctype->type;
  		if (ZEND_FFI_TYPE_IS_OWNED(type)) {
  			type = ZEND_FFI_TYPE(type);
  			if (!(type->attr & ZEND_FFI_ATTR_STORED)) {
  				if (GC_REFCOUNT(&ctype->std) == 1) {
  					/* transfer type ownership */
  					ctype->type = type;
  				} else {
  					ctype->type = type_ptr = type = zend_ffi_remember_type(type);
  				}
  			}
  		}
  	}
  
  	if (Z_TYPE_P(zv) != IS_OBJECT || Z_OBJCE_P(zv) != zend_ffi_cdata_ce) {
  		if (type->kind < ZEND_FFI_TYPE_POINTER && Z_TYPE_P(zv) < IS_STRING) {
  			/* numeric conversion */
  			cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce);
  			cdata->std.handlers = &zend_ffi_cdata_value_handlers;
  			cdata->type = type_ptr;
  			cdata->ptr = emalloc(type->size);
  			zend_ffi_zval_to_cdata(cdata->ptr, type, zv);
  			cdata->flags = ZEND_FFI_FLAG_OWNED;
  			if (is_const) {
  				cdata->flags |= ZEND_FFI_FLAG_CONST;
  			}
  			RETURN_OBJ(&cdata->std);
  		} else if (type->kind == ZEND_FFI_TYPE_POINTER && Z_TYPE_P(zv) == IS_LONG) {
  			/* number to pointer conversion */
  			cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce);
  			cdata->type = type_ptr;
  			cdata->ptr = &cdata->ptr_holder;
  			cdata->ptr_holder = (void*)(intptr_t)Z_LVAL_P(zv);
  			if (is_const) {
  				cdata->flags |= ZEND_FFI_FLAG_CONST;
  			}
  			RETURN_OBJ(&cdata->std);
  		} else if (type->kind == ZEND_FFI_TYPE_POINTER && Z_TYPE_P(zv) == IS_NULL) {
  			/* null -> pointer */
  			cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce);
  			cdata->type = type_ptr;
  			cdata->ptr = &cdata->ptr_holder;
  			cdata->ptr_holder = NULL;
  			if (is_const) {
  				cdata->flags |= ZEND_FFI_FLAG_CONST;
  			}
  			RETURN_OBJ(&cdata->std);
  		} else {
  			zend_wrong_parameter_class_error(2, "FFI\\CData", zv);
  			return;
  		}
  	}
  
  	old_cdata = (zend_ffi_cdata*)Z_OBJ_P(zv);
  	old_type = ZEND_FFI_TYPE(old_cdata->type);
  	ptr = old_cdata->ptr;
  
  	cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce);
  	if (type->kind < ZEND_FFI_TYPE_POINTER) {
  		cdata->std.handlers = &zend_ffi_cdata_value_handlers;
  	}
  	cdata->type = type_ptr;
  
  	if (old_type->kind == ZEND_FFI_TYPE_POINTER
  	 && type->kind != ZEND_FFI_TYPE_POINTER
  	 && ZEND_FFI_TYPE(old_type->pointer.type)->kind == ZEND_FFI_TYPE_VOID) {
  		/* automatically dereference void* pointers ??? */
  		cdata->ptr = *(void**)ptr;
  	} else if (old_type->kind == ZEND_FFI_TYPE_ARRAY
  	 && type->kind == ZEND_FFI_TYPE_POINTER) {
  		cdata->ptr = &cdata->ptr_holder;
  		cdata->ptr_holder = old_cdata->ptr;
  	} else if (type->size > old_type->size) {
  		zend_object_release(&cdata->std);
  		zend_throw_error(zend_ffi_exception_ce, "attempt to cast to larger type");
  		RETURN_THROWS();
  	} else if (ptr != &old_cdata->ptr_holder) {
  		cdata->ptr = ptr;
  	} else {
  		cdata->ptr = &cdata->ptr_holder;
  		cdata->ptr_holder = old_cdata->ptr_holder;
  	}
  	if (is_const) {
  		cdata->flags |= ZEND_FFI_FLAG_CONST;
  	}
  
  	if (old_cdata->flags & ZEND_FFI_FLAG_OWNED) {
  		if (GC_REFCOUNT(&old_cdata->std) == 1 && Z_REFCOUNT_P(arg) == 1) {
  			/* transfer ownership */
  			old_cdata->flags &= ~ZEND_FFI_FLAG_OWNED;
  			cdata->flags |= ZEND_FFI_FLAG_OWNED;
  		} else {
  			//???zend_throw_error(zend_ffi_exception_ce, "Attempt to cast owned C pointer");
  		}
  	}
  
  	RETURN_OBJ(&cdata->std);
  }
  /* }}} */
  
  ZEND_METHOD(FFI, type) /* {{{ */
  {
  	zend_ffi_ctype *ctype;
  	zend_ffi_dcl dcl = ZEND_FFI_ATTR_INIT;
  	zend_string *type_def;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(1, 1)
  		Z_PARAM_STR(type_def);
  	ZEND_PARSE_PARAMETERS_END();
  
  	if (Z_TYPE(EX(This)) == IS_OBJECT) {
  		zend_ffi *ffi = (zend_ffi*)Z_OBJ(EX(This));
  		FFI_G(symbols) = ffi->symbols;
  		FFI_G(tags) = ffi->tags;
  	} else {
  		FFI_G(symbols) = NULL;
  		FFI_G(tags) = NULL;
  	}
  
  	FFI_G(default_type_attr) = 0;
  
  	if (zend_ffi_parse_type(ZSTR_VAL(type_def), ZSTR_LEN(type_def), &dcl) != SUCCESS) {
  		zend_ffi_type_dtor(dcl.type);
  		if (Z_TYPE(EX(This)) != IS_OBJECT) {
  			if (FFI_G(tags)) {
  				zend_hash_destroy(FFI_G(tags));
  				efree(FFI_G(tags));
  				FFI_G(tags) = NULL;
  			}
  			if (FFI_G(symbols)) {
  				zend_hash_destroy(FFI_G(symbols));
  				efree(FFI_G(symbols));
  				FFI_G(symbols) = NULL;
  			}
  		}
  		return;
  	}
  
  	if (Z_TYPE(EX(This)) != IS_OBJECT) {
  		if (FFI_G(tags)) {
  			zend_ffi_tags_cleanup(&dcl);
  		}
  		if (FFI_G(symbols)) {
  			zend_hash_destroy(FFI_G(symbols));
  			efree(FFI_G(symbols));
  			FFI_G(symbols) = NULL;
  		}
  	}
  	FFI_G(symbols) = NULL;
  	FFI_G(tags) = NULL;
  
  	ctype = (zend_ffi_ctype*)zend_ffi_ctype_new(zend_ffi_ctype_ce);
  	ctype->type = dcl.type;
  
  	RETURN_OBJ(&ctype->std);
  }
  /* }}} */
  
  ZEND_METHOD(FFI, typeof) /* {{{ */
  {
  	zval *zv, *arg;
  	zend_ffi_ctype *ctype;
  	zend_ffi_type *type;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(1, 1)
  		Z_PARAM_ZVAL(zv);
  	ZEND_PARSE_PARAMETERS_END();
  
  	arg = zv;
  	ZVAL_DEREF(zv);
  	if (Z_TYPE_P(zv) == IS_OBJECT && Z_OBJCE_P(zv) == zend_ffi_cdata_ce) {
  		zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(zv);
  
  		type = cdata->type;
  		if (ZEND_FFI_TYPE_IS_OWNED(type)) {
  			type = ZEND_FFI_TYPE(type);
  			if (!(type->attr & ZEND_FFI_ATTR_STORED)) {
  				if (GC_REFCOUNT(&cdata->std) == 1 && Z_REFCOUNT_P(arg) == 1) {
  					/* transfer type ownership */
  					cdata->type = type;
  					type = ZEND_FFI_TYPE_MAKE_OWNED(type);
  				} else {
  					cdata->type = type = zend_ffi_remember_type(type);
  				}
  			}
  		}
  	} else {
  		zend_wrong_parameter_class_error(1, "FFI\\CData", zv);
  		return;
  	}
  
  	ctype = (zend_ffi_ctype*)zend_ffi_ctype_new(zend_ffi_ctype_ce);
  	ctype->type = type;
  
  	RETURN_OBJ(&ctype->std);
  }
  /* }}} */
  
  ZEND_METHOD(FFI, arrayType) /* {{{ */
  {
  	zval *ztype;
  	zend_ffi_ctype *ctype;
  	zend_ffi_type *type;
  	HashTable *dims;
  	zval *val;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(2, 2)
  		Z_PARAM_OBJECT_OF_CLASS(ztype, zend_ffi_ctype_ce)
  		Z_PARAM_ARRAY_HT(dims)
  	ZEND_PARSE_PARAMETERS_END();
  
  	ctype = (zend_ffi_ctype*)Z_OBJ_P(ztype);
  	type = ZEND_FFI_TYPE(ctype->type);
  
  	if (type->kind == ZEND_FFI_TYPE_FUNC) {
  		zend_throw_error(zend_ffi_exception_ce, "array of functions is not allowed");
  		RETURN_THROWS();
  	} else if (type->kind == ZEND_FFI_TYPE_ARRAY && (type->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY)) {
  		zend_throw_error(zend_ffi_exception_ce, "only the leftmost array can be undimensioned");
  		RETURN_THROWS();
  	} else if (type->kind == ZEND_FFI_TYPE_VOID) {
  		zend_throw_error(zend_ffi_exception_ce, "array of 'void' is not allowed");
  		RETURN_THROWS();
  	} else if (type->attr & ZEND_FFI_ATTR_INCOMPLETE_TAG) {
  		zend_throw_error(zend_ffi_exception_ce, "array of incomplete type is not allowed");
  		RETURN_THROWS();
  	}
  
  	if (ZEND_FFI_TYPE_IS_OWNED(ctype->type)) {
  		if (!(type->attr & ZEND_FFI_ATTR_STORED)) {
  			if (GC_REFCOUNT(&ctype->std) == 1) {
  				/* transfer type ownership */
  				ctype->type = type;
  				type = ZEND_FFI_TYPE_MAKE_OWNED(type);
  			} else {
  				ctype->type = type = zend_ffi_remember_type(type);
  			}
  		}
  	}
  
  	ZEND_HASH_REVERSE_FOREACH_VAL(dims, val) {
  		zend_long n = zval_get_long(val);
  		zend_ffi_type *new_type;
  
  		if (n < 0) {
  			zend_throw_error(zend_ffi_exception_ce, "negative array index");
  			zend_ffi_type_dtor(type);
  			RETURN_THROWS();
  		} else if (ZEND_FFI_TYPE(type)->kind == ZEND_FFI_TYPE_ARRAY && (ZEND_FFI_TYPE(type)->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY)) {
  			zend_throw_error(zend_ffi_exception_ce, "only the leftmost array can be undimensioned");
  			zend_ffi_type_dtor(type);
  			RETURN_THROWS();
  		}
  
  		new_type = emalloc(sizeof(zend_ffi_type));
  		new_type->kind = ZEND_FFI_TYPE_ARRAY;
  		new_type->attr = 0;
  		new_type->size = n * ZEND_FFI_TYPE(type)->size;
  		new_type->align = ZEND_FFI_TYPE(type)->align;
  		new_type->array.type = type;
  		new_type->array.length = n;
  
  		if (n == 0) {
  			new_type->attr |= ZEND_FFI_ATTR_INCOMPLETE_ARRAY;
  		}
  
  		type = ZEND_FFI_TYPE_MAKE_OWNED(new_type);
  	} ZEND_HASH_FOREACH_END();
  
  	ctype = (zend_ffi_ctype*)zend_ffi_ctype_new(zend_ffi_ctype_ce);
  	ctype->type = type;
  
  	RETURN_OBJ(&ctype->std);
  }
  /* }}} */
  
  ZEND_METHOD(FFI, addr) /* {{{ */
  {
  	zend_ffi_type *type, *new_type;
  	zend_ffi_cdata *cdata, *new_cdata;
  	zval *zv, *arg;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(1, 1)
  		Z_PARAM_ZVAL(zv)
  	ZEND_PARSE_PARAMETERS_END();
  
  	arg = zv;
  	ZVAL_DEREF(zv);
  	if (Z_TYPE_P(zv) != IS_OBJECT || Z_OBJCE_P(zv) != zend_ffi_cdata_ce) {
  		zend_wrong_parameter_class_error(1, "FFI\\CData", zv);
  		return;
  	}
  
  	cdata = (zend_ffi_cdata*)Z_OBJ_P(zv);
  	type = ZEND_FFI_TYPE(cdata->type);
  
  	new_type = emalloc(sizeof(zend_ffi_type));
  	new_type->kind = ZEND_FFI_TYPE_POINTER;
  	new_type->attr = 0;
  	new_type->size = sizeof(void*);
  	new_type->align = _Alignof(void*);
  	/* life-time (source must relive the resulting pointer) ??? */
  	new_type->pointer.type = type;
  
  	new_cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce);
  	new_cdata->type = ZEND_FFI_TYPE_MAKE_OWNED(new_type);
  	new_cdata->ptr_holder = cdata->ptr;
  	new_cdata->ptr = &new_cdata->ptr_holder;
  
  	if (GC_REFCOUNT(&cdata->std) == 1 && Z_REFCOUNT_P(arg) == 1) {
  		if (ZEND_FFI_TYPE_IS_OWNED(cdata->type)) {
  			/* transfer type ownership */
  			cdata->type = type;
  			new_type->pointer.type = ZEND_FFI_TYPE_MAKE_OWNED(type);
  		}
  		if (cdata->flags & ZEND_FFI_FLAG_OWNED) {
  			/* transfer ownership */
  			cdata->flags &= ~ZEND_FFI_FLAG_OWNED;
  			new_cdata->flags |= ZEND_FFI_FLAG_OWNED;
  		}
  	}
  
  	RETURN_OBJ(&new_cdata->std);
  }
  /* }}} */
  
  ZEND_METHOD(FFI, sizeof) /* {{{ */
  {
  	zval *zv;
  	zend_ffi_type *type;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(1, 1)
  		Z_PARAM_ZVAL(zv);
  	ZEND_PARSE_PARAMETERS_END();
  
  	ZVAL_DEREF(zv);
  	if (Z_TYPE_P(zv) == IS_OBJECT && Z_OBJCE_P(zv) == zend_ffi_cdata_ce) {
  		zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(zv);
  		type = ZEND_FFI_TYPE(cdata->type);
  	} else if (Z_TYPE_P(zv) == IS_OBJECT && Z_OBJCE_P(zv) == zend_ffi_ctype_ce) {
  		zend_ffi_ctype *ctype = (zend_ffi_ctype*)Z_OBJ_P(zv);
  		type = ZEND_FFI_TYPE(ctype->type);
  	} else {
  		zend_wrong_parameter_class_error(1, "FFI\\CData or FFI\\CType", zv);
  		return;
  	}
  
  	RETURN_LONG(type->size);
  }
  /* }}} */
  
  ZEND_METHOD(FFI, alignof) /* {{{ */
  {
  	zval *zv;
  	zend_ffi_type *type;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(1, 1)
  		Z_PARAM_ZVAL(zv);
  	ZEND_PARSE_PARAMETERS_END();
  
  	ZVAL_DEREF(zv);
  	if (Z_TYPE_P(zv) == IS_OBJECT && Z_OBJCE_P(zv) == zend_ffi_cdata_ce) {
  		zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(zv);
  		type = ZEND_FFI_TYPE(cdata->type);
  	} else if (Z_TYPE_P(zv) == IS_OBJECT && Z_OBJCE_P(zv) == zend_ffi_ctype_ce) {
  		zend_ffi_ctype *ctype = (zend_ffi_ctype*)Z_OBJ_P(zv);
  		type = ZEND_FFI_TYPE(ctype->type);
  	} else {
  		zend_wrong_parameter_class_error(1, "FFI\\CData or FFI\\CType", zv);
  		return;
  	}
  
  	RETURN_LONG(type->align);
  }
  /* }}} */
  
  ZEND_METHOD(FFI, memcpy) /* {{{ */
  {
  	zval *zv1, *zv2;
  	zend_ffi_cdata *cdata1, *cdata2;
  	zend_ffi_type *type1, *type2;
  	void *ptr1, *ptr2;
  	zend_long size;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(3, 3)
  		Z_PARAM_OBJECT_OF_CLASS_EX2(zv1, zend_ffi_cdata_ce, 0, 1, 0);
  		Z_PARAM_ZVAL(zv2)
  		Z_PARAM_LONG(size)
  	ZEND_PARSE_PARAMETERS_END();
  
  	cdata1 = (zend_ffi_cdata*)Z_OBJ_P(zv1);
  	type1 = ZEND_FFI_TYPE(cdata1->type);
  	if (type1->kind == ZEND_FFI_TYPE_POINTER) {
  		ptr1 = *(void**)cdata1->ptr;
  	} else {
  		ptr1 = cdata1->ptr;
  		if (type1->kind != ZEND_FFI_TYPE_POINTER && size > type1->size) {
  			zend_throw_error(zend_ffi_exception_ce, "attempt to write over data boundary");
  			RETURN_THROWS();
  		}
  	}
  
  	ZVAL_DEREF(zv2);
  	if (Z_TYPE_P(zv2) == IS_STRING) {
  		ptr2 = Z_STRVAL_P(zv2);
  		if (size > Z_STRLEN_P(zv2)) {
  			zend_throw_error(zend_ffi_exception_ce, "attempt to read over string boundary");
  			RETURN_THROWS();
  		}
  	} else if (Z_TYPE_P(zv2) == IS_OBJECT && Z_OBJCE_P(zv2) == zend_ffi_cdata_ce) {
  		cdata2 = (zend_ffi_cdata*)Z_OBJ_P(zv2);
  		type2 = ZEND_FFI_TYPE(cdata2->type);
  		if (type2->kind == ZEND_FFI_TYPE_POINTER) {
  			ptr2 = *(void**)cdata2->ptr;
  		} else {
  			ptr2 = cdata2->ptr;
  			if (type2->kind != ZEND_FFI_TYPE_POINTER && size > type2->size) {
  				zend_throw_error(zend_ffi_exception_ce, "attempt to read over data boundary");
  				RETURN_THROWS();
  			}
  		}
  	} else {
  		zend_wrong_parameter_class_error(2, "FFI\\CData or string", zv2);
  		return;
  	}
  
  	memcpy(ptr1, ptr2, size);
  }
  /* }}} */
  
  ZEND_METHOD(FFI, memcmp) /* {{{ */
  {
  	zval *zv1, *zv2;
  	zend_ffi_cdata *cdata1, *cdata2;
  	zend_ffi_type *type1, *type2;
  	void *ptr1, *ptr2;
  	zend_long size;
  	int ret;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(3, 3)
  		Z_PARAM_ZVAL(zv1);
  		Z_PARAM_ZVAL(zv2);
  		Z_PARAM_LONG(size)
  	ZEND_PARSE_PARAMETERS_END();
  
  	ZVAL_DEREF(zv1);
  	if (Z_TYPE_P(zv1) == IS_STRING) {
  		ptr1 = Z_STRVAL_P(zv1);
  		if (size > Z_STRLEN_P(zv1)) {
  			zend_throw_error(zend_ffi_exception_ce, "attempt to read over string boundary");
  			RETURN_THROWS();
  		}
  	} else if (Z_TYPE_P(zv1) == IS_OBJECT && Z_OBJCE_P(zv1) == zend_ffi_cdata_ce) {
  		cdata1 = (zend_ffi_cdata*)Z_OBJ_P(zv1);
  		type1 = ZEND_FFI_TYPE(cdata1->type);
  		if (type1->kind == ZEND_FFI_TYPE_POINTER) {
  			ptr1 = *(void**)cdata1->ptr;
  		} else {
  			ptr1 = cdata1->ptr;
  			if (type1->kind != ZEND_FFI_TYPE_POINTER && size > type1->size) {
  				zend_throw_error(zend_ffi_exception_ce, "attempt to read over data boundary");
  				RETURN_THROWS();
  			}
  		}
  	} else {
  		zend_wrong_parameter_class_error(1, "FFI\\CData or string", zv1);
  		return;
  	}
  
  	ZVAL_DEREF(zv2);
  	if (Z_TYPE_P(zv2) == IS_STRING) {
  		ptr2 = Z_STRVAL_P(zv2);
  		if (size > Z_STRLEN_P(zv2)) {
  			zend_throw_error(zend_ffi_exception_ce, "attempt to read over string boundary");
  			RETURN_THROWS();
  		}
  	} else if (Z_TYPE_P(zv2) == IS_OBJECT && Z_OBJCE_P(zv2) == zend_ffi_cdata_ce) {
  		cdata2 = (zend_ffi_cdata*)Z_OBJ_P(zv2);
  		type2 = ZEND_FFI_TYPE(cdata2->type);
  		if (type2->kind == ZEND_FFI_TYPE_POINTER) {
  			ptr2 = *(void**)cdata2->ptr;
  		} else {
  			ptr2 = cdata2->ptr;
  			if (type2->kind != ZEND_FFI_TYPE_POINTER && size > type2->size) {
  				zend_throw_error(zend_ffi_exception_ce, "attempt to read over data boundary");
  				RETURN_THROWS();
  			}
  		}
  	} else {
  		zend_wrong_parameter_class_error(2, "FFI\\CData or string", zv2);
  		return;
  	}
  
  	ret = memcmp(ptr1, ptr2, size);
  	if (ret == 0) {
  		RETVAL_LONG(0);
  	} else if (ret < 0) {
  		RETVAL_LONG(-1);
  	} else {
  		RETVAL_LONG(1);
  	}
  }
  /* }}} */
  
  ZEND_METHOD(FFI, memset) /* {{{ */
  {
  	zval *zv;
  	zend_ffi_cdata *cdata;
  	zend_ffi_type *type;
  	void *ptr;
  	zend_long ch, size;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(3, 3)
  		Z_PARAM_OBJECT_OF_CLASS_EX2(zv, zend_ffi_cdata_ce, 0, 1, 0);
  		Z_PARAM_LONG(ch)
  		Z_PARAM_LONG(size)
  	ZEND_PARSE_PARAMETERS_END();
  
  	cdata = (zend_ffi_cdata*)Z_OBJ_P(zv);
  	type = ZEND_FFI_TYPE(cdata->type);
  	if (type->kind == ZEND_FFI_TYPE_POINTER) {
  		ptr = *(void**)cdata->ptr;
  	} else {
  		ptr = cdata->ptr;
  		if (type->kind != ZEND_FFI_TYPE_POINTER && size > type->size) {
  			zend_throw_error(zend_ffi_exception_ce, "attempt to write over data boundary");
  			RETURN_THROWS();
  		}
  	}
  
  	memset(ptr, ch, size);
  }
  /* }}} */
  
  ZEND_METHOD(FFI, string) /* {{{ */
  {
  	zval *zv;
  	zend_ffi_cdata *cdata;
  	zend_ffi_type *type;
  	void *ptr;
  	zend_long size = 0;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(1, 2)
  		Z_PARAM_OBJECT_OF_CLASS_EX2(zv, zend_ffi_cdata_ce, 0, 1, 0);
  		Z_PARAM_OPTIONAL
  		Z_PARAM_LONG(size)
  	ZEND_PARSE_PARAMETERS_END();
  
  	cdata = (zend_ffi_cdata*)Z_OBJ_P(zv);
  	type = ZEND_FFI_TYPE(cdata->type);
  	if (EX_NUM_ARGS() == 2) {
  		if (type->kind == ZEND_FFI_TYPE_POINTER) {
  			ptr = *(void**)cdata->ptr;
  		} else {
  			ptr = cdata->ptr;
  			if (type->kind != ZEND_FFI_TYPE_POINTER && size > type->size) {
  				zend_throw_error(zend_ffi_exception_ce, "attempt to read over data boundary");
  				RETURN_THROWS();
  			}
  		}
  		RETURN_STRINGL((char*)ptr, size);
  	} else {
  		if (type->kind == ZEND_FFI_TYPE_POINTER && ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) {
  			ptr = *(void**)cdata->ptr;
  		} else if (type->kind == ZEND_FFI_TYPE_ARRAY && ZEND_FFI_TYPE(type->array.type)->kind == ZEND_FFI_TYPE_CHAR) {
  			ptr = cdata->ptr;
  		} else {
  			zend_throw_error(zend_ffi_exception_ce, "FFI\\Cdata is not a C string");
  			RETURN_THROWS();
  		}
  		RETURN_STRING((char*)ptr);
  	}
  }
  /* }}} */
  
  ZEND_METHOD(FFI, isNull) /* {{{ */
  {
  	zval *zv;
  	zend_ffi_cdata *cdata;
  	zend_ffi_type *type;
  
  	ZEND_FFI_VALIDATE_API_RESTRICTION();
  	ZEND_PARSE_PARAMETERS_START(1, 1)
  		Z_PARAM_ZVAL(zv);
  	ZEND_PARSE_PARAMETERS_END();
  
  	ZVAL_DEREF(zv);
  	if (Z_TYPE_P(zv) != IS_OBJECT || Z_OBJCE_P(zv) != zend_ffi_cdata_ce) {
  		zend_wrong_parameter_class_error(1, "FFI\\CData", zv);
  		return;
  	}
  
  	cdata = (zend_ffi_cdata*)Z_OBJ_P(zv);
  	type = ZEND_FFI_TYPE(cdata->type);
  
  	if (type->kind != ZEND_FFI_TYPE_POINTER){
  		zend_throw_error(zend_ffi_exception_ce, "FFI\\Cdata is not a pointer");
  		RETURN_THROWS();
  	}
  
  	RETURN_BOOL(*(void**)cdata->ptr == NULL);
  }
  /* }}} */
  
  static const zend_function_entry zend_ffi_functions[] = {
  	ZEND_ME(FFI, cdef,        arginfo_class_FFI_cdef,      ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, load,        arginfo_class_FFI_load,      ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, scope,       arginfo_class_FFI_scope,     ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, new,         arginfo_class_FFI_new,       ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, free,        arginfo_class_FFI_free,      ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, cast,        arginfo_class_FFI_cast,      ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, type,        arginfo_class_FFI_type,      ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, typeof,      arginfo_class_FFI_typeof,    ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, arrayType,   arginfo_class_FFI_arrayType, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, addr,        arginfo_class_FFI_addr,      ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, sizeof,      arginfo_class_FFI_sizeof,    ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, alignof,     arginfo_class_FFI_alignof,   ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, memcpy,      arginfo_class_FFI_memcpy,    ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, memcmp,      arginfo_class_FFI_memcmp,    ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, memset,      arginfo_class_FFI_memset,    ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, string,      arginfo_class_FFI_string,    ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_ME(FFI, isNull,      arginfo_class_FFI_isNull,    ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  	ZEND_FE_END
  };
  
  static char *zend_ffi_parse_directives(const char *filename, char *code_pos, char **scope_name, char **lib, zend_bool preload) /* {{{ */
  {
  	char *p;
  
  	*scope_name = NULL;
  	*lib = NULL;
  	while (*code_pos == '#') {
  		if (strncmp(code_pos, "#define FFI_SCOPE", sizeof("#define FFI_SCOPE") - 1) == 0
  		 && (code_pos[sizeof("#define FFI_SCOPE") - 1] == ' '
  		  || code_pos[sizeof("#define FFI_SCOPE") - 1] == '\t')) {
  			p = code_pos + sizeof("#define FFI_SCOPE");
  			while (*p == ' ' || *p == '\t') {
  				p++;
  			}
  			if (*p != '"') {
  				if (preload) {
  					zend_error(E_WARNING, "FFI: failed pre-loading '%s', bad FFI_SCOPE define", filename);
  				} else {
  					zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', bad FFI_SCOPE define", filename);
  				}
  				return NULL;
  			}
  			p++;
  			if (*scope_name) {
  				if (preload) {
  					zend_error(E_WARNING, "FFI: failed pre-loading '%s', FFI_SCOPE defined twice", filename);
  				} else {
  					zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', FFI_SCOPE defined twice", filename);
  				}
  				return NULL;
  			}
  			*scope_name = p;
  			while (1) {
  				if (*p == '\"') {
  					*p = 0;
  					p++;
  					break;
  				} else if (*p <= ' ') {
  					if (preload) {
  						zend_error(E_WARNING, "FFI: failed pre-loading '%s', bad FFI_SCOPE define", filename);
  					} else {
  						zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', bad FFI_SCOPE define", filename);
  					}
  					return NULL;
  				}
  				p++;
  			}
  			while (*p == ' ' || *p == '\t') {
  				p++;
  			}
  			while (*p == '\r' || *p == '\n') {
  				p++;
  			}
  			code_pos = p;
  		} else if (strncmp(code_pos, "#define FFI_LIB", sizeof("#define FFI_LIB") - 1) == 0
  		 && (code_pos[sizeof("#define FFI_LIB") - 1] == ' '
  		  || code_pos[sizeof("#define FFI_LIB") - 1] == '\t')) {
  			p = code_pos + sizeof("#define FFI_LIB");
  			while (*p == ' ' || *p == '\t') {
  				p++;
  			}
  			if (*p != '"') {
  				if (preload) {
  					zend_error(E_WARNING, "FFI: failed pre-loading '%s', bad FFI_LIB define", filename);
  				} else {
  					zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', bad FFI_LIB define", filename);
  				}
  				return NULL;
  			}
  			p++;
  			if (*lib) {
  				if (preload) {
  					zend_error(E_WARNING, "FFI: failed pre-loading '%s', FFI_LIB defined twice", filename);
  				} else {
  					zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', FFI_LIB defined twice", filename);
  				}
  				return NULL;
  			}
  			*lib = p;
  			while (1) {
  				if (*p == '\"') {
  					*p = 0;
  					p++;
  					break;
  				} else if (*p <= ' ') {
  					if (preload) {
  						zend_error(E_WARNING, "FFI: failed pre-loading '%s', bad FFI_LIB define", filename);
  					} else {
  						zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', bad FFI_LIB define", filename);
  					}
  					return NULL;
  				}
  				p++;
  			}
  			while (*p == ' ' || *p == '\t') {
  				p++;
  			}
  			while (*p == '\r' || *p == '\n') {
  				p++;
  			}
  			code_pos = p;
  		} else {
  			break;
  		}
  	}
  	return code_pos;
  }
  /* }}} */
  
  static ZEND_COLD zend_function *zend_fake_get_constructor(zend_object *object) /* {{{ */
  {
  	zend_throw_error(NULL, "Instantiation of '%s' is not allowed", ZSTR_VAL(object->ce->name));
  	return NULL;
  }
  /* }}} */
  
  static ZEND_COLD zend_never_inline void zend_bad_array_access(zend_class_entry *ce) /* {{{ */
  {
  	zend_throw_error(NULL, "Cannot use object of type %s as array", ZSTR_VAL(ce->name));
  }
  /* }}} */
  
  static ZEND_COLD zval *zend_fake_read_dimension(zend_object *obj, zval *offset, int type, zval *rv) /* {{{ */
  {
      zend_bad_array_access(obj->ce);
  	return NULL;
  }
  /* }}} */
  
  static ZEND_COLD void zend_fake_write_dimension(zend_object *obj, zval *offset, zval *value) /* {{{ */
  {
      zend_bad_array_access(obj->ce);
  }
  /* }}} */
  
  static ZEND_COLD int zend_fake_has_dimension(zend_object *obj, zval *offset, int check_empty) /* {{{ */
  {
      zend_bad_array_access(obj->ce);
  	return 0;
  }
  /* }}} */
  
  static ZEND_COLD void zend_fake_unset_dimension(zend_object *obj, zval *offset) /* {{{ */
  {
      zend_bad_array_access(obj->ce);
  }
  /* }}} */
  
  static ZEND_COLD zend_never_inline void zend_bad_property_access(zend_class_entry *ce) /* {{{ */
  {
  	zend_throw_error(NULL, "Cannot access property of object of type %s", ZSTR_VAL(ce->name));
  }
  /* }}} */
  
  static ZEND_COLD zval *zend_fake_read_property(zend_object *obj, zend_string *member, int type, void **cache_slot, zval *rv) /* {{{ */
  {
      zend_bad_property_access(obj->ce);
  	return &EG(uninitialized_zval);
  }
  /* }}} */
  
  static ZEND_COLD zval *zend_fake_write_property(zend_object *obj, zend_string *member, zval *value, void **cache_slot) /* {{{ */
  {
      zend_bad_array_access(obj->ce);
      return value;
  }
  /* }}} */
  
  static ZEND_COLD int zend_fake_has_property(zend_object *obj, zend_string *member, int has_set_exists, void **cache_slot) /* {{{ */
  {
      zend_bad_array_access(obj->ce);
  	return 0;
  }
  /* }}} */
  
  static ZEND_COLD void zend_fake_unset_property(zend_object *obj, zend_string *member, void **cache_slot) /* {{{ */
  {
      zend_bad_array_access(obj->ce);
  }
  /* }}} */
  
  static zval *zend_fake_get_property_ptr_ptr(zend_object *obj, zend_string *member, int type, void **cache_slot) /* {{{ */
  {
  	return NULL;
  }
  /* }}} */
  
  static ZEND_COLD zend_function *zend_fake_get_method(zend_object **obj_ptr, zend_string *method_name, const zval *key) /* {{{ */
  {
  	zend_class_entry *ce = (*obj_ptr)->ce;
  	zend_throw_error(NULL, "Object of type %s does not support method calls", ZSTR_VAL(ce->name));
  	return NULL;
  }
  /* }}} */
  
  static HashTable *zend_fake_get_properties(zend_object *obj) /* {{{ */
  {
  	return (HashTable*)&zend_empty_array;
  }
  /* }}} */
  
  static HashTable *zend_fake_get_gc(zend_object *ob, zval **table, int *n) /* {{{ */
  {
  	*table = NULL;
  	*n = 0;
  	return NULL;
  }
  /* }}} */
  
  static ZEND_COLD zend_never_inline void zend_ffi_use_after_free(void) /* {{{ */
  {
  	zend_throw_error(zend_ffi_exception_ce, "Use after free()");
  }
  /* }}} */
  
  static zend_object *zend_ffi_free_clone_obj(zend_object *obj) /* {{{ */
  {
  	zend_ffi_use_after_free();
  	return NULL;
  }
  /* }}} */
  
  static ZEND_COLD zval *zend_ffi_free_read_dimension(zend_object *obj, zval *offset, int type, zval *rv) /* {{{ */
  {
  	zend_ffi_use_after_free();
  	return NULL;
  }
  /* }}} */
  
  static ZEND_COLD void zend_ffi_free_write_dimension(zend_object *obj, zval *offset, zval *value) /* {{{ */
  {
  	zend_ffi_use_after_free();
  }
  /* }}} */
  
  static ZEND_COLD int zend_ffi_free_has_dimension(zend_object *obj, zval *offset, int check_empty) /* {{{ */
  {
  	zend_ffi_use_after_free();
  	return 0;
  }
  /* }}} */
  
  static ZEND_COLD void zend_ffi_free_unset_dimension(zend_object *obj, zval *offset) /* {{{ */
  {
  	zend_ffi_use_after_free();
  }
  /* }}} */
  
  static ZEND_COLD zval *zend_ffi_free_read_property(zend_object *obj, zend_string *member, int type, void **cache_slot, zval *rv) /* {{{ */
  {
  	zend_ffi_use_after_free();
  	return &EG(uninitialized_zval);
  }
  /* }}} */
  
  static ZEND_COLD zval *zend_ffi_free_write_property(zend_object *obj, zend_string *member, zval *value, void **cache_slot) /* {{{ */
  {
  	zend_ffi_use_after_free();
  	return value;
  }
  /* }}} */
  
  static ZEND_COLD int zend_ffi_free_has_property(zend_object *obj, zend_string *member, int has_set_exists, void **cache_slot) /* {{{ */
  {
  	zend_ffi_use_after_free();
  	return 0;
  }
  /* }}} */
  
  static ZEND_COLD void zend_ffi_free_unset_property(zend_object *obj, zend_string *member, void **cache_slot) /* {{{ */
  {
  	zend_ffi_use_after_free();
  }
  /* }}} */
  
  static HashTable *zend_ffi_free_get_debug_info(zend_object *obj, int *is_temp) /* {{{ */
  {
  	zend_ffi_use_after_free();
  	return NULL;
  }
  /* }}} */
  
  static ZEND_INI_MH(OnUpdateFFIEnable) /* {{{ */
  {
  	if (zend_string_equals_literal_ci(new_value, "preload")) {
  		FFI_G(restriction) = ZEND_FFI_PRELOAD;
  	} else {
  		FFI_G(restriction) = (zend_ffi_api_restriction)zend_ini_parse_bool(new_value);
  	}
  	return SUCCESS;
  }
  /* }}} */
  
  static ZEND_INI_DISP(zend_ffi_enable_displayer_cb) /* {{{ */
  {
  	if (FFI_G(restriction) == ZEND_FFI_PRELOAD) {
  		ZEND_PUTS("preload");
  	} else if (FFI_G(restriction) == ZEND_FFI_ENABLED) {
  		ZEND_PUTS("On");
  	} else {
  		ZEND_PUTS("Off");
  	}
  }
  /* }}} */
  
  ZEND_INI_BEGIN()
  	ZEND_INI_ENTRY3_EX("ffi.enable", "preload", ZEND_INI_SYSTEM, OnUpdateFFIEnable, NULL, NULL, NULL, zend_ffi_enable_displayer_cb)
  	STD_ZEND_INI_ENTRY("ffi.preload", NULL, ZEND_INI_SYSTEM, OnUpdateString, preload, zend_ffi_globals, ffi_globals)
  ZEND_INI_END()
  
  static int zend_ffi_preload_glob(const char *filename) /* {{{ */
  {
  #ifdef HAVE_GLOB
  	glob_t globbuf;
  	int    ret;
  	unsigned int i;
  
  	memset(&globbuf, 0, sizeof(glob_t));
  
  	ret = glob(filename, 0, NULL, &globbuf);
  #ifdef GLOB_NOMATCH
  	if (ret == GLOB_NOMATCH || !globbuf.gl_pathc) {
  #else
  	if (!globbuf.gl_pathc) {
  #endif
  		/* pass */
  	} else {
  		for(i=0 ; i<globbuf.gl_pathc; i++) {
  			zend_ffi *ffi = zend_ffi_load(globbuf.gl_pathv[i], 1);
  			if (!ffi) {
  				globfree(&globbuf);
  				return FAILURE;
  			}
  			efree(ffi);
  		}
  		globfree(&globbuf);
  	}
  #else
  	zend_ffi *ffi = zend_ffi_load(filename, 1);
  	if (!ffi) {
  		return FAILURE;
  	}
  	efree(ffi);
  #endif
  
  	return SUCCESS;
  }
  /* }}} */
  
  static int zend_ffi_preload(char *preload) /* {{{ */
  {
  	zend_ffi *ffi;
  	char *s = NULL, *e, *filename;
  	zend_bool is_glob = 0;
  
  	e = preload;
  	while (*e) {
  		switch (*e) {
  			case ZEND_PATHS_SEPARATOR:
  				if (s) {
  					filename = estrndup(s, e-s);
  					s = NULL;
  					if (!is_glob) {
  						ffi = zend_ffi_load(filename, 1);
  						efree(filename);
  						if (!ffi) {
  							return FAILURE;
  						}
  						efree(ffi);
  					} else {
  						int ret = zend_ffi_preload_glob(filename);
  
  						efree(filename);
  						if (ret != SUCCESS) {
  							return FAILURE;
  						}
  						is_glob = 0;
  					}
  				}
  				break;
  			case '*':
  			case '?':
  			case '[':
  				is_glob = 1;
  				break;
  			default:
  				if (!s) {
  					s = e;
  				}
  				break;
  		}
  		e++;
  	}
  	if (s) {
  		filename = estrndup(s, e-s);
  		if (!is_glob) {
  			ffi = zend_ffi_load(filename, 1);
  			efree(filename);
  			if (!ffi) {
  				return FAILURE;
  			}
  			efree(ffi);
  		} else {
  			int ret = zend_ffi_preload_glob(filename);
  			efree(filename);
  			if (ret != SUCCESS) {
  				return FAILURE;
  			}
  		}
  	}
  
  	return SUCCESS;
  }
  /* }}} */
  
  /* {{{ ZEND_MINIT_FUNCTION
   */
  ZEND_MINIT_FUNCTION(ffi)
  {
  	zend_class_entry ce;
  
  	REGISTER_INI_ENTRIES();
  
  	FFI_G(is_cli) = strcmp(sapi_module.name, "cli") == 0;
  
  	INIT_NS_CLASS_ENTRY(ce, "FFI", "Exception", NULL);
  	zend_ffi_exception_ce = zend_register_internal_class_ex(&ce, zend_ce_error);
  
  	INIT_NS_CLASS_ENTRY(ce, "FFI", "ParserException", NULL);
  	zend_ffi_parser_exception_ce = zend_register_internal_class_ex(&ce, zend_ffi_exception_ce);
  	zend_ffi_parser_exception_ce->ce_flags |= ZEND_ACC_FINAL;
  
  	INIT_CLASS_ENTRY(ce, "FFI", zend_ffi_functions);
  	zend_ffi_ce = zend_register_internal_class(&ce);
  	zend_ffi_ce->ce_flags |= ZEND_ACC_FINAL;
  	zend_ffi_ce->create_object = zend_ffi_new;
  	zend_ffi_ce->serialize = zend_class_serialize_deny;
  	zend_ffi_ce->unserialize = zend_class_unserialize_deny;
  
  	memcpy(&zend_ffi_new_fn, zend_hash_str_find_ptr(&zend_ffi_ce->function_table, "new", sizeof("new")-1), sizeof(zend_internal_function));
  	zend_ffi_new_fn.fn_flags &= ~ZEND_ACC_STATIC;
  	memcpy(&zend_ffi_cast_fn, zend_hash_str_find_ptr(&zend_ffi_ce->function_table, "cast", sizeof("cast")-1), sizeof(zend_internal_function));
  	zend_ffi_cast_fn.fn_flags &= ~ZEND_ACC_STATIC;
  	memcpy(&zend_ffi_type_fn, zend_hash_str_find_ptr(&zend_ffi_ce->function_table, "type", sizeof("type")-1), sizeof(zend_internal_function));
  	zend_ffi_type_fn.fn_flags &= ~ZEND_ACC_STATIC;
  
  	memcpy(&zend_ffi_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
  	zend_ffi_handlers.get_constructor      = zend_fake_get_constructor;
  	zend_ffi_handlers.free_obj             = zend_ffi_free_obj;
  	zend_ffi_handlers.clone_obj            = NULL;
  	zend_ffi_handlers.read_property        = zend_ffi_read_var;
  	zend_ffi_handlers.write_property       = zend_ffi_write_var;
  	zend_ffi_handlers.read_dimension       = zend_fake_read_dimension;
  	zend_ffi_handlers.write_dimension      = zend_fake_write_dimension;
  	zend_ffi_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr;
  	zend_ffi_handlers.has_property         = zend_fake_has_property;
  	zend_ffi_handlers.unset_property       = zend_fake_unset_property;
  	zend_ffi_handlers.has_dimension        = zend_fake_has_dimension;
  	zend_ffi_handlers.unset_dimension      = zend_fake_unset_dimension;
  	zend_ffi_handlers.get_method           = zend_ffi_get_func;
  	zend_ffi_handlers.compare              = NULL;
  	zend_ffi_handlers.cast_object          = NULL;
  	zend_ffi_handlers.get_debug_info       = NULL;
  	zend_ffi_handlers.get_closure          = NULL;
  	zend_ffi_handlers.get_properties       = zend_fake_get_properties;
  	zend_ffi_handlers.get_gc               = zend_fake_get_gc;
  
  	INIT_NS_CLASS_ENTRY(ce, "FFI", "CData", NULL);
  	zend_ffi_cdata_ce = zend_register_internal_class(&ce);
  	zend_ffi_cdata_ce->ce_flags |= ZEND_ACC_FINAL;
  	zend_ffi_cdata_ce->create_object = zend_ffi_cdata_new;
  	zend_ffi_cdata_ce->get_iterator = zend_ffi_cdata_get_iterator;
  	zend_ffi_cdata_ce->serialize = zend_class_serialize_deny;
  	zend_ffi_cdata_ce->unserialize = zend_class_unserialize_deny;
  
  	memcpy(&zend_ffi_cdata_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
  	zend_ffi_cdata_handlers.get_constructor      = zend_fake_get_constructor;
  	zend_ffi_cdata_handlers.free_obj             = zend_ffi_cdata_free_obj;
  	zend_ffi_cdata_handlers.clone_obj            = zend_ffi_cdata_clone_obj;
  	zend_ffi_cdata_handlers.read_property        = zend_ffi_cdata_read_field;
  	zend_ffi_cdata_handlers.write_property       = zend_ffi_cdata_write_field;
  	zend_ffi_cdata_handlers.read_dimension       = zend_ffi_cdata_read_dim;
  	zend_ffi_cdata_handlers.write_dimension      = zend_ffi_cdata_write_dim;
  	zend_ffi_cdata_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr;
  	zend_ffi_cdata_handlers.has_property         = zend_fake_has_property;
  	zend_ffi_cdata_handlers.unset_property       = zend_fake_unset_property;
  	zend_ffi_cdata_handlers.has_dimension        = zend_fake_has_dimension;
  	zend_ffi_cdata_handlers.unset_dimension      = zend_fake_unset_dimension;
  	zend_ffi_cdata_handlers.get_method           = zend_fake_get_method;
  	zend_ffi_cdata_handlers.get_class_name       = zend_ffi_cdata_get_class_name;
  	zend_ffi_cdata_handlers.do_operation         = zend_ffi_cdata_do_operation;
  	zend_ffi_cdata_handlers.compare              = zend_ffi_cdata_compare_objects;
  	zend_ffi_cdata_handlers.cast_object          = zend_ffi_cdata_cast_object;
  	zend_ffi_cdata_handlers.count_elements       = zend_ffi_cdata_count_elements;
  	zend_ffi_cdata_handlers.get_debug_info       = zend_ffi_cdata_get_debug_info;
  	zend_ffi_cdata_handlers.get_closure          = zend_ffi_cdata_get_closure;
  	zend_ffi_cdata_handlers.get_properties       = zend_fake_get_properties;
  	zend_ffi_cdata_handlers.get_gc               = zend_fake_get_gc;
  
  	memcpy(&zend_ffi_cdata_value_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
  	zend_ffi_cdata_value_handlers.get_constructor      = zend_fake_get_constructor;
  	zend_ffi_cdata_value_handlers.free_obj             = zend_ffi_cdata_free_obj;
  	zend_ffi_cdata_value_handlers.clone_obj            = zend_ffi_cdata_clone_obj;
  	zend_ffi_cdata_value_handlers.read_property        = zend_ffi_cdata_get;
  	zend_ffi_cdata_value_handlers.write_property       = zend_ffi_cdata_set;
  	zend_ffi_cdata_value_handlers.read_dimension       = zend_fake_read_dimension;
  	zend_ffi_cdata_value_handlers.write_dimension      = zend_fake_write_dimension;
  	zend_ffi_cdata_value_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr;
  	zend_ffi_cdata_value_handlers.has_property         = zend_fake_has_property;
  	zend_ffi_cdata_value_handlers.unset_property       = zend_fake_unset_property;
  	zend_ffi_cdata_value_handlers.has_dimension        = zend_fake_has_dimension;
  	zend_ffi_cdata_value_handlers.unset_dimension      = zend_fake_unset_dimension;
  	zend_ffi_cdata_value_handlers.get_method           = zend_fake_get_method;
  	zend_ffi_cdata_value_handlers.get_class_name       = zend_ffi_cdata_get_class_name;
  	zend_ffi_cdata_value_handlers.compare              = zend_ffi_cdata_compare_objects;
  	zend_ffi_cdata_value_handlers.cast_object          = zend_ffi_cdata_cast_object;
  	zend_ffi_cdata_value_handlers.count_elements       = NULL;
  	zend_ffi_cdata_value_handlers.get_debug_info       = zend_ffi_cdata_get_debug_info;
  	zend_ffi_cdata_value_handlers.get_closure          = NULL;
  	zend_ffi_cdata_value_handlers.get_properties       = zend_fake_get_properties;
  	zend_ffi_cdata_value_handlers.get_gc               = zend_fake_get_gc;
  
  	memcpy(&zend_ffi_cdata_free_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
  	zend_ffi_cdata_free_handlers.get_constructor      = zend_fake_get_constructor;
  	zend_ffi_cdata_free_handlers.free_obj             = zend_ffi_cdata_free_obj;
  	zend_ffi_cdata_free_handlers.clone_obj            = zend_ffi_free_clone_obj;
  	zend_ffi_cdata_free_handlers.read_property        = zend_ffi_free_read_property;
  	zend_ffi_cdata_free_handlers.write_property       = zend_ffi_free_write_property;
  	zend_ffi_cdata_free_handlers.read_dimension       = zend_ffi_free_read_dimension;
  	zend_ffi_cdata_free_handlers.write_dimension      = zend_ffi_free_write_dimension;
  	zend_ffi_cdata_free_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr;
  	zend_ffi_cdata_free_handlers.has_property         = zend_ffi_free_has_property;
  	zend_ffi_cdata_free_handlers.unset_property       = zend_ffi_free_unset_property;
  	zend_ffi_cdata_free_handlers.has_dimension        = zend_ffi_free_has_dimension;
  	zend_ffi_cdata_free_handlers.unset_dimension      = zend_ffi_free_unset_dimension;
  	zend_ffi_cdata_free_handlers.get_method           = zend_fake_get_method;
  	zend_ffi_cdata_free_handlers.get_class_name       = zend_ffi_cdata_get_class_name;
  	zend_ffi_cdata_free_handlers.compare              = zend_ffi_cdata_compare_objects;
  	zend_ffi_cdata_free_handlers.cast_object          = NULL;
  	zend_ffi_cdata_free_handlers.count_elements       = NULL;
  	zend_ffi_cdata_free_handlers.get_debug_info       = zend_ffi_free_get_debug_info;
  	zend_ffi_cdata_free_handlers.get_closure          = NULL;
  	zend_ffi_cdata_free_handlers.get_properties       = zend_fake_get_properties;
  	zend_ffi_cdata_free_handlers.get_gc               = zend_fake_get_gc;
  
  	INIT_NS_CLASS_ENTRY(ce, "FFI", "CType", NULL);
  	zend_ffi_ctype_ce = zend_register_internal_class(&ce);
  	zend_ffi_ctype_ce->ce_flags |= ZEND_ACC_FINAL;
  	zend_ffi_ctype_ce->create_object = zend_ffi_ctype_new;
  	zend_ffi_ctype_ce->serialize = zend_class_serialize_deny;
  	zend_ffi_ctype_ce->unserialize = zend_class_unserialize_deny;
  
  	memcpy(&zend_ffi_ctype_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
  	zend_ffi_ctype_handlers.get_constructor      = zend_fake_get_constructor;
  	zend_ffi_ctype_handlers.free_obj             = zend_ffi_ctype_free_obj;
  	zend_ffi_ctype_handlers.clone_obj            = NULL;
  	zend_ffi_ctype_handlers.read_property        = zend_fake_read_property;
  	zend_ffi_ctype_handlers.write_property       = zend_fake_write_property;
  	zend_ffi_ctype_handlers.read_dimension       = zend_fake_read_dimension;
  	zend_ffi_ctype_handlers.write_dimension      = zend_fake_write_dimension;
  	zend_ffi_ctype_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr;
  	zend_ffi_ctype_handlers.has_property         = zend_fake_has_property;
  	zend_ffi_ctype_handlers.unset_property       = zend_fake_unset_property;
  	zend_ffi_ctype_handlers.has_dimension        = zend_fake_has_dimension;
  	zend_ffi_ctype_handlers.unset_dimension      = zend_fake_unset_dimension;
  	zend_ffi_ctype_handlers.get_method           = zend_fake_get_method;
  	zend_ffi_ctype_handlers.get_class_name       = zend_ffi_ctype_get_class_name;
  	zend_ffi_ctype_handlers.compare              = zend_ffi_ctype_compare_objects;
  	zend_ffi_ctype_handlers.cast_object          = NULL;
  	zend_ffi_ctype_handlers.count_elements       = NULL;
  	zend_ffi_ctype_handlers.get_debug_info       = zend_ffi_ctype_get_debug_info;
  	zend_ffi_ctype_handlers.get_closure          = NULL;
  	zend_ffi_ctype_handlers.get_properties       = zend_fake_get_properties;
  	zend_ffi_ctype_handlers.get_gc               = zend_fake_get_gc;
  
  	if (FFI_G(preload)) {
  		if (zend_ffi_preload(FFI_G(preload)) != SUCCESS) {
  			return FAILURE;
  		}
  	}
  
  	return SUCCESS;
  }
  /* }}} */
  
  /* {{{ ZEND_RSHUTDOWN_FUNCTION
   */
  ZEND_RSHUTDOWN_FUNCTION(ffi)
  {
  	if (FFI_G(callbacks)) {
  		zend_hash_destroy(FFI_G(callbacks));
  		efree(FFI_G(callbacks));
  		FFI_G(callbacks) = NULL;
  	}
  	if (FFI_G(weak_types)) {
  #if 0
  		fprintf(stderr, "WeakTypes: %d\n", zend_hash_num_elements(FFI_G(weak_types)));
  #endif
  		zend_hash_destroy(FFI_G(weak_types));
  		efree(FFI_G(weak_types));
  		FFI_G(weak_types) = NULL;
  	}
  	return SUCCESS;
  }
  /* }}} */
  
  /* {{{ ZEND_MINFO_FUNCTION
   */
  ZEND_MINFO_FUNCTION(ffi)
  {
  	php_info_print_table_start();
  	php_info_print_table_header(2, "FFI support", "enabled");
  	php_info_print_table_end();
  
  	DISPLAY_INI_ENTRIES();
  }
  /* }}} */
  
  static const zend_ffi_type zend_ffi_type_void = {.kind=ZEND_FFI_TYPE_VOID, .size=1, .align=1};
  static const zend_ffi_type zend_ffi_type_char = {.kind=ZEND_FFI_TYPE_CHAR, .size=1, .align=_Alignof(char)};
  static const zend_ffi_type zend_ffi_type_bool = {.kind=ZEND_FFI_TYPE_BOOL, .size=1, .align=_Alignof(uint8_t)};
  static const zend_ffi_type zend_ffi_type_sint8 = {.kind=ZEND_FFI_TYPE_SINT8, .size=1, .align=_Alignof(int8_t)};
  static const zend_ffi_type zend_ffi_type_uint8 = {.kind=ZEND_FFI_TYPE_UINT8, .size=1, .align=_Alignof(uint8_t)};
  static const zend_ffi_type zend_ffi_type_sint16 = {.kind=ZEND_FFI_TYPE_SINT16, .size=2, .align=_Alignof(int16_t)};
  static const zend_ffi_type zend_ffi_type_uint16 = {.kind=ZEND_FFI_TYPE_UINT16, .size=2, .align=_Alignof(uint16_t)};
  static const zend_ffi_type zend_ffi_type_sint32 = {.kind=ZEND_FFI_TYPE_SINT32, .size=4, .align=_Alignof(int32_t)};
  static const zend_ffi_type zend_ffi_type_uint32 = {.kind=ZEND_FFI_TYPE_UINT32, .size=4, .align=_Alignof(uint32_t)};
  static const zend_ffi_type zend_ffi_type_sint64 = {.kind=ZEND_FFI_TYPE_SINT64, .size=8, .align=_Alignof(int64_t)};
  static const zend_ffi_type zend_ffi_type_uint64 = {.kind=ZEND_FFI_TYPE_UINT64, .size=8, .align=_Alignof(uint64_t)};
  static const zend_ffi_type zend_ffi_type_float = {.kind=ZEND_FFI_TYPE_FLOAT, .size=sizeof(float), .align=_Alignof(float)};
  static const zend_ffi_type zend_ffi_type_double = {.kind=ZEND_FFI_TYPE_DOUBLE, .size=sizeof(double), .align=_Alignof(double)};
  
  #ifdef HAVE_LONG_DOUBLE
  static const zend_ffi_type zend_ffi_type_long_double = {.kind=ZEND_FFI_TYPE_LONGDOUBLE, .size=sizeof(long double), .align=_Alignof(long double)};
  #endif
  
  static const zend_ffi_type zend_ffi_type_ptr = {.kind=ZEND_FFI_TYPE_POINTER, .size=sizeof(void*), .align=_Alignof(void*), .pointer.type = (zend_ffi_type*)&zend_ffi_type_void};
  
  const struct {
  	const char *name;
  	const zend_ffi_type *type;
  } zend_ffi_types[] = {
  	{"void",        &zend_ffi_type_void},
  	{"char",        &zend_ffi_type_char},
  	{"bool",        &zend_ffi_type_bool},
  	{"int8_t",      &zend_ffi_type_sint8},
  	{"uint8_t",     &zend_ffi_type_uint8},
  	{"int16_t",     &zend_ffi_type_sint16},
  	{"uint16_t",    &zend_ffi_type_uint16},
  	{"int32_t",     &zend_ffi_type_sint32},
  	{"uint32_t",    &zend_ffi_type_uint32},
  	{"int64_t",     &zend_ffi_type_sint64},
  	{"uint64_t",    &zend_ffi_type_uint64},
  	{"float",       &zend_ffi_type_float},
  	{"double",      &zend_ffi_type_double},
  #ifdef HAVE_LONG_DOUBLE
  	{"long double", &zend_ffi_type_long_double},
  #endif
  #if SIZEOF_SIZE_T == 4
  	{"uintptr_t",  &zend_ffi_type_uint32},
  	{"intptr_t",   &zend_ffi_type_sint32},
  	{"size_t",     &zend_ffi_type_uint32},
  	{"ssize_t",    &zend_ffi_type_sint32},
  	{"ptrdiff_t",  &zend_ffi_type_sint32},
  #else
  	{"uintptr_t",  &zend_ffi_type_uint64},
  	{"intptr_t",   &zend_ffi_type_sint64},
  	{"size_t",     &zend_ffi_type_uint64},
  	{"ssize_t",    &zend_ffi_type_sint64},
  	{"ptrdiff_t",  &zend_ffi_type_sint64},
  #endif
  #if SIZEOF_OFF_T == 4
  	{"off_t",      &zend_ffi_type_sint32},
  #else
  	{"off_t",      &zend_ffi_type_sint64},
  #endif
  
  	{"va_list",           &zend_ffi_type_ptr},
  	{"__builtin_va_list", &zend_ffi_type_ptr},
  	{"__gnuc_va_list",    &zend_ffi_type_ptr},
  };
  
  /* {{{ ZEND_GINIT_FUNCTION
   */
  static ZEND_GINIT_FUNCTION(ffi)
  {
  	size_t i;
  
  #if defined(COMPILE_DL_FFI) && defined(ZTS)
  	ZEND_TSRMLS_CACHE_UPDATE();
  #endif
  	memset(ffi_globals, 0, sizeof(*ffi_globals));
  	zend_hash_init(&ffi_globals->types, 0, NULL, NULL, 1);
  	for (i = 0; i < sizeof(zend_ffi_types)/sizeof(zend_ffi_types[0]); i++) {
  		zend_hash_str_add_new_ptr(&ffi_globals->types, zend_ffi_types[i].name, strlen(zend_ffi_types[i].name), (void*)zend_ffi_types[i].type);
  	}
  }
  /* }}} */
  
  /* {{{ ZEND_GINIT_FUNCTION
   */
  static ZEND_GSHUTDOWN_FUNCTION(ffi)
  {
  	if (ffi_globals->scopes) {
  		zend_hash_destroy(ffi_globals->scopes);
  		free(ffi_globals->scopes);
  	}
  	zend_hash_destroy(&ffi_globals->types);
  }
  /* }}} */
  
  /* {{{ ffi_module_entry
   */
  zend_module_entry ffi_module_entry = {
  	STANDARD_MODULE_HEADER,
  	"FFI",					/* Extension name */
  	NULL,					/* zend_function_entry */
  	ZEND_MINIT(ffi),		/* ZEND_MINIT - Module initialization */
  	NULL,					/* ZEND_MSHUTDOWN - Module shutdown */
  	NULL,					/* ZEND_RINIT - Request initialization */
  	ZEND_RSHUTDOWN(ffi),	/* ZEND_RSHUTDOWN - Request shutdown */
  	ZEND_MINFO(ffi),		/* ZEND_MINFO - Module info */
  	PHP_VERSION,			/* Version */
  	ZEND_MODULE_GLOBALS(ffi),
  	ZEND_GINIT(ffi),
  	ZEND_GSHUTDOWN(ffi),
  	NULL,
  	STANDARD_MODULE_PROPERTIES_EX
  };
  /* }}} */
  
  #ifdef COMPILE_DL_FFI
  # ifdef ZTS
  ZEND_TSRMLS_CACHE_DEFINE()
  # endif
  ZEND_GET_MODULE(ffi)
  #endif
  
  /* parser callbacks */
  void zend_ffi_parser_error(const char *format, ...) /* {{{ */
  {
  	va_list va;
  	char *message = NULL;
  
  	va_start(va, format);
  	zend_vspprintf(&message, 0, format, va);
  
  	if (EG(current_execute_data)) {
  		zend_throw_exception(zend_ffi_parser_exception_ce, message, 0);
  	} else {
  		zend_error(E_WARNING, "FFI Parser: %s", message);
  	}
  
  	efree(message);
  	va_end(va);
  
  	LONGJMP(FFI_G(bailout), FAILURE);
  }
  /* }}} */
  
  static void zend_ffi_finalize_type(zend_ffi_dcl *dcl) /* {{{ */
  {
  	if (!dcl->type) {
  		switch (dcl->flags & ZEND_FFI_DCL_TYPE_SPECIFIERS) {
  			case ZEND_FFI_DCL_VOID:
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_void;
  				break;
  			case ZEND_FFI_DCL_CHAR:
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_char;
  				break;
  			case ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_SIGNED:
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_sint8;
  				break;
  			case ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_UNSIGNED:
  			case ZEND_FFI_DCL_BOOL:
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_uint8;
  				break;
  			case ZEND_FFI_DCL_SHORT:
  			case ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_SIGNED:
  			case ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_INT:
  			case ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_SIGNED|ZEND_FFI_DCL_INT:
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_sint16;
  				break;
  			case ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_UNSIGNED:
  			case ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_UNSIGNED|ZEND_FFI_DCL_INT:
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_uint16;
  				break;
  			case ZEND_FFI_DCL_INT:
  			case ZEND_FFI_DCL_SIGNED:
  			case ZEND_FFI_DCL_SIGNED|ZEND_FFI_DCL_INT:
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_sint32;
  				break;
  			case ZEND_FFI_DCL_UNSIGNED:
  			case ZEND_FFI_DCL_UNSIGNED|ZEND_FFI_DCL_INT:
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_uint32;
  				break;
  			case ZEND_FFI_DCL_LONG:
  			case ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_SIGNED:
  			case ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_INT:
  			case ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_SIGNED|ZEND_FFI_DCL_INT:
  				if (sizeof(long) == 4) {
  					dcl->type = (zend_ffi_type*)&zend_ffi_type_sint32;
  				} else {
  					dcl->type = (zend_ffi_type*)&zend_ffi_type_sint64;
  				}
  				break;
  			case ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_UNSIGNED:
  			case ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_UNSIGNED|ZEND_FFI_DCL_INT:
  				if (sizeof(long) == 4) {
  					dcl->type = (zend_ffi_type*)&zend_ffi_type_uint32;
  				} else {
  					dcl->type = (zend_ffi_type*)&zend_ffi_type_uint64;
  				}
  				break;
  			case ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_LONG:
  			case ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_SIGNED:
  			case ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_INT:
  			case ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_SIGNED|ZEND_FFI_DCL_INT:
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_sint64;
  				break;
  			case ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_UNSIGNED:
  			case ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_UNSIGNED|ZEND_FFI_DCL_INT:
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_uint64;
  				break;
  			case ZEND_FFI_DCL_FLOAT:
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_float;
  				break;
  			case ZEND_FFI_DCL_DOUBLE:
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_double;
  				break;
  			case ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_DOUBLE:
  #ifdef _WIN32
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_double;
  #else
  				dcl->type = (zend_ffi_type*)&zend_ffi_type_long_double;
  #endif
  				break;
  			case ZEND_FFI_DCL_FLOAT|ZEND_FFI_DCL_COMPLEX:
  			case ZEND_FFI_DCL_DOUBLE|ZEND_FFI_DCL_COMPLEX:
  			case ZEND_FFI_DCL_DOUBLE|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_COMPLEX:
  				zend_ffi_parser_error("unsupported type '_Complex' at line %d", FFI_G(line));
  				break;
  			default:
  				zend_ffi_parser_error("unsupported type specifier combination at line %d", FFI_G(line));
  				break;
  		}
  		dcl->flags &= ~ZEND_FFI_DCL_TYPE_SPECIFIERS;
  		dcl->flags |= ZEND_FFI_DCL_TYPEDEF_NAME;
  	}
  }
  /* }}} */
  
  int zend_ffi_is_typedef_name(const char *name, size_t name_len) /* {{{ */
  {
  	zend_ffi_symbol *sym;
  	zend_ffi_type *type;
  
  	if (FFI_G(symbols)) {
  		sym = zend_hash_str_find_ptr(FFI_G(symbols), name, name_len);
  		if (sym) {
  			return (sym->kind == ZEND_FFI_SYM_TYPE);
  		}
  	}
  	type = zend_hash_str_find_ptr(&FFI_G(types), name, name_len);
  	if (type) {
  		return 1;
  	}
  	return 0;
  }
  /* }}} */
  
  void zend_ffi_resolve_typedef(const char *name, size_t name_len, zend_ffi_dcl *dcl) /* {{{ */
  {
  	zend_ffi_symbol *sym;
  	zend_ffi_type *type;
  
  	if (FFI_G(symbols)) {
  		sym = zend_hash_str_find_ptr(FFI_G(symbols), name, name_len);
  		if (sym && sym->kind == ZEND_FFI_SYM_TYPE) {
  			dcl->type = ZEND_FFI_TYPE(sym->type);;
  			if (sym->is_const) {
  				dcl->attr |= ZEND_FFI_ATTR_CONST;
  			}
  			return;
  		}
  	}
  	type = zend_hash_str_find_ptr(&FFI_G(types), name, name_len);
  	if (type) {
  		dcl->type = type;
  		return;
  	}
  	zend_ffi_parser_error("undefined C type '%.*s' at line %d", name_len, name, FFI_G(line));
  }
  /* }}} */
  
  void zend_ffi_resolve_const(const char *name, size_t name_len, zend_ffi_val *val) /* {{{ */
  {
  	zend_ffi_symbol *sym;
  
  	if (UNEXPECTED(FFI_G(attribute_parsing))) {
  		val->kind = ZEND_FFI_VAL_NAME;
  		val->str = name;
  		val->len = name_len;
  		return;
  	} else if (FFI_G(symbols)) {
  		sym = zend_hash_str_find_ptr(FFI_G(symbols), name, name_len);
  		if (sym && sym->kind == ZEND_FFI_SYM_CONST) {
  			val->i64 = sym->value;
  			switch (sym->type->kind) {
  				case ZEND_FFI_TYPE_SINT8:
  				case ZEND_FFI_TYPE_SINT16:
  				case ZEND_FFI_TYPE_SINT32:
  					val->kind = ZEND_FFI_VAL_INT32;
  					break;
  				case ZEND_FFI_TYPE_SINT64:
  					val->kind = ZEND_FFI_VAL_INT64;
  					break;
  				case ZEND_FFI_TYPE_UINT8:
  				case ZEND_FFI_TYPE_UINT16:
  				case ZEND_FFI_TYPE_UINT32:
  					val->kind = ZEND_FFI_VAL_UINT32;
  					break;
  				case ZEND_FFI_TYPE_UINT64:
  					val->kind = ZEND_FFI_VAL_UINT64;
  					break;
  				default:
  					ZEND_ASSERT(0);
  			}
  			return;
  		}
  	}
  	val->kind = ZEND_FFI_VAL_ERROR;
  }
  /* }}} */
  
  void zend_ffi_make_enum_type(zend_ffi_dcl *dcl) /* {{{ */
  {
  	zend_ffi_type *type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent));
  	type->kind = ZEND_FFI_TYPE_ENUM;
  	type->attr = FFI_G(default_type_attr) | (dcl->attr & ZEND_FFI_ENUM_ATTRS);
  	type->enumeration.tag_name = NULL;
  	if (type->attr & ZEND_FFI_ATTR_PACKED) {
  		type->size = zend_ffi_type_uint8.size;
  		type->align = zend_ffi_type_uint8.align;
  		type->enumeration.kind = ZEND_FFI_TYPE_UINT8;
  	} else {
  		type->size = zend_ffi_type_uint32.size;
  		type->align = zend_ffi_type_uint32.align;
  		type->enumeration.kind = ZEND_FFI_TYPE_UINT32;
  	}
  	dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type);
  	dcl->attr &= ~ZEND_FFI_ENUM_ATTRS;
  }
  /* }}} */
  
  void zend_ffi_add_enum_val(zend_ffi_dcl *enum_dcl, const char *name, size_t name_len, zend_ffi_val *val, int64_t *min, int64_t *max, int64_t *last) /* {{{ */
  {
  	zend_ffi_symbol *sym;
  	const zend_ffi_type *sym_type;
  	int64_t value;
  	zend_ffi_type *enum_type = ZEND_FFI_TYPE(enum_dcl->type);
  	zend_bool overflow = 0;
  	zend_bool is_signed =
  		(enum_type->enumeration.kind == ZEND_FFI_TYPE_SINT8 ||
  		 enum_type->enumeration.kind == ZEND_FFI_TYPE_SINT16 ||
  		 enum_type->enumeration.kind == ZEND_FFI_TYPE_SINT32 ||
  		 enum_type->enumeration.kind == ZEND_FFI_TYPE_SINT64);
  
  	ZEND_ASSERT(enum_type && enum_type->kind == ZEND_FFI_TYPE_ENUM);
  	if (val->kind == ZEND_FFI_VAL_EMPTY) {
  		if (is_signed) {
  			if (*last == 0x7FFFFFFFFFFFFFFFLL) {
  				overflow = 1;
  			}
  		} else {
  			if ((*min != 0 || *max != 0)
  			 && (uint64_t)*last == 0xFFFFFFFFFFFFFFFFULL) {
  				overflow = 1;
  			}
  		}
  		value = *last + 1;
  	} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  		if (!is_signed && val->ch < 0) {
  			if ((uint64_t)*max > 0x7FFFFFFFFFFFFFFFULL) {
  				overflow = 1;
  			} else {
  				is_signed = 1;
  			}
  		}
  		value = val->ch;
  	} else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) {
  		if (!is_signed && val->i64 < 0) {
  			if ((uint64_t)*max > 0x7FFFFFFFFFFFFFFFULL) {
  				overflow = 1;
  			} else {
  				is_signed = 1;
  			}
  		}
  		value = val->i64;
  	} else if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) {
  		if (is_signed && val->u64 > 0x7FFFFFFFFFFFFFFFULL) {
  			overflow = 1;
  		}
  		value = val->u64;
  	} else {
  		zend_ffi_parser_error("enumerator value '%.*s' must be an integer at line %d", name_len, name, FFI_G(line));
  		return;
  	}
  
  	if (overflow) {
  		zend_ffi_parser_error("overflow in enumeration values '%.*s' at line %d", name_len, name, FFI_G(line));
  		return;
  	}
  
  	if (is_signed) {
  		*min = MIN(*min, value);
  		*max = MAX(*max, value);
  		if ((enum_type->attr & ZEND_FFI_ATTR_PACKED)
  		 && *min >= -0x7FLL-1 && *max <= 0x7FLL) {
  			sym_type = &zend_ffi_type_sint8;
  		} else if ((enum_type->attr & ZEND_FFI_ATTR_PACKED)
  		 && *min >= -0x7FFFLL-1 && *max <= 0x7FFFLL) {
  			sym_type = &zend_ffi_type_sint16;
  		} else if (*min >= -0x7FFFFFFFLL-1 && *max <= 0x7FFFFFFFLL) {
  			sym_type = &zend_ffi_type_sint32;
  		} else {
  			sym_type = &zend_ffi_type_sint64;
  		}
  	} else {
  		*min = MIN((uint64_t)*min, (uint64_t)value);
  		*max = MAX((uint64_t)*max, (uint64_t)value);
  		if ((enum_type->attr & ZEND_FFI_ATTR_PACKED)
  		 && (uint64_t)*max <= 0xFFULL) {
  			sym_type = &zend_ffi_type_uint8;
  		} else if ((enum_type->attr & ZEND_FFI_ATTR_PACKED)
  		 && (uint64_t)*max <= 0xFFFFULL) {
  			sym_type = &zend_ffi_type_uint16;
  		} else if ((uint64_t)*max <= 0xFFFFFFFFULL) {
  			sym_type = &zend_ffi_type_uint32;
  		} else {
  			sym_type = &zend_ffi_type_uint64;
  		}
  	}
  	enum_type->enumeration.kind = sym_type->kind;
  	enum_type->size = sym_type->size;
  	enum_type->align = sym_type->align;
  	*last = value;
  
  	if (!FFI_G(symbols)) {
  		FFI_G(symbols) = pemalloc(sizeof(HashTable), FFI_G(persistent));
  		zend_hash_init(FFI_G(symbols), 0, NULL, FFI_G(persistent) ? zend_ffi_symbol_hash_persistent_dtor : zend_ffi_symbol_hash_dtor, FFI_G(persistent));
  	}
  	sym = zend_hash_str_find_ptr(FFI_G(symbols), name, name_len);
  	if (sym) {
  		zend_ffi_parser_error("redeclaration of '%.*s' at line %d", name_len, name, FFI_G(line));
  	} else {
  		sym = pemalloc(sizeof(zend_ffi_symbol), FFI_G(persistent));
  		sym->kind  = ZEND_FFI_SYM_CONST;
  		sym->type  = (zend_ffi_type*)sym_type;
  		sym->value = value;
  		zend_hash_str_add_new_ptr(FFI_G(symbols), name, name_len, sym);
  	}
  }
  /* }}} */
  
  void zend_ffi_make_struct_type(zend_ffi_dcl *dcl) /* {{{ */
  {
  	zend_ffi_type *type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent));
  	type->kind = ZEND_FFI_TYPE_STRUCT;
  	type->attr = FFI_G(default_type_attr) | (dcl->attr & ZEND_FFI_STRUCT_ATTRS);
  	type->size = 0;
  	type->align = dcl->align > 1 ? dcl->align : 1;
  	if (dcl->flags & ZEND_FFI_DCL_UNION) {
  		type->attr |= ZEND_FFI_ATTR_UNION;
  	}
  	dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type);
  	type->record.tag_name = NULL;
  	zend_hash_init(&type->record.fields, 0, NULL, FFI_G(persistent) ? zend_ffi_field_hash_persistent_dtor :zend_ffi_field_hash_dtor, FFI_G(persistent));
  	dcl->attr &= ~ZEND_FFI_STRUCT_ATTRS;
  	dcl->align = 0;
  }
  /* }}} */
  
  static int zend_ffi_validate_prev_field_type(zend_ffi_type *struct_type) /* {{{ */
  {
  	if (zend_hash_num_elements(&struct_type->record.fields) > 0) {
  		zend_ffi_field *field = NULL;
  
  		ZEND_HASH_REVERSE_FOREACH_PTR(&struct_type->record.fields, field) {
  			break;
  		} ZEND_HASH_FOREACH_END();
  		if (ZEND_FFI_TYPE(field->type)->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY) {
  			zend_ffi_throw_parser_error("flexible array member not at end of struct at line %d", FFI_G(line));
  			return FAILURE;
  		}
  	}
  	return SUCCESS;
  }
  /* }}} */
  
  static int zend_ffi_validate_field_type(zend_ffi_type *type, zend_ffi_type *struct_type) /* {{{ */
  {
  	if (type == struct_type) {
  		zend_ffi_throw_parser_error("struct/union can't contain an instance of itself at line %d", FFI_G(line));
  		return FAILURE;
  	} else if (zend_ffi_validate_var_type(type, 1) != SUCCESS) {
  		return FAILURE;
  	} else if (struct_type->attr & ZEND_FFI_ATTR_UNION) {
  		if (type->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY) {
  			zend_ffi_throw_parser_error("flexible array member in union at line %d", FFI_G(line));
  			return FAILURE;
  		}
  	}
  	return zend_ffi_validate_prev_field_type(struct_type);
  }
  /* }}} */
  
  void zend_ffi_add_field(zend_ffi_dcl *struct_dcl, const char *name, size_t name_len, zend_ffi_dcl *field_dcl) /* {{{ */
  {
  	zend_ffi_field *field;
  	zend_ffi_type *struct_type = ZEND_FFI_TYPE(struct_dcl->type);
  	zend_ffi_type *field_type;
  
  	ZEND_ASSERT(struct_type && struct_type->kind == ZEND_FFI_TYPE_STRUCT);
  	zend_ffi_finalize_type(field_dcl);
  	field_type = ZEND_FFI_TYPE(field_dcl->type);
  	if (zend_ffi_validate_field_type(field_type, struct_type) != SUCCESS) {
  		zend_ffi_cleanup_dcl(field_dcl);
  		LONGJMP(FFI_G(bailout), FAILURE);
  	}
  
  	field = pemalloc(sizeof(zend_ffi_field), FFI_G(persistent));
  	if (!(struct_type->attr & ZEND_FFI_ATTR_PACKED) && !(field_dcl->attr & ZEND_FFI_ATTR_PACKED)) {
  		struct_type->align = MAX(struct_type->align, MAX(field_type->align, field_dcl->align));
  	}
  	if (struct_type->attr & ZEND_FFI_ATTR_UNION) {
  		field->offset = 0;
  		struct_type->size = MAX(struct_type->size, field_type->size);
  	} else {
  		if (!(struct_type->attr & ZEND_FFI_ATTR_PACKED) && !(field_dcl->attr & ZEND_FFI_ATTR_PACKED)) {
  			uint32_t field_align = MAX(field_type->align, field_dcl->align);
  			struct_type->size = ((struct_type->size + (field_align - 1)) / field_align) * field_align;
  		}
  		field->offset = struct_type->size;
  		struct_type->size += field_type->size;
  	}
  	field->type = field_dcl->type;
  	field->is_const = (zend_bool)(field_dcl->attr & ZEND_FFI_ATTR_CONST);
  	field->is_nested = 0;
  	field->first_bit = 0;
  	field->bits = 0;
  	field_dcl->type = field_type; /* reset "owned" flag */
  
  	if (!zend_hash_str_add_ptr(&struct_type->record.fields, name, name_len, field)) {
  		zend_ffi_type_dtor(field->type);
  		pefree(field, FFI_G(persistent));
  		zend_ffi_parser_error("duplicate field name '%.*s' at line %d", name_len, name, FFI_G(line));
  	}
  }
  /* }}} */
  
  void zend_ffi_add_anonymous_field(zend_ffi_dcl *struct_dcl, zend_ffi_dcl *field_dcl) /* {{{ */
  {
  	zend_ffi_type *struct_type = ZEND_FFI_TYPE(struct_dcl->type);
  	zend_ffi_type *field_type;
  	zend_ffi_field *field;
  	zend_string *key;
  
  	ZEND_ASSERT(struct_type && struct_type->kind == ZEND_FFI_TYPE_STRUCT);
  	zend_ffi_finalize_type(field_dcl);
  	field_type = ZEND_FFI_TYPE(field_dcl->type);
  	if (field_type->kind != ZEND_FFI_TYPE_STRUCT) {
  		zend_ffi_cleanup_dcl(field_dcl);
  		zend_ffi_parser_error("declaration does not declare anything at line %d", FFI_G(line));
  		return;
  	}
  
  	if (!(struct_type->attr & ZEND_FFI_ATTR_PACKED) && !(field_dcl->attr & ZEND_FFI_ATTR_PACKED)) {
  		struct_type->align = MAX(struct_type->align, MAX(field_type->align, field_dcl->align));
  	}
  	if (!(struct_type->attr & ZEND_FFI_ATTR_UNION)) {
  		if (zend_ffi_validate_prev_field_type(struct_type) != SUCCESS) {
  			zend_ffi_cleanup_dcl(field_dcl);
  			LONGJMP(FFI_G(bailout), FAILURE);
  		}
  		if (!(struct_type->attr & ZEND_FFI_ATTR_PACKED) && !(field_dcl->attr & ZEND_FFI_ATTR_PACKED)) {
  			uint32_t field_align = MAX(field_type->align, field_dcl->align);
  			struct_type->size = ((struct_type->size + (field_align - 1)) / field_align) * field_align;
  		}
  	}
  
  	ZEND_HASH_FOREACH_STR_KEY_PTR(&field_type->record.fields, key, field) {
  		zend_ffi_field *new_field = pemalloc(sizeof(zend_ffi_field), FFI_G(persistent));
  
  		if (struct_type->attr & ZEND_FFI_ATTR_UNION) {
  			new_field->offset = field->offset;
  		} else {
  			new_field->offset = struct_type->size + field->offset;
  		}
  		new_field->type = field->type;
  		new_field->is_const = field->is_const;
  		new_field->is_nested = 1;
  		new_field->first_bit = field->first_bit;
  		new_field->bits = field->bits;
  		field->type = ZEND_FFI_TYPE(field->type); /* reset "owned" flag */
  
  		if (key) {
  			if (!zend_hash_add_ptr(&struct_type->record.fields, key, new_field)) {
  				zend_ffi_type_dtor(new_field->type);
  				pefree(new_field, FFI_G(persistent));
  				zend_ffi_parser_error("duplicate field name '%s' at line %d", ZSTR_VAL(key), FFI_G(line));
  				return;
  			}
  		} else {
  			zend_hash_next_index_insert_ptr(&struct_type->record.fields, field);
  		}
  	} ZEND_HASH_FOREACH_END();
  
  	if (struct_type->attr & ZEND_FFI_ATTR_UNION) {
  		struct_type->size = MAX(struct_type->size, field_type->size);
  	} else {
  		struct_type->size += field_type->size;
  	}
  
  	zend_ffi_type_dtor(field_dcl->type);
  	field_dcl->type = NULL;
  }
  /* }}} */
  
  void zend_ffi_add_bit_field(zend_ffi_dcl *struct_dcl, const char *name, size_t name_len, zend_ffi_dcl *field_dcl, zend_ffi_val *bits) /* {{{ */
  {
  	zend_ffi_type *struct_type = ZEND_FFI_TYPE(struct_dcl->type);
  	zend_ffi_type *field_type;
  	zend_ffi_field *field;
  
  	ZEND_ASSERT(struct_type && struct_type->kind == ZEND_FFI_TYPE_STRUCT);
  	zend_ffi_finalize_type(field_dcl);
  	field_type = ZEND_FFI_TYPE(field_dcl->type);
  	if (zend_ffi_validate_field_type(field_type, struct_type) != SUCCESS) {
  		zend_ffi_cleanup_dcl(field_dcl);
  		LONGJMP(FFI_G(bailout), FAILURE);
  	}
  
  	if (field_type->kind < ZEND_FFI_TYPE_UINT8 || field_type->kind > ZEND_FFI_TYPE_BOOL) {
  		zend_ffi_cleanup_dcl(field_dcl);
  		zend_ffi_parser_error("wrong type of bit field '%.*s' at line %d", name ? name_len : sizeof("<anonymous>")-1, name ? name : "<anonymous>", FFI_G(line));
  	}
  
  	if (bits->kind == ZEND_FFI_VAL_INT32 || bits->kind == ZEND_FFI_VAL_INT64) {
  		if (bits->i64 < 0) {
  			zend_ffi_cleanup_dcl(field_dcl);
  			zend_ffi_parser_error("negative width in bit-field '%.*s' at line %d", name ? name_len : sizeof("<anonymous>")-1, name ? name : "<anonymous>", FFI_G(line));
  		} else if (bits->i64 == 0) {
  			zend_ffi_cleanup_dcl(field_dcl);
  			if (name) {
  				zend_ffi_parser_error("zero width in bit-field '%.*s' at line %d", name ? name_len : sizeof("<anonymous>")-1, name ? name : "<anonymous>", FFI_G(line));
  			}
  			return;
  		} else if (bits->i64 > field_type->size * 8) {
  			zend_ffi_cleanup_dcl(field_dcl);
  			zend_ffi_parser_error("width of '%.*s' exceeds its type at line %d", name ? name_len : sizeof("<anonymous>")-1, name ? name : "<anonymous>", FFI_G(line));
  		}
  	} else if (bits->kind == ZEND_FFI_VAL_UINT32 || bits->kind == ZEND_FFI_VAL_UINT64) {
  		if (bits->u64 == 0) {
  			zend_ffi_cleanup_dcl(field_dcl);
  			if (name) {
  				zend_ffi_parser_error("zero width in bit-field '%.*s' at line %d", name ? name_len : sizeof("<anonymous>")-1, name ? name : "<anonymous>", FFI_G(line));
  			}
  			return;
  		} else if (bits->u64 > field_type->size * 8) {
  			zend_ffi_cleanup_dcl(field_dcl);
  			zend_ffi_parser_error("width of '%.*s' exceeds its type at line %d", name ? name_len : sizeof("<anonymous>")-1, name ? name : "<anonymous>", FFI_G(line));
  		}
  	} else {
  		zend_ffi_cleanup_dcl(field_dcl);
  		zend_ffi_parser_error("bit field '%.*s' width not an integer constant at line %d", name ? name_len : sizeof("<anonymous>")-1, name ? name : "<anonymous>", FFI_G(line));
  	}
  
  	field = pemalloc(sizeof(zend_ffi_field), FFI_G(persistent));
  	if (!(struct_type->attr & ZEND_FFI_ATTR_PACKED)) {
  		struct_type->align = MAX(struct_type->align, sizeof(uint32_t));
  	}
  	if (struct_type->attr & ZEND_FFI_ATTR_UNION) {
  		field->offset = 0;
  		field->first_bit = 0;
  		field->bits = bits->u64;
  		if (struct_type->attr & ZEND_FFI_ATTR_PACKED) {
  			struct_type->size = MAX(struct_type->size, (bits->u64 + 7) / 8);
  		} else {
  			struct_type->size = MAX(struct_type->size, ((bits->u64 + 31) / 32) * 4);
  		}
  	} else {
  		zend_ffi_field *prev_field = NULL;
  
  		if (zend_hash_num_elements(&struct_type->record.fields) > 0) {
  			ZEND_HASH_REVERSE_FOREACH_PTR(&struct_type->record.fields, prev_field) {
  				break;
  			} ZEND_HASH_FOREACH_END();
  		}
  		if (prev_field && prev_field->bits) {
  			field->offset = prev_field->offset;
  			field->first_bit = prev_field->first_bit + prev_field->bits;
  			field->bits = bits->u64;
  		} else {
  			field->offset = struct_type->size;
  			field->first_bit = 0;
  			field->bits = bits->u64;
  		}
  		if (struct_type->attr & ZEND_FFI_ATTR_PACKED) {
  			struct_type->size = field->offset + ((field->first_bit + field->bits) + 7) / 8;
  		} else {
  			struct_type->size = field->offset + (((field->first_bit + field->bits) + 31) / 32) * 4;
  		}
  	}
  	field->type = field_dcl->type;
  	field->is_const = (zend_bool)(field_dcl->attr & ZEND_FFI_ATTR_CONST);
  	field->is_nested = 0;
  	field_dcl->type = field_type; /* reset "owned" flag */
  
  	if (name) {
  		if (!zend_hash_str_add_ptr(&struct_type->record.fields, name, name_len, field)) {
  			zend_ffi_type_dtor(field->type);
  			pefree(field, FFI_G(persistent));
  			zend_ffi_parser_error("duplicate field name '%.*s' at line %d", name_len, name, FFI_G(line));
  		}
  	} else {
  		zend_hash_next_index_insert_ptr(&struct_type->record.fields, field);
  	}
  }
  /* }}} */
  
  void zend_ffi_adjust_struct_size(zend_ffi_dcl *dcl) /* {{{ */
  {
  	zend_ffi_type *struct_type = ZEND_FFI_TYPE(dcl->type);
  
  	ZEND_ASSERT(struct_type->kind == ZEND_FFI_TYPE_STRUCT);
  	if (dcl->align > struct_type->align) {
  		struct_type->align = dcl->align;
  	}
  	if (!(struct_type->attr & ZEND_FFI_ATTR_PACKED)) {
  		struct_type->size = ((struct_type->size + (struct_type->align - 1)) / struct_type->align) * struct_type->align;
  	}
  	dcl->align = 0;
  }
  /* }}} */
  
  void zend_ffi_make_pointer_type(zend_ffi_dcl *dcl) /* {{{ */
  {
  	zend_ffi_type *type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent));
  	type->kind = ZEND_FFI_TYPE_POINTER;
  	type->attr = FFI_G(default_type_attr) | (dcl->attr & ZEND_FFI_POINTER_ATTRS);
  	type->size = sizeof(void*);
  	type->align = _Alignof(void*);
  	zend_ffi_finalize_type(dcl);
  	if (zend_ffi_validate_vla(ZEND_FFI_TYPE(dcl->type)) != SUCCESS) {
  		zend_ffi_cleanup_dcl(dcl);
  		LONGJMP(FFI_G(bailout), FAILURE);
  	}
  	type->pointer.type = dcl->type;
  	dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type);
  	dcl->flags &= ~ZEND_FFI_DCL_TYPE_QUALIFIERS;
  	dcl->attr &= ~ZEND_FFI_POINTER_ATTRS;
  	dcl->align = 0;
  }
  /* }}} */
  
  static int zend_ffi_validate_array_element_type(zend_ffi_type *type) /* {{{ */
  {
  	if (type->kind == ZEND_FFI_TYPE_FUNC) {
  		zend_ffi_throw_parser_error("array of functions is not allowed at line %d", FFI_G(line));
  		return FAILURE;
  	} else if (type->kind == ZEND_FFI_TYPE_ARRAY && (type->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY)) {
  		zend_ffi_throw_parser_error("only the leftmost array can be undimensioned at line %d", FFI_G(line));
  		return FAILURE;
  	}
  	return zend_ffi_validate_type(type, 0, 1);
  }
  /* }}} */
  
  void zend_ffi_make_array_type(zend_ffi_dcl *dcl, zend_ffi_val *len) /* {{{ */
  {
  	int length = 0;
  	zend_ffi_type *element_type;
  	zend_ffi_type *type;
  
  	zend_ffi_finalize_type(dcl);
  	element_type = ZEND_FFI_TYPE(dcl->type);
  
  	if (len->kind == ZEND_FFI_VAL_EMPTY) {
  		length = 0;
  	} else if (len->kind == ZEND_FFI_VAL_UINT32 || len->kind == ZEND_FFI_VAL_UINT64) {
  		length = len->u64;
  	} else if (len->kind == ZEND_FFI_VAL_INT32 || len->kind == ZEND_FFI_VAL_INT64) {
  		length = len->i64;
  	} else if (len->kind == ZEND_FFI_VAL_CHAR) {
  		length = len->ch;
  	} else {
  		zend_ffi_cleanup_dcl(dcl);
  		zend_ffi_parser_error("unsupported array index type at line %d", FFI_G(line));
  		return;
  	}
  	if (length < 0) {
  		zend_ffi_cleanup_dcl(dcl);
  		zend_ffi_parser_error("negative array index at line %d", FFI_G(line));
  		return;
  	}
  
  	if (zend_ffi_validate_array_element_type(element_type) != SUCCESS) {
  		zend_ffi_cleanup_dcl(dcl);
  		LONGJMP(FFI_G(bailout), FAILURE);
  	}
  
  	type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent));
  	type->kind = ZEND_FFI_TYPE_ARRAY;
  	type->attr = FFI_G(default_type_attr) | (dcl->attr & ZEND_FFI_ARRAY_ATTRS);
  	type->size = length * element_type->size;
  	type->align = element_type->align;
  	type->array.type = dcl->type;
  	type->array.length = length;
  	dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type);
  	dcl->flags &= ~ZEND_FFI_DCL_TYPE_QUALIFIERS;
  	dcl->attr &= ~ZEND_FFI_ARRAY_ATTRS;
  	dcl->align = 0;
  }
  /* }}} */
  
  static int zend_ffi_validate_func_ret_type(zend_ffi_type *type) /* {{{ */
  {
  	if (type->kind == ZEND_FFI_TYPE_FUNC) {
  		zend_ffi_throw_parser_error("function returning function is not allowed at line %d", FFI_G(line));
  		return FAILURE;
  	 } else if (type->kind == ZEND_FFI_TYPE_ARRAY) {
  		zend_ffi_throw_parser_error("function returning array is not allowed at line %d", FFI_G(line));
  		return FAILURE;
  	}
  	return zend_ffi_validate_incomplete_type(type, 1, 0);
  }
  /* }}} */
  
  void zend_ffi_make_func_type(zend_ffi_dcl *dcl, HashTable *args, zend_ffi_dcl *nested_dcl) /* {{{ */
  {
  	zend_ffi_type *type;
  	zend_ffi_type *ret_type;
  
  	zend_ffi_finalize_type(dcl);
  	ret_type = ZEND_FFI_TYPE(dcl->type);
  
  	if (args) {
  		int no_args = 0;
  		zend_ffi_type *arg_type;
  
  		ZEND_HASH_FOREACH_PTR(args, arg_type) {
  			arg_type = ZEND_FFI_TYPE(arg_type);
  			if (arg_type->kind == ZEND_FFI_TYPE_VOID) {
  				if (zend_hash_num_elements(args) != 1) {
  					zend_ffi_cleanup_dcl(nested_dcl);
  					zend_ffi_cleanup_dcl(dcl);
  					zend_hash_destroy(args);
  					pefree(args, FFI_G(persistent));
  					zend_ffi_parser_error("'void' type is not allowed at line %d", FFI_G(line));
  					return;
  				} else {
  					no_args = 1;
  				}
  			}
  		} ZEND_HASH_FOREACH_END();
  		if (no_args) {
  			zend_hash_destroy(args);
  			pefree(args, FFI_G(persistent));
  			args = NULL;
  		}
  	}
  
  #ifdef HAVE_FFI_VECTORCALL_PARTIAL
  	if (dcl->abi == ZEND_FFI_ABI_VECTORCALL && args) {
  		zend_ulong i;
  		zend_ffi_type *arg_type;
  
  		ZEND_HASH_FOREACH_NUM_KEY_PTR(args, i, arg_type) {
  			arg_type = ZEND_FFI_TYPE(arg_type);
  # ifdef _WIN64
  			if (i >= 4 && i <= 5 && (arg_type->kind == ZEND_FFI_TYPE_FLOAT || arg_type->kind == ZEND_FFI_TYPE_DOUBLE)) {
  # else
  			if (i < 6 && (arg_type->kind == ZEND_FFI_TYPE_FLOAT || arg_type->kind == ZEND_FFI_TYPE_DOUBLE)) {
  # endif
  				zend_ffi_cleanup_dcl(nested_dcl);
  				zend_ffi_cleanup_dcl(dcl);
  				zend_hash_destroy(args);
  				pefree(args, FFI_G(persistent));
  				zend_ffi_parser_error("'float'/'double' type not allowed at position " ZEND_ULONG_FMT " with __vectorcall at line %d", i+1, FFI_G(line));
  				return;
  			}
  		} ZEND_HASH_FOREACH_END();
  	}
  #endif
  
  	if (zend_ffi_validate_func_ret_type(ret_type) != SUCCESS) {
  		zend_ffi_cleanup_dcl(nested_dcl);
  		zend_ffi_cleanup_dcl(dcl);
  		if (args) {
  			zend_hash_destroy(args);
  			pefree(args, FFI_G(persistent));
  		}
  		LONGJMP(FFI_G(bailout), FAILURE);
  	}
  
  	type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent));
  	type->kind = ZEND_FFI_TYPE_FUNC;
  	type->attr = FFI_G(default_type_attr) | (dcl->attr & ZEND_FFI_FUNC_ATTRS);
  	type->size = sizeof(void*);
  	type->align = 1;
  	type->func.ret_type = dcl->type;
  	switch (dcl->abi) {
  		case ZEND_FFI_ABI_DEFAULT:
  		case ZEND_FFI_ABI_CDECL:
  			type->func.abi = FFI_DEFAULT_ABI;
  			break;
  #ifdef HAVE_FFI_FASTCALL
  		case ZEND_FFI_ABI_FASTCALL:
  			type->func.abi = FFI_FASTCALL;
  			break;
  #endif
  #ifdef HAVE_FFI_THISCALL
  		case ZEND_FFI_ABI_THISCALL:
  			type->func.abi = FFI_THISCALL;
  			break;
  #endif
  #ifdef HAVE_FFI_STDCALL
  		case ZEND_FFI_ABI_STDCALL:
  			type->func.abi = FFI_STDCALL;
  			break;
  #endif
  #ifdef HAVE_FFI_PASCAL
  		case ZEND_FFI_ABI_PASCAL:
  			type->func.abi = FFI_PASCAL;
  			break;
  #endif
  #ifdef HAVE_FFI_REGISTER
  		case ZEND_FFI_ABI_REGISTER:
  			type->func.abi = FFI_REGISTER;
  			break;
  #endif
  #ifdef HAVE_FFI_MS_CDECL
  		case ZEND_FFI_ABI_MS:
  			type->func.abi = FFI_MS_CDECL;
  			break;
  #endif
  #ifdef HAVE_FFI_SYSV
  		case ZEND_FFI_ABI_SYSV:
  			type->func.abi = FFI_SYSV;
  			break;
  #endif
  #ifdef HAVE_FFI_VECTORCALL_PARTIAL
  		case ZEND_FFI_ABI_VECTORCALL:
  			type->func.abi = FFI_VECTORCALL_PARTIAL;
  			break;
  #endif
  		default:
  			type->func.abi = FFI_DEFAULT_ABI;
  			zend_ffi_cleanup_dcl(nested_dcl);
  			if (args) {
  				zend_hash_destroy(args);
  				pefree(args, FFI_G(persistent));
  			}
  			type->func.args = NULL;
  			_zend_ffi_type_dtor(type);
  			zend_ffi_parser_error("unsupported calling convention line %d", FFI_G(line));
  			break;
  	}
  	type->func.args = args;
  	dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type);
  	dcl->attr &= ~ZEND_FFI_FUNC_ATTRS;
  	dcl->align = 0;
  	dcl->abi = 0;
  }
  /* }}} */
  
  void zend_ffi_add_arg(HashTable **args, const char *name, size_t name_len, zend_ffi_dcl *arg_dcl) /* {{{ */
  {
  	zend_ffi_type *type;
  
  	if (!*args) {
  		*args = pemalloc(sizeof(HashTable), FFI_G(persistent));
  		zend_hash_init(*args, 0, NULL, zend_ffi_type_hash_dtor, FFI_G(persistent));
  	}
  	zend_ffi_finalize_type(arg_dcl);
  	type = ZEND_FFI_TYPE(arg_dcl->type);
  	if (type->kind == ZEND_FFI_TYPE_ARRAY) {
  		if (ZEND_FFI_TYPE_IS_OWNED(arg_dcl->type)) {
  			type->kind = ZEND_FFI_TYPE_POINTER;
  			type->size = sizeof(void*);
  		} else {
  			zend_ffi_type *new_type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent));
  			new_type->kind = ZEND_FFI_TYPE_POINTER;
  			new_type->attr = FFI_G(default_type_attr) | (type->attr & ZEND_FFI_POINTER_ATTRS);
  			new_type->size = sizeof(void*);
  			new_type->align = _Alignof(void*);
  			new_type->pointer.type = ZEND_FFI_TYPE(type->array.type);
  			arg_dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(new_type);
  		}
  	} else if (type->kind == ZEND_FFI_TYPE_FUNC) {
  		zend_ffi_type *new_type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent));
  		new_type->kind = ZEND_FFI_TYPE_POINTER;
  		new_type->attr = FFI_G(default_type_attr);
  		new_type->size = sizeof(void*);
  		new_type->align = _Alignof(void*);
  		new_type->pointer.type = arg_dcl->type;
  		arg_dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(new_type);
  	}
  	if (zend_ffi_validate_incomplete_type(type, 1, 1) != SUCCESS) {
  		zend_ffi_cleanup_dcl(arg_dcl);
  		zend_hash_destroy(*args);
  		pefree(*args, FFI_G(persistent));
  		*args = NULL;
  		LONGJMP(FFI_G(bailout), FAILURE);
  	}
  	zend_hash_next_index_insert_ptr(*args, (void*)arg_dcl->type);
  }
  /* }}} */
  
  void zend_ffi_declare(const char *name, size_t name_len, zend_ffi_dcl *dcl) /* {{{ */
  {
  	zend_ffi_symbol *sym;
  
  	if (!FFI_G(symbols)) {
  		FFI_G(symbols) = pemalloc(sizeof(HashTable), FFI_G(persistent));
  		zend_hash_init(FFI_G(symbols), 0, NULL, FFI_G(persistent) ? zend_ffi_symbol_hash_persistent_dtor : zend_ffi_symbol_hash_dtor, FFI_G(persistent));
  	}
  	zend_ffi_finalize_type(dcl);
  	sym = zend_hash_str_find_ptr(FFI_G(symbols), name, name_len);
  	if (sym) {
  		if ((dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_TYPEDEF
  		 && sym->kind == ZEND_FFI_SYM_TYPE
  		 && zend_ffi_is_same_type(ZEND_FFI_TYPE(sym->type), ZEND_FFI_TYPE(dcl->type))
  		 && sym->is_const == (zend_bool)(dcl->attr & ZEND_FFI_ATTR_CONST)) {
  			/* allowed redeclaration */
  			zend_ffi_type_dtor(dcl->type);
  			return;
  		} else if ((dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == 0
  		 || (dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_EXTERN) {
  			zend_ffi_type *type = ZEND_FFI_TYPE(dcl->type);
  
  			if (type->kind == ZEND_FFI_TYPE_FUNC) {
  				if (sym->kind == ZEND_FFI_SYM_FUNC
  				 && zend_ffi_same_types(ZEND_FFI_TYPE(sym->type), type)) {
  					/* allowed redeclaration */
  					zend_ffi_type_dtor(dcl->type);
  					return;
  				}
  			} else {
  				if (sym->kind == ZEND_FFI_SYM_VAR
  				 && zend_ffi_is_same_type(ZEND_FFI_TYPE(sym->type), type)
  				 && sym->is_const == (zend_bool)(dcl->attr & ZEND_FFI_ATTR_CONST)) {
  					/* allowed redeclaration */
  					zend_ffi_type_dtor(dcl->type);
  					return;
  				}
  			}
  		}
  		zend_ffi_parser_error("redeclaration of '%.*s' at line %d", name_len, name, FFI_G(line));
  	} else {
  		if ((dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_TYPEDEF) {
  			if (zend_ffi_validate_vla(ZEND_FFI_TYPE(dcl->type)) != SUCCESS) {
  				zend_ffi_cleanup_dcl(dcl);
  				LONGJMP(FFI_G(bailout), FAILURE);
  			}
  			if (dcl->align && dcl->align > ZEND_FFI_TYPE(dcl->type)->align) {
  				if (ZEND_FFI_TYPE_IS_OWNED(dcl->type)) {
  					ZEND_FFI_TYPE(dcl->type)->align = dcl->align;
  				} else {
  					zend_ffi_type *type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent));
  
  					memcpy(type, ZEND_FFI_TYPE(dcl->type), sizeof(zend_ffi_type));
  					type->attr |= FFI_G(default_type_attr);
  					type->align = dcl->align;
  					dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type);
  				}
  			}
  			sym = pemalloc(sizeof(zend_ffi_symbol), FFI_G(persistent));
  			sym->kind = ZEND_FFI_SYM_TYPE;
  			sym->type = dcl->type;
  			sym->is_const = (zend_bool)(dcl->attr & ZEND_FFI_ATTR_CONST);
  			dcl->type = ZEND_FFI_TYPE(dcl->type); /* reset "owned" flag */
  			zend_hash_str_add_new_ptr(FFI_G(symbols), name, name_len, sym);
  		} else {
  			zend_ffi_type *type;
  
  			type = ZEND_FFI_TYPE(dcl->type);
  			if (zend_ffi_validate_type(type, (dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_EXTERN, 1) != SUCCESS) {
  				zend_ffi_cleanup_dcl(dcl);
  				LONGJMP(FFI_G(bailout), FAILURE);
  			}
  			if ((dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == 0 ||
  			    (dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_EXTERN) {
  				sym = pemalloc(sizeof(zend_ffi_symbol), FFI_G(persistent));
  				sym->kind = (type->kind == ZEND_FFI_TYPE_FUNC) ? ZEND_FFI_SYM_FUNC : ZEND_FFI_SYM_VAR;
  				sym->type = dcl->type;
  				sym->is_const = (zend_bool)(dcl->attr & ZEND_FFI_ATTR_CONST);
  				dcl->type = type; /* reset "owned" flag */
  				zend_hash_str_add_new_ptr(FFI_G(symbols), name, name_len, sym);
  			} else {
  				/* useless declarartion */
  				zend_ffi_type_dtor(dcl->type);
  			}
  		}
  	}
  }
  /* }}} */
  
  void zend_ffi_declare_tag(const char *name, size_t name_len, zend_ffi_dcl *dcl, zend_bool incomplete) /* {{{ */
  {
  	zend_ffi_tag *tag;
  	zend_ffi_type *type;
  
  	if (!FFI_G(tags)) {
  		FFI_G(tags) = pemalloc(sizeof(HashTable), FFI_G(persistent));
  		zend_hash_init(FFI_G(tags), 0, NULL, FFI_G(persistent) ? zend_ffi_tag_hash_persistent_dtor : zend_ffi_tag_hash_dtor, FFI_G(persistent));
  	}
  	tag = zend_hash_str_find_ptr(FFI_G(tags), name, name_len);
  	if (tag) {
  		zend_ffi_type *type = ZEND_FFI_TYPE(tag->type);
  
  		if (dcl->flags & ZEND_FFI_DCL_STRUCT) {
  			if (tag->kind != ZEND_FFI_TAG_STRUCT) {
  				zend_ffi_parser_error("'%.*s' defined as wrong kind of tag at line %d", name_len, name, FFI_G(line));
  				return;
  			} else if (!incomplete && !(type->attr & ZEND_FFI_ATTR_INCOMPLETE_TAG)) {
  				zend_ffi_parser_error("redefinition of 'struct %.*s' at line %d", name_len, name, FFI_G(line));
  				return;
  			}
  		} else if (dcl->flags & ZEND_FFI_DCL_UNION) {
  			if (tag->kind != ZEND_FFI_TAG_UNION) {
  				zend_ffi_parser_error("'%.*s' defined as wrong kind of tag at line %d", name_len, name, FFI_G(line));
  				return;
  			} else if (!incomplete && !(type->attr & ZEND_FFI_ATTR_INCOMPLETE_TAG)) {
  				zend_ffi_parser_error("redefinition of 'union %.*s' at line %d", name_len, name, FFI_G(line));
  				return;
  			}
  		} else if (dcl->flags & ZEND_FFI_DCL_ENUM) {
  			if (tag->kind != ZEND_FFI_TAG_ENUM) {
  				zend_ffi_parser_error("'%.*s' defined as wrong kind of tag at line %d", name_len, name, FFI_G(line));
  				return;
  			} else if (!incomplete && !(type->attr & ZEND_FFI_ATTR_INCOMPLETE_TAG)) {
  				zend_ffi_parser_error("redefinition of 'enum %.*s' at line %d", name_len, name, FFI_G(line));
  				return;
  			}
  		} else {
  			ZEND_ASSERT(0);
  			return;
  		}
  		dcl->type = type;
  		if (!incomplete) {
  			type->attr &= ~ZEND_FFI_ATTR_INCOMPLETE_TAG;
  		}
  	} else {
  		zend_ffi_tag *tag = pemalloc(sizeof(zend_ffi_tag), FFI_G(persistent));
  		zend_string *tag_name = zend_string_init(name, name_len, FFI_G(persistent));
  
  		if (dcl->flags & ZEND_FFI_DCL_STRUCT) {
  			tag->kind = ZEND_FFI_TAG_STRUCT;
  			zend_ffi_make_struct_type(dcl);
  			type = ZEND_FFI_TYPE(dcl->type);
  			type->record.tag_name = zend_string_copy(tag_name);
  		} else if (dcl->flags & ZEND_FFI_DCL_UNION) {
  			tag->kind = ZEND_FFI_TAG_UNION;
  			zend_ffi_make_struct_type(dcl);
  			type = ZEND_FFI_TYPE(dcl->type);
  			type->record.tag_name = zend_string_copy(tag_name);
  		} else if (dcl->flags & ZEND_FFI_DCL_ENUM) {
  			tag->kind = ZEND_FFI_TAG_ENUM;
  			zend_ffi_make_enum_type(dcl);
  			type = ZEND_FFI_TYPE(dcl->type);
  			type->enumeration.tag_name = zend_string_copy(tag_name);
  		} else {
  			ZEND_ASSERT(0);
  		}
  		tag->type = ZEND_FFI_TYPE_MAKE_OWNED(dcl->type);
  		dcl->type = ZEND_FFI_TYPE(dcl->type);
  		if (incomplete) {
  			dcl->type->attr |= ZEND_FFI_ATTR_INCOMPLETE_TAG;
  		}
  		zend_hash_add_new_ptr(FFI_G(tags), tag_name, tag);
  		zend_string_release(tag_name);
  	}
  }
  /* }}} */
  
  void zend_ffi_set_abi(zend_ffi_dcl *dcl, uint16_t abi) /* {{{ */
  {
  	if (dcl->abi != ZEND_FFI_ABI_DEFAULT) {
  		zend_ffi_parser_error("multiple calling convention specifiers at line %d", FFI_G(line));
  	} else {
  		dcl->abi = abi;
  	}
  }
  /* }}} */
  
  #ifndef __BIGGEST_ALIGNMENT__
  /* XXX need something better, perhaps with regard to SIMD, etc. */
  # define __BIGGEST_ALIGNMENT__ sizeof(size_t)
  #endif
  
  #define SIMPLE_ATTRIBUTES(_) \
  	_(cdecl) \
  	_(fastcall) \
  	_(thiscall) \
  	_(stdcall) \
  	_(ms_abi) \
  	_(sysv_abi) \
  	_(vectorcall) \
  	_(aligned) \
  	_(packed) \
  	_(ms_struct) \
  	_(gcc_struct) \
  	_(const) \
  	_(malloc) \
  	_(deprecated) \
  	_(nothrow) \
  	_(leaf) \
  	_(pure) \
  	_(noreturn) \
  	_(warn_unused_result)
  
  #define ATTR_ID(name)   attr_ ## name,
  #define ATTR_NAME(name) {sizeof(#name)-1, #name},
  
  void zend_ffi_add_attribute(zend_ffi_dcl *dcl, const char *name, size_t name_len) /* {{{ */
  {
  	enum {
  		SIMPLE_ATTRIBUTES(ATTR_ID)
  		attr_unsupported
  	};
  	static const struct {
  		size_t len;
  		const char * const name;
  	} names[] = {
  		SIMPLE_ATTRIBUTES(ATTR_NAME)
  		{0, NULL}
  	};
  	int id;
  
  	if (name_len > 4
  	 && name[0] == '_'
  	 && name[1] == '_'
  	 && name[name_len-2] == '_'
  	 && name[name_len-1] == '_') {
  		name += 2;
  		name_len -= 4;
  	}
  	for (id = 0; names[id].len != 0; id++) {
  		if (name_len == names[id].len) {
  			if (memcmp(name, names[id].name, name_len) == 0) {
  				break;
  			}
  		}
  	}
  	switch (id) {
  		case attr_cdecl:
  			zend_ffi_set_abi(dcl, ZEND_FFI_ABI_CDECL);
  			break;
  		case attr_fastcall:
  			zend_ffi_set_abi(dcl, ZEND_FFI_ABI_FASTCALL);
  			break;
  		case attr_thiscall:
  			zend_ffi_set_abi(dcl, ZEND_FFI_ABI_THISCALL);
  			break;
  		case attr_stdcall:
  			zend_ffi_set_abi(dcl, ZEND_FFI_ABI_STDCALL);
  			break;
  		case attr_ms_abi:
  			zend_ffi_set_abi(dcl, ZEND_FFI_ABI_MS);
  			break;
  		case attr_sysv_abi:
  			zend_ffi_set_abi(dcl, ZEND_FFI_ABI_SYSV);
  			break;
  		case attr_vectorcall:
  			zend_ffi_set_abi(dcl, ZEND_FFI_ABI_VECTORCALL);
  			break;
  		case attr_aligned:
  			dcl->align = __BIGGEST_ALIGNMENT__;
  			break;
  		case attr_packed:
  			dcl->attr |= ZEND_FFI_ATTR_PACKED;
  			break;
  		case attr_ms_struct:
  			dcl->attr |= ZEND_FFI_ATTR_MS_STRUCT;
  			break;
  		case attr_gcc_struct:
  			dcl->attr |= ZEND_FFI_ATTR_GCC_STRUCT;
  			break;
  		case attr_unsupported:
  			zend_ffi_parser_error("unsupported attribute '%.*s' at line %d", name_len, name, FFI_G(line));
  			break;
  		default:
  			/* ignore */
  			break;
  	}
  }
  /* }}} */
  
  #define VALUE_ATTRIBUTES(_) \
  	_(regparam) \
  	_(aligned) \
  	_(mode) \
  	_(nonnull) \
  	_(alloc_size) \
  	_(format) \
  	_(deprecated)
  
  void zend_ffi_add_attribute_value(zend_ffi_dcl *dcl, const char *name, size_t name_len, int n, zend_ffi_val *val) /* {{{ */
  {
  	enum {
  		VALUE_ATTRIBUTES(ATTR_ID)
  		attr_unsupported
  	};
  	static const struct {
  		size_t len;
  		const char * const name;
  	} names[] = {
  		VALUE_ATTRIBUTES(ATTR_NAME)
  		{0, NULL}
  	};
  	int id;
  
  	if (name_len > 4
  	 && name[0] == '_'
  	 && name[1] == '_'
  	 && name[name_len-2] == '_'
  	 && name[name_len-1] == '_') {
  		name += 2;
  		name_len -= 4;
  	}
  	for (id = 0; names[id].len != 0; id++) {
  		if (name_len == names[id].len) {
  			if (memcmp(name, names[id].name, name_len) == 0) {
  				break;
  			}
  		}
  	}
  	switch (id) {
  		case attr_regparam:
  			if (n == 0
  			 && (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_INT64 || val->kind == ZEND_FFI_VAL_UINT64)
  			 && val->i64 == 3) {
  				zend_ffi_set_abi(dcl, ZEND_FFI_ABI_REGISTER);
  			} else {
  				zend_ffi_parser_error("incorrect 'regparam' value at line %d", FFI_G(line));
  			}
  			break;
  		case attr_aligned:
  			if (n == 0
  			 && (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_INT64 || val->kind == ZEND_FFI_VAL_UINT64)
  			 && val->i64 > 0 && val->i64 <= 0x80000000 && (val->i64 & (val->i64 - 1)) == 0) {
  				dcl->align = val->i64;
  			} else {
  				zend_ffi_parser_error("incorrect 'alignment' value at line %d", FFI_G(line));
  			}
  			break;
  		case attr_mode:
  			if (n == 0
  			 && (val->kind == ZEND_FFI_VAL_NAME)) {
  				const char *str = val->str;
  				size_t len = val->len;
  				if (len > 4
  				 && str[0] == '_'
  				 && str[1] == '_'
  				 && str[len-2] == '_'
  				 && str[len-1] == '_') {
  					str += 2;
  					len -= 4;
  				}
  				// TODO: Add support for vector type 'VnXX' ???
  				if (len == 2) {
  					if (str[1] == 'I') {
  						if (dcl->flags & (ZEND_FFI_DCL_TYPE_SPECIFIERS-(ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_INT|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_SIGNED|ZEND_FFI_DCL_UNSIGNED))) {
  							/* inappropriate type */
  						} else if (str[0] == 'Q') {
  							dcl->flags &= ~(ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_INT|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_LONG_LONG);
  							dcl->flags |= ZEND_FFI_DCL_CHAR;
  							break;
  						} else if (str[0] == 'H') {
  							dcl->flags &= ~(ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_INT|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_LONG_LONG);
  							dcl->flags |= ZEND_FFI_DCL_SHORT;
  							break;
  						} else if (str[0] == 'S') {
  							dcl->flags &= ~(ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_INT|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_LONG_LONG);
  							dcl->flags |= ZEND_FFI_DCL_INT;
  							break;
  						} else if (str[0] == 'D') {
  							dcl->flags &= ~(ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_INT|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_LONG_LONG);
  							if (sizeof(long) == 8) {
  								dcl->flags |= ZEND_FFI_DCL_LONG;
  							} else {
  								dcl->flags |= ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_LONG_LONG;
  							}
  							break;
  						}
  					} else if (str[1] == 'F') {
  						if (dcl->flags & (ZEND_FFI_DCL_TYPE_SPECIFIERS-(ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_FLOAT|ZEND_FFI_DCL_DOUBLE))) {
  							/* inappropriate type */
  						} else if (str[0] == 'S') {
  							dcl->flags &= ~(ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_FLOAT|ZEND_FFI_DCL_DOUBLE);
  							dcl->flags |= ZEND_FFI_DCL_FLOAT;
  							break;
  						} else if (str[0] == 'D') {
  							dcl->flags &= ~(ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_FLOAT|ZEND_FFI_DCL_DOUBLE);
  							dcl->flags |= ZEND_FFI_DCL_DOUBLE;
  							break;
  						}
  					}
  				}
  			}
  			zend_ffi_parser_error("unsupported 'mode' value at line %d", FFI_G(line));
  			// TODO: ???
  		case attr_unsupported:
  			zend_ffi_parser_error("unsupported attribute '%.*s' at line %d", name_len, name, FFI_G(line));
  			break;
  		default:
  			/* ignore */
  			break;
  	}
  }
  /* }}} */
  
  void zend_ffi_add_msvc_attribute_value(zend_ffi_dcl *dcl, const char *name, size_t name_len, zend_ffi_val *val) /* {{{ */
  {
  	if (name_len == sizeof("align")-1 && memcmp(name, "align", sizeof("align")-1) == 0) {
  		if ((val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_INT64 || val->kind == ZEND_FFI_VAL_UINT64)
  		 && val->i64 > 0 && val->i64 <= 0x80000000 && (val->i64 & (val->i64 - 1)) == 0) {
  			dcl->align = val->i64;
  		} else {
  			zend_ffi_parser_error("incorrect 'alignment' value at line %d", FFI_G(line));
  		}
  	} else {
  		/* ignore */
  	}
  }
  /* }}} */
  
  static int zend_ffi_nested_type(zend_ffi_type *type, zend_ffi_type *nested_type) /* {{{ */
  {
  	nested_type = ZEND_FFI_TYPE(nested_type);
  	switch (nested_type->kind) {
  		case ZEND_FFI_TYPE_POINTER:
  			/* "char" is used as a terminator of nested declaration */
  			if (nested_type->pointer.type == &zend_ffi_type_char) {
  				nested_type->pointer.type = type;
  				return zend_ffi_validate_vla(ZEND_FFI_TYPE(type));
  			} else {
  				return zend_ffi_nested_type(type, nested_type->pointer.type);
  			}
  			break;
  		case ZEND_FFI_TYPE_ARRAY:
  			/* "char" is used as a terminator of nested declaration */
  			if (nested_type->array.type == &zend_ffi_type_char) {
  				nested_type->array.type = type;
  				if (zend_ffi_validate_array_element_type(ZEND_FFI_TYPE(type)) != SUCCESS) {
  					return FAILURE;
  				}
  			} else {
  				if (zend_ffi_nested_type(type, nested_type->array.type) != SUCCESS) {
  					return FAILURE;
  				}
  			}
  			nested_type->size = nested_type->array.length * ZEND_FFI_TYPE(nested_type->array.type)->size;
  			nested_type->align = ZEND_FFI_TYPE(nested_type->array.type)->align;
  			return SUCCESS;
  			break;
  		case ZEND_FFI_TYPE_FUNC:
  			/* "char" is used as a terminator of nested declaration */
  			if (nested_type->func.ret_type == &zend_ffi_type_char) {
  				nested_type->func.ret_type = type;
  				return zend_ffi_validate_func_ret_type(ZEND_FFI_TYPE(type));
  			} else {
  				return zend_ffi_nested_type(type, nested_type->func.ret_type);
  			}
  			break;
  		default:
  			ZEND_ASSERT(0);
  	}
  }
  /* }}} */
  
  void zend_ffi_nested_declaration(zend_ffi_dcl *dcl, zend_ffi_dcl *nested_dcl) /* {{{ */
  {
  	/* "char" is used as a terminator of nested declaration */
  	zend_ffi_finalize_type(dcl);
  	if (!nested_dcl->type || nested_dcl->type == &zend_ffi_type_char) {
  		nested_dcl->type = dcl->type;
  	} else {
  		if (zend_ffi_nested_type(dcl->type, nested_dcl->type) != SUCCESS) {
  			zend_ffi_cleanup_dcl(nested_dcl);
  			LONGJMP(FFI_G(bailout), FAILURE);
  		}
  	}
  	dcl->type = nested_dcl->type;
  }
  /* }}} */
  
  void zend_ffi_align_as_type(zend_ffi_dcl *dcl, zend_ffi_dcl *align_dcl) /* {{{ */
  {
  	zend_ffi_finalize_type(align_dcl);
  	dcl->align = MAX(align_dcl->align, ZEND_FFI_TYPE(align_dcl->type)->align);
  }
  /* }}} */
  
  void zend_ffi_align_as_val(zend_ffi_dcl *dcl, zend_ffi_val *align_val) /* {{{ */
  {
  	switch (align_val->kind) {
  		case ZEND_FFI_VAL_INT32:
  		case ZEND_FFI_VAL_UINT32:
  			dcl->align = zend_ffi_type_uint32.align;
  			break;
  		case ZEND_FFI_VAL_INT64:
  		case ZEND_FFI_VAL_UINT64:
  			dcl->align = zend_ffi_type_uint64.align;
  			break;
  		case ZEND_FFI_VAL_FLOAT:
  			dcl->align = zend_ffi_type_float.align;
  			break;
  		case ZEND_FFI_VAL_DOUBLE:
  			dcl->align = zend_ffi_type_double.align;
  			break;
  #ifdef HAVE_LONG_DOUBLE
  		case ZEND_FFI_VAL_LONG_DOUBLE:
  			dcl->align = zend_ffi_type_long_double.align;
  			break;
  #endif
  		case ZEND_FFI_VAL_CHAR:
  		case ZEND_FFI_VAL_STRING:
  			dcl->align = zend_ffi_type_char.align;
  			break;
  		default:
  			break;
  	}
  }
  /* }}} */
  
  #define zend_ffi_expr_bool(val) do { \
  	if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { \
  		val->kind = ZEND_FFI_VAL_INT32; \
  		val->i64 = !!val->u64; \
  	} else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { \
  		val->kind = ZEND_FFI_VAL_INT32; \
  		val->i64 = !!val->i64; \
  	} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  		val->kind = ZEND_FFI_VAL_INT32; \
  		val->i64 = !!val->d; \
  	} else if (val->kind == ZEND_FFI_VAL_CHAR) { \
  		val->kind = ZEND_FFI_VAL_INT32; \
  		val->i64 = !!val->ch; \
  	} else { \
  		val->kind = ZEND_FFI_VAL_ERROR; \
  	} \
  } while (0)
  
  #define zend_ffi_expr_math(val, op2, OP) do { \
  	if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { \
  		if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \
  			val->kind = MAX(val->kind, op2->kind); \
  			val->u64 = val->u64 OP op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_INT32) { \
  			val->u64 = val->u64 OP op2->i64; \
  		} else if (op2->kind == ZEND_FFI_VAL_INT64) { \
  			val->u64 = val->u64 OP op2->i64; \
  		} else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  			val->kind = op2->kind; \
  			val->d = (zend_ffi_double)val->u64 OP op2->d; \
  		} else if (op2->kind == ZEND_FFI_VAL_CHAR) { \
  			val->u64 = val->u64 OP op2->ch; \
  		} else { \
  			val->kind = ZEND_FFI_VAL_ERROR; \
  		} \
  	} else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { \
  		if (op2->kind == ZEND_FFI_VAL_UINT32) { \
  			val->i64 = val->i64 OP op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_UINT64) { \
  			val->i64 = val->i64 OP op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \
  			val->kind = MAX(val->kind, op2->kind); \
  			val->i64 = val->i64 OP op2->i64; \
  		} else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  			val->kind = op2->kind; \
  			val->d = (zend_ffi_double)val->i64 OP op2->d; \
  		} else if (op2->kind == ZEND_FFI_VAL_CHAR) { \
  			val->i64 = val->i64 OP op2->ch; \
  		} else { \
  			val->kind = ZEND_FFI_VAL_ERROR; \
  		} \
  	} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  		if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \
  			val->d = val->d OP (zend_ffi_double)op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_INT32 ||op2->kind == ZEND_FFI_VAL_INT64) { \
  			val->d = val->d OP (zend_ffi_double)op2->i64; \
  		} else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  			val->kind = MAX(val->kind, op2->kind); \
  			val->d = val->d OP op2->d; \
  		} else if (op2->kind == ZEND_FFI_VAL_CHAR) { \
  			val->d = val->d OP (zend_ffi_double)op2->ch; \
  		} else { \
  			val->kind = ZEND_FFI_VAL_ERROR; \
  		} \
  	} else if (val->kind == ZEND_FFI_VAL_CHAR) { \
  		if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \
  			val->kind = op2->kind; \
  			val->u64 = val->ch OP op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \
  			val->kind = ZEND_FFI_VAL_INT64; \
  			val->i64 = val->ch OP op2->i64; \
  		} else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  			val->kind = op2->kind; \
  			val->d = (zend_ffi_double)val->ch OP op2->d; \
  		} else if (op2->kind == ZEND_FFI_VAL_CHAR) { \
  			val->ch = val->ch OP op2->ch; \
  		} else { \
  			val->kind = ZEND_FFI_VAL_ERROR; \
  		} \
  	} else { \
  		val->kind = ZEND_FFI_VAL_ERROR; \
  	} \
  } while (0)
  
  #define zend_ffi_expr_int_math(val, op2, OP) do { \
  	if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { \
  		if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \
  			val->kind = MAX(val->kind, op2->kind); \
  			val->u64 = val->u64 OP op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_INT32) { \
  			val->u64 = val->u64 OP op2->i64; \
  		} else if (op2->kind == ZEND_FFI_VAL_INT64) { \
  			val->u64 = val->u64 OP op2->i64; \
  		} else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  			val->u64 = val->u64 OP (uint64_t)op2->d; \
  		} else if (op2->kind == ZEND_FFI_VAL_CHAR) { \
  			val->u64 = val->u64 OP op2->ch; \
  		} else { \
  			val->kind = ZEND_FFI_VAL_ERROR; \
  		} \
  	} else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { \
  		if (op2->kind == ZEND_FFI_VAL_UINT32) { \
  			val->i64 = val->i64 OP op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_UINT64) { \
  			val->i64 = val->i64 OP op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \
  			val->kind = MAX(val->kind, op2->kind); \
  			val->i64 = val->i64 OP op2->i64; \
  		} else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  			val->u64 = val->u64 OP (int64_t)op2->d; \
  		} else if (op2->kind == ZEND_FFI_VAL_CHAR) { \
  			val->i64 = val->i64 OP op2->ch; \
  		} else { \
  			val->kind = ZEND_FFI_VAL_ERROR; \
  		} \
  	} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  		if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \
  			val->kind = op2->kind; \
  			val->u64 = (uint64_t)val->d OP op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \
  			val->kind = op2->kind; \
  			val->i64 = (int64_t)val->d OP op2->i64; \
  		} else { \
  			val->kind = ZEND_FFI_VAL_ERROR; \
  		} \
  	} else if (val->kind == ZEND_FFI_VAL_CHAR) { \
  		if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \
  			val->kind = op2->kind; \
  			val->u64 = (uint64_t)val->ch OP op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \
  			val->kind = op2->kind; \
  			val->i64 = (int64_t)val->ch OP op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_CHAR) { \
  			val->ch = val->ch OP op2->ch; \
  		} else { \
  			val->kind = ZEND_FFI_VAL_ERROR; \
  		} \
  	} else { \
  		val->kind = ZEND_FFI_VAL_ERROR; \
  	} \
  } while (0)
  
  #define zend_ffi_expr_cmp(val, op2, OP) do { \
  	if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { \
  		if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->u64 OP op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->u64 OP op2->u64; /*signed/unsigned */ \
  		} else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = (zend_ffi_double)val->u64 OP op2->d; \
  		} else if (op2->kind == ZEND_FFI_VAL_CHAR) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->u64 OP op2->d; \
  		} else { \
  			val->kind = ZEND_FFI_VAL_ERROR; \
  		} \
  	} else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { \
  		if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->i64 OP op2->i64; /* signed/unsigned */ \
  		} else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->i64 OP op2->i64; \
  		} else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = (zend_ffi_double)val->i64 OP op2->d; \
  		} else if (op2->kind == ZEND_FFI_VAL_CHAR) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->i64 OP op2->ch; \
  		} else { \
  			val->kind = ZEND_FFI_VAL_ERROR; \
  		} \
  	} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  		if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->d OP (zend_ffi_double)op2->u64; \
  		} else if (op2->kind == ZEND_FFI_VAL_INT32 ||op2->kind == ZEND_FFI_VAL_INT64) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->d OP (zend_ffi_double)op2->i64; \
  		} else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->d OP op2->d; \
  		} else if (op2->kind == ZEND_FFI_VAL_CHAR) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->d OP (zend_ffi_double)op2->ch; \
  		} else { \
  			val->kind = ZEND_FFI_VAL_ERROR; \
  		} \
  	} else if (val->kind == ZEND_FFI_VAL_CHAR) { \
  		if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->ch OP op2->i64; /* signed/unsigned */ \
  		} else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->ch OP op2->i64; \
  		} else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = (zend_ffi_double)val->ch OP op2->d; \
  		} else if (op2->kind == ZEND_FFI_VAL_CHAR) { \
  			val->kind = ZEND_FFI_VAL_INT32; \
  			val->i64 = val->ch OP op2->ch; \
  		} else { \
  			val->kind = ZEND_FFI_VAL_ERROR; \
  		} \
  	} else { \
  		val->kind = ZEND_FFI_VAL_ERROR; \
  	} \
  } while (0)
  
  void zend_ffi_expr_conditional(zend_ffi_val *val, zend_ffi_val *op2, zend_ffi_val *op3) /* {{{ */
  {
  	zend_ffi_expr_bool(val);
  	if (val->kind == ZEND_FFI_VAL_INT32) {
  		if (val->i64) {
  			*val = *op2;
  		} else {
  			*val = *op3;
  		}
  	}
  }
  /* }}} */
  
  void zend_ffi_expr_bool_or(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_bool(val);
  	zend_ffi_expr_bool(op2);
  	if (val->kind == ZEND_FFI_VAL_INT32 && op2->kind == ZEND_FFI_VAL_INT32) {
  		val->i64 = val->i64 || op2->i64;
  	} else {
  		val->kind = ZEND_FFI_VAL_ERROR;
  	}
  }
  /* }}} */
  
  void zend_ffi_expr_bool_and(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_bool(val);
  	zend_ffi_expr_bool(op2);
  	if (val->kind == ZEND_FFI_VAL_INT32 && op2->kind == ZEND_FFI_VAL_INT32) {
  		val->i64 = val->i64 && op2->i64;
  	} else {
  		val->kind = ZEND_FFI_VAL_ERROR;
  	}
  }
  /* }}} */
  
  void zend_ffi_expr_bw_or(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_int_math(val, op2, |);
  }
  /* }}} */
  
  void zend_ffi_expr_bw_xor(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_int_math(val, op2, ^);
  }
  /* }}} */
  
  void zend_ffi_expr_bw_and(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_int_math(val, op2, &);
  }
  /* }}} */
  
  void zend_ffi_expr_is_equal(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_cmp(val, op2, ==);
  }
  /* }}} */
  
  void zend_ffi_expr_is_not_equal(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_cmp(val, op2, !=);
  }
  /* }}} */
  
  void zend_ffi_expr_is_less(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_cmp(val, op2, <);
  }
  /* }}} */
  
  void zend_ffi_expr_is_greater(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_cmp(val, op2, >);
  }
  /* }}} */
  
  void zend_ffi_expr_is_less_or_equal(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_cmp(val, op2, <=);
  }
  /* }}} */
  
  void zend_ffi_expr_is_greater_or_equal(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_cmp(val, op2, >=);
  }
  /* }}} */
  
  void zend_ffi_expr_shift_left(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_int_math(val, op2, <<);
  }
  /* }}} */
  
  void zend_ffi_expr_shift_right(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_int_math(val, op2, >>);
  }
  /* }}} */
  
  void zend_ffi_expr_add(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_math(val, op2, +);
  }
  /* }}} */
  
  void zend_ffi_expr_sub(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_math(val, op2, -);
  }
  /* }}} */
  
  void zend_ffi_expr_mul(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_math(val, op2, *);
  }
  /* }}} */
  
  void zend_ffi_expr_div(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_math(val, op2, /);
  }
  /* }}} */
  
  void zend_ffi_expr_mod(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */
  {
  	zend_ffi_expr_int_math(val, op2, %); // ???
  }
  /* }}} */
  
  void zend_ffi_expr_cast(zend_ffi_val *val, zend_ffi_dcl *dcl) /* {{{ */
  {
  	zend_ffi_finalize_type(dcl);
  	switch (ZEND_FFI_TYPE(dcl->type)->kind) {
  		case ZEND_FFI_TYPE_FLOAT:
  			if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) {
  				val->kind = ZEND_FFI_VAL_FLOAT;
  				val->d = val->u64;
  			} else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) {
  				val->kind = ZEND_FFI_VAL_FLOAT;
  				val->d = val->i64;
  			} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) {
  				val->kind = ZEND_FFI_VAL_FLOAT;
  			} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  				val->kind = ZEND_FFI_VAL_FLOAT;
  				val->d = val->ch;
  			} else {
  				val->kind = ZEND_FFI_VAL_ERROR;
  			}
  			break;
  		case ZEND_FFI_TYPE_DOUBLE:
  			if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) {
  				val->kind = ZEND_FFI_VAL_DOUBLE;
  				val->d = val->u64;
  			} else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) {
  				val->kind = ZEND_FFI_VAL_DOUBLE;
  				val->d = val->i64;
  			} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) {
  				val->kind = ZEND_FFI_VAL_DOUBLE;
  			} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  				val->kind = ZEND_FFI_VAL_DOUBLE;
  				val->d = val->ch;
  			} else {
  				val->kind = ZEND_FFI_VAL_ERROR;
  			}
  			break;
  #ifdef HAVE_LONG_DOUBLE
  		case ZEND_FFI_TYPE_LONGDOUBLE:
  			if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) {
  				val->kind = ZEND_FFI_VAL_LONG_DOUBLE;
  				val->d = val->u64;
  			} else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) {
  				val->kind = ZEND_FFI_VAL_LONG_DOUBLE;
  				val->d = val->i64;
  			} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) {
  				val->kind = ZEND_FFI_VAL_LONG_DOUBLE;
  			} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  				val->kind = ZEND_FFI_VAL_LONG_DOUBLE;
  				val->d = val->ch;
  			} else {
  				val->kind = ZEND_FFI_VAL_ERROR;
  			}
  			break;
  #endif
  		case ZEND_FFI_TYPE_UINT8:
  		case ZEND_FFI_TYPE_UINT16:
  		case ZEND_FFI_TYPE_UINT32:
  		case ZEND_FFI_TYPE_BOOL:
  			if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64 || val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) {
  				val->kind = ZEND_FFI_VAL_UINT32;
  			} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) {
  				val->kind = ZEND_FFI_VAL_UINT32;
  				val->u64 = val->d;
  			} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  				val->kind = ZEND_FFI_VAL_UINT32;
  				val->u64 = val->ch;
  			} else {
  				val->kind = ZEND_FFI_VAL_ERROR;
  			}
  			break;
  		case ZEND_FFI_TYPE_SINT8:
  		case ZEND_FFI_TYPE_SINT16:
  		case ZEND_FFI_TYPE_SINT32:
  			if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64 || val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) {
  				val->kind = ZEND_FFI_VAL_INT32;
  			} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) {
  				val->kind = ZEND_FFI_VAL_INT32;
  				val->i64 = val->d;
  			} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  				val->kind = ZEND_FFI_VAL_INT32;
  				val->i64 = val->ch;
  			} else {
  				val->kind = ZEND_FFI_VAL_ERROR;
  			}
  			break;
  		case ZEND_FFI_TYPE_UINT64:
  			if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64 || val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) {
  				val->kind = ZEND_FFI_VAL_UINT64;
  			} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) {
  				val->kind = ZEND_FFI_VAL_UINT64;
  				val->u64 = val->d;
  			} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  				val->kind = ZEND_FFI_VAL_UINT64;
  				val->u64 = val->ch;
  			} else {
  				val->kind = ZEND_FFI_VAL_ERROR;
  			}
  			break;
  		case ZEND_FFI_TYPE_SINT64:
  			if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) {
  				val->kind = ZEND_FFI_VAL_CHAR;
  				val->ch = val->u64;
  			} else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) {
  				val->kind = ZEND_FFI_VAL_CHAR;
  				val->ch = val->i64;
  			} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) {
  				val->kind = ZEND_FFI_VAL_CHAR;
  				val->ch = val->d;
  			} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  			} else {
  				val->kind = ZEND_FFI_VAL_ERROR;
  			}
  			break;
  		case ZEND_FFI_TYPE_CHAR:
  			if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64 || val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) {
  				val->kind = ZEND_FFI_VAL_UINT32;
  			} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) {
  				val->kind = ZEND_FFI_VAL_UINT32;
  				val->u64 = val->d;
  			} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  				val->kind = ZEND_FFI_VAL_UINT32;
  				val->u64 = val->ch;
  			} else {
  				val->kind = ZEND_FFI_VAL_ERROR;
  			}
  			break;
  		default:
  			val->kind = ZEND_FFI_VAL_ERROR;
  			break;
  	}
  	zend_ffi_type_dtor(dcl->type);
  }
  /* }}} */
  
  void zend_ffi_expr_plus(zend_ffi_val *val) /* {{{ */
  {
  	if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) {
  	} else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) {
  	} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) {
  	} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  	} else {
  		val->kind = ZEND_FFI_VAL_ERROR;
  	}
  }
  /* }}} */
  
  void zend_ffi_expr_neg(zend_ffi_val *val) /* {{{ */
  {
  	if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) {
  		val->u64 = -val->u64;
  	} else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) {
  		val->i64 = -val->i64;
  	} else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) {
  		val->d = -val->d;
  	} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  		val->ch = -val->ch;
  	} else {
  		val->kind = ZEND_FFI_VAL_ERROR;
  	}
  }
  /* }}} */
  
  void zend_ffi_expr_bw_not(zend_ffi_val *val) /* {{{ */
  {
  	if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) {
  		val->u64 = ~val->u64;
  	} else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) {
  		val->i64 = ~val->i64;
  	} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  		val->ch = ~val->ch;
  	} else {
  		val->kind = ZEND_FFI_VAL_ERROR;
  	}
  }
  /* }}} */
  
  void zend_ffi_expr_bool_not(zend_ffi_val *val) /* {{{ */
  {
  	zend_ffi_expr_bool(val);
  	if (val->kind == ZEND_FFI_VAL_INT32) {
  		val->i64 = !val->i64;
  	}
  }
  /* }}} */
  
  void zend_ffi_expr_sizeof_val(zend_ffi_val *val) /* {{{ */
  {
  	if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_INT32) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  		val->u64 = zend_ffi_type_uint32.size;
  	} else if (val->kind == ZEND_FFI_VAL_UINT64 || val->kind == ZEND_FFI_VAL_INT64) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  		val->u64 = zend_ffi_type_uint64.size;
  	} else if (val->kind == ZEND_FFI_VAL_FLOAT) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  		val->u64 = zend_ffi_type_float.size;
  	} else if (val->kind == ZEND_FFI_VAL_DOUBLE) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  		val->u64 = zend_ffi_type_double.size;
  	} else if (val->kind == ZEND_FFI_VAL_LONG_DOUBLE) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  #ifdef _WIN32
  		val->u64 = zend_ffi_type_double.size;
  #else
  		val->u64 = zend_ffi_type_long_double.size;
  #endif
  	} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  		val->u64 = zend_ffi_type_char.size;
  	} else if (val->kind == ZEND_FFI_VAL_STRING) {
  		if (memchr(val->str, '\\', val->len)) {
  			// TODO: support for escape sequences ???
  			val->kind = ZEND_FFI_VAL_ERROR;
  		} else {
  			val->kind = ZEND_FFI_VAL_UINT32;
  			val->u64 = val->len + 1;
  		}
  	} else {
  		val->kind = ZEND_FFI_VAL_ERROR;
  	}
  }
  /* }}} */
  
  void zend_ffi_expr_sizeof_type(zend_ffi_val *val, zend_ffi_dcl *dcl) /* {{{ */
  {
  	zend_ffi_type *type;
  
  	zend_ffi_finalize_type(dcl);
  	type = ZEND_FFI_TYPE(dcl->type);
  	val->kind = (type->size > 0xffffffff) ? ZEND_FFI_VAL_UINT64 : ZEND_FFI_VAL_UINT32;
  	val->u64 = type->size;
  	zend_ffi_type_dtor(dcl->type);
  }
  /* }}} */
  
  void zend_ffi_expr_alignof_val(zend_ffi_val *val) /* {{{ */
  {
  	if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_INT32) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  		val->u64 = zend_ffi_type_uint32.align;
  	} else if (val->kind == ZEND_FFI_VAL_UINT64 || val->kind == ZEND_FFI_VAL_INT64) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  		val->u64 = zend_ffi_type_uint64.align;
  	} else if (val->kind == ZEND_FFI_VAL_FLOAT) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  		val->u64 = zend_ffi_type_float.align;
  	} else if (val->kind == ZEND_FFI_VAL_DOUBLE) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  		val->u64 = zend_ffi_type_double.align;
  #ifdef HAVE_LONG_DOUBLE
  	} else if (val->kind == ZEND_FFI_VAL_LONG_DOUBLE) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  		val->u64 = zend_ffi_type_long_double.align;
  #endif
  	} else if (val->kind == ZEND_FFI_VAL_CHAR) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  		val->u64 = zend_ffi_type_char.size;
  	} else if (val->kind == ZEND_FFI_VAL_STRING) {
  		val->kind = ZEND_FFI_VAL_UINT32;
  		val->u64 = _Alignof(char*);
  	} else {
  		val->kind = ZEND_FFI_VAL_ERROR;
  	}
  }
  /* }}} */
  
  void zend_ffi_expr_alignof_type(zend_ffi_val *val, zend_ffi_dcl *dcl) /* {{{ */
  {
  	zend_ffi_finalize_type(dcl);
  	val->kind = ZEND_FFI_VAL_UINT32;
  	val->u64 = ZEND_FFI_TYPE(dcl->type)->align;
  	zend_ffi_type_dtor(dcl->type);
  }
  /* }}} */
  
  void zend_ffi_val_number(zend_ffi_val *val, int base, const char *str, size_t str_len) /* {{{ */
  {
  	int u = 0;
  	int l = 0;
  
  	if (str[str_len-1] == 'u' || str[str_len-1] == 'U') {
  		u = 1;
  		if (str[str_len-2] == 'l' || str[str_len-2] == 'L') {
  			l = 1;
  			if (str[str_len-3] == 'l' || str[str_len-3] == 'L') {
  				l = 2;
  			}
  		}
  	} else if (str[str_len-1] == 'l' || str[str_len-1] == 'L') {
  		l = 1;
  		if (str[str_len-2] == 'l' || str[str_len-2] == 'L') {
  			l = 2;
  			if (str[str_len-3] == 'u' || str[str_len-3] == 'U') {
  				u = 1;
  			}
  		} else if (str[str_len-2] == 'u' || str[str_len-2] == 'U') {
  			u = 1;
  		}
  	}
  	if (u) {
  		val->u64 = strtoull(str, NULL, base);
  		if (l == 0) {
  			val->kind = ZEND_FFI_VAL_UINT32;
  		} else if (l == 1) {
  			val->kind = (sizeof(long) == 4) ? ZEND_FFI_VAL_UINT32 : ZEND_FFI_VAL_UINT64;
  		} else if (l == 2) {
  			val->kind = ZEND_FFI_VAL_UINT64;
  		}
  	} else {
  		val->i64 = strtoll(str, NULL, base);
  		if (l == 0) {
  			val->kind = ZEND_FFI_VAL_INT32;
  		} else if (l == 1) {
  			val->kind = (sizeof(long) == 4) ? ZEND_FFI_VAL_INT32 : ZEND_FFI_VAL_INT64;
  		} else if (l == 2) {
  			val->kind = ZEND_FFI_VAL_INT64;
  		}
  	}
  }
  /* }}} */
  
  void zend_ffi_val_float_number(zend_ffi_val *val, const char *str, size_t str_len) /* {{{ */
  {
  	val->d = strtold(str, NULL);
  	if (str[str_len-1] == 'f' || str[str_len-1] == 'F') {
  		val->kind = ZEND_FFI_VAL_FLOAT;
  	} else if (str[str_len-1] == 'l' || str[str_len-1] == 'L') {
  		val->kind = ZEND_FFI_VAL_LONG_DOUBLE;
  	} else {
  		val->kind = ZEND_FFI_VAL_DOUBLE;
  	}
  }
  /* }}} */
  
  void zend_ffi_val_string(zend_ffi_val *val, const char *str, size_t str_len) /* {{{ */
  {
  	if (str[0] != '\"') {
  		val->kind = ZEND_FFI_VAL_ERROR;
  	} else {
  		val->kind = ZEND_FFI_VAL_STRING;
  		val->str = str + 1;
  		val->len = str_len - 2;
  	}
  }
  /* }}} */
  
  void zend_ffi_val_character(zend_ffi_val *val, const char *str, size_t str_len) /* {{{ */
  {
  	int n;
  
  	if (str[0] != '\'') {
  		val->kind = ZEND_FFI_VAL_ERROR;
  	} else {
  		val->kind = ZEND_FFI_VAL_CHAR;
  		if (str_len == 3) {
  			val->ch = str[1];
  		} else if (str[1] == '\\') {
  			if (str[2] == 'a') {
  			} else if (str[2] == 'b' && str_len == 4) {
  				val->ch = '\b';
  			} else if (str[2] == 'f' && str_len == 4) {
  				val->ch = '\f';
  			} else if (str[2] == 'n' && str_len == 4) {
  				val->ch = '\n';
  			} else if (str[2] == 'r' && str_len == 4) {
  				val->ch = '\r';
  			} else if (str[2] == 't' && str_len == 4) {
  				val->ch = '\t';
  			} else if (str[2] == 'v' && str_len == 4) {
  				val->ch = '\v';
  			} else if (str[2] >= '0' || str[2] <= '7') {
  				n = str[2] - '0';
  				if (str[3] >= '0' || str[3] <= '7') {
  					n = n * 8 + (str[3] - '0');
  					if ((str[4] >= '0' || str[4] <= '7') && str_len == 6) {
  						n = n * 8 + (str[4] - '0');
  					} else if (str_len != 5) {
  						val->kind = ZEND_FFI_VAL_ERROR;
  					}
  				} else if (str_len != 4) {
  					val->kind = ZEND_FFI_VAL_ERROR;
  				}
  				if (n <= 0xff) {
  					val->ch = n;
  				} else {
  					val->kind = ZEND_FFI_VAL_ERROR;
  				}
  			} else if (str[2] == 'x') {
  				if (str[3] >= '0' || str[3] <= '7') {
  					n = str[3] - '0';
  				} else if (str[3] >= 'A' || str[3] <= 'F') {
  					n = str[3] - 'A';
  				} else if (str[3] >= 'a' || str[3] <= 'f') {
  					n = str[3] - 'a';
  				} else {
  					val->kind = ZEND_FFI_VAL_ERROR;
  				}
  				if ((str[4] >= '0' || str[4] <= '7') && str_len == 6) {
  					n = n * 16 + (str[4] - '0');
  				} else if ((str[4] >= 'A' || str[4] <= 'F') && str_len == 6) {
  					n = n * 16 + (str[4] - 'A');
  				} else if ((str[4] >= 'a' || str[4] <= 'f') && str_len == 6) {
  					n = n * 16 + (str[4] - 'a');
  				} else if (str_len != 5) {
  					val->kind = ZEND_FFI_VAL_ERROR;
  				}
  				val->ch = n;
  			} else if (str_len == 4) {
  				val->ch = str[2];
  			} else {
  				val->kind = ZEND_FFI_VAL_ERROR;
  			}
  		} else {
  			val->kind = ZEND_FFI_VAL_ERROR;
  		}
  	}
  }
  /* }}} */
  