Bug 1383777 - Support idempotent ICs that access missing properties and object lengths, r=jandem.

--HG--
extra : rebase_source : 405e682faa19e1e710f4d9f1c3692baf0a1b8299
This commit is contained in:
Brian Hackett 2017-07-25 17:18:29 -06:00
Родитель 142609c919
Коммит d00e270300
9 изменённых файлов: 157 добавлений и 67 удалений

Просмотреть файл

@ -0,0 +1,34 @@
// Test that we don't attach ICs to idempotent caches that are incompatible
// with the cache result type.
var missingObjs = [{a:1},Object.create({a:2}),{}];
function testMissing(limit)
{
var res = 0;
for (var i = 0; i < 1000; i++) {
for (var j = 0; j < missingObjs.length; j++) {
var obj = missingObjs[j];
if (j < limit)
res += obj.a;
}
}
return res;
}
assertEq(testMissing(2), 3000);
assertEq(testMissing(3), NaN);
var lengthObjs = [{length:{a:1}},Object.create({length:{a:2}}),[0,1]];
function testArrayLength(limit)
{
var res = 0;
for (var i = 0; i < 1000; i++) {
for (var j = 0; j < lengthObjs.length; j++) {
var obj = lengthObjs[j];
if (j < limit)
res += obj.length.a;
}
}
return res;
}
assertEq(testArrayLength(2), 3000);
assertEq(testArrayLength(3), NaN);

Просмотреть файл

