[llvm-dev] [RFC] Granular Return Attributes

Tim Northover via llvm-dev llvm-dev at lists.llvm.org
Wed Dec 9 05:30:58 PST 2020


One line proposal: I want to be able to write

    declare { i8 signext, i16 zeroext } @foo()

I think it's probably justifiable purely on symmetry grounds, a returned struct
is really just a glorified parameter list to fit in with LLVM's requirement that
a call produce a single Value, and why shouldn't you have as much control over
how that parameter passing happens as you do on the call itself?

But I do have a real motivating bugbear. Swifterror and the unfortunate fact
that I need a second attribute like it.

The swifterror attribute was implemented to support a special kind of function
parameter that gets passed and then returned in a specific register,
but functions
can change.

The "specific register" requirement is slightly odd, but shared by "swiftself"
parameters and not a big problem to represent.

But because we can't currently describe that final return (of a possibly
different value), we perform an elaborate trick on the IR. Values are given a
pseudo-memory location (created with a special alloca), and syntactic load/store
operations to this get converted to normal vreg dataflow by a special
SwiftErrorValueTracking class that essentially implements a subset of mem2reg
behaviour during CodeGen. The final value is then magically returned in x21 (for
AArch64).

So in current IR you will see functions like this (with AArch64 real behaviour
in comments):

    define i32 @foo(i8** swifterror %loc) {
      %errval = load i8*, i8** %loc     ; mov xErrVal, x21
      ; Use current errval.
      store i8* %newerr, i8** %loc      ; mov x21, xNewErr
      [...]
      ret i32 42 ; x0=42, x21=either incoming error, or new one if
stored at some point.
    }

I'd like to replace them with what's really happening:

    define { i32, i8* swifterror } @foo(i8* swifterror %errval) {
      [...]
      %ret.0 = insertvalue { i32, i8* } undef, i32 42, 0
      %ret = insertvalue { i32, i8* } %ret.0, i8* %newerr, 1
      ret { i32, i8* } %ret
    }

Front-ends can of course use a normal alloca to avoid explicitly value-tracking
%newerr themselves and the real mem2reg will clean up the details.

What about sret?
----------------

I think in this new world we'd have to relax the restriction that sret functions
must otherwise return void, certainly to support the swifterror use-case. I
don't think this is a huge problem though.

Which attributes would be allowed?
----------------------------------

I'd propose starting small. Perhaps just allow signext and zeroext and
convert swifterror in a separate patch. If people find uses for other attributes
they can be added later.

How do these combine with top-level return attributes?
------------------------------------------------------

Struct return types don't currently allow attributes; you can't write

    define { i32, i32 } signext @foo() {

I think it's fine to keep that restriction and only allow attributes on
first-level inner types of structs.

We'd be essentially creating a dual to the function's parameter list, but it
has to be written as a struct because call instructions can only produce
a single Value in LLVM (at one point I toyed with a new syntax like C++'s "auto
foo() -> (i32, i32)" but discarded that idea for this reason, as well as the
scale of that change).

Where's the code?
-----------------

I haven't implemented it yet because it's quite a big change and I wanted to
make sure there weren't too many objections, and that I hadn't missed
an unmovable blocker.

Cheers.

Tim.


More information about the llvm-dev mailing list