https://github.com/mozilla/gecko-dev
Raw File
Tip revision: 771393b303319e834c9b4642866a60708c75bbb4 authored by ffxbld on 15 March 2016, 22:31:00 UTC
Added FIREFOX_45_0_1_RELEASE FIREFOX_45_0_1_BUILD1 tag(s) for changeset e0e51efe7b15. DONTBUILD CLOSED TREE a=release
Tip revision: 771393b
Wasm.h
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 *
 * Copyright 2015 Mozilla Foundation
 *
 * 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.
 */

#ifndef asmjs_wasm_h
#define asmjs_wasm_h

#include "mozilla/HashFunctions.h"

#include "ds/LifoAlloc.h"
#include "jit/IonTypes.h"
#include "js/Utility.h"
#include "js/Vector.h"

namespace js {
namespace wasm {

using mozilla::Move;

// The ValType enum represents the WebAssembly "value type", which are used to
// specify the type of locals and parameters.

enum class ValType : uint8_t
{
    I32,
    I64,
    F32,
    F64,
    I32x4,
    F32x4
};

static inline bool
IsSimdType(ValType vt)
{
    return vt == ValType::I32x4 || vt == ValType::F32x4;
}

static inline jit::MIRType
ToMIRType(ValType vt)
{
    switch (vt) {
      case ValType::I32: return jit::MIRType_Int32;
      case ValType::I64: MOZ_CRASH("NYI");
      case ValType::F32: return jit::MIRType_Float32;
      case ValType::F64: return jit::MIRType_Double;
      case ValType::I32x4: return jit::MIRType_Int32x4;
      case ValType::F32x4: return jit::MIRType_Float32x4;
    }
    MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("bad type");
}

// The Val class represents a single WebAssembly value of a given value type,
// mostly for the purpose of numeric literals and initializers. A Val does not
// directly map to a JS value since there is not (currently) a precise
// representation of i64 values. A Val may contain non-canonical NaNs since,
// within WebAssembly, floats are not canonicalized. Canonicalization must
// happen at the JS boundary.

class Val
{
  public:
    typedef int32_t I32x4[4];
    typedef float F32x4[4];

  private:
    ValType type_;
    union {
        uint32_t i32_;
        uint64_t i64_;
        float f32_;
        double f64_;
        I32x4 i32x4_;
        F32x4 f32x4_;
    } u;

  public:
    Val() = default;

    explicit Val(uint32_t i32) : type_(ValType::I32) { u.i32_ = i32; }
    explicit Val(uint64_t i64) : type_(ValType::I64) { u.i64_ = i64; }
    explicit Val(float f32) : type_(ValType::F32) { u.f32_ = f32; }
    explicit Val(double f64) : type_(ValType::F64) { u.f64_ = f64; }
    explicit Val(const I32x4& i32x4) : type_(ValType::I32x4) { memcpy(u.i32x4_, i32x4, sizeof(u.i32x4_)); }
    explicit Val(const F32x4& f32x4) : type_(ValType::F32x4) { memcpy(u.f32x4_, f32x4, sizeof(u.f32x4_)); }

    ValType type() const { return type_; }
    bool isSimd() const { return IsSimdType(type()); }

