//===- subzero/runtime/wasm-runtime.cpp - Subzero WASM runtime source -----===//
//
//                        The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements the system calls required by the libc that is included
// in WebAssembly programs.
//
//===----------------------------------------------------------------------===//

#include <algorithm>
#include <cassert>
#include <cmath>
#include <iostream>
#include <vector>

#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>

#ifdef WASM_TRACE_RUNTIME
#define TRACE_ENTRY()                                                          \
  { std::cerr << __func__ << "(...) = "; }
template <typename T> T trace(T x) {
  std::cerr << x << std::endl;
  return x;
}
void trace() { std::cerr << "(void)" << std::endl; }
#else
#define TRACE_ENTRY()
template <typename T> T trace(T x) { return x; }
void trace() {}
#endif // WASM_TRACE_RUNTIME

extern "C" {
char *WASM_MEMORY;
extern uint32_t WASM_DATA_SIZE;
extern uint32_t WASM_NUM_PAGES;
} // end of extern "C"

namespace {
uint32_t HeapBreak;

// TODO (eholk): make all of these constexpr.
const uint32_t PageSizeLog2 = 16;
const uint32_t PageSize = 1 << PageSizeLog2; // 64KB
const uint32_t StackPtrLoc = 1024;           // defined by emscripten

uint32_t pageNum(uint32_t Index) { return Index >> PageSizeLog2; }
} // end of anonymous namespace

namespace env {
double floor(double X) { return std::floor(X); }

float floor(float X) { return std::floor(X); }
} // end of namespace env

// TODO (eholk): move the C parts outside and use C++ name mangling.

namespace {

/// Some runtime functions need to return pointers. The WasmData struct is used
/// to preallocate space for these on the heap.
struct WasmData {

  /// StrBuf is returned by functions that return strings.
  char StrBuf[256];
};

WasmData *GlobalData = NULL;

int toWasm(void *Ptr) {
  return reinterpret_cast<int>(reinterpret_cast<char *>(Ptr) - WASM_MEMORY);
}

template <typename T> T *wasmPtr(int Index) {
  if (pageNum(Index) < WASM_NUM_PAGES) {
    return reinterpret_cast<T *>(WASM_MEMORY + Index);
  }
  abort();
}

template <typename T> class WasmPtr {
  int Ptr;

public:
  WasmPtr(int Ptr) : Ptr(Ptr) {
    // TODO (eholk): make this a static_assert once we have C++11
    assert(sizeof(*this) == sizeof(int));
  }

  WasmPtr(T *Ptr) : Ptr(toWasm(Ptr)) {}

  T &operator*() const { return *asPtr(); }

  T *asPtr() const { return wasmPtr<T>(Ptr); }

  int asInt() const { return Ptr; }
};

typedef WasmPtr<char> WasmCharPtr;

template <typename T> class WasmArray {
  int Ptr;

public:
  WasmArray(int Ptr) : Ptr(Ptr) {
    // TODO (eholk): make this a static_assert once we have C++11.
    assert(sizeof(*this) == sizeof(int));
  }

  T &operator[](unsigned int Index) const { return wasmPtr<T>(Ptr)[Index]; }
};
} // end of anonymous namespace

