Extensible Standalone CAPI

Hi all.
I’m trying to use the CAPI of the MLIR example standalone dialect in a way that I can use it together with the MLIR CAPI (libMLIR-C.so). The end goal is to easily be able to create multiple external dialects that can then be used from within Julia.

Right now, the standalone example generates libStandaloneCAPITestLib.so. When I get the dialect handle from this library and try to register it in the context, I get an error.

(snippet of Julia code:)

# `API` is the default CAPI generated by MLIR (libmlir.so).
# `Standalone` uses the CAPI generated by the standalone dialect example (libStandaloneCAPITestLib.so).
using MLIR: API, Standalone

ctx = API.mlirContextCreate() 
registry = API.mlirDialectRegistryCreate()
API.mlirRegisterAllDialects(registry)
API.mlirContextAppendDialectRegistry(ctx, registry)
API.mlirDialectRegistryDestroy(registry)
API.mlirContextLoadAllAvailableDialects(ctx)
handle = Standalone.mlirGetDialectHandle__standalone__() # Note that this function is from Standalone module
# <--- Everything runs fine till here --->

Standalone.mlirDialectHandleRegisterDialect(handle, ctx) # ERROR: LLVM error: Trying to register different dialects for the same namespace: builtin

I’m not sure whether there is a way to make something like this work. Currently, if I only use libStandaloneCAPITestLib.so to also create the context, registerAllDialects, … things do work and I’m able to generate IR from the standalone dialect as well as the builtin dialects.
This, however, isn’t a feasible solution since you migth want to add multiple different out-of-tree dialects.

This issue has also been discussed a bit here: How does the exterior dialect work? · beaver-lodge/beaver · Discussion #208 · GitHub
I’m curious to hear whether someone knows a solution for this, thanks!

Jules

I think the issue is that the linker is linking all MLIR bits, including the in-tree dialects, into libStandaloneCAPITestLib.so. The solution is to change the build to have one library with all MLIR bits, e.g. libMLIRC.so, and have all the external dialect libraries dynamically link against that. I’m not too familiar with this area though, so maybe someone else with more experience can chime in.

1 Like

For cases I’ve done, I consider the default built libraries to basically be demos – MLIR is a collection of a lot of pieces and when using it for a specific domain, you need to instantiate what you need. For ease of upstream maintenance and testing, various things get bundled together, but this doesn’t necessarily map to any specific use case.

With that said, I think the issue you were experiencing in the first message has less to do with linking and more that you are calling APIs that register the same dialect twice with a context. Generally, if you are using APIs from RegisterEverything, this is going to interfere badly with explicit dialect registration. There is no dialect registration happening as an artifact of linking, so the duplication must be due to something your sample is calling.

1 Like

Note that for the Python bindings, we build dedicated shared libraries to depend on: https://github.com/llvm/llvm-project/blob/main/mlir/cmake/modules/AddMLIRPython.cmake#L451 (the CMake is thick in that file but this is the salient bit – it just makes a dedicated libMyProjectCAPI.so from a collection of MLIRCAPI* libraries declared like here). Most of the complexity of that CMake is trying to track the Python build rules back to the list of MLIRCAPI dependencies and can be ignored.

Deploying Python requires these kind of standalone libraries. Not sure what you need for Julia. If just working with upstream dialects, then libMLIR-C.so should be fine. But if building a combination, you’re going to want to put your own aggregate together like the Python side does above (and then just include upstream and downstream libraries you need).

Looking closer at the error you are getting, I think I was somewhat mistaken. This is the result of trying to register the builtin dialect from two different binaries. This is detected and errors out. It is a symptom of a one-definition-rule violation. These shared libraries weren’t meant to be used in combination, and that is probably what is causing the issue.

1 Like

Thank you very much for the comprehensive explanation.
I added all the libraries and this does indeed generate a libary with all libmlir-c.so symbols as well as the standalone symbols.

standalone/test/CAPI/CMakeLists.txt
add_mlir_aggregate(StandaloneCAPITestLib
  SHARED
  EMBED_LIBS
  MLIRCAPIIR
  MLIRCAPIRegisterEverything
  StandaloneCAPI

  MLIRCAPIConversion
  MLIRCAPIDebug
  MLIRCAPIExecutionEngine
  MLIRCAPIInterfaces
  MLIRCAPITransforms

  MLIRCAPIAsync
  MLIRCAPIControlFlow
  MLIRCAPIGPU
  MLIRCAPILLVM
  MLIRCAPILinalg
  MLIRCAPISCF
  MLIRCAPIShape
  MLIRCAPISparseTensor
  MLIRCAPIFunc
  MLIRCAPITensor
  MLIRCAPIQuant
  MLIRCAPIPDL
)

Reading your reply, I conclude it is not (easily) possible to have a setup where each external dialect is a different .so file, it’s always necessary to build an aggregate library ahead of time?
In any case, thanks again, I’ve currently marked your reply as the answer.

I had this vision in mind at one point, but I eventually had to let pragmatism guide me to a simpler setup that let us build what we needed and live to fight another day. It is definitely possible, but MLIR’s shared linking setup isn’t layered that way (unless if you use -DBUILD_SHARED_LIBS=ON, which has a whole host of other issues).

On the good news side, it used to not even be possible – there were bugs with how MLIR handled dynamic type ids/registration which made it a non starter from the get-go. Now, I think it is “just” a CMake/library layering issue without actual show stoppers.

You probably want something that is more like -DBUILD_SHARED_LIBS=ON mode vs the existing libMLIR.dylib mode for this, since the former actually layers libraries correctly and the latter does this weird “static overlinking” thing that is not going to interact well with more of standalone “dialect libraries”.

I think I remember enough about this that I’d be willing to be another set of eyes if someone took it up, but I don’t have the time/use case to work on it myself.

1 Like