Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Everything needed to build actual MIR instructions: the actual opcodes and
* instructions, the instruction interface, and use chains.
*/
#ifndef jit_MIR_wasm_h
#define jit_MIR_wasm_h
#include "mozilla/Array.h"
#include "mozilla/HashFunctions.h"
#ifdef JS_JITSPEW
# include "mozilla/Sprintf.h"
# include "mozilla/Vector.h"
#endif
#include <algorithm>
#include <initializer_list>
#include "jit/MIR.h"
namespace js {
class WasmInstanceObject;
namespace wasm {
class FuncExport;
extern uint32_t MIRTypeToABIResultSize(jit::MIRType);
} // namespace wasm
namespace jit {
class MWasmNullConstant : public MNullaryInstruction {
explicit MWasmNullConstant() : MNullaryInstruction(classOpcode) {
setResultType(MIRType::WasmAnyRef);
setMovable();
}
public:
INSTRUCTION_HEADER(WasmNullConstant)
TRIVIAL_NEW_WRAPPERS
HashNumber valueHash() const override;
bool congruentTo(const MDefinition* ins) const override {
return ins->isWasmNullConstant();
}
AliasSet getAliasSet() const override { return AliasSet::None(); }
ALLOW_CLONE(MWasmNullConstant)
};
// Floating-point value as created by wasm. Just a constant value, used to
// effectively inhibit all the MIR optimizations. This uses the same LIR nodes
// as a MConstant of the same type would.
class MWasmFloatConstant : public MNullaryInstruction {
union {
float f32_;
double f64_;
#ifdef ENABLE_WASM_SIMD
int8_t s128_[16];
uint64_t bits_[2];
#else
uint64_t bits_[1];
#endif
} u;
explicit MWasmFloatConstant(MIRType type) : MNullaryInstruction(classOpcode) {
u.bits_[0] = 0;
#ifdef ENABLE_WASM_SIMD
u.bits_[1] = 0;
#endif
setResultType(type);
}
public:
INSTRUCTION_HEADER(WasmFloatConstant)
static MWasmFloatConstant* NewDouble(TempAllocator& alloc, double d) {
auto* ret = new (alloc) MWasmFloatConstant(MIRType::Double);
ret->u.f64_ = d;
return ret;
}
static MWasmFloatConstant* NewFloat32(TempAllocator& alloc, float f) {
auto* ret = new (alloc) MWasmFloatConstant(MIRType::Float32);
ret->u.f32_ = f;
return ret;
}
#ifdef ENABLE_WASM_SIMD
static MWasmFloatConstant* NewSimd128(TempAllocator& alloc,
const SimdConstant& s) {
auto* ret = new (alloc) MWasmFloatConstant(MIRType::Simd128);
memcpy(ret->u.s128_, s.bytes(), 16);
return ret;
}
#endif
HashNumber valueHash() const override;
bool congruentTo(const MDefinition* ins) const override;
AliasSet getAliasSet() const override { return AliasSet::None(); }
const double& toDouble() const {
MOZ_ASSERT(type() == MIRType::Double);
return u.f64_;
}
const float& toFloat32() const {
MOZ_ASSERT(type() == MIRType::Float32);
return u.f32_;
}
#ifdef ENABLE_WASM_SIMD
const SimdConstant toSimd128() const {
MOZ_ASSERT(type() == MIRType::Simd128);
return SimdConstant::CreateX16(u.s128_);
}
#endif
#ifdef JS_JITSPEW
void getExtras(ExtrasCollector* extras) override {
char buf[64];
switch (type()) {
case MIRType::Float32:
SprintfLiteral(buf, "f32{%e}", (double)u.f32_);
break;
case MIRType::Double:
SprintfLiteral(buf, "f64{%e}", u.f64_);
break;
# ifdef ENABLE_WASM_SIMD
case MIRType::Simd128:
SprintfLiteral(buf, "v128{[1]=%016llx:[0]=%016llx}",
(unsigned long long int)u.bits_[1],
(unsigned long long int)u.bits_[0]);
break;
# endif
default:
SprintfLiteral(buf, "!!getExtras: missing case!!");
break;
}
extras->add(buf);
}
#endif
};
// Converts a uint32 to a float32 (coming from wasm).
class MWasmUnsignedToFloat32 : public MUnaryInstruction,
public NoTypePolicy::Data {
explicit MWasmUnsignedToFloat32(MDefinition* def)
: MUnaryInstruction(classOpcode, def) {
setResultType(MIRType::Float32);
setMovable();
}
public:
INSTRUCTION_HEADER(WasmUnsignedToFloat32)
TRIVIAL_NEW_WRAPPERS
MDefinition* foldsTo(TempAllocator& alloc) override;
bool congruentTo(const MDefinition* ins) const override {
return congruentIfOperandsEqual(ins);
}
AliasSet getAliasSet() const override { return AliasSet::None(); }
bool canProduceFloat32() const override { return true; }
};
// The same as MWasmTruncateToInt64 but with the Instance dependency.
// It used only for arm now because on arm we need to call builtin to truncate
// to i64.
class MWasmBuiltinTruncateToInt64 : public MAryInstruction<2>,
public NoTypePolicy::Data {
TruncFlags flags_;
wasm::BytecodeOffset bytecodeOffset_;
MWasmBuiltinTruncateToInt64(MDefinition* def, MDefinition* instance,
TruncFlags flags,
wasm::BytecodeOffset bytecodeOffset)
: MAryInstruction(classOpcode),
flags_(flags),
bytecodeOffset_(bytecodeOffset) {
initOperand(0, def);
initOperand(1, instance);
setResultType(MIRType::Int64);
setGuard(); // neither removable nor movable because of possible
// side-effects.
}
public:
INSTRUCTION_HEADER(WasmBuiltinTruncateToInt64)
NAMED_OPERANDS((0, input), (1, instance));
TRIVIAL_NEW_WRAPPERS
bool isUnsigned() const { return flags_ & TRUNC_UNSIGNED; }
bool isSaturating() const { return flags_ & TRUNC_SATURATING; }
TruncFlags flags() const { return flags_; }
wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
bool congruentTo(const MDefinition* ins) const override {
return congruentIfOperandsEqual(ins) &&
ins->toWasmBuiltinTruncateToInt64()->flags() == flags_;
}
AliasSet getAliasSet() const override { return AliasSet::None(); }
};
class MWasmTruncateToInt64 : public MUnaryInstruction,
public NoTypePolicy::Data {
TruncFlags flags_;
wasm::BytecodeOffset bytecodeOffset_;
MWasmTruncateToInt64(MDefinition* def, TruncFlags flags,
wasm::BytecodeOffset bytecodeOffset)
: MUnaryInstruction(classOpcode, def),
flags_(flags),
bytecodeOffset_(bytecodeOffset) {
setResultType(MIRType::Int64);
setGuard(); // neither removable nor movable because of possible
// side-effects.
}
public:
INSTRUCTION_HEADER(WasmTruncateToInt64)
TRIVIAL_NEW_WRAPPERS
bool isUnsigned() const { return flags_ & TRUNC_UNSIGNED; }
bool isSaturating() const { return flags_ & TRUNC_SATURATING; }
TruncFlags flags() const { return flags_; }
wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
bool congruentTo(const MDefinition* ins) const override {
return congruentIfOperandsEqual(ins) &&
ins->toWasmTruncateToInt64()->flags() == flags_;
}
AliasSet getAliasSet() const override { return AliasSet::None(); }
};
// Truncate a value to an int32, with wasm semantics: this will trap when the
// value is out of range.
class MWasmTruncateToInt32 : public MUnaryInstruction,
public NoTypePolicy::Data {
TruncFlags flags_;
wasm::BytecodeOffset bytecodeOffset_;
explicit MWasmTruncateToInt32(MDefinition* def, TruncFlags flags,
wasm::BytecodeOffset bytecodeOffset)
: MUnaryInstruction(classOpcode, def),
flags_(flags),
bytecodeOffset_(bytecodeOffset) {
setResultType(MIRType::Int32);
setGuard(); // neither removable nor movable because of possible
// side-effects.
}
public:
INSTRUCTION_HEADER(WasmTruncateToInt32)
TRIVIAL_NEW_WRAPPERS
bool isUnsigned() const { return flags_ & TRUNC_UNSIGNED; }
bool isSaturating() const { return flags_ & TRUNC_SATURATING; }
TruncFlags flags() const { return flags_; }
wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
MDefinition* foldsTo(TempAllocator& alloc) override;
bool congruentTo(const MDefinition* ins) const override {
return congruentIfOperandsEqual(ins) &&
ins->toWasmTruncateToInt32()->flags() == flags_;
}
AliasSet getAliasSet() const override { return AliasSet::None(); }
};
// It is like MTruncateToInt32 but with instance dependency.
class MWasmBuiltinTruncateToInt32 : public MAryInstruction<2>,
public ToInt32Policy::Data {
wasm::BytecodeOffset bytecodeOffset_;
MWasmBuiltinTruncateToInt32(
MDefinition* def, MDefinition* instance,
wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset())
: MAryInstruction(classOpcode), bytecodeOffset_(bytecodeOffset) {
initOperand(0, def);
initOperand(1, instance);
setResultType(MIRType::Int32);
setMovable();
// Guard unless the conversion is known to be non-effectful & non-throwing.
if (MTruncateToInt32::mightHaveSideEffects(def)) {
setGuard();
}
}
public:
INSTRUCTION_HEADER(WasmBuiltinTruncateToInt32)
NAMED_OPERANDS((0, input), (1, instance))
TRIVIAL_NEW_WRAPPERS
bool congruentTo(const MDefinition* ins) const override {
return congruentIfOperandsEqual(ins);
}
AliasSet getAliasSet() const override { return AliasSet::None(); }
wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
ALLOW_CLONE(MWasmBuiltinTruncateToInt32)
};
class MWasmBuiltinDivI64 : public MAryInstruction<3>, public ArithPolicy::Data {
bool canBeNegativeZero_;
bool canBeNegativeOverflow_;
bool canBeDivideByZero_;
bool canBeNegativeDividend_;
bool unsigned_; // If false, signedness will be derived from operands
bool trapOnError_;
wasm::BytecodeOffset bytecodeOffset_;
MWasmBuiltinDivI64(MDefinition* left, MDefinition* right,
MDefinition* instance)
: MAryInstruction(classOpcode),
canBeNegativeZero_(true),
canBeNegativeOverflow_(true),
canBeDivideByZero_(true),
canBeNegativeDividend_(true),
unsigned_(false),
trapOnError_(false) {
initOperand(0, left);
initOperand(1, right);
initOperand(2, instance);
setResultType(MIRType::Int64);
setMovable();
}
public:
INSTRUCTION_HEADER(WasmBuiltinDivI64)
NAMED_OPERANDS((0, lhs), (1, rhs), (2, instance))
static MWasmBuiltinDivI64* New(
TempAllocator& alloc, MDefinition* left, MDefinition* right,
MDefinition* instance, bool unsignd, bool trapOnError = false,
wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset()) {
auto* wasm64Div = new (alloc) MWasmBuiltinDivI64(left, right, instance);
wasm64Div->unsigned_ = unsignd;
wasm64Div->trapOnError_ = trapOnError;
wasm64Div->bytecodeOffset_ = bytecodeOffset;
if (trapOnError) {
wasm64Div->setGuard(); // not removable because of possible side-effects.
wasm64Div->setNotMovable();
}
return wasm64Div;
}
bool canBeNegativeZero() const { return canBeNegativeZero_; }
void setCanBeNegativeZero(bool negativeZero) {
canBeNegativeZero_ = negativeZero;
}
bool canBeNegativeOverflow() const { return canBeNegativeOverflow_; }
bool canBeDivideByZero() const { return canBeDivideByZero_; }
bool canBeNegativeDividend() const {
// "Dividend" is an ambiguous concept for unsigned truncated
// division, because of the truncation procedure:
// ((x>>>0)/2)|0, for example, gets transformed in
// MWasmDiv::truncate into a node with lhs representing x (not
// x>>>0) and rhs representing the constant 2; in other words,
// the MIR node corresponds to "cast operands to unsigned and
// divide" operation. In this case, is the dividend x or is it
// x>>>0? In order to resolve such ambiguities, we disallow
// the usage of this method for unsigned division.
MOZ_ASSERT(!unsigned_);
return canBeNegativeDividend_;
}
bool isUnsigned() const { return unsigned_; }
bool trapOnError() const { return trapOnError_; }
wasm::BytecodeOffset bytecodeOffset() const {
MOZ_ASSERT(bytecodeOffset_.isValid());
return bytecodeOffset_;
}
ALLOW_CLONE(MWasmBuiltinDivI64)
};
class MWasmBuiltinModD : public MAryInstruction<3>, public ArithPolicy::Data {
wasm::BytecodeOffset bytecodeOffset_;
MWasmBuiltinModD(MDefinition* left, MDefinition* right, MDefinition* instance,
MIRType type)
: MAryInstruction(classOpcode) {
initOperand(0, left);
initOperand(1, right);
initOperand(2, instance);
setResultType(type);
setMovable();
}
public:
INSTRUCTION_HEADER(WasmBuiltinModD)
NAMED_OPERANDS((0, lhs), (1, rhs), (2, instance))
static MWasmBuiltinModD* New(
TempAllocator& alloc, MDefinition* left, MDefinition* right,
MDefinition* instance, MIRType type,
wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset()) {
auto* wasmBuiltinModD =
new (alloc) MWasmBuiltinModD(left, right, instance, type);
wasmBuiltinModD->bytecodeOffset_ = bytecodeOffset;
return wasmBuiltinModD;
}
wasm::BytecodeOffset bytecodeOffset() const {
MOZ_ASSERT(bytecodeOffset_.isValid());
return bytecodeOffset_;
}
ALLOW_CLONE(MWasmBuiltinModD)
};
class MWasmBuiltinModI64 : public MAryInstruction<3>, public ArithPolicy::Data {
bool unsigned_; // If false, signedness will be derived from operands
bool canBeNegativeDividend_;
bool canBeDivideByZero_;
bool trapOnError_;
wasm::BytecodeOffset bytecodeOffset_;
MWasmBuiltinModI64(MDefinition* left, MDefinition* right,
MDefinition* instance)
: MAryInstruction(classOpcode),
unsigned_(false),
canBeNegativeDividend_(true),
canBeDivideByZero_(true),
trapOnError_(false) {
initOperand(0, left);
initOperand(1, right);
initOperand(2, instance);
setResultType(MIRType::Int64);
setMovable();
}
public:
INSTRUCTION_HEADER(WasmBuiltinModI64)
NAMED_OPERANDS((0, lhs), (1, rhs), (2, instance))
static MWasmBuiltinModI64* New(
TempAllocator& alloc, MDefinition* left, MDefinition* right,
MDefinition* instance, bool unsignd, bool trapOnError = false,
wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset()) {
auto* mod = new (alloc) MWasmBuiltinModI64(left, right, instance);
mod->unsigned_ = unsignd;
mod->trapOnError_ = trapOnError;
mod->bytecodeOffset_ = bytecodeOffset;
if (trapOnError) {
mod->setGuard(); // not removable because of possible side-effects.
mod->setNotMovable();
}
return mod;
}
bool canBeNegativeDividend() const {
MOZ_ASSERT(!unsigned_);
return canBeNegativeDividend_;
}
bool canBeDivideByZero() const { return canBeDivideByZero_; }
bool isUnsigned() const { return unsigned_; }
bool trapOnError() const { return trapOnError_; }
wasm::BytecodeOffset bytecodeOffset() const {
MOZ_ASSERT(bytecodeOffset_.isValid());
return bytecodeOffset_;
}
ALLOW_CLONE(MWasmBuiltinModI64)
};
// Check whether we need to fire the interrupt handler (in wasm code).
class MWasmInterruptCheck : public MUnaryInstruction,
public NoTypePolicy::Data {
wasm::BytecodeOffset bytecodeOffset_;
MWasmInterruptCheck(MDefinition* instance,
wasm::BytecodeOffset bytecodeOffset)
: MUnaryInstruction(classOpcode, instance),
bytecodeOffset_(bytecodeOffset) {
setGuard();
}
public:
INSTRUCTION_HEADER(WasmInterruptCheck)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, instance))
AliasSet getAliasSet() const override { return AliasSet::None(); }
wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
};
// Directly jumps to the indicated trap, leaving Wasm code and reporting a
// runtime error.
class MWasmTrap : public MAryControlInstruction<0, 0>,
public NoTypePolicy::Data {
wasm::Trap trap_;
wasm::BytecodeOffset bytecodeOffset_;
explicit MWasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset)
: MAryControlInstruction(classOpcode),
trap_(trap),
bytecodeOffset_(bytecodeOffset) {}
public:
INSTRUCTION_HEADER(WasmTrap)
TRIVIAL_NEW_WRAPPERS
AliasSet getAliasSet() const override { return AliasSet::None(); }
wasm::Trap trap() const { return trap_; }
wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
};
// Flips the input's sign bit, independently of the rest of the number's
// payload. Note this is different from multiplying by minus-one, which has
// side-effects for e.g. NaNs.
class MWasmNeg : public MUnaryInstruction, public NoTypePolicy::Data {
MWasmNeg(MDefinition* op, MIRType type) : MUnaryInstruction(classOpcode, op) {
setResultType(type);
setMovable();
}
public:
INSTRUCTION_HEADER(WasmNeg)
TRIVIAL_NEW_WRAPPERS
};
// Machine-level bitwise AND/OR/XOR, avoiding all JS-level complexity embodied
// in MBinaryBitwiseInstruction.
class MWasmBinaryBitwise : public MBinaryInstruction,
public NoTypePolicy::Data {
public:
enum class SubOpcode { And, Or, Xor };
protected:
MWasmBinaryBitwise(MDefinition* left, MDefinition* right, MIRType type,
SubOpcode subOpcode)
: MBinaryInstruction(classOpcode, left, right), subOpcode_(subOpcode) {
MOZ_ASSERT(type == MIRType::Int32 || type == MIRType::Int64);
setResultType(type);
setMovable();
setCommutative();
}
public:
INSTRUCTION_HEADER(WasmBinaryBitwise)
TRIVIAL_NEW_WRAPPERS
SubOpcode subOpcode() const { return subOpcode_; }
MDefinition* foldsTo(TempAllocator& alloc) override;
bool congruentTo(const MDefinition* ins) const override {
return ins->isWasmBinaryBitwise() &&
ins->toWasmBinaryBitwise()->subOpcode() == subOpcode() &&
binaryCongruentTo(ins);
}
AliasSet getAliasSet() const override { return AliasSet::None(); }
#ifdef JS_JITSPEW
void getExtras(ExtrasCollector* extras) override {
const char* what = "!!unknown!!";
switch (subOpcode()) {
case SubOpcode::And:
what = "And";
break;
case SubOpcode::Or:
what = "Or";
break;
case SubOpcode::Xor:
what = "Xor";
break;
}
extras->add(what);
}
#endif
private:
SubOpcode subOpcode_;
ALLOW_CLONE(MWasmBinaryBitwise)
};
class MWasmLoadInstance : public MUnaryInstruction, public NoTypePolicy::Data {
uint32_t offset_;
AliasSet aliases_;
explicit MWasmLoadInstance(MDefinition* instance, uint32_t offset,
MIRType type, AliasSet aliases)
: MUnaryInstruction(classOpcode, instance),
offset_(offset),
aliases_(aliases) {
// Different instance data have different alias classes and only those
// classes are allowed.
MOZ_ASSERT(
aliases_.flags() == AliasSet::Load(AliasSet::WasmHeapMeta).flags() ||
aliases_.flags() == AliasSet::Load(AliasSet::WasmTableMeta).flags() ||
aliases_.flags() ==
AliasSet::Load(AliasSet::WasmPendingException).flags() ||
aliases_.flags() == AliasSet::None().flags());
// The only types supported at the moment.
MOZ_ASSERT(type == MIRType::Pointer || type == MIRType::Int32 ||
type == MIRType::Int64 || type == MIRType::WasmAnyRef);
setMovable();
setResultType(type);
}
public:
INSTRUCTION_HEADER(WasmLoadInstance)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, instance))
uint32_t offset() const { return offset_; }
bool congruentTo(const MDefinition* ins) const override {
return op() == ins->op() &&
offset() == ins->toWasmLoadInstance()->offset() &&
type() == ins->type();
}
HashNumber valueHash() const override {
return addU32ToHash(HashNumber(op()), offset());
}
AliasSet getAliasSet() const override { return aliases_; }
};
class MWasmStoreInstance : public MBinaryInstruction,
public NoTypePolicy::Data {
uint32_t offset_;
AliasSet aliases_;
explicit MWasmStoreInstance(MDefinition* instance, MDefinition* value,
uint32_t offset, MIRType type, AliasSet aliases)
: MBinaryInstruction(classOpcode, instance, value),
offset_(offset),
aliases_(aliases) {
// Different instance data have different alias classes and only those
// classes are allowed.
MOZ_ASSERT(aliases_.flags() ==
AliasSet::Store(AliasSet::WasmPendingException).flags());
// The only types supported at the moment.
MOZ_ASSERT(type == MIRType::Pointer || type == MIRType::Int32 ||
type == MIRType::Int64 || type == MIRType::WasmAnyRef);
}
public:
INSTRUCTION_HEADER(WasmStoreInstance)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, instance), (1, value))
uint32_t offset() const { return offset_; }
AliasSet getAliasSet() const override { return aliases_; }
};
class MWasmHeapReg : public MNullaryInstruction {
AliasSet aliases_;
explicit MWasmHeapReg(AliasSet aliases)
: MNullaryInstruction(classOpcode), aliases_(aliases) {
setMovable();
setResultType(MIRType::Pointer);
}
public:
INSTRUCTION_HEADER(WasmHeapReg)
TRIVIAL_NEW_WRAPPERS
bool congruentTo(const MDefinition* ins) const override {
return ins->isWasmHeapReg();
}
AliasSet getAliasSet() const override { return aliases_; }
};
// For memory32, bounds check nodes are of type Int32 on 32-bit systems for both
// wasm and asm.js code, as well as on 64-bit systems for asm.js code and for
// wasm code that is known to have a bounds check limit that fits into 32 bits.
// They are of type Int64 only on 64-bit systems for wasm code with 4GB heaps.
// There is no way for nodes of both types to be present in the same function.
// Should this change, then BCE must be updated to take type into account.
//
// For memory64, bounds check nodes are always of type Int64.
class MWasmBoundsCheck : public MBinaryInstruction, public NoTypePolicy::Data {
public:
enum Target {
// Linear memory at index zero, which is the only memory allowed so far.
Memory0,
// Everything else. Currently comprises tables, and arrays in the GC
// proposal.
Unknown
};
private:
wasm::BytecodeOffset bytecodeOffset_;
Target target_;
explicit MWasmBoundsCheck(MDefinition* index, MDefinition* boundsCheckLimit,
wasm::BytecodeOffset bytecodeOffset, Target target)
: MBinaryInstruction(classOpcode, index, boundsCheckLimit),
bytecodeOffset_(bytecodeOffset),
target_(target) {
MOZ_ASSERT(index->type() == boundsCheckLimit->type());
// Bounds check is effectful: it throws for OOB.
setGuard();
if (JitOptions.spectreIndexMasking) {
setResultType(index->type());
}
}
public:
INSTRUCTION_HEADER(WasmBoundsCheck)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, index), (1, boundsCheckLimit))
AliasSet getAliasSet() const override { return AliasSet::None(); }
bool isMemory0() const { return target_ == MWasmBoundsCheck::Memory0; }
bool isRedundant() const { return !isGuard(); }
void setRedundant() { setNotGuard(); }
wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
};
class MWasmAddOffset : public MUnaryInstruction, public NoTypePolicy::Data {
uint64_t offset_;
wasm::BytecodeOffset bytecodeOffset_;
MWasmAddOffset(MDefinition* base, uint64_t offset,
wasm::BytecodeOffset bytecodeOffset)
: MUnaryInstruction(classOpcode, base),
offset_(offset),
bytecodeOffset_(bytecodeOffset) {
setGuard();
MOZ_ASSERT(base->type() == MIRType::Int32 ||
base->type() == MIRType::Int64);
setResultType(base->type());
}
public:
INSTRUCTION_HEADER(WasmAddOffset)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, base))
MDefinition* foldsTo(TempAllocator& alloc) override;
AliasSet getAliasSet() const override { return AliasSet::None(); }
uint64_t offset() const { return offset_; }
wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
};
class MWasmAlignmentCheck : public MUnaryInstruction,
public NoTypePolicy::Data {
uint32_t byteSize_;
wasm::BytecodeOffset bytecodeOffset_;
explicit MWasmAlignmentCheck(MDefinition* index, uint32_t byteSize,
wasm::BytecodeOffset bytecodeOffset)
: MUnaryInstruction(classOpcode, index),
byteSize_(byteSize),
bytecodeOffset_(bytecodeOffset) {
MOZ_ASSERT(mozilla::IsPowerOfTwo(byteSize));
// Alignment check is effectful: it throws for unaligned.
setGuard();
}
public:
INSTRUCTION_HEADER(WasmAlignmentCheck)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, index))
bool congruentTo(const MDefinition* ins) const override;
AliasSet getAliasSet() const override { return AliasSet::None(); }
uint32_t byteSize() const { return byteSize_; }
wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
};
class MWasmLoad
: public MVariadicInstruction, // memoryBase is nullptr on some platforms
public NoTypePolicy::Data {
wasm::MemoryAccessDesc access_;
explicit MWasmLoad(const wasm::MemoryAccessDesc& access, MIRType resultType)
: MVariadicInstruction(classOpcode), access_(access) {
setGuard();
setResultType(resultType);
}
public:
INSTRUCTION_HEADER(WasmLoad)
NAMED_OPERANDS((0, base), (1, memoryBase));
static MWasmLoad* New(TempAllocator& alloc, MDefinition* memoryBase,
MDefinition* base, const wasm::MemoryAccessDesc& access,
MIRType resultType) {
MWasmLoad* load = new (alloc) MWasmLoad(access, resultType);
if (!load->init(alloc, 1 + !!memoryBase)) {
return nullptr;
}
load->initOperand(0, base);
if (memoryBase) {
load->initOperand(1, memoryBase);
}
return load;
}
const wasm::MemoryAccessDesc& access() const { return access_; }
AliasSet getAliasSet() const override {
// When a barrier is needed, make the instruction effectful by giving
// it a "store" effect.
if (access_.isAtomic()) {
return AliasSet::Store(AliasSet::WasmHeap);
}
return AliasSet::Load(AliasSet::WasmHeap);
}
bool hasMemoryBase() const { return numOperands() > 1; }
#ifdef JS_JITSPEW
void getExtras(ExtrasCollector* extras) override {
char buf[64];
SprintfLiteral(buf, "(offs=%lld)", (long long int)access().offset64());
extras->add(buf);
}
#endif
};
class MWasmStore : public MVariadicInstruction, public NoTypePolicy::Data {
wasm::MemoryAccessDesc access_;
explicit MWasmStore(const wasm::MemoryAccessDesc& access)
: MVariadicInstruction(classOpcode), access_(access) {
setGuard();
}
public:
INSTRUCTION_HEADER(WasmStore)
NAMED_OPERANDS((0, base), (1, value), (2, memoryBase))
static MWasmStore* New(TempAllocator& alloc, MDefinition* memoryBase,
MDefinition* base,
const wasm::MemoryAccessDesc& access,
MDefinition* value) {
MWasmStore* store = new (alloc) MWasmStore(access);
if (!store->init(alloc, 2 + !!memoryBase)) {
return nullptr;
}
store->initOperand(0, base);
store->initOperand(1, value);
if (memoryBase) {
store->initOperand(2, memoryBase);
}
return store;
}
const wasm::MemoryAccessDesc& access() const { return access_; }
AliasSet getAliasSet() const override {
return AliasSet::Store(AliasSet::WasmHeap);
}
bool hasMemoryBase() const { return numOperands() > 2; }
#ifdef JS_JITSPEW
void getExtras(ExtrasCollector* extras) override {
char buf[64];
SprintfLiteral(buf, "(offs=%lld)", (long long int)access().offset64());
extras->add(buf);
}
#endif
};
class MAsmJSMemoryAccess {
Scalar::Type accessType_;
bool needsBoundsCheck_;
public:
explicit MAsmJSMemoryAccess(Scalar::Type accessType)
: accessType_(accessType), needsBoundsCheck_(true) {
MOZ_ASSERT(accessType != Scalar::Uint8Clamped);
}
Scalar::Type accessType() const { return accessType_; }
unsigned byteSize() const { return TypedArrayElemSize(accessType()); }
bool needsBoundsCheck() const { return needsBoundsCheck_; }
wasm::MemoryAccessDesc access() const {
return wasm::MemoryAccessDesc(0, accessType_, Scalar::byteSize(accessType_),
0, wasm::BytecodeOffset(), false);
}
void removeBoundsCheck() { needsBoundsCheck_ = false; }
};
class MAsmJSLoadHeap
: public MVariadicInstruction, // 1 plus optional memoryBase and
// boundsCheckLimit
public MAsmJSMemoryAccess,
public NoTypePolicy::Data {
uint32_t memoryBaseIndex_;
explicit MAsmJSLoadHeap(uint32_t memoryBaseIndex, Scalar::Type accessType)
: MVariadicInstruction(classOpcode),
MAsmJSMemoryAccess(accessType),
memoryBaseIndex_(memoryBaseIndex) {
setResultType(ScalarTypeToMIRType(accessType));
}
public:
INSTRUCTION_HEADER(AsmJSLoadHeap)
NAMED_OPERANDS((0, base), (1, boundsCheckLimit))
static MAsmJSLoadHeap* New(TempAllocator& alloc, MDefinition* memoryBase,
MDefinition* base, MDefinition* boundsCheckLimit,
Scalar::Type accessType) {
uint32_t nextIndex = 2;
uint32_t memoryBaseIndex = memoryBase ? nextIndex++ : UINT32_MAX;
MAsmJSLoadHeap* load =
new (alloc) MAsmJSLoadHeap(memoryBaseIndex, accessType);
if (!load->init(alloc, nextIndex)) {
return nullptr;
}
load->initOperand(0, base);
load->initOperand(1, boundsCheckLimit);
if (memoryBase) {
load->initOperand(memoryBaseIndex, memoryBase);
}
return load;
}
bool hasMemoryBase() const { return memoryBaseIndex_ != UINT32_MAX; }
MDefinition* memoryBase() const {
MOZ_ASSERT(hasMemoryBase());
return getOperand(memoryBaseIndex_);
}
bool congruentTo(const MDefinition* ins) const override;
AliasSet getAliasSet() const override {
return AliasSet::Load(AliasSet::WasmHeap);
}
AliasType mightAlias(const MDefinition* def) const override;
};
class MAsmJSStoreHeap
: public MVariadicInstruction, // 2 plus optional memoryBase and
// boundsCheckLimit
public MAsmJSMemoryAccess,
public NoTypePolicy::Data {
uint32_t memoryBaseIndex_;
explicit MAsmJSStoreHeap(uint32_t memoryBaseIndex, Scalar::Type accessType)
: MVariadicInstruction(classOpcode),
MAsmJSMemoryAccess(accessType),
memoryBaseIndex_(memoryBaseIndex) {}
public:
INSTRUCTION_HEADER(AsmJSStoreHeap)
NAMED_OPERANDS((0, base), (1, value), (2, boundsCheckLimit))
static MAsmJSStoreHeap* New(TempAllocator& alloc, MDefinition* memoryBase,
MDefinition* base, MDefinition* boundsCheckLimit,
Scalar::Type accessType, MDefinition* v) {
uint32_t nextIndex = 3;
uint32_t memoryBaseIndex = memoryBase ? nextIndex++ : UINT32_MAX;
MAsmJSStoreHeap* store =
new (alloc) MAsmJSStoreHeap(memoryBaseIndex, accessType);
if (!store->init(alloc, nextIndex)) {
return nullptr;
}
store->initOperand(0, base);
store->initOperand(1, v);
store->initOperand(2, boundsCheckLimit);
if (memoryBase) {
store->initOperand(memoryBaseIndex, memoryBase);
}
return store;
}
bool hasMemoryBase() const { return memoryBaseIndex_ != UINT32_MAX; }
MDefinition* memoryBase() const {
MOZ_ASSERT(hasMemoryBase());
return getOperand(memoryBaseIndex_);
}
AliasSet getAliasSet() const override {
return AliasSet::Store(AliasSet::WasmHeap);
}
};
class MWasmCompareExchangeHeap : public MVariadicInstruction,
public NoTypePolicy::Data {
wasm::MemoryAccessDesc access_;
wasm::BytecodeOffset bytecodeOffset_;
explicit MWasmCompareExchangeHeap(const wasm::MemoryAccessDesc& access,
wasm::BytecodeOffset bytecodeOffset)
: MVariadicInstruction(classOpcode),
access_(access),
bytecodeOffset_(bytecodeOffset) {
setGuard(); // Not removable
setResultType(ScalarTypeToMIRType(access.type()));
}
public:
INSTRUCTION_HEADER(WasmCompareExchangeHeap)
NAMED_OPERANDS((0, base), (1, oldValue), (2, newValue), (3, instance),
(4, memoryBase))
static MWasmCompareExchangeHeap* New(TempAllocator& alloc,
wasm::BytecodeOffset bytecodeOffset,
MDefinition* memoryBase,
MDefinition* base,
const wasm::MemoryAccessDesc& access,
MDefinition* oldv, MDefinition* newv,
MDefinition* instance) {
MWasmCompareExchangeHeap* cas =
new (alloc) MWasmCompareExchangeHeap(access, bytecodeOffset);
if (!cas->init(alloc, 4 + !!memoryBase)) {
return nullptr;
}
cas->initOperand(0, base);
cas->initOperand(1, oldv);
cas->initOperand(2, newv);
cas->initOperand(3, instance);
if (memoryBase) {
cas->initOperand(4, memoryBase);
}
return cas;
}
const wasm::MemoryAccessDesc& access() const { return access_; }
wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
AliasSet getAliasSet() const override {
return AliasSet::Store(AliasSet::WasmHeap);
}
bool hasMemoryBase() const { return numOperands() > 4; }
};
class MWasmAtomicExchangeHeap : public MVariadicInstruction,
public NoTypePolicy::Data {
wasm::MemoryAccessDesc access_;
wasm::BytecodeOffset bytecodeOffset_;
explicit MWasmAtomicExchangeHeap(const wasm::MemoryAccessDesc& access,
wasm::BytecodeOffset bytecodeOffset)
: MVariadicInstruction(classOpcode),
access_(access),
bytecodeOffset_(bytecodeOffset) {
setGuard(); // Not removable
setResultType(ScalarTypeToMIRType(access.type()));
}
public:
INSTRUCTION_HEADER(WasmAtomicExchangeHeap)
NAMED_OPERANDS((0, base), (1, value), (2, instance), (3, memoryBase))
static MWasmAtomicExchangeHeap* New(TempAllocator& alloc,
wasm::BytecodeOffset bytecodeOffset,
MDefinition* memoryBase,
MDefinition* base,
const wasm::MemoryAccessDesc& access,
MDefinition* value,
MDefinition* instance) {
MWasmAtomicExchangeHeap* xchg =
new (alloc) MWasmAtomicExchangeHeap(access, bytecodeOffset);
if (!xchg->init(alloc, 3 + !!memoryBase)) {
return nullptr;
}
xchg->initOperand(0, base);
xchg->initOperand(1, value);
xchg->initOperand(2, instance);
if (memoryBase) {
xchg->initOperand(3, memoryBase);
}
return xchg;
}
const wasm::MemoryAccessDesc& access() const { return access_; }
wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
AliasSet getAliasSet() const override {
return AliasSet::Store(AliasSet::WasmHeap);
}
bool hasMemoryBase() const { return numOperands() > 3; }
};
class MWasmAtomicBinopHeap : public MVariadicInstruction,
public NoTypePolicy::Data {
AtomicOp op_;
wasm::MemoryAccessDesc access_;
wasm::BytecodeOffset bytecodeOffset_;
explicit MWasmAtomicBinopHeap(AtomicOp op,
const wasm::MemoryAccessDesc& access,
wasm::BytecodeOffset bytecodeOffset)
: MVariadicInstruction(classOpcode),
op_(op),
access_(access),
bytecodeOffset_(bytecodeOffset) {
setGuard(); // Not removable
setResultType(ScalarTypeToMIRType(access.type()));
}
public:
INSTRUCTION_HEADER(WasmAtomicBinopHeap)
NAMED_OPERANDS((0, base), (1, value), (2, instance), (3, memoryBase))
static MWasmAtomicBinopHeap* New(TempAllocator& alloc,
wasm::BytecodeOffset bytecodeOffset,
AtomicOp op, MDefinition* memoryBase,
MDefinition* base,
const wasm::MemoryAccessDesc& access,
MDefinition* v, MDefinition* instance) {
MWasmAtomicBinopHeap* binop =
new (alloc) MWasmAtomicBinopHeap(op, access, bytecodeOffset);
if (!binop->init(alloc, 3 + !!memoryBase)) {
return nullptr;
}
binop->initOperand(0, base);
binop->initOperand(1, v);
binop->initOperand(2, instance);
if (memoryBase) {
binop->initOperand(3, memoryBase);
}
return binop;
}
AtomicOp operation() const { return op_; }
const wasm::MemoryAccessDesc& access() const { return access_; }
wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
AliasSet getAliasSet() const override {
return AliasSet::Store(AliasSet::WasmHeap);
}
bool hasMemoryBase() const { return numOperands() > 3; }
};
class MWasmLoadInstanceDataField : public MUnaryInstruction,
public NoTypePolicy::Data {
MWasmLoadInstanceDataField(MIRType type, unsigned instanceDataOffset,
bool isConstant, MDefinition* instance)
: MUnaryInstruction(classOpcode, instance),
instanceDataOffset_(instanceDataOffset),
isConstant_(isConstant) {
MOZ_ASSERT(IsNumberType(type) || type == MIRType::Simd128 ||
type == MIRType::Pointer || type == MIRType::WasmAnyRef);
setResultType(type);
setMovable();
}
unsigned instanceDataOffset_;
bool isConstant_;
public:
INSTRUCTION_HEADER(WasmLoadInstanceDataField)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, instance))
unsigned instanceDataOffset() const { return instanceDataOffset_; }
HashNumber valueHash() const override;
bool congruentTo(const MDefinition* ins) const override;
MDefinition* foldsTo(TempAllocator& alloc) override;
AliasSet getAliasSet() const override {
return isConstant_ ? AliasSet::None()
: AliasSet::Load(AliasSet::WasmInstanceData);
}
AliasType mightAlias(const MDefinition* def) const override;
#ifdef JS_JITSPEW
void getExtras(ExtrasCollector* extras) override {
char buf[96];
SprintfLiteral(buf, "(offs=%lld, isConst=%s)",
(long long int)instanceDataOffset_,
isConstant_ ? "true" : "false");
extras->add(buf);
}
#endif
};
class MWasmLoadGlobalCell : public MUnaryInstruction,
public NoTypePolicy::Data {
MWasmLoadGlobalCell(MIRType type, MDefinition* cellPtr)
: MUnaryInstruction(classOpcode, cellPtr) {
setResultType(type);
setMovable();
}
public:
INSTRUCTION_HEADER(WasmLoadGlobalCell)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, cellPtr))
// The default valueHash is good enough, because there are no non-operand
// fields.
bool congruentTo(const MDefinition* ins) const override;
AliasSet getAliasSet() const override {
return AliasSet::Load(AliasSet::WasmGlobalCell);
}
AliasType mightAlias(const MDefinition* def) const override;
};
class MWasmLoadTableElement : public MBinaryInstruction,
public NoTypePolicy::Data {
MWasmLoadTableElement(MDefinition* elements, MDefinition* index)
: MBinaryInstruction(classOpcode, elements, index) {
setResultType(MIRType::WasmAnyRef);
setMovable();
}
public:
INSTRUCTION_HEADER(WasmLoadTableElement)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, elements))
NAMED_OPERANDS((1, index))
AliasSet getAliasSet() const override {
return AliasSet::Load(AliasSet::WasmTableElement);
}
};
class MWasmStoreInstanceDataField : public MBinaryInstruction,
public NoTypePolicy::Data {
MWasmStoreInstanceDataField(unsigned instanceDataOffset, MDefinition* value,
MDefinition* instance)
: MBinaryInstruction(classOpcode, value, instance),
instanceDataOffset_(instanceDataOffset) {}
unsigned instanceDataOffset_;
public:
INSTRUCTION_HEADER(WasmStoreInstanceDataField)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, value), (1, instance))
unsigned instanceDataOffset() const { return instanceDataOffset_; }
AliasSet getAliasSet() const override {
return AliasSet::Store(AliasSet::WasmInstanceData);
}
};
class MWasmStoreGlobalCell : public MBinaryInstruction,
public NoTypePolicy::Data {
MWasmStoreGlobalCell(MDefinition* value, MDefinition* cellPtr)
: MBinaryInstruction(classOpcode, value, cellPtr) {}
public:
INSTRUCTION_HEADER(WasmStoreGlobalCell)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, value), (1, cellPtr))
AliasSet getAliasSet() const override {
return AliasSet::Store(AliasSet::WasmGlobalCell);
}
};
class MWasmStoreStackResult : public MBinaryInstruction,
public NoTypePolicy::Data {
MWasmStoreStackResult(MDefinition* stackResultArea, uint32_t offset,
MDefinition* value)
: MBinaryInstruction(classOpcode, stackResultArea, value),
offset_(offset) {}
uint32_t offset_;
public:
INSTRUCTION_HEADER(WasmStoreStackResult)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, stackResultArea), (1, value))
uint32_t offset() const { return offset_; }
AliasSet getAliasSet() const override {
return AliasSet::Store(AliasSet::WasmStackResult);
}
};
// Represents a known-good derived pointer into an object or memory region (in
// the most general sense) that will not move while the derived pointer is live.
// The `offset` *must* be a valid offset into the object represented by `base`;
// hence overflow in the address calculation will never be an issue. `offset`
// must be representable as a 31-bit unsigned integer.
//
// DO NOT use this with a base value of any JS-heap-resident object type.
// Such a value would need to be adjusted during GC, yet we have no mechanism
// to do that. See bug 1810090.
class MWasmDerivedPointer : public MUnaryInstruction,
public NoTypePolicy::Data {
MWasmDerivedPointer(MDefinition* base, size_t offset)
: MUnaryInstruction(classOpcode, base), offset_(uint32_t(offset)) {
MOZ_ASSERT(offset <= INT32_MAX);
// Do not change this to allow `base` to be a GC-heap allocated type.
MOZ_ASSERT(base->type() == MIRType::Pointer ||
base->type() == TargetWordMIRType());
setResultType(MIRType::Pointer);
setMovable();
}
uint32_t offset_;
public:
INSTRUCTION_HEADER(WasmDerivedPointer)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, base))
uint32_t offset() const { return offset_; }
AliasSet getAliasSet() const override { return AliasSet::None(); }
bool congruentTo(const MDefinition* ins) const override {
return congruentIfOperandsEqual(ins) &&
ins->toWasmDerivedPointer()->offset() == offset();
}
#ifdef JS_JITSPEW
void getExtras(ExtrasCollector* extras) override {
char buf[64];
SprintfLiteral(buf, "(offs=%lld)", (long long int)offset_);
extras->add(buf);
}
#endif
ALLOW_CLONE(MWasmDerivedPointer)
};
// As with MWasmDerivedPointer, DO NOT use this with a base value of any
// JS-heap-resident object type.
class MWasmDerivedIndexPointer : public MBinaryInstruction,
public NoTypePolicy::Data {
MWasmDerivedIndexPointer(MDefinition* base, MDefinition* index, Scale scale)
: MBinaryInstruction(classOpcode, base, index), scale_(scale) {
// Do not change this to allow `base` to be a GC-heap allocated type.
MOZ_ASSERT(base->type() == MIRType::Pointer);
setResultType(MIRType::Pointer);
setMovable();
}
Scale scale_;
public:
INSTRUCTION_HEADER(WasmDerivedIndexPointer)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, base))
NAMED_OPERANDS((1, index))
Scale scale() const { return scale_; }
AliasSet getAliasSet() const override { return AliasSet::None(); }
bool congruentTo(const MDefinition* ins) const override {
return congruentIfOperandsEqual(ins) &&
ins->toWasmDerivedIndexPointer()->scale() == scale();
}
ALLOW_CLONE(MWasmDerivedIndexPointer)
};
// Whether to perform a pre-write barrier for a wasm store reference.
enum class WasmPreBarrierKind : uint8_t { None, Normal };
// Stores a reference to an address. This performs a pre-barrier on the address,
// but not a post-barrier. A post-barrier must be performed separately, if it's
// required. The accessed location is `valueBase + valueOffset`. The latter
// must be be representable as a 31-bit unsigned integer.
class MWasmStoreRef : public MAryInstruction<3>, public NoTypePolicy::Data {
uint32_t offset_;
AliasSet::Flag aliasSet_;
WasmPreBarrierKind preBarrierKind_;
MWasmStoreRef(MDefinition* instance, MDefinition* valueBase,
size_t valueOffset, MDefinition* value, AliasSet::Flag aliasSet,
WasmPreBarrierKind preBarrierKind)
: MAryInstruction<3>(classOpcode),
offset_(uint32_t(valueOffset)),
aliasSet_(aliasSet),
preBarrierKind_(preBarrierKind) {
MOZ_ASSERT(valueOffset <= INT32_MAX);
MOZ_ASSERT(valueBase->type() == MIRType::Pointer ||
valueBase->type() == MIRType::StackResults);
MOZ_ASSERT(value->type() == MIRType::WasmAnyRef);
initOperand(0, instance);
initOperand(1, valueBase);
initOperand(2, value);
}
public:
INSTRUCTION_HEADER(WasmStoreRef)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, instance), (1, valueBase), (2, value))
uint32_t offset() const { return offset_; }
AliasSet getAliasSet() const override { return AliasSet::Store(aliasSet_); }
WasmPreBarrierKind preBarrierKind() const { return preBarrierKind_; }
#ifdef JS_JITSPEW
void getExtras(ExtrasCollector* extras) override {
char buf[64];
SprintfLiteral(buf, "(offs=%lld)", (long long int)offset_);
extras->add(buf);
}
#endif
};
// Given a value being written to another object, update the generational store
// buffer if the value is in the nursery and object is in the tenured heap.
class MWasmPostWriteBarrierImmediate : public MQuaternaryInstruction,
public NoTypePolicy::Data {
uint32_t valueOffset_;
MWasmPostWriteBarrierImmediate(MDefinition* instance, MDefinition* object,
MDefinition* valueBase, uint32_t valueOffset,
MDefinition* value)
: MQuaternaryInstruction(classOpcode, instance, object, valueBase, value),
valueOffset_(valueOffset) {
setGuard();
}
public:
INSTRUCTION_HEADER(WasmPostWriteBarrierImmediate)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, instance), (1, object), (2, valueBase), (3, value))
AliasSet getAliasSet() const override { return AliasSet::None(); }
uint32_t valueOffset() const { return valueOffset_; }
ALLOW_CLONE(MWasmPostWriteBarrierImmediate)
};
// Given a value being written to another object, update the generational store
// buffer if the value is in the nursery and object is in the tenured heap.
class MWasmPostWriteBarrierIndex : public MAryInstruction<5>,
public NoTypePolicy::Data {
uint32_t elemSize_;
MWasmPostWriteBarrierIndex(MDefinition* instance, MDefinition* object,
MDefinition* valueBase, MDefinition* index,
uint32_t scale, MDefinition* value)
: MAryInstruction<5>(classOpcode), elemSize_(scale) {
initOperand(0, instance);
initOperand(1, object);
initOperand(2, valueBase);
initOperand(3, index);
initOperand(4, value);
setGuard();
}
public:
INSTRUCTION_HEADER(WasmPostWriteBarrierIndex)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, instance), (1, object), (2, valueBase), (3, index),
(4, value))
AliasSet getAliasSet() const override { return AliasSet::None(); }
uint32_t elemSize() const { return elemSize_; }
ALLOW_CLONE(MWasmPostWriteBarrierIndex)
};
class MWasmParameter : public MNullaryInstruction {
ABIArg abi_;
MWasmParameter(ABIArg abi, MIRType mirType)
: MNullaryInstruction(classOpcode), abi_(abi) {
setResultType(mirType);
}
public:
INSTRUCTION_HEADER(WasmParameter)
TRIVIAL_NEW_WRAPPERS
ABIArg abi() const { return abi_; }
};
class MWasmReturn : public MAryControlInstruction<2, 0>,
public NoTypePolicy::Data {
MWasmReturn(MDefinition* ins, MDefinition* instance)
: MAryControlInstruction(classOpcode) {
initOperand(0, ins);
initOperand(1, instance);
}
public:
INSTRUCTION_HEADER(WasmReturn)
TRIVIAL_NEW_WRAPPERS
AliasSet getAliasSet() const override { return AliasSet::None(); }
};
class MWasmReturnVoid : public MAryControlInstruction<1, 0>,
public NoTypePolicy::Data {
explicit MWasmReturnVoid(MDefinition* instance)
: MAryControlInstruction(classOpcode) {
initOperand(0, instance);
}
public:
INSTRUCTION_HEADER(WasmReturnVoid)
TRIVIAL_NEW_WRAPPERS
AliasSet getAliasSet() const override { return AliasSet::None(); }
};
class MWasmStackArg : public MUnaryInstruction, public NoTypePolicy::Data {
MWasmStackArg(uint32_t spOffset, MDefinition* ins)
: MUnaryInstruction(classOpcode, ins), spOffset_(spOffset) {}
uint32_t spOffset_;
public:
INSTRUCTION_HEADER(WasmStackArg)
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, arg))
uint32_t spOffset() const { return spOffset_; }
void incrementOffset(uint32_t inc) { spOffset_ += inc; }
};
template <typename Location>
class MWasmResultBase : public MNullaryInstruction {
Location loc_;
protected:
MWasmResultBase(Opcode op, MIRType type, Location loc)
: MNullaryInstruction(op), loc_(loc) {
setResultType(type);
setCallResultCapture();
}
public:
Location loc() { return loc_; }
};
class MWasmRegisterResult : public MWasmResultBase<Register> {
MWasmRegisterResult(MIRType type, Register reg)
: MWasmResultBase(classOpcode, type, reg) {}
public:
INSTRUCTION_HEADER(WasmRegisterResult)
TRIVIAL_NEW_WRAPPERS
};
class MWasmFloatRegisterResult : public MWasmResultBase<FloatRegister> {
MWasmFloatRegisterResult(MIRType type, FloatRegister reg)
: MWasmResultBase(classOpcode, type, reg) {}
public:
INSTRUCTION_HEADER(WasmFloatRegisterResult)
TRIVIAL_NEW_WRAPPERS
};
class MWasmRegister64Result : public MWasmResultBase<Register64> {
explicit MWasmRegister64Result(Register64 reg)
: MWasmResultBase(classOpcode, MIRType::Int64, reg) {}
public:
INSTRUCTION_HEADER(WasmRegister64Result)
TRIVIAL_NEW_WRAPPERS
};
class MWasmStackResultArea : public MNullaryInstruction {
public:
class StackResult {
// Offset in bytes from lowest address of stack result area.
uint32_t offset_;
MIRType type_;
public:
StackResult() : type_(MIRType::Undefined) {}
StackResult(uint32_t offset, MIRType type) : offset_(offset), type_(type) {}
bool initialized() const { return type_ != MIRType::Undefined; }
uint32_t offset() const {
MOZ_ASSERT(initialized());
return offset_;
}
MIRType type() const {
MOZ_ASSERT(initialized());
return type_;
}
uint32_t endOffset() const {
return offset() + wasm::MIRTypeToABIResultSize(type());
}
};
private:
FixedList<StackResult> results_;