[RFC] Granular Return Attributes

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?

We may eventually find some use for this as well, though it’s speculative.

This may be a good time to raise the question: How do people feel about changing LLVM IR to allow multiple values defined by a single instruction?

That’s a pretty significant change, but I think there are very good reasons for wanting to do this. I know we’d appreciate not having the IR obfuscated by extractvalue, for example. Smoothing the road of LLVM IR / MLIR integration is another one.

I understand you may not want to get your particular problem blocked by such a major change. Either way, it doesn’t feel like the changes would be in conflict with each other anyway.

Cheers,
Nicolai

+1, this seems like clear goodness to me.

Philip

We may eventually find some use for this as well, though it’s speculative.

This may be a good time to raise the question: How do people feel about changing LLVM IR to allow multiple values defined by a single instruction?

I would love to see this conceptually. “First class aggregates” in LLVM are a historical mistake in my opinion.

Such a transition is going to be a beast though.

-Chris

Tim’s proposal sounds good to me. Can you say more about the motivation for doing away with the current swifterror alloca convention? It seems self-evidently ugly, but is there something that makes removing it urgent?

Regarding sret’s void return requirement, keep in mind that many targets (x86, others) return the sret pointer in the standard return register, and we typically don’t represent that at the IR level. Perhaps this doesn’t affect swiftcc, but it’s something to keep in mind.

The long term goal of replacing first-class-aggregates with multi-value producing instructions seems like a good future design direction, but it’s probably best left for another day.