瀏覽代碼

init project

lxg 2 年之前
當前提交
13f6f01e88
共有 36 個文件被更改,包括 5582 次插入0 次删除
  1. 8 0
      .idea/.gitignore
  2. 8 0
      .idea/modules.xml
  3. 9 0
      .idea/php.iml
  4. 95 0
      README.md
  5. 143 0
      extension/class.c
  6. 32 0
      extension/class.h
  7. 740 0
      extension/extension.c
  8. 580 0
      extension/extension.go
  9. 12 0
      extension/extension.h
  10. 20 0
      extension/init.go
  11. 147 0
      extension/objectmap.c
  12. 28 0
      extension/objectmap.h
  13. 12 0
      extension/phpgo.go
  14. 1063 0
      extension/uthash.h
  15. 3 0
      go.mod
  16. 88 0
      zend/array.c
  17. 31 0
      zend/array.h
  18. 34 0
      zend/c_util.go
  19. 27 0
      zend/clog.c
  20. 635 0
      zend/clog.h
  21. 35 0
      zend/compat.c
  22. 45 0
      zend/compat.h
  23. 5 0
      zend/customize.h
  24. 5 0
      zend/goapi.c
  25. 543 0
      zend/goapi.go
  26. 123 0
      zend/goapi.h
  27. 72 0
      zend/init.go
  28. 10 0
      zend/sphp.c
  29. 259 0
      zend/szend.c
  30. 21 0
      zend/szend.h
  31. 14 0
      zend/sztypes.h
  32. 330 0
      zend/typeconv.go
  33. 71 0
      zend/utils.go
  34. 66 0
      zend/zend.go
  35. 196 0
      zend/zend_ini.go
  36. 72 0
      zend/zend_string.go

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/php.iml" filepath="$PROJECT_DIR$/.idea/php.iml" />
+    </modules>
+  </component>
+</project>

+ 9 - 0
.idea/php.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="Go" enabled="true" />
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 95 - 0
README.md

@@ -0,0 +1,95 @@
+### php-go
+
+Write PHP extension using go/golang. Zend API wrapper for go/golang.
+
+Simple, easy, fun to write PHP extensions.
+
+### Features
+
+* function support
+* struct/class support
+* constant support
+* primitive data type as parameters and return values, (u)int*/float*/string/bool
+* complex data type as parameters, map/slice/array
+* all can be done by programmatic
+
+### Environment
+
+* Modern Linux/Unix system
+* PHP 5.5+/7.x
+* go version 1.4+
+* php5-dev installed (`apt-get install php5-dev`)
+* for MacOS, go 1.8+
+
+### Build & Install
+
+go get:
+
+```
+go get github.com/kitech/php-go
+cd $GOPATH/src/github.com/kitech/php-go
+# adjust PHPCFG path if needed
+PHPCFG=`which php-config` make
+```
+
+manual:
+
+    mkdir -p $GOPATH/src/github.com/kitech
+    git clone https://github.com/kitech/php-go.git $GOPATH/src/github.com/kitech/php-go
+    cd $GOPATH/src/github.com/kitech/php-go
+    make
+    ls -lh php-go/hello.so
+    php56 -d extension=./hello.so examples/hello.php
+
+
+### Examples
+
+```go
+// package main is required
+package main
+
+import "git.nspix.com/golang/php/extension"
+
+func foo_in_go() {
+}
+
+type Bar struct{}
+func NewBar() *Bar{
+    return &Bar{}
+}
+
+func init() {
+	extension.InitExtension("mymod", "1.0")
+	extension.AddFunc("foo_in_php", foo_in_go)
+	extension.AddClass("bar_in_php", NewBar)
+}
+
+// should not run this function
+// required for go build though.
+func main() { panic("wtf") }
+```
+
+### Limitations
+
+* Deadlock in pcntl_fork'ed subprocess when call extension functions. #47
+
+
+### TODO
+
+- [ ] install with go get
+- [x] improve php7 support
+- [ ] namespace support
+- [ ] multiple extension support
+- [ ] class member access support
+- [x] unlimited function/method/class count support
+- [x] global ini var support
+- [ ] fill phpinfo
+
+
+Contributing
+------------
+1. Fork it
+2. Create your feature branch (``git checkout -b my-new-feature``)
+3. Commit your changes (``git commit -am 'Add some feature'``)
+4. Push to the branch (``git push origin my-new-feature``)
+5. Create new Pull Request

+ 143 - 0
extension/class.c

@@ -0,0 +1,143 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <assert.h>
+
+/**
+ *  PHP includes
+ */
+#include <php.h>
+#include <zend_exceptions.h>
+#include <zend_interfaces.h>
+#include <zend_ini.h>
+#include <SAPI.h>
+#include <zend_hash.h>
+#include <zend_API.h>
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+
+#include "_cgo_export.h"
+
+// #include "sztypes.h"
+#include "../zend/goapi.h"
+#include "../zend/clog.h"
+#include "uthash.h"
+#include "objectmap.h"
+
+#include "class.h"
+
+struct _phpgo_class_entry {
+    zend_class_entry *ce;
+    // phpgo_function_entry **fes;
+    const char *class_name;
+
+    phpgo_object_map *fmap;
+    zend_function_entry *fes;
+};
+
+phpgo_class_entry* phpgo_class_new(const char *class_name) {
+    phpgo_class_entry* pce = (phpgo_class_entry*)calloc(1, sizeof(phpgo_class_entry));
+    memset(pce, 0, sizeof(phpgo_class_entry));
+    pce->ce = (zend_class_entry*)calloc(1, sizeof(zend_class_entry));
+    pce->class_name = class_name;
+    pce->fmap = NULL;
+
+    return pce;
+}
+
+void phpgo_class_method_add(phpgo_class_entry* pce, const char *func_name) {
+    phpgo_function_entry* pfe = phpgo_function_new(func_name);
+    phpgo_object_map_add(&pce->fmap, func_name, pfe);
+
+    int count = phpgo_object_map_count(pce->fmap);
+    if (count == 1) {
+        pce->fes = (zend_function_entry*)calloc(count + 1, sizeof(zend_function_entry));
+    } else {
+        pce->fes = (zend_function_entry*)realloc(pce->fes, (count + 1) * sizeof(zend_function_entry));
+    }
+    memset(&pce->fes[count], 0, sizeof(zend_function_entry));
+    memcpy(&pce->fes[count-1], phpgo_function_get(pfe), sizeof(zend_function_entry));
+    pce->ce->info.internal.builtin_functions = pce->fes;
+}
+
+zend_class_entry* phpgo_class_get(phpgo_class_entry* pce) {
+    return pce->ce;
+}
+
+zend_function_entry* phpgo_class_get_funcs(phpgo_class_entry* pce) {
+    return (zend_function_entry*)pce->ce->info.internal.builtin_functions;
+}
+
+phpgo_function_entry* phpgo_class_method_get(phpgo_class_entry* pce, const char *func_name) {
+    phpgo_function_entry* pfe = (phpgo_function_entry*)phpgo_object_map_get(pce->fmap, func_name);
+    if (pfe != NULL) {
+        return pfe;
+    }
+    return NULL;
+}
+
+int phpgo_class_method_count(phpgo_class_entry* pce) {
+    return phpgo_object_map_count(pce->fmap);
+}
+
+// function operations
+struct _phpgo_function_entry {
+    const char *func_name;
+    zend_function_entry *fe;
+};
+
+#ifdef ZEND_ENGINE_3
+extern void phpgo_function_handler(zend_execute_data *execute_data, zval *return_value);
+#else
+extern void phpgo_function_handler(int ht, zval *return_value, zval **return_value_ptr,
+                                   zval *this_ptr, int return_value_used TSRMLS_DC);
+#endif
+
+phpgo_function_entry *phpgo_function_new(const char *func_name) {
+    phpgo_function_entry *pce = (phpgo_function_entry*)calloc(1, sizeof(phpgo_function_entry));
+    pce->func_name = func_name;
+    pce->fe = (zend_function_entry*)calloc(1, sizeof(zend_function_entry));
+
+    zend_function_entry *fe = pce->fe;
+    zend_function_entry e = {strdup(func_name), phpgo_function_handler, NULL, 0, 0};
+    memcpy(fe, &e, sizeof(e));
+
+    return pce;
+}
+
+int phpgo_function_delete(phpgo_function_entry *pfe) {
+    free((void*)(pfe->fe->fname));
+    free(pfe->fe);
+    free(pfe);
+    return 0;
+}
+
+zend_function_entry* phpgo_function_get(phpgo_function_entry* pfe) {
+    return pfe->fe;
+}
+
+//
+#define MAX_ARG_NUM 10
+struct _phpgo_callback_info {
+    char arg_types[MAX_ARG_NUM];
+    int ret_type;
+    int varidict;
+};
+
+phpgo_callback_info* phpgo_callback_info_new(char *arg_types, int ret_type) {
+    phpgo_callback_info* cbi = (phpgo_callback_info*)calloc(1, sizeof(phpgo_callback_info));
+    strncpy(cbi->arg_types, arg_types, sizeof(cbi->arg_types));
+    cbi->ret_type = ret_type;
+    return cbi;
+}
+
+char* phpgo_callback_info_get_arg_types(phpgo_callback_info* cbi) {
+    return cbi->arg_types;
+}
+
+int phpgo_callback_info_get_ret_type(phpgo_callback_info* cbi) {
+    return cbi->ret_type;
+}

+ 32 - 0
extension/class.h

@@ -0,0 +1,32 @@
+#ifndef _PHPGO_CLASS_H_
+#define _PHPGO_CLASS_H_
+
+#define GLOBAL_VCLASS_NAME "_PHPGO_GLOBAL_"
+#define MAX_ARG_NUM 10
+
+// struct _phpgo_function_entry;
+typedef struct _phpgo_function_entry phpgo_function_entry;
+
+phpgo_function_entry *phpgo_function_new(const char *func_name);
+int phpgo_function_delete(phpgo_function_entry *pfe);
+zend_function_entry* phpgo_function_get(phpgo_function_entry* pfe);
+
+///////
+// struct _phpgo_class_entry;
+typedef struct _phpgo_class_entry phpgo_class_entry;
+
+phpgo_class_entry* phpgo_class_new(const char *class_name);
+void phpgo_class_method_add(phpgo_class_entry* pce, const char *func_name);
+phpgo_function_entry* phpgo_class_method_get(phpgo_class_entry* pce, const char *func_name);
+zend_class_entry* phpgo_class_get(phpgo_class_entry* pce);
+zend_function_entry* phpgo_class_get_funcs(phpgo_class_entry* pce);
+int phpgo_class_method_count(phpgo_class_entry* pce);
+
+//
+typedef struct _phpgo_callback_info phpgo_callback_info;
+phpgo_callback_info* phpgo_callback_info_new(char *arg_types, int ret_type);
+char* phpgo_callback_info_get_arg_types(phpgo_callback_info* cbi);
+int phpgo_callback_info_get_ret_type(phpgo_callback_info* cbi);
+
+#endif
+

+ 740 - 0
extension/extension.c

