[Pitch] Don't require & for UnsafeRawPointer


(Anders Kierulf) #1

Summary: Currently, only mutable values can be passed to UnsafeRawPointer, except for a special case for arrays. That special case should be generalized, allowing any values to be passed to UnsafeRawPointer without using &.

The following code shows the inconsistency in passing values to UnsafeRawPointer:

var varArray = [Int](repeating: 0, count: 6)
var varTuple = (0, 0, 0, 0, 0, 0)

let letArray = [Int](repeating: 0, count: 6)
let letTuple = (0, 0, 0, 0, 0, 0)

func get(_ pointer: UnsafeRawPointer, at index: Int) -> Int {
    let a = pointer.bindMemory(to: Int.self, capacity: 6)
    return a[index]
}

// Array can be passed directly, automatically takes address.
_ = get(varArray, at: 2) // okay
_ = get(letArray, at: 2) // okay

// When explicitly taking address, can only pass mutable variables.
_ = get(&varArray, at: 2) // okay, but seems inconsistent
_ = get(&letArray, at: 2) // fails: & not allowed

// Passing tuple instead of array fails.
_ = get(varTuple, at: 2) // fails: wrong type
_ = get(letTuple, at: 2) // fails: wrong type

// Adding & to pass a tuple only works for mutable values. Having to
// pass the address using & means that any methods calling this must
// be mutating, even though they don't mutate.
_ = get(&varTuple, at: 2) // okay, but forces mutating
_ = get(&letTuple, at: 2) // fails: cannot pass immutable value

Passing a value to an UnsafeRawPointer parameter should not require use of &, as that forces all code calling it to be mutating. Having a special case for array also doesn't make sense, as the idea of UnsafeRawPointer is that we're interpreting that memory content as whatever we want. Logged as SR-4649.

Proposal:
- Never require & when passing value to UnsafeRawPointer.
- Always require & when passing value to UnsafeMutableRawPointer.

(Fixed size arrays are still needed, but with this tweak, workarounds can be made to work.)

Anders Kierulf


(Anders Kierulf) #2

I want to bring this up one last time in swift-evolution to give this issue a chance to not fall through the cracks. Passing a value to UnsafeRawPointer should not force that method to be mutating. Either the call should not require &, or this use of & should not require the method to be mutating. The workaround of copying to a temporary variable is not viable (see sample project in SR-4649).

Hope there’s some way this issue can be addressed sooner rather than later.

Thanks,
  Anders Kierulf

···

On Apr 20, 2017, at 1:10 PM, Anders Kierulf <anders@smartgo.com> wrote:

Summary: Currently, only mutable values can be passed to UnsafeRawPointer, except for a special case for arrays. That special case should be generalized, allowing any values to be passed to UnsafeRawPointer without using &.

The following code shows the inconsistency in passing values to UnsafeRawPointer:

var varArray = [Int](repeating: 0, count: 6)
var varTuple = (0, 0, 0, 0, 0, 0)

let letArray = [Int](repeating: 0, count: 6)
let letTuple = (0, 0, 0, 0, 0, 0)

func get(_ pointer: UnsafeRawPointer, at index: Int) -> Int {
   let a = pointer.bindMemory(to: Int.self, capacity: 6)
   return a[index]
}

// Array can be passed directly, automatically takes address.
_ = get(varArray, at: 2) // okay
_ = get(letArray, at: 2) // okay

// When explicitly taking address, can only pass mutable variables.
_ = get(&varArray, at: 2) // okay, but seems inconsistent
_ = get(&letArray, at: 2) // fails: & not allowed

// Passing tuple instead of array fails.
_ = get(varTuple, at: 2) // fails: wrong type
_ = get(letTuple, at: 2) // fails: wrong type

// Adding & to pass a tuple only works for mutable values. Having to
// pass the address using & means that any methods calling this must
// be mutating, even though they don't mutate.
_ = get(&varTuple, at: 2) // okay, but forces mutating
_ = get(&letTuple, at: 2) // fails: cannot pass immutable value

