Closed
Bug 620406
Opened 14 years ago
Closed 14 years ago
Constant folding branch condition leads to assert in Assembler.cpp
Categories
(Core Graveyard :: Nanojit, defect, P2)
Core Graveyard
Nanojit
Tracking
(Not tracked)
RESOLVED
FIXED
flash10.1.x-Spicy
People
(Reporter: edwsmith, Unassigned)
References
Details
(Whiteboard: fixed-in-nanojit, fixed-in-tracemonkey)
Attachments
(1 file, 1 obsolete file)
2.29 KB,
patch
|
n.nethercote
:
review+
|
Details | Diff | Splinter Review |
Simple test:
test.as:
function test(s:String) {
var x, y
return x[y>>5] <--- x is undefined, therefore branch-to-UPE is a jump.
}
$ asc test.as
$ avmshell -Dverifyonly test.abc
Assertion failure: !ins->callInfo()->_isPure || ins->isExtant() (.../nanojit/Assembler.cpp:2043)
The cause is related to:
1. AvmCore::integer() is marked pure, but should not be. See bug 620403.
2. The unconditional branch makes the >> expresion dead, which should leave
the implicit integer(y) call dead, but doesn't... the assert fires because
the seemingly dead expression isn't dead (isExtant()==false, but isLive() == true).
Reporter | ||
Comment 1•14 years ago
|
||
Here is the relevant LIR. Y is the undefined constant (4). For the y>>5 expression, we generate a pure call to integer(4), then right shift it.
integer1 = calli.none #integer ( immi5/*4*/ )
rshi1 = rshi integer1, immi8/*5*/
Then we unconditionally branch, and emit some unreachable code, too:
j -> unpatched
getpropertylate_i1 = calli.all #getpropertylate_i ( parami1 immi5/*4*/ rshi1 )
The unreachable reference to rshi1 makes it "live" but doesn't assign it to any register. In assembler.gen, we have:
case LIR_rshi:
ins->oprnd1()->setResultLive(); // sets integer1 live
ins->oprnd2()->setResultLive(); // sets immi5 (4) live
if (ins->isExtant()) // false
asm_arith(ins); // skipped
break;
And the code for LIR_calli for a pure call to integer():
case LIR_calli:
for (int i = 0, argc = ins->argc(); i < argc; i++)
ins->arg(i)->setResultLive();
// It must be impure or pure-and-extant -- it couldn't be
// pure-and-not-extant, because there's no way the codegen
// for a call can be folded into the codegen of another
// LIR instruction.
NanoAssert(!ins->callInfo()->_isPure || ins->isExtant());
asm_call(ins);
break;
The assert imples that the only reason an instruction could be
live but !extant, is if it was folded into another instruction.
However, in this case, nothing was folded - the rshi wasn't folded,
it was in dead code, so its register assignments were cleared, but
its 'live' bit wasn't cleared. We didn't assert in LIR_rshi because
it could have been folded in other scenarios.
Possible bugs:
1. handling of dead paths (code after an unconditional jump or after
a return) doesn't clear live bits. (How could it? how do we know
the instruction really is live?)
2. The assert is too strong on LIR_calli. A call to a pure pure function
should behave just like a plain instruction. Folding isn't the only way
a plain instruction can be !extant, therefore calls are subject to the
same situations.
Relaxing the assert is easy. Rethinking "live" vs "extant" doesn't
seem quite as easy.
This bug might be related to bug 607816
Reporter | ||
Comment 2•14 years ago
|
||
> 2. The assert is too strong on LIR_calli.
This is a patch that fixes bug #2, if we decide it's really a bug.
Rick - can you figure out if this is in any way related to bug 607816?
Nick - looking for input... whats the bug, really? A third possible bug is that our frontend is emitting dead code. But, I think we've had a few precedents where we decided the backend should "just work" even if the LIR code is dumb.
> 1. handling of dead paths (code after an unconditional jump or after
> a return) doesn't clear live bits.
Unreachable code is code that follows an unconditional branch, without a label:
j -> somewhere
/* no label */
unreachable code
Since we assemble bottom-up, we dont know if some code is unreachable
until we see that unconditional branch. By then, there could be many
instructions we have marked (live=1, extant=1).
Anything that spilled will stay extant at this point. (we clear register
assignments but we dont clear the inAr flag). So, the only instructions
that get into this (live=1, extant=0) state are ones that haven't spilled.
The complexity of true unreachable code elimination seems beyond the scope
of the backend, without a lot of work.
Assignee: nobody → edwsmith
Attachment #498757 -
Flags: feedback?(rreitmai)
Reporter | ||
Updated•14 years ago
|
Attachment #498757 -
Flags: feedback?(nnethercote)
Reporter | ||
Updated•14 years ago
|
Component: JIT Compiler (NanoJIT) → Nanojit
Priority: -- → P2
Product: Tamarin → Core
QA Contact: nanojit → nanojit
Reporter | ||
Updated•14 years ago
|
Target Milestone: --- → flash10.1.x-Spicy
Reporter | ||
Updated•14 years ago
|
Flags: in-testsuite?
Comment 3•14 years ago
|
||
Comment on attachment 498757 [details] [diff] [review]
Relax the assert in case LIR_call, and don't emit dead pure calls.
>--- a/nanojit/Assembler.cpp Sat Dec 18 16:56:53 2010 -0500
>+++ b/nanojit/Assembler.cpp Mon Dec 20 11:53:54 2010 -0500
>@@ -2036,12 +2036,14 @@
> countlir_call();
> for (int i = 0, argc = ins->argc(); i < argc; i++)
> ins->arg(i)->setResultLive();
>- // It must be impure or pure-and-extant -- it couldn't be
>- // pure-and-not-extant, because there's no way the codegen
>- // for a call can be folded into the codegen of another
>- // LIR instruction.
>- NanoAssert(!ins->callInfo()->_isPure || ins->isExtant());
>- asm_call(ins);
>+ // Calls are not folded into other instructions,
>+ // but they can be referenced by simple instructions that
>+ // are foldable, such as LIR_rshi. This can leave a pure
>+ // call live but not extant. We treat it like any other
>+ // instruction in that situation.
>+ if (!ins->callInfo()->_isPure || ins->isExtant()) {
>+ asm_call(ins);
>+ }
> break;
I didn't think of the case where the call's result was only used in code
that was dead due to an always-taken branch. You are right, detecting
dead code of this sort is difficult with our backwards assembly pass, so
removing the assert is the best way to go.
But I think the comment isn't quite right because it doesn't mention the
dead code. I suggest:
You might think that a call cannot be pure-and-not-extant, because
there's no way the codegen for a call can be folded into the codegen of
another LIR instruction. However, it's possible that a pure call, C,
has a result that is only be used (directly or indirectly) in a section
of code that is dead due to an always-taken branch. C is dead, but the
assembly pass doesn't realize is dead. So C may end up non-extant, in
which case we don't generate code for it. See bug 620406 for an
example.
It would also be worth adding a comment to isLive() to indicate that it is
conservative, ie. it can say that an instruction is live when it's really
dead due to always-taken branches.
Attachment #498757 -
Flags: feedback?(nnethercote) → feedback+
Reporter | ||
Comment 4•14 years ago
|
||
Agreed. Assembler's notion of "live" really just means an instruction has been used by another instruction the assembler visited. The whole method could be unreachable, for all the assembler knows -- reachability is just not part of the equation at all.
Reporter | ||
Comment 5•14 years ago
|
||
Updated the comment, added comment to LIns.isLive().
Passed acceptance runs on tamarin.
Attachment #499135 -
Flags: review?(nnethercote)
Reporter | ||
Updated•14 years ago
|
Attachment #498757 -
Attachment is patch: false
Reporter | ||
Updated•14 years ago
|
Attachment #498757 -
Attachment is obsolete: true
Attachment #498757 -
Attachment is patch: true
Attachment #498757 -
Flags: feedback?(rreitmai)
Comment 6•14 years ago
|
||
Comment on attachment 499135 [details] [diff] [review]
(v2) Relax the assert in case LIR_call, and don't emit dead pure calls.
> // Generally, void instructions (statements) are always live and
> // non-void instructions (expressions) are live if used by another
> // live instruction. But there are some trickier cases.
>+ // Any non-void instruction can be marked isResultLive=1 even
>+ // when it is unreachable. The assembler marks it live if it
>+ // sees any uses, regardless of whether those uses are in reachable
>+ // code or not.
Nit: Can you clarify slightly, eg. "when it is unreachable, eg. due to an always taken branch"?
Attachment #499135 -
Flags: review?(nnethercote) → review+
Reporter | ||
Comment 7•14 years ago
|
||
sure, no problem.
Reporter | ||
Comment 8•14 years ago
|
||
Reporter | ||
Updated•14 years ago
|
Whiteboard: fixed-in-nanojit
Reporter | ||
Updated•14 years ago
|
Assignee: edwsmith → nobody
Comment 9•14 years ago
|
||
Whiteboard: fixed-in-nanojit → fixed-in-nanojit, fixed-in-tracemonkey
Comment 10•14 years ago
|
||
cdleary-bot mozilla-central merge info:
http://hg.mozilla.org/mozilla-central/rev/41739de5f01d
Updated•14 years ago
|
Status: NEW → RESOLVED
Closed: 14 years ago
Resolution: --- → FIXED
Assignee | ||
Updated•11 years ago
|
Product: Core → Core Graveyard
You need to log in
before you can comment on or make changes to this bug.
Description
•