Question about 'CodeGenFunction::EmitGotoStmt'

Hi All,

From a spec benchmark, I have seen that the ‘goto’ statement goes to

its destination through the cleanup function as below.

void CodeGenFunction::EmitGotoStmt(const GotoStmt &S) {
  // If this code is reachable then emit a stop point (if generating
  // debug info). We have to do this ourselves because we are on the
  // "simple" statement path.
  if (HaveInsertPoint())
    EmitStopPoint(&S);
  EmitBranchThroughCleanup(getJumpDestForLabel(S.getLabel()));
}

I guess we could emit the branch for the target directly. If possible,
can someone let me know why the goto statement has to go through the
cleanup function please? If I missed something, please let me know.

Additionally, I would like to subscribe to cfe-dev with the email
address jingu.kang@arm.com. I have sent the email to
subscribe@llvm.org. If possible, can someone check it please?

Thanks
JinGu Kang

From a spec benchmark, I have seen that the ‘goto’ statement goes to
its destination through the cleanup function as below.

void CodeGenFunction::EmitGotoStmt(const GotoStmt &S) {
  // If this code is reachable then emit a stop point (if generating
  // debug info). We have to do this ourselves because we are on the
  // "simple" statement path.
  if (HaveInsertPoint())
    EmitStopPoint(&S);
  EmitBranchThroughCleanup(getJumpDestForLabel(S.getLabel()));
}

I guess we could emit the branch for the target directly. If possible,
can someone let me know why the goto statement has to go through the
cleanup function please? If I missed something, please let me know.

I haven't looked, but one reason would be if the 'goto' transfers out
of a block that has a local variable with a destructor; the destructor
has to run before control transfers to the 'goto' label. If there are
no such local variables, there is no cleanup to do, and the 'goto'
becomes a simple branch.
--paulr

Hi Paul

Thanks for your kind explanation.

I have investigated the situation more.

clang pushes cleanup for lifetime.marker of local variable. When the
cleanup is popped, the fixup for the goto statement is updated with
the cleanup.

From a spec benchmark, it looks like it causes more instructions as below.

For AArch64
<test+11400> ldr x8, [x19, #16]
<test+11404> lsl x9, x24, #4
<test+11408> ldrh w8, [x8, x9]
<test+11412> cbz w8, 0x34ce94 <test+12868> --> goto cleanup1

cleanup1:
<test+12868> mov w8, #0x8
<test+12872> b 0x34d034 <test+13284> --> goto cleanup2

cleanup2:
<test+13284> cmp w8, #0x6
<test+13288> b.eq 0x34edc8 <test+20856>
<test+13292> cmp w8, #0x8
<test+13296> b.eq 0x34edc8 <test+20856> --> goto destination

destination:
<test+20856> ldr w8, [sp, #392]
<test+20860> tbz w8, #0, 0x34ee28 <test+20952>
<test+20864> ldr x9, [sp, #296]
<test+20868> cbz x9, 0x34f4f8 <test+22696>

We could disable the lifetime.marker with the cmd option "-Xclang
-disable-lifetime-markers" but it looks bad for llvm passes...

At this moment, I am looking at below comment and code...

/// A branch fixup. These are required when emitting a goto to a
/// label which hasn't been emitted yet. The goto is optimistically
/// emitted as a branch to the basic block for the label, and (if it
/// occurs in a scope with non-trivial cleanups) a fixup is added to
/// the innermost cleanup. When a (normal) cleanup is popped, any
/// unresolved fixups in that scope are threaded through the cleanup.
struct BranchFixup {

void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough)
...
      // IV. Pop the cleanup and emit it.
...
      // Optimistically hope that any fixups will continue falling through.
      for (unsigned I = FixupDepth, E = EHStack.getNumBranchFixups();
           I < E; ++I) {
        BranchFixup &Fixup = EHStack.getBranchFixup(I);
        if (!Fixup.Destination) continue;
        if (!Fixup.OptimisticBranchBlock) {
          createStoreInstBefore(Builder.getInt32(Fixup.DestinationIndex),
                                getNormalCleanupDestSlot(),
                                Fixup.InitialBranch);
          Fixup.InitialBranch->setSuccessor(0, NormalEntry);
        }
        Fixup.OptimisticBranchBlock = NormalExit;
      }

Is it possible to avoid adding a fixup to the cleanup with
lifetime.marker or something like that? If I missed something, please
let me know.

Thanks
JinGu Kang

2021년 6월 29일 (화) 오후 6:40, <paul.robinson@sony.com>님이 작성:

Additionally, I am looking at this case from perlbench of spec2017.

2021년 6월 30일 (수) 오후 12:49, jingu kang <jaykang10@gmail.com>님이 작성:

From: jingu kang <jaykang10@gmail.com>
Sent: Wednesday, June 30, 2021 9:00 AM
To: Robinson, Paul <paul.robinson@sony.com>; rjmccall@apple.com
Cc: Jingu.Kang@arm.com; cfe-dev@lists.llvm.org
Subject: Re: [cfe-dev] Question about 'CodeGenFunction::EmitGotoStmt'

Additionally, I am looking at this case from perlbench of spec2017.

2021년 6월 30일 (수) 오후 12:49, jingu kang <jaykang10@gmail.com>님이 작성:
>
> Hi Paul
>
> Thanks for your kind explanation.
>
> I have investigated the situation more.
>
> clang pushes cleanup for lifetime.marker of local variable. When the
> cleanup is popped, the fixup for the goto statement is updated with
> the cleanup.
>
> From a spec benchmark, it looks like it causes more instructions as
below.
> For AArch64
> <test+11400> ldr x8, [x19, #16]
> <test+11404> lsl x9, x24, #4
> <test+11408> ldrh w8, [x8, x9]
> <test+11412> cbz w8, 0x34ce94 <test+12868> --> goto cleanup1
>
> cleanup1:
> <test+12868> mov w8, #0x8
> <test+12872> b 0x34d034 <test+13284> --> goto cleanup2
>
> cleanup2:
> <test+13284> cmp w8, #0x6
> <test+13288> b.eq 0x34edc8 <test+20856>
> <test+13292> cmp w8, #0x8
> <test+13296> b.eq 0x34edc8 <test+20856> --> goto destination
>
> destination:
> <test+20856> ldr w8, [sp, #392]
> <test+20860> tbz w8, #0, 0x34ee28 <test+20952>
> <test+20864> ldr x9, [sp, #296]
> <test+20868> cbz x9, 0x34f4f8 <test+22696>
>
> We could disable the lifetime.marker with the cmd option "-Xclang
> -disable-lifetime-markers" but it looks bad for llvm passes...
>
> At this moment, I am looking at below comment and code...
>
> /// A branch fixup. These are required when emitting a goto to a
> /// label which hasn't been emitted yet. The goto is optimistically
> /// emitted as a branch to the basic block for the label, and (if it
> /// occurs in a scope with non-trivial cleanups) a fixup is added to
> /// the innermost cleanup. When a (normal) cleanup is popped, any
> /// unresolved fixups in that scope are threaded through the cleanup.
> struct BranchFixup {
>
> void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough)
> ...
> // IV. Pop the cleanup and emit it.
> ...
> // Optimistically hope that any fixups will continue falling
through.
> for (unsigned I = FixupDepth, E = EHStack.getNumBranchFixups();
> I < E; ++I) {
> BranchFixup &Fixup = EHStack.getBranchFixup(I);
> if (!Fixup.Destination) continue;
> if (!Fixup.OptimisticBranchBlock) {
>
createStoreInstBefore(Builder.getInt32(Fixup.DestinationIndex),
> getNormalCleanupDestSlot(),
> Fixup.InitialBranch);
> Fixup.InitialBranch->setSuccessor(0, NormalEntry);
> }
> Fixup.OptimisticBranchBlock = NormalExit;
> }
>
> Is it possible to avoid adding a fixup to the cleanup with
> lifetime.marker or something like that? If I missed something, please
> let me know.

Unfortunately I am unfamiliar with the lifetime marker mechanism,
so I cannot help you there.
--paulr

Normally I'd expect jump threading to simplify this sort of construct. Probably better to look from that angle, rather than try to convince clang to emit code in the form you want.

-Eli

From: jingu kang <jaykang10@gmail.com>
Sent: Wednesday, June 30, 2021 9:00 AM
To: Robinson, Paul <paul.robinson@sony.com>; rjmccall@apple.com
Cc: Jingu.Kang@arm.com; cfe-dev@lists.llvm.org
Subject: Re: [cfe-dev] Question about 'CodeGenFunction::EmitGotoStmt'

Additionally, I am looking at this case from perlbench of spec2017.

2021년 6월 30일 (수) 오후 12:49, jingu kang <jaykang10@gmail.com>님이 작성:

Hi Paul

Thanks for your kind explanation.

I have investigated the situation more.

clang pushes cleanup for lifetime.marker of local variable. When the
cleanup is popped, the fixup for the goto statement is updated with
the cleanup.

From a spec benchmark, it looks like it causes more instructions as

below.

For AArch64
<test+11400> ldr x8, [x19, #16]
<test+11404> lsl x9, x24, #4
<test+11408> ldrh w8, [x8, x9]
<test+11412> cbz w8, 0x34ce94 <test+12868> --> goto cleanup1

cleanup1:
<test+12868> mov w8, #0x8
<test+12872> b 0x34d034 <test+13284> --> goto cleanup2

cleanup2:
<test+13284> cmp w8, #0x6
<test+13288> b.eq 0x34edc8 <test+20856>
<test+13292> cmp w8, #0x8
<test+13296> b.eq 0x34edc8 <test+20856> --> goto destination

destination:
<test+20856> ldr w8, [sp, #392]
<test+20860> tbz w8, #0, 0x34ee28 <test+20952>
<test+20864> ldr x9, [sp, #296]
<test+20868> cbz x9, 0x34f4f8 <test+22696>

We could disable the lifetime.marker with the cmd option "-Xclang
-disable-lifetime-markers" but it looks bad for llvm passes...

At this moment, I am looking at below comment and code...

/// A branch fixup. These are required when emitting a goto to a
/// label which hasn't been emitted yet. The goto is optimistically
/// emitted as a branch to the basic block for the label, and (if it
/// occurs in a scope with non-trivial cleanups) a fixup is added to
/// the innermost cleanup. When a (normal) cleanup is popped, any
/// unresolved fixups in that scope are threaded through the cleanup.
struct BranchFixup {

void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough)
...
      // IV. Pop the cleanup and emit it.
...
      // Optimistically hope that any fixups will continue falling

through.

      for (unsigned I = FixupDepth, E = EHStack.getNumBranchFixups();
           I < E; ++I) {
        BranchFixup &Fixup = EHStack.getBranchFixup(I);
        if (!Fixup.Destination) continue;
        if (!Fixup.OptimisticBranchBlock) {

createStoreInstBefore(Builder.getInt32(Fixup.DestinationIndex),

                                getNormalCleanupDestSlot(),
                                Fixup.InitialBranch);
          Fixup.InitialBranch->setSuccessor(0, NormalEntry);
        }
        Fixup.OptimisticBranchBlock = NormalExit;
      }

Is it possible to avoid adding a fixup to the cleanup with
lifetime.marker or something like that? If I missed something, please
let me know.

We could treat branches through lifetime scopes differently, but
then we’d emit worse lifetime annotations, which could interfere
with optimization in other ways. I don’t know what problem you’re
seeing, but generally all the branch-fixup stuff is pretty simple
to eliminate with jump-folding. I don’t think this is a problem we
can solve in the frontend without major rearchitecture.

John.

I appreciate your kind comments. Paul, Eli, John.

I was looking at gcc's output from the same c code as below.

<test+2580> ldr x14, [x12, #16]
<test+2584> ubfiz x3, x11, #4, #32
<test+2588> ldrh w6, [x14, x3]
<test+2592> cbz w6, 0x4fe920 <test+480> --> goto destination

destination:
<test+480> ldr w2, [sp, #120]
<test+484> cbnz w2, 0x4fe9b0 <test+624>
<test+488> cbz w23, 0x501e7c <test+14140>
<test+492> adrp x12, 0x697000 <PL_sv_consts+32>
<test+496> sub x19, x19, #0x78

As you can see on the above output, there is direct branch
instruction. I tried to figure out what causes branches through the
cleanup from the goto statement in clang/llvm.
As Eli and John mentioned, it could be handled on jump threading or
something like that. Let me check it.

Thanks for good comments again,
JinGu Kang

2021년 6월 30일 (수) 오후 7:12, John McCall <rjmccall@apple.com>님이 작성:

As an update, I can see the JumpThreading pass detects the branches
with cleanup. However, it fails to thread the branches because it
could cause irreducible loops...
It looks like we can thread them using the
'-jump-threading-across-loop-headers' option which is for testing.
The source code has lots of goto statements and it causes llvm's
LooInfo to detect weird loops. It seems I need to look at the weird
loops, which block the jump threading, before tackle the JumpThreading
pass.

Thanks
JinGu Kang