@@ -0,0 +1,740 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <assert.h>
+
+/**
+ *  PHP includes
+ */
+#include <php.h>
+#include <zend_exceptions.h>
+#include <zend_interfaces.h>
+#include <zend_ini.h>
+#include <SAPI.h>
+#include <zend_hash.h>
+#include <zend_API.h>
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+
+#include "_cgo_export.h"
+
+// #include "sztypes.h"
+#include "../zend/goapi.h"
+#include "../zend/clog.h"
+#include "objectmap.h"
+#include "class.h"
+#include "../zend/customize.h"
+
+// TODO PHP7支持
+static phpgo_object_map* g_module_map = NULL;
+static zend_module_entry g_entry = {0};
+static phpgo_object_map* g_class_map = NULL;
+static phpgo_object_map* g_cbinfo_map = NULL;
+
+// ZEND_DECLARE_MODULE_GLOBALS(phpgo);
+// static void init_globals(zend_phpgo_globals *globals) {}
+
+static int(*phpgo_module_startup_cbfunc)(int, int) = 0;
+static int(*phpgo_module_shutdown_cbfunc)(int, int) = 0;
+static int(*phpgo_request_startup_cbfunc)(int, int) = 0;
+static int(*phpgo_request_shutdown_cbfunc)(int, int) = 0;
+
+int phpgo_module_startup_func(int type, int module_number TSRMLS_DC)
+{
+    if (phpgo_module_startup_cbfunc) {
+        return call_golang_function(phpgo_module_startup_cbfunc, type, module_number, 0, 0, 0, 0, 0, 0, 0, 0);
+    }
+    return 0;
+}
+
+int phpgo_module_shutdown_func(int type, int module_number TSRMLS_DC)
+{
+    if (phpgo_module_shutdown_cbfunc) {
+        return call_golang_function(phpgo_module_shutdown_cbfunc, type, module_number, 0, 0, 0, 0, 0, 0, 0, 0);
+    }
+    return 0;
+}
+
+int phpgo_request_startup_func(int type, int module_number TSRMLS_DC)
+{
+    if (phpgo_request_startup_cbfunc) {
+        return call_golang_function(phpgo_request_startup_cbfunc, type, module_number, 0, 0, 0, 0, 0, 0, 0, 0);
+    }
+    return 0;
+}
+
+int phpgo_request_shutdown_func(int type, int module_number TSRMLS_DC)
+{
+    if (phpgo_request_shutdown_cbfunc) {
+        return call_golang_function(phpgo_request_shutdown_cbfunc, type, module_number, 0, 0, 0, 0, 0, 0, 0, 0);
+    }
+    return 0;
+}
+
+void phpgo_register_init_functions(void *module_startup_func, void *module_shutdown_func,
+                                   void *request_startup_func, void *request_shutdown_func)
+{
+    phpgo_module_startup_cbfunc = (int (*)(int, int))module_startup_func;
+    phpgo_module_shutdown_cbfunc = (int (*)(int, int))module_shutdown_func;
+    phpgo_request_startup_cbfunc = (int (*)(int, int))request_startup_func;
+    phpgo_request_shutdown_cbfunc = (int (*)(int, int))request_shutdown_func;
+    return;
+}
+
+// TODO module name from args
+// TODO module version from args
+// TODO module startup/shutdown... function from args, or set seperator
+void *phpgo_get_module(char *name, char *version) {
+    char *cname = (char*)GLOBAL_VCLASS_NAME;
+    phpgo_class_entry* pce = (phpgo_class_entry*)phpgo_object_map_get(g_class_map, cname);
+    zend_function_entry* funcs = phpgo_class_get_funcs(pce);
+
+    zend_module_entry te = {
+        STANDARD_MODULE_HEADER,
+        name, // "phpgo",
+        funcs, // g_funcs[GLOBAL_VCLASS_ID],
+        phpgo_module_startup_func,
+        phpgo_module_shutdown_func,
+        phpgo_request_startup_func,		/* Replace with NULL if there's nothing to do at request start */
+        phpgo_request_shutdown_func,	/* Replace with NULL if there's nothing to do at request end */
+        NULL,
+        version, // "1.0",
+        STANDARD_MODULE_PROPERTIES
+    };
+
+    memcpy(&g_entry, &te, sizeof(zend_module_entry));
+    return &g_entry;
+}
+
+int phpgo_get_module_number() {
+    return g_entry.module_number;
+}
+
+
+// TODO 支持php callable类型,方便实现回调?
+// TODO 支持php array类型?有点复杂。
+char *type2name(int type)
+{
+    switch (type) {
+    case IS_ARRAY:
+        return "array";
+    case IS_STRING:
+        return "string";
+    case IS_DOUBLE:
+        return "double";
+#ifdef ZEND_ENGINE_3
+    case _IS_BOOL:
+#else
+    case IS_BOOL:
+#endif
+        return "bool";
+    case IS_LONG:
+        return "long";
+#ifdef ZEND_ENGINE_3
+    case IS_UNDEF:
+#endif
+    case IS_NULL:
+        return "void";
+    case IS_OBJECT:
+        return "object";
+    case IS_RESOURCE:
+        return "pointer";
+    case IS_CALLABLE:
+        return "callable";
+#ifdef IS_LEXICAL_VAR
+    case IS_LEXICAL_VAR:
+        return "var";
+#endif
+    default:
+        return "unknown";
+    }
+    return NULL;
+ }
+
+// 计算要转换的参数个数,有效的参数个数
+// php提供的参数个数要>=go函数需要的参数个数
+// 如果go是变长参数个数, 则计算出 最小 值,并把php提供的所有参数全部传递
+static int phpgo_function_num_args(int cbid, phpgo_callback_info* cbi, int supply_num_args)
+{
+    char* arg_types = phpgo_callback_info_get_arg_types(cbi);
+    int num_args = arg_types != NULL ? strlen(arg_types) : 0;
+
+    return num_args;
+}
+
+// PHP函数参数转换为go类型的参数
+static void* phpgo_function_conv_arg(int cbid, int idx, char ch, int zty, zval *zarg)
+{
+    int prmty = zty;
+    void *rv = NULL;
+
+#ifdef ZEND_ENGINE_3
+    zval *conv_zarg = zarg;
+    zval *macro_zarg = zarg;
+#else
+    zval **conv_zarg = &zarg;
+    zval *macro_zarg = zarg;
+#endif
+
+    if (ch == 's') {
+        if (prmty != IS_STRING) {
+            zend_error(E_WARNING, "Parameters type not match, need (%c), given: %s",
+                       ch, type2name(prmty));
+        }
+
+        convert_to_string_ex(conv_zarg);
+        char *arg = Z_STRVAL_P(macro_zarg);
+        dlog_debug("arg%d(%c), %s(%d)", idx, ch, arg, Z_STRLEN_P(macro_zarg));
+        goapi_new_value(GT_String, (uint64_t)arg, &rv);
+    } else if (ch == 'l') {
+        if (prmty != IS_LONG) {
+            zend_error(E_WARNING, "Parameters type not match, need (%c), given: %s",
+                       ch, type2name(prmty));
+            dlog_error("Parameters type not match, need (%c), given: %s",
+                       ch, type2name(prmty));
+        }
+
+        convert_to_long_ex(conv_zarg);
+        long arg = (long)Z_LVAL_P(macro_zarg);
+        dlog_debug("arg%d(%c), %d", idx, ch, arg);
+        goapi_new_value(GT_Int64, (uint64_t)arg, &rv);
+    } else if (ch == 'b') {
+        convert_to_boolean_ex(conv_zarg);
+#ifdef ZEND_ENGINE_3
+        if (Z_TYPE_P(conv_zarg) == IS_TRUE) {
+            goapi_new_value(GT_Bool, (uint64_t)1, &rv);
+        } else {
+            goapi_new_value(GT_Bool, (uint64_t)0, &rv);
+        }
+#else
+        zend_bool arg = (zend_bool)Z_BVAL_P(macro_zarg);
+        goapi_new_value(GT_Bool, (uint64_t)arg, &rv);
+#endif
+    } else if (ch == 'd') {
+        convert_to_double_ex(conv_zarg);
+        double arg = (double)Z_DVAL_P(macro_zarg);
+        double* parg = calloc(1, sizeof(double));
+        *parg = (double)Z_DVAL_P(macro_zarg);
+        // argv[idx] = (void*)(ulong)arg;  // error
+        // memcpy(&argv[idx], &arg, sizeof(argv[idx])); // ok
+        // float32(uintptr(ax))
+        goapi_new_value(GT_Float64, (uint64_t)parg, &rv);
+    } else if (ch == 'a') {
+        if (Z_TYPE_P(macro_zarg) == IS_STRING) {
+            char *arg = Z_STRVAL_P(macro_zarg);
+            goapi_new_value(GT_String, (uint64_t)arg, &rv);
+#ifdef ZEND_ENGINE_3
+        } else if (Z_TYPE_P(macro_zarg) == IS_TRUE) {
+            goapi_new_value(GT_Bool, (uint64_t)1, &rv);
+        } else if (Z_TYPE_P(macro_zarg) == IS_FALSE) {
+            goapi_new_value(GT_Bool, (uint64_t)0, &rv);
+#else
+        } else if (Z_TYPE_P(macro_zarg) == IS_BOOL) {
+            zend_bool arg = (zend_bool)Z_BVAL_P(macro_zarg);
+            goapi_new_value(GT_Bool, (uint64_t)arg, &rv);
+#endif
+        } else if (Z_TYPE_P(macro_zarg) == IS_DOUBLE) {
+            double* parg = calloc(1, sizeof(double));
+            *parg = (double)Z_DVAL_P(macro_zarg);
+            goapi_new_value(GT_Float64, (uint64_t)parg, &rv);
+        } else if (Z_TYPE_P(macro_zarg) == IS_LONG) {
+            long arg = (long)Z_LVAL_P(macro_zarg);
+            goapi_new_value(GT_Int64, (uint64_t)arg, &rv);
+        } else if (Z_TYPE_P(zarg) == IS_OBJECT) {
+            void *parg = (void*)macro_zarg;
+            goapi_new_value(GT_UnsafePointer, (uint64_t)parg, &rv);
+        } else if (Z_TYPE_P(zarg) == IS_RESOURCE) {
+            void *parg = (void*)macro_zarg;
+            goapi_new_value(GT_UnsafePointer, (uint64_t)parg, &rv);
+        } else {
+            dlog_debug("arg%d(%c), %s(%d) unsported to Any", idx, ch,
+                   type2name(Z_TYPE_P(macro_zarg)), Z_TYPE_P(macro_zarg));
+        }
+
+        // TODO array/vector convert
+    } else if (ch == 'v') {
+        HashTable *arr_hash = Z_ARRVAL_P(macro_zarg);
+        HashPosition pos;
+        zval *edata = NULL;
+
+        for (zend_hash_internal_pointer_reset_ex(arr_hash, &pos);
+#ifdef ZEND_ENGINE_3
+             (edata = zend_hash_get_current_data_ex(arr_hash, &pos)) != NULL;
+#else
+             zend_hash_get_current_data_ex(arr_hash, (void**)&edata, &pos) == SUCCESS;
+#endif
+             zend_hash_move_forward_ex(arr_hash, &pos)) {
+
+#ifdef ZEND_ENGINE_3
+            zval *conv_edata = edata;
+#else
+            zval **conv_edata = &edata;
+#endif
+
+            if (rv == NULL) {
+                switch (Z_TYPE_P(edata)) {
+                case IS_LONG:
+                    goapi_array_new(GT_Int64, &rv);
+                    break;
+                default:
+                    goapi_array_new(GT_String, &rv);
+                    break;
+                }
+            }
+
+            switch (Z_TYPE_P(edata)) {
+            case IS_LONG:
+                goapi_array_push(rv, (void*)Z_LVAL_P(edata), &rv);
+                break;
+            default:
+                convert_to_string_ex(conv_edata);
+                dlog_debug("array idx(%d)=T(%d=%s, val=%s)", pos,
+                       Z_TYPE_P(edata), type2name(Z_TYPE_P(edata)), Z_STRVAL_P(edata));
+                goapi_array_push(rv, Z_STRVAL_P(edata), &rv);
+                break;
+            }
+        }
+
+        // TODO array/map convert
+    } else if (ch == 'm') {
+        // 简化成string=>string映射吧
+        goapi_map_new(&rv);
+
+        HashTable *arr_hash = Z_ARRVAL_P(macro_zarg);
+        HashPosition pos;
+        zval *edata;
+
+        for (zend_hash_internal_pointer_reset_ex(arr_hash, &pos);
+#ifdef ZEND_ENGINE_3
+             (edata = zend_hash_get_current_data_ex(arr_hash, &pos)) != NULL;
+#else
+             zend_hash_get_current_data_ex(arr_hash, (void**)&edata, &pos) == SUCCESS;
+#endif
+             zend_hash_move_forward_ex(arr_hash, &pos)) {
+
+            zval vkey = {0};
+            zval *ekey = &vkey;
+            zend_hash_get_current_key_zval_ex(arr_hash, &vkey, &pos);
+
+#ifdef ZEND_ENGINE_3
+            zval *conv_ekey = ekey;
+            zval *conv_edata = edata;
+#else
+            zval **conv_ekey = &ekey;
+            zval **conv_edata = &edata;
+#endif
+
+            switch (Z_TYPE_P(edata)) {
+            default:
+                convert_to_string_ex(conv_ekey);
+                convert_to_string_ex(conv_edata);
+                dlog_debug("array idx(%d)=K(%s)=T(%d=%s, val=%s)", pos, Z_STRVAL_P(ekey),
+                       Z_TYPE_P(edata), type2name(Z_TYPE_P(edata)), Z_STRVAL_P(edata));
+                goapi_map_add(rv, Z_STRVAL_P(ekey), Z_STRVAL_P(edata));
+                break;
+            }
+        }
+
+    } else {
+        dlog_debug("arg%d(%c), %s(%d)", idx, ch, type2name(prmty), prmty);
+        zend_error(E_WARNING, "Parameters type not match, need (%c), given: %s",
+                   ch, type2name(prmty));
+        dlog_error("Parameters type not match, need (%c), given: %s",
+                   ch, type2name(prmty));
+        return NULL;
+    }
+
+    return rv;
+}
+
+static void phpgo_function_conv_args(int cbid, phpgo_callback_info* cbi, int supply_num_args, void *argv[])
+{
+    int num_args = phpgo_function_num_args(cbid, cbi, supply_num_args);
+    // int supply_num_args = ZEND_NUM_ARGS();
+
+#ifdef ZEND_ENGINE_3
+    zval args[MAX_ARG_NUM] = {0};
+#else
+    zval **args[MAX_ARG_NUM] = {0};
+#endif
+    if (zend_get_parameters_array_ex(num_args, args) == FAILURE) {
+        dlog_error("param count error: %d", num_args);
+        WRONG_PARAM_COUNT;
+        return;
+    }
+
+    // void *argv[MAX_ARG_NUM] = {0};
+    dlog_debug("parse params: %d", num_args);
+    char* arg_types = phpgo_callback_info_get_arg_types(cbi);
+    // function has not this arg, don't -1
+    for (int idx = 0; idx < num_args; idx ++) {
+#ifdef ZEND_ENGINE_3
+        zval *zarg = &args[idx];
+#else
+        zval *zarg = *(args[idx]);
+#endif
+        dlog_debug("arg%d, type=%d", idx, Z_TYPE_P(zarg));
+        int prmty = Z_TYPE_P(zarg);
+        char ch = arg_types[idx];
+
+        argv[idx] = phpgo_function_conv_arg(cbid, idx, ch, prmty, zarg);
+    }
+
+    return;
+}
+
+static void phpgo_method_conv_args(int cbid, phpgo_callback_info* cbi, int supply_num_args, void *argv[])
+{
+    int num_args = phpgo_function_num_args(cbid, cbi, supply_num_args);
+    // int supply_num_args = ZEND_NUM_ARGS();
+
+#ifdef ZEND_ENGINE_3
+    zval args[MAX_ARG_NUM] = {0};
+#else
+    zval **args[MAX_ARG_NUM] = {0};
+#endif
+
+    if (zend_get_parameters_array_ex(num_args-1, args) == FAILURE) {
+        dlog_error("param count error: %d", num_args);
+        WRONG_PARAM_COUNT;
+        return;
+    }
+
+    // void *argv[MAX_ARG_NUM] = {0};
+    dlog_debug("parse params: %d", num_args);
+    char* arg_types = phpgo_callback_info_get_arg_types(cbi);
+    for (int idx = 0; idx < num_args-1; idx ++) {
+#ifdef ZEND_ENGINE_3
+        zval *zarg = &args[idx];
+#else
+        zval *zarg = *(args[idx]);
+#endif
+
+        dlog_debug("arg%d, type=%d", idx, Z_TYPE_P(zarg));
+        int prmty = Z_TYPE_P(zarg);
+        char ch = arg_types[idx+1];
+
+        argv[idx] = phpgo_function_conv_arg(cbid, idx, ch, prmty, zarg);
+    }
+
+    return;
+}
+
+static void phpgo_function_reutrn_php_array(void *p0, zval *return_value) {
+    array_init(return_value);
+
+    goapi_set_php_array(p0, (void**)&return_value);
+}
+
+// go类型的返回值转换为PHP类型的变量值
+static int phpgo_function_conv_ret(int cbid, phpgo_callback_info* cbi, void *p0, zval *return_value, zend_execute_data *execute_data)
+{
+    RETVAL_LONG(5);
+
+    int ret_type = phpgo_callback_info_get_ret_type(cbi);
+    dlog_debug("convert ret: %p, type: %d(%s)", p0, ret_type, type2name(ret_type));
+
+    int rv_type = 0;
+    uint64_t rv = (uint64_t)goapi_get_value(p0, (void*)&rv_type);
+    if (ret_type == IS_ZVAL) {
+        if (rv_type == 0) {
+            RETVAL_NULL();
+            return 0;
+        }
+
+        ret_type = rv_type;
+    }
+
+    zval* self = getThis();
+    // 返回值解析转换
+    switch (ret_type) {
+    case IS_STRING:
+#ifdef ZEND_ENGINE_3
+        RETVAL_STRINGL((char*)rv, strlen((char*)rv));
+#else
+        RETVAL_STRINGL((char*)rv, strlen((char*)rv), 1);
+#endif
+        free((char*)rv);
+        break;
+    case IS_DOUBLE:
+        RETVAL_DOUBLE(*(double*)rv);
+        free((double*)rv);
+        break;
+#ifdef ZEND_ENGINE_3
+    case IS_TRUE:
+        RETVAL_TRUE;
+        break;
+    case IS_FALSE:
+        RETVAL_FALSE;
+        break;
+    case _IS_BOOL:
+#else
+    case IS_BOOL:
+#endif
+        RETVAL_BOOL(rv);
+        break;
+    case IS_LONG:
+        RETVAL_LONG(rv);
+        break;
+    case IS_NULL:
+        RETVAL_NULL();
+        break;
+#ifdef ZEND_ENGINE_3
+    case IS_UNDEF:
+        RETVAL_NULL();
+        break;
+#endif
+    case IS_RESOURCE:
+        RETVAL_NULL();
+        break;
+    case IS_ARRAY:
+        phpgo_function_reutrn_php_array(p0, return_value);
+        break;
+    case IS_SELF:
+        RETVAL_ZVAL(self, 1, 0);
+        break;
+    default:
+        // wtf?
+        zend_error(E_WARNING, "unrecognized return value: %d, %s.", ret_type, type2name(ret_type));
+        dlog_error("unrecognized return value: %d, %s.", ret_type, type2name(ret_type));
+        break;
+    }
+
+    return 0;
+}
+
+
+#ifdef ZEND_ENGINE_3
+void phpgo_function_handler7(int cbid, phpgo_callback_info* cbi, zend_execute_data *execute_data, zval *return_value)
+{
+    char* arg_types = phpgo_callback_info_get_arg_types(cbi);
+
+    zval *this_ptr = &execute_data->This;  // always not null for php7
+    zend_object* op = NULL;
+    if (NULL != this_ptr && IS_OBJECT == execute_data->This.u1.v.type) {
+        op = execute_data->This.value.obj;
+    }
+    zend_string *func_name = execute_data->func->common.function_name;
+    char *class_name = ZEND_FN_SCOPE_NAME(execute_data->func);
+    dlog_debug("function handler called.%d, this=%p, atys=%s, op=%p, func=%s, class=%s",
+           cbid, this_ptr, arg_types, op, ZSTR_VAL(func_name), class_name);
+
+    zend_function_entry *fe = phpgo_function_map_get(class_name, ZSTR_VAL(func_name));
+    int cbid_dyn = phpgo_callback_map_get(class_name, ZSTR_VAL(func_name));
+    dlog_debug("mapget: %p, %d=?%d", fe, cbid_dyn, cbid);
+    cbid = cbid_dyn; // for transition
+
+    void *argv[MAX_ARG_NUM] = {0};
+    if (op == NULL) {
+        phpgo_function_conv_args(cbid, cbi, (ZEND_NUM_ARGS()), argv);
+    } else {
+        phpgo_method_conv_args(cbid, cbi, (ZEND_NUM_ARGS()), argv);
+    }
+
+    void* rv = NULL;
+    on_phpgo_function_callback_p(cbid, this_ptr, argv[0], argv[1],
+                                 argv[2], argv[3], argv[4], argv[5],
+                                 argv[6], argv[7], argv[8], argv[9], &rv, (void*) op);
+    dlog_debug("inout ret:%p", rv);
+    phpgo_function_conv_ret(cbid, cbi, rv, return_value, execute_data);
+}
+
+void phpgo_function_handler(zend_execute_data *execute_data, zval *return_value)
+{
+    zend_string *zfunc_name = execute_data->func->common.function_name;
+    char *func_name = ZSTR_VAL(zfunc_name);
+    char *class_name = ZEND_FN_SCOPE_NAME(execute_data->func);
+
+    int cbid = phpgo_callback_map_get(class_name, func_name);
+    char fname[strlen((class_name == NULL || strlen(class_name) == 0) ? GLOBAL_VCLASS_NAME : class_name)
+               + strlen(func_name) + 3];
+    snprintf(fname, sizeof(fname), "%s::%s",
+             (class_name == NULL || strlen(class_name) == 0) ? GLOBAL_VCLASS_NAME : class_name, func_name);
+    phpgo_callback_info* cbi = (phpgo_callback_info*)phpgo_object_map_get(g_cbinfo_map, fname);
+    if (cbi == NULL) {
+        dlog_error("callback info can not be NULL: '%s=%p::%p'", fname, class_name, func_name);
+        exit(-1);
+    }
+
+    phpgo_function_handler7(cbid, cbi, execute_data, return_value);
+}
+
+#else  // php < 7.0
+void phpgo_function_handler5(int cbid, phpgo_callback_info* cbi, int ht, zval *return_value, zval **return_value_ptr,
+                            zval *this_ptr, int return_value_used TSRMLS_DC)
+{
+    char* arg_types = phpgo_callback_info_get_arg_types(cbi);
+
+    void* op = NULL;
+    if (NULL != this_ptr && IS_OBJECT == this_ptr->type) {
+        zend_object_handle handle = this_ptr->value.obj.handle;
+        struct _store_object *obj;
+        obj = &EG(objects_store).object_buckets[handle].bucket.obj;
+        op = &obj->object;
+    }
+    const char *func_name = get_active_function_name();
+    const char *class_name = NULL;
+    if (NULL != this_ptr) {
+        zend_class_entry *ce = zend_get_class_entry(this_ptr);
+        class_name = ce->name;
+    }
+    dlog_debug("function handler called.%d, this=%p, atys=%s, op=%p, func=%s, class=%s",
+           cbid, this_ptr, arg_types, op, func_name, class_name);
+
+    void *argv[MAX_ARG_NUM] = {0};
+    if (this_ptr == NULL) {
+        phpgo_function_conv_args(cbid, cbi, (ZEND_NUM_ARGS()), argv);
+    } else {
+        phpgo_method_conv_args(cbid, cbi, (ZEND_NUM_ARGS()), argv);
+    }
+
+    void* rv = NULL;
+    on_phpgo_function_callback_p(cbid, this_ptr, argv[0], argv[1],
+                                 argv[2], argv[3], argv[4], argv[5],
+                                 argv[6], argv[7], argv[8], argv[9], &rv, (void*) op);
+    dlog_debug("inout ret:%p", rv);
+    phpgo_function_conv_ret(cbid, cbi, rv, return_value, execute_data);
+}
+
+void phpgo_function_handler(int ht, zval *return_value, zval **return_value_ptr,
+                             zval *this_ptr, int return_value_used TSRMLS_DC)
+{
+    const char *func_name = get_active_function_name(TSRMLS_C);
+    const char *class_name = NULL;
+    if (NULL != this_ptr) {
+        zend_class_entry *ce = zend_get_class_entry(this_ptr);
+        class_name = ce->name;
+    }
+
+    // object maybe destruct after script execute finished, and out of executed file scope
+    if (!zend_is_executing()) {
+        if (func_name == NULL && class_name != NULL) {
+            func_name = "__destruct";
+        }
+    }
+
+    int cbid = phpgo_callback_map_get(class_name, func_name);
+    char fname[strlen((class_name == NULL || strlen(class_name) == 0)
+                      ? GLOBAL_VCLASS_NAME : class_name) + strlen(func_name) + 3];
+    snprintf(fname, sizeof(fname), "%s::%s",
+             (class_name == NULL || strlen(class_name) == 0) ? GLOBAL_VCLASS_NAME : class_name, func_name);
+    phpgo_callback_info* cbi = (phpgo_callback_info*)phpgo_object_map_get(g_cbinfo_map, fname);
+    if (cbi == NULL) {
+        dlog_error("callback info can not be NULL");
+        exit(-1);
+    }
+
+    phpgo_function_handler5(cbid, cbi, ht, return_value, return_value_ptr, this_ptr, return_value_used);
+}
+
+#endif  // #ifdef ZEND_ENGINE_3
+
+
+int zend_add_class(int cidx, char *cname)
+{
+    if (phpgo_object_map_get(g_class_map, cname) == NULL) {
+        dlog_error("Class %s not added.", cname);
+        return -1;
+    }
+
+    phpgo_class_entry* pce = phpgo_object_map_get(g_class_map, cname);
+    zend_class_entry *ce = (zend_class_entry*)phpgo_class_get(pce);
+    INIT_CLASS_ENTRY_EX((*ce), cname, strlen(cname), phpgo_class_get_funcs(pce));
+    zend_register_internal_class(ce TSRMLS_CC);
+
+    phpgo_class_map_add(cname, ce);
+
+    return 0;
+}
+
+int zend_add_class_not_register(int cidx, char *cname)
+{
+    if (phpgo_object_map_get(g_class_map, cname) != NULL) {
+        dlog_error("Class %s already added.", cname);
+        return -1;
+    }
+
+    // TODO thread safe?
+    phpgo_class_entry* pce = phpgo_class_new(cname);
+    phpgo_object_map_add(&g_class_map, cname, pce);
+    int class_count = phpgo_object_map_count(g_class_map);
+    assert(cidx == class_count);
+
+    zend_class_entry *ce = (zend_class_entry*)phpgo_class_get(pce);
+    phpgo_class_map_add(cname, ce);
+
+    return 0;
+}
+
+int zend_add_function(int cidx, int fidx, int cbid, char *name, char *atys, int rety)
+{
+    dlog_debug("add func %s at %d:%d=%d, atys=%s, rety=%d",
+           name, cidx, fidx, cbid, atys, rety);
+
+    // TODO 检测是否是phpgo注册的?
+    if (gozend_function_registered(name) == 1) {
+        dlog_error("function already exists: %s", name);
+    }
+
+    int cnlen = strlen(GLOBAL_VCLASS_NAME);
+    char *cname = GLOBAL_VCLASS_NAME;
+    char *mname = name;
+
+    phpgo_class_entry* pce = (phpgo_class_entry*)phpgo_object_map_get(g_class_map, cname);
+    if (pce == NULL) {
+        dlog_debug("pce empty: %d for %s", phpgo_object_map_count(g_class_map), cname);
+        zend_add_class_not_register(cidx, cname);
+        pce = (phpgo_class_entry*)phpgo_object_map_get(g_class_map, cname);
+    }
+    phpgo_class_method_add(pce, mname);
+    phpgo_function_entry* pfe = phpgo_class_method_get(pce, mname);
+    zend_function_entry* fe = (zend_function_entry*)phpgo_function_get(pfe);
+
+    zend_function_entry e = {strdup(name), phpgo_function_handler, NULL, 0, 0};
+    memcpy(fe, &e, sizeof(e));
+    (&g_entry)->functions = phpgo_class_get_funcs(pce);
+
+    phpgo_callback_info* cbi = phpgo_callback_info_new(atys, rety);
+    int fnlen = strlen(GLOBAL_VCLASS_NAME) + strlen(name) + 3;
+    char* fname = (char*)calloc(1, fnlen);
+    snprintf(fname, fnlen, "%s::%s", GLOBAL_VCLASS_NAME, name);
+    phpgo_object_map_add(&g_cbinfo_map, fname, cbi);
+
+    phpgo_function_map_add(NULL, name, fe);
+    phpgo_callback_map_add(NULL, name, cbid);
+
+    return 0;
+}
+
+
+int zend_add_method(int cidx, int fidx, int cbid, char *cname, char *mname, char *atys, int rety)
+{
+    dlog_debug("add mth %s::%s at %d:%d=%d, atys=%s, rety=%d",
+           cname, mname, cidx, fidx, cbid, atys, rety);
+
+    phpgo_class_entry* pce = (phpgo_class_entry*)phpgo_object_map_get(g_class_map, cname);
+    if (pce == NULL) {
+        dlog_debug("pce empty: %d", phpgo_object_map_count(g_class_map));
+        zend_add_class_not_register(cidx, cname);
+        pce = (phpgo_class_entry*)phpgo_object_map_get(g_class_map, cname);
+    }
+    phpgo_class_method_add(pce, mname);
+    phpgo_function_entry* pfe = phpgo_class_method_get(pce, mname);
+    zend_function_entry* fe = (zend_function_entry*)phpgo_function_get(pfe);
+
+    phpgo_callback_info* cbi = phpgo_callback_info_new(atys, rety);
+    int fnlen = strlen(cname) + strlen(mname) + 3;
+    char* fname = (char*)calloc(1, fnlen);
+    snprintf(fname, fnlen, "%s::%s", cname, mname);
+    phpgo_object_map_add(&g_cbinfo_map, fname, cbi);
+
+    phpgo_function_map_add(cname, mname, fe);
+    phpgo_callback_map_add(cname, mname, cbid);
+
+    return 0;
+}
+
+

+ 580 - 0
extension/extension.go

