diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/asm2wasm.h | 18 | ||||
-rw-r--r-- | src/binaryen-shell.cpp | 23 | ||||
-rw-r--r-- | src/parsing.h | 39 | ||||
-rw-r--r-- | src/passes/RemoveUnusedBrs.cpp | 2 | ||||
-rw-r--r-- | src/s2wasm-main.cpp | 11 | ||||
-rw-r--r-- | src/s2wasm.h | 36 | ||||
-rw-r--r-- | src/support/bits.cpp | 22 | ||||
-rw-r--r-- | src/support/bits.h | 6 | ||||
-rw-r--r-- | src/wasm-binary.h | 36 | ||||
-rw-r--r-- | src/wasm-interpreter.h | 136 | ||||
-rw-r--r-- | src/wasm-s-parser.h | 3 | ||||
-rw-r--r-- | src/wasm.h | 66 | ||||
-rw-r--r-- | src/wasm2asm.h | 8 |
13 files changed, 244 insertions, 162 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index 473384f2e..d56bf0cce 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -804,8 +804,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { ret->offset = 0; ret->align = ret->bytes; auto ptr = allocator.alloc<Const>(); - ptr->value.type = WasmType::i32; // XXX for wasm64, need 64 - ptr->value.i32 = global.address; + ptr->value = Literal(int32_t(global.address)); // XXX for wasm64, need 64 ret->ptr = ptr; ret->value = process(ast[3]); ret->type = global.type; @@ -874,14 +873,11 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { auto ret = allocator.alloc<Const>(); double num = ast[1]->getNumber(); if (isSInteger32(num)) { - ret->value.type = WasmType::i32; - ret->value.i32 = toSInteger32(num); + ret->value = Literal(int32_t(toSInteger32(num))); } else if (isUInteger32(num)) { - ret->value.type = WasmType::i32; - ret->value.i32 = toUInteger32(num); + ret->value = Literal(uint32_t(toUInteger32(num))); } else { - ret->value.type = WasmType::f64; - ret->value.f64 = num; + ret->value = Literal(num); } ret->type = ret->value.type; return ret; @@ -919,8 +915,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { ret->offset = 0; ret->align = ret->bytes; auto ptr = allocator.alloc<Const>(); - ptr->value.type = WasmType::i32; // XXX for wasm64, need 64 - ptr->value.i32 = global.address; + ptr->value = Literal(int32_t(global.address)); // XXX for wasm64, need 64 ret->ptr = ptr; ret->type = global.type; return ret; @@ -1446,8 +1441,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { unsigned addr = ptr[1]->getInteger(); unsigned shifted = addr << shifts; auto ret = allocator.alloc<Const>(); - ret->value.type = WasmType::i32; - ret->value.i32 = shifted; + ret->value = Literal(int32_t(shifted)); return (Expression*)ret; } abort_on("bad processUnshifted", ptr); diff --git a/src/binaryen-shell.cpp b/src/binaryen-shell.cpp index a96716a02..d8277ad80 100644 --- a/src/binaryen-shell.cpp +++ b/src/binaryen-shell.cpp @@ -74,8 +74,10 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface { public: Memory() {} void resize(size_t newSize) { - // Allocate at least this size to get proper alignment: the allocator will - // usually start allocating page-sized chunks which are properly aligned. + // Ensure the smallest allocation is large enough that most allocators + // will provide page-aligned storage. This hopefully allows the + // interpreter's memory to be as aligned as the memory being simulated, + // ensuring that the performance doesn't needlessly degrade. // // The code is optimistic this will work until WG21's p0035r0 happens. const size_t minSize = 1 << 12; @@ -224,6 +226,19 @@ struct Invocation { } }; +static void verify_result(Literal a, Literal b) { + if (a == b) return; + // accept equal nans if equal in all bits + assert(a.type == b.type); + if (a.type == f32) { + assert(a.reinterpreti32() == b.reinterpreti32()); + } else if (a.type == f64) { + assert(a.reinterpreti64() == b.reinterpreti64()); + } else { + abort(); + } +} + static void run_asserts(size_t* i, bool* checked, AllocatingModule* wasm, Element* root, std::unique_ptr<SExpressionWasmBuilder>* builder, @@ -300,11 +315,11 @@ static void run_asserts(size_t* i, bool* checked, AllocatingModule* wasm, ->dyn_cast<Const>() ->value; std::cerr << "seen " << result << ", expected " << expected << '\n'; - assert(expected == result); + verify_result(expected, result); } else { Literal expected; std::cerr << "seen " << result << ", expected " << expected << '\n'; - assert(expected == result); + verify_result(expected, result); } } if (id == ASSERT_TRAP) assert(trapped); diff --git a/src/parsing.h b/src/parsing.h index 1ea0ec254..ed2f65b59 100644 --- a/src/parsing.h +++ b/src/parsing.h @@ -29,12 +29,12 @@ namespace wasm { Expression* parseConst(cashew::IString s, WasmType type, MixedArena& allocator) { const char *str = s.str; auto ret = allocator.alloc<Const>(); - ret->type = ret->value.type = type; + ret->type = type; if (isWasmTypeFloat(type)) { if (s == INFINITY_) { switch (type) { - case f32: ret->value.f32 = std::numeric_limits<float>::infinity(); break; - case f64: ret->value.f64 = std::numeric_limits<double>::infinity(); break; + case f32: ret->value = Literal(std::numeric_limits<float>::infinity()); break; + case f64: ret->value = Literal(std::numeric_limits<double>::infinity()); break; default: return nullptr; } //std::cerr << "make constant " << str << " ==> " << ret->value << '\n'; @@ -42,8 +42,8 @@ Expression* parseConst(cashew::IString s, WasmType type, MixedArena& allocator) } if (s == NEG_INFINITY) { switch (type) { - case f32: ret->value.f32 = -std::numeric_limits<float>::infinity(); break; - case f64: ret->value.f64 = -std::numeric_limits<double>::infinity(); break; + case f32: ret->value = Literal(-std::numeric_limits<float>::infinity()); break; + case f64: ret->value = Literal(-std::numeric_limits<double>::infinity()); break; default: return nullptr; } //std::cerr << "make constant " << str << " ==> " << ret->value << '\n'; @@ -51,8 +51,8 @@ Expression* parseConst(cashew::IString s, WasmType type, MixedArena& allocator) } if (s == NAN_) { switch (type) { - case f32: ret->value.f32 = std::nan(""); break; - case f64: ret->value.f64 = std::nan(""); break; + case f32: ret->value = Literal(float(std::nan(""))); break; + case f64: ret->value = Literal(double(std::nan(""))); break; default: return nullptr; } //std::cerr << "make constant " << str << " ==> " << ret->value << '\n'; @@ -80,8 +80,7 @@ Expression* parseConst(cashew::IString s, WasmType type, MixedArena& allocator) } if (negative) pattern |= 0x80000000U; if (!isnan(bit_cast<float>(pattern))) pattern |= 1U; - ret->value.f32 = bit_cast<float>(pattern); - assert(isnan(ret->value.f32)); + ret->value = Literal(pattern).castToF32(); break; } case f64: { @@ -95,8 +94,7 @@ Expression* parseConst(cashew::IString s, WasmType type, MixedArena& allocator) } if (negative) pattern |= 0x8000000000000000ULL; if (!isnan(bit_cast<double>(pattern))) pattern |= 1ULL; - ret->value.f64 = bit_cast<double>(pattern); - assert(isnan(ret->value.f64)); + ret->value = Literal(pattern).castToF64(); break; } default: return nullptr; @@ -106,8 +104,8 @@ Expression* parseConst(cashew::IString s, WasmType type, MixedArena& allocator) } if (s == NEG_NAN) { switch (type) { - case f32: ret->value.f32 = -std::nan(""); break; - case f64: ret->value.f64 = -std::nan(""); break; + case f32: ret->value = Literal(float(-std::nan(""))); break; + case f64: ret->value = Literal(double(-std::nan(""))); break; default: return nullptr; } //std::cerr << "make constant " << str << " ==> " << ret->value << '\n'; @@ -122,12 +120,12 @@ Expression* parseConst(cashew::IString s, WasmType type, MixedArena& allocator) std::istringstream istr(str); uint32_t temp; istr >> std::hex >> temp; - ret->value.i32 = negative ? -temp : temp; + ret->value = Literal(negative ? -temp : temp); } else { std::istringstream istr(str); int32_t temp; istr >> temp; - ret->value.i32 = temp; + ret->value = Literal(temp); } break; } @@ -138,29 +136,28 @@ Expression* parseConst(cashew::IString s, WasmType type, MixedArena& allocator) std::istringstream istr(str); uint64_t temp; istr >> std::hex >> temp; - ret->value.i64 = negative ? -temp : temp; + ret->value = Literal(negative ? -temp : temp); } else { std::istringstream istr(str); int64_t temp; istr >> temp; - ret->value.i64 = temp; + ret->value = Literal(temp); } break; } case f32: { char *end; - ret->value.f32 = strtof(str, &end); - assert(!isnan(ret->value.f32)); + ret->value = Literal(strtof(str, &end)); break; } case f64: { char *end; - ret->value.f64 = strtod(str, &end); - assert(!isnan(ret->value.f64)); + ret->value = Literal(strtod(str, &end)); break; } default: return nullptr; } + assert(ret->value.type == type); //std::cerr << "make constant " << str << " ==> " << ret->value << '\n'; return ret; } diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index c7fa43df9..5302c368a 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -37,7 +37,7 @@ struct RemoveUnusedBrs : public WalkerPass<WasmWalker<RemoveUnusedBrs>> { } return; } - if (curr->type != none) return; // already has a returned value + if (isConcreteWasmType(curr->type)) return; // already has a returned value // an if_else that indirectly returns a value by breaking to the same target can potentially remove both breaks, and break outside once auto getLast = [](Expression *side) -> Expression* { Block* b = side->dyn_cast<Block>(); diff --git a/src/s2wasm-main.cpp b/src/s2wasm-main.cpp index c3f3d0a2f..e75147b75 100644 --- a/src/s2wasm-main.cpp +++ b/src/s2wasm-main.cpp @@ -46,6 +46,11 @@ int main(int argc, const char *argv[]) { [](Options *o, const std::string &argument) { o->extra["global-base"] = argument; }) + .add("--allocate-stack", "-s", "Size of the user stack in linear memory", + Options::Arguments::One, + [](Options *o, const std::string &argument) { + o->extra["stack-allocation"] = argument; + }) .add_positional("INFILE", Options::Arguments::One, [](Options *o, const std::string &argument) { o->extra["infile"] = argument; @@ -59,9 +64,13 @@ int main(int argc, const char *argv[]) { size_t globalBase = options.extra.find("global-base") != options.extra.end() ? std::stoull(options.extra["global-base"]) : 1; + size_t stackAllocation = + options.extra.find("stack-allocation") != options.extra.end() + ? std::stoull(options.extra["stack-allocation"]) + : 0; if (options.debug) std::cerr << "Global base " << globalBase << '\n'; S2WasmBuilder s2wasm(wasm, input.c_str(), options.debug, globalBase, - ignoreUnknownSymbols); + stackAllocation, ignoreUnknownSymbols); if (options.debug) std::cerr << "Emscripten gluing..." << std::endl; std::stringstream meta; diff --git a/src/s2wasm.h b/src/s2wasm.h index abfb4a92d..1e0a28b43 100644 --- a/src/s2wasm.h +++ b/src/s2wasm.h @@ -42,7 +42,8 @@ class S2WasmBuilder { public: S2WasmBuilder(AllocatingModule& wasm, const char* input, bool debug, - size_t globalBase, bool ignoreUnknownSymbols) + size_t globalBase, size_t stackAllocation, + bool ignoreUnknownSymbols) : wasm(wasm), allocator(wasm.allocator), debug(debug), @@ -52,8 +53,13 @@ class S2WasmBuilder { s = input; scan(); s = input; - prepare(); + // Place the stack pointer at the bottom of the linear memory, to keep its + // address small (and thus with a small encoding). + placeStackPointer(stackAllocation); process(); + // Place the stack after the user's static data, to keep those addresses + // small. + if (stackAllocation) placeStack(stackAllocation); fix(); } @@ -370,10 +376,30 @@ class S2WasmBuilder { } } - void prepare() { + void placeStackPointer(size_t stackAllocation) { assert(nextStatic == globalBase); // we are the first allocation + // Allocate space for the stack pointer staticAddresses["__stack_pointer"] = nextStatic; - nextStatic += 4; + const size_t pointerSize = 4; + if (stackAllocation) { + // If we are also allocating the stack, initialize the stack pointer to + // point to one past-the-end of the stack allocation. + auto* raw = new uint32_t; + relocations.emplace_back(raw, ".stack", stackAllocation); + assert(wasm.memory.segments.size() == 0); + addressSegments[nextStatic] = wasm.memory.segments.size(); + wasm.memory.segments.emplace_back( + nextStatic, reinterpret_cast<char*>(raw), pointerSize); + wasm.memory.initial = nextStatic + pointerSize; + } + nextStatic += pointerSize; + } + + void placeStack(size_t stackAllocation) { + // Allocate space for a user stack. It starts zeroed-out so needs no segment + staticAddresses[".stack"] = nextStatic; + nextStatic += stackAllocation; + wasm.memory.initial = nextStatic; } void process() { @@ -744,7 +770,7 @@ class S2WasmBuilder { // may be a relocation auto curr = allocator.alloc<Const>(); curr->type = curr->value.type = i32; - getConst((uint32_t*)&curr->value.i32); + getConst((uint32_t*)curr->value.geti32Ptr()); setOutput(curr, assign); } else { cashew::IString str = getStr(); diff --git a/src/support/bits.cpp b/src/support/bits.cpp index 7bf00e595..c1de4da8b 100644 --- a/src/support/bits.cpp +++ b/src/support/bits.cpp @@ -17,8 +17,10 @@ #define wasm_support_bits_definitions #include "support/bits.h" +namespace wasm { + template<> -int wasm::PopCount<uint8_t>(uint8_t v) { +int PopCount<uint8_t>(uint8_t v) { // Small table lookup. static const uint8_t tbl[32] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, @@ -28,12 +30,12 @@ int wasm::PopCount<uint8_t>(uint8_t v) { } template<> -int wasm::PopCount<uint16_t>(uint16_t v) { +int PopCount<uint16_t>(uint16_t v) { return PopCount((uint8_t)(v & 0xff)) + PopCount((uint8_t)(v >> 8)); } template<> -int wasm::PopCount<uint32_t>(uint32_t v) { +int PopCount<uint32_t>(uint32_t v) { // See Stanford bithacks, counting bits set in parallel, "best method": // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel v = v - ((v >> 1) & 0x55555555); @@ -42,12 +44,12 @@ int wasm::PopCount<uint32_t>(uint32_t v) { } template<> -int wasm::PopCount<uint64_t>(uint64_t v) { +int PopCount<uint64_t>(uint64_t v) { return PopCount((uint32_t)v) + PopCount((uint32_t)(v >> 32)); } template<> -uint32_t wasm::BitReverse<uint32_t>(uint32_t v) { +uint32_t BitReverse<uint32_t>(uint32_t v) { // See Hacker's Delight, first edition, figure 7-1. v = ((v & 0x55555555) << 1) | ((v >> 1) & 0x55555555); v = ((v & 0x33333333) << 2) | ((v >> 2) & 0x33333333); @@ -57,7 +59,7 @@ uint32_t wasm::BitReverse<uint32_t>(uint32_t v) { } template<> -int wasm::CountTrailingZeroes<uint32_t>(uint32_t v) { +int CountTrailingZeroes<uint32_t>(uint32_t v) { // See Stanford bithacks, count the consecutive zero bits (trailing) on the // right with multiply and lookup: // http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup @@ -69,13 +71,13 @@ int wasm::CountTrailingZeroes<uint32_t>(uint32_t v) { } template<> -int wasm::CountTrailingZeroes<uint64_t>(uint64_t v) { +int CountTrailingZeroes<uint64_t>(uint64_t v) { return (uint32_t)v ? CountTrailingZeroes((uint32_t)v) : 32 + CountTrailingZeroes((uint32_t)(v >> 32)); } template<> -int wasm::CountLeadingZeroes<uint32_t>(uint32_t v) { +int CountLeadingZeroes<uint32_t>(uint32_t v) { // See Stanford bithacks, find the log base 2 of an N-bit integer in // O(lg(N)) operations with multiply and lookup: // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn @@ -92,7 +94,9 @@ int wasm::CountLeadingZeroes<uint32_t>(uint32_t v) { } template<> -int wasm::CountLeadingZeroes<uint64_t>(uint64_t v) { +int CountLeadingZeroes<uint64_t>(uint64_t v) { return v >> 32 ? CountLeadingZeroes((uint32_t)(v >> 32)) : 32 + CountLeadingZeroes((uint32_t)v); } + +} // namespace wasm diff --git a/src/support/bits.h b/src/support/bits.h index 3049a2cf1..bbafb29d4 100644 --- a/src/support/bits.h +++ b/src/support/bits.h @@ -53,15 +53,15 @@ extern template int CountLeadingZeroes(uint64_t); // Convenience signed -> unsigned. It usually doesn't make much sense to use bit // functions on signed types. template <typename T> -inline int PopCount(T v) { +int PopCount(T v) { return PopCount(typename std::make_unsigned<T>::type(v)); } template <typename T> -inline int CountTrailingZeroes(T v) { +int CountTrailingZeroes(T v) { return CountTrailingZeroes(typename std::make_unsigned<T>::type(v)); } template <typename T> -inline int CountLeadingZeroes(T v) { +int CountLeadingZeroes(T v) { return CountLeadingZeroes(typename std::make_unsigned<T>::type(v)); } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 1cc6f0673..440689ea0 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -647,7 +647,14 @@ public: return breakStack.size() - 1 - i; } } +printf("bad!\n"); std::cerr << "bad break: " << name << std::endl; + + + +assert(0); + + abort(); } @@ -782,10 +789,10 @@ public: recurse(curr->value); } void visitConst(Const *curr) { - if (debug) std::cerr << "zz node: Const" << std::endl; + if (debug) std::cerr << "zz node: Const" << curr << " : " << curr->type << std::endl; switch (curr->type) { case i32: { - uint32_t value = curr->value.i32; + uint32_t value = curr->value.geti32(); if (value <= 255) { o << int8_t(BinaryConsts::I8Const) << uint8_t(value); break; @@ -794,19 +801,20 @@ public: break; } case i64: { - o << int8_t(BinaryConsts::I64Const) << curr->value.i64; + o << int8_t(BinaryConsts::I64Const) << curr->value.geti64(); break; } case f32: { - o << int8_t(BinaryConsts::F32Const) << curr->value.f32; + o << int8_t(BinaryConsts::F32Const) << curr->value.getf32(); break; } case f64: { - o << int8_t(BinaryConsts::F64Const) << curr->value.f64; + o << int8_t(BinaryConsts::F64Const) << curr->value.getf64(); break; } default: abort(); } + if (debug) std::cerr << "zz const node done.\n"; } void visitUnary(Unary *curr) { if (debug) std::cerr << "zz node: Unary" << std::endl; @@ -843,7 +851,7 @@ public: void visitBinary(Binary *curr) { if (debug) std::cerr << "zz node: Binary" << std::endl; #define TYPED_CODE(code) { \ - switch (curr->left->type) { \ + switch (getReachableWasmType(curr->left->type, curr->right->type)) { \ case i32: o << int8_t(BinaryConsts::I32##code); break; \ case i64: o << int8_t(BinaryConsts::I64##code); break; \ case f32: o << int8_t(BinaryConsts::F32##code); break; \ @@ -853,7 +861,7 @@ public: break; \ } #define INT_TYPED_CODE(code) { \ - switch (curr->left->type) { \ + switch (getReachableWasmType(curr->left->type, curr->right->type)) { \ case i32: o << int8_t(BinaryConsts::I32##code); break; \ case i64: o << int8_t(BinaryConsts::I64##code); break; \ default: abort(); \ @@ -861,7 +869,7 @@ public: break; \ } #define FLOAT_TYPED_CODE(code) { \ - switch (curr->left->type) { \ + switch (getReachableWasmType(curr->left->type, curr->right->type)) { \ case f32: o << int8_t(BinaryConsts::F32##code); break; \ case f64: o << int8_t(BinaryConsts::F64##code); break; \ default: abort(); \ @@ -1480,14 +1488,14 @@ public: } bool maybeVisitImpl(Const *curr, uint8_t code) { switch (code) { - case BinaryConsts::I8Const: curr->value.i32 = getInt8(); curr->type = i32; break; - case BinaryConsts::I32Const: curr->value.i32 = getInt32(); curr->type = i32; break; - case BinaryConsts::I64Const: curr->value.i64 = getInt64(); curr->type = i64; break; - case BinaryConsts::F32Const: curr->value.f32 = getFloat32(); curr->type = f32; break; - case BinaryConsts::F64Const: curr->value.f64 = getFloat64(); curr->type = f64; break; + case BinaryConsts::I8Const: curr->value = Literal(int32_t(getInt8())); break; + case BinaryConsts::I32Const: curr->value = Literal(getInt32()); break; + case BinaryConsts::I64Const: curr->value = Literal(getInt64()); break; + case BinaryConsts::F32Const: curr->value = Literal(getFloat32()); break; + case BinaryConsts::F64Const: curr->value = Literal(getFloat64()); break; default: return false; } - curr->value.type = curr->type; + curr->type = curr->value.type; if (debug) std::cerr << "zz node: Const" << std::endl; return true; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index e0006f37b..022dd04bc 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -24,6 +24,7 @@ #define wasm_wasm_interpreter_h #include <limits.h> +#include <sstream> #include "support/bits.h" #include "wasm.h" @@ -378,13 +379,7 @@ private: case Clz: return Literal((int32_t)CountLeadingZeroes(v)); case Ctz: return Literal((int32_t)CountTrailingZeroes(v)); case Popcnt: return Literal((int32_t)PopCount(v)); - case ReinterpretInt: { - float v = value.reinterpretf32(); - if (isnan(v)) { - return Literal(Literal(value.geti32() | 0x7f800000).reinterpretf32()); - } - return Literal(value.reinterpretf32()); - } + case ReinterpretInt: return value.castToF32(); case ExtendSInt32: return Literal(int64_t(value.geti32())); case ExtendUInt32: return Literal(uint64_t((uint32_t)value.geti32())); case ConvertUInt32: return curr->type == f32 ? Literal(float(uint32_t(value.geti32()))) : Literal(double(uint32_t(value.geti32()))); @@ -399,9 +394,7 @@ private: case Ctz: return Literal((int64_t)CountTrailingZeroes(v)); case Popcnt: return Literal((int64_t)PopCount(v)); case WrapInt64: return Literal(int32_t(value.geti64())); - case ReinterpretInt: { - return Literal(value.reinterpretf64()); - } + case ReinterpretInt: return value.castToF64(); case ConvertUInt64: return curr->type == f32 ? Literal(float((uint64_t)value.geti64())) : Literal(double((uint64_t)value.geti64())); case ConvertSInt64: return curr->type == f32 ? Literal(float(value.geti64())) : Literal(double(value.geti64())); default: abort(); @@ -411,8 +404,9 @@ private: float v = value.getf32(); float ret; switch (curr->op) { - case Neg: ret = -v; break; - case Abs: ret = std::abs(v); break; + // operate on bits directly, to avoid signalling bit being set on a float + case Neg: return Literal(value.reinterpreti32() ^ 0x80000000).castToF32(); break; + case Abs: return Literal(value.reinterpreti32() & 0x7fffffff).castToF32(); break; case Ceil: ret = std::ceil(v); break; case Floor: ret = std::floor(v); break; case Trunc: ret = std::trunc(v); break; @@ -430,8 +424,9 @@ private: double v = value.getf64(); double ret; switch (curr->op) { - case Neg: ret = -v; break; - case Abs: ret = std::abs(v); break; + // operate on bits directly, to avoid signalling bit being set on a float + case Neg: return Literal(uint64_t((value.reinterpreti64() ^ 0x8000000000000000ULL))).castToF64(); break; + case Abs: return Literal(uint64_t(value.reinterpreti64() & 0x7fffffffffffffffULL)).castToF64(); break; case Ceil: ret = std::ceil(v); break; case Floor: ret = std::floor(v); break; case Trunc: ret = std::trunc(v); break; @@ -459,28 +454,29 @@ private: assert(left.type == curr->left->type); assert(right.type == curr->right->type); if (left.type == i32) { - int32_t l = left.geti32(), r = right.geti32(); + uint32_t l = left.geti32(), r = right.geti32(); + int32_t l_signed = l, r_signed = r; switch (curr->op) { case Add: return Literal(l + r); case Sub: return Literal(l - r); case Mul: return Literal(l * r); case DivS: { - if (r == 0) trap("i32.div_s by 0"); - if (l == INT32_MIN && r == -1) trap("i32.div_s overflow"); // signed division overflow - return Literal(l / r); + if (r_signed == 0) trap("i32.div_s by 0"); + if (l_signed == INT32_MIN && r_signed == -1) trap("i32.div_s overflow"); // signed division overflow + return Literal(l_signed / r_signed); } case DivU: { if (r == 0) trap("i32.div_u by 0"); - return Literal(int32_t(uint32_t(l) / uint32_t(r))); + return Literal(l / r); } case RemS: { - if (r == 0) trap("i32.rem_s by 0"); - if (l == INT32_MIN && r == -1) return Literal(int32_t(0)); - return Literal(l % r); + if (r_signed == 0) trap("i32.rem_s by 0"); + if (l_signed == INT32_MIN && r_signed == -1) return Literal(int32_t(0)); + return Literal(l_signed % r_signed); } case RemU: { if (r == 0) trap("i32.rem_u by 0"); - return Literal(int32_t(uint32_t(l) % uint32_t(r))); + return Literal(l % r); } case And: return Literal(l & r); case Or: return Literal(l | r); @@ -491,47 +487,48 @@ private: } case ShrU: { r = r & 31; - return Literal(int32_t(uint32_t(l) >> uint32_t(r))); + return Literal(l >> r); } case ShrS: { - r = r & 31; - return Literal(l >> r); + r_signed = r_signed & 31; + return Literal(l_signed >> r_signed); } case Eq: return Literal(l == r); case Ne: return Literal(l != r); - case LtS: return Literal(l < r); - case LtU: return Literal(uint32_t(l) < uint32_t(r)); - case LeS: return Literal(l <= r); - case LeU: return Literal(uint32_t(l) <= uint32_t(r)); - case GtS: return Literal(l > r); - case GtU: return Literal(uint32_t(l) > uint32_t(r)); - case GeS: return Literal(l >= r); - case GeU: return Literal(uint32_t(l) >= uint32_t(r)); + case LtS: return Literal(l_signed < r_signed); + case LtU: return Literal(l < r); + case LeS: return Literal(l_signed <= r_signed); + case LeU: return Literal(l <= r); + case GtS: return Literal(l_signed > r_signed); + case GtU: return Literal(l > r); + case GeS: return Literal(l_signed >= r_signed); + case GeU: return Literal(l >= r); default: abort(); } } else if (left.type == i64) { - int64_t l = left.geti64(), r = right.geti64(); + uint64_t l = left.geti64(), r = right.geti64(); + int64_t l_signed = l, r_signed = r; switch (curr->op) { case Add: return Literal(l + r); case Sub: return Literal(l - r); case Mul: return Literal(l * r); case DivS: { - if (r == 0) trap("i64.div_s by 0"); - if (l == LLONG_MIN && r == -1) trap("i64.div_s overflow"); // signed division overflow - return Literal(l / r); + if (r_signed == 0) trap("i64.div_s by 0"); + if (l_signed == LLONG_MIN && r_signed == -1LL) trap("i64.div_s overflow"); // signed division overflow + return Literal(l_signed / r_signed); } case DivU: { if (r == 0) trap("i64.div_u by 0"); - return Literal(int64_t(uint64_t(l) / uint64_t(r))); + return Literal(l / r); } case RemS: { - if (r == 0) trap("i64.rem_s by 0"); - if (l == LLONG_MIN && r == -1) return Literal(int64_t(0)); - return Literal(l % r); + if (r_signed == 0) trap("i64.rem_s by 0"); + if (l_signed == LLONG_MIN && r_signed == -1LL) return Literal(int64_t(0)); + return Literal(l_signed % r_signed); } case RemU: { if (r == 0) trap("i64.rem_u by 0"); - return Literal(int64_t(uint64_t(l) % uint64_t(r))); + return Literal(l % r); } case And: return Literal(l & r); case Or: return Literal(l | r); @@ -542,22 +539,22 @@ private: } case ShrU: { r = r & 63; - return Literal(int64_t(uint64_t(l) >> uint64_t(r))); + return Literal(l >> r); } case ShrS: { - r = r & 63; - return Literal(l >> r); + r_signed = r_signed & 63; + return Literal(l_signed >> r_signed); } case Eq: return Literal(l == r); case Ne: return Literal(l != r); - case LtS: return Literal(l < r); - case LtU: return Literal(uint64_t(l) < uint64_t(r)); - case LeS: return Literal(l <= r); - case LeU: return Literal(uint64_t(l) <= uint64_t(r)); - case GtS: return Literal(l > r); - case GtU: return Literal(uint64_t(l) > uint64_t(r)); - case GeS: return Literal(l >= r); - case GeU: return Literal(uint64_t(l) >= uint64_t(r)); + case LtS: return Literal(l_signed < r_signed); + case LtU: return Literal(l < r); + case LeS: return Literal(l_signed <= r_signed); + case LeU: return Literal(l <= r); + case GtS: return Literal(l_signed > r_signed); + case GtU: return Literal(l > r); + case GeS: return Literal(l_signed >= r_signed); + case GeU: return Literal(l >= r); default: abort(); } } else if (left.type == f32) { @@ -568,10 +565,8 @@ private: case Sub: ret = l - r; break; case Mul: ret = l * r; break; case Div: ret = l / r; break; - case CopySign: { - ret = std::copysign(l, r); - return Literal(ret); - } + // operate on bits directly, to avoid signalling bit being set on a float + case CopySign: return Literal((left.reinterpreti32() & 0x7fffffff) | (right.reinterpreti32() & 0x80000000)).castToF32(); break; case Min: { if (l == r && l == 0) ret = 1/l < 0 ? l : r; else ret = std::min(l, r); @@ -599,10 +594,8 @@ private: case Sub: ret = l - r; break; case Mul: ret = l * r; break; case Div: ret = l / r; break; - case CopySign: { - ret = std::copysign(l, r); - return Literal(ret); - } + // operate on bits directly, to avoid signalling bit being set on a float + case CopySign: return Literal((left.reinterpreti64() & 0x7fffffffffffffffUL) | (right.reinterpreti64() & 0x8000000000000000UL)).castToF64(); break; case Min: { if (l == r && l == 0) ret = 1/l < 0 ? l : r; else ret = std::min(l, r); @@ -775,14 +768,21 @@ private: size_t memorySize; - template<class LS> - size_t getFinalAddress(LS *curr, Literal ptr) { + template <class LS> + size_t getFinalAddress(LS* curr, Literal ptr) { + auto trapIfGt = [this](size_t lhs, size_t rhs, const char* msg) { + if (lhs > rhs) { + std::stringstream ss; + ss << msg << ": " << lhs << " > " << rhs; + externalInterface->trap(ss.str().c_str()); + } + }; uint64_t addr = ptr.type == i32 ? ptr.geti32() : ptr.geti64(); - if (memorySize < curr->offset) externalInterface->trap("offset > memory"); - if (addr > memorySize - curr->offset) externalInterface->trap("final > memory"); + trapIfGt(curr->offset, memorySize, "offset > memory"); + trapIfGt(addr, memorySize - curr->offset, "final > memory"); addr += curr->offset; - if (curr->bytes > memorySize) externalInterface->trap("bytes > memory"); - if (addr > memorySize - curr->bytes) externalInterface->trap("highest > memory"); + trapIfGt(curr->bytes, memorySize, "bytes > memory"); + trapIfGt(addr, memorySize - curr->bytes, "highest > memory"); return addr; } diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index 61f3be3c0..1c704ac74 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -796,6 +796,9 @@ private: if (ret->list.size() > 0) { ret->type = ret->list.back()->type; } + // Note that we do not name these implicit/synthetic blocks. They + // are the effects of syntactic sugar, and nothing can branch to + // them anyhow. return ret; } diff --git a/src/wasm.h b/src/wasm.h index 6f889737b..c030d9d60 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -138,35 +138,58 @@ inline WasmType getReachableWasmType(WasmType a, WasmType b) { return a != unreachable ? a : b; } +inline bool isConcreteWasmType(WasmType type) { + return type != none && type != unreachable; +} + // Literals -struct Literal { +class Literal { +public: WasmType type; + +private: + // store only integers, whose bits are deterministic. floats + // can have their signalling bit set, for example. union { int32_t i32; int64_t i64; - float f32; - double f64; }; - Literal() : Literal(WasmType::none) {} - explicit Literal(WasmType type) : type(type) { memset(&f64, 0, sizeof(f64)); } +public: + Literal() : type(WasmType::none), i64(0) {} + explicit Literal(WasmType type) : type(type), i64(0) {} explicit Literal(int32_t init) : type(WasmType::i32), i32(init) {} explicit Literal(uint32_t init) : type(WasmType::i32), i32(init) {} explicit Literal(int64_t init) : type(WasmType::i64), i64(init) {} explicit Literal(uint64_t init) : type(WasmType::i64), i64(init) {} - explicit Literal(float init) : type(WasmType::f32), f32(init) {} - explicit Literal(double init) : type(WasmType::f64), f64(init) {} + explicit Literal(float init) : type(WasmType::f32), i32(bit_cast<int32_t>(init)) {} + explicit Literal(double init) : type(WasmType::f64), i64(bit_cast<int64_t>(init)) {} + + Literal castToF32() { + assert(type == WasmType::i32); + Literal ret(i32); + ret.type = f32; + return ret; + } + Literal castToF64() { + assert(type == WasmType::i64); + Literal ret(i64); + ret.type = f64; + return ret; + } int32_t geti32() { assert(type == WasmType::i32); return i32; } int64_t geti64() { assert(type == WasmType::i64); return i64; } - float getf32() { assert(type == WasmType::f32); return f32; } - double getf64() { assert(type == WasmType::f64); return f64; } + float getf32() { assert(type == WasmType::f32); return bit_cast<float>(i32); } + double getf64() { assert(type == WasmType::f64); return bit_cast<double>(i64); } + + int32_t* geti32Ptr() { assert(type == WasmType::i32); return &i32; } // careful! int32_t reinterpreti32() { assert(type == WasmType::f32); return i32; } int64_t reinterpreti64() { assert(type == WasmType::f64); return i64; } - float reinterpretf32() { assert(type == WasmType::i32); return f32; } - double reinterpretf64() { assert(type == WasmType::i64); return f64; } + float reinterpretf32() { assert(type == WasmType::i32); return bit_cast<float>(i32); } + double reinterpretf64() { assert(type == WasmType::i64); return bit_cast<double>(i64); } int64_t getInteger() { switch (type) { @@ -178,8 +201,8 @@ struct Literal { double getFloat() { switch (type) { - case WasmType::f32: return f32; - case WasmType::f64: return f64; + case WasmType::f32: return getf32(); + case WasmType::f64: return getf64(); default: abort(); } } @@ -189,10 +212,9 @@ struct Literal { switch (type) { case WasmType::none: return true; case WasmType::i32: return i32 == other.i32; + case WasmType::f32: return getf32() == other.getf32(); case WasmType::i64: return i64 == other.i64; - // reinterpret floating-point, to avoid nan != nan - case WasmType::f32: return reinterpreti32() == other.reinterpreti32(); - case WasmType::f64: return reinterpreti64() == other.reinterpreti64(); + case WasmType::f64: return getf64() == other.getf64(); default: abort(); } } @@ -244,8 +266,8 @@ struct Literal { case none: o << "?"; break; case WasmType::i32: o << literal.i32; break; case WasmType::i64: o << literal.i64; break; - case WasmType::f32: literal.printFloat(o, literal.f32); break; - case WasmType::f64: literal.printDouble(o, literal.f64); break; + case WasmType::f32: literal.printFloat(o, literal.getf32()); break; + case WasmType::f64: literal.printDouble(o, literal.getf64()); break; default: WASM_UNREACHABLE(); } restoreNormalColor(o); @@ -479,7 +501,9 @@ public: class Break : public Expression { public: - Break() : Expression(BreakId), condition(nullptr), value(nullptr) {} + Break() : Expression(BreakId), condition(nullptr), value(nullptr) { + type = unreachable; + } Expression *condition; Name name; @@ -908,7 +932,9 @@ class Return : public Expression { public: Expression *value; - Return() : Expression(ReturnId), value(nullptr) {} + Return() : Expression(ReturnId), value(nullptr) { + type = unreachable; + } std::ostream& doPrint(std::ostream &o, unsigned indent) { printOpening(o, "return"); diff --git a/src/wasm2asm.h b/src/wasm2asm.h index 76ccbf8b6..c87e81eec 100644 --- a/src/wasm2asm.h +++ b/src/wasm2asm.h @@ -915,21 +915,21 @@ Ref Wasm2AsmBuilder::processFunctionBody(Expression* curr, IString result) { } Ref visitConst(Const *curr) { switch (curr->type) { - case i32: return ValueBuilder::makeInt(curr->value.i32); + case i32: return ValueBuilder::makeInt(curr->value.geti32()); case f32: { Ref ret = ValueBuilder::makeCall(MATH_FROUND); Const fake; - fake.value = Literal(double(curr->value.f32)); + fake.value = Literal(double(curr->value.getf32())); fake.type = f64; ret[2]->push_back(visitConst(&fake)); return ret; } case f64: { - double d = curr->value.f64; + double d = curr->value.getf64(); if (d == 0 && std::signbit(d)) { // negative zero return ValueBuilder::makeUnary(PLUS, ValueBuilder::makeUnary(MINUS, ValueBuilder::makeDouble(0))); } - return ValueBuilder::makeUnary(PLUS, ValueBuilder::makeDouble(curr->value.f64)); + return ValueBuilder::makeUnary(PLUS, ValueBuilder::makeDouble(curr->value.getf64())); } default: abort(); } |