@ -810,8 +810,10 @@ DoGetElemFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub_
if (stub->state().canAttachStub()) {
ICStubEngine engine = ICStubEngine::Baseline;
GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElem, stub->state().mode(),
&isTemporarilyUnoptimizable, lhs, rhs, lhs, CanAttachGetter::Yes);
GetPropIRGenerator gen(cx, script, pc,
CacheKind::GetElem, stub->state().mode(),
&isTemporarilyUnoptimizable, lhs, rhs, lhs,
GetPropertyResultFlags::All);
if (gen.tryAttachStub()) {
ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
BaselineCacheIRStubKind::Monitored,
@ -884,7 +886,7 @@ DoGetElemSuperFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback*
ICStubEngine engine = ICStubEngine::Baseline;
GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElemSuper, stub->state().mode(),
&isTemporarilyUnoptimizable, lhs, rhs, receiver,
CanAttachGetter::Yes);
GetPropertyResultFlags::All);
if (gen.tryAttachStub()) {
ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
BaselineCacheIRStubKind::Monitored,

Просмотреть файл

@ -48,13 +48,13 @@ GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script, jsbyt
CacheKind cacheKind, ICState::Mode mode,
bool* isTemporarilyUnoptimizable, HandleValue val,
HandleValue idVal, HandleValue receiver,
CanAttachGetter canAttachGetter)
GetPropertyResultFlags resultFlags)
: IRGenerator(cx, script, pc, cacheKind, mode),
val_(val),
idVal_(idVal),
receiver_(receiver),
isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable),
canAttachGetter_(canAttachGetter),
resultFlags_(resultFlags),
preliminaryObjectAction_(PreliminaryObjectAction::None)
{}
@ -247,11 +247,9 @@ GetPropIRGenerator::tryAttachStub()
bool
GetPropIRGenerator::tryAttachIdempotentStub()
{
// For idempotent ICs, only attach stubs for plain data properties.
// This ensures (1) the lookup has no side-effects and (2) Ion has complete
// static type information and we don't have to monitor the result. Because
// of (2), we don't support for instance missing properties or array
// lengths, as TI does not account for these cases.
// For idempotent ICs, only attach stubs which we can be sure have no side
// effects and produce a result which the MIR in the calling code is able
// to handle, since we do not have a pc to explicitly monitor the result.
MOZ_ASSERT(idempotent());
@ -263,6 +261,10 @@ GetPropIRGenerator::tryAttachIdempotentStub()
if (tryAttachNative(obj, objId, id))
return true;
// Object lengths are supported only if int32 results are allowed.
if ((resultFlags_ & GetPropertyResultFlags::AllowInt32) && tryAttachObjectLength(obj, objId, id))
return true;
// Also support native data properties on DOMProxy prototypes.
if (GetProxyStubType(cx_, obj, id) == ProxyStubType::DOMUnshadowed)
return tryAttachDOMProxyUnshadowed(obj, objId, id);
@ -272,22 +274,23 @@ GetPropIRGenerator::tryAttachIdempotentStub()
static bool
IsCacheableNoProperty(JSContext* cx, JSObject* obj, JSObject* holder, Shape* shape, jsid id,
jsbytecode* pc)
jsbytecode* pc, GetPropertyResultFlags resultFlags)
{
if (shape)
return false;
MOZ_ASSERT(!holder);
if (!pc) {
// This is an idempotent IC, don't attach a missing-property stub.
// See tryAttachStub.
// Idempotent ICs may only attach missing-property stubs if undefined
// results are explicitly allowed, since no monitoring is done of the
// cache result.
if (!pc && !(resultFlags & GetPropertyResultFlags::AllowUndefined))
return false;
}
// If we're doing a name lookup, we have to throw a ReferenceError. If
// extra warnings are enabled, we may have to report a warning.
if (*pc == JSOP_GETBOUNDNAME || cx->compartment()->behaviors().extraWarnings(cx))
// Note that Ion does not generate idempotent caches for JSOP_GETBOUNDNAME.
if ((pc && *pc == JSOP_GETBOUNDNAME) || cx->compartment()->behaviors().extraWarnings(cx))
return false;
return CheckHasNoSuchProperty(cx, obj, id);
@ -302,7 +305,7 @@ enum NativeGetPropCacheability {
static NativeGetPropCacheability
CanAttachNativeGetProp(JSContext* cx, HandleObject obj, HandleId id,
MutableHandleNativeObject holder, MutableHandleShape shape,
jsbytecode* pc, CanAttachGetter canAttachGetter,
jsbytecode* pc, GetPropertyResultFlags resultFlags,
bool* isTemporarilyUnoptimizable)
{
MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
@ -327,22 +330,17 @@ CanAttachNativeGetProp(JSContext* cx, HandleObject obj, HandleId id,
if (IsCacheableGetPropReadSlotForIonOrCacheIR(obj, holder, prop))
return CanAttachReadSlot;
// Idempotent ICs only support plain data properties, see
// tryAttachIdempotentStub.
if (!pc)
return CanAttachNone;
if (IsCacheableNoProperty(cx, obj, holder, shape, id, pc))
if (IsCacheableNoProperty(cx, obj, holder, shape, id, pc, resultFlags))
return CanAttachReadSlot;
if (canAttachGetter == CanAttachGetter::No)
return CanAttachNone;
// Idempotent ICs cannot call getters, see tryAttachIdempotentStub.
if (pc && (resultFlags & GetPropertyResultFlags::Monitored)) {
if (IsCacheableGetPropCallScripted(obj, holder, shape, isTemporarilyUnoptimizable))
return CanAttachCallGetter;
if (IsCacheableGetPropCallScripted(obj, holder, shape, isTemporarilyUnoptimizable))
return CanAttachCallGetter;
if (IsCacheableGetPropCallNative(obj, holder, shape))
return CanAttachCallGetter;
if (IsCacheableGetPropCallNative(obj, holder, shape))
return CanAttachCallGetter;
}
return CanAttachNone;
}
@ -582,10 +580,8 @@ GetPropIRGenerator::tryAttachNative(HandleObject obj, ObjOperandId objId, Handle
RootedNativeObject holder(cx_);
NativeGetPropCacheability type = CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_,
canAttachGetter_,
resultFlags_,
isTemporarilyUnoptimizable_);
MOZ_ASSERT_IF(idempotent(),
type == CanAttachNone || (type == CanAttachReadSlot && holder));
switch (type) {
case CanAttachNone:
return false;
@ -613,6 +609,7 @@ GetPropIRGenerator::tryAttachNative(HandleObject obj, ObjOperandId objId, Handle
return true;
case CanAttachCallGetter: {
// |super.prop| accesses use a |this| value that differs from lookup object
MOZ_ASSERT(!idempotent());
ObjOperandId receiverId = isSuper() ? writer.guardIsObject(getSuperReceiverValueId())
: objId;
maybeEmitIdGuard(id);
@ -651,7 +648,7 @@ GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj, ObjOperandId objId, H
RootedShape shape(cx_);
RootedNativeObject holder(cx_);
NativeGetPropCacheability type = CanAttachNativeGetProp(cx_, windowObj, id, &holder, &shape, pc_,
canAttachGetter_,
resultFlags_,
isTemporarilyUnoptimizable_);
switch (type) {
case CanAttachNone:
@ -738,8 +735,8 @@ GetPropIRGenerator::tryAttachCrossCompartmentWrapper(HandleObject obj, ObjOperan
RootedShape shape(cx_);
RootedNativeObject holder(cx_);
NativeGetPropCacheability canCache =
CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &shape, pc_, canAttachGetter_,
isTemporarilyUnoptimizable_);
CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &shape, pc_,
resultFlags_, isTemporarilyUnoptimizable_);
if (canCache != CanAttachReadSlot)
return false;
@ -963,7 +960,7 @@ GetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objI
RootedShape propShape(cx_);
NativeGetPropCacheability canCache =
CanAttachNativeGetProp(cx_, expandoObj, id, &holder, &propShape, pc_,
canAttachGetter_, isTemporarilyUnoptimizable_);
resultFlags_, isTemporarilyUnoptimizable_);
if (canCache != CanAttachReadSlot && canCache != CanAttachCallGetter)
return false;
if (!holder)
@ -1054,10 +1051,8 @@ GetPropIRGenerator::tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId o
RootedNativeObject holder(cx_);
RootedShape shape(cx_);
NativeGetPropCacheability canCache = CanAttachNativeGetProp(cx_, checkObj, id, &holder, &shape,
pc_, canAttachGetter_,
pc_, resultFlags_,
isTemporarilyUnoptimizable_);
MOZ_ASSERT_IF(idempotent(),
canCache == CanAttachNone || (canCache == CanAttachReadSlot && holder));
if (canCache == CanAttachNone)
return false;
@ -1394,7 +1389,7 @@ GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId, HandleId id)
RootedShape shape(cx_);
RootedNativeObject holder(cx_);
NativeGetPropCacheability type = CanAttachNativeGetProp(cx_, proto, id, &holder, &shape, pc_,
canAttachGetter_,
resultFlags_,
isTemporarilyUnoptimizable_);
if (type != CanAttachReadSlot)
return false;

Просмотреть файл

@ -1147,7 +1147,38 @@ class MOZ_RAII IRGenerator
CacheKind cacheKind() const { return cacheKind_; }
};
enum class CanAttachGetter { Yes, No };
// Flags used to describe what values a GetProperty cache may produce.
enum class GetPropertyResultFlags {
None = 0,
// Values produced by this cache will go through a type barrier,
// so the cache may produce any type of value that is compatible with its
// result operand.
Monitored = 1 << 0,
// Whether particular primitives may be produced by this cache.
AllowUndefined = 1 << 1,
AllowInt32 = 1 << 2,
AllowDouble = 1 << 3,
All = Monitored | AllowUndefined | AllowInt32 | AllowDouble
};
static inline bool operator&(GetPropertyResultFlags a, GetPropertyResultFlags b)
{
return static_cast<int>(a) & static_cast<int>(b);
}
static inline GetPropertyResultFlags operator|(GetPropertyResultFlags a, GetPropertyResultFlags b)
{
return static_cast<GetPropertyResultFlags>(static_cast<int>(a) | static_cast<int>(b));
}
static inline GetPropertyResultFlags& operator|=(GetPropertyResultFlags& lhs, GetPropertyResultFlags b)
{
lhs = lhs | b;
return lhs;
}
// GetPropIRGenerator generates CacheIR for a GetProp IC.
class MOZ_RAII GetPropIRGenerator : public IRGenerator
@ -1156,7 +1187,7 @@ class MOZ_RAII GetPropIRGenerator : public IRGenerator
HandleValue idVal_;
HandleValue receiver_;
bool* isTemporarilyUnoptimizable_;
CanAttachGetter canAttachGetter_;
GetPropertyResultFlags resultFlags_;
enum class PreliminaryObjectAction { None, Unlink, NotePreliminary };
PreliminaryObjectAction preliminaryObjectAction_;
@ -1233,7 +1264,8 @@ class MOZ_RAII GetPropIRGenerator : public IRGenerator
public:
GetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
ICState::Mode mode, bool* isTemporarilyUnoptimizable, HandleValue val,
HandleValue idVal, HandleValue receiver, CanAttachGetter canAttachGetter);
HandleValue idVal, HandleValue receiver,
GetPropertyResultFlags resultFlags);
bool tryAttachStub();
bool tryAttachIdempotentStub();

Просмотреть файл

@ -10285,7 +10285,7 @@ void
CodeGenerator::addGetPropertyCache(LInstruction* ins, LiveRegisterSet liveRegs,
TypedOrValueRegister value, const ConstantOrRegister& id,
TypedOrValueRegister output, Register maybeTemp,
bool monitoredResult, bool allowDoubleResult,
GetPropertyResultFlags resultFlags,
jsbytecode* profilerLeavePc)
{
CacheKind kind = CacheKind::GetElem;
@ -10295,8 +10295,7 @@ CodeGenerator::addGetPropertyCache(LInstruction* ins, LiveRegisterSet liveRegs,
if (idString->isAtom() && !idString->asAtom().isIndex(&dummy))
kind = CacheKind::GetProp;
}
IonGetPropertyIC cache(kind, liveRegs, value, id, output, maybeTemp, monitoredResult,
allowDoubleResult);
IonGetPropertyIC cache(kind, liveRegs, value, id, output, maybeTemp, resultFlags);
addIC(ins, allocateIC(cache));
}
@ -10333,6 +10332,35 @@ CodeGenerator::toConstantOrRegister(LInstruction* lir, size_t n, MIRType type)
return TypedOrValueRegister(type, ToAnyRegister(value));
}
static GetPropertyResultFlags
IonGetPropertyICFlags(const MGetPropertyCache* mir)
{
GetPropertyResultFlags flags = GetPropertyResultFlags::None;
if (mir->monitoredResult())
flags |= GetPropertyResultFlags::Monitored;
if (mir->type() == MIRType::Value) {
if (TemporaryTypeSet* types = mir->resultTypeSet()) {
if (types->hasType(TypeSet::UndefinedType()))
flags |= GetPropertyResultFlags::AllowUndefined;
if (types->hasType(TypeSet::Int32Type()))
flags |= GetPropertyResultFlags::AllowInt32;
if (types->hasType(TypeSet::DoubleType()))
flags |= GetPropertyResultFlags::AllowDouble;
} else {
flags |= GetPropertyResultFlags::AllowUndefined
| GetPropertyResultFlags::AllowInt32
| GetPropertyResultFlags::AllowDouble;
}
} else if (mir->type() == MIRType::Int32) {
flags |= GetPropertyResultFlags::AllowInt32;
} else if (mir->type() == MIRType::Double) {
flags |= GetPropertyResultFlags::AllowInt32 | GetPropertyResultFlags::AllowDouble;
}
return flags;
}
void
CodeGenerator::visitGetPropertyCacheV(LGetPropertyCacheV* ins)
{
@ -10340,12 +10368,11 @@ CodeGenerator::visitGetPropertyCacheV(LGetPropertyCacheV* ins)
TypedOrValueRegister value =
toConstantOrRegister(ins, LGetPropertyCacheV::Value, ins->mir()->value()->type()).reg();
ConstantOrRegister id = toConstantOrRegister(ins, LGetPropertyCacheV::Id, ins->mir()->idval()->type());
bool monitoredResult = ins->mir()->monitoredResult();
TypedOrValueRegister output = TypedOrValueRegister(GetValueOutput(ins));
Register maybeTemp = ins->temp()->isBogusTemp() ? InvalidReg : ToRegister(ins->temp());
addGetPropertyCache(ins, liveRegs, value, id, output, maybeTemp, monitoredResult,
ins->mir()->allowDoubleResult(), ins->mir()->profilerLeavePc());
addGetPropertyCache(ins, liveRegs, value, id, output, maybeTemp,
IonGetPropertyICFlags(ins->mir()), ins->mir()->profilerLeavePc());
}
void
@ -10355,12 +10382,11 @@ CodeGenerator::visitGetPropertyCacheT(LGetPropertyCacheT* ins)
TypedOrValueRegister value =
toConstantOrRegister(ins, LGetPropertyCacheV::Value, ins->mir()->value()->type()).reg();
ConstantOrRegister id = toConstantOrRegister(ins, LGetPropertyCacheT::Id, ins->mir()->idval()->type());
bool monitoredResult = ins->mir()->monitoredResult();
TypedOrValueRegister output(ins->mir()->type(), ToAnyRegister(ins->getDef(0)));
Register maybeTemp = ins->temp()->isBogusTemp() ? InvalidReg : ToRegister(ins->temp());
addGetPropertyCache(ins, liveRegs, value, id, output, maybeTemp, monitoredResult,
ins->mir()->allowDoubleResult(), ins->mir()->profilerLeavePc());
addGetPropertyCache(ins, liveRegs, value, id, output, maybeTemp,
IonGetPropertyICFlags(ins->mir()), ins->mir()->profilerLeavePc());
}
void