@@ -0,0 +1,580 @@
+package phpgo
+
+/*
+#include "extension.h"
+#include "../zend/compat.h"
+#include "../zend/szend.h"
+
+#include <php.h>
+#include <zend_exceptions.h>
+#include <zend_interfaces.h>
+#include <zend_ini.h>
+#include <zend_constants.h>
+#include <SAPI.h>
+
+*/
+import "C"
+import "unsafe"
+import "reflect"
+import "errors"
+import "fmt"
+import "log"
+import "os"
+import "strings"
+
+import "git.nspix.com/golang/php/zend"
+
+// 一个程序只能创建一个扩展
+// 所以使用全局变量也没有问题。
+var (
+	ExtName string = ""
+	ExtVer  string = "1.0"
+)
+
+//export InitExtension
+func InitExtension(name string, version string) int {
+	ExtName = name
+	if len(version) > 0 {
+		ExtVer = version
+	}
+
+	return 0
+}
+
+var gext = NewExtension()
+
+type FuncEntry struct {
+	class  string
+	method string
+	fn     interface{}
+	isctor bool
+	isdtor bool
+}
+
+func NewFuncEntry(class string, method string, fn interface{}) *FuncEntry {
+	return &FuncEntry{class, method, fn, false, false}
+}
+
+func (this *FuncEntry) Name() string {
+	return this.class + "_" + this.method
+}
+
+func (this *FuncEntry) IsGlobal() bool {
+	return this.class == "global"
+}
+
+func (this *FuncEntry) IsCtor() bool {
+	return !this.IsGlobal() && this.isctor
+}
+
+func (this *FuncEntry) IsDtor() bool {
+	return !this.IsGlobal() && this.isdtor
+}
+
+func (this *FuncEntry) IsMethod() bool {
+	return !this.IsGlobal() && !this.isctor && !this.isdtor
+}
+
+// 支持的函数类型为,
+// 至少要是个函数或者方法
+// 最多只能返回一个值
+// 参数个数小于等于10
+// 返回值类型,必须是以下4类,string, intx, floatx, bool
+func (this *FuncEntry) IsSupported() bool {
+	return true
+}
+
+type Extension struct {
+	syms    map[string]int
+	classes map[string]int
+	cbs     map[int]*FuncEntry // cbid => callable callbak
+
+	fidx int // = 0
+
+	objs   map[uintptr]interface{}        // php's this => go's this
+	objs_p map[unsafe.Pointer]interface{} // php's this => go's this
+
+	// phpgo init function
+	module_startup_func   func(int, int) int
+	module_shutdown_func  func(int, int) int
+	request_startup_func  func(int, int) int
+	request_shutdown_func func(int, int) int
+
+	inis *zend.IniEntries // ini entries
+
+	//
+	me *C.zend_module_entry
+	fe *C.zend_function_entry
+}
+
+// 与C中的同名结构体对应
+type phpgo_callback_signature struct {
+	argtys   [10]int8
+	rety     int
+	varidict int
+}
+
+// TODO 把entry位置与cbid分开,这样cbfunc就能够更紧凑了
+func NewExtension() *Extension {
+	syms := make(map[string]int, 0)
+	classes := make(map[string]int, 0)
+	cbs := make(map[int]*FuncEntry, 0)
+	objs := make(map[uintptr]interface{}, 0)
+	objs_p := make(map[unsafe.Pointer]interface{}, 0)
+
+	classes["global"] = 0 // 可以看作内置函数的类
+
+	this := &Extension{syms: syms, classes: classes, cbs: cbs,
+		objs: objs, objs_p: objs_p}
+	this.inis = zend.NewIniEntries()
+
+	return this
+}
+
+// depcreated
+func gencbid(cidx int, fidx int) int {
+	return cidx*128 + fidx
+}
+
+func nxtcbid() int {
+	return len(gext.syms)
+}
+
+func AddFunc(name string, f interface{}) error {
+	fe := NewFuncEntry("global", name, f)
+	sname := fe.Name()
+
+	if _, has := gext.syms[sname]; !has {
+		// TODO check f type
+
+		cidx := 0
+		fidx := gext.fidx
+		// cbid := gencbid(0, fidx)
+		cbid := nxtcbid()
+
+		argtys := zend.ArgTypes2Php(f)
+		var cargtys *C.char = nil
+		if argtys != nil {
+			cargtys = C.CString(*argtys)
+		}
+		rety := zend.RetType2Php(f)
+
+		cname := C.CString(name)
+		n := C.zend_add_function(C.int(cidx), C.int(fidx), C.int(cbid), cname, cargtys, C.int(rety))
+		C.free(unsafe.Pointer(cname))
+		if argtys != nil {
+			C.free(unsafe.Pointer(cargtys))
+		}
+
+		if int(n) == 0 {
+			gext.syms[sname] = cbid
+			gext.cbs[cbid] = fe
+			gext.fidx += 1
+			return nil
+		}
+	}
+
+	return errors.New("add func error.")
+}
+
+// 添加新类的时候,可以把类的公共方法全部导出吧
+// 不用逐个方法添加,简单多了。
+// @param ctor 是该类的构造函数,原型 func NewClass(...) *Class
+func AddClass(name string, ctor interface{}) error {
+
+	if _, has := gext.classes[name]; !has {
+		cidx := len(gext.classes)
+		var n C.int = 0
+
+		// must add begin add class
+		if int(n) == 0 {
+			addCtor(cidx, name, ctor)
+			addDtor(cidx, name, ctor)
+			addMethods(cidx, name, ctor)
+		}
+
+		cname := C.CString(name)
+		n = C.zend_add_class(C.int(cidx), cname)
+		C.free(unsafe.Pointer(cname))
+
+		if int(n) == 0 {
+			gext.classes[name] = cidx
+		}
+
+		return nil
+	}
+
+	return errors.New("add class error.")
+}
+
+func addDtor(cidx int, cname string, ctor interface{}) {
+	mname := "__destruct"
+	fidx := 1
+	addMethod(ctor, cidx, fidx, cname, mname, ctor, false, true)
+}
+
+func addCtor(cidx int, cname string, ctor interface{}) {
+	mname := "__construct"
+	fidx := 0
+	addMethod(ctor, cidx, fidx, cname, mname, ctor, true, false)
+}
+
+func addMethods(cidx int, cname string, ctor interface{}) {
+	fty := reflect.TypeOf(ctor)
+	cls := fty.Out(0)
+
+	for idx := 0; idx < cls.NumMethod(); idx++ {
+		mth := cls.Method(idx)
+		addMethod(ctor, cidx, idx+2, cname, mth.Name, mth.Func.Interface(), false, false)
+	}
+}
+
+func addMethod(ctor interface{}, cidx int, fidx int, cname string, mname string, fn interface{}, isctor, isdtor bool) {
+	// cidx := gext.classes[cname]
+	// cbid := gencbid(cidx, fidx)
+	cbid := nxtcbid()
+
+	fe := NewFuncEntry(cname, mname, fn)
+	fe.isctor = isctor
+	fe.isdtor = isdtor
+
+	argtys := zend.ArgTypes2Php(fn)
+	var cargtys *C.char = nil
+	if argtys != nil {
+		cargtys = C.CString(*argtys)
+	}
+
+	isSelf := false
+    methodRetType := reflect.TypeOf(fn)
+    if methodRetType.NumOut() > 0 {
+        classType := reflect.TypeOf(ctor).Out(0)
+        isSelf = classType == methodRetType.Out(0)
+    }
+
+	var rety int
+    if !isSelf {
+    	rety = zend.RetType2Php(fn)
+    } else {
+        rety = zend.PHPTY_IS_SELF
+    }
+
+	ccname := C.CString(cname)
+	cmname := C.CString(mname)
+
+	mn := C.zend_add_method(C.int(cidx), C.int(fidx), C.int(cbid), ccname, cmname, cargtys, C.int(rety))
+	C.free(unsafe.Pointer(ccname))
+	C.free(unsafe.Pointer(cmname))
+	if argtys != nil {
+		C.free(unsafe.Pointer(cargtys))
+	}
+
+	if mn == 0 {
+		gext.cbs[cbid] = fe
+		gext.syms[fe.Name()] = cbid
+	}
+}
+
+func validFunc(fn interface{}) bool {
+	fty := reflect.TypeOf(fn)
+	if fty.Kind() != reflect.Func {
+		log.Panicln("What's that?", fty.Kind().String())
+	}
+
+	if fty.IsVariadic() {
+		log.Panicln("Can't support variadic func.", fty.Kind().String())
+	}
+
+	for idx := 0; idx < fty.NumIn(); idx++ {
+		switch fty.In(idx).Kind() {
+		case reflect.Func:
+			fallthrough
+		case reflect.Array:
+			fallthrough
+		case reflect.Slice:
+			fallthrough
+		case reflect.Chan:
+			fallthrough
+		case reflect.Map:
+			fallthrough
+		default:
+			log.Panicln("Can't support arg type:", idx, fty.In(idx).Kind().String())
+		}
+	}
+
+	return true
+}
+
+/*
+* @param namespace string optional
+ */
+func AddConstant(name string, val interface{}, namespace interface{}) error {
+	if len(name) == 0 {
+		return nil
+	}
+
+	if namespace != nil {
+		log.Println("Warning, namespace parameter not supported now. omited.")
+	}
+
+	module_number := C.phpgo_get_module_number()
+	modname := C.CString(strings.ToUpper(name))
+	defer C.free(unsafe.Pointer(modname))
+
+	if val != nil {
+		valty := reflect.TypeOf(val)
+
+		switch valty.Kind() {
+		case reflect.String:
+			v := val.(string)
+			modval := C.CString(v)
+			defer C.free(unsafe.Pointer(modval))
+
+			C.zend_register_stringl_constant_compat(modname, C.size_t(len(name)), modval, C.size_t(len(v)),
+				C.CONST_CS|C.CONST_PERSISTENT, C.int(module_number))
+		case reflect.Int, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64,
+			reflect.Int8, reflect.Uint8:
+			iv := reflect.ValueOf(val).Convert(reflect.TypeOf(int64(1))).Interface()
+			C.zend_register_long_constant_compat(modname, C.size_t(len(name)), C.zend_long(iv.(int64)),
+				C.CONST_CS|C.CONST_PERSISTENT, C.int(module_number))
+		case reflect.Float32, reflect.Float64:
+			fv := reflect.ValueOf(val).Convert(reflect.TypeOf(float64(1.0))).Interface()
+			C.zend_register_double_constant_compat(modname, C.size_t(len(name)), C.double(fv.(float64)),
+				C.CONST_CS|C.CONST_PERSISTENT, C.int(module_number))
+		case reflect.Bool:
+			v := val.(bool)
+			var bv int8 = 1
+			if v == false {
+				bv = 0
+			}
+			C.zend_register_bool_constant_compat(modname, C.size_t(len(name)), C.zend_bool(bv),
+				C.CONST_CS|C.CONST_PERSISTENT, C.int(module_number))
+		default:
+			err := fmt.Errorf("Warning, unsported constant value type: %v", valty.Kind().String())
+			log.Println(err)
+			return err
+		}
+	} else {
+		C.zend_register_null_constant_compat(modname, C.size_t(len(name)),
+			C.CONST_CS|C.CONST_PERSISTENT, C.int(module_number))
+	}
+
+	return nil
+}
+
+func AddIniVar(name string, value interface{}, modifiable bool,
+	onModifier func(*zend.IniEntry, string, int) int,
+	displayer func(*zend.IniEntry, int)) {
+	ie := zend.NewIniEntryDef()
+	ie.Fill(name, value, modifiable, nil, nil)
+	ie.SetModifier(onModifier)
+	ie.SetDisplayer(displayer)
+	gext.inis.Add(ie)
+}
+
+// TODO 如果比较多的话,可以摘出来,放在builtin.go中
+// 内置函数注册,内置类注册。
+func addBuiltins() {
+	// nice fix exit crash bug.
+	var iret C.int = 0
+	if iret = C.gozend_function_registered(C.CString("GoExit")); iret == C.int(0) {
+		AddFunc("GoExit", func(code int) { os.Exit(code) })
+	}
+	if iret = C.gozend_function_registered(C.CString("GoGo")); iret == C.int(0) {
+		AddFunc("GoGo", func(fn interface{}) { log.Println(fn) })
+	}
+	if iret = C.gozend_function_registered(C.CString("GoPanic")); iret == C.int(0) {
+		AddFunc("GoPanic", func() { panic("got") })
+	}
+	if iret = C.gozend_function_registered(C.CString("GoRecover")); iret == C.int(0) {
+		AddFunc("GoRecover", func() { recover() })
+	}
+	if iret = C.gozend_function_registered(C.CString("GoPrintln")); iret == C.int(0) {
+		AddFunc("GoPrintln", func(p0 int, v interface{}) { log.Println(v, 123333) })
+	}
+}
+
+// TODO init func with go's //export
+// 注册php module 初始化函数
+func RegisterInitFunctions(module_startup_func func(int, int) int,
+	module_shutdown_func func(int, int) int,
+	request_startup_func func(int, int) int,
+	request_shutdown_func func(int, int) int) {
+
+	gext.module_startup_func = module_startup_func
+	gext.module_shutdown_func = module_shutdown_func
+	gext.request_startup_func = request_startup_func
+	gext.request_shutdown_func = request_shutdown_func
+
+	tocip := func(f interface{}) unsafe.Pointer {
+		return unsafe.Pointer(&f)
+	}
+
+	C.phpgo_register_init_functions(tocip(go_module_startup_func), tocip(gext.module_shutdown_func),
+		tocip(gext.request_startup_func), tocip(gext.request_shutdown_func))
+}
+
+// the module_startup_func proxy
+func go_module_startup_func(a0 int, a1 int) int {
+	// for test begin
+	modifier := func(ie *zend.IniEntry, newValue string, stage int) int {
+		// log.Println(ie.Name(), newValue, stage)
+		return 0
+	}
+	displayer := func(ie *zend.IniEntry, itype int) {
+		// log.Println(ie.Name(), itype)
+	}
+	AddIniVar("phpgo.hehe_int", 123, true, modifier, displayer)
+	AddIniVar("phpgo.hehe_bool", true, true, modifier, displayer)
+	AddIniVar("phpgo.hehe_long", 123, true, modifier, displayer)
+	AddIniVar("phpgo.hehe_string", "strval123", true, modifier, displayer)
+	// for test end
+
+	gext.inis.Register(a1)
+
+	return gext.module_startup_func(a0, a1)
+}
+
+//
+// 以整数类型传递go值类型的实现的回调方式
+//export on_phpgo_function_callback
+func on_phpgo_function_callback(cbid int, phpthis uintptr,
+	a0 uintptr, a1 uintptr, a2 uintptr, a3 uintptr, a4 uintptr,
+	a5 uintptr, a6 uintptr, a7 uintptr, a8 uintptr, a9 uintptr) uintptr {
+
+	args := []uintptr{a0, a1, a2, a3, a4, a5, a6, a7, a8, a9}
+	if len(args) > 0 {
+	}
+
+	log.Println("go callback called:", cbid, phpthis, gext.cbs[cbid])
+	log.Println("go callback called:", args)
+
+	fe := gext.cbs[cbid]
+	// fe.fn.(func())()
+
+	// 根据方法原型中的参数个数与类型,从当前函数中的a0-a9中提取正确的值出来
+	fval := reflect.ValueOf(fe.fn)
+	argv := zend.ArgValuesFromPhp(fe.fn, args)
+
+	if fe.IsMethod() {
+		if phpthis == 0 {
+			panic("wtf")
+		}
+		if _, has := gext.objs[phpthis]; !has {
+			panic("wtf")
+		}
+		gothis := gext.objs[phpthis]
+		// argv = append([]reflect.Value{reflect.ValueOf(gothis)}, argv...)
+		argv[0] = reflect.ValueOf(gothis)
+	}
+
+	outs := fval.Call(argv)
+	ret := zend.RetValue2Php(fe.fn, outs)
+	fmt.Println("meta call ret:", outs, ret)
+
+	if fe.IsCtor() {
+		if phpthis == 0 {
+			panic("wtf")
+		}
+		if _, has := gext.objs[phpthis]; has {
+			panic("wtf")
+		}
+		gext.objs[phpthis] = outs[0].Interface()
+	}
+
+	return ret
+}
+
+//
+// 以指针类型传递go值类型的实现的回调方式
+//export on_phpgo_function_callback_p
+func on_phpgo_function_callback_p(cbid int, phpthis unsafe.Pointer,
+	a0 unsafe.Pointer, a1 unsafe.Pointer, a2 unsafe.Pointer, a3 unsafe.Pointer, a4 unsafe.Pointer,
+	a5 unsafe.Pointer, a6 unsafe.Pointer, a7 unsafe.Pointer, a8 unsafe.Pointer, a9 unsafe.Pointer,
+	retpp *unsafe.Pointer, op unsafe.Pointer) {
+
+	args := []unsafe.Pointer{a0, a1, a2, a3, a4, a5, a6, a7, a8, a9}
+	if len(args) > 0 {
+	}
+
+	log.Println("go callback called:", cbid, phpthis, gext.cbs[cbid], op)
+	log.Println("go callback called:", args)
+
+	fe := gext.cbs[cbid]
+	// fe.fn.(func())()
+
+	if op == nil && !fe.IsGlobal() {
+		panic("is not a class or a function")
+	}
+
+	// 根据方法原型中的参数个数与类型,从当前函数中的a0-a9中提取正确的值出来
+	fval := reflect.ValueOf(fe.fn)
+	argv := zend.ArgValuesFromPhp_p(fe.fn, args, fe.IsMethod())
+
+	if fe.IsMethod() {
+		zend.CHKNILEXIT(phpthis, "wtf")
+		gothis, has := gext.objs_p[op]
+		if !has {
+			panic("wtf")
+		}
+		// argv = append([]reflect.Value{reflect.ValueOf(gothis)}, argv...)
+		argv[0] = reflect.ValueOf(gothis)
+	}
+
+	outs := fval.Call(argv)
+	ret := zend.RetValue2Php_p(fe.fn, outs)
+	log.Println("meta call ret:", outs, ret)
+
+	if fe.IsCtor() {
+		zend.CHKNILEXIT(phpthis, "wtf")
+		if _, has := gext.objs_p[op]; has {
+			panic("wtf")
+		}
+		gext.objs_p[op] = outs[0].Interface()
+	}
+
+	if fe.IsDtor() {
+		zend.CHKNILEXIT(phpthis, "wtf")
+		if _, has := gext.objs_p[op]; !has {
+			panic("wtf")
+		}
+		delete(gext.objs_p, op)
+	}
+
+	*retpp = ret
+	// return ret
+}
+
+//
+// 比较通用的在C中调用go任意函数的方法
+// on_phpgo_function_callback是根据cbid来确定如何调用函数
+// 该函数直接根据函数指定fp函数指针对应的函数。
+//export call_golang_function
+func call_golang_function(fp unsafe.Pointer, a0 uintptr, a1 uintptr, a2 uintptr, a3 uintptr, a4 uintptr,
+	a5 uintptr, a6 uintptr, a7 uintptr, a8 uintptr, a9 uintptr) uintptr {
+
+	fval := reflect.ValueOf(*(*interface{})(fp))
+	if fval.Interface() == nil {
+		panic("wtf")
+	}
+
+	args := []uintptr{a0, a1, a2, a3, a4, a5, a6, a7, a8, a9}
+	if len(args) > 0 {
+	}
+	argv := zend.ArgValuesFromPhp(fval.Interface(), args)
+	if len(argv) > 0 {
+	}
+	outs := fval.Call(argv)
+	ret := zend.RetValue2Php(fval.Interface(), outs)
+	return ret
+}
+
+//
+// 比较通用的在C中调用go任意函数的方法(但参数是都指针形式的)
+// 该函数直接根据函数指定fp函数指针对应的函数。
+//export call_golang_function_p
+func call_golang_function_p(fp unsafe.Pointer, a0 unsafe.Pointer, a1 unsafe.Pointer, a2 unsafe.Pointer,
+	a3 unsafe.Pointer, a4 unsafe.Pointer, a5 unsafe.Pointer, a6 unsafe.Pointer,
+	a7 unsafe.Pointer, a8 unsafe.Pointer, a9 unsafe.Pointer) unsafe.Pointer {
+
+	return nil
+}

+ 12 - 0
extension/extension.h

@@ -0,0 +1,12 @@
+#ifndef _PHPGO_EXTENSION_H_
+#define _PHPGO_EXTENSION_H_
+
+extern void* phpgo_get_module(char *name, char *version);
+extern int phpgo_get_module_number();
+void phpgo_register_init_functions(void *module_startup_func, void *module_shutdown_func,
+                                   void *request_startup_func, void *request_shutdown_func);
+extern int zend_add_function(int cidx, int fidx, int cbid, char *name, char *atys, int rety);
+extern int zend_add_class(int cidx, char *name);
+extern int zend_add_method(int cidx, int fidx, int cbid, char *name, char *mname, char *atys, int rety);
+
+#endif

+ 20 - 0
extension/init.go

@@ -0,0 +1,20 @@
+package phpgo
+
+/*
+#include "extension.h"
+*/
+import "C"
+import "unsafe"
+
+//export get_module
+func get_module() unsafe.Pointer {
+	if len(ExtName) == 0 {
+		panic("ext name not set.")
+	}
+
+	addBuiltins()
+
+	mod := C.phpgo_get_module(C.CString(ExtName), C.CString(ExtVer))
+
+	return unsafe.Pointer(mod)
+}

+ 147 - 0
extension/objectmap.c

@@ -0,0 +1,147 @@
+
+#include <stdio.h>
+
+#include "objectmap.h"
+#include "uthash.h"
+
+#include <zend_API.h>
+
+// static zend_function_entry g_funcs[MCN][MFN] = {0};
+// static zend_class_entry g_centries[MCN] = {0};
+// static zend_module_entry g_entry = {0};
+
+typedef struct _phpgo_function_map {
+    const char *name;
+    zend_function_entry *fe;
+    int id;
+    UT_hash_handle hh;
+} phpgo_function_map;
+
+typedef struct _phpgo_class_map {
+    const char *name;
+    zend_class_entry *ce;
+    int id;
+    UT_hash_handle hh;
+} phpgo_class_map;
+
+typedef struct _phpgo_module_map {
+    const char *name;
+    zend_module_entry *me;
+    int id;
+    UT_hash_handle hh;
+} phpgo_module_map;
+
+typedef struct _phpgo_callback_map {
+    const char *name;
+    int cbid;
+    UT_hash_handle hh;
+} phpgo_callback_map;
+
+struct _phpgo_object_map {
+    const char *name;
+    void *obj;
+    UT_hash_handle hh;
+};
+
+static phpgo_function_map *g_funcs_map = NULL;
+static phpgo_class_map *g_classes_map = NULL;
+static phpgo_callback_map *g_callbacks_map = NULL;
+
+void phpgo_function_map_add(const char *class_name, const char *func_name, zend_function_entry *fe)
+{
+    int id = UTHASH_CNT(hh, g_funcs_map);
+    phpgo_function_map *m = (phpgo_function_map*)malloc(sizeof(phpgo_function_map));
+    char *key = (char*)calloc(1, class_name == NULL ? 0 : strlen(class_name) + strlen(func_name) + 2 + 1);
+    sprintf(key, "%s::%s%c", class_name == NULL ? "" : class_name, func_name, '\0');
+    m->name = key;
+    m->fe = fe;
+    m->id = id;
+    UTHASH_ADD_KEYPTR(hh, g_funcs_map, key, strlen(key), m);
+}
+
+zend_function_entry* phpgo_function_map_get(const char *class_name, const char *func_name)
+{
+    phpgo_function_map *m = NULL;
+    char key[(class_name == NULL ? 0 : strlen(class_name)) + strlen(func_name) + 2 + 1];
+    sprintf(key, "%s::%s%c", (class_name == NULL ? "" : class_name), func_name, '\0');
+    UTHASH_FIND_STR(g_funcs_map, key, m);
+    if (m == NULL) {
+        return NULL;
+    }
+    return m->fe;
+}
+
+void phpgo_class_map_add(const char *class_name, zend_class_entry *ce)
+{
+    int id = UTHASH_CNT(hh, g_classes_map);
+    phpgo_class_map *m = (phpgo_class_map*)malloc(sizeof(phpgo_class_map));
+    m->name = class_name;
+    m->ce = ce;
+    m->id = id;
+    UTHASH_ADD_KEYPTR(hh, g_classes_map, class_name, strlen(class_name), m);
+}
+
+zend_class_entry* phpgo_class_map_get(const char *class_name)
+{
+    phpgo_class_map *m = NULL;
+    UTHASH_FIND_STR(g_classes_map, class_name, m);
+    if (m == NULL) {
+        return NULL;
+    }
+
+    return m->ce;
+}
+
+void phpgo_callback_map_add(const char *class_name, const char *func_name, int cbid)
+{
+    int id = UTHASH_CNT(hh, g_callbacks_map);
+    phpgo_callback_map *m = (phpgo_callback_map*)malloc(sizeof(phpgo_callback_map));
+    char *key = (char*)calloc(1, class_name == NULL ? 0 : strlen(class_name) + strlen(func_name) + 2 + 1);
+    sprintf(key, "%s::%s%c", class_name == NULL ? "" : class_name, func_name, '\0');
+    m->name = key;
+    m->cbid = cbid;
+    UTHASH_ADD_KEYPTR(hh, g_callbacks_map, key, strlen(key), m);
+}
+
+int phpgo_callback_map_get(const char *class_name, const char *func_name)
+{
+    phpgo_callback_map *m = NULL;
+    char key[(class_name == NULL ? 0 : strlen(class_name)) + strlen(func_name) + 2 + 1];
+    sprintf(key, "%s::%s%c", (class_name == NULL ? "" : class_name), func_name, '\0');
+    UTHASH_FIND_STR(g_callbacks_map, key, m);
+    if (m == NULL) {
+        return -1;
+    }
+    return m->cbid;
+}
+
+
+phpgo_object_map* phpgo_object_map_new() {
+    phpgo_object_map *om = (phpgo_object_map*)malloc(sizeof(phpgo_object_map));
+    return om;
+}
+
+void phpgo_object_map_add(phpgo_object_map** om, const char *name, void* obj)
+{
+    phpgo_object_map *m = (phpgo_object_map*)malloc(sizeof(phpgo_object_map));
+    char *key = strdup(name);
+    m->name = key;
+    m->obj = obj;
+    UTHASH_ADD_KEYPTR(hh, *om, key, strlen(key), m);
+}
+
+void* phpgo_object_map_get(phpgo_object_map* om, const char *name)
+{
+    phpgo_object_map *m = NULL;
+    const char *key = name;
+    UTHASH_FIND_STR(om, key, m);
+    if (m == NULL) {
+        return NULL;
+    }
+    return m->obj;
+}
+
+int phpgo_object_map_count(phpgo_object_map* om) {
+    return UTHASH_COUNT(om);
+}
+

+ 28 - 0
extension/objectmap.h

@@ -0,0 +1,28 @@
+#ifndef _OBJECT_HASH_H_
+#define _OBJECT_HASH_H_
+
+struct _zend_function_entry;
+struct _zend_class_entry;
+struct _zend_module_entry;
+
+typedef struct _phpgo_object_map phpgo_object_map;
+
+void phpgo_function_map_add(const char *class_name, const char *func_name, struct _zend_function_entry *fe);
+
+struct _zend_function_entry* phpgo_function_map_get(const char *class_name, const char *func_name);
+
+void phpgo_class_map_add(const char *class_name, struct _zend_class_entry *ce);
+
+struct _zend_class_entry* phpgo_class_map_get(const char *class_name);
+
+void phpgo_callback_map_add(const char *class_name, const char *func_name, int cbid);
+
+int phpgo_callback_map_get(const char *class_name, const char *func_name);
+
+phpgo_object_map *phpgo_object_map_new();
+void phpgo_object_map_add(phpgo_object_map** om, const char *name, void* obj);
+void* phpgo_object_map_get(phpgo_object_map* om, const char *name);
+int phpgo_object_map_count(phpgo_object_map* om);
+
+#endif
+

+ 12 - 0
extension/phpgo.go

@@ -0,0 +1,12 @@
+package phpgo
+
+/*
+// #cgo CFLAGS: -I/usr/include/php -I/usr/include/php/Zend -I/usr/include/php/TSRM
+// #cgo LDFLAGS: -L/home/dev/php5/lib -lphp5
+#cgo CFLAGS: -g -O2 -std=c99 -D_GNU_SOURCE
+// #cgo LDFLAGS: -lphp5
+#cgo linux LDFLAGS: -Wl,--warn-unresolved-symbols -Wl,--unresolved-symbols=ignore-all
+#cgo darwin LDFLAGS: -undefined dynamic_lookup
+
+*/
+import "C"

+ 1063 - 0
extension/uthash.h

