clangir icon indicating copy to clipboard operation
clangir copied to clipboard

[CIR][WIP] Add ABI lowering pass

Open Lancern opened this issue 9 months ago • 5 comments

This PR attempts to add a new pass cir-abi-lowering to the CIR dialect. This pass runs before the CallConvLowering pass, and it expands all ABI-dependent types and operations inside a function to their ABI-independent equivalences according to the ABI specification.

The patch also moves the lowering code of the following types and operations from the LLVM lowering conversion to the new pass:

  • The pointer-to-data-member type cir.data_member;
  • The pointer-to-member-function type cir.method;
  • All operations working on operands of the above types.

Lancern avatar Mar 12 '25 14:03 Lancern

The direct motivation for this new pass is the proper CallConvLowering of the !cir.method type. Currently, this type is lowered during LLVM lowering, which is too late to achieve a proper lowering. Consider the following CIR code:

cir.func @test(%arg0: !cir.method) {
  // ...
}

Following the current lowering approach, which keeps !cir.method until we arrive at LLVM lowering, we would eventually get the following LLVM IR:

define dso_local @test({ i64, i64 } %0) {
  ; ...
}

But we actually expect the following LLVM IR (note the differences on the function signatures):

define dso_local @test(i64 %0, i64 %1) {
  ; ...
}

To achieve this, I have 3 choices:

  1. Teach the CallConvLowering pass about the !cir.method type.
  2. Move the lowering of !cir.method to the LoweringPrepare pass, which runs before CallConvLowering.
  3. Add a new pass before CallConvLowering that lowers !cir.method to !cir.struct.

At the beginning I thought option 1 would be the easiest way. But as I dig through the rabbit hole I found some tricky stuff behind the scene. The problem comes from the CodeGen of function prologue and epilogue. In the prologue, each argument is assigned a stack slot and stored there. For an argument of type !cir.method, after CallConvLowering it would expands into two arguments of type !s64i. Thus in the function prologue I would have to come up a way to store two !s64i values into the stack slot allocated for a !cir.method value, which is tricky. Similar problems also exist in the epilogue.

The problem of option 3 is that the LoweringPrepare pass is not a conversion pass, which could be really tricky if you want to do type conversion stuff in it. In my case I have to convert every appearances of !cir.method to !cir.struct and this kind of job is better suited for a conversion pass.

Anyway, this PR is still very incomplete and under construction, I'd like to hear some early comments about this from the community.

Lancern avatar Mar 12 '25 15:03 Lancern

Sorry for the delay here, I finally got the bandwidth to update this PR. The primary goal of this PR is to introduce the cir-abi-lowering pass which performs ABI lowering work for globals and inside each function.

The cir-abi-lowering pass converts ABI dependent types to more "fundamental" CIR types, and replaces operations that act on these ABI dependent types with more "fundamental" CIR operations. The idea of cir-abi-lowering is to avoid mixing such ABI lowering logic in LLVM lowering code, which could be confusing and make the code more complex. With the introduction of cir-abi-lowering, we got the following benefits:

  • The LLVM lowering pass becomes more simple and easy to comprehend. It only has to deal with "fundamental" CIR operations and types, and does not have to deal with complex ABI details.
  • The lower to MLIR path could potentially benefit from the ABI lowering pass in the future. We don't need to implement ABI lowering again in the MLIR lowering path.
  • As stated in https://github.com/llvm/clangir/pull/1471#issuecomment-2718188055, some ABI types are better lowered to more "fundamental" types before CallConvLowering, which is impossible if we put their lowering code inline in the LLVM lowering pass.

Lancern avatar Aug 04 '25 16:08 Lancern

I feel it's a bit confusing to have both cir-abi-lowering and cir-call-conv-lowering being distinct passes. Can we merge cir-call-conv-lowering into the former while keeping a switch to enable/disable the call conv lowering part (should be default to off for now)?

That makes sense to me, and I'll do that in a follow-up PR. Actually in short term I also plan to migrate part of LoweringPrepare as well, as some ABI lowering code is put there for now (e.g. the handling of dynamic_cast). The long-term goal would be to put all ABI-related code into this pass, although I'm not quite sure whether this is actually achievable under current CIR design. For example, some ABI-related code is necessary during CIRGen for now because the CIR emitted from there is already ABI-dependent.

Lancern avatar Aug 10 '25 16:08 Lancern

The long-term goal would be to put all ABI-related code into this pass, although I'm not quite sure whether this is actually achievable under current CIR design. For example, some ABI-related code is necessary during CIRGen for now because the CIR emitted from there is already ABI-dependent.

Yea, since we have no need for postponing some of the ABI decisions we just do it right away, avoiding duplicating AST information to only when necessary

bcardosolopes avatar Aug 12 '25 02:08 bcardosolopes

Rebased.

Lancern avatar Aug 18 '25 15:08 Lancern