/*
  +----------------------------------------------------------------------+
  | parallel                                                             |
  +----------------------------------------------------------------------+
  | Copyright (c) Joe Watkins 2019-2024                                  |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | license@php.net so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author: krakjoe                                                      |
  +----------------------------------------------------------------------+
 */
#ifndef HAVE_PARALLEL_DEPENDENCIES
#define HAVE_PARALLEL_DEPENDENCIES

#include "parallel.h"

static struct {
    pthread_mutex_t mutex;
    HashTable       table;
} php_parallel_dependencies_map;

#define PDM(e) php_parallel_dependencies_map.e

TSRM_TLS struct {
    HashTable activated;
    HashTable used;
} php_parallel_dependencies_globals;

#define PDG(e) php_parallel_dependencies_globals.e

/* {{{ */
static zend_always_inline void php_parallel_dependencies_load_globals_vars(const zend_function *function) {
    zend_string **variables = function->op_array.vars;
    int it = 0,
        end = function->op_array.last_var;

    while (it < end) {
        zend_is_auto_global(variables[it]);
        it++;
    }
} /* }}} */

/* {{{ */
static zend_always_inline void php_parallel_dependencies_load_globals_literals(const zend_function *function) {
    zval *literals = function->op_array.literals;
    int it = 0,
        end = function->op_array.last_literal;

    while (it < end) {
        if (Z_TYPE(literals[it]) == IS_STRING) {
            zend_is_auto_global(Z_STR(literals[it]));
        }
        it++;
    }
} /* }}} */

/* {{{ */
static void php_parallel_dependencies_load_globals(const zend_function *function) {
    if (zend_hash_index_exists(&PDG(activated), (zend_ulong) function->op_array.opcodes)) {
        return;
    }

    php_parallel_dependencies_load_globals_vars(function);
    php_parallel_dependencies_load_globals_literals(function);

#if PHP_VERSION_ID >= 80100
    if (function->op_array.dynamic_func_defs) {
    	uint32_t it = 0, end = function->op_array.num_dynamic_func_defs;
    	
    	while (it < end) {
    	    php_parallel_dependencies_load_globals(
    	    	(zend_function*) function->op_array.dynamic_func_defs[it]);
    	    it++;
    	}
    }
#endif

    zend_hash_index_add_empty_element(&PDG(activated), (zend_ulong) function->op_array.opcodes);
} /* }}} */


void php_parallel_dependencies_store(const zend_function *function) { /* {{{ */
#if PHP_VERSION_ID < 80100
    HashTable dependencies;

    pthread_mutex_lock(&PDM(mutex));

    if (zend_hash_index_exists(&PDM(table), (zend_ulong) function->op_array.opcodes)) {
        pthread_mutex_unlock(&PDM(mutex));
        return;
    }

    memset(&dependencies, 0, sizeof(HashTable));
    {
        zend_op *opline = function->op_array.opcodes,
                *end = opline + function->op_array.last;

        while (opline < end) {
            if (opline->opcode == ZEND_DECLARE_LAMBDA_FUNCTION) {
                zend_string   *key;
                zend_function *dependency;

                PARALLEL_COPY_OPLINE_TO_FUNCTION(function, opline, &key, &dependency);

                dependency = php_parallel_copy_function(dependency, 1);

                if (dependencies.nNumUsed == 0) {
                    zend_hash_init(&dependencies, 8, NULL, NULL, 1);
                }

                zend_hash_add_ptr(
                    &dependencies,
                    php_parallel_copy_string_interned(key),
                    dependency);

                php_parallel_dependencies_store(dependency);
            }
            opline++;
        }
    }

    zend_hash_index_add_mem(
        &PDM(table),
        (zend_ulong) function->op_array.opcodes,
        &dependencies, sizeof(HashTable));

    pthread_mutex_unlock(&PDM(mutex));
#endif
} /* }}} */

void php_parallel_dependencies_load(const zend_function *function) { /* {{{ */
#if PHP_VERSION_ID < 80100
    HashTable *dependencies;
    zend_string *key;
    zend_function *dependency;
#endif

    php_parallel_dependencies_load_globals(function);

#if PHP_VERSION_ID < 80100
    pthread_mutex_lock(&PDM(mutex));
    dependencies = zend_hash_index_find_ptr(
        &PDM(table), (zend_ulong) function->op_array.opcodes);
    pthread_mutex_unlock(&PDM(mutex));

    /* read only table */

    if (!dependencies || !dependencies->nNumUsed) {
        return;
    }

    ZEND_HASH_FOREACH_STR_KEY_PTR(dependencies, key, dependency) {
        if (!zend_hash_exists(EG(function_table), key)) {
            zend_op_array *used =
                (zend_op_array*)
                    php_parallel_copy_function(dependency, 0);

            zend_hash_add_ptr(EG(function_table), key, used);

            ZEND_MAP_PTR_NEW(used->run_time_cache);

            zend_hash_add_empty_element(&PDG(used), key);
        }
    } ZEND_HASH_FOREACH_END();
#endif
} /* }}} */

static void php_parallel_dependencies_dtor(zval *zv) { /* {{{ */
    zend_hash_destroy(Z_PTR_P(zv));
    pefree(Z_PTR_P(zv), 1);
} /* }}} */

PHP_RINIT_FUNCTION(PARALLEL_DEPENDENCIES)
{
    zend_hash_init(&PDG(activated), 32, NULL, NULL, 0);
    zend_hash_init(&PDG(used), 32, NULL, NULL, 0);

    return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(PARALLEL_DEPENDENCIES)
{
    zend_string *key;

    zend_hash_destroy(&PDG(activated));
    ZEND_HASH_FOREACH_STR_KEY(&PDG(used), key) {
        zend_hash_del(EG(function_table), key);
    } ZEND_HASH_FOREACH_END();
    zend_hash_destroy(&PDG(used));

    return SUCCESS;
}

PHP_MINIT_FUNCTION(PARALLEL_DEPENDENCIES)
{
    php_parallel_mutex_init(&PDM(mutex), 1);

    zend_hash_init(&PDM(table), 32, NULL, php_parallel_dependencies_dtor, 1);

    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(PARALLEL_DEPENDENCIES)
{
    zend_hash_destroy(&PDM(table));
    php_parallel_mutex_destroy(&PDM(mutex));

    return SUCCESS;
}
#endif
