/* Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef JMEM_H
#define JMEM_H

#include "jrt.h"

/** \addtogroup mem Memory allocation
 * @{
 *
 * \addtogroup heap Heap
 * @{
 */

/**
 * Logarithm of required alignment for allocated units/blocks
 */
#define JMEM_ALIGNMENT_LOG   3

/**
 * Representation of NULL value for compressed pointers
 */
#define JMEM_CP_NULL ((jmem_cpointer_t) 0)

/**
 * Required alignment for allocated units/blocks
 */
#define JMEM_ALIGNMENT (1u << JMEM_ALIGNMENT_LOG)

/**
 * Compressed pointer representations
 *
 * 16 bit representation:
 *   The jmem_cpointer_t is defined as uint16_t
 *   and it can contain any sixteen bit value.
 *
 * 32 bit representation:
 *   The jmem_cpointer_t is defined as uint32_t.
 *   The lower JMEM_ALIGNMENT_LOG bits must be zero.
 *   The other bits can have any value.
 *
 * The 16 bit representation always encodes an offset from
 * a heap base. The 32 bit representation currently encodes
 * raw 32 bit JMEM_ALIGNMENT aligned pointers on 32 bit systems.
 * This can be extended to encode a 32 bit offset from a heap
 * base on 64 bit systems in the future. There are no plans
 * to support more than 4G address space for JerryScript.
 */

/**
 * Compressed pointer
 */
#if ENABLED (JERRY_CPOINTER_32_BIT)
typedef uint32_t jmem_cpointer_t;
#else /* !ENABLED (JERRY_CPOINTER_32_BIT) */
typedef uint16_t jmem_cpointer_t;
#endif /* ENABLED (JERRY_CPOINTER_32_BIT) */

/**
 * Memory usage pressure for reclaiming unused memory.
 *
 * Each failed allocation will try to reclaim memory with increasing pressure,
 * until enough memory is freed to fulfill the allocation request.
 *
 * If not enough memory is freed and JMEM_PRESSURE_FULL is reached,
 * then the engine is shut down with ERR_OUT_OF_MEMORY.
 */
typedef enum
{
  JMEM_PRESSURE_NONE, /**< no memory pressure */
  JMEM_PRESSURE_LOW,  /**< low memory pressure */
  JMEM_PRESSURE_HIGH, /**< high memory pressure */
  JMEM_PRESSURE_FULL, /**< memory full */
} jmem_pressure_t;

/**
 * Node for free chunk list
 */
typedef struct jmem_pools_chunk_t
{
  struct jmem_pools_chunk_t *next_p; /**< pointer to next pool chunk */
} jmem_pools_chunk_t;

/**
 *  Free region node
 */
typedef struct
{
  uint32_t next_offset; /**< Offset of next region in list */
  uint32_t size; /**< Size of region */
} jmem_heap_free_t;

void jmem_init (void);
void jmem_finalize (void);

void *jmem_heap_alloc_block (const size_t size);
void *jmem_heap_alloc_block_null_on_error (const size_t size);
void *jmem_heap_realloc_block (void *ptr, const size_t old_size, const size_t new_size);
void jmem_heap_free_block (void *ptr, const size_t size);

#if ENABLED (JERRY_MEM_STATS)
/**
 * Heap memory usage statistics
 */
typedef struct
{
  size_t size; /**< heap total size */

  size_t allocated_bytes; /**< currently allocated bytes */
  size_t peak_allocated_bytes; /**< peak allocated bytes */

  size_t waste_bytes; /**< bytes waste due to blocks filled partially */
  size_t peak_waste_bytes; /**< peak wasted bytes */

  size_t byte_code_bytes; /**< allocated memory for byte code */
  size_t peak_byte_code_bytes; /**< peak allocated memory for byte code */

  size_t string_bytes; /**< allocated memory for strings */
  size_t peak_string_bytes; /**< peak allocated memory for strings */

  size_t object_bytes; /**< allocated memory for objects */
  size_t peak_object_bytes; /**< peak allocated memory for objects */

  size_t property_bytes; /**< allocated memory for properties */
  size_t peak_property_bytes; /**< peak allocated memory for properties */
} jmem_heap_stats_t;

