text: // Copyright (c) 2019, The Monero Project
  //
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification, are
  // permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice, this list of
  //    conditions and the following disclaimer.
  //
  // 2. Redistributions in binary form must reproduce the above copyright notice, this list
  //    of conditions and the following disclaimer in the documentation and/or other
  //    materials provided with the distribution.
  //
  // 3. Neither the name of the copyright holder nor the names of its contributors may be
  //    used to endorse or promote products derived from this software without specific
  //    prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
  // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
  // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  #include <assert.h>
  #include <stddef.h>
  #include <stdint.h>
  #include <stdbool.h>
  #include <string.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  
  #include "randomx.h"
  #include "c_threads.h"
  #include "hash-ops.h"
  
  static CTHR_MUTEX_TYPE rx_mutex = CTHR_MUTEX_INIT;
  
  typedef struct rx_state {
    volatile uint64_t  rs_height;
    randomx_cache *rs_cache;
  } rx_state;
  
  static rx_state rx_s[2];
  
  static randomx_dataset *rx_dataset;
  
  static void local_abort(const char *msg)
  {
    fprintf(stderr, "%s\n", msg);
  #ifdef NDEBUG
    _exit(1);
  #else
    abort();
  #endif
  }
  
  
  
  static volatile int use_rx_jit_flag = -1;
  
  static inline int use_rx_jit(void)
  {
  #if defined(__x86_64__)
  
    if (use_rx_jit_flag != -1)
      return use_rx_jit_flag;
  
    const char *env = getenv("MONERO_USE_RX_JIT");
    if (!env) {
      use_rx_jit_flag = 1;
    }
    else if (!strcmp(env, "0") || !strcmp(env, "no")) {
      use_rx_jit_flag = 0;
    }
    else {
      use_rx_jit_flag = 1;
    }
    return use_rx_jit_flag;
  #else
    return 0;
  #endif
  }
  
  #define SEEDHASH_EPOCH_BLOCKS	2048	/* Must be same as BLOCKS_SYNCHRONIZING_MAX_COUNT in cryptonote_config.h */
  #define SEEDHASH_EPOCH_LAG		64
  
  void rx_reorg(const uint64_t split_height) {
    int i;
    CTHR_MUTEX_LOCK(rx_mutex);
    for (i=0; i<2; i++) {
      if (split_height < rx_s[i].rs_height)
        rx_s[i].rs_height = 1;	/* set to an invalid seed height */
    }
    CTHR_MUTEX_UNLOCK(rx_mutex);
  }
  
  uint64_t rx_seedheight(const uint64_t height) {
    uint64_t s_height =  (height <= SEEDHASH_EPOCH_BLOCKS+SEEDHASH_EPOCH_LAG) ? 0 :
                         (height - SEEDHASH_EPOCH_LAG - 1) & ~(SEEDHASH_EPOCH_BLOCKS-1);
    return s_height;
  }
  
  void rx_seedheights(const uint64_t height, uint64_t *seedheight, uint64_t *nextheight) {
    *seedheight = rx_seedheight(height);
    *nextheight = rx_seedheight(height + SEEDHASH_EPOCH_LAG);
  }
  
  bool rx_needhash(cn_hash_context_t *context, const uint64_t height, uint64_t *seedheight) {
    rx_state *rx_sp;
    uint64_t s_height = rx_seedheight(height);
    int toggle = (s_height & SEEDHASH_EPOCH_BLOCKS) != 0;
    bool ret;
    bool changed = (toggle != context->rx_s_toggle);
    *seedheight = s_height;
    context->rx_s_toggle = toggle;
    rx_sp = &rx_s[context->rx_s_toggle];
    ret = (rx_sp->rs_cache == NULL) || (rx_sp->rs_height != s_height);
    /* if cache is ok but we've flipped caches, reset vm */
    if (!ret && changed && context->rx_vm != NULL)
      randomx_vm_set_cache(context->rx_vm, rx_sp->rs_cache);
    return ret;
  }
  
  typedef struct seedinfo {
    randomx_cache *si_cache;
    unsigned long si_start;
    unsigned long si_count;
  } seedinfo;
  
  static CTHR_THREAD_RTYPE rx_seedthread(void *arg) {
    seedinfo *si = arg;
    randomx_init_dataset(rx_dataset, si->si_cache, si->si_start, si->si_count);
    CTHR_THREAD_RETURN;
  }
  
  static void rx_initdata(randomx_cache *rs_cache, const int miners) {
    if (miners > 1) {
      unsigned long delta = randomx_dataset_item_count() / miners;
      unsigned long start = 0;
      int i;
      seedinfo *si;
      CTHR_THREAD_TYPE *st;
      si = malloc(miners * sizeof(seedinfo));
      if (si == NULL)
        local_abort("Couldn't allocate RandomX mining threadinfo");
      st = malloc(miners * sizeof(CTHR_THREAD_TYPE));
      if (st == NULL) {
        free(si);
        local_abort("Couldn't allocate RandomX mining threadlist");
      }
      for (i=0; i<miners-1; i++) {
        si[i].si_cache = rs_cache;
        si[i].si_start = start;
        si[i].si_count = delta;
        start += delta;
      }
      si[i].si_cache = rs_cache;
      si[i].si_start = start;
      si[i].si_count = randomx_dataset_item_count() - start;
      for (i=1; i<miners; i++) {
        CTHR_THREAD_CREATE(st[i], rx_seedthread, &si[i]);
      }
      randomx_init_dataset(rx_dataset, rs_cache, 0, si[0].si_count);
      for (i=1; i<miners; i++) {
        CTHR_THREAD_JOIN(st[i]);
      }
      free(st);
      free(si);
    } else {
      randomx_init_dataset(rx_dataset, rs_cache, 0, randomx_dataset_item_count());
    }
  }
  
  static void rx_seedhash_int(cn_hash_context_t *context, rx_state *rx_sp, const uint64_t height, const char *hash, const int miners) {
    randomx_flags flags = RANDOMX_FLAG_DEFAULT;
    randomx_cache *cache;
    CTHR_MUTEX_LOCK(rx_mutex);
    cache = rx_sp->rs_cache;
    if (rx_sp->rs_height != height || cache == NULL) {
      if (use_rx_jit())
        flags |= RANDOMX_FLAG_JIT;
      if (cache == NULL) {
        cache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES);
        if (cache == NULL)
          cache = randomx_alloc_cache(flags);
        if (cache == NULL)
          local_abort("Couldn't allocate RandomX cache");
      }
      randomx_init_cache(cache, hash, 32);
      if (miners && rx_dataset != NULL)
        rx_initdata(cache, miners);
      rx_sp->rs_height = height;
      if (rx_sp->rs_cache == NULL)
        rx_sp->rs_cache = cache;
    }
    CTHR_MUTEX_UNLOCK(rx_mutex);
    if (context->rx_vm != NULL)
      randomx_vm_set_cache(context->rx_vm, rx_sp->rs_cache);
  }
  
  void rx_seedhash(cn_hash_context_t *context, const uint64_t height, const char *hash, const int miners) {
    rx_state *rx_sp = &rx_s[context->rx_s_toggle];
    rx_seedhash_int(context, rx_sp, height, hash, miners);
  }
  
  static char rx_althash[32];	// seedhash for alternate blocks
  
  void rx_alt_slowhash(cn_hash_context_t *context, const uint64_t mainheight, const uint64_t seedheight, const char *seedhash, const void *data, size_t length, char *hash) {
    uint64_t s_height = rx_seedheight(mainheight);
    int alt_toggle = (s_height & SEEDHASH_EPOCH_BLOCKS) == 0;
    rx_state *rx_sp = &rx_s[alt_toggle];
    if (rx_sp->rs_height != seedheight || rx_sp->rs_cache == NULL || memcmp(hash, rx_althash, sizeof(rx_althash))) {
      memcpy(rx_althash, seedhash, sizeof(rx_althash));
      rx_sp->rs_height = 1;
      rx_seedhash_int(context, rx_sp, seedheight, seedhash, 0);
    }
    if (context->rx_vm == NULL) {
      randomx_flags flags = RANDOMX_FLAG_DEFAULT;
      if (use_rx_jit())
        flags |= RANDOMX_FLAG_JIT;
  #if !defined(CN_USE_SOFTWARE_AES)
        flags |= RANDOMX_FLAG_HARD_AES;
  #endif
      context->rx_vm = randomx_create_vm(flags | RANDOMX_FLAG_LARGE_PAGES, rx_sp->rs_cache, NULL);
      if(context->rx_vm == NULL) //large pages failed
        context->rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, NULL);
      if(context->rx_vm == NULL) {//fallback if everything fails
        flags = RANDOMX_FLAG_DEFAULT;
        context->rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, NULL);
      }
      if (context->rx_vm == NULL)
        local_abort("Couldn't allocate RandomX VM");
    }
  
    randomx_calculate_hash(context->rx_vm, data, length, hash);
  }
  
  void rx_slow_hash(cn_hash_context_t *context, const void *data, size_t length, char *hash, int miners) {
    if (context->rx_vm == NULL) {
      rx_state *rx_sp = &rx_s[context->rx_s_toggle];
      randomx_flags flags = RANDOMX_FLAG_DEFAULT;
      if (use_rx_jit())
        flags |= RANDOMX_FLAG_JIT;
  #if !defined(CN_USE_SOFTWARE_AES)
      flags |= RANDOMX_FLAG_HARD_AES;
  #endif
      if (miners) {
        if (rx_dataset == NULL) {
          CTHR_MUTEX_LOCK(rx_mutex);
          if (rx_dataset == NULL) {
            rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_LARGE_PAGES);
            if (rx_dataset == NULL)
              rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT);
            if (rx_dataset != NULL)
              rx_initdata(rx_sp->rs_cache, miners);
          }
          CTHR_MUTEX_UNLOCK(rx_mutex);
        }
        if (rx_dataset != NULL)
          flags |= RANDOMX_FLAG_FULL_MEM;
        else
          miners = 0;
      }
      context->rx_vm = randomx_create_vm(flags | RANDOMX_FLAG_LARGE_PAGES, rx_sp->rs_cache, rx_dataset);
      if(context->rx_vm == NULL) //large pages failed
        context->rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, rx_dataset);
      if(context->rx_vm == NULL) {//fallback if everything fails
        flags = RANDOMX_FLAG_DEFAULT | (miners ? RANDOMX_FLAG_FULL_MEM : 0);
        context->rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, rx_dataset);
      }
      if (context->rx_vm == NULL)
        local_abort("Couldn't allocate RandomX VM");
    }
    randomx_calculate_hash(context->rx_vm, data, length, hash);
  }
  
  void rx_stop_mining(void) {
    if (rx_dataset != NULL) {
      randomx_release_dataset(rx_dataset);
  	rx_dataset = NULL;
    }
  }
  