Commit 4aae81af by Eric Holk

Subzero. Wasm. Implement sbrk and correctly do bounds checks.

Cleans up and generally improves memory handling in WASM. WasmTranslator now outputs the number of pages requested so the runtime can do correct bounds checks. The runtime also initializes the stack pointer correctly (stored at address 1024), so we no longer have to deal with negative pointers. This allows bounds checks to be done with a single comparison against the size of the heap. Because of this, we now support non-power-of-two heap sizes. Sbrk is implemented by having the runtime keep track of the current heap break and incrementing it as necessary. The heap break is initialized to the start of the first page beyond any initialized data in the WASM heap. These changes allow us to pass the complete set of torture tests that are passing on the Wasm waterfall. BUG= https://bugs.chromium.org/p/nativeclient/issues/detail?id=4369 R=kschimpf@google.com, stichnot@chromium.org Review URL: https://codereview.chromium.org/1913153003 .
parent 1a478b1c
#!/bin/bash #!/bin/bash
BUILDID=5430 BUILDID=5528
BUILD_PATH=https://storage.googleapis.com/wasm-llvm/builds/git BUILD_PATH=https://storage.googleapis.com/wasm-llvm/builds/git
wget -O - /wasm-torture-s-$BUILDID.tbz2 \ wget -O - $BUILD_PATH/wasm-torture-s-$BUILDID.tbz2 \
| tar xj | tar xj
wget -O - $BUILD_PATH/wasm-torture-s2wasm-sexpr-wasm-$BUILDID.tbz2 \ wget -O - $BUILD_PATH/wasm-torture-s2wasm-sexpr-wasm-$BUILDID.tbz2 \
......
...@@ -21,25 +21,8 @@ import sys ...@@ -21,25 +21,8 @@ import sys
import threading import threading
IGNORED_TESTS = set([ IGNORED_TESTS = set([
'loop-2f.c.wasm', # mmap not in MVP # The remaining tests are known waterfall failures
'loop-2g.c.wasm', # mmap not in MVP
'960521-1.c.wasm', # sbrk
'ipa-sra-2.c.wasm', # sbrk
'pr41463.c.wasm', # sbrk
'20051113-1.c.wasm', # sbrk
'990628-1.c.wasm', # sbrk
'pr41395-2.c.wasm', # sbrk
'pr42614.c.wasm', # sbrk
'pr41395-1.c.wasm', # sbrk
'920810-1.c.wasm', # sbrk
'20000914-1.c.wasm', # sbrk
'pr15262-1.c.wasm', # sbrk
'941014-2.c.wasm', # sbrk
'va-arg-21.c.wasm', # sbrk
'20020406-1.c.wasm', # sbrk
# waterfall known failures
'20010122-1.c.wasm', '20010122-1.c.wasm',
'20031003-1.c.wasm', '20031003-1.c.wasm',
'20071018-1.c.wasm', '20071018-1.c.wasm',
......
...@@ -12,13 +12,27 @@ ...@@ -12,13 +12,27 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
#include <cassert>
#include <cmath> #include <cmath>
// TODO (eholk): change to cstdint
#include <stdint.h>
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 { namespace env {
double floor(double X) { return std::floor(X); } double floor(double X) { return std::floor(X); }
float floor(float 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. // TODO (eholk): move the C parts outside and use C++ name mangling.
extern "C" { extern "C" {
...@@ -33,6 +47,8 @@ extern "C" { ...@@ -33,6 +47,8 @@ extern "C" {
#include <unistd.h> #include <unistd.h>
extern char WASM_MEMORY[]; extern char WASM_MEMORY[];
extern uint32_t WASM_DATA_SIZE;
extern uint32_t WASM_NUM_PAGES;
void env$$abort() { void env$$abort() {
fprintf(stderr, "Aborting...\n"); fprintf(stderr, "Aborting...\n");
...@@ -53,7 +69,11 @@ void env$$_exit(int Status) { env$$exit(Status); } ...@@ -53,7 +69,11 @@ void env$$_exit(int Status) { env$$exit(Status); }
abort(); \ abort(); \
} }
UNIMPLEMENTED(sbrk) int32_t env$$sbrk(int32_t Increment) {
HeapBreak += Increment;
return HeapBreak;
}
UNIMPLEMENTED(setjmp) UNIMPLEMENTED(setjmp)
UNIMPLEMENTED(longjmp) UNIMPLEMENTED(longjmp)
UNIMPLEMENTED(__assert_fail) UNIMPLEMENTED(__assert_fail)
...@@ -83,18 +103,16 @@ UNIMPLEMENTED(__lock) ...@@ -83,18 +103,16 @@ UNIMPLEMENTED(__lock)
UNIMPLEMENTED(__unlock) UNIMPLEMENTED(__unlock)
UNIMPLEMENTED(__syscall6) // sys_close UNIMPLEMENTED(__syscall6) // sys_close
UNIMPLEMENTED(__syscall140) // sys_llseek UNIMPLEMENTED(__syscall140) // sys_llseek
UNIMPLEMENTED(__syscall192) // sys_mmap?
UNIMPLEMENTED(__unordtf2) UNIMPLEMENTED(__unordtf2)
UNIMPLEMENTED(__fixunstfsi) UNIMPLEMENTED(__fixunstfsi)
UNIMPLEMENTED(__floatunsitf) UNIMPLEMENTED(__floatunsitf)
UNIMPLEMENTED(__extenddftf2) UNIMPLEMENTED(__extenddftf2)
void *wasmPtr(int Index) { void *wasmPtr(uint32_t Index) {
// TODO (eholk): get the mask from the WASM file. if (pageNum(Index) < WASM_NUM_PAGES) {
const int MASK = 0xffffff;
Index &= MASK;
return WASM_MEMORY + Index; return WASM_MEMORY + Index;
}
abort();
} }
extern int __szwasm_main(int, const char **); extern int __szwasm_main(int, const char **);
...@@ -102,7 +120,15 @@ extern int __szwasm_main(int, const char **); ...@@ -102,7 +120,15 @@ extern int __szwasm_main(int, const char **);
#define WASM_REF(Type, Index) ((Type *)wasmPtr(Index)) #define WASM_REF(Type, Index) ((Type *)wasmPtr(Index))
#define WASM_DEREF(Type, Index) (*WASM_REF(Type, Index)) #define WASM_DEREF(Type, Index) (*WASM_REF(Type, Index))
int main(int argc, const char **argv) { return __szwasm_main(argc, argv); } int main(int argc, const char **argv) {
// 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);
}
int env$$abs(int a) { return abs(a); } int env$$abs(int a) { return abs(a); }
...@@ -168,4 +194,14 @@ int env$$__syscall146(int Which, int VarArgs) { ...@@ -168,4 +194,14 @@ int env$$__syscall146(int Which, int VarArgs) {
} }
return Count; return Count;
} }
/// sys_mmap_pgoff
int env$$__syscall192(int Which, int VarArgs) {
(void)Which;
(void)VarArgs;
// TODO (eholk): figure out how to implement this.
return -ENOMEM;
}
} // end of extern "C" } // end of extern "C"
...@@ -1065,9 +1065,7 @@ public: ...@@ -1065,9 +1065,7 @@ public:
assert(Module); assert(Module);
const auto &IndirectTable = Module->function_table; const auto &IndirectTable = Module->function_table;
// TODO(eholk): This should probably actually call abort instead. auto *Abort = getAbortTarget();
auto *Abort = Func->makeNode();
Abort->appendInst(InstUnreachable::create(Func));
assert(Args[0].toOperand()); assert(Args[0].toOperand());
...@@ -1137,14 +1135,23 @@ public: ...@@ -1137,14 +1135,23 @@ public:
Operand *sanitizeAddress(Operand *Base, uint32_t Offset) { Operand *sanitizeAddress(Operand *Base, uint32_t Offset) {
SizeT MemSize = Module->module->min_mem_pages * WASM_PAGE_SIZE; SizeT MemSize = Module->module->min_mem_pages * WASM_PAGE_SIZE;
SizeT MemMask = MemSize - 1;
bool ConstZeroBase = false; bool ConstZeroBase = false;
// first, add the index and the offset together. // first, add the index and the offset together.
if (auto *ConstBase = llvm::dyn_cast<ConstantInteger32>(Base)) { if (auto *ConstBase = llvm::dyn_cast<ConstantInteger32>(Base)) {
uint32_t RealOffset = Offset + ConstBase->getValue(); uint32_t RealOffset = Offset + ConstBase->getValue();
RealOffset &= MemMask; if (RealOffset >= MemSize) {
// We've proven this will always be an out of bounds access, so insert
// an unconditional trap.
Control()->appendInst(InstUnreachable::create(Func));
// It doesn't matter what we return here, so return something that will
// allow the rest of code generation to happen.
//
// We might be tempted to just abort translation here, but out of bounds
// memory access is a runtime trap, not a compile error.
return Ctx->getConstantZero(getPointerType());
}
Base = Ctx->getConstantInt32(RealOffset); Base = Ctx->getConstantInt32(RealOffset);
ConstZeroBase = (0 == RealOffset); ConstZeroBase = (0 == RealOffset);
} else if (0 != Offset) { } else if (0 != Offset) {
...@@ -1156,12 +1163,25 @@ public: ...@@ -1156,12 +1163,25 @@ public:
Base = Addr; Base = Addr;
} }
// Do the bounds check.
//
// TODO (eholk): Add a command line argument to control whether bounds
// checks are inserted, and maybe add a way to duplicate bounds checks to
// get a better sense of the overhead.
if (!llvm::dyn_cast<ConstantInteger32>(Base)) { if (!llvm::dyn_cast<ConstantInteger32>(Base)) {
auto *ClampedAddr = makeVariable(Ice::getPointerType()); // TODO (eholk): creating a new basic block on every memory access is
// 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 *Check = makeVariable(IceType_i1);
Control()->appendInst(InstIcmp::create(Func, InstIcmp::Ult, Check, Base,
Ctx->getConstantInt32(MemSize)));
Control()->appendInst( Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::And, ClampedAddr, Base, InstBr::create(Func, Check, CheckPassed, CheckFailed));
Ctx->getConstantInt32(MemSize - 1)));
Base = ClampedAddr; *ControlPtr = OperandNode(CheckPassed);
} }
Ice::Operand *RealAddr = nullptr; Ice::Operand *RealAddr = nullptr;
...@@ -1261,6 +1281,8 @@ private: ...@@ -1261,6 +1281,8 @@ private:
class Cfg *Func; class Cfg *Func;
GlobalContext *Ctx; GlobalContext *Ctx;
CfgNode *AbortTarget = nullptr;
SizeT NextArg = 0; SizeT NextArg = 0;
CfgUnorderedMap<Operand *, InstPhi *> PhiMap; CfgUnorderedMap<Operand *, InstPhi *> PhiMap;
...@@ -1297,6 +1319,18 @@ private: ...@@ -1297,6 +1319,18 @@ private:
return Iter->second; return Iter->second;
} }
CfgNode *getAbortTarget() {
if (!AbortTarget) {
// 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));
}
return AbortTarget;
}
template <typename F = std::function<void(Ostream &)>> void log(F Fn) const { template <typename F = std::function<void(Ostream &)>> void log(F Fn) const {
if (BuildDefs::dump() && (getFlags().getVerbose() & IceV_Wasm)) { if (BuildDefs::dump() && (getFlags().getVerbose() & IceV_Wasm)) {
Fn(Ctx->getStrDump()); Fn(Ctx->getStrDump());
...@@ -1470,6 +1504,14 @@ void WasmTranslator::translate( ...@@ -1470,6 +1504,14 @@ void WasmTranslator::translate(
WritePtr += Seg.source_size; WritePtr += Seg.source_size;
} }
// Save the size of the initialized data in a global variable so the runtime
// can use it to determine the initial heap break.
auto *GlobalDataSize = VariableDeclaration::createExternal(Globals.get());
GlobalDataSize->setName(Ctx->getGlobalString("WASM_DATA_SIZE"));
GlobalDataSize->addInitializer(VariableDeclaration::DataInitializer::create(
Globals.get(), reinterpret_cast<const char *>(&WritePtr),
sizeof(WritePtr)));
// Pad the rest with zeros // Pad the rest with zeros
SizeT DataSize = Module->min_mem_pages * WASM_PAGE_SIZE; SizeT DataSize = Module->min_mem_pages * WASM_PAGE_SIZE;
if (WritePtr < DataSize) { if (WritePtr < DataSize) {
...@@ -1477,7 +1519,16 @@ void WasmTranslator::translate( ...@@ -1477,7 +1519,16 @@ void WasmTranslator::translate(
Globals.get(), DataSize - WritePtr)); Globals.get(), DataSize - WritePtr));
} }
// Save the number of pages for the runtime
auto *GlobalNumPages = VariableDeclaration::createExternal(Globals.get());
GlobalNumPages->setName(Ctx->getGlobalString("WASM_NUM_PAGES"));
GlobalNumPages->addInitializer(VariableDeclaration::DataInitializer::create(
Globals.get(), reinterpret_cast<const char *>(&Module->min_mem_pages),
sizeof(Module->min_mem_pages)));
Globals->push_back(WasmMemory); Globals->push_back(WasmMemory);
Globals->push_back(GlobalDataSize);
Globals->push_back(GlobalNumPages);
lowerGlobals(std::move(Globals)); lowerGlobals(std::move(Globals));
} }
......
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