Skip to content

Instantly share code, notes, and snippets.

@erica erica/memorylayout.md
Last active Jun 30, 2016

Embed
What would you like to do?

Reconfiguring sizeof and related functions into a unified MemoryLayout struct

Introduction

This proposal addresses sizeof, sizeofValue, strideof, strideofValue, align, and alignOf. It discards the value-style standalone functions and combines the remaining items into a unified structure.

Review 1:

Prior Discussions:

Motivation

Although sizeof(), etc are treated as terms of art, these names are appropriated from C. The functions do not correspond to anything named sizeof in LLVM. Swift's six freestanding memory functions increase the API surface area while providing lightly-used and unsafe functionality. Dave Abrahams writes, "These APIs are not like map, filter, and Dictionary. They're specialty items that you should only reach for when performing unsafe operations, mostly inside the guts of higher-level constructs."

Although I believe my original design in the first version of this proposal offered sound usability engineering, under these circumstances, I have refactored this proposal to adopt Dave's single-namespace approach. This approach increases discoverability, provides a single entry point for related operations, and enables future expansions without introducing further freestanding functions.

Detailed Design

This proposal introduces a new struct, MemoryLayout

/// Accesses the memory layout of `T` through its
/// `size`, `spacing`, and `alignment` properties
public struct MemoryLayout<T> {
    /// Returns the contiguous memory footprint of `T`.
    ///
    /// Does not include any dynamically-allocated or "remote" storage.
    /// In particular, `MemoryLayout<X>.size`, when `X` is a class type, is the
    /// same regardless of how many stored properties `X` has.
    public static var size: Int // currently sizeof()
    
    /// Returns the spacing between instances of `T` in `Array<T>`, 
    /// or the number of bytes moved by an `UnsafePointer<T>` when incremented. 
    /// `T` may have a lower minimal alignment that trades runtime performance 
    /// for space efficiency. The result is always positive.
    public static var spacing: Int // currently strideof()
    
    /// Returns the minimum memory alignment of `T`.
    public static var alignment: Int // currently alignof()
    
    /// Initialize with a value
    public init(_ : @autoclosure () -> T)
}

As stride already has a well-established meaning in the standard library, this proposal changes its name to spacing, matching existing documentation and align is renamed to alignment.

public struct MemoryLayout<T> {
    public static var size: Int { return _sizeof(T) }
    public static var spacing: Int { return _strideof(T) }
    public static var alignment: Int { return _alignof(T) }
}

With this design, consumers call:

// Types
MemoryLayout<Int>.size // 8
MemoryLayout<Int>.spacing // 8
MemoryLayout<Int>.alignment // 8

Values

This proposal removes sizeofValue(), strideofValue(), and alignofValue() from the standard library. This proposal takes the stance that sizes relate to types, not values.

Russ Bishop writes in the initial review thread, "Asking about the size of an instance implies things that aren’t true. Sticking value labels on everything doesn’t change the fact that sizeOf(swift_array) is not going to give you the size of the underlying buffer no matter how you slice it."

As the following chart shows, type-based calls consistently outnumber instance-based calls in gist, github, and stdlib searches. The Google search for sizeof is probably too general based on its use in other languages.

Term stdlib search gist search Google site:github.com swift
sizeof 157 169 (18,600, term is probably too general)
sizeofValue 4 34 584
alignof 44 11 334
alignofValue 5 5 154
strideof 24 19 347
strideofValue 1 5 163

If for some reason, the core team decides that there's a compelling reason to include value calls, an implementation would look something like this:

extension MemoryLayout<T> {
    init(_ : @autoclosure () -> T) {}
    public static func of(_ candidate : @autoclosure () -> T) -> MemoryLayout<T>.Type {
        return MemoryLayout.init(candidate).dynamicType
    }
}

// Value
let x: UInt8 = 5
MemoryLayout.of(x).size // 1
MemoryLayout.of(1).size // 8
MemoryLayout.of("hello").spacing // 24
MemoryLayout.of(29.2).alignment // 8

Known Limitations and Bugs

According to Joe Groff, concerns about existential values (it's illegal to ask for the size of an existential value's dynamic type) could be addressed by

"having sizeof and friends formally take an Any.Type instead of T.Type. (This might need some tweaking of the underlying builtins to be able to open existential metatypes, but it seems implementable.)"

This proposal uses <T> / T.Type to reflect Swift's current implementation.

Note: There is a known bug (cite D. Gregor) that does not enforce .self when used with sizeof, allowing sizeof(UInt). This call should be sizeof(UInt.self). This proposal is written as if the bug were resolved without relying on adoption of SE-0090.

Impact on Existing Code

This proposal requires migration support to replace function calls with struct-based namespacing. This should be a simple substitution with limited impact on existing code that is easily addressed with a fixit.

Alternatives Considered

My original proposal introduced three renamed standalone functions:

public func memorySize<T>(ofValue _: @autoclosure T -> Void) -> Int
public func memoryInterval<T>(ofValue _: @autoclosure T -> Void) -> Int // or memorySpacing, etc
public func memoryAlignment<T>(ofValue _: @autoclosure T -> Void) -> Int

These functions offered human factor advantages over the current proposal but didn't address Dave Abrahams concerns about namespacing and overall safety. This alternative has been discarded and can be referenced by reading the original proposal.

Acknowledgements

Thank you, Xiaodi Wu, Matthew Johnson, Pyry Jahkola, Tony Allevato, Joe Groff, Dave Abrahams, and everyone else who contributed to this proposal

@dabrahams

This comment has been minimized.

Copy link

dabrahams commented Jun 25, 2016

I don’t think there’s ay way to support any of these syntaxes:

MemoryLayout<Int.self>.size
MemoryLayout<x.dynamicType>.interval
MemoryLayout<x.Self>.alignment

Suggestion: implement the proposal using facilities already exposed by the library, and try using it.

Also, I still think something like arraySpacing would be much clearer than interval.

@erica

This comment has been minimized.

Copy link
Owner Author

erica commented Jun 28, 2016

public struct MemoryLayout<T> {
    public static var size: Int { return sizeof(T) }
    public static var interval: Int { return strideof(T) }
    public static var alignment: Int { return alignof(T) }

    init(_ : @autoclosure () -> T) {}
}

print(MemoryLayout<Int>.size)
print(MemoryLayout<Int>.interval)
print(MemoryLayout<Int>.alignment)

let x: UInt8 = 5
MemoryLayout(x).dynamicType.size
MemoryLayout(x).dynamicType.interval
MemoryLayout(x).dynamicType.alignment
@erica

This comment has been minimized.

Copy link
Owner Author

erica commented Jun 28, 2016

If it returns the "least possible interval between distinct instances of T in memory." then interval sounds better to me than arraySpacing

@dabrahams

This comment has been minimized.

Copy link

dabrahams commented Jun 29, 2016

It doesn’t return that; it returns the spacing between instances of T in an Array<T>, or the number of bytes moved by an UnsafePointer<T> when incremented. T may have a lower minimal alignment that trades runtime performance for space efficiency.

@xwu

This comment has been minimized.

Copy link

xwu commented Jun 30, 2016

Not sure I'm a fan of the "array" in arraySpacing--it's useful for more than just arrays. Plus, we're already qualifying with MemoryLayout--i.e., it's already the "memory layout spacing."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.