Nested modules translation and symbol table

Hi,

I have 2 modules that contain LLVM dialect code which I want to translate to proper LLVM. At the moment, I don’t see a way of doing it directly. I think I can inline one module into another, but I would rather keep the nested structure as it is more clean. Is there a way to translate nested modules to proper LLVM?

If it is possible to translate nested modules, I am also interested how to handle a situation when accessing a global from inner module. For example,

module {

  module {
     llvm.mlir.global external @v() : !llvm.i64
  }

  llvm.func @main() {
    // error here
    %ptr = llvm.mlir.addressof @v : !llvm.ptr<i64>
    llvm.return
  }

}

this gives error as v's symbolic name cannot be found. So far I have found a workaround by introducing a helper function in inner module, for example

module {

  module {
    llvm.mlir.global external @v() : !llvm.i64

    llvm.func @foo() -> !llvm.ptr<i64> {
      %ptr = llvm.mlir.addressof @v : !llvm.ptr<i64>
      llvm.return %ptr : !llvm.ptr<i64>
    }
  }

  llvm.func @main() {
    %ptr = llvm.call @foo() : () -> !llvm.ptr<i64>
    llvm.return
  }
}

Is this the right way to handle this or are there any better approaches?

Thanks,
George

What are you trying to achieve? LLVM IR does not have nested modules, so you cannot preserve module nesting at the LLVM IR level… What you can do is embed each LLVM IR module into the top-level MLIR module using some operation that you would introduce. For example, in the CUDA flow, we compile the kernel modules down to a binary blob that we embed as a string to the LLVM module.

My actual aim is to JIT-compile SPIR-V via SPIR-V to LLVM conversion. The idea is that the gpu module containing the kernel is lowered to SPIR-V, then to LLVM, so in the result I get 2 nested modules - a main one and a kernel one.

Then it can follow the same model as the other GPU flows. If you were writing this in C, how it would look like? E.g., for CUDA, the kernel code can be stored in a const char * in the host module. I’m assuming something similar can happen here. For example, you’d have a spirv.mlir.code operation with a string attribute that contains some representation of a kernel and process that separately when translating the actual LLVM modules.

I decided that it is easier to create a separate thread for this.

In contrast to other GPU flows, I do not want to store (pre)compiled kernel code, but rather call kernel code as if it was a “normal” host function.

Then you need to either put the content of the kernel module directly into the host module (possibly with some renaming to avoid clashes), or produce two different LLVM IR modules with the host module containing declarations of the functions it intends to call from the kernel module and link the modules together at JIT time.

Yes, indeed. I actually put these options into RFC to see if there is any preference to one or the other amongst the community :slightly_smiling_face:. I am currently looking at how to produce two valid different LLVM IR modules with host and device code in the current ecosystem.