Просмотреть файл

@ -7,6 +7,7 @@
#ifndef jit_CodeGenerator_h
#define jit_CodeGenerator_h
#include "jit/CacheIR.h"
#include "jit/IonCaches.h"
#if defined(JS_ION_PERF)
# include "jit/PerfSpewer.h"
@ -468,8 +469,8 @@ class CodeGenerator final : public CodeGeneratorSpecific
private:
void addGetPropertyCache(LInstruction* ins, LiveRegisterSet liveRegs,
TypedOrValueRegister value, const ConstantOrRegister& id,
TypedOrValueRegister output, Register maybeTemp, bool monitoredResult,
bool allowDoubleResult, jsbytecode* profilerLeavePc);
TypedOrValueRegister output, Register maybeTemp,
GetPropertyResultFlags flags, jsbytecode* profilerLeavePc);
void addSetPropertyCache(LInstruction* ins, LiveRegisterSet liveRegs, Register objReg,
Register temp, FloatRegister tempDouble,
FloatRegister tempF32, const ConstantOrRegister& id,

Просмотреть файл

@ -135,12 +135,11 @@ IonGetPropertyIC::update(JSContext* cx, HandleScript outerScript, IonGetProperty
// needs a type barrier. Unfortunately, PropertyReadNeedsTypeBarrier
// does not account for getters, so we should only attach a getter
// stub if we inserted a type barrier.
CanAttachGetter canAttachGetter =
ic->monitoredResult() ? CanAttachGetter::Yes : CanAttachGetter::No;
jsbytecode* pc = ic->idempotent() ? nullptr : ic->pc();
bool isTemporarilyUnoptimizable = false;
GetPropIRGenerator gen(cx, outerScript, pc, ic->kind(), ic->state().mode(),
&isTemporarilyUnoptimizable, val, idVal, val, canAttachGetter);
&isTemporarilyUnoptimizable, val, idVal, val,
ic->resultFlags());
if (ic->idempotent() ? gen.tryAttachIdempotentStub() : gen.tryAttachStub())
ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached);

