Permalink
Fetching contributors…
Cannot retrieve contributors at this time
170 lines (130 sloc) 7.26 KB

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.

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.

Refactoring this proposal to use a single namespace 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`, `stride`, 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<T>.size`, when 
    /// `T` is a class type, is the same regardless of how many 
    /// stored properties `T` has.
     public static var size: Int { return _sizeof(T) }
    
    /// For instances of `T` in an `Array<T>`, returns the number of
    /// bytes from the start of one instance to the start of the
    /// next. This is the same as the number of bytes moved when an
    /// `UnsafePointer<T>` is incremented. `T` may have a lower minimal
    /// alignment that trades runtime performance for space
    /// efficiency. The result is always positive.
    public static var stride: Int { return _strideof(T) }
    
    /// Returns the default memory alignment of `T`.
    public static var alignment: Int { return _alignof(T) }
}

With this design, consumers call:

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

Values

This proposal removes sizeofValue(), strideofValue(), and alignofValue() from the standard library. This proposal adopts 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 might 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").stride // 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

The 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 
public func memoryAlignment<T>(ofValue _: @autoclosure T -> Void) -> Int

These functions offered human factor advantages over the current proposal but didn't address Dave's 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, Russ Bishop, and everyone else who contributed to this proposal