Subzero, Wasm: Dynamically reallocate read buffer. Runtime improvements.

This change fills in several more runtime functions needed by several
benchmarks, as well as changing the buffer handling in the WASM
decoder. Now the decoder will resize the buffer as needed to
accomodate large .wasm modules.

Tracing can now be enabled on runtime functions to aid with
debugging. Additionally, runtime failures such as bounds check
failures or invalid indirect function calls tell what kind of failure
occured.

BUG= https://bugs.chromium.org/p/nativeclient/issues/detail?id=4369
R=jpp@chromium.org, stichnot@chromium.org

Review URL: https://codereview.chromium.org/1918213003 .
diff --git a/runtime/wasm-runtime.cpp b/runtime/wasm-runtime.cpp
index d867509..3ea82a0 100644
--- a/runtime/wasm-runtime.cpp
+++ b/runtime/wasm-runtime.cpp
@@ -14,8 +14,40 @@
 
 #include <cassert>
 #include <cmath>
-// TODO (eholk): change to cstdint
+#include <errno.h>
+#include <fcntl.h>
+#include <iostream>
+#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" {
+extern char WASM_MEMORY[];
+extern uint32_t WASM_DATA_SIZE;
+extern uint32_t WASM_NUM_PAGES;
+} // end of extern "C"
 
 namespace {
 uint32_t HeapBreak;
@@ -35,20 +67,75 @@
 } // end of namespace env
 
 // TODO (eholk): move the C parts outside and use C++ name mangling.
-extern "C" {
-#include <errno.h>
-#include <fcntl.h>
-#include <math.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
 
-extern char WASM_MEMORY[];
-extern uint32_t WASM_DATA_SIZE;
-extern uint32_t WASM_NUM_PAGES;
+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();
+}
 
 void env$$abort() {
   fprintf(stderr, "Aborting...\n");
@@ -57,11 +144,23 @@
 
 void env$$_abort() { env$$abort(); }
 
-double env$$floor_f(float X) { return env::floor(X); }
-double env$$floor_d(double X) { return env::floor(X); }
+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) { exit(Status); }
-void env$$_exit(int Status) { env$$exit(Status); }
+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() {                                                            \
@@ -70,138 +169,259 @@
   }
 
 int32_t env$$sbrk(int32_t Increment) {
+  TRACE_ENTRY();
+  uint32_t OldBreak = HeapBreak;
   HeapBreak += Increment;
-  return HeapBreak;
+  return trace(OldBreak);
 }
 
-UNIMPLEMENTED(setjmp)
-UNIMPLEMENTED(longjmp)
+UNIMPLEMENTED(__addtf3)
 UNIMPLEMENTED(__assert_fail)
-UNIMPLEMENTED(__builtin_malloc)
-UNIMPLEMENTED(__builtin_isinff)
-UNIMPLEMENTED(__builtin_isinfl)
 UNIMPLEMENTED(__builtin_apply)
 UNIMPLEMENTED(__builtin_apply_args)
-UNIMPLEMENTED(pthread_cleanup_push)
-UNIMPLEMENTED(pthread_cleanup_pop)
-UNIMPLEMENTED(pthread_self)
-UNIMPLEMENTED(__floatditf)
-UNIMPLEMENTED(__floatsitf)
+UNIMPLEMENTED(__builtin_isinff)
+UNIMPLEMENTED(__builtin_isinfl)
+UNIMPLEMENTED(__builtin_malloc)
+UNIMPLEMENTED(__divtf3)
+UNIMPLEMENTED(__eqtf2)
+UNIMPLEMENTED(__extenddftf2)
+UNIMPLEMENTED(__extendsftf2)
+UNIMPLEMENTED(__fixsfti)
 UNIMPLEMENTED(__fixtfdi)
 UNIMPLEMENTED(__fixtfsi)
-UNIMPLEMENTED(__fixsfti)
-UNIMPLEMENTED(__netf2)
+UNIMPLEMENTED(__fixunstfsi)
+UNIMPLEMENTED(__floatditf)
+UNIMPLEMENTED(__floatsitf)
+UNIMPLEMENTED(__floatunsitf)
 UNIMPLEMENTED(__getf2)
-UNIMPLEMENTED(__eqtf2)
+UNIMPLEMENTED(__letf2)
 UNIMPLEMENTED(__lttf2)
-UNIMPLEMENTED(__addtf3)
-UNIMPLEMENTED(__subtf3)
-UNIMPLEMENTED(__divtf3)
 UNIMPLEMENTED(__multf3)
 UNIMPLEMENTED(__multi3)