    uint32_t i32() const { MOZ_ASSERT(type_ == ValType::I32); return u.i32_; }
    uint64_t i64() const { MOZ_ASSERT(type_ == ValType::I64); return u.i64_; }
    float f32() const { MOZ_ASSERT(type_ == ValType::F32); return u.f32_; }
    double f64() const { MOZ_ASSERT(type_ == ValType::F64); return u.f64_; }
    const I32x4& i32x4() const { MOZ_ASSERT(type_ == ValType::I32x4); return u.i32x4_; }
    const F32x4& f32x4() const { MOZ_ASSERT(type_ == ValType::F32x4); return u.f32x4_; }
};

// The ExprType enum represents the type of a WebAssembly expression or return
// value and may either be a value type or void. A future WebAssembly extension
// may generalize expression types to instead be a list of value types (with
// void represented by the empty list). For now it's easier to have a flat enum
// and be explicit about conversions to/from value types.

enum class ExprType : uint8_t
{
    I32 = uint8_t(ValType::I32),
    I64 = uint8_t(ValType::I64),
    F32 = uint8_t(ValType::F32),
    F64 = uint8_t(ValType::F64),
    I32x4 = uint8_t(ValType::I32x4),
    F32x4 = uint8_t(ValType::F32x4),
    Void
};

static inline bool
IsVoid(ExprType et)
{
    return et == ExprType::Void;
}

static inline ValType
NonVoidToValType(ExprType et)
{
    MOZ_ASSERT(!IsVoid(et));
    return ValType(et);
}

static inline ExprType
ToExprType(ValType vt)
{
    return ExprType(vt);
}

static inline bool
IsSimdType(ExprType et)
{
    return IsVoid(et) ? false : IsSimdType(ValType(et));
}

static inline jit::MIRType
ToMIRType(ExprType et)
{
    return IsVoid(et) ? jit::MIRType_None : ToMIRType(ValType(et));
}

// The Sig class represents a WebAssembly function signature which takes a list
// of value types and returns an expression type. The engine uses two in-memory
// representations of the argument Vector's memory (when elements do not fit
// inline): normal malloc allocation (via SystemAllocPolicy) and allocation in
// a LifoAlloc (via LifoAllocPolicy). The former Sig objects can have any
// lifetime since they own the memory. The latter Sig objects must not outlive
// the associated LifoAlloc mark/release interval (which is currently the
// duration of module validation+compilation). Thus, long-lived objects like
// WasmModule must use malloced allocation.

template <class AllocPolicy>
class Sig
{
  public:
    typedef Vector<ValType, 4, AllocPolicy> ArgVector;

  private:
    ArgVector args_;
    ExprType ret_;

  protected:
    explicit Sig(AllocPolicy alloc = AllocPolicy()) : args_(alloc) {}
    Sig(Sig&& rhs) : args_(Move(rhs.args_)), ret_(rhs.ret_) {}
    Sig(ArgVector&& args, ExprType ret) : args_(Move(args)), ret_(ret) {}

  public:
    void init(ArgVector&& args, ExprType ret) {
        MOZ_ASSERT(args_.empty());
        args_ = Move(args);
        ret_ = ret;
    }

    ValType arg(unsigned i) const { return args_[i]; }
    const ArgVector& args() const { return args_; }
    const ExprType& ret() const { return ret_; }

    HashNumber hash() const {
        HashNumber hn = HashNumber(ret_);
        for (unsigned i = 0; i < args_.length(); i++)
            hn = mozilla::AddToHash(hn, HashNumber(args_[i]));
        return hn;
    }

    template <class AllocPolicy2>
    bool operator==(const Sig<AllocPolicy2>& rhs) const {
        if (ret() != rhs.ret())
            return false;
        if (args().length() != rhs.args().length())
            return false;
        for (unsigned i = 0; i < args().length(); i++) {
            if (arg(i) != rhs.arg(i))
                return false;
        }
        return true;
    }

    template <class AllocPolicy2>
    bool operator!=(const Sig<AllocPolicy2>& rhs) const {
        return !(*this == rhs);
    }
};

class MallocSig : public Sig<SystemAllocPolicy>
{
    typedef Sig<SystemAllocPolicy> BaseSig;

  public:
    MallocSig() = default;
    MallocSig(MallocSig&& rhs) : BaseSig(Move(rhs)) {}
    MallocSig(ArgVector&& args, ExprType ret) : BaseSig(Move(args), ret) {}
};

class LifoSig : public Sig<LifoAllocPolicy<Fallible>>
{
    typedef Sig<LifoAllocPolicy<Fallible>> BaseSig;
    LifoSig(ArgVector&& args, ExprType ret) : BaseSig(Move(args), ret) {}

