summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmBCFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/wasm/WasmBCFrame.cpp544
1 files changed, 544 insertions, 0 deletions
diff --git a/js/src/wasm/WasmBCFrame.cpp b/js/src/wasm/WasmBCFrame.cpp
new file mode 100644
index 0000000000..454f732823
--- /dev/null
+++ b/js/src/wasm/WasmBCFrame.cpp
@@ -0,0 +1,544 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ *
+ * Copyright 2016 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.
+ */
+
+#include "wasm/WasmBCFrame.h"
+
+#include "wasm/WasmBaselineCompile.h" // For BaseLocalIter
+#include "wasm/WasmBCClass.h"
+
+#include "jit/MacroAssembler-inl.h"
+#include "wasm/WasmBCClass-inl.h"
+#include "wasm/WasmBCCodegen-inl.h"
+#include "wasm/WasmBCRegDefs-inl.h"
+#include "wasm/WasmBCRegMgmt-inl.h"
+#include "wasm/WasmBCStkMgmt-inl.h"
+
+namespace js {
+namespace wasm {
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// BaseLocalIter methods.
+
+BaseLocalIter::BaseLocalIter(const ValTypeVector& locals,
+ const ArgTypeVector& args, bool debugEnabled)
+ : locals_(locals),
+ args_(args),
+ argsIter_(args_),
+ index_(0),
+ frameSize_(0),
+ nextFrameSize_(debugEnabled ? DebugFrame::offsetOfFrame() : 0),
+ frameOffset_(INT32_MAX),
+ stackResultPointerOffset_(INT32_MAX),
+ mirType_(MIRType::Undefined),
+ done_(false) {
+ MOZ_ASSERT(args.lengthWithoutStackResults() <= locals.length());
+ settle();
+}
+
+int32_t BaseLocalIter::pushLocal(size_t nbytes) {
+ MOZ_ASSERT(nbytes % 4 == 0 && nbytes <= 16);
+ nextFrameSize_ = AlignBytes(frameSize_, nbytes) + nbytes;
+ return nextFrameSize_; // Locals grow down so capture base address.
+}
+
+void BaseLocalIter::settle() {
+ MOZ_ASSERT(!done_);
+ frameSize_ = nextFrameSize_;
+
+ if (!argsIter_.done()) {
+ mirType_ = argsIter_.mirType();
+ MIRType concreteType = mirType_;
+ switch (mirType_) {
+ case MIRType::StackResults:
+ // The pointer to stack results is handled like any other argument:
+ // either addressed in place if it is passed on the stack, or we spill
+ // it in the frame if it's in a register.
+ MOZ_ASSERT(args_.isSyntheticStackResultPointerArg(index_));
+ concreteType = MIRType::Pointer;
+ [[fallthrough]];
+ case MIRType::Int32:
+ case MIRType::Int64:
+ case MIRType::Double:
+ case MIRType::Float32:
+ case MIRType::RefOrNull:
+#ifdef ENABLE_WASM_SIMD
+ case MIRType::Simd128:
+#endif
+ if (argsIter_->argInRegister()) {
+ frameOffset_ = pushLocal(MIRTypeToSize(concreteType));
+ } else {
+ frameOffset_ = -(argsIter_->offsetFromArgBase() + sizeof(Frame));
+ }
+ break;
+ default:
+ MOZ_CRASH("Argument type");
+ }
+ if (mirType_ == MIRType::StackResults) {
+ stackResultPointerOffset_ = frameOffset();
+ // Advance past the synthetic stack result pointer argument and fall
+ // through to the next case.
+ argsIter_++;
+ frameSize_ = nextFrameSize_;
+ MOZ_ASSERT(argsIter_.done());
+ } else {
+ return;
+ }
+ }
+
+ if (index_ < locals_.length()) {
+ switch (locals_[index_].kind()) {
+ case ValType::I32:
+ case ValType::I64:
+ case ValType::F32:
+ case ValType::F64:
+#ifdef ENABLE_WASM_SIMD
+ case ValType::V128:
+#endif
+ case ValType::Ref:
+ // TODO/AnyRef-boxing: With boxed immediates and strings, the
+ // debugger must be made aware that AnyRef != Pointer.
+ ASSERT_ANYREF_IS_JSOBJECT;
+ mirType_ = locals_[index_].toMIRType();
+ frameOffset_ = pushLocal(MIRTypeToSize(mirType_));
+ break;
+ default:
+ MOZ_CRASH("Compiler bug: Unexpected local type");
+ }
+ return;
+ }
+
+ done_ = true;
+}
+
+void BaseLocalIter::operator++(int) {
+ MOZ_ASSERT(!done_);
+ index_++;
+ if (!argsIter_.done()) {
+ argsIter_++;
+ }
+ settle();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Stack map methods.
+
+bool BaseCompiler::createStackMap(const char* who) {
+ const ExitStubMapVector noExtras;
+ return stackMapGenerator_.createStackMap(who, noExtras, masm.currentOffset(),
+ HasDebugFrameWithLiveRefs::No, stk_);
+}
+
+bool BaseCompiler::createStackMap(const char* who, CodeOffset assemblerOffset) {
+ const ExitStubMapVector noExtras;
+ return stackMapGenerator_.createStackMap(who, noExtras,
+ assemblerOffset.offset(),
+ HasDebugFrameWithLiveRefs::No, stk_);
+}
+
+bool BaseCompiler::createStackMap(
+ const char* who, HasDebugFrameWithLiveRefs debugFrameWithLiveRefs) {
+ const ExitStubMapVector noExtras;
+ return stackMapGenerator_.createStackMap(who, noExtras, masm.currentOffset(),
+ debugFrameWithLiveRefs, stk_);
+}
+
+bool BaseCompiler::createStackMap(
+ const char* who, const ExitStubMapVector& extras, uint32_t assemblerOffset,
+ HasDebugFrameWithLiveRefs debugFrameWithLiveRefs) {
+ return stackMapGenerator_.createStackMap(who, extras, assemblerOffset,
+ debugFrameWithLiveRefs, stk_);
+}
+
+bool MachineStackTracker::cloneTo(MachineStackTracker* dst) {
+ MOZ_ASSERT(dst->vec_.empty());
+ if (!dst->vec_.appendAll(vec_)) {
+ return false;
+ }
+ dst->numPtrs_ = numPtrs_;
+ return true;
+}
+
+bool StackMapGenerator::generateStackmapEntriesForTrapExit(
+ const ArgTypeVector& args, ExitStubMapVector* extras) {
+ return GenerateStackmapEntriesForTrapExit(args, trapExitLayout_,
+ trapExitLayoutNumWords_, extras);
+}
+
+bool StackMapGenerator::createStackMap(
+ const char* who, const ExitStubMapVector& extras, uint32_t assemblerOffset,
+ HasDebugFrameWithLiveRefs debugFrameWithLiveRefs, const StkVector& stk) {
+ size_t countedPointers = machineStackTracker.numPtrs() + memRefsOnStk;
+#ifndef DEBUG
+ // An important optimization. If there are obviously no pointers, as
+ // we expect in the majority of cases, exit quickly.
+ if (countedPointers == 0 &&
+ debugFrameWithLiveRefs == HasDebugFrameWithLiveRefs::No) {
+ // We can skip creating the map if there are no |true| elements in
+ // |extras|.
+ bool extrasHasRef = false;
+ for (bool b : extras) {
+ if (b) {
+ extrasHasRef = true;
+ break;
+ }
+ }
+ if (!extrasHasRef) {
+ return true;
+ }
+ }
+#else
+ // In the debug case, create the stackmap regardless, and cross-check
+ // the pointer-counting below. We expect the final map to have
+ // |countedPointers| in total. This doesn't include those in the
+ // DebugFrame, but they do not appear in the map's bitmap. Note that
+ // |countedPointers| is debug-only from this point onwards.
+ for (bool b : extras) {
+ countedPointers += (b ? 1 : 0);
+ }
+#endif
+
+ // Start with the frame-setup map, and add operand-stack information to
+ // that. augmentedMst holds live data only within individual calls to
+ // createStackMap.
+ augmentedMst.clear();
+ if (!machineStackTracker.cloneTo(&augmentedMst)) {
+ return false;
+ }
+
+ // At this point, augmentedMst only contains entries covering the
+ // incoming argument area (if any) and for the area allocated by this
+ // function's prologue. We now need to calculate how far the machine's
+ // stack pointer is below where it was at the start of the body. But we
+ // must take care not to include any words pushed as arguments to an
+ // upcoming function call, since those words "belong" to the stackmap of
+ // the callee, not to the stackmap of this function. Note however that
+ // any alignment padding pushed prior to pushing the args *does* belong to
+ // this function.
+ //
+ // That padding is taken into account at the point where
+ // framePushedExcludingOutboundCallArgs is set, viz, in startCallArgs(),
+ // and comprises two components:
+ //
+ // * call->frameAlignAdjustment
+ // * the padding applied to the stack arg area itself. That is:
+ // StackArgAreaSize(argTys) - StackArgAreaSizeUnpadded(argTys)
+ Maybe<uint32_t> framePushedExcludingArgs;
+ if (framePushedAtEntryToBody.isNothing()) {
+ // Still in the prologue. framePushedExcludingArgs remains Nothing.
+ MOZ_ASSERT(framePushedExcludingOutboundCallArgs.isNothing());
+ } else {
+ // In the body.
+ MOZ_ASSERT(masm_.framePushed() >= framePushedAtEntryToBody.value());
+ if (framePushedExcludingOutboundCallArgs.isSome()) {
+ // In the body, and we've potentially pushed some args onto the stack.
+ // We must ignore them when sizing the stackmap.
+ MOZ_ASSERT(masm_.framePushed() >=
+ framePushedExcludingOutboundCallArgs.value());
+ MOZ_ASSERT(framePushedExcludingOutboundCallArgs.value() >=
+ framePushedAtEntryToBody.value());
+ framePushedExcludingArgs =
+ Some(framePushedExcludingOutboundCallArgs.value());
+ } else {
+ // In the body, but not with call args on the stack. The stackmap
+ // must be sized so as to extend all the way "down" to
+ // masm_.framePushed().
+ framePushedExcludingArgs = Some(masm_.framePushed());
+ }
+ }
+
+ if (framePushedExcludingArgs.isSome()) {
+ uint32_t bodyPushedBytes =
+ framePushedExcludingArgs.value() - framePushedAtEntryToBody.value();
+ MOZ_ASSERT(0 == bodyPushedBytes % sizeof(void*));
+ if (!augmentedMst.pushNonGCPointers(bodyPushedBytes / sizeof(void*))) {
+ return false;
+ }
+ }
+
+ // Scan the operand stack, marking pointers in the just-added new
+ // section.
+ MOZ_ASSERT_IF(framePushedAtEntryToBody.isNothing(), stk.empty());
+ MOZ_ASSERT_IF(framePushedExcludingArgs.isNothing(), stk.empty());
+
+ for (const Stk& v : stk) {
+#ifndef DEBUG
+ // We don't track roots in registers, per rationale below, so if this
+ // doesn't hold, something is seriously wrong, and we're likely to get a
+ // GC-related crash.
+ MOZ_RELEASE_ASSERT(v.kind() != Stk::RegisterRef);
+ if (v.kind() != Stk::MemRef) {
+ continue;
+ }
+#else
+ // Take the opportunity to check everything we reasonably can about
+ // operand stack elements.
+ switch (v.kind()) {
+ case Stk::MemI32:
+ case Stk::MemI64:
+ case Stk::MemF32:
+ case Stk::MemF64:
+ case Stk::ConstI32:
+ case Stk::ConstI64:
+ case Stk::ConstF32:
+ case Stk::ConstF64:
+# ifdef ENABLE_WASM_SIMD
+ case Stk::MemV128:
+ case Stk::ConstV128:
+# endif
+ // All of these have uninteresting type.
+ continue;
+ case Stk::LocalI32:
+ case Stk::LocalI64:
+ case Stk::LocalF32:
+ case Stk::LocalF64:
+# ifdef ENABLE_WASM_SIMD
+ case Stk::LocalV128:
+# endif
+ // These also have uninteresting type. Check that they live in the
+ // section of stack set up by beginFunction(). The unguarded use of
+ // |value()| here is safe due to the assertion above this loop.
+ MOZ_ASSERT(v.offs() <= framePushedAtEntryToBody.value());
+ continue;
+ case Stk::RegisterI32:
+ case Stk::RegisterI64:
+ case Stk::RegisterF32:
+ case Stk::RegisterF64:
+# ifdef ENABLE_WASM_SIMD
+ case Stk::RegisterV128:
+# endif
+ // These also have uninteresting type, but more to the point: all
+ // registers holding live values should have been flushed to the
+ // machine stack immediately prior to the instruction to which this
+ // stackmap pertains. So these can't happen.
+ MOZ_CRASH("createStackMap: operand stack has Register-non-Ref");
+ case Stk::MemRef:
+ // This is the only case we care about. We'll handle it after the
+ // switch.
+ break;
+ case Stk::LocalRef:
+ // We need the stackmap to mention this pointer, but it should
+ // already be in the machineStackTracker section created by
+ // beginFunction().
+ MOZ_ASSERT(v.offs() <= framePushedAtEntryToBody.value());
+ continue;
+ case Stk::ConstRef:
+ // This can currently only be a null pointer.
+ MOZ_ASSERT(v.refval() == 0);
+ continue;
+ case Stk::RegisterRef:
+ // This can't happen, per rationale above.
+ MOZ_CRASH("createStackMap: operand stack contains RegisterRef");
+ default:
+ MOZ_CRASH("createStackMap: unknown operand stack element");
+ }
+#endif
+ // v.offs() holds masm.framePushed() at the point immediately after it
+ // was pushed on the stack. Since it's still on the stack,
+ // masm.framePushed() can't be less.
+ MOZ_ASSERT(v.offs() <= framePushedExcludingArgs.value());
+ uint32_t offsFromMapLowest = framePushedExcludingArgs.value() - v.offs();
+ MOZ_ASSERT(0 == offsFromMapLowest % sizeof(void*));
+ augmentedMst.setGCPointer(offsFromMapLowest / sizeof(void*));
+ }
+
+ // Create the final StackMap. The initial map is zeroed out, so there's
+ // no need to write zero bits in it.
+ const uint32_t extraWords = extras.length();
+ const uint32_t augmentedMstWords = augmentedMst.length();
+ const uint32_t numMappedWords = extraWords + augmentedMstWords;
+ StackMap* stackMap = StackMap::create(numMappedWords);
+ if (!stackMap) {
+ return false;
+ }
+
+ {
+ // First the exit stub extra words, if any.
+ uint32_t i = 0;
+ for (bool b : extras) {
+ if (b) {
+ stackMap->setBit(i);
+ }
+ i++;
+ }
+ }
+ {
+ // Followed by the "main" part of the map.
+ //
+ // This is really just a bit-array copy, so it is reasonable to ask
+ // whether the representation of MachineStackTracker could be made more
+ // similar to that of StackMap, so that the copy could be done with
+ // `memcpy`. Unfortunately it's not so simple; see comment on `class
+ // MachineStackTracker` for details.
+ MachineStackTracker::Iter iter(augmentedMst);
+ while (true) {
+ size_t i = iter.get();
+ if (i == MachineStackTracker::Iter::FINISHED) {
+ break;
+ }
+ stackMap->setBit(extraWords + i);
+ }
+ }
+
+ stackMap->setExitStubWords(extraWords);
+
+ // Record in the map, how far down from the highest address the Frame* is.
+ // Take the opportunity to check that we haven't marked any part of the
+ // Frame itself as a pointer.
+ stackMap->setFrameOffsetFromTop(numStackArgWords +
+ sizeof(Frame) / sizeof(void*));
+#ifdef DEBUG
+ for (uint32_t i = 0; i < sizeof(Frame) / sizeof(void*); i++) {
+ MOZ_ASSERT(stackMap->getBit(stackMap->header.numMappedWords -
+ stackMap->header.frameOffsetFromTop + i) == 0);
+ }
+#endif
+
+ // Note the presence of a DebugFrame with live pointers, if any.
+ if (debugFrameWithLiveRefs != HasDebugFrameWithLiveRefs::No) {
+ stackMap->setHasDebugFrameWithLiveRefs();
+ }
+
+ // Add the completed map to the running collection thereof.
+ if (!stackMaps_->add((uint8_t*)(uintptr_t)assemblerOffset, stackMap)) {
+ stackMap->destroy();
+ return false;
+ }
+
+#ifdef DEBUG
+ {
+ // Crosscheck the map pointer counting.
+ uint32_t nw = stackMap->header.numMappedWords;
+ uint32_t np = 0;
+ for (uint32_t i = 0; i < nw; i++) {
+ np += stackMap->getBit(i);
+ }
+ MOZ_ASSERT(size_t(np) == countedPointers);
+ }
+#endif
+
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Stack frame methods.
+
+void BaseStackFrame::zeroLocals(BaseRegAlloc* ra) {
+ MOZ_ASSERT(varLow_ != UINT32_MAX);
+
+ if (varLow_ == varHigh_) {
+ return;
+ }
+
+ static const uint32_t wordSize = sizeof(void*);
+
+ // The adjustments to 'low' by the size of the item being stored compensates
+ // for the fact that locals offsets are the offsets from Frame to the bytes
+ // directly "above" the locals in the locals area. See comment at Local.
+
+ // On 64-bit systems we may have 32-bit alignment for the local area as it
+ // may be preceded by parameters and prologue/debug data.
+
+ uint32_t low = varLow_;
+ if (low % wordSize) {
+ masm.store32(Imm32(0), Address(sp_, localOffset(low + 4)));
+ low += 4;
+ }
+ MOZ_ASSERT(low % wordSize == 0);
+
+ const uint32_t high = AlignBytes(varHigh_, wordSize);
+
+ // An UNROLL_LIMIT of 16 is chosen so that we only need an 8-bit signed
+ // immediate to represent the offset in the store instructions in the loop
+ // on x64.
+
+ const uint32_t UNROLL_LIMIT = 16;
+ const uint32_t initWords = (high - low) / wordSize;
+ const uint32_t tailWords = initWords % UNROLL_LIMIT;
+ const uint32_t loopHigh = high - (tailWords * wordSize);
+
+ // With only one word to initialize, just store an immediate zero.
+
+ if (initWords == 1) {
+ masm.storePtr(ImmWord(0), Address(sp_, localOffset(low + wordSize)));
+ return;
+ }
+
+ // For other cases, it's best to have a zero in a register.
+ //
+ // One can do more here with SIMD registers (store 16 bytes at a time) or
+ // with instructions like STRD on ARM (store 8 bytes at a time), but that's
+ // for another day.
+
+ RegI32 zero = ra->needI32();
+ masm.mov(ImmWord(0), zero);
+
+ // For the general case we want to have a loop body of UNROLL_LIMIT stores
+ // and then a tail of less than UNROLL_LIMIT stores. When initWords is less
+ // than 2*UNROLL_LIMIT the loop trip count is at most 1 and there is no
+ // benefit to having the pointer calculations and the compare-and-branch.
+ // So we completely unroll when we have initWords < 2 * UNROLL_LIMIT. (In
+ // this case we'll end up using 32-bit offsets on x64 for up to half of the
+ // stores, though.)
+
+ // Fully-unrolled case.
+
+ if (initWords < 2 * UNROLL_LIMIT) {
+ for (uint32_t i = low; i < high; i += wordSize) {
+ masm.storePtr(zero, Address(sp_, localOffset(i + wordSize)));
+ }
+ ra->freeI32(zero);
+ return;
+ }
+
+ // Unrolled loop with a tail. Stores will use negative offsets. That's OK
+ // for x86 and ARM, at least.
+
+ // Compute pointer to the highest-addressed slot on the frame.
+ RegI32 p = ra->needI32();
+ masm.computeEffectiveAddress(Address(sp_, localOffset(low + wordSize)), p);
+
+ // Compute pointer to the lowest-addressed slot on the frame that will be
+ // initialized by the loop body.
+ RegI32 lim = ra->needI32();
+ masm.computeEffectiveAddress(Address(sp_, localOffset(loopHigh + wordSize)),
+ lim);
+
+ // The loop body. Eventually we'll have p == lim and exit the loop.
+ Label again;
+ masm.bind(&again);
+ for (uint32_t i = 0; i < UNROLL_LIMIT; ++i) {
+ masm.storePtr(zero, Address(p, -(wordSize * i)));
+ }
+ masm.subPtr(Imm32(UNROLL_LIMIT * wordSize), p);
+ masm.branchPtr(Assembler::LessThan, lim, p, &again);
+
+ // The tail.
+ for (uint32_t i = 0; i < tailWords; ++i) {
+ masm.storePtr(zero, Address(p, -(wordSize * i)));
+ }
+
+ ra->freeI32(p);
+ ra->freeI32(lim);
+ ra->freeI32(zero);
+}
+
+} // namespace wasm
+} // namespace js