@@ -0,0 +1,1063 @@
+/*
+Copyright (c) 2003-2016, Troy D. Hanson     http://troydhanson.github.com/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTHASH_H
+#define UTHASH_H
+
+#define UTHASH_VERSION 2.0.1
+
+#include <string.h>   /* memcmp,strlen */
+#include <stddef.h>   /* ptrdiff_t */
+#include <stdlib.h>   /* exit() */
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+   As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+   when compiling c++ source) this code uses whatever method is needed
+   or, for VS2008 where neither is available, uses casting workarounds. */
+#if defined(_MSC_VER)   /* MS compiler */
+#if _MSC_VER >= 1600 && defined(__cplusplus)  /* VS2010 or newer in C++ mode */
+#define DECLTYPE(x) (decltype(x))
+#else                   /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#define DECLTYPE(x)
+#endif
+#elif defined(__BORLANDC__) || defined(__LCC__) || defined(__WATCOMC__)
+#define NO_DECLTYPE
+#define DECLTYPE(x)
+#else                   /* GNU, Sun and other compilers */
+#define DECLTYPE(x) (__typeof(x))
+#endif
+
+#ifdef NO_DECLTYPE
+#define DECLTYPE_ASSIGN(dst,src)                                                 \
+do {                                                                             \
+  char **_da_dst = (char**)(&(dst));                                             \
+  *_da_dst = (char*)(src);                                                       \
+} while (0)
+#else
+#define DECLTYPE_ASSIGN(dst,src)                                                 \
+do {                                                                             \
+  (dst) = DECLTYPE(dst)(src);                                                    \
+} while (0)
+#endif
+
+/* a number of the hash function use uint32_t which isn't defined on Pre VS2010 */
+#if defined(_WIN32)
+#if defined(_MSC_VER) && _MSC_VER >= 1600
+#include <stdint.h>
+#elif defined(__WATCOMC__) || defined(__MINGW32__) || defined(__CYGWIN__)
+#include <stdint.h>
+#else
+typedef unsigned int uint32_t;
+typedef unsigned char uint8_t;
+#endif
+#elif defined(__GNUC__) && !defined(__VXWORKS__)
+#include <stdint.h>
+#else
+typedef unsigned int uint32_t;
+typedef unsigned char uint8_t;
+#endif
+
+#ifndef uthash_fatal
+#define uthash_fatal(msg) exit(-1)        /* fatal error (out of memory,etc) */
+#endif
+#ifndef uthash_malloc
+#define uthash_malloc(sz) malloc(sz)      /* malloc fcn                      */
+#endif
+#ifndef uthash_free
+#define uthash_free(ptr,sz) free(ptr)     /* free fcn                        */
+#endif
+
+#ifndef uthash_noexpand_fyi
+#define uthash_noexpand_fyi(tbl)          /* can be defined to log noexpand  */
+#endif
+#ifndef uthash_expand_fyi
+#define uthash_expand_fyi(tbl)            /* can be defined to log expands   */
+#endif
+
+/* initial number of buckets */
+#define UTHASH_INITIAL_NUM_BUCKETS 32U     /* initial number of buckets        */
+#define UTHASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */
+#define UTHASH_BKT_CAPACITY_THRESH 10U     /* expand when bucket count reaches */
+
+/* calculate the element whose hash handle address is hhp */
+#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))
+/* calculate the hash handle from element address elp */
+#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle *)(((char*)(elp)) + ((tbl)->hho)))
+
+#define UTHASH_VALUE(keyptr,keylen,hashv)                                          \
+do {                                                                             \
+  UTHASH_FCN(keyptr, keylen, hashv);                                               \
+} while (0)
+
+#define UTHASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out)                 \
+do {                                                                             \
+  (out) = NULL;                                                                  \
+  if (head) {                                                                    \
+    unsigned _hf_bkt;                                                            \
+    UTHASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt);                  \
+    if (UTHASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) {                         \
+      UTHASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \
+    }                                                                            \
+  }                                                                              \
+} while (0)
+
+#define UTHASH_FIND(hh,head,keyptr,keylen,out)                                     \
+do {                                                                             \
+  unsigned _hf_hashv;                                                            \
+  UTHASH_VALUE(keyptr, keylen, _hf_hashv);                                         \
+  UTHASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out);               \
+} while (0)
+
+#ifdef UTHASH_BLOOM
+#define UTHASH_BLOOM_BITLEN (1UL << UTHASH_BLOOM)
+#define UTHASH_BLOOM_BYTELEN (UTHASH_BLOOM_BITLEN/8UL) + (((UTHASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL)
+#define UTHASH_BLOOM_MAKE(tbl)                                                     \
+do {                                                                             \
+  (tbl)->bloom_nbits = UTHASH_BLOOM;                                               \
+  (tbl)->bloom_bv = (uint8_t*)uthash_malloc(UTHASH_BLOOM_BYTELEN);                 \
+  if (!((tbl)->bloom_bv))  { uthash_fatal( "out of memory"); }                   \
+  memset((tbl)->bloom_bv, 0, UTHASH_BLOOM_BYTELEN);                                \
+  (tbl)->bloom_sig = UTHASH_BLOOM_SIGNATURE;                                       \
+} while (0)
+
+#define UTHASH_BLOOM_FREE(tbl)                                                     \
+do {                                                                             \
+  uthash_free((tbl)->bloom_bv, UTHASH_BLOOM_BYTELEN);                              \
+} while (0)
+
+#define UTHASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U)))
+#define UTHASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U)))
+
+#define UTHASH_BLOOM_ADD(tbl,hashv)                                                \
+  UTHASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1U)))
+
+#define UTHASH_BLOOM_TEST(tbl,hashv)                                               \
+  UTHASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1U)))
+
+#else
+#define UTHASH_BLOOM_MAKE(tbl)
+#define UTHASH_BLOOM_FREE(tbl)
+#define UTHASH_BLOOM_ADD(tbl,hashv)
+#define UTHASH_BLOOM_TEST(tbl,hashv) (1)
+#define UTHASH_BLOOM_BYTELEN 0U
+#endif
+
+#define UTHASH_MAKE_TABLE(hh,head)                                                 \
+do {                                                                             \
+  (head)->hh.tbl = (UT_hash_table*)uthash_malloc(                                \
+                  sizeof(UT_hash_table));                                        \
+  if (!((head)->hh.tbl))  { uthash_fatal( "out of memory"); }                    \
+  memset((head)->hh.tbl, 0, sizeof(UT_hash_table));                              \
+  (head)->hh.tbl->tail = &((head)->hh);                                          \
+  (head)->hh.tbl->num_buckets = UTHASH_INITIAL_NUM_BUCKETS;                        \
+  (head)->hh.tbl->log2_num_buckets = UTHASH_INITIAL_NUM_BUCKETS_LOG2;              \
+  (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head);                    \
+  (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc(                      \
+          UTHASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket));               \
+  if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); }             \
+  memset((head)->hh.tbl->buckets, 0,                                             \
+          UTHASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket));               \
+  UTHASH_BLOOM_MAKE((head)->hh.tbl);                                               \
+  (head)->hh.tbl->signature = UTHASH_SIGNATURE;                                    \
+} while (0)
+
+#define UTHASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \
+do {                                                                             \
+  (replaced) = NULL;                                                             \
+  UTHASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \
+  if (replaced) {                                                                \
+     UTHASH_DELETE(hh, head, replaced);                                            \
+  }                                                                              \
+  UTHASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \
+} while (0)
+
+#define UTHASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \
+do {                                                                             \
+  (replaced) = NULL;                                                             \
+  UTHASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \
+  if (replaced) {                                                                \
+     UTHASH_DELETE(hh, head, replaced);                                            \
+  }                                                                              \
+  UTHASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \
+} while (0)
+
+#define UTHASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced)                   \
+do {                                                                             \
+  unsigned _hr_hashv;                                                            \
+  UTHASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv);                         \
+  UTHASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \
+} while (0)
+
+#define UTHASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn)    \
+do {                                                                             \
+  unsigned _hr_hashv;                                                            \
+  UTHASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv);                         \
+  UTHASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \
+} while (0)
+
+#define UTHASH_APPEND_LIST(hh, head, add)                                          \
+do {                                                                             \
+  (add)->hh.next = NULL;                                                         \
+  (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail);           \
+  (head)->hh.tbl->tail->next = (add);                                            \
+  (head)->hh.tbl->tail = &((add)->hh);                                           \
+} while (0)
+
+#define UTHASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \
+do {                                                                             \
+  unsigned _ha_bkt;                                                              \
+  (add)->hh.hashv = (hashval);                                                   \
+  (add)->hh.key = (char*) (keyptr);                                              \
+  (add)->hh.keylen = (unsigned) (keylen_in);                                     \
+  if (!(head)) {                                                                 \
+    (add)->hh.next = NULL;                                                       \
+    (add)->hh.prev = NULL;                                                       \
+    (head) = (add);                                                              \
+    UTHASH_MAKE_TABLE(hh, head);                                                   \
+  } else {                                                                       \
+    struct UT_hash_handle *_hs_iter = &(head)->hh;                               \
+    (add)->hh.tbl = (head)->hh.tbl;                                              \
+    do {                                                                         \
+      if (cmpfcn(DECLTYPE(head) ELMT_FROM_HH((head)->hh.tbl, _hs_iter), add) > 0) \
+        break;                                                                   \
+    } while ((_hs_iter = _hs_iter->next));                                       \
+    if (_hs_iter) {                                                              \
+      (add)->hh.next = _hs_iter;                                                 \
+      if (((add)->hh.prev = _hs_iter->prev)) {                                   \
+        HH_FROM_ELMT((head)->hh.tbl, _hs_iter->prev)->next = (add);              \
+      } else {                                                                   \
+        (head) = (add);                                                          \
+      }                                                                          \
+      _hs_iter->prev = (add);                                                    \
+    } else {                                                                     \
+      UTHASH_APPEND_LIST(hh, head, add);                                           \
+    }                                                                            \
+  }                                                                              \
+  (head)->hh.tbl->num_items++;                                                   \
+  UTHASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt);                    \
+  UTHASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], &(add)->hh);                 \
+  UTHASH_BLOOM_ADD((head)->hh.tbl, hashval);                                       \
+  UTHASH_EMIT_KEY(hh, head, keyptr, keylen_in);                                    \
+  UTHASH_FSCK(hh, head);                                                           \
+} while (0)
+
+#define UTHASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn)             \
+do {                                                                             \
+  unsigned _hs_hashv;                                                            \
+  UTHASH_VALUE(keyptr, keylen_in, _hs_hashv);                                      \
+  UTHASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \
+} while (0)
+
+#define UTHASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \
+  UTHASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn)
+
+#define UTHASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn)                 \
+  UTHASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn)
+
+#define UTHASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add)        \
+do {                                                                             \
+  unsigned _ha_bkt;                                                              \
+  (add)->hh.hashv = (hashval);                                                   \
+  (add)->hh.key = (char*) (keyptr);                                              \
+  (add)->hh.keylen = (unsigned) (keylen_in);                                     \
+  if (!(head)) {                                                                 \
+    (add)->hh.next = NULL;                                                       \
+    (add)->hh.prev = NULL;                                                       \
+    (head) = (add);                                                              \
+    UTHASH_MAKE_TABLE(hh, head);                                                   \
+  } else {                                                                       \
+    (add)->hh.tbl = (head)->hh.tbl;                                              \
+    UTHASH_APPEND_LIST(hh, head, add);                                             \
+  }                                                                              \
+  (head)->hh.tbl->num_items++;                                                   \
+  UTHASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt);                    \
+  UTHASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], &(add)->hh);                 \
+  UTHASH_BLOOM_ADD((head)->hh.tbl, hashval);                                       \
+  UTHASH_EMIT_KEY(hh, head, keyptr, keylen_in);                                    \
+  UTHASH_FSCK(hh, head);                                                           \
+} while (0)
+
+#define UTHASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add)                            \
+do {                                                                             \
+  unsigned _ha_hashv;                                                            \
+  UTHASH_VALUE(keyptr, keylen_in, _ha_hashv);                                      \
+  UTHASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add);      \
+} while (0)
+
+#define UTHASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add)            \
+  UTHASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add)
+
+#define UTHASH_ADD(hh,head,fieldname,keylen_in,add)                                \
+  UTHASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add)
+
+#define UTHASH_TO_BKT(hashv,num_bkts,bkt)                                          \
+do {                                                                             \
+  bkt = ((hashv) & ((num_bkts) - 1U));                                           \
+} while (0)
+
+/* delete "delptr" from the hash table.
+ * "the usual" patch-up process for the app-order doubly-linked-list.
+ * The use of _hd_hh_del below deserves special explanation.
+ * These used to be expressed using (delptr) but that led to a bug
+ * if someone used the same symbol for the head and deletee, like
+ *  UTHASH_DELETE(hh,users,users);
+ * We want that to work, but by changing the head (users) below
+ * we were forfeiting our ability to further refer to the deletee (users)
+ * in the patch-up process. Solution: use scratch space to
+ * copy the deletee pointer, then the latter references are via that
+ * scratch pointer rather than through the repointed (users) symbol.
+ */
+#define UTHASH_DELETE(hh,head,delptr)                                              \
+do {                                                                             \
+    struct UT_hash_handle *_hd_hh_del;                                           \
+    if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) )  {         \
+        uthash_free((head)->hh.tbl->buckets,                                     \
+                    (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \
+        UTHASH_BLOOM_FREE((head)->hh.tbl);                                         \
+        uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                      \
+        head = NULL;                                                             \
+    } else {                                                                     \
+        unsigned _hd_bkt;                                                        \
+        _hd_hh_del = &((delptr)->hh);                                            \
+        if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) {     \
+            (head)->hh.tbl->tail =                                               \
+                (UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) +               \
+                (head)->hh.tbl->hho);                                            \
+        }                                                                        \
+        if ((delptr)->hh.prev != NULL) {                                         \
+            ((UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) +                  \
+                    (head)->hh.tbl->hho))->next = (delptr)->hh.next;             \
+        } else {                                                                 \
+            DECLTYPE_ASSIGN(head,(delptr)->hh.next);                             \
+        }                                                                        \
+        if (_hd_hh_del->next != NULL) {                                          \
+            ((UT_hash_handle*)((ptrdiff_t)_hd_hh_del->next +                     \
+                    (head)->hh.tbl->hho))->prev =                                \
+                    _hd_hh_del->prev;                                            \
+        }                                                                        \
+        UTHASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt);   \
+        UTHASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del);        \
+        (head)->hh.tbl->num_items--;                                             \
+    }                                                                            \
+    UTHASH_FSCK(hh,head);                                                          \
+} while (0)
+
+
+/* convenience forms of UTHASH_FIND/HASH_ADD/HASH_DEL */
+#define UTHASH_FIND_STR(head,findstr,out)                                          \
+    UTHASH_FIND(hh,head,findstr,(unsigned)strlen(findstr),out)
+#define UTHASH_ADD_STR(head,strfield,add)                                          \
+    UTHASH_ADD(hh,head,strfield[0],(unsigned int)strlen(add->strfield),add)
+#define UTHASH_REPLACE_STR(head,strfield,add,replaced)                             \
+    UTHASH_REPLACE(hh,head,strfield[0],(unsigned)strlen(add->strfield),add,replaced)
+#define UTHASH_FIND_INT(head,findint,out)                                          \
+    UTHASH_FIND(hh,head,findint,sizeof(int),out)
+#define UTHASH_ADD_INT(head,intfield,add)                                          \
+    UTHASH_ADD(hh,head,intfield,sizeof(int),add)
+#define UTHASH_REPLACE_INT(head,intfield,add,replaced)                             \
+    UTHASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced)
+#define UTHASH_FIND_PTR(head,findptr,out)                                          \
+    UTHASH_FIND(hh,head,findptr,sizeof(void *),out)
+#define UTHASH_ADD_PTR(head,ptrfield,add)                                          \
+    UTHASH_ADD(hh,head,ptrfield,sizeof(void *),add)
+#define UTHASH_REPLACE_PTR(head,ptrfield,add,replaced)                             \
+    UTHASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced)
+#define UTHASH_DEL(head,delptr)                                                    \
+    UTHASH_DELETE(hh,head,delptr)
+
+/* UTHASH_FSCK checks hash integrity on every add/delete when UTHASH_DEBUG is defined.
+ * This is for uthash developer only; it compiles away if UTHASH_DEBUG isn't defined.
+ */
+#ifdef UTHASH_DEBUG
+#define UTHASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0)
+#define UTHASH_FSCK(hh,head)                                                       \
+do {                                                                             \
+    struct UT_hash_handle *_thh;                                                 \
+    if (head) {                                                                  \
+        unsigned _bkt_i;                                                         \
+        unsigned _count;                                                         \
+        char *_prev;                                                             \
+        _count = 0;                                                              \
+        for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) {       \
+            unsigned _bkt_count = 0;                                             \
+            _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head;                      \
+            _prev = NULL;                                                        \
+            while (_thh) {                                                       \
+               if (_prev != (char*)(_thh->hh_prev)) {                            \
+                   UTHASH_OOPS("invalid hh_prev %p, actual %p\n",                  \
+                    _thh->hh_prev, _prev );                                      \
+               }                                                                 \
+               _bkt_count++;                                                     \
+               _prev = (char*)(_thh);                                            \
+               _thh = _thh->hh_next;                                             \
+            }                                                                    \
+            _count += _bkt_count;                                                \
+            if ((head)->hh.tbl->buckets[_bkt_i].count !=  _bkt_count) {          \
+               UTHASH_OOPS("invalid bucket count %u, actual %u\n",                 \
+                (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count);              \
+            }                                                                    \
+        }                                                                        \
+        if (_count != (head)->hh.tbl->num_items) {                               \
+            UTHASH_OOPS("invalid hh item count %u, actual %u\n",                   \
+                (head)->hh.tbl->num_items, _count );                             \
+        }                                                                        \
+        /* traverse hh in app order; check next/prev integrity, count */         \
+        _count = 0;                                                              \
+        _prev = NULL;                                                            \
+        _thh =  &(head)->hh;                                                     \
+        while (_thh) {                                                           \
+           _count++;                                                             \
+           if (_prev !=(char*)(_thh->prev)) {                                    \
+              UTHASH_OOPS("invalid prev %p, actual %p\n",                          \
+                    _thh->prev, _prev );                                         \
+           }                                                                     \
+           _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh);                    \
+           _thh = ( _thh->next ?  (UT_hash_handle*)((char*)(_thh->next) +        \
+                                  (head)->hh.tbl->hho) : NULL );                 \
+        }                                                                        \
+        if (_count != (head)->hh.tbl->num_items) {                               \
+            UTHASH_OOPS("invalid app item count %u, actual %u\n",                  \
+                (head)->hh.tbl->num_items, _count );                             \
+        }                                                                        \
+    }                                                                            \
+} while (0)
+#else
+#define UTHASH_FSCK(hh,head)
+#endif
+
+/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to
+ * the descriptor to which this macro is defined for tuning the hash function.
+ * The app can #include <unistd.h> to get the prototype for write(2). */
+#ifdef UTHASH_EMIT_KEYS
+#define UTHASH_EMIT_KEY(hh,head,keyptr,fieldlen)                                   \
+do {                                                                             \
+    unsigned _klen = fieldlen;                                                   \
+    write(UTHASH_EMIT_KEYS, &_klen, sizeof(_klen));                                \
+    write(UTHASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen);                      \
+} while (0)
+#else
+#define UTHASH_EMIT_KEY(hh,head,keyptr,fieldlen)
+#endif
+
+/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */
+#ifdef UTHASH_FUNCTION
+#define UTHASH_FCN UTHASH_FUNCTION
+#else
+#define UTHASH_FCN UTHASH_JEN
+#endif
+
+/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */
+#define UTHASH_BER(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _hb_keylen=(unsigned)keylen;                                          \
+  const unsigned char *_hb_key=(const unsigned char*)(key);                      \
+  (hashv) = 0;                                                                   \
+  while (_hb_keylen-- != 0U) {                                                   \
+      (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++;                         \
+  }                                                                              \
+} while (0)
+
+
+/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at
+ * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */
+#define UTHASH_SAX(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _sx_i;                                                                \
+  const unsigned char *_hs_key=(const unsigned char*)(key);                      \
+  hashv = 0;                                                                     \
+  for(_sx_i=0; _sx_i < keylen; _sx_i++) {                                        \
+      hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i];                     \
+  }                                                                              \
+} while (0)
+/* FNV-1a variation */
+#define UTHASH_FNV(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _fn_i;                                                                \
+  const unsigned char *_hf_key=(const unsigned char*)(key);                      \
+  hashv = 2166136261U;                                                           \
+  for(_fn_i=0; _fn_i < keylen; _fn_i++) {                                        \
+      hashv = hashv ^ _hf_key[_fn_i];                                            \
+      hashv = hashv * 16777619U;                                                 \
+  }                                                                              \
+} while (0)
+
+#define UTHASH_OAT(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _ho_i;                                                                \
+  const unsigned char *_ho_key=(const unsigned char*)(key);                      \
+  hashv = 0;                                                                     \
+  for(_ho_i=0; _ho_i < keylen; _ho_i++) {                                        \
+      hashv += _ho_key[_ho_i];                                                   \
+      hashv += (hashv << 10);                                                    \
+      hashv ^= (hashv >> 6);                                                     \
+  }                                                                              \
+  hashv += (hashv << 3);                                                         \
+  hashv ^= (hashv >> 11);                                                        \
+  hashv += (hashv << 15);                                                        \
+} while (0)
+
+#define UTHASH_JEN_MIX(a,b,c)                                                      \
+do {                                                                             \
+  a -= b; a -= c; a ^= ( c >> 13 );                                              \
+  b -= c; b -= a; b ^= ( a << 8 );                                               \
+  c -= a; c -= b; c ^= ( b >> 13 );                                              \
+  a -= b; a -= c; a ^= ( c >> 12 );                                              \
+  b -= c; b -= a; b ^= ( a << 16 );                                              \
+  c -= a; c -= b; c ^= ( b >> 5 );                                               \
+  a -= b; a -= c; a ^= ( c >> 3 );                                               \
+  b -= c; b -= a; b ^= ( a << 10 );                                              \
+  c -= a; c -= b; c ^= ( b >> 15 );                                              \
+} while (0)
+
+#define UTHASH_JEN(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _hj_i,_hj_j,_hj_k;                                                    \
+  unsigned const char *_hj_key=(unsigned const char*)(key);                      \
+  hashv = 0xfeedbeefu;                                                           \
+  _hj_i = _hj_j = 0x9e3779b9u;                                                   \
+  _hj_k = (unsigned)(keylen);                                                    \
+  while (_hj_k >= 12U) {                                                         \
+    _hj_i +=    (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 )                      \
+        + ( (unsigned)_hj_key[2] << 16 )                                         \
+        + ( (unsigned)_hj_key[3] << 24 ) );                                      \
+    _hj_j +=    (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 )                      \
+        + ( (unsigned)_hj_key[6] << 16 )                                         \
+        + ( (unsigned)_hj_key[7] << 24 ) );                                      \
+    hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 )                         \
+        + ( (unsigned)_hj_key[10] << 16 )                                        \
+        + ( (unsigned)_hj_key[11] << 24 ) );                                     \
+                                                                                 \
+     UTHASH_JEN_MIX(_hj_i, _hj_j, hashv);                                          \
+                                                                                 \
+     _hj_key += 12;                                                              \
+     _hj_k -= 12U;                                                               \
+  }                                                                              \
+  hashv += (unsigned)(keylen);                                                   \
+  switch ( _hj_k ) {                                                             \
+     case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */        \
+     case 10: hashv += ( (unsigned)_hj_key[9] << 16 );  /* FALLTHROUGH */        \
+     case 9:  hashv += ( (unsigned)_hj_key[8] << 8 );   /* FALLTHROUGH */        \
+     case 8:  _hj_j += ( (unsigned)_hj_key[7] << 24 );  /* FALLTHROUGH */        \
+     case 7:  _hj_j += ( (unsigned)_hj_key[6] << 16 );  /* FALLTHROUGH */        \
+     case 6:  _hj_j += ( (unsigned)_hj_key[5] << 8 );   /* FALLTHROUGH */        \
+     case 5:  _hj_j += _hj_key[4];                      /* FALLTHROUGH */        \
+     case 4:  _hj_i += ( (unsigned)_hj_key[3] << 24 );  /* FALLTHROUGH */        \
+     case 3:  _hj_i += ( (unsigned)_hj_key[2] << 16 );  /* FALLTHROUGH */        \
+     case 2:  _hj_i += ( (unsigned)_hj_key[1] << 8 );   /* FALLTHROUGH */        \
+     case 1:  _hj_i += _hj_key[0];                                               \
+  }                                                                              \
+  UTHASH_JEN_MIX(_hj_i, _hj_j, hashv);                                             \
+} while (0)
+
+/* The Paul Hsieh hash function */
+#undef get16bits
+#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__)             \
+  || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
+#define get16bits(d) (*((const uint16_t *) (d)))
+#endif
+
+#if !defined (get16bits)
+#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)             \
+                       +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+#define UTHASH_SFH(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned const char *_sfh_key=(unsigned const char*)(key);                     \
+  uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen;                                \
+                                                                                 \
+  unsigned _sfh_rem = _sfh_len & 3U;                                             \
+  _sfh_len >>= 2;                                                                \
+  hashv = 0xcafebabeu;                                                           \
+                                                                                 \
+  /* Main loop */                                                                \
+  for (;_sfh_len > 0U; _sfh_len--) {                                             \
+    hashv    += get16bits (_sfh_key);                                            \
+    _sfh_tmp  = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv;              \
+    hashv     = (hashv << 16) ^ _sfh_tmp;                                        \
+    _sfh_key += 2U*sizeof (uint16_t);                                            \
+    hashv    += hashv >> 11;                                                     \
+  }                                                                              \
+                                                                                 \
+  /* Handle end cases */                                                         \
+  switch (_sfh_rem) {                                                            \
+    case 3: hashv += get16bits (_sfh_key);                                       \
+            hashv ^= hashv << 16;                                                \
+            hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18;              \
+            hashv += hashv >> 11;                                                \
+            break;                                                               \
+    case 2: hashv += get16bits (_sfh_key);                                       \
+            hashv ^= hashv << 11;                                                \
+            hashv += hashv >> 17;                                                \
+            break;                                                               \
+    case 1: hashv += *_sfh_key;                                                  \
+            hashv ^= hashv << 10;                                                \
+            hashv += hashv >> 1;                                                 \
+  }                                                                              \
+                                                                                 \
+    /* Force "avalanching" of final 127 bits */                                  \
+    hashv ^= hashv << 3;                                                         \
+    hashv += hashv >> 5;                                                         \
+    hashv ^= hashv << 4;                                                         \
+    hashv += hashv >> 17;                                                        \
+    hashv ^= hashv << 25;                                                        \
+    hashv += hashv >> 6;                                                         \
+} while (0)
+
+#ifdef UTHASH_USING_NO_STRICT_ALIASING
+/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads.
+ * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error.
+ * MurmurHash uses the faster approach only on CPU's where we know it's safe.
+ *
+ * Note the preprocessor built-in defines can be emitted using:
+ *
+ *   gcc -m64 -dM -E - < /dev/null                  (on gcc)
+ *   cc -## a.c (where a.c is a simple test file)   (Sun Studio)
+ */
+#if (defined(__i386__) || defined(__x86_64__)  || defined(_M_IX86))
+#define MUR_GETBLOCK(p,i) p[i]
+#else /* non intel */
+#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 3UL) == 0UL)
+#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 3UL) == 1UL)
+#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 3UL) == 2UL)
+#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 3UL) == 3UL)
+#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL))
+#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__))
+#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24))
+#define MUR_TWO_TWO(p)   ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16))
+#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >>  8))
+#else /* assume little endian non-intel */
+#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24))
+#define MUR_TWO_TWO(p)   ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16))
+#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) <<  8))
+#endif
+#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) :           \
+                            (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \
+                             (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) :  \
+                                                      MUR_ONE_THREE(p))))
+#endif
+#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r))))
+#define MUR_FMIX(_h) \
+do {                 \
+  _h ^= _h >> 16;    \
+  _h *= 0x85ebca6bu; \
+  _h ^= _h >> 13;    \
+  _h *= 0xc2b2ae35u; \
+  _h ^= _h >> 16;    \
+} while (0)
+
+#define UTHASH_MUR(key,keylen,hashv)                                     \
+do {                                                                   \
+  const uint8_t *_mur_data = (const uint8_t*)(key);                    \
+  const int _mur_nblocks = (int)(keylen) / 4;                          \
+  uint32_t _mur_h1 = 0xf88D5353u;                                      \
+  uint32_t _mur_c1 = 0xcc9e2d51u;                                      \
+  uint32_t _mur_c2 = 0x1b873593u;                                      \
+  uint32_t _mur_k1 = 0;                                                \
+  const uint8_t *_mur_tail;                                            \
+  const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+(_mur_nblocks*4)); \
+  int _mur_i;                                                          \
+  for(_mur_i = -_mur_nblocks; _mur_i!=0; _mur_i++) {                   \
+    _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i);                        \
+    _mur_k1 *= _mur_c1;                                                \
+    _mur_k1 = MUR_ROTL32(_mur_k1,15);                                  \
+    _mur_k1 *= _mur_c2;                                                \
+                                                                       \
+    _mur_h1 ^= _mur_k1;                                                \
+    _mur_h1 = MUR_ROTL32(_mur_h1,13);                                  \
+    _mur_h1 = (_mur_h1*5U) + 0xe6546b64u;                              \
+  }                                                                    \
+  _mur_tail = (const uint8_t*)(_mur_data + (_mur_nblocks*4));          \
+  _mur_k1=0;                                                           \
+  switch((keylen) & 3U) {                                              \
+    case 3: _mur_k1 ^= (uint32_t)_mur_tail[2] << 16; /* FALLTHROUGH */ \
+    case 2: _mur_k1 ^= (uint32_t)_mur_tail[1] << 8;  /* FALLTHROUGH */ \
+    case 1: _mur_k1 ^= (uint32_t)_mur_tail[0];                         \
+    _mur_k1 *= _mur_c1;                                                \
+    _mur_k1 = MUR_ROTL32(_mur_k1,15);                                  \
+    _mur_k1 *= _mur_c2;                                                \
+    _mur_h1 ^= _mur_k1;                                                \
+  }                                                                    \
+  _mur_h1 ^= (uint32_t)(keylen);                                       \
+  MUR_FMIX(_mur_h1);                                                   \
+  hashv = _mur_h1;                                                     \
+} while (0)
+#endif  /* UTHASH_USING_NO_STRICT_ALIASING */
+
+/* key comparison function; return 0 if keys equal */
+#define UTHASH_KEYCMP(a,b,len) memcmp(a,b,(unsigned long)(len))
+
+/* iterate over items in a known bucket to find desired item */
+#define UTHASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out)               \
+do {                                                                             \
+ if (head.hh_head != NULL) { DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,head.hh_head)); } \
+ else { out=NULL; }                                                              \
+ while (out != NULL) {                                                           \
+    if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) {       \
+        if ((UTHASH_KEYCMP((out)->hh.key,keyptr,keylen_in)) == 0) { break; }         \
+    }                                                                            \
+    if ((out)->hh.hh_next != NULL) { DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,(out)->hh.hh_next)); } \
+    else { out = NULL; }                                                         \
+ }                                                                               \
+} while (0)
+
+/* add an item to a bucket  */
+#define UTHASH_ADD_TO_BKT(head,addhh)                                              \
+do {                                                                             \
+ head.count++;                                                                   \
+ (addhh)->hh_next = head.hh_head;                                                \
+ (addhh)->hh_prev = NULL;                                                        \
+ if (head.hh_head != NULL) { (head).hh_head->hh_prev = (addhh); }                \
+ (head).hh_head=addhh;                                                           \
+ if ((head.count >= ((head.expand_mult+1U) * UTHASH_BKT_CAPACITY_THRESH))          \
+     && ((addhh)->tbl->noexpand != 1U)) {                                        \
+       UTHASH_EXPAND_BUCKETS((addhh)->tbl);                                        \
+ }                                                                               \
+} while (0)
+
+/* remove an item from a given bucket */
+#define UTHASH_DEL_IN_BKT(hh,head,hh_del)                                          \
+    (head).count--;                                                              \
+    if ((head).hh_head == hh_del) {                                              \
+      (head).hh_head = hh_del->hh_next;                                          \
+    }                                                                            \
+    if (hh_del->hh_prev) {                                                       \
+        hh_del->hh_prev->hh_next = hh_del->hh_next;                              \
+    }                                                                            \
+    if (hh_del->hh_next) {                                                       \
+        hh_del->hh_next->hh_prev = hh_del->hh_prev;                              \
+    }
+
+/* Bucket expansion has the effect of doubling the number of buckets
+ * and redistributing the items into the new buckets. Ideally the
+ * items will distribute more or less evenly into the new buckets
+ * (the extent to which this is true is a measure of the quality of
+ * the hash function as it applies to the key domain).
+ *
+ * With the items distributed into more buckets, the chain length
+ * (item count) in each bucket is reduced. Thus by expanding buckets
+ * the hash keeps a bound on the chain length. This bounded chain
+ * length is the essence of how a hash provides constant time lookup.
+ *
+ * The calculation of tbl->ideal_chain_maxlen below deserves some
+ * explanation. First, keep in mind that we're calculating the ideal
+ * maximum chain length based on the *new* (doubled) bucket count.
+ * In fractions this is just n/b (n=number of items,b=new num buckets).
+ * Since the ideal chain length is an integer, we want to calculate
+ * ceil(n/b). We don't depend on floating point arithmetic in this
+ * hash, so to calculate ceil(n/b) with integers we could write
+ *
+ *      ceil(n/b) = (n/b) + ((n%b)?1:0)
+ *
+ * and in fact a previous version of this hash did just that.
+ * But now we have improved things a bit by recognizing that b is
+ * always a power of two. We keep its base 2 log handy (call it lb),
+ * so now we can write this with a bit shift and logical AND:
+ *
+ *      ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)
+ *
+ */
+#define UTHASH_EXPAND_BUCKETS(tbl)                                                 \
+do {                                                                             \
+    unsigned _he_bkt;                                                            \
+    unsigned _he_bkt_i;                                                          \
+    struct UT_hash_handle *_he_thh, *_he_hh_nxt;                                 \
+    UT_hash_bucket *_he_new_buckets, *_he_newbkt;                                \
+    _he_new_buckets = (UT_hash_bucket*)uthash_malloc(                            \
+             2UL * tbl->num_buckets * sizeof(struct UT_hash_bucket));            \
+    if (!_he_new_buckets) { uthash_fatal( "out of memory"); }                    \
+    memset(_he_new_buckets, 0,                                                   \
+            2UL * tbl->num_buckets * sizeof(struct UT_hash_bucket));             \
+    tbl->ideal_chain_maxlen =                                                    \
+       (tbl->num_items >> (tbl->log2_num_buckets+1U)) +                          \
+       (((tbl->num_items & ((tbl->num_buckets*2U)-1U)) != 0U) ? 1U : 0U);        \
+    tbl->nonideal_items = 0;                                                     \
+    for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++)                \
+    {                                                                            \
+        _he_thh = tbl->buckets[ _he_bkt_i ].hh_head;                             \
+        while (_he_thh != NULL) {                                                \
+           _he_hh_nxt = _he_thh->hh_next;                                        \
+           UTHASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2U, _he_bkt);           \
+           _he_newbkt = &(_he_new_buckets[ _he_bkt ]);                           \
+           if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) {                \
+             tbl->nonideal_items++;                                              \
+             _he_newbkt->expand_mult = _he_newbkt->count /                       \
+                                        tbl->ideal_chain_maxlen;                 \
+           }                                                                     \
+           _he_thh->hh_prev = NULL;                                              \
+           _he_thh->hh_next = _he_newbkt->hh_head;                               \
+           if (_he_newbkt->hh_head != NULL) { _he_newbkt->hh_head->hh_prev =     \
+                _he_thh; }                                                       \
+           _he_newbkt->hh_head = _he_thh;                                        \
+           _he_thh = _he_hh_nxt;                                                 \
+        }                                                                        \
+    }                                                                            \
+    uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \
+    tbl->num_buckets *= 2U;                                                      \
+    tbl->log2_num_buckets++;                                                     \
+    tbl->buckets = _he_new_buckets;                                              \
+    tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ?         \
+        (tbl->ineff_expands+1U) : 0U;                                            \
+    if (tbl->ineff_expands > 1U) {                                               \
+        tbl->noexpand=1;                                                         \
+        uthash_noexpand_fyi(tbl);                                                \
+    }                                                                            \
+    uthash_expand_fyi(tbl);                                                      \
+} while (0)
+
+
+/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */
+/* Note that UTHASH_SORT assumes the hash handle name to be hh.
+ * UTHASH_SRT was added to allow the hash handle name to be passed in. */
+#define UTHASH_SORT(head,cmpfcn) UTHASH_SRT(hh,head,cmpfcn)
+#define UTHASH_SRT(hh,head,cmpfcn)                                                 \
+do {                                                                             \
+  unsigned _hs_i;                                                                \
+  unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize;               \
+  struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail;            \
+  if (head != NULL) {                                                            \
+      _hs_insize = 1;                                                            \
+      _hs_looping = 1;                                                           \
+      _hs_list = &((head)->hh);                                                  \
+      while (_hs_looping != 0U) {                                                \
+          _hs_p = _hs_list;                                                      \
+          _hs_list = NULL;                                                       \
+          _hs_tail = NULL;                                                       \
+          _hs_nmerges = 0;                                                       \
+          while (_hs_p != NULL) {                                                \
+              _hs_nmerges++;                                                     \
+              _hs_q = _hs_p;                                                     \
+              _hs_psize = 0;                                                     \
+              for ( _hs_i = 0; _hs_i  < _hs_insize; _hs_i++ ) {                  \
+                  _hs_psize++;                                                   \
+                  _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ?              \
+                          ((void*)((char*)(_hs_q->next) +                        \
+                          (head)->hh.tbl->hho)) : NULL);                         \
+                  if (! (_hs_q) ) { break; }                                     \
+              }                                                                  \
+              _hs_qsize = _hs_insize;                                            \
+              while ((_hs_psize > 0U) || ((_hs_qsize > 0U) && (_hs_q != NULL))) {\
+                  if (_hs_psize == 0U) {                                         \
+                      _hs_e = _hs_q;                                             \
+                      _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ?          \
+                              ((void*)((char*)(_hs_q->next) +                    \
+                              (head)->hh.tbl->hho)) : NULL);                     \
+                      _hs_qsize--;                                               \
+                  } else if ( (_hs_qsize == 0U) || (_hs_q == NULL) ) {           \
+                      _hs_e = _hs_p;                                             \
+                      if (_hs_p != NULL){                                        \
+                        _hs_p = (UT_hash_handle*)((_hs_p->next != NULL) ?        \
+                                ((void*)((char*)(_hs_p->next) +                  \
+                                (head)->hh.tbl->hho)) : NULL);                   \
+                       }                                                         \
+                      _hs_psize--;                                               \
+                  } else if ((                                                   \
+                      cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \
+                             DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \
+                             ) <= 0) {                                           \
+                      _hs_e = _hs_p;                                             \
+                      if (_hs_p != NULL){                                        \
+                        _hs_p = (UT_hash_handle*)((_hs_p->next != NULL) ?        \
+                               ((void*)((char*)(_hs_p->next) +                   \
+                               (head)->hh.tbl->hho)) : NULL);                    \
+                       }                                                         \
+                      _hs_psize--;                                               \
+                  } else {                                                       \
+                      _hs_e = _hs_q;                                             \
+                      _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ?          \
+                              ((void*)((char*)(_hs_q->next) +                    \
+                              (head)->hh.tbl->hho)) : NULL);                     \
+                      _hs_qsize--;                                               \
+                  }                                                              \
+                  if ( _hs_tail != NULL ) {                                      \
+                      _hs_tail->next = ((_hs_e != NULL) ?                        \
+                            ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL);          \
+                  } else {                                                       \
+                      _hs_list = _hs_e;                                          \
+                  }                                                              \
+                  if (_hs_e != NULL) {                                           \
+                  _hs_e->prev = ((_hs_tail != NULL) ?                            \
+                     ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL);              \
+                  }                                                              \
+                  _hs_tail = _hs_e;                                              \
+              }                                                                  \
+              _hs_p = _hs_q;                                                     \
+          }                                                                      \
+          if (_hs_tail != NULL){                                                 \
+            _hs_tail->next = NULL;                                               \
+          }                                                                      \
+          if ( _hs_nmerges <= 1U ) {                                             \
+              _hs_looping=0;                                                     \
+              (head)->hh.tbl->tail = _hs_tail;                                   \
+              DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list));      \
+          }                                                                      \
+          _hs_insize *= 2U;                                                      \
+      }                                                                          \
+      UTHASH_FSCK(hh,head);                                                        \
+ }                                                                               \
+} while (0)
+
+/* This function selects items from one hash into another hash.
+ * The end result is that the selected items have dual presence
+ * in both hashes. There is no copy of the items made; rather
+ * they are added into the new hash through a secondary hash
+ * hash handle that must be present in the structure. */
+#define UTHASH_SELECT(hh_dst, dst, hh_src, src, cond)                              \
+do {                                                                             \
+  unsigned _src_bkt, _dst_bkt;                                                   \
+  void *_last_elt=NULL, *_elt;                                                   \
+  UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL;                         \
+  ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst));                 \
+  if (src != NULL) {                                                             \
+    for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) {     \
+      for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head;                \
+          _src_hh != NULL;                                                       \
+          _src_hh = _src_hh->hh_next) {                                          \
+          _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh);                       \
+          if (cond(_elt)) {                                                      \
+            _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho);               \
+            _dst_hh->key = _src_hh->key;                                         \
+            _dst_hh->keylen = _src_hh->keylen;                                   \
+            _dst_hh->hashv = _src_hh->hashv;                                     \
+            _dst_hh->prev = _last_elt;                                           \
+            _dst_hh->next = NULL;                                                \
+            if (_last_elt_hh != NULL) { _last_elt_hh->next = _elt; }             \
+            if (dst == NULL) {                                                   \
+              DECLTYPE_ASSIGN(dst,_elt);                                         \
+              UTHASH_MAKE_TABLE(hh_dst,dst);                                       \
+            } else {                                                             \
+              _dst_hh->tbl = (dst)->hh_dst.tbl;                                  \
+            }                                                                    \
+            UTHASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt);    \
+            UTHASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh);            \
+            (dst)->hh_dst.tbl->num_items++;                                      \
+            _last_elt = _elt;                                                    \
+            _last_elt_hh = _dst_hh;                                              \
+          }                                                                      \
+      }                                                                          \
+    }                                                                            \
+  }                                                                              \
+  UTHASH_FSCK(hh_dst,dst);                                                         \
+} while (0)
+
+#define UTHASH_CLEAR(hh,head)                                                      \
+do {                                                                             \
+  if (head != NULL) {                                                            \
+    uthash_free((head)->hh.tbl->buckets,                                         \
+                (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket));      \
+    UTHASH_BLOOM_FREE((head)->hh.tbl);                                             \
+    uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                          \
+    (head)=NULL;                                                                 \
+  }                                                                              \
+} while (0)
+
+#define UTHASH_OVERHEAD(hh,head)                                                   \
+ ((head != NULL) ? (                                                             \
+ (size_t)(((head)->hh.tbl->num_items   * sizeof(UT_hash_handle))   +             \
+          ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket))   +             \
+           sizeof(UT_hash_table)                                   +             \
+           (UTHASH_BLOOM_BYTELEN))) : 0U)
+
+#ifdef NO_DECLTYPE
+#define UTHASH_ITER(hh,head,el,tmp)                                                \
+for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \
+  (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL)))
+#else
+#define UTHASH_ITER(hh,head,el,tmp)                                                \
+for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL));      \
+  (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL)))
+#endif
+
+/* obtain a count of items in the hash */
+#define UTHASH_COUNT(head) UTHASH_CNT(hh,head)
+#define UTHASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U)
+
+typedef struct UT_hash_bucket {
+   struct UT_hash_handle *hh_head;
+   unsigned count;
+
+   /* expand_mult is normally set to 0. In this situation, the max chain length
+    * threshold is enforced at its default value, UTHASH_BKT_CAPACITY_THRESH. (If
+    * the bucket's chain exceeds this length, bucket expansion is triggered).
+    * However, setting expand_mult to a non-zero value delays bucket expansion
+    * (that would be triggered by additions to this particular bucket)
+    * until its chain length reaches a *multiple* of UTHASH_BKT_CAPACITY_THRESH.
+    * (The multiplier is simply expand_mult+1). The whole idea of this
+    * multiplier is to reduce bucket expansions, since they are expensive, in
+    * situations where we know that a particular bucket tends to be overused.
+    * It is better to let its chain length grow to a longer yet-still-bounded
+    * value, than to do an O(n) bucket expansion too often.
+    */
+   unsigned expand_mult;
+
+} UT_hash_bucket;
+
+/* random signature used only to find hash tables in external analysis */
+#define UTHASH_SIGNATURE 0xa0111fe1u
+#define UTHASH_BLOOM_SIGNATURE 0xb12220f2u
+
+typedef struct UT_hash_table {
+   UT_hash_bucket *buckets;
+   unsigned num_buckets, log2_num_buckets;
+   unsigned num_items;
+   struct UT_hash_handle *tail; /* tail hh in app order, for fast append    */
+   ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */
+
+   /* in an ideal situation (all buckets used equally), no bucket would have
+    * more than ceil(#items/#buckets) items. that's the ideal chain length. */
+   unsigned ideal_chain_maxlen;
+
+   /* nonideal_items is the number of items in the hash whose chain position
+    * exceeds the ideal chain maxlen. these items pay the penalty for an uneven
+    * hash distribution; reaching them in a chain traversal takes >ideal steps */
+   unsigned nonideal_items;
+
+   /* ineffective expands occur when a bucket doubling was performed, but
+    * afterward, more than half the items in the hash had nonideal chain
+    * positions. If this happens on two consecutive expansions we inhibit any
+    * further expansion, as it's not helping; this happens when the hash
+    * function isn't a good fit for the key domain. When expansion is inhibited
+    * the hash will still work, albeit no longer in constant time. */
+   unsigned ineff_expands, noexpand;
+
+   uint32_t signature; /* used only to find hash tables in external analysis */
+#ifdef UTHASH_BLOOM
+   uint32_t bloom_sig; /* used only to test bloom exists in external analysis */
+   uint8_t *bloom_bv;
+   uint8_t bloom_nbits;
+#endif
+
+} UT_hash_table;
+
+typedef struct UT_hash_handle {
+   struct UT_hash_table *tbl;
+   void *prev;                       /* prev element in app order      */
+   void *next;                       /* next element in app order      */
+   struct UT_hash_handle *hh_prev;   /* previous hh in bucket order    */
+   struct UT_hash_handle *hh_next;   /* next hh in bucket order        */
+   void *key;                        /* ptr to enclosing struct's key  */
+   unsigned keylen;                  /* enclosing struct's key len     */
+   unsigned hashv;                   /* result of hash-fcn(key)        */
+} UT_hash_handle;
+
+#endif /* UTHASH_H */

