Skip to content

Commit 5f0a0a2

Browse files
committed
[MERGE chakra-core#855] Fix bug with negative 0 handling in global optimizer
Merge pull request chakra-core#855 from rajatd:negZero-abortOptOnBailout The forward pass sets a bit, m_wasNegativeZeroPreventedByBailout, on an operand if it was int-type-specialized and was protected by a BailOutOnNegativeZero (which would happen if there are uses of that operand downstream in manners which require distinction between -0 and +0 and the forward pass cannot say for sure that the operand can't be -0). The Deadstore pass then looks at this bit to determine if it can remove some -0 bailouts. However, this bit being set to false for an operand doesn't necessarily mean that that operand cannot be -0 if the operation that produced this operand wasn't type-spec'ed at all. Negative zero tracking in the deadstore pass wasn't taking this into account and was getting rid of more -0 bailouts than it should have, resulting in wrong results. Fix being proposed here is, - for the current instruction, detect the source operand for whose defintion we can remove negative zero bailout. - if there is any bailout instruction between this definition and the current instruction, then don't remove the negative zero bailout. Reasoning behind this is that if we removed -0 bailout for, say, s1 and - a bailout happened between def of s1 and an int specialized use of s1, and - s1 was actually -0 (but we removed the bailout), s1 will be +0 in the interpreter leading to wrong results.
2 parents f7c5dbb + b6a616f commit 5f0a0a2

6 files changed

Lines changed: 171 additions & 37 deletions

File tree

lib/Backend/BackwardPass.cpp

Lines changed: 116 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ BackwardPass::CleanupBackwardPassInfoInFlowGraph()
249249
block->noImplicitCallNativeArrayUses = nullptr;
250250
block->noImplicitCallJsArrayHeadSegmentSymUses = nullptr;
251251
block->noImplicitCallArrayLengthSymUses = nullptr;
252+
block->couldRemoveNegZeroBailoutForDef = nullptr;
252253

253254
if (block->loop != nullptr)
254255
{
@@ -372,6 +373,7 @@ BackwardPass::MergeSuccBlocksInfo(BasicBlock * block)
372373
BVSparse<JitArenaAllocator> * fieldHoistCandidates = nullptr;
373374
BVSparse<JitArenaAllocator> * slotDeadStoreCandidates = nullptr;
374375
BVSparse<JitArenaAllocator> * byteCodeUpwardExposedUsed = nullptr;
376+
BVSparse<JitArenaAllocator> * couldRemoveNegZeroBailoutForDef = nullptr;
375377
#if DBG
376378
uint byteCodeLocalsCount = func->GetJnFunction()->GetLocalsCount();
377379
StackSym ** byteCodeRestoreSyms = nullptr;
@@ -435,6 +437,10 @@ BackwardPass::MergeSuccBlocksInfo(BasicBlock * block)
435437
{
436438
cloneStrCandidates = JitAnew(this->globOpt->alloc, BVSparse<JitArenaAllocator>, this->globOpt->alloc);
437439
}
440+
else
441+
{
442+
couldRemoveNegZeroBailoutForDef = JitAnew(this->tempAlloc, BVSparse<JitArenaAllocator>, this->tempAlloc);
443+
}
438444
}
439445

440446
bool firstSucc = true;
@@ -802,6 +808,16 @@ BackwardPass::MergeSuccBlocksInfo(BasicBlock * block)
802808
}
803809
#endif
804810
}
811+
812+
if (blockSucc->couldRemoveNegZeroBailoutForDef != nullptr)
813+
{
814+
couldRemoveNegZeroBailoutForDef->And(blockSucc->couldRemoveNegZeroBailoutForDef);
815+
if (deleteData)
816+
{
817+
JitAdelete(this->tempAlloc, blockSucc->couldRemoveNegZeroBailoutForDef);
818+
blockSucc->couldRemoveNegZeroBailoutForDef = nullptr;
819+
}
820+
}
805821
}
806822

