Commit 87def2c8 by Eric Holk

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 .
parent de29f120
......@@ -5,8 +5,8 @@
./wasm-install/bin/emscripten/emcc "$1" -s BINARYEN=1 \
-s 'BINARYEN_METHOD="native-wasm"' \
--em-config wasm-install/emscripten_config_vanilla && \
--em-config wasm-install/emscripten_config_vanilla -O2 && \
./wasm-install/bin/sexpr-wasm a.out.wast -o a.out.wasm && \
./pnacl-sz a.out.wasm -o a.out.o -filetype=obj -O2 && \
clang -m32 a.out.o ./runtime/szrt.c \
./runtime/wasm-runtime.cpp -lm -g
./runtime/wasm-runtime.cpp -lm -g -lstdc++
......@@ -163,7 +163,7 @@ def run_test(test_file, verbose=False):
# Try to link and run the program.
cmd = "clang -g -m32 {} -o {} " + \
"./runtime/szrt.c ./runtime/wasm-runtime.cpp -lm"
"./runtime/szrt.c ./runtime/wasm-runtime.cpp -lm -lstdc++"
cmd = cmd.format(obj_file, exe_file)
if not run_test or os.system(cmd) == 0:
......
......@@ -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 @@ 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" {
#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;
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() {
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 @@ void env$$_exit(int Status) { env$$exit(Status); }
}
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)
void *wasmPtr(uint32_t Index) {
if (pageNum(Index) < WASM_NUM_PAGES) {
return WASM_MEMORY + Index;
}
abort();
}
UNIMPLEMENTED(longjmp)
UNIMPLEMENTED(pthread_cleanup_pop)
UNIMPLEMENTED(pthread_cleanup_push)
UNIMPLEMENTED(pthread_self)
UNIMPLEMENTED(setjmp)
extern int __szwasm_main(int, const char **);
extern int __szwasm_main(int, WasmPtr<WasmCharPtr>);
#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) {
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());
}
int env$$abs(int a) { return abs(a); }
double env$$pow(double x, double y) {
TRACE_ENTRY();
return trace(pow(x, y));
}
double env$$pow(double x, double y) { return 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 = read(Fd, Iov[I].Ptr.asPtr(), Iov[I].Length);
if (Curr < 0) {
return trace(-1);
}
Count += Curr;
}
return trace(Count);
}
int Curr = write(Fd, Ptr, Length);
/// 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 -1;
return trace(-1);
}
Count += Curr;
}
return Count;
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"
......@@ -56,7 +56,7 @@ bool llvmIRInput(const std::string &Filename) {
}
bool wasmInput(const std::string &Filename) {
return BuildDefs::llvmIrAsInput() &&
return BuildDefs::wasm() &&
std::regex_match(Filename, std::regex(".*\\.wasm"));
}
......
......@@ -680,6 +680,7 @@ public:
<< ") = ");
Ice::Variable *Dest = nullptr;
switch (Opcode) {
// TODO (eholk): merge these next two cases using getConstantInteger
case kExprI32Eqz: {
Dest = makeVariable(IceType_i32);
auto *Tmp = makeVariable(IceType_i1);
......@@ -772,6 +773,34 @@ public:
Control()->appendInst(Call);
break;
}
case kExprF32Sqrt: {
Dest = makeVariable(IceType_f32);
const auto FnName = Ctx->getGlobalString("llvm.sqrt.f32");
bool BadInstrinsic = false;
const auto *Info = Ctx->getIntrinsicsInfo().find(FnName, BadInstrinsic);
assert(!BadInstrinsic);
assert(Info);
auto *Call = InstIntrinsicCall::create(
Func, 1, Dest, Ctx->getConstantExternSym(FnName), Info->Info);
Call->addArg(Input);
Control()->appendInst(Call);
break;
}
case kExprF64Sqrt: {
Dest = makeVariable(IceType_f64);
const auto FnName = Ctx->getGlobalString("llvm.sqrt.f64");
bool BadInstrinsic = false;
const auto *Info = Ctx->getIntrinsicsInfo().find(FnName, BadInstrinsic);
assert(!BadInstrinsic);
assert(Info);
auto *Call = InstIntrinsicCall::create(
Func, 1, Dest, Ctx->getConstantExternSym(FnName), Info->Info);
Call->addArg(Input);
Control()->appendInst(Call);
break;
}
case kExprI64UConvertI32:
Dest = makeVariable(IceType_i64);
Control()->appendInst(
......@@ -1065,7 +1094,7 @@ public:
assert(Module);
const auto &IndirectTable = Module->function_table;
auto *Abort = getAbortTarget();
auto *Abort = getIndirectFailTarget();
assert(Args[0].toOperand());
......@@ -1173,7 +1202,7 @@ public:
// terrible (see https://goo.gl/Zj7DTr). Try adding a new instruction that
// encapsulates this "abort if false" pattern.
auto *CheckPassed = Func->makeNode();
auto *CheckFailed = getAbortTarget();
auto *CheckFailed = getBoundsFailTarget();
auto *Check = makeVariable(IceType_i1);
Control()->appendInst(InstIcmp::create(Func, InstIcmp::Ult, Check, Base,
......@@ -1281,7 +1310,8 @@ private:
class Cfg *Func;
GlobalContext *Ctx;
CfgNode *AbortTarget = nullptr;
CfgNode *BoundsFailTarget = nullptr;
CfgNode *IndirectFailTarget = nullptr;
SizeT NextArg = 0;
......@@ -1319,16 +1349,33 @@ private:
return Iter->second;
}
CfgNode *getAbortTarget() {
if (!AbortTarget) {
CfgNode *getBoundsFailTarget() {
if (!BoundsFailTarget) {
// TODO (eholk): Move this node to the end of the CFG, or even better,
// have only one abort block for the whole module.
BoundsFailTarget = Func->makeNode();
BoundsFailTarget->appendInst(InstCall::create(
Func, 0, nullptr,
Ctx->getConstantExternSym(Ctx->getGlobalString("__Sz_bounds_fail")),
false));
BoundsFailTarget->appendInst(InstUnreachable::create(Func));
}
return BoundsFailTarget;
}
CfgNode *getIndirectFailTarget() {
if (!IndirectFailTarget) {
// TODO (eholk): Move this node to the end of the CFG, or even better,
// have only one abort block for the whole module.
AbortTarget = Func->makeNode();
// TODO (eholk): This should probably actually call abort instead.
AbortTarget->appendInst(InstUnreachable::create(Func));
IndirectFailTarget = Func->makeNode();
IndirectFailTarget->appendInst(InstCall::create(
Func, 0, nullptr,
Ctx->getConstantExternSym(Ctx->getGlobalString("__Sz_indirect_fail")),
false));
IndirectFailTarget->appendInst(InstUnreachable::create(Func));
}
return AbortTarget;
return IndirectFailTarget;
}
template <typename F = std::function<void(Ostream &)>> void log(F Fn) const {
......@@ -1364,13 +1411,10 @@ std::unique_ptr<Cfg> WasmTranslator::translateFunction(Zone *Zone,
return Func;
}
// TODO(eholk): compute the correct buffer size. This uses 256k by default,
// which has been big enough for testing but is not a general solution.
constexpr SizeT BufferSize = 256 << 10;
constexpr SizeT InitialBufferSize = 16 << 10; // 16KB
WasmTranslator::WasmTranslator(GlobalContext *Ctx)
: Translator(Ctx), Buffer(new uint8_t[ ::BufferSize]),
BufferSize(::BufferSize) {}
: Translator(Ctx), Buffer(InitialBufferSize) {}
void WasmTranslator::translate(
const std::string &IRFilename,
......@@ -1380,16 +1424,22 @@ void WasmTranslator::translate(
Zone Zone;
ZoneScope _(&Zone);
SizeT BytesRead = InputStream->GetBytes(Buffer.get(), BufferSize);
LOG(out << "Read " << BytesRead << " bytes"
<< "\n");
assert(BytesRead < BufferSize);
SizeT BytesRead = 0;
while (true) {
BytesRead +=
InputStream->GetBytes(&Buffer[BytesRead], Buffer.size() - BytesRead);
LOG(out << "Read " << BytesRead << " bytes"
<< "\n");
if (BytesRead < Buffer.size())
break;
Buffer.resize(Buffer.size() * 2);
}
LOG(out << "Decoding module " << IRFilename << "\n");
constexpr v8::internal::Isolate *NoIsolate = nullptr;
auto Result = DecodeWasmModule(NoIsolate, &Zone, Buffer.get(),
Buffer.get() + BytesRead, false, kWasmOrigin);
auto Result = DecodeWasmModule(NoIsolate, &Zone, Buffer.data(),
Buffer.data() + BytesRead, false, kWasmOrigin);
auto Module = Result.val;
......@@ -1540,9 +1590,9 @@ void WasmTranslator::translate(
LOG(out << " " << Fn.func_index << ": " << FnName << "...");
Body.sig = Fn.sig;
Body.base = Buffer.get();
Body.start = Buffer.get() + Fn.code_start_offset;
Body.end = Buffer.get() + Fn.code_end_offset;
Body.base = Buffer.data();
Body.start = Buffer.data() + Fn.code_start_offset;
Body.end = Buffer.data() + Fn.code_end_offset;
auto Func = translateFunction(&Zone, Body);
Func->setFunctionName(Ctx->getGlobalString(FnName));
......
......@@ -70,8 +70,7 @@ public:
v8::internal::wasm::FunctionBody &Body);
private:
std::unique_ptr<uint8_t[]> Buffer;
SizeT BufferSize;
std::vector<uint8_t> Buffer;
};
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment