Source code

Revision control

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:
*
* Copyright 2019 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
*
*
* 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/WasmGC.h"
#include "wasm/WasmInstance.h"
#include "jit/MacroAssembler-inl.h"
namespace js {
namespace wasm {
wasm::StackMap* ConvertStackMapBoolVectorToStackMap(
const StackMapBoolVector& vec, bool hasRefs) {
wasm::StackMap* stackMap = wasm::StackMap::create(vec.length());
if (!stackMap) {
return nullptr;
}
bool hasRefsObserved = false;
size_t i = 0;
for (bool b : vec) {
if (b) {
stackMap->setBit(i);
hasRefsObserved = true;
}
i++;
}
MOZ_RELEASE_ASSERT(hasRefs == hasRefsObserved);
return stackMap;
}
// Generate a stackmap for a function's stack-overflow-at-entry trap, with
// the structure:
//
// <reg dump area>
// | ++ <space reserved before trap, if any>
// | ++ <space for Frame>
// | ++ <inbound arg area>
// | |
// Lowest Addr Highest Addr
//
// The caller owns the resulting stackmap. This assumes a grow-down stack.
//
// For non-debug builds, if the stackmap would contain no pointers, no
// stackmap is created, and nullptr is returned. For a debug build, a
// stackmap is always created and returned.
//
// The "space reserved before trap" is the space reserved by
// MacroAssembler::wasmReserveStackChecked, in the case where the frame is
// "small", as determined by that function.
bool CreateStackMapForFunctionEntryTrap(const wasm::ArgTypeVector& argTypes,
const MachineState& trapExitLayout,
size_t trapExitLayoutWords,
size_t nBytesReservedBeforeTrap,
size_t nInboundStackArgBytes,
wasm::StackMap** result) {
// Ensure this is defined on all return paths.
*result = nullptr;
// The size of the wasm::Frame itself.
const size_t nFrameBytes = sizeof(wasm::Frame);
// The size of the register dump (trap) area.
const size_t trapExitLayoutBytes = trapExitLayoutWords * sizeof(void*);
// This is the total number of bytes covered by the map.
const DebugOnly<size_t> nTotalBytes = trapExitLayoutBytes +
nBytesReservedBeforeTrap + nFrameBytes +
nInboundStackArgBytes;
// Create the stackmap initially in this vector. Since most frames will
// contain 128 or fewer words, heap allocation is avoided in the majority of
// cases. vec[0] is for the lowest address in the map, vec[N-1] is for the
// highest address in the map.
StackMapBoolVector vec;
// Keep track of whether we've actually seen any refs.
bool hasRefs = false;
// REG DUMP AREA
wasm::ExitStubMapVector trapExitExtras;
if (!GenerateStackmapEntriesForTrapExit(
argTypes, trapExitLayout, trapExitLayoutWords, &trapExitExtras)) {
return false;
}
MOZ_ASSERT(trapExitExtras.length() == trapExitLayoutWords);
if (!vec.appendN(false, trapExitLayoutWords)) {
return false;
}
for (size_t i = 0; i < trapExitLayoutWords; i++) {
vec[i] = trapExitExtras[i];
hasRefs |= vec[i];
}
// SPACE RESERVED BEFORE TRAP
MOZ_ASSERT(nBytesReservedBeforeTrap % sizeof(void*) == 0);
if (!vec.appendN(false, nBytesReservedBeforeTrap / sizeof(void*))) {
return false;
}
// SPACE FOR FRAME
if (!vec.appendN(false, nFrameBytes / sizeof(void*))) {
return false;
}
// INBOUND ARG AREA
MOZ_ASSERT(nInboundStackArgBytes % sizeof(void*) == 0);
const size_t numStackArgWords = nInboundStackArgBytes / sizeof(void*);
const size_t wordsSoFar = vec.length();
if (!vec.appendN(false, numStackArgWords)) {
return false;
}
for (ABIArgIter i(argTypes); !i.done(); i++) {
ABIArg argLoc = *i;
if (argLoc.kind() == ABIArg::Stack &&
argTypes[i.index()] == MIRType::RefOrNull) {
uint32_t offset = argLoc.offsetFromArgBase();
MOZ_ASSERT(offset < nInboundStackArgBytes);
MOZ_ASSERT(offset % sizeof(void*) == 0);
vec[wordsSoFar + offset / sizeof(void*)] = true;
hasRefs = true;
}
}
#ifndef DEBUG
// We saw no references, and this is a non-debug build, so don't bother
// building the stackmap.
if (!hasRefs) {
return true;
}
#endif
// Convert vec into a wasm::StackMap.
MOZ_ASSERT(vec.length() * sizeof(void*) == nTotalBytes);
wasm::StackMap* stackMap = ConvertStackMapBoolVectorToStackMap(vec, hasRefs);
if (!stackMap) {
return false;
}
stackMap->setExitStubWords(trapExitLayoutWords);
stackMap->setFrameOffsetFromTop(nFrameBytes / sizeof(void*) +
numStackArgWords);
#ifdef DEBUG
for (uint32_t i = 0; i < nFrameBytes / sizeof(void*); i++) {
MOZ_ASSERT(stackMap->getBit(stackMap->numMappedWords -
stackMap->frameOffsetFromTop + i) == 0);
}
#endif
*result = stackMap;
return true;
}
bool GenerateStackmapEntriesForTrapExit(const ArgTypeVector& args,
const MachineState& trapExitLayout,
const size_t trapExitLayoutNumWords,
ExitStubMapVector* extras) {
MOZ_ASSERT(extras->empty());
// If this doesn't hold, we can't distinguish saved and not-saved
// registers in the MachineState. See MachineState::MachineState().
MOZ_ASSERT(trapExitLayoutNumWords < 0x100);
if (!extras->appendN(false, trapExitLayoutNumWords)) {
return false;
}
for (ABIArgIter i(args); !i.done(); i++) {
if (!i->argInRegister() || i.mirType() != MIRType::RefOrNull) {
continue;
}
size_t offsetFromTop =
reinterpret_cast<size_t>(trapExitLayout.address(i->gpr()));
// If this doesn't hold, the associated register wasn't saved by
// the trap exit stub. Better to crash now than much later, in
// some obscure place, and possibly with security consequences.
MOZ_RELEASE_ASSERT(offsetFromTop < trapExitLayoutNumWords);
// offsetFromTop is an offset in words down from the highest
// address in the exit stub save area. Switch it around to be an
// offset up from the bottom of the (integer register) save area.
size_t offsetFromBottom = trapExitLayoutNumWords - 1 - offsetFromTop;
(*extras)[offsetFromBottom] = true;
}
return true;
}
void EmitWasmPreBarrierGuard(MacroAssembler& masm, Register tls,
Register scratch, Register valueAddr,
Label* skipBarrier) {
// If no incremental GC has started, we don't need the barrier.
masm.loadPtr(
Address(tls, offsetof(TlsData, addressOfNeedsIncrementalBarrier)),
scratch);
masm.branchTest32(Assembler::Zero, Address(scratch, 0), Imm32(0x1),
skipBarrier);
// If the previous value is null, we don't need the barrier.
masm.loadPtr(Address(valueAddr, 0), scratch);
masm.branchTestPtr(Assembler::Zero, scratch, scratch, skipBarrier);
}
void EmitWasmPreBarrierCall(MacroAssembler& masm, Register tls,
Register scratch, Register valueAddr) {
MOZ_ASSERT(valueAddr == PreBarrierReg);
masm.loadPtr(Address(tls, offsetof(TlsData, instance)), scratch);
masm.loadPtr(Address(scratch, Instance::offsetOfPreBarrierCode()), scratch);
#if defined(DEBUG) && defined(JS_CODEGEN_ARM64)
// The prebarrier assumes that x28 == sp.
Label ok;
masm.Cmp(sp, vixl::Operand(x28));
masm.B(&ok, Assembler::Equal);
masm.breakpoint();
masm.bind(&ok);
#endif
masm.call(scratch);
}
void EmitWasmPostBarrierGuard(MacroAssembler& masm,
const Maybe<Register>& object,
Register otherScratch, Register setValue,
Label* skipBarrier) {
// If the pointer being stored is null, no barrier.
masm.branchTestPtr(Assembler::Zero, setValue, setValue, skipBarrier);
// If there is a containing object and it is in the nursery, no barrier.
if (object) {
masm.branchPtrInNurseryChunk(Assembler::Equal, *object, otherScratch,
skipBarrier);
}
// If the pointer being stored is to a tenured object, no barrier.
masm.branchPtrInNurseryChunk(Assembler::NotEqual, setValue, otherScratch,
skipBarrier);
}
} // namespace wasm
} // namespace js