/*
 * Copyright 2018 WebAssembly Community Group participants
 *
 * 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.
 */

#include "wasm-rt-impl.h"

#include <assert.h>
#include <stdio.h>

#ifdef _WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#endif

#define WASM_PAGE_SIZE 65536

#ifdef WASM_RT_GROW_FAILED_HANDLER
extern void WASM_RT_GROW_FAILED_HANDLER();
#endif

#define C11_MEMORY_LOCK_VAR_INIT(name)                \
  if (mtx_init(&(name), mtx_plain) != thrd_success) { \
    fprintf(stderr, "Lock init failed\n");            \
    abort();                                          \
  }
#define C11_MEMORY_LOCK_AQUIRE(name)          \
  if (mtx_lock(&(name)) != thrd_success) {    \
    fprintf(stderr, "Lock acquire failed\n"); \
    abort();                                  \
  }
#define C11_MEMORY_LOCK_RELEASE(name)         \
  if (mtx_unlock(&(name)) != thrd_success) {  \
    fprintf(stderr, "Lock release failed\n"); \
    abort();                                  \
  }

#define PTHREAD_MEMORY_LOCK_VAR_INIT(name)      \
  if (pthread_mutex_init(&(name), NULL) != 0) { \
    fprintf(stderr, "Lock init failed\n");      \
    abort();                                    \
  }
#define PTHREAD_MEMORY_LOCK_AQUIRE(name)      \
  if (pthread_mutex_lock(&(name)) != 0) {     \
    fprintf(stderr, "Lock acquire failed\n"); \
    abort();                                  \
  }
#define PTHREAD_MEMORY_LOCK_RELEASE(name)     \
  if (pthread_mutex_unlock(&(name)) != 0) {   \
    fprintf(stderr, "Lock release failed\n"); \
    abort();                                  \
  }

#define WIN_MEMORY_LOCK_VAR_INIT(name) InitializeCriticalSection(&(name))
#define WIN_MEMORY_LOCK_AQUIRE(name) EnterCriticalSection(&(name))
#define WIN_MEMORY_LOCK_RELEASE(name) LeaveCriticalSection(&(name))

#if WASM_RT_USE_MMAP

#ifdef _WIN32
static void* os_mmap(size_t size) {
  void* ret = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS);
  return ret;
}

static int os_munmap(void* addr, size_t size) {
  // Windows can only unmap the whole mapping
  (void)size; /* unused */
  BOOL succeeded = VirtualFree(addr, 0, MEM_RELEASE);
  return succeeded ? 0 : -1;
}

static int os_mprotect(void* addr, size_t size) {
  if (size == 0) {
    return 0;
  }
  void* ret = VirtualAlloc(addr, size, MEM_COMMIT, PAGE_READWRITE);
  if (ret == addr) {
    return 0;
  }
  VirtualFree(addr, 0, MEM_RELEASE);
  return -1;
}

static void os_print_last_error(const char* msg) {
  DWORD errorMessageID = GetLastError();
  if (errorMessageID != 0) {
    LPSTR messageBuffer = 0;
    // The api creates the buffer that holds the message
    size_t size = FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPSTR)&messageBuffer, 0, NULL);
    (void)size;
    printf("%s. %s\n", msg, messageBuffer);
    LocalFree(messageBuffer);
  } else {
    printf("%s. No error code.\n", msg);
  }
}

#else
static void* os_mmap(size_t size) {
  int map_prot = PROT_NONE;
  int map_flags = MAP_ANONYMOUS | MAP_PRIVATE;
  uint8_t* addr = mmap(NULL, size, map_prot, map_flags, -1, 0);
  if (addr == MAP_FAILED)
    return NULL;
  return addr;
}

static int os_munmap(void* addr, size_t size) {
  return munmap(addr, size);
}

static int os_mprotect(void* addr, size_t size) {
  return mprotect(addr, size, PROT_READ | PROT_WRITE);
}

static void os_print_last_error(const char* msg) {
  perror(msg);
}

#endif

static uint64_t get_alloc_size_for_mmap(uint64_t max_pages, bool is64) {
  assert(!is64 && "memory64 is not yet compatible with WASM_RT_USE_MMAP");
#if WASM_RT_MEMCHECK_GUARD_PAGES
  /* Reserve 8GiB. */
  const uint64_t max_size = 0x200000000ul;
  return max_size;
#else
  if (max_pages != 0) {
    const uint64_t max_size = max_pages * WASM_PAGE_SIZE;
    return max_size;
  }

  /* Reserve 4GiB. */
  const uint64_t max_size = 0x100000000ul;
  return max_size;
#endif
}

#endif

// Include operations for memory
#define WASM_RT_MEM_OPS
#include "wasm-rt-mem-impl-helper.inc"
#undef WASM_RT_MEM_OPS

// Include operations for shared memory
#define WASM_RT_MEM_OPS_SHARED
#include "wasm-rt-mem-impl-helper.inc"
#undef WASM_RT_MEM_OPS_SHARED

#undef C11_MEMORY_LOCK_VAR_INIT
#undef C11_MEMORY_LOCK_AQUIRE
#undef C11_MEMORY_LOCK_RELEASE
#undef PTHREAD_MEMORY_LOCK_VAR_INIT
#undef PTHREAD_MEMORY_LOCK_AQUIRE
#undef PTHREAD_MEMORY_LOCK_RELEASE
#undef WIN_MEMORY_LOCK_VAR_INIT
#undef WIN_MEMORY_LOCK_AQUIRE
#undef WIN_MEMORY_LOCK_RELEASE
#undef WASM_PAGE_SIZE
