Skip to content
  • Andreas Klebinger's avatar
    Fix bug in the x86 backend involving the CFG. · aa778152
    Andreas Klebinger authored and Marge Bot's avatar Marge Bot committed
    This is part two of fixing #17334.
    
    There are two parts to this commit:
    
    - A bugfix for computing loop levels
    - A bugfix of basic block invariants in the NCG.
    
    -----------------------------------------------------------
    
    In the first bug we ended up with a CFG of the sort: [A -> B -> C]
    This was represented via maps as fromList [(A,B),(B,C)] and later
    transformed into a adjacency array. However the transformation did
    not include block C in the array (since we only looked at the keys of
    the map).
    
    This was still fine until we tried to look up successors for C and tried
    to read outside of the array bounds when accessing C.
    
    In order to prevent this in the future I refactored to code to include
    all nodes as keys in the map representation. And make this a invariant
    which is checked in a few places.
    
    Overall I expect this to make the code more robust as now any failed
    lookup will represent an error, versus failed lookups sometimes being
    expected and sometimes not.
    
    In terms of performance this makes some things cheaper (getting a list
    of all nodes) and others more expensive (adding a new edge). Overall
    this adds up to no noteable performance difference.
    
    -----------------------------------------------------------
    
    Part 2: When the NCG generated a new basic block, it did
    not always insert a NEWBLOCK meta instruction in the stream which
    caused a quite subtle bug.
    
        During instruction selection a statement `s`
        in a block B with control of the sort: B -> C
        will sometimes result in control
        flow of the sort:
    
                ┌ < ┐
                v   ^
          B ->  B1  ┴ -> C
    
        as is the case for some atomic operations.
    
        Now to keep the CFG in sync when introducing B1 we clearly
        want to insert it between B and C. However there is
        a catch when we have to deal with self loops.
    
        We might start with code and a CFG of these forms:
    
        loop:
            stmt1               ┌ < ┐
            ....                v   ^
            stmtX              loop ┘
            stmtY
            ....
            goto loop:
    
        Now we introduce B1:
                                ┌ ─ ─ ─ ─ ─┐
            loop:               │   ┌ <  ┐ │
            instrs              v   │    │ ^
            ....               loop ┴ B1 ┴ ┘
            instrsFromX
            stmtY
            goto loop:
    
        This is simple, all outgoing edges from loop now simply
        start from B1 instead and the code generator knows which
        new edges it introduced for the self loop of B1.
    
        Disaster strikes if the statement Y follows the same pattern.
        If we apply the same rule that all outgoing edges change then
        we end up with:
    
            loop ─> B1 ─> B2 ┬─┐
              │      │    └─<┤ │
              │      └───<───┘ │
              └───────<────────┘
    
        This is problematic. The edge B1->B1 is modified as expected.
        However the modification is wrong!
    
        The assembly in this case looked like this:
    
        _loop:
            <instrs>
        _B1:
            ...
            cmpxchgq ...
            jne _B1
            <instrs>
            <end _B1>
        _B2:
            ...
            cmpxchgq ...
            jne _B2
            <instrs>
            jmp loop
    
        There is no edge _B2 -> _B1 here. It's still a self loop onto _B1.
    
        The problem here is that really B1 should be two basic blocks.
        Otherwise we have control flow in the *middle* of a basic block.
        A contradiction!
    
        So to account for this we add yet another basic block marker:
    
        _B:
            <instrs>
        _B1:
            ...
            cmpxchgq ...
            jne _B1
            jmp _B1'
        _B1':
            <instrs>
            <end _B1>
        _B2:
            ...
    
        Now when inserting B2 we will only look at the outgoing edges of B1' and
        everything will work out nicely.
    
        You might also wonder why we don't insert jumps at the end of _B1'. There is
        no way another block ends up jumping to the labels _B1 or _B2 since they are
        essentially invisible to other blocks. View them as control flow labels local
        to the basic block if you'd like.
    
        Not doing this ultimately caused (part 2 of) #17334.
    aa778152