+ 3 - 0
go.mod

@@ -0,0 +1,3 @@
+module git.nspix.com/golang/php
+
+go 1.16

+ 88 - 0
zend/array.c

@@ -0,0 +1,88 @@
+#include "array.h"
+
+#ifdef ZEND_ENGINE_3
+#include <zend_long.h>
+#endif
+
+//create zval
+//php7 zval在栈 无法返回 故返回nil 调用方处理
+zval* php_array_create_zval() {
+#ifdef ZEND_ENGINE_3
+    return NULL;
+#else
+    zval* arr;
+    ALLOC_INIT_ZVAL(arr);
+    array_init(arr);
+    return arr;
+#endif
+}
+
+//create php7 zval
+void php7_array_init(zval* zv) {
+    array_init(zv);
+}
+
+//$arr[int] = int;
+void php_array_add_index_long(void* arr, ulong idx, long n) {
+#ifdef ZEND_ENGINE_3
+    add_index_long((zval*)arr, (zend_ulong)idx, (zend_long)n);
+#else
+    add_index_long((zval*)arr, idx, n);
+#endif
+}
+//$arr[int] = 'string';
+void php_array_add_index_string(void* arr, ulong idx, char* value) {
+#ifdef ZEND_ENGINE_3
+    add_index_string((zval*)arr, (zend_ulong)idx, value);
+#else
+    add_index_string((zval*)arr, idx, value, 1);
+#endif
+}
+//$arr[int] = 3.14;
+void php_array_add_index_double(void* arr, ulong idx, double d) {
+#ifdef ZEND_ENGINE_3
+    add_index_double((zval*)arr, (zend_ulong)idx, d);
+#else
+    add_index_double((zval*)arr, idx, d);
+#endif
+}
+
+
+//$arr['string'] = int;
+void php_array_add_assoc_long(void* arr, char* key, long n) {
+#ifdef ZEND_ENGINE_3
+    add_assoc_long((zval*)arr, key, (zend_long)n);
+#else
+    add_assoc_long((zval*)arr, key, n);
+#endif
+}
+//$arr['string'] = 'string';
+void php_array_add_assoc_string(void* arr, char* key, char* value) {
+#ifdef ZEND_ENGINE_3
+    add_assoc_string((zval*)arr, key, value);
+#else
+    add_assoc_string((zval*)arr, key, value, 1);
+#endif
+}
+//$arr['string'] = 3.14
+void php_array_add_assoc_double(void* arr, char* key, double d) {
+    add_assoc_double((zval*)arr, key, d);
+}
+
+
+//$array[int] = array[]
+void php_array_add_index_zval(void* arr, ulong index, void* value) {
+#ifdef ZEND_ENGINE_3
+    add_index_zval((zval*)arr, (zend_ulong)index, (zval*)value);
+#else
+    add_index_zval((zval*)arr, index, (zval*)value);
+#endif
+}
+//$array['string'] = array[]
+void php_array_add_assoc_zval(void* arr, char* key, void* value) {
+    add_assoc_zval((zval*)arr, key, (zval*)value);
+}
+//$array[] = array[]
+void php_array_add_next_index_zval(void* arr, void* zvalue) {
+    add_next_index_zval((zval*)arr, (zval*)zvalue);
+}

+ 31 - 0
zend/array.h

@@ -0,0 +1,31 @@
+#include <zend_API.h>
+
+//create zval
+zval* php_array_create_zval();
+
+//create php7 zval
+void php7_array_init(zval* zv);
+
+//$arr[int] = int;
+void php_array_add_index_long(void* arr, ulong idx, long n);
+//$arr[int] = 'string';
+void php_array_add_index_string(void* arr, ulong idx, char* value);
+//$arr[int] = 3.14;
+void php_array_add_index_double(void* arr, ulong idx, double d);
+
+
+//$arr['string'] = int;
+void php_array_add_assoc_long(void* arr, char* key, long n);
+//$arr['string'] = 'string';
+void php_array_add_assoc_string(void* arr, char* key, char* value);
+//$arr['string'] = 3.14;
+void php_array_add_assoc_double(void* arr, char* key, double d);
+
+
+
+//$array[int] = array[]
+void php_array_add_index_zval(void* arr, ulong index, void* value);
+//$array['string'] = array[]
+void php_array_add_assoc_zval(void* arr, char* key, void* value);
+//$array[] = array[]
+void php_array_add_next_index_zval(void* arr, void* zvalue);

+ 34 - 0
zend/c_util.go

@@ -0,0 +1,34 @@
+package zend
+
+/*
+ */
+import "C"
+import "unsafe"
+
+// import "reflect"
+
+func c2goBool(ok C.int) bool {
+	if ok == 1 {
+		return true
+	}
+	return false
+}
+
+func go2cBool(ok bool) C.int {
+	if ok {
+		return 1
+	}
+	return 0
+}
+
+//
+type go2cfnty *[0]byte
+
+// 参数怎么传递
+func go2cfnp(fn unsafe.Pointer) *[0]byte {
+	return go2cfnty(fn)
+}
+func go2cfn(fn interface{}) *[0]byte {
+	// assert(reflect.TypeOf(fn).Kind == reflect.Ptrx)
+	return go2cfnp(fn.(unsafe.Pointer))
+}

+ 27 - 0
zend/clog.c

@@ -0,0 +1,27 @@
+
+#define CLOG_MAIN
+#include "clog.h"
+
+// only for inline compile
+
+#define STRINGIZE_NX(A) #A
+#define STRINGIZE(A) STRINGIZE_NX(A)
+
+void dlog_set_level(int id, int level)
+{
+#ifdef LOGLEVEL
+    if (strcasecmp(STRINGIZE(LOGLEVEL), "error") == 0) {
+        clog_set_level(id, CLOG_ERROR);
+    } else if (strcasecmp(STRINGIZE(LOGLEVEL), "info") == 0) {
+        clog_set_level(id, CLOG_INFO);
+    } else if (strcasecmp(STRINGIZE(LOGLEVEL), "warn") == 0) {
+        clog_set_level(id, CLOG_WARN);
+    } else {
+        clog_set_level(id, CLOG_DEBUG);
+    }
+    // #warning "defined log level"
+#else
+    // #warning "not defined log level"
+    clog_set_level(id, CLOG_DEBUG);
+#endif
+}

+ 635 - 0
zend/clog.h