void jmem_stats_allocate_byte_code_bytes (size_t property_size);
void jmem_stats_free_byte_code_bytes (size_t property_size);
void jmem_stats_allocate_string_bytes (size_t string_size);
void jmem_stats_free_string_bytes (size_t string_size);
void jmem_stats_allocate_object_bytes (size_t object_size);
void jmem_stats_free_object_bytes (size_t string_size);
void jmem_stats_allocate_property_bytes (size_t property_size);
void jmem_stats_free_property_bytes (size_t property_size);

void jmem_heap_get_stats (jmem_heap_stats_t *);
void jmem_heap_stats_reset_peak (void);
void jmem_heap_stats_print (void);
#endif /* ENABLED (JERRY_MEM_STATS) */

jmem_cpointer_t JERRY_ATTR_PURE jmem_compress_pointer (const void *pointer_p);
void * JERRY_ATTR_PURE jmem_decompress_pointer (uintptr_t compressed_pointer);

/**
 * Define a local array variable and allocate memory for the array on the heap.
 *
 * If requested number of elements is zero, assign NULL to the variable.
 *
 * Warning:
 *         if there is not enough memory on the heap, shutdown engine with ERR_OUT_OF_MEMORY.
 */
#define JMEM_DEFINE_LOCAL_ARRAY(var_name, number, type) \
{ \
  size_t var_name ## ___size = (size_t) (number) * sizeof (type); \
  type *var_name = (type *) (jmem_heap_alloc_block (var_name ## ___size));

/**
 * Free the previously defined local array variable, freeing corresponding block on the heap,
 * if it was allocated (i.e. if the array's size was non-zero).
 */
#define JMEM_FINALIZE_LOCAL_ARRAY(var_name) \
  if (var_name != NULL) \
  { \
    JERRY_ASSERT (var_name ## ___size != 0); \
    \
    jmem_heap_free_block (var_name, var_name ## ___size); \
  } \
  else \
  { \
    JERRY_ASSERT (var_name ## ___size == 0); \
  } \
}

/**
 * Get value of pointer from specified non-null compressed pointer value
 */
#define JMEM_CP_GET_NON_NULL_POINTER(type, cp_value) \
  ((type *) (jmem_decompress_pointer (cp_value)))

/**
 * Get value of pointer from specified compressed pointer value
 */
#define JMEM_CP_GET_POINTER(type, cp_value) \
  (((JERRY_UNLIKELY ((cp_value) == JMEM_CP_NULL)) ? NULL : JMEM_CP_GET_NON_NULL_POINTER (type, cp_value)))

/**
 * Set value of non-null compressed pointer so that it will correspond
 * to specified non_compressed_pointer
 */
#define JMEM_CP_SET_NON_NULL_POINTER(cp_value, non_compressed_pointer) \
  (cp_value) = jmem_compress_pointer (non_compressed_pointer)

/**
 * Set value of compressed pointer so that it will correspond
 * to specified non_compressed_pointer
 */
#define JMEM_CP_SET_POINTER(cp_value, non_compressed_pointer) \
  do \
  { \
    void *ptr_value = (void *) non_compressed_pointer; \
    \
    if (JERRY_UNLIKELY ((ptr_value) == NULL)) \
    { \
      (cp_value) = JMEM_CP_NULL; \
    } \
    else \
    { \
      JMEM_CP_SET_NON_NULL_POINTER (cp_value, ptr_value); \
    } \
  } while (false);

/**
 * @}
 * \addtogroup poolman Memory pool manager
 * @{
 */

void *jmem_pools_alloc (size_t size);
void jmem_pools_free (void *chunk_p, size_t size);
void jmem_pools_collect_empty (void);

/**
 * @}
 * @}
 */

#endif /* !JMEM_H */
