[llvm-dev] Strange coroutine optimizations

Ariya Shajii via llvm-dev llvm-dev at lists.llvm.org
Sat Oct 6 11:21:54 PDT 2018


Hi,

I've been using LLVM coroutines with great success over the last couple
months. Everything works nicely, but I encountered the following strange
optimization quirk. Take a look at the following two cases, which I'd
expect to produce identical optimized code:

### Case 1: Constant argument

Code (simple integer iterator up to a specified value):

```
def range(n: Int) -> Int {
  var i = 0
  while i < n {
    yield i
    i = i + 1
  }
}

for i in range(2) {
  print i
}
```

Generated IR (similar structure to the examples in the coroutine docs):

```
define private void @main([...]) {
preamble:
  [...]
  %2 = alloca i64, i64 1
  br label %entry

entry:                                            ; preds = %preamble
  %3 = call i8* @range(i64 2)
  br label %for

for_cont:                                         ; No predecessors!
  br label %for

for:                                              ; preds = %body,
%for_cont, %entry
  call void @llvm.coro.resume(i8* %3)
  %4 = call i1 @llvm.coro.done(i8* %3)
  br i1 %4, label %cleanup, label %body

body:                                             ; preds = %for
  %5 = call i8* @llvm.coro.promise(i8* %3, i32 8, i1 false)
  %6 = bitcast i8* %5 to i64*
  %7 = load i64, i64* %6
  store i64 %7, i64* %2
  %8 = load i64, i64* %2
  call void @print_int(i64 %8)
  br label %for

cleanup:                                          ; preds = %for
  call void @llvm.coro.destroy(i8* %3)
  br label %exit

exit:                                             ; preds = %cleanup
  ret void
}

define private i8* @range(i64) {
preamble:
  %promise = alloca i64, i64 1
  %1 = bitcast i64* %promise to i8*
  %id = call token @llvm.coro.id(i32 0, i8* %1, i8* null, i8* null)
  %2 = alloca i64, i64 1
  store i64 %0, i64* %2
  %3 = alloca i64, i64 1
  %4 = call i1 @llvm.coro.alloc(token %id)
  br i1 %4, label %alloc, label %entry

alloc:                                            ; preds = %preamble
  %5 = call i64 @llvm.coro.size.i64()
  %6 = call i8* @alloc(i64 %5)
  br label %entry

entry:                                            ; preds = %preamble,
%alloc
  %7 = phi i8* [ null, %preamble ], [ %6, %alloc ]
  %hdl = call i8* @llvm.coro.begin(token %id, i8* %7)
  %8 = call i8 @llvm.coro.suspend(token none, i1 false)
  switch i8 %8, label %suspend [
    i8 0, label %10
    i8 1, label %cleanup
  ]

final:                                            ; preds = %exit
  %9 = call i8 @llvm.coro.suspend(token none, i1 true)
  switch i8 %9, label %suspend [
    i8 0, label %21
    i8 1, label %cleanup
  ]

; <label>:10:                                     ; preds = %entry
  store i64 0, i64* %3
  br label %while

while:                                            ; preds = %18, %10
  %11 = load i64, i64* %3
  %12 = load i64, i64* %2
  %13 = icmp slt i64 %11, %12
  %14 = zext i1 %13 to i8
  %15 = trunc i8 %14 to i1
  br i1 %15, label %body, label %exit

body:                                             ; preds = %while
  %16 = load i64, i64* %3
  store i64 %16, i64* %promise
  %17 = call i8 @llvm.coro.suspend(token none, i1 false)
  switch i8 %17, label %suspend [
    i8 0, label %18
    i8 1, label %cleanup
  ]

; <label>:18:                                     ; preds = %body
  %19 = load i64, i64* %3
  %20 = add i64 %19, 1
  store i64 %20, i64* %3
  br label %while

exit:                                             ; preds = %while
  br label %final

; <label>:21:                                     ; preds = %final
  unreachable

cleanup:                                          ; preds = %final, %body,
%entry
  %22 = call i8* @llvm.coro.free(token %id, i8* %hdl)
  br label %suspend

suspend:                                          ; preds = %final, %body,
%entry, %cleanup
  %23 = call i1 @llvm.coro.end(i8* %hdl, i1 false)
  ret i8* %hdl
}
```

Optimized IR:

```
define i32 @main([...]) local_unnamed_addr {
entry:
  [...]
  tail call void @print_int(i64 0)
  tail call void @print_int(i64 1)
  ret i32 0
}
```

Everything works as expected in this case.


### Case 2: Inline constant

Almost identical code, but get rid of the argument and just paste the `2`
into the function:

```
def range() -> Int {
  var i = 0
  while i < 2 {
    yield i
    i = i + 1
  }
}

for i in range() {
  print i
}
```

Unoptimized LLVM code is almost identical, except `range` is called without
arguments (and the corresponding `alloca`+`store` in the first block of
`range` is gone) and the 2nd argument of the `icmp` is a constant `2`.

But strangely the optimized code is very different:

```
define i32 @main([...]) local_unnamed_addr {
entry:
  [...]
  br label %body.i

body.i:                                           ; preds =
%body.i.backedge, %exit
  %.sroa.6.1.i = phi i64 [ 0, %exit ], [ %.sroa.6.1.i.be, %body.i.backedge ]
  %.sroa.11.1.i = phi i2 [ 1, %exit ], [ %.sroa.11.1.i.be, %body.i.backedge
]
  tail call void @print_int(i64 %.sroa.6.1.i)
  switch i2 %.sroa.11.1.i, label %unreachable.i8.i [
    i2 0, label %body.i.backedge
    i2 1, label %AfterCoroSuspend11.i5.i
    i2 -2, label %main.exit
  ]

AfterCoroSuspend11.i5.i:                          ; preds = %body.i
  br label %body.i.backedge

body.i.backedge:                                  ; preds =
%AfterCoroSuspend11.i5.i, %body.i
  %.sroa.6.1.i.be = phi i64 [ 1, %AfterCoroSuspend11.i5.i ], [ 0, %body.i ]
  %.sroa.11.1.i.be = phi i2 [ -2, %AfterCoroSuspend11.i5.i ], [ 1, %body.i ]
  br label %body.i

unreachable.i8.i:                                 ; preds = %body.i
  unreachable

main.exit:                                        ; preds = %body.i
  ret i32 0
}
```

What's going on here? Am I doing something wrong when optimizing through
the C++ API? This is my optimization code:

```
codegen(module);
// verifyModule etc.

std::unique_ptr<legacy::PassManager> pm(new legacy::PassManager());
std::unique_ptr<legacy::FunctionPassManager> fpm(new
legacy::FunctionPassManager(module));

unsigned optLevel = 3;
unsigned sizeLevel = 0;
PassManagerBuilder builder;

if (!debug) {
builder.OptLevel = optLevel;
builder.SizeLevel = sizeLevel;
builder.Inliner = createFunctionInliningPass(optLevel, sizeLevel, false);
builder.DisableUnitAtATime = false;
builder.DisableUnrollLoops = false;
builder.LoopVectorize = true;
builder.SLPVectorize = true;
}

builder.MergeFunctions = true;
addCoroutinePassesToExtensionPoints(builder);
builder.populateModulePassManager(*pm);
builder.populateFunctionPassManager(*fpm);

fpm->doInitialization();
for (Function& f : *module)
fpm->run(f);
fpm->doFinalization();

pm->run(*module);
```

Any help would be much appreciated.

Many Thanks,
Ariya
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20181006/5b523901/attachment-0001.html>


More information about the llvm-dev mailing list