807823
if (blockSucc->noImplicitCallUses != nullptr)
@@ -1002,6 +1018,7 @@ BackwardPass::MergeSuccBlocksInfo(BasicBlock * block)
10021018
Assert(block->noImplicitCallNativeArrayUses == nullptr);
10031019
Assert(block->noImplicitCallJsArrayHeadSegmentSymUses == nullptr);
10041020
Assert(block->noImplicitCallArrayLengthSymUses == nullptr);
1021+
Assert(block->couldRemoveNegZeroBailoutForDef == nullptr);
10051022
}
10061023
else
10071024
{
@@ -1053,6 +1070,7 @@ BackwardPass::MergeSuccBlocksInfo(BasicBlock * block)
10531070
block->noImplicitCallNativeArrayUses = noImplicitCallNativeArrayUses;
10541071
block->noImplicitCallJsArrayHeadSegmentSymUses = noImplicitCallJsArrayHeadSegmentSymUses;
10551072
block->noImplicitCallArrayLengthSymUses = noImplicitCallArrayLengthSymUses;
1073+
block->couldRemoveNegZeroBailoutForDef = couldRemoveNegZeroBailoutForDef;
10561074
}
10571075

10581076
ObjTypeGuardBucket
@@ -1220,6 +1238,11 @@ BackwardPass::DeleteBlockData(BasicBlock * block)
12201238
block->byteCodeRestoreSyms = nullptr;
12211239
#endif
12221240
}
1241+
if (block->couldRemoveNegZeroBailoutForDef != nullptr)
1242+
{
1243+
JitAdelete(this->tempAlloc, block->couldRemoveNegZeroBailoutForDef);
1244+
block->couldRemoveNegZeroBailoutForDef = nullptr;
1245+
}
12231246
}
12241247

12251248
void
@@ -4933,6 +4956,26 @@ BackwardPass::TrackBitWiseOrNumberOp(IR::Instr *const instr)
49334956
}
49344957
}
49354958

