Value-result ABI for small trivial inouts

We currently always pass inout parameters indirectly, but it seems to me that for inout parameters that are of small trivial types like Int or CGSize, a value-result calling convention might always be preferable, and we might want to codify that in the stable ABI. Values of these types are likely to be SSAed, and the indirect convention forces memory traffic that would otherwise be unnecessary. On ARMv7 and ARM64, the argument and return register sets are the same, so a value-result convention is likely to be able to compile down to in-place operations on those registers, so it's a potential code size win too.

(Value-result is not a clear win for nontrivial types, since it forces a load and retain onto the outermost caller that inouts a value in memory, since we can't invalidate the memory during the inout call. The extra retain would potentially defeat COW optimization. We have primitives like `isUniquelyReferenced` that depend on mutable references being in memory too.)

-Joe

Nit: True on arm64. Not true on armv7; the GPR parameters are r0-r3 but GPR return is r0-r1.

···

On Dec 17, 2015, at 3:34 PM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

On ARMv7 and ARM64, the argument and return register sets are the same

--
Greg Parker gparker@apple.com Runtime Wrangler

IIRC, the mutation model does try to make stronger promises about updates to stored variables and properties; this would interfere with that.

My larger concern, though, is that I’m not sure that the set of non-inlinable functions taking inouts that are concrete, fixed-size, and trivial is actually worth adding special-case ABI rules for.

John.

···

On Dec 17, 2015, at 3:34 PM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:
We currently always pass inout parameters indirectly, but it seems to me that for inout parameters that are of small trivial types like Int or CGSize, a value-result calling convention might always be preferable, and we might want to codify that in the stable ABI. Values of these types are likely to be SSAed, and the indirect convention forces memory traffic that would otherwise be unnecessary. On ARMv7 and ARM64, the argument and return register sets are the same, so a value-result convention is likely to be able to compile down to in-place operations on those registers, so it's a potential code size win too.

(Value-result is not a clear win for nontrivial types, since it forces a load and retain onto the outermost caller that inouts a value in memory, since we can't invalidate the memory during the inout call. The extra retain would potentially defeat COW optimization. We have primitives like `isUniquelyReferenced` that depend on mutable references being in memory too.)

We currently always pass inout parameters indirectly, but it seems to me that for inout parameters that are of small trivial types like Int or CGSize, a value-result calling convention might always be preferable, and we might want to codify that in the stable ABI. Values of these types are likely to be SSAed, and the indirect convention forces memory traffic that would otherwise be unnecessary. On ARMv7 and ARM64, the argument and return register sets are the same, so a value-result convention is likely to be able to compile down to in-place operations on those registers, so it's a potential code size win too.

(Value-result is not a clear win for nontrivial types, since it forces a load and retain onto the outermost caller that inouts a value in memory, since we can't invalidate the memory during the inout call. The extra retain would potentially defeat COW optimization. We have primitives like `isUniquelyReferenced` that depend on mutable references being in memory too.)

This makes me wonder, are stores to inouts ever visible if a function throws?

Also, it seems you’ll need to re-abstract when passing (inout Int) -> () as (inout T) -> (), does this fit in with the existing mechanisms?

···

On Dec 17, 2015, at 3:34 PM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

-Joe
_______________________________________________
swift-dev mailing list
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev

True, I meant that one sequence is a prefix of the other. If you pass the first argument in and return it out via {r0, r1} the operations could be done in-place.

-Joe

···

On Dec 17, 2015, at 3:43 PM, Greg Parker <gparker@apple.com> wrote:

On Dec 17, 2015, at 3:34 PM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

On ARMv7 and ARM64, the argument and return register sets are the same

Nit: True on arm64. Not true on armv7; the GPR parameters are r0-r3 but GPR return is r0-r1.

I believe we do all writebacks right now on the throwing path because they’re just set up as cleanups. I think this is more-or-less a language guarantee, because if we were going to discard them, we’d need to at least explain *when* we do that, and I’m not sure there’s a user-meaningful criterion for that. Notably, we always have to call the callback for materializeForSet because otherwise we might leak or leave something pinned, which is clearly unacceptable.

We’re definitely not going to go all the way and formalize a model where a throw “reverses the transaction” by discarding all writes to inout variables.

John.

···

On Dec 17, 2015, at 11:09 PM, Slava Pestov via swift-dev <swift-dev@swift.org> wrote:

On Dec 17, 2015, at 3:34 PM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

We currently always pass inout parameters indirectly, but it seems to me that for inout parameters that are of small trivial types like Int or CGSize, a value-result calling convention might always be preferable, and we might want to codify that in the stable ABI. Values of these types are likely to be SSAed, and the indirect convention forces memory traffic that would otherwise be unnecessary. On ARMv7 and ARM64, the argument and return register sets are the same, so a value-result convention is likely to be able to compile down to in-place operations on those registers, so it's a potential code size win too.

(Value-result is not a clear win for nontrivial types, since it forces a load and retain onto the outermost caller that inouts a value in memory, since we can't invalidate the memory during the inout call. The extra retain would potentially defeat COW optimization. We have primitives like `isUniquelyReferenced` that depend on mutable references being in memory too.)

This makes me wonder, are stores to inouts ever visible if a function throws?

Maybe. I know we try to make stronger promises about partial object updates, so things like swap(&a.x, &a.y) or swap(&a[0], &a[1]) work with stored `x` and `y` or addressed subscripts, but we're still pretty cavalier about when the updates get published. A capture or reabstraction can implicitly introduce writebacks on the callee side even if the caller passed in a property they know to be stored.

-Joe

···

On Dec 17, 2015, at 3:43 PM, John McCall <rjmccall@apple.com> wrote:

On Dec 17, 2015, at 3:34 PM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:
We currently always pass inout parameters indirectly, but it seems to me that for inout parameters that are of small trivial types like Int or CGSize, a value-result calling convention might always be preferable, and we might want to codify that in the stable ABI. Values of these types are likely to be SSAed, and the indirect convention forces memory traffic that would otherwise be unnecessary. On ARMv7 and ARM64, the argument and return register sets are the same, so a value-result convention is likely to be able to compile down to in-place operations on those registers, so it's a potential code size win too.

(Value-result is not a clear win for nontrivial types, since it forces a load and retain onto the outermost caller that inouts a value in memory, since we can't invalidate the memory during the inout call. The extra retain would potentially defeat COW optimization. We have primitives like `isUniquelyReferenced` that depend on mutable references being in memory too.)

IIRC, the mutation model does try to make stronger promises about updates to stored variables and properties; this would interfere with that.

Also, Swift functions can have their own ABIs. Extending the result registers to use r0-r3 on ARM32 seems perfectly reasonable.

-Chris

···

On Dec 17, 2015, at 3:44 PM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

On Dec 17, 2015, at 3:43 PM, Greg Parker <gparker@apple.com> wrote:

On Dec 17, 2015, at 3:34 PM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

On ARMv7 and ARM64, the argument and return register sets are the same

Nit: True on arm64. Not true on armv7; the GPR parameters are r0-r3 but GPR return is r0-r1.

True, I meant that one sequence is a prefix of the other. If you pass the first argument in and return it out via {r0, r1} the operations could be done in-place.

True.

John.

···

On Dec 18, 2015, at 10:29 AM, Joe Groff <jgroff@apple.com> wrote:

On Dec 17, 2015, at 3:43 PM, John McCall <rjmccall@apple.com> wrote:

On Dec 17, 2015, at 3:34 PM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:
We currently always pass inout parameters indirectly, but it seems to me that for inout parameters that are of small trivial types like Int or CGSize, a value-result calling convention might always be preferable, and we might want to codify that in the stable ABI. Values of these types are likely to be SSAed, and the indirect convention forces memory traffic that would otherwise be unnecessary. On ARMv7 and ARM64, the argument and return register sets are the same, so a value-result convention is likely to be able to compile down to in-place operations on those registers, so it's a potential code size win too.

(Value-result is not a clear win for nontrivial types, since it forces a load and retain onto the outermost caller that inouts a value in memory, since we can't invalidate the memory during the inout call. The extra retain would potentially defeat COW optimization. We have primitives like `isUniquelyReferenced` that depend on mutable references being in memory too.)

IIRC, the mutation model does try to make stronger promises about updates to stored variables and properties; this would interfere with that.

Maybe. I know we try to make stronger promises about partial object updates, so things like swap(&a.x, &a.y) or swap(&a[0], &a[1]) work with stored `x` and `y` or addressed subscripts, but we're still pretty cavalier about when the updates get published. A capture or reabstraction can implicitly introduce writebacks on the callee side even if the caller passed in a property they know to be stored.