[Review] SE-0184: Unsafe[Mutable][Raw][Buffer]Pointer: add missing methods, adjust existing labels for clarity, and remove deallocation size

I am mostly in favor of this two-method approach, but 'to' may not be the right label. Today we have both initialize(to:…) and initialize(from:…), which are not opposites. I think we can live with that, but we definitely can't use assign(to:). "x.assign(to: y)" means some form of "y = x".

That said, we don't actually need a single-element 'assign' variant, because you can also write it "x.pointee = y". But I agree that symmetry is nice, if we can get it.

I also want to note that we don't get to remove any of the old signatures; we need to preserve them for compatibility with Swift 4.0 and Swift 3.0. They can be marked obsoleted in Swift 5 and even deprecated in Swift 4.1 if we feel strongly enough, but we don't get to drop them.

Jordan

···

On Sep 5, 2017, at 14:50, Andrew Trick via swift-evolution <swift-evolution@swift.org> wrote:

We don't have a consensus, but I think the suggestion to distinguish
between single value vs. multiple semantics was good. Otherwise,
adding the default count could be very misleading. Normally, we try to
minimize surface area, but adding two methods for the single-value case
avoids ambiguity between the buffer and pointer semantics:

UMP (pointer)
--- func initialize(to:count:(=1))
+++ func initialize(to:)
+++ func initialize(repeating:count:) // no default count
+++ func assign(to:)
+++ func assign(repeating:count:) // no default count

UMRP (raw pointer):
--- func initializeMemory<T>(as:at:(=0)count:(1)to:)
+++ func initializeMemory<T>(as:repeating:count:) // remove default count

We do want the Swift memory model to be consistent with the reality that on most platforms, we need the runtime to track block size.

I don't know where this comes from. If you don't need to be malloc-compatible, you don't strictly "need" this information. (It would break tools like 'leaks', though.)

There are two distinct but related issues (1) malloc compatibility (2) malloc/free like functionality. I know developers sometimes expect or want #2. Realistically, we will always want the runtime to provide malloc_size, even if it’s not super fast, so we’re not giving up anything long term by providing #2. The fact the #1 is also a likely goal on major platforms just reinforces that position.

If our memory model states that the runtime tracks capacity of manually allocated blocks, then the deallocation capacity should be optional to reflect that.

Still waiting to hear any arguments that something about that memory model is bad.

I don't see an advantage to having the parameter if we're going to promise that it's always present. It is additional complexity for very little win in a generic interface. If someone wants to write a special allocation entry point that actually makes use of this, they can do so, but it shouldn't live on UnsafePointer.

(We also have nothing that prevents you from doing `(ptr+1).deallocate()`, but, well, "unsafe".)

I also don’t see any usability advantage in providing the extra argument for anyone directly using UnsafePointer, which is why I initially objected to Joe Groff’s request. Then I realized I care less about usability and potential confusion than I care that UnsafePointer API can be viewed is a specification of the Swift's basic memory management functionality. We want to communicate that data structures doing manual allocation/deallocation should provide the allocated capacity during deallocation if it is available. The runtime could make good use of that. In particular, I want UnsafeBufferPointer’s deallocate() to be able to call UnsafePointer.deallocate(allocatedCapacity: buffer.count), rather than implementing it in terms of Builtins.

Incidentally, FWIW, I think compiled code should continue to be required to pass the capacity to the runtime during deallocation. That way, any changes in the runtime implementation of deallocation, particularly extra address checks, are isolated to the standard library.

-Andy

···

On Sep 7, 2017, at 2:29 PM, Jordan Rose <jordan_rose@apple.com> wrote:

Thanks, I was being a bit silly...
We also have malloc_good_size(4097) = 4608.

What I was getting at is, can malloc_good_size be “dumb” for any legal implementation of malloc zones?

