blob: 9e80c21583a0cae12110bf4981f0c7378c60a0c6 [file] [log] [blame] [edit]
/*
* Copyright 2015 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// wasm.h: WebAssembly representation and processing library, in one
// header file.
//
// This represents WebAssembly in an AST format, with a focus on making
// it easy to not just inspect but also to process. For example, some
// things that this enables are:
//
// * Interpreting: See wasm-interpreter.h.
// * Optimizing: See asm2wasm.h, which performs some optimizations
// after code generation.
// * Validation: See wasm-validator.h.
// * Pretty-printing: See Print.cpp.
//
//
// wasm.js internal WebAssembly representation design:
//
// * Unify where possible. Where size isn't a concern, combine
// classes, so binary ops and relational ops are joined. This
// simplifies that AST and makes traversals easier.
// * Optimize for size? This might justify separating if and if_else
// (so that if doesn't have an always-empty else; also it avoids
// a branch).
//
#ifndef wasm_wasm_h
#define wasm_wasm_h
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <map>
#include <string>
#include <vector>
#include "compiler-support.h"
#include "emscripten-optimizer/simple_ast.h"
#include "mixed_arena.h"
#include "pretty_printing.h"
#include "support/bits.h"
#include "support/utilities.h"
namespace wasm {
// We use a Name for all of the identifiers. These are IStrings, so they are
// all interned - comparisons etc are just pointer comparisons, so there is no
// perf loss. Having names everywhere makes using the AST much nicer (for
// example, block names are strings and not offsets, which makes composition
// - adding blocks, removing blocks - easy). One exception is local variables,
// where we do use indices, as they are a large proportion of the AST,
// perf matters a lot there, and compositionality is not a problem.
// TODO: as an optimization, IString values < some threshold could be considered
// numerical indices directly.
struct Name : public cashew::IString {
Name() : cashew::IString() {}
Name(const char* str) : cashew::IString(str, false) {}
Name(cashew::IString str) : cashew::IString(str) {}
Name(const std::string& str) : cashew::IString(str.c_str(), false) {}
friend std::ostream& operator<<(std::ostream& o, Name name) {
assert(name.str);
return o << '$' << name.str; // reference interpreter requires we prefix all names
}
static Name fromInt(size_t i) {
return cashew::IString(std::to_string(i).c_str(), false);
}
};
// An index in a wasm module
typedef uint32_t Index;
// An address in linear memory. For now only wasm32
struct Address {
typedef uint32_t address_t;
address_t addr;
Address() : addr(0) {}
Address(uint64_t a) : addr(static_cast<address_t>(a)) {
assert(a <= std::numeric_limits<address_t>::max());
}
Address& operator=(uint64_t a) {
assert(a <= std::numeric_limits<address_t>::max());
addr = static_cast<address_t>(a);
return *this;
}
operator address_t() const { return addr; }
Address& operator++() { ++addr; return *this; }
};
// An offset into memory
typedef int32_t Offset;
// Types
enum WasmType {
none,
i32,
i64,
f32,
f64,
unreachable // none means no type, e.g. a block can have no return type. but
// unreachable is different, as it can be "ignored" when doing
// type checking across branches
};
inline const char* printWasmType(WasmType type) {
switch (type) {
case WasmType::none: return "none";
case WasmType::i32: return "i32";
case WasmType::i64: return "i64";
case WasmType::f32: return "f32";
case WasmType::f64: return "f64";
case WasmType::unreachable: return "unreachable";
default: WASM_UNREACHABLE();
}
}
inline unsigned getWasmTypeSize(WasmType type) {
switch (type) {
case WasmType::none: abort();
case WasmType::i32: return 4;
case WasmType::i64: return 8;
case WasmType::f32: return 4;
case WasmType::f64: return 8;
default: WASM_UNREACHABLE();
}
}
inline bool isWasmTypeFloat(WasmType type) {
switch (type) {
case f32:
case f64: return true;
default: return false;
}
}
inline WasmType getWasmType(unsigned size, bool float_) {
if (size < 4) return WasmType::i32;
if (size == 4) return float_ ? WasmType::f32 : WasmType::i32;
if (size == 8) return float_ ? WasmType::f64 : WasmType::i64;
abort();
}
inline WasmType getReachableWasmType(WasmType a, WasmType b) {
return a != unreachable ? a : b;
}
inline bool isConcreteWasmType(WasmType type) {
return type != none && type != unreachable;
}
// Literals
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;
};
// The RHS of shl/shru/shrs must be masked by bitwidth.
template <typename T>
static T shiftMask(T val) {
return val & (sizeof(T) * 8 - 1);
}
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), 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 = WasmType::f32;
return ret;
}
Literal castToF64() {
assert(type == WasmType::i64);
Literal ret(i64);
ret.type = WasmType::f64;
return ret;
}
Literal castToI32() {
assert(type == WasmType::f32);
Literal ret(i32);
ret.type = WasmType::i32;
return ret;
}
Literal castToI64() {
assert(type == WasmType::f64);
Literal ret(i64);
ret.type = WasmType::i64;
return ret;
}
int32_t geti32() const { assert(type == WasmType::i32); return i32; }
int64_t geti64() const { assert(type == WasmType::i64); return i64; }
float getf32() const { assert(type == WasmType::f32); return bit_cast<float>(i32); }
double getf64() const { assert(type == WasmType::f64); return bit_cast<double>(i64); }
int32_t* geti32Ptr() { assert(type == WasmType::i32); return &i32; } // careful!
int32_t reinterpreti32() const { assert(type == WasmType::f32); return i32; }
int64_t reinterpreti64() const { assert(type == WasmType::f64); return i64; }
float reinterpretf32() const { assert(type == WasmType::i32); return bit_cast<float>(i32); }
double reinterpretf64() const { assert(type == WasmType::i64); return bit_cast<double>(i64); }
int64_t getInteger() {
switch (type) {
case WasmType::i32: return i32;
case WasmType::i64: return i64;
default: abort();
}
}
double getFloat() {
switch (type) {
case WasmType::f32: return getf32();
case WasmType::f64: return getf64();
default: abort();
}
}
int64_t getBits() {
switch (type) {
case WasmType::i32: case WasmType::f32: return i32;
case WasmType::i64: case WasmType::f64: return i64;
default: abort();
}
}
bool operator==(const Literal& other) const {
if (type != other.type) return false;
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;
case WasmType::f64: return getf64() == other.getf64();
default: abort();
}
}
bool operator!=(const Literal& other) const {
return !(*this == other);
}
static uint32_t NaNPayload(float f) {
assert(std::isnan(f) && "expected a NaN");
// SEEEEEEE EFFFFFFF FFFFFFFF FFFFFFFF
// NaN has all-one exponent and non-zero fraction.
return ~0xff800000u & bit_cast<uint32_t>(f);
}
static uint64_t NaNPayload(double f) {
assert(std::isnan(f) && "expected a NaN");
// SEEEEEEE EEEEFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
// NaN has all-one exponent and non-zero fraction.
return ~0xfff0000000000000ull & bit_cast<uint64_t>(f);
}
static float setQuietNaN(float f) {
assert(std::isnan(f) && "expected a NaN");
// An SNaN is a NaN with the most significant fraction bit clear.
return bit_cast<float>(0x00400000u | bit_cast<uint32_t>(f));
}
static double setQuietNaN(double f) {
assert(std::isnan(f) && "expected a NaN");
// An SNaN is a NaN with the most significant fraction bit clear.
return bit_cast<double>(0x0008000000000000ull | bit_cast<uint64_t>(f));
}
static void printFloat(std::ostream &o, float f) {
if (std::isnan(f)) {
const char* sign = std::signbit(f) ? "-" : "";
o << sign << "nan";
if (uint32_t payload = NaNPayload(f)) {
o << ":0x" << std::hex << payload << std::dec;
}
return;
}
printDouble(o, f);
}
static void printDouble(std::ostream& o, double d) {
if (d == 0 && std::signbit(d)) {
o << "-0";
return;
}
if (std::isnan(d)) {
const char* sign = std::signbit(d) ? "-" : "";
o << sign << "nan";
if (uint64_t payload = NaNPayload(d)) {
o << ":0x" << std::hex << payload << std::dec;
}
return;
}
if (!std::isfinite(d)) {
o << (std::signbit(d) ? "-infinity" : "infinity");
return;
}
const char* text = cashew::JSPrinter::numToString(d);
// spec interpreter hates floats starting with '.'
if (text[0] == '.') {
o << '0';
} else if (text[0] == '-' && text[1] == '.') {
o << "-0";
text++;
}
o << text;
}
friend std::ostream& operator<<(std::ostream& o, Literal literal) {
o << '(';
prepareMinorColor(o) << printWasmType(literal.type) << ".const ";
switch (literal.type) {
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.getf32()); break;
case WasmType::f64: literal.printDouble(o, literal.getf64()); break;
default: WASM_UNREACHABLE();
}
restoreNormalColor(o);
return o << ')';
}
Literal countLeadingZeroes() const {
if (type == WasmType::i32) return Literal((int32_t)CountLeadingZeroes(i32));
if (type == WasmType::i64) return Literal((int64_t)CountLeadingZeroes(i64));
WASM_UNREACHABLE();
}
Literal countTrailingZeroes() const {
if (type == WasmType::i32) return Literal((int32_t)CountTrailingZeroes(i32));
if (type == WasmType::i64) return Literal((int64_t)CountTrailingZeroes(i64));
WASM_UNREACHABLE();
}
Literal popCount() const {
if (type == WasmType::i32) return Literal((int32_t)PopCount(i32));
if (type == WasmType::i64) return Literal((int64_t)PopCount(i64));
WASM_UNREACHABLE();
}
Literal extendToSI64() const {
assert(type == WasmType::i32);
return Literal((int64_t)i32);
}
Literal extendToUI64() const {
assert(type == WasmType::i32);
return Literal((uint64_t)(uint32_t)i32);
}
Literal extendToF64() const {
assert(type == WasmType::f32);
return Literal(double(getf32()));
}
Literal truncateToI32() const {
assert(type == WasmType::i64);
return Literal((int32_t)i64);
}
Literal truncateToF32() const {
assert(type == WasmType::f64);
return Literal(float(getf64()));
}
Literal convertSToF32() const {
if (type == WasmType::i32) return Literal(float(i32));
if (type == WasmType::i64) return Literal(float(i64));
WASM_UNREACHABLE();
}
Literal convertUToF32() const {
if (type == WasmType::i32) return Literal(float(uint32_t(i32)));
if (type == WasmType::i64) return Literal(float(uint64_t(i64)));
WASM_UNREACHABLE();
}
Literal convertSToF64() const {
if (type == WasmType::i32) return Literal(double(i32));
if (type == WasmType::i64) return Literal(double(i64));
WASM_UNREACHABLE();
}
Literal convertUToF64() const {
if (type == WasmType::i32) return Literal(double(uint32_t(i32)));
if (type == WasmType::i64) return Literal(double(uint64_t(i64)));
WASM_UNREACHABLE();
}
Literal neg() const {
switch (type) {
case WasmType::i32: return Literal(i32 ^ 0x80000000);
case WasmType::i64: return Literal(int64_t(i64 ^ 0x8000000000000000ULL));
case WasmType::f32: return Literal(i32 ^ 0x80000000).castToF32();
case WasmType::f64: return Literal(int64_t(i64 ^ 0x8000000000000000ULL)).castToF64();
default: WASM_UNREACHABLE();
}
}
Literal abs() const {
switch (type) {
case WasmType::i32: return Literal(i32 & 0x7fffffff);
case WasmType::i64: return Literal(int64_t(i64 & 0x7fffffffffffffffULL));
case WasmType::f32: return Literal(i32 & 0x7fffffff).castToF32();
case WasmType::f64: return Literal(int64_t(i64 & 0x7fffffffffffffffULL)).castToF64();
default: WASM_UNREACHABLE();
}
}
Literal ceil() const {
switch (type) {
case WasmType::f32: return Literal(std::ceil(getf32()));
case WasmType::f64: return Literal(std::ceil(getf64()));
default: WASM_UNREACHABLE();
}
}
Literal floor() const {
switch (type) {
case WasmType::f32: return Literal(std::floor(getf32()));
case WasmType::f64: return Literal(std::floor(getf64()));
default: WASM_UNREACHABLE();
}
}
Literal trunc() const {
switch (type) {
case WasmType::f32: return Literal(std::trunc(getf32()));
case WasmType::f64: return Literal(std::trunc(getf64()));
default: WASM_UNREACHABLE();
}
}
Literal nearbyint() const {
switch (type) {
case WasmType::f32: return Literal(std::nearbyint(getf32()));
case WasmType::f64: return Literal(std::nearbyint(getf64()));
default: WASM_UNREACHABLE();
}
}
Literal sqrt() const {
switch (type) {
case WasmType::f32: return Literal(std::sqrt(getf32()));
case WasmType::f64: return Literal(std::sqrt(getf64()));
default: WASM_UNREACHABLE();
}
}
Literal add(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(uint32_t(i32) + uint32_t(other.i32));
case WasmType::i64: return Literal(uint64_t(i64) + uint64_t(other.i64));
case WasmType::f32: return Literal(getf32() + other.getf32());
case WasmType::f64: return Literal(getf64() + other.getf64());
default: WASM_UNREACHABLE();
}
}
Literal sub(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(uint32_t(i32) - uint32_t(other.i32));
case WasmType::i64: return Literal(uint64_t(i64) - uint64_t(other.i64));
case WasmType::f32: return Literal(getf32() - other.getf32());
case WasmType::f64: return Literal(getf64() - other.getf64());
default: WASM_UNREACHABLE();
}
}
Literal mul(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(uint32_t(i32) * uint32_t(other.i32));
case WasmType::i64: return Literal(uint64_t(i64) * uint64_t(other.i64));
case WasmType::f32: return Literal(getf32() * other.getf32());
case WasmType::f64: return Literal(getf64() * other.getf64());
default: WASM_UNREACHABLE();
}
}
Literal div(const Literal& other) const {
switch (type) {
case WasmType::f32: {
float lhs = getf32(), rhs = other.getf32();
float sign = std::signbit(lhs) == std::signbit(rhs) ? 0.f : -0.f;
switch (std::fpclassify(rhs)) {
case FP_ZERO:
switch (std::fpclassify(lhs)) {
case FP_NAN: return Literal(setQuietNaN(lhs));
case FP_ZERO: return Literal(std::copysign(std::numeric_limits<float>::quiet_NaN(), sign));
case FP_NORMAL: // fallthrough
case FP_SUBNORMAL: // fallthrough
case FP_INFINITE: return Literal(std::copysign(std::numeric_limits<float>::infinity(), sign));
default: WASM_UNREACHABLE();
}
case FP_NAN: // fallthrough
case FP_INFINITE: // fallthrough
case FP_NORMAL: // fallthrough
case FP_SUBNORMAL: return Literal(lhs / rhs);
default: WASM_UNREACHABLE();
}
}
case WasmType::f64: {
double lhs = getf64(), rhs = other.getf64();
double sign = std::signbit(lhs) == std::signbit(rhs) ? 0. : -0.;
switch (std::fpclassify(rhs)) {
case FP_ZERO:
switch (std::fpclassify(lhs)) {
case FP_NAN: return Literal(setQuietNaN(lhs));
case FP_ZERO: return Literal(std::copysign(std::numeric_limits<double>::quiet_NaN(), sign));
case FP_NORMAL: // fallthrough
case FP_SUBNORMAL: // fallthrough
case FP_INFINITE: return Literal(std::copysign(std::numeric_limits<double>::infinity(), sign));
default: WASM_UNREACHABLE();
}
case FP_NAN: // fallthrough
case FP_INFINITE: // fallthrough
case FP_NORMAL: // fallthrough
case FP_SUBNORMAL: return Literal(lhs / rhs);
default: WASM_UNREACHABLE();
}
}
default: WASM_UNREACHABLE();
}
}
Literal divS(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(i32 / other.i32);
case WasmType::i64: return Literal(i64 / other.i64);
default: WASM_UNREACHABLE();
}
}
Literal divU(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(uint32_t(i32) / uint32_t(other.i32));
case WasmType::i64: return Literal(uint64_t(i64) / uint64_t(other.i64));
default: WASM_UNREACHABLE();
}
}
Literal remS(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(i32 % other.i32);
case WasmType::i64: return Literal(i64 % other.i64);
default: WASM_UNREACHABLE();
}
}
Literal remU(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(uint32_t(i32) % uint32_t(other.i32));
case WasmType::i64: return Literal(uint64_t(i64) % uint64_t(other.i64));
default: WASM_UNREACHABLE();
}
}
Literal and_(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(i32 & other.i32);
case WasmType::i64: return Literal(i64 & other.i64);
default: WASM_UNREACHABLE();
}
}
Literal or_(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(i32 | other.i32);
case WasmType::i64: return Literal(i64 | other.i64);
default: WASM_UNREACHABLE();
}
}
Literal xor_(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(i32 ^ other.i32);
case WasmType::i64: return Literal(i64 ^ other.i64);
default: WASM_UNREACHABLE();
}
}
Literal shl(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(uint32_t(i32) << shiftMask(other.i32));
case WasmType::i64: return Literal(uint64_t(i64) << shiftMask(other.i64));
default: WASM_UNREACHABLE();
}
}
Literal shrS(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(i32 >> shiftMask(other.i32));
case WasmType::i64: return Literal(i64 >> shiftMask(other.i64));
default: WASM_UNREACHABLE();
}
}
Literal shrU(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(uint32_t(i32) >> shiftMask(other.i32));
case WasmType::i64: return Literal(uint64_t(i64) >> shiftMask(other.i64));
default: WASM_UNREACHABLE();
}
}
Literal rotL(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(RotateLeft(uint32_t(i32), uint32_t(other.i32)));
case WasmType::i64: return Literal(RotateLeft(uint64_t(i64), uint64_t(other.i64)));
default: WASM_UNREACHABLE();
}
}
Literal rotR(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(RotateRight(uint32_t(i32), uint32_t(other.i32)));
case WasmType::i64: return Literal(RotateRight(uint64_t(i64), uint64_t(other.i64)));
default: WASM_UNREACHABLE();
}
}
Literal eq(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(i32 == other.i32);
case WasmType::i64: return Literal(i64 == other.i64);
case WasmType::f32: return Literal(getf32() == other.getf32());
case WasmType::f64: return Literal(getf64() == other.getf64());
default: WASM_UNREACHABLE();
}
}
Literal ne(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(i32 != other.i32);
case WasmType::i64: return Literal(i64 != other.i64);
case WasmType::f32: return Literal(getf32() != other.getf32());
case WasmType::f64: return Literal(getf64() != other.getf64());
default: WASM_UNREACHABLE();
}
}
Literal ltS(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(i32 < other.i32);
case WasmType::i64: return Literal(i64 < other.i64);
default: WASM_UNREACHABLE();
}
}
Literal ltU(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(uint32_t(i32) < uint32_t(other.i32));
case WasmType::i64: return Literal(uint64_t(i64) < uint64_t(other.i64));
default: WASM_UNREACHABLE();
}
}
Literal lt(const Literal& other) const {
switch (type) {
case WasmType::f32: return Literal(getf32() < other.getf32());
case WasmType::f64: return Literal(getf64() < other.getf64());
default: WASM_UNREACHABLE();
}
}
Literal leS(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(i32 <= other.i32);
case WasmType::i64: return Literal(i64 <= other.i64);
default: WASM_UNREACHABLE();
}
}
Literal leU(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(uint32_t(i32) <= uint32_t(other.i32));
case WasmType::i64: return Literal(uint64_t(i64) <= uint64_t(other.i64));
default: WASM_UNREACHABLE();
}
}
Literal le(const Literal& other) const {
switch (type) {
case WasmType::f32: return Literal(getf32() <= other.getf32());
case WasmType::f64: return Literal(getf64() <= other.getf64());
default: WASM_UNREACHABLE();
}
}
Literal gtS(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(i32 > other.i32);
case WasmType::i64: return Literal(i64 > other.i64);
default: WASM_UNREACHABLE();
}
}
Literal gtU(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(uint32_t(i32) > uint32_t(other.i32));
case WasmType::i64: return Literal(uint64_t(i64) > uint64_t(other.i64));
default: WASM_UNREACHABLE();
}
}
Literal gt(const Literal& other) const {
switch (type) {
case WasmType::f32: return Literal(getf32() > other.getf32());
case WasmType::f64: return Literal(getf64() > other.getf64());
default: WASM_UNREACHABLE();
}
}
Literal geS(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(i32 >= other.i32);
case WasmType::i64: return Literal(i64 >= other.i64);
default: WASM_UNREACHABLE();
}
}
Literal geU(const Literal& other) const {
switch (type) {
case WasmType::i32: return Literal(uint32_t(i32) >= uint32_t(other.i32));
case WasmType::i64: return Literal(uint64_t(i64) >= uint64_t(other.i64));
default: WASM_UNREACHABLE();
}
}
Literal ge(const Literal& other) const {
switch (type) {
case WasmType::f32: return Literal(getf32() >= other.getf32());
case WasmType::f64: return Literal(getf64() >= other.getf64());
default: WASM_UNREACHABLE();
}
}
Literal min(const Literal& other) const {
switch (type) {
case WasmType::f32: {
auto l = getf32(), r = other.getf32();
if (l == r && l == 0) return Literal(std::signbit(l) ? l : r);
auto result = std::min(l, r);
bool lnan = std::isnan(l), rnan = std::isnan(r);
if (!std::isnan(result) && !lnan && !rnan) return Literal(result);
if (!lnan && !rnan) return Literal((int32_t)0x7fc00000).castToF32();
return Literal(lnan ? l : r).castToI32().or_(Literal(0xc00000)).castToF32();
}
case WasmType::f64: {
auto l = getf64(), r = other.getf64();
if (l == r && l == 0) return Literal(std::signbit(l) ? l : r);
auto result = std::min(l, r);
bool lnan = std::isnan(l), rnan = std::isnan(r);
if (!std::isnan(result) && !lnan && !rnan) return Literal(result);
if (!lnan && !rnan) return Literal((int64_t)0x7ff8000000000000LL).castToF64();
return Literal(lnan ? l : r).castToI64().or_(Literal(int64_t(0x8000000000000LL))).castToF64();
}
default: WASM_UNREACHABLE();
}
}
Literal max(const Literal& other) const {
switch (type) {
case WasmType::f32: {
auto l = getf32(), r = other.getf32();
if (l == r && l == 0) return Literal(std::signbit(l) ? r : l);
auto result = std::max(l, r);
bool lnan = std::isnan(l), rnan = std::isnan(r);
if (!std::isnan(result) && !lnan && !rnan) return Literal(result);
if (!lnan && !rnan) return Literal((int32_t)0x7fc00000).castToF32();
return Literal(lnan ? l : r).castToI32().or_(Literal(0xc00000)).castToF32();
}
case WasmType::f64: {
auto l = getf64(), r = other.getf64();
if (l == r && l == 0) return Literal(std::signbit(l) ? r : l);
auto result = std::max(l, r);
bool lnan = std::isnan(l), rnan = std::isnan(r);
if (!std::isnan(result) && !lnan && !rnan) return Literal(result);
if (!lnan && !rnan) return Literal((int64_t)0x7ff8000000000000LL).castToF64();
return Literal(lnan ? l : r).castToI64().or_(Literal(int64_t(0x8000000000000LL))).castToF64();
}
default: WASM_UNREACHABLE();
}
}
Literal copysign(const Literal& other) const {
// operate on bits directly, to avoid signalling bit being set on a float
switch (type) {
case WasmType::f32: return Literal((i32 & 0x7fffffff) | (other.i32 & 0x80000000)).castToF32(); break;
case WasmType::f64: return Literal((i64 & 0x7fffffffffffffffUL) | (other.i64 & 0x8000000000000000UL)).castToF64(); break;
default: WASM_UNREACHABLE();
}
}
};
// Operators
enum UnaryOp {
ClzInt32, ClzInt64, CtzInt32, CtzInt64, PopcntInt32, PopcntInt64, // int
NegFloat32, NegFloat64, AbsFloat32, AbsFloat64, CeilFloat32, CeilFloat64, FloorFloat32, FloorFloat64, TruncFloat32, TruncFloat64, NearestFloat32, NearestFloat64, SqrtFloat32, SqrtFloat64, // float
// relational
EqZInt32, EqZInt64,
// conversions
ExtendSInt32, ExtendUInt32, // extend i32 to i64
WrapInt64, // i64 to i32
TruncSFloat32ToInt32, TruncSFloat32ToInt64, TruncUFloat32ToInt32, TruncUFloat32ToInt64, TruncSFloat64ToInt32, TruncSFloat64ToInt64, TruncUFloat64ToInt32, TruncUFloat64ToInt64, // float to int
ReinterpretFloat32, ReinterpretFloat64, // reintepret bits to int
ConvertSInt32ToFloat32, ConvertSInt32ToFloat64, ConvertUInt32ToFloat32, ConvertUInt32ToFloat64, ConvertSInt64ToFloat32, ConvertSInt64ToFloat64, ConvertUInt64ToFloat32, ConvertUInt64ToFloat64, // int to float
PromoteFloat32, // f32 to f64
DemoteFloat64, // f64 to f32
ReinterpretInt32, ReinterpretInt64, // reinterpret bits to float
};
enum BinaryOp {
AddInt32, SubInt32, MulInt32, // int or float
DivSInt32, DivUInt32, RemSInt32, RemUInt32, AndInt32, OrInt32, XorInt32, ShlInt32, ShrUInt32, ShrSInt32, RotLInt32, RotRInt32, // int
// relational ops
EqInt32, NeInt32, // int or float
LtSInt32, LtUInt32, LeSInt32, LeUInt32, GtSInt32, GtUInt32, GeSInt32, GeUInt32, // int
AddInt64, SubInt64, MulInt64, // int or float
DivSInt64, DivUInt64, RemSInt64, RemUInt64, AndInt64, OrInt64, XorInt64, ShlInt64, ShrUInt64, ShrSInt64, RotLInt64, RotRInt64, // int
// relational ops
EqInt64, NeInt64, // int or float
LtSInt64, LtUInt64, LeSInt64, LeUInt64, GtSInt64, GtUInt64, GeSInt64, GeUInt64, // int
AddFloat32, SubFloat32, MulFloat32, // int or float
DivFloat32, CopySignFloat32, MinFloat32, MaxFloat32, // float
// relational ops
EqFloat32, NeFloat32, // int or float
LtFloat32, LeFloat32, GtFloat32, GeFloat32, // float
AddFloat64, SubFloat64, MulFloat64, // int or float
DivFloat64, CopySignFloat64, MinFloat64, MaxFloat64, // float
// relational ops
EqFloat64, NeFloat64, // int or float
LtFloat64, LeFloat64, GtFloat64, GeFloat64, // float
};
enum HostOp {
PageSize, CurrentMemory, GrowMemory, HasFeature
};
//
// Expressions
//
// Note that little is provided in terms of constructors for these. The rationale
// is that writing new Something(a, b, c, d, e) is not the clearest, and it would
// be better to write new Something(name=a, leftOperand=b... etc., but C++
// lacks named operands, so in asm2wasm etc. you will see things like
// auto x = new Something();
// x->name = a;
// x->leftOperand = b;
// ..
// which is less compact but less ambiguous. See wasm-builder.h for a more
// friendly API for building nodes.
//
// Most nodes have no need of internal allocation, and when arena-allocated
// they drop the provided arena on the floor. You can create random instances
// of those that are not in an arena without issue. However, the nodes that
// have internal allocation will need an allocator provided to them in order
// to be constructed.
class Expression {
public:
enum Id {
InvalidId = 0,
BlockId,
IfId,
LoopId,
BreakId,
SwitchId,
CallId,
CallImportId,
CallIndirectId,
GetLocalId,
SetLocalId,
GetGlobalId,
SetGlobalId,
LoadId,
StoreId,
ConstId,
UnaryId,
BinaryId,
SelectId,
DropId,
ReturnId,
HostId,
NopId,
UnreachableId,
NumExpressionIds
};
Id _id;
WasmType type; // the type of the expression: its *output*, not necessarily its input(s)
Expression(Id id) : _id(id), type(none) {}
void finalize() {}
template<class T>
bool is() {
return int(_id) == int(T::SpecificId);
}
template<class T>
T* dynCast() {
return int(_id) == int(T::SpecificId) ? (T*)this : nullptr;
}
template<class T>
T* cast() {
assert(int(_id) == int(T::SpecificId));
return (T*)this;
}
};
inline const char* getExpressionName(Expression* curr) {
switch (curr->_id) {
case Expression::Id::InvalidId: abort();
case Expression::Id::BlockId: return "block";
case Expression::Id::IfId: return "if";
case Expression::Id::LoopId: return "loop";
case Expression::Id::BreakId: return "break";
case Expression::Id::SwitchId: return "switch";
case Expression::Id::CallId: return "call";
case Expression::Id::CallImportId: return "call_import";
case Expression::Id::CallIndirectId: return "call_indirect";
case Expression::Id::GetLocalId: return "get_local";
case Expression::Id::SetLocalId: return "set_local";
case Expression::Id::GetGlobalId: return "get_global";
case Expression::Id::SetGlobalId: return "set_global";
case Expression::Id::LoadId: return "load";
case Expression::Id::StoreId: return "store";
case Expression::Id::ConstId: return "const";
case Expression::Id::UnaryId: return "unary";
case Expression::Id::BinaryId: return "binary";
case Expression::Id::SelectId: return "select";
case Expression::Id::DropId: return "drop";
case Expression::Id::ReturnId: return "return";
case Expression::Id::HostId: return "host";
case Expression::Id::NopId: return "nop";
case Expression::Id::UnreachableId: return "unreachable";
default: WASM_UNREACHABLE();
}
}
typedef ArenaVector<Expression*> ExpressionList;
template<Expression::Id SID>
class SpecificExpression : public Expression {
public:
enum {
SpecificId = SID // compile-time access to the type for the class
};
SpecificExpression() : Expression(SID) {}
};
class Nop : public SpecificExpression<Expression::NopId> {
public:
Nop() {}
};
class Block : public SpecificExpression<Expression::BlockId> {
public:
Name name;
ExpressionList list;
// set the type given you know its type, which is the case when parsing
// s-expression or binary, as explicit types are given. the only additional work
// this does is to set the type to unreachable in the cases that is needed.
void finalize(WasmType type_);
// set the type purely based on its contents. this scans the block, so it is not fast
void finalize();
};
class If : public SpecificExpression<Expression::IfId> {
public:
If() : ifFalse(nullptr) {}
Expression* condition;
Expression* ifTrue;
Expression* ifFalse;
// set the type given you know its type, which is the case when parsing
// s-expression or binary, as explicit types are given. the only additional work
// this does is to set the type to unreachable in the cases that is needed.
void finalize(WasmType type_);
// set the type purely based on its contents.
void finalize();
};
class Loop : public SpecificExpression<Expression::LoopId> {
public:
Loop() {}
Name name;
Expression* body;
// set the type given you know its type, which is the case when parsing
// s-expression or binary, as explicit types are given. the only additional work
// this does is to set the type to unreachable in the cases that is needed.
void finalize(WasmType type_);
// set the type purely based on its contents.
void finalize();
};
class Break : public SpecificExpression<Expression::BreakId> {
public:
Break() : value(nullptr), condition(nullptr) {
type = unreachable;
}
Name name;
Expression* value;
Expression* condition;
void finalize() {
if (condition) {
if (value) {
type = value->type;
} else {
type = none;
}
} else {
type = unreachable;
}
}
};
class Switch : public SpecificExpression<Expression::SwitchId> {
public:
Switch() : condition(nullptr), value(nullptr) {
type = unreachable;
}
ArenaVector<Name> targets;
Name default_;
Expression* condition;
Expression* value;
};
class Call : public SpecificExpression<Expression::CallId> {
public:
ExpressionList operands;
Name target;
};
class CallImport : public SpecificExpression<Expression::CallImportId> {
public:
ExpressionList operands;
Name target;
};
class FunctionType {
public:
Name name;
WasmType result;
std::vector<WasmType> params;
FunctionType() : result(none) {}
bool structuralComparison(FunctionType& b) {
if (result != b.result) return false;
if (params.size() != b.params.size()) return false;
for (size_t i = 0; i < params.size(); i++) {
if (params[i] != b.params[i]) return false;
}
return true;
}
bool operator==(FunctionType& b) {
if (name != b.name) return false;
return structuralComparison(b);
}
bool operator!=(FunctionType& b) {
return !(*this == b);
}
};
class CallIndirect : public SpecificExpression<Expression::CallIndirectId> {
public:
ExpressionList operands;
Name fullType;
Expression* target;
};
class GetLocal : public SpecificExpression<Expression::GetLocalId> {
public:
Index index;
};
class SetLocal : public SpecificExpression<Expression::SetLocalId> {
public:
Index index;
Expression* value;
bool isTee() {
return type != none;
}
void setTee(bool is) {
if (is) type = value->type;
else type = none;
}
};
class GetGlobal : public SpecificExpression<Expression::GetGlobalId> {
public:
Name name;
};
class SetGlobal : public SpecificExpression<Expression::SetGlobalId> {
public:
Name name;
Expression* value;
};
class Load : public SpecificExpression<Expression::LoadId> {
public:
uint8_t bytes;
bool signed_;
Address offset;
Address align;
Expression* ptr;
// type must be set during creation, cannot be inferred
};
class Store : public SpecificExpression<Expression::StoreId> {
public:
uint8_t bytes;
Address offset;
Address align;
Expression* ptr;
Expression* value;
WasmType valueType; // the store never returns a value
void finalize() {
assert(valueType != none); // must be set
}
};
class Const : public SpecificExpression<Expression::ConstId> {
public:
Literal value;
Const* set(Literal value_) {
value = value_;
type = value.type;
return this;
}
};
class Unary : public SpecificExpression<Expression::UnaryId> {
public:
UnaryOp op;
Expression* value;
bool isRelational() { return op == EqZInt32 || op == EqZInt64; }
void finalize() {
switch (op) {
case ClzInt32:
case CtzInt32:
case PopcntInt32:
case NegFloat32:
case AbsFloat32:
case CeilFloat32:
case FloorFloat32:
case TruncFloat32:
case NearestFloat32:
case SqrtFloat32:
case ClzInt64:
case CtzInt64:
case PopcntInt64:
case NegFloat64:
case AbsFloat64:
case CeilFloat64:
case FloorFloat64:
case TruncFloat64:
case NearestFloat64:
case SqrtFloat64: type = value->type; break;
case EqZInt32:
case EqZInt64: type = i32; break;
case ExtendSInt32: case ExtendUInt32: type = i64; break;
case WrapInt64: type = i32; break;
case PromoteFloat32: type = f64; break;
case DemoteFloat64: type = f32; break;
case TruncSFloat32ToInt32:
case TruncUFloat32ToInt32:
case TruncSFloat64ToInt32:
case TruncUFloat64ToInt32:
case ReinterpretFloat32: type = i32; break;
case TruncSFloat32ToInt64:
case TruncUFloat32ToInt64:
case TruncSFloat64ToInt64:
case TruncUFloat64ToInt64:
case ReinterpretFloat64: type = i64; break;
case ReinterpretInt32:
case ConvertSInt32ToFloat32:
case ConvertUInt32ToFloat32:
case ConvertSInt64ToFloat32:
case ConvertUInt64ToFloat32: type = f32; break;
case ReinterpretInt64:
case ConvertSInt32ToFloat64:
case ConvertUInt32ToFloat64:
case ConvertSInt64ToFloat64:
case ConvertUInt64ToFloat64: type = f64; break;
default: std::cerr << "waka " << op << '\n'; WASM_UNREACHABLE();
}
}
};
class Binary : public SpecificExpression<Expression::BinaryId> {
public:
BinaryOp op;
Expression* left;
Expression* right;
// the type is always the type of the operands,
// except for relationals
bool isRelational() {
switch (op) {
case EqFloat64:
case NeFloat64:
case LtFloat64:
case LeFloat64:
case GtFloat64:
case GeFloat64:
case EqInt32:
case NeInt32:
case LtSInt32:
case LtUInt32:
case LeSInt32:
case LeUInt32:
case GtSInt32:
case GtUInt32:
case GeSInt32:
case GeUInt32:
case EqInt64:
case NeInt64:
case LtSInt64:
case LtUInt64:
case LeSInt64:
case LeUInt64:
case GtSInt64:
case GtUInt64:
case GeSInt64:
case GeUInt64:
case EqFloat32:
case NeFloat32:
case LtFloat32:
case LeFloat32:
case GtFloat32:
case GeFloat32: return true;
default: return false;
}
}
void finalize() {
assert(left && right);
if (isRelational()) {
type = i32;
} else {
type = getReachableWasmType(left->type, right->type);
}
}
};
class Select : public SpecificExpression<Expression::SelectId> {
public:
Expression* ifTrue;
Expression* ifFalse;
Expression* condition;
void finalize() {
assert(ifTrue && ifFalse);
type = getReachableWasmType(ifTrue->type, ifFalse->type);
}
};
class Drop : public SpecificExpression<Expression::DropId> {
public:
Expression* value;
};
class Return : public SpecificExpression<Expression::ReturnId> {
public:
Return() : value(nullptr) {
type = unreachable;
}
Expression* value;
};
class Host : public SpecificExpression<Expression::HostId> {
public:
HostOp op;
Name nameOperand;
ExpressionList operands;
void finalize() {
switch (op) {
case PageSize: case CurrentMemory: case HasFeature: {
type = i32;
break;
}
case GrowMemory: {
type = i32;
break;
}
default: abort();
}
}
};
class Unreachable : public SpecificExpression<Expression::UnreachableId> {
public:
Unreachable() {
type = unreachable;
}
};
// Globals
class Function {
public:
Name name;
WasmType result;
std::vector<WasmType> params; // function locals are
std::vector<WasmType> vars; // params plus vars
Name type; // if null, it is implicit in params and result
Expression* body;
// local names. these are optional.
std::vector<Name> localNames;
std::map<Name, Index> localIndices;
Function() : result(none) {}
size_t getNumParams() {
return params.size();
}
size_t getNumVars() {
return vars.size();
}
size_t getNumLocals() {
return params.size() + vars.size();
}
bool isParam(Index index) {
return index < params.size();
}
bool isVar(Index index) {
return index >= params.size();
}
Name getLocalName(Index index) {
assert(index < localNames.size() && localNames[index].is());
return localNames[index];
}
Name tryLocalName(Index index) {
if (index < localNames.size() && localNames[index].is()) {
return localNames[index];
}
// this is an unnamed local
return Name();
}
Index getLocalIndex(Name name) {
assert(localIndices.count(name) > 0);
return localIndices[name];
}
Index getVarIndexBase() {
return params.size();
}
WasmType getLocalType(Index index) {
if (isParam(index)) {
return params[index];
} else if (isVar(index)) {
return vars[index - getVarIndexBase()];
} else {
WASM_UNREACHABLE();
}
}
};
enum class ExternalKind {
Function = 0,
Table = 1,
Memory = 2,
Global = 3
};
class Import {
public:
Import() : functionType(nullptr), globalType(none) {}
Name name, module, base; // name = module.base
ExternalKind kind;
FunctionType* functionType; // for Function imports
WasmType globalType; // for Global imports
};
class Export {
public:
Name name; // exported name - note that this is the key, as the internal name is non-unique (can have multiple exports for an internal, also over kinds)
Name value; // internal name
ExternalKind kind;
};
class Table {
public:
static const Index kMaxSize = Index(-1);
struct Segment {
Expression* offset;
std::vector<Name> data;
Segment() {}
Segment(Expression* offset) : offset(offset) {
}
Segment(Expression* offset, std::vector<Name>& init) : offset(offset) {
data.swap(init);
}
};
// Currently the wasm object always 'has' one Table. It 'exists' if it has been defined or imported.
// The table can exist but be empty and have no defined initial or max size.
bool exists;
bool imported;
Name name;
Address initial, max;
std::vector<Segment> segments;
Table() : exists(false), imported(false), initial(0), max(kMaxSize) {
name = Name::fromInt(0);
}
};
class Memory {
public:
static const Address::address_t kPageSize = 64 * 1024;
static const Address::address_t kMaxSize = ~Address::address_t(0) / kPageSize;
static const Address::address_t kPageMask = ~(kPageSize - 1);
struct Segment {
Expression* offset;
std::vector<char> data; // TODO: optimize
Segment() {}
Segment(Expression* offset, const char* init, Address size) : offset(offset) {
data.resize(size);
std::copy_n(init, size, data.begin());
}
Segment(Expression* offset, std::vector<char>& init) : offset(offset) {
data.swap(init);
}
};
Name name;
Address initial, max; // sizes are in pages
std::vector<Segment> segments;
// See comment in Table.
bool exists;
bool imported;
Memory() : initial(0), max(kMaxSize), exists(false), imported(false) {
name = Name::fromInt(0);
}
};
class Global {
public:
Name name;
WasmType type;
Expression* init;
bool mutable_;
};
class Module {
public:
// wasm contents (generally you shouldn't access these from outside, except maybe for iterating; use add*() and the get() functions)
std::vector<std::unique_ptr<FunctionType>> functionTypes;
std::vector<std::unique_ptr<Import>> imports;
std::vector<std::unique_ptr<Export>> exports;
std::vector<std::unique_ptr<Function>> functions;
std::vector<std::unique_ptr<Global>> globals;
Table table;
Memory memory;
Name start;
MixedArena allocator;
private:
// TODO: add a build option where Names are just indices, and then these methods are not needed
std::map<Name, FunctionType*> functionTypesMap;
std::map<Name, Import*> importsMap;
std::map<Name, Export*> exportsMap; // exports map is by the *exported* name, which is unique
std::map<Name, Function*> functionsMap;
std::map<Name, Global*> globalsMap;
public:
Module() {};
FunctionType* getFunctionType(Name name) { assert(functionTypesMap.count(name)); return functionTypesMap[name]; }
Import* getImport(Name name) { assert(importsMap.count(name)); return importsMap[name]; }
Export* getExport(Name name) { assert(exportsMap.count(name)); return exportsMap[name]; }
Function* getFunction(Name name) { assert(functionsMap.count(name)); return functionsMap[name]; }
Global* getGlobal(Name name) { assert(globalsMap.count(name)); return globalsMap[name]; }
FunctionType* checkFunctionType(Name name) { if (!functionTypesMap.count(name)) return nullptr; return functionTypesMap[name]; }
Import* checkImport(Name name) { if (!importsMap.count(name)) return nullptr; return importsMap[name]; }
Export* checkExport(Name name) { if (!exportsMap.count(name)) return nullptr; return exportsMap[name]; }
Function* checkFunction(Name name) { if (!functionsMap.count(name)) return nullptr; return functionsMap[name]; }
Global* checkGlobal(Name name) { if (!globalsMap.count(name)) return nullptr; return globalsMap[name]; }
void addFunctionType(FunctionType* curr) {
assert(curr->name.is());
functionTypes.push_back(std::unique_ptr<FunctionType>(curr));
assert(functionTypesMap.find(curr->name) == functionTypesMap.end());
functionTypesMap[curr->name] = curr;
}
void addImport(Import* curr) {
assert(curr->name.is());
imports.push_back(std::unique_ptr<Import>(curr));
assert(importsMap.find(curr->name) == importsMap.end());
importsMap[curr->name] = curr;
}
void addExport(Export* curr) {
assert(curr->name.is());
exports.push_back(std::unique_ptr<Export>(curr));
assert(exportsMap.find(curr->name) == exportsMap.end());
exportsMap[curr->name] = curr;
}
void addFunction(Function* curr) {
assert(curr->name.is());
functions.push_back(std::unique_ptr<Function>(curr));
assert(functionsMap.find(curr->name) == functionsMap.end());
functionsMap[curr->name] = curr;
}
void addGlobal(Global* curr) {
assert(curr->name.is());
globals.push_back(std::unique_ptr<Global>(curr));
assert(globalsMap.find(curr->name) == globalsMap.end());
globalsMap[curr->name] = curr;
}
void addStart(const Name& s) {
start = s;
}
void removeImport(Name name) {
for (size_t i = 0; i < imports.size(); i++) {
if (imports[i]->name == name) {
imports.erase(imports.begin() + i);
break;
}
}
importsMap.erase(name);
}
// TODO: remove* for other elements
void updateMaps() {
functionsMap.clear();
for (auto& curr : functions) {
functionsMap[curr->name] = curr.get();
}
functionTypesMap.clear();
for (auto& curr : functionTypes) {
functionTypesMap[curr->name] = curr.get();
}
importsMap.clear();
for (auto& curr : imports) {
importsMap[curr->name] = curr.get();
}
exportsMap.clear();
for (auto& curr : exports) {
exportsMap[curr->name] = curr.get();
}
globalsMap.clear();
for (auto& curr : globals) {
globalsMap[curr->name] = curr.get();
}
}
};
} // namespace wasm
namespace std {
template<> struct hash<wasm::Address> {
size_t operator()(const wasm::Address a) const {
return std::hash<wasm::Address::address_t>()(a.addr);
}
};
}
#endif // wasm_wasm_h