-UNIMPLEMENTED(__lock)
-UNIMPLEMENTED(__unlock)
-UNIMPLEMENTED(__syscall6)   // sys_close
+UNIMPLEMENTED(__netf2)
+UNIMPLEMENTED(__subtf3)
 UNIMPLEMENTED(__syscall140) // sys_llseek
+UNIMPLEMENTED(__syscall221) // sys_fcntl64
+UNIMPLEMENTED(__trunctfdf2)
+UNIMPLEMENTED(__trunctfsf2)
 UNIMPLEMENTED(__unordtf2)
-UNIMPLEMENTED(__fixunstfsi)
-UNIMPLEMENTED(__floatunsitf)
-UNIMPLEMENTED(__extenddftf2)
+UNIMPLEMENTED(longjmp)
+UNIMPLEMENTED(pthread_cleanup_pop)
+UNIMPLEMENTED(pthread_cleanup_push)
+UNIMPLEMENTED(pthread_self)
+UNIMPLEMENTED(setjmp)
 
-void *wasmPtr(uint32_t Index) {
-  if (pageNum(Index) < WASM_NUM_PAGES) {
-    return WASM_MEMORY + Index;
-  }
-  abort();
-}
+extern int __szwasm_main(int, WasmPtr<WasmCharPtr>);
 
-extern int __szwasm_main(int, const char **);
-
-#define WASM_REF(Type, Index) ((Type *)wasmPtr(Index))
+#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) {
+  // 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, argv);
+  return __szwasm_main(argc, WasmArgV);
 }
 
-int env$$abs(int a) { return abs(a); }
+int env$$abs(int a) {
+  TRACE_ENTRY();
+  return trace(abs(a));
+}
 
-double env$$pow(double x, double y) { return pow(x, y); }
+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, int VarArgs) {
-  int Fd = WASM_DEREF(int, VarArgs + 0 * sizeof(int));
-  int Buffer = WASM_DEREF(int, VarArgs + 1 * sizeof(int));
-  int Length = WASM_DEREF(int, VarArgs + 2 * sizeof(int));
+int env$$__syscall4(int Which, WasmArray<int> VarArgs) {
+  TRACE_ENTRY();
+  int Fd = VarArgs[0];
+  int Buffer = VarArgs[1];
+  int Length = VarArgs[2];
 
-  return write(Fd, WASM_REF(char *, Buffer), Length);
+  return trace(write(Fd, WASM_REF(char *, Buffer), Length));
 }
 
 /// sys_open
-int env$$__syscall5(int Which, int VarArgs) {
-  int WasmPath = WASM_DEREF(int, VarArgs);
-  int Flags = WASM_DEREF(int, VarArgs + 4);
-  int Mode = WASM_DEREF(int, VarArgs + 8);
+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);
 
-  fprintf(stderr, "sys_open(%s, %d, %d)\n", Path, Flags, Mode);
+  return trace(open(Path, Flags, Mode));
+}
 
-  return 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, int VarArgs) {
+int env$$__syscall20(int Which, WasmArray<int> VarArgs) {
+  TRACE_ENTRY();
   (void)Which;
   (void)VarArgs;
 
-  return getpid();
+  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 A, int B) {
-  int Fd = WASM_DEREF(int, B + 0 * sizeof(int));
-  int Op = WASM_DEREF(int, B + 1 * sizeof(int));
-  int ArgP = WASM_DEREF(int, B + 2 * sizeof(int));
-  // TODO (eholk): implement sys_ioctl
-  return -ENOTTY;
+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);
+  }
 }
 
-/// sys_write
-int env$$__syscall146(int Which, int VarArgs) {
+struct IoVec {
+  WasmPtr<char> Ptr;
+  int Length;
+};
 
-  int Fd = WASM_DEREF(int, VarArgs);
-  int Iov = WASM_DEREF(int, VarArgs + sizeof(int));
-  int Iovcnt = WASM_DEREF(int, VarArgs + 2 * sizeof(int));
+/// 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) {
-    void *Ptr = WASM_REF(void, WASM_DEREF(int, Iov + I * 8));
-    int Length = WASM_DEREF(int, Iov + I * 8 + 4);
-
-    int Curr = write(Fd, Ptr, Length);
+    int Curr = read(Fd, Iov[I].Ptr.asPtr(), Iov[I].Length);
 
     if (Curr < 0) {
-      return -1;
+      return trace(-1);
     }
     Count += Curr;
   }
-  return Count;
+  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, int VarArgs) {
+int env$$__syscall192(int Which, WasmArray<int> VarArgs) {
+  TRACE_ENTRY();
   (void)Which;
   (void)VarArgs;
 
   // TODO (eholk): figure out how to implement this.
 
-  return -ENOMEM;
+  return trace(-ENOMEM);
 }
 } // end of extern "C"