@@ -0,0 +1,635 @@
+/* clog: Extremely simple logger for C.
+ *
+ * Features:
+ * - Implemented purely as a single header file.
+ * - Create multiple loggers.
+ * - Four log levels (debug, info, warn, error).
+ * - Custom formats.
+ * - Fast.
+ *
+ * Dependencies:
+ * - Should conform to C89, C++98 (but requires vsnprintf, unfortunately).
+ * - POSIX environment.
+ *
+ * USAGE:
+ *
+ * Include this header in any file that wishes to write to logger(s).  In
+ * exactly one file (per executable), define CLOG_MAIN first (e.g. in your
+ * main .c file).
+ *
+ *     #define CLOG_MAIN
+ *     #include "clog.h"
+ *
+ * This will define the actual objects that all the other units will use.
+ *
+ * Loggers are identified by integers (0 - 15).  It's expected that you'll
+ * create meaningful constants and then refer to the loggers as such.
+ *
+ * Example:
+ *
+ *  const int MY_LOGGER = 0;
+ *
+ *  int main() {
+ *      int r;
+ *      r = clog_init_path(MY_LOGGER, "my_log.txt");
+ *      if (r != 0) {
+ *          fprintf(stderr, "Logger initialization failed.\n");
+ *          return 1;
+ *      }
+ *      clog_info(CLOG(MY_LOGGER), "Hello, world!");
+ *      clog_free(MY_LOGGER);
+ *      return 0;
+ *  }
+ *
+ * The CLOG macro used in the call to clog_info is a helper that passes the
+ * __FILE__ and __LINE__ parameters for you, so you don't have to type them
+ * every time. (It could be prettier with variadic macros, but that requires
+ * C99 or C++11 to be standards compliant.)
+ *
+ * Errors encountered by clog will be printed to stderr.  You can suppress
+ * these by defining a macro called CLOG_SILENT before including clog.h.
+ *
+ * License: Do whatever you want. It would be nice if you contribute
+ * improvements as pull requests here:
+ *
+ *   https://github.com/mmueller/clog
+ *
+ * Copyright 2013 Mike Mueller <mike@subfocal.net>.
+ *
+ * As is; no warranty is provided; use at your own risk.
+ */
+
+#ifndef __CLOG_H__
+#define __CLOG_H__
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+/* Number of loggers that can be defined. */
+#define CLOG_MAX_LOGGERS 16
+
+/* Format strings cannot be longer than this. */
+#define CLOG_FORMAT_LENGTH 256
+
+/* Formatted times and dates should be less than this length. If they are not,
+ * they will not appear in the log. */
+#define CLOG_DATETIME_LENGTH 256
+
+/* Default format strings. */
+#define CLOG_DEFAULT_FORMAT "%d %t %f(%n): %l: %m\n"
+#define CLOG_DEFAULT_DATE_FORMAT "%Y-%m-%d"
+#define CLOG_DEFAULT_TIME_FORMAT "%H:%M:%S"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum clog_level {
+    CLOG_DEBUG,
+    CLOG_INFO,
+    CLOG_WARN,
+    CLOG_ERROR
+};
+
+struct clog;
+
+/**
+ * Create a new logger writing to the given file path.  The file will always
+ * be opened in append mode.
+ *
+ * @param id
+ * A constant integer between 0 and 15 that uniquely identifies this logger.
+ *
+ * @param path
+ * Path to the file where log messages will be written.
+ *
+ * @return
+ * Zero on success, non-zero on failure.
+ */
+int clog_init_path(int id, const char *const path);
+
+/**
+ * Create a new logger writing to a file descriptor.
+ *
+ * @param id
+ * A constant integer between 0 and 15 that uniquely identifies this logger.
+ *
+ * @param fd
+ * The file descriptor where log messages will be written.
+ *
+ * @return
+ * Zero on success, non-zero on failure.
+ */
+int clog_init_fd(int id, int fd);
+
+/**
+ * Destroy (clean up) a logger.  You should do this at the end of execution,
+ * or when you are done using the logger.
+ *
+ * @param id
+ * The id of the logger to destroy.
+ */
+void clog_free(int id);
+
+#define CLOG(id) __FILE__, __LINE__, id
+
+/**
+ * Log functions (one per level).  Call these to write messages to the log
+ * file.  The first three arguments can be replaced with a call to the CLOG
+ * macro defined above, e.g.:
+ *
+ *     clog_debug(CLOG(MY_LOGGER_ID), "This is a log message.");
+ *
+ * @param sfile
+ * The name of the source file making this log call (e.g. __FILE__).
+ *
+ * @param sline
+ * The line number of the call in the source code (e.g. __LINE__).
+ *
+ * @param id
+ * The id of the logger to write to.
+ *
+ * @param fmt
+ * The format string for the message (printf formatting).
+ *
+ * @param ...
+ * Any additional format arguments.
+ */
+void clog_debug(const char *sfile, int sline, int id, const char *fmt, ...);
+void clog_info(const char *sfile, int sline, int id, const char *fmt, ...);
+void clog_warn(const char *sfile, int sline, int id, const char *fmt, ...);
+void clog_error(const char *sfile, int sline, int id, const char *fmt, ...);
+
+    // add for simple usage
+#define dlog_debug(...) clog_debug(CLOG(STDOUT_FILENO), __VA_ARGS__)
+#define dlog_info(...) clog_info(CLOG(STDOUT_FILENO), __VA_ARGS__)
+#define dlog_warn(...) clog_warn(CLOG(STDOUT_FILENO), __VA_ARGS__)
+#define dlog_error(...) clog_error(CLOG(STDOUT_FILENO), __VA_ARGS__)
+void dlog_set_level(int id, int level);
+
+/**
+ * Set the minimum level of messages that should be written to the log.
+ * Messages below this level will not be written.  By default, loggers are
+ * created with level == CLOG_DEBUG.
+ *
+ * @param id
+ * The identifier of the logger.
+ *
+ * @param level
+ * The new minimum log level.
+ *
+ * @return
+ * Zero on success, non-zero on failure.
+ */
+int clog_set_level(int id, enum clog_level level);
+
+/**
+ * Set the format string used for times.  See strftime(3) for how this string
+ * should be defined.  The default format string is CLOG_DEFAULT_TIME_FORMAT.
+ *
+ * @param fmt
+ * The new format string, which must be less than CLOG_FORMAT_LENGTH bytes.
+ *
+ * @return
+ * Zero on success, non-zero on failure.
+ */
+int clog_set_time_fmt(int id, const char *fmt);
+
+/**
+ * Set the format string used for dates.  See strftime(3) for how this string
+ * should be defined.  The default format string is CLOG_DEFAULT_DATE_FORMAT.
+ *
+ * @param fmt
+ * The new format string, which must be less than CLOG_FORMAT_LENGTH bytes.
+ *
+ * @return
+ * Zero on success, non-zero on failure.
+ */
+int clog_set_date_fmt(int id, const char *fmt);
+
+/**
+ * Set the format string for log messages.  Here are the substitutions you may
+ * use:
+ *
+ *     %f: Source file name generating the log call.
+ *     %n: Source line number where the log call was made.
+ *     %m: The message text sent to the logger (after printf formatting).
+ *     %d: The current date, formatted using the logger's date format.
+ *     %t: The current time, formatted using the logger's time format.
+ *     %l: The log level (one of "DEBUG", "INFO", "WARN", or "ERROR").
+ *     %%: A literal percent sign.
+ *
+ * The default format string is CLOG_DEFAULT_FORMAT.
+ *
+ * @param fmt
+ * The new format string, which must be less than CLOG_FORMAT_LENGTH bytes.
+ * You probably will want to end this with a newline (\n).
+ *
+ * @return
+ * Zero on success, non-zero on failure.
+ */
+int clog_set_fmt(int id, const char *fmt);
+
+/*
+ * No need to read below this point.
+ */
+
+/**
+ * The C logger structure.
+ */
+struct clog {
+
+    /* The current level of this logger. Messages below it will be dropped. */
+    enum clog_level level;
+
+    /* The file being written. */
+    int fd;
+
+    /* The format specifier. */
+    char fmt[CLOG_FORMAT_LENGTH];
+
+    /* Date format */
+    char date_fmt[CLOG_FORMAT_LENGTH];
+
+    /* Time format */
+    char time_fmt[CLOG_FORMAT_LENGTH];
+
+    /* Tracks whether the fd needs to be closed eventually. */
+    int opened;
+};
+
+void _clog_err(const char *fmt, ...);
+
+#ifdef CLOG_MAIN
+struct clog *_clog_loggers[CLOG_MAX_LOGGERS] = { 0 };
+#else
+extern struct clog *_clog_loggers[CLOG_MAX_LOGGERS];
+#endif
+
+#ifdef CLOG_MAIN
+
+const char *const CLOG_LEVEL_NAMES[] = {
+    "DEBUG",
+    "INFO",
+    "WARN",
+    "ERROR",
+};
+
+int
+clog_init_path(int id, const char *const path)
+{
+    int fd = open(path, O_CREAT | O_WRONLY | O_APPEND, 0666);
+    if (fd == -1) {
+        _clog_err("Unable to open %s: %s\n", path, strerror(errno));
+        return 1;
+    }
+    if (clog_init_fd(id, fd)) {
+        close(fd);
+        return 1;
+    }
+    _clog_loggers[id]->opened = 1;
+    return 0;
+}
+
+int
+clog_init_fd(int id, int fd)
+{
+    struct clog *logger;
+
+    if (_clog_loggers[id] != NULL) {
+        _clog_err("Logger %d already initialized.\n", id);
+        return 1;
+    }
+
+    logger = (struct clog *) malloc(sizeof(struct clog));
+    if (logger == NULL) {
+        _clog_err("Failed to allocate logger: %s\n", strerror(errno));
+        return 1;
+    }
+
+    logger->level = CLOG_DEBUG;
+    logger->fd = fd;
+    logger->opened = 0;
+    strcpy(logger->fmt, CLOG_DEFAULT_FORMAT);
+    strcpy(logger->date_fmt, CLOG_DEFAULT_DATE_FORMAT);
+    strcpy(logger->time_fmt, CLOG_DEFAULT_TIME_FORMAT);
+
+    _clog_loggers[id] = logger;
+    return 0;
+}
+
+void
+clog_free(int id)
+{
+    if (_clog_loggers[id]) {
+        if (_clog_loggers[id]->opened) {
+            close(_clog_loggers[id]->fd);
+        }
+        free(_clog_loggers[id]);
+    }
+}
+
+int
+clog_set_level(int id, enum clog_level level)
+{
+    if (_clog_loggers[id] == NULL) {
+        return 1;
+    }
+    if ((unsigned) level > CLOG_ERROR) {
+        return 1;
+    }
+    _clog_loggers[id]->level = level;
+    return 0;
+}
+
+int
+clog_set_time_fmt(int id, const char *fmt)
+{
+    struct clog *logger = _clog_loggers[id];
+    if (logger == NULL) {
+        _clog_err("clog_set_time_fmt: No such logger: %d\n", id);
+        return 1;
+    }
+    if (strlen(fmt) >= CLOG_FORMAT_LENGTH) {
+        _clog_err("clog_set_time_fmt: Format specifier too long.\n");
+        return 1;
+    }
+    strcpy(logger->time_fmt, fmt);
+    return 0;
+}
+
+int
+clog_set_date_fmt(int id, const char *fmt)
+{
+    struct clog *logger = _clog_loggers[id];
+    if (logger == NULL) {
+        _clog_err("clog_set_date_fmt: No such logger: %d\n", id);
+        return 1;
+    }
+    if (strlen(fmt) >= CLOG_FORMAT_LENGTH) {
+        _clog_err("clog_set_date_fmt: Format specifier too long.\n");
+        return 1;
+    }
+    strcpy(logger->date_fmt, fmt);
+    return 0;
+}
+
+int
+clog_set_fmt(int id, const char *fmt)
+{
+    struct clog *logger = _clog_loggers[id];
+    if (logger == NULL) {
+        _clog_err("clog_set_fmt: No such logger: %d\n", id);
+        return 1;
+    }
+    if (strlen(fmt) >= CLOG_FORMAT_LENGTH) {
+        _clog_err("clog_set_fmt: Format specifier too long.\n");
+        return 1;
+    }
+    strcpy(logger->fmt, fmt);
+    return 0;
+}
+
+/* Internal functions */
+
+size_t
+_clog_append_str(char **dst, char *orig_buf, const char *src, size_t cur_size)
+{
+    size_t new_size = cur_size;
+
+    while (strlen(*dst) + strlen(src) >= new_size) {
+        new_size *= 2;
+    }
+    if (new_size != cur_size) {
+        if (*dst == orig_buf) {
+            *dst = (char *) malloc(new_size);
+            strcpy(*dst, orig_buf);
+        } else {
+            *dst = (char *) realloc(*dst, new_size);
+        }
+    }
+
+    strcat(*dst, src);
+    return new_size;
+}
+
+size_t
+_clog_append_int(char **dst, char *orig_buf, long int d, size_t cur_size)
+{
+    char buf[40]; /* Enough for 128-bit decimal */
+    if (snprintf(buf, 40, "%ld", d) >= 40) {
+        return cur_size;
+    }
+    return _clog_append_str(dst, orig_buf, buf, cur_size);
+}
+
+size_t
+_clog_append_time(char **dst, char *orig_buf, struct tm *lt,
+                  const char *fmt, size_t cur_size)
+{
+    char buf[CLOG_DATETIME_LENGTH];
+    size_t result = strftime(buf, CLOG_DATETIME_LENGTH, fmt, lt);
+
+    if (result > 0) {
+        return _clog_append_str(dst, orig_buf, buf, cur_size);
+    }
+
+    return cur_size;
+}
+
+const char *
+_clog_basename(const char *path)
+{
+    const char *slash = strrchr(path, '/');
+    if (slash) {
+        path = slash + 1;
+    }
+#ifdef _WIN32
+    slash = strrchr(path, '\\');
+    if (slash) {
+        path = slash + 1;
+    }
+#endif
+    return path;
+}
+
+char *
+_clog_format(const struct clog *logger, char buf[], size_t buf_size,
+             const char *sfile, int sline, const char *level,
+             const char *message)
+{
+    size_t cur_size = buf_size;
+    char *result = buf;
+    enum { NORMAL, SUBST } state = NORMAL;
+    size_t fmtlen = strlen(logger->fmt);
+    size_t i;
+    time_t t = time(NULL);
+    struct tm *lt = localtime(&t);
+
+    sfile = _clog_basename(sfile);
+    result[0] = 0;
+    for (i = 0; i < fmtlen; ++i) {
+        if (state == NORMAL) {
+            if (logger->fmt[i] == '%') {
+                state = SUBST;
+            } else {
+                char str[2] = { 0 };
+                str[0] = logger->fmt[i];
+                cur_size = _clog_append_str(&result, buf, str, cur_size);
+            }
+        } else {
+            switch (logger->fmt[i]) {
+                case '%':
+                    cur_size = _clog_append_str(&result, buf, "%", cur_size);
+                    break;
+                case 't':
+                    cur_size = _clog_append_time(&result, buf, lt,
+                                                 logger->time_fmt, cur_size);
+                    break;
+                case 'd':
+                    cur_size = _clog_append_time(&result, buf, lt,
+                                                 logger->date_fmt, cur_size);
+                    break;
+                case 'l':
+                    cur_size = _clog_append_str(&result, buf, level, cur_size);
+                    break;
+                case 'n':
+                    cur_size = _clog_append_int(&result, buf, sline, cur_size);
+                    break;
+                case 'f':
+                    cur_size = _clog_append_str(&result, buf, sfile, cur_size);
+                    break;
+                case 'm':
+                    cur_size = _clog_append_str(&result, buf, message,
+                                                cur_size);
+                    break;
+            }
+            state = NORMAL;
+        }
+    }
+
+    return result;
+}
+
+void
+_clog_log(const char *sfile, int sline, enum clog_level level,
+          int id, const char *fmt, va_list ap)
+{
+    /* For speed: Use a stack buffer until message exceeds 4096, then switch
+     * to dynamically allocated.  This should greatly reduce the number of
+     * memory allocations (and subsequent fragmentation). */
+    char buf[4096];
+    size_t buf_size = 4096;
+    char *dynbuf = buf;
+    char *message;
+    int result;
+    struct clog *logger = _clog_loggers[id];
+
+    if (!logger) {
+        _clog_err("No such logger: %d\n", id);
+        return;
+    }
+
+    if (level < logger->level) {
+        return;
+    }
+
+    /* Format the message text with the argument list. */
+    result = vsnprintf(dynbuf, buf_size, fmt, ap);
+    if ((size_t) result >= buf_size) {
+        buf_size = result + 1;
+        dynbuf = (char *) malloc(buf_size);
+        result = vsnprintf(dynbuf, buf_size, fmt, ap);
+        if ((size_t) result >= buf_size) {
+            /* Formatting failed -- too large */
+            _clog_err("Formatting failed (1).\n");
+            free(dynbuf);
+            return;
+        }
+    }
+
+    /* Format according to log format and write to log */
+    {
+        char message_buf[4096];
+        message = _clog_format(logger, message_buf, 4096, sfile, sline,
+                               CLOG_LEVEL_NAMES[level], dynbuf);
+        if (!message) {
+            _clog_err("Formatting failed (2).\n");
+            if (dynbuf != buf) {
+                free(dynbuf);
+            }
+            return;
+        }
+        result = write(logger->fd, message, strlen(message));
+        if (result == -1) {
+            _clog_err("Unable to write to log file: %s\n", strerror(errno));
+        }
+        if (message != message_buf) {
+            free(message);
+        }
+        if (dynbuf != buf) {
+            free(dynbuf);
+        }
+    }
+}
+
+void
+clog_debug(const char *sfile, int sline, int id, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    _clog_log(sfile, sline, CLOG_DEBUG, id, fmt, ap);
+}
+
+void
+clog_info(const char *sfile, int sline, int id, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    _clog_log(sfile, sline, CLOG_INFO, id, fmt, ap);
+}
+
+void
+clog_warn(const char *sfile, int sline, int id, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    _clog_log(sfile, sline, CLOG_WARN, id, fmt, ap);
+}
+
+void
+clog_error(const char *sfile, int sline, int id, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    _clog_log(sfile, sline, CLOG_ERROR, id, fmt, ap);
+}
+
+void
+_clog_err(const char *fmt, ...)
+{
+#ifdef CLOG_SILENT
+    (void) fmt;
+#else
+    va_list ap;
+
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+#endif
+}
+
+#endif /* CLOG_MAIN */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __CLOG_H__ */

+ 35 - 0
zend/compat.c

@@ -0,0 +1,35 @@
+#include "compat.h"
+
+ZEND_API void zend_register_stringl_constant_compat(const char *name, size_t name_len, char *strval, size_t strlen, int flags, int module_number)
+{
+    zend_register_stringl_constant(name, name_len, strval, strlen, flags, module_number);
+}
+
+ZEND_API void zend_register_long_constant_compat(const char *name, size_t name_len, zend_long lval, int flags, int module_number)
+{
+    zend_register_long_constant(name, name_len, lval, flags, module_number);
+}
+
+ZEND_API void zend_register_double_constant_compat(const char *name, size_t name_len, double dval, int flags, int module_number)
+{
+    zend_register_double_constant(name, name_len, dval, flags, module_number);
+}
+
+ZEND_API void zend_register_bool_constant_compat(const char *name, size_t name_len, zend_bool bval, int flags, int module_number)
+{
+#ifdef ZEND_ENGINE_3
+    zend_register_bool_constant(name, name_len, bval, flags, module_number);
+#else
+    zend_register_long_constant(name, name_len, bval, flags, module_number);
+#endif
+}
+
+ZEND_API void zend_register_null_constant_compat(const char *name, size_t name_len, int flags, int module_number)
+{
+#ifdef ZEND_ENGINE_3
+    zend_register_null_constant(name, name_len, flags, module_number);
+#else
+    zend_register_stringl_constant(name, name_len, NULL, 0, flags, module_number);
+#endif
+}
+

+ 45 - 0
zend/compat.h

@@ -0,0 +1,45 @@
+#ifndef _PHP_COMPAT_H_
+#define _PHP_COMPAT_H_
+
+#include <zend.h>
+#include <zend_constants.h>
+#include <zend_modules.h>
+
+#ifdef ZEND_ENGINE_3
+// #define Z_BVAL_P(zv) Z_LVAL_P(zv)
+/*
+#define Z_BVAL_PP(zv) Z_LVAL_P(*(zv))
+#define Z_LVAL_PP(zv) Z_LVAL_P(*(zv))
+#define Z_DVAL_PP(zv) Z_DVAL_P(*(zv))
+#define Z_STRVAL_PP(zv) Z_STRVAL_P(*(zv))
+#define Z_STRLEN_PP(zv) Z_STRLEN_P(*(zv))
+#define Z_ARRVAL_PP(zv) Z_ARRVAL_P(*(zv))
+#define Z_TYPE_PP(zv) Z_TYPE_P(*(zv))
+*/
+#endif
+
+#ifdef ZEND_ENGINE_2
+typedef long zend_long;
+#define _IS_BOOL IS_BOOL
+#define IS_TRUE (IS_CALLABLE+1)
+#define IS_FALSE (IS_CALLABLE+2)
+#define IS_UNDEF (IS_CALLABLE+3)
+
+#if !defined(IS_CONSTANT_AST)
+#if ZEND_MODULE_API_NO < 20121212  // < PHP 5.5
+#error "Maybe you are using not supported zend < 2.5.0 (PHP < 5.5.0)"
+#else
+#define IS_CONSTANT_AST (IS_CALLABLE+4)
+#endif
+#endif
+
+#endif
+
+ZEND_API void zend_register_stringl_constant_compat(const char *name, size_t name_len, char *strval, size_t strlen, int flags, int module_number);
+ZEND_API void zend_register_long_constant_compat(const char *name, size_t name_len, zend_long lval, int flags, int module_number);
+ZEND_API void zend_register_double_constant_compat(const char *name, size_t name_len, double dval, int flags, int module_number);
+ZEND_API void zend_register_bool_constant_compat(const char *name, size_t name_len, zend_bool bval, int flags, int module_number);
+ZEND_API void zend_register_null_constant_compat(const char *name, size_t name_len, int flags, int module_number);
+
+
+#endif

+ 5 - 0
zend/customize.h

@@ -0,0 +1,5 @@
+// return $this
+#define IS_SELF 30
+
+// return interface
+#define IS_ZVAL 31

+ 5 - 0
zend/goapi.c

@@ -0,0 +1,5 @@
+#include "goapi.h"
+/*void php_array_add_index_string(void* arr, int* key, char* value, int d) {
+    add_index_string(arr, (ulong) *key, value, 1);
+}*/
+

+ 543 - 0
zend/goapi.go

