зеркало из https://github.com/mozilla/gecko-dev.git
Bug 966743 - Inline Array.prototype.push with more than one argument. r=jandem
This commit is contained in:
Родитель
15de1c0cb3
Коммит
e3bca1c862
|
@ -0,0 +1,87 @@
|
||||||
|
// |jit-test| --no-threads
|
||||||
|
|
||||||
|
// This test case check's Ion ability to recover from an allocation failure in
|
||||||
|
// the inlining of Array.prototype.push, when given multiple arguments. Note,
|
||||||
|
// that the following are not equivalent in case of failures:
|
||||||
|
//
|
||||||
|
// arr = [];
|
||||||
|
// arr.push(1,2,3); // OOM ---> arr == []
|
||||||
|
//
|
||||||
|
// arr = [];
|
||||||
|
// arr.push(1);
|
||||||
|
// arr.push(2); // OOM --> arr == [1]
|
||||||
|
// arr.push(3);
|
||||||
|
|
||||||
|
function canIoncompile() {
|
||||||
|
while (true) {
|
||||||
|
var i = inIon();
|
||||||
|
if (i)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!("oomAtAllocation" in this))
|
||||||
|
quit();
|
||||||
|
if (canIoncompile() != true)
|
||||||
|
quit();
|
||||||
|
if ("gczeal" in this)
|
||||||
|
gczeal(0);
|
||||||
|
|
||||||
|
function pushLimits(limit, offset, arr, freeze) {
|
||||||
|
arr = arr || [];
|
||||||
|
arr.push(0,1,2,3,4,5,6,7,8,9);
|
||||||
|
arr.length = offset;
|
||||||
|
var l = arr.length;
|
||||||
|
var was = inIon();
|
||||||
|
oomAtAllocation(limit);
|
||||||
|
try {
|
||||||
|
for (var i = 0; i < 50; i++)
|
||||||
|
arr.push(0,1,2,3,4,5,6,7,8,9);
|
||||||
|
if (freeze)
|
||||||
|
arr.frozen();
|
||||||
|
for (var i = 0; i < 100; i++)
|
||||||
|
arr.push(0,1,2,3,4,5,6,7,8,9);
|
||||||
|
} catch (e) {
|
||||||
|
// Catch OOM.
|
||||||
|
}
|
||||||
|
resetOOMFailure();
|
||||||
|
assertEq(arr.length % 10, l);
|
||||||
|
// Check for a bailout.
|
||||||
|
var is = inIon();
|
||||||
|
return was ? is ? 1 : 2 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need this limit to be high enough to be able to OSR in the for-loop of
|
||||||
|
// pushLimits.
|
||||||
|
var limit = 1024 * 1024 * 1024;
|
||||||
|
while(true) {
|
||||||
|
var res = pushLimits(limit, 0);
|
||||||
|
|
||||||
|
if (res == 0) {
|
||||||
|
limit = 1024 * 1024 * 1024;
|
||||||
|
} else if (res == 1) { // Started and finished in Ion.
|
||||||
|
if (limit == 0) // If we are not in the Jit.
|
||||||
|
break;
|
||||||
|
// We want to converge quickly to a state where the memory is limited
|
||||||
|
// enough to cause failures in array.prototype.push.
|
||||||
|
limit = (limit / 2) | 0;
|
||||||
|
} else if (res == 2) { // Started in Ion, and finished in Baseline.
|
||||||
|
if (limit < 10) {
|
||||||
|
// This is used to offset the OOM location, such that we can test
|
||||||
|
// each steps of the Array.push function, when it is jitted.
|
||||||
|
for (var off = 1; off < 10; off++) {
|
||||||
|
var arr = [];
|
||||||
|
try {
|
||||||
|
pushLimits(limit, off, arr, true);
|
||||||
|
} catch (e) {
|
||||||
|
// Cacth OOM produced while generating the error message.
|
||||||
|
}
|
||||||
|
if (arr.length > 10) assertEq(arr[arr.length - 1], 9);
|
||||||
|
else assertEq(arr[arr.length - 1], arr.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (limit == 1)
|
||||||
|
break;
|
||||||
|
limit--;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
// |jit-test| --no-threads
|
||||||
|
|
||||||
|
// This test case check's Ion ability to recover from an allocation failure in
|
||||||
|
// the inlining of Array.prototype.push, when given multiple arguments. Note,
|
||||||
|
// that the following are not equivalent in case of failures:
|
||||||
|
//
|
||||||
|
// arr = [];
|
||||||
|
// arr.push(1,2,3); // OOM ---> arr == []
|
||||||
|
//
|
||||||
|
// arr = [];
|
||||||
|
// arr.push(1);
|
||||||
|
// arr.push(2); // OOM --> arr == [1]
|
||||||
|
// arr.push(3);
|
||||||
|
|
||||||
|
function canIoncompile() {
|
||||||
|
while (true) {
|
||||||
|
var i = inIon();
|
||||||
|
if (i)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!("oomAtAllocation" in this))
|
||||||
|
quit();
|
||||||
|
if (canIoncompile() != true)
|
||||||
|
quit();
|
||||||
|
if ("gczeal" in this)
|
||||||
|
gczeal(0);
|
||||||
|
|
||||||
|
function pushLimits(limit, offset) {
|
||||||
|
var arr = [0,1,2,3,4,5,6,7,8,9];
|
||||||
|
arr.length = offset;
|
||||||
|
var l = arr.length;
|
||||||
|
var was = inIon();
|
||||||
|
oomAtAllocation(limit);
|
||||||
|
try {
|
||||||
|
for (var i = 0; i < 100; i++)
|
||||||
|
arr.push(0,1,2,3,4,5,6,7,8,9);
|
||||||
|
} catch (e) {
|
||||||
|
// Catch OOM.
|
||||||
|
}
|
||||||
|
resetOOMFailure();
|
||||||
|
assertEq(arr.length % 10, l);
|
||||||
|
// Check for a bailout.
|
||||||
|
var is = inIon();
|
||||||
|
return was ? is ? 1 : 2 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need this limit to be high enough to be able to OSR in the for-loop of
|
||||||
|
// pushLimits.
|
||||||
|
var limit = 1024 * 1024 * 1024;
|
||||||
|
while(true) {
|
||||||
|
var res = pushLimits(limit, 0);
|
||||||
|
|
||||||
|
if (res == 0) {
|
||||||
|
limit = 1024 * 1024 * 1024;
|
||||||
|
} else if (res == 1) { // Started and finished in Ion.
|
||||||
|
if (limit == 0) // If we are not in the Jit.
|
||||||
|
break;
|
||||||
|
// We want to converge quickly to a state where the memory is limited
|
||||||
|
// enough to cause failures in array.prototype.push.
|
||||||
|
limit = (limit / 2) | 0;
|
||||||
|
} else if (res == 2) { // Started in Ion, and finished in Baseline.
|
||||||
|
if (limit < 10) {
|
||||||
|
// This is used to offset the OOM location, such that we can test
|
||||||
|
// each steps of the Array.push function, when it is jitted.
|
||||||
|
for (var off = 1; off < 10; off++)
|
||||||
|
pushLimits(limit, off);
|
||||||
|
}
|
||||||
|
if (limit == 1)
|
||||||
|
break;
|
||||||
|
limit--;
|
||||||
|
}
|
||||||
|
}
|
|
@ -790,18 +790,21 @@ IonBuilder::inlineArrayJoin(CallInfo& callInfo)
|
||||||
IonBuilder::InliningResult
|
IonBuilder::InliningResult
|
||||||
IonBuilder::inlineArrayPush(CallInfo& callInfo)
|
IonBuilder::inlineArrayPush(CallInfo& callInfo)
|
||||||
{
|
{
|
||||||
if (callInfo.argc() != 1 || callInfo.constructing()) {
|
const uint32_t inlineArgsLimit = 10;
|
||||||
|
if (callInfo.argc() < 1 || callInfo.argc() > inlineArgsLimit || callInfo.constructing()) {
|
||||||
trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
|
trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
|
||||||
return InliningStatus_NotInlined;
|
return InliningStatus_NotInlined;
|
||||||
}
|
}
|
||||||
|
|
||||||
MDefinition* obj = convertUnboxedObjects(callInfo.thisArg());
|
MDefinition* obj = convertUnboxedObjects(callInfo.thisArg());
|
||||||
MDefinition* value = callInfo.getArg(0);
|
for (uint32_t i = 0; i < callInfo.argc(); i++) {
|
||||||
if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
|
MDefinition* value = callInfo.getArg(i);
|
||||||
&obj, nullptr, &value, /* canModify = */ false))
|
if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
|
||||||
{
|
&obj, nullptr, &value, /* canModify = */ false))
|
||||||
trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
|
{
|
||||||
return InliningStatus_NotInlined;
|
trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
|
||||||
|
return InliningStatus_NotInlined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getInlineReturnType() != MIRType::Int32)
|
if (getInlineReturnType() != MIRType::Int32)
|
||||||
|
@ -845,24 +848,73 @@ IonBuilder::inlineArrayPush(CallInfo& callInfo)
|
||||||
|
|
||||||
callInfo.setImplicitlyUsedUnchecked();
|
callInfo.setImplicitlyUsedUnchecked();
|
||||||
|
|
||||||
if (conversion == TemporaryTypeSet::AlwaysConvertToDoubles ||
|
bool toDouble =
|
||||||
conversion == TemporaryTypeSet::MaybeConvertToDoubles)
|
conversion == TemporaryTypeSet::AlwaysConvertToDoubles ||
|
||||||
{
|
conversion == TemporaryTypeSet::MaybeConvertToDoubles;
|
||||||
MInstruction* valueDouble = MToDouble::New(alloc(), value);
|
|
||||||
current->add(valueDouble);
|
|
||||||
value = valueDouble;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unboxedType == JSVAL_TYPE_MAGIC)
|
if (unboxedType == JSVAL_TYPE_MAGIC)
|
||||||
obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false);
|
obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false);
|
||||||
|
|
||||||
if (needsPostBarrier(value))
|
// If we have more than one argument, we are splitting the function into
|
||||||
current->add(MPostWriteBarrier::New(alloc(), obj, value));
|
// multiple inlined calls to Array.push.
|
||||||
|
//
|
||||||
|
// Still, in case of bailouts, we have to keep the atomicity of the call, as
|
||||||
|
// we cannot resume within Array.push function. To do so, we register a
|
||||||
|
// resume point which would be captured by the upcoming MArrayPush
|
||||||
|
// instructions, and this resume point contains an instruction which
|
||||||
|
// truncates the length of the Array, to its original length in case of
|
||||||
|
// bailouts, and resume before the Array.push call.
|
||||||
|
MResumePoint* lastRp = nullptr;
|
||||||
|
MInstruction* truncate = nullptr;
|
||||||
|
if (callInfo.argc() > 1) {
|
||||||
|
MInstruction* elements = MElements::New(alloc(), obj);
|
||||||
|
MInstruction* length = MArrayLength::New(alloc(), elements);
|
||||||
|
truncate = MSetArrayLength::New(alloc(), obj, length);
|
||||||
|
truncate->setRecoveredOnBailout();
|
||||||
|
|
||||||
MArrayPush* ins = MArrayPush::New(alloc(), obj, value, unboxedType);
|
current->add(elements);
|
||||||
current->add(ins);
|
current->add(length);
|
||||||
|
current->add(truncate);
|
||||||
|
|
||||||
|
// Restore the stack, such that resume points are created with the stack
|
||||||
|
// as it was before the call.
|
||||||
|
callInfo.pushFormals(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
MInstruction* ins = nullptr;
|
||||||
|
for (uint32_t i = 0; i < callInfo.argc(); i++) {
|
||||||
|
MDefinition* value = callInfo.getArg(i);
|
||||||
|
if (toDouble) {
|
||||||
|
MInstruction* valueDouble = MToDouble::New(alloc(), value);
|
||||||
|
current->add(valueDouble);
|
||||||
|
value = valueDouble;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsPostBarrier(value))
|
||||||
|
current->add(MPostWriteBarrier::New(alloc(), obj, value));
|
||||||
|
|
||||||
|
ins = MArrayPush::New(alloc(), obj, value, unboxedType);
|
||||||
|
current->add(ins);
|
||||||
|
|
||||||
|
if (callInfo.argc() > 1) {
|
||||||
|
// Restore that call stack and the array length.
|
||||||
|
MOZ_TRY(resumeAt(ins, pc));
|
||||||
|
ins->resumePoint()->addStore(alloc(), truncate, lastRp);
|
||||||
|
lastRp = ins->resumePoint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callInfo.argc() > 1) {
|
||||||
|
// Fix the stack to represent the state after the call execution.
|
||||||
|
callInfo.popFormals(current);
|
||||||
|
}
|
||||||
current->push(ins);
|
current->push(ins);
|
||||||
|
|
||||||
|
if (callInfo.argc() > 1) {
|
||||||
|
ins = MNop::New(alloc());
|
||||||
|
current->add(ins);
|
||||||
|
}
|
||||||
|
|
||||||
MOZ_TRY(resumeAfter(ins));
|
MOZ_TRY(resumeAfter(ins));
|
||||||
return InliningStatus_Inlined;
|
return InliningStatus_Inlined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9226,6 +9226,12 @@ class MSetArrayLength
|
||||||
AliasSet getAliasSet() const override {
|
AliasSet getAliasSet() const override {
|
||||||
return AliasSet::Store(AliasSet::ObjectFields);
|
return AliasSet::Store(AliasSet::ObjectFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// By default no, unless built as a recovered instruction.
|
||||||
|
MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
|
||||||
|
bool canRecoverOnBailout() const override {
|
||||||
|
return isRecoveredOnBailout();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class MGetNextEntryForIterator
|
class MGetNextEntryForIterator
|
||||||
|
|
|
@ -1687,6 +1687,38 @@ RArrayState::recover(JSContext* cx, SnapshotIterator& iter) const
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MSetArrayLength::writeRecoverData(CompactBufferWriter& writer) const
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(canRecoverOnBailout());
|
||||||
|
// For simplicity, we capture directly the object instead of the elements
|
||||||
|
// pointer.
|
||||||
|
MOZ_ASSERT(elements()->type() != MIRType::Elements);
|
||||||
|
writer.writeUnsigned(uint32_t(RInstruction::Recover_SetArrayLength));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RSetArrayLength::RSetArrayLength(CompactBufferReader& reader)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
RSetArrayLength::recover(JSContext* cx, SnapshotIterator& iter) const
|
||||||
|
{
|
||||||
|
RootedValue result(cx);
|
||||||
|
RootedArrayObject obj(cx, &iter.read().toObject().as<ArrayObject>());
|
||||||
|
RootedValue len(cx, iter.read());
|
||||||
|
|
||||||
|
RootedId id(cx, NameToId(cx->names().length));
|
||||||
|
ObjectOpResult error;
|
||||||
|
if (!ArraySetLength(cx, obj, id, JSPROP_PERMANENT, len, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
result.setObject(*obj);
|
||||||
|
iter.storeInstructionResult(result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
MAssertRecoveredOnBailout::writeRecoverData(CompactBufferWriter& writer) const
|
MAssertRecoveredOnBailout::writeRecoverData(CompactBufferWriter& writer) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -110,6 +110,7 @@ namespace jit {
|
||||||
_(SimdBox) \
|
_(SimdBox) \
|
||||||
_(ObjectState) \
|
_(ObjectState) \
|
||||||
_(ArrayState) \
|
_(ArrayState) \
|
||||||
|
_(SetArrayLength) \
|
||||||
_(AtomicIsLockFree) \
|
_(AtomicIsLockFree) \
|
||||||
_(AssertRecoveredOnBailout)
|
_(AssertRecoveredOnBailout)
|
||||||
|
|
||||||
|
@ -688,6 +689,14 @@ class RArrayState final : public RInstruction
|
||||||
MOZ_MUST_USE bool recover(JSContext* cx, SnapshotIterator& iter) const override;
|
MOZ_MUST_USE bool recover(JSContext* cx, SnapshotIterator& iter) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class RSetArrayLength final : public RInstruction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RINSTRUCTION_HEADER_NUM_OP_(SetArrayLength, 2)
|
||||||
|
|
||||||
|
MOZ_MUST_USE bool recover(JSContext* cx, SnapshotIterator& iter) const override;
|
||||||
|
};
|
||||||
|
|
||||||
class RAtomicIsLockFree final : public RInstruction
|
class RAtomicIsLockFree final : public RInstruction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
Загрузка…
Ссылка в новой задаче