mono/mono

Add C code backend through llvm-cbe

Open

#11,940 opened on Dec 5, 2018

 (13 comments) (3 reactions) (0 assignees)C# (3,813 forks)batch import
help wantedproposal

Repository metrics

Stars
 (11,435 stars)
PR merge metrics
 (No merged PRs in 30d)

Description

Why compile to C?

The Holy Grail of platform compatibility is had by the C programming language. It is the first language to be supported on many boards. When a programming language compiles to C, it gets access to an entirely new world where most developers cannot go without using pointers and unsafe code.

OpenCL code bases, projects using proprietary C compilers to target ancient hardware, scientific computing clusters, and very exotic game consoles are all projects that are currently unable to use the Mono runtime.

As a quick win, I wanted to see if I could get llvm to emit C code instead of using a new pipeline. Success was pretty quick to be had. By using the llvm-cbe backend, I was able to make mono emit C code for a .exe.

How to Run

  1. Check out https://github.com/JuliaComputing/llvm-cbe into external/llvm/projects (or use https://github.com/mono/llvm/pull/37) cd external/llvm/project && git clone https://github.com/JuliaComputing/llvm-cbe.git

  2. Edit https://github.com/mono/mono/blob/d5547b1929afa91944ba941d66a93767ca534a97/llvm/build.mk#L30 to add llvm-cbe to the list of LLVM_TOOLS_TO_BUILD

  3. Make aot compiler save bitcode files (mono --aot=llvmonly,asmonly)

  4. Pass each bitcode file to that to llvm-cbe llvm/usr/bin/llvm-cbe HelloWorld.exe.bc -o HelloWorld.exe.c

  5. Compile your C into your AOT lib: gcc -c -o HelloWorld.exe.dylib HelloWorld.exe.c

I was able to turn a HelloWorld.exe app into a C source file using this stack. Compiling it with a C compiler and giving it the output name produced a valid native library.

Ongoing:

It's important to make sure that this generated code works with mono, but it all looks very much like it should. It's not the nicest C to read, but it's definitely legal C code. The only runtime support it seems to need is setjmp / longjmp for exception handling.

I think that with a very small amount of effort, we could get mkbundle to produce a collection of C source files for use with the developer's existing C stack.

This PR adds the backend and tool: https://github.com/mono/llvm/pull/37

It was 30 lines of changes to aot-compiler the other night to get the backend emitting a C file beside each .so file automatically. I'll put that in a PR when I have some time to polish it.

Using direct-pinvokes and direct-icalls with this backend would let our AOT images emit C code that calls our runtime and platform code through conventional C function calls.

What does it generate now?

This overlooks a lot of support code that it includes (much of which doesn't quite come out correct for us but does for some bitcode files produced by clang). That said, we can see the following "count args Main"

public class TestFinallyException {

	public static int Main (string[] args)
	{
		return args.Length;
	}

}

compiled to

static uint32_t TestFinallyException_Main_string__(uint64_t* llvm_cbe_arg_args) {
  uint8_t llvm_cbe_is_inited;
  uint64_t* llvm_cbe_tmp__16;
  uint32_t llvm_cbe_t18;

  llvm_cbe_is_inited = *((&mono_inited.array[((int32_t)0)]));
  if ((((llvm_cbe_is_inited == ((uint8_t)0))&1))) {
    goto llvm_cbe_NOTINITED_BB7;
  } else {
    goto llvm_cbe_BB2;
  }

llvm_cbe_BB2:
  if ((((llvm_cbe_arg_args == ((uint64_t*)/*NULL*/0))&1))) {
    goto llvm_cbe_EX_BB3;
  } else {
    goto llvm_cbe_NOEX_BB5;
  }

llvm_cbe_EX_BB3:
  llvm_cbe_tmp__16 = *((&mono_aot_exception19_llvm_got.array[((int32_t)24)]));
  (((l_fptr_4*)llvm_cbe_tmp__16))(281);
  __builtin_unreachable();

llvm_cbe_NOEX_BB5:
  llvm_cbe_t18 = *((&(((uint32_t*)llvm_cbe_arg_args))[((int32_t)6)]));
  return llvm_cbe_t18;
llvm_cbe_NOTINITED_BB7:
  init_method(0);
  goto llvm_cbe_BB2;

}

from the llvm bitcode

define internal i32 @TestFinallyException_Main_string__(i64* %arg_args) #5 {
BB0:
  br label %INIT_BB1

INIT_BB1:                                         ; preds = %BB0
  %is_inited = load i8, i8* getelementptr inbounds ([1 x i8], [1 x i8]* @mono_inited, i32 0, i32 0)
  %0 = call i8 @llvm.expect.i8(i8 %is_inited, i8 1)
  %1 = icmp eq i8 %0, 0
  br i1 %1, label %NOTINITED_BB7, label %INITED_BB2

INITED_BB2:                                       ; preds = %NOTINITED_BB7, %INIT_BB1
  br label %BB3

BB3:                                              ; preds = %INITED_BB2
  br label %BB2

BB2:                                              ; preds = %BB3
  %2 = icmp eq i64* %arg_args, null
  br i1 %2, label %EX_BB3, label %NOEX_BB5

EX_BB3:                                           ; preds = %BB2
  %3 = load i64*, i64** getelementptr inbounds ([25 x i64*], [25 x i64*]* @mono_aot_exception19_llvm_got, i32 0, i32 24)
  %JIT_ICALL_ADDR_24 = bitcast i64* %3 to void (i32)*
  br label %EX2_BB4

EX2_BB4:                                          ; preds = %EX_BB3
  call void %JIT_ICALL_ADDR_24(i32 281)
  unreachable

NOEX_BB5:                                         ; preds = %BB2
  %4 = bitcast i64* %arg_args to i32*
  %5 = getelementptr i32, i32* %4, i32 6
  %t18 = load volatile i32, i32* %5
  br label %BB4

BB4:                                              ; preds = %NOEX_BB5
  br label %BB1

BB1:                                              ; preds = %BB4
  ret i32 %t18

NOTINITED_BB7:                                    ; preds = %INIT_BB1
  call preserve_allcc void @init_method(i32 0)
  br label %INITED_BB2
}

Contributor guide