//! @file a68g-mem.c
//! @author J. Marcel van der Veer

//! @section Copyright
//!
//! This file is part of Algol68G - an Algol 68 compiler-interpreter.
//! Copyright 2001-2025 J. Marcel van der Veer [algol68g@xs4all.nl].

//! @section License
//!
//! This program is free software; you can redistribute it and/or modify it
//! under the terms of the GNU General Public License as published by the
//! Free Software Foundation; either version 3 of the License, or
//! (at your option) any later version.
//!
//! This program is distributed in the hope that it will be useful, but
//! WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
//! or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
//! more details. You should have received a copy of the GNU General Public
//! License along with this program. If not, see [http://www.gnu.org/licenses/].

//! @section Synopsis
//!
//! Low-level memory management.

// A68G memory is organised following below schema.
//
// +------------+----------+-----------+---------+-------------+----------------+
// | HEAP                                        | STACK                        |
// | FIXED HEAP | A68 HEAP | TEMP HEAP | HANDLES | FRAME STACK | ARGUMENT STACK |
// +------------+----------+-----------+---------+-------------+----------------+
//                ^                                ^             ^
//                A68G_HP                          A68G_FP       A68G_SP
//
// The HEAP starts at the lowest address, and the ARGUMENT STACK ends at the highest address. 
//
// The HEAP is for both C and A68 objects. For C objects, this simple scheme is more efficient 
// than many calls to 'malloc' and 'free'.
//
// FIXED HEAP contains permanent C objects forming the syntax tree, symbol tables, etcetera.
// A68 HEAP contains data stored by HEAP generators in the running A68 program.
// TEMP HEAP stores temporary run-time data in the heap.
// HANDLES store A68 REFs enabling garbage collecting of the A68 HEAP.
// FRAME STACK stores local variables according the lexical levels.
// ARGUMENT STACK is for PROC/OP arguments and results, the "evaluation stack".
//
// The 'heap pointer' counts up and the 'temp heap pointer' counts down.
// Should they collide, then the heap is full.
// This way there is no need to reserve space for the heap segments a priori.
// The stack could follow the same mechanism to have a joined space for 
// the FRAME STACK and ARGUMENT STACK, but is not implemented like that.
//
// Care must be taken to not sweep intermediate HEAP data pointed to from the STACK.
// Therefore the garbage collector only acts in between units.
//
// Note that A68G stores all row elements in the heap, to save space in the stack. 
// If a row is local, only the descriptor is in the FRAME STACK.
// The garbage collector will sweep the elements of a stale local row.

#include "a68g.h"
#include "a68g-prelude.h"

//! Reasonable limits for chunk sizes.

void storage_limit (size_t n) 
{
  ABEND ((n) > MAX_MEM_SIZE, ERROR_VALUE_TOO_LARGE, ERROR_MEMORY_FULL);
}

//! @brief Initialise C and A68 heap management.

void init_heap (void) 
{
// Note that some overhead is allowed for every size that can be specified.
  size_t heap_a_size = A68G_ALIGN (A68G (heap_size) + A68G (storage_overhead));
  size_t handle_a_size = A68G_ALIGN (A68G (handle_pool_size) + A68G (storage_overhead));
  size_t frame_a_size = A68G_ALIGN (A68G (frame_stack_size) + A68G (storage_overhead));
  size_t expr_a_size = A68G_ALIGN (A68G (expr_stack_size) + A68G (storage_overhead));
  storage_limit (heap_a_size);
  storage_limit (handle_a_size);
  storage_limit (frame_a_size);
  storage_limit (expr_a_size);
  size_t total_size = A68G_ALIGN (heap_a_size + handle_a_size + frame_a_size + expr_a_size);
  storage_limit (total_size);
// Reserve the space.
  errno = 0;
  BYTE_T *core = (BYTE_T *) (A68G_ALIGN_T *) a68g_alloc (total_size, __func__, __LINE__);
  if (core == NO_BYTE || errno != 0) {
    ABEND (A68G_TRUE, ERROR_MEMORY_FULL, NO_TEXT);
  }
// Initialise.
  A68G_HEAP = NO_BYTE;
  A68G_HANDLES = NO_BYTE;
  A68G_STACK = NO_BYTE;
  A68G_SP = 0;
  A68G_FP = 0;
  A68G_HP = 0;
  A68G_GLOBALS = 0;
  A68G_HEAP = & (core[0]);
  A68G_HANDLES = & (A68G_HEAP[heap_a_size]);
  A68G_STACK = & (A68G_HANDLES[handle_a_size]);
  A68G (fixed_heap_pointer) = A68G_ALIGNMENT;
  A68G (temp_heap_pointer) = heap_a_size;
  A68G (frame_start) = 0;
  A68G (frame_end) = A68G (frame_start) + frame_a_size;
  A68G (stack_start) = A68G (frame_end);
  A68G (stack_end) = A68G (stack_start) + expr_a_size;
  ABEND (errno != 0, ERROR_MEMORY_FULL, NO_TEXT);
}