  public:
    static LifoSig* new_(LifoAlloc& lifo, const MallocSig& src) {
        void* mem = lifo.alloc(sizeof(LifoSig));
        if (!mem)
            return nullptr;
        ArgVector args(lifo);
        if (!args.appendAll(src.args()))
            return nullptr;
        return new (mem) LifoSig(Move(args), src.ret());
    }
};

// While the frame-pointer chain allows the stack to be unwound without
// metadata, Error.stack still needs to know the line/column of every call in
// the chain. A CallSiteDesc describes a single callsite to which CallSite adds
// the metadata necessary to walk up to the next frame. Lastly CallSiteAndTarget
// adds the function index of the callee.

class CallSiteDesc
{
    uint32_t line_;
    uint32_t column_ : 31;
    uint32_t kind_ : 1;
  public:
    enum Kind {
        Relative,  // pc-relative call
        Register   // call *register
    };
    CallSiteDesc() {}
    explicit CallSiteDesc(Kind kind)
      : line_(0), column_(0), kind_(kind)
    {}
    CallSiteDesc(uint32_t line, uint32_t column, Kind kind)
      : line_(line), column_(column), kind_(kind)
    {
        MOZ_ASSERT(column_ == column, "column must fit in 31 bits");
    }
    uint32_t line() const { return line_; }
    uint32_t column() const { return column_; }
    Kind kind() const { return Kind(kind_); }
};

class CallSite : public CallSiteDesc
{
    uint32_t returnAddressOffset_;
    uint32_t stackDepth_;

  public:
    CallSite() {}

    CallSite(CallSiteDesc desc, uint32_t returnAddressOffset, uint32_t stackDepth)
      : CallSiteDesc(desc),
        returnAddressOffset_(returnAddressOffset),
        stackDepth_(stackDepth)
    { }

    void setReturnAddressOffset(uint32_t r) { returnAddressOffset_ = r; }
    void offsetReturnAddressBy(int32_t o) { returnAddressOffset_ += o; }
    uint32_t returnAddressOffset() const { return returnAddressOffset_; }

    // The stackDepth measures the amount of stack space pushed since the
    // function was called. In particular, this includes the pushed return
    // address on all archs (whether or not the call instruction pushes the
    // return address (x86/x64) or the prologue does (ARM/MIPS)).
    uint32_t stackDepth() const { return stackDepth_; }
};

class CallSiteAndTarget : public CallSite
{
    uint32_t targetIndex_;

  public:
    CallSiteAndTarget(CallSite cs, uint32_t targetIndex)
      : CallSite(cs), targetIndex_(targetIndex)
    { }

    static const uint32_t NOT_INTERNAL = UINT32_MAX;

    bool isInternal() const { return targetIndex_ != NOT_INTERNAL; }
    uint32_t targetIndex() const { MOZ_ASSERT(isInternal()); return targetIndex_; }
};

typedef Vector<CallSite, 0, SystemAllocPolicy> CallSiteVector;
typedef Vector<CallSiteAndTarget, 0, SystemAllocPolicy> CallSiteAndTargetVector;

// Summarizes a heap access made by wasm code that needs to be patched later
// and/or looked up by the wasm signal handlers. Different architectures need
// to know different things (x64: offset and length, ARM: where to patch in
// heap length, x86: where to patch in heap length and base).

#if defined(JS_CODEGEN_X86)
class HeapAccess
{
    uint32_t insnOffset_;
    uint8_t opLength_;  // the length of the load/store instruction
    uint8_t cmpDelta_;  // the number of bytes from the cmp to the load/store instruction

  public:
    HeapAccess() = default;
    static const uint32_t NoLengthCheck = UINT32_MAX;

    // If 'cmp' equals 'insnOffset' or if it is not supplied then the
    // cmpDelta_ is zero indicating that there is no length to patch.
    HeapAccess(uint32_t insnOffset, uint32_t after, uint32_t cmp = NoLengthCheck) {
        mozilla::PodZero(this);  // zero padding for Valgrind
        insnOffset_ = insnOffset;
        opLength_ = after - insnOffset;
        cmpDelta_ = cmp == NoLengthCheck ? 0 : insnOffset - cmp;
    }

    uint32_t insnOffset() const { return insnOffset_; }
    void setInsnOffset(uint32_t insnOffset) { insnOffset_ = insnOffset; }
    void offsetInsnOffsetBy(uint32_t offset) { insnOffset_ += offset; }
    void* patchHeapPtrImmAt(uint8_t* code) const { return code + (insnOffset_ + opLength_); }
    bool hasLengthCheck() const { return cmpDelta_ > 0; }
    void* patchLengthAt(uint8_t* code) const {
        MOZ_ASSERT(hasLengthCheck());
        return code + (insnOffset_ - cmpDelta_);
    }
};
#elif defined(JS_CODEGEN_X64)
class HeapAccess
{
  public:
    enum WhatToDoOnOOB {
        CarryOn, // loads return undefined, stores do nothing.
        Throw    // throw a RangeError
    };

