Bug 933317 - Improve write guard to consider out pointers r=shu

This commit is contained in:
Nicholas D. Matsakis 2014-01-07 21:35:32 -05:00
parent e78aced9e2
commit 731f2e75f4
17 changed files with 195 additions and 32 deletions

View File

@ -1475,14 +1475,14 @@ TypedDatum::createUnattachedWithClass(JSContext *cx,
return static_cast<TypedDatum*>(&*obj);
}
/*static*/ void
void
TypedDatum::attach(uint8_t *memory)
{
setPrivate(memory);
setReservedSlot(JS_DATUM_SLOT_OWNER, ObjectValue(*this));
}
/*static*/ void
void
TypedDatum::attach(JSObject &datum, uint32_t offset)
{
JS_ASSERT(IsTypedDatum(datum));
@ -2244,6 +2244,15 @@ TypedDatum::typedMem() const
return TypedMem(*this);
}
TypedDatum *
TypedDatum::owner() const
{
JSObject *owner = getReservedSlot(JS_DATUM_SLOT_OWNER).toObjectOrNull();
if (!owner)
return nullptr;
return &AsTypedDatum(*owner);
}
/******************************************************************************
* Typed Objects
*/

View File

@ -400,6 +400,7 @@ class TypedDatum : public JSObject
TypeRepresentation *datumTypeRepresentation() const;
uint8_t *typedMem() const;
TypedDatum *owner() const;
};
class TypedObject : public TypedDatum

View File

@ -1496,7 +1496,7 @@ CodeGenerator::visitForkJoinSlice(LForkJoinSlice *lir)
}
bool
CodeGenerator::visitGuardThreadLocalObject(LGuardThreadLocalObject *lir)
CodeGenerator::visitGuardThreadExclusive(LGuardThreadExclusive *lir)
{
JS_ASSERT(gen->info().executionMode() == ParallelExecution);
@ -1504,7 +1504,7 @@ CodeGenerator::visitGuardThreadLocalObject(LGuardThreadLocalObject *lir)
masm.setupUnalignedABICall(2, tempReg);
masm.passABIArg(ToRegister(lir->forkJoinSlice()));
masm.passABIArg(ToRegister(lir->object()));
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, IsThreadLocalObject));
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParallelWriteGuard));
OutOfLineAbortPar *bail = oolAbortPar(ParallelBailoutIllegalWrite, lir);
if (!bail)

View File

@ -207,7 +207,7 @@ class CodeGenerator : public CodeGeneratorSpecific
bool visitStringSplit(LStringSplit *lir);
bool visitFunctionEnvironment(LFunctionEnvironment *lir);
bool visitForkJoinSlice(LForkJoinSlice *lir);
bool visitGuardThreadLocalObject(LGuardThreadLocalObject *lir);
bool visitGuardThreadExclusive(LGuardThreadExclusive *lir);
bool visitCallGetProperty(LCallGetProperty *lir);
bool visitCallGetElement(LCallGetElement *lir);
bool visitCallSetElement(LCallSetElement *lir);

View File

@ -5144,12 +5144,12 @@ class LRestPar : public LCallInstructionHelper<1, 2, 3>
}
};
class LGuardThreadLocalObject : public LCallInstructionHelper<0, 2, 1>
class LGuardThreadExclusive : public LCallInstructionHelper<0, 2, 1>
{
public:
LIR_HEADER(GuardThreadLocalObject);
LIR_HEADER(GuardThreadExclusive);
LGuardThreadLocalObject(const LAllocation &slice, const LAllocation &object, const LDefinition &temp1) {
LGuardThreadExclusive(const LAllocation &slice, const LAllocation &object, const LDefinition &temp1) {
setOperand(0, slice);
setOperand(1, object);
setTemp(0, temp1);

View File

@ -168,7 +168,7 @@
_(GuardObjectType) \
_(GuardObjectIdentity) \
_(GuardClass) \
_(GuardThreadLocalObject) \
_(GuardThreadExclusive) \
_(TypeBarrierV) \
_(TypeBarrierO) \
_(MonitorTypes) \

View File

@ -2055,12 +2055,16 @@ LIRGenerator::visitForkJoinSlice(MForkJoinSlice *ins)
}
bool
LIRGenerator::visitGuardThreadLocalObject(MGuardThreadLocalObject *ins)
LIRGenerator::visitGuardThreadExclusive(MGuardThreadExclusive *ins)
{
LGuardThreadLocalObject *lir =
new(alloc()) LGuardThreadLocalObject(useFixed(ins->forkJoinSlice(), CallTempReg0),
useFixed(ins->object(), CallTempReg1),
tempFixed(CallTempReg2));
// FIXME (Bug 956281) -- For now, we always generate the most
// general form of write guard check. we could employ TI feedback
// to optimize this if we know that the object being tested is a
// typed object or know that it is definitely NOT a typed object.
LGuardThreadExclusive *lir =
new(alloc()) LGuardThreadExclusive(useFixed(ins->forkJoinSlice(), CallTempReg0),
useFixed(ins->object(), CallTempReg1),
tempFixed(CallTempReg2));
lir->setMir(ins);
return add(lir, ins);
}

View File

@ -159,7 +159,7 @@ class LIRGenerator : public LIRGeneratorSpecific
bool visitLoadSlot(MLoadSlot *ins);
bool visitFunctionEnvironment(MFunctionEnvironment *ins);
bool visitForkJoinSlice(MForkJoinSlice *ins);
bool visitGuardThreadLocalObject(MGuardThreadLocalObject *ins);
bool visitGuardThreadExclusive(MGuardThreadExclusive *ins);
bool visitInterruptCheck(MInterruptCheck *ins);
bool visitCheckInterruptPar(MCheckInterruptPar *ins);
bool visitStoreSlot(MStoreSlot *ins);

View File

@ -8579,24 +8579,24 @@ class MRestPar
}
};
// Guard on an object being allocated in the current slice.
class MGuardThreadLocalObject
// Guard on an object being safe for writes by current parallel slice.
// Must be either thread-local or else a handle into the destination array.
class MGuardThreadExclusive
: public MBinaryInstruction,
public ObjectPolicy<1>
{
MGuardThreadLocalObject(MDefinition *slice, MDefinition *obj)
MGuardThreadExclusive(MDefinition *slice, MDefinition *obj)
: MBinaryInstruction(slice, obj)
{
setResultType(MIRType_None);
setGuard();
setMovable();
}
public:
INSTRUCTION_HEADER(GuardThreadLocalObject);
INSTRUCTION_HEADER(GuardThreadExclusive);
static MGuardThreadLocalObject *New(TempAllocator &alloc, MDefinition *slice, MDefinition *obj) {
return new(alloc) MGuardThreadLocalObject(slice, obj);
static MGuardThreadExclusive *New(TempAllocator &alloc, MDefinition *slice, MDefinition *obj) {
return new(alloc) MGuardThreadExclusive(slice, obj);
}
MDefinition *forkJoinSlice() const {
return getOperand(0);

View File

@ -209,7 +209,7 @@ namespace jit {
_(LambdaPar) \
_(RestPar) \
_(ForkJoinSlice) \
_(GuardThreadLocalObject) \
_(GuardThreadExclusive) \
_(CheckInterruptPar) \
_(RecompileCheck)

View File

@ -6,6 +6,7 @@
#include "jit/ParallelFunctions.h"
#include "builtin/TypedObject.h"
#include "vm/ArrayObject.h"
#include "jsgcinlines.h"
@ -37,15 +38,86 @@ jit::NewGCThingPar(ForkJoinSlice *slice, gc::AllocKind allocKind)
return gc::NewGCThing<JSObject, NoGC>(slice, allocKind, thingSize, gc::DefaultHeap);
}
// Check that the object was created by the current thread
// (and hence is writable).
bool
jit::IsThreadLocalObject(ForkJoinSlice *slice, JSObject *object)
jit::ParallelWriteGuard(ForkJoinSlice *slice, JSObject *object)
{
// Implements the most general form of the write guard, which is
// suitable for writes to any object O. There are two cases to
// consider and test for:
//
// 1. Writes to thread-local memory are safe. Thread-local memory
// is defined as memory allocated by the current thread.
// The definition of the PJS API guarantees that such memory
// cannot have escaped to other parallel threads.
//
// 2. Writes into the output buffer are safe. Some PJS operations
// supply an out pointer into the final target buffer. The design
// of the API ensures that this out pointer is always pointing
// at a fresh region of the buffer that is not accessible to
// other threads. Thus, even though this output buffer has not
// been created by the current thread, it is writable.
//
// There are some subtleties to consider:
//
// A. Typed objects and typed arrays are just views onto a base buffer.
// For the purposes of guarding parallel writes, it is not important
// whether the *view* is thread-local -- what matters is whether
// the *underlying buffer* is thread-local.
//
// B. With regard to the output buffer, we have to be careful
// because of the potential for sequential iterations to be
// intermingled with parallel ones. During a sequential
// iteration, the out pointer could escape into global
// variables and so forth, and thus be used during later
// parallel operations. However, those out pointers must be
// pointing to distinct regions of the final output buffer than
// the ones that are currently being written, so there is no
// harm done in letting them be read (but not written).
//
// In order to be able to distinguish escaped out pointers from
// prior iterations and the proper out pointers from the
// current iteration, we always track a *target memory region*
// (which is a span of bytes within the output buffer) and not
// just the output buffer itself.
JS_ASSERT(ForkJoinSlice::current() == slice);
if (IsTypedDatum(*object)) {
TypedDatum &datum = AsTypedDatum(*object);
// Note: check target region based on `datum`, not the owner.
// This is because `datum` may point to some subregion of the
// owner and we only care if that *subregion* is within the
// target region, not the entire owner.
if (IsInTargetRegion(slice, &datum))
return true;
// Also check whether owner is thread-local.
TypedDatum *owner = datum.owner();
return owner && slice->isThreadLocal(owner);
}
// For other kinds of writable objects, must be thread-local.
return slice->isThreadLocal(object);
}
// Check that |object| (which must be a typed datum) maps
// to memory in the target region.
//
// For efficiency, we assume that all handles which the user has
// access to are either entirely within the target region or entirely
// without, but not straddling the target region nor encompassing
// it. This invariant is maintained by the PJS APIs, where the target
// region and handles are always elements of the same output array.
bool
jit::IsInTargetRegion(ForkJoinSlice *slice, TypedDatum *datum)
{
JS_ASSERT(IsTypedDatum(*datum)); // in case JIT supplies something bogus
uint8_t *typedMem = datum->typedMem();
return (typedMem >= slice->targetRegionStart &&
typedMem < slice->targetRegionEnd);
}
#ifdef DEBUG
static void
printTrace(const char *prefix, struct IonLIRTraceData *cached)

View File

@ -11,11 +11,15 @@
#include "vm/ForkJoin.h"
namespace js {
class TypedDatum; // subclass of JSObject* defined in builtin/TypedObject.h
namespace jit {
ForkJoinSlice *ForkJoinSlicePar();
JSObject *NewGCThingPar(ForkJoinSlice *slice, gc::AllocKind allocKind);
bool IsThreadLocalObject(ForkJoinSlice *slice, JSObject *object);
bool ParallelWriteGuard(ForkJoinSlice *slice, JSObject *object);
bool IsInTargetRegion(ForkJoinSlice *slice, TypedDatum *object);
bool CheckOverRecursedPar(ForkJoinSlice *slice);
bool CheckInterruptPar(ForkJoinSlice *slice);

View File

@ -143,7 +143,7 @@ class ParallelSafetyVisitor : public MInstructionVisitor
UNSAFE_OP(FilterArgumentsOrEval)
UNSAFE_OP(CallDirectEval)
SAFE_OP(BitNot)
UNSAFE_OP(TypeOf)
SAFE_OP(TypeOf)
UNSAFE_OP(ToId)
SAFE_OP(BitAnd)
SAFE_OP(BitOr)
@ -287,7 +287,7 @@ class ParallelSafetyVisitor : public MInstructionVisitor
UNSAFE_OP(NewDeclEnvObject)
UNSAFE_OP(In)
UNSAFE_OP(InArray)
SAFE_OP(GuardThreadLocalObject)
SAFE_OP(GuardThreadExclusive)
SAFE_OP(CheckInterruptPar)
SAFE_OP(CheckOverRecursedPar)
SAFE_OP(FunctionDispatch)
@ -656,6 +656,10 @@ ParallelSafetyVisitor::insertWriteGuard(MInstruction *writeInstruction,
object = valueBeingWritten->toTypedArrayElements()->object();
break;
case MDefinition::Op_TypedObjectElements:
object = valueBeingWritten->toTypedObjectElements()->object();
break;
default:
SpewMIR(writeInstruction, "cannot insert write guard for %s",
valueBeingWritten->opName());
@ -682,8 +686,8 @@ ParallelSafetyVisitor::insertWriteGuard(MInstruction *writeInstruction,
}
MBasicBlock *block = writeInstruction->block();
MGuardThreadLocalObject *writeGuard =
MGuardThreadLocalObject::New(alloc(), forkJoinSlice(), object);
MGuardThreadExclusive *writeGuard =
MGuardThreadExclusive::New(alloc(), forkJoinSlice(), object);
block->insertBefore(writeInstruction, writeGuard);
writeGuard->adjustInputs(alloc(), writeGuard);
return true;

View File

@ -18,6 +18,8 @@
#include "jslock.h"
#include "jsprf.h"
#include "builtin/TypedObject.h"
#ifdef JS_THREADSAFE
# include "jit/BaselineJIT.h"
# include "vm/Monitor.h"
@ -1582,6 +1584,8 @@ ForkJoinSlice::ForkJoinSlice(PerThreadData *perThreadData,
sliceId(sliceId),
workerId(workerId),
bailoutRecord(bailoutRecord),
targetRegionStart(nullptr),
targetRegionEnd(nullptr),
shared(shared),
acquiredContext_(false),
nogc_(shared->runtime())
@ -2103,4 +2107,46 @@ js::ParallelTestsShouldPass(JSContext *cx)
cx->runtime()->gcZeal() == 0;
}
bool
js::intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp)
{
// This version of SetForkJoinTargetRegion is called during
// sequential execution. It is a no-op. The parallel version
// is intrinsic_SetForkJoinTargetRegionPar(), below.
return true;
}
static bool
intrinsic_SetForkJoinTargetRegionPar(ForkJoinSlice *slice, unsigned argc, Value *vp)
{
// Sets the *target region*, which is the portion of the output
// buffer that the current iteration is permitted to write to.
//
// Note: it is important that the target region should be an
// entire element (or several elements) of the output array and
// not some region that spans from the middle of one element into
// the middle of another. This is because the guarding code
// assumes that handles, which never straddle across elements,
// will either be contained entirely within the target region or
// be contained entirely without of the region, and not straddling
// the region nor encompassing it.
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(argc == 3);
JS_ASSERT(args[0].isObject() && IsTypedDatum(args[0].toObject()));
JS_ASSERT(args[1].isInt32());
JS_ASSERT(args[2].isInt32());
uint8_t *mem = AsTypedDatum(args[0].toObject()).typedMem();
int32_t start = args[1].toInt32();
int32_t end = args[2].toInt32();
slice->targetRegionStart = mem + start;
slice->targetRegionEnd = mem + end;
return true;
}
const JSJitInfo js::intrinsic_SetForkJoinTargetRegionInfo =
JS_JITINFO_NATIVE_PARALLEL(intrinsic_SetForkJoinTargetRegionPar);
#endif // JS_THREADSAFE && JS_ION

View File

@ -319,6 +319,23 @@ class ForkJoinSlice : public ThreadSafeContext
uint32_t maxWorkerId;
#endif
// When we run a par operation like mapPar, we create an out pointer
// into a specific region of the destination buffer. Even though the
// destination buffer is not thread-local, it is permissible to write into
// it via the handles provided. These two fields identify the memory
// region where writes are allowed so that the write guards can test for
// it.
//
// Note: we only permit writes into the *specific region* that the user
// is supposed to write. Normally, they only have access to this region
// anyhow. But due to sequential fallback it is possible for handles into
// other regions to escape into global variables in the sequential
// execution and then get accessed by later parallel sections. Thus we
// must be careful and ensure that the write is going through a handle
// into the correct *region* of the buffer.
uint8_t *targetRegionStart;
uint8_t *targetRegionEnd;
ForkJoinSlice(PerThreadData *perThreadData, uint16_t sliceId, uint32_t workerId,
Allocator *allocator, ForkJoinShared *shared,
ParallelBailoutRecord *bailoutRecord);
@ -433,6 +450,9 @@ bool InExclusiveParallelSection();
bool ParallelTestsShouldPass(JSContext *cx);
bool intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp);
extern const JSJitInfo intrinsic_SetForkJoinTargetRegionInfo;
///////////////////////////////////////////////////////////////////////////
// Debug Spew

View File

@ -1607,7 +1607,7 @@ inline void
ObjectImpl::privateWriteBarrierPre(void **oldval)
{
#ifdef JSGC_INCREMENTAL
JS::shadow::Zone *shadowZone = this->shadowZone();
JS::shadow::Zone *shadowZone = this->shadowZoneFromAnyThread();
if (shadowZone->needsBarrier()) {
if (*oldval && getClass()->trace)
getClass()->trace(shadowZone->barrierTracer(), this->asObjectPtr());

View File

@ -626,6 +626,9 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("NewDenseArray", intrinsic_NewDenseArray, 1,0),
JS_FN("ShouldForceSequential", intrinsic_ShouldForceSequential, 0,0),
JS_FN("ParallelTestsShouldPass", intrinsic_ParallelTestsShouldPass, 0,0),
JS_FNINFO("SetForkJoinTargetRegion",
intrinsic_SetForkJoinTargetRegion,
&intrinsic_SetForkJoinTargetRegionInfo, 2, 0),
// See builtin/TypedObject.h for descriptors of the typedobj functions.
JS_FN("NewTypedHandle",