Or can we assert malloc_good_size(x) == malloc_size(malloc(x)?

-Andy

···

On Sep 9, 2017, at 3:15 AM, Jean-Daniel <mailing@xenonium.com> wrote:

Le 8 sept. 2017 à 03:03, Andrew Trick via swift-evolution <swift-evolution@swift.org> a écrit :

On Sep 7, 2017, at 5:37 PM, Joe Groff <jgroff@apple.com> wrote:

The important thing is that the UnsafeBufferPointer API is clearly documented. We do not want users to think it’s ok to deallocate a smaller buffer than they allocated.

Unfortunately, there’s actually no way to assert this in the runtime because malloc_size could be larger than the allocated capacity. Incorrect code could happen to work and we can live with that.

Would it be sufficient to assert that malloc_good_size(passedCapacity) == malloc_size(base) ? It wouldn't be perfect but could still catch a lot of misuses.

That theory does hold up for a million random values, but I don’t know if we can rely on malloc_size never being larger than roundUp(sz, 16). Greg?

You can’t. This may be true while alloc size if less than a page, but a quick test show that:

malloc_size(malloc(4097)) = 4608

Answer:
- this assumption is obviously dependent on a particular implementation of libc.
- users implement their malloc zone however they like (although Swift doesn't strictly *need* to be compatible with them).
- even current implementations of libc have various operating modes that could violate the assertion, you would need to guard the check
  with a runtime condition.

-Andy

···

On Sep 9, 2017, at 8:37 AM, Andrew Trick <atrick@apple.com> wrote:

On Sep 9, 2017, at 3:15 AM, Jean-Daniel <mailing@xenonium.com> wrote:

Le 8 sept. 2017 à 03:03, Andrew Trick via swift-evolution <swift-evolution@swift.org> a écrit :

On Sep 7, 2017, at 5:37 PM, Joe Groff <jgroff@apple.com> wrote:

The important thing is that the UnsafeBufferPointer API is clearly documented. We do not want users to think it’s ok to deallocate a smaller buffer than they allocated.

Unfortunately, there’s actually no way to assert this in the runtime because malloc_size could be larger than the allocated capacity. Incorrect code could happen to work and we can live with that.

Would it be sufficient to assert that malloc_good_size(passedCapacity) == malloc_size(base) ? It wouldn't be perfect but could still catch a lot of misuses.

That theory does hold up for a million random values, but I don’t know if we can rely on malloc_size never being larger than roundUp(sz, 16). Greg?

You can’t. This may be true while alloc size if less than a page, but a quick test show that:

malloc_size(malloc(4097)) = 4608

Thanks, I was being a bit silly...
We also have malloc_good_size(4097) = 4608.

What I was getting at is, can malloc_good_size be “dumb” for any legal implementation of malloc zones?

Or can we assert malloc_good_size(x) == malloc_size(malloc(x)?

-Andy

Sorry, a better analogy would have been:

buf[i...].intialize(from: source)

Whether you specify the slice’s end point depends on whether you want to completely initialize that slice or whether you’re just filling up as much of the buffer as you can. It also depends on whether `source` is also a buffer (of known size) or some arbitrary Sequence.

Otherwise, point taken.

-Andy

···

On Sep 29, 2017, at 1:23 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

Instead of

  buf.intialize(at: i, from: source)

We want to force a more obvious idiom:

  buf[i..<n].intialize(from: source)

The problem with subscript notation is we currently get the n argument from the source argument. So what would really have to be written is

buf[i ..< i + source.count].initialize(from: source)

which is a lot more ugly and redundant. One option could be to decouple the count parameter from the length of the source buffer, but that opens up the whole can of worms in which length do we use? What happens if n - i is less than or longer than source.count? If we enforce the precondition that source.count == n - i, then this syntax seems horribly redundant.

Those are good points. I’m also somewhat concerned about -Onone performance. Can you point to some code in your library that becomes excessively redundant by specifying the source.count at the call site?

-Andy

···

On Sep 29, 2017, at 1:23 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

I think this is a reasonable argument and convinced myself that it's possible to extend the slice API. I'm also convinced now that we don't need overloads to handle an UnsafeBufferPointer source, instead we can provide a single generic entry point on both UnsafeMutableBufferPointer and its slice, optimized within the implementation:

`initialize<S : Sequence>(from: S) -> (S.Iterator, Index)

We can always drop down to the UnsafePointer API to get back to a direct unsafe implementation as a temporary workaround for performance issues.

Using Sequences throws out a whole host of assumptions we’ve been taking advantage of. We can’t check for source.countanymore since that requires traversing the entire Sequence. And if the performance is so bad relative to the UnsafePointer API, we have to ask what the purpose of such a buffer API would even be. The whole purpose of the buffer API was to make it easier to do things with pointers without having to keep track of buffer lengths externally. It should be built around the UnsafePointer API, not the much higher level Sequence API.

I think this is a reasonable argument and convinced myself that it's possible to extend the slice API. I'm also convinced now that we don't need overloads to handle an UnsafeBufferPointer source, instead we can provide a single generic entry point on both UnsafeMutableBufferPointer and its slice, optimized within the implementation:

`initialize<S : Sequence>(from: S) -> (S.Iterator, Index)

We can always drop down to the UnsafePointer API to get back to a direct unsafe implementation as a temporary workaround for performance issues.

Using Sequences throws out a whole host of assumptions we’ve been taking advantage of. We can’t check for source.count anymore since that requires traversing the entire Sequence.

Sequence provides underestimatedCount. In the case of unsafe buffer types, underestimatedCount just gives the count. A requirement of initialize(from: source) is that the caller must provide at least enough room for the underestimatedCount.

And if the performance is so bad relative to the UnsafePointer API, we have to ask what the purpose of such a buffer API would even be.

It shouldn’t be much different, performance-wise. initialize(from:) bounces directly into a call to source._copyContents(initializing:), which can be specialized to perform lower-level memory copying operations (as it is already for types like array). The optimizer needs to ensure that this is just as efficient as if we had an initialize that took an unsafe buffer. It ought to do this already, but if not, the implementation of this SE should include making sure it does. As a last resort, we could add concrete overloads for buffers – but bear in mind, you want at least to be able to initialize from 1) an UnsafeBufferPointer, 2) an UnsafeMutableBufferPointer, and 3) slices of 1 and 2. This would probably mean we need some kind of generic method regardless.

The whole purpose of the buffer API was to make it easier to do things with pointers without having to keep track of buffer lengths externally. It should be built around the UnsafePointer API, not the much higher level Sequence API.

This would come at a cost of essentially duplicating API surface area for both concrete and generic source. This should only be done for very good reasons, and performance issues that can be resolved as part of the implementation aren’t necessarily a sufficient one.

···

On Sep 29, 2017, at 1:23 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

Let's set aside for now whether we support full or partial initialization/assignment, how to handle moveInitialize, and whether we need to return the Iterator/Index. This is going to require another iteration on swift-evolution, which we should discuss in a separate thread.

At this point, I suggest removing the controversial aspects of SE-0184 so that we can put the other changes behind us and focus future discussion around a smaller follow-up proposal.

Here I've summarized the changes that I think could be accepted as-is:
SE-0184-part1.md · GitHub

If you amend SE-0184 accordingly and file a new PR, I think it can be quickly approved.

-Andy

After thinking about this more, one-sided ranges might provide just the
expressivity we need. What if:

buf[offset...].initialize(from: source) // initializes source.count
elements from source starting from offset

buf[offset ..< endIndex].initialize(from: source) // initializes up to
source.count elements from source starting from offset

The one sided one does not give a full initialization guarantee. The two
sided one guarantees the entire segment is initialized. For move
operations, the one sided one will fully deinitialize the source buffer
while the two sided one will only deinitialize endIndex - offset elements.

···

On Fri, Sep 29, 2017 at 4:13 PM, Andrew Trick <atrick@apple.com> wrote:

On Sep 29, 2017, at 1:23 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

Instead of

  buf.intialize(at: i, from: source)

We want to force a more obvious idiom:

  buf[i..<n].intialize(from: source)

The problem with subscript notation is we currently get the n argument
from the source argument. So what would really have to be written is

buf[i ..< i + source.count].initialize(from: source)

which is a lot more ugly and redundant. One option could be to decouple
the count parameter from the length of the source buffer, but that opens up
the whole can of worms in which length do we use? What happens if n - i is
less than or longer than source.count? If we enforce the precondition that
source.count == n - i, then this syntax seems horribly redundant.

Sorry, a better analogy would have been:

buf[i...].intialize(from: source)

Whether you specify the slice’s end point depends on whether you want to
completely initialize that slice or whether you’re just filling up as much
of the buffer as you can. It also depends on whether `source` is also a
buffer (of known size) or some arbitrary Sequence.

Otherwise, point taken.

-Andy

this function initializeMemory<C:Collection>(as:from:) says it will be
removed in Swift 4.0. It is now Swift 4.0. can I remove it?