Просмотреть файл

@ -178,6 +178,7 @@ class IonIC
class IonGetPropertyIC : public IonIC
{
private:
LiveRegisterSet liveRegs_;
TypedOrValueRegister value_;
@ -185,30 +186,29 @@ class IonGetPropertyIC : public IonIC
TypedOrValueRegister output_;
Register maybeTemp_; // Might be InvalidReg.
bool monitoredResult_ : 1;
bool allowDoubleResult_ : 1;
GetPropertyResultFlags resultFlags_;
public:
IonGetPropertyIC(CacheKind kind, LiveRegisterSet liveRegs, TypedOrValueRegister value,
const ConstantOrRegister& id, TypedOrValueRegister output, Register maybeTemp,
bool monitoredResult, bool allowDoubleResult)
GetPropertyResultFlags resultFlags)
: IonIC(kind),
liveRegs_(liveRegs),
value_(value),
id_(id),
output_(output),
maybeTemp_(maybeTemp),
monitoredResult_(monitoredResult),
allowDoubleResult_(allowDoubleResult)
resultFlags_(resultFlags)
{ }
bool monitoredResult() const { return monitoredResult_; }
TypedOrValueRegister value() const { return value_; }
ConstantOrRegister id() const { return id_; }
TypedOrValueRegister output() const { return output_; }
Register maybeTemp() const { return maybeTemp_; }
LiveRegisterSet liveRegs() const { return liveRegs_; }
bool allowDoubleResult() const { return allowDoubleResult_; }
GetPropertyResultFlags resultFlags() const { return resultFlags_; }
bool monitoredResult() const { return resultFlags_ & GetPropertyResultFlags::Monitored; }
bool allowDoubleResult() const { return resultFlags_ & GetPropertyResultFlags::AllowDouble; }
static MOZ_MUST_USE bool update(JSContext* cx, HandleScript outerScript, IonGetPropertyIC* ic,
HandleValue val, HandleValue idVal, MutableHandleValue res);

Просмотреть файл

@ -2069,7 +2069,8 @@ DoGetPropFallback(JSContext* cx, BaselineFrame* frame, ICGetProp_Fallback* stub_
if (stub->state().canAttachStub()) {
RootedValue idVal(cx, StringValue(name));
GetPropIRGenerator gen(cx, script, pc, CacheKind::GetProp, stub->state().mode(),
&isTemporarilyUnoptimizable, val, idVal, val, CanAttachGetter::Yes);
&isTemporarilyUnoptimizable, val, idVal, val,
GetPropertyResultFlags::All);
if (gen.tryAttachStub()) {
ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
BaselineCacheIRStubKind::Monitored,
@ -2140,7 +2141,7 @@ DoGetPropSuperFallback(JSContext* cx, BaselineFrame* frame, ICGetProp_Fallback*
RootedValue idVal(cx, StringValue(name));
GetPropIRGenerator gen(cx, script, pc, CacheKind::GetPropSuper, stub->state().mode(),
&isTemporarilyUnoptimizable, val, idVal, receiver,
CanAttachGetter::Yes);
GetPropertyResultFlags::All);
if (gen.tryAttachStub()) {
ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
BaselineCacheIRStubKind::Monitored,