/*
 * FixScript Task v0.2 - http://www.fixscript.org/
 * Copyright (c) 2020-2021 Martin Dvorak <jezek2@advel.cz>
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from
 * the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose, 
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>
#if defined(_WIN32)
#include <windows.h>
#else
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#endif
#include "fixtask.h"

enum {
   HANDLE_destroy,
   HANDLE_compare,
   HANDLE_calc_hash,
   HANDLE_to_string,
   HANDLE_mark_refs,
   HANDLE_SIZE
};

#ifdef _WIN32
#define ETIMEDOUT -1000
typedef CRITICAL_SECTION pthread_mutex_t;
typedef HANDLE pthread_cond_t;
#endif

typedef struct {
   HeapCreateFunc create_func;
   void *create_data;
   LoadScriptFunc load_func;
   void *load_data;
} HeapCreateData;

typedef struct {
   volatile int refcnt;
   HeapCreateData hc;
   int load_scripts;
   char *fname, *func_name;
   Heap *comm_heap;
   Value comm_arr, reply_arr;
   int max_messages;
   Value start_params;
   Value task_val;
   pthread_mutex_t mutex;
   pthread_cond_t cond;
} Task;

typedef struct ComputeHeap {
   Heap *heap;
   Value process_func;
   Value process_data;
   Value finish_func;
   Value finish_data;
   Value result;
   Value error;
   ComputeHeapRunFunc run_func;
   void *run_data;
   struct ComputeHeap *active_next;
   struct ComputeHeap *inactive_next;
   struct ComputeHeap *finished_next;
} ComputeHeap;

typedef struct {
   volatile int refcnt;
   int num_cores;
   int num_heaps;
   int quit;
   ComputeHeap *heaps;
   ComputeHeap *active_heaps;
   ComputeHeap *inactive_heaps;
   ComputeHeap *finished_heaps;
   pthread_mutex_t mutex;
   pthread_cond_t *conds;
   pthread_cond_t cond;
} ComputeTasks;

typedef struct ScriptHandle ScriptHandle;

typedef struct {
   volatile int refcnt;
   pthread_mutex_t mutex;
   Heap *heap;
   ScriptHandle *handles;
} ScriptHeap;

typedef struct {
   ScriptHeap *script_heap;
} AsyncHeap;

struct ScriptHandle {
   Heap *heap;
   Value value;
   ScriptHeap *script_heap;
   Value script_heap_val;
   ScriptHandle *prev;
   ScriptHandle *next;
};

#define NUM_HANDLE_TYPES 4
#define HANDLE_TYPE_TASK       (handles_offset+0)
#define HANDLE_TYPE_HEAP       (handles_offset+1)
#define HANDLE_TYPE_ASYNC_HEAP (handles_offset+2)
#define HANDLE_TYPE_HANDLE     (handles_offset+3)

static volatile int handles_offset = 0;
static volatile int heap_create_data_key;
static volatile int cur_task_key;
static volatile int compute_tasks_key;

static volatile pthread_mutex_t *global_mutex;
static Heap *global_heap;
static Value global_hash;


#ifdef _WIN32
static int pthread_mutex_init(pthread_mutex_t *mutex, void *attr)
{
   InitializeCriticalSection(mutex);
   return 0;
}

static int pthread_mutex_destroy(pthread_mutex_t *mutex)
{
   DeleteCriticalSection(mutex);
   return 0;
}

static int pthread_mutex_lock(pthread_mutex_t *mutex)
{
   EnterCriticalSection(mutex);
   return 0;
}

static int pthread_mutex_unlock(pthread_mutex_t *mutex)
{
   LeaveCriticalSection(mutex);
   return 0;
}

static int pthread_cond_init(pthread_cond_t *cond, void *attr)
{
   *cond = CreateEvent(NULL, FALSE, FALSE, NULL);
   if (!(*cond)) {
      return -1;
   }
   return 0;
}

static int pthread_cond_destroy(pthread_cond_t *cond)
{
   CloseHandle(*cond);
   *cond = NULL;
   return 0;
}

static int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
{
   LeaveCriticalSection(mutex);
   WaitForSingleObject(*cond, INFINITE);
   EnterCriticalSection(mutex);
   return 0;
}

static int pthread_cond_timedwait_relative(pthread_cond_t *cond, pthread_mutex_t *mutex, int64_t timeout)
{
   int ret = 0;
   LeaveCriticalSection(mutex);
   if (WaitForSingleObject(*cond, timeout/1000000) == WAIT_TIMEOUT) {
      ret = ETIMEDOUT;
   }
   EnterCriticalSection(mutex);
   return ret;
}

static int pthread_cond_signal(pthread_cond_t *cond)
{
   SetEvent(*cond);
   return 0;
}

#else

static int pthread_cond_timedwait_relative(pthread_cond_t *cond, pthread_mutex_t *mutex, int64_t timeout)
{
   struct timespec ts;
#if defined(__APPLE__)
   struct timeval tv;
   gettimeofday(&tv, NULL);
   ts.tv_sec = tv.tv_sec;
   ts.tv_nsec = tv.tv_usec * 1000;
#else
   clock_gettime(CLOCK_REALTIME, &ts);
#endif
   ts.tv_nsec += timeout % 1000000000;
   ts.tv_sec += ts.tv_nsec / 1000000000 + timeout / 1000000000;
   ts.tv_nsec %= 1000000000;
   return pthread_cond_timedwait(cond, mutex, &ts);
}
#endif


static void *task_handle_func(Heap *heap, int op, void *p1, void *p2)
{
   Task *task = p1;
   char buf[32];

   switch (op) {
      case HANDLE_OP_FREE:
         if (__sync_sub_and_fetch(&task->refcnt, 1) == 0) {
            if (task->comm_heap) {
               fixscript_free_heap(task->comm_heap);
            }
            free(task->fname);
            free(task->func_name);
            pthread_mutex_destroy(&task->mutex);
            pthread_cond_destroy(&task->cond);
            free(task);
         }
         break;

      case HANDLE_OP_COPY:
         (void)__sync_add_and_fetch(&task->refcnt, 1);
         return task;

      case HANDLE_OP_COMPARE:
         return (void *)(intptr_t)(p1 == p2);

      case HANDLE_OP_HASH:
         return p1;
         
      case HANDLE_OP_TO_STRING:
         snprintf(buf, sizeof(buf), "task(%p)", p1);
         return strdup(buf);
   }

   return NULL;
}


#if defined(_WIN32)
static DWORD WINAPI thread_main(void *data)
#else
static void *thread_main(void *data)
#endif
{
   Task *task = data;
   Heap *heap;
   Script *script;
   Value params, *values = NULL, func_val, error;
   int err, num_params;
   char buf[128];

   heap = task->hc.create_func(task->hc.create_data);
   if (!heap) {
      goto error;
   }
   
   script = task->hc.load_func(heap, task->fname, &error, task->hc.load_data);
   if (!script) {
      fprintf(stderr, "%s\n", fixscript_get_compiler_error(heap, error));
      goto error;
   }

   err = fixscript_clone_between(heap, task->comm_heap, task->start_params, &params, task->load_scripts? task->hc.load_func : fixscript_resolve_existing, task->hc.load_data, &error);
   if (err) {
      if (!error.value) {
         fixscript_error(heap, &error, err);
      }
      fixscript_dump_value(heap, error, 1);
      goto error;
   }

   fixscript_unref(task->comm_heap, task->start_params);

   err = fixscript_get_array_length(heap, params, &num_params);
   if (err) {
      fixscript_error(heap, &error, err);
      fixscript_dump_value(heap, error, 1);
      goto error;
   }

   values = malloc(num_params * sizeof(Value));
   if (!values) goto error;

   err = fixscript_get_array_range(heap, params, 0, num_params, values);
   if (err) {
      fixscript_error(heap, &error, err);
      fixscript_dump_value(heap, error, 1);
      goto error;
   }

   func_val = fixscript_get_function(heap, script, task->func_name);
   if (!func_val.value) {
      snprintf(buf, sizeof(buf), "can't find %s in %s", task->func_name, task->fname);
      fixscript_dump_value(heap, fixscript_create_error_string(heap, buf), 1);
      goto error;
   }

   err = fixscript_set_heap_data(heap, cur_task_key, task, NULL);
   if (err) {
      fixscript_dump_value(heap, fixscript_create_error_string(heap, "can't set current task"), 1);
      goto error;
   }

   fixscript_call_args(heap, func_val, num_params, &error, values);
   if (error.value) {
      fixscript_dump_value(heap, error, 1);
   }

   fixscript_set_heap_data(heap, cur_task_key, NULL, NULL);

error:
   free(values);
   fixscript_unref(task->comm_heap, task->task_val);
   fixscript_collect_heap(task->comm_heap);
   task_handle_func(NULL, HANDLE_OP_FREE, task, NULL);
   if (heap) {
      fixscript_free_heap(heap);
   }
#if defined(_WIN32)
   return 0;
#else
   return NULL;
#endif
}


static Value task_create(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   char *fname = NULL, *func_name = NULL;
   Task *task = NULL;
   Value task_val, retval = fixscript_int(0);
#if defined(_WIN32)
   HANDLE thread;
#else
   pthread_t thread;
#endif
   int err;

   err = fixscript_get_string(heap, params[0], 0, -1, &fname, NULL);
   if (!err) {
      err = fixscript_get_string(heap, params[1], 0, -1, &func_name, NULL);
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   task = calloc(1, sizeof(Task));
   if (!task) goto error;

   if (pthread_mutex_init(&task->mutex, NULL) != 0) {
      free(task);
      task = NULL;
      goto error;
   }

   if (pthread_cond_init(&task->cond, NULL) != 0) {
      pthread_mutex_destroy(&task->mutex);
      free(task);
      task = NULL;
      goto error;
   }

   task->refcnt = 1;
   task->hc = *((HeapCreateData *)data);
   task->load_scripts = (num_params == 4 && params[3].value);
   task->comm_heap = fixscript_create_heap();
   if (!task->comm_heap) goto error;

   task->comm_arr = fixscript_create_array(task->comm_heap, 0);
   task->reply_arr = fixscript_create_array(task->comm_heap, 0);
   if (!task->comm_arr.value || !task->reply_arr.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }
   fixscript_ref(task->comm_heap, task->comm_arr);
   fixscript_ref(task->comm_heap, task->reply_arr);
   task->max_messages = 100;

   err = fixscript_clone_between(task->comm_heap, heap, params[2], &task->start_params, NULL, NULL, NULL);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }
   fixscript_ref(task->comm_heap, task->start_params);

   task->refcnt += 2;
   task_val = fixscript_create_value_handle(heap, HANDLE_TYPE_TASK, task, task_handle_func);
   task->task_val = fixscript_create_value_handle(task->comm_heap, HANDLE_TYPE_TASK, task, task_handle_func);
   if (!task_val.value || !task->task_val.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }
   fixscript_ref(task->comm_heap, task->task_val);

   task->fname = fname;
   task->func_name = func_name;

#if defined(_WIN32)
   thread = CreateThread(NULL, 0, thread_main, task, 0, NULL);
   if (!thread)
#else
   if (pthread_create(&thread, NULL, thread_main, task) != 0)
#endif
   {
      task->fname = NULL;
      task->func_name = NULL;
      *error = fixscript_create_error_string(heap, "can't create thread");
      goto error;
   }
#if defined(_WIN32)
   CloseHandle(thread);
#else
   pthread_detach(thread);
#endif
   task = NULL;
   fname = NULL;
   func_name = NULL;

   retval = task_val;

error:
   free(fname);
   free(func_name);
   if (task) {
      task_handle_func(NULL, HANDLE_OP_FREE, task, NULL);
   }
   return retval;
}


static Value task_get(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Task *task;
   Value task_val;
   int err;

   task = fixscript_get_heap_data(heap, cur_task_key);
   if (!task) {
      *error = fixscript_create_error_string(heap, "not in task thread");
      return fixscript_int(0);
   }

   if (pthread_mutex_lock(&task->mutex) != 0) {
      *error = fixscript_create_error_string(heap, "can't lock mutex");
      return fixscript_int(0);
   }

   err = fixscript_clone_between(heap, task->comm_heap, task->task_val, &task_val, NULL, NULL, NULL);

   pthread_mutex_unlock(&task->mutex);

   if (err) {
      return fixscript_error(heap, error, err);
   }
   return task_val;
}


static Value task_send(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Task *task;
   int in_task;
   Value arr, msg;
   int err, len;

   if (num_params == 1) {
      in_task = 1;
      task = fixscript_get_heap_data(heap, cur_task_key);
      msg = params[0];
   }
   else {
      in_task = 0;
      task = fixscript_get_handle(heap, params[0], HANDLE_TYPE_TASK, NULL);
      msg = params[1];
   }

   if (!task) {
      if (in_task) {
         *error = fixscript_create_error_string(heap, "not in task thread");
      }
      else {
         *error = fixscript_create_error_string(heap, "invalid task");
      }
      return fixscript_int(0);
   }

   if (pthread_mutex_lock(&task->mutex) != 0) {
      *error = fixscript_create_error_string(heap, "can't lock mutex");
      return fixscript_int(0);
   }

   if (in_task) {
      arr = task->reply_arr;
   }
   else {
      arr = task->comm_arr;
   }

   for (;;) {
      err = fixscript_get_array_length(task->comm_heap, arr, &len);
      if (err) break;
      if (len < task->max_messages) break;
      pthread_cond_wait(&task->cond, &task->mutex);
   }

   if (!err) {
      err = fixscript_clone_between(task->comm_heap, heap, msg, &msg, NULL, NULL, NULL);
   }
   if (!err) {
      err = fixscript_append_array_elem(task->comm_heap, arr, msg);
   }
   if (err) {
      pthread_mutex_unlock(&task->mutex);
      return fixscript_error(heap, error, err);
   }

   pthread_cond_signal(&task->cond);
   pthread_mutex_unlock(&task->mutex);
   return fixscript_int(0);
}


static Value task_receive(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int wait = (data != NULL);
   int in_task;
   Task *task;
   Value arr, msg;
   int timeout;
   int err, len;

   if (wait) {
      if (num_params == 1) {
         in_task = 1;
         task = fixscript_get_heap_data(heap, cur_task_key);
         timeout = params[0].value;
      }
      else {
         in_task = 0;
         task = fixscript_get_handle(heap, params[0], HANDLE_TYPE_TASK, NULL);
         timeout = params[1].value;
      }
   }
   else {
      if (num_params == 0) {
         in_task = 1;
         task = fixscript_get_heap_data(heap, cur_task_key);
      }
      else {
         in_task = 0;
         task = fixscript_get_handle(heap, params[0], HANDLE_TYPE_TASK, NULL);
      }
      timeout = -1;
   }

   if (!task) {
      if (in_task) {
         *error = fixscript_create_error_string(heap, "not in task thread");
      }
      else {
         *error = fixscript_create_error_string(heap, "invalid task");
      }
      return fixscript_int(0);
   }

   if (pthread_mutex_lock(&task->mutex) != 0) {
      *error = fixscript_create_error_string(heap, "can't lock mutex");
      return fixscript_int(0);
   }

   if (in_task) {
      arr = task->comm_arr;
   }
   else {
      arr = task->reply_arr;
   }

   for (;;) {
      err = fixscript_get_array_length(task->comm_heap, arr, &len);
      if (err) {
         pthread_mutex_unlock(&task->mutex);
         return fixscript_error(heap, error, err);
      }
      if (len > 0) break;

      if (timeout == 0) {
         pthread_mutex_unlock(&task->mutex);
         return fixscript_int(0);
      }
      if (timeout > 0) {
         if (pthread_cond_timedwait_relative(&task->cond, &task->mutex, timeout*1000000LL) == ETIMEDOUT) {
            pthread_mutex_unlock(&task->mutex);
            return fixscript_int(0);
         }
      }
      else {
         pthread_cond_wait(&task->cond, &task->mutex);
      }
   }

   err = fixscript_get_array_elem(task->comm_heap, arr, 0, &msg);
   if (!err) {
      err = fixscript_copy_array(task->comm_heap, arr, 0, arr, 1, len-1);
   }
   if (!err) {
      err = fixscript_set_array_length(task->comm_heap, arr, len-1);
   }
   if (!err) {
      err = fixscript_clone_between(heap, task->comm_heap, msg, &msg, task->load_scripts? task->hc.load_func : fixscript_resolve_existing, task->hc.load_data, error);
   }

   if (len == 1) {
      fixscript_collect_heap(task->comm_heap);
   }

   pthread_cond_signal(&task->cond);
   pthread_mutex_unlock(&task->mutex);

   if (err) {
      if (error->value) {
         *error = fixscript_create_error(heap, *error);
      }
      else {
         fixscript_error(heap, error, err);
      }
      return fixscript_int(0);
   }
   return msg;
}


static Value sleep_func(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
#if defined(_WIN32)
   Sleep(params[0].value);
#else
   usleep(params[0].value * 1000);
#endif
   return fixscript_int(0);
}


static void unref_compute_tasks(ComputeTasks *tasks)
{
   int i;

   if (__sync_sub_and_fetch(&tasks->refcnt, 1) == 0) {
      for (i=0; i<tasks->num_heaps; i++) {
         fixscript_free_heap(tasks->heaps[i].heap);
      }
      for (i=0; i<tasks->num_cores; i++) {
         pthread_cond_destroy(&tasks->conds[i]);
      }
      free(tasks->conds);
      pthread_mutex_destroy(&tasks->mutex);
      pthread_cond_destroy(&tasks->cond);
      free(tasks->heaps);
      free(tasks);
   }
}


typedef struct {
   ComputeTasks *tasks;
   int id;
} ComputeThreadData;

#if defined(_WIN32)
static DWORD WINAPI compute_thread_main(void *data)
#else
static void *compute_thread_main(void *data)
#endif
{
   ComputeThreadData *ctd = data;
   ComputeTasks *tasks;
   ComputeHeap *heap;
   pthread_cond_t *cond;
   int id;

   tasks = ctd->tasks;
   id = ctd->id;
   cond = &tasks->conds[id];
   free(ctd);

   pthread_mutex_lock(&tasks->mutex);
   for (;;) {
      while (!tasks->quit && !tasks->active_heaps) {
         pthread_cond_wait(cond, &tasks->mutex);
      }
      if (tasks->quit) {
         break;
      }
      heap = tasks->active_heaps;
      tasks->active_heaps = heap->active_next;
      heap->active_next = NULL;
      pthread_mutex_unlock(&tasks->mutex);

      if (heap->run_func) {
         heap->run_func(heap->heap, id, heap->run_data);
         fixscript_collect_heap(heap->heap);
      }
      else {
         heap->result = fixscript_call(heap->heap, heap->process_func, 1, &heap->error, heap->process_data);
         fixscript_unref(heap->heap, heap->process_data);
         fixscript_ref(heap->heap, heap->result);
         fixscript_ref(heap->heap, heap->error);
      }

      pthread_mutex_lock(&tasks->mutex);
      if (heap->run_func) {
         heap->inactive_next = tasks->inactive_heaps;
         tasks->inactive_heaps = heap;
         heap->run_func = NULL;
      }
      else {
         heap->finished_next = tasks->finished_heaps;
         tasks->finished_heaps = heap;
      }
      pthread_cond_signal(&tasks->cond);
   }
   pthread_mutex_unlock(&tasks->mutex);

   unref_compute_tasks(tasks);

#if defined(_WIN32)
   return 0;
#else
   return NULL;
#endif
}


static int get_number_of_cores()
{
#if defined(_WIN32)
   SYSTEM_INFO si;
   GetSystemInfo(&si);
   return si.dwNumberOfProcessors;
#else
   return sysconf(_SC_NPROCESSORS_ONLN);
#endif
}


static void free_compute_tasks(void *data)
{
   ComputeTasks *tasks = data;
   int i;

   pthread_mutex_lock(&tasks->mutex);
   tasks->quit = 1;
   tasks->active_heaps = NULL;
   for (i=0; i<tasks->num_cores; i++) {
      pthread_cond_signal(&tasks->conds[i]);
   }
   pthread_mutex_unlock(&tasks->mutex);

   unref_compute_tasks(tasks);
}


static ComputeTasks *get_compute_tasks(Heap *heap, HeapCreateData *hc)
{
   ComputeTasks *tasks;
   int i, err;
   ComputeThreadData *ctd;
#if defined(_WIN32)
   HANDLE thread;
#else
   pthread_t thread;
#endif

   tasks = fixscript_get_heap_data(heap, compute_tasks_key);
   if (tasks) {
      return tasks;
   }

   if (!hc) {
      return NULL;
   }

   tasks = calloc(1, sizeof(ComputeTasks));
   if (!tasks) {
      return NULL;
   }

   tasks->refcnt = 1;
   tasks->num_cores = get_number_of_cores();
   if (tasks->num_cores < 1) tasks->num_cores = 1;
   tasks->num_heaps = tasks->num_cores > 1? tasks->num_cores+1 : 1;

   tasks->heaps = calloc(tasks->num_heaps, sizeof(ComputeHeap));
   if (!tasks->heaps) {
      free(tasks);
      return NULL;
   }

   if (pthread_mutex_init(&tasks->mutex, NULL) != 0) {
      free(tasks->heaps);
      free(tasks);
      return NULL;
   }
   
   if (pthread_cond_init(&tasks->cond, NULL) != 0) {
      pthread_mutex_destroy(&tasks->mutex);
      free(tasks->heaps);
      free(tasks);
      return NULL;
   }

   tasks->conds = calloc(tasks->num_cores, sizeof(pthread_cond_t));
   if (!tasks->conds) {
      pthread_mutex_destroy(&tasks->mutex);
      pthread_cond_destroy(&tasks->cond);
      free(tasks->heaps);
      free(tasks);
      return NULL;
   }

   for (i=0; i<tasks->num_cores; i++) {
      if (pthread_cond_init(&tasks->conds[i], NULL) != 0) {
         while (--i >= 0) {
            pthread_cond_destroy(&tasks->conds[i]);
         }
         pthread_mutex_destroy(&tasks->mutex);
         pthread_cond_destroy(&tasks->cond);
         free(tasks->heaps);
         free(tasks->conds);
         free(tasks);
         return NULL;
      }
   }

   for (i=0; i<tasks->num_heaps; i++) {
      tasks->heaps[i].heap = hc->create_func(hc->create_data);
      if (!tasks->heaps[i].heap) {
         while (--i >= 0) {
            fixscript_free_heap(tasks->heaps[i].heap);
         }
         for (i=0; i<tasks->num_cores; i++) {
            pthread_cond_destroy(&tasks->conds[i]);
         }
         pthread_mutex_destroy(&tasks->mutex);
         pthread_cond_destroy(&tasks->cond);
         free(tasks->heaps);
         free(tasks->conds);
         free(tasks);
         return NULL;
      }
   }
   
   for (i=0; i<tasks->num_cores; i++) {
      ctd = calloc(1, sizeof(ComputeThreadData));
      if (!ctd) {
         free_compute_tasks(tasks);
         return NULL;
      }
      
      ctd->tasks = tasks;
      ctd->id = i;
      (void)__sync_add_and_fetch(&tasks->refcnt, 1);

      #if defined(_WIN32)
      thread = CreateThread(NULL, 0, compute_thread_main, ctd, 0, NULL);
      if (!thread)
      #else
      if (pthread_create(&thread, NULL, compute_thread_main, ctd) != 0)
      #endif
      {
         (void)__sync_sub_and_fetch(&tasks->refcnt, 1);
         free(ctd);
         free_compute_tasks(tasks);
         return NULL;
      }
      #if defined(_WIN32)
      CloseHandle(thread);
      #else
      pthread_detach(thread);
      #endif
   }

   for (i=tasks->num_heaps-1; i>=0; i--) {
      tasks->heaps[i].inactive_next = tasks->inactive_heaps;
      tasks->inactive_heaps = &tasks->heaps[i];
   }

   err = fixscript_set_heap_data(heap, compute_tasks_key, tasks, free_compute_tasks);
   if (err) {
      return NULL;
   }
   return tasks;
}


static void finish_tasks(Heap *heap, Value *error, ComputeTasks *tasks)
{
   ComputeHeap *cheap;
   Value result;
   int err;

   for (;;) {
      cheap = tasks->finished_heaps;
      if (!cheap) break;

      tasks->finished_heaps = cheap->finished_next;
      cheap->finished_next = NULL;

      if (cheap->error.value) {
         err = fixscript_clone_between(heap, cheap->heap, cheap->error, error, fixscript_resolve_existing, NULL, NULL);
         if (err) {
            fixscript_error(heap, error, err);
         }
         else {
            *error = fixscript_create_error(heap, *error);
         }
         fixscript_unref(cheap->heap, cheap->result);
         fixscript_unref(cheap->heap, cheap->error);
         fixscript_collect_heap(cheap->heap);
         cheap->inactive_next = tasks->inactive_heaps;
         tasks->inactive_heaps = cheap;
         return;
      }

      err = fixscript_clone_between(heap, cheap->heap, cheap->result, &result, fixscript_resolve_existing, NULL, NULL);
      fixscript_unref(cheap->heap, cheap->result);
      fixscript_collect_heap(cheap->heap);
      if (err) {
         fixscript_error(heap, error, err);
         cheap->inactive_next = tasks->inactive_heaps;
         tasks->inactive_heaps = cheap;
         return;
      }

      if (cheap->finish_func.value) {
         pthread_mutex_unlock(&tasks->mutex);
         fixscript_call(heap, cheap->finish_func, 2, error, cheap->finish_data, result);
         fixscript_unref(heap, cheap->finish_data);
         pthread_mutex_lock(&tasks->mutex);
         cheap->inactive_next = tasks->inactive_heaps;
         tasks->inactive_heaps = cheap;
         if (error->value) {
            return;
         }
      }
      else {
         cheap->inactive_next = tasks->inactive_heaps;
         tasks->inactive_heaps = cheap;
      }
   }
}


static Value compute_task_run(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   HeapCreateData *hc = data;
   ComputeTasks *tasks;
   ComputeHeap *cheap;
   int i, err;

   tasks = get_compute_tasks(heap, hc);
   if (!tasks) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   if (!params[0].value) {
      *error = fixscript_create_error_string(heap, "must provide process function");
      return fixscript_int(0);
   }

   pthread_mutex_lock(&tasks->mutex);
   
   for (;;) {
      if (tasks->inactive_heaps) break;

      finish_tasks(heap, error, tasks);
      if (error->value) {
         pthread_mutex_unlock(&tasks->mutex);
         return fixscript_int(0);
      }
   
      if (tasks->inactive_heaps) break;

      pthread_cond_wait(&tasks->cond, &tasks->mutex);
   }

   cheap = tasks->inactive_heaps;
   tasks->inactive_heaps = cheap->inactive_next;

   pthread_mutex_unlock(&tasks->mutex);

   err = fixscript_clone_between(cheap->heap, heap, params[0], &cheap->process_func, hc->load_func, hc->load_data, error);
   if (!err) {
      err = fixscript_clone_between(cheap->heap, heap, params[1], &cheap->process_data, hc->load_func, hc->load_data, error);
   }
   if (err) {
      if (error->value) {
         if (fixscript_clone_between(heap, cheap->heap, *error, error, NULL, NULL, NULL) != FIXSCRIPT_SUCCESS) {
            *error = fixscript_int(0);
         }
      }
      if (error->value) {
         *error = fixscript_create_error(heap, *error);
      }
      else {
         fixscript_error(heap, error, err);
      }
      pthread_mutex_lock(&tasks->mutex);
      cheap->inactive_next = tasks->inactive_heaps;
      tasks->inactive_heaps = cheap;
      pthread_mutex_unlock(&tasks->mutex);
      return fixscript_int(0);
   }
   
   if (num_params > 2 && params[2].value) {
      cheap->finish_func = params[2];
      cheap->finish_data = params[3];
   }
   else {
      cheap->finish_func = fixscript_int(0);
      cheap->finish_data = fixscript_int(0);
   }

   fixscript_ref(cheap->heap, cheap->process_data);
   fixscript_ref(heap, cheap->finish_data);

   pthread_mutex_lock(&tasks->mutex);
   cheap->active_next = tasks->active_heaps;
   tasks->active_heaps = cheap;
   for (i=0; i<tasks->num_cores; i++) {
      pthread_cond_signal(&tasks->conds[i]);
   }
   pthread_mutex_unlock(&tasks->mutex);

   return fixscript_int(0);
}


static Value compute_task_check_finished(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ComputeTasks *tasks;

   tasks = get_compute_tasks(heap, NULL);
   if (!tasks) {
      return fixscript_int(0);
   }

   pthread_mutex_lock(&tasks->mutex);
   finish_tasks(heap, error, tasks);
   pthread_mutex_unlock(&tasks->mutex);
   return fixscript_int(0);
}


static Value compute_task_finish_all(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ComputeTasks *tasks;
   ComputeHeap *cheap;
   int num_inactive;

   tasks = get_compute_tasks(heap, NULL);
   if (!tasks) {
      return fixscript_int(0);
   }

   pthread_mutex_lock(&tasks->mutex);
   
   for (;;) {
      num_inactive = 0;
      cheap = tasks->inactive_heaps;
      while (cheap) {
         num_inactive++;
         cheap = cheap->inactive_next;
      }
      if (num_inactive == tasks->num_heaps) break;

      while (!tasks->finished_heaps) {
         pthread_cond_wait(&tasks->cond, &tasks->mutex);
      }

      while (tasks->finished_heaps) {
         finish_tasks(heap, error, tasks);
         if (error->value) {
            pthread_mutex_unlock(&tasks->mutex);
            return fixscript_int(0);
         }
      }
   }

   pthread_mutex_unlock(&tasks->mutex);
   return fixscript_int(0);
}


static void unref_script_heap(ScriptHeap *script_heap)
{
   if (__sync_sub_and_fetch(&script_heap->refcnt, 1) == 0) {
      pthread_mutex_destroy(&script_heap->mutex);
      free(script_heap);
   }
}


static void *script_heap_handle_func(Heap *heap, int op, void *p1, void *p2)
{
   ScriptHeap *script_heap = p1;
   ScriptHandle *h;
   char buf[64];

   switch (op) {
      case HANDLE_OP_FREE:
         pthread_mutex_lock(&script_heap->mutex);
         if (script_heap->heap) {
            fixscript_free_heap(script_heap->heap);
            script_heap->heap = NULL;
         }
         pthread_mutex_unlock(&script_heap->mutex);
         unref_script_heap(script_heap);
         break;
         
      case HANDLE_OP_TO_STRING:
         if (script_heap->heap) {
            #ifdef _WIN32
            snprintf(buf, sizeof(buf), "heap(%p,size=%I64d)", p1, fixscript_heap_size(script_heap->heap));
            #else
            snprintf(buf, sizeof(buf), "heap(%p,size=%lld)", p1, fixscript_heap_size(script_heap->heap));
            #endif
         }
         else {
            snprintf(buf, sizeof(buf), "heap(%p,<destroyed>)", p1);
         }
         return strdup(buf);

      case HANDLE_OP_MARK_REFS:
         for (h = script_heap->handles; h; h = h->next) {
            fixscript_mark_ref(heap, h->value);
         }
         break;
   }

   return NULL;
}


static void *async_heap_handle_func(Heap *heap, int op, void *p1, void *p2)
{
   AsyncHeap *async_heap = p1, *copy;
   char buf[64];

   switch (op) {
      case HANDLE_OP_FREE:
         unref_script_heap(async_heap->script_heap);
         free(async_heap);
         break;
         
      case HANDLE_OP_COPY:
         copy = calloc(1, sizeof(AsyncHeap));
         if (!copy) return NULL;
         copy->script_heap = async_heap->script_heap;
         (void)__sync_add_and_fetch(&copy->script_heap->refcnt, 1);
         return copy;

      case HANDLE_OP_COMPARE:
         return (void *)(intptr_t)(async_heap->script_heap == ((AsyncHeap *)p2)->script_heap);

      case HANDLE_OP_HASH:
         return async_heap->script_heap;

      case HANDLE_OP_TO_STRING:
         snprintf(buf, sizeof(buf), "async_heap(%p)", async_heap->script_heap);
         return strdup(buf);
   }

   return NULL;
}


static void *script_handle_handle_func(Heap *script_heap, int op, void *p1, void *p2)
{
   ScriptHandle *script_handle = p1;
   Heap *heap = script_handle->heap;
   Value func, error, ret;
   char *s;
   int err;

   switch (op) {
      case HANDLE_OP_FREE:
         err = fixscript_get_array_elem(heap, script_handle->value, HANDLE_destroy, &func);
         if (err) {
            fixscript_error(heap, &error, err);
            fixscript_dump_value(heap, error, 1);
         }
         else {
            fixscript_call(heap, func, 2, &error, script_handle->value, script_handle->script_heap_val);
            if (error.value) {
               fixscript_dump_value(heap, error, 1);
            }
         }
         if (script_handle->script_heap->handles == script_handle) {
            script_handle->script_heap->handles = script_handle->next;
         }
         if (script_handle->prev) {
            script_handle->prev->next = script_handle->next;
         }
         if (script_handle->next) {
            script_handle->next->prev = script_handle->prev;
         }
         free(script_handle);
         break;

      case HANDLE_OP_COMPARE:
         err = fixscript_get_array_elem(heap, script_handle->value, HANDLE_compare, &func);
         if (err) {
            fixscript_error(heap, &error, err);
            fixscript_dump_value(heap, error, 1);
            return (void *)1;
         }
         ret = fixscript_call(heap, func, 3, &error, script_handle->value, script_handle->script_heap_val, ((ScriptHandle *)p2)->value);
         if (error.value) {
            fixscript_dump_value(heap, error, 1);
            return (void *)1;
         }
         return (void *)(intptr_t)ret.value;

      case HANDLE_OP_HASH:
         err = fixscript_get_array_elem(heap, script_handle->value, HANDLE_calc_hash, &func);
         if (err) {
            fixscript_error(heap, &error, err);
            fixscript_dump_value(heap, error, 1);
            return (void *)1;
         }
         ret = fixscript_call(heap, func, 2, &error, script_handle->value, script_handle->script_heap_val);
         if (error.value) {
            fixscript_dump_value(heap, error, 1);
            return (void *)1;
         }
         return (void *)(intptr_t)ret.value;
         
      case HANDLE_OP_TO_STRING:
         err = fixscript_get_array_elem(heap, script_handle->value, HANDLE_to_string, &func);
         if (err) {
            fixscript_error(heap, &error, err);
            fixscript_dump_value(heap, error, 1);
            return NULL;
         }
         ret = fixscript_call(heap, func, 2, &error, script_handle->value, script_handle->script_heap_val);
         if (error.value) {
            fixscript_dump_value(heap, error, 1);
            return NULL;
         }
         err = fixscript_get_string(heap, ret, 0, -1, &s, NULL);
         if (err) {
            fixscript_error(heap, &error, err);
            fixscript_dump_value(heap, error, 1);
            return NULL;
         }
         return s;

      case HANDLE_OP_MARK_REFS:
         err = fixscript_get_array_elem(heap, script_handle->value, HANDLE_mark_refs, &func);
         if (err) {
            fixscript_error(heap, &error, err);
            fixscript_dump_value(heap, error, 1);
            return NULL;
         }
         fixscript_call(heap, func, 2, &error, script_handle->value, script_handle->script_heap_val);
         if (error.value) {
            fixscript_dump_value(heap, error, 1);
            return NULL;
         }
         return NULL;
   }

   return NULL;
}


typedef struct {
   ScriptHeap *script_heap;
   Heap *heap;
   Value load_func;
   Value load_data;
   Value heap_val;
} LoadScriptData;

static Script *load_script_func(Heap *heap, const char *fname, Value *error, void *data)
{
   LoadScriptData *lsd = data;
   Script *script;
   char *buf;
   int err;

   fixscript_call(lsd->heap, lsd->load_func, 3, error, lsd->load_data, lsd->heap_val, fixscript_create_string(lsd->heap, fname, -1));
   if (error->value) {
      err = fixscript_clone_between(heap, lsd->heap, *error, error, NULL, NULL, NULL);
      if (err) {
         fixscript_error(heap, error, err);
      }
      return NULL;
   }

   buf = malloc(strlen(fname)+4+1);
   strcpy(buf, fname);
   strcat(buf, ".fix");
   script = fixscript_get(lsd->script_heap->heap, buf);
   free(buf);

   if (!script) {
      *error = fixscript_create_string(heap, "script wasn't loaded by callback function", -1);
   }
   return script;
}


static ScriptHeap *get_script_heap(Heap *heap, Value *error, Value value)
{
   ScriptHeap *script_heap;

   script_heap = fixscript_get_handle(heap, value, HANDLE_TYPE_HEAP, NULL);
   if (!script_heap) {
      *error = fixscript_create_error_string(heap, "invalid heap");
      return NULL;
   }
   if (!script_heap->heap) {
      *error = fixscript_create_error_string(heap, "heap is already destroyed");
      return NULL;
   }
   return script_heap;
}


static int get_script_value(Heap *heap, Value value, Value *out)
{
   Value values[2];
   int err;

   err = fixscript_get_array_range(heap, value, 0, 2, values);
   if (err) {
      return err;
   }

   out->value = values[0].value;
   out->is_array = values[1].value;
   return FIXSCRIPT_SUCCESS;
}


static int create_script_value(Heap *heap, Value value, Value *out)
{
   Value values[2];

   *out = fixscript_create_array(heap, 2);
   if (!out->value) {
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }

   values[0] = fixscript_int(value.value);
   values[1] = fixscript_int(value.is_array);
   return fixscript_set_array_range(heap, *out, 0, 2, values);
}


static Value script_heap_create(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   HeapCreateData *hc = data;
   ScriptHeap *script_heap;
   Value handle;

   script_heap = calloc(1, sizeof(ScriptHeap));
   if (!script_heap) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   if (hc) {
      script_heap->heap = hc->create_func(hc->create_data);
   }
   else {
      script_heap->heap = fixscript_create_heap();
   }
   if (!script_heap->heap) {
      free(script_heap);
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   script_heap->refcnt = 1;
   if (pthread_mutex_init(&script_heap->mutex, NULL) != 0) {
      fixscript_free_heap(script_heap->heap);
      free(script_heap);
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   handle = fixscript_create_value_handle(heap, HANDLE_TYPE_HEAP, script_heap, script_heap_handle_func);
   if (!handle.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }
   return handle;
}


static Value script_heap_destroy(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   pthread_mutex_lock(&script_heap->mutex);
   fixscript_free_heap(script_heap->heap);
   script_heap->heap = NULL;
   pthread_mutex_unlock(&script_heap->mutex);

   return fixscript_int(0);
}


static Value script_heap_collect(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   fixscript_collect_heap(script_heap->heap);
   return fixscript_int(0);
}


static Value script_heap_get_size(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   long long size;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   size = (fixscript_heap_size(script_heap->heap) + 1023) >> 10;
   if (size > INT_MAX) {
      return fixscript_int(INT_MAX);
   }
   return fixscript_int((int)size);
}


static Value script_heap_adjust_size(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   fixscript_adjust_heap_size(script_heap->heap, params[1].value);
   return fixscript_int(0);
}


static Value script_heap_ref(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int do_ref = (int)(intptr_t)data;
   ScriptHeap *script_heap;
   Value value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (do_ref) {
      fixscript_ref(script_heap->heap, value);
   }
   else {
      fixscript_unref(script_heap->heap, value);
   }
   return fixscript_int(0);
}


static Value script_heap_set_time_limit(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   fixscript_set_time_limit(script_heap->heap, params[1].value);
   return fixscript_int(0);
}


static Value script_heap_get_remaining_time(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   return fixscript_int(fixscript_get_remaining_time(script_heap->heap));
}


static Value script_heap_get_async(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   AsyncHeap *async_heap;
   Value handle;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   async_heap = calloc(1, sizeof(AsyncHeap));
   if (!async_heap) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   async_heap->script_heap = script_heap;
   (void)__sync_add_and_fetch(&script_heap->refcnt, 1);

   handle = fixscript_create_value_handle(heap, HANDLE_TYPE_ASYNC_HEAP, async_heap, async_heap_handle_func);
   if (!handle.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }
   return handle;
}


static Value async_heap_stop_execution(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   AsyncHeap *async_heap;

   async_heap = fixscript_get_handle(heap, params[0], HANDLE_TYPE_ASYNC_HEAP, NULL);
   if (!async_heap) {
      *error = fixscript_create_error_string(heap, "invalid async heap");
      return fixscript_int(0);
   }

   pthread_mutex_lock(&async_heap->script_heap->mutex);
   if (async_heap->script_heap->heap) {
      fixscript_stop_execution(async_heap->script_heap->heap);
   }
   pthread_mutex_unlock(&async_heap->script_heap->mutex);
   return fixscript_int(0);
}


static Value script_heap_mark_ref(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   fixscript_mark_ref(script_heap->heap, value);
   return fixscript_int(0);
}


static Value script_heap_create_array(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   value = fixscript_create_array(script_heap->heap, params[1].value);
   if (!value.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   err = create_script_value(heap, value, &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return value;
}


static Value script_heap_set_array_length(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   err = fixscript_set_array_length(script_heap->heap, value, params[2].value);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(0);
}


static Value script_heap_get_array_length(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value;
   int err, len;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   err = fixscript_get_array_length(script_heap->heap, value, &len);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(len);
}


static Value script_heap_is_array(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   return fixscript_int(fixscript_is_array(script_heap->heap, value));
}


static Value script_heap_set_array_elem(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value array, value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &array);
   if (!err) {
      err = get_script_value(heap, params[3], &value);
   }
   if (!err) {
      err = fixscript_set_array_elem(script_heap->heap, array, params[2].value, value);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(0);
}


static Value script_heap_get_array_elem(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value array, value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &array);
   if (!err) {
      err = fixscript_get_array_elem(script_heap->heap, array, params[2].value, &value);
   }
   if (!err) {
      err = create_script_value(heap, value, &value);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return value;
}


static Value script_heap_append_array_elem(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value array, value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &array);
   if (!err) {
      err = get_script_value(heap, params[2], &value);
   }
   if (!err) {
      err = fixscript_append_array_elem(script_heap->heap, array, value);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(0);
}


static Value script_heap_get_array_range(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value array, *values = NULL;
   int64_t size;
   int i, err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      goto error;
   }

   err = get_script_value(heap, params[1], &array);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   size = (int64_t)params[3].value * sizeof(Value);
   if (size < 0 || size > INT_MAX) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   values = malloc((int)size);
   if (!values) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   err = fixscript_get_array_range(script_heap->heap, array, params[2].value, params[3].value, values);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   for (i=0; i<params[3].value; i++) {
      err = create_script_value(heap, values[i], &values[i]);
      if (err) {
         fixscript_error(heap, error, err);
         goto error;
      }
   }

   err = fixscript_set_array_range(heap, params[4], params[5].value, params[3].value, values);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

error:
   free(values);
   return fixscript_int(0);
}


static Value script_heap_set_array_range(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value array, *values = NULL;
   int64_t size;
   int i, err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      goto error;
   }

   err = get_script_value(heap, params[1], &array);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   size = (int64_t)params[3].value * sizeof(Value);
   if (size < 0 || size > INT_MAX) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   values = malloc((int)size);
   if (!values) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   err = fixscript_get_array_range(heap, params[4], params[5].value, params[3].value, values);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   for (i=0; i<params[3].value; i++) {
      err = get_script_value(heap, values[i], &values[i]);
      if (err) {
         fixscript_error(heap, error, err);
         goto error;
      }
   }

   err = fixscript_set_array_range(script_heap->heap, array, params[2].value, params[3].value, values);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

error:
   free(values);
   return fixscript_int(0);
}


static Value script_heap_get_array_values(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value array, *values = NULL;
   int64_t size;
   int i, err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      goto error;
   }

   err = get_script_value(heap, params[1], &array);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   size = (int64_t)params[3].value * sizeof(Value) * 2;
   if (size < 0 || size > INT_MAX) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   values = malloc((int)size);
   if (!values) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   err = fixscript_get_array_range(script_heap->heap, array, params[2].value, params[3].value, values);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   for (i=params[3].value-1; i>=0; i--) {
      values[i*2+0] = fixscript_int(values[i].value);
      values[i*2+1] = fixscript_int(values[i].is_array);
   }

   err = fixscript_set_array_range(heap, params[4], params[5].value, params[3].value*2, values);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

error:
   free(values);
   return fixscript_int(0);
}


static Value script_heap_set_array_values(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value array, *values = NULL;
   int64_t size;
   int i, err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      goto error;
   }

   err = get_script_value(heap, params[1], &array);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   size = (int64_t)params[3].value * sizeof(Value) * 2;
   if (size < 0 || size > INT_MAX) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   values = malloc((int)size);
   if (!values) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   err = fixscript_get_array_range(heap, params[4], params[5].value, params[3].value*2, values);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   for (i=0; i<params[3].value; i++) {
      values[i].value = values[i*2+0].value;
      values[i].is_array = values[i*2+1].value;
   }

   err = fixscript_set_array_range(script_heap->heap, array, params[2].value, params[3].value, values);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

error:
   free(values);
   return fixscript_int(0);
}


static Value script_heap_get_array_numbers(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value array, *values = NULL;
   int64_t size;
   int i, err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      goto error;
   }

   err = get_script_value(heap, params[1], &array);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   size = (int64_t)params[3].value * sizeof(Value);
   if (size < 0 || size > INT_MAX) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   values = malloc((int)size);
   if (!values) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   err = fixscript_get_array_range(script_heap->heap, array, params[2].value, params[3].value, values);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   for (i=0; i<params[3].value; i++) {
      if (!fixscript_is_int(values[i]) && !fixscript_is_float(values[i])) {
         values[i].is_array = 0;
      }
   }

   err = fixscript_set_array_range(heap, params[4], params[5].value, params[3].value, values);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

error:
   free(values);
   return fixscript_int(0);
}


static Value script_heap_set_array_numbers(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value array, *values = NULL;
   int64_t size;
   int i, err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      goto error;
   }

   err = get_script_value(heap, params[1], &array);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   size = (int64_t)params[3].value * sizeof(Value);
   if (size < 0 || size > INT_MAX) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   values = malloc((int)size);
   if (!values) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   err = fixscript_get_array_range(heap, params[4], params[5].value, params[3].value, values);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   for (i=0; i<params[3].value; i++) {
      if (!fixscript_is_int(values[i]) && !fixscript_is_float(values[i])) {
         values[i].is_array = 0;
      }
   }

   err = fixscript_set_array_range(script_heap->heap, array, params[2].value, params[3].value, values);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

error:
   free(values);
   return fixscript_int(0);
}


static Value script_heap_copy_array(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value dest, src;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &dest);
   if (!err) {
      err = get_script_value(heap, params[3], &src);
   }
   if (!err) {
      err = fixscript_copy_array(script_heap->heap, dest, params[2].value, src, params[4].value, params[5].value);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(0);
}


static Value script_heap_create_string(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value *values = NULL, str, ret = fixscript_int(0);
   int64_t size;
   int off, len;
   int i, err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      goto error;
   }

   if (num_params == 2) {
      off = 0;
      err = fixscript_get_array_length(heap, params[1], &len);
      if (err) {
         fixscript_error(heap, error, err);
         goto error;
      }
   }
   else {
      off = params[2].value;
      len = params[3].value;
   }

   size = (int64_t)len * sizeof(Value);
   if (size < 0 || size > INT_MAX) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   values = malloc((int)size);
   if (!values) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   str = fixscript_create_string(script_heap->heap, NULL, 0);
   if (!str.value) {
      fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      goto error;
   }

   err = fixscript_set_array_length(script_heap->heap, str, len);
   if (!err) {
      err = fixscript_get_array_range(heap, params[1], off, len, values);
   }
   if (!err) {
      for (i=0; i<len; i++) {
         values[i].is_array = 0;
      }
      err = fixscript_set_array_range(script_heap->heap, str, 0, len, values);
   }
   if (!err) {
      err = create_script_value(heap, str, &ret);
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

error:
   free(values);
   return ret;
}


static Value script_heap_is_string(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   return fixscript_int(fixscript_is_string(script_heap->heap, value));
}


static Value script_heap_create_hash(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value hash;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   hash = fixscript_create_hash(script_heap->heap);
   if (!hash.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   err = create_script_value(heap, hash, &hash);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return hash;
}


static Value script_heap_is_hash(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   return fixscript_int(fixscript_is_hash(script_heap->heap, value));
}


static Value script_heap_set_hash_elem(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value hash, key, value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &hash);
   if (!err) {
      err = get_script_value(heap, params[2], &key);
   }
   if (!err) {
      err = get_script_value(heap, params[3], &value);
   }
   if (!err) {
      err = fixscript_set_hash_elem(script_heap->heap, hash, key, value);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(0);
}


static Value script_heap_get_hash_elem(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value hash, key, value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &hash);
   if (!err) {
      err = get_script_value(heap, params[2], &key);
   }
   if (!err) {
      err = fixscript_get_hash_elem(script_heap->heap, hash, key, &value);
      if (err == FIXSCRIPT_ERR_KEY_NOT_FOUND) {
         return fixscript_int(0);
      }
   }
   if (!err) {
      err = create_script_value(heap, value, &value);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return value;
}


static Value script_heap_remove_hash_elem(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value hash, key, value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &hash);
   if (!err) {
      err = get_script_value(heap, params[2], &key);
   }
   if (!err) {
      err = fixscript_remove_hash_elem(script_heap->heap, hash, key, &value);
      if (err == FIXSCRIPT_ERR_KEY_NOT_FOUND) {
         return fixscript_int(0);
      }
   }
   if (!err) {
      err = create_script_value(heap, value, &value);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return value;
}


static Value script_heap_clear_hash(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value hash;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &hash);
   if (!err) {
      err = fixscript_clear_hash(script_heap->heap, hash);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(0);
}


static Value script_heap_get_hash_entry(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value hash, key, value, args[2];
   NativeFunc nfunc;
   void *ndata;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   nfunc = fixscript_get_native_func(script_heap->heap, "hash_entry#2", &ndata);
   if (!nfunc) {
      *error = fixscript_create_error_string(heap, "hash_entry native function not found");
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &hash);
   if (!err) {
      args[0] = hash;
      args[1] = params[2];
      key = nfunc(script_heap->heap, &value, 2, args, ndata);
   }
   if (!err) {
      err = create_script_value(heap, key, &key);
   }
   if (!err) {
      err = create_script_value(heap, value, &value);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   *error = value;
   return key;
}


static Value script_heap_create_handle(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   ScriptHandle *script_handle;
   Value handle;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   script_handle = calloc(1, sizeof(ScriptHandle));
   script_handle->heap = heap;
   script_handle->value = params[1];
   script_handle->script_heap = script_heap;
   script_handle->script_heap_val = params[0];

   script_handle->next = script_heap->handles;
   if (script_handle->next) {
      script_handle->next->prev = script_handle;
   }
   script_heap->handles = script_handle;

   handle = fixscript_create_value_handle(script_heap->heap, HANDLE_TYPE_HANDLE, script_handle, script_handle_handle_func);
   if (!handle.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   err = create_script_value(heap, handle, &handle);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return handle;
}


static Value script_heap_is_handle(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   return fixscript_int(fixscript_get_handle(script_heap->heap, value, -1, NULL) != NULL);
}


static Value script_heap_get_handle(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   ScriptHandle *script_handle;
   Value handle;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &handle);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   script_handle = fixscript_get_handle(script_heap->heap, handle, HANDLE_TYPE_HANDLE, NULL);
   if (!script_handle) {
      *error = fixscript_create_error_string(heap, "invalid handle");
      return fixscript_int(0);
   }

   return script_handle->value;
}


static Value script_heap_create_weak_ref(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value ref, value, container = fixscript_int(0), key = fixscript_int(0);
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (!err && num_params >= 3) {
      err = get_script_value(heap, params[2], &container);
   }
   if (!err && num_params >= 4) {
      err = get_script_value(heap, params[3], &key);
   }
   if (!err) {
      err = fixscript_create_weak_ref(script_heap->heap, value, num_params >= 3? &container : NULL, num_params >= 4? &key : NULL, &ref);
   }
   if (!err) {
      err = create_script_value(heap, ref, &ref);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return ref;
}


static Value script_heap_get_weak_ref(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value ref, value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &ref);
   if (!err) {
      err = fixscript_get_weak_ref(script_heap->heap, ref, &value);
   }
   if (!err) {
      err = create_script_value(heap, value, &value);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return value;
}


static Value script_heap_is_weak_ref(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   return fixscript_int(fixscript_is_weak_ref(script_heap->heap, value));
}


static Value script_heap_create_error(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   value = fixscript_create_error(script_heap->heap, value);
   if (!value.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   err = create_script_value(heap, value, &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return value;
}


static Value script_heap_dump_value(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value;
   int newlines = 1;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (num_params == 3) {
      newlines = params[2].value;
   }

   err = fixscript_dump_value(script_heap->heap, value, newlines);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(0);
}


static Value script_heap_to_string(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value, ret;
   char *s;
   int newlines = 0;
   int err, len;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (num_params == 3) {
      newlines = params[2].value;
   }

   err = fixscript_to_string(script_heap->heap, value, newlines, &s, &len);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   ret = fixscript_create_string(heap, s, len);
   free(s);
   if (!ret.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }
   return ret;
}


static Value script_heap_clone(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int deep = (int)(intptr_t)data;
   ScriptHeap *script_heap;
   Value value;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &value);
   if (!err) {
      err = fixscript_clone(script_heap->heap, value, deep, &value);
   }
   if (!err) {
      err = create_script_value(heap, value, &value);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return value;
}


static Value script_heap_clone_to(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   LoadScriptData lsd;
   LoadScriptFunc load_func = NULL;
   void *load_data = NULL;
   Value clone;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   if (num_params == 4 && params[2].value) {
      lsd.script_heap = script_heap;
      lsd.heap = heap;
      lsd.load_func = params[2];
      lsd.load_data = params[3];
      lsd.heap_val = params[0];
      if (lsd.load_func.value) {
         load_func = load_script_func;
         load_data = &lsd;
      }
   }

   err = fixscript_clone_between(script_heap->heap, heap, params[1], &clone, load_func, load_data, error);
   if (err) {
      if (error->value) {
         if (fixscript_clone_between(heap, script_heap->heap, *error, error, NULL, NULL, NULL) != FIXSCRIPT_SUCCESS) {
            *error = fixscript_int(0);
         }
      }
      if (error->value) {
         *error = fixscript_create_error(heap, *error);
      }
      else {
         fixscript_error(heap, error, err);
      }
      return fixscript_int(0);
   }

   err = create_script_value(heap, clone, &clone);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return clone;
}


static Value script_heap_clone_from(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value clone;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &params[1]);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   err = fixscript_clone_between(heap, script_heap->heap, params[1], &clone, num_params == 3 && params[2].value? fixscript_resolve_existing : NULL, NULL, error);
   if (err) {
      if (error->value) {
         *error = fixscript_create_error(heap, *error);
      }
      else {
         fixscript_error(heap, error, err);
      }
      return fixscript_int(0);
   }

   return clone;
}


static Value script_heap_clone_between(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap_dest;
   ScriptHeap *script_heap_src;
   LoadScriptData lsd;
   LoadScriptFunc load_func = NULL;
   void *load_data = NULL;
   Value clone;
   int err;

   script_heap_dest = get_script_heap(heap, error, params[0]);
   if (!script_heap_dest) {
      return fixscript_int(0);
   }

   script_heap_src = get_script_heap(heap, error, params[1]);
   if (!script_heap_src) {
      return fixscript_int(0);
   }

   if (num_params == 5 && params[3].value) {
      lsd.script_heap = script_heap_dest;
      lsd.heap = heap;
      lsd.load_func = params[3];
      lsd.load_data = params[4];
      lsd.heap_val = params[0];
      if (lsd.load_func.value) {
         load_func = load_script_func;
         load_data = &lsd;
      }
   }

   err = get_script_value(heap, params[2], &params[2]);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   err = fixscript_clone_between(script_heap_dest->heap, script_heap_src->heap, params[2], &clone, load_func, load_data, error);
   if (err) {
      if (error->value) {
         if (fixscript_clone_between(heap, script_heap_dest->heap, *error, error, NULL, NULL, NULL) != FIXSCRIPT_SUCCESS) {
            *error = fixscript_int(0);
         }
      }
      if (error->value) {
         *error = fixscript_create_error(heap, *error);
      }
      else {
         fixscript_error(heap, error, err);
      }
      return fixscript_int(0);
   }

   err = create_script_value(heap, clone, &clone);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return clone;
}


static Value script_heap_serialize(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value, array;
   char *buf = NULL;
   int64_t size;
   int err, len, pos=0;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[num_params-1], &value);
   if (!err) {
      err = fixscript_serialize_to_array(script_heap->heap, &buf, &len, value);
   }
   if (!err) {
      if (num_params == 3) {
         array = params[1];
         err = fixscript_get_array_length(heap, array, &pos);
         if (!err) {
            size = (int64_t)pos + len;
            if (size > INT_MAX) {
               err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }
         }
         if (!err) {
            err = fixscript_set_array_length(heap, array, pos + len);
         }
      }
      else {
         array = fixscript_create_array(heap, len);
         if (!array.value) {
            err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
      }
   }
   if (!err) {
      err = fixscript_set_array_bytes(heap, array, pos, len, buf);
   }
   free(buf);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return array;
}


static Value script_heap_unserialize(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value value;
   char *buf;
   int err=0, off, off_ref, len;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   if (num_params == 2) {
      off = 0;
      err = fixscript_get_array_length(heap, params[1], &len);
   }
   else if (num_params == 3) {
      err = fixscript_get_array_elem(heap, params[2], 0, &value);
      if (!err) {
         off = value.value;
         err = fixscript_get_array_length(heap, params[1], &len);
         len -= off;
      }
   }
   else {
      off = params[2].value;
      len = params[3].value;
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }

   err = fixscript_lock_array(heap, params[1], off, len, (void **)&buf, 1, 1);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   err = fixscript_unserialize_from_array(script_heap->heap, buf, num_params == 3? &off_ref : NULL, len, &value);
   if (!err && num_params == 3) {
      err = fixscript_set_array_elem(heap, params[2], 0, fixscript_int(off + off_ref));
   }
   if (!err) {
      err = create_script_value(heap, value, &value);
   }

   fixscript_unlock_array(heap, params[1], off, len, (void **)&buf, 1, 0);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return value;
}


static Value script_heap_load(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   char *script_name = NULL;
   char *src = NULL;
   const char *error_msg;
   Script *script;
   LoadScriptData lsd;
   int err, len;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      goto error;
   }

   err = fixscript_get_string(heap, params[1], 0, -1, &script_name, NULL);
   if (!err) {
      err = fixscript_get_array_length(heap, params[2], &len);
   }
   if (!err) {
      src = malloc(len+1);
      if (!src) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (!err) {
      err = fixscript_get_array_bytes(heap, params[2], 0, len, src);
      src[len] = 0;
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   lsd.script_heap = script_heap;
   lsd.heap = heap;
   lsd.load_func = params[3];
   lsd.load_data = params[4];
   lsd.heap_val = params[0];
   script = fixscript_load(script_heap->heap, src, script_name, error, lsd.load_func.value? load_script_func : NULL, &lsd);
   if (!script) {
      error_msg = fixscript_get_compiler_error(script_heap->heap, *error);
      if (strchr(error_msg, '\n')) {
         err = fixscript_clone_between(heap, script_heap->heap, *error, error, NULL, NULL, NULL);
         if (err) {
            fixscript_error(heap, error, err);
         }
         else {
            *error = fixscript_create_error(heap, *error);
         }
      }
      else {
         *error = fixscript_create_error_string(heap, error_msg);
      }
      goto error;
   }

error:
   free(script_name);
   free(src);
   return fixscript_int(0);
}


static Value script_heap_load_script(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   HeapCreateData *hc = data;
   ScriptHeap *script_heap;
   Script *script;
   char *name;
   const char *error_msg;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = fixscript_get_string(heap, params[1], 0, -1, &name, NULL);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   script = hc->load_func(script_heap->heap, name, error, hc->load_data);
   free(name);
   if (!script) {
      error_msg = fixscript_get_compiler_error(script_heap->heap, *error);
      if (strchr(error_msg, '\n')) {
         err = fixscript_clone_between(heap, script_heap->heap, *error, error, NULL, NULL, NULL);
         if (err) {
            fixscript_error(heap, error, err);
         }
         else {
            *error = fixscript_create_error(heap, *error);
         }
      }
      else {
         *error = fixscript_create_error_string(heap, error_msg);
      }
   }
   return fixscript_int(0);
}


static Value script_heap_reload(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   char *script_name = NULL;
   char *src = NULL;
   Script *script;
   LoadScriptData lsd;
   int err, len;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      goto error;
   }

   err = fixscript_get_string(heap, params[1], 0, -1, &script_name, NULL);
   if (!err) {
      err = fixscript_get_array_length(heap, params[2], &len);
   }
   if (!err) {
      src = malloc(len+1);
      if (!src) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (!err) {
      err = fixscript_get_array_bytes(heap, params[2], 0, len, src);
      src[len] = 0;
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   lsd.script_heap = script_heap;
   lsd.heap = heap;
   lsd.load_func = params[3];
   lsd.load_data = params[4];
   lsd.heap_val = params[0];
   script = fixscript_reload(script_heap->heap, src, script_name, error, lsd.load_func.value? load_script_func : NULL, &lsd);
   if (!script) {
      err = fixscript_clone_between(heap, script_heap->heap, *error, error, NULL, NULL, NULL);
      if (err) {
         fixscript_error(heap, error, err);
      }
      else {
         *error = fixscript_create_error(heap, *error);
      }
      goto error;
   }

error:
   free(script_name);
   free(src);
   return fixscript_int(0);
}


static Value script_heap_is_loaded(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Script *script;
   char *name;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = fixscript_get_string(heap, params[1], 0, -1, &name, NULL);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   script = fixscript_get(script_heap->heap, name);
   free(name);
   return fixscript_int(script != NULL);
}


static Value script_heap_get_function(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   char *script_name = NULL;
   char *func_name = NULL;
   Script *script;
   Value func, ret=fixscript_int(0);
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      goto error;
   }
   
   err = fixscript_get_string(heap, params[1], 0, -1, &script_name, NULL);
   if (!err) {
      err = fixscript_get_string(heap, params[2], 0, -1, &func_name, NULL);
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   script = fixscript_get(script_heap->heap, script_name);
   if (!script) {
      *error = fixscript_create_error_string(heap, "script not found");
      goto error;
   }

   func = fixscript_get_function(script_heap->heap, script, func_name);
   if (!func.value) {
      *error = fixscript_create_error_string(heap, "function not found");
      goto error;
   }

   err = create_script_value(heap, func, &func);
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   ret = func;

error:
   free(script_name);
   free(func_name);
   return ret;
}


static Value script_heap_get_function_info(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value func, values[3], ret;
   char *script_name;
   char *func_name;
   int err, num_args;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = get_script_value(heap, params[1], &func);
   if (!err) {
      err = fixscript_get_function_name(script_heap->heap, func, &script_name, &func_name, &num_args);
   }
   if (!err) {
      values[0] = fixscript_create_string(heap, script_name, -1);
      values[1] = fixscript_create_string(heap, func_name, -1);
      values[2] = fixscript_int(num_args);
      free(script_name);
      free(func_name);
      if (!values[0].value || !values[1].value) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (!err) {
      ret = fixscript_create_array(heap, 3);
      if (!ret.value) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (!err) {
      err = fixscript_set_array_range(heap, ret, 0, 3, values);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return ret;
}


static Value script_heap_run(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   char *script_name = NULL;
   char *func_name = NULL;
   Value *args = NULL, script_values[2], func, array, ret=fixscript_int(0);
   Script *script;
   int64_t size;
   int i, err, len;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      goto error;
   }

   err = fixscript_get_string(heap, params[1], 0, -1, &script_name, NULL);
   if (!err) {
      err = fixscript_get_string(heap, params[2], 0, -1, &func_name, NULL);
   }
   if (!err) {
      err = fixscript_get_array_length(heap, params[3], &len);
   }
   if (!err) {
      size = len * sizeof(Value);
      if (size < 0 || size > INT_MAX) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      else {
         args = malloc((int)size);
         if (!args) {
            err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
      }
   }
   if (!err) {
      err = fixscript_get_array_range(heap, params[3], 0, len, args);
   }
   if (!err) {
      for (i=0; i<len; i++) {
         err = get_script_value(heap, args[i], &args[i]);
         if (err) break;
      }
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   script = fixscript_get(script_heap->heap, script_name);
   if (!script) {
      *error = fixscript_create_error_string(heap, "script not found");
      goto error;
   }

   func = fixscript_get_function(script_heap->heap, script, func_name);
   if (!func.value) {
      *error = fixscript_create_error_string(heap, "function not found");
      goto error;
   }
   
   script_values[0] = fixscript_call_args(script_heap->heap, func, len, &script_values[1], args);

   err = create_script_value(heap, script_values[0], &script_values[0]);
   if (!err) {
      err = create_script_value(heap, script_values[1], &script_values[1]);
   }
   if (!err) {
      array = fixscript_create_array(heap, 2);
      if (!array.value) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (!err) {
      err = fixscript_set_array_range(heap, array, 0, 2, script_values);
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   ret = array;

error:
   free(script_name);
   free(func_name);
   free(args);
   return ret;
}


static Value script_heap_call(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   Value *args = NULL, script_values[2], array, func, ret = fixscript_int(0);
   int64_t size;
   int i, err, len;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      goto error;
   }

   err = get_script_value(heap, params[1], &func);
   if (!err) {
      err = fixscript_get_array_length(heap, params[2], &len);
   }
   if (!err) {
      size = len * sizeof(Value);
      if (size < 0 || size > INT_MAX) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      else {
         args = malloc((int)size);
         if (!args) {
            err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
      }
   }
   if (!err) {
      err = fixscript_get_array_range(heap, params[2], 0, len, args);
   }
   if (!err) {
      for (i=0; i<len; i++) {
         err = get_script_value(heap, args[i], &args[i]);
         if (err) break;
      }
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }
   
   script_values[0] = fixscript_call_args(script_heap->heap, func, len, &script_values[1], args);

   err = create_script_value(heap, script_values[0], &script_values[0]);
   if (!err) {
      err = create_script_value(heap, script_values[1], &script_values[1]);
   }
   if (!err) {
      array = fixscript_create_array(heap, 2);
      if (!array.value) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }
   if (!err) {
      err = fixscript_set_array_range(heap, array, 0, 2, script_values);
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   ret = array;

error:
   free(args);
   return ret;
}


typedef struct {
   Value script_heap_val;
   Heap *heap;
   Value func;
   Value data;
} ScriptNativeFunc;

static Value script_native_func(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptNativeFunc *snf = data;
   Value ret, args, host_error, values[2];
   int i, err=0;

   args = fixscript_create_array(snf->heap, num_params);
   if (!args.value) {
      err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }
   if (!err) {
      for (i=0; i<num_params; i++) {
         err = create_script_value(snf->heap, params[i], &params[i]);
         if (err) break;
      }
   }
   if (!err) {
      err = fixscript_set_array_range(snf->heap, args, 0, num_params, params);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }

   ret = fixscript_call(snf->heap, snf->func, 3, &host_error, snf->data, snf->script_heap_val, args);
   if (host_error.value) {
      err = fixscript_clone_between(heap, snf->heap, host_error, &host_error, NULL, NULL, NULL);
      if (err) {
         *error = fixscript_create_error_string(heap, "unknown error");
      }
      else {
         *error = fixscript_create_error(heap, host_error);
      }
      return fixscript_int(0);
   }
   if (!ret.value) {
      *error = fixscript_create_error_string(heap, "return value not provided");
      return fixscript_int(0);
   }

   err = fixscript_get_array_range(snf->heap, ret, 0, 2, values);
   if (!err) {
      err = get_script_value(snf->heap, values[0], &values[0]);
   }
   if (!err) {
      err = get_script_value(snf->heap, values[1], &values[1]);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }

   *error = values[1];
   return values[0];
}

static Value script_heap_register_native_function(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   ScriptHeap *script_heap;
   ScriptNativeFunc *snf;
   char *name;
   int err;

   script_heap = get_script_heap(heap, error, params[0]);
   if (!script_heap) {
      return fixscript_int(0);
   }

   err = fixscript_get_string(heap, params[1], 0, -1, &name, NULL);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   snf = calloc(1, sizeof(ScriptNativeFunc));
   if (!snf) {
      free(name);
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   snf->script_heap_val = params[0];
   snf->heap = heap;
   snf->func = params[2];
   snf->data = params[3];
   fixscript_ref(heap, snf->data);
   fixscript_register_cleanup(heap, free, snf);

   fixscript_register_native_func(script_heap->heap, name, script_native_func, snf);
   free(name);
   return fixscript_int(0);
}


static pthread_mutex_t *get_global_mutex()
{
   pthread_mutex_t *mutex, *new_mutex;

   mutex = (pthread_mutex_t *)global_mutex;
   if (mutex) {
      return mutex;
   }
   
   new_mutex = calloc(1, sizeof(pthread_mutex_t));
   if (!new_mutex) {
      return NULL;
   }
   if (pthread_mutex_init(new_mutex, NULL) != 0) {
      free(new_mutex);
      return NULL;
   }
   
   mutex = (pthread_mutex_t *)__sync_val_compare_and_swap(&global_mutex, NULL, new_mutex);
   if (mutex) {
      pthread_mutex_destroy(new_mutex);
      free(new_mutex);
   }
   else {
      mutex = new_mutex;
   }
   return mutex;
}


static int ensure_global_heap()
{
   if (!global_heap) {
      global_heap = fixscript_create_heap();
      if (!global_heap) {
         return 0;
      }
      global_hash = fixscript_create_hash(global_heap);
      fixscript_ref(global_heap, global_hash);
   }
   return 1;
}


static Value global_set(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   pthread_mutex_t *mutex;
   Value key, value;
   int err;
   
   mutex = get_global_mutex();
   if (!mutex) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   pthread_mutex_lock(mutex);
   if (!ensure_global_heap()) {
      pthread_mutex_unlock(mutex);
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   err = fixscript_clone_between(global_heap, heap, params[0], &key, NULL, NULL, NULL);
   if (!err) {
      err = fixscript_clone_between(global_heap, heap, params[1], &value, NULL, NULL, NULL);
   }
   if (!err) {
      err = fixscript_set_hash_elem(global_heap, global_hash, key, value);
   }
   if (err) {
      pthread_mutex_unlock(mutex);
      return fixscript_error(heap, error, err);
   }

   pthread_mutex_unlock(mutex);

   return fixscript_int(0);
}


static Value global_get(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   HeapCreateData *hc = data;
   pthread_mutex_t *mutex;
   Value key, value;
   int err;
   
   mutex = get_global_mutex();
   if (!mutex) {
      return fixscript_int(0);
   }

   pthread_mutex_lock(mutex);

   if (!global_heap) {
      pthread_mutex_unlock(mutex);
      return fixscript_int(0);
   }

   err = fixscript_clone_between(global_heap, heap, params[0], &key, NULL, NULL, NULL);
   if (!err) {
      err = fixscript_get_hash_elem(global_heap, global_hash, key, &value);
      if (err == FIXSCRIPT_ERR_KEY_NOT_FOUND) {
         value = fixscript_int(0);
         err = FIXSCRIPT_SUCCESS;
      }
   }
   if (err) {
      pthread_mutex_unlock(mutex);
      return fixscript_error(heap, error, err);
   }

   err = fixscript_clone_between(heap, global_heap, value, &value, hc->load_func, hc->load_data, error);
   pthread_mutex_unlock(mutex);

   if (err) {
      if (error->value) {
         *error = fixscript_create_error(heap, *error);
      }
      else {
         fixscript_error(heap, error, err);
      }
      return fixscript_int(0);
   }

   return value;
}


static Value global_add(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   pthread_mutex_t *mutex;
   Value key, value;
   int err, prev_value=0;
   
   mutex = get_global_mutex();
   if (!mutex) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   pthread_mutex_lock(mutex);
   if (!ensure_global_heap()) {
      pthread_mutex_unlock(mutex);
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   err = fixscript_clone_between(global_heap, heap, params[0], &key, NULL, NULL, NULL);
   if (!err) {
      err = fixscript_get_hash_elem(global_heap, global_hash, key, &value);
      if (err == FIXSCRIPT_ERR_KEY_NOT_FOUND) {
         value = fixscript_int(0);
         err = FIXSCRIPT_SUCCESS;
      }
   }
   if (!err) {
      prev_value = value.value;
      value = fixscript_int((uint32_t)value.value + (uint32_t)params[1].value);
      err = fixscript_set_hash_elem(global_heap, global_hash, key, value);
   }
   if (err) {
      pthread_mutex_unlock(mutex);
      return fixscript_error(heap, error, err);
   }

   pthread_mutex_unlock(mutex);

   return fixscript_int(prev_value);
}


void fixtask_register_functions(Heap *heap, HeapCreateFunc create_func, void *create_data, LoadScriptFunc load_func, void *load_data)
{
   HeapCreateData *hc;

   fixscript_register_handle_types(&handles_offset, NUM_HANDLE_TYPES);
   fixscript_register_heap_key(&heap_create_data_key);
   fixscript_register_heap_key(&cur_task_key);
   fixscript_register_heap_key(&compute_tasks_key);

   hc = malloc(sizeof(HeapCreateData));
   hc->create_func = create_func;
   hc->create_data = create_data;
   hc->load_func = load_func;
   hc->load_data = load_data;
   fixscript_set_heap_data(heap, heap_create_data_key, hc, free);

   fixscript_register_native_func(heap, "task_create#3", task_create, hc);
   fixscript_register_native_func(heap, "task_create#4", task_create, hc);
   fixscript_register_native_func(heap, "task_get#0", task_get, NULL);
   fixscript_register_native_func(heap, "task_send#1", task_send, NULL);
   fixscript_register_native_func(heap, "task_send#2", task_send, NULL);
   fixscript_register_native_func(heap, "task_receive#0", task_receive, (void *)0);
   fixscript_register_native_func(heap, "task_receive#1", task_receive, (void *)0);
   fixscript_register_native_func(heap, "task_receive_wait#1", task_receive, (void *)1);
   fixscript_register_native_func(heap, "task_receive_wait#2", task_receive, (void *)1);
   fixscript_register_native_func(heap, "task_sleep#1", sleep_func, NULL);

   fixscript_register_native_func(heap, "compute_task_run#2", compute_task_run, hc);
   fixscript_register_native_func(heap, "compute_task_run#4", compute_task_run, hc);
   fixscript_register_native_func(heap, "compute_task_check_finished#0", compute_task_check_finished, NULL);
   fixscript_register_native_func(heap, "compute_task_finish_all#0", compute_task_finish_all, NULL);

   fixscript_register_native_func(heap, "heap_create#0", script_heap_create, NULL);
   fixscript_register_native_func(heap, "heap_create_full#0", script_heap_create, hc);
   fixscript_register_native_func(heap, "heap_destroy#1", script_heap_destroy, NULL);
   fixscript_register_native_func(heap, "heap_collect#1", script_heap_collect, NULL);
   fixscript_register_native_func(heap, "heap_get_size#1", script_heap_get_size, NULL);
   fixscript_register_native_func(heap, "heap_adjust_size#2", script_heap_adjust_size, NULL);
   fixscript_register_native_func(heap, "heap_ref#2", script_heap_ref, (void *)1);
   fixscript_register_native_func(heap, "heap_unref#2", script_heap_ref, (void *)0);
   fixscript_register_native_func(heap, "heap_set_time_limit#2", script_heap_set_time_limit, NULL);
   fixscript_register_native_func(heap, "heap_get_remaining_time#1", script_heap_get_remaining_time, NULL);
   fixscript_register_native_func(heap, "heap_get_async#1", script_heap_get_async, NULL);
   fixscript_register_native_func(heap, "async_heap_stop_execution#1", async_heap_stop_execution, NULL);
   fixscript_register_native_func(heap, "heap_mark_ref#2", script_heap_mark_ref, NULL);
   fixscript_register_native_func(heap, "heap_create_array#2", script_heap_create_array, NULL);
   fixscript_register_native_func(heap, "heap_set_array_length#3", script_heap_set_array_length, NULL);
   fixscript_register_native_func(heap, "heap_get_array_length#2", script_heap_get_array_length, NULL);
   fixscript_register_native_func(heap, "heap_is_array#2", script_heap_is_array, NULL);
   fixscript_register_native_func(heap, "heap_set_array_elem#4", script_heap_set_array_elem, NULL);
   fixscript_register_native_func(heap, "heap_get_array_elem#3", script_heap_get_array_elem, NULL);
   fixscript_register_native_func(heap, "heap_append_array_elem#3", script_heap_append_array_elem, NULL);
   fixscript_register_native_func(heap, "heap_get_array_range#6", script_heap_get_array_range, NULL);
   fixscript_register_native_func(heap, "heap_set_array_range#6", script_heap_set_array_range, NULL);
   fixscript_register_native_func(heap, "heap_get_array_values#6", script_heap_get_array_values, NULL);
   fixscript_register_native_func(heap, "heap_set_array_values#6", script_heap_set_array_values, NULL);
   fixscript_register_native_func(heap, "heap_get_array_numbers#6", script_heap_get_array_numbers, NULL);
   fixscript_register_native_func(heap, "heap_set_array_numbers#6", script_heap_set_array_numbers, NULL);
   fixscript_register_native_func(heap, "heap_copy_array#6", script_heap_copy_array, NULL);
   fixscript_register_native_func(heap, "heap_create_string#2", script_heap_create_string, NULL);
   fixscript_register_native_func(heap, "heap_create_string#4", script_heap_create_string, NULL);
   fixscript_register_native_func(heap, "heap_is_string#2", script_heap_is_string, NULL);
   fixscript_register_native_func(heap, "heap_create_hash#1", script_heap_create_hash, NULL);
   fixscript_register_native_func(heap, "heap_is_hash#2", script_heap_is_hash, NULL);
   fixscript_register_native_func(heap, "heap_set_hash_elem#4", script_heap_set_hash_elem, NULL);
   fixscript_register_native_func(heap, "heap_get_hash_elem#3", script_heap_get_hash_elem, NULL);
   fixscript_register_native_func(heap, "heap_remove_hash_elem#3", script_heap_remove_hash_elem, NULL);
   fixscript_register_native_func(heap, "heap_clear_hash#2", script_heap_clear_hash, NULL);
   fixscript_register_native_func(heap, "heap_get_hash_entry#3", script_heap_get_hash_entry, NULL);
   fixscript_register_native_func(heap, "heap_create_handle#2", script_heap_create_handle, NULL);
   fixscript_register_native_func(heap, "heap_is_handle#2", script_heap_is_handle, NULL);
   fixscript_register_native_func(heap, "heap_get_handle#2", script_heap_get_handle, NULL);
   fixscript_register_native_func(heap, "heap_create_weak_ref#2", script_heap_create_weak_ref, NULL);
   fixscript_register_native_func(heap, "heap_create_weak_ref#3", script_heap_create_weak_ref, NULL);
   fixscript_register_native_func(heap, "heap_create_weak_ref#4", script_heap_create_weak_ref, NULL);
   fixscript_register_native_func(heap, "heap_get_weak_ref#2", script_heap_get_weak_ref, NULL);
   fixscript_register_native_func(heap, "heap_is_weak_ref#2", script_heap_is_weak_ref, NULL);
   fixscript_register_native_func(heap, "heap_create_error#2", script_heap_create_error, NULL);
   fixscript_register_native_func(heap, "heap_dump_value#2", script_heap_dump_value, NULL);
   fixscript_register_native_func(heap, "heap_dump_value#3", script_heap_dump_value, NULL);
   fixscript_register_native_func(heap, "heap_to_string#2", script_heap_to_string, NULL);
   fixscript_register_native_func(heap, "heap_to_string#3", script_heap_to_string, NULL);
   fixscript_register_native_func(heap, "heap_clone#2", script_heap_clone, (void *)0);
   fixscript_register_native_func(heap, "heap_clone_deep#2", script_heap_clone, (void *)1);
   fixscript_register_native_func(heap, "heap_clone_to#2", script_heap_clone_to, NULL);
   fixscript_register_native_func(heap, "heap_clone_to#4", script_heap_clone_to, NULL);
   fixscript_register_native_func(heap, "heap_clone_from#2", script_heap_clone_from, NULL);
   fixscript_register_native_func(heap, "heap_clone_from#3", script_heap_clone_from, NULL);
   fixscript_register_native_func(heap, "heap_clone_between#3", script_heap_clone_between, NULL);
   fixscript_register_native_func(heap, "heap_clone_between#5", script_heap_clone_between, NULL);
   fixscript_register_native_func(heap, "heap_serialize#2", script_heap_serialize, NULL);
   fixscript_register_native_func(heap, "heap_serialize#3", script_heap_serialize, NULL);
   fixscript_register_native_func(heap, "heap_unserialize#2", script_heap_unserialize, NULL);
   fixscript_register_native_func(heap, "heap_unserialize#3", script_heap_unserialize, NULL);
   fixscript_register_native_func(heap, "heap_unserialize#4", script_heap_unserialize, NULL);
   fixscript_register_native_func(heap, "heap_load#5", script_heap_load, NULL);
   fixscript_register_native_func(heap, "heap_load_script#2", script_heap_load_script, hc);
   fixscript_register_native_func(heap, "heap_reload#5", script_heap_reload, NULL);
   fixscript_register_native_func(heap, "heap_is_loaded#2", script_heap_is_loaded, NULL);
   fixscript_register_native_func(heap, "heap_get_function#3", script_heap_get_function, NULL);
   fixscript_register_native_func(heap, "heap_get_function_info#2", script_heap_get_function_info, NULL);
   fixscript_register_native_func(heap, "heap_run#4", script_heap_run, NULL);
   fixscript_register_native_func(heap, "heap_call#3", script_heap_call, NULL);
   fixscript_register_native_func(heap, "heap_register_native_function#4", script_heap_register_native_function, NULL);

   fixscript_register_native_func(heap, "global_set#2", global_set, NULL);
   fixscript_register_native_func(heap, "global_get#1", global_get, hc);
   fixscript_register_native_func(heap, "global_add#2", global_add, hc);
}


void fixtask_get_script_load_function(Heap *heap, LoadScriptFunc *load_func, void **load_data)
{
   HeapCreateData *hc = fixscript_get_heap_data(heap, heap_create_data_key);

   if (hc) {
      *load_func = hc->load_func;
      *load_data = hc->load_data;
   }
   else {
      *load_func = NULL;
      *load_data = NULL;
   }
}


int fixtask_get_cores_count(Heap *heap)
{
   ComputeTasks *tasks;
   int num_cores;

   tasks = get_compute_tasks(heap, fixscript_get_heap_data(heap, heap_create_data_key));
   if (tasks) {
      return tasks->num_cores;
   }
   
   num_cores = get_number_of_cores();
   if (num_cores < 1) num_cores = 1;
   return num_cores;
}


void fixtask_run_on_compute_threads(Heap *heap, Value *error, ComputeHeapRunFunc func, void *data)
{
   ComputeTasks *tasks;
   ComputeHeap *cheap;
   int i, num_inactive;

   if (error) {
      *error = fixscript_int(0);
   }
   compute_task_finish_all(heap, error, 0, NULL, NULL);
   if (error->value) {
      return;
   }

   tasks = get_compute_tasks(heap, fixscript_get_heap_data(heap, heap_create_data_key));
   if (!tasks) {
      *error = fixscript_create_error_string(heap, "can't initialize compute threads");
      return;
   }

   pthread_mutex_lock(&tasks->mutex);
   for (i=0; i<tasks->num_cores; i++) {
      cheap = tasks->inactive_heaps;
      tasks->inactive_heaps = cheap->inactive_next;

      cheap->run_func = func;
      cheap->run_data = data;

      cheap->active_next = tasks->active_heaps;
      tasks->active_heaps = cheap;
      pthread_cond_signal(&tasks->conds[i]);
   }
   pthread_mutex_unlock(&tasks->mutex);

   pthread_mutex_lock(&tasks->mutex);
   for (;;) {
      num_inactive = 0;
      cheap = tasks->inactive_heaps;
      while (cheap) {
         num_inactive++;
         cheap = cheap->inactive_next;
      }
      if (num_inactive == tasks->num_heaps) break;

      pthread_cond_wait(&tasks->cond, &tasks->mutex);
   }
   pthread_mutex_unlock(&tasks->mutex);
}