···

On Sat, Sep 30, 2017 at 2:15 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

On Thu, Sep 28, 2017 at 7:59 PM, Andrew Trick <atrick@apple.com> wrote:

On Sep 6, 2017, at 10:15 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

okay so I think so far what’s been agreed on is

*1*. rename “Bytes” to “Memory” in the raw pointer API. Note: this
brings the `copyBytes<C>(from:)` collection method into the scope of
this proposal

*2*. change raw offsets to be in terms of bytes, not typed strides. This
argument will be called `atByteOffset:` and will only appear in
UnsafeMutableRawBufferPointer. “at:” arguments are no longer needed in
UnsafeMutableRawPointer, since we can just use pointer arithmetic now.

*3*. move UnsafeMutableBufferPointer’s `at:` arguments to the front of
the parameter list. mostly cause any pointer arithmetic happens in the
front so structurally we want to mirror that.

*4*. add dual (to:) single element initializers and assigners to
UnsafeMutablePointer, but not UnsafeMutableRawPointer because it’s
apparently not useful there. `UnsafeMutableRawPointer.initi
alizeMemory<T>(as:repeating:count:)` still loses its default count to
prevent confusion with its buffer variant.

*5*. memory deallocation on buffer pointers is clearly documented to
only be defined behavior when the buffer matches a whole heap block.

Kelvin,

Attempting to limit the scope of this proposal backfired. I was hoping to
avoid discussing changes to the slice API, instead providing basic
functionality within the buffer API itself. However, Dave Abrahams has
argued that once the slice API is extended, the positional arguments are
extraneous and less clear.

Instead of

  buf.intialize(at: i, from: source)

We want to force a more obvious idiom:

  buf[i..<n].intialize(from: source)

I think this is a reasonable argument and convinced myself that it's
possible to extend the slice API. I'm also convinced now that we don't need
overloads to handle an UnsafeBufferPointer source, instead we can provide a
single generic entry point on both UnsafeMutableBufferPointer and its
slice, optimized within the implementation:

`initialize<S : Sequence>(from: S) -> (S.Iterator, Index)

We can always drop down to the UnsafePointer API to get back to a direct
unsafe implementation as a temporary workaround for performance issues.

Let's set aside for now whether we support full or partial
initialization/assignment, how to handle moveInitialize, and whether we
need to return the Iterator/Index. This is going to require another
iteration on swift-evolution, which *we should discuss in a separate
thread*.

At this point, I suggest removing the controversial aspects of SE-0184 so
that we can put the other changes behind us and focus future discussion
around a smaller follow-up proposal.

Here I've summarized the changes that I think could be accepted as-is:
SE-0184-part1.md · GitHub

If you amend SE-0184 accordingly and file a new PR, I think it can be
quickly approved.

-Andy

Part one of SE-0184 is here as SE-0184 A
<https://github.com/kelvin13/swift-evolution/blob/improved-pointers/proposals/0184a-unsafe-pointers-part-1.md&gt;
I’ll implement it tomorrow and then make the PR

···

On Sat, Sep 30, 2017 at 2:15 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

On Thu, Sep 28, 2017 at 7:59 PM, Andrew Trick <atrick@apple.com> wrote:

On Sep 6, 2017, at 10:15 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

okay so I think so far what’s been agreed on is

*1*. rename “Bytes” to “Memory” in the raw pointer API. Note: this
brings the `copyBytes<C>(from:)` collection method into the scope of
this proposal

*2*. change raw offsets to be in terms of bytes, not typed strides. This
argument will be called `atByteOffset:` and will only appear in
UnsafeMutableRawBufferPointer. “at:” arguments are no longer needed in
UnsafeMutableRawPointer, since we can just use pointer arithmetic now.

*3*. move UnsafeMutableBufferPointer’s `at:` arguments to the front of
the parameter list. mostly cause any pointer arithmetic happens in the
front so structurally we want to mirror that.

*4*. add dual (to:) single element initializers and assigners to
UnsafeMutablePointer, but not UnsafeMutableRawPointer because it’s
apparently not useful there. `UnsafeMutableRawPointer.initi
alizeMemory<T>(as:repeating:count:)` still loses its default count to
prevent confusion with its buffer variant.

*5*. memory deallocation on buffer pointers is clearly documented to
only be defined behavior when the buffer matches a whole heap block.

Kelvin,

Attempting to limit the scope of this proposal backfired. I was hoping to
avoid discussing changes to the slice API, instead providing basic
functionality within the buffer API itself. However, Dave Abrahams has
argued that once the slice API is extended, the positional arguments are
extraneous and less clear.

Instead of

  buf.intialize(at: i, from: source)

We want to force a more obvious idiom:

  buf[i..<n].intialize(from: source)

I think this is a reasonable argument and convinced myself that it's
possible to extend the slice API. I'm also convinced now that we don't need
overloads to handle an UnsafeBufferPointer source, instead we can provide a
single generic entry point on both UnsafeMutableBufferPointer and its
slice, optimized within the implementation:

`initialize<S : Sequence>(from: S) -> (S.Iterator, Index)

We can always drop down to the UnsafePointer API to get back to a direct
unsafe implementation as a temporary workaround for performance issues.

Let's set aside for now whether we support full or partial
initialization/assignment, how to handle moveInitialize, and whether we
need to return the Iterator/Index. This is going to require another
iteration on swift-evolution, which *we should discuss in a separate
thread*.

At this point, I suggest removing the controversial aspects of SE-0184 so
that we can put the other changes behind us and focus future discussion
around a smaller follow-up proposal.

Here I've summarized the changes that I think could be accepted as-is:
SE-0184-part1.md · GitHub

If you amend SE-0184 accordingly and file a new PR, I think it can be
quickly approved.

-Andy

Part one of SE-0184 is here as SE-0184 A
<https://github.com/kelvin13/swift-evolution/blob/improved-pointers/proposals/0184a-unsafe-pointers-part-1.md&gt;
I’ll implement it tomorrow and then make the PR

I’m fine with most of this

we agreed to deprecate the strided at:? Note that
UnsafeMutableRawBufferPointer would still need a byteOffset: argument.
I’m also still not comfortable with duplicating functionality for the sake
of having two names

I’ll summarize what I think happened in this thread...

I don't think the suggested copyBytes rename was controversial:

UMRP (raw pointer):
--- copyBytes(from:count)
+++ copyMemory(from:bytes:)

