Identifying loop guards (created by loop-rotation pass)

Hello. I have two problems here.

I have some troubles understanding how you define the term ‘loop guards’. In LoopInfo.h, there is a IsGuarded convenient API that returns true if it the loop is guarded by a branching. I wrote some C function and a simple pass that calls this API, I get the following, which is expected:

void some_func(int i);
void my_loop(int *arr, int sz) {
    for (int i = 0; i < sz; ++i) {
        some_func(i);
    }
}

then emit the ir:

; clang -O0 -Xclang -disable-O0-optnone -emit-llvm -c loop.c
; llvm-dis < loop.bc > loop.ll
; opt -mem2reg -S  loop.ll -o loop.ll

; Function Attrs: noinline nounwind uwtable
define dso_local void @my_loop(i32* %arr, i32 %sz) #0 {
entry:
  br label %for.cond
for.cond:                                         ; preds = %for.inc, %entry
  %i.0 = phi i32 [ 0, %entry ], [ %inc, %for.inc ]
  %cmp = icmp slt i32 %i.0, %sz
  br i1 %cmp, label %for.body, label %for.end
for.body:                                         ; preds = %for.cond
  call void @some_func(i32 %i.0)
  br label %for.inc
for.inc:                                          ; preds = %for.body
  %inc = add nsw i32 %i.0, 1
  br label %for.cond, !llvm.loop !2
for.end:                                          ; preds = %for.cond
  ret void
}

declare dso_local void @some_func(i32) #1

The IsGuarded API will return false for this loop. If we further opt -loop-rotate -S < loop.ll > loop_rotate.ll, then we obtain

; Function Attrs: noinline nounwind uwtable
define dso_local void @my_loop(i32* %arr, i32 %sz) #0 {
entry:
  %cmp1 = icmp slt i32 0, %sz
  br i1 %cmp1, label %for.body.lr.ph, label %for.end
for.body.lr.ph:                                   ; preds = %entry
  br label %for.body
for.body:                                         ; preds = %for.body.lr.ph, %for.inc
  %i.02 = phi i32 [ 0, %for.body.lr.ph ], [ %inc, %for.inc ]
  call void @some_func(i32 %i.02)
  br label %for.inc
for.inc:                                          ; preds = %for.body
  %inc = add nsw i32 %i.02, 1
  %cmp = icmp slt i32 %inc, %sz
  br i1 %cmp, label %for.body, label %for.cond.for.end_crit_edge, !llvm.loop !2
for.cond.for.end_crit_edge:                       ; preds = %for.inc
  br label %for.end
for.end:                                          ; preds = %for.cond.for.end_crit_edge, %entry
  ret void
}

declare dso_local void @some_func(i32) #1

The API returns true this time. To summarize, this is good.

Now what if I write the ‘equivalent C code already rotated’:

void my_loop2(int *arr, int sz) {
    // a rotated version of c code
    int i = 0;
    if (sz > 0) {  // i < sz is an equivalent check
        do {
            some_func(i);
        } while (++i < sz);
    }
}

then convert to ir:

; Function Attrs: noinline nounwind uwtable
define dso_local void @my_loop2(i32* %arr, i32 %sz) #0 {
entry:
  %cmp = icmp sgt i32 %sz, 0
  br i1 %cmp, label %if.then, label %if.end
if.then:                                          ; preds = %entry
  br label %do.body
do.body:                                          ; preds = %do.cond, %if.then
  %i.0 = phi i32 [ 0, %if.then ], [ %inc, %do.cond ]
  call void @some_func(i32 %i.0)
  br label %do.cond
do.cond:                                          ; preds = %do.body
  %inc = add nsw i32 %i.0, 1
  %cmp1 = icmp slt i32 %inc, %sz
  br i1 %cmp1, label %do.body, label %do.end, !llvm.loop !4
do.end:                                           ; preds = %do.cond
  br label %if.end
if.end:                                           ; preds = %do.end, %entry
  ret void
}

The API now returns true, which makes some sense because the IR looks rotated and the IsGuarded API seems to just walk around the CFG to find a ‘guard’.

So this loop guard terminology has nothing to do with the loop-rotate pass, ie it does not differentiate whether a guard is created by the IR operation, or is provided by the user by hand? I looked at D63885, which strengthened this opinion.

Also if I just replace the handwritten if check with something totally irrelevant with the loop itself, like this:

int may_return_anything();
void my_loop3(int *arr, int sz) {
    // here 'guard' is irrelevant to loop contents
    int i = 0;
    if (may_return_anything()) {
        do {
            some_func(i);
        } while (++i < sz);
    }
}

The IsGuarded method still thinks my if is a guard! This starts to make less sense to me though. I was thinking the guard should duplicate the checking of the while part. Looks like I am a bit off.

So these are all expected?

Second problem is if the above are all correct definitions, what should one do to differentiate the loop guards created by a loop rotation pass (my_loop) and those provided by user (my_loop2)? This would be useful in some platform-specific codegen passes.

Thinking of adding metadata to the loop-rotate-generated loop guard and then checking it later - but I am afraid that other passes will arbitrarily discard those metadata.

Thanks for reading the lengthy post.