C-API for NewPM

Is anyone aware of efforts to expose the new pass-manager interface through the C-API?
we will need an interface like that for Julia and so I don鈥檛 want to duplicate efforts :slight_smile:

x-ref: C-API for NewPM 路 Issue #344 路 maleadt/LLVM.jl 路 GitHub

The C API for the new pass manager is defined in llvm-project/PassBuilder.h at main 路 llvm/llvm-project 路 GitHub.

1 Like

We have found the PassBuilder not flexible enough, and in Julia as an example we mostly bypass it.

It鈥檚 in particular a little bit strange (and cumbersome) to use strings to program the API.

The problem with a simple API for adding passes is that the new PM requires explicit nesting of different pass types, e.g. you need to explicitly create an adaptor that transforms function passes to a module pass. The legacy PM did that, which people generally agree was error prone.

We could create APIs that explicitly handle the nesting. That鈥檚 kinda annoying but perhaps doable.

I assume when you say 鈥渘ot flexible enough鈥 you mean dynamically turning on/off some passes is hard to do with strings?

Yeah the Julia pipeline itself has grown organically over the years and certain things we don鈥檛 need (IPO)

We also use the C-API in the Julia ecosystem for Enzyme and specializing the pipeline for GPU compilation

I don鈥檛 think we should expose individual passes (and all the infra needed to work with them, like pass managers and pass adaptors) via the C API. This would expose a very large API surface.

If you use a custom optimization pipeline and the textual representation is not good enough, then you should implement your custom pipeline in C++ and only expose the construction of the whole pipeline via the C API. It looks like you already do that.

I鈥檓 not sure how to feel about this choice. I think the principal behind the C API is that we want to expose a relatively stable API that lets users build a standard, AoT compiler on top of LLVM. In practice, most major frontends end up tweaking the pass pipeline (correct me if wrong). Clang, Swift, Rust, and Julia all do this. They all use the C++ API to adjust the pipeline.

If our goal is to provide frontends with stability, I鈥檓 not sure we can do that with the stringly-typed pass pipeline DSL. We can鈥檛 keep it stable because we add and remove passes all the time. If we recommend frontends use this API, how will they recover from errors when pass name lookup fails?

Google is interested in finding a way to stabilize the API surface that Rust uses from LLVM, since we are trying to get all of our LLVM based toolchains to use the same version of LLVM for cross-language LTO, sanitizers, and various other ABI unstable features. I鈥檝e been advocating for a plan of 鈥渇igure out whatever C++ APIs Rust needs, put a high-level C wrapper around it, and contribute it to LLVM鈥. If the pipeline is just too tightly coupled with LLVM and we can鈥檛 provide any kind of stable API for it, maybe we have to give up, compromise, and accept this level of API instability.

I don鈥檛 want to push hard for a stable C API, since that鈥檚 a lot of work, but higher-level C++ APIs that reduce duplication between all of the major LLVM frontends would be a great place to start.


We do a lot of experimentation and customization of the pipeline. Not having to use C++ is a big boon for users of LLVM.jl (the Julia based LLVM interface).

If our goal is to provide frontends with stability, I鈥檓 not sure we can do that with the stringly-typed pass pipeline DSL.

Yeah, I feel the same. String based API鈥檚 make compatibility across API versions harder.

I don鈥檛 want to push hard for a stable C API, since that鈥檚 a lot of work.

I think in particularly for the PassManager the effort wouldn鈥檛 be to big (and the current situation is a regression from the old PM). From the Julia side we might make the effort to add this.

I should probably also do a pass over LLVM.jl/deps/LLVMExtra at master 路 maleadt/LLVM.jl 路 GitHub and figure out what pieces of that we should upstream. We use this shim-library to extend the API surface the C-API covers and to backport C-API components.

Rust uses the standard optimization pipeline. The setup for how to do that with the NewPM has seen a lot of churn, in part because inserting sanitizer passes in all the right places is considered a frontend responsibility. If we could move sanitizer setup into PassBuilder (which is imho the right place to do it), that would allow sharing this logic across frontends and reduce the amount of churn.

I鈥檓 not really sure how that differs from a C API? If a pass is removed, that鈥檚 going to break explicit uses of that pass no matter how they look like.

The main way the C API for the NewPM is intended to be used is with the default pipelines, i.e. using something like "default<O3>" as the pipeline. That kind of usage should be stable.

There are quite a few simple holes in the C API that don鈥檛 require any tricky design work, but just upstreaming lots of missing bits. Most things defined in rust/RustWrapper.cpp at master 路 rust-lang/rust 路 GitHub are like that.

1 Like

Maybe to clarify I differentiate between PassBuilder which indeed as an C-API already,
and users (like Julia) who use the (Loop|Function|Module)PassMangager directly and bypass the PassBuilder for the most part.

For us PassBuilder is not flexible enough, it has a lot of extra capabilities that we don鈥檛 quite care about (like setting up IPO/LTO/鈥) and inserting passes is limited to the extension points. We have a pass for example that is doing allocation optimization and we would like to schedule passes like IRCE.

Since we live down-stream it easier for us to maintain/adjust/tune our own pipeline instead of relying on PassBuilder or changing it away from it鈥檚 clang/opt roots. I suspect this is also true for other front-ends that use LLVM (since otherwise I would assume Azul to have added an option to run IRCE, but maybe extension points is good enough for them).

Now the assumption thatPassBuilder is the only way of creating a pipeline has been frustrating, since backends use it to schedule their own passes using extension points. This is what motivated 鈿 D148561 Expose PassBuilder extension point callbacks so that we could schedule target passes using the PassBuilder EPs鈥

So most of the Julia based ecosystem uses the old pass-manager interface to schedule passes in a bespoke way, we even added infrastructure to write pure Julia passes and schedule them. All in a programmatic fashion.

So I want to provide functionality parity for the NewPM, and yes I could programmatically build up a large string鈥, then pass that to the PassBuilder and call that a day, but if I thought strings were a good programming interface I would use Perl :wink: We deal with API deprecations and removals anyhow so I am not to worried about churn caused by changes to the passes.