//! @brief aligned allocation.

void *a68g_alloc (size_t len, const char *f, int line) 
{
// We need this since malloc aligns to "standard C types".
// __float128 is not a standard type, apparently ...
//
  (void) f;
  (void) line;
  storage_limit (len);
  void *p = NULL;
  size_t align = sizeof (A68G_ALIGN_T);
  int save = errno;
  errno = 0;
// Allocation is platform dependent.
  #if defined (BUILD_WINDOWS) 
    p = _aligned_malloc (len, align);
    ABEND (p == NULL || errno != 0, "cannot allocate memory", NO_TEXT);
  #elif defined (HAVE_POSIX_MEMALIGN) 
    errno = posix_memalign (&p, align, len);
    if (errno != 0) {
      p = NULL;
    }
  #elif defined (HAVE_ALIGNED_ALLOC) // Glibc version of posix_memalign.
    if (align < sizeof (void *)) {
      errno = EINVAL;
    } else {
      p = aligned_alloc (align, len);
    }
  #else
    p = malloc (len); // Aude audenda.
  #endif
//
  if (p == (void *) NULL || errno != 0) {
    static BUFFER msg;
    if (len > A68G_GIGA) {
      a68g_bufprt (msg, SNPRINTF_SIZE, "request for " A68G_LU " GB", len / A68G_GIGA);
    } else if (len > A68G_MEGA) {
      a68g_bufprt (msg, SNPRINTF_SIZE, "request for " A68G_LU " MB", len / A68G_MEGA);
    } else {
      a68g_bufprt (msg, SNPRINTF_SIZE, "request for " A68G_LU " bytes", len);
    }
    ABEND (A68G_TRUE, ERROR_MEMORY_FULL, msg);
  }
  errno = save;
  return p;
}

void a68g_free (void *z) 
{
  if (z != NULL) {
  #if defined (BUILD_WINDOWS) 
    _aligned_free (z); // On WINDOWS, free cannot deallocate _aligned_malloc
  #else
    free (z);
  #endif
  }
}

//! @brief Give pointer to block of "s" bytes.

BYTE_T *get_heap_space (size_t s) 
{
  ABEND (s == 0, ERROR_INVALID_SIZE, NO_TEXT);
  BYTE_T *z = (BYTE_T *) (A68G_ALIGN_T *) a68g_alloc (A68G_ALIGN (s), __func__, __LINE__);
  ABEND (z == NO_BYTE, ERROR_MEMORY_FULL, NO_TEXT);
  return z;
}

//! @brief Make a new copy of concatenated strings.

char *new_string (char *t, ...) 
{
  va_list vl;
  va_start (vl, t);
  char *q = t;
  if (q == NO_TEXT) {
    va_end (vl);
    return NO_TEXT;
  }
  size_t len = 0;
  while (q != NO_TEXT) {
    len += strlen (q);
    q = va_arg (vl, char *);
  }
  va_end (vl);
  len++;
  char *z = (char *) get_heap_space (len);
  z[0] = NULL_CHAR;
  q = t;
  va_start (vl, t);
  while (q != NO_TEXT) {
    a68g_bufcat (z, q, len);
    q = va_arg (vl, char *);
  }
  va_end (vl);
  return z;
}

//! @brief Make a new copy of "t".

char *new_fixed_string (char *t) 
{
  size_t n = strlen (t) + 1;
  char *z = (char *) get_fixed_heap_space (n);
  a68g_bufcpy (z, t, n);
  return z;
}

//! @brief Make a new copy of "t".

char *new_temp_string (char *t) 
{
  size_t n = strlen (t) + 1;
  char *z = (char *) get_temp_heap_space (n);
  a68g_bufcpy (z, t, n);
  return z;
}

//! @brief Get (preferably fixed) heap space.

BYTE_T *get_fixed_heap_space (size_t s) 
{
  if (A68G (heap_is_fluid)) {
    BYTE_T *z = HEAP_ADDRESS (A68G (fixed_heap_pointer));
    A68G (fixed_heap_pointer) += A68G_ALIGN (s);
// Allow for extra storage for diagnostics etcetera.
    ABEND (((A68G (fixed_heap_pointer) + A68G (storage_overhead)) >= A68G (temp_heap_pointer)), ERROR_MEMORY_FULL, NO_TEXT);
    return z;
  } else {
    return get_heap_space (s);
  }
}

//! @brief Get (preferably temporary) heap space.

BYTE_T *get_temp_heap_space (size_t s) 
{
  if (A68G (heap_is_fluid)) {
    A68G (temp_heap_pointer) -= A68G_ALIGN (s);
// Allow for extra storage for diagnostics etcetera.
    ABEND (((A68G (fixed_heap_pointer) + A68G (storage_overhead)) >= A68G (temp_heap_pointer)), ERROR_MEMORY_FULL, NO_TEXT);
    return HEAP_ADDRESS (A68G (temp_heap_pointer));
  } else {
    return get_heap_space (s);
  }
}

//! @brief Get size of C stack.

void get_C_stack_size (void) 
{
  #if defined (BUILD_WINDOWS) 
    A68G (stack_size) = A68G_MEGA; // Guestimate
  #else
    errno = 0;
// Some systems do not implement RLIMIT_STACK so if getrlimit fails, we do not abend.
    struct rlimit limits;
    if (! (getrlimit (RLIMIT_STACK, &limits) == 0 && errno == 0)) {
      A68G (stack_size) = A68G_MEGA;
    }
    A68G (stack_size) = (size_t) (RLIM_CUR (&limits) < RLIM_MAX (&limits) ? RLIM_CUR (&limits) : RLIM_MAX (&limits));
// A heuristic in case getrlimit yields extreme numbers: the frame stack is
// assumed to fill at a rate comparable to the C stack, so the C stack needs
// not be larger than the frame stack. This may not be true.
    if (A68G (stack_size) < A68G_KILO || (A68G (stack_size) > 96 * A68G_MEGA && A68G (stack_size) > A68G (frame_stack_size))) {
      A68G (stack_size) = A68G (frame_stack_size);
    }
  #endif
  A68G (stack_limit) = (A68G (stack_size) > (4 * A68G (storage_overhead)) ? (A68G (stack_size) - A68G (storage_overhead)) : A68G (stack_size) / 2);
}

//! @brief Free heap allocated by genie.

void genie_free (NODE_T *p) 
{
  for (; p != NO_NODE; FORWARD (p)) {
    genie_free (SUB (p));
    if (GINFO (p) != NO_GINFO) {
      a68g_free (CONSTANT (GINFO (p)));
      CONSTANT (GINFO (p)) = NO_CONSTANT;
      a68g_free (COMPILE_NAME (GINFO (p)));
      COMPILE_NAME (GINFO (p)) = NO_TEXT;
    }
  }
}

//! @brief Free heap allocated by genie.

void free_syntax_tree (NODE_T *p) 
{
  for (; p != NO_NODE; FORWARD (p)) {
    free_syntax_tree (SUB (p));
    a68g_free (NPRAGMENT (p));
    NPRAGMENT (p) = NO_TEXT;
    DIAGNOSTIC_T *d = DIAGNOSTICS (LINE (INFO (p)));
    while (d != NO_DIAGNOSTIC) {
      a68g_free (TEXT (d));
      DIAGNOSTIC_T *stale = d;
      FORWARD (d);
      a68g_free (stale);
    }
    DIAGNOSTICS (LINE (INFO (p))) = NO_DIAGNOSTIC;
  }
}