UMRBP (raw buffer):
--- copyBytes(from:)
+++ copyMemory(atByteOffset:from:)

--- copyBytes<C>(from:)
+++ copyMemory(from:)

no problem with this

In the new raw initializeMemory methods, Xiaodi and I agreed to make
it more clear that the offset is in terms of `self` rather than
`from`, and to further reduce ambiguity by forcing manual stride
computation and using an explicit "offset" label. The call site will
be just as explicit as dropping down to `baseAddress` but without any
pointer conversion boilerplate.

UMRBP (raw buffer):
+++ func initializeMemory<T>(atByteOffset:as:from:)
+++ func moveInitializeMemory<T>(atByteOffset:as:from:)

Agree, but the label should just be `atByte:`, not `atByteOffset:`. after
all, we don’t use `atOffset:` in the strided case; its obvious that it’s
an offset

Than, in the one existing initializeMemory method, just drop the strided
index.
It's never used, we want to be consistent in using a byte offset
for the raw index, and that can be expressed just as easily as pointer
arithmetic:

UMRP (raw pointer):
--- func initializeMemory<T>(as:T.Type, at:Int = 0, count:Int = 1, to:T)
+++ func initializeMemory<T>(as:T.Type, repeating:T, count:Int = 1)

Then you can simply leave these methods as-is:
> func initializeMemory<T>(as:T.Type, from:UnsafePointer<T>, count:Int)
> func moveInitializeMemory<T>(as:T.Type, from:UnsafeMutablePointer<T>,
count:Int)

yes

We don't have a consensus, but I think the suggestion to distinguish
between single value vs. multiple semantics was good. Otherwise,
adding the default count could be very misleading. Normally, we try to
minimize surface area, but adding two methods for the single-value case
avoids ambiguity between the buffer and pointer semantics:

UMP (pointer)
--- func initialize(to:count:(=1))
+++ func initialize(to:)
+++ func initialize(repeating:count:) // no default count
+++ func assign(to:)
+++ func assign(repeating:count:) // no default count

UMRP (raw pointer):
--- func initializeMemory<T>(as:at:(=0)count:(1)to:)
+++ func initializeMemory<T>(as:repeating:count:) // remove default
count

still extremely suspicious of this but i’m willing to compromise. also
there should be an `initializeMemory<T>(at:to:)` to match the typed
methods

*initializeMemory<T>(as:to:)

···

On Tue, Sep 5, 2017 at 5:16 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

On Tue, Sep 5, 2017 at 4:49 PM, Andrew Trick <atrick@apple.com> wrote:

On Sep 5, 2017, at 9:53 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

-Andy

On Tue, Sep 5, 2017 at 11:31 AM, Andrew Trick <atrick@apple.com> wrote:

I think we’ve agreed to a few minor updates to this proposal. Since
there hasn’t been any other feedback on the thread it may be worth posting
an amended proposal so we all know what we’ve agreed on.

-Andy

On Sep 3, 2017, at 8:23 PM, Andrew Trick via swift-evolution < >>> swift-evolution@swift.org> wrote:

On Sep 3, 2017, at 8:05 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

If we use byte offset, then the at parameter in UnsafeMutableRawPointer

should be removed, since pointer arithmetic can be used instead (just
like with UnsafeMutablePointer).

I agree that it seems quite sensible to remove the ‘at’ parameter
altogether from the UMRP method.

No code in tree or on github is using the `at` argument. I think it can
be removed. A fixit should still be possible.

Not convinced moving the at: argument to come before the as: argument

is worth it in terms of source breakage.

Since much of this proposal involves shuffling and relabeling arguments,
I’d argue it’s better to break slight more source in one go for the optimal
API than to break slightly less for a slightly less optimal API, no? (This
is assuming there is agreement that ‘at:as:’ is less prone to
misinterpretation than ‘as:at:’.)

To be clear, we’re just talking about UnsafeMutableRawBufferPointer.initializeMemory
now, so this is purely additive.
I think the label needs to be `atByteOffset`, and placing it before `as`
makes a lot of sense because it no longer depends on the type’s stride.

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

I’m fine with most of this

we agreed to deprecate the strided at:? Note that
UnsafeMutableRawBufferPointer would still need a byteOffset: argument.
I’m also still not comfortable with duplicating functionality for the sake
of having two names

I’ll summarize what I think happened in this thread...

I don't think the suggested copyBytes rename was controversial:

UMRP (raw pointer):
--- copyBytes(from:count)
+++ copyMemory(from:bytes:)

UMRBP (raw buffer):
--- copyBytes(from:)
+++ copyMemory(atByteOffset:from:)

--- copyBytes<C>(from:)
+++ copyMemory(from:)

no problem with this

In the new raw initializeMemory methods, Xiaodi and I agreed to make
it more clear that the offset is in terms of `self` rather than
`from`, and to further reduce ambiguity by forcing manual stride
computation and using an explicit "offset" label. The call site will
be just as explicit as dropping down to `baseAddress` but without any
pointer conversion boilerplate.

UMRBP (raw buffer):
+++ func initializeMemory<T>(atByteOffset:as:from:)
+++ func moveInitializeMemory<T>(atByteOffset:as:from:)

Agree, but the label should just be `atByte:`, not `atByteOffset:`. after
all, we don’t use `atOffset:` in the strided case; its obvious that it’s
an offset

The existing APIs use the terminology "byte offset"--for example,
URP.load(fromByteOffset:as:). The rationale is that "at" without a noun
that follows implies, per Swift API naming guidelines, "at index." If you
want to specify, as we do here, what the index _is_, then it's written out
in full.

Than, in the one existing initializeMemory method, just drop the strided
index.
It's never used, we want to be consistent in using a byte offset
for the raw index, and that can be expressed just as easily as pointer
arithmetic:

UMRP (raw pointer):
--- func initializeMemory<T>(as:T.Type, at:Int = 0, count:Int = 1, to:T)
+++ func initializeMemory<T>(as:T.Type, repeating:T, count:Int = 1)

Then you can simply leave these methods as-is:
> func initializeMemory<T>(as:T.Type, from:UnsafePointer<T>, count:Int)
> func moveInitializeMemory<T>(as:T.Type, from:UnsafeMutablePointer<T>,
count:Int)

yes

