unsafe memory model questions


(Ray Fix) #1

Hello,

So bindMemory is part of the memory model and memory can only bind to one type at a time to avoid aliasing. But what does binding actually do? Is it somehow communicating with the optimizer?

A tangentially related second question, in the following example:

  let count = 3
  let mutablePointer = UnsafeMutablePointer<Int16>.allocate(capacity: count)
  defer {
    mutablePointer.deallocate(capacity: count)
  }

  mutablePointer.initialize(to: 1234, count: count)
  defer {
    mutablePointer.deinitialize(count: count) // must I do this?
  }

Is it bad form if I don’t deinitialize and go straight to deallocate? I can see where it is important for ref types (to update ref counts, etc). Is it one of those things that the optimizer can remove for the case of value types?

Finally, if I initalize with some other means, such as with a raw buffer pointer subscript, is there any need to deinitialize? Can such memory be considered initialized if I bind it with a type?

  // 1
  let pointer = malloc(byteCount)
  defer {
    free(pointer)
  }

  let mutableRawBufferPointer = UnsafeMutableRawBufferPointer(start: pointer, count: byteCount)
  
  for index in mutableRawBufferPointer.indices {
    mutableRawBufferPointer[index] = 42 + UInt8(index)
  }

Perhaps there is a document or proposal somewhere that talks about these things. Sorry if I missed it.

Thanks as always,
Ray


(Jordan Rose) #2

Hi, Ray. Most of the pointer model is covered in the original proposal for the UnsafeRawPointer type, SE-0107 <https://github.com/apple/swift-evolution/blob/master/proposals/0107-unsaferawpointer.md>. That's a good place to find answers to pointer- and memory-related questions in general.

The short forms are "yes, binding memory to types is mostly about communication with the optimizer, but it may also be checkable at run-time some day", and "it's okay to skip 'deinitialize' and use assignment instead of 'initialize' if and only if the element type is trivial <https://github.com/apple/swift-evolution/blob/master/proposals/0107-unsaferawpointer.md#trivial-types>".

Additionally, as far as I know, mutation through UnsafeMutableRawBufferPointer doesn't count as binding any type to the memory, but Andy will have to check me on that.

Best,
Jordan

···

On Dec 11, 2016, at 14:51, Ray Fix via swift-users <swift-users@swift.org> wrote:

Hello,

So bindMemory is part of the memory model and memory can only bind to one type at a time to avoid aliasing. But what does binding actually do? Is it somehow communicating with the optimizer?

A tangentially related second question, in the following example:

  let count = 3
  let mutablePointer = UnsafeMutablePointer<Int16>.allocate(capacity: count)
  defer {
    mutablePointer.deallocate(capacity: count)
  }

  mutablePointer.initialize(to: 1234, count: count)
  defer {
    mutablePointer.deinitialize(count: count) // must I do this?
  }

Is it bad form if I don’t deinitialize and go straight to deallocate? I can see where it is important for ref types (to update ref counts, etc). Is it one of those things that the optimizer can remove for the case of value types?

Finally, if I initalize with some other means, such as with a raw buffer pointer subscript, is there any need to deinitialize? Can such memory be considered initialized if I bind it with a type?

  // 1
  let pointer = malloc(byteCount)
  defer {
    free(pointer)
  }

  let mutableRawBufferPointer = UnsafeMutableRawBufferPointer(start: pointer, count: byteCount)
  
  for index in mutableRawBufferPointer.indices {
    mutableRawBufferPointer[index] = 42 + UInt8(index)
  }

Perhaps there is a document or proposal somewhere that talks about these things. Sorry if I missed it.

Thanks as always,
Ray

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


(Andrew Trick) #3

Hello,

So bindMemory is part of the memory model and memory can only bind to one type at a time to avoid aliasing. But what does binding actually do? Is it somehow communicating with the optimizer?

Binding memory to a type tells the compiler what type the memory can hold. Normally that happens implicitly when you allocate memory for a particular type, so you don’t need to think about it (your memory below is implicitly bound to Int16). But if you’re playing around with raw memory/type punning and get the bound type wrong, future versions of the optimizer could “break” your code in very confusing ways.

A tangentially related second question, in the following example:

  let count = 3
  let mutablePointer = UnsafeMutablePointer<Int16>.allocate(capacity: count)
  defer {
    mutablePointer.deallocate(capacity: count)
  }

  mutablePointer.initialize(to: 1234, count: count)
  defer {
    mutablePointer.deinitialize(count: count) // must I do this?
  }

Is it bad form if I don’t deinitialize and go straight to deallocate? I can see where it is important for ref types (to update ref counts, etc). Is it one of those things that the optimizer can remove for the case of value types?

You don’t need to deinitialize trivial types—it’s semantically legal to omit the deinitialize. But it doesn’t hurt—the optimizer will remove the call. I think think it’s good form to deinitialize in examples where others are using your code as reference for best practice, or if the type isn’t obviously trivial; e.g. someone may add a reference to your previously trivial struct.

Finally, if I initalize with some other means, such as with a raw buffer pointer subscript, is there any need to deinitialize? Can such memory be considered initialized if I bind it with a type?

  // 1
  let pointer = malloc(byteCount)
  defer {
    free(pointer)
  }

  let mutableRawBufferPointer = UnsafeMutableRawBufferPointer(start: pointer, count: byteCount)

The above is essentially equivalent to:

let mutableRawBufferPointer = UnsafeMutableRawBufferPointer.allocate(bytes: byteCount, alignedTo: 16)
// alignment depends on the platform
defer {
  mutableRawBufferPointer.deallocate(bytes: byteCount, alignedTo: 16)
}
  

  for index in mutableRawBufferPointer.indices {
    mutableRawBufferPointer[index] = 42 + UInt8(index)
  }

This is an interesting case because you’ve initialized memory to “raw bytes”. The memory isn’t bound to any type. The documentation talks about initialized memory always being bound to a type, but the underlying assumption is that you’ve used a typed operation to initialize the memory. Assigning bytes via a raw pointer subscript or storeBytes isn’t a typed operation.

It wouldn’t even make sense to deinitialize this memory because it doesn’t hold any typed values!

You could bulk-reinterpret that memory as some type (without loading the values), simply by binding the memory. It is only valid to reinterpret those raw bytes as a trivial type though, so you still don’t need to deinitialize.

The UnsafeRawPointer API specifies that copying raw bytes to/from nontrivially typed memory or is illegal. The only way to get around that would be calling down to memcpy/memmove. Similarly, reinterpreting (rebinding) trivial to nontrivial types is illegal.

Perhaps there is a document or proposal somewhere that talks about these things. Sorry if I missed it.

https://github.com/apple/swift-evolution/blob/master/proposals/0107-unsaferawpointer.md
https://swift.org/migration-guide/se-0107-migrate.html

The rules are codified in API doc comments, but the language is not very user-friendly. Those API docs are being worked on, so in the future you typically won’t need to refer back to the evolution proposal just to be able to understand the comments.

-Andy

···

On Dec 11, 2016, at 2:51 PM, Ray Fix via swift-users <swift-users@swift.org> wrote:

Thanks as always,
Ray

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