[llvm-dev] [cfe-dev] [RFC] Refactor Clang: move frontend/driver/diagnostics code to LLVM

comex via llvm-dev llvm-dev at lists.llvm.org
Thu Jun 4 01:20:19 PDT 2020


On Wed, Jun 3, 2020 at 4:26 PM Chris Lattner <clattner at nondot.org> wrote:
> On Jun 2, 2020, at 4:21 PM, comex via cfe-dev <cfe-dev at lists.llvm.org> wrote:
>
> While this is a different area of the codebase, another thing that
> would benefit greatly from being moved out of Clang is function call
> ABI handling.  Currently, that handling is split awkwardly between
> Clang and LLVM proper, forcing frontends that implement C FFI to
> either recreate the Clang parts themselves (like Rust does), depend on
> Clang (like Swift does), or live with FFI just not working with some
> function signatures.  I'm not sure what Flang currently does, but my
> understanding is that Flang does support C FFI, so it would probably
> benefit from this as well.  Just something to consider. :)
>
>
> For what its worth, I think there is a pretty clear path on this, but it hinges on Clang moving to MLIR as its code generation backend (an intermediary to generating LLVM IR).

I'd be interested in seeing a higher-level Clang IR for many different
reasons. :) On the other hand, when it comes to calling conventions,
at least some of the things currently handled by Clang seem like they
would fit well into the existing LLVM IR.

For example, this C code, compiled for x86-64 Unix:

struct foo { uint64_t a, b; };
struct foo get_foo() { return (struct foo){0, 1}; }

is translated straightforwardly to LLVM IR (trimmed for readability):

define { i64, i64 } @get_foo() {
 ret { i64, i64 } { i64 0, i64 1 }
}

and the generated assembly returns the values in RAX and RDX,
corresponding to the C ABI.

If you add a third field to the struct, the ABI demands the struct be
returned in memory with a hidden parameter.  Rather than leave this to
LLVM, Clang implements this itself, generating IR like:

define void @get_foo(%struct.foo* noalias nocapture sret align 8 %0) {
 // ...
}

If, however, you instead modify the IR from the two-field case to add
a third field:

define { i64, i64, i64 } @get_foo() {
 ret { i64, i64, i64 } { i64 0, i64 1, i64 2 }
}

...LLVM accepts it, but the generated assembly returns the values in
RAX, RDX, and *RCX*, which is not part of the ABI at all!

If you proceed to add a fourth field, LLVM suddenly decides to handle
the out-parameter transformation itself, so all is well again.  Except
that the transformation seemingly happens too late in the pipeline, so
the generated code isn't vectorized.  (I'm not sure exactly how this
works.)

In these examples, I'd say LLVM IR is capable of expressing the
desired semantics ('follow the C ABI for returning a struct with these
fields'), and LLVM tries to implement those semantics, but it's
slightly off.  And then Clang papers over that by reimplementing parts
of those semantics itself, and only generating IR that LLVM does
handle correctly.  This seems inelegant to me; it would be better if
LLVM just 'did the right thing' here.

On the other hand, LLVM IR struct returns aren't currently expressive
enough to handle *all* C/C++ struct returns; you run into problems
with things like C++ guaranteed copy elision (which effectively
exposes the out pointer directly to user code), and call ABIs
depending on arcane C++ concepts like 'trivial for the purposes of
calls'.  I suppose a higher-level IR might help here... but I think an
ideal design might put parts of this in LLVM IR as well.  I'm not
sure.


More information about the llvm-dev mailing list