We don't have a consensus, but I think the suggestion to distinguish
between single value vs. multiple semantics was good. Otherwise,
adding the default count could be very misleading. Normally, we try to
minimize surface area, but adding two methods for the single-value case
avoids ambiguity between the buffer and pointer semantics:

UMP (pointer)
--- func initialize(to:count:(=1))
+++ func initialize(to:)
+++ func initialize(repeating:count:) // no default count
+++ func assign(to:)
+++ func assign(repeating:count:) // no default count

UMRP (raw pointer):
--- func initializeMemory<T>(as:at:(=0)count:(1)to:)
+++ func initializeMemory<T>(as:repeating:count:) // remove default count

still extremely suspicious of this but i’m willing to compromise. also
there should be an `initializeMemory<T>(at:to:)` to match the typed
methods

Do you mean initializeMemory<T>(as:to:)?

···

On Tue, Sep 5, 2017 at 17:16 Taylor Swift <kelvin13ma@gmail.com> wrote:

On Tue, Sep 5, 2017 at 4:49 PM, Andrew Trick <atrick@apple.com> wrote:

On Sep 5, 2017, at 9:53 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

-Andy

On Tue, Sep 5, 2017 at 11:31 AM, Andrew Trick <atrick@apple.com> wrote:

I think we’ve agreed to a few minor updates to this proposal. Since
there hasn’t been any other feedback on the thread it may be worth posting
an amended proposal so we all know what we’ve agreed on.

-Andy

On Sep 3, 2017, at 8:23 PM, Andrew Trick via swift-evolution < >>> swift-evolution@swift.org> wrote:

On Sep 3, 2017, at 8:05 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

If we use byte offset, then the at parameter in UnsafeMutableRawPointer should

be removed, since pointer arithmetic can be used instead (just like with
UnsafeMutablePointer).

I agree that it seems quite sensible to remove the ‘at’ parameter
altogether from the UMRP method.

No code in tree or on github is using the `at` argument. I think it can
be removed. A fixit should still be possible.

Not convinced moving the at: argument to come before the as: argument

is worth it in terms of source breakage.

Since much of this proposal involves shuffling and relabeling arguments,
I’d argue it’s better to break slight more source in one go for the optimal
API than to break slightly less for a slightly less optimal API, no? (This
is assuming there is agreement that ‘at:as:’ is less prone to
misinterpretation than ‘as:at:’.)

To be clear, we’re just talking
about UnsafeMutableRawBufferPointer.initializeMemory now, so this is purely
additive.
I think the label needs to be `atByteOffset`, and placing it before `as`
makes a lot of sense because it no longer depends on the type’s stride.

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

assign(to:) was never part of the original proposal, now that you mention
it, it’s stupid and should be left out

···

On Thu, Sep 7, 2017 at 1:33 PM, Jordan Rose <jordan_rose@apple.com> wrote:

On Sep 5, 2017, at 14:50, Andrew Trick via swift-evolution < > swift-evolution@swift.org> wrote:

We don't have a consensus, but I think the suggestion to distinguish
between single value vs. multiple semantics was good. Otherwise,
adding the default count could be very misleading. Normally, we try to
minimize surface area, but adding two methods for the single-value case
avoids ambiguity between the buffer and pointer semantics:

UMP (pointer)
--- func initialize(to:count:(=1))
+++ func initialize(to:)
+++ func initialize(repeating:count:) // no default count
+++ func assign(to:)
+++ func assign(repeating:count:) // no default count

UMRP (raw pointer):
--- func initializeMemory<T>(as:at:(=0)count:(1)to:)
+++ func initializeMemory<T>(as:repeating:count:) // remove default count

I am *mostly* in favor of this two-method approach, but 'to' may not be
the right label. Today we have both initialize(to:…) and
initialize(from:…), which are not opposites. I think we can live with that,
but we *definitely* can't use assign(to:). "x.assign(to: y)" means some
form of "y = x".

That said, we don't actually *need* a single-element 'assign' variant,
because you can also write it "x.pointee = y". But I agree that symmetry is
nice, if we can get it.

We do want the Swift memory model to be consistent with the reality that on most platforms, we need the runtime to track block size.

I don't know where this comes from. If you don't need to be malloc-compatible, you don't strictly "need" this information. (It would break tools like 'leaks', though.)

There are two distinct but related issues (1) malloc compatibility (2) malloc/free like functionality. I know developers sometimes expect or want #2. Realistically, we will always want the runtime to provide malloc_size, even if it’s not super fast, so we’re not giving up anything long term by providing #2. The fact the #1 is also a likely goal on major platforms just reinforces that position.

I don't understand why "realistically, we will always want the runtime to provide malloc_size". Could you explain why?

But then given that, I don't understand why the 'capacity' parameter is necessary. Under what circumstances would it actually be faster than "just" calling malloc_size?

Jordan

···

On Sep 7, 2017, at 17:09, Andrew Trick <atrick@apple.com> wrote:

On Sep 7, 2017, at 2:29 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

If our memory model states that the runtime tracks capacity of manually allocated blocks, then the deallocation capacity should be optional to reflect that.

Still waiting to hear any arguments that something about that memory model is bad.

I don't see an advantage to having the parameter if we're going to promise that it's always present. It is additional complexity for very little win in a generic interface. If someone wants to write a special allocation entry point that actually makes use of this, they can do so, but it shouldn't live on UnsafePointer.

(We also have nothing that prevents you from doing `(ptr+1).deallocate()`, but, well, "unsafe".)

I also don’t see any usability advantage in providing the extra argument for anyone directly using UnsafePointer, which is why I initially objected to Joe Groff’s request. Then I realized I care less about usability and potential confusion than I care that UnsafePointer API can be viewed is a specification of the Swift's basic memory management functionality. We want to communicate that data structures doing manual allocation/deallocation should provide the allocated capacity during deallocation if it is available. The runtime could make good use of that. In particular, I want UnsafeBufferPointer’s deallocate() to be able to call UnsafePointer.deallocate(allocatedCapacity: buffer.count), rather than implementing it in terms of Builtins.

Incidentally, FWIW, I think compiled code should continue to be required to pass the capacity to the runtime during deallocation. That way, any changes in the runtime implementation of deallocation, particularly extra address checks, are isolated to the standard library.

Instead of

  buf.intialize(at: i, from: source)

We want to force a more obvious idiom:

  buf[i..<n].intialize(from: source)

The problem with subscript notation is we currently get the n argument from the source argument. So what would really have to be written is

