Search code examples
llvmllvm-irllvm-c++-api

LLVM linker: weird error message when importing a function


I'm trying to implement a module system in my programming language. I've read the docs (https://llvm.org/doxygen/classllvm_1_1Linker.html) and the linkModules() function is exactly what I need. When linking two modules I'm getting a weird error message. I don't get what I'm doing wrong.

export.mylang:

export func eoeo(x: i32) -> void {}

import.mylang:

import "@project/eo.mylang";

func main() -> i32 {

    return 0;
}

by the way, import.mylang doesn't even call to eoeo() ...

Then I use linkModules() to glue the two modules together:

llvm::Linker::linkModules(*mylang::CompilationUnits[mylang::CompilationUnits.size() - 2]->Module, 
    std::unique_ptr<llvm::Module>(mylang::CompilationUnits.back()->Module));

this code generates the following LLVM IR (after connecting the two modules):

define void @eoeo(i32 %x) {
entry:
  %x1 = alloca i32, align 4
  store i32 %x, i32* %x1, align 4
  ret void
}

define i32 @main() {
entry:
  ret i32 0
}

And in my terminal I see this error message:

Function context does not match Module context!
void (i32)* @eoeo

What does that even mean? Yeah, the two modules do have different contexts, but isn't linkModules() supposed to glue them together into a single large module (regardless of different contexts)? I'm so confused.

(I'm writing this an hour later). So I've tried to call eoeo() and my source file did compile but I was left with a weird error message:

Call parameter type does not match function signature!
i32 3
 i32  call void @eoeo(i32 3)
Function context does not match Module context!
void (i32)* @eoeo
Call parameter type does not match function signature!
i32 3
 i32  call void @eoeo(i32 3)

So now I guess types are scoped to an LLVM context?

How can I simply take one module and put it's stuff into another module without worrying about contexts? What's interesting is that I can not define but declare a function in a module (without the body) and then have the body defined in a different module. then I can dump these modules to .ll files and use the llvm-link command line tool to link them (everything works). The thing is that I really don't want to deal with the file system and use std::system() to interact with LLVM's tools. It does work, but it's tedious to deal with file paths manually.


Solution

  • How can I simply take one module and put it's stuff into another module without worrying about contexts?

    The only way to do this is to serialize it down to bitcode out of one context, and then deserialize it into the target context.

    Here's what LLVMContext is for. Suppose you're writing a video game and you want to use a scripting engine, a 3d audio subsystem and a graphics system. Each of these libraries uses LLVM under the hood for their own purposes. Instead of having global anything which would cause them to potentially step on each other's data, everything, everything, is scoped to the LLVMContext, keeping each library's data separate. Both Type* and Constant* are stored as members of the LLVMContext. The LLVM module linker assumes that the inputs come from the same context.

    You're free to create multiple modules within a single context. These can share Type* and Constant*.

    Sometimes people also use contexts for threading, since we have the same problem of making sure two threads don't touch each other's data. If you do that, you then need to serialize to bitcode and deserialize.