Passing a value to an UnsafeRawPointer parameter should not require use of &, as that forces all code calling it to be mutating. Having a special case for array also doesn't make sense, as the idea of UnsafeRawPointer is that we're interpreting that memory content as whatever we want. Logged as SR-4649.

Proposal:
- Never require & when passing value to UnsafeRawPointer.
- Always require & when passing value to UnsafeMutableRawPointer.

(Fixed size arrays are still needed, but with this tweak, workarounds can be made to work.)

Anders Kierulf


(Andrew Trick) #3

Thanks for refusing to let your pitch die. Something should eventually be done here and it's good to get feedback. The only reason to bring this up in Swift 4 is if we decide to outlaw some code pattern that's already in use. If this ends up just an additive convenience request, than it can be a bug report for now and come back in Swift 5. There have been a couple +1's already but I don't know whether it's a serious problem or just an annoyance.

Side note: don’t worry, simply declaring everything ‘var’ and passing them ‘inout’ shouldn’t break exclusive memory access. If you pass ‘inout’ to a non-mutable pointer, it’s just considered a “read” access.

I'll speculate a bit and make some suggestions...

Summary: Currently, only mutable values can be passed to UnsafeRawPointer, except for a special case for arrays. That special case should be generalized, allowing any values to be passed to UnsafeRawPointer without using &.

The following code shows the inconsistency in passing values to UnsafeRawPointer:

var varArray = [Int](repeating: 0, count: 6)
var varTuple = (0, 0, 0, 0, 0, 0)

let letArray = [Int](repeating: 0, count: 6)
let letTuple = (0, 0, 0, 0, 0, 0)

func get(_ pointer: UnsafeRawPointer, at index: Int) -> Int {
   let a = pointer.bindMemory(to: Int.self, capacity: 6)
   return a[index]
}

// Array can be passed directly, automatically takes address.
_ = get(varArray, at: 2) // okay
_ = get(letArray, at: 2) // okay

This works out because a special implicit conversion is doing the work for us. I think the special case is all about lightweight C interop with Swift Array.

Let's not change this.

// When explicitly taking address, can only pass mutable variables.
_ = get(&varArray, at: 2) // okay, but seems inconsistent
_ = get(&letArray, at: 2) // fails: & not allowed

I'll speculate that the type checker has a special case for `&varArray` for two reasons:

1. To prevent users from accidentally exposing the Array struct rather than its storage as a C pointer. That would be confusing to debug.

2. We need a mutable analog to the non-inout immutable case above, for lightweight C interop.

We allow implicit UnsafeMutablePointer to UnsafePointer, so, I don't think there's anything inconsistent about `&varArray` here.

I also think it's fine that we don't allow `&letArray`.

Let's not change this.

// Passing tuple instead of array fails.
_ = get(varTuple, at: 2) // fails: wrong type
_ = get(letTuple, at: 2) // fails: wrong type

Refusing to compile these cases would probably seem normal if we didn't support the first non-inout array case.

I think we should allow both cases as a new, additive type system feature.

// Adding & to pass a tuple only works for mutable values. Having to
// pass the address using & means that any methods calling this must
// be mutating, even though they don't mutate.
_ = get(&varTuple, at: 2) // okay, but forces mutating
_ = get(&letTuple, at: 2) // fails: cannot pass immutable value

Well, the `&varTuple` case only works by accident for raw pointers, but typed pointers will have the wrong type!

See [SR-3590] Implicitly convert &Tuple to UnsafePointer<Tuple.Element>.

The only reason I haven't pushed for this in Swift 4, along with several other important usability issues with UnsafePointer, is that (I thought) it's additive and I haven't wanted to compete for review bandwidth in this release cycle.

However, come to think of it, this will break some very unlikely code:

func foo(_ p: UnsafeMutablePointer<(Int, Int, Int)>) {
  p[0].0 = 42
}

var a = (0, 1, 2)
foo(&a)
print(a)

I think that's fair if migration is provided.

Passing a value to an UnsafeRawPointer parameter should not require use of &, as that forces all code calling it to be mutating. Having a special case for array also doesn't make sense, as the idea of UnsafeRawPointer is that we're interpreting that memory content as whatever we want. Logged as SR-4649.

Splitting hairs: the array case is really a special case and it does make sense. I'm *also* proposing a similar special case for homogeneous tuples.

Proposal:
- Never require & when passing value to UnsafeRawPointer.
- Always require & when passing value to UnsafeMutableRawPointer.

+1 to your basic proposal. But I think each type system change needs to be more clearly spelled out. Probably with a separate bug for each.

- Support non-inout homogeneous tuple argument conversion to UnsafePointer<Element>.
(additive)

- Support non-homgenous non-inout tuple argument conversion to UnsafeRawPointer.
(additive)

- Support inout homogeneous tuple argument conversion to UnsafeMutablePointer<Element>.
See [SR-3590] Implicitly convert &Tuple to UnsafePointer<Tuple.Element>.
(source breaking in ridiculous cases)

- [SR-1956] `withUnsafePointer` shouldn't take its argument as `inout`
(additive... we don't need to ban the inout syntax)

The problem I have with [SR-4649] "Don't require & to pass value to UnsafeRawPointer", is that it seeks to create inconsistency between raw and non-raw pointers. That might make sense for non-homogenous tupes, but otherwise I don't think it's desirable.

(Fixed size arrays are still needed, but with this tweak, workarounds can be made to work.)

Yes, they very much are needed to get people out of unsafe territory.

-Andy

···

On Apr 20, 2017, at 12:10 PM, Anders Kierulf via swift-evolution <swift-evolution@swift.org> wrote:


(Charles Srstka) #4

Reading over this again, the proposal should also mention changing withUnsafePointer(to:_:slight_smile: to no longer take an inout as its first argument.

Charles

···

On Apr 20, 2017, at 2:10 PM, Anders Kierulf via swift-evolution <swift-evolution@swift.org> wrote:

Summary: Currently, only mutable values can be passed to UnsafeRawPointer, except for a special case for arrays. That special case should be generalized, allowing any values to be passed to UnsafeRawPointer without using &.

The following code shows the inconsistency in passing values to UnsafeRawPointer:

var varArray = [Int](repeating: 0, count: 6)
var varTuple = (0, 0, 0, 0, 0, 0)

let letArray = [Int](repeating: 0, count: 6)
let letTuple = (0, 0, 0, 0, 0, 0)

func get(_ pointer: UnsafeRawPointer, at index: Int) -> Int {
   let a = pointer.bindMemory(to: Int.self, capacity: 6)
   return a[index]
}

// Array can be passed directly, automatically takes address.
_ = get(varArray, at: 2) // okay
_ = get(letArray, at: 2) // okay

// When explicitly taking address, can only pass mutable variables.
_ = get(&varArray, at: 2) // okay, but seems inconsistent
_ = get(&letArray, at: 2) // fails: & not allowed

// Passing tuple instead of array fails.
_ = get(varTuple, at: 2) // fails: wrong type
_ = get(letTuple, at: 2) // fails: wrong type

// Adding & to pass a tuple only works for mutable values. Having to
// pass the address using & means that any methods calling this must
// be mutating, even though they don't mutate.
_ = get(&varTuple, at: 2) // okay, but forces mutating
_ = get(&letTuple, at: 2) // fails: cannot pass immutable value

Passing a value to an UnsafeRawPointer parameter should not require use of &, as that forces all code calling it to be mutating. Having a special case for array also doesn't make sense, as the idea of UnsafeRawPointer is that we're interpreting that memory content as whatever we want. Logged as SR-4649.

Proposal:
- Never require & when passing value to UnsafeRawPointer.
- Always require & when passing value to UnsafeMutableRawPointer.

(Fixed size arrays are still needed, but with this tweak, workarounds can be made to work.)

Anders Kierulf

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


(Guillaume Lessard) #5

Back to the initial example, here’s a variant that shows a “non-mutable” pointer lying; try it in a playground.

func get(_ pointer: UnsafePointer<Int>, at index: Int) -> Int
{
  let mutator = UnsafeMutablePointer(mutating: pointer)
  mutator[index] += 1
  return pointer[index]
}

let constantArray = [Int](repeating: 0, count: 3)

print(constantArray)	// [0,0,0]

let mutated = get(constantArray, at: 2) // returns 1

print(constantArray) // [0,0,1] oops!

I’d argue that this case should also require an ampersand and a mutable array.

Cheers,
Guillaume Lessard


(Charles Srstka) #6

+1

Charles

···

On May 17, 2017, at 12:48 PM, Anders Kierulf via swift-evolution <swift-evolution@swift.org> wrote:

I want to bring this up one last time in swift-evolution to give this issue a chance to not fall through the cracks. Passing a value to UnsafeRawPointer should not force that method to be mutating. Either the call should not require &, or this use of & should not require the method to be mutating. The workaround of copying to a temporary variable is not viable (see sample project in SR-4649).

Hope there’s some way this issue can be addressed sooner rather than later.

Thanks,
Anders Kierulf

On Apr 20, 2017, at 1:10 PM, Anders Kierulf <anders@smartgo.com> wrote:

Summary: Currently, only mutable values can be passed to UnsafeRawPointer, except for a special case for arrays. That special case should be generalized, allowing any values to be passed to UnsafeRawPointer without using &.

The following code shows the inconsistency in passing values to UnsafeRawPointer:

var varArray = [Int](repeating: 0, count: 6)
var varTuple = (0, 0, 0, 0, 0, 0)

let letArray = [Int](repeating: 0, count: 6)
let letTuple = (0, 0, 0, 0, 0, 0)

func get(_ pointer: UnsafeRawPointer, at index: Int) -> Int {
  let a = pointer.bindMemory(to: Int.self, capacity: 6)
  return a[index]
}

// Array can be passed directly, automatically takes address.
_ = get(varArray, at: 2) // okay
_ = get(letArray, at: 2) // okay

// When explicitly taking address, can only pass mutable variables.
_ = get(&varArray, at: 2) // okay, but seems inconsistent
_ = get(&letArray, at: 2) // fails: & not allowed

// Passing tuple instead of array fails.
_ = get(varTuple, at: 2) // fails: wrong type
_ = get(letTuple, at: 2) // fails: wrong type

// Adding & to pass a tuple only works for mutable values. Having to
// pass the address using & means that any methods calling this must
// be mutating, even though they don't mutate.
_ = get(&varTuple, at: 2) // okay, but forces mutating
_ = get(&letTuple, at: 2) // fails: cannot pass immutable value

Passing a value to an UnsafeRawPointer parameter should not require use of &, as that forces all code calling it to be mutating. Having a special case for array also doesn't make sense, as the idea of UnsafeRawPointer is that we're interpreting that memory content as whatever we want. Logged as SR-4649.

Proposal:
- Never require & when passing value to UnsafeRawPointer.
- Always require & when passing value to UnsafeMutableRawPointer.

(Fixed size arrays are still needed, but with this tweak, workarounds can be made to work.)

Anders Kierulf

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


(Jordan Rose) #7

This can come up even without &. It is illegal to use UnsafeMutablePointer.init(mutating:) on something that was not a mutable pointer to begin with, and the program is not guaranteed to do anything sensible if you break that rule.

Jordan

···

On May 17, 2017, at 14:22, Guillaume Lessard via swift-evolution <swift-evolution@swift.org> wrote:

Back to the initial example, here’s a variant that shows a “non-mutable” pointer lying; try it in a playground.

func get(_ pointer: UnsafePointer<Int>, at index: Int) -> Int
{
 let mutator = UnsafeMutablePointer(mutating: pointer)
 mutator[index] += 1
 return pointer[index]
}

let constantArray = [Int](repeating: 0, count: 3)

print(constantArray)	// [0,0,0]

let mutated = get(constantArray, at: 2) // returns 1

print(constantArray) // [0,0,1] oops!

I’d argue that this case should also require an ampersand and a mutable array.

Cheers,
Guillaume Lessard

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


(Guillaume Lessard) #8

How would you deal with the existence of:
UnsafeMutablePointer<Pointee>.init(mutating other: UnsafePointer<Pointee>)

No unsafe pointer can guarantee that it won’t be used to mutate its pointee.
Requiring a mutable variable seems like truth in advertising.

Sincerely,
Guillaume Lessard


(Guillaume Lessard) #9

“Illegal" or no, if it can be done it will de done. I just think it would be more sensible to *not* allow taking pointers to non-mutable variables at all.

Cheers,
Guillaume

···

On May 17, 2017, at 15:28, Jordan Rose <jordan_rose@apple.com> wrote:

This can come up even without &. It is illegal to use UnsafeMutablePointer.init(mutating:) on something that was not a mutable pointer to begin with, and the program is not guaranteed to do anything sensible if you break that rule.


(Richard Wei) #10

+1.

-Richard

···

On May 18, 2017, at 01:48, Anders Kierulf via swift-evolution <swift-evolution@swift.org> wrote:

I want to bring this up one last time in swift-evolution to give this issue a chance to not fall through the cracks. Passing a value to UnsafeRawPointer should not force that method to be mutating. Either the call should not require &, or this use of & should not require the method to be mutating. The workaround of copying to a temporary variable is not viable (see sample project in SR-4649).

Hope there’s some way this issue can be addressed sooner rather than later.

Thanks,
Anders Kierulf

On Apr 20, 2017, at 1:10 PM, Anders Kierulf <anders@smartgo.com> wrote:

Summary: Currently, only mutable values can be passed to UnsafeRawPointer, except for a special case for arrays. That special case should be generalized, allowing any values to be passed to UnsafeRawPointer without using &.

The following code shows the inconsistency in passing values to UnsafeRawPointer:

var varArray = [Int](repeating: 0, count: 6)
var varTuple = (0, 0, 0, 0, 0, 0)

let letArray = [Int](repeating: 0, count: 6)
let letTuple = (0, 0, 0, 0, 0, 0)

func get(_ pointer: UnsafeRawPointer, at index: Int) -> Int {
  let a = pointer.bindMemory(to: Int.self, capacity: 6)
  return a[index]
}

// Array can be passed directly, automatically takes address.
_ = get(varArray, at: 2) // okay
_ = get(letArray, at: 2) // okay

// When explicitly taking address, can only pass mutable variables.
_ = get(&varArray, at: 2) // okay, but seems inconsistent
_ = get(&letArray, at: 2) // fails: & not allowed

// Passing tuple instead of array fails.
_ = get(varTuple, at: 2) // fails: wrong type
_ = get(letTuple, at: 2) // fails: wrong type

// Adding & to pass a tuple only works for mutable values. Having to
// pass the address using & means that any methods calling this must
// be mutating, even though they don't mutate.
_ = get(&varTuple, at: 2) // okay, but forces mutating
_ = get(&letTuple, at: 2) // fails: cannot pass immutable value

Passing a value to an UnsafeRawPointer parameter should not require use of &, as that forces all code calling it to be mutating. Having a special case for array also doesn't make sense, as the idea of UnsafeRawPointer is that we're interpreting that memory content as whatever we want. Logged as SR-4649.

Proposal:
- Never require & when passing value to UnsafeRawPointer.
- Always require & when passing value to UnsafeMutableRawPointer.

(Fixed size arrays are still needed, but with this tweak, workarounds can be made to work.)

Anders Kierulf

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


(Anders Kierulf) #11

Thanks for refusing to let your pitch die. Something should eventually be done here and it's good to get feedback. The only reason to bring this up in Swift 4 is if we decide to outlaw some code pattern that's already in use. If this ends up just an additive convenience request, than it can be a bug report for now and come back in Swift 5. There have been a couple +1's already but I don't know whether it's a serious problem or just an annoyance.

For me, it’s a serious annoyance. A single wrong `mutating` in an API would be a minor issue, but the problem is that it infects the rest of the code like a plague. It’s bad enough that I’ve delayed work on that project, hoping for a resolution.

The problem I have with [SR-4649] "Don't require & to pass value to UnsafeRawPointer", is that it seeks to create inconsistency between raw and non-raw pointers. That might make sense for non-homogenous tupes, but otherwise I don't think it's desirable.

If a more consistent fix can be applied also to non-raw pointers, that would be great. That bug documents the problem I ran into, not necessarily the best solution. Others will know better what fits with the future direction of Swift and the underlying implementation.

Anders


(Jordan Rose) #12

That's what "unsafe" means. To take one specific use case, there's a big difference between withUnsafeBufferPointer and withUnsafeMutableBufferPointer on an Array: the latter triggers copy-on-write behavior before handing you the pointer. That's not an acceptable price to pay just for pointer access to the contents of the array.

(Another possible rule would be to drop UnsafeMutablePointer.init(mutating:), but unfortunately there are too many C APIs out there that don't use 'const'.)

Jordan

···

On May 17, 2017, at 14:53, Guillaume Lessard <glessard@tffenterprises.com> wrote:

On May 17, 2017, at 15:28, Jordan Rose <jordan_rose@apple.com> wrote:

This can come up even without &. It is illegal to use UnsafeMutablePointer.init(mutating:) on something that was not a mutable pointer to begin with, and the program is not guaranteed to do anything sensible if you break that rule.

“Illegal" or no, if it can be done it will de done. I just think it would be more sensible to *not* allow taking pointers to non-mutable variables at all.


(Charles Srstka) #13

There’s also no guarantee that unsafeBitCast won’t be used to mess with any argument to *anything* in ways that it’s not supposed to. This is just one of the perils of using unsafe APIs, of which UnsafePointer is a part.

By taking an UnsafePointer, however, the API is claiming it won’t mutate the pointer, much like a const void * in C. If it does in fact mutate it, that should be logged as a bug against the API, and the interface should be changed to use UnsafeMutablePointer instead.

Charles

···

On May 17, 2017, at 4:09 PM, Guillaume Lessard via swift-evolution <swift-evolution@swift.org> wrote:

How would you deal with the existence of:
UnsafeMutablePointer<Pointee>.init(mutating other: UnsafePointer<Pointee>)

No unsafe pointer can guarantee that it won’t be used to mutate its pointee.
Requiring a mutable variable seems like truth in advertising.

Sincerely,
Guillaume Lessard

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


(Andrew Trick) #14

Ok. I hear you.

To be clear, the only case that’s really giving you grief is this one right?

  func get(_ pointer: UnsafeRawPointer, at index: Int) -> Int

  _ = get(letTuple, at: 2) // fails: wrong type

And you don’t want to create a temp copy:

  var tupleMemory = letTuple
  get(&tupleMemory, at: 2)

In this case, the letTuple->UnsafeRawPointer conversion is likely going to create that copy anyway in order to give the tuple a memory address. A slightly more compelling example would be:

struct S {
  var tuple: (Int, Int, Int, Int, Int, Int)
}

func foo(s: S) -> Int {
  var tupleMemory = s.t
  return get(&tupleMemory, at: 2) // fails: wrong type
}

Are you more concerned that the copy won't be optimized away or that you need the extra line of code?

… I forgot to mention. Regarding this line:

  let a = pointer.bindMemory(to: Int.self, capacity: 6)

If the tuple memory is always viewed as (Int, Int...), then you can use assumeMemoryBound(to:) and don't need to specify capacity.

-Andy

···

On May 18, 2017, at 7:23 AM, Anders Kierulf <anders@smartgo.com> wrote:

Thanks for refusing to let your pitch die. Something should eventually be done here and it's good to get feedback. The only reason to bring this up in Swift 4 is if we decide to outlaw some code pattern that's already in use. If this ends up just an additive convenience request, than it can be a bug report for now and come back in Swift 5. There have been a couple +1's already but I don't know whether it's a serious problem or just an annoyance.

For me, it’s a serious annoyance. A single wrong `mutating` in an API would be a minor issue, but the problem is that it infects the rest of the code like a plague. It’s bad enough that I’ve delayed work on that project, hoping for a resolution.


(Anders Kierulf) #15

To be clear, the only case that’s really giving you grief is this one right?

  func get(_ pointer: UnsafeRawPointer, at index: Int) -> Int

  _ = get(letTuple, at: 2) // fails: wrong type

Yes. If that one could work without performance penalty, that’s perfect. (Assuming that homogeneous tuples are the best approximation of fixed-size arrays, for now.)

Actually, an indexed access into homogeneous tuples would solve this without even needing to use UnsafeRawPointer. Any +1 for that?

And you don’t want to create a temp copy:

  var tupleMemory = letTuple
  get(&tupleMemory, at: 2)

In this case, the letTuple->UnsafeRawPointer conversion is likely going to create that copy anyway in order to give the tuple a memory address. A slightly more compelling example would be:

struct S {
  var tuple: (Int, Int, Int, Int, Int, Int)
}

func foo(s: S) -> Int {
  var tupleMemory = s.tuple // was s.t, should be s.tuple [AK]
  return get(&tupleMemory, at: 2) // fails: wrong type
}

Are you more concerned that the copy won't be optimized away or that you need the extra line of code?

My main concern is performance. In my code, the tuple is often 380 words long, so a copy completely kills performance. This part of my code is performance critical, which is why I can’t just use Swift’s standard Array type.

… I forgot to mention. Regarding this line:

  let a = pointer.bindMemory(to: Int.self, capacity: 6)

If the tuple memory is always viewed as (Int, Int...), then you can use assumeMemoryBound(to:) and don't need to specify capacity.

-Andy

Thanks, assumingMemoryBound(to:) is good to know.

Anders


(Andrew Trick) #16

I agree with your proposed language changes, but there’s really a larger issue. Until we have move-only types and ‘shared’ argument conventions, I’m afraid that passing around a large struct, or even implicitly converting it to a pointer creates copies. `let` doesn’t give you a no-copy guarantee. If those copies aren’t already optimized at -O, it’s possible the optimizer could be improved to handle your cases. However, I don’t think you should rely on that. For now, until the ownership work is further along, the best way to avoid copies is to embrace mutability. Declare your large value types as `var` and always pass them `inout`.

I just noticed this bug, which has shows why the type system can make a copy impossible to avoid.
https://bugs.swift.org/browse/SR-4581
If you don’t care about conforming to UnsafeMutableCollection and implementing subscript { get } conformance, maybe you can avoid that problem.

Another option is to use indirect reference-counted storage for your fixed array, just like Array, so incidental copies aren’t expensive.

-Andy

···

On May 18, 2017, at 12:38 PM, Anders Kierulf <anders@smartgo.com> wrote:

And you don’t want to create a temp copy:

var tupleMemory = letTuple
get(&tupleMemory, at: 2)

In this case, the letTuple->UnsafeRawPointer conversion is likely going to create that copy anyway in order to give the tuple a memory address. A slightly more compelling example would be:

struct S {
var tuple: (Int, Int, Int, Int, Int, Int)
}

func foo(s: S) -> Int {
var tupleMemory = s.tuple // was s.t, should be s.tuple [AK]
return get(&tupleMemory, at: 2) // fails: wrong type
}

Are you more concerned that the copy won't be optimized away or that you need the extra line of code?

My main concern is performance. In my code, the tuple is often 380 words long, so a copy completely kills performance. This part of my code is performance critical, which is why I can’t just use Swift’s standard Array type.