buf[i ..< i + source.count].initialize(from: source)

which is a lot more ugly and redundant. One option could be to decouple the count parameter from the length of the source buffer, but that opens up the whole can of worms in which length do we use? What happens if n - i is less than or longer than source.count? If we enforce the precondition that source.count == n - i, then this syntax seems horribly redundant.

Sorry, a better analogy would have been:

buf[i...].intialize(from: source)

Whether you specify the slice’s end point depends on whether you want to completely initialize that slice or whether you’re just filling up as much of the buffer as you can. It also depends on whether `source` is also a buffer (of known size) or some arbitrary Sequence.

Otherwise, point taken.

-Andy

After thinking about this more, one-sided ranges might provide just the expressivity we need. What if:

buf[offset...].initialize(from: source) // initializes source.count elements from source starting from offset

buf[offset ..< endIndex].initialize(from: source) // initializes up to source.count elements from source starting from offset

The one sided one does not give a full initialization guarantee. The two sided one guarantees the entire segment is initialized.

In every other context, x[i...] is equivalent to x[i..<x.endIndex]

I don't think breaking that precedent is a good idea.

···

On Sep 29, 2017, at 3:48 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:
On Fri, Sep 29, 2017 at 4:13 PM, Andrew Trick <atrick@apple.com <mailto:atrick@apple.com>> wrote:

On Sep 29, 2017, at 1:23 PM, Taylor Swift <kelvin13ma@gmail.com <mailto:kelvin13ma@gmail.com>> wrote:

For move operations, the one sided one will fully deinitialize the source buffer while the two sided one will only deinitialize endIndex - offset elements.


-Dave

well since people want to use subscript notation so much we need some way of expressing case 1. writing both bounds in the subscript seems to imply a full initialization (and thus partial movement) guarantee.

···

On Sep 29, 2017, at 5:56 PM, Dave Abrahams <dabrahams@apple.com> wrote:

On Sep 29, 2017, at 3:48 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

On Fri, Sep 29, 2017 at 4:13 PM, Andrew Trick <atrick@apple.com> wrote:

On Sep 29, 2017, at 1:23 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

Instead of

  buf.intialize(at: i, from: source)

We want to force a more obvious idiom:

  buf[i..<n].intialize(from: source)

The problem with subscript notation is we currently get the n argument from the source argument. So what would really have to be written is

buf[i ..< i + source.count].initialize(from: source)

which is a lot more ugly and redundant. One option could be to decouple the count parameter from the length of the source buffer, but that opens up the whole can of worms in which length do we use? What happens if n - i is less than or longer than source.count? If we enforce the precondition that source.count == n - i, then this syntax seems horribly redundant.

Sorry, a better analogy would have been:

buf[i...].intialize(from: source)

Whether you specify the slice’s end point depends on whether you want to completely initialize that slice or whether you’re just filling up as much of the buffer as you can. It also depends on whether `source` is also a buffer (of known size) or some arbitrary Sequence.

Otherwise, point taken.

-Andy

After thinking about this more, one-sided ranges might provide just the expressivity we need. What if:

buf[offset...].initialize(from: source) // initializes source.count elements from source starting from offset

buf[offset ..< endIndex].initialize(from: source) // initializes up to source.count elements from source starting from offset

The one sided one does not give a full initialization guarantee. The two sided one guarantees the entire segment is initialized.

In every other context, x[i...] is equivalent to x[i..<x.endIndex]

I don't think breaking that precedent is a good idea.

For move operations, the one sided one will fully deinitialize the source buffer while the two sided one will only deinitialize endIndex - offset elements.


-Dave

To this and your other method about the real library code; right now the
PNG library doesn’t use buffer methods (since they don’t exist yet) and
pixels are plain-old-data so i’m not using the memory state API there
anyway (the original proposal didn’t even have anything to do with those
APIs actually). However I did take a few minutes to write a quick queue
implementation
<https://gist.github.com/kelvin13/0860334278aeab5c1cbaefbefb050268#file-dequeue-swift&gt;
in Swift which does not make any assumptions about the Element type. I then
rewrote it using the hypothetical buffer API I proposed, and then the
hypothetical buffer API using subscript notation.

*These are the relevant function calls using the current API (the rest of
the code has been stripped out)*

        newBuffer:UnsafeMutablePointer<Element> =
                    UnsafeMutablePointer<Element>.allocate(capacity:
newCapacity)

        newBuffer .moveInitialize( from: buffer + self.zero,
                                                        count:
self.capacity - self.zero)
        (newBuffer + self.zero).moveInitialize( from: buffer,
                                                        count: self.zero)
        buffer.deallocate(capacity: self.capacity)

        (self.buffer! + self.bufferPosition(of: self.count)).initialize(to:
data)

        let dequeued:Element = (self.buffer! + self.zero).move()

*These are the function calls using the SE 184 API *

        newBuffer:UnsafeMutableBufferPointer<Element> =
                    UnsafeMutableBufferPointer<Element>.allocate(capacity:
newCapacity)

        newBuffer.moveInitialize(at: 0, from:
self.buffer[self.zero... ])
        newBuffer.moveInitialize(at: self.zero, from: self.buffer[0 ..<
self.zero])
        self.buffer.deallocate()

        (self.buffer.baseAddress! + self.bufferPosition(of:
self.count)).initialize(to: data)

        let dequeued:Element = (self.buffer! + self.zero).move()

*And with the proposed subscript notation*

        newBuffer:UnsafeMutableBufferPointer<Element> =
                    UnsafeMutableBufferPointer<Element>.allocate(capacity:
newCapacity)

        newBuffer[0... ].moveInitialize(from:
self.buffer[self.zero... ])
        newBuffer[self.zero ... self.zero << 1].moveInitialize(from:
self.buffer[0 ..< self.zero])
        self.buffer.deallocate()

        (self.buffer.baseAddress! + self.bufferPosition(of:
self.count)).initialize(to: data)

        let dequeued:Element = (self.buffer! + self.zero).move()

I took out the rebasing calls since apparently we’re bringing slices into
the scope of this proposal.

···

On Fri, Sep 29, 2017 at 6:17 PM, Andrew Trick <atrick@apple.com> wrote:

On Sep 29, 2017, at 4:03 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

On Sep 29, 2017, at 5:56 PM, Dave Abrahams <dabrahams@apple.com> wrote:

On Sep 29, 2017, at 3:48 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

On Fri, Sep 29, 2017 at 4:13 PM, Andrew Trick <atrick@apple.com> wrote:

On Sep 29, 2017, at 1:23 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

Instead of

  buf.intialize(at: i, from: source)

We want to force a more obvious idiom:

  buf[i..<n].intialize(from: source)

The problem with subscript notation is we currently get the n argument
from the source argument. So what would really have to be written is

buf[i ..< i + source.count].initialize(from: source)

which is a lot more ugly and redundant. One option could be to decouple
the count parameter from the length of the source buffer, but that opens up
the whole can of worms in which length do we use? What happens if n - i is
less than or longer than source.count? If we enforce the precondition
that source.count == n - i, then this syntax seems horribly redundant.

Sorry, a better analogy would have been:

buf[i...].intialize(from: source)

Whether you specify the slice’s end point depends on whether you want to
completely initialize that slice or whether you’re just filling up as much
of the buffer as you can. It also depends on whether `source` is also a
buffer (of known size) or some arbitrary Sequence.

Otherwise, point taken.

-Andy

After thinking about this more, one-sided ranges might provide just the
expressivity we need. What if:

buf[offset...].initialize(from: source) // initializes source.count
elements from source starting from offset

buf[offset ..< endIndex].initialize(from: source) // initializes up to
source.count elements from source starting from offset

The one sided one does not give a full initialization guarantee. The two
sided one guarantees the entire segment is initialized.

In every other context, x[i...] is equivalent to x[i..<x.endIndex]

I don't think breaking that precedent is a good idea.

For move operations, the one sided one will fully deinitialize the source
buffer while the two sided one will only deinitialize endIndex - offset
elements.


-Dave

well since people want to use subscript notation so much we need some way
of expressing case 1. writing both bounds in the subscript seems to imply a
full initialization (and thus partial movement) guarantee.

Presumably, in your use case, you’re working directly with buffers on both
sides (please point us to the real code). With the slicing approach, that
would be done as follows:

bufA[i ..< i + bufB.count].initialize(from: bufB)

That will enforce full initialization of the slice:
precondition(self.count == source.count).

Your point stands that it is redundant. That point will need to be weighed
against other points, which I think should be discussed in another thread.

-Andy

this function initializeMemory<C:Collection>(as:from:) says it will be removed in Swift 4.0. It is now Swift 4.0. can I remove it?

It looks safe to remove. However, the doc comments in the same file should be updated to refer to `initializeMemory<T>(as:from:count:)`.

+cc Nate Cook

-Andy

···

On Sep 30, 2017, at 10:45 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

On Sat, Sep 30, 2017 at 2:15 AM, Taylor Swift <kelvin13ma@gmail.com <mailto:kelvin13ma@gmail.com>> wrote:

On Thu, Sep 28, 2017 at 7:59 PM, Andrew Trick <atrick@apple.com <mailto:atrick@apple.com>> wrote:

On Sep 6, 2017, at 10:15 PM, Taylor Swift <kelvin13ma@gmail.com <mailto:kelvin13ma@gmail.com>> wrote:

okay so I think so far what’s been agreed on is

1. rename “Bytes” to “Memory” in the raw pointer API. Note: this brings the `copyBytes<C>(from:)` collection method into the scope of this proposal

2. change raw offsets to be in terms of bytes, not typed strides. This argument will be called `atByteOffset:` and will only appear in UnsafeMutableRawBufferPointer. “at:” arguments are no longer needed in UnsafeMutableRawPointer, since we can just use pointer arithmetic now.

3. move UnsafeMutableBufferPointer’s `at:` arguments to the front of the parameter list. mostly cause any pointer arithmetic happens in the front so structurally we want to mirror that.

4. add dual (to:) single element initializers and assigners to UnsafeMutablePointer, but not UnsafeMutableRawPointer because it’s apparently not useful there. `UnsafeMutableRawPointer.initializeMemory<T>(as:repeating:count:)` still loses its default count to prevent confusion with its buffer variant.

5. memory deallocation on buffer pointers is clearly documented to only be defined behavior when the buffer matches a whole heap block.

Kelvin,

Attempting to limit the scope of this proposal backfired. I was hoping to avoid discussing changes to the slice API, instead providing basic functionality within the buffer API itself. However, Dave Abrahams has argued that once the slice API is extended, the positional arguments are extraneous and less clear.

Instead of

  buf.intialize(at: i, from: source)

We want to force a more obvious idiom:

  buf[i..<n].intialize(from: source)

I think this is a reasonable argument and convinced myself that it's possible to extend the slice API. I'm also convinced now that we don't need overloads to handle an UnsafeBufferPointer source, instead we can provide a single generic entry point on both UnsafeMutableBufferPointer and its slice, optimized within the implementation:

`initialize<S : Sequence>(from: S) -> (S.Iterator, Index)

We can always drop down to the UnsafePointer API to get back to a direct unsafe implementation as a temporary workaround for performance issues.

Let's set aside for now whether we support full or partial initialization/assignment, how to handle moveInitialize, and whether we need to return the Iterator/Index. This is going to require another iteration on swift-evolution, which we should discuss in a separate thread.

At this point, I suggest removing the controversial aspects of SE-0184 so that we can put the other changes behind us and focus future discussion around a smaller follow-up proposal.

Here I've summarized the changes that I think could be accepted as-is:
SE-0184-part1.md · GitHub

If you amend SE-0184 accordingly and file a new PR, I think it can be quickly approved.

-Andy

Part one of SE-0184 is here as SE-0184 A <https://github.com/kelvin13/swift-evolution/blob/improved-pointers/proposals/0184a-unsafe-pointers-part-1.md&gt;
I’ll implement it tomorrow and then make the PR

The implementation is complete and building successfully:

···

On Sat, Sep 30, 2017 at 2:56 PM, Taylor Swift via swift-evolution < swift-evolution@swift.org> wrote:

SE-184a (Unsafe[Mutable][Raw][Buffer]Pointer overhaul, part 1) by tayloraswift · Pull Request #750 · apple/swift-evolution · GitHub
SE 0184a implementation by tayloraswift · Pull Request #12200 · apple/swift · GitHub

On Sat, Sep 30, 2017 at 2:15 AM, Taylor Swift <kelvin13ma@gmail.com> > wrote:

On Thu, Sep 28, 2017 at 7:59 PM, Andrew Trick <atrick@apple.com> wrote:

On Sep 6, 2017, at 10:15 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

okay so I think so far what’s been agreed on is

*1*. rename “Bytes” to “Memory” in the raw pointer API. Note: this
brings the `copyBytes<C>(from:)` collection method into the scope of
this proposal

*2*. change raw offsets to be in terms of bytes, not typed strides.
This argument will be called `atByteOffset:` and will only appear in
UnsafeMutableRawBufferPointer. “at:” arguments are no longer needed in
UnsafeMutableRawPointer, since we can just use pointer arithmetic now.

*3*. move UnsafeMutableBufferPointer’s `at:` arguments to the front of
the parameter list. mostly cause any pointer arithmetic happens in the
front so structurally we want to mirror that.

*4*. add dual (to:) single element initializers and assigners to
UnsafeMutablePointer, but not UnsafeMutableRawPointer because it’s
apparently not useful there. `UnsafeMutableRawPointer.initi
alizeMemory<T>(as:repeating:count:)` still loses its default count to
prevent confusion with its buffer variant.

*5*. memory deallocation on buffer pointers is clearly documented to
only be defined behavior when the buffer matches a whole heap block.

Kelvin,

Attempting to limit the scope of this proposal backfired. I was hoping
to avoid discussing changes to the slice API, instead providing basic
functionality within the buffer API itself. However, Dave Abrahams has
argued that once the slice API is extended, the positional arguments are
extraneous and less clear.

Instead of

  buf.intialize(at: i, from: source)

We want to force a more obvious idiom:

  buf[i..<n].intialize(from: source)

I think this is a reasonable argument and convinced myself that it's
possible to extend the slice API. I'm also convinced now that we don't need
overloads to handle an UnsafeBufferPointer source, instead we can provide a
single generic entry point on both UnsafeMutableBufferPointer and its
slice, optimized within the implementation:

`initialize<S : Sequence>(from: S) -> (S.Iterator, Index)

We can always drop down to the UnsafePointer API to get back to a direct
unsafe implementation as a temporary workaround for performance issues.

Let's set aside for now whether we support full or partial
initialization/assignment, how to handle moveInitialize, and whether we
need to return the Iterator/Index. This is going to require another
iteration on swift-evolution, which *we should discuss in a separate
thread*.

At this point, I suggest removing the controversial aspects of SE-0184
so that we can put the other changes behind us and focus future discussion
around a smaller follow-up proposal.

Here I've summarized the changes that I think could be accepted as-is:
SE-0184-part1.md · GitHub

If you amend SE-0184 accordingly and file a new PR, I think it can be
quickly approved.

-Andy

Part one of SE-0184 is here as SE-0184 A
<https://github.com/kelvin13/swift-evolution/blob/improved-pointers/proposals/0184a-unsafe-pointers-part-1.md&gt;
I’ll implement it tomorrow and then make the PR

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

In the new raw initializeMemory methods, Xiaodi and I agreed to make
it more clear that the offset is in terms of `self` rather than
`from`, and to further reduce ambiguity by forcing manual stride
computation and using an explicit "offset" label. The call site will
be just as explicit as dropping down to `baseAddress` but without any
pointer conversion boilerplate.

UMRBP (raw buffer):
+++ func initializeMemory<T>(atByteOffset:as:from:)
+++ func moveInitializeMemory<T>(atByteOffset:as:from:)

Agree, but the label should just be `atByte:`, not `atByteOffset:`. after all, we don’t use `atOffset:` in the strided case; its obvious that it’s an offset

The existing APIs use the terminology "byte offset"--for example, URP.load(fromByteOffset:as:). The rationale is that "at" without a noun that follows implies, per Swift API naming guidelines, "at index." If you want to specify, as we do here, what the index _is_, then it's written out in full.

Yes, it seems overly cumbersome, but I was following existing conventions which were intentionally very explicit.

We don't have a consensus, but I think the suggestion to distinguish
between single value vs. multiple semantics was good. Otherwise,
adding the default count could be very misleading. Normally, we try to
minimize surface area, but adding two methods for the single-value case
avoids ambiguity between the buffer and pointer semantics:

UMP (pointer)
--- func initialize(to:count:(=1))
+++ func initialize(to:)
+++ func initialize(repeating:count:) // no default count
+++ func assign(to:)
+++ func assign(repeating:count:) // no default count

UMRP (raw pointer):
--- func initializeMemory<T>(as:at:(=0)count:(1)to:)
+++ func initializeMemory<T>(as:repeating:count:) // remove default count

still extremely suspicious of this but i’m willing to compromise. also there should be an `initializeMemory<T>(at:to:)` to match the typed methods

Do you mean initializeMemory<T>(as:to:)?

I don’t think it’s necessary since raw buffers are not normally used to hold a single typed element. We don’t need symmetry between raw and typed pointers and I wouldn’t want to add an API that will never be used. So, I would be ok with it only if there’s some use case that I’m overlooking.

-Andy

···

On Tue, Sep 5, 2017 at 11:31 AM, Andrew Trick <atrick@apple.com <mailto:atrick@apple.com>> wrote:
I think we’ve agreed to a few minor updates to this proposal. Since there hasn’t been any other feedback on the thread it may be worth posting an amended proposal so we all know what we’ve agreed on.

-Andy

On Sep 3, 2017, at 8:23 PM, Andrew Trick via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Sep 3, 2017, at 8:05 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

If we use byte offset, then the at parameter in UnsafeMutableRawPointer should be removed, since pointer arithmetic can be used instead (just like with UnsafeMutablePointer).

I agree that it seems quite sensible to remove the ‘at’ parameter altogether from the UMRP method.

No code in tree or on github is using the `at` argument. I think it can be removed. A fixit should still be possible.

Not convinced moving the at: argument to come before the as: argument is worth it in terms of source breakage.

Since much of this proposal involves shuffling and relabeling arguments, I’d argue it’s better to break slight more source in one go for the optimal API than to break slightly less for a slightly less optimal API, no? (This is assuming there is agreement that ‘at:as:’ is less prone to misinterpretation than ‘as:at:’.)

To be clear, we’re just talking about UnsafeMutableRawBufferPointer.initializeMemory now, so this is purely additive.
I think the label needs to be `atByteOffset`, and placing it before `as` makes a lot of sense because it no longer depends on the type’s stride.

-Andy
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution