diff --git a/Zend/zend.c b/Zend/zend.c index b4a084b1f95c7..b7e94dce51f93 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -98,6 +98,9 @@ ZEND_API void (*zend_accel_schedule_restart_hook)(int reason) = NULL; ZEND_ATTRIBUTE_NONNULL ZEND_API zend_result (*zend_random_bytes)(void *bytes, size_t size, char *errstr, size_t errstr_size) = NULL; ZEND_ATTRIBUTE_NONNULL ZEND_API void (*zend_random_bytes_insecure)(zend_random_bytes_insecure_state *state, void *bytes, size_t size) = NULL; +/* FFI/OPCache interopability API */ +ZEND_API struct _zend_ffi_api *zend_ffi_api = NULL; + /* This callback must be signal handler safe! */ void (*zend_on_timeout)(int seconds); diff --git a/Zend/zend.h b/Zend/zend.h index 0cf1faeb653fe..3efd723e86151 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -407,6 +407,9 @@ ZEND_API ZEND_COLD ZEND_NORETURN void zend_strerror_noreturn(int type, int errn, extern ZEND_API zend_class_entry *zend_standard_class_def; extern ZEND_API zend_utility_values zend_uv; +/* FFI/OPCache interopability API */ +extern ZEND_API struct _zend_ffi_api *zend_ffi_api; + /* If DTrace is available and enabled */ extern ZEND_API bool zend_dtrace_enabled; END_EXTERN_C() diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 8f012868ddab4..8ce99639e97da 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -818,6 +818,8 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define IS_STR_PERSISTENT GC_PERSISTENT /* allocated using malloc */ #define IS_STR_PERMANENT (1<<8) /* relives request boundary */ #define IS_STR_VALID_UTF8 (1<<9) /* valid UTF-8 according to PCRE */ +#define IS_STR_FFI_TYPE (1<<10) /* intrned string associated with FFI type */ +#define IS_STR_FFI_SCOPE (1<<11) /* intrned string associated with FFI declaration object */ /* array flags */ #define IS_ARRAY_IMMUTABLE GC_IMMUTABLE diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index 4e7253b9010e4..42e123a9bdfee 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -60,146 +60,14 @@ ZEND_DECLARE_MODULE_GLOBALS(ffi) -typedef enum _zend_ffi_tag_kind { - ZEND_FFI_TAG_ENUM, - ZEND_FFI_TAG_STRUCT, - ZEND_FFI_TAG_UNION -} zend_ffi_tag_kind; - static const char *zend_ffi_tag_kind_name[3] = {"enum", "struct", "union"}; - -typedef struct _zend_ffi_tag { - zend_ffi_tag_kind kind; - zend_ffi_type *type; -} zend_ffi_tag; - -typedef enum _zend_ffi_type_kind { - ZEND_FFI_TYPE_VOID, - ZEND_FFI_TYPE_FLOAT, - ZEND_FFI_TYPE_DOUBLE, -#ifdef HAVE_LONG_DOUBLE - ZEND_FFI_TYPE_LONGDOUBLE, -#endif - ZEND_FFI_TYPE_UINT8, - ZEND_FFI_TYPE_SINT8, - ZEND_FFI_TYPE_UINT16, - ZEND_FFI_TYPE_SINT16, - ZEND_FFI_TYPE_UINT32, - ZEND_FFI_TYPE_SINT32, - ZEND_FFI_TYPE_UINT64, - ZEND_FFI_TYPE_SINT64, - ZEND_FFI_TYPE_ENUM, - ZEND_FFI_TYPE_BOOL, - ZEND_FFI_TYPE_CHAR, - ZEND_FFI_TYPE_POINTER, - ZEND_FFI_TYPE_FUNC, - ZEND_FFI_TYPE_ARRAY, - ZEND_FFI_TYPE_STRUCT, -} zend_ffi_type_kind; - #include "ffi_arginfo.h" -typedef enum _zend_ffi_flags { - ZEND_FFI_FLAG_CONST = (1 << 0), - ZEND_FFI_FLAG_OWNED = (1 << 1), - ZEND_FFI_FLAG_PERSISTENT = (1 << 2), -} zend_ffi_flags; - -struct _zend_ffi_type { - zend_ffi_type_kind kind; - size_t size; - uint32_t align; - uint32_t attr; - union { - struct { - zend_string *tag_name; - zend_ffi_type_kind kind; - } enumeration; - struct { - zend_ffi_type *type; - zend_long length; - } array; - struct { - zend_ffi_type *type; - } pointer; - struct { - zend_string *tag_name; - HashTable fields; - } record; - struct { - zend_ffi_type *ret_type; - HashTable *args; - ffi_abi abi; - } func; - }; -}; - -typedef struct _zend_ffi_field { - size_t offset; - bool is_const; - bool is_nested; /* part of nested anonymous struct */ - uint8_t first_bit; - uint8_t bits; - zend_ffi_type *type; -} zend_ffi_field; - -typedef enum _zend_ffi_symbol_kind { - ZEND_FFI_SYM_TYPE, - ZEND_FFI_SYM_CONST, - ZEND_FFI_SYM_VAR, - ZEND_FFI_SYM_FUNC -} zend_ffi_symbol_kind; - -typedef struct _zend_ffi_symbol { - zend_ffi_symbol_kind kind; - bool is_const; - zend_ffi_type *type; - union { - void *addr; - int64_t value; - }; -} zend_ffi_symbol; - -typedef struct _zend_ffi_scope { - HashTable *symbols; - HashTable *tags; -} zend_ffi_scope; - -typedef struct _zend_ffi { - zend_object std; - DL_HANDLE lib; - HashTable *symbols; - HashTable *tags; - bool persistent; -} zend_ffi; - -#define ZEND_FFI_TYPE_OWNED (1<<0) - -#define ZEND_FFI_TYPE(t) \ - ((zend_ffi_type*)(((uintptr_t)(t)) & ~ZEND_FFI_TYPE_OWNED)) - -#define ZEND_FFI_TYPE_IS_OWNED(t) \ - (((uintptr_t)(t)) & ZEND_FFI_TYPE_OWNED) - -#define ZEND_FFI_TYPE_MAKE_OWNED(t) \ - ((zend_ffi_type*)(((uintptr_t)(t)) | ZEND_FFI_TYPE_OWNED)) - #define ZEND_FFI_SIZEOF_ARG \ MAX(FFI_SIZEOF_ARG, sizeof(double)) -typedef struct _zend_ffi_cdata { - zend_object std; - zend_ffi_type *type; - void *ptr; - void *ptr_holder; - zend_ffi_flags flags; -} zend_ffi_cdata; - -typedef struct _zend_ffi_ctype { - zend_object std; - zend_ffi_type *type; -} zend_ffi_ctype; +static struct _zend_ffi_api ffi_api; static zend_class_entry *zend_ffi_exception_ce; static zend_class_entry *zend_ffi_parser_exception_ce; @@ -2428,12 +2296,12 @@ static void zend_ffi_free_obj(zend_object *object) /* {{{ */ ffi->lib = NULL; } - if (ffi->symbols) { + if (ffi->symbols && !(GC_FLAGS(ffi->symbols) & IS_ARRAY_IMMUTABLE)) { zend_hash_destroy(ffi->symbols); efree(ffi->symbols); } - if (ffi->tags) { + if (ffi->tags && !(GC_FLAGS(ffi->tags) & IS_ARRAY_IMMUTABLE)) { zend_hash_destroy(ffi->tags); efree(ffi->tags); } @@ -3009,6 +2877,119 @@ static void *dlsym_loaded(char *symbol) # define DL_FETCH_SYMBOL(h, s) (h == NULL ? dlsym_loaded(s) : GetProcAddress(h, s)) #endif +static zend_ffi_cdata* zend_ffi_cdata_create(void *ptr, zend_ffi_type *type) /* {{{ */ +{ + zend_ffi_cdata *cdata = emalloc(sizeof(zend_ffi_cdata)); + + zend_ffi_object_init(&cdata->std, zend_ffi_cdata_ce); + cdata->std.handlers = + (type->kind < ZEND_FFI_TYPE_POINTER) ? + &zend_ffi_cdata_value_handlers : + &zend_ffi_cdata_handlers; + cdata->type = type; + cdata->flags = 0; + cdata->ptr = ptr; + return cdata; +} +/* }}} */ + +static zend_ffi_ctype* zend_ffi_ctype_create(zend_ffi_type *type) /* {{{ */ +{ + zend_ffi_ctype *ctype = emalloc(sizeof(zend_ffi_ctype)); + + zend_ffi_object_init(&ctype->std, zend_ffi_ctype_ce); + ctype->type = type; + return ctype; +} +/* }}} */ + +static void zend_ffi_type_print(FILE *f, const zend_ffi_type *type) /* {{{ */ +{ + zend_ffi_ctype_name_buf buf; + + buf.start = buf.end = buf.buf + ((MAX_TYPE_NAME_LEN * 3) / 4); + if (!zend_ffi_ctype_name(&buf, ZEND_FFI_TYPE(type))) { + } else { + fwrite(buf.start, buf.end - buf.start, 1, f); + } +} + +static bool zend_ffi_cache_type_get(zend_string *type_def, zend_ffi_dcl *dcl) /* {{{ */ +{ + if (ffi_api.cache_type_get) { + zend_ffi_dcl *cached_dcl = ffi_api.cache_type_get(type_def, FFI_G(symbols)); + + if (cached_dcl) { + memcpy(dcl, cached_dcl, sizeof(zend_ffi_dcl)); + return true; + } + } + return false; +} +/* }}} */ + +static void zend_ffi_cache_type_add(zend_string *type_def, zend_ffi_dcl *dcl) /* {{{ */ +{ + if (ffi_api.cache_type_add) { + zend_ffi_dcl *cached_dcl = ffi_api.cache_type_add(type_def, dcl, FFI_G(symbols)); + + if (cached_dcl) { + if (ZEND_FFI_TYPE_IS_OWNED(dcl->type)) { + _zend_ffi_type_dtor(dcl->type); + } + memcpy(dcl, cached_dcl, sizeof(zend_ffi_dcl)); + } + } +} +/* }}} */ + +static zend_ffi* zend_ffi_cache_scope_get(zend_string *code, DL_HANDLE handle) /* {{{ */ +{ + if (ffi_api.cache_scope_get) { + zend_ffi_scope *scope = ffi_api.cache_scope_get(code); + + if (scope) { + zend_ffi *ffi = (zend_ffi*)zend_ffi_new(zend_ffi_ce); + ffi->lib = handle; + ffi->symbols = scope->symbols; + ffi->tags = scope->tags; + ffi->persistent = true; + return ffi; + } + } + + return NULL; +} +/* }}} */ + +static bool zend_ffi_cache_scope_add(zend_string *code) /* {{{ */ +{ + if (ffi_api.cache_scope_add) { + zend_ffi_scope scope, *cached_scope; + + scope.symbols = FFI_G(symbols); + scope.tags = FFI_G(tags); + cached_scope = ffi_api.cache_scope_add(code, &scope); + if (cached_scope) { + if (FFI_G(symbols)) { + zend_hash_destroy(FFI_G(symbols)); + efree(FFI_G(symbols)); + FFI_G(symbols) = NULL; + } + if (FFI_G(tags)) { + zend_hash_destroy(FFI_G(tags)); + efree(FFI_G(tags)); + FFI_G(tags) = NULL; + } + FFI_G(symbols) = cached_scope->symbols; + FFI_G(tags) = cached_scope->tags; + return true; + } + } + return false; +} +/* }}} */ + ZEND_METHOD(FFI, cdef) /* {{{ */ { zend_string *code = NULL; @@ -3017,6 +2998,7 @@ ZEND_METHOD(FFI, cdef) /* {{{ */ DL_HANDLE handle = NULL; char *err; void *addr; + bool persistent = false; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(0, 2) @@ -3054,6 +3036,11 @@ ZEND_METHOD(FFI, cdef) /* {{{ */ FFI_G(tags) = NULL; if (code && ZSTR_LEN(code)) { + ffi = zend_ffi_cache_scope_get(code, handle); + if (ffi) { + RETURN_OBJ(&ffi->std); + } + /* Parse C definitions */ FFI_G(default_type_attr) = ZEND_FFI_ATTR_STORED; @@ -3096,12 +3083,15 @@ ZEND_METHOD(FFI, cdef) /* {{{ */ } } ZEND_HASH_FOREACH_END(); } + + persistent = zend_ffi_cache_scope_add(code); } ffi = (zend_ffi*)zend_ffi_new(zend_ffi_ce); ffi->lib = handle; ffi->symbols = FFI_G(symbols); ffi->tags = FFI_G(tags); + ffi->persistent = persistent; FFI_G(symbols) = NULL; FFI_G(tags) = NULL; @@ -3295,7 +3285,8 @@ static zend_ffi *zend_ffi_load(const char *filename, bool preload) /* {{{ */ { struct stat buf; int fd; - char *code, *code_pos, *scope_name, *lib, *err; + zend_string *code; + char *code_pos, *scope_name, *lib, *err; size_t code_size, scope_name_len; zend_ffi *ffi; DL_HANDLE handle = NULL; @@ -3304,6 +3295,7 @@ static zend_ffi *zend_ffi_load(const char *filename, bool preload) /* {{{ */ zend_ffi_symbol *sym; zend_ffi_tag *tag; void *addr; + bool persistent = false, old_persistent; if (stat(filename, &buf) != 0) { if (preload) { @@ -3324,24 +3316,33 @@ static zend_ffi *zend_ffi_load(const char *filename, bool preload) /* {{{ */ } code_size = buf.st_size; - code = emalloc(code_size + 1); + code = zend_string_alloc(code_size, 0); int open_flags = O_RDONLY; #ifdef PHP_WIN32 open_flags |= _O_BINARY; #endif fd = open(filename, open_flags, 0); - if (fd < 0 || read(fd, code, code_size) != code_size) { + if (fd < 0 || read(fd, ZSTR_VAL(code), code_size) != code_size) { if (preload) { zend_error(E_WARNING, "FFI: Failed pre-loading '%s', cannot read_file", filename); } else { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', cannot read_file", filename); } - efree(code); + zend_string_release(code); close(fd); return NULL; } close(fd); - code[code_size] = 0; + ZSTR_VAL(code)[code_size] = 0; + + if (!preload) { + ffi = zend_ffi_cache_scope_get(code, handle); + if (ffi) { + return ffi; + } + } + + old_persistent = FFI_G(persistent); FFI_G(symbols) = NULL; FFI_G(tags) = NULL; @@ -3353,13 +3354,13 @@ static zend_ffi *zend_ffi_load(const char *filename, bool preload) /* {{{ */ scope_name = NULL; scope_name_len = 0; lib = NULL; - code_pos = zend_ffi_parse_directives(filename, code, &scope_name, &lib, preload); + code_pos = zend_ffi_parse_directives(filename, ZSTR_VAL(code), &scope_name, &lib, preload); if (!code_pos) { - efree(code); - FFI_G(persistent) = 0; + zend_string_release(code); + FFI_G(persistent) = old_persistent; return NULL; } - code_size -= code_pos - code; + code_size -= code_pos - ZSTR_VAL(code); if (zend_ffi_parse_decl(code_pos, code_size) == FAILURE) { if (preload) { @@ -3542,21 +3543,24 @@ static zend_ffi *zend_ffi_load(const char *filename, bool preload) /* {{{ */ ffi->tags = scope->tags; ffi->persistent = 1; } else { + persistent = zend_ffi_cache_scope_add(code); + ffi = (zend_ffi*)zend_ffi_new(zend_ffi_ce); ffi->lib = handle; ffi->symbols = FFI_G(symbols); ffi->tags = FFI_G(tags); + ffi->persistent = persistent; } - efree(code); + zend_string_release(code); FFI_G(symbols) = NULL; FFI_G(tags) = NULL; - FFI_G(persistent) = 0; + FFI_G(persistent) = old_persistent; return ffi; cleanup: - efree(code); + zend_string_release(code); if (FFI_G(symbols)) { zend_hash_destroy(FFI_G(symbols)); pefree(FFI_G(symbols), preload); @@ -3846,7 +3850,9 @@ ZEND_METHOD(FFI, new) /* {{{ */ FFI_G(default_type_attr) = 0; - if (zend_ffi_parse_type(ZSTR_VAL(type_def), ZSTR_LEN(type_def), &dcl) == FAILURE) { + if (zend_ffi_cache_type_get(type_def, &dcl)) { + /* pass */ + } else if (zend_ffi_parse_type(ZSTR_VAL(type_def), ZSTR_LEN(type_def), &dcl) == FAILURE) { zend_ffi_type_dtor(dcl.type); if (clean_tags && FFI_G(tags)) { zend_hash_destroy(FFI_G(tags)); @@ -3859,6 +3865,20 @@ ZEND_METHOD(FFI, new) /* {{{ */ FFI_G(symbols) = NULL; } return; + } else { + if (clean_tags && FFI_G(tags)) { + zend_ffi_tags_cleanup(&dcl); + } + + zend_ffi_cache_type_add(type_def, &dcl); + + if (clean_symbols && FFI_G(symbols)) { + zend_hash_destroy(FFI_G(symbols)); + efree(FFI_G(symbols)); + FFI_G(symbols) = NULL; + } + FFI_G(symbols) = NULL; + FFI_G(tags) = NULL; } type = ZEND_FFI_TYPE(dcl.type); @@ -3866,17 +3886,6 @@ ZEND_METHOD(FFI, new) /* {{{ */ is_const = 1; } - if (clean_tags && FFI_G(tags)) { - zend_ffi_tags_cleanup(&dcl); - } - if (clean_symbols && FFI_G(symbols)) { - zend_hash_destroy(FFI_G(symbols)); - efree(FFI_G(symbols)); - FFI_G(symbols) = NULL; - } - FFI_G(symbols) = NULL; - FFI_G(tags) = NULL; - type_ptr = dcl.type; } else { zend_ffi_ctype *ctype = (zend_ffi_ctype*) type_obj; @@ -3919,22 +3928,12 @@ ZEND_METHOD(FFI, new) /* {{{ */ } /* }}} */ -ZEND_METHOD(FFI, free) /* {{{ */ +static bool zend_ffi_cdata_free(zend_ffi_cdata *cdata) /* {{{ */ { - zval *zv; - zend_ffi_cdata *cdata; - - ZEND_FFI_VALIDATE_API_RESTRICTION(); - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_OBJECT_OF_CLASS_EX(zv, zend_ffi_cdata_ce, 0, 1); - ZEND_PARSE_PARAMETERS_END(); - - cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); - if (ZEND_FFI_TYPE(cdata->type)->kind == ZEND_FFI_TYPE_POINTER) { if (!cdata->ptr) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); - RETURN_THROWS(); + return false; } if (cdata->ptr != (void*)&cdata->ptr_holder) { pefree(*(void**)cdata->ptr, cdata->flags & ZEND_FFI_FLAG_PERSISTENT); @@ -3949,6 +3948,25 @@ ZEND_METHOD(FFI, free) /* {{{ */ cdata->std.handlers = &zend_ffi_cdata_free_handlers; } else { zend_throw_error(zend_ffi_exception_ce, "free() non a C pointer"); + return false; + } + return true; +} +/* }}} */ + +ZEND_METHOD(FFI, free) /* {{{ */ +{ + zval *zv; + zend_ffi_cdata *cdata; + + ZEND_FFI_VALIDATE_API_RESTRICTION(); + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS_EX(zv, zend_ffi_cdata_ce, 0, 1); + ZEND_PARSE_PARAMETERS_END(); + + cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); + if (!zend_ffi_cdata_free(cdata)) { + RETURN_THROWS(); } } /* }}} */ @@ -3996,7 +4014,9 @@ ZEND_METHOD(FFI, cast) /* {{{ */ FFI_G(default_type_attr) = 0; - if (zend_ffi_parse_type(ZSTR_VAL(type_def), ZSTR_LEN(type_def), &dcl) == FAILURE) { + if (zend_ffi_cache_type_get(type_def, &dcl)) { + /* pass */ + } else if (zend_ffi_parse_type(ZSTR_VAL(type_def), ZSTR_LEN(type_def), &dcl) == FAILURE) { zend_ffi_type_dtor(dcl.type); if (clean_tags && FFI_G(tags)) { zend_hash_destroy(FFI_G(tags)); @@ -4009,6 +4029,20 @@ ZEND_METHOD(FFI, cast) /* {{{ */ FFI_G(symbols) = NULL; } return; + } else { + if (clean_tags && FFI_G(tags)) { + zend_ffi_tags_cleanup(&dcl); + } + + zend_ffi_cache_type_add(type_def, &dcl); + + if (clean_symbols && FFI_G(symbols)) { + zend_hash_destroy(FFI_G(symbols)); + efree(FFI_G(symbols)); + FFI_G(symbols) = NULL; + } + FFI_G(symbols) = NULL; + FFI_G(tags) = NULL; } type = ZEND_FFI_TYPE(dcl.type); @@ -4016,17 +4050,6 @@ ZEND_METHOD(FFI, cast) /* {{{ */ is_const = 1; } - if (clean_tags && FFI_G(tags)) { - zend_ffi_tags_cleanup(&dcl); - } - if (clean_symbols && FFI_G(symbols)) { - zend_hash_destroy(FFI_G(symbols)); - efree(FFI_G(symbols)); - FFI_G(symbols) = NULL; - } - FFI_G(symbols) = NULL; - FFI_G(tags) = NULL; - type_ptr = dcl.type; } else { zend_ffi_ctype *ctype = (zend_ffi_ctype*) ztype; @@ -4168,7 +4191,9 @@ ZEND_METHOD(FFI, type) /* {{{ */ FFI_G(default_type_attr) = 0; - if (zend_ffi_parse_type(ZSTR_VAL(type_def), ZSTR_LEN(type_def), &dcl) == FAILURE) { + if (zend_ffi_cache_type_get(type_def, &dcl)) { + /* pass */ + } else if (zend_ffi_parse_type(ZSTR_VAL(type_def), ZSTR_LEN(type_def), &dcl) == FAILURE) { zend_ffi_type_dtor(dcl.type); if (clean_tags && FFI_G(tags)) { zend_hash_destroy(FFI_G(tags)); @@ -4181,18 +4206,21 @@ ZEND_METHOD(FFI, type) /* {{{ */ FFI_G(symbols) = NULL; } return; - } + } else { + if (clean_tags && FFI_G(tags)) { + zend_ffi_tags_cleanup(&dcl); + } - if (clean_tags && FFI_G(tags)) { - zend_ffi_tags_cleanup(&dcl); - } - if (clean_symbols && FFI_G(symbols)) { - zend_hash_destroy(FFI_G(symbols)); - efree(FFI_G(symbols)); + zend_ffi_cache_type_add(type_def, &dcl); + + if (clean_symbols && FFI_G(symbols)) { + zend_hash_destroy(FFI_G(symbols)); + efree(FFI_G(symbols)); + FFI_G(symbols) = NULL; + } FFI_G(symbols) = NULL; + FFI_G(tags) = NULL; } - FFI_G(symbols) = NULL; - FFI_G(tags) = NULL; ctype = (zend_ffi_ctype*)zend_ffi_ctype_new(zend_ffi_ctype_ce); ctype->type = dcl.type; @@ -5593,6 +5621,20 @@ ZEND_MINIT_FUNCTION(ffi) zend_ffi_ctype_handlers.get_properties = zend_fake_get_properties; zend_ffi_ctype_handlers.get_gc = zend_fake_get_gc; + memset(&ffi_api, 0, sizeof(ffi_api)); + + ffi_api.scope_ce = zend_ffi_ce; + ffi_api.cdata_ce = zend_ffi_cdata_ce; + ffi_api.ctype_ce = zend_ffi_ctype_ce; + + ffi_api.cdata_create = zend_ffi_cdata_create; + ffi_api.ctype_create = zend_ffi_ctype_create; + ffi_api.cdata_free = zend_ffi_cdata_free; + ffi_api.type_print = zend_ffi_type_print; + ffi_api.is_compatible_type = zend_ffi_is_compatible_type; + + zend_ffi_api = &ffi_api; + if (FFI_G(preload)) { return zend_ffi_preload(FFI_G(preload)); } @@ -5632,25 +5674,25 @@ ZEND_MINFO_FUNCTION(ffi) } /* }}} */ -static const zend_ffi_type zend_ffi_type_void = {.kind=ZEND_FFI_TYPE_VOID, .size=1, .align=1}; -static const zend_ffi_type zend_ffi_type_char = {.kind=ZEND_FFI_TYPE_CHAR, .size=1, .align=_Alignof(char)}; -static const zend_ffi_type zend_ffi_type_bool = {.kind=ZEND_FFI_TYPE_BOOL, .size=1, .align=_Alignof(uint8_t)}; -static const zend_ffi_type zend_ffi_type_sint8 = {.kind=ZEND_FFI_TYPE_SINT8, .size=1, .align=_Alignof(int8_t)}; -static const zend_ffi_type zend_ffi_type_uint8 = {.kind=ZEND_FFI_TYPE_UINT8, .size=1, .align=_Alignof(uint8_t)}; -static const zend_ffi_type zend_ffi_type_sint16 = {.kind=ZEND_FFI_TYPE_SINT16, .size=2, .align=_Alignof(int16_t)}; -static const zend_ffi_type zend_ffi_type_uint16 = {.kind=ZEND_FFI_TYPE_UINT16, .size=2, .align=_Alignof(uint16_t)}; -static const zend_ffi_type zend_ffi_type_sint32 = {.kind=ZEND_FFI_TYPE_SINT32, .size=4, .align=_Alignof(int32_t)}; -static const zend_ffi_type zend_ffi_type_uint32 = {.kind=ZEND_FFI_TYPE_UINT32, .size=4, .align=_Alignof(uint32_t)}; -static const zend_ffi_type zend_ffi_type_sint64 = {.kind=ZEND_FFI_TYPE_SINT64, .size=8, .align=_Alignof(int64_t)}; -static const zend_ffi_type zend_ffi_type_uint64 = {.kind=ZEND_FFI_TYPE_UINT64, .size=8, .align=_Alignof(uint64_t)}; -static const zend_ffi_type zend_ffi_type_float = {.kind=ZEND_FFI_TYPE_FLOAT, .size=sizeof(float), .align=_Alignof(float)}; -static const zend_ffi_type zend_ffi_type_double = {.kind=ZEND_FFI_TYPE_DOUBLE, .size=sizeof(double), .align=_Alignof(double)}; +static const zend_ffi_type zend_ffi_type_void = {.kind=ZEND_FFI_TYPE_VOID, .size=1, .align=1, .attr=ZEND_FFI_ATTR_PERSISTENT}; +static const zend_ffi_type zend_ffi_type_char = {.kind=ZEND_FFI_TYPE_CHAR, .size=1, .align=_Alignof(char), .attr=ZEND_FFI_ATTR_PERSISTENT}; +static const zend_ffi_type zend_ffi_type_bool = {.kind=ZEND_FFI_TYPE_BOOL, .size=1, .align=_Alignof(uint8_t), .attr=ZEND_FFI_ATTR_PERSISTENT}; +static const zend_ffi_type zend_ffi_type_sint8 = {.kind=ZEND_FFI_TYPE_SINT8, .size=1, .align=_Alignof(int8_t), .attr=ZEND_FFI_ATTR_PERSISTENT}; +static const zend_ffi_type zend_ffi_type_uint8 = {.kind=ZEND_FFI_TYPE_UINT8, .size=1, .align=_Alignof(uint8_t), .attr=ZEND_FFI_ATTR_PERSISTENT}; +static const zend_ffi_type zend_ffi_type_sint16 = {.kind=ZEND_FFI_TYPE_SINT16, .size=2, .align=_Alignof(int16_t), .attr=ZEND_FFI_ATTR_PERSISTENT}; +static const zend_ffi_type zend_ffi_type_uint16 = {.kind=ZEND_FFI_TYPE_UINT16, .size=2, .align=_Alignof(uint16_t), .attr=ZEND_FFI_ATTR_PERSISTENT}; +static const zend_ffi_type zend_ffi_type_sint32 = {.kind=ZEND_FFI_TYPE_SINT32, .size=4, .align=_Alignof(int32_t), .attr=ZEND_FFI_ATTR_PERSISTENT}; +static const zend_ffi_type zend_ffi_type_uint32 = {.kind=ZEND_FFI_TYPE_UINT32, .size=4, .align=_Alignof(uint32_t), .attr=ZEND_FFI_ATTR_PERSISTENT}; +static const zend_ffi_type zend_ffi_type_sint64 = {.kind=ZEND_FFI_TYPE_SINT64, .size=8, .align=_Alignof(int64_t), .attr=ZEND_FFI_ATTR_PERSISTENT}; +static const zend_ffi_type zend_ffi_type_uint64 = {.kind=ZEND_FFI_TYPE_UINT64, .size=8, .align=_Alignof(uint64_t), .attr=ZEND_FFI_ATTR_PERSISTENT}; +static const zend_ffi_type zend_ffi_type_float = {.kind=ZEND_FFI_TYPE_FLOAT, .size=sizeof(float), .align=_Alignof(float), .attr=ZEND_FFI_ATTR_PERSISTENT}; +static const zend_ffi_type zend_ffi_type_double = {.kind=ZEND_FFI_TYPE_DOUBLE, .size=sizeof(double), .align=_Alignof(double), .attr=ZEND_FFI_ATTR_PERSISTENT}; #ifdef HAVE_LONG_DOUBLE -static const zend_ffi_type zend_ffi_type_long_double = {.kind=ZEND_FFI_TYPE_LONGDOUBLE, .size=sizeof(long double), .align=_Alignof(long double)}; +static const zend_ffi_type zend_ffi_type_long_double = {.kind=ZEND_FFI_TYPE_LONGDOUBLE, .size=sizeof(long double), .align=_Alignof(long double), .attr=ZEND_FFI_ATTR_PERSISTENT}; #endif -static const zend_ffi_type zend_ffi_type_ptr = {.kind=ZEND_FFI_TYPE_POINTER, .size=sizeof(void*), .align=_Alignof(void*), .pointer.type = (zend_ffi_type*)&zend_ffi_type_void}; +static const zend_ffi_type zend_ffi_type_ptr = {.kind=ZEND_FFI_TYPE_POINTER, .size=sizeof(void*), .align=_Alignof(void*), .attr=ZEND_FFI_ATTR_PERSISTENT, .pointer.type = (zend_ffi_type*)&zend_ffi_type_void}; static const struct { const char *name; @@ -6684,6 +6726,7 @@ void zend_ffi_declare(const char *name, size_t name_len, zend_ffi_dcl *dcl) /* { zend_ffi_type *type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent)); memcpy(type, ZEND_FFI_TYPE(dcl->type), sizeof(zend_ffi_type)); + type->attr &= ~ZEND_FFI_ATTR_PERSISTENT; type->attr |= FFI_G(default_type_attr); type->align = dcl->align; dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type); diff --git a/ext/ffi/php_ffi.h b/ext/ffi/php_ffi.h index 430b8a2e568f8..7c06fcba052ee 100644 --- a/ext/ffi/php_ffi.h +++ b/ext/ffi/php_ffi.h @@ -277,4 +277,159 @@ void zend_ffi_val_float_number(zend_ffi_val *val, const char *str, size_t str_le void zend_ffi_val_string(zend_ffi_val *val, const char *str, size_t str_len); void zend_ffi_val_character(zend_ffi_val *val, const char *str, size_t str_len); + +/* FFI internals exported for JIT */ + +typedef enum _zend_ffi_type_kind { + ZEND_FFI_TYPE_VOID, + ZEND_FFI_TYPE_FLOAT, + ZEND_FFI_TYPE_DOUBLE, +#ifdef HAVE_LONG_DOUBLE + ZEND_FFI_TYPE_LONGDOUBLE, +#endif + ZEND_FFI_TYPE_UINT8, + ZEND_FFI_TYPE_SINT8, + ZEND_FFI_TYPE_UINT16, + ZEND_FFI_TYPE_SINT16, + ZEND_FFI_TYPE_UINT32, + ZEND_FFI_TYPE_SINT32, + ZEND_FFI_TYPE_UINT64, + ZEND_FFI_TYPE_SINT64, + ZEND_FFI_TYPE_ENUM, + ZEND_FFI_TYPE_BOOL, + ZEND_FFI_TYPE_CHAR, + ZEND_FFI_TYPE_POINTER, + ZEND_FFI_TYPE_FUNC, + ZEND_FFI_TYPE_ARRAY, + ZEND_FFI_TYPE_STRUCT, +} zend_ffi_type_kind; + +typedef enum _zend_ffi_flags { + ZEND_FFI_FLAG_CONST = (1 << 0), + ZEND_FFI_FLAG_OWNED = (1 << 1), + ZEND_FFI_FLAG_PERSISTENT = (1 << 2), +} zend_ffi_flags; + +struct _zend_ffi_type { + zend_ffi_type_kind kind; + size_t size; + uint32_t align; + uint32_t attr; + union { + struct { + zend_string *tag_name; + zend_ffi_type_kind kind; + } enumeration; + struct { + zend_ffi_type *type; + zend_long length; + } array; + struct { + zend_ffi_type *type; + } pointer; + struct { + zend_string *tag_name; + HashTable fields; + } record; + struct { + zend_ffi_type *ret_type; + HashTable *args; + int abi; /*ffi_abi*/ + } func; + }; +}; + +typedef struct _zend_ffi_field { + size_t offset; + bool is_const; + bool is_nested; /* part of nested anonymous struct */ + uint8_t first_bit; + uint8_t bits; + zend_ffi_type *type; +} zend_ffi_field; + +typedef struct _zend_ffi_cdata { + zend_object std; + zend_ffi_type *type; + void *ptr; + void *ptr_holder; + zend_ffi_flags flags; +} zend_ffi_cdata; + +typedef struct _zend_ffi_ctype { + zend_object std; + zend_ffi_type *type; +} zend_ffi_ctype; + +typedef enum _zend_ffi_tag_kind { + ZEND_FFI_TAG_ENUM, + ZEND_FFI_TAG_STRUCT, + ZEND_FFI_TAG_UNION +} zend_ffi_tag_kind; + +typedef struct _zend_ffi_tag { + zend_ffi_tag_kind kind; + zend_ffi_type *type; +} zend_ffi_tag; + +typedef enum _zend_ffi_symbol_kind { + ZEND_FFI_SYM_TYPE, + ZEND_FFI_SYM_CONST, + ZEND_FFI_SYM_VAR, + ZEND_FFI_SYM_FUNC +} zend_ffi_symbol_kind; + +typedef struct _zend_ffi_symbol { + zend_ffi_symbol_kind kind; + bool is_const; + zend_ffi_type *type; + union { + void *addr; + int64_t value; + }; +} zend_ffi_symbol; + +typedef struct _zend_ffi_scope { + HashTable *symbols; + HashTable *tags; +} zend_ffi_scope; + +typedef struct _zend_ffi { + zend_object std; + DL_HANDLE lib; + HashTable *symbols; + HashTable *tags; + bool persistent; +} zend_ffi; + +#define ZEND_FFI_TYPE_OWNED (1<<0) + +#define ZEND_FFI_TYPE(t) \ + ((zend_ffi_type*)(((uintptr_t)(t)) & ~ZEND_FFI_TYPE_OWNED)) + +#define ZEND_FFI_TYPE_IS_OWNED(t) \ + (((uintptr_t)(t)) & ZEND_FFI_TYPE_OWNED) + +#define ZEND_FFI_TYPE_MAKE_OWNED(t) \ + ((zend_ffi_type*)(((uintptr_t)(t)) | ZEND_FFI_TYPE_OWNED)) + +struct _zend_ffi_api { + zend_class_entry *scope_ce; + zend_class_entry *cdata_ce; + zend_class_entry *ctype_ce; + + /* ext/ffi interface for ext/opcache */ + zend_ffi_cdata* (*cdata_create)(void *ptr, zend_ffi_type *type); + zend_ffi_ctype* (*ctype_create)(zend_ffi_type *type); + bool (*cdata_free)(zend_ffi_cdata *cdata); + void (*type_print)(FILE *f, const zend_ffi_type *type); + bool (*is_compatible_type)(zend_ffi_type *dst_type, zend_ffi_type *src_type); + + /* ext/opcache interface for ext/ffi */ + zend_ffi_dcl* (*cache_type_get)(zend_string *str, void *context); + zend_ffi_dcl* (*cache_type_add)(zend_string *str, zend_ffi_dcl *dcl, void *context); + zend_ffi_scope* (*cache_scope_get)(zend_string *str); + zend_ffi_scope* (*cache_scope_add)(zend_string *str, zend_ffi_scope *scope); +}; + #endif /* PHP_FFI_H */ diff --git a/ext/ffi/tests/jit/001_var_read_scalar.phpt b/ext/ffi/tests/jit/001_var_read_scalar.phpt new file mode 100644 index 0000000000000..c7b61f61b83b7 --- /dev/null +++ b/ext/ffi/tests/jit/001_var_read_scalar.phpt @@ -0,0 +1,37 @@ +--TEST-- +FFI/JIT 001: Read Variable (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + } + var_dump($x); +} +test(); +?> +--EXPECTF-- +int(%i) diff --git a/ext/ffi/tests/jit/002_var_read_array.phpt b/ext/ffi/tests/jit/002_var_read_array.phpt new file mode 100644 index 0000000000000..147d6a0606576 --- /dev/null +++ b/ext/ffi/tests/jit/002_var_read_array.phpt @@ -0,0 +1,54 @@ +--TEST-- +FFI/JIT 002: Read Variable (array) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + } + var_dump($x); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int8_t[8])#%d (8) { + [0]=> + int(%i) + [1]=> + int(%i) + [2]=> + int(%i) + [3]=> + int(%i) + [4]=> + int(%i) + [5]=> + int(%i) + [6]=> + int(%i) + [7]=> + int(%i) +} diff --git a/ext/ffi/tests/jit/003_var_read_struct.phpt b/ext/ffi/tests/jit/003_var_read_struct.phpt new file mode 100644 index 0000000000000..947b9fc074360 --- /dev/null +++ b/ext/ffi/tests/jit/003_var_read_struct.phpt @@ -0,0 +1,43 @@ +--TEST-- +FFI/JIT 003: Read Variable (struct) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + } + var_dump($x); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct _IO_FILE)#%d (2) { + ["a"]=> + int(%i) + ["b"]=> + int(%i) +} diff --git a/ext/ffi/tests/jit/004_var_read_struct_ptr.phpt b/ext/ffi/tests/jit/004_var_read_struct_ptr.phpt new file mode 100644 index 0000000000000..d96dff8b8bc4e --- /dev/null +++ b/ext/ffi/tests/jit/004_var_read_struct_ptr.phpt @@ -0,0 +1,42 @@ +--TEST-- +FFI/JIT 004: Read Variable (struct ptr) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + } + var_dump($x); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct _IO_FILE*)#%d (1) { + [0]=> + object(FFI\CData:struct _IO_FILE)#%d (0) { + } +} diff --git a/ext/ffi/tests/jit/005_var_read_func.phpt b/ext/ffi/tests/jit/005_var_read_func.phpt new file mode 100644 index 0000000000000..07a1f6a9d7433 --- /dev/null +++ b/ext/ffi/tests/jit/005_var_read_func.phpt @@ -0,0 +1,42 @@ +--TEST-- +FFI/JIT 005: Read Variable (func) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + } + var_dump($x); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int32_t(*)())#%d (1) { + [0]=> + object(FFI\CData:int32_t())#%d (0) { + } +} diff --git a/ext/ffi/tests/jit/006_var_write_scalar.phpt b/ext/ffi/tests/jit/006_var_write_scalar.phpt new file mode 100644 index 0000000000000..4cf3338b22c5f --- /dev/null +++ b/ext/ffi/tests/jit/006_var_write_scalar.phpt @@ -0,0 +1,39 @@ +--TEST-- +FFI/JIT 006: Write Variable (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + for ($i = 0; $i < 5; $i++) { + $ffi->stdout = $i; + } + $out = $ffi->stdout; + $ffi->stdout = $old; + var_dump($out); +} +test(); +?> +--EXPECT-- +int(4) diff --git a/ext/ffi/tests/jit/007_var_write_scalar_cdata.phpt b/ext/ffi/tests/jit/007_var_write_scalar_cdata.phpt new file mode 100644 index 0000000000000..b4cfe4a3d7e2d --- /dev/null +++ b/ext/ffi/tests/jit/007_var_write_scalar_cdata.phpt @@ -0,0 +1,45 @@ +--TEST-- +FFI/JIT 007: Write Variable (scalar cdata) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + $x = $ffi->cast('intptr_t', 42); + var_dump($x); + for ($i = 0; $i < 5; $i++) { + $ffi->stdout = $x; + } + $out = $ffi->stdout; + $ffi->stdout = $old; + var_dump($out); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int%d_t)#%d (1) { + ["cdata"]=> + int(42) +} +int(42) diff --git a/ext/ffi/tests/jit/008_var_write_struct_ptr_null.phpt b/ext/ffi/tests/jit/008_var_write_struct_ptr_null.phpt new file mode 100644 index 0000000000000..ba15c2abfab54 --- /dev/null +++ b/ext/ffi/tests/jit/008_var_write_struct_ptr_null.phpt @@ -0,0 +1,40 @@ +--TEST-- +FFI/JIT 008: Write Variable (struct ptr null) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + for ($i = 0; $i < 5; $i++) { + $ffi->stdout = null; + } + $out = $ffi->stdout; + $ffi->stdout = $old; + var_dump($out); +} +test(); +?> +--EXPECT-- +NULL diff --git a/ext/ffi/tests/jit/009_var_write_struct_ptr_cdata.phpt b/ext/ffi/tests/jit/009_var_write_struct_ptr_cdata.phpt new file mode 100644 index 0000000000000..89f4f85c9cde5 --- /dev/null +++ b/ext/ffi/tests/jit/009_var_write_struct_ptr_cdata.phpt @@ -0,0 +1,53 @@ +--TEST-- +FFI/JIT 009: Write Variable (struct ptr cdata) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + $x = $ffi->cast('FILE*', 42); + var_dump($x); + for ($i = 0; $i < 5; $i++) { + $ffi->stdout = $x; + } + $out1 = $ffi->stdout; + $out2 = $ffi->cast('intptr_t', $ffi->stdout)->cdata; + $ffi->stdout = $old; + var_dump($out1, $out2); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct _IO_FILE*)#%d (1) { + [0]=> + object(FFI\CData:struct _IO_FILE)#%d (0) { + } +} +object(FFI\CData:struct _IO_FILE*)#%d (1) { + [0]=> + object(FFI\CData:struct _IO_FILE)#%d (0) { + } +} +int(42) diff --git a/ext/ffi/tests/jit/010_var_modify_scalar.phpt b/ext/ffi/tests/jit/010_var_modify_scalar.phpt new file mode 100644 index 0000000000000..26802fb1ab068 --- /dev/null +++ b/ext/ffi/tests/jit/010_var_modify_scalar.phpt @@ -0,0 +1,40 @@ +--TEST-- +FFI/JIT 010: Modify Variable (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + $ffi->stdout = null; + for ($i = 0; $i < 5; $i++) { + $ffi->stdout += $i; + } + $out = $ffi->stdout; + $ffi->stdout = $old; + var_dump($out); +} +test(); +?> +--EXPECT-- +int(10) diff --git a/ext/ffi/tests/jit/011_var_modify_struct_ptr.phpt b/ext/ffi/tests/jit/011_var_modify_struct_ptr.phpt new file mode 100644 index 0000000000000..dc1cf85ea414e --- /dev/null +++ b/ext/ffi/tests/jit/011_var_modify_struct_ptr.phpt @@ -0,0 +1,41 @@ +--TEST-- +FFI/JIT 011: Modify Variable (struct ptr) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + $ffi->stdout = $ffi->cast('FILE*', 42); + for ($i = 0; $i < 5; $i++) { + $ffi->stdout += $i; + } + $out = $ffi->cast('intptr_t', $ffi->stdout)->cdata; + $ffi->stdout = $old; + var_dump($out); +} +test(); +?> +--EXPECT-- +int(82) diff --git a/ext/ffi/tests/jit/012_var_write_scalar_ret.phpt b/ext/ffi/tests/jit/012_var_write_scalar_ret.phpt new file mode 100644 index 0000000000000..923cd5b7b09fd --- /dev/null +++ b/ext/ffi/tests/jit/012_var_write_scalar_ret.phpt @@ -0,0 +1,40 @@ +--TEST-- +FFI/JIT 012: Write Variable (scalar + ret) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + for ($i = 0; $i < 5; $i++) { + $ret = $ffi->stdout = $i; + } + $out = $ffi->stdout; + $ffi->stdout = $old; + var_dump($out, $ret); +} +test(); +?> +--EXPECT-- +int(4) +int(4) diff --git a/ext/ffi/tests/jit/013_array_read_scalar.phpt b/ext/ffi/tests/jit/013_array_read_scalar.phpt new file mode 100644 index 0000000000000..560f509ebb8fc --- /dev/null +++ b/ext/ffi/tests/jit/013_array_read_scalar.phpt @@ -0,0 +1,31 @@ +--TEST-- +FFI/JIT 013: Read Array (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int32_t[5]"); + $a[0] = 0; + $a[1] = 2; + $a[2] = 4; + $a[3] = 6; + $a[4] = 8; + $x = 0; + for ($i = 0; $i < 5; $i++) { + $x += $a[$i]; + } + var_dump($x); +} +test(); +?> +--EXPECTF-- +int(20) diff --git a/ext/ffi/tests/jit/014_array_read_nested_array.phpt b/ext/ffi/tests/jit/014_array_read_nested_array.phpt new file mode 100644 index 0000000000000..9aea8b4004b1c --- /dev/null +++ b/ext/ffi/tests/jit/014_array_read_nested_array.phpt @@ -0,0 +1,31 @@ +--TEST-- +FFI/JIT 014: Read Array (nested array) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int32_t[5][2]"); + $a[0][1] = 0; + $a[1][1] = 2; + $a[2][1] = 4; + $a[3][1] = 6; + $a[4][1] = 8; + $x = 0; + for ($i = 0; $i < 5; $i++) { + $x += $a[$i][1]; + } + var_dump($x); +} +test(); +?> +--EXPECTF-- +int(20) diff --git a/ext/ffi/tests/jit/015_array_read_nested_struct.phpt b/ext/ffi/tests/jit/015_array_read_nested_struct.phpt new file mode 100644 index 0000000000000..89978b74726de --- /dev/null +++ b/ext/ffi/tests/jit/015_array_read_nested_struct.phpt @@ -0,0 +1,31 @@ +--TEST-- +FFI/JIT 015: Read Array (nested struct) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t a; int32_t b;}[5]"); + $a[0]->a = 0; + $a[1]->a = 2; + $a[2]->a = 4; + $a[3]->a = 6; + $a[4]->a = 8; + $x = 0; + for ($i = 0; $i < 5; $i++) { + $x += $a[$i]->a; + } + var_dump($x); +} +test(); +?> +--EXPECTF-- +int(20) diff --git a/ext/ffi/tests/jit/016_struct_read_scalar.phpt b/ext/ffi/tests/jit/016_struct_read_scalar.phpt new file mode 100644 index 0000000000000..fbe417dd79121 --- /dev/null +++ b/ext/ffi/tests/jit/016_struct_read_scalar.phpt @@ -0,0 +1,27 @@ +--TEST-- +FFI/JIT 016: Read Struct (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t a; int32_t b;}"); + $a->a = 12; + $x = 0; + for ($i = 0; $i < 5; $i++) { + $x += $a->a; + } + var_dump($x); +} +test(); +?> +--EXPECTF-- +int(60) diff --git a/ext/ffi/tests/jit/017_struct_read_nested_array.phpt b/ext/ffi/tests/jit/017_struct_read_nested_array.phpt new file mode 100644 index 0000000000000..ff1bbf9e57748 --- /dev/null +++ b/ext/ffi/tests/jit/017_struct_read_nested_array.phpt @@ -0,0 +1,31 @@ +--TEST-- +FFI/JIT 017: Read Struct (nested array) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t a[5]; int32_t b;}"); + $a->a[0] = 0; + $a->a[1] = 2; + $a->a[2] = 4; + $a->a[3] = 6; + $a->a[4] = 8; + $x = 0; + for ($i = 0; $i < 5; $i++) { + $x += $a->a[$i]; + } + var_dump($x); +} +test(); +?> +--EXPECTF-- +int(20) diff --git a/ext/ffi/tests/jit/018_array_write_scalar.phpt b/ext/ffi/tests/jit/018_array_write_scalar.phpt new file mode 100644 index 0000000000000..94af6e0bef7bd --- /dev/null +++ b/ext/ffi/tests/jit/018_array_write_scalar.phpt @@ -0,0 +1,37 @@ +--TEST-- +FFI/JIT 018: Array Write (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int32_t[5]"); + for ($i = 0; $i < 5; $i++) { + $a[$i] = $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int32_t[5])#%d (5) { + [0]=> + int(0) + [1]=> + int(1) + [2]=> + int(2) + [3]=> + int(3) + [4]=> + int(4) +} + diff --git a/ext/ffi/tests/jit/019_array_write_nested_array.phpt b/ext/ffi/tests/jit/019_array_write_nested_array.phpt new file mode 100644 index 0000000000000..d68b756fb565c --- /dev/null +++ b/ext/ffi/tests/jit/019_array_write_nested_array.phpt @@ -0,0 +1,61 @@ +--TEST-- +FFI/JIT 019: Array Write (nested array) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int32_t[5][2]"); + for ($i = 0; $i < 5; $i++) { + $a[$i][1] = $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int32_t[5][2])#%d (5) { + [0]=> + object(FFI\CData:int32_t[2])#%d (2) { + [0]=> + int(0) + [1]=> + int(0) + } + [1]=> + object(FFI\CData:int32_t[2])#%d (2) { + [0]=> + int(0) + [1]=> + int(1) + } + [2]=> + object(FFI\CData:int32_t[2])#%d (2) { + [0]=> + int(0) + [1]=> + int(2) + } + [3]=> + object(FFI\CData:int32_t[2])#%d (2) { + [0]=> + int(0) + [1]=> + int(3) + } + [4]=> + object(FFI\CData:int32_t[2])#%d (2) { + [0]=> + int(0) + [1]=> + int(4) + } +} diff --git a/ext/ffi/tests/jit/020_array_write_nested_struct.phpt b/ext/ffi/tests/jit/020_array_write_nested_struct.phpt new file mode 100644 index 0000000000000..4b4becc15cf24 --- /dev/null +++ b/ext/ffi/tests/jit/020_array_write_nested_struct.phpt @@ -0,0 +1,61 @@ +--TEST-- +FFI/JIT 020: Array Write (nested struct) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t a; int32_t b;}[5]"); + for ($i = 0; $i < 5; $i++) { + $a[$i]->a = $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct [5])#%d (5) { + [0]=> + object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(0) + ["b"]=> + int(0) + } + [1]=> + object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(0) + } + [2]=> + object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(2) + ["b"]=> + int(0) + } + [3]=> + object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(3) + ["b"]=> + int(0) + } + [4]=> + object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(4) + ["b"]=> + int(0) + } +} diff --git a/ext/ffi/tests/jit/021_struct_write_scalar.phpt b/ext/ffi/tests/jit/021_struct_write_scalar.phpt new file mode 100644 index 0000000000000..96912f2caabbd --- /dev/null +++ b/ext/ffi/tests/jit/021_struct_write_scalar.phpt @@ -0,0 +1,30 @@ +--TEST-- +FFI/JIT 021: Write Struct (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t a; int32_t b;}"); + for ($i = 0; $i < 5; $i++) { + $a->a = $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(4) + ["b"]=> + int(0) +} diff --git a/ext/ffi/tests/jit/022_struct_write_nested_array.phpt b/ext/ffi/tests/jit/022_struct_write_nested_array.phpt new file mode 100644 index 0000000000000..07e5b09393205 --- /dev/null +++ b/ext/ffi/tests/jit/022_struct_write_nested_array.phpt @@ -0,0 +1,41 @@ +--TEST-- +FFI/JIT 022: Write Struct (nested array) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t a[5]; int32_t b;}"); + for ($i = 0; $i < 5; $i++) { + $a->a[$i] = $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct )#%d (2) { + ["a"]=> + object(FFI\CData:int32_t[5])#%d (5) { + [0]=> + int(0) + [1]=> + int(1) + [2]=> + int(2) + [3]=> + int(3) + [4]=> + int(4) + } + ["b"]=> + int(0) +} diff --git a/ext/ffi/tests/jit/023_struct_write_nested_struct.phpt b/ext/ffi/tests/jit/023_struct_write_nested_struct.phpt new file mode 100644 index 0000000000000..7fea436e988f2 --- /dev/null +++ b/ext/ffi/tests/jit/023_struct_write_nested_struct.phpt @@ -0,0 +1,38 @@ +--TEST-- +FFI/JIT 022: Write Struct (nested array) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct { struct { struct {int32_t x; int32_t y} a; } a; int32_t b;}"); + for ($i = 0; $i < 5; $i++) { + $a->a->a->x = $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct )#2 (2) { + ["a"]=> + object(FFI\CData:struct )#3 (1) { + ["a"]=> + object(FFI\CData:struct )#1 (2) { + ["x"]=> + int(4) + ["y"]=> + int(0) + } + } + ["b"]=> + int(0) +} diff --git a/ext/ffi/tests/jit/024_array_modify_scalar.phpt b/ext/ffi/tests/jit/024_array_modify_scalar.phpt new file mode 100644 index 0000000000000..8cd48ce9ab923 --- /dev/null +++ b/ext/ffi/tests/jit/024_array_modify_scalar.phpt @@ -0,0 +1,37 @@ +--TEST-- +FFI/JIT 024: Array Modification (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int32_t[5]"); + for ($i = 0; $i < 5; $i++) { + $a[$i] += $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int32_t[5])#%d (5) { + [0]=> + int(0) + [1]=> + int(1) + [2]=> + int(2) + [3]=> + int(3) + [4]=> + int(4) +} + diff --git a/ext/ffi/tests/jit/025_array_modify_nested_array.phpt b/ext/ffi/tests/jit/025_array_modify_nested_array.phpt new file mode 100644 index 0000000000000..b0a4df12e3246 --- /dev/null +++ b/ext/ffi/tests/jit/025_array_modify_nested_array.phpt @@ -0,0 +1,61 @@ +--TEST-- +FFI/JIT 025: Array Modification (nested array) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int32_t[5][2]"); + for ($i = 0; $i < 5; $i++) { + $a[$i][1] += $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int32_t[5][2])#%d (5) { + [0]=> + object(FFI\CData:int32_t[2])#%d (2) { + [0]=> + int(0) + [1]=> + int(0) + } + [1]=> + object(FFI\CData:int32_t[2])#%d (2) { + [0]=> + int(0) + [1]=> + int(1) + } + [2]=> + object(FFI\CData:int32_t[2])#%d (2) { + [0]=> + int(0) + [1]=> + int(2) + } + [3]=> + object(FFI\CData:int32_t[2])#%d (2) { + [0]=> + int(0) + [1]=> + int(3) + } + [4]=> + object(FFI\CData:int32_t[2])#%d (2) { + [0]=> + int(0) + [1]=> + int(4) + } +} diff --git a/ext/ffi/tests/jit/026_array_modify_nested_struct.phpt b/ext/ffi/tests/jit/026_array_modify_nested_struct.phpt new file mode 100644 index 0000000000000..ebee45e98c7bd --- /dev/null +++ b/ext/ffi/tests/jit/026_array_modify_nested_struct.phpt @@ -0,0 +1,61 @@ +--TEST-- +FFI/JIT 026: Array Modification (nested struct) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t a; int32_t b;}[5]"); + for ($i = 0; $i < 5; $i++) { + $a[$i]->a += $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct [5])#%d (5) { + [0]=> + object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(0) + ["b"]=> + int(0) + } + [1]=> + object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(0) + } + [2]=> + object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(2) + ["b"]=> + int(0) + } + [3]=> + object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(3) + ["b"]=> + int(0) + } + [4]=> + object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(4) + ["b"]=> + int(0) + } +} diff --git a/ext/ffi/tests/jit/027_struct_modify_scalar.phpt b/ext/ffi/tests/jit/027_struct_modify_scalar.phpt new file mode 100644 index 0000000000000..191e0cbee934d --- /dev/null +++ b/ext/ffi/tests/jit/027_struct_modify_scalar.phpt @@ -0,0 +1,30 @@ +--TEST-- +FFI/JIT 027: Modify Struct (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t a; int32_t b;}"); + for ($i = 0; $i < 5; $i++) { + $a->a += $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(10) + ["b"]=> + int(0) +} diff --git a/ext/ffi/tests/jit/028_struct_modify_nested_array.phpt b/ext/ffi/tests/jit/028_struct_modify_nested_array.phpt new file mode 100644 index 0000000000000..8675589a7a570 --- /dev/null +++ b/ext/ffi/tests/jit/028_struct_modify_nested_array.phpt @@ -0,0 +1,41 @@ +--TEST-- +FFI/JIT 028: Modify Struct (nested array) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t a[5]; int32_t b;}"); + for ($i = 0; $i < 5; $i++) { + $a->a[$i] += $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct )#%d (2) { + ["a"]=> + object(FFI\CData:int32_t[5])#%d (5) { + [0]=> + int(0) + [1]=> + int(1) + [2]=> + int(2) + [3]=> + int(3) + [4]=> + int(4) + } + ["b"]=> + int(0) +} diff --git a/ext/ffi/tests/jit/029_struct_modify_nested_struct.phpt b/ext/ffi/tests/jit/029_struct_modify_nested_struct.phpt new file mode 100644 index 0000000000000..16d15f3e95f2f --- /dev/null +++ b/ext/ffi/tests/jit/029_struct_modify_nested_struct.phpt @@ -0,0 +1,38 @@ +--TEST-- +FFI/JIT 029: Modify Struct (nested array) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct { struct { struct {int32_t x; int32_t y} a; } a; int32_t b;}"); + for ($i = 0; $i < 5; $i++) { + $a->a->a->x += $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct )#2 (2) { + ["a"]=> + object(FFI\CData:struct )#3 (1) { + ["a"]=> + object(FFI\CData:struct )#1 (2) { + ["x"]=> + int(10) + ["y"]=> + int(0) + } + } + ["b"]=> + int(0) +} diff --git a/ext/ffi/tests/jit/030_var_inc_scalar.phpt b/ext/ffi/tests/jit/030_var_inc_scalar.phpt new file mode 100644 index 0000000000000..c715108cf338f --- /dev/null +++ b/ext/ffi/tests/jit/030_var_inc_scalar.phpt @@ -0,0 +1,40 @@ +--TEST-- +FFI/JIT 030: Increment Variable (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + $ffi->stdout = null; + for ($i = 0; $i < 5; $i++) { + $ffi->stdout++; + } + $out = $ffi->stdout; + $ffi->stdout = $old; + var_dump($out); +} +test(); +?> +--EXPECT-- +int(5) diff --git a/ext/ffi/tests/jit/031_var_dec_ptr.phpt b/ext/ffi/tests/jit/031_var_dec_ptr.phpt new file mode 100644 index 0000000000000..bdd075e187241 --- /dev/null +++ b/ext/ffi/tests/jit/031_var_dec_ptr.phpt @@ -0,0 +1,42 @@ +--TEST-- +FFI/JIT 031: Decrement Variable (ptr) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout; + $ffi->stdout = $old; + $ffi->stdout = $ffi->cast('FILE*', 42); + for ($i = 0; $i < 5; $i++) { + $ffi->stdout--; + } + $out = $ffi->cast('intptr_t', $ffi->stdout)->cdata; + $ffi->stdout = $old; + var_dump($out); +} +test(); +?> +--EXPECT-- +int(22) diff --git a/ext/ffi/tests/jit/032_struct_preinc_scalar.phpt b/ext/ffi/tests/jit/032_struct_preinc_scalar.phpt new file mode 100644 index 0000000000000..a831fc8ea1564 --- /dev/null +++ b/ext/ffi/tests/jit/032_struct_preinc_scalar.phpt @@ -0,0 +1,31 @@ +--TEST-- +FFI/JIT 032: PREINC Struct (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t a; int32_t b;}"); + for ($i = 0; $i < 5; $i++) { + $x = ++$a->a; + } + var_dump($a, $x); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(5) + ["b"]=> + int(0) +} +int(5) diff --git a/ext/ffi/tests/jit/033_struct_postdec_scalar.phpt b/ext/ffi/tests/jit/033_struct_postdec_scalar.phpt new file mode 100644 index 0000000000000..073143d1fd9d0 --- /dev/null +++ b/ext/ffi/tests/jit/033_struct_postdec_scalar.phpt @@ -0,0 +1,32 @@ +--TEST-- +FFI/JIT 033: POSTDEC Struct (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t a; int32_t b;}"); + $a->a = 42; + for ($i = 0; $i < 5; $i++) { + $x = $a->a--; + } + var_dump($a, $x); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct )#%d (2) { + ["a"]=> + int(37) + ["b"]=> + int(0) +} +int(38) diff --git a/ext/ffi/tests/jit/034_struct_postinc_ptr.phpt b/ext/ffi/tests/jit/034_struct_postinc_ptr.phpt new file mode 100644 index 0000000000000..5ef5f402efc8e --- /dev/null +++ b/ext/ffi/tests/jit/034_struct_postinc_ptr.phpt @@ -0,0 +1,34 @@ +--TEST-- +FFI/JIT 034: POSTINC Struct (ptr) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t *a; int32_t b;}"); + $a->a = $ffi->cast('int32_t*', 42); + for ($i = 0; $i < 5; $i++) { + $x = $a->a++; + } + var_dump($ffi->cast('intptr_t', $a->a), $ffi->cast('intptr_t', $x)); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int%d_t)#%d (1) { + ["cdata"]=> + int(62) +} +object(FFI\CData:int%d_t)#%d (1) { + ["cdata"]=> + int(58) +} diff --git a/ext/ffi/tests/jit/035_struct_predec_ptr.phpt b/ext/ffi/tests/jit/035_struct_predec_ptr.phpt new file mode 100644 index 0000000000000..6e756d4ba8ee4 --- /dev/null +++ b/ext/ffi/tests/jit/035_struct_predec_ptr.phpt @@ -0,0 +1,34 @@ +--TEST-- +FFI/JIT 035: PREDEC Struct (ptr) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t *a; int32_t b;}"); + $a->a = $ffi->cast('int32_t*', 42); + for ($i = 0; $i < 5; $i++) { + $x = --$a->a; + } + var_dump($ffi->cast('intptr_t', $a->a), $ffi->cast('intptr_t', $x)); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int%d_t)#%d (1) { + ["cdata"]=> + int(22) +} +object(FFI\CData:int%d_t)#%d (1) { + ["cdata"]=> + int(22) +} diff --git a/ext/ffi/tests/jit/036_scalar_cdata_read.phpt b/ext/ffi/tests/jit/036_scalar_cdata_read.phpt new file mode 100644 index 0000000000000..59659d5219b2a --- /dev/null +++ b/ext/ffi/tests/jit/036_scalar_cdata_read.phpt @@ -0,0 +1,27 @@ +--TEST-- +FFI/JIT 036: Scalar CData Read +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int"); + $a->cdata = 42; + $x = 0; + for ($i = 0; $i < 5; $i++) { + $x += $a->cdata; + } + var_dump($x); +} +test(); +?> +--EXPECT-- +int(210) diff --git a/ext/ffi/tests/jit/037_scalar_cdata_write.phpt b/ext/ffi/tests/jit/037_scalar_cdata_write.phpt new file mode 100644 index 0000000000000..55ba8a7b7052f --- /dev/null +++ b/ext/ffi/tests/jit/037_scalar_cdata_write.phpt @@ -0,0 +1,29 @@ +--TEST-- +FFI/JIT 037: Scalar CData Write +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int"); + for ($i = 0; $i < 5; $i++) { + $ret = $x->cdata = 42; + } + var_dump($ret, $x); +} +test(); +?> +--EXPECTF-- +int(42) +object(FFI\CData:int32_t)#%d (1) { + ["cdata"]=> + int(42) +} diff --git a/ext/ffi/tests/jit/038_scalar_cdata_modify.phpt b/ext/ffi/tests/jit/038_scalar_cdata_modify.phpt new file mode 100644 index 0000000000000..c9e069ab4d415 --- /dev/null +++ b/ext/ffi/tests/jit/038_scalar_cdata_modify.phpt @@ -0,0 +1,28 @@ +--TEST-- +FFI/JIT 038: Scalar CData Modification +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int"); + for ($i = 0; $i < 5; $i++) { + $x->cdata += 42; + } + var_dump($x); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int32_t)#%d (1) { + ["cdata"]=> + int(210) +} diff --git a/ext/ffi/tests/jit/039_scalar_cdata_postinc.phpt b/ext/ffi/tests/jit/039_scalar_cdata_postinc.phpt new file mode 100644 index 0000000000000..92a4279bbbb15 --- /dev/null +++ b/ext/ffi/tests/jit/039_scalar_cdata_postinc.phpt @@ -0,0 +1,29 @@ +--TEST-- +FFI/JIT 039: Scalar CData post increment +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int"); + for ($i = 0; $i < 5; $i++) { + $ret = $x->cdata++; + } + var_dump($ret, $x); +} +test(); +?> +--EXPECTF-- +int(4) +object(FFI\CData:int32_t)#%d (1) { + ["cdata"]=> + int(5) +} diff --git a/ext/ffi/tests/jit/040_call.phpt b/ext/ffi/tests/jit/040_call.phpt new file mode 100644 index 0000000000000..461e3feede8b7 --- /dev/null +++ b/ext/ffi/tests/jit/040_call.phpt @@ -0,0 +1,38 @@ +--TEST-- +FFI/JIT 040: Function call +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +printf("Hello %s\n", $name); + } +} +test("FFI"); +?> +--EXPECT-- +Hello FFI +Hello FFI +Hello FFI +Hello FFI +Hello FFI diff --git a/ext/ffi/tests/jit/041_call_tmp_scalar.phpt b/ext/ffi/tests/jit/041_call_tmp_scalar.phpt new file mode 100644 index 0000000000000..49b41e3855284 --- /dev/null +++ b/ext/ffi/tests/jit/041_call_tmp_scalar.phpt @@ -0,0 +1,39 @@ +--TEST-- +FFI/JIT 041: Function call (tmp scalar arg) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +fprintf($ffi->stdout, "Hello %s\n", $name); + } +} +test("FFI"); +?> +--EXPECT-- +Hello FFI +Hello FFI +Hello FFI +Hello FFI +Hello FFI diff --git a/ext/ffi/tests/jit/042_call_tmp_cdata.phpt b/ext/ffi/tests/jit/042_call_tmp_cdata.phpt new file mode 100644 index 0000000000000..b9771b468a536 --- /dev/null +++ b/ext/ffi/tests/jit/042_call_tmp_cdata.phpt @@ -0,0 +1,40 @@ +--TEST-- +FFI/JIT 042: Function call (tmp CData arg) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +fprintf($ffi->stdout, "Hello %s\n", $name); + } +} +test("FFI"); +?> +--EXPECT-- +Hello FFI +Hello FFI +Hello FFI +Hello FFI +Hello FFI diff --git a/ext/ffi/tests/jit/043_call_callback.phpt b/ext/ffi/tests/jit/043_call_callback.phpt new file mode 100644 index 0000000000000..55380a24cf206 --- /dev/null +++ b/ext/ffi/tests/jit/043_call_callback.phpt @@ -0,0 +1,43 @@ +--TEST-- +FFI/JIT 040: Function call +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +new("T"); + $x->f = $ffi->printf; + for ($i = 0; $i < 5; $i++) { + ($x->f)("Hello %s\n", $name); + } +} +test("FFI"); +?> +--EXPECT-- +Hello FFI +Hello FFI +Hello FFI +Hello FFI +Hello FFI diff --git a/ext/ffi/tests/jit/044_struct_write_nested_array2.phpt b/ext/ffi/tests/jit/044_struct_write_nested_array2.phpt new file mode 100644 index 0000000000000..be65d7f19f462 --- /dev/null +++ b/ext/ffi/tests/jit/044_struct_write_nested_array2.phpt @@ -0,0 +1,96 @@ +--TEST-- +FFI/JIT 044: Write Struct (nested array - FETCH_DIM_W/VAR) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("struct {int32_t a[5][5]; int32_t b;}"); + for ($i = 0; $i < 5; $i++) { + $a->a[$i][$i] = $i; + } + var_dump($a); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:struct )#%d (2) { + ["a"]=> + object(FFI\CData:int32_t[5][5])#%d (5) { + [0]=> + object(FFI\CData:int32_t[5])#%d (5) { + [0]=> + int(0) + [1]=> + int(0) + [2]=> + int(0) + [3]=> + int(0) + [4]=> + int(0) + } + [1]=> + object(FFI\CData:int32_t[5])#%d (5) { + [0]=> + int(0) + [1]=> + int(1) + [2]=> + int(0) + [3]=> + int(0) + [4]=> + int(0) + } + [2]=> + object(FFI\CData:int32_t[5])#%d (5) { + [0]=> + int(0) + [1]=> + int(0) + [2]=> + int(2) + [3]=> + int(0) + [4]=> + int(0) + } + [3]=> + object(FFI\CData:int32_t[5])#%d (5) { + [0]=> + int(0) + [1]=> + int(0) + [2]=> + int(0) + [3]=> + int(3) + [4]=> + int(0) + } + [4]=> + object(FFI\CData:int32_t[5])#%d (5) { + [0]=> + int(0) + [1]=> + int(0) + [2]=> + int(0) + [3]=> + int(0) + [4]=> + int(4) + } + } + ["b"]=> + int(0) +} diff --git a/ext/ffi/tests/jit/050_isNull.phpt b/ext/ffi/tests/jit/050_isNull.phpt new file mode 100644 index 0000000000000..e5995cf44f234 --- /dev/null +++ b/ext/ffi/tests/jit/050_isNull.phpt @@ -0,0 +1,36 @@ +--TEST-- +FFI/JIT 050: FFI::isNull() +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +stdout); + } + var_dump($ret); +} +test(); +?> +--EXPECT-- +bool(false) diff --git a/ext/ffi/tests/jit/051_string.phpt b/ext/ffi/tests/jit/051_string.phpt new file mode 100644 index 0000000000000..bad524688748b --- /dev/null +++ b/ext/ffi/tests/jit/051_string.phpt @@ -0,0 +1,28 @@ +--TEST-- +FFI/JIT 051: FFI::string() +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("char[10]"); + $x[0] = "X"; + for ($i = 0; $i < 5; $i++) { + $ret = FFI::string($x, 1); + } + var_dump($ret); +} +test(); +?> +--EXPECT-- +string(1) "X" + diff --git a/ext/ffi/tests/jit/052_sizeof.phpt b/ext/ffi/tests/jit/052_sizeof.phpt new file mode 100644 index 0000000000000..52a3c42131abe --- /dev/null +++ b/ext/ffi/tests/jit/052_sizeof.phpt @@ -0,0 +1,27 @@ +--TEST-- +FFI/JIT 052: FFI::sizeof() +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int[10]"); + $x[0] = 42; + for ($i = 0; $i < 5; $i++) { + $ret = FFI::sizeof($x); + } + var_dump($ret); +} +test(); +?> +--EXPECT-- +int(40) diff --git a/ext/ffi/tests/jit/053_alignof.phpt b/ext/ffi/tests/jit/053_alignof.phpt new file mode 100644 index 0000000000000..5797124d38eb9 --- /dev/null +++ b/ext/ffi/tests/jit/053_alignof.phpt @@ -0,0 +1,27 @@ +--TEST-- +FFI/JIT 053: FFI::alignof() +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int[10]"); + $x[0] = 42; + for ($i = 0; $i < 5; $i++) { + $ret = FFI::alignof($x); + } + var_dump($ret); +} +test(); +?> +--EXPECT-- +int(4) diff --git a/ext/ffi/tests/jit/055_memset.phpt b/ext/ffi/tests/jit/055_memset.phpt new file mode 100644 index 0000000000000..7258a9a677d63 --- /dev/null +++ b/ext/ffi/tests/jit/055_memset.phpt @@ -0,0 +1,26 @@ +--TEST-- +FFI/JIT 055: FFI::memset() +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("char[10]"); + for ($i = 0; $i < 5; $i++) { + FFI::memset($x, ord("a"), 9); + } + var_dump(FFI::string($x)); +} +test(); +?> +--EXPECT-- +string(9) "aaaaaaaaa" diff --git a/ext/ffi/tests/jit/056_memcpy_string.phpt b/ext/ffi/tests/jit/056_memcpy_string.phpt new file mode 100644 index 0000000000000..61bbf22da84f7 --- /dev/null +++ b/ext/ffi/tests/jit/056_memcpy_string.phpt @@ -0,0 +1,26 @@ +--TEST-- +FFI/JIT 056: FFI::memcpy() (string) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("char[10]"); + for ($i = 0; $i < 5; $i++) { + FFI::memcpy($x, "12345678", 8); + } + var_dump(FFI::string($x)); +} +test(); +?> +--EXPECT-- +string(8) "12345678" diff --git a/ext/ffi/tests/jit/057_memcpy_cdata.phpt b/ext/ffi/tests/jit/057_memcpy_cdata.phpt new file mode 100644 index 0000000000000..192987aa09c79 --- /dev/null +++ b/ext/ffi/tests/jit/057_memcpy_cdata.phpt @@ -0,0 +1,29 @@ +--TEST-- +FFI/JIT 057: FFI::memcpy() (CData) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("char[10]"); + $y = $ffi->new("char[10]"); + FFI::memcpy($y, "123456789", 9); + for ($i = 0; $i < 5; $i++) { + FFI::memcpy($x, $y, 8); + } + var_dump(FFI::string($x), FFI::string($y)); +} +test(); +?> +--EXPECT-- +string(8) "12345678" +string(9) "123456789" diff --git a/ext/ffi/tests/jit/058_memcmp_cdata_string.phpt b/ext/ffi/tests/jit/058_memcmp_cdata_string.phpt new file mode 100644 index 0000000000000..5d17fd286446a --- /dev/null +++ b/ext/ffi/tests/jit/058_memcmp_cdata_string.phpt @@ -0,0 +1,27 @@ +--TEST-- +FFI/JIT 058: FFI::memcmp() (CData + string) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("char[10]"); + FFI::memcpy($x, "123456789", 9); + for ($i = 0; $i < 5; $i++) { + $ret = FFI::memcmp($x, "1234", 4); + } + var_dump($ret); +} +test(); +?> +--EXPECT-- +int(0) diff --git a/ext/ffi/tests/jit/059_memcmp_cdata_string.phpt b/ext/ffi/tests/jit/059_memcmp_cdata_string.phpt new file mode 100644 index 0000000000000..45e152822b9c9 --- /dev/null +++ b/ext/ffi/tests/jit/059_memcmp_cdata_string.phpt @@ -0,0 +1,27 @@ +--TEST-- +FFI/JIT 059: FFI::memcmp() (string + CData) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("char[10]"); + FFI::memcpy($x, "123456789", 9); + for ($i = 0; $i < 5; $i++) { + $ret = FFI::memcmp("124", $x, 3); + } + var_dump($ret); +} +test(); +?> +--EXPECT-- +int(1) diff --git a/ext/ffi/tests/jit/060_memcmp_cdata_cdata.phpt b/ext/ffi/tests/jit/060_memcmp_cdata_cdata.phpt new file mode 100644 index 0000000000000..edfbc55eb602b --- /dev/null +++ b/ext/ffi/tests/jit/060_memcmp_cdata_cdata.phpt @@ -0,0 +1,29 @@ +--TEST-- +FFI/JIT 060: FFI::memcmp() (CData + CData) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("char[10]"); + $y = $ffi->new("char[10]"); + FFI::memcpy($x, "123456789", 9); + FFI::memcpy($y, "124456789", 9); + for ($i = 0; $i < 5; $i++) { + $ret = FFI::memcmp($x, $y, 9); + } + var_dump($ret); +} +test(); +?> +--EXPECT-- +int(-1) diff --git a/ext/ffi/tests/jit/061_new_string.phpt b/ext/ffi/tests/jit/061_new_string.phpt new file mode 100644 index 0000000000000..f0584da7c152a --- /dev/null +++ b/ext/ffi/tests/jit/061_new_string.phpt @@ -0,0 +1,34 @@ +--TEST-- +FFI/JIT 061: FFI::new() (string) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int[4]"); + } + var_dump($ret); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int32_t[4])#%d (4) { + [0]=> + int(0) + [1]=> + int(0) + [2]=> + int(0) + [3]=> + int(0) +} diff --git a/ext/ffi/tests/jit/062_new_ctype.phpt b/ext/ffi/tests/jit/062_new_ctype.phpt new file mode 100644 index 0000000000000..ae87d42ab5840 --- /dev/null +++ b/ext/ffi/tests/jit/062_new_ctype.phpt @@ -0,0 +1,35 @@ +--TEST-- +FFI/JIT 062: FFI::new() (CType) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +type("int[4]"); + for ($i = 0; $i < 5; $i++) { + $ret = $ffi->new($type); + } + var_dump($ret); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int32_t[4])#%d (4) { + [0]=> + int(0) + [1]=> + int(0) + [2]=> + int(0) + [3]=> + int(0) +} diff --git a/ext/ffi/tests/jit/063_free.phpt b/ext/ffi/tests/jit/063_free.phpt new file mode 100644 index 0000000000000..77c756f0e2b0c --- /dev/null +++ b/ext/ffi/tests/jit/063_free.phpt @@ -0,0 +1,26 @@ +--TEST-- +FFI/JIT 063: FFI::free() +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("int", false); + FFI::free($ret); + } +} +test(); +?> +DONE +--EXPECT-- +DONE diff --git a/ext/ffi/tests/jit/064_cast_string.phpt b/ext/ffi/tests/jit/064_cast_string.phpt new file mode 100644 index 0000000000000..20b1f4ddab468 --- /dev/null +++ b/ext/ffi/tests/jit/064_cast_string.phpt @@ -0,0 +1,36 @@ +--TEST-- +FFI/JIT 064: FFI::cast() (string) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("unsigned int[4]"); + $x[1] = 42; + for ($i = 0; $i < 5; $i++) { + $ret = $ffi->cast("int[4]", $x); + } + var_dump($ret); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int32_t[4])#%d (4) { + [0]=> + int(0) + [1]=> + int(42) + [2]=> + int(0) + [3]=> + int(0) +} diff --git a/ext/ffi/tests/jit/065_cast_ctype.phpt b/ext/ffi/tests/jit/065_cast_ctype.phpt new file mode 100644 index 0000000000000..611cdcc0f89a6 --- /dev/null +++ b/ext/ffi/tests/jit/065_cast_ctype.phpt @@ -0,0 +1,37 @@ +--TEST-- +FFI/JIT 065: FFI::cast() (CType) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +new("unsigned int[4]"); + $x[1] = 42; + $type = $ffi->type("int[4]"); + for ($i = 0; $i < 5; $i++) { + $ret = $ffi->cast($type, $x); + } + var_dump($ret); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int32_t[4])#%d (4) { + [0]=> + int(0) + [1]=> + int(42) + [2]=> + int(0) + [3]=> + int(0) +} diff --git a/ext/ffi/tests/jit/066_cast_scalar.phpt b/ext/ffi/tests/jit/066_cast_scalar.phpt new file mode 100644 index 0000000000000..f8569be5e85b4 --- /dev/null +++ b/ext/ffi/tests/jit/066_cast_scalar.phpt @@ -0,0 +1,28 @@ +--TEST-- +FFI/JIT 066: FFI::cast() (scalar) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +cast("double", 42); + } + var_dump($ret); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:double)#%d (1) { + ["cdata"]=> + float(42) +} diff --git a/ext/ffi/tests/jit/067_cast_ptr_int.phpt b/ext/ffi/tests/jit/067_cast_ptr_int.phpt new file mode 100644 index 0000000000000..0cb9346111cc2 --- /dev/null +++ b/ext/ffi/tests/jit/067_cast_ptr_int.phpt @@ -0,0 +1,28 @@ +--TEST-- +FFI/JIT 067: FFI::cast() (ptr/int) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +cast("int*", 0); + } + var_dump($ret); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int32_t*)#%d (1) { + [0]=> + NULL +} diff --git a/ext/ffi/tests/jit/068_cast_ptr_null.phpt b/ext/ffi/tests/jit/068_cast_ptr_null.phpt new file mode 100644 index 0000000000000..8d50367706730 --- /dev/null +++ b/ext/ffi/tests/jit/068_cast_ptr_null.phpt @@ -0,0 +1,28 @@ +--TEST-- +FFI/JIT 068: FFI::cast() (ptr/null) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--FILE-- +cast("int*", null); + } + var_dump($ret); +} +test(); +?> +--EXPECTF-- +object(FFI\CData:int32_t*)#2 (1) { + [0]=> + NULL +} diff --git a/ext/ffi/tests/jit/069_addr.phpt b/ext/ffi/tests/jit/069_addr.phpt new file mode 100644 index 0000000000000..b7d1aea367b32 --- /dev/null +++ b/ext/ffi/tests/jit/069_addr.phpt @@ -0,0 +1,38 @@ +--TEST-- +FFI/JIT 069: Function call with FFI::addr() +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +;opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=0 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=0 +;opcache.jit_debug=0x180005 +--SKIPIF-- + +--FILE-- +new("BUF"); + for ($i = 0; $i < 5; $i++) { + $ret = $ffi->sprintf(FFI::addr($x), "Hello %s", $name); + } + var_dump($ret, FFI::string($x->buf)); +} +test("FFI"); +?> +--EXPECT-- +int(9) +string(9) "Hello FFI" diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 406c539f44a27..3269bd7b56858 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2475,6 +2475,502 @@ static zend_class_entry* zend_accel_inheritance_cache_add(zend_class_entry *ce, return new_ce; } +#if HAVE_FFI +#include "ext/ffi/php_ffi.h" + +typedef struct _accel_ffi_cache_type_entry accel_ffi_cache_type_entry; + +struct _accel_ffi_cache_type_entry { + accel_ffi_cache_type_entry *next; + void *context; + zend_ffi_dcl dcl; +}; + +static ssize_t accel_ffi_persist_type_calc(zend_ffi_type *type) +{ + HashTable *ht; + Bucket *b; + zval *zv; + zend_ffi_field *f; + ssize_t s, size = 0; + + if (!ZEND_FFI_TYPE_IS_OWNED(type) + && (type->attr & ZEND_FFI_ATTR_PERSISTENT)) { + return 0; + } + + type = ZEND_FFI_TYPE(type); + + if (zend_shared_alloc_get_xlat_entry(type)) { + return 0; + } + + zend_shared_alloc_register_xlat_entry(type, type); + + size = sizeof(zend_ffi_type); + switch (type->kind) { + case ZEND_FFI_TYPE_ENUM: + if (type->enumeration.tag_name) { + type->enumeration.tag_name = accel_new_interned_string(type->enumeration.tag_name); + if (!IS_ACCEL_INTERNED(type->enumeration.tag_name)) { + return -1; + } + } + break; + case ZEND_FFI_TYPE_POINTER: + s = accel_ffi_persist_type_calc(type->pointer.type); + if (s == -1) { + return -1; + } + size += s; + break; + case ZEND_FFI_TYPE_ARRAY: + s = accel_ffi_persist_type_calc(type->array.type); + if (s == -1) { + return -1; + } + size += s; + break; + case ZEND_FFI_TYPE_STRUCT: + if (type->record.tag_name) { + type->record.tag_name = accel_new_interned_string(type->record.tag_name); + if (!IS_ACCEL_INTERNED(type->record.tag_name)) { + return -1; + } + } + ht = &type->record.fields; + if (HT_IS_PACKED(ht)) { + size += HT_PACKED_USED_SIZE(ht); + } else { + size += HT_USED_SIZE(ht); + } + ZEND_HASH_FOREACH_BUCKET(ht, b) { + if (b->key) { + b->key = accel_new_interned_string(b->key); + if (!IS_ACCEL_INTERNED(b->key)) { + return -1; + } + } + size += sizeof(zend_ffi_field); + f = (zend_ffi_field*)Z_PTR(b->val); + s = accel_ffi_persist_type_calc(f->type); + if (s == -1) { + return -1; + } + size += s; + } ZEND_HASH_FOREACH_END(); + break; + case ZEND_FFI_TYPE_FUNC: + if (type->func.ret_type) { + s = accel_ffi_persist_type_calc(type->func.ret_type); + if (s == -1) { + return -1; + } + size += s; + } + if (type->func.args) { + ht = type->func.args; + size += sizeof(HashTable); + ZEND_ASSERT(HT_IS_PACKED(ht)); + size += HT_PACKED_USED_SIZE(ht); + ZEND_HASH_PACKED_FOREACH_VAL(ht, zv) { + s = accel_ffi_persist_type_calc((zend_ffi_type*)Z_PTR_P(zv)); + if (s == -1) { + return -1; + } + size += s; + } ZEND_HASH_FOREACH_END(); + } + break; + default: + break; + } + + return size; +} + +static zend_ffi_type* accel_ffi_persist_type_copy(void** ptr, zend_ffi_type *type) +{ + HashTable *ht; + Bucket *b; + zval *zv; + zend_ffi_field *f; + zend_ffi_type *new_type; + + if (!ZEND_FFI_TYPE_IS_OWNED(type) + && (type->attr & ZEND_FFI_ATTR_PERSISTENT)) { + return type; + } + + type = ZEND_FFI_TYPE(type); + + new_type = zend_shared_alloc_get_xlat_entry(type); + if (new_type) { + return new_type; + } + + new_type = (zend_ffi_type*)*ptr; + (*ptr) = (void*)((char*)(*ptr) + sizeof(zend_ffi_type)); + memcpy(new_type, type, sizeof(zend_ffi_type)); + new_type->attr |= ZEND_FFI_ATTR_PERSISTENT; + + zend_shared_alloc_register_xlat_entry(type, new_type); + + switch (new_type->kind) { + case ZEND_FFI_TYPE_POINTER: + new_type->pointer.type = accel_ffi_persist_type_copy(ptr, new_type->pointer.type); + break; + case ZEND_FFI_TYPE_ARRAY: + new_type->array.type = accel_ffi_persist_type_copy(ptr, new_type->array.type); + break; + case ZEND_FFI_TYPE_STRUCT: + ht = &new_type->record.fields; + GC_SET_REFCOUNT(ht, 2); + GC_ADD_FLAGS(ht, IS_ARRAY_IMMUTABLE); + HT_FLAGS(ht) |= HASH_FLAG_STATIC_KEYS; + ht->pDestructor = NULL; + ht->nInternalPointer = 0; + if (HT_IS_PACKED(ht)) { + memcpy(*ptr, ht->arPacked, HT_PACKED_USED_SIZE(ht)); + ht->arPacked = (zval*)(*ptr); + (*ptr) = (void*)((char*)(*ptr) + HT_PACKED_USED_SIZE(ht)); + } else { + memcpy(*ptr, HT_GET_DATA_ADDR(ht), HT_USED_SIZE(ht)); + HT_SET_DATA_ADDR(ht, (Bucket*)(*ptr)); + (*ptr) = (void*)((char*)(*ptr) + HT_USED_SIZE(ht)); + } + ZEND_HASH_FOREACH_BUCKET(&new_type->record.fields, b) { + f = (zend_ffi_field*)(*ptr); + memcpy(f, Z_PTR(b->val), sizeof(zend_ffi_field)); + Z_PTR(b->val) = f; + (*ptr) = (void*)((char*)(*ptr) + sizeof(zend_ffi_field)); + f->type = accel_ffi_persist_type_copy(ptr, f->type); + } ZEND_HASH_FOREACH_END(); + break; + case ZEND_FFI_TYPE_FUNC: + if (new_type->func.ret_type) { + new_type->func.ret_type = accel_ffi_persist_type_copy(ptr, new_type->func.ret_type); + } + if (new_type->func.args) { + ht = (*ptr); + memcpy(ht, new_type->func.args, sizeof(HashTable)); + (*ptr) = (void*)((char*)(*ptr) + sizeof(HashTable)); + new_type->func.args = ht; + GC_SET_REFCOUNT(ht, 2); + GC_ADD_FLAGS(ht, IS_ARRAY_IMMUTABLE); + HT_FLAGS(ht) |= HASH_FLAG_STATIC_KEYS; + ht->pDestructor = NULL; + ht->nInternalPointer = 0; + ZEND_ASSERT(HT_IS_PACKED(ht)); + memcpy(*ptr, ht->arPacked, HT_PACKED_USED_SIZE(ht)); + ht->arPacked = (zval*)(*ptr); + (*ptr) = (void*)((char*)(*ptr) + HT_PACKED_USED_SIZE(ht)); + ZEND_HASH_PACKED_FOREACH_VAL(ht, zv) { + Z_PTR_P(zv) = accel_ffi_persist_type_copy(ptr, (zend_ffi_type*)Z_PTR_P(zv)); + } ZEND_HASH_FOREACH_END(); + } + break; + default: + break; + } + + return new_type; +} + +static accel_ffi_cache_type_entry* accel_ffi_persist_type(zend_ffi_dcl *dcl, void *context) +{ + void *ptr; + accel_ffi_cache_type_entry *entry; + zend_ffi_dcl *new_dcl; + ssize_t size; + + zend_shared_alloc_init_xlat_table(); + size = accel_ffi_persist_type_calc(ZEND_FFI_TYPE(dcl->type)); + zend_shared_alloc_destroy_xlat_table(); + + if (size == -1) { + return NULL; + } + entry = zend_shared_alloc(sizeof(accel_ffi_cache_type_entry) + size); + if (!entry) { + return NULL; + } + + zend_shared_alloc_init_xlat_table(); + + entry->next = NULL; + entry->context = context; + new_dcl = &entry->dcl; + memcpy(new_dcl, dcl, sizeof(zend_ffi_dcl)); + ptr = entry + 1; + new_dcl->type = accel_ffi_persist_type_copy(&ptr, new_dcl->type); + + zend_shared_alloc_destroy_xlat_table(); + + return entry; +} + +static uint32_t accel_ffi_cache_ptr_to_offset(const void *ptr) +{ + ZEND_ASSERT(((uintptr_t)ptr & 0x7) == 0); /* should be 8 byte aligned */ + ZEND_ASSERT(((uintptr_t)ZSMMG(shared_segments)[0]->p & 0x7) == 0); + ZEND_ASSERT((uint64_t*)ptr - (uint64_t*)ZSMMG(shared_segments)[0]->p > 0 && + (uint64_t*)ptr - (uint64_t*)ZSMMG(shared_segments)[0]->p < 0x7fffffff); + return (uint64_t*)ptr - (uint64_t*)ZSMMG(shared_segments)[0]->p; +} + +static void* accel_ffi_cache_offset_to_ptr(uint32_t offset) +{ + return (uint64_t*)ZSMMG(shared_segments)[0]->p + offset; +} + +static zend_ffi_dcl* accel_ffi_cache_type_get(zend_string *str, void *context) +{ + str = accel_find_interned_string(str); + if (str && (str->gc.u.type_info & IS_STR_FFI_TYPE)) { + accel_ffi_cache_type_entry *ptr = accel_ffi_cache_offset_to_ptr(str->gc.refcount); + while (ptr && ptr->context != context) { + ptr = ptr->next; + } + if (ptr) { + return &ptr->dcl; + } + } + return NULL; +} + +static zend_ffi_dcl* accel_ffi_cache_type_add(zend_string *str, zend_ffi_dcl *dcl, void *context) +{ + zend_ffi_dcl *new_dcl = NULL; + + SHM_UNPROTECT(); + zend_shared_alloc_lock(); + + new_dcl = accel_ffi_cache_type_get(str, context); + if (!new_dcl) { + str = accel_new_interned_string(zend_string_copy(str)); + if (IS_ACCEL_INTERNED(str)) { + accel_ffi_cache_type_entry *ptr = accel_ffi_persist_type(dcl, context); + if (ptr) { + ZEND_ASSERT(!(str->gc.u.type_info & (IS_STR_CLASS_NAME_MAP_PTR|IS_STR_FFI_SCOPE))); + if (str->gc.u.type_info & IS_STR_FFI_TYPE) { + ptr->next = accel_ffi_cache_offset_to_ptr(str->gc.refcount); + } else { + ptr->next = NULL; + str->gc.u.type_info |= IS_STR_FFI_TYPE; + } + str->gc.refcount = accel_ffi_cache_ptr_to_offset(ptr); + new_dcl = &ptr->dcl; + } + } + } + + zend_shared_alloc_unlock(); + SHM_PROTECT(); + + return new_dcl; +} + +static ssize_t accel_ffi_persist_scope_calc(zend_ffi_scope *scope) +{ + HashTable *ht; + Bucket *b; + zend_ffi_symbol *sym; + zend_ffi_tag *tag; + ssize_t size = 0; + + if (scope->symbols) { + ht = scope->symbols; + size += sizeof(HashTable); + if (HT_IS_PACKED(ht)) { + size += HT_PACKED_USED_SIZE(ht); + } else { + size += HT_USED_SIZE(ht); + } + ZEND_HASH_FOREACH_BUCKET(ht, b) { + if (b->key) { + b->key = accel_new_interned_string(b->key); + if (!IS_ACCEL_INTERNED(b->key)) { + return -1; + } + } + size += sizeof(zend_ffi_symbol); + sym = (zend_ffi_symbol*)Z_PTR(b->val); + if (sym->type) { + ssize_t s = accel_ffi_persist_type_calc(sym->type); + if (s == -1) { + return -1; + } + size += s; + } + } ZEND_HASH_FOREACH_END(); + } + + if (scope->tags) { + ht = scope->tags; + size += sizeof(HashTable); + if (HT_IS_PACKED(ht)) { + size += HT_PACKED_USED_SIZE(ht); + } else { + size += HT_USED_SIZE(ht); + } + ZEND_HASH_FOREACH_BUCKET(ht, b) { + if (b->key) { + b->key = accel_new_interned_string(b->key); + if (!IS_ACCEL_INTERNED(b->key)) { + return -1; + } + } + size += sizeof(zend_ffi_tag); + tag = (zend_ffi_tag*)Z_PTR(b->val); + if (tag->type) { + ssize_t s = accel_ffi_persist_type_calc(tag->type); + if (s == -1) { + return -1; + } + size += s; + } + } ZEND_HASH_FOREACH_END(); + } + return size; +} + +static void accel_ffi_persist_scope_copy(void **ptr, zend_ffi_scope *scope) +{ + HashTable *ht; + Bucket *b; + zend_ffi_symbol *sym; + zend_ffi_tag *tag; + + if (scope->symbols) { + ht = (*ptr); + memcpy(ht, scope->symbols, sizeof(HashTable)); + (*ptr) = (void*)((char*)(*ptr) + sizeof(HashTable)); + scope->symbols = ht; + GC_SET_REFCOUNT(ht, 2); + GC_ADD_FLAGS(ht, IS_ARRAY_IMMUTABLE); + HT_FLAGS(ht) |= HASH_FLAG_STATIC_KEYS; + ht->pDestructor = NULL; + ht->nInternalPointer = 0; + if (HT_IS_PACKED(ht)) { + memcpy(*ptr, ht->arPacked, HT_PACKED_USED_SIZE(ht)); + ht->arPacked = (zval*)(*ptr); + (*ptr) = (void*)((char*)(*ptr) + HT_PACKED_USED_SIZE(ht)); + } else { + memcpy(*ptr, HT_GET_DATA_ADDR(ht), HT_USED_SIZE(ht)); + HT_SET_DATA_ADDR(ht, (Bucket*)(*ptr)); + (*ptr) = (void*)((char*)(*ptr) + HT_USED_SIZE(ht)); + } + ZEND_HASH_FOREACH_BUCKET(ht, b) { + sym = (zend_ffi_symbol*)(*ptr); + memcpy(sym, Z_PTR(b->val), sizeof(zend_ffi_symbol)); + Z_PTR(b->val) = sym; + (*ptr) = (void*)((char*)(*ptr) + sizeof(zend_ffi_symbol)); + if (sym->type) { + sym->type = accel_ffi_persist_type_copy(ptr, sym->type); + } + } ZEND_HASH_FOREACH_END(); + } + + if (scope->tags) { + ht = (*ptr); + memcpy(ht, scope->tags, sizeof(HashTable)); + (*ptr) = (void*)((char*)(*ptr) + sizeof(HashTable)); + scope->tags = ht; + GC_SET_REFCOUNT(ht, 2); + GC_ADD_FLAGS(ht, IS_ARRAY_IMMUTABLE); + HT_FLAGS(ht) |= HASH_FLAG_STATIC_KEYS; + ht->pDestructor = NULL; + ht->nInternalPointer = 0; + if (HT_IS_PACKED(ht)) { + memcpy(*ptr, ht->arPacked, HT_PACKED_USED_SIZE(ht)); + ht->arPacked = (zval*)(*ptr); + (*ptr) = (void*)((char*)(*ptr) + HT_PACKED_USED_SIZE(ht)); + } else { + memcpy(*ptr, HT_GET_DATA_ADDR(ht), HT_USED_SIZE(ht)); + HT_SET_DATA_ADDR(ht, (Bucket*)(*ptr)); + (*ptr) = (void*)((char*)(*ptr) + HT_USED_SIZE(ht)); + } + ZEND_HASH_FOREACH_BUCKET(ht, b) { + tag = (zend_ffi_tag*)(*ptr); + memcpy(tag, Z_PTR(b->val), sizeof(zend_ffi_tag)); + Z_PTR(b->val) = tag; + (*ptr) = (void*)((char*)(*ptr) + sizeof(zend_ffi_tag)); + if (tag->type) { + tag->type = accel_ffi_persist_type_copy(ptr, tag->type); + } + } ZEND_HASH_FOREACH_END(); + } +} + +static zend_ffi_scope* accel_ffi_persist_scope(zend_ffi_scope *scope) +{ + void *ptr; + zend_ffi_scope *new_scope; + ssize_t size; + + zend_shared_alloc_init_xlat_table(); + size = accel_ffi_persist_scope_calc(scope); + zend_shared_alloc_destroy_xlat_table(); + + if (size == -1) { + return NULL; + } + new_scope = zend_shared_alloc(sizeof(zend_ffi_scope) + size); + if (!new_scope) { + return NULL; + } + + new_scope->symbols = scope->symbols; + new_scope->tags = scope->tags; + ptr = (char*)new_scope + sizeof(zend_ffi_scope); + + zend_shared_alloc_init_xlat_table(); + + accel_ffi_persist_scope_copy(&ptr, new_scope); + + zend_shared_alloc_destroy_xlat_table(); + + return new_scope; +} + +static zend_ffi_scope* accel_ffi_cache_scope_get(zend_string *str) +{ + str = accel_find_interned_string(str); + if (str && (str->gc.u.type_info & IS_STR_FFI_SCOPE)) { + zend_ffi_scope *ptr = (zend_ffi_scope*)accel_ffi_cache_offset_to_ptr(str->gc.refcount); + return ptr; + } + return NULL; +} + +static zend_ffi_scope* accel_ffi_cache_scope_add(zend_string *str, zend_ffi_scope *scope) +{ + zend_ffi_scope *new_scope = NULL; + + SHM_UNPROTECT(); + zend_shared_alloc_lock(); + + new_scope = accel_ffi_cache_scope_get(str); + if (!new_scope) { + str = accel_new_interned_string(zend_string_copy(str)); + if (IS_ACCEL_INTERNED(str)) { + new_scope = accel_ffi_persist_scope(scope); + if (new_scope) { + ZEND_ASSERT(!(str->gc.u.type_info & (IS_STR_CLASS_NAME_MAP_PTR|IS_STR_FFI_TYPE|IS_STR_FFI_SCOPE))); + str->gc.u.type_info |= IS_STR_FFI_SCOPE; + str->gc.refcount = accel_ffi_cache_ptr_to_offset(new_scope); + } + } + } + + zend_shared_alloc_unlock(); + SHM_PROTECT(); + + return new_scope; +} +#endif + #ifdef ZEND_WIN32 static zend_result accel_gen_uname_id(void) { @@ -3362,6 +3858,15 @@ static zend_result accel_post_startup(void) accelerator_orig_inheritance_cache_add = zend_inheritance_cache_add; zend_inheritance_cache_get = zend_accel_inheritance_cache_get; zend_inheritance_cache_add = zend_accel_inheritance_cache_add; + +#if HAVE_FFI + if (zend_ffi_api) { + zend_ffi_api->cache_type_get = accel_ffi_cache_type_get; + zend_ffi_api->cache_type_add = accel_ffi_cache_type_add; + zend_ffi_api->cache_scope_get = accel_ffi_cache_scope_get; + zend_ffi_api->cache_scope_add = accel_ffi_cache_scope_add; + } +#endif } return SUCCESS; @@ -3416,6 +3921,15 @@ void accel_shutdown(void) zend_inheritance_cache_get = accelerator_orig_inheritance_cache_get; zend_inheritance_cache_add = accelerator_orig_inheritance_cache_add; +#if HAVE_FFI + if (zend_ffi_api) { + zend_ffi_api->cache_type_get = NULL; + zend_ffi_api->cache_type_add = NULL; + zend_ffi_api->cache_scope_get = NULL; + zend_ffi_api->cache_scope_add = NULL; + } +#endif + if ((ini_entry = zend_hash_str_find_ptr(EG(ini_directives), "include_path", sizeof("include_path")-1)) != NULL) { ini_entry->on_modify = orig_include_path_on_modify; } diff --git a/ext/opcache/jit/Makefile.frag b/ext/opcache/jit/Makefile.frag index 863556d6744d7..7667d0bc005a5 100644 --- a/ext/opcache/jit/Makefile.frag +++ b/ext/opcache/jit/Makefile.frag @@ -18,7 +18,8 @@ $(builddir)/jit/ir/ir.lo: \ $(builddir)/jit/zend_jit.lo: \ $(srcdir)/jit/zend_jit_helpers.c \ - $(srcdir)/jit/zend_jit_ir.c + $(srcdir)/jit/zend_jit_ir.c \ + $(srcdir)/jit/zend_jit_ir_ffi.c # For non-GNU make, jit/zend_jit.lo and ./jit/zend_jit.lo are considered distinct targets. # Use this workaround to allow building from inside ext/opcache. diff --git a/ext/opcache/jit/Makefile.frag.w32 b/ext/opcache/jit/Makefile.frag.w32 index 7caf814588705..ec12dea4fe267 100644 --- a/ext/opcache/jit/Makefile.frag.w32 +++ b/ext/opcache/jit/Makefile.frag.w32 @@ -33,6 +33,7 @@ $(BUILD_DIR)\ext\opcache\jit\ir\ir.obj: \ $(BUILD_DIR)\ext\opcache\jit\zend_jit.obj: \ ext\opcache\jit\zend_jit_ir.c \ + ext\opcache\jit\zend_jit_ir_ffi.c \ ext\opcache\jit\zend_jit_helpers.c \ ext\opcache\jit\ir\ir.h \ ext\opcache\jit\ir\ir_builder.h diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index ffcb8daaf568f..9067f81d58319 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -42,6 +42,279 @@ #include "jit/zend_jit_internal.h" +#if HAVE_FFI +# include "ext/ffi/php_ffi.h" + +# define FFI_TYPE_GUARD (1<<0) +# define FFI_SYMBOLS_GUARD (1<<1) + +typedef struct zend_jit_ffi_info { + union { + zend_ffi_type *type; + HashTable *symbols; + }; + uint32_t info; +} zend_jit_ffi_info; + +static zend_ffi_type *zend_jit_ffi_type_pointer_to(const zend_ffi_type *type, zend_ffi_type *holder) +{ + holder->kind = ZEND_FFI_TYPE_POINTER; + holder->attr = 0; + holder->size = sizeof(void*); + holder->align = _Alignof(void*); + holder->pointer.type = ZEND_FFI_TYPE(type); + return holder; +} + +static bool zend_jit_ffi_supported_type(zend_ffi_type *type) +{ + if (sizeof(void*) == 4) { + if (ZEND_FFI_TYPE(type)->kind == ZEND_FFI_TYPE_UINT64 + || ZEND_FFI_TYPE(type)->kind == ZEND_FFI_TYPE_SINT64) { + return false; + } + } + return true; +} + +static bool zend_jit_ffi_supported_func(zend_ffi_type *type) +{ + zend_ffi_type *t; + + type = ZEND_FFI_TYPE(type); + + if (type->func.abi != ZEND_FFI_ABI_CDECL + && type->func.abi != ZEND_FFI_ABI_FASTCALL) { + return false; + } + + t = ZEND_FFI_TYPE(type->func.ret_type); + if (t->kind == ZEND_FFI_TYPE_STRUCT + || !zend_jit_ffi_supported_type(t)) { + return false; + } + + if (type->func.args) { + ZEND_HASH_PACKED_FOREACH_PTR(type->func.args, t) { + t = ZEND_FFI_TYPE(t); + if (t->kind == ZEND_FFI_TYPE_STRUCT + || !zend_jit_ffi_supported_type(t)) { + return false; + } + } ZEND_HASH_FOREACH_END(); + } + + return true; +} + +static bool zend_jit_is_send_bool_const(const zend_jit_trace_rec *p) +{ + if (p->op == ZEND_JIT_TRACE_VM + && (p->opline->opcode == ZEND_SEND_VAL + || p->opline->opcode == ZEND_SEND_VAL_EX) + && p->opline->op1_type == IS_CONST) { + zval *zv = RT_CONSTANT(p->opline, p->opline->op1); + + if (Z_TYPE_P(zv) <= IS_STRING) { + return true; + } + } + return false; +} + +static zend_ffi_type* zend_jit_ffi_supported_new(const zend_op *opline, HashTable *ffi_symbols, const zend_jit_trace_rec *p) +{ + zend_ffi_type *type = NULL; + + if ((opline->extended_value == 1 + || opline->extended_value == 2 + || opline->extended_value == 3) + && (p+1)->op == ZEND_JIT_TRACE_INIT_CALL + && (p+2)->op == ZEND_JIT_TRACE_VM) { + if (((p+2)->opline->opcode == ZEND_SEND_VAL + || (p+2)->opline->opcode == ZEND_SEND_VAL_EX) + && (p+2)->opline->op1_type == IS_CONST) { + zval *zv = RT_CONSTANT((p+2)->opline, (p+2)->opline->op1); + + if (Z_TYPE_P(zv) == IS_STRING) { + zend_ffi_dcl *dcl = zend_ffi_api->cache_type_get(Z_STR_P(zv), ffi_symbols); + + if (dcl) { + type = dcl->type; + p += 3; + } + } + } else if ((p+2)->opline->opcode == ZEND_SEND_VAR_EX + && (p+3)->op == ZEND_JIT_TRACE_OP1_TYPE + && (p+3)->ce == zend_ffi_api->ctype_ce + && (p+4)->op == ZEND_JIT_TRACE_OP1_FFI_TYPE) { + type = (zend_ffi_type*)(p+4)->ptr; + p += 5; + } + } + + if (type + && !ZEND_FFI_TYPE_IS_OWNED(type) + && (type->attr & ZEND_FFI_ATTR_PERSISTENT) + && type->size != 0 + && (opline->extended_value == 1 + || (opline->extended_value == 2 + && zend_jit_is_send_bool_const(p)) + || (opline->extended_value == 3 + && zend_jit_is_send_bool_const(p) + && zend_jit_is_send_bool_const(p + 1)))) { + return type; + } + + return NULL; +} + +static zend_ffi_type* zend_jit_ffi_supported_cast(const zend_op *opline, HashTable *ffi_symbols, const zend_jit_trace_rec *p) +{ + zend_ffi_type *type = NULL; + + if (opline->extended_value == 2 + && (p+1)->op == ZEND_JIT_TRACE_INIT_CALL + && (p+2)->op == ZEND_JIT_TRACE_VM) { + if (((p+2)->opline->opcode == ZEND_SEND_VAL + || (p+2)->opline->opcode == ZEND_SEND_VAL_EX) + && (p+2)->opline->op1_type == IS_CONST) { + zval *zv = RT_CONSTANT((p+2)->opline, (p+2)->opline->op1); + + if (Z_TYPE_P(zv) == IS_STRING) { + zend_ffi_dcl *dcl = zend_ffi_api->cache_type_get(Z_STR_P(zv), ffi_symbols); + + if (dcl) { + type = dcl->type; + p += 3; + } + } + } else if ((p+2)->opline->opcode == ZEND_SEND_VAR_EX + && (p+3)->op == ZEND_JIT_TRACE_OP1_TYPE + && (p+3)->ce == zend_ffi_api->ctype_ce + && (p+4)->op == ZEND_JIT_TRACE_OP1_FFI_TYPE) { + type = (zend_ffi_type*)(p+4)->ptr; + p += 5; + } + } + + if (type + && !ZEND_FFI_TYPE_IS_OWNED(type) + && (type->attr & ZEND_FFI_ATTR_PERSISTENT) + && type->size != 0) { + return type; + } + + return NULL; +} + +static bool zend_jit_ffi_compatible(zend_ffi_type *dst_type, uint32_t src_info, zend_ffi_type *src_type) +{ + dst_type = ZEND_FFI_TYPE(dst_type); + if (!zend_jit_ffi_supported_type(dst_type)) { + return false; + } else if ((src_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG + || (src_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_DOUBLE) { + return dst_type->kind < ZEND_FFI_TYPE_POINTER && dst_type->kind != ZEND_FFI_TYPE_VOID; + } else if ((src_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_FALSE + || (src_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_TRUE + || (src_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == (MAY_BE_FALSE|MAY_BE_TRUE)) { + return dst_type->kind == ZEND_FFI_TYPE_BOOL; + } else if ((src_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING) { + return dst_type->kind == ZEND_FFI_TYPE_CHAR; + } else if ((src_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_NULL) { + return dst_type->kind == ZEND_FFI_TYPE_POINTER; + } else if (src_type) { + if (!zend_jit_ffi_supported_type(src_type)) { + return false; + } + if (dst_type == src_type + || zend_ffi_api->is_compatible_type(dst_type, src_type)) { + return true; + } + } + return false; +} + +static bool zend_jit_ffi_compatible_op(zend_ffi_type *dst_type, uint32_t src_info, zend_ffi_type *src_type, uint8_t op) +{ + dst_type = ZEND_FFI_TYPE(dst_type); + if (!zend_jit_ffi_supported_type(dst_type)) { + return false; + } else if (dst_type->kind == ZEND_FFI_TYPE_FLOAT || dst_type->kind == ZEND_FFI_TYPE_DOUBLE) { + return (op == ZEND_ADD || op == ZEND_SUB || op == ZEND_MUL) + && ((src_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_DOUBLE + || (src_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG); + } else if (dst_type->kind == ZEND_FFI_TYPE_BOOL) { + return (op == ZEND_BW_AND || op == ZEND_BW_OR) + && (src_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG; + } else if (dst_type->kind == ZEND_FFI_TYPE_UINT8 + || dst_type->kind == ZEND_FFI_TYPE_UINT16 + || dst_type->kind == ZEND_FFI_TYPE_UINT32 + || dst_type->kind == ZEND_FFI_TYPE_UINT64 + || dst_type->kind == ZEND_FFI_TYPE_SINT8 + || dst_type->kind == ZEND_FFI_TYPE_SINT16 + || dst_type->kind == ZEND_FFI_TYPE_SINT32 + || dst_type->kind == ZEND_FFI_TYPE_SINT64 + || dst_type->kind == ZEND_FFI_TYPE_CHAR) { + return (op == ZEND_ADD || op == ZEND_SUB || op == ZEND_MUL + || op == ZEND_BW_AND || op == ZEND_BW_OR || op == ZEND_BW_XOR + || op == ZEND_SL || op == ZEND_SR || op == ZEND_MOD) + && (src_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG; + } else if (dst_type->kind == ZEND_FFI_TYPE_POINTER + && ZEND_FFI_TYPE(dst_type->pointer.type)->size != 0 + && src_info == MAY_BE_LONG + && (op == ZEND_ADD || op == ZEND_SUB)) { + return true; + } + return false; +} + +static uint32_t zend_jit_ffi_type_info(zend_ffi_type *type) +{ + uint32_t type_kind = type->kind; + uint32_t info = MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN; + + if (type_kind == ZEND_FFI_TYPE_ENUM) { + type_kind = type->enumeration.kind; + } + switch (type_kind) { + case ZEND_FFI_TYPE_VOID: + info = MAY_BE_NULL; + break; + case ZEND_FFI_TYPE_FLOAT: + case ZEND_FFI_TYPE_DOUBLE: + info = MAY_BE_DOUBLE; + break; + case ZEND_FFI_TYPE_UINT8: + case ZEND_FFI_TYPE_SINT8: + case ZEND_FFI_TYPE_UINT16: + case ZEND_FFI_TYPE_SINT16: + case ZEND_FFI_TYPE_UINT32: + case ZEND_FFI_TYPE_SINT32: + case ZEND_FFI_TYPE_UINT64: + case ZEND_FFI_TYPE_SINT64: + info = MAY_BE_LONG; + break; + case ZEND_FFI_TYPE_BOOL: + info = MAY_BE_FALSE|MAY_BE_TRUE; + break; + case ZEND_FFI_TYPE_CHAR: + info = MAY_BE_STRING; + break; + case ZEND_FFI_TYPE_POINTER: + if ((type->attr & ZEND_FFI_ATTR_CONST) + && ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) { + info = MAY_BE_STRING; + } + break; + default: + break; + } + return info; +} +#endif + #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP #include #endif @@ -802,6 +1075,10 @@ static bool zend_jit_may_be_modified(const zend_function *func, const zend_op_ar #include "jit/zend_jit_ir.c" +#if HAVE_FFI +# include "jit/zend_jit_ir_ffi.c" +#endif + #if defined(__clang__) # pragma clang diagnostic pop #endif diff --git a/ext/opcache/jit/zend_jit.h b/ext/opcache/jit/zend_jit.h index 0ce6c1a4409a2..0d2f6c1b7575c 100644 --- a/ext/opcache/jit/zend_jit.h +++ b/ext/opcache/jit/zend_jit.h @@ -182,6 +182,11 @@ void zend_jit_restart(void); #define ZREG_ZVAL_ADDREF (4<<4) #define ZREG_THIS (5<<4) +#define ZREG_FFI_PTR_LOAD (1<<3) +#define ZREG_FFI_ZVAL_DTOR (1<<4) +#define ZREG_FFI_ZVAL_DEREF (1<<5) +#define ZREG_FFI_CTYPE (1<<6) + #define ZREG_NONE -1 #endif /* HAVE_JIT_H */ diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 56b8b2d69cac2..e9af2367079a5 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -3362,3 +3362,169 @@ static zend_string* ZEND_FASTCALL zend_jit_rope_end(zend_string **rope, uint32_t *target = '\0'; return ret; } + +#ifdef HAVE_FFI +static void ZEND_FASTCALL zend_jit_zval_string(zval *zv, const char *str) +{ + if (str) { + ZVAL_STRING(zv, str); + } else { + ZVAL_NULL(zv); + } +} + +static void ZEND_FASTCALL zend_jit_zval_stringl(zval *zv, const char *str, size_t len) +{ + if (str) { + ZVAL_STRINGL(zv, str, len); + } else { + ZVAL_NULL(zv); + } +} + +static zend_ffi_cdata* ZEND_FASTCALL zend_jit_ffi_create_ptr(zend_ffi_type *type, void *ptr) +{ + ZEND_ASSERT(type->kind == ZEND_FFI_TYPE_POINTER); + zend_ffi_cdata *cdata = emalloc(sizeof(zend_ffi_cdata)); + + // inlined zend_ffi_object_init() + GC_SET_REFCOUNT(&cdata->std, 1); + GC_TYPE_INFO(&cdata->std) = GC_OBJECT | (IS_OBJ_DESTRUCTOR_CALLED << GC_FLAGS_SHIFT); + cdata->std.extra_flags = 0; + cdata->std.ce = zend_ffi_api->cdata_ce; + cdata->std.handlers = zend_ffi_api->cdata_ce->default_object_handlers; /* zend_ffi_cdata_handlers */ + cdata->std.properties = NULL; + zend_objects_store_put(&cdata->std); + cdata->type = type; + cdata->flags = 0; + cdata->ptr = (void*)&cdata->ptr_holder; + cdata->ptr_holder = ptr; + return cdata; +} + +static void ZEND_FASTCALL zend_jit_zval_ffi_ptr(zval *zv, zend_ffi_type *type, void *ptr) +{ + ZEND_ASSERT(type->kind == ZEND_FFI_TYPE_POINTER); + if (ptr) { + zend_ffi_cdata *cdata = zend_jit_ffi_create_ptr(type, ptr); + ZVAL_OBJ(zv, &cdata->std); + } else { + ZVAL_NULL(zv); + } +} + +static void ZEND_FASTCALL zend_jit_zval_ffi_obj(zval *zv, zend_ffi_type *type, void *ptr) +{ + if (!ptr) { + ZEND_ASSERT(type->kind == ZEND_FFI_TYPE_POINTER); + ZVAL_NULL(zv); + } else { + zend_ffi_cdata *cdata = emalloc(sizeof(zend_ffi_cdata)); + + // inlined zend_ffi_object_init() + GC_SET_REFCOUNT(&cdata->std, 1); + GC_TYPE_INFO(&cdata->std) = GC_OBJECT | (IS_OBJ_DESTRUCTOR_CALLED << GC_FLAGS_SHIFT); + cdata->std.extra_flags = 0; + cdata->std.ce = zend_ffi_api->cdata_ce; + cdata->std.handlers = zend_ffi_api->cdata_ce->default_object_handlers; /* zend_ffi_cdata_handlers */ + cdata->std.properties = NULL; + zend_objects_store_put(&cdata->std); + cdata->type = type; + cdata->flags = 0; + cdata->ptr = ptr; + cdata->ptr_holder = NULL; + ZVAL_OBJ(zv, &cdata->std); + } +} + +static zend_ffi_cdata* ZEND_FASTCALL zend_jit_zval_ffi_addr(zend_ffi_cdata *cdata) +{ + zend_ffi_cdata *new_cdata; + zend_ffi_type *type, *new_type; + + type = ZEND_FFI_TYPE(cdata->type); + +// if (GC_REFCOUNT(&cdata->std) == 1 && Z_REFCOUNT_P(arg) == 1 && type->kind == ZEND_FFI_TYPE_POINTER +// && cdata->ptr == &cdata->ptr_holder) { +// zend_throw_error(zend_ffi_exception_ce, "FFI::addr() cannot create a reference to a temporary pointer"); +// return NULL; +// } + + new_type = emalloc(sizeof(zend_ffi_type)); + new_type->kind = ZEND_FFI_TYPE_POINTER; + new_type->attr = 0; + new_type->size = sizeof(void*); + new_type->align = _Alignof(void*); + /* life-time (source must relive the resulting pointer) ??? */ + new_type->pointer.type = type; + + new_cdata = emalloc(sizeof(zend_ffi_cdata)); + // inlined zend_ffi_object_init() + GC_SET_REFCOUNT(&new_cdata->std, 1); + GC_TYPE_INFO(&new_cdata->std) = GC_OBJECT | (IS_OBJ_DESTRUCTOR_CALLED << GC_FLAGS_SHIFT); + new_cdata->std.extra_flags = 0; + new_cdata->std.ce = zend_ffi_api->cdata_ce; + new_cdata->std.handlers = zend_ffi_api->cdata_ce->default_object_handlers; /* zend_ffi_cdata_handlers */ + new_cdata->std.properties = NULL; + zend_objects_store_put(&new_cdata->std); + new_cdata->type = ZEND_FFI_TYPE_MAKE_OWNED(new_type); + new_cdata->flags = 0; + + new_cdata->ptr = (void*)&new_cdata->ptr_holder; + new_cdata->ptr_holder = cdata->ptr; + + return new_cdata; +} + +static zend_ffi_cdata* ZEND_FASTCALL zend_jit_zval_ffi_addr_var(zend_ffi_cdata *cdata) +{ + zend_ffi_cdata *new_cdata; + zend_ffi_type *type, *new_type; + + type = ZEND_FFI_TYPE(cdata->type); + +// if (GC_REFCOUNT(&cdata->std) == 1 && Z_REFCOUNT_P(arg) == 1 && type->kind == ZEND_FFI_TYPE_POINTER +// && cdata->ptr == &cdata->ptr_holder) { +// zend_throw_error(zend_ffi_exception_ce, "FFI::addr() cannot create a reference to a temporary pointer"); +// return NULL; +// } + + new_type = emalloc(sizeof(zend_ffi_type)); + new_type->kind = ZEND_FFI_TYPE_POINTER; + new_type->attr = 0; + new_type->size = sizeof(void*); + new_type->align = _Alignof(void*); + /* life-time (source must relive the resulting pointer) ??? */ + new_type->pointer.type = type; + + new_cdata = emalloc(sizeof(zend_ffi_cdata)); + // inlined zend_ffi_object_init() + GC_SET_REFCOUNT(&new_cdata->std, 1); + GC_TYPE_INFO(&new_cdata->std) = GC_OBJECT | (IS_OBJ_DESTRUCTOR_CALLED << GC_FLAGS_SHIFT); + new_cdata->std.extra_flags = 0; + new_cdata->std.ce = zend_ffi_api->cdata_ce; + new_cdata->std.handlers = zend_ffi_api->cdata_ce->default_object_handlers; /* zend_ffi_cdata_handlers */ + new_cdata->std.properties = NULL; + zend_objects_store_put(&new_cdata->std); + new_cdata->type = ZEND_FFI_TYPE_MAKE_OWNED(new_type); + new_cdata->flags = 0; + + new_cdata->ptr = (void*)&new_cdata->ptr_holder; + new_cdata->ptr_holder = cdata->ptr; + + if (GC_REFCOUNT(&cdata->std) == 1 /*&& Z_REFCOUNT_P(arg) == 1*/) { + if (ZEND_FFI_TYPE_IS_OWNED(cdata->type)) { + /* transfer type ownership */ + cdata->type = type; + new_type->pointer.type = ZEND_FFI_TYPE_MAKE_OWNED(type); + } + if (cdata->flags & ZEND_FFI_FLAG_OWNED) { + /* transfer ownership */ + cdata->flags &= ~ZEND_FFI_FLAG_OWNED; + new_cdata->flags |= ZEND_FFI_FLAG_OWNED; + } + } + + return new_cdata; +} +#endif diff --git a/ext/opcache/jit/zend_jit_internal.h b/ext/opcache/jit/zend_jit_internal.h index 61d8a853fa005..efb71ffd87b0b 100644 --- a/ext/opcache/jit/zend_jit_internal.h +++ b/ext/opcache/jit/zend_jit_internal.h @@ -349,6 +349,11 @@ typedef enum _zend_jit_trace_op { ZEND_JIT_TRACE_VM, ZEND_JIT_TRACE_OP1_TYPE, ZEND_JIT_TRACE_OP2_TYPE, + ZEND_JIT_TRACE_OP3_TYPE, + ZEND_JIT_TRACE_OP1_FFI_TYPE, + ZEND_JIT_TRACE_OP2_FFI_TYPE, + ZEND_JIT_TRACE_OP3_FFI_TYPE, + ZEND_JIT_TRACE_OP1_FFI_SYMBOLS, ZEND_JIT_TRACE_VAL_INFO, ZEND_JIT_TRACE_INIT_CALL, ZEND_JIT_TRACE_DO_ICALL, @@ -550,6 +555,11 @@ struct _zend_jit_trace_stack_frame { int used_stack; int old_checked_stack; int old_peek_checked_stack; +#ifdef HAVE_FFI + uint32_t ffi_info; + int ffi_obj_ref; + int ffi_func_ref; +#endif zend_jit_trace_stack stack[1]; }; @@ -569,6 +579,25 @@ struct _zend_jit_trace_stack_frame { #define TRACE_FRAME_MASK_CLOSURE_CALL 0x00000200 #define TRACE_FRAME_MASK_ALWAYS_RELEASE_THIS 0x00000400 +#define TRACE_FRAME_MASK_FFI 0x00000800 + +#define TRACE_FRAME_FFI_FUNC_NEW 0x00000001 +#define TRACE_FRAME_FFI_FUNC_FREE 0x00000002 +#define TRACE_FRAME_FFI_FUNC_CAST 0x00000003 +#define TRACE_FRAME_FFI_FUNC_TYPEOF 0x00000004 +#define TRACE_FRAME_FFI_FUNC_ARRAY_TYPE 0x00000005 +#define TRACE_FRAME_FFI_FUNC_ADDR 0x00000006 +#define TRACE_FRAME_FFI_FUNC_ALIGNOF 0x00000007 +#define TRACE_FRAME_FFI_FUNC_SIZEOF 0x00000008 +#define TRACE_FRAME_FFI_FUNC_MEMCPY 0x00000009 +#define TRACE_FRAME_FFI_FUNC_MEMCMP 0x0000000a +#define TRACE_FRAME_FFI_FUNC_MEMSET 0x0000000b +#define TRACE_FRAME_FFI_FUNC_STRING 0x0000000c +#define TRACE_FRAME_FFI_FUNC_IS_NULL 0x0000000d +#define TRACE_FRAME_FFI_FUNC_TYPE 0x0000000e + +#define TRACE_FRAME_MASK_FFI_FUNC 0x0000000f +#define TRACE_FRAME_MASK_FFI_OBJ_DTOR 0x00000010 #define TRACE_FRAME_INIT(frame, _func, _flags, num_args) do { \ zend_jit_trace_stack_frame *_frame = (frame); \ @@ -607,6 +636,13 @@ struct _zend_jit_trace_stack_frame { ((frame)->_info & TRACE_FRAME_MASK_CLOSURE_CALL) #define TRACE_FRAME_ALWAYS_RELEASE_THIS(frame) \ ((frame)->_info & TRACE_FRAME_MASK_ALWAYS_RELEASE_THIS) +#define TRACE_FRAME_FFI(frame) \ + ((frame)->_info & TRACE_FRAME_MASK_FFI) + +#define TRACE_FRAME_FFI_FUNC(frame) \ + ((frame)->ffi_info & TRACE_FRAME_MASK_FFI_FUNC) +#define TRACE_FRAME_FFI_OBJ_DTOR(frame) \ + ((frame)->ffi_info & TRACE_FRAME_MASK_FFI_OBJ_DTOR) #define TRACE_FRAME_SET_UNKNOWN_NUM_ARGS(frame) do { \ (frame)->_info |= (0xffffu << TRACE_FRAME_SHIFT_NUM_ARGS); \ diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 9124b8400bcea..98400517fbf43 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -3100,6 +3100,16 @@ static void zend_jit_setup_disasm(void) REGISTER_HELPER(zend_jit_rope_end); REGISTER_HELPER(zend_fcall_interrupt); +#ifdef HAVE_FFI + REGISTER_HELPER(zend_jit_zval_string); + REGISTER_HELPER(zend_jit_zval_stringl); + REGISTER_HELPER(zend_jit_zval_ffi_ptr); + REGISTER_HELPER(zend_jit_zval_ffi_obj); + REGISTER_HELPER(zend_jit_zval_ffi_addr); + REGISTER_HELPER(zend_jit_zval_ffi_addr_var); + REGISTER_HELPER(zend_jit_ffi_create_ptr); +#endif + #ifndef ZTS REGISTER_DATA(EG(current_execute_data)); REGISTER_DATA(EG(exception)); @@ -17352,9 +17362,32 @@ static bool zend_jit_opline_supports_reg(const zend_op_array *op_array, zend_ssa && (trace->op1_type & ~(IS_TRACE_REFERENCE|IS_TRACE_INDIRECT|IS_TRACE_PACKED)) == IS_ARRAY) { op1_info &= ~((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_ARRAY); } - return ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) && - (((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) || - ((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING)); + if (((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) + && (((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) + || ((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING))) { + return 1; + } +#ifdef HAVE_FFI + if (trace + && (trace+1)->op == ZEND_JIT_TRACE_OP1_TYPE + && (trace+2)->op == ZEND_JIT_TRACE_OP1_FFI_TYPE) { + zend_ffi_type *op1_ffi_type = (zend_ffi_type*)(trace+2)->ptr; + if (op1_ffi_type) { + zend_ffi_type holder; + if (ZEND_FFI_TYPE_IS_OWNED(op1_ffi_type)) { + op1_ffi_type = zend_jit_ffi_type_pointer_to(op1_ffi_type, &holder); + } + if ((op1_ffi_type->kind == ZEND_FFI_TYPE_ARRAY || op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) + && (op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG + && ZEND_FFI_TYPE(op1_ffi_type->array.type)->kind < ZEND_FFI_TYPE_POINTER + && ZEND_FFI_TYPE(op1_ffi_type->array.type)->kind != ZEND_FFI_TYPE_VOID + && zend_jit_ffi_supported_type(ZEND_FFI_TYPE(op1_ffi_type->array.type))) { + return 1; + } + } + } +#endif + return 0; case ZEND_ASSIGN_DIM_OP: if (opline->result_type != IS_UNUSED) { return 0; @@ -17394,9 +17427,42 @@ static bool zend_jit_opline_supports_reg(const zend_op_array *op_array, zend_ssa return 0; } } - return ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) && - (((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) || - ((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING)); + if (((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) + && (((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) + || ((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING))) { + return 1; + } +#ifdef HAVE_FFI + if ((opline->opcode == ZEND_ASSIGN_DIM || opline->opcode == ZEND_ASSIGN_DIM_OP) + && trace + && (trace+1)->op == ZEND_JIT_TRACE_OP1_TYPE + && (trace+2)->op == ZEND_JIT_TRACE_OP1_FFI_TYPE) { + zend_ffi_type *op1_ffi_type = (zend_ffi_type*)(trace+2)->ptr; + zend_ffi_type *op3_ffi_type = NULL; + uint32_t op1_data_info = OP1_DATA_INFO(); + zend_ffi_type holder, holder2; + + if ((trace+3)->op == ZEND_JIT_TRACE_OP3_TYPE + && (trace+4)->op == ZEND_JIT_TRACE_OP3_FFI_TYPE) { + op3_ffi_type = (zend_ffi_type*)(trace+4)->ptr; + if (ZEND_FFI_TYPE_IS_OWNED(op3_ffi_type)) { + op3_ffi_type = zend_jit_ffi_type_pointer_to(op3_ffi_type, &holder2); + } + } + + if (op1_ffi_type) { + if (ZEND_FFI_TYPE_IS_OWNED(op1_ffi_type)) { + op1_ffi_type = zend_jit_ffi_type_pointer_to(op1_ffi_type, &holder); + } + if ((op1_ffi_type->kind == ZEND_FFI_TYPE_ARRAY || op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) + && (op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG + && zend_jit_ffi_compatible(op1_ffi_type->array.type, op1_data_info, op3_ffi_type)) { + return 1; + } + } + } +#endif + return 0; case ZEND_ASSIGN_OBJ_OP: if (opline->result_type != IS_UNUSED) { return 0; diff --git a/ext/opcache/jit/zend_jit_ir_ffi.c b/ext/opcache/jit/zend_jit_ir_ffi.c new file mode 100644 index 0000000000000..d94b58603e485 --- /dev/null +++ b/ext/opcache/jit/zend_jit_ir_ffi.c @@ -0,0 +1,2954 @@ +/* + * +----------------------------------------------------------------------+ + * | Zend JIT | + * +----------------------------------------------------------------------+ + * | Copyright (c) The PHP Group | + * +----------------------------------------------------------------------+ + * | This source file is subject to version 3.01 of the PHP license, | + * | that is bundled with this package in the file LICENSE, and is | + * | available through the world-wide-web at the following url: | + * | https://www.php.net/license/3_01.txt | + * | If you did not receive a copy of the PHP license and are unable to | + * | obtain it through the world-wide-web, please send a note to | + * | license@php.net so we can mail you a copy immediately. | + * +----------------------------------------------------------------------+ + * | Authors: Dmitry Stogov | + * +----------------------------------------------------------------------+ + */ + +static ir_ref jit_FFI_CDATA_PTR(zend_jit_ctx *jit, ir_ref obj_ref) +{ + return ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, ptr))); +} + +static ir_ref jit_FFI_CDATA_TYPE(zend_jit_ctx *jit, ir_ref obj_ref) +{ + return ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, type))); +} + +static ir_ref jit_FFI_CTYPE_TYPE(zend_jit_ctx *jit, ir_ref obj_ref) +{ + return ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_ctype, type))); +} + +static int zend_jit_ffi_symbols_guard(zend_jit_ctx *jit, + const zend_op *opline, + zend_ssa *ssa, + int use, + int def, + uint32_t info, + zend_jit_addr addr, + HashTable *ffi_symbols, + zend_jit_ffi_info *ffi_info); + +static ir_ref zend_jit_ffi_guard(zend_jit_ctx *jit, + const zend_op *opline, + zend_ssa *ssa, + int use, + int def, + uint32_t info, + zend_jit_addr addr, + zend_ffi_type *ffi_type, + zend_jit_ffi_info *ffi_info); + +static int zend_jit_ffi_write(zend_jit_ctx *jit, + zend_ssa *ssa, + zend_jit_ffi_info *ffi_info, + zend_ffi_type *ffi_type, + ir_ref ptr, + uint32_t val_info, + zend_jit_addr val_addr, + zend_ffi_type *val_ffi_type, + zend_jit_addr res_addr, + int res_def); + +static ir_ref zend_jit_calloc(zend_jit_ctx *jit, + size_t size, + bool persistent, + const zend_op *opline, + const zend_op_array *op_array) +{ + ir_ref ref; + + if (persistent) { + ref = ir_CALL_2(IR_ADDR, ir_CONST_FUNC(calloc), ir_CONST_LONG(1), ir_CONST_ADDR(size)); + } else { +#if ZEND_DEBUG + ref = ir_CALL_6(IR_ADDR, ir_CONST_FC_FUNC(_ecalloc), + ir_CONST_LONG(1), + ir_CONST_ADDR(size), + op_array->filename ? ir_CONST_ADDR(op_array->filename->val) : IR_NULL, + ir_CONST_U32(opline ? opline->lineno : 0), + IR_NULL, + ir_CONST_U32(0)); +#else + ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(_ecalloc), ir_CONST_LONG(1), ir_CONST_ADDR(size)); +#endif + } + return ref; +} + +static int zend_jit_ffi_init_call_sym(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + zend_ffi_symbol *sym, + HashTable *op1_ffi_symbols, + zend_jit_ffi_info *ffi_info, + ir_ref *ffi_func_ref) +{ + zend_ffi_type *type; + + ZEND_ASSERT(sym->kind == ZEND_FFI_SYM_FUNC); + type = ZEND_FFI_TYPE(sym->type); + ZEND_ASSERT(type->kind == ZEND_FFI_TYPE_FUNC); + + if (!zend_jit_ffi_symbols_guard(jit, opline, ssa, ssa_op->op1_use, -1, op1_info, op1_addr, op1_ffi_symbols, ffi_info)) { + return 0; + } + + if (type->func.abi == ZEND_FFI_ABI_FASTCALL) { + *ffi_func_ref = ir_CONST_FC_FUNC(sym->addr); + } else { + *ffi_func_ref = ir_CONST_FUNC(sym->addr); + } + return 1; +} + +static int zend_jit_ffi_init_call_obj(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + uint32_t op2_info, + zend_jit_addr op2_addr, + zend_ffi_type *op2_ffi_type, + zend_jit_ffi_info *ffi_info, + ir_ref *ffi_func_ref) +{ + ir_ref obj_ref; + zend_ffi_type *type; + + ZEND_ASSERT(op2_ffi_type->kind == ZEND_FFI_TYPE_POINTER); + type = ZEND_FFI_TYPE(op2_ffi_type->pointer.type); + ZEND_ASSERT(type->kind == ZEND_FFI_TYPE_FUNC); + + obj_ref = zend_jit_ffi_guard(jit, opline, ssa, ssa_op->op2_use, -1, op2_info, op2_addr, op2_ffi_type, ffi_info); + if (!obj_ref) { + return 0; + } + + *ffi_func_ref = obj_ref; + + return 1; +} + +static int zend_jit_ffi_send_val(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + zend_jit_addr op1_def_addr, + zend_class_entry *op1_ce, + zend_ffi_type *op1_ffi_type) +{ + zend_jit_trace_stack_frame *call = JIT_G(current_frame)->call; + zend_jit_trace_stack *stack = call->stack; + zend_ffi_type *type = (zend_ffi_type*)(void*)call->call_opline; + ir_ref ref = IR_UNUSED; + uint8_t arg_type = IS_UNDEF; + uint8_t arg_flags = 0; + + if (TRACE_FRAME_FFI_FUNC(call)) { + if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_ADDR + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_TYPEOF + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_IS_NULL + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_FREE) { + ZEND_ASSERT(opline->op2.num == 1); + ZEND_ASSERT(op1_ffi_type); + + if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_IS_NULL) { + ZEND_ASSERT(op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER); + arg_flags |= ZREG_FFI_PTR_LOAD; + } + + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 0, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 0, ref, arg_flags); + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_ALIGNOF + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_SIZEOF) { + ZEND_ASSERT(opline->op2.num == 1); + + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + ZEND_ASSERT(op1_ffi_type); + if (op1_ce == zend_ffi_api->ctype_ce) { + arg_flags |= ZREG_FFI_CTYPE; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 0, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 0, ref, arg_flags); + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_STRING) { + if (opline->op2.num == 1) { + ZEND_ASSERT(op1_ffi_type); + if (op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) { + arg_flags |= ZREG_FFI_PTR_LOAD; + } + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 0, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 0, ref, arg_flags); + } else { + ZEND_ASSERT(opline->op2.num == 2); + if (op1_info == MAY_BE_LONG) { + ref = jit_Z_LVAL(jit, op1_addr); + } else if (op1_info == MAY_BE_NULL) { + ref = IR_NULL; + } else { + ZEND_ASSERT(0 && "NIY"); + } + SET_STACK_TYPE(stack, 1, IS_LONG, 0); + SET_STACK_REF_EX(stack, 1, ref, 0); + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_MEMCPY) { + if (opline->op2.num == 1) { + ZEND_ASSERT(op1_ffi_type); + if (op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) { + arg_flags |= ZREG_FFI_PTR_LOAD; + } + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 0, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 0, ref, arg_flags); + } else if (opline->op2.num == 2) { + if (op1_ffi_type) { + if (op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) { + arg_flags |= ZREG_FFI_PTR_LOAD; + } + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 1, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 1, ref, arg_flags); + } else { + ZEND_ASSERT((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING); + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 1, IS_STRING, 0); + SET_STACK_REF_EX(stack, 1, ref, arg_flags); + } + } else { + ZEND_ASSERT(opline->op2.num == 3); + ZEND_ASSERT(op1_info == MAY_BE_LONG); + ref = jit_Z_LVAL(jit, op1_addr); + SET_STACK_TYPE(stack, 2, IS_LONG, 0); + SET_STACK_REF_EX(stack, 2, ref, 0); + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_MEMCMP) { + if (opline->op2.num == 1) { + if (op1_ffi_type) { + if (op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) { + arg_flags |= ZREG_FFI_PTR_LOAD; + } + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 0, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 0, ref, arg_flags); + } else { + ZEND_ASSERT((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING); + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 0, IS_STRING, 0); + SET_STACK_REF_EX(stack, 0, ref, arg_flags); + } + } else if (opline->op2.num == 2) { + if (op1_ffi_type) { + if (op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) { + arg_flags |= ZREG_FFI_PTR_LOAD; + } + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 1, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 1, ref, arg_flags); + } else { + ZEND_ASSERT((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING); + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 1, IS_STRING, 0); + SET_STACK_REF_EX(stack, 1, ref, arg_flags); + } + } else { + ZEND_ASSERT(opline->op2.num == 3); + ZEND_ASSERT(op1_info == MAY_BE_LONG); + ref = jit_Z_LVAL(jit, op1_addr); + SET_STACK_TYPE(stack, 2, IS_LONG, 0); + SET_STACK_REF_EX(stack, 2, ref, 0); + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_MEMSET) { + if (opline->op2.num == 1) { + ZEND_ASSERT(op1_ffi_type); + if (op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) { + arg_flags |= ZREG_FFI_PTR_LOAD; + } + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 0, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 0, ref, arg_flags); + } else if (opline->op2.num == 2) { + ZEND_ASSERT(op1_info == MAY_BE_LONG); + ref = jit_Z_LVAL(jit, op1_addr); + SET_STACK_TYPE(stack, 1, IS_LONG, 0); + SET_STACK_REF_EX(stack, 1, ref, 0); + } else { + ZEND_ASSERT(opline->op2.num == 3); + ZEND_ASSERT(op1_info == MAY_BE_LONG); + ref = jit_Z_LVAL(jit, op1_addr); + SET_STACK_TYPE(stack, 2, IS_LONG, 0); + SET_STACK_REF_EX(stack, 2, ref, 0); + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_NEW) { + if (opline->op2.num == 1) { + if (op1_ffi_type && (opline->op1_type & (IS_VAR|IS_TMP_VAR))) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 0, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 0, ref, arg_flags); + } + } else if (opline->op2.num == 2) { + ZEND_ASSERT(opline->op1_type == IS_CONST); + SET_STACK_TYPE(stack, 1, zend_is_true(RT_CONSTANT(opline, opline->op1)) ? IS_TRUE : IS_FALSE, 0); + } else { + ZEND_ASSERT(opline->op2.num == 3); + ZEND_ASSERT(opline->op1_type == IS_CONST); + SET_STACK_TYPE(stack, 2, zend_is_true(RT_CONSTANT(opline, opline->op1)) ? IS_TRUE : IS_FALSE, 0); + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_CAST) { + if (opline->op2.num == 1) { + if (op1_ffi_type && (opline->op1_type & (IS_VAR|IS_TMP_VAR))) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 0, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 0, ref, arg_flags); + } + } else { + ZEND_ASSERT(opline->op2.num == 2); + + /* Call FFI::cast() right here and pass result */ + if (op1_ffi_type) { + + ref = jit_Z_PTR(jit, op1_addr); + if (op1_info & MAY_BE_REF) { + ir_ref if_ref = ir_IF(ir_EQ(jit_Z_TYPE(jit, op1_addr), ir_CONST_U8(IS_REFERENCE))); + ir_IF_TRUE(if_ref); + ir_ref ref2 = jit_Z_PTR_ref(jit, ir_ADD_OFFSET(ref, offsetof(zend_reference, val))); + ir_MERGE_WITH_EMPTY_FALSE(if_ref); + ref = ir_PHI_2(IR_ADDR, ref2, ref); + } + + if (op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER + && type->kind != ZEND_FFI_TYPE_POINTER + && ZEND_FFI_TYPE(op1_ffi_type->pointer.type)->kind == ZEND_FFI_TYPE_VOID) { + /* automatically dereference void* pointers ??? */ + // JIT: cdata->ptr = *(void**)arg->ptr + ref = ir_LOAD_A(jit_FFI_CDATA_PTR(jit, ref)); + ref = ir_CALL_2(IR_ADDR, ir_CONST_FUNC(zend_ffi_api->cdata_create), ref, ir_CONST_ADDR(type)); + } else if (op1_ffi_type->kind == ZEND_FFI_TYPE_ARRAY + && type->kind == ZEND_FFI_TYPE_POINTER + && zend_ffi_api->is_compatible_type(ZEND_FFI_TYPE(op1_ffi_type->array.type), ZEND_FFI_TYPE(type->pointer.type))) { + ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_ffi_create_ptr), + ir_CONST_ADDR(type), jit_FFI_CDATA_PTR(jit, ref)); + SET_STACK_TYPE(stack, 1, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 1, ref, 0); + } else if (op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER + && type->kind == ZEND_FFI_TYPE_ARRAY + && zend_ffi_api->is_compatible_type(ZEND_FFI_TYPE(op1_ffi_type->pointer.type), ZEND_FFI_TYPE(type->array.type))) { + // JIT: cdata->ptr = *arg->ptr; + ref = ir_LOAD_A(jit_FFI_CDATA_PTR(jit, ref)); + ref = ir_CALL_2(IR_ADDR, ir_CONST_FUNC(zend_ffi_api->cdata_create), ref, ir_CONST_ADDR(type)); + } else if (type->size > op1_ffi_type->size) { + ZEND_ASSERT(type->size <= op1_ffi_type->size); + } else { + // TODO: holder usage ??? + ref = jit_FFI_CDATA_PTR(jit, ref); + ref = ir_CALL_2(IR_ADDR, ir_CONST_FUNC(zend_ffi_api->cdata_create), ref, ir_CONST_ADDR(type)); + } + SET_STACK_TYPE(stack, 1, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 1, ref, 0); + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + /* release argument and transfer ownweship */ + ir_ref src = jit_Z_PTR(jit, op1_addr); + + // JIT: if (!GC_DELREF(src)) + ir_ref if_not_zero = ir_IF(jit_GC_DELREF(jit, src)); + ir_IF_FALSE(if_not_zero); + + // JIT: if (Z_TYPE_P(arg) == IS_REFERENCE) + ir_ref if_ref = ir_IF(ir_EQ(jit_Z_TYPE(jit, op1_addr), ir_CONST_U8(IS_REFERENCE))); + ir_IF_TRUE(if_ref); + + // JIT: arg = Z_PTR(Z_REFVAL_P(arg)); + ir_ref src_obj = jit_Z_PTR_ref(jit, ir_ADD_OFFSET(src, offsetof(zend_reference, val))); + + // JIT: if (GC_REFCOUNT(arg_obj) == 1) + ir_ref if_one = ir_IF(ir_EQ(jit_GC_REFCOUNT(jit, src_obj), ir_CONST_U32(1))); + ir_IF_TRUE(if_one); + ir_MERGE_WITH_EMPTY_FALSE(if_ref); + src_obj = ir_PHI_2(IR_ADDR, src_obj, src); + + // JIT: if (old_cdata->flags & ZEND_FFI_FLAG_OWNED) + ir_ref a = ir_ADD_OFFSET(src_obj, offsetof(zend_ffi_cdata, flags)); + ir_ref f = ir_LOAD_U32(a); + ir_ref if_owned = ir_IF(ir_AND_U32(f, ir_CONST_U32(ZEND_FFI_FLAG_OWNED))); + ir_IF_TRUE(if_owned); + + /* transfer ownership */ + // JIT: old_cdata->flags &= ~ZEND_FFI_FLAG_OWNED; + ir_STORE(a, ir_AND_U32(f, ir_CONST_U32(~ZEND_FFI_FLAG_OWNED))); + // JIT: cdata->flags |= ZEND_FFI_FLAG_OWNED; + a = ir_ADD_OFFSET(ref, offsetof(zend_ffi_cdata, flags)); + ir_STORE(a, ir_OR_U32(ir_LOAD_U32(a), ir_CONST_U32(ZEND_FFI_FLAG_OWNED))); + + ir_MERGE_WITH_EMPTY_FALSE(if_owned); + ir_MERGE_WITH_EMPTY_FALSE(if_one); + + /* release */ + jit_ZVAL_DTOR(jit, src, op1_info, opline); + ir_MERGE_WITH_EMPTY_TRUE(if_not_zero); + } + } else if (type->kind < ZEND_FFI_TYPE_POINTER) { + /* numeric conversion */ + ref = zend_jit_calloc(jit, type->size, 0, opline, op_array); + if (!zend_jit_ffi_write(jit, NULL, NULL, type, ref, op1_info, op1_addr, op1_ffi_type, 0, -1)) { + return 0; + } + ref = ir_CALL_2(IR_ADDR, ir_CONST_FUNC(zend_ffi_api->cdata_create), ref, ir_CONST_ADDR(type)); + ir_STORE(ir_ADD_OFFSET(ref, offsetof(zend_ffi_cdata, flags)), ir_CONST_U32(ZEND_FFI_FLAG_OWNED)); + SET_STACK_TYPE(stack, 1, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 1, ref, 0); + } else if (type->kind == ZEND_FFI_TYPE_POINTER) { + if ((op1_info & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_NULL) { + ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_ffi_create_ptr), + ir_CONST_ADDR(type), IR_NULL); + SET_STACK_TYPE(stack, 1, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 1, ref, 0); + } else if ((op1_info & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) { + ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_ffi_create_ptr), + ir_CONST_ADDR(type), jit_Z_LVAL(jit, op1_addr)); + SET_STACK_TYPE(stack, 1, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 1, ref, 0); + } else { + ZEND_UNREACHABLE(); + } + } else { + ZEND_UNREACHABLE(); + } + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_TYPE) { + ZEND_ASSERT(opline->op2.num == 1); + if (op1_ffi_type && (opline->op1_type & (IS_VAR|IS_TMP_VAR))) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + if (op1_info & MAY_BE_REF) { + arg_flags |= ZREG_FFI_ZVAL_DEREF; + } + ref = jit_Z_PTR(jit, op1_addr); + SET_STACK_TYPE(stack, 0, IS_OBJECT, 0); + SET_STACK_REF_EX(stack, 0, ref, arg_flags); + } + } else { + ZEND_UNREACHABLE(); + } + return 1; + } + ZEND_ASSERT(type->kind == ZEND_FFI_TYPE_FUNC); + if (type->attr & ZEND_FFI_ATTR_VARIADIC) { + ZEND_ASSERT(TRACE_FRAME_NUM_ARGS(call) >= zend_hash_num_elements(type->func.args)); + } else { + ZEND_ASSERT(TRACE_FRAME_NUM_ARGS(call) == zend_hash_num_elements(type->func.args)); + } + ZEND_ASSERT(opline->op2.num > 0 && opline->op2.num <= TRACE_FRAME_NUM_ARGS(call)); + + if (op1_info & MAY_BE_REF) { + op1_addr = jit_ZVAL_DEREF(jit, op1_addr); + op1_info &= ~MAY_BE_REF; + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + // TODO: dtor ??? + } + } + + if (opline->op2.num - 1 < zend_hash_num_elements(type->func.args)) { + type = zend_hash_index_find_ptr(type->func.args, opline->op2.num - 1); + type = ZEND_FFI_TYPE(type); + zend_ffi_type_kind type_kind = type->kind; + + if (type_kind == ZEND_FFI_TYPE_ENUM) { + type_kind = type->enumeration.kind; + } + switch (type_kind) { + case ZEND_FFI_TYPE_FLOAT: + if (op1_info == MAY_BE_LONG) { + ref = ir_INT2F((jit_Z_LVAL(jit, op1_addr))); + } else if (op1_info == MAY_BE_DOUBLE) { + ref = ir_D2F(jit_Z_DVAL(jit, op1_addr)); + } else { + ZEND_UNREACHABLE(); + } + break; + case ZEND_FFI_TYPE_DOUBLE: + if (op1_info == MAY_BE_LONG) { + ref = ir_INT2D((jit_Z_LVAL(jit, op1_addr))); + } else if (op1_info == MAY_BE_DOUBLE) { + ref = jit_Z_DVAL(jit, op1_addr); + } else { + ZEND_UNREACHABLE(); + } + break; + case ZEND_FFI_TYPE_UINT8: + if (op1_info == MAY_BE_LONG) { + ref = ir_TRUNC_U8(jit_Z_LVAL(jit, op1_addr)); + } else { + ZEND_UNREACHABLE(); + } + break; + case ZEND_FFI_TYPE_SINT8: + if (op1_info == MAY_BE_LONG) { + ref = ir_TRUNC_I8(jit_Z_LVAL(jit, op1_addr)); + } else { + ZEND_UNREACHABLE(); + } + break; + case ZEND_FFI_TYPE_UINT16: + if (op1_info == MAY_BE_LONG) { + ref = ir_TRUNC_U16(jit_Z_LVAL(jit, op1_addr)); + } else { + ZEND_UNREACHABLE(); + } + break; + case ZEND_FFI_TYPE_SINT16: + if (op1_info == MAY_BE_LONG) { + ref = ir_TRUNC_I16(jit_Z_LVAL(jit, op1_addr)); + } else { + ZEND_UNREACHABLE(); + } + break; +#ifdef ZEND_ENABLE_ZVAL_LONG64 + case ZEND_FFI_TYPE_UINT32: + if (op1_info == MAY_BE_LONG) { + ref = ir_TRUNC_U32(jit_Z_LVAL(jit, op1_addr)); + } else { + ZEND_UNREACHABLE(); + } + break; + case ZEND_FFI_TYPE_SINT32: + if (op1_info == MAY_BE_LONG) { + ref = ir_TRUNC_I32(jit_Z_LVAL(jit, op1_addr)); + } else { + ZEND_UNREACHABLE(); + } + break; + case ZEND_FFI_TYPE_UINT64: + case ZEND_FFI_TYPE_SINT64: + if (op1_info == MAY_BE_LONG) { + ref = jit_Z_LVAL(jit, op1_addr); + } else { + ZEND_UNREACHABLE(); + } + break; +#else + case ZEND_FFI_TYPE_UINT32: + case ZEND_FFI_TYPE_SINT32: + if (op1_info == MAY_BE_LONG) { + ref = jit_Z_LVAL(jit, op1_addr); + } else { + ZEND_UNREACHABLE(); + } + break; +#endif + case ZEND_FFI_TYPE_BOOL: + if (op1_info == MAY_BE_NULL || op1_info == MAY_BE_FALSE) { + ref = IR_FALSE; + } else if (op1_info == MAY_BE_TRUE) { + ref = IR_TRUE; + } else if (op1_info == (MAY_BE_FALSE|MAY_BE_TRUE)) { + ref = ir_SUB_U8(jit_Z_TYPE(jit, op1_addr), ir_CONST_U8(IS_FALSE)); + } else if (op1_info == MAY_BE_LONG) { + ref = ir_NE(jit_Z_LVAL(jit, op1_addr), ir_CONST_LONG(0)); + } else if (op1_info == MAY_BE_DOUBLE) { + ref = ir_NE(jit_Z_DVAL(jit, op1_addr), ir_CONST_DOUBLE(0.0)); + } else { + ZEND_UNREACHABLE(); + } + break; + case ZEND_FFI_TYPE_CHAR: + if (op1_info == MAY_BE_LONG) { + ref = ir_TRUNC_C(jit_Z_LVAL(jit, op1_addr)); + } else { + ZEND_UNREACHABLE(); + } + break; + case ZEND_FFI_TYPE_POINTER: + if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING + && ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) { + arg_type = IS_STRING; + ref = jit_Z_PTR(jit, op1_addr); + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + } else if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_NULL) { + arg_type = IS_NULL; + ref = IR_NULL; + } else if (op1_ffi_type + && op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER + && (ZEND_FFI_TYPE(type->pointer.type) == ZEND_FFI_TYPE(op1_ffi_type->pointer.type) + || zend_ffi_api->is_compatible_type(ZEND_FFI_TYPE(op1_ffi_type->pointer.type), ZEND_FFI_TYPE(type->pointer.type)))) { + // TODO: guards ??? + arg_type = IS_OBJECT; + ref = jit_Z_PTR(jit, op1_addr); + arg_flags |= ZREG_FFI_PTR_LOAD; + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + } else if (op1_ffi_type + && op1_ffi_type->kind == ZEND_FFI_TYPE_ARRAY + && (ZEND_FFI_TYPE(type->pointer.type) == ZEND_FFI_TYPE(op1_ffi_type->array.type) + || zend_ffi_api->is_compatible_type(ZEND_FFI_TYPE(op1_ffi_type->pointer.type), ZEND_FFI_TYPE(type->pointer.type)))) { + // TODO: guards ??? + arg_type = IS_OBJECT; + ref = jit_Z_PTR(jit, op1_addr); + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + } else { + ZEND_UNREACHABLE(); + } + break; + default: + ZEND_UNREACHABLE(); + } + } else { + if (op1_info == MAY_BE_NULL) { + ref = IR_NULL; + } else if (op1_info == MAY_BE_FALSE) { + ref = IR_FALSE; + } else if (op1_info == MAY_BE_TRUE) { + ref = IR_TRUE; + } else if (op1_info == (MAY_BE_FALSE|MAY_BE_TRUE)) { + ref = ir_SUB_U8(jit_Z_TYPE(jit, op1_addr), ir_CONST_U8(IS_FALSE)); + } else if (op1_info == MAY_BE_LONG) { + ref = jit_Z_LVAL(jit, op1_addr); + } else if (op1_info == MAY_BE_DOUBLE) { + ref = jit_Z_DVAL(jit, op1_addr); + } else if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING) { + arg_type = IS_STRING; + ref = jit_Z_PTR(jit, op1_addr); + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + arg_flags |= ZREG_FFI_ZVAL_DTOR; + } + } else { + ZEND_UNREACHABLE(); + } + } + + SET_STACK_TYPE(stack, opline->op2.num - 1, arg_type, 0); + SET_STACK_REF_EX(stack, opline->op2.num - 1, ref, arg_flags); + + return 1; +} + +static ir_ref zend_jit_gc_deref(zend_jit_ctx *jit, ir_ref ref) +{ + ir_ref if_ref, ref2; + + if_ref = ir_IF(ir_EQ( + ir_AND_U32( + ir_LOAD_U32(ir_ADD_OFFSET(ref, offsetof(zend_refcounted_h, u.type_info))), + ir_CONST_U32(GC_TYPE_MASK)), + ir_CONST_U32(IS_REFERENCE))); + ir_IF_TRUE(if_ref); + ref2 = jit_Z_PTR_ref(jit, ir_ADD_OFFSET(ref, offsetof(zend_reference, val))); + ir_MERGE_WITH_EMPTY_FALSE(if_ref); + return ir_PHI_2(IR_ADDR, ref2, ref); +} + +static int zend_jit_ffi_do_call(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + zend_jit_addr res_addr, + zend_jit_ffi_info *ffi_info) +{ + zend_jit_trace_stack_frame *call = JIT_G(current_frame)->call; + zend_jit_trace_stack *stack = call->stack; + zend_ffi_type *type = (zend_ffi_type*)(void*)call->call_opline; + ir_ref func_ref = call->ffi_func_ref; + uint32_t i, num_args = TRACE_FRAME_NUM_ARGS(call);; + ir_type ret_type = IR_VOID; + ir_ref ref = IR_UNUSED; + zend_ffi_type_kind type_kind; + + if (TRACE_FRAME_FFI_FUNC(call)) { + if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_ADDR) { + ref = STACK_REF(stack, 0); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref = zend_jit_gc_deref(jit, ref); + } + if (STACK_FLAGS(stack, 0) & ZREG_FFI_ZVAL_DTOR) { + ref = ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_zval_ffi_addr_var), ref); + } else { + ref = ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_zval_ffi_addr), ref); + } + jit_set_Z_PTR(jit, res_addr, ref); + jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX); + ZEND_ASSERT(ssa_op->result_def >= 0); + ssa->var_info[ssa_op->result_def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[ssa_op->result_def].is_instanceof = 0; + //??? ffi_info[ssa_op->result_def].type = type; + //??? ffi_info[ssa_op->result_def].info = 0; + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_ALIGNOF) { + ref = STACK_REF(stack, 0); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref = zend_jit_gc_deref(jit, ref); + } + if (STACK_FLAGS(stack, 0) & ZREG_FFI_CTYPE) { + ref = jit_FFI_CTYPE_TYPE(jit, ref); + } else { + ref = jit_FFI_CDATA_TYPE(jit, ref); + } + // TODO: type flags ??? + ref = ir_LOAD_U32(ir_ADD_OFFSET(ref, offsetof(zend_ffi_type, align))); + if (sizeof(void*) == 8) { + ref = ir_ZEXT_L(ref); + } + jit_set_Z_LVAL(jit, res_addr, ref); + jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG); + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_SIZEOF) { + ref = STACK_REF(stack, 0); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref = zend_jit_gc_deref(jit, ref); + } + if (STACK_FLAGS(stack, 0) & ZREG_FFI_CTYPE) { + ref = jit_FFI_CTYPE_TYPE(jit, ref); + } else { + ref = jit_FFI_CDATA_TYPE(jit, ref); + } + // TODO: type flags ??? + ref = ir_LOAD_U32(ir_ADD_OFFSET(ref, offsetof(zend_ffi_type, size))); + if (sizeof(void*) == 8) { + ref = ir_ZEXT_L(ref); + } + jit_set_Z_LVAL(jit, res_addr, ref); + jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG); + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_TYPEOF) { + ref = STACK_REF(stack, 0); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref = zend_jit_gc_deref(jit, ref); + } + ref = jit_FFI_CDATA_TYPE(jit, ref); + // TODO: type flags ??? + ref = ir_CALL_1(IR_ADDR, ir_CONST_FUNC(zend_ffi_api->ctype_create), ref); + jit_set_Z_PTR(jit, res_addr, ref); + jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX); + ZEND_ASSERT(ssa_op->result_def >= 0); + ssa->var_info[ssa_op->result_def].ce = zend_ffi_api->ctype_ce; + ssa->var_info[ssa_op->result_def].is_instanceof = 0; + //??? ffi_info[ssa_op->result_def].type = type; + //??? ffi_info[ssa_op->result_def].info = 0; + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_IS_NULL) { + ref = STACK_REF(stack, 0); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref = zend_jit_gc_deref(jit, ref); + } + ref = jit_FFI_CDATA_TYPE(jit, ref); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_PTR_LOAD) { + ref = ir_LOAD_A(ref); + } + ref = ir_ADD_U32(ir_ZEXT_U32(ir_EQ(ref, IR_NULL)), ir_CONST_U32(IS_FALSE)); + jit_set_Z_TYPE_INFO_ex(jit, res_addr, ref); + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_FREE) { + ref = STACK_REF(stack, 0); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref = zend_jit_gc_deref(jit, ref); + } + ir_CALL_1(IR_VOID, ir_CONST_FUNC(zend_ffi_api->cdata_free), ref); + if (res_addr) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL); + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_STRING) { + ZEND_ASSERT(num_args > 0 && STACK_TYPE(stack, 0) == IS_OBJECT); + ref = STACK_REF(stack, 0); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref = zend_jit_gc_deref(jit, ref); + } + ref = jit_FFI_CDATA_PTR(jit, ref); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_PTR_LOAD) { + ref = ir_LOAD_A(ref); + } + if (num_args == 1 || STACK_REF(stack, 1) == IR_NULL) { + ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_string), + jit_ZVAL_ADDR(jit, res_addr), ref); + } else { + ZEND_ASSERT(num_args == 2); + ZEND_ASSERT(STACK_TYPE(stack, 1) == IS_LONG); + ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_stringl), + jit_ZVAL_ADDR(jit, res_addr), ref, STACK_REF(stack, 1)); + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_MEMCPY) { + ir_ref ref2; + + ref = STACK_REF(stack, 0); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref = zend_jit_gc_deref(jit, ref); + } + ref = jit_FFI_CDATA_PTR(jit, ref); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_PTR_LOAD) { + ref = ir_LOAD_A(ref); + } + + if (STACK_TYPE(stack, 1) == IS_OBJECT) { + ref2 = STACK_REF(stack, 1); + if (STACK_FLAGS(stack, 1) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref2 = zend_jit_gc_deref(jit, ref2); + } + ref2 = jit_FFI_CDATA_PTR(jit, ref2); + if (STACK_FLAGS(stack, 1) & ZREG_FFI_PTR_LOAD) { + ref2 = ir_LOAD_A(ref2); + } + } else { + ZEND_ASSERT(STACK_TYPE(stack, 1) == IS_STRING); + ref2 = STACK_REF(stack, 1); + if (STACK_FLAGS(stack, 1) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref2 = zend_jit_gc_deref(jit, ref2); + } + ref2 = ir_ADD_OFFSET(ref2, offsetof(zend_string, val)); + } + + ir_CALL_3(IR_VOID, ir_CONST_FUNC(memcpy), ref, ref2, STACK_REF(stack, 2)); + if (res_addr) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL); + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_MEMCMP) { + ir_ref ref2; + + if (STACK_TYPE(stack, 0) == IS_OBJECT) { + ref = STACK_REF(stack, 0); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref = zend_jit_gc_deref(jit, ref); + } + ref = jit_FFI_CDATA_PTR(jit, ref); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_PTR_LOAD) { + ref = ir_LOAD_A(ref); + } + } else { + ZEND_ASSERT(STACK_TYPE(stack, 0) == IS_STRING); + ref = STACK_REF(stack, 0); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref = zend_jit_gc_deref(jit, ref); + } + ref = ir_ADD_OFFSET(ref, offsetof(zend_string, val)); + } + + if (STACK_TYPE(stack, 1) == IS_OBJECT) { + ref2 = STACK_REF(stack, 1); + if (STACK_FLAGS(stack, 1) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref2 = zend_jit_gc_deref(jit, ref2); + } + ref2 = jit_FFI_CDATA_PTR(jit, ref2); + if (STACK_FLAGS(stack, 1) & ZREG_FFI_PTR_LOAD) { + ref2 = ir_LOAD_A(ref2); + } + } else { + ZEND_ASSERT(STACK_TYPE(stack, 1) == IS_STRING); + ref2 = STACK_REF(stack, 1); + if (STACK_FLAGS(stack, 1) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref2 = zend_jit_gc_deref(jit, ref2); + } + ref2 = ir_ADD_OFFSET(ref2, offsetof(zend_string, val)); + } + + ref = ir_CALL_3(IR_I32, ir_CONST_FUNC(memcmp), ref, ref2, STACK_REF(stack, 2)); + if (res_addr) { + /* (-1, 0, 1) = (ret > 0) - (ret < 0) */ + ref = ir_SUB_L(ir_SEXT_L(ir_GT(ref, ir_CONST_I32(0))), ir_SEXT_L(ir_LT(ref, ir_CONST_I32(0)))); + jit_set_Z_LVAL(jit, res_addr, ref); + jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG); + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_MEMSET) { + ref = STACK_REF(stack, 0); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_ZVAL_DEREF) { + // TODO: try to remove this dereference ??? + ref = zend_jit_gc_deref(jit, ref); + } + ref = jit_FFI_CDATA_PTR(jit, ref); + if (STACK_FLAGS(stack, 0) & ZREG_FFI_PTR_LOAD) { + ref = ir_LOAD_A(ref); + } + + ir_CALL_3(IR_VOID, ir_CONST_FUNC(memset), ref, STACK_REF(stack, 1), STACK_REF(stack, 2)); + if (res_addr) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL); + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_NEW) { + if (res_addr) { + uint32_t flags = ZEND_FFI_FLAG_OWNED; + + if (num_args > 1 && STACK_TYPE(stack, 1) == IS_FALSE) { + flags &= ~ZEND_FFI_FLAG_OWNED; + } + if (num_args > 2 && STACK_TYPE(stack, 2) == IS_TRUE) { + flags |= ZEND_FFI_FLAG_PERSISTENT; + } + // TODO: ZEND_FFI_FLAG_CONST flag ??? + ref = zend_jit_calloc(jit, type->size, (flags & ZEND_FFI_FLAG_PERSISTENT) != 0, opline, op_array); + ref = ir_CALL_2(IR_ADDR, ir_CONST_FUNC(zend_ffi_api->cdata_create), ref, ir_CONST_ADDR(type)); + ir_STORE(ir_ADD_OFFSET(ref, offsetof(zend_ffi_cdata, flags)), ir_CONST_U32(flags)); + jit_set_Z_PTR(jit, res_addr, ref); + jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX); + ZEND_ASSERT(ssa_op->result_def >= 0); + ssa->var_info[ssa_op->result_def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[ssa_op->result_def].is_instanceof = 0; + ffi_info[ssa_op->result_def].type = type; + ffi_info[ssa_op->result_def].info = 0; + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_CAST) { + ZEND_ASSERT(STACK_TYPE(stack, 1) == IS_OBJECT); + ref = STACK_REF(stack, 1); + if (res_addr) { + jit_set_Z_PTR(jit, res_addr, ref); + jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX); + ZEND_ASSERT(ssa_op->result_def >= 0); + ssa->var_info[ssa_op->result_def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[ssa_op->result_def].is_instanceof = 0; + ffi_info[ssa_op->result_def].type = type; + ffi_info[ssa_op->result_def].info = 0; + } else { + // TODO: release object ??? + ZEND_UNREACHABLE(); + } + } else if (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_TYPE) { + if (res_addr) { + ref = ir_CONST_ADDR(type); + ref = ir_CALL_1(IR_ADDR, ir_CONST_FUNC(zend_ffi_api->ctype_create), ref); + jit_set_Z_PTR(jit, res_addr, ref); + jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX); + ZEND_ASSERT(ssa_op->result_def >= 0); + ssa->var_info[ssa_op->result_def].ce = zend_ffi_api->ctype_ce; + ssa->var_info[ssa_op->result_def].is_instanceof = 0; + ffi_info[ssa_op->result_def].type = type; + ffi_info[ssa_op->result_def].info = 0; + } + } else { + ZEND_UNREACHABLE(); + } + goto cleanup; + } + + ZEND_ASSERT(type->kind == ZEND_FFI_TYPE_FUNC); + + type_kind = ZEND_FFI_TYPE(type->func.ret_type)->kind; + if (type_kind == ZEND_FFI_TYPE_ENUM) { + type_kind = ZEND_FFI_TYPE(type->func.ret_type)->enumeration.kind; + } + switch (type_kind) { + case ZEND_FFI_TYPE_VOID: + ret_type = IR_VOID; + break; + case ZEND_FFI_TYPE_FLOAT: + ret_type = IR_FLOAT; + break; + case ZEND_FFI_TYPE_DOUBLE: + ret_type = IR_DOUBLE; + break; + case ZEND_FFI_TYPE_UINT8: + ret_type = IR_U8; + break; + case ZEND_FFI_TYPE_SINT8: + ret_type = IR_I8; + break; + case ZEND_FFI_TYPE_UINT16: + ret_type = IR_U16; + break; + case ZEND_FFI_TYPE_SINT16: + ret_type = IR_I16; + break; + case ZEND_FFI_TYPE_UINT32: + ret_type = IR_U32; + break; + case ZEND_FFI_TYPE_SINT32: + ret_type = IR_I32; + break; +#ifdef ZEND_ENABLE_ZVAL_LONG64 + case ZEND_FFI_TYPE_UINT64: + ret_type = IR_U64; + break; + case ZEND_FFI_TYPE_SINT64: + ret_type = IR_I64; + break; +#endif + case ZEND_FFI_TYPE_BOOL: + ret_type = IR_BOOL; + break; + case ZEND_FFI_TYPE_CHAR: + ret_type = IR_CHAR; + break; + case ZEND_FFI_TYPE_POINTER: + ret_type = IR_ADDR; + break; + default: + ZEND_UNREACHABLE(); + } + + if (!IR_IS_CONST_REF(func_ref)) { + func_ref = ir_LOAD_A(jit_FFI_CDATA_PTR(jit, func_ref)); + if (type->func.abi == ZEND_FFI_ABI_FASTCALL) { + func_ref = ir_CAST_FC_FUNC(func_ref); + } + } + + if (num_args) { + ir_ref *args = alloca(sizeof(ir_ref) * num_args); + + for (i = 0; i < num_args; i++) { + uint8_t type = STACK_TYPE(stack, i); + ir_ref ref = STACK_REF(stack, i); + + if (type == IS_STRING) { + ref = ir_ADD_OFFSET(ref, offsetof(zend_string, val)); + } else if (type == IS_OBJECT) { + ref = jit_FFI_CDATA_PTR(jit, ref); + if (STACK_FLAGS(stack, i) & ZREG_FFI_PTR_LOAD) { + ref = ir_LOAD_A(ref); + } + } + args[i] = ref; + } + ref = ir_CALL_N(ret_type, func_ref, num_args, args); + } else { + ZEND_ASSERT(!type->func.args); + ref = ir_CALL(ret_type, func_ref); + } + + if (RETURN_VALUE_USED(opline)) { + zend_ffi_type *ret_type = ZEND_FFI_TYPE(type->func.ret_type); + uint32_t res_type = IS_UNDEF; + + type_kind = ret_type->kind; + if (type_kind == ZEND_FFI_TYPE_ENUM) { + type_kind = ret_type->enumeration.kind; + } + switch (type_kind) { + case ZEND_FFI_TYPE_VOID: + res_type = IS_NULL; + break; + case ZEND_FFI_TYPE_FLOAT: + jit_set_Z_DVAL(jit, res_addr, ir_F2D(ref)); + res_type = IS_DOUBLE; + break; + case ZEND_FFI_TYPE_DOUBLE: + jit_set_Z_DVAL(jit, res_addr, ref); + res_type = IS_DOUBLE; + break; + case ZEND_FFI_TYPE_UINT8: + jit_set_Z_LVAL(jit, res_addr, ir_ZEXT_L(ref)); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_SINT8: + jit_set_Z_LVAL(jit, res_addr, ir_SEXT_L(ref)); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_UINT16: + jit_set_Z_LVAL(jit, res_addr, ir_ZEXT_L(ref)); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_SINT16: + jit_set_Z_LVAL(jit, res_addr, ir_SEXT_L(ref)); + res_type = IS_LONG; + break; +#ifdef ZEND_ENABLE_ZVAL_LONG64 + case ZEND_FFI_TYPE_UINT32: + jit_set_Z_LVAL(jit, res_addr, ir_ZEXT_L(ref)); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_SINT32: + jit_set_Z_LVAL(jit, res_addr, ir_SEXT_L(ref)); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_UINT64: + jit_set_Z_LVAL(jit, res_addr, ref); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_SINT64: + jit_set_Z_LVAL(jit, res_addr, ref); + res_type = IS_LONG; + break; +#else + case ZEND_FFI_TYPE_UINT32: + jit_set_Z_LVAL(jit, res_addr, ref); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_SINT32: + jit_set_Z_LVAL(jit, res_addr, ref); + res_type = IS_LONG; + break; +#endif + case ZEND_FFI_TYPE_BOOL: + jit_set_Z_TYPE_INFO(jit, res_addr, + ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE))); + goto cleanup; + case ZEND_FFI_TYPE_CHAR: + jit_set_Z_PTR(jit, res_addr, ir_LOAD_A( + ir_ADD_A(ir_CONST_ADDR(zend_one_char_string), + ir_MUL_L(ir_ZEXT_L(ref), ir_CONST_LONG(sizeof(void*)))))); + res_type = IS_STRING; + break; + case ZEND_FFI_TYPE_POINTER: + if ((ret_type->attr & ZEND_FFI_ATTR_CONST) + && ZEND_FFI_TYPE(ret_type->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) { + ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_string), jit_ZVAL_ADDR(jit, res_addr), ref); + goto cleanup; + } else { + ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_ffi_ptr), + jit_ZVAL_ADDR(jit, res_addr), ir_CONST_ADDR(ret_type), ref); + ZEND_ASSERT(ssa_op->result_def >= 0); + ssa->var_info[ssa_op->result_def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[ssa_op->result_def].is_instanceof = 0; + ffi_info[ssa_op->result_def].type = type; + ffi_info[ssa_op->result_def].info = 0; + goto cleanup; + } + break; + default: + ZEND_UNREACHABLE(); + } + + if (Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, res_type); + } + } + +cleanup: + if (num_args) { + zend_jit_trace_stack *stack = call->stack; + + for (i = 0; i < num_args; i++) { + if (STACK_FLAGS(stack, i) & ZREG_FFI_ZVAL_DTOR) { + uint8_t type = STACK_TYPE(stack, i); + ir_ref ref = STACK_REF(stack, i); + + if (STACK_FLAGS(stack, i) & ZREG_FFI_ZVAL_DEREF) { + ir_ref if_not_zero = ir_IF(jit_GC_DELREF(jit, ref)); + ir_IF_FALSE(if_not_zero); + jit_ZVAL_DTOR(jit, ref, MAY_BE_REF|MAY_BE_ANY, opline); + ir_MERGE_WITH_EMPTY_TRUE(if_not_zero); /* don't add to GC roots */ + } else if (type == IS_STRING) { + ir_ref if_interned = ir_IF(ir_AND_U32( + ir_LOAD_U32(ir_ADD_OFFSET(ref, offsetof(zend_refcounted, gc.u.type_info))), + ir_CONST_U32(IS_STR_INTERNED))); + ir_IF_FALSE(if_interned); + ir_ref if_not_zero = ir_IF(jit_GC_DELREF(jit, ref)); + ir_IF_FALSE(if_not_zero); + jit_ZVAL_DTOR(jit, ref, MAY_BE_STRING, opline); + ir_MERGE_WITH_EMPTY_TRUE(if_not_zero); + ir_MERGE_WITH_EMPTY_TRUE(if_interned); + } else if (type == IS_OBJECT) { + ir_ref if_not_zero = ir_IF(jit_GC_DELREF(jit, ref)); + ir_IF_FALSE(if_not_zero); + jit_ZVAL_DTOR(jit, ref, MAY_BE_OBJECT, opline); + ir_MERGE_WITH_EMPTY_TRUE(if_not_zero); /* don't add to GC roots */ + } else { + ZEND_ASSERT(0); + } + } + } + } + + if (!TRACE_FRAME_FFI_FUNC(call) + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_NEW + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_CAST + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_TYPE) { + func_ref = call->ffi_func_ref; + if (func_ref && !IR_IS_CONST_REF(func_ref)) { + ir_ref if_not_zero = ir_IF(jit_GC_DELREF(jit, func_ref)); + ir_IF_FALSE(if_not_zero); + jit_ZVAL_DTOR(jit, func_ref, MAY_BE_OBJECT, opline); + ir_MERGE_WITH_EMPTY_TRUE(if_not_zero); /* don't add to GC roots */ + } + if (TRACE_FRAME_FFI_OBJ_DTOR(call)) { + ir_ref obj_ref = call->ffi_obj_ref; + + ZEND_ASSERT(obj_ref && !IR_IS_CONST_REF(obj_ref)); + ir_ref if_not_zero = ir_IF(jit_GC_DELREF(jit, obj_ref)); + ir_IF_FALSE(if_not_zero); + jit_ZVAL_DTOR(jit, obj_ref, MAY_BE_OBJECT, opline); + ir_MERGE_WITH_EMPTY_TRUE(if_not_zero); /* don't add to GC roots */ + } + } + + return 1; +} + +static int zend_jit_ffi_type_guard(zend_jit_ctx *jit, const zend_op *opline, ir_ref obj_ref, zend_ffi_type *ffi_type) +{ + int32_t exit_point = zend_jit_trace_get_exit_point(opline, 0); + const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point); + + if (!exit_addr) { + return 0; + } + + ir_GUARD(ir_EQ(ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, type))), ir_CONST_ADDR(ffi_type)), + ir_CONST_ADDR(exit_addr)); + + return 1; +} + +static int zend_jit_ffi_abc(zend_jit_ctx *jit, + const zend_op *opline, + zend_ffi_type *ffi_type, + uint32_t op2_info, + zend_jit_addr op2_addr, + zend_ssa_range *op2_range) +{ + int32_t exit_point; + const void *exit_addr; + + ZEND_ASSERT(op2_info == MAY_BE_LONG); + if (ffi_type->kind == ZEND_FFI_TYPE_ARRAY + && !(ffi_type->attr & (ZEND_FFI_ATTR_VLA|ZEND_FFI_ATTR_INCOMPLETE_ARRAY))) { + if (Z_MODE(op2_addr) == IS_CONST_ZVAL) { + zval *zv = Z_ZV(op2_addr); + ZEND_ASSERT(Z_TYPE_P(zv) == IS_LONG); + if (Z_LVAL_P(zv) < 0 || Z_LVAL_P(zv) >= ffi_type->array.length) { + /* Always out of range */ + exit_point = zend_jit_trace_get_exit_point(opline, 0); + exit_addr = zend_jit_trace_get_exit_addr(exit_point); + if (!exit_addr) { + return 0; + } + jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr)); + } + } else if (!op2_range || op2_range->min < 0 || op2_range->max >= ffi_type->array.length) { + if (op2_range && (op2_range->max < 0 || op2_range->min >= ffi_type->array.length)) { + /* Always out of range */ + exit_point = zend_jit_trace_get_exit_point(opline, 0); + exit_addr = zend_jit_trace_get_exit_addr(exit_point); + if (!exit_addr) { + return 0; + } + jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr)); + } else { + /* Array Bounds Check */ + exit_point = zend_jit_trace_get_exit_point(opline, 0); + exit_addr = zend_jit_trace_get_exit_addr(exit_point); + if (!exit_addr) { + return 0; + } + ir_GUARD(ir_LT(jit_Z_LVAL(jit, op2_addr), ir_CONST_LONG(ffi_type->array.length)), + ir_CONST_ADDR(exit_addr)); + } + } + } + + return 1; +} + +static int zend_jit_ffi_read(zend_jit_ctx *jit, + zend_ssa *ssa, + zend_jit_ffi_info *ffi_info, + zend_ffi_type *ffi_type, + ir_ref ptr, + zend_jit_addr res_addr, + int res_def) +{ + uint32_t res_type; + zend_ffi_type_kind type_kind = ffi_type->kind; + + if (type_kind == ZEND_FFI_TYPE_ENUM) { + type_kind = ffi_type->enumeration.kind; + } + switch (type_kind) { + case ZEND_FFI_TYPE_FLOAT: + jit_set_Z_DVAL(jit, res_addr, ir_F2D(ir_LOAD_F(ptr))); + res_type = IS_DOUBLE; + break; + case ZEND_FFI_TYPE_DOUBLE: + jit_set_Z_DVAL(jit, res_addr, ir_LOAD_D(ptr)); + res_type = IS_DOUBLE; + break; + case ZEND_FFI_TYPE_UINT8: + jit_set_Z_LVAL(jit, res_addr, ir_ZEXT_L(ir_LOAD_U8(ptr))); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_SINT8: + jit_set_Z_LVAL(jit, res_addr, ir_SEXT_L(ir_LOAD_I8(ptr))); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_UINT16: + jit_set_Z_LVAL(jit, res_addr, ir_ZEXT_L(ir_LOAD_U16(ptr))); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_SINT16: + jit_set_Z_LVAL(jit, res_addr, ir_SEXT_L(ir_LOAD_I16(ptr))); + res_type = IS_LONG; + break; +#ifdef ZEND_ENABLE_ZVAL_LONG64 + case ZEND_FFI_TYPE_UINT32: + jit_set_Z_LVAL(jit, res_addr, ir_ZEXT_L(ir_LOAD_U32(ptr))); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_SINT32: + jit_set_Z_LVAL(jit, res_addr, ir_SEXT_L(ir_LOAD_I32(ptr))); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_UINT64: + jit_set_Z_LVAL(jit, res_addr, ir_LOAD_U64(ptr)); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_SINT64: + jit_set_Z_LVAL(jit, res_addr, ir_LOAD_I64(ptr)); + res_type = IS_LONG; + break; +#else + case ZEND_FFI_TYPE_UINT32: + jit_set_Z_LVAL(jit, res_addr, ir_LOAD_U32(ptr)); + res_type = IS_LONG; + break; + case ZEND_FFI_TYPE_SINT32: + jit_set_Z_LVAL(jit, res_addr, ir_LOAD_I32(ptr)); + res_type = IS_LONG; + break; +#endif + case ZEND_FFI_TYPE_BOOL: + jit_set_Z_TYPE_INFO(jit, res_addr, + ir_ADD_U32(ir_ZEXT_U32(ir_LOAD_U8(ptr)), ir_CONST_U32(IS_FALSE))); + return 1; + case ZEND_FFI_TYPE_CHAR: + jit_set_Z_PTR(jit, res_addr, ir_LOAD_A( + ir_ADD_A(ir_CONST_ADDR(zend_one_char_string), + ir_MUL_L(ir_ZEXT_L(ir_LOAD_U8(ptr)), ir_CONST_LONG(sizeof(void*)))))); + res_type = IS_STRING; + break; + case ZEND_FFI_TYPE_ARRAY: + case ZEND_FFI_TYPE_STRUCT: + ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_ffi_obj), + jit_ZVAL_ADDR(jit, res_addr), ir_CONST_ADDR(ffi_type), ptr); + if (res_def >= 0) { + ssa->var_info[res_def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[res_def].is_instanceof = 0; + ffi_info[res_def].type = ffi_type; + ffi_info[res_def].info = 0; + } + return 1; + case ZEND_FFI_TYPE_POINTER: + if ((ffi_type->attr & ZEND_FFI_ATTR_CONST) + && ZEND_FFI_TYPE(ffi_type->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) { + ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_string), jit_ZVAL_ADDR(jit, res_addr), ptr); + return 1; + } else { + ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_ffi_ptr), + jit_ZVAL_ADDR(jit, res_addr), ir_CONST_ADDR(ffi_type), ir_LOAD_A(ptr)); + if (res_def >= 0) { + ssa->var_info[res_def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[res_def].is_instanceof = 0; + ffi_info[res_def].type = ffi_type; + ffi_info[res_def].info = 0; + } + return 1; + } + break; + break; + default: + ZEND_UNREACHABLE(); + } + + if (Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, res_type); + } + + return 1; +} + +static ir_ref zend_jit_ffi_guard(zend_jit_ctx *jit, + const zend_op *opline, + zend_ssa *ssa, + int use, + int def, + uint32_t info, + zend_jit_addr addr, + zend_ffi_type *ffi_type, + zend_jit_ffi_info *ffi_info) +{ + ir_ref ref; + + /* MAY_BE_GUARD may be added by zend_jit_fetch_reference() */ + ZEND_ASSERT((info & (/*MAY_BE_GUARD|*/MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_OBJECT); + + ref = jit_Z_PTR(jit, addr); + if (ssa->var_info && use >= 0) { + if (ssa->var_info[use].ce != zend_ffi_api->cdata_ce) { + if (!zend_jit_class_guard(jit, opline, ref, zend_ffi_api->cdata_ce)) { + return 0; + } + ssa->var_info[use].type |= MAY_BE_CLASS_GUARD; + ssa->var_info[use].ce = zend_ffi_api->cdata_ce; + ssa->var_info[use].is_instanceof = 0; + } + if (def >= 0) { + ssa->var_info[def].type |= MAY_BE_CLASS_GUARD; + ssa->var_info[def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[def].is_instanceof = 0; + } + } + + if (ffi_info && use >= 0) { + if (ffi_info[use].type != ffi_type + || (ffi_info[use].info & FFI_TYPE_GUARD)) { + if (!zend_jit_ffi_type_guard(jit, opline, ref, ffi_type)) { + return 0; + } + ffi_info[use].info &= ~FFI_TYPE_GUARD; + ffi_info[use].type = ffi_type; + } + if (def >= 0) { + ffi_info[def].info &= ~FFI_TYPE_GUARD; + ffi_info[def].type = ffi_type; + } + } + + return ref; +} + +static int zend_jit_ffi_symbols_guard(zend_jit_ctx *jit, + const zend_op *opline, + zend_ssa *ssa, + int use, + int def, + uint32_t info, + zend_jit_addr addr, + HashTable *ffi_symbols, + zend_jit_ffi_info *ffi_info) +{ + ir_ref ref = IR_UNUSED; + + ZEND_ASSERT((info & (MAY_BE_GUARD|MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_OBJECT); + + if (ssa->var_info && use >= 0) { + if (ssa->var_info[use].ce != zend_ffi_api->scope_ce) { + ref = jit_Z_PTR(jit, addr); + if (!zend_jit_class_guard(jit, opline, ref, zend_ffi_api->scope_ce)) { + return 0; + } + ssa->var_info[use].type |= MAY_BE_CLASS_GUARD; + ssa->var_info[use].ce = zend_ffi_api->scope_ce; + ssa->var_info[use].is_instanceof = 0; + } + if (def >= 0) { + ssa->var_info[def].type |= MAY_BE_CLASS_GUARD; + ssa->var_info[def].ce = zend_ffi_api->scope_ce; + ssa->var_info[def].is_instanceof = 0; + } + } + + if (ffi_info && use >= 0) { + if (ffi_info[use].symbols != ffi_symbols + || (ffi_info[use].info & FFI_SYMBOLS_GUARD)) { + if (!ref) { + ref = jit_Z_PTR(jit, addr); + } + + int32_t exit_point = zend_jit_trace_get_exit_point(opline, 0); + const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point); + + if (!exit_addr) { + return 0; + } + + ir_GUARD(ir_EQ(ir_LOAD_A(ir_ADD_OFFSET(ref, offsetof(zend_ffi, symbols))), ir_CONST_ADDR(ffi_symbols)), + ir_CONST_ADDR(exit_addr)); + + ffi_info[use].info &= ~FFI_SYMBOLS_GUARD; + ffi_info[use].symbols = ffi_symbols; + } + if (def >= 0) { + ffi_info[def].info &= ~FFI_SYMBOLS_GUARD; + ffi_info[def].symbols = ffi_symbols; + } + } + + return 1; +} + +static int zend_jit_ffi_fetch_dim(zend_jit_ctx *jit, + const zend_op *opline, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + bool op1_avoid_refcounting, + uint32_t op2_info, + zend_jit_addr op2_addr, + zend_ssa_range *op2_range, + uint32_t res_info, + zend_jit_addr res_addr, + zend_ffi_type *op1_ffi_type, + zend_jit_ffi_info *ffi_info) +{ + zend_ffi_type *el_type = ZEND_FFI_TYPE(op1_ffi_type->array.type); + ir_ref obj_ref; + + obj_ref = zend_jit_ffi_guard(jit, opline, ssa, ssa_op->op1_use, -1, op1_info, op1_addr, op1_ffi_type, ffi_info); + if (!obj_ref) { + return 0; + } + + if (!zend_jit_ffi_abc(jit, opline, op1_ffi_type, op2_info, op2_addr, op2_range)) { + return 0; + } + + ir_ref cdata_ref = jit_FFI_CDATA_PTR(jit, obj_ref); +// ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, ptr))); + + if (op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) { + cdata_ref = ir_LOAD_A(cdata_ref); + } + + ir_ref ptr = ir_ADD_A(cdata_ref, ir_MUL_L(jit_Z_LVAL(jit, op2_addr), ir_CONST_LONG(el_type->size))); + + if (opline->opcode == ZEND_FETCH_DIM_W || opline->opcode == ZEND_FETCH_DIM_RW) { + jit_set_Z_PTR(jit, res_addr, + ir_CALL_2(IR_ADDR, ir_CONST_FUNC(zend_ffi_api->cdata_create), + ptr, ir_CONST_ADDR(el_type))); + jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX); + ZEND_ASSERT(ssa_op->result_def >= 0); + ssa->var_info[ssa_op->result_def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[ssa_op->result_def].is_instanceof = 0; + ffi_info[ssa_op->result_def].type = el_type; + ffi_info[ssa_op->result_def].info = 0; + } else { + if (!zend_jit_ffi_read(jit, ssa, ffi_info, el_type, ptr, res_addr, ssa_op->result_def)) { + return 0; + } + } + + if (res_info & MAY_BE_GUARD) { + // TODO: ??? + ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD; + } + + if (opline->opcode != ZEND_FETCH_LIST_R && !op1_avoid_refcounting && !op1_indirect) { + if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + } + + return 1; +} + +static int zend_jit_ffi_write(zend_jit_ctx *jit, + zend_ssa *ssa, + zend_jit_ffi_info *ffi_info, + zend_ffi_type *ffi_type, + ir_ref ptr, + uint32_t val_info, + zend_jit_addr val_addr, + zend_ffi_type *val_ffi_type, + zend_jit_addr res_addr, + int res_def) +{ + ir_ref ref = IR_UNUSED; + zend_ffi_type_kind type_kind = ffi_type->kind; + + if (type_kind == ZEND_FFI_TYPE_ENUM) { + type_kind = ffi_type->enumeration.kind; + } + switch (type_kind) { + case ZEND_FFI_TYPE_FLOAT: + if (val_info == MAY_BE_LONG) { + ref = ir_INT2F(jit_Z_LVAL(jit, val_addr)); + } else if (val_info == MAY_BE_DOUBLE) { + ref = ir_D2F(jit_Z_DVAL(jit, val_addr)); + } else if (val_ffi_type && val_ffi_type->kind == ffi_type->kind) { + ref = ir_LOAD_F(jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr))); + } else { + ZEND_UNREACHABLE(); + } + ir_STORE(ptr, ref); + if (res_addr) { + ref = ir_F2D(ref); + jit_set_Z_DVAL(jit, res_addr, ref); + if (Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE); + } + } + break; + case ZEND_FFI_TYPE_DOUBLE: + if (val_info == MAY_BE_LONG) { + ref = ir_INT2D(jit_Z_LVAL(jit, val_addr)); + } else if (val_info == MAY_BE_DOUBLE) { + ref = jit_Z_DVAL(jit, val_addr); + } else if (val_ffi_type && val_ffi_type->kind == ffi_type->kind) { + ref = ir_LOAD_D(jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr))); + } else { + ZEND_UNREACHABLE(); + } + ir_STORE(ptr, ref); + if (res_addr) { + jit_set_Z_DVAL(jit, res_addr, ref); + if (Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE); + } + } + break; + case ZEND_FFI_TYPE_BOOL: + if (val_info == MAY_BE_FALSE) { + ir_STORE(ptr, IR_FALSE); + if (res_addr) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_FALSE); + } + return 1; + } else if (val_info == MAY_BE_TRUE) { + ir_STORE(ptr, IR_TRUE); + if (res_addr) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_TRUE); + } + return 1; + } else if (val_info == (MAY_BE_FALSE|MAY_BE_TRUE)) { + if (res_addr) { + ref = jit_Z_TYPE_INFO(jit, val_addr); + jit_set_Z_TYPE_INFO_ex(jit, res_addr, ref); + ref = ir_TRUNC_U8(ref); + } else { + ref = jit_Z_TYPE(jit, val_addr); + } + ir_STORE(ptr, ir_SUB_U8(ref, ir_CONST_U8(IS_FALSE))); + return 1; + } + ZEND_FALLTHROUGH; + case ZEND_FFI_TYPE_UINT8: + if (val_info == MAY_BE_LONG) { + ref = ir_TRUNC_U8(jit_Z_LVAL(jit, val_addr)); + } else if (val_ffi_type && val_ffi_type->kind == ffi_type->kind) { + ref = ir_LOAD_U8(jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr))); + } else { + ZEND_UNREACHABLE(); + } + ir_STORE(ptr, ref); + if (res_addr) { + ref = ir_ZEXT_L(ref); + jit_set_Z_LVAL(jit, res_addr, ref); + if (Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG); + } + } + break; + case ZEND_FFI_TYPE_CHAR: + if ((val_info & (MAY_BE_GUARD|MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING) { + ir_ref str = jit_Z_PTR(jit, val_addr); + + // TODO: ZSTR_LEN() == 1 ??? + ref = ir_LOAD_C(ir_ADD_OFFSET(str, offsetof(zend_string, val))); + ir_STORE(ptr, ref); + if (res_addr) { + ir_ref type_info = jit_Z_TYPE_INFO(jit, val_addr); + ir_ref if_refcounted = ir_IF(ir_AND_U32(type_info, ir_CONST_U32(0xff00))); + + ir_IF_TRUE(if_refcounted); + jit_GC_ADDREF(jit, str); + ir_MERGE_WITH_EMPTY_FALSE(if_refcounted); + jit_set_Z_PTR(jit, res_addr, str); + jit_set_Z_TYPE_INFO_ex(jit, res_addr, type_info); + } + return 1; + } + ZEND_FALLTHROUGH; + case ZEND_FFI_TYPE_SINT8: + if (val_info == MAY_BE_LONG) { + ref = ir_TRUNC_I8(jit_Z_LVAL(jit, val_addr)); + } else if (val_ffi_type && val_ffi_type->kind == ffi_type->kind) { + ref = ir_LOAD_I8(jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr))); + } else { + ZEND_UNREACHABLE(); + } + ir_STORE(ptr, ref); + if (res_addr) { + ref = ir_SEXT_L(ref); + jit_set_Z_LVAL(jit, res_addr, ref); + if (Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG); + } + } + break; + case ZEND_FFI_TYPE_UINT16: + if (val_info == MAY_BE_LONG) { + ref = ir_TRUNC_U16(jit_Z_LVAL(jit, val_addr)); + } else if (val_ffi_type && val_ffi_type->kind == ffi_type->kind) { + ref = ir_LOAD_U16(jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr))); + } else { + ZEND_UNREACHABLE(); + } + ir_STORE(ptr, ref); + if (res_addr) { + ref = ir_ZEXT_L(ref); + jit_set_Z_LVAL(jit, res_addr, ref); + if (Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG); + } + } + break; + case ZEND_FFI_TYPE_SINT16: + if (val_info == MAY_BE_LONG) { + ref = ir_TRUNC_I16(jit_Z_LVAL(jit, val_addr)); + } else if (val_ffi_type && val_ffi_type->kind == ffi_type->kind) { + ref = ir_LOAD_I16(jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr))); + } else { + ZEND_UNREACHABLE(); + } + ir_STORE(ptr, ref); + if (res_addr) { + ref = ir_SEXT_L(ref); + jit_set_Z_LVAL(jit, res_addr, ref); + if (Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG); + } + } + break; +#ifdef ZEND_ENABLE_ZVAL_LONG64 + case ZEND_FFI_TYPE_UINT32: + if (val_info == MAY_BE_LONG) { + ref = ir_TRUNC_U32(jit_Z_LVAL(jit, val_addr)); + } else if (val_ffi_type && val_ffi_type->kind == ffi_type->kind) { + ref = ir_LOAD_U32(jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr))); + } else { + ZEND_UNREACHABLE(); + } + ir_STORE(ptr, ref); + if (res_addr) { + ref = ir_ZEXT_L(ref); + jit_set_Z_LVAL(jit, res_addr, ref); + if (Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG); + } + } + break; + case ZEND_FFI_TYPE_SINT32: + if (val_info == MAY_BE_LONG) { + ref = ir_TRUNC_I32(jit_Z_LVAL(jit, val_addr)); + } else if (val_ffi_type && val_ffi_type->kind == ffi_type->kind) { + ref = ir_LOAD_I32(jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr))); + } else { + ZEND_UNREACHABLE(); + } + ir_STORE(ptr, ref); + if (res_addr) { + ref = ir_SEXT_L(ref); + jit_set_Z_LVAL(jit, res_addr, ref); + if (Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG); + } + } + break; + case ZEND_FFI_TYPE_UINT64: + case ZEND_FFI_TYPE_SINT64: + if (val_info == MAY_BE_LONG) { + ref = jit_Z_LVAL(jit, val_addr); + } else if (val_ffi_type && val_ffi_type->kind == ffi_type->kind) { + ref = ir_LOAD_I64(jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr))); + } else { + ZEND_UNREACHABLE(); + } + ir_STORE(ptr, ref); + if (res_addr) { + jit_set_Z_LVAL(jit, res_addr, ref); + if (Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG); + } + } + break; +#else + case ZEND_FFI_TYPE_UINT32: + case ZEND_FFI_TYPE_SINT32: + if (val_info == MAY_BE_LONG) { + ref = jit_Z_LVAL(jit, val_addr); + } else if (val_ffi_type && val_ffi_type->kind == ffi_type->kind) { + ref = ir_LOAD_I32(jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr))); + } else { + ZEND_UNREACHABLE(); + } + ir_STORE(ptr, ref); + if (res_addr) { + jit_set_Z_LVAL(jit, res_addr, ref); + if (Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG); + } + } + break; +#endif + case ZEND_FFI_TYPE_POINTER: + if (val_info == MAY_BE_NULL) { + ref = IR_NULL; + } else if (val_ffi_type + && val_ffi_type->kind == ZEND_FFI_TYPE_POINTER + && (val_ffi_type == ffi_type + || ZEND_FFI_TYPE(val_ffi_type->pointer.type) == ZEND_FFI_TYPE(ffi_type->pointer.type) + || ZEND_FFI_TYPE(val_ffi_type->pointer.type)->kind == ZEND_FFI_TYPE_VOID + || ZEND_FFI_TYPE(ffi_type->pointer.type)->kind == ZEND_FFI_TYPE_VOID)) { + ref = ir_LOAD_A(jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr))); + } else if (val_ffi_type + && val_ffi_type->kind == ZEND_FFI_TYPE_ARRAY + && (ZEND_FFI_TYPE(val_ffi_type->pointer.type) == ZEND_FFI_TYPE(ffi_type->pointer.type) + || ZEND_FFI_TYPE(ffi_type->pointer.type)->kind == ZEND_FFI_TYPE_VOID)) { + ref = jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr)); + } else { + ZEND_UNREACHABLE(); + } + ir_STORE(ptr, ref); + if (res_addr) { + ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_ffi_ptr), + jit_ZVAL_ADDR(jit, res_addr), ir_CONST_ADDR(ffi_type), ref); + if (res_def >= 0) { + ssa->var_info[res_def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[res_def].is_instanceof = 0; + ffi_info[res_def].type = ffi_type; + ffi_info[res_def].info = 0; + } + } + break; + default: + if (val_ffi_type + && (val_ffi_type == ffi_type + || (zend_ffi_api->is_compatible_type(ffi_type, val_ffi_type) + && ffi_type->size == val_ffi_type->size))) { + ref = jit_FFI_CDATA_PTR(jit, jit_Z_PTR(jit, val_addr)); + ir_CALL_3(IR_ADDR, ir_CONST_FUNC(memcpy), ptr, ref, ir_CONST_LONG(ffi_type->size)); + if (res_addr) { + ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_ffi_obj), + jit_ZVAL_ADDR(jit, res_addr), ir_CONST_ADDR(ffi_type), ref); + if (res_def >= 0) { + ssa->var_info[res_def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[res_def].is_instanceof = 0; + ffi_info[res_def].type = ffi_type; + ffi_info[res_def].info = 0; + } + } + break; + } + ZEND_UNREACHABLE(); + } + + return 1; +} + +static int zend_jit_ffi_assign_dim(zend_jit_ctx *jit, + const zend_op *opline, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + uint32_t op2_info, + zend_jit_addr op2_addr, + zend_ssa_range *op2_range, + uint32_t val_info, + zend_jit_addr val_addr, + zend_jit_addr val_def_addr, + zend_jit_addr res_addr, + zend_ffi_type *op1_ffi_type, + zend_ffi_type *val_ffi_type, + zend_jit_ffi_info *ffi_info) +{ + zend_ffi_type *el_type = ZEND_FFI_TYPE(op1_ffi_type->array.type); + ir_ref obj_ref; + + obj_ref = zend_jit_ffi_guard(jit, opline, ssa, ssa_op->op1_use, ssa_op->op1_def, op1_info, op1_addr, op1_ffi_type, ffi_info); + if (!obj_ref) { + return 0; + } + + if (val_addr != val_def_addr && val_def_addr) { + if (!zend_jit_update_regs(jit, (opline+1)->op1.var, val_addr, val_def_addr, val_info)) { + return 0; + } + if (Z_MODE(val_def_addr) == IS_REG && Z_MODE(val_addr) != IS_REG) { + val_addr = val_def_addr; + } + } + + if (!zend_jit_ffi_abc(jit, opline, op1_ffi_type, op2_info, op2_addr, op2_range)) { + return 0; + } + + ir_ref cdata_ref = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, ptr))); + + if (op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) { + cdata_ref = ir_LOAD_A(cdata_ref); + } + + ir_ref ptr = ir_ADD_A(cdata_ref, ir_MUL_L(jit_Z_LVAL(jit, op2_addr), ir_CONST_LONG(el_type->size))); + + ZEND_ASSERT(!res_addr || RETURN_VALUE_USED(opline)); + + if (!zend_jit_ffi_write(jit, ssa, ffi_info, el_type, ptr, val_info, val_addr, val_ffi_type, res_addr, ssa_op->result_def)) { + return 0; + } + + jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, val_info, opline); + + if (!op1_indirect) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + + return 1; +} + +static int zend_jit_ffi_assign_op_helper(zend_jit_ctx *jit, + const zend_op *opline, + uint8_t opcode, + zend_ffi_type *el_type, + ir_ref op1, + uint32_t op2_info, + zend_jit_addr op2_addr) +{ + ir_op op; + ir_type type; + ir_ref op2; + + switch (opcode) { + case ZEND_ADD: + op = IR_ADD; + break; + case ZEND_SUB: + op = IR_SUB; + break; + case ZEND_MUL: + op = IR_MUL; + break; + case ZEND_BW_OR: + op = IR_OR; + break; + case ZEND_BW_AND: + op = IR_AND; + break; + case ZEND_BW_XOR: + op = IR_XOR; + break; + case ZEND_SL: + // TODO: negative shift + op = IR_SHL; + break; + case ZEND_SR: + // TODO: negative shift + op = IR_SAR; /* TODO: SAR or SHR */ + break; + case ZEND_MOD: + // TODO: mod by zero and -1 + op = IR_MOD; + break; + default: + ZEND_UNREACHABLE(); + return 0; + } + + zend_ffi_type_kind type_kind = el_type->kind; + + if (type_kind == ZEND_FFI_TYPE_ENUM) { + type_kind = el_type->enumeration.kind; + } + switch (type_kind) { + case ZEND_FFI_TYPE_FLOAT: + ZEND_ASSERT(op == IR_ADD || op == IR_SUB || op == IR_MUL); + type = IR_FLOAT; + if (op2_info == MAY_BE_LONG) { + op2 = ir_INT2F(jit_Z_LVAL(jit, op2_addr)); + } else if (op2_info == MAY_BE_DOUBLE) { + op2 = ir_D2F(jit_Z_DVAL(jit, op2_addr)); + } else { + ZEND_UNREACHABLE(); + return 0; + } + break; + case ZEND_FFI_TYPE_DOUBLE: + ZEND_ASSERT(op == IR_ADD || op == IR_SUB || op == IR_MUL); + type = IR_DOUBLE; + if (op2_info == MAY_BE_LONG) { + op2 = ir_INT2D(jit_Z_LVAL(jit, op2_addr)); + } else if (op2_info == MAY_BE_DOUBLE) { + op2 = jit_Z_DVAL(jit, op2_addr); + } else { + ZEND_UNREACHABLE(); + return 0; + } + break; + case ZEND_FFI_TYPE_BOOL: + type = IR_U8; + ZEND_ASSERT(opcode == ZEND_BW_AND || opcode == ZEND_BW_OR); + if (op2_info == MAY_BE_LONG) { + op2 = ir_TRUNC_U8(jit_Z_LVAL(jit, op2_addr)); + } else { + ZEND_UNREACHABLE(); + return 0; + } + break; + case ZEND_FFI_TYPE_UINT8: + type = IR_U8; + if (op2_info == MAY_BE_LONG) { + op2 = ir_TRUNC_U8(jit_Z_LVAL(jit, op2_addr)); + } else { + ZEND_UNREACHABLE(); + return 0; + } + break; + case ZEND_FFI_TYPE_SINT8: + case ZEND_FFI_TYPE_CHAR: + type = IR_I8; + if (op2_info == MAY_BE_LONG) { + op2 = ir_TRUNC_I8(jit_Z_LVAL(jit, op2_addr)); + } else { + ZEND_UNREACHABLE(); + return 0; + } + break; + case ZEND_FFI_TYPE_UINT16: + type = IR_U16; + if (op2_info == MAY_BE_LONG) { + op2 = ir_TRUNC_U16(jit_Z_LVAL(jit, op2_addr)); + } else { + ZEND_UNREACHABLE(); + return 0; + } + break; + case ZEND_FFI_TYPE_SINT16: + type = IR_I16; + if (op2_info == MAY_BE_LONG) { + op2 = ir_TRUNC_I16(jit_Z_LVAL(jit, op2_addr)); + } else { + ZEND_UNREACHABLE(); + return 0; + } + break; +#ifdef ZEND_ENABLE_ZVAL_LONG64 + case ZEND_FFI_TYPE_UINT32: + type = IR_U32; + if (op2_info == MAY_BE_LONG) { + op2 = ir_TRUNC_U32(jit_Z_LVAL(jit, op2_addr)); + } else { + ZEND_UNREACHABLE(); + return 0; + } + break; + case ZEND_FFI_TYPE_SINT32: + type = IR_I32; + if (op2_info == MAY_BE_LONG) { + op2 = ir_TRUNC_I32(jit_Z_LVAL(jit, op2_addr)); + } else { + ZEND_UNREACHABLE(); + return 0; + } + break; + case ZEND_FFI_TYPE_UINT64: + case ZEND_FFI_TYPE_SINT64: + type = IR_I64; + if (op2_info == MAY_BE_LONG) { + op2 = jit_Z_LVAL(jit, op2_addr); + } else { + ZEND_UNREACHABLE(); + return 0; + } + break; +#else + case ZEND_FFI_TYPE_UINT32: + case ZEND_FFI_TYPE_SINT32: + type = IR_I32; + if (op2_info == MAY_BE_LONG) { + op2 = jit_Z_LVAL(jit, op2_addr); + } else { + ZEND_UNREACHABLE(); + return 0; + } + break; +#endif + case ZEND_FFI_TYPE_POINTER: + ZEND_ASSERT(opcode == ZEND_ADD || opcode == ZEND_SUB); + ZEND_ASSERT(ZEND_FFI_TYPE(el_type->pointer.type)->size != 0); + type = IR_ADDR; + if (op2_info == MAY_BE_LONG) { + op2 = ir_MUL_A(jit_Z_LVAL(jit, op2_addr), ir_CONST_LONG(ZEND_FFI_TYPE(el_type->pointer.type)->size)); + } else { + ZEND_UNREACHABLE(); + return 0; + } + break; + default: + ZEND_UNREACHABLE(); + return 0; + } + + ir_STORE(op1, ir_BINARY_OP(op, type, ir_LOAD(type, op1), op2)); + + return 1; +} + +static int zend_jit_ffi_assign_dim_op(zend_jit_ctx *jit, + const zend_op *opline, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + uint32_t op1_def_info, + zend_jit_addr op1_addr, + bool op1_indirect, + uint32_t op2_info, + zend_jit_addr op2_addr, + zend_ssa_range *op2_range, + uint32_t op1_data_info, + zend_jit_addr op3_addr, + zend_ssa_range *op1_data_range, + zend_ffi_type *op1_ffi_type, + zend_jit_ffi_info *ffi_info) +{ + zend_ffi_type *el_type = ZEND_FFI_TYPE(op1_ffi_type->array.type); + ir_ref obj_ref; + + obj_ref = zend_jit_ffi_guard(jit, opline, ssa, ssa_op->op1_use, ssa_op->op1_def, op1_info, op1_addr, op1_ffi_type, ffi_info); + if (!obj_ref) { + return 0; + } + + if (!zend_jit_ffi_abc(jit, opline, op1_ffi_type, op2_info, op2_addr, op2_range)) { + return 0; + } + + ir_ref cdata_ref = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, ptr))); + + if (op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) { + cdata_ref = ir_LOAD_A(cdata_ref); + } + + ir_ref ptr = ir_ADD_A(cdata_ref, ir_MUL_L(jit_Z_LVAL(jit, op2_addr), ir_CONST_LONG(el_type->size))); + + if (!zend_jit_ffi_assign_op_helper(jit, opline, opline->extended_value, + el_type, ptr, op1_data_info, op3_addr)) { + return 0; + } + + jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, op1_data_info, opline); + + if (!op1_indirect) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + + return 1; +} + +static int zend_jit_ffi_fetch_obj(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + bool op1_avoid_refcounting, + zend_ffi_field *field, + zend_jit_addr res_addr, + zend_ffi_type *op1_ffi_type, + zend_jit_ffi_info *ffi_info) +{ + uint32_t res_info = RES_INFO(); + zend_ffi_type *field_type = ZEND_FFI_TYPE(field->type); + ir_ref obj_ref; + + obj_ref = zend_jit_ffi_guard(jit, opline, ssa, ssa_op->op1_use, -1, op1_info, op1_addr, op1_ffi_type, ffi_info); + if (!obj_ref) { + return 0; + } + + ir_ref cdata_ref = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, ptr))); + ir_ref ptr = ir_ADD_A(cdata_ref, ir_CONST_LONG(field->offset)); + + if (opline->opcode == ZEND_FETCH_OBJ_W) { + jit_set_Z_PTR(jit, res_addr, + ir_CALL_2(IR_ADDR, ir_CONST_FUNC(zend_ffi_api->cdata_create), + ptr, ir_CONST_ADDR(field_type))); + jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX); + ZEND_ASSERT(ssa_op->result_def >= 0); + ssa->var_info[ssa_op->result_def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[ssa_op->result_def].is_instanceof = 0; + ffi_info[ssa_op->result_def].type = op1_ffi_type; + ffi_info[ssa_op->result_def].info = 0; + } else { + if (!zend_jit_ffi_read(jit, ssa, ffi_info, field_type, ptr, res_addr, ssa_op->result_def)) { + return 0; + } + } + + if (res_info & MAY_BE_GUARD) { + // TODO: ??? + ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD; + } + + if (!op1_avoid_refcounting && !op1_indirect) { + if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + } + + return 1; +} + +static int zend_jit_ffi_fetch_val(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + bool op1_avoid_refcounting, + zend_jit_addr res_addr, + zend_ffi_type *op1_ffi_type, + zend_jit_ffi_info *ffi_info) +{ + uint32_t res_info = RES_INFO(); + ir_ref obj_ref; + + obj_ref = zend_jit_ffi_guard(jit, opline, ssa, ssa_op->op1_use, -1, op1_info, op1_addr, op1_ffi_type, ffi_info); + if (!obj_ref) { + return 0; + } + + ir_ref ptr = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, ptr))); + + if (opline->opcode == ZEND_FETCH_OBJ_W) { + jit_set_Z_PTR(jit, res_addr, + ir_CALL_2(IR_ADDR, ir_CONST_FUNC(zend_ffi_api->cdata_create), + ptr, ir_CONST_ADDR(op1_ffi_type))); + jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX); + ZEND_ASSERT(ssa_op->result_def >= 0); + ssa->var_info[ssa_op->result_def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[ssa_op->result_def].is_instanceof = 0; + ffi_info[ssa_op->result_def].type = op1_ffi_type; + ffi_info[ssa_op->result_def].info = 0; + } else { + if (!zend_jit_ffi_read(jit, ssa, ffi_info, op1_ffi_type, ptr, res_addr, ssa_op->result_def)) { + return 0; + } + } + + if (res_info & MAY_BE_GUARD) { + // TODO: ??? + ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD; + } + + if (!op1_avoid_refcounting && !op1_indirect) { + if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + } + + return 1; +} + +static int zend_jit_ffi_fetch_sym(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + bool op1_avoid_refcounting, + zend_ffi_symbol *sym, + zend_jit_addr res_addr, + HashTable *op1_ffi_symbols, + zend_jit_ffi_info *ffi_info) +{ + uint32_t res_info = RES_INFO(); + zend_ffi_type *sym_type = ZEND_FFI_TYPE(sym->type); + + if (!zend_jit_ffi_symbols_guard(jit, opline, ssa, ssa_op->op1_use, -1, op1_info, op1_addr, op1_ffi_symbols, ffi_info)) { + return 0; + } + + ir_ref ptr = ir_CONST_ADDR(sym->addr); + + if (opline->opcode == ZEND_FETCH_OBJ_W) { + jit_set_Z_PTR(jit, res_addr, + ir_CALL_2(IR_ADDR, ir_CONST_FUNC(zend_ffi_api->cdata_create), + ptr, ir_CONST_ADDR(sym_type))); + jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX); + ZEND_ASSERT(ssa_op->result_def >= 0); + ssa->var_info[ssa_op->result_def].ce = zend_ffi_api->cdata_ce; + ssa->var_info[ssa_op->result_def].is_instanceof = 0; + ffi_info[ssa_op->result_def].type = sym_type; + ffi_info[ssa_op->result_def].info = 0; + } else { + if (!zend_jit_ffi_read(jit, ssa, ffi_info, sym_type, ptr, res_addr, ssa_op->result_def)) { + return 0; + } + } + + if (res_info & MAY_BE_GUARD) { + // TODO: ??? + ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD; + } + + if (!op1_avoid_refcounting && !op1_indirect) { + if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + } + + return 1; +} + +static int zend_jit_ffi_assign_obj(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + zend_ffi_field *field, + uint32_t val_info, + zend_jit_addr val_addr, + zend_jit_addr val_def_addr, + zend_jit_addr res_addr, + zend_ffi_type *op1_ffi_type, + zend_ffi_type *val_ffi_type, + zend_jit_ffi_info *ffi_info) +{ + zend_ffi_type *field_type = ZEND_FFI_TYPE(field->type); + ir_ref obj_ref; + + obj_ref = zend_jit_ffi_guard(jit, opline, ssa, ssa_op->op1_use, ssa_op->op1_def, op1_info, op1_addr, op1_ffi_type, ffi_info); + if (!obj_ref) { + return 0; + } + + if (val_addr != val_def_addr && val_def_addr) { + if (!zend_jit_update_regs(jit, (opline+1)->op1.var, val_addr, val_def_addr, val_info)) { + return 0; + } + if (Z_MODE(val_def_addr) == IS_REG && Z_MODE(val_addr) != IS_REG) { + val_addr = val_def_addr; + } + } + + ir_ref cdata_ref = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, ptr))); + ir_ref ptr = ir_ADD_A(cdata_ref, ir_CONST_LONG(field->offset)); + + ZEND_ASSERT(!res_addr || RETURN_VALUE_USED(opline)); + + if (!zend_jit_ffi_write(jit, ssa, ffi_info, field_type, ptr, val_info, val_addr, val_ffi_type, res_addr, ssa_op->result_def)) { + return 0; + } + + jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, val_info, opline); + + if (!op1_indirect) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + + return 1; +} + +static int zend_jit_ffi_assign_val(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + uint32_t val_info, + zend_jit_addr val_addr, + zend_jit_addr val_def_addr, + zend_jit_addr res_addr, + zend_ffi_type *op1_ffi_type, + zend_ffi_type *val_ffi_type, + zend_jit_ffi_info *ffi_info) +{ + ir_ref obj_ref; + + obj_ref = zend_jit_ffi_guard(jit, opline, ssa, ssa_op->op1_use, ssa_op->op1_def, op1_info, op1_addr, op1_ffi_type, ffi_info); + if (!obj_ref) { + return 0; + } + + if (val_addr != val_def_addr && val_def_addr) { + if (!zend_jit_update_regs(jit, (opline+1)->op1.var, val_addr, val_def_addr, val_info)) { + return 0; + } + if (Z_MODE(val_def_addr) == IS_REG && Z_MODE(val_addr) != IS_REG) { + val_addr = val_def_addr; + } + } + + ir_ref ptr = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, ptr))); + + ZEND_ASSERT(!res_addr || RETURN_VALUE_USED(opline)); + + if (!zend_jit_ffi_write(jit, ssa, ffi_info, op1_ffi_type, ptr, val_info, val_addr, val_ffi_type, res_addr, ssa_op->result_def)) { + return 0; + } + + jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, val_info, opline); + + if (!op1_indirect) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + + return 1; +} + +static int zend_jit_ffi_assign_sym(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + zend_ffi_symbol *sym, + uint32_t val_info, + zend_jit_addr val_addr, + zend_jit_addr val_def_addr, + zend_jit_addr res_addr, + HashTable *op1_ffi_symbols, + zend_ffi_type *val_ffi_type, + zend_jit_ffi_info *ffi_info) +{ + zend_ffi_type *sym_type = ZEND_FFI_TYPE(sym->type); + + if (!zend_jit_ffi_symbols_guard(jit, opline, ssa, ssa_op->op1_use, ssa_op->op1_def, op1_info, op1_addr, op1_ffi_symbols, ffi_info)) { + return 0; + } + + if (val_addr != val_def_addr && val_def_addr) { + if (!zend_jit_update_regs(jit, (opline+1)->op1.var, val_addr, val_def_addr, val_info)) { + return 0; + } + if (Z_MODE(val_def_addr) == IS_REG && Z_MODE(val_addr) != IS_REG) { + val_addr = val_def_addr; + } + } + + ZEND_ASSERT(!res_addr || RETURN_VALUE_USED(opline)); + + ir_ref ptr = ir_CONST_ADDR(sym->addr); + if (!zend_jit_ffi_write(jit, ssa, ffi_info, sym_type, ptr, val_info, val_addr, val_ffi_type, res_addr, ssa_op->result_def)) { + return 0; + } + + jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, val_info, opline); + + if (!op1_indirect) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + + return 1; +} + +static int zend_jit_ffi_assign_obj_op(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + zend_ffi_field *field, + uint32_t val_info, + zend_jit_addr val_addr, + zend_ffi_type *op1_ffi_type, + zend_jit_ffi_info *ffi_info) +{ + zend_ffi_type *field_type = ZEND_FFI_TYPE(field->type); + ir_ref obj_ref; + + obj_ref = zend_jit_ffi_guard(jit, opline, ssa, ssa_op->op1_use, ssa_op->op1_def, op1_info, op1_addr, op1_ffi_type, ffi_info); + if (!obj_ref) { + return 0; + } + + ir_ref cdata_ref = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, ptr))); + ir_ref ptr = ir_ADD_A(cdata_ref, ir_CONST_LONG(field->offset)); + + if (!zend_jit_ffi_assign_op_helper(jit, opline, opline->extended_value, + field_type, ptr, val_info, val_addr)) { + return 0; + } + + jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, val_info, opline); + + if (!op1_indirect) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + + return 1; +} + +static int zend_jit_ffi_assign_val_op(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + uint32_t val_info, + zend_jit_addr val_addr, + zend_ffi_type *op1_ffi_type, + zend_jit_ffi_info *ffi_info) +{ + ir_ref obj_ref; + + obj_ref = zend_jit_ffi_guard(jit, opline, ssa, ssa_op->op1_use, ssa_op->op1_def, op1_info, op1_addr, op1_ffi_type, ffi_info); + if (!obj_ref) { + return 0; + } + + ir_ref ptr = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, ptr))); + + if (!zend_jit_ffi_assign_op_helper(jit, opline, opline->extended_value, + op1_ffi_type, ptr, val_info, val_addr)) { + return 0; + } + + jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, val_info, opline); + + if (!op1_indirect) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + + return 1; +} + +static int zend_jit_ffi_assign_sym_op(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + zend_ffi_symbol *sym, + uint32_t val_info, + zend_jit_addr val_addr, + HashTable *op1_ffi_symbols, + zend_jit_ffi_info *ffi_info) +{ + zend_ffi_type *sym_type = ZEND_FFI_TYPE(sym->type); + + if (!zend_jit_ffi_symbols_guard(jit, opline, ssa, ssa_op->op1_use, ssa_op->op1_def, op1_info, op1_addr, op1_ffi_symbols, ffi_info)) { + return 0; + } + + ir_ref ptr = ir_CONST_ADDR(sym->addr); + if (!zend_jit_ffi_assign_op_helper(jit, opline, opline->extended_value, + sym_type, ptr, val_info, val_addr)) { + return 0; + } + + jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, val_info, opline); + + if (!op1_indirect) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + + return 1; +} + +static int zend_jit_ffi_incdec_helper(zend_jit_ctx *jit, + const zend_op *opline, + uint8_t opcode, + zend_ffi_type *el_type, + ir_ref op1, + zend_jit_addr res_addr) +{ + ir_op op; + ir_type type; + ir_ref op2; + ir_ref ref = IR_UNUSED; + + switch (opcode) { + case ZEND_PRE_INC_OBJ: + case ZEND_POST_INC_OBJ: + op = IR_ADD; + break; + case ZEND_PRE_DEC_OBJ: + case ZEND_POST_DEC_OBJ: + op = IR_SUB; + break; + default: + ZEND_UNREACHABLE(); + return 0; + } + + zend_ffi_type_kind type_kind = el_type->kind; + + if (type_kind == ZEND_FFI_TYPE_ENUM) { + type_kind = el_type->enumeration.kind; + } + switch (type_kind) { + case ZEND_FFI_TYPE_FLOAT: + type = IR_FLOAT; + op2 = ir_CONST_FLOAT(1.0); + break; + case ZEND_FFI_TYPE_DOUBLE: + type = IR_DOUBLE; + op2 = ir_CONST_DOUBLE(1.0); + break; + case ZEND_FFI_TYPE_UINT8: + type = IR_U8; + op2 = ir_CONST_U8(1); + break; + case ZEND_FFI_TYPE_SINT8: + case ZEND_FFI_TYPE_CHAR: + type = IR_I8; + op2 = ir_CONST_I8(1); + break; + case ZEND_FFI_TYPE_UINT16: + type = IR_U16; + op2 = ir_CONST_U16(1); + break; + case ZEND_FFI_TYPE_SINT16: + type = IR_I16; + op2 = ir_CONST_I16(1); + break; + case ZEND_FFI_TYPE_UINT32: + type = IR_U32; + op2 = ir_CONST_U32(1); + break; + case ZEND_FFI_TYPE_SINT32: + type = IR_I32; + op2 = ir_CONST_I32(1); + break; + case ZEND_FFI_TYPE_UINT64: + type = IR_U64; + op2 = ir_CONST_U64(1); + break; + case ZEND_FFI_TYPE_SINT64: + type = IR_I64; + op2 = ir_CONST_I64(1); + break; + case ZEND_FFI_TYPE_POINTER: + ZEND_ASSERT(ZEND_FFI_TYPE(el_type->pointer.type)->size != 0); + type = IR_ADDR; + op2 = ir_CONST_LONG(ZEND_FFI_TYPE(el_type->pointer.type)->size); + break; + default: + ZEND_UNREACHABLE(); + return 0; + } + + ref = ir_LOAD(type, op1); + + if (res_addr && (opcode == ZEND_POST_INC_OBJ || opcode == ZEND_POST_DEC_OBJ)) { + uint32_t res_type = IS_UNDEF; + + switch (type) { + case IR_FLOAT: + jit_set_Z_DVAL(jit, res_addr, ir_F2D(ref)); + res_type = IS_DOUBLE; + break; + case IR_DOUBLE: + jit_set_Z_DVAL(jit, res_addr, ref); + res_type = IS_DOUBLE; + break; + case IR_I8: + case IR_I16: +#ifdef ZEND_ENABLE_ZVAL_LONG64 + case IR_I32: + jit_set_Z_LVAL(jit, res_addr, ir_SEXT_L(ref)); + res_type = IS_LONG; + break; + case IR_I64: +#else + case IR_I32: +#endif + jit_set_Z_LVAL(jit, res_addr, ref); + res_type = IS_LONG; + break; + case IR_U8: + case IR_U16: +#ifdef ZEND_ENABLE_ZVAL_LONG64 + case IR_U32: + jit_set_Z_LVAL(jit, res_addr, ir_ZEXT_L(ref)); + res_type = IS_LONG; + break; + case IR_U64: +#else + case IR_U32: +#endif + jit_set_Z_LVAL(jit, res_addr, ref); + res_type = IS_LONG; + break; + case IR_ADDR: + if ((el_type->attr & ZEND_FFI_ATTR_CONST) + && ZEND_FFI_TYPE(el_type->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) { + ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_string), jit_ZVAL_ADDR(jit, res_addr), ref); + } else { + ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_ffi_ptr), + jit_ZVAL_ADDR(jit, res_addr), ir_CONST_ADDR(el_type), ref); + } + break; + default: + ZEND_UNREACHABLE(); + return 0; + } + if (res_type && Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, res_type); + } + } + + ref = ir_BINARY_OP(op, type, ref, op2); + ir_STORE(op1, ref); + + if (res_addr && (opcode == ZEND_PRE_INC_OBJ || opcode == ZEND_PRE_DEC_OBJ)) { + uint32_t res_type = IS_UNDEF; + + switch (type) { + case IR_FLOAT: + jit_set_Z_DVAL(jit, res_addr, ir_F2D(ref)); + res_type = IS_DOUBLE; + break; + case IR_DOUBLE: + jit_set_Z_DVAL(jit, res_addr, ref); + res_type = IS_DOUBLE; + break; + case IR_I8: + case IR_I16: +#ifdef ZEND_ENABLE_ZVAL_LONG64 + case IR_I32: + jit_set_Z_LVAL(jit, res_addr, ir_SEXT_L(ref)); + res_type = IS_LONG; + break; + case IR_I64: +#else + case IR_I32: +#endif + jit_set_Z_LVAL(jit, res_addr, ref); + res_type = IS_LONG; + break; + case IR_U8: + case IR_U16: +#ifdef ZEND_ENABLE_ZVAL_LONG64 + case IR_U32: + jit_set_Z_LVAL(jit, res_addr, ir_ZEXT_L(ref)); + res_type = IS_LONG; + break; + case IR_U64: +#else + case IR_U32: +#endif + jit_set_Z_LVAL(jit, res_addr, ref); + res_type = IS_LONG; + break; + case IR_ADDR: + if ((el_type->attr & ZEND_FFI_ATTR_CONST) + && ZEND_FFI_TYPE(el_type->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) { + ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_string), jit_ZVAL_ADDR(jit, res_addr), ref); + } else { + ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_zval_ffi_ptr), + jit_ZVAL_ADDR(jit, res_addr), ir_CONST_ADDR(el_type), ref); + } + break; + default: + ZEND_UNREACHABLE(); + return 0; + } + if (res_type && Z_MODE(res_addr) != IS_REG) { + jit_set_Z_TYPE_INFO(jit, res_addr, res_type); + } + } + + return 1; +} + +static int zend_jit_ffi_incdec_obj(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + zend_ffi_field *field, + zend_jit_addr res_addr, + zend_ffi_type *op1_ffi_type, + zend_jit_ffi_info *ffi_info) +{ + zend_ffi_type *field_type = ZEND_FFI_TYPE(field->type); + ir_ref obj_ref; + + obj_ref = zend_jit_ffi_guard(jit, opline, ssa, ssa_op->op1_use, ssa_op->op1_def, op1_info, op1_addr, op1_ffi_type, ffi_info); + if (!obj_ref) { + return 0; + } + + ir_ref cdata_ref = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, ptr))); + ir_ref ptr = ir_ADD_A(cdata_ref, ir_CONST_LONG(field->offset)); + + if (!zend_jit_ffi_incdec_helper(jit, opline, opline->opcode, field_type, ptr, res_addr)) { + return 0; + } + + if (!op1_indirect) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + + return 1; +} + +static int zend_jit_ffi_incdec_val(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + zend_jit_addr res_addr, + zend_ffi_type *op1_ffi_type, + zend_jit_ffi_info *ffi_info) +{ + ir_ref obj_ref; + + obj_ref = zend_jit_ffi_guard(jit, opline, ssa, ssa_op->op1_use, ssa_op->op1_def, op1_info, op1_addr, op1_ffi_type, ffi_info); + if (!obj_ref) { + return 0; + } + + ir_ref ptr = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_ffi_cdata, ptr))); + + if (!zend_jit_ffi_incdec_helper(jit, opline, opline->opcode, op1_ffi_type, ptr, res_addr)) { + return 0; + } + + if (!op1_indirect) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + + return 1; +} + +static int zend_jit_ffi_incdec_sym(zend_jit_ctx *jit, + const zend_op *opline, + const zend_op_array *op_array, + zend_ssa *ssa, + const zend_ssa_op *ssa_op, + uint32_t op1_info, + zend_jit_addr op1_addr, + bool op1_indirect, + zend_ffi_symbol *sym, + zend_jit_addr res_addr, + HashTable *op1_ffi_symbols, + zend_jit_ffi_info *ffi_info) +{ + zend_ffi_type *sym_type = ZEND_FFI_TYPE(sym->type); + + if (!zend_jit_ffi_symbols_guard(jit, opline, ssa, ssa_op->op1_use, ssa_op->op1_def, op1_info, op1_addr, op1_ffi_symbols, ffi_info)) { + return 0; + } + + ir_ref ptr = ir_CONST_ADDR(sym->addr); + if (!zend_jit_ffi_incdec_helper(jit, opline, opline->opcode, sym_type, ptr, res_addr)) { + return 0; + } + + if (!op1_indirect) { + jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline); + } + + return 1; +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 3a6e3f0eb9d28..24a5ecb87fee4 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -1330,6 +1330,31 @@ static uint32_t find_trampoline_num_args(zend_jit_trace_rec *start, zend_jit_tra return 0; } +#ifdef HAVE_FFI +static bool zend_jit_ffi_is_send_by_ref(zend_jit_trace_stack_frame *call, uint32_t arg_num) +{ + if (TRACE_FRAME_FFI_FUNC(call)) { + if (((TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_FREE + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_TYPEOF + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_ADDR + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_SIZEOF + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_ALIGNOF + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_MEMSET + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_STRING + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_IS_NULL) + && arg_num == 1) + || (TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_CAST + && arg_num == 2) + || ((TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_MEMCPY + || TRACE_FRAME_FFI_FUNC(call) == TRACE_FRAME_FFI_FUNC_MEMCMP) + && (arg_num == 1 || arg_num == 2))) { + return true; + } + } + return false; +} +#endif + static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uint32_t parent_trace, uint32_t exit_num, zend_script *script, const zend_op_array **op_arrays, int *num_op_arrays_ptr) { zend_ssa *tssa; @@ -1804,6 +1829,11 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin max_used_stack = used_stack = -1; } + uint32_t frame_flags = 0; +#ifdef HAVE_FFI + zend_ffi_type *frame_ffi_func_type = NULL; +#endif + p = trace_buffer + ZEND_JIT_TRACE_START_REC_SIZE; idx = 0; level = 0; @@ -1814,6 +1844,19 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin uint8_t val_type = IS_UNKNOWN; // zend_class_entry *op1_ce = NULL; zend_class_entry *op2_ce = NULL; +// zend_class_entry *op3_ce = NULL; +#ifdef HAVE_FFI + zend_ffi_type *op1_ffi_type = NULL; + zend_ffi_type *op2_ffi_type = NULL; + zend_ffi_type *op3_ffi_type = NULL; + zend_ffi_type holder1, holder2, holder3; + HashTable *op1_ffi_symbols = NULL; + (void)op2_ffi_type; + (void)op3_ffi_type; + + frame_flags = 0; + frame_ffi_func_type = NULL; +#endif opline = p->opline; @@ -1837,10 +1880,44 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin // op1_ce = (zend_class_entry*)(p+1)->ce; p++; } +#ifdef HAVE_FFI + if ((p+1)->op == ZEND_JIT_TRACE_OP1_FFI_TYPE) { + op1_ffi_type = (zend_ffi_type*)(p+1)->ptr; + if (ZEND_FFI_TYPE_IS_OWNED(op1_ffi_type)) { + op1_ffi_type = zend_jit_ffi_type_pointer_to(op1_ffi_type, &holder1); + } + p++; + } else if ((p+1)->op == ZEND_JIT_TRACE_OP1_FFI_SYMBOLS) { + op1_ffi_symbols = (HashTable*)(p+1)->ptr; + p++; + } +#endif if ((p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) { op2_ce = (zend_class_entry*)(p+1)->ce; p++; } +#ifdef HAVE_FFI + if ((p+1)->op == ZEND_JIT_TRACE_OP2_FFI_TYPE) { + op2_ffi_type = (zend_ffi_type*)(p+1)->ptr; + if (ZEND_FFI_TYPE_IS_OWNED(op2_ffi_type)) { + op2_ffi_type = zend_jit_ffi_type_pointer_to(op2_ffi_type, &holder2); + } + p++; + } +#endif + if ((p+1)->op == ZEND_JIT_TRACE_OP3_TYPE) { +// op3_ce = (zend_class_entry*)(p+1)->ce; + p++; + } +#ifdef HAVE_FFI + if ((p+1)->op == ZEND_JIT_TRACE_OP3_FFI_TYPE) { + op3_ffi_type = (zend_ffi_type*)(p+1)->ptr; + if (ZEND_FFI_TYPE_IS_OWNED(op3_ffi_type)) { + op3_ffi_type = zend_jit_ffi_type_pointer_to(op3_ffi_type, &holder3); + } + p++; + } +#endif if ((p+1)->op == ZEND_JIT_TRACE_VAL_INFO) { val_type = (p+1)->op1_type; p++; @@ -2012,7 +2089,6 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin case ZEND_FETCH_DIM_FUNC_ARG: if (!frame || !frame->call - || !frame->call->func || !TRACE_FRAME_IS_LAST_SEND_BY_VAL(frame->call)) { break; } @@ -2101,6 +2177,9 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin // case ZEND_FETCH_DIM_UNSET: case ZEND_FETCH_LIST_W: if (opline->op1_type != IS_CV +#ifdef HAVE_FFI + && !op1_ffi_type +#endif && (orig_op1_type == IS_UNKNOWN || !(orig_op1_type & IS_TRACE_INDIRECT))) { break; @@ -2141,7 +2220,26 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin /* Named parameters not supported in JIT */ break; } +#ifdef HAVE_FFI + if (!op1_ffi_type + || !frame->call->func + || !ARG_SHOULD_BE_SENT_BY_REF(frame->call->func, opline->op2.num)) { + ADD_OP1_TRACE_GUARD(); + } +#else ADD_OP1_TRACE_GUARD(); +#endif +/* + if (!frame->call->func + || (tssa->ops[idx].op1_use >= 0 + && !(tssa->var_info[tssa->ops[idx].op1_use].type & MAY_BE_NULL)) +#ifdef HAVE_FFI + || (TRACE_FRAME_FFI(frame->call) && !TRACE_FRAME_FFI_FUNC(frame->call)) +#endif + ) { + ADD_OP1_TRACE_GUARD(); + } +*/ propagate_arg: /* Propagate argument type */ if (frame->call @@ -2207,8 +2305,21 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin break; case ZEND_CHECK_FUNC_ARG: if (!frame - || !frame->call - || !frame->call->func) { + || !frame->call) { + break; + } +#ifdef HAVE_FFI + if (TRACE_FRAME_FFI(frame->call)) { + if (zend_jit_ffi_is_send_by_ref(frame->call, opline->op2.num)) { + TRACE_FRAME_SET_LAST_SEND_BY_REF(frame->call); + break; + } + /* FFI arguments alwyas sent by value ??? */ + TRACE_FRAME_SET_LAST_SEND_BY_VAL(frame->call); + break; + } +#endif + if (!frame->call->func) { break; } if (opline->op2_type == IS_CONST @@ -2226,7 +2337,6 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin case ZEND_FETCH_OBJ_FUNC_ARG: if (!frame || !frame->call - || !frame->call->func || !TRACE_FRAME_IS_LAST_SEND_BY_VAL(frame->call)) { break; } @@ -2249,11 +2359,31 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin break; } ADD_OP1_TRACE_GUARD(); +#ifdef HAVE_FFI + if (op1_ffi_symbols) { + zend_ffi_symbol *sym = zend_hash_find_ptr(op1_ffi_symbols, + Z_STR_P(RT_CONSTANT(opline, opline->op2))); + if (sym + && sym->kind == ZEND_FFI_SYM_FUNC + && zend_jit_ffi_supported_func(ZEND_FFI_TYPE(sym->type))) { + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_func_type = ZEND_FFI_TYPE(sym->type); + } + } +#endif break; case ZEND_INIT_DYNAMIC_CALL: - if (orig_op2_type == IS_OBJECT && op2_ce == zend_ce_closure) { - ADD_OP2_TRACE_GUARD(); +#ifdef HAVE_FFI + if (orig_op2_type != IS_OBJECT + || (op2_ce != zend_ce_closure && !op2_ffi_type)) { + break; } +#else + if (orig_op2_type != IS_OBJECT || op2_ce != zend_ce_closure) { + break; + } +#endif + ADD_OP2_TRACE_GUARD(); break; case ZEND_SEND_ARRAY: case ZEND_SEND_UNPACK: @@ -2346,6 +2476,11 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin } } } + +#ifdef HAVE_FFI + zend_ssa_op *ssa_op = ssa_ops +idx; +#endif + if (opline->opcode == ZEND_RECV_INIT && !(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) { /* RECV_INIT always copy the constant */ @@ -2355,6 +2490,38 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin if (ssa_ops[idx].op2_use >= 0 && ssa_ops[idx].op2_def >= 0) { ssa_var_info[ssa_ops[idx].op2_def] = ssa_var_info[ssa_ops[idx].op2_use]; } +#ifdef HAVE_FFI + } else if (opline->opcode == ZEND_DO_FCALL + && RETURN_VALUE_USED(opline) + && frame + && frame->call + && TRACE_FRAME_FFI(frame->call)) { + zend_ffi_type *type = (zend_ffi_type*)frame->call->call_opline; + + ssa_var_info[ssa_ops[idx].result_def].type = + zend_jit_ffi_type_info(ZEND_FFI_TYPE(type->func.ret_type)); + } else if (op1_ffi_type + && (op1_ffi_type->kind == ZEND_FFI_TYPE_ARRAY || op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) + && (opline->opcode == ZEND_FETCH_DIM_R + || opline->opcode == ZEND_FETCH_DIM_IS + || opline->opcode == ZEND_FETCH_LIST_R) + && (OP2_INFO() & MAY_BE_ANY) == MAY_BE_LONG + && ZEND_FFI_TYPE(op1_ffi_type->array.type)->kind != ZEND_FFI_TYPE_VOID + && zend_jit_ffi_supported_type(ZEND_FFI_TYPE(op1_ffi_type->array.type))) { + ssa_var_info[ssa_ops[idx].result_def].type = + zend_jit_ffi_type_info(ZEND_FFI_TYPE(op1_ffi_type->array.type)); + } else if (ssa_ops[idx].op1_def >= 0 + && frame + && frame->call + && TRACE_FRAME_FFI(frame->call) + && (opline->opcode == ZEND_SEND_VAR_EX + || opline->opcode == ZEND_SEND_VAR_NO_REF + || opline->opcode == ZEND_SEND_VAR_NO_REF_EX + || opline->opcode == ZEND_SEND_FUNC_ARG) + && opline->op2_type != IS_CONST) { + ssa_var_info[ssa_ops[idx].op1_def] = ssa_var_info[ssa_ops[idx].op1_use]; + ssa_var_info[ssa_ops[idx].op1_def].type &= ~MAY_BE_GUARD; +#endif } else { if (zend_update_type_info(op_array, tssa, script, (zend_op*)opline, ssa_ops + idx, ssa_opcodes, optimization_level) == FAILURE) { // TODO: @@ -2607,7 +2774,12 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin } else if (p->op == ZEND_JIT_TRACE_INIT_CALL) { call = top; - TRACE_FRAME_INIT(call, p->func, 0, 0); + TRACE_FRAME_INIT(call, p->func, frame_flags, 0); +#ifdef HAVE_FFI + if (TRACE_FRAME_FFI(call)) { + call->call_opline = (const zend_op*)(void*)frame_ffi_func_type; + } +#endif call->prev = frame->call; call->used_stack = 0; frame->call = call; @@ -3916,6 +4088,9 @@ static bool zend_jit_trace_next_is_send_result(const zend_op *oplin || ((opline+1)->opcode == ZEND_SEND_VAL_EX && frame && frame->call +#ifdef HAVE_FFI + && !TRACE_FRAME_FFI(frame->call) +#endif && frame->call->func && !ARG_MUST_BE_SENT_BY_REF(frame->call->func, (opline+1)->op2.num))) && (opline+1)->op1_type == IS_TMP_VAR @@ -4121,6 +4296,13 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par int checked_stack; int peek_checked_stack; uint32_t frame_flags = 0; +#ifdef HAVE_FFI + zend_jit_ffi_info *ffi_info = NULL; + zend_ffi_type *frame_ffi_func_type = NULL; + uint32_t frame_ffi_info = 0; + ir_ref frame_ffi_obj_ref = IR_UNUSED; + ir_ref frame_ffi_func_ref = IR_UNUSED; +#endif JIT_G(current_trace) = trace_buffer; @@ -4391,6 +4573,16 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par bool op1_indirect; zend_class_entry *op1_ce = NULL; zend_class_entry *op2_ce = NULL; + zend_class_entry *op3_ce = NULL; + (void)op3_ce; +#ifdef HAVE_FFI + zend_ffi_type *op1_ffi_type = NULL; + zend_ffi_type *op2_ffi_type = NULL; + zend_ffi_type *op3_ffi_type = NULL; + zend_ffi_type holder1, holder2, holder3; + HashTable *op1_ffi_symbols = NULL; + (void)op2_ffi_type; +#endif bool gen_handler = false; opline = p->opline; @@ -4411,16 +4603,56 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par op1_ce = (zend_class_entry*)(p+1)->ce; p++; } +#ifdef HAVE_FFI + if ((p+1)->op == ZEND_JIT_TRACE_OP1_FFI_TYPE) { + op1_ffi_type = (zend_ffi_type*)(p+1)->ptr; + if (ZEND_FFI_TYPE_IS_OWNED(op1_ffi_type)) { + op1_ffi_type = zend_jit_ffi_type_pointer_to(op1_ffi_type, &holder1); + } + p++; + } else if ((p+1)->op == ZEND_JIT_TRACE_OP1_FFI_SYMBOLS) { + op1_ffi_symbols = (HashTable*)(p+1)->ptr; + p++; + } +#endif if ((p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) { op2_ce = (zend_class_entry*)(p+1)->ce; p++; } +#ifdef HAVE_FFI + if ((p+1)->op == ZEND_JIT_TRACE_OP2_FFI_TYPE) { + op2_ffi_type = (zend_ffi_type*)(p+1)->ptr; + if (ZEND_FFI_TYPE_IS_OWNED(op2_ffi_type)) { + op2_ffi_type = zend_jit_ffi_type_pointer_to(op2_ffi_type, &holder2); + } + p++; + } +#endif + if ((p+1)->op == ZEND_JIT_TRACE_OP3_TYPE) { + op3_ce = (zend_class_entry*)(p+1)->ce; + p++; + } +#ifdef HAVE_FFI + if ((p+1)->op == ZEND_JIT_TRACE_OP3_FFI_TYPE) { + op3_ffi_type = (zend_ffi_type*)(p+1)->ptr; + if (ZEND_FFI_TYPE_IS_OWNED(op3_ffi_type)) { + op3_ffi_type = zend_jit_ffi_type_pointer_to(op3_ffi_type, &holder3); + } + p++; + } +#endif if ((p+1)->op == ZEND_JIT_TRACE_VAL_INFO) { val_type = (p+1)->op1_type; p++; } frame_flags = 0; +#ifdef HAVE_FFI + frame_ffi_func_type = NULL; + frame_ffi_info = 0; + frame_ffi_obj_ref = IR_UNUSED; + frame_ffi_func_ref = IR_UNUSED; +#endif switch (opline->opcode) { case ZEND_INIT_FCALL: @@ -4754,6 +4986,24 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par op1_data_info = OP1_DATA_INFO(); CHECK_OP1_DATA_TRACE_TYPE(); op1_def_info = OP1_DEF_INFO(); +#ifdef HAVE_FFI + if (op1_ffi_type + && (op1_ffi_type->kind == ZEND_FFI_TYPE_ARRAY || op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) + && op2_info == MAY_BE_LONG + && zend_jit_ffi_compatible_op(op1_ffi_type->array.type, op1_data_info, op3_ffi_type, opline->extended_value)) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_assign_dim_op(&ctx, opline, ssa, ssa_op, + op1_info, op1_def_info, op1_addr, op1_indirect, + op2_info, (opline->op2_type != IS_UNUSED) ? OP2_REG_ADDR() : 0, + (opline->op2_type != IS_UNUSED) ? OP2_RANGE() : NULL, + op1_data_info, OP1_DATA_REG_ADDR(), OP1_DATA_RANGE(), + op1_ffi_type, ffi_info)) { + goto jit_failure; + } + } else +#endif if (!zend_jit_assign_dim_op(&ctx, opline, op1_info, op1_def_info, op1_addr, op1_indirect, op2_info, (opline->op2_type != IS_UNUSED) ? OP2_REG_ADDR() : 0, @@ -4844,6 +5094,68 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par on_this = op_array->opcodes[op_array_ssa->vars[op_array_ssa->ops[opline-op_array->opcodes].op1_use].definition].opcode == ZEND_FETCH_THIS; } } +#ifdef HAVE_FFI + if (op1_ffi_type && op1_ffi_type->kind == ZEND_FFI_TYPE_STRUCT) { + zend_ffi_field *field = zend_hash_find_ptr(&op1_ffi_type->record.fields, + Z_STR_P(RT_CONSTANT(opline, opline->op2))); + + if (field + && !field->is_const + && !field->bits + && ZEND_FFI_TYPE(field->type)->kind != ZEND_FFI_TYPE_VOID + && zend_jit_ffi_supported_type(field->type) + && (ZEND_FFI_TYPE(field->type)->kind < ZEND_FFI_TYPE_POINTER + || (ZEND_FFI_TYPE(field->type)->kind == ZEND_FFI_TYPE_POINTER + && ZEND_FFI_TYPE(ZEND_FFI_TYPE(field->type)->pointer.type)->size != 0))) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_incdec_obj(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, field, + (opline->result_type != IS_UNUSED) ? RES_REG_ADDR() : 0, + op1_ffi_type, ffi_info)) { + goto jit_failure; + } + goto done; + } + } else if (op1_ffi_type + && op1_ffi_type->kind < ZEND_FFI_TYPE_POINTER + && op1_ffi_type->kind != ZEND_FFI_TYPE_VOID + && zend_string_equals_literal(Z_STR_P(RT_CONSTANT(opline, opline->op2)), "cdata") + && zend_jit_ffi_supported_type(op1_ffi_type)) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_incdec_val(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, + (opline->result_type != IS_UNUSED) ? RES_REG_ADDR() : 0, + op1_ffi_type, ffi_info)) { + goto jit_failure; + } + goto done; + } else if (op1_ffi_symbols) { + zend_ffi_symbol *sym = zend_hash_find_ptr(op1_ffi_symbols, + Z_STR_P(RT_CONSTANT(opline, opline->op2))); + if (sym + && sym->kind == ZEND_FFI_SYM_VAR + && ZEND_FFI_TYPE(sym->type)->kind != ZEND_FFI_TYPE_VOID + && zend_jit_ffi_supported_type(sym->type) + && (ZEND_FFI_TYPE(sym->type)->kind < ZEND_FFI_TYPE_POINTER + || (ZEND_FFI_TYPE(sym->type)->kind == ZEND_FFI_TYPE_POINTER + && ZEND_FFI_TYPE(ZEND_FFI_TYPE(sym->type)->pointer.type)->size != 0))) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_incdec_sym(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, sym, + (opline->result_type != IS_UNUSED) ? RES_REG_ADDR() : 0, + op1_ffi_symbols, ffi_info)) { + goto jit_failure; + } + goto done; + } + } +#endif if (!zend_jit_incdec_obj(&ctx, opline, op_array, ssa, ssa_op, op1_info, op1_addr, op1_indirect, ce, ce_is_instanceof, on_this, delayed_fetch_this, op1_ce, @@ -4940,6 +5252,60 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par } op1_data_info = OP1_DATA_INFO(); CHECK_OP1_DATA_TRACE_TYPE(); +#ifdef HAVE_FFI + if (op1_ffi_type && op1_ffi_type->kind == ZEND_FFI_TYPE_STRUCT) { + zend_ffi_field *field = zend_hash_find_ptr(&op1_ffi_type->record.fields, + Z_STR_P(RT_CONSTANT(opline, opline->op2))); + + if (field + && !field->is_const + && !field->bits + && zend_jit_ffi_compatible_op(field->type, op1_data_info, op3_ffi_type, opline->extended_value)) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_assign_obj_op(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, field, + op1_data_info, OP1_DATA_REG_ADDR(), + op1_ffi_type, ffi_info)) { + goto jit_failure; + } + goto done; + } + } else if (op1_ffi_type + && op1_ffi_type->kind < ZEND_FFI_TYPE_POINTER + && op1_ffi_type->kind != ZEND_FFI_TYPE_VOID + && zend_string_equals_literal(Z_STR_P(RT_CONSTANT(opline, opline->op2)), "cdata") + && zend_jit_ffi_supported_type(op1_ffi_type)) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_assign_val_op(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, + op1_data_info, OP1_DATA_REG_ADDR(), + op1_ffi_type, ffi_info)) { + goto jit_failure; + } + goto done; + } else if (op1_ffi_symbols) { + zend_ffi_symbol *sym = zend_hash_find_ptr(op1_ffi_symbols, + Z_STR_P(RT_CONSTANT(opline, opline->op2))); + if (sym + && sym->kind == ZEND_FFI_SYM_VAR + && zend_jit_ffi_compatible_op(sym->type, op1_data_info, op3_ffi_type, opline->extended_value)) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_assign_sym_op(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, sym, + op1_data_info, OP1_DATA_REG_ADDR(), + op1_ffi_symbols, ffi_info)) { + goto jit_failure; + } + goto done; + } + } +#endif if (!zend_jit_assign_obj_op(&ctx, opline, op_array, ssa, ssa_op, op1_info, op1_addr, op1_data_info, OP1_DATA_REG_ADDR(), OP1_DATA_RANGE(), op1_indirect, ce, ce_is_instanceof, on_this, delayed_fetch_this, op1_ce, @@ -5023,6 +5389,78 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par } op1_data_info = OP1_DATA_INFO(); CHECK_OP1_DATA_TRACE_TYPE(); +#ifdef HAVE_FFI + if (op1_ffi_type && op1_ffi_type->kind == ZEND_FFI_TYPE_STRUCT) { + zend_ffi_field *field = zend_hash_find_ptr(&op1_ffi_type->record.fields, + Z_STR_P(RT_CONSTANT(opline, opline->op2))); + + if (field + && !field->is_const + && !field->bits + && zend_jit_ffi_compatible(field->type, op1_data_info, op3_ffi_type)) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_assign_obj(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, field, + op1_data_info, OP1_DATA_REG_ADDR(), OP1_DATA_DEF_REG_ADDR(), + (opline->result_type != IS_UNUSED) ? RES_REG_ADDR() : 0, + op1_ffi_type, op3_ffi_type, ffi_info)) { + goto jit_failure; + } + if ((opline+1)->op1_type == IS_CV + && (ssa_op+1)->op1_def >= 0 + && ssa->vars[(ssa_op+1)->op1_def].alias == NO_ALIAS) { + ssa->var_info[(ssa_op+1)->op1_def].guarded_reference = ssa->var_info[(ssa_op+1)->op1_use].guarded_reference; + } + goto done; + } + } else if (op1_ffi_type + && op1_ffi_type->kind < ZEND_FFI_TYPE_POINTER + && op1_ffi_type->kind != ZEND_FFI_TYPE_VOID + && zend_string_equals_literal(Z_STR_P(RT_CONSTANT(opline, opline->op2)), "cdata") + && zend_jit_ffi_supported_type(op1_ffi_type)) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_assign_val(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, + op1_data_info, OP1_DATA_REG_ADDR(), OP1_DATA_DEF_REG_ADDR(), + (opline->result_type != IS_UNUSED) ? RES_REG_ADDR() : 0, + op1_ffi_type, op3_ffi_type, ffi_info)) { + goto jit_failure; + } + if ((opline+1)->op1_type == IS_CV + && (ssa_op+1)->op1_def >= 0 + && ssa->vars[(ssa_op+1)->op1_def].alias == NO_ALIAS) { + ssa->var_info[(ssa_op+1)->op1_def].guarded_reference = ssa->var_info[(ssa_op+1)->op1_use].guarded_reference; + } + goto done; + } else if (op1_ffi_symbols) { + zend_ffi_symbol *sym = zend_hash_find_ptr(op1_ffi_symbols, + Z_STR_P(RT_CONSTANT(opline, opline->op2))); + if (sym + && sym->kind == ZEND_FFI_SYM_VAR + && zend_jit_ffi_compatible(sym->type, op1_data_info, op3_ffi_type)) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_assign_sym(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, sym, + op1_data_info, OP1_DATA_REG_ADDR(), OP1_DATA_DEF_REG_ADDR(), + (opline->result_type != IS_UNUSED) ? RES_REG_ADDR() : 0, + op1_ffi_symbols, op3_ffi_type, ffi_info)) { + goto jit_failure; + } + if ((opline+1)->op1_type == IS_CV + && (ssa_op+1)->op1_def >= 0 + && ssa->vars[(ssa_op+1)->op1_def].alias == NO_ALIAS) { + ssa->var_info[(ssa_op+1)->op1_def].guarded_reference = ssa->var_info[(ssa_op+1)->op1_use].guarded_reference; + } + goto done; + } + } +#endif if (!zend_jit_assign_obj(&ctx, opline, op_array, ssa, ssa_op, op1_info, op1_addr, op1_data_info, OP1_DATA_REG_ADDR(), OP1_DATA_DEF_REG_ADDR(), (opline->result_type != IS_UNUSED) ? RES_REG_ADDR() : 0, @@ -5074,6 +5512,26 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par CHECK_OP2_TRACE_TYPE(); op1_data_info = OP1_DATA_INFO(); CHECK_OP1_DATA_TRACE_TYPE(); +#ifdef HAVE_FFI + if (op1_ffi_type + && (op1_ffi_type->kind == ZEND_FFI_TYPE_ARRAY || op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) + && op2_info == MAY_BE_LONG + && zend_jit_ffi_compatible(op1_ffi_type->array.type, op1_data_info, op3_ffi_type)) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_assign_dim(&ctx, opline, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, + op2_info, (opline->op2_type != IS_UNUSED) ? OP2_REG_ADDR() : 0, + (opline->op2_type != IS_UNUSED) ? OP2_RANGE() : NULL, + op1_data_info, OP1_DATA_REG_ADDR(), + (ctx.ra && (ssa_op+1)->op1_def >= 0) ? OP1_DATA_DEF_REG_ADDR() : 0, + (opline->result_type != IS_UNUSED) ? RES_REG_ADDR() : 0, + op1_ffi_type, op3_ffi_type, ffi_info)) { + goto jit_failure; + } + } else +#endif if (!zend_jit_assign_dim(&ctx, opline, op1_info, op1_addr, op1_indirect, op2_info, (opline->op2_type != IS_UNUSED) ? OP2_REG_ADDR() : 0, @@ -5281,6 +5739,17 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par } op1_info = OP1_INFO(); CHECK_OP1_TRACE_TYPE(); +#ifdef HAVE_FFI + if (JIT_G(current_frame) + && JIT_G(current_frame)->call + && TRACE_FRAME_FFI(JIT_G(current_frame)->call)) { + if (!zend_jit_ffi_send_val(&ctx, opline, op_array, ssa, ssa_op, + op1_info, OP1_REG_ADDR(), 0, op1_ce, op1_ffi_type)) { + goto jit_failure; + } + goto done; + } +#endif if (!zend_jit_send_val(&ctx, opline, op1_info, OP1_REG_ADDR())) { goto jit_failure; @@ -5302,6 +5771,17 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par break; } op1_info = OP1_INFO(); +#ifdef HAVE_FFI + if (JIT_G(current_frame) + && JIT_G(current_frame)->call + && TRACE_FRAME_FFI(JIT_G(current_frame)->call)) { + if (!zend_jit_ffi_send_val(&ctx, opline, op_array, ssa, ssa_op, + op1_info, OP1_REG_ADDR(), 0, op1_ce, op1_ffi_type)) { + goto jit_failure; + } + goto done; + } +#endif if (!zend_jit_send_ref(&ctx, opline, op_array, op1_info, 0)) { goto jit_failure; @@ -5333,6 +5813,17 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par } op1_info = OP1_INFO(); CHECK_OP1_TRACE_TYPE(); +#ifdef HAVE_FFI + if (JIT_G(current_frame) + && JIT_G(current_frame)->call + && TRACE_FRAME_FFI(JIT_G(current_frame)->call)) { + if (!zend_jit_ffi_send_val(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_def_addr, op1_ce, op1_ffi_type)) { + goto jit_failure; + } + goto done; + } +#endif if (!zend_jit_send_var(&ctx, opline, op_array, op1_info, op1_addr, op1_def_addr)) { goto jit_failure; @@ -5373,8 +5864,21 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par goto done; case ZEND_CHECK_FUNC_ARG: if (!JIT_G(current_frame) - || !JIT_G(current_frame)->call - || !JIT_G(current_frame)->call->func) { + || !JIT_G(current_frame)->call) { + break; + } +#ifdef HAVE_FFI + if (TRACE_FRAME_FFI(JIT_G(current_frame)->call)) { + if (zend_jit_ffi_is_send_by_ref(JIT_G(current_frame)->call, opline->op2.num)) { + TRACE_FRAME_SET_LAST_SEND_BY_REF(JIT_G(current_frame)->call); + goto done; + } + /* FFI arguments alwyas sent by value ??? */ + TRACE_FRAME_SET_LAST_SEND_BY_VAL(JIT_G(current_frame)->call); + goto done; + } +#endif + if (!JIT_G(current_frame)->call->func) { break; } if (opline->op2_type == IS_CONST @@ -5400,6 +5904,20 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par case ZEND_DO_ICALL: case ZEND_DO_FCALL_BY_NAME: case ZEND_DO_FCALL: +#ifdef HAVE_FFI + if (JIT_G(current_frame) + && JIT_G(current_frame)->call + && TRACE_FRAME_FFI(JIT_G(current_frame)->call)) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_do_call(&ctx, opline, op_array, ssa, ssa_op, + (opline->result_type != IS_UNUSED) ? RES_REG_ADDR() : 0, ffi_info)) { + goto jit_failure; + } + goto done; + } +#endif if (!zend_jit_do_fcall(&ctx, opline, op_array, op_array_ssa, frame->call_level, -1, p + 1)) { goto jit_failure; } @@ -5810,7 +6328,6 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par case ZEND_FETCH_DIM_FUNC_ARG: if (!JIT_G(current_frame) || !JIT_G(current_frame)->call - || !JIT_G(current_frame)->call->func || !TRACE_FRAME_IS_LAST_SEND_BY_VAL(JIT_G(current_frame)->call)) { break; } @@ -5862,6 +6379,24 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par } } } +#ifdef HAVE_FFI + if (op1_ffi_type + && (op1_ffi_type->kind == ZEND_FFI_TYPE_ARRAY || op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) + && op2_info == MAY_BE_LONG + && ZEND_FFI_TYPE(op1_ffi_type->array.type)->kind != ZEND_FFI_TYPE_VOID + && zend_jit_ffi_supported_type(ZEND_FFI_TYPE(op1_ffi_type->array.type))) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_fetch_dim(&ctx, opline, ssa, ssa_op, + op1_info, op1_addr, 0, avoid_refcounting, + op2_info, OP2_REG_ADDR(), OP2_RANGE(), + res_info, RES_REG_ADDR(), + op1_ffi_type, ffi_info)) { + goto jit_failure; + } + } else +#endif if (!zend_jit_fetch_dim_read(&ctx, opline, ssa, ssa_op, op1_info, op1_addr, avoid_refcounting, op2_info, OP2_REG_ADDR(), OP2_RANGE(), @@ -5877,21 +6412,24 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par // case ZEND_FETCH_DIM_UNSET: case ZEND_FETCH_LIST_W: if (opline->op1_type != IS_CV +#ifdef HAVE_FFI + && !op1_ffi_type +#endif && (orig_op1_type == IS_UNKNOWN || !(orig_op1_type & IS_TRACE_INDIRECT))) { break; } op1_info = OP1_INFO(); op1_addr = OP1_REG_ADDR(); + op1_indirect = 0; if (opline->op1_type == IS_VAR) { if (orig_op1_type != IS_UNKNOWN && (orig_op1_type & IS_TRACE_INDIRECT)) { + op1_indirect = 1; if (!zend_jit_fetch_indirect_var(&ctx, opline, orig_op1_type, &op1_info, &op1_addr, !ssa->var_info[ssa_op->op1_use].indirect_reference)) { goto jit_failure; } - } else { - break; } } if (orig_op1_type != IS_UNKNOWN @@ -5910,6 +6448,24 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par op2_info = OP2_INFO(); CHECK_OP2_TRACE_TYPE(); op1_def_info = OP1_DEF_INFO(); +#ifdef HAVE_FFI + if (op1_ffi_type + && (op1_ffi_type->kind == ZEND_FFI_TYPE_ARRAY || op1_ffi_type->kind == ZEND_FFI_TYPE_POINTER) + && op2_info == MAY_BE_LONG + && ZEND_FFI_TYPE(op1_ffi_type->array.type)->kind != ZEND_FFI_TYPE_VOID + && zend_jit_ffi_supported_type(ZEND_FFI_TYPE(op1_ffi_type->array.type))) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_fetch_dim(&ctx, opline, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, avoid_refcounting, + op2_info, OP2_REG_ADDR(), OP2_RANGE(), + RES_INFO(), RES_REG_ADDR(), + op1_ffi_type, ffi_info)) { + goto jit_failure; + } + } else +#endif if (!zend_jit_fetch_dim(&ctx, opline, op1_info, op1_addr, op2_info, (opline->op2_type != IS_UNUSED) ? OP2_REG_ADDR() : 0, @@ -6012,7 +6568,6 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par case ZEND_FETCH_OBJ_FUNC_ARG: if (!JIT_G(current_frame) || !JIT_G(current_frame)->call - || !JIT_G(current_frame)->call->func || !TRACE_FRAME_IS_LAST_SEND_BY_VAL(JIT_G(current_frame)->call)) { break; } @@ -6096,6 +6651,65 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par on_this = op_array->opcodes[op_array_ssa->vars[op_array_ssa->ops[opline-op_array->opcodes].op1_use].definition].opcode == ZEND_FETCH_THIS; } } +#ifdef HAVE_FFI + if (op1_ffi_type && op1_ffi_type->kind == ZEND_FFI_TYPE_STRUCT) { + zend_ffi_field *field = zend_hash_find_ptr(&op1_ffi_type->record.fields, + Z_STR_P(RT_CONSTANT(opline, opline->op2))); + + if (field + && (!field->is_const || opline->opcode != ZEND_FETCH_OBJ_W) + && !field->bits + && ZEND_FFI_TYPE(field->type)->kind != ZEND_FFI_TYPE_VOID + && zend_jit_ffi_supported_type(ZEND_FFI_TYPE(field->type))) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_fetch_obj(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, + avoid_refcounting, field, + RES_REG_ADDR(), + op1_ffi_type, ffi_info)) { + goto jit_failure; + } + goto done; + } + } else if (op1_ffi_type + && op1_ffi_type->kind < ZEND_FFI_TYPE_POINTER + && op1_ffi_type->kind != ZEND_FFI_TYPE_VOID + && zend_string_equals_literal(Z_STR_P(RT_CONSTANT(opline, opline->op2)), "cdata") + && zend_jit_ffi_supported_type(op1_ffi_type)) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_fetch_val(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, + avoid_refcounting, + RES_REG_ADDR(), + op1_ffi_type, ffi_info)) { + goto jit_failure; + } + goto done; + } else if (op1_ffi_symbols) { + zend_ffi_symbol *sym = zend_hash_find_ptr(op1_ffi_symbols, + Z_STR_P(RT_CONSTANT(opline, opline->op2))); + if (sym + && sym->kind == ZEND_FFI_SYM_VAR + && ZEND_FFI_TYPE(sym->type)->kind != ZEND_FFI_TYPE_VOID + && zend_jit_ffi_supported_type(ZEND_FFI_TYPE(sym->type))) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_fetch_sym(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, op1_indirect, + avoid_refcounting, sym, + RES_REG_ADDR(), + op1_ffi_symbols, ffi_info)) { + goto jit_failure; + } + goto done; + } + } +#endif if (!zend_jit_fetch_obj(&ctx, opline, op_array, ssa, ssa_op, op1_info, op1_addr, op1_indirect, ce, ce_is_instanceof, on_this, delayed_fetch_this, avoid_refcounting, op1_ce, @@ -6386,6 +7000,115 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par on_this = op_array->opcodes[op_array_ssa->vars[op_array_ssa->ops[opline-op_array->opcodes].op1_use].definition].opcode == ZEND_FETCH_THIS; } } +#ifdef HAVE_FFI + if (zend_ffi_api && op1_ce == zend_ffi_api->scope_ce && !ZEND_OBSERVER_ENABLED) { + zend_string *name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + + if (zend_string_equals_literal_ci(name, "new")) { + zend_ffi_type *type = zend_jit_ffi_supported_new(opline, op1_ffi_symbols, p); + if (type) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_symbols_guard(&ctx, opline, ssa, + ssa_op->op1_use, -1, op1_info, op1_addr, op1_ffi_symbols, ffi_info)) { + goto jit_failure; + } + // TODO: Guard for FFI::CType argument ??? + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_NEW; + frame_ffi_func_type = type; + frame_ffi_func_ref = IR_UNUSED; + frame_ffi_obj_ref = jit_Z_PTR(jit, op1_addr); + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + frame_ffi_info |= TRACE_FRAME_MASK_FFI_OBJ_DTOR; + } + goto done; + } + } else if (zend_string_equals_literal_ci(name, "cast")) { + zend_ffi_type *type = zend_jit_ffi_supported_cast(opline, op1_ffi_symbols, p); + if (type) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_symbols_guard(&ctx, opline, ssa, + ssa_op->op1_use, -1, op1_info, op1_addr, op1_ffi_symbols, ffi_info)) { + goto jit_failure; + } + // TODO: Guard for FFI::CType argument ??? + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_CAST; + frame_ffi_func_type = type; + frame_ffi_func_ref = IR_UNUSED; + frame_ffi_obj_ref = jit_Z_PTR(jit, op1_addr); + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + frame_ffi_info |= TRACE_FRAME_MASK_FFI_OBJ_DTOR; + } + goto done; + } + } else if (zend_string_equals_literal_ci(name, "type")) { + if (opline->extended_value == 1 + && (p+1)->op == ZEND_JIT_TRACE_INIT_CALL + && (p+2)->op == ZEND_JIT_TRACE_VM + && ((p+2)->opline->opcode == ZEND_SEND_VAL + || (p+2)->opline->opcode == ZEND_SEND_VAL_EX) + && (p+2)->opline->op1_type == IS_CONST) { + zval *zv = RT_CONSTANT((p+2)->opline, (p+2)->opline->op1); + + if (Z_TYPE_P(zv) == IS_STRING) { + zend_ffi_dcl *dcl = zend_ffi_api->cache_type_get(Z_STR_P(zv), op1_ffi_symbols); + + if (dcl + && !ZEND_FFI_TYPE_IS_OWNED(dcl->type) + && (dcl->type->attr & ZEND_FFI_ATTR_PERSISTENT)) { + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_symbols_guard(&ctx, opline, ssa, + ssa_op->op1_use, -1, op1_info, op1_addr, op1_ffi_symbols, ffi_info)) { + goto jit_failure; + } + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_TYPE; + frame_ffi_func_type = dcl->type; + frame_ffi_func_ref = IR_UNUSED; + frame_ffi_obj_ref = jit_Z_PTR(jit, op1_addr); + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + frame_ffi_info |= TRACE_FRAME_MASK_FFI_OBJ_DTOR; + } + goto done; + } + } + } + } else if (op1_ffi_symbols) { + zend_ffi_symbol *sym = zend_hash_find_ptr(op1_ffi_symbols, name); + + if (sym + && sym->kind == ZEND_FFI_SYM_FUNC + && zend_jit_ffi_supported_func(ZEND_FFI_TYPE(sym->type))) { + ir_ref ffi_func_ref; + + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_init_call_sym(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, + sym, + op1_ffi_symbols, ffi_info, &ffi_func_ref)) { + goto jit_failure; + } + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_func_type = ZEND_FFI_TYPE(sym->type); + frame_ffi_func_ref = ffi_func_ref; + frame_ffi_obj_ref = jit_Z_PTR(jit, op1_addr); + if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { + frame_ffi_info |= TRACE_FRAME_MASK_FFI_OBJ_DTOR; + } + goto done; + } + } + } +#endif frame_flags = TRACE_FRAME_MASK_NESTED; if (!zend_jit_init_method_call(&ctx, opline, op_array_ssa->cfg.map ? op_array_ssa->cfg.map[opline - op_array->opcodes] : -1, @@ -6406,6 +7129,99 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par || (opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_PARENT))))) { break; } +#ifdef HAVE_FFI + if (opline->op1_type == IS_CONST + && opline->op2_type == IS_CONST + && !ZEND_OBSERVER_ENABLED) { + zval *zv = RT_CONSTANT(opline, opline->op1); + if (Z_TYPE_P(zv) == IS_STRING + && (zend_string_equals_literal_ci(Z_STR_P(zv), "FFI") + || zend_string_equals_literal_ci(Z_STR_P(zv), "\\FFI"))) { + zval *zv = RT_CONSTANT(opline, opline->op2); + if (Z_TYPE_P(zv) == IS_STRING + && zend_string_equals_literal_ci(Z_STR_P(zv), "addr") + && opline->extended_value == 1) { + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_ADDR; + frame_ffi_func_type = NULL; + frame_ffi_func_ref = IR_UNUSED; + goto done; + } else if (Z_TYPE_P(zv) == IS_STRING + && zend_string_equals_literal_ci(Z_STR_P(zv), "string") + && (opline->extended_value == 1 || opline->extended_value == 2)) { + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_STRING; + frame_ffi_func_type = NULL; + frame_ffi_func_ref = IR_UNUSED; + goto done; + } else if (Z_TYPE_P(zv) == IS_STRING + && zend_string_equals_literal_ci(Z_STR_P(zv), "alignof") + && opline->extended_value == 1) { + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_ALIGNOF; + frame_ffi_func_type = NULL; + frame_ffi_func_ref = IR_UNUSED; + goto done; + } else if (Z_TYPE_P(zv) == IS_STRING + && zend_string_equals_literal_ci(Z_STR_P(zv), "sizeof") + && opline->extended_value == 1) { + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_SIZEOF; + frame_ffi_func_type = NULL; + frame_ffi_func_ref = IR_UNUSED; + goto done; + } else if (Z_TYPE_P(zv) == IS_STRING + && zend_string_equals_literal_ci(Z_STR_P(zv), "typeof") + && opline->extended_value == 1) { + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_TYPEOF; + frame_ffi_func_type = NULL; + frame_ffi_func_ref = IR_UNUSED; + goto done; + } else if (Z_TYPE_P(zv) == IS_STRING + && zend_string_equals_literal_ci(Z_STR_P(zv), "isnull") + && opline->extended_value == 1) { + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_IS_NULL; + frame_ffi_func_type = NULL; + frame_ffi_func_ref = IR_UNUSED; + goto done; + } else if (Z_TYPE_P(zv) == IS_STRING + && zend_string_equals_literal_ci(Z_STR_P(zv), "memcpy") + && opline->extended_value == 3) { + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_MEMCPY; + frame_ffi_func_type = NULL; + frame_ffi_func_ref = IR_UNUSED; + goto done; + } else if (Z_TYPE_P(zv) == IS_STRING + && zend_string_equals_literal_ci(Z_STR_P(zv), "memcmp") + && opline->extended_value == 3) { + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_MEMCMP; + frame_ffi_func_type = NULL; + frame_ffi_func_ref = IR_UNUSED; + goto done; + } else if (Z_TYPE_P(zv) == IS_STRING + && zend_string_equals_literal_ci(Z_STR_P(zv), "memset") + && opline->extended_value == 3) { + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_MEMSET; + frame_ffi_func_type = NULL; + frame_ffi_func_ref = IR_UNUSED; + goto done; + } else if (Z_TYPE_P(zv) == IS_STRING + && zend_string_equals_literal_ci(Z_STR_P(zv), "free") + && opline->extended_value == 1) { + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_info = TRACE_FRAME_FFI_FUNC_FREE; + frame_ffi_func_type = NULL; + frame_ffi_func_ref = IR_UNUSED; + goto done; + } + } + } +#endif if (!zend_jit_init_static_method_call(&ctx, opline, op_array_ssa->cfg.map ? op_array_ssa->cfg.map[opline - op_array->opcodes] : -1, op_array, ssa, ssa_op, frame->call_level, @@ -6414,12 +7230,41 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par } goto done; case ZEND_INIT_DYNAMIC_CALL: +#ifdef HAVE_FFI + if (orig_op2_type != IS_OBJECT + || (op2_ce != zend_ce_closure && !op2_ffi_type)) { + break; + } +#else if (orig_op2_type != IS_OBJECT || op2_ce != zend_ce_closure) { break; } +#endif op2_info = OP2_INFO(); CHECK_OP2_TRACE_TYPE(); frame_flags = TRACE_FRAME_MASK_NESTED; +#ifdef HAVE_FFI + if (op2_ffi_type + && op2_ffi_type->kind == ZEND_FFI_TYPE_POINTER + && ZEND_FFI_TYPE(op2_ffi_type->pointer.type)->kind == ZEND_FFI_TYPE_FUNC + && zend_jit_ffi_supported_func(ZEND_FFI_TYPE(op2_ffi_type->pointer.type))) { + ir_ref ffi_func_ref; + + if (!ffi_info) { + ffi_info = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_jit_ffi_info)); + } + if (!zend_jit_ffi_init_call_obj(&ctx, opline, op_array, ssa, ssa_op, + op1_info, op1_addr, + op2_info, OP2_REG_ADDR(), + op2_ffi_type, ffi_info, &ffi_func_ref)) { + goto jit_failure; + } + frame_flags = TRACE_FRAME_MASK_FFI; + frame_ffi_func_type = ZEND_FFI_TYPE(op2_ffi_type->pointer.type); + frame_ffi_func_ref = ffi_func_ref; + goto done; + } +#endif if (!zend_jit_init_closure_call(&ctx, opline, op_array_ssa->cfg.map ? op_array_ssa->cfg.map[opline - op_array->opcodes] : -1, op_array, ssa, ssa_op, frame->call_level, p + 1, peek_checked_stack - checked_stack)) { goto jit_failure; } @@ -6428,6 +7273,11 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par case ZEND_SEND_UNPACK: if (JIT_G(current_frame) && JIT_G(current_frame)->call) { +#ifdef HAVE_FFI + if (TRACE_FRAME_FFI(JIT_G(current_frame)->call)) { + goto jit_failure; + } +#endif TRACE_FRAME_SET_UNKNOWN_NUM_ARGS(JIT_G(current_frame)->call); } break; @@ -6670,6 +7520,18 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par || opline->opcode == ZEND_FE_RESET_R) { /* keep old value */ type = STACK_TYPE(stack, EX_VAR_TO_NUM(opline->op1.var)); +#ifdef HAVE_FFI + } else if (JIT_G(current_frame) + && JIT_G(current_frame)->call + && TRACE_FRAME_FFI(JIT_G(current_frame)->call) + && (opline->opcode == ZEND_SEND_VAR_EX + || opline->opcode == ZEND_SEND_VAR_NO_REF + || opline->opcode == ZEND_SEND_VAR_NO_REF_EX + || opline->opcode == ZEND_SEND_FUNC_ARG) + && opline->op2_type != IS_CONST) { + /* keep old value */ + type = STACK_TYPE(stack, EX_VAR_TO_NUM(opline->op1.var)); +#endif } SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->op1.var), type, (gen_handler || type == IS_UNKNOWN || !ra || @@ -7010,6 +7872,14 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par call = top; TRACE_FRAME_INIT(call, p->func, frame_flags, num_args); +#ifdef HAVE_FFI + if (TRACE_FRAME_FFI(call)) { + call->call_opline = (const zend_op*)(void*)frame_ffi_func_type; + call->ffi_info = frame_ffi_info; + call->ffi_obj_ref = frame_ffi_obj_ref; + call->ffi_func_ref = frame_ffi_func_ref; + } +#endif call->prev = frame->call; if (!(p->info & ZEND_JIT_TRACE_FAKE_INIT_CALL)) { TRACE_FRAME_SET_LAST_SEND_BY_VAL(call); @@ -7847,6 +8717,24 @@ static void zend_jit_dump_trace(zend_jit_trace_rec *trace_buffer, zend_ssa *tssa ((op1_type & IS_TRACE_REFERENCE) ? "&" : ""); if ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE) { p++; +#ifdef HAVE_FFI + if ((p+1)->op == ZEND_JIT_TRACE_OP1_FFI_TYPE) { + fprintf(stderr, " op1(%sobject of class %s: ", ref, + ZSTR_VAL(p->ce->name)); + p++; + if (ZEND_FFI_TYPE_IS_OWNED(p->ptr)) { + zend_ffi_type holder; + zend_ffi_api->type_print(stderr, zend_jit_ffi_type_pointer_to(p->ptr, &holder)); + } else { + zend_ffi_api->type_print(stderr, p->ptr); + } + fprintf(stderr, ")"); + } else if ((p+1)->op == ZEND_JIT_TRACE_OP1_FFI_SYMBOLS) { + fprintf(stderr, " op1(%sobject of class %s: ffi_symbols)", ref, + ZSTR_VAL(p->ce->name)); + p++; + } else +#endif fprintf(stderr, " op1(%sobject of class %s)", ref, ZSTR_VAL(p->ce->name)); } else { @@ -7860,6 +8748,20 @@ static void zend_jit_dump_trace(zend_jit_trace_rec *trace_buffer, zend_ssa *tssa ((op2_type & IS_TRACE_REFERENCE) ? "&" : ""); if ((p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) { p++; +#ifdef HAVE_FFI + if ((p+1)->op == ZEND_JIT_TRACE_OP2_FFI_TYPE) { + fprintf(stderr, " op2(%sobject of class %s: ", ref, + ZSTR_VAL(p->ce->name)); + p++; + if (ZEND_FFI_TYPE_IS_OWNED(p->ptr)) { + zend_ffi_type holder; + zend_ffi_api->type_print(stderr, zend_jit_ffi_type_pointer_to(p->ptr, &holder)); + } else { + zend_ffi_api->type_print(stderr, p->ptr); + } + fprintf(stderr, ")"); + } else +#endif fprintf(stderr, " op2(%sobject of class %s)", ref, ZSTR_VAL(p->ce->name)); } else { @@ -7871,8 +8773,28 @@ static void zend_jit_dump_trace(zend_jit_trace_rec *trace_buffer, zend_ssa *tssa const char *ref = (op3_type & IS_TRACE_INDIRECT) ? ((op3_type & IS_TRACE_REFERENCE) ? "*&" : "*") : ((op3_type & IS_TRACE_REFERENCE) ? "&" : ""); - const char *type = ((op3_type & ~IS_TRACE_INDIRECT) == 0) ? "undef" : zend_get_type_by_const(op3_type & ~(IS_TRACE_REFERENCE|IS_TRACE_INDIRECT)); - fprintf(stderr, " op3(%s%s)", ref, type); + if ((p+1)->op == ZEND_JIT_TRACE_OP3_TYPE) { + p++; +#ifdef HAVE_FFI + if ((p+1)->op == ZEND_JIT_TRACE_OP3_FFI_TYPE) { + fprintf(stderr, " op3(%sobject of class %s: ", ref, + ZSTR_VAL(p->ce->name)); + p++; + if (ZEND_FFI_TYPE_IS_OWNED(p->ptr)) { + zend_ffi_type holder; + zend_ffi_api->type_print(stderr, zend_jit_ffi_type_pointer_to(p->ptr, &holder)); + } else { + zend_ffi_api->type_print(stderr, p->ptr); + } + fprintf(stderr, ")"); + } else +#endif + fprintf(stderr, " op3(%sobject of class %s)", ref, + ZSTR_VAL(p->ce->name)); + } else { + const char *type = ((op3_type & ~IS_TRACE_INDIRECT) == 0) ? "undef" : zend_get_type_by_const(op3_type & ~(IS_TRACE_REFERENCE|IS_TRACE_INDIRECT)); + fprintf(stderr, " op3(%s%s)", ref, type); + } } } if ((p+1)->op == ZEND_JIT_TRACE_VAL_INFO) { @@ -7961,7 +8883,7 @@ static void zend_jit_dump_trace(zend_jit_trace_rec *trace_buffer, zend_ssa *tssa level, ' ', (p->func && p->func->common.scope) ? ZSTR_VAL(p->func->common.scope->name) : "", (p->func && p->func->common.scope) ? "::" : "", - (p->func && p->func->common.function_name) ? ZSTR_VAL(p->func->common.function_name) : "???"); + (p->func && p->func->common.function_name) ? ZSTR_VAL(p->func->common.function_name) : "???"); } else { fprintf(stderr, " %*c>skip\n", level, ' '); diff --git a/ext/opcache/jit/zend_jit_vm_helpers.c b/ext/opcache/jit/zend_jit_vm_helpers.c index 165896acbdaed..41d8c624c344b 100644 --- a/ext/opcache/jit/zend_jit_vm_helpers.c +++ b/ext/opcache/jit/zend_jit_vm_helpers.c @@ -31,6 +31,10 @@ #include "zend_jit_internal.h" +#ifdef HAVE_FFI +# include "ext/ffi/php_ffi.h" +#endif + #ifdef HAVE_GCC_GLOBAL_REGS # pragma GCC diagnostic ignored "-Wvolatile-register-var" # if defined(__x86_64__) @@ -555,6 +559,29 @@ static int zend_jit_trace_subtrace(zend_jit_trace_rec *trace_buffer, int start, return idx + (end - start); } +#ifdef HAVE_FFI +static zend_ffi_type* zend_jit_trace_ffi_persistent_type(zend_ffi_type *ffi_type) +{ + if (!ZEND_FFI_TYPE_IS_OWNED(ffi_type)) { + if (ffi_type->attr & ZEND_FFI_ATTR_PERSISTENT) { + return ffi_type; + } + } else { + ffi_type = ZEND_FFI_TYPE(ffi_type); + if (ffi_type->kind == ZEND_FFI_TYPE_POINTER) { + ffi_type = ffi_type->pointer.type; + if (!ZEND_FFI_TYPE_IS_OWNED(ffi_type)) { + if (ffi_type->attr & ZEND_FFI_ATTR_PERSISTENT) { + /* OWNED flag means POINTER TO */ + return ZEND_FFI_TYPE_MAKE_OWNED(ffi_type); + } + } + } + } + return NULL; +} +#endif + /* * Trace Linking Rules * =================== @@ -609,7 +636,11 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, size_t offset; int idx, count; uint8_t trace_flags, op1_type, op2_type, op3_type; - zend_class_entry *ce1, *ce2; + zend_class_entry *ce1, *ce2, *ce3; +#ifdef HAVE_FFI + zend_ffi_type *op1_ffi_type, *op2_ffi_type, *op3_ffi_type; + HashTable *op1_ffi_symbols; +#endif const zend_op *link_to_enter_opline = NULL; int backtrack_link_to_enter = -1; int backtrack_recursion = -1; @@ -679,7 +710,11 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, } while (1) { - ce1 = ce2 = NULL; + ce1 = ce2 = ce3 = NULL; +#ifdef HAVE_FFI + op1_ffi_type = op2_ffi_type = op3_ffi_type = NULL; + op1_ffi_symbols = NULL; +#endif op1_type = op2_type = op3_type = IS_UNKNOWN; if ((opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) && opline->opcode != ZEND_ROPE_ADD @@ -703,6 +738,22 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, } if (Z_TYPE_P(zv) == IS_OBJECT) { ce1 = Z_OBJCE_P(zv); +#ifdef HAVE_FFI + if (zend_ffi_api && ce1 == zend_ffi_api->cdata_ce) { + zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); + zend_ffi_type *ffi_type = cdata->type; + op1_ffi_type = zend_jit_trace_ffi_persistent_type(ffi_type); + } else if (zend_ffi_api && ce1 == zend_ffi_api->ctype_ce) { + zend_ffi_ctype *ctype = (zend_ffi_ctype*)Z_OBJ_P(zv); + zend_ffi_type *ffi_type = ctype->type; + op1_ffi_type = zend_jit_trace_ffi_persistent_type(ffi_type); + } else if (zend_ffi_api && ce1 == zend_ffi_api->scope_ce) { + zend_ffi *ffi = (zend_ffi*)Z_OBJ_P(zv); + if (ffi->persistent && ffi->symbols) { + op1_ffi_symbols = ffi->symbols; + } + } +#endif } else if (Z_TYPE_P(zv) == IS_ARRAY) { if (HT_IS_PACKED(Z_ARRVAL_P(zv))) { flags |= IS_TRACE_PACKED; @@ -752,6 +803,17 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, } if (Z_TYPE_P(zv) == IS_OBJECT) { ce2 = Z_OBJCE_P(zv); +#ifdef HAVE_FFI + if (zend_ffi_api && ce2 == zend_ffi_api->cdata_ce) { + zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); + zend_ffi_type *ffi_type = cdata->type; + op2_ffi_type = zend_jit_trace_ffi_persistent_type(ffi_type); + } else if (zend_ffi_api && ce2 == zend_ffi_api->ctype_ce) { + zend_ffi_ctype *ctype = (zend_ffi_ctype*)Z_OBJ_P(zv); + zend_ffi_type *ffi_type = ctype->type; + op2_ffi_type = zend_jit_trace_ffi_persistent_type(ffi_type); + } +#endif } op2_type |= flags; } @@ -778,6 +840,20 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, op3_type = Z_TYPE_P(zv); flags |= IS_TRACE_REFERENCE; } + if (Z_TYPE_P(zv) == IS_OBJECT) { + ce3 = Z_OBJCE_P(zv); +#ifdef HAVE_FFI + if (zend_ffi_api && ce3 == zend_ffi_api->cdata_ce) { + zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); + zend_ffi_type *ffi_type = cdata->type; + op3_ffi_type = zend_jit_trace_ffi_persistent_type(ffi_type); + } else if (zend_ffi_api && ce3 == zend_ffi_api->ctype_ce) { + zend_ffi_ctype *ctype = (zend_ffi_ctype*)Z_OBJ_P(zv); + zend_ffi_type *ffi_type = ctype->type; + op3_ffi_type = zend_jit_trace_ffi_persistent_type(ffi_type); + } +#endif + } op3_type |= flags; } } @@ -786,10 +862,31 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, if (ce1) { TRACE_RECORD(ZEND_JIT_TRACE_OP1_TYPE, 0, ce1); +#ifdef HAVE_FFI + if (op1_ffi_type) { + TRACE_RECORD(ZEND_JIT_TRACE_OP1_FFI_TYPE, 0, op1_ffi_type); + } else if (op1_ffi_symbols) { + TRACE_RECORD(ZEND_JIT_TRACE_OP1_FFI_SYMBOLS, 0, op1_ffi_symbols); + } +#endif } if (ce2) { TRACE_RECORD(ZEND_JIT_TRACE_OP2_TYPE, 0, ce2); +#ifdef HAVE_FFI + if (op2_ffi_type) { + TRACE_RECORD(ZEND_JIT_TRACE_OP2_FFI_TYPE, 0, op2_ffi_type); + } +#endif + } + + if (ce3) { + TRACE_RECORD(ZEND_JIT_TRACE_OP3_TYPE, 0, ce3); +#ifdef HAVE_FFI + if (op3_ffi_type) { + TRACE_RECORD(ZEND_JIT_TRACE_OP3_FFI_TYPE, 0, op3_ffi_type); + } +#endif } switch (opline->opcode) {