// TODO (eholk): move the C parts outside and use C++ name mangling.
extern "C" {

void __Sz_bounds_fail() {
  std::cerr << "Bounds check failure" << std::endl;
  abort();
}

void __Sz_indirect_fail() {
  std::cerr << "Invalid indirect call target" << std::endl;
  abort();
}

extern char WASM_DATA_INIT[];

void env$$abort() {
  fprintf(stderr, "Aborting...\n");
  abort();
}

void env$$_abort() { env$$abort(); }

double env$$floor_f(float X) {
  TRACE_ENTRY();
  return env::floor(X);
}
double env$$floor_d(double X) {
  TRACE_ENTRY();
  return env::floor(X);
}

void env$$exit(int Status) {
  TRACE_ENTRY();
  exit(Status);
}
void env$$_exit(int Status) {
  TRACE_ENTRY();
  env$$exit(Status);
}

#define UNIMPLEMENTED(f)                                                       \
  void env$$##f() {                                                            \
    fprintf(stderr, "Unimplemented: " #f "\n");                                \
    abort();                                                                   \
  }

int32_t env$$sbrk(int32_t Increment) {
  TRACE_ENTRY();
  uint32_t OldBreak = HeapBreak;
  HeapBreak += Increment;
  return trace(OldBreak);
}

UNIMPLEMENTED(__addtf3)
UNIMPLEMENTED(__assert_fail)
UNIMPLEMENTED(__builtin_apply)
UNIMPLEMENTED(__builtin_apply_args)
UNIMPLEMENTED(__builtin_isinff)
UNIMPLEMENTED(__builtin_isinfl)
UNIMPLEMENTED(__builtin_malloc)
UNIMPLEMENTED(__divtf3)
UNIMPLEMENTED(__eqtf2)
UNIMPLEMENTED(__extenddftf2)
UNIMPLEMENTED(__extendsftf2)
UNIMPLEMENTED(__fixsfti)
UNIMPLEMENTED(__fixtfdi)
UNIMPLEMENTED(__fixtfsi)
UNIMPLEMENTED(__fixunstfsi)
UNIMPLEMENTED(__floatditf)
UNIMPLEMENTED(__floatsitf)
UNIMPLEMENTED(__floatunsitf)
UNIMPLEMENTED(__getf2)
UNIMPLEMENTED(__letf2)
UNIMPLEMENTED(__lttf2)
UNIMPLEMENTED(__multf3)
UNIMPLEMENTED(__multi3)
UNIMPLEMENTED(__netf2)
UNIMPLEMENTED(__subtf3)
UNIMPLEMENTED(__syscall140) // sys_llseek
UNIMPLEMENTED(__syscall221) // sys_fcntl64
UNIMPLEMENTED(__trunctfdf2)
UNIMPLEMENTED(__trunctfsf2)
UNIMPLEMENTED(__unordtf2)
UNIMPLEMENTED(longjmp)
UNIMPLEMENTED(pthread_cleanup_pop)
UNIMPLEMENTED(pthread_cleanup_push)
UNIMPLEMENTED(pthread_self)
UNIMPLEMENTED(setjmp)

extern int __szwasm_main(int, WasmPtr<WasmCharPtr>);

#define WASM_REF(Type, Index) (WasmPtr<Type>(Index).asPtr())
#define WASM_DEREF(Type, Index) (*WASM_REF(Type, Index))

int main(int argc, const char **argv) {
  // Create the heap.
  std::vector<char> WasmHeap(WASM_NUM_PAGES << PageSizeLog2);
  WASM_MEMORY = WasmHeap.data();
  std::copy(WASM_DATA_INIT, WASM_DATA_INIT + WASM_DATA_SIZE, WasmHeap.begin());

  // TODO (eholk): align these allocations correctly.

  // Allocate space for the global data.
  HeapBreak = WASM_DATA_SIZE;
  GlobalData = WASM_REF(WasmData, HeapBreak);
  HeapBreak += sizeof(WasmData);

  // copy the command line arguments.
  WasmPtr<WasmCharPtr> WasmArgV = HeapBreak;
  WasmPtr<char> *WasmArgVPtr = WasmArgV.asPtr();
  HeapBreak += argc * sizeof(*WasmArgVPtr);

  for (int i = 0; i < argc; ++i) {
    WasmArgVPtr[i] = HeapBreak;
    strcpy(WASM_REF(char, HeapBreak), argv[i]);
    HeapBreak += strlen(argv[i]) + 1;
  }

  // Initialize the break to the nearest page boundary after the data segment
  HeapBreak = (WASM_DATA_SIZE + PageSize - 1) & ~(PageSize - 1);

  // Initialize the stack pointer.
  WASM_DEREF(int32_t, StackPtrLoc) = WASM_NUM_PAGES << PageSizeLog2;

  return __szwasm_main(argc, WasmArgV);
}

int env$$abs(int a) {
  TRACE_ENTRY();
  return trace(abs(a));
}

clock_t env$$clock() {
  TRACE_ENTRY();
  return trace(clock());
}

int env$$ctime(WasmPtr<time_t> Time) {
  TRACE_ENTRY();
  char *CTime = ctime(Time.asPtr());
  strncpy(GlobalData->StrBuf, CTime, sizeof(GlobalData->StrBuf));
  GlobalData->StrBuf[sizeof(GlobalData->StrBuf) - 1] = '\0';
  return trace(WasmPtr<char>(GlobalData->StrBuf).asInt());
}

double env$$pow(double x, double y) {
  TRACE_ENTRY();
  return trace(pow(x, y));
}

time_t env$$time(WasmPtr<time_t> Time) {
  TRACE_ENTRY();
  time_t *TimePtr = WASM_REF(time_t, Time);
  return trace(time(TimePtr));
}

// lock and unlock are no-ops in wasm.js, so we mimic that behavior.
void env$$__lock(int32_t) {
  TRACE_ENTRY();
  trace();
}

void env$$__unlock(int32_t) {
  TRACE_ENTRY();
  trace();
}

/// sys_read
int env$$__syscall3(int Which, WasmArray<int> VarArgs) {
  TRACE_ENTRY();
  int Fd = VarArgs[0];
  int Buffer = VarArgs[1];
  int Length = VarArgs[2];

  return trace(read(Fd, WASM_REF(char *, Buffer), Length));
}

/// sys_write
int env$$__syscall4(int Which, WasmArray<int> VarArgs) {
  TRACE_ENTRY();
  int Fd = VarArgs[0];
  int Buffer = VarArgs[1];
  int Length = VarArgs[2];

  return trace(write(Fd, WASM_REF(char *, Buffer), Length));
}

/// sys_open
int env$$__syscall5(int Which, WasmArray<int> VarArgs) {
  TRACE_ENTRY();
  int WasmPath = VarArgs[0];
  int Flags = VarArgs[1];
  int Mode = VarArgs[2];
  const char *Path = WASM_REF(char, WasmPath);

  return trace(open(Path, Flags, Mode));
}

/// sys_close
int env$$__syscall6(int Which, WasmArray<int> VarArgs) {
  TRACE_ENTRY();
  int Fd = VarArgs[0];

  return trace(close(Fd));
}

/// sys_unlink
int env$$__syscall10(int Which, WasmArray<int> VarArgs) {
  TRACE_ENTRY();
  int WasmPath = VarArgs[0];
  const char *Path = WASM_REF(char, WasmPath);

  return trace(unlink(Path));
}

/// sys_getpid
int env$$__syscall20(int Which, WasmArray<int> VarArgs) {
  TRACE_ENTRY();
  (void)Which;
  (void)VarArgs;

  return trace(getpid());
}

/// sys_rmdir
int env$$__syscall40(int Which, WasmArray<int> VarArgs) {
  TRACE_ENTRY();
  int WasmPath = VarArgs[0];
  const char *Path = WASM_REF(char, WasmPath);

  return trace(rmdir(Path));
}

/// sys_ioctl
int env$$__syscall54(int Which, WasmArray<int> VarArgs) {
  TRACE_ENTRY();
  int Fd = VarArgs[0];
  int Op = VarArgs[1];
  int ArgP = VarArgs[2];

  switch (Op) {
  case TCGETS: {
    // struct termios has no pointers. Otherwise, we'd have to rewrite them.
    struct termios *TermIOS = WASM_REF(struct termios, ArgP);
    return trace(ioctl(Fd, TCGETS, TermIOS));
  }
  default:
    // TODO (eholk): implement more ioctls
    return trace(-ENOTTY);
  }
}

struct IoVec {
  WasmPtr<char> Ptr;
  int Length;
};

/// sys_readv
int env$$__syscall145(int Which, WasmArray<int> VarArgs) {
  TRACE_ENTRY();
  int Fd = VarArgs[0];
  WasmArray<IoVec> Iov = VarArgs[1];
  int Iovcnt = VarArgs[2];

  int Count = 0;

  for (int I = 0; I < Iovcnt; ++I) {
    int Curr = read(Fd, Iov[I].Ptr.asPtr(), Iov[I].Length);

    if (Curr < 0) {
      return trace(-1);
    }
    Count += Curr;
  }
  return trace(Count);
}

/// sys_writev
int env$$__syscall146(int Which, WasmArray<int> VarArgs) {
  TRACE_ENTRY();
  int Fd = VarArgs[0];
  WasmArray<IoVec> Iov = VarArgs[1];
  int Iovcnt = VarArgs[2];

  int Count = 0;

  for (int I = 0; I < Iovcnt; ++I) {
    int Curr = write(Fd, Iov[I].Ptr.asPtr(), Iov[I].Length);

    if (Curr < 0) {
      return trace(-1);
    }
    Count += Curr;
  }
  return trace(Count);
}

/// sys_mmap_pgoff
int env$$__syscall192(int Which, WasmArray<int> VarArgs) {
  TRACE_ENTRY();
  (void)Which;
  (void)VarArgs;

  // TODO (eholk): figure out how to implement this.

  return trace(-ENOMEM);
}
} // end of extern "C"