@@ -0,0 +1,543 @@
+package zend
+
+/*
+#include <stdlib.h>
+#include "array.h"
+*/
+import "C"
+import "reflect"
+import "unsafe"
+import "log"
+
+////
+//export goapi_array_new
+func goapi_array_new(kind int, retpp *unsafe.Pointer) {
+	sty := FROMCIP(goapi_type_r(kind)).(reflect.Type)
+	log.Println(sty.Kind().String(), sty)
+	arrval := reflect.MakeSlice(reflect.SliceOf(sty), 0, 0)
+	*retpp = TOCIP(arrval.Interface())
+}
+
+//export goapi_array_push
+func goapi_array_push(arrp unsafe.Pointer, elm unsafe.Pointer, retpp *unsafe.Pointer) {
+	// *arr = append(*arr, s)
+	arr := FROMCIP(arrp)
+	vty := reflect.TypeOf(arr)
+	if vty != nil {
+	}
+
+	switch vty.Elem().Kind() {
+	case reflect.Int64:
+		elmval := (int64)(C.int64_t(uintptr(elm)))
+		narr := append(arr.([]int64), elmval)
+		*retpp = TOCIP(narr)
+	case reflect.String:
+		// only support string element
+		elmval := C.GoString((*C.char)(elm))
+		narr := append(arr.([]string), elmval)
+		*retpp = TOCIP(narr)
+	}
+	return
+}
+
+//export goapi_map_new
+func goapi_map_new(retpp *unsafe.Pointer) {
+	// m := make(map[string]string, 0)
+	kty := FROMCIP(goapi_type_r(int(reflect.String))).(reflect.Type)
+	vty := FROMCIP(goapi_type_r(int(reflect.String))).(reflect.Type)
+	mv := reflect.MakeMap(reflect.MapOf(kty, vty))
+	m := mv.Interface().(map[string]string)
+
+	*retpp = TOCIP(m)
+}
+
+//export goapi_map_add
+func goapi_map_add(mp unsafe.Pointer, keyp unsafe.Pointer, valuep unsafe.Pointer) {
+	m := FROMCIP(mp).(map[string]string)
+	key := C.GoString((*C.char)(keyp))
+	value := C.GoString((*C.char)(valuep))
+
+	m[key] = value
+}
+
+//export goapi_map_get
+func goapi_map_get(mp unsafe.Pointer, keyp unsafe.Pointer, retpp *unsafe.Pointer) {
+	m := FROMCIP(mp).(map[string]string)
+	key := C.GoString((*C.char)(keyp))
+
+	if _, has := m[key]; has {
+		v := m[key]
+		*retpp = unsafe.Pointer(C.CString(v))
+	}
+
+	return
+}
+
+//export goapi_map_del
+func goapi_map_del(mp unsafe.Pointer, keyp unsafe.Pointer) {
+	m := FROMCIP(mp).(map[string]string)
+	key := C.GoString((*C.char)(keyp))
+
+	if _, has := m[key]; has {
+		delete(m, key)
+	}
+}
+
+//export goapi_map_has
+func goapi_map_has(mp unsafe.Pointer, keyp unsafe.Pointer) bool {
+	m := FROMCIP(mp).(map[string]string)
+	key := C.GoString((*C.char)(keyp))
+
+	if _, has := m[key]; has {
+		return true
+	}
+
+	return false
+}
+
+//export goapi_chan_new
+func goapi_chan_new(kind int, buffer int, retpp *unsafe.Pointer) {
+	cty := FROMCIP(goapi_type_r(kind)).(reflect.Type)
+	chval := reflect.MakeChan(cty, buffer)
+
+	*retpp = TOCIP(chval.Interface())
+}
+
+// TODO
+//export goapi_chan_read
+func goapi_chan_read(chp unsafe.Pointer, retpp *unsafe.Pointer) {
+	ch := FROMCIP(chp)
+	chv := reflect.ValueOf(ch)
+
+	rv, ok := chv.Recv()
+	if ok {
+		*retpp = TOCIP(rv.Interface())
+	}
+}
+
+// TODO
+//export goapi_chan_write
+func goapi_chan_write(chp unsafe.Pointer, elm unsafe.Pointer) {
+	ch := FROMCIP(chp)
+	chv := reflect.ValueOf(ch)
+
+	chv.Send(reflect.ValueOf(FROMCIP(elm)))
+}
+
+// TODO
+//export goapi_chan_close
+func goapi_chan_close(chp unsafe.Pointer) {
+	ch := FROMCIP(chp)
+	chv := reflect.ValueOf(ch)
+
+	chv.Close()
+}
+
+func goapi_type_r(kind int) unsafe.Pointer {
+	var retpp unsafe.Pointer
+	goapi_type(kind, &retpp)
+	return retpp
+}
+
+//export goapi_type
+func goapi_type(kind int, retpp *unsafe.Pointer) {
+	wkind := (reflect.Kind)(kind)
+
+	var refty reflect.Type
+
+	switch wkind {
+	case reflect.Invalid:
+	case reflect.Bool:
+		refty = reflect.TypeOf(true)
+	case reflect.Int:
+		refty = reflect.TypeOf(int(0))
+	case reflect.Int8:
+		refty = reflect.TypeOf(int8(0))
+	case reflect.Int16:
+		refty = reflect.TypeOf(int16(0))
+	case reflect.Int32:
+		refty = reflect.TypeOf(int32(0))
+	case reflect.Int64:
+		refty = reflect.TypeOf(int64(0))
+	case reflect.Uint:
+		refty = reflect.TypeOf(uint(0))
+	case reflect.Uint8:
+		refty = reflect.TypeOf(uint8(0))
+	case reflect.Uint16:
+		refty = reflect.TypeOf(uint16(0))
+	case reflect.Uint32:
+		refty = reflect.TypeOf(uint32(0))
+	case reflect.Uint64:
+		refty = reflect.TypeOf(uint64(0))
+	case reflect.Uintptr:
+		refty = reflect.TypeOf(uintptr(0))
+	case reflect.Float32:
+		refty = reflect.TypeOf(float32(1.0))
+	case reflect.Float64:
+		refty = reflect.TypeOf(float64(1.0))
+	case reflect.Complex64:
+	case reflect.Complex128:
+	case reflect.Array:
+		refty = reflect.TypeOf([]interface{}{})
+	case reflect.Chan:
+	case reflect.Func:
+	case reflect.Interface:
+	case reflect.Map:
+		refty = reflect.TypeOf(map[interface{}]interface{}{})
+	case reflect.Ptr:
+	case reflect.Slice:
+	case reflect.String:
+		refty = reflect.TypeOf("")
+	case reflect.Struct:
+	case reflect.UnsafePointer:
+		refty = reflect.TypeOf(unsafe.Pointer(uintptr(0)))
+	}
+
+	*retpp = TOCIP(refty)
+}
+
+//export goapi_typeof
+func goapi_typeof(v unsafe.Pointer, retpp *unsafe.Pointer) {
+	gv := FROMCIP(v)
+	*retpp = TOCIP(reflect.TypeOf(gv))
+}
+
+//export goapi_typeid
+func goapi_typeid(v unsafe.Pointer) int {
+	gv := FROMCIP(v)
+	return int(reflect.TypeOf(gv).Kind())
+}
+
+//export goapi_new
+func goapi_new(kind int, retpp *unsafe.Pointer) {
+	refty := goapi_type_r(kind)
+	refval := reflect.New(FROMCIP(refty).(reflect.Type))
+
+	*retpp = TOCIP(refval)
+}
+func goapi_new_kind(kind reflect.Kind, retpp *unsafe.Pointer) {
+	goapi_new(int(kind), retpp)
+}
+func goapi_new_type(kty reflect.Type, retpp *unsafe.Pointer) {
+	goapi_new_kind(kty.Kind(), retpp)
+}
+
+//export goapi_new_value
+func goapi_new_value(kind int, v uintptr, retpp *unsafe.Pointer) {
+	wkind := reflect.Kind(kind)
+
+	var refval interface{}
+	switch wkind {
+	case reflect.Invalid:
+	case reflect.Bool:
+		if v == 0 {
+			refval = false
+		} else {
+			refval = true
+		}
+	case reflect.Int:
+		refval = int(v)
+	case reflect.Int8:
+		refval = int8(v)
+	case reflect.Int16:
+		refval = int16(v)
+	case reflect.Int32:
+		refval = int32(v)
+	case reflect.Int64:
+		refval = int64(v)
+	case reflect.Uint:
+		refval = uint(v)
+	case reflect.Uint8:
+		refval = uint8(v)
+	case reflect.Uint16:
+		refval = uint16(v)
+	case reflect.Uint32:
+		refval = uint32(v)
+	case reflect.Uint64:
+		refval = uint64(v)
+	case reflect.Uintptr:
+		refval = v
+	case reflect.Float32:
+		refval = float32(*(*C.float)(unsafe.Pointer(v)))
+		C.free(unsafe.Pointer(v))
+	case reflect.Float64:
+		refval = float64(*(*C.double)(unsafe.Pointer(v)))
+		C.free(unsafe.Pointer(v))
+	case reflect.Complex64:
+	case reflect.Complex128:
+	case reflect.Array:
+	case reflect.Chan:
+	case reflect.Func:
+	case reflect.Interface:
+	case reflect.Map:
+	case reflect.Ptr:
+	case reflect.Slice:
+	case reflect.String:
+		refval = C.GoString((*C.char)(unsafe.Pointer(v)))
+	case reflect.Struct:
+	case reflect.UnsafePointer:
+		refval = unsafe.Pointer(v)
+	}
+
+	*retpp = TOCIP(refval)
+}
+
+//export goapi_set_value
+func goapi_set_value(gv unsafe.Pointer, v uintptr, retpp *unsafe.Pointer) {
+	giv := FROMCIP(gv)
+	gvty := reflect.TypeOf(giv)
+	var nv unsafe.Pointer
+	goapi_new_value(int(gvty.Kind()), v, &nv)
+	reflect.ValueOf(giv).Set(reflect.ValueOf(FROMCIP(nv)))
+
+	if *retpp != gv {
+		*retpp = gv
+	}
+}
+
+//export goapi_set_php_array
+func goapi_set_php_array(rp unsafe.Pointer, arr *unsafe.Pointer) {
+	ru := reflect.ValueOf(FROMCIP(rp))
+	set_php_array(ru, arr)
+}
+
+func set_php_array(ru reflect.Value, rv *unsafe.Pointer) {
+	switch ru.Kind() {
+	case reflect.Array, reflect.Slice:
+		n := ru.Len()
+		if n == 0 {
+			break
+		}
+		for i := 0; i < n; i++ {
+			son := ru.Index(i)
+			switch son.Kind() {
+			case reflect.Array, reflect.Slice, reflect.Map:
+				temp := unsafe.Pointer(C.php_array_create_zval())
+				//php7 zval处理为栈内存
+				if nil == temp {
+					var tm C.zval
+					tmp := &tm
+					C.php7_array_init(tmp)
+					temp = unsafe.Pointer(tmp)
+				}
+				sonup := &temp
+				push_php_array(son, sonup, int64(i), 1)
+				C.php_array_add_next_index_zval(*rv, *sonup)
+			default:
+				push_php_array(son, rv, int64(i), 1)
+			}
+		}
+	case reflect.Map:
+		t := ru.MapKeys()
+		if len(t) == 0 {
+			break
+		}
+		mk := t[0]
+		mapKeyType := 0
+		switch mk.Kind() {
+		case reflect.String:
+			mapKeyType = 2
+		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
+			reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16,
+			reflect.Uint32, reflect.Uint64:
+			mapKeyType = 1
+		default:
+			return
+		}
+		for _, v := range t {
+			son := ru.MapIndex(v)
+			switch son.Kind() {
+			case reflect.Array, reflect.Slice, reflect.Map:
+				temp := unsafe.Pointer(C.php_array_create_zval())
+				if nil == temp {
+					var tm C.zval
+					tmp := &tm
+					C.php7_array_init(tmp)
+					temp = unsafe.Pointer(tmp)
+				}
+				sonup := &temp
+				if 1 == mapKeyType {
+					ik := v.Int()
+					push_php_array(son, sonup, ik, mapKeyType)
+					C.php_array_add_index_zval(*rv, C.ulong(ik), *sonup)
+				} else {
+					ik := v.String()
+					push_php_array(son, sonup, ik, mapKeyType)
+					C.php_array_add_assoc_zval(*rv, C.CString(ik), *sonup)
+				}
+			default:
+				if 1 == mapKeyType {
+					push_php_array(son, rv, v.Int(), mapKeyType)
+				} else {
+					push_php_array(son, rv, v.String(), mapKeyType)
+				}
+			}
+		}
+	default:
+		panic("wft")
+	}
+}
+
+func push_php_array(ru reflect.Value, rv *unsafe.Pointer, key interface{}, at int) {
+	if rv == nil {
+		return
+	}
+	var stringKey string
+	var numberKey int64
+
+	if at == 1 {
+		numberKey = key.(int64)
+	} else {
+		stringKey = key.(string)
+	}
+
+	switch ru.Kind() {
+	case reflect.Array, reflect.Slice, reflect.Map:
+		set_php_array(ru, rv)
+	case reflect.Int64:
+		fallthrough
+	case reflect.Int8:
+		fallthrough
+	case reflect.Int32:
+		fallthrough
+	case reflect.Int:
+		fallthrough
+	case reflect.Int16:
+		if 1 == at {
+			C.php_array_add_index_long(*rv, C.ulong(numberKey), C.long(ru.Int()))
+		} else {
+			C.php_array_add_assoc_long(*rv, C.CString(stringKey), C.long(ru.Int()))
+		}
+	case reflect.Uint:
+		fallthrough
+	case reflect.Uint64:
+		fallthrough
+	case reflect.Uint32:
+		fallthrough
+	case reflect.Uint16:
+		fallthrough
+	case reflect.Uint8:
+		if 1 == at {
+			C.php_array_add_index_long(*rv, C.ulong(numberKey), C.long(ru.Uint()))
+		} else {
+			C.php_array_add_assoc_long(*rv, C.CString(stringKey), C.long(ru.Uint()))
+		}
+	case reflect.Float64:
+		fallthrough
+	case reflect.Float32:
+		if 1 == at {
+			C.php_array_add_index_double(*rv, C.ulong(numberKey), C.double(ru.Float()))
+		} else {
+			C.php_array_add_assoc_double(*rv, C.CString(stringKey), C.double(ru.Float()))
+		}
+	case reflect.String:
+		if 1 == at {
+			C.php_array_add_index_string(*rv, C.ulong(numberKey), C.CString(ru.String()))
+		} else {
+			C.php_array_add_assoc_string(*rv, C.CString(stringKey), C.CString(ru.String()))
+		}
+	}
+}
+
+//export goapi_get_value
+func goapi_get_value(gv unsafe.Pointer, gvt unsafe.Pointer) uintptr {
+	giv := FROMCIP(gv)
+	gvty := reflect.TypeOf(giv)
+	if gvty == nil {
+		return 0
+	}
+
+    rvt := 0
+    
+	var rv uintptr
+
+	switch gvty.Kind() {
+	case reflect.Invalid:
+	case reflect.Bool:
+		if giv.(bool) {
+			rv = 1
+		} else {
+			rv = 0
+		}
+
+        rvt = PHPTY_IS_BOOL
+	case reflect.Int:
+		rv = (uintptr)(giv.(int))
+        rvt = PHPTY_IS_LONG
+	case reflect.Int8:
+		rv = (uintptr)(giv.(int8))
+        rvt = PHPTY_IS_LONG
+	case reflect.Int16:
+		rv = (uintptr)(giv.(int16))
+        rvt = PHPTY_IS_LONG
+	case reflect.Int32:
+		rv = (uintptr)(giv.(int32))
+        rvt = PHPTY_IS_LONG
+	case reflect.Int64:
+		rv = (uintptr)(giv.(int64))
+        rvt = PHPTY_IS_LONG
+	case reflect.Uint:
+		rv = (uintptr)(giv.(uint))
+        rvt = PHPTY_IS_LONG
+	case reflect.Uint8:
+		rv = (uintptr)(giv.(uint8))
+        rvt = PHPTY_IS_LONG
+	case reflect.Uint16:
+		rv = (uintptr)(giv.(uint16))
+        rvt = PHPTY_IS_LONG
+	case reflect.Uint32:
+		rv = (uintptr)(giv.(uint32))
+        rvt = PHPTY_IS_LONG
+	case reflect.Uint64:
+		rv = (uintptr)(giv.(uint64))
+        rvt = PHPTY_IS_LONG
+	case reflect.Uintptr:
+		rv = giv.(uintptr)
+	case reflect.Float32:
+        var drv *C.double = (*C.double)(C.malloc(8))
+        *drv = (C.double)(giv.(float32))
+        rv = uintptr(unsafe.Pointer(drv))
+        rvt = PHPTY_IS_DOUBLE
+	case reflect.Float64:
+        var drv *C.double = (*C.double)(C.malloc(8))
+        *drv = (C.double)(giv.(float64))
+        rv = uintptr(unsafe.Pointer(drv))
+        rvt = PHPTY_IS_DOUBLE
+	case reflect.Complex64:
+	case reflect.Complex128:
+	case reflect.Chan:
+	case reflect.Func:
+	case reflect.Interface:
+	case reflect.Ptr:
+	case reflect.String:
+		rv = uintptr(unsafe.Pointer(C.CString(giv.(string))))
+        rvt = PHPTY_IS_STRING
+	case reflect.Map:
+        rvt = PHPTY_IS_ARRAY
+	case reflect.Slice:
+        rvt = PHPTY_IS_ARRAY
+	case reflect.Array:
+        rvt = PHPTY_IS_ARRAY
+	case reflect.Struct:
+	case reflect.UnsafePointer:
+		rv = (uintptr)(giv.(unsafe.Pointer))
+	}
+
+	// 简洁方式
+	/*rvty := FROMCIP(goapi_type_r(int(reflect.Uintptr))).(reflect.Type)
+	log.Println(rvty)
+	log.Println(goapi_type_r(int(reflect.Uintptr)))
+	if gvty.ConvertibleTo(rvty) {
+		rv = reflect.ValueOf(giv).Convert(rvty).Interface().(uintptr)
+	} else {
+		switch gvty.Kind() {
+		case reflect.Ptr:
+			rv = reflect.ValueOf(giv).Pointer()
+		default:
+			log.Panicln("can not convert:", giv, gvty.Kind(), gvty, rvty)
+		}
+	}*/
+    
+    *(*C.int)(gvt) = C.int(rvt)
+	return rv
+}

+ 123 - 0
zend/goapi.h

@@ -0,0 +1,123 @@
+#ifndef _GOAPI_H_
+#define _GOAPI_H_
+
+#include <stdlib.h>
+
+
+enum GoType {
+   GT_Invalid    ,
+   GT_Bool       ,
+   GT_Int        ,
+   GT_Int8       ,
+   GT_Int16      ,
+   GT_Int32      ,
+   GT_Int64      ,
+   GT_Uint       ,
+   GT_Uint8      ,
+   GT_Uint16     ,
+   GT_Uint32     ,
+   GT_Uint64     ,
+   GT_Uintptr    ,
+   GT_Float32    ,
+   GT_Float64    ,
+   GT_Complex64  ,
+   GT_Complex128 ,
+   GT_Array      ,
+   GT_Chan       ,
+   GT_Func       ,
+   GT_Interface  ,
+   GT_Map        ,
+   GT_Ptr        ,
+   GT_Slice      ,
+   GT_String     ,
+   GT_Struct     ,
+   GT_UnsafePointer,
+};
+
+
+/* Start of boilerplate cgo prologue.  */
+
+#ifndef GO_CGO_PROLOGUE_H
+#define GO_CGO_PROLOGUE_H
+
+typedef signed char GoInt8;
+typedef unsigned char GoUint8;
+typedef short GoInt16;
+typedef unsigned short GoUint16;
+typedef int GoInt32;
+typedef unsigned int GoUint32;
+typedef long long GoInt64;
+typedef unsigned long long GoUint64;
+typedef GoInt64 GoInt;
+typedef GoUint64 GoUint;
+typedef __SIZE_TYPE__ GoUintptr;
+typedef float GoFloat32;
+typedef double GoFloat64;
+typedef __complex float GoComplex64;
+typedef __complex double GoComplex128;
+
+// static assertion to make sure the file is being used on architecture
+// at least with matching size of GoInt.
+typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
+
+typedef struct { char *p; GoInt n; } GoString;
+typedef void *GoMap;
+typedef void *GoChan;
+typedef struct { void *t; void *v; } GoInterface;
+typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
+
+#endif
+
+/* End of boilerplate cgo prologue.  */
+
+///////////
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//// put goapi.go's export function here
+
+extern void goapi_array_new(GoInt p0, void** p1);
+
+extern void goapi_array_push(void* p0, void* p1, void** p2);
+
+extern void goapi_map_new(void** p0);
+
+extern void goapi_map_add(void* p0, void* p1, void* p2);
+
+extern void goapi_map_get(void* p0, void* p1, void** p2);
+
+extern void goapi_map_del(void* p0, void* p1);
+
+extern GoUint8 goapi_map_has(void* p0, void* p1);
+
+extern void goapi_chan_new(GoInt p0, GoInt p1, void** p2);
+
+extern void goapi_chan_read(void* p0, void** p1);
+
+extern void goapi_chan_write(void* p0, void* p1);
+
+extern void goapi_chan_close(void* p0);
+
+extern void goapi_type(GoInt p0, void** p1);
+
+extern void goapi_typeof(void* p0, void** p1);
+
+extern GoInt goapi_typeid(void* p0);
+
+extern void goapi_new(GoInt p0, void** p1);
+
+extern void goapi_new_value(GoInt p0, GoUintptr p1, void** p2);
+
+extern void goapi_set_value(void* p0, GoUintptr p1, void** p2);
+
+extern void goapi_set_php_array(void* p0, void** p1);
+
+extern GoUintptr goapi_get_value(void* p0, void* p1);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+

+ 72 - 0
zend/init.go

@@ -0,0 +1,72 @@
+package zend
+
+/*
+#include "szend.h"
+#include "clog.h"
+*/
+import "C"
+
+// import "unsafe"
+import (
+	"io/ioutil"
+	"log"
+	"math/rand"
+	"runtime"
+	"time"
+)
+
+const (
+	SINGLE_THREAD = 1
+)
+
+func init() {
+	initRawLog()
+	initCLog()
+	initSingleThread()
+}
+
+func initRawLog() {
+	// To enable use true:
+	if false {
+		log.SetFlags(log.Lshortfile | log.LstdFlags)
+		log.SetPrefix("[phpgo] ")
+	} else {
+		log.SetFlags(0)
+		log.SetOutput(ioutil.Discard)
+	}
+}
+
+func initCLog() {
+	C.clog_init_fd(C.STDOUT_FILENO, C.STDOUT_FILENO)
+	C.clog_init_fd(C.STDERR_FILENO, C.STDERR_FILENO)
+	C.dlog_set_level(C.STDOUT_FILENO, 0)
+	C.dlog_set_level(C.STDERR_FILENO, 0)
+}
+
+func initSingleThread() {
+	if C.gozend_iszts() == 0 {
+		omp := runtime.GOMAXPROCS(0)
+		if omp > SINGLE_THREAD {
+			runtime.GOMAXPROCS(SINGLE_THREAD)
+			log.Printf("Adjust GOMAXPROCS %d => %d\n", omp, SINGLE_THREAD)
+		}
+	}
+
+	rand.Seed(time.Now().UnixNano())
+}
+
+const (
+	LOG_NONE  = int(-1)
+	LOG_ERROR = int(C.CLOG_ERROR)
+	LOG_WARN  = int(C.CLOG_WARN)
+	LOG_INFO  = int(C.CLOG_INFO)
+	LOG_DEBUG = int(C.CLOG_DEBUG)
+)
+
+func LogInitFD(fd int) {
+	C.clog_init_fd(C.int(fd), C.int(fd))
+}
+
+func LogSetLevel(fd int, level int) {
+	C.dlog_set_level(C.int(fd), C.int(level))
+}

+ 10 - 0
zend/sphp.c

@@ -0,0 +1,10 @@
+#include <php.h>
+
+char* gozend_php_version() {
+    return PHP_VERSION;
+}
+
+int gozend_php_version_id() {
+    return PHP_VERSION_ID;
+}
+

+ 259 - 0
zend/szend.c

@@ -0,0 +1,259 @@
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "sztypes.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "zend_API.h"
+
+#ifdef ZEND_ENGINE_3
+int gozend_call_user_function(zval *object, char *func_name, zval *retval_ptr, int argc, zval *params)
+{
+    zval function_name;
+    ZVAL_NULL(&function_name);
+    ZVAL_STRING(&function_name, func_name);
+
+    assert(retval_ptr != NULL);
+    if (call_user_function(CG(function_table), object , &function_name,
+                           retval_ptr, argc, params TSRMLS_CC) == SUCCESS) {
+        return 0;
+    }
+    return -1;
+}
+#else /* ZEND_ENGINE_2 */
+int gozend_call_user_function(zval **object, char *func_name, zval *retval_ptr, int argc, zval **params)
+{
+  zval function_name;
+  INIT_ZVAL(function_name);
+  ZVAL_STRING(&function_name, func_name, 1);
+
+  assert(retval_ptr != NULL);
+  if (call_user_function(CG(function_table), object , &function_name,
+                         retval_ptr, argc, params TSRMLS_CC) == SUCCESS) {
+    return 0;
+  }
+  return -1;
+}
+#endif
+
+
+#ifdef ZEND_ENGINE_3
+static int gozend_call_user_function_string_ex(char *func_name, char *str, char **retstr, zval *object)
+{
+  zval args[9];
+  ZVAL_NULL(&args[0]);
+  ZVAL_STRING(&args[0], str);
+
+  zval retval_ptr;
+  ZVAL_NULL(&retval_ptr);
+
+    int ret = gozend_call_user_function(object, func_name, &retval_ptr, 1, args);
+    if (ret == 0) {
+    char *cstr = NULL;
+    int cslen = 0;
+
+    if (Z_TYPE_P(&retval_ptr) == IS_STRING) {
+      cslen = Z_STRLEN_P(&retval_ptr);
+      cstr = estrndup(Z_STRVAL_P(&retval_ptr), cslen);
+      *retstr = cstr;
+    }
+  }
+  return ret;
+}
+#else /* ZEND_ENGINE_2 */
+static int gozend_call_user_function_string_ex(char *func_name, char *str, char **retstr, zval **object)
+{
+  zval *args[9];
+  MAKE_STD_ZVAL(args[0]);
+  ZVAL_STRING(args[0], str, 1);
+
+  zval retval_ptr;
+  ZVAL_NULL(&retval_ptr)
+
+    int ret = gozend_call_user_function(object, func_name, &retval_ptr, 1, args);
+  if (ret == 0) {
+    char *cstr = NULL;
+    int cslen = 0;
+
+    if (Z_TYPE_P(&retval_ptr) == IS_STRING) {
+      cslen = Z_STRLEN_P(&retval_ptr);
+      cstr = estrndup(Z_STRVAL_P(&retval_ptr), cslen);
+      *retstr = cstr;
+    }
+  }
+  return ret;
+}
+#endif
+
+
+char *gozend_call_user_function_string(char *func_name, char *str)
+{
+    char *res = NULL;
+    int ret = gozend_call_user_function_string_ex(func_name, str, &res, NULL);
+    return res;
+}
+
+int gozend_iszts() {
+#ifdef ZTS
+    return 1;
+#else
+    return 0;
+#endif
+}
+
+int gozend_zend_version_no() {
+#ifdef ZEND_ENGINE_3
+     return 3;
+#else
+     return 2;
+#endif
+}
+
+char* gozend_zend_version() {
+    return get_zend_version();
+}
+
+void gozend_efree(void *ptr) {
+    efree(ptr);
+}
+
+char *gozend_estrdup(char *str) {
+    return estrdup(str);
+}
+
+char *gozend_estrndup(char *str, unsigned int length) {
+    return estrndup(str, length);
+}
+
+void *gozend_emalloc(size_t size) {
+    return emalloc(size);
+}
+
+void *gozend_ecalloc(size_t nmemb, size_t size) {
+    return ecalloc(nmemb, size);
+}
+
+void *gozend_erealloc(void *ptr, size_t size) {
+    return erealloc(ptr, size);
+}
+
+char gozend_eval_string(char *code)
+{
+    zval retval;
+    #ifdef ZEND_ENGINE_3
+    ZVAL_NULL(&retval);
+    #else
+    INIT_ZVAL(retval);
+    #endif
+
+    int ret = zend_eval_string(code, &retval, (char*)"" TSRMLS_CC);
+
+    // zval_ptr_dtor(&retval);
+    zval_dtor(&retval);
+    return ret == FAILURE;
+}
+
+
+void call_user_function_callback(char *data)
+{
+    uint32_t argc = 0;
+    zval retval_ptr;
+    char *func_name = "say_hello_123";
+    zval function_name;
+#ifdef ZEND_ENGINE_3
+    ZVAL_NULL(&retval_ptr);
+    ZVAL_NULL(&function_name);
+    ZVAL_STRING(&function_name, func_name);
+#else
+    INIT_ZVAL(retval_ptr);
+    INIT_ZVAL(function_name);
+    ZVAL_STRING(&function_name, func_name, 1);
+#endif
+
+    void *cobj = NULL; /* no object */
+    if (call_user_function(CG(function_table), cobj , &function_name,
+                           &retval_ptr, argc, NULL TSRMLS_CC) == SUCCESS) {
+        /* do something with retval_ptr here if you like */
+    }
+
+    /* don't forget to free the zvals */
+    // zval_ptr_dtor(&retval_ptr);
+    zval_dtor(&function_name);
+}
+
+// find function name in HashTable
+static int _gozend_function_exists_ht(char *fname, HashTable* ht) {
+#ifdef ZEND_ENGINE_3
+	zend_string *name;
+	zend_function *func;
+	zend_string *lcname;
+
+    name = zend_string_init(fname, strlen(fname), 0);
+
+	if (ZSTR_VAL(name)[0] == '\\') {
+		/* Ignore leading "\" */
+		lcname = zend_string_alloc(ZSTR_LEN(name) - 1, 0);
+		zend_str_tolower_copy(ZSTR_VAL(lcname), ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1);
+	} else {
+		lcname = zend_string_tolower(name);
+	}
+
+	func = zend_hash_find_ptr(ht, lcname);
+	zend_string_release(lcname);
+    zend_string_release(name);
+
+	/*
+	 * A bit of a hack, but not a bad one: we see if the handler of the function
+	 * is actually one that displays "function is disabled" message.
+	 */
+	if (func && (func->type != ZEND_INTERNAL_FUNCTION ||
+                 func->internal_function.handler != zif_display_disabled_function)) {
+        return 1;
+    } else {
+        return 0;
+    }
+#else
+	char *name;
+	int name_len;
+	zend_function *func;
+    int retval = 0;
+
+    name = fname;
+    name_len = strlen(fname);
+	/* Ignore leading "\" */
+	if (name[0] == '\\') {
+		name = &name[1];
+		name_len--;
+	}
+
+	retval = (zend_hash_find(ht, name, name_len+1, (void **)&func) == SUCCESS);
+
+	/*
+	 * A bit of a hack, but not a bad one: we see if the handler of the function
+	 * is actually one that displays "function is disabled" message.
+	 */
+	if (retval && func->type == ZEND_INTERNAL_FUNCTION &&
+		func->internal_function.handler == zif_display_disabled_function) {
+		retval = 0;
+	}
+
+    return retval;
+#endif
+    return 0;
+}
+
+// seems only can be used when vm executing
+int gozend_function_exists(char *fname) {
+    return _gozend_function_exists_ht(fname, EG(function_table));
+}
+
+int gozend_function_registered(char *fname) {
+    // zif_function_exists(); // this call method not compatible on MacOS
+    // zif_function_existsccc();
+
+    return _gozend_function_exists_ht(fname, CG(function_table));
+}
+

+ 21 - 0
zend/szend.h

@@ -0,0 +1,21 @@
+#ifndef _SZEND_H_
+#define _SZEND_H_
+
+#include <stdlib.h>
+#include <stdint.h>
+
+extern char* gozend_call_user_function_string(char *func_name, char *arg);
+extern int gozend_iszts();
+extern void gozend_efree(void *ptr);
+extern char *gozend_estrdup(char *str);
+extern char *gozend_estrndup(char *str, unsigned int lenght);
+extern void *gozend_emalloc(size_t size);
+extern void *gozend_ecalloc(size_t nmemb, size_t size);
+extern void *gozend_erealloc(void *ptr, size_t size);
+extern char gozend_eval_string(char *code);
+extern void call_user_function_callback(char *arg);
+extern int gozend_function_exists(char *fname);
+extern int gozend_function_registered(char *fname);
+extern int gozend_zend_version_no();
+
+#endif

+ 14 - 0
zend/sztypes.h

@@ -0,0 +1,14 @@
+#ifndef _SZ_TYPES_H_
+#define _SZ_TYPES_H_
+
+#include <sys/types.h>
+
+#ifndef uint
+#define uint unsigned int
+#endif
+#ifndef ulong
+#define ulong unsigned long
+#endif
+
+#endif
+

+ 330 - 0
zend/typeconv.go

@@ -0,0 +1,330 @@
+package zend
+
+// go类型转换为PHP类型
+
+/*
+#include <stdlib.h>
+
+#include <zend.h>
+#include "../zend/compat.h"
+#include "../zend/customize.h"
+*/
+import "C"
+import "unsafe"
+
+import (
+	"log"
+	"reflect"
+)
+
+const (
+	PHPTY_IS_NULL         = C.IS_NULL
+	PHPTY_IS_LONG         = C.IS_LONG
+	PHPTY_IS_DOUBLE       = C.IS_DOUBLE
+	PHPTY_IS_BOOL         = C._IS_BOOL
+	PHPTY_IS_TRUE         = C.IS_TRUE
+	PHPTY_IS_FALSE        = C.IS_FALSE
+	PHPTY_IS_ARRAY        = C.IS_ARRAY
+	PHPTY_IS_OBJECT       = C.IS_OBJECT
+	PHPTY_IS_STRING       = C.IS_STRING
+	PHPTY_IS_RESOURCE     = C.IS_RESOURCE
+	PHPTY_IS_CONSTANT     = C.IS_CONSTANT
+	PHPTY_IS_CONSTANT_AST = C.IS_CONSTANT_AST
+	PHPTY_IS_CALLABLE     = C.IS_CALLABLE
+    PHPTY_IS_SELF         = C.IS_SELF
+    PHPTY_IS_INTERFACE    = C.IS_ZVAL
+
+	PHPTY_IS_CONSTANT_TYPE_MASK    = 0x00f
+	PHPTY_IS_CONSTANT_UNQUALIFIED  = 0x010
+	PHPTY_IS_LEXICAL_VAR           = 0x020
+	PHPTY_IS_LEXICAL_REF           = 0x040
+	PHPTY_IS_CONSTANT_IN_NAMESPACE = 0x100
+)
+
+func ArgTypes2Php(fn interface{}) (ptfs *string) {
+	fty := reflect.TypeOf(fn)
+	if fty.Kind() != reflect.Func {
+		log.Panicln("What's that?", fty.Kind().String())
+	}
+
+	var tfs string
+
+	for idx := 0; idx < fty.NumIn(); idx++ {
+		switch fty.In(idx).Kind() {
+		case reflect.String:
+			tfs = tfs + "s"
+		case reflect.Float64:
+			fallthrough
+		case reflect.Float32:
+			tfs = tfs + "d"
+		case reflect.Bool:
+			tfs = tfs + "b"
+		case reflect.Int64:
+			fallthrough
+		case reflect.Uint64:
+			fallthrough
+		case reflect.Int32:
+			fallthrough
+		case reflect.Uint32:
+			fallthrough
+		case reflect.Int:
+			fallthrough
+		case reflect.Uint:
+			fallthrough
+		case reflect.Int16:
+			fallthrough
+		case reflect.Uint16:
+			fallthrough
+		case reflect.Int8:
+			fallthrough
+		case reflect.Uint8:
+			tfs = tfs + "l"
+		case reflect.Ptr:
+			tfs = tfs + "p"
+		case reflect.Interface:
+			tfs = tfs + "a" // Any/interface
+		case reflect.Slice:
+			tfs = tfs + "v" // vector
+		case reflect.Map:
+			tfs = tfs + "m"
+		default:
+			log.Panicln("Unsupported kind:", "wtf", fty.In(idx).String(),
+				fty.In(idx).Kind(), reflect.TypeOf(fn).IsVariadic())
+		}
+	}
+	log.Println("===", tfs, fty.NumIn())
+	return &tfs
+}
+
+func RetType2Php(fn interface{}) (rety int) {
+	fty := reflect.TypeOf(fn)
+	if fty.Kind() != reflect.Func {
+		log.Panicln("What's that?", fty.Kind().String())
+	}
+
+	if fty.NumOut() > 0 {
+		switch fty.Out(0).Kind() {
+		case reflect.String:
+			rety = PHPTY_IS_STRING
+		case reflect.Float64:
+			fallthrough
+		case reflect.Float32:
+			rety = PHPTY_IS_DOUBLE
+		case reflect.Bool:
+			rety = PHPTY_IS_BOOL
+		case reflect.Int64:
+			fallthrough
+		case reflect.Uint64:
+			fallthrough
+		case reflect.Int32:
+			fallthrough
+		case reflect.Uint32:
+			fallthrough
+		case reflect.Int:
+			fallthrough
+		case reflect.Uint:
+			fallthrough
+		case reflect.Int16:
+			fallthrough
+		case reflect.Uint16:
+			fallthrough
+		case reflect.Int8:
+			fallthrough
+		case reflect.Uint8:
+			rety = PHPTY_IS_LONG
+		case reflect.Ptr:
+			rety = PHPTY_IS_RESOURCE
+		case reflect.Slice, reflect.Array, reflect.Map:
+			rety = PHPTY_IS_ARRAY
+        case reflect.Interface:
+            // 尝试支持返回interface
+            rety = PHPTY_IS_INTERFACE
+		default:
+			log.Panicln("wtf", fty.Out(0).String(), fty.Out(0).Kind().String())
+		}
+	}
+
+	return
+}
+
+func ArgValuesFromPhp(fn interface{}, args []uintptr) (argv []reflect.Value) {
+	fty := reflect.TypeOf(fn)
+	if fty.Kind() != reflect.Func {
+		log.Panicln("What's that?", fty.Kind().String())
+	}
+
+	argv = make([]reflect.Value, 0)
+	for idx := 0; idx < fty.NumIn(); idx++ {
+		switch fty.In(idx).Kind() {
+		case reflect.String:
+			var arg = C.GoString((*C.char)(unsafe.Pointer(args[idx])))
+			var v = reflect.ValueOf(arg).Convert(fty.In(idx))
+			argv = append(argv, v)
+		case reflect.Float64:
+			fallthrough
+		case reflect.Float32:
+			var arg = (*C.double)(unsafe.Pointer(args[idx]))
+			var v = reflect.ValueOf(*arg).Convert(fty.In(idx))
+			argv = append(argv, v)
+			C.free(unsafe.Pointer(args[idx]))
+		case reflect.Bool:
+			var arg = (C.int)(args[idx])
+			if arg == 1 {
+				argv = append(argv, reflect.ValueOf(true))
+			} else {
+				argv = append(argv, reflect.ValueOf(false))
+			}
+		case reflect.Int64:
+			fallthrough
+		case reflect.Uint64:
+			fallthrough
+		case reflect.Int32:
+			fallthrough
+		case reflect.Uint32:
+			fallthrough
+		case reflect.Int:
+			fallthrough
+		case reflect.Uint:
+			fallthrough
+		case reflect.Int16:
+			fallthrough
+		case reflect.Uint16:
+			fallthrough
+		case reflect.Int8:
+			fallthrough
+		case reflect.Uint8:
+			var arg = (C.ulonglong)(args[idx])
+			var v = reflect.ValueOf(arg).Convert(fty.In(idx))
+			argv = append(argv, v)
+		case reflect.Ptr:
+			// 有可能是go类的this指针
+			if idx == 0 {
+				// 这里仅是设置一个点位符号,这个gothis指针的位置
+				argv = append(argv, reflect.ValueOf(0))
+			} else {
+				// 不支持其他非this参数的指针
+				panic("wtf")
+			}
+		case reflect.Interface:
+			log.Println("Unsupported interface kind:",
+				fty.In(idx).Kind().String(), fty.In(idx).String())
+		default:
+			log.Panicln("Unsupported kind:",
+				fty.In(idx).Kind().String(), fty.In(idx).String())
+		}
+	}
+
+	if len(argv) != fty.NumIn() {
+		panic("wtf")
+	}
+	return
+}
+
+func ArgValuesFromPhp_p(fn interface{}, args []unsafe.Pointer, ismth bool) (argv []reflect.Value) {
+	fty := reflect.TypeOf(fn)
+	if fty.Kind() != reflect.Func {
+		log.Panicln("What's that?", fty.Kind().String())
+	}
+
+	argv = make([]reflect.Value, fty.NumIn())
+
+	aidx := 0
+	for idx := 0; idx < fty.NumIn(); idx++ {
+		if ismth && idx == 0 {
+			continue
+		}
+
+		aty := fty.In(idx)
+		aiv := FROMCIP(args[aidx])
+		if aiv != nil {
+			if !reflect.TypeOf(aiv).ConvertibleTo(aty) {
+				log.Panicln("can't convert ", reflect.TypeOf(aiv).Kind().String(), aty.Kind().String(),
+					aty.Elem().Kind(), reflect.TypeOf(aiv).AssignableTo(aty), reflect.TypeOf(aiv).Elem().Kind())
+			}
+			argv[idx] = reflect.ValueOf(aiv).Convert(aty)
+		} else {
+			log.Panicln("nil arg", idx, ismth, fty.Name())
+		}
+
+		aidx += 1
+	}
+
+	if len(argv) != fty.NumIn() {
+		panic("wtf")
+	}
+	return
+}
+
+// TODO 多值返回的支持?
+func RetValue2Php(fn interface{}, rvs []reflect.Value) (retv uintptr) {
+	fty := reflect.TypeOf(fn)
+	if fty.Kind() != reflect.Func {
+		log.Panicln("What's that?", fty.Kind().String())
+	}
+	retv = 0
+
+	if fty.NumOut() > 0 {
+		switch fty.Out(0).Kind() {
+		case reflect.String:
+			// 需要reciever 释放内存
+			retv = uintptr(unsafe.Pointer(C.CString(rvs[0].Interface().(string))))
+		case reflect.Float64:
+			fallthrough
+		case reflect.Float32:
+			// 需要reciever 释放内存
+			var pdv *C.double = (*C.double)(C.malloc(8))
+			*pdv = (C.double)(rvs[0].Interface().(float64))
+			retv = uintptr(unsafe.Pointer(pdv))
+		case reflect.Bool:
+			var bv = rvs[0].Interface().(bool)
+			if bv {
+				retv = uintptr(1)
+			} else {
+				retv = uintptr(0)
+			}
+		case reflect.Int64:
+			fallthrough
+		case reflect.Uint64:
+			fallthrough
+		case reflect.Int32:
+			fallthrough
+		case reflect.Uint32:
+			fallthrough
+		case reflect.Int:
+			fallthrough
+		case reflect.Uint:
+			fallthrough
+		case reflect.Int16:
+			fallthrough
+		case reflect.Uint16:
+			fallthrough
+		case reflect.Int8:
+			fallthrough
+		case reflect.Uint8:
+			var dty = reflect.TypeOf(uint64(0))
+			var nv = rvs[0].Convert(dty).Interface().(uint64)
+			retv = uintptr(nv)
+		case reflect.Ptr:
+			var nv = rvs[0].Pointer()
+			retv = uintptr(nv)
+		default:
+			log.Panicln("Unsupported kind:", fty.Out(0).Kind().String())
+		}
+	}
+
+	return
+}
+
+// TODO 多值返回的支持?
+func RetValue2Php_p(fn interface{}, rvs []reflect.Value) (retv unsafe.Pointer) {
+	fty := reflect.TypeOf(fn)
+	if fty.Kind() != reflect.Func {
+		log.Panicln("What's that?", fty.Kind().String())
+	}
+
+	if fty.NumOut() > 0 {
+		rvr := rvs[0].Interface()
+		return TOCIP(rvr)
+	}
+	return nil
+}

+ 71 - 0
zend/utils.go

@@ -0,0 +1,71 @@
+package zend
+
+import "reflect"
+import "unsafe"
+import "log"
+
+func CHKNILOMIT(v interface{}) {}
+func CHKNILEXIT(v interface{}, args ...interface{}) {
+	if v == nil {
+		if len(args) > 0 {
+			log.Panicln(args...)
+		}
+		panic(v)
+	}
+}
+
+// 看来不可能实现,需要在调用处展开的
+func CHKNILRET(v interface{}) (dfn func()) {
+
+	dfn = func() {
+		if r := recover(); r != nil {
+		}
+	}
+
+	if v == nil || v.(bool) == false {
+		panic(nil)
+	}
+
+	return
+}
+
+// 看来不可能实现,需要在调用处展开的
+// 也许两个连用,接近实现return到上层函数的功能,还是很啰嗦。
+func RETURN_IF_DECL(v interface{}) {
+	if v == nil || v.(bool) == false {
+		panic(nil)
+	}
+}
+
+// defer RETURN_IF_EXEC()()
+func RETURN_IF_EXEC(v interface{}) func() {
+	return func() {
+		if r := recover(); r != nil {
+		}
+	}
+}
+
+type Any interface{}
+
+func init() {
+	// log.SetFlags(log.Llongfile | log.LstdFlags)
+	log.SetFlags(log.Lshortfile | log.LstdFlags)
+	log.SetPrefix("[phpgo] ")
+}
+
+func TOCIP(v interface{}) unsafe.Pointer {
+	return unsafe.Pointer(&v)
+}
+
+func FROMCIP(p unsafe.Pointer) interface{} {
+	if p == nil {
+		return nil
+	}
+
+	rp := (*interface{})(p)
+	if rp == nil || *rp == nil {
+        return nil
+    }
+    
+	return reflect.ValueOf(*rp).Interface()
+}

+ 66 - 0
zend/zend.go

@@ -0,0 +1,66 @@
+package zend
+
+// 用go封装Zend的C API函数。
+
+import (
+	"errors"
+	// "fmt"
+)
+
+/*
+// #cgo CFLAGS: -I/usr/include/php -I/usr/include/php/Zend -I/usr/include/php/TSRM
+// #cgo LDFLAGS: -L/home/dev/php5/lib -lphp5
+#cgo CFLAGS: -g -O2 -std=c99 -D_GNU_SOURCE
+// #cgo LDFLAGS: -lphp5
+#cgo linux LDFLAGS: -Wl,--warn-unresolved-symbols -Wl,--unresolved-symbols=ignore-all
+#cgo darwin LDFLAGS: -undefined dynamic_lookup
+
+#include <stdlib.h>
+#include "sztypes.h"
+#include "szend.h"
+
+*/
+import "C"
+import "unsafe"
+import "fmt"
+
+const (
+	ZEND_ENGINE_3 = 3
+	ZEND_ENGINE_2 = 2
+)
+
+var ZEND_ENGINE = int(C.gozend_zend_version_no())
+
+////export call_user_function_string
+func Call_user_function_string(func_name string, arg string) (string, error) {
+	cfname := C.CString(func_name)
+	carg := C.CString(arg)
+	defer C.free(unsafe.Pointer(cfname))
+	defer C.free(unsafe.Pointer(carg))
+
+	ret := C.gozend_call_user_function_string(cfname, carg)
+	if ret == nil {
+		return "", errors.New("call error")
+	}
+
+	fmt.Printf("retp: %p\n", ret)
+	fmt.Println("ret:", C.GoString(ret))
+	defer C.gozend_efree(unsafe.Pointer(ret))
+	return C.GoString(ret), nil
+}
+
+func Call_user_function(func_name string, args ...interface{}) {
+	argc := len(args)
+	if argc > 0 {
+	}
+
+	// TODO args to zval**
+}
+
+func Eval_string(code string) bool {
+	ccode := C.CString(code)
+	defer C.free(unsafe.Pointer(ccode))
+
+	ret := C.gozend_eval_string(ccode)
+	return ret == 1
+}

+ 196 - 0
zend/zend_ini.go

@@ -0,0 +1,196 @@
+package zend
+
+/*
+#include <zend_API.h>
+#include <zend_ini.h>
+
+#ifdef ZEND_ENGINE_2
+typedef zend_ini_entry zend_ini_entry_def;
+typedef char zend_string;  // 为了让gozend_ini_modifier7在php5下能够编译过
+#endif
+
+extern int gozend_ini_modifier7(zend_ini_entry *entry, zend_string *new_value, void *mh_arg1, void *mh_arg2, void *mh_arg3, int stage);
+extern int gozend_ini_modifier5(zend_ini_entry *entry, char *new_value, uint new_value_length, void *mh_arg1, void *mh_arg2, void *mh_arg3, int stage);
+extern void gozend_ini_displayer(zend_ini_entry *ini_entry, int type);
+*/
+import "C"
+import "unsafe"
+
+import (
+	"fmt"
+	// "reflect"
+	"log"
+	"runtime"
+)
+
+type IniEntries struct {
+	zies []C.zend_ini_entry_def
+}
+
+var zend_ini_entry_def_zero C.zend_ini_entry_def
+
+func NewIniEntries() *IniEntries {
+	this := &IniEntries{}
+	this.zies = make([]C.zend_ini_entry_def, 1)
+	this.zies[0] = zend_ini_entry_def_zero
+	return this
+}
+
+func (this *IniEntries) Register(module_number int) int {
+	r := C.zend_register_ini_entries(&this.zies[0], C.int(module_number))
+	log.Println(r)
+	return int(r)
+}
+func (this *IniEntryDef) Unregister(module_number int) {
+	C.zend_unregister_ini_entries(C.int(module_number))
+}
+
+func (this *IniEntries) Add(ie *IniEntryDef) {
+	this.zies[len(this.zies)-1] = ie.zie
+	this.zies = append(this.zies, zend_ini_entry_def_zero)
+}
+
+type IniEntry struct {
+	zie *C.zend_ini_entry
+}
+
+func newZendIniEntryFrom(ie *C.zend_ini_entry) *IniEntry {
+	return &IniEntry{ie}
+}
+func (this *IniEntry) Name() string      { return fromZString(this.zie.name) }
+func (this *IniEntry) Value() string     { return fromZString(this.zie.value) }
+func (this *IniEntry) OrigValue() string { return fromZString(this.zie.orig_value) }
+
+const (
+	INI_USER   = int(C.ZEND_INI_USER)
+	INI_PERDIR = int(C.ZEND_INI_PERDIR)
+	INI_SYSTEM = int(C.ZEND_INI_SYSTEM)
+)
+
+type IniEntryDef struct {
+	zie C.zend_ini_entry_def
+
+	onModify  func(ie *IniEntry, newValue string, stage int) int
+	onDisplay func(ie *IniEntry, itype int)
+}
+
+func NewIniEntryDef() *IniEntryDef {
+	this := &IniEntryDef{}
+	// this.zie = (*C.zend_ini_entry_def)(C.calloc(1, C.sizeof_zend_ini_entry_def))
+	runtime.SetFinalizer(this, zendIniEntryDefFree)
+	return this
+}
+
+func zendIniEntryDefFree(this *IniEntryDef) {
+	if _, ok := iniNameEntries[C.GoString(this.zie.name)]; ok {
+		delete(iniNameEntries, C.GoString(this.zie.name))
+	}
+
+	if this.zie.name != nil {
+		C.free(unsafe.Pointer(this.zie.name))
+	}
+	if this.zie.value != nil {
+		C.free(unsafe.Pointer(this.zie.value))
+	}
+}
+
+func (this *IniEntryDef) Fill3(name string, defaultValue interface{}, modifiable bool,
+	onModify func(), arg1, arg2, arg3 interface{}, displayer func()) {
+	this.zie.name = C.CString(name)
+	this.zie.modifiable = go2cBool(modifiable)
+	// this.zie.orig_modifiable = go2cBool(modifiable)
+	if ZEND_ENGINE == ZEND_ENGINE_3 {
+		this.zie.on_modify = go2cfn(C.gozend_ini_modifier7)
+	} else {
+		this.zie.on_modify = go2cfn(C.gozend_ini_modifier5)
+	}
+	this.zie.displayer = go2cfn(C.gozend_ini_displayer)
+
+	value := fmt.Sprintf("%v", defaultValue)
+	this.zie.value = C.CString(value)
+
+	if arg1 == nil {
+		this.zie.mh_arg1 = nil
+	}
+	if arg2 == nil {
+		this.zie.mh_arg2 = nil
+	}
+	if arg3 == nil {
+		this.zie.mh_arg3 = nil
+	}
+
+	if ZEND_ENGINE == ZEND_ENGINE_3 {
+		this.zie.name_length = C.uint32_t(len(name))
+		this.zie.value_length = C.uint32_t(len(value))
+	} else {
+		// why need +1 for php5?
+		// if not, zend_alter_ini_entry_ex:280行会出现zend_hash_find无结果失败
+		this.zie.name_length = C.uint32_t(len(name) + 1)
+		this.zie.value_length = C.uint32_t(len(value) + 1)
+	}
+	log.Println(name, len(name))
+
+	iniNameEntries[name] = this
+}
+
+func (this *IniEntryDef) Fill2(name string, defaultValue interface{}, modifiable bool,
+	onModify func(), arg1, arg2 interface{}, displayer func()) {
+	this.Fill3(name, defaultValue, modifiable, onModify, arg1, arg2, nil, displayer)
+}
+
+func (this *IniEntryDef) Fill1(name string, defaultValue interface{}, modifiable bool,
+	onModify func(), arg1 interface{}, displayer func()) {
+	this.Fill3(name, defaultValue, modifiable, onModify, arg1, nil, nil, displayer)
+}
+
+func (this *IniEntryDef) Fill(name string, defaultValue interface{}, modifiable bool,
+	onModify func(), displayer func()) {
+	this.Fill3(name, defaultValue, modifiable, onModify, nil, nil, nil, displayer)
+}
+
+func (this *IniEntryDef) SetModifier(modifier func(ie *IniEntry, newValue string, state int) int) {
+	this.onModify = modifier
+}
+
+func (this *IniEntryDef) SetDisplayer(displayer func(ie *IniEntry, itype int)) {
+	this.onDisplay = displayer
+}
+
+var iniNameEntries = make(map[string]*IniEntryDef, 0)
+
+// the new_value is really not *C.char, it's *C.zend_string
+//export gozend_ini_modifier7
+func gozend_ini_modifier7(ie *C.zend_ini_entry, new_value *C.zend_string, mh_arg1 unsafe.Pointer, mh_arg2 unsafe.Pointer, mh_arg3 unsafe.Pointer, stage C.int) C.int {
+	// log.Println(ie, "//", new_value, stage, ie.modifiable)
+	// log.Println(ie.orig_modifiable, ie.modified, fromZString(ie.orig_value))
+	// log.Println(fromZString(new_value), fromZString(ie.name), fromZString(ie.value))
+	if iedef, ok := iniNameEntries[fromZString(ie.name)]; ok {
+		iedef.onModify(newZendIniEntryFrom(ie), fromZString(new_value), int(stage))
+	} else {
+		log.Println("wtf", fromZString(ie.name))
+	}
+	return 0
+}
+
+//export gozend_ini_modifier5
+func gozend_ini_modifier5(ie *C.zend_ini_entry, new_value *C.char, new_value_length C.uint, mh_arg1 unsafe.Pointer, mh_arg2 unsafe.Pointer, mh_arg3 unsafe.Pointer, stage C.int) C.int {
+	// log.Println(ie, "//", new_value, new_value_length, stage, ie.modifiable)
+	// log.Println(ie.orig_modifiable, ie.modified, fromZString(ie.orig_value))
+	// log.Println(fromZString(new_value), fromZString(ie.name), fromZString(ie.value))
+	if iedef, ok := iniNameEntries[fromZString(ie.name)]; ok {
+		iedef.onModify(newZendIniEntryFrom(ie), fromZString(new_value), int(stage))
+	} else {
+		log.Println("wtf", fromZString(ie.name))
+	}
+	return 0
+}
+
+//export gozend_ini_displayer
+func gozend_ini_displayer(ie *C.zend_ini_entry, itype C.int) {
+	log.Println(ie, itype)
+	if iedef, ok := iniNameEntries[fromZString(ie.name)]; ok {
+		iedef.onDisplay(newZendIniEntryFrom(ie), int(itype))
+	} else {
+		log.Println("wtf", fromZString(ie.name))
+	}
+}

+ 72 - 0
zend/zend_string.go

@@ -0,0 +1,72 @@
+package zend
+
+/*
+#include <zend_API.h>
+#include <zend_string.h>
+
+#ifdef ZEND_ENGINE_3
+zend_string *zend_string_new_ownered(char *str, size_t len, int persistent) {
+	zend_string *ret = zend_string_alloc(len, persistent);
+
+	// memcpy(ZSTR_VAL(ret), str, len);
+    memcpy(&ZSTR_VAL(ret), &str, sizeof(&str));
+	ZSTR_VAL(ret)[len] = '\0';
+	return ret;
+}
+char *zend_string_to_char(void *zs) { return &((zend_string*)zs)->val[0];}
+#else
+char *zend_string_to_char(void *zs) { return (char*)zs;}
+#endif
+
+*/
+import "C"
+import "unsafe"
+import "reflect"
+import "log"
+
+// type ZString *C.zend_string
+
+/*
+func fromZString(s *C.zend_string) string {
+	r := C.GoStringN(&s.val[0], C.int(s.len))
+	return r
+}
+*/
+
+func fromZString(s interface{}) string {
+	if s == nil {
+		return ""
+	}
+
+	sv := reflect.ValueOf(s)
+	// log.Println(s, sv.Type().String(), sv.Type().Kind(), sv.Pointer())
+	if sv.Type().Kind() != reflect.Ptr {
+		log.Println("not supported type:", sv.Type().Kind())
+		return ""
+	}
+	if sv.IsNil() {
+		return ""
+	}
+
+	switch sv.Type().String() {
+	case "*zend._Ctype_struct__zend_string":
+		s_c := C.zend_string_to_char(unsafe.Pointer(sv.Pointer()))
+		s_go := C.GoString(s_c)
+		return s_go
+	case "*zend._Ctype_char":
+		s_go := C.GoString(s.(*C.char))
+		return s_go
+	}
+	return ""
+}
+
+/*
+func toZString(s string) *C.zend_string {
+	persistent := 0
+	s_ := C.CString(s)
+	// defer C.free(s_)
+	// r := C.zend_string_init(s_, C.size_t(len(s)), C.int(persistent))
+	r := C.zend_string_new_ownered(s_, C.size_t(len(s)), C.int(persistent))
+	return r
+}
+*/