#ifndef AIHC_RUNTIME_H
#define AIHC_RUNTIME_H

#include <stdint.h>

#ifndef AIHC_SEMISPACE_BYTES
#define AIHC_SEMISPACE_BYTES (UINT64_C(1024) * UINT64_C(1024))
#endif

enum {
  AIHC_OBJECT_NODE,
  AIHC_OBJECT_CLOSURE,
  AIHC_OBJECT_THUNK,
  AIHC_OBJECT_PARTIAL_CONSTRUCTOR,
  AIHC_OBJECT_INDIRECTION,
  AIHC_OBJECT_BLACKHOLE,
  AIHC_OBJECT_ARRAY,
  AIHC_OBJECT_THREAD,
};
typedef uint64_t AihcObjectKind;

typedef struct AihcValue AihcValue;
typedef struct AihcMachine AihcMachine;
typedef struct AihcInfo AihcInfo;
typedef struct AihcThread AihcThread;
typedef struct AihcBlackhole AihcBlackhole;
typedef struct AihcIoHandle AihcIoHandle;
typedef struct AihcIoRequest AihcIoRequest;
typedef struct AihcIoBackend AihcIoBackend;
typedef struct AihcMVar AihcMVar;
typedef struct AihcStableName AihcStableName;
typedef uint64_t AihcSlot;
typedef void (*AihcEntry)(AihcSlot *arguments);
/* The backend entry is interpreted by the selected code generator. Common
   runtime code preserves it but never calls it. */
typedef void (*AihcBackendEntry)(void);

enum {
  AIHC_RESUME_NONE,
  AIHC_RESUME_APPLY,
  AIHC_RESUME_CONTINUE,
  AIHC_RESUME_RAISE,
};
typedef uint64_t AihcResumeKind;

typedef struct {
  AihcResumeKind kind;
  AihcValue *function;
  AihcValue *continuation;
  AihcSlot value;
  uint64_t count;
} AihcResume;

enum {
  AIHC_FRAME_NONE = 0,
  AIHC_FRAME_NORMAL = 1,
  AIHC_FRAME_CATCH = 2,
  AIHC_FRAME_UPDATE = 3,
  AIHC_FRAME_RESTORE_MASK = 4,
  AIHC_FRAME_STOP = 5,
};
typedef uint64_t AihcFrameKind;

struct AihcInfo {
  uintptr_t identity;
  AihcEntry entry;
  uint64_t field_count;
  uint64_t remaining_arity;
  const uint8_t *field_is_pointer;
  const AihcInfo *next;
  /* Backend-owned dynamic entry. Native and WebAssembly adapters give this
     word their own callable type. */
  AihcBackendEntry backend_entry;
  /* Continuation closures have their parent in field zero. This kind is
     backend-independent so the runtime can unwind them uniformly. */
  AihcFrameKind frame_kind;
  AihcObjectKind object_kind;
};

struct AihcValue {
  /* Ordinarily an unmodified info-table pointer. During semispace collection,
     a forwarded from-space object temporarily holds its to-space address. */
  AihcSlot header;
  AihcSlot fields[];
};

struct AihcMachine {
  AihcSlot *globals;
  uint64_t global_count;
  AihcEntry exit_code;
  uint8_t *heap_next;
  uint8_t *heap_limit;
  uint8_t *heap_start;
  uint8_t *other_space;
  uint64_t semispace_bytes;
  AihcValue *thread_done_continuation;
  AihcThread *current_thread;
  AihcThread *run_queue_head;
  AihcThread *run_queue_tail;
  AihcBlackhole *blackholes;
  AihcMVar *mvars;
  AihcStableName *stable_names;
  uint64_t next_stable_name;
  AihcIoRequest *io_requests_head;
  AihcIoRequest *io_requests_tail;
  uint64_t io_request_count;
  const AihcIoBackend *io_backend;
  uint64_t allocation_count;
  AihcSlot *locals;
  uint64_t locals_capacity;
  void *trampoline_state;
  AihcResume selected_resume;
  int64_t exit_status;
};

_Static_assert(sizeof(AihcValue) == sizeof(AihcSlot),
               "AIHC objects must have a one-word base header");

static inline const AihcInfo *aihc_value_info_table(const AihcValue *value) {
  return (const AihcInfo *)(uintptr_t)value->header;
}

static inline AihcObjectKind aihc_value_kind(const AihcValue *value) {
  return aihc_value_info_table(value)->object_kind;
}

static inline uintptr_t aihc_value_info(const AihcValue *value) {
  return aihc_value_info_table(value)->identity;
}

static inline AihcEntry aihc_value_entry(const AihcValue *value) {
  return aihc_value_info_table(value)->entry;
}

static inline uint64_t aihc_value_arity(const AihcValue *value) {
  return aihc_value_info_table(value)->remaining_arity;
}

static inline uint64_t aihc_value_count(const AihcValue *value) {
  return aihc_value_info_table(value)->field_count;
}

static inline AihcSlot *aihc_value_fields(AihcValue *value) {
  return value->fields;
}

static inline const AihcSlot *aihc_value_fields_const(const AihcValue *value) {
  return value->fields;
}

AihcValue *aihc_make_node(AihcMachine *machine, const AihcInfo *info);
AihcValue *aihc_make_node_unchecked(AihcMachine *machine, const AihcInfo *info);
void aihc_ensure_heap(AihcMachine *machine, uint64_t words, uint64_t root_count,
                      AihcSlot *roots);
AihcMachine *aihc_machine_new(uint64_t global_count);
uint64_t aihc_allocation_count(const AihcMachine *machine);
void aihc_reset_allocation_count(AihcMachine *machine);
AihcSlot *aihc_alloc_locals(AihcMachine *machine, uint64_t count);
void aihc_no_match(void);
void aihc_unsupported_primitive(void);
/* The complete process argument vector is copied before the Haskell machine
   starts. argv[0] is retained because getProgName and withProgName share the
   same mutable vector as getArgs and withArgs. */
void aihc_program_arguments_initialize(int argc, char *const argv[]);
int64_t aihc_program_arguments_size(void);
int64_t aihc_program_arguments_copy(void *buffer, int64_t capacity);
int64_t aihc_program_arguments_replace(const void *buffer, int64_t length);
void aihc_set_field(AihcValue *value, uint64_t index, AihcSlot field);
/* Boxed arrays are contiguous managed objects. GrinEnsureHeap reserves their
   length-dependent storage before this initializer advances the heap. */
AihcValue *aihc_array_new(AihcMachine *machine, int64_t count,
                          AihcSlot initial);
AihcSlot aihc_array_index(AihcValue *array, int64_t index);
AihcSlot aihc_array_write(AihcValue *array, int64_t index, AihcSlot value);
uint64_t aihc_array_same(AihcValue *left, AihcValue *right);
AihcValue *aihc_mutvar_new(AihcMachine *machine, AihcSlot initial);
AihcSlot aihc_mutvar_read(AihcValue *mutvar);
AihcSlot aihc_mutvar_write(AihcValue *mutvar, AihcSlot value);
uint64_t aihc_mutvar_compare_and_swap(AihcValue *mutvar, AihcSlot expected,
                                      AihcSlot replacement);
uint64_t aihc_mutvar_same(AihcValue *left, AihcValue *right);
/* Stable-name handles are auxiliary, non-moving objects. The machine-owned
   table keeps their referents synchronized with a moving collector. */
void *aihc_stable_name_make(AihcMachine *machine, AihcValue *value);
uint64_t aihc_stable_name_equal(const void *left, const void *right);
int64_t aihc_stable_name_hash(const void *name);
/* State and allocation helpers used by native code. None of these functions
   transfers control to a generated user function. */
AihcValue *aihc_apply_slow(AihcMachine *machine, AihcValue *function,
                           uint64_t count, const AihcSlot *arguments,
                           AihcValue **continuation);