  private:
    uint32_t insnOffset_;
    uint8_t offsetWithinWholeSimdVector_; // if is this e.g. the Z of an XYZ
    bool throwOnOOB_;                     // should we throw on OOB?
    uint8_t cmpDelta_;                    // the number of bytes from the cmp to the load/store instruction

  public:
    HeapAccess() = default;
    static const uint32_t NoLengthCheck = UINT32_MAX;

    // If 'cmp' equals 'insnOffset' or if it is not supplied then the
    // cmpDelta_ is zero indicating that there is no length to patch.
    HeapAccess(uint32_t insnOffset, WhatToDoOnOOB oob,
               uint32_t cmp = NoLengthCheck,
               uint32_t offsetWithinWholeSimdVector = 0)
    {
        mozilla::PodZero(this);  // zero padding for Valgrind
        insnOffset_ = insnOffset;
        offsetWithinWholeSimdVector_ = offsetWithinWholeSimdVector;
        throwOnOOB_ = oob == Throw;
        cmpDelta_ = cmp == NoLengthCheck ? 0 : insnOffset - cmp;
        MOZ_ASSERT(offsetWithinWholeSimdVector_ == offsetWithinWholeSimdVector);
    }

    uint32_t insnOffset() const { return insnOffset_; }
    void setInsnOffset(uint32_t insnOffset) { insnOffset_ = insnOffset; }
    void offsetInsnOffsetBy(uint32_t offset) { insnOffset_ += offset; }
    bool throwOnOOB() const { return throwOnOOB_; }
    uint32_t offsetWithinWholeSimdVector() const { return offsetWithinWholeSimdVector_; }
    bool hasLengthCheck() const { return cmpDelta_ > 0; }
    void* patchLengthAt(uint8_t* code) const {
        MOZ_ASSERT(hasLengthCheck());
        return code + (insnOffset_ - cmpDelta_);
    }
};
#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \
      defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
class HeapAccess
{
    uint32_t insnOffset_;
  public:
    HeapAccess() = default;
    explicit HeapAccess(uint32_t insnOffset) : insnOffset_(insnOffset) {}
    uint32_t insnOffset() const { return insnOffset_; }
    void setInsnOffset(uint32_t insnOffset) { insnOffset_ = insnOffset; }
    void offsetInsnOffsetBy(uint32_t offset) { insnOffset_ += offset; }
};
#elif defined(JS_CODEGEN_NONE)
class HeapAccess {
  public:
    void offsetInsnOffsetBy(uint32_t) { MOZ_CRASH(); }
    uint32_t insnOffset() const { MOZ_CRASH(); }
};
#endif

typedef Vector<HeapAccess, 0, SystemAllocPolicy> HeapAccessVector;

// A wasm::Builtin represents a function implemented by the engine that is
// called directly from wasm code and should show up in the callstack.

enum class Builtin : uint16_t
{
    ToInt32,
#if defined(JS_CODEGEN_ARM)
    aeabi_idivmod,
    aeabi_uidivmod,
    AtomicCmpXchg,
    AtomicXchg,
    AtomicFetchAdd,
    AtomicFetchSub,
    AtomicFetchAnd,
    AtomicFetchOr,
    AtomicFetchXor,
#endif
    ModD,
    SinD,
    CosD,
    TanD,
    ASinD,
    ACosD,
    ATanD,
    CeilD,
    CeilF,
    FloorD,
    FloorF,
    ExpD,
    LogD,
    PowD,
    ATan2D,
    Limit
};

// A wasm::SymbolicAddress represents a pointer to a well-known function or
// object that is embedded in wasm code. Since wasm code is serialized and
// later deserialized into a different address space, symbolic addresses must be
// used for *all* pointers into the address space. The MacroAssembler records a
// list of all SymbolicAddresses and the offsets of their use in the code for
// later patching during static linking.

enum class SymbolicAddress
{
    ToInt32         = unsigned(Builtin::ToInt32),
#if defined(JS_CODEGEN_ARM)
    aeabi_idivmod   = unsigned(Builtin::aeabi_idivmod),
    aeabi_uidivmod  = unsigned(Builtin::aeabi_uidivmod),
    AtomicCmpXchg   = unsigned(Builtin::AtomicCmpXchg),
    AtomicXchg      = unsigned(Builtin::AtomicXchg),
    AtomicFetchAdd  = unsigned(Builtin::AtomicFetchAdd),
    AtomicFetchSub  = unsigned(Builtin::AtomicFetchSub),
    AtomicFetchAnd  = unsigned(Builtin::AtomicFetchAnd),
    AtomicFetchOr   = unsigned(Builtin::AtomicFetchOr),
    AtomicFetchXor  = unsigned(Builtin::AtomicFetchXor),
#endif
    ModD            = unsigned(Builtin::ModD),
    SinD            = unsigned(Builtin::SinD),
    CosD            = unsigned(Builtin::CosD),
    TanD            = unsigned(Builtin::TanD),
    ASinD           = unsigned(Builtin::ASinD),
    ACosD           = unsigned(Builtin::ACosD),
    ATanD           = unsigned(Builtin::ATanD),
    CeilD           = unsigned(Builtin::CeilD),
    CeilF           = unsigned(Builtin::CeilF),
    FloorD          = unsigned(Builtin::FloorD),
    FloorF          = unsigned(Builtin::FloorF),
    ExpD            = unsigned(Builtin::ExpD),
    LogD            = unsigned(Builtin::LogD),
    PowD            = unsigned(Builtin::PowD),
    ATan2D          = unsigned(Builtin::ATan2D),
    Runtime,
    RuntimeInterruptUint32,
    StackLimit,
    ReportOverRecursed,
    OnDetached,
    OnOutOfBounds,
    OnImpreciseConversion,
    HandleExecutionInterrupt,
    InvokeFromAsmJS_Ignore,
    InvokeFromAsmJS_ToInt32,
    InvokeFromAsmJS_ToNumber,
    CoerceInPlace_ToInt32,
    CoerceInPlace_ToNumber,
    Limit
};

static inline SymbolicAddress
BuiltinToImmediate(Builtin b)
{
    return SymbolicAddress(b);
}

static inline bool
ImmediateIsBuiltin(SymbolicAddress imm, Builtin* builtin)
{
    if (uint32_t(imm) < uint32_t(Builtin::Limit)) {
        *builtin = Builtin(imm);
        return true;
    }
    return false;
}

// An ExitReason describes the possible reasons for leaving compiled wasm code
// or the state of not having left compiled wasm code (ExitReason::None).

class ExitReason
{
  public:
    // List of reasons for execution leaving compiled wasm code (or None, if
    // control hasn't exited).
    enum Kind
    {
        None,       // default state, the pc is in wasm code
        Jit,        // fast-path exit to JIT code
        Slow,       // general case exit to C++ Invoke
        Interrupt,  // executing an interrupt callback
        Builtin     // calling into a builtin (native) function
    };

