//===- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WASM_TRACE_RUNTIME #define TRACE_ENTRY() \ { std::cerr << __func__ << "(...) = "; } template T trace(T x) { std::cerr << x << std::endl; return x; } void trace() { std::cerr << "(void)" << std::endl; } #else #define TRACE_ENTRY() template 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(reinterpret_cast(Ptr) - WASM_MEMORY); } template T *wasmPtr(int Index) { if (pageNum(Index) < WASM_NUM_PAGES) { return reinterpret_cast(WASM_MEMORY + Index); } abort(); } template 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(Ptr); } int asInt() const { return Ptr; } }; typedef WasmPtr WasmCharPtr; template 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(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); #define WASM_REF(Type, Index) (WasmPtr(Index).asPtr()) #define WASM_DEREF(Type, Index) (*WASM_REF(Type, Index)) int main(int argc, const char **argv) { // Create the heap. std::vector 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 WasmArgV = HeapBreak; WasmPtr *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) { TRACE_ENTRY(); char *CTime = ctime(Time.asPtr()); strncpy(GlobalData->StrBuf, CTime, sizeof(GlobalData->StrBuf)); GlobalData->StrBuf[sizeof(GlobalData->StrBuf) - 1] = '\0'; return trace(WasmPtr(GlobalData->StrBuf).asInt()); } double env$$pow(double x, double y) { TRACE_ENTRY(); return trace(pow(x, y)); } time_t env$$time(WasmPtr 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 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 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 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 VarArgs) { TRACE_ENTRY(); int Fd = VarArgs[0]; return trace(close(Fd)); } /// sys_unlink int env$$__syscall10(int Which, WasmArray 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 VarArgs) { TRACE_ENTRY(); (void)Which; (void)VarArgs; return trace(getpid()); } /// sys_rmdir int env$$__syscall40(int Which, WasmArray 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 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 Ptr; int Length; }; /// sys_readv int env$$__syscall145(int Which, WasmArray VarArgs) { TRACE_ENTRY(); int Fd = VarArgs[0]; WasmArray 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 VarArgs) { TRACE_ENTRY(); int Fd = VarArgs[0]; WasmArray 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 VarArgs) { TRACE_ENTRY(); (void)Which; (void)VarArgs; // TODO (eholk): figure out how to implement this. return trace(-ENOMEM); } } // end of extern "C"