4959+
void
4960+
BackwardPass::RemoveNegativeZeroBailout(IR::Instr* instr)
4961+
{
4962+
Assert(instr->HasBailOutInfo() && (instr->GetBailOutKind() & IR::BailOutOnNegativeZero));
4963+
IR::BailOutKind bailOutKind = instr->GetBailOutKind();
4964+
bailOutKind = bailOutKind & ~IR::BailOutOnNegativeZero;
4965+
if (bailOutKind)
4966+
{
4967+
instr->SetBailOutKind(bailOutKind);
4968+
}
4969+
else
4970+
{
4971+
instr->ClearBailOutInfo();
4972+
if (preOpBailOutInstrToProcess == instr)
4973+
{
4974+
preOpBailOutInstrToProcess = nullptr;
4975+
}
4976+
}
4977+
}
4978+
49364979
void
49374980
BackwardPass::TrackIntUsage(IR::Instr *const instr)
49384981
{
@@ -4967,29 +5010,48 @@ BackwardPass::TrackIntUsage(IR::Instr *const instr)
49675010
if(dstSym)
49685011
{
49695012
// For a dst where the def is in this block, transfer the current info into the instruction
4970-
if(trackNegativeZero && negativeZeroDoesNotMatterBySymId->TestAndClear(dstSym->m_id))
5013+
if(trackNegativeZero)
49715014
{
4972-
instr->ignoreNegativeZero = true;
4973-
if(tag == Js::DeadStorePhase && instr->HasBailOutInfo())
5015+
if (negativeZeroDoesNotMatterBySymId->Test(dstSym->m_id))
49745016
{
4975-
IR::BailOutKind bailOutKind = instr->GetBailOutKind();
4976-
if(bailOutKind & IR::BailOutOnNegativeZero)
5017+
instr->ignoreNegativeZero = true;
5018+
}
5019+
5020+
if (tag == Js::DeadStorePhase)
5021+
{
5022+
if (negativeZeroDoesNotMatterBySymId->TestAndClear(dstSym->m_id))
49775023
{
4978-
bailOutKind -= IR::BailOutOnNegativeZero;
4979-
if(bailOutKind)
5024+
if (instr->HasBailOutInfo())
49805025
{
4981-
instr->SetBailOutKind(bailOutKind);
5026+
IR::BailOutKind bailOutKind = instr->GetBailOutKind();
5027+
if (bailOutKind & IR::BailOutOnNegativeZero)
5028+
{
5029+
RemoveNegativeZeroBailout(instr);
5030+
}
49825031
}
4983-
else
5032+
}
5033+
else
5034+
{
5035+
if (instr->HasBailOutInfo())
49845036
{
4985-
instr->ClearBailOutInfo();
4986-
if(preOpBailOutInstrToProcess == instr)
5037+
if (instr->GetBailOutKind() & IR::BailOutOnNegativeZero)
49875038
{
4988-
preOpBailOutInstrToProcess = nullptr;
5039+
if (this->currentBlock->couldRemoveNegZeroBailoutForDef->TestAndClear(dstSym->m_id))
5040+
{
5041+
RemoveNegativeZeroBailout(instr);
5042+
}
49895043
}
5044+
// This instruction could potentially bail out. Hence, we cannot reliably remove negative zero
5045+
// bailouts upstream. If we did, and the operation actually produced a -0, and this instruction
5046+
// bailed out, we'd use +0 instead of -0 in the interpreter.
5047+
this->currentBlock->couldRemoveNegZeroBailoutForDef->ClearAll();
49905048
}
49915049
}
49925050
}
5051+
else
5052+
{
5053+
this->negativeZeroDoesNotMatterBySymId->Clear(dstSym->m_id);
5054+
}
49935055
}
49945056
if(trackIntOverflow)
49955057
{
@@ -5096,38 +5158,41 @@ BackwardPass::TrackIntUsage(IR::Instr *const instr)
50965158
break;
50975159

50985160
case Js::OpCode::Add_I4:
5161+
{
50995162
Assert(dstSym);
51005163
Assert(instr->GetSrc1());
51015164
Assert(instr->GetSrc1()->IsRegOpnd() || instr->GetSrc1()->IsIntConstOpnd());
51025165
Assert(instr->GetSrc2());
51035166
Assert(instr->GetSrc2()->IsRegOpnd() || instr->GetSrc2()->IsIntConstOpnd());
51045167

5105-
if(instr->ignoreNegativeZero ||
5106-
!(instr->GetSrc1()->IsRegOpnd() && instr->GetSrc1()->AsRegOpnd()->m_wasNegativeZeroPreventedByBailout) ||
5107-
!(instr->GetSrc2()->IsRegOpnd() && instr->GetSrc2()->AsRegOpnd()->m_wasNegativeZeroPreventedByBailout))
5168+
if (instr->ignoreNegativeZero ||
5169+
(instr->GetSrc1()->IsIntConstOpnd() && instr->GetSrc1()->AsIntConstOpnd()->GetValue() != 0) ||
5170+
instr->GetSrc2()->IsIntConstOpnd() && instr->GetSrc2()->AsIntConstOpnd()->GetValue() != 0)
51085171
{
5109-
// -0 does not matter for dst, or this instruction does not generate -0 since one of the srcs is not -0
5110-
// (regardless of -0 bailout checks)
51115172
SetNegativeZeroDoesNotMatterIfLastUse(instr->GetSrc1());
51125173
SetNegativeZeroDoesNotMatterIfLastUse(instr->GetSrc2());
51135174
break;
51145175
}
5115-
5176+
51165177
// -0 + -0 == -0. As long as one src is guaranteed to not be -0, -0 does not matter for the other src. Pick a
51175178
// src for which to ignore negative zero, based on which sym is last-use. If both syms are last-use, src2 is
51185179
// picked arbitrarily.
5119-
if(instr->GetSrc2()->IsRegOpnd() &&
5120-
!currentBlock->upwardExposedUses->Test(instr->GetSrc2()->AsRegOpnd()->m_sym->m_id))
5121-
{
5122-
SetNegativeZeroDoesNotMatterIfLastUse(instr->GetSrc2());
5123-
SetNegativeZeroMatters(instr->GetSrc1());
5124-
}
5125-
else
5180+
SetNegativeZeroMatters(instr->GetSrc1());
5181+
SetNegativeZeroMatters(instr->GetSrc2());
5182+
if (tag == Js::DeadStorePhase)
51265183
{
5127-
SetNegativeZeroDoesNotMatterIfLastUse(instr->GetSrc1());
5128-
SetNegativeZeroMatters(instr->GetSrc2());
5184+
if (instr->GetSrc2()->IsRegOpnd() &&
5185+
!currentBlock->upwardExposedUses->Test(instr->GetSrc2()->AsRegOpnd()->m_sym->m_id))
5186+
{
5187+
SetCouldRemoveNegZeroBailoutForDefIfLastUse(instr->GetSrc2());
5188+
}
5189+
else
5190+
{
5191+
SetCouldRemoveNegZeroBailoutForDefIfLastUse(instr->GetSrc1());
5192+
}
51295193
}
51305194
break;
5195+
}
51315196

51325197
case Js::OpCode::Add_A:
51335198
Assert(dstSym);
@@ -5149,26 +5214,26 @@ BackwardPass::TrackIntUsage(IR::Instr *const instr)
51495214
break;
51505215

51515216
case Js::OpCode::Sub_I4:
5217+
{
51525218
Assert(dstSym);
51535219
Assert(instr->GetSrc1());
51545220
Assert(instr->GetSrc1()->IsRegOpnd() || instr->GetSrc1()->IsIntConstOpnd());
51555221
Assert(instr->GetSrc2());
51565222
Assert(instr->GetSrc2()->IsRegOpnd() || instr->GetSrc2()->IsIntConstOpnd());
51575223

5158-
if(instr->ignoreNegativeZero ||
5159-
!(instr->GetSrc1()->IsRegOpnd() && instr->GetSrc1()->AsRegOpnd()->m_wasNegativeZeroPreventedByBailout) ||
5224+
if (instr->ignoreNegativeZero ||
5225+
(instr->GetSrc1()->IsIntConstOpnd() && instr->GetSrc1()->AsIntConstOpnd()->GetValue() != 0) ||
51605226
instr->GetSrc2()->IsIntConstOpnd() && instr->GetSrc2()->AsIntConstOpnd()->GetValue() != 0)
51615227
{
5162-
// At least one of the following is true:
5163-
// - -0 does not matter for dst
5164-
// - Src1 is not -0 (regardless of -0 bailout checks), and so this instruction cannot generate -0
5165-
// - Src2 is a nonzero int constant, and so this instruction cannot generate -0
51665228
SetNegativeZeroDoesNotMatterIfLastUse(instr->GetSrc1());
51675229
SetNegativeZeroDoesNotMatterIfLastUse(instr->GetSrc2());
5168-
break;
51695230
}
5170-
goto NegativeZero_Sub_Default;
5171-
5231+
else
5232+
{
5233+
goto NegativeZero_Sub_Default;
5234+
}
5235+
break;
5236+
}
51725237
case Js::OpCode::Sub_A:
51735238
Assert(dstSym);
51745239
Assert(instr->GetSrc1());
@@ -5197,7 +5262,11 @@ BackwardPass::TrackIntUsage(IR::Instr *const instr)
51975262
NegativeZero_Sub_Default:
51985263
// -0 - 0 == -0. As long as src1 is guaranteed to not be -0, -0 does not matter for src2.
51995264
SetNegativeZeroMatters(instr->GetSrc1());
5200-
SetNegativeZeroDoesNotMatterIfLastUse(instr->GetSrc2());
5265+
SetNegativeZeroMatters(instr->GetSrc2());
5266+
if (this->tag == Js::DeadStorePhase)
5267+
{
5268+
SetCouldRemoveNegZeroBailoutForDefIfLastUse(instr->GetSrc2());
5269+
}
52015270
break;
52025271

52035272
case Js::OpCode::BrEq_I4:
@@ -5628,6 +5697,16 @@ BackwardPass::SetNegativeZeroMatters(IR::Opnd *const opnd)
56285697
}
56295698
}
56305699

5700+
void
5701+
BackwardPass::SetCouldRemoveNegZeroBailoutForDefIfLastUse(IR::Opnd *const opnd)
5702+
{
5703+
StackSym * stackSym = IR::RegOpnd::TryGetStackSym(opnd);
5704+
if (stackSym && !this->currentBlock->upwardExposedUses->Test(stackSym->m_id))
5705+
{
5706+
this->currentBlock->couldRemoveNegZeroBailoutForDef->Set(stackSym->m_id);
5707+
}
5708+
}
5709+
56315710
void
56325711
BackwardPass::SetIntOverflowDoesNotMatterIfLastUse(IR::Opnd *const opnd)
56335712
{

lib/Backend/BackwardPass.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ class BackwardPass
7777
void SetSymIsNotUsedOnlyInNumber(IR::Opnd *const opnd);
7878
void SetSymIsUsedOnlyInNumberIfLastUse(IR::Opnd *const opnd);
7979
void TrackIntUsage(IR::Instr *const instr);
80+
void RemoveNegativeZeroBailout(IR::Instr* instr);
8081
void SetNegativeZeroDoesNotMatterIfLastUse(IR::Opnd *const opnd);
8182
void SetNegativeZeroMatters(IR::Opnd *const opnd);
83+
void SetCouldRemoveNegZeroBailoutForDefIfLastUse(IR::Opnd *const opnd);
8284
void SetIntOverflowDoesNotMatterIfLastUse(IR::Opnd *const opnd);
8385
void SetIntOverflowMatters(IR::Opnd *const opnd);
8486
bool SetIntOverflowDoesNotMatterInRangeIfLastUse(IR::Opnd *const opnd, const int addSubUses);

lib/Backend/FlowGraph.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ class BasicBlock
375375
BVSparse<JitArenaAllocator> * noImplicitCallJsArrayHeadSegmentSymUses;
376376
BVSparse<JitArenaAllocator> * noImplicitCallArrayLengthSymUses;
377377
BVSparse<JitArenaAllocator> * cloneStrCandidates;
378+
BVSparse<JitArenaAllocator> * couldRemoveNegZeroBailoutForDef; // Deadstore pass only
378379
Loop * backwardPassCurrentLoop;
379380

380381
// Global optimizer data
@@ -417,6 +418,7 @@ class BasicBlock
417418
noImplicitCallJsArrayHeadSegmentSymUses(nullptr),
418419
noImplicitCallArrayLengthSymUses(nullptr),
419420
cloneStrCandidates(nullptr),
421+
couldRemoveNegZeroBailoutForDef(nullptr),
420422
byteCodeUpwardExposedUsed(nullptr),
421423
isAirLockCompensationBlock(false),
422424
#if DBG
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2
2+
1
3+
2
4+
1
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4+
//-------------------------------------------------------------------------------------------------------
5+
6+
function test0(addOrSub) {
7+
function makeArrayLength() {
8+
}
9+
var obj0 = {};
10+
var obj1 = {};
11+
var protoObj1 = {};
12+
var arrObj0 = {};
13+
14+
var func4 = function () {
15+
return arrObj0 * (f > obj1.prop1 ? h++ : Function());
16+
};
17+
18+
var f = 1;
19+
var h = 0;
20+
obj1.prop1 = -1;
21+
switch(addOrSub)
22+
{
23+
case 1:
24+
f /= ((1 - 1) * -1) - -(func4.call(arrObj0) << (typeof arrObj0.length == null));
25+
break;
26+
27+
case 2:
28+
f /= ((1 - 1) * -1) + -(func4.call(arrObj0) << (typeof arrObj0.length == null));
29+
break;
30+
}
31+
32+
func4();
33+
34+
print(h);
35+
}
36+
test0(1);
37+
test0(2);
38+
39+
test0(1);
40+
test0(2);

test/Optimizer/rlexe.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,4 +1366,11 @@
13661366
<baseline>bug7100343.baseline</baseline>
13671367
</default>
13681368
</test>
1369+
<test>
1370+
<default>
1371+
<files>negativeZero_bugs.js</files>
1372+
<compile-flags>-mic:2 -off:simplejit</compile-flags>
1373+
<baseline>negativeZero_bugs.baseline</baseline>
1374+
</default>
1375+
</test>
13691376
</regress-exe>

0 commit comments

Comments
 (0)