void aihc_begin_blackhole(AihcMachine *machine, AihcValue *value);
const AihcResume *aihc_block_on_blackhole(AihcMachine *machine,
                                          AihcValue *value,
                                          AihcValue *continuation);
void aihc_update(AihcValue *object, AihcValue *value);
void aihc_update_blackhole(AihcMachine *machine, AihcValue *object,
                           AihcValue *value);
const AihcResume *aihc_raise(AihcMachine *machine, AihcValue *exception,
                             AihcValue *continuation);
AihcSlot aihc_fork(AihcMachine *machine, AihcValue *action);
void *aihc_mvar_new(AihcMachine *machine);
const AihcResume *aihc_mvar_read(AihcMachine *machine, void *mvar,
                                 AihcValue *continuation);
const AihcResume *aihc_mvar_take(AihcMachine *machine, void *mvar,
                                 AihcValue *continuation);
const AihcResume *aihc_mvar_put(AihcMachine *machine, void *mvar,
                                AihcSlot value, AihcValue *continuation);
const AihcResume *aihc_yield(AihcMachine *machine, AihcValue *continuation);
const AihcResume *aihc_await_io(AihcMachine *machine, void *request,
                                AihcValue *continuation);
const AihcResume *aihc_thread_done(AihcMachine *machine);
void *aihc_io_stdin(void);
void *aihc_io_stdout(void);
void *aihc_io_stderr(void);
int64_t aihc_io_open_result_error(void *result);
int64_t aihc_io_close(void *handle);
int64_t aihc_memory_write_byte(void *buffer, int64_t offset, int64_t value);
int64_t aihc_memory_read_byte(const void *buffer, int64_t offset);
_Noreturn int64_t aihc_io_raise_error(int64_t error);
uint64_t aihc_addr_index_word8(void *address, int64_t index);
uint64_t aihc_addr_index_word32(void *address, int64_t index);
uint64_t aihc_addr_index_word64(void *address, int64_t index);
/* Proof-of-concept byte arrays use stable auxiliary allocations and are not
   released. Freeze and thaw are representation-preserving compiler
   primitives. */
void *aihc_byte_array_new(int64_t size);
void *aihc_byte_array_new_pinned(int64_t size);
void *aihc_byte_array_new_aligned_pinned(int64_t size, int64_t alignment);
uint64_t aihc_byte_array_is_pinned(void *array);
void *aihc_byte_array_contents(void *array);
uint64_t aihc_byte_array_shrink(void *array, int64_t size);
void *aihc_byte_array_resize(void *array, int64_t size);
uint64_t aihc_byte_array_get_size(void *array);
uint64_t aihc_byte_array_copy_from_addr(void *source, void *array,
                                        int64_t offset, int64_t length);
uint64_t aihc_byte_array_index_word(void *array, int64_t index);
uint64_t aihc_byte_array_read_word(void *array, int64_t index);
uint64_t aihc_byte_array_write_word(void *array, int64_t index, uint64_t value);
uint64_t aihc_byte_array_copy(void *source, int64_t source_offset,
                              void *destination, int64_t destination_offset,
                              int64_t length);
uint64_t aihc_word_clz(uint64_t value);
uint64_t aihc_word_ctz(uint64_t value);
uint64_t aihc_word_popcount(uint64_t value);
void *aihc_io_submit_read(void *handle, void *buffer, int64_t offset,
                          int64_t length);
void *aihc_io_submit_write(void *handle, void *buffer, int64_t offset,
                           int64_t length);
void *aihc_io_submit_open(void *path, int64_t length, int64_t mode);
int64_t aihc_io_take_result(void *request);
void *aihc_io_take_open_result(void *request);
void aihc_set_thread_done_continuation(AihcMachine *machine,
                                       AihcValue *thread_done_continuation);
void aihc_set_exit_status(AihcMachine *machine, int64_t status);
int64_t aihc_get_exit_status(const AihcMachine *machine);
_Noreturn void aihc_exit_process(int64_t status);
AihcEntry aihc_halt(AihcMachine *machine);
#endif