  private:
    Kind kind_;
    wasm::Builtin builtin_;

  public:
    ExitReason() = default;
    MOZ_IMPLICIT ExitReason(Kind kind) : kind_(kind) { MOZ_ASSERT(kind != Builtin); }
    MOZ_IMPLICIT ExitReason(wasm::Builtin builtin) : kind_(Builtin), builtin_(builtin) {}
    Kind kind() const { return kind_; }
    wasm::Builtin builtin() const { MOZ_ASSERT(kind_ == Builtin); return builtin_; }

    uint32_t pack() const {
        static_assert(sizeof(wasm::Builtin) == 2, "fits");
        return uint16_t(kind_) | (uint16_t(builtin_) << 16);
    }
    static ExitReason unpack(uint32_t u32) {
        static_assert(sizeof(wasm::Builtin) == 2, "fits");
        ExitReason r;
        r.kind_ = Kind(uint16_t(u32));
        r.builtin_ = wasm::Builtin(uint16_t(u32 >> 16));
        return r;
    }
};

// A hoisting of constants that would otherwise require #including WasmModule.h
// everywhere. Values are asserted in WasmModule.h.

static const unsigned ActivationGlobalDataOffset = 0;
static const unsigned HeapGlobalDataOffset = sizeof(void*);
static const unsigned NaN64GlobalDataOffset = 2 * sizeof(void*);
static const unsigned NaN32GlobalDataOffset = 2 * sizeof(void*) + sizeof(double);

} // namespace wasm
} // namespace js

#endif // asmjs_wasm_h
back to top