LLVM-IR for coroutines not properly lowered by opt

Viewed 177

I am trying to understand how llvm lowers coroutines. For that purpose I created a small c program called llvm_intrinsics.c that calls llvm builtins:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

void* f(int n) {
  __builtin_coro_id(0, 0, 0, 0);
  int8_t* hdl = __builtin_coro_begin((int*)malloc(__builtin_coro_size()));
  for (;;) {
    printf("%d\n",++n);
    switch (__builtin_coro_suspend(0)) {
      case 0:
        continue;
      case 1:
        goto CLEANUP;
      default:
        goto SUSPEND;
    }
  }
CLEANUP:
  free(__builtin_coro_free(hdl));
SUSPEND:
  __builtin_coro_end(hdl, 0);
  return hdl;
}

int main() {
  void* hdl = f(4);
  __builtin_coro_resume(hdl);
  __builtin_coro_resume(hdl);
  __builtin_coro_destroy(hdl);
  return 0;
}

This should (and upon inspection does indeed) generate LLVM-IR code that is almost exactly the example in the LLVM documentation code from docs:

define i8* @f(i32 %n) {
entry:
  %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
  %size = call i32 @llvm.coro.size.i32()
  %alloc = call i8* @malloc(i32 %size)
  %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc)
  br label %loop
loop:
  %n.val = phi i32 [ %n, %entry ], [ %inc, %loop ]
  %inc = add nsw i32 %n.val, 1
  call void @print(i32 %n.val)
  %0 = call i8 @llvm.coro.suspend(token none, i1 false)
  switch i8 %0, label %suspend [i8 0, label %loop
                                i8 1, label %cleanup]
cleanup:
  %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
  call void @free(i8* %mem)
  br label %suspend
suspend:
  %unused = call i1 @llvm.coro.end(i8* %hdl, i1 false)
  ret i8* %hdl
}

clang when building C does not run the llvm transformation passes at all, so one needs to run opt by hand. This is unfortunate generally but since the purposes of this exercise are pedagogic it is not (yet) a problem. Here is a build script that builds the C file also emiting the intermediate LLVM-IR files.

#!/usr/bin/env bash
set -e

echo "Compiling into LLVM IR"
clang -fcoroutines-ts  -emit-llvm -S llvm_intrinsics.c -o llvm_intrinsics.ll

# echo "Early.."
if [ "$1" == "--holistic-opt" ]; then
    echo "Running all coroutine passes"
    opt -coro-early -coro-split -coro-elide -coro-cleanup llvm_intrinsics.ll -S -o llvm_intrinsics_clean.ll
else
    echo "Running each pass"
    echo "Early.."
    opt -coro-early llvm_intrinsics.ll -S -o llvm_intrinsics_early.ll
    echo "Split.."
    opt -coro-split llvm_intrinsics_early.ll -S -o llvm_intrinsics_split.ll
    echo "Elide.."
    opt -coro-elide llvm_intrinsics_split.ll -S -o llvm_intrinsics_elide.ll
    echo "Cleanup.."
    opt -coro-cleanup llvm_intrinsics_elide.ll -S -o llvm_intrinsics_clean.ll
fi

echo "Compiling LLVM"
clang -lm llvm_intrinsics_clean.ll -o llvm_intrinsics
./llvm_intrinsics

The result I expect when using this script to build and run llvm_intrinsics.c is a) a successful build regardless of the argument provided to the script and b) an executable that only prints 4\n5\n. However here is what it does:

$ ./build.sh
Compiling into LLVM IR
Running each pass
Early..
Split..
Elide..
Cleanup..
Compiling LLVM
fatal error: error in backend: Cannot select: intrinsic %llvm.coro.size
clang-10: error: clang frontend command failed with exit code 70 (use -v to see invocation)
clang version 10.0.1 
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /nix/store/6pzqj9q656vc1msa675k75hmhsrfizsy-clang-10.0.1/bin
clang-10: note: diagnostic msg: PLEASE submit a bug report to  and include the crash backtrace, preprocessed source, and associated run script.
clang-10: note: diagnostic msg: Error generating preprocessed source(s) - no preprocessable inputs.


$ ./build.sh --holistic-opt | head
Compiling into LLVM IR
Running all coroutine passes
Compiling LLVM
5
6
7
8
9
10
11

To put it in words opt fails to run the passes separately and when it runs them together it seems to run them but yields a wrong result. Since C++ coroutines work fine I assume this has to do with something I have misunderstood? Is there another way other than opt to run the passes?

0 Answers
Related