Is MemoryLayout<T>.size/stride/alignment compile time?

341 views Asked by At

For reference, in C/C++, the equivalent (sizeof operator) is compile time, and can be used with template programming (Generics).

I was looking through Swift Algorithms Club for implementations of common data structures and came across their implementation of a Bit Set:

public struct BitSet {
  private(set) public var size: Int

  private let N = 64
  public typealias Word = UInt64
  fileprivate(set) public var words: [Word]

  public init(size: Int) {
    precondition(size > 0)
    self.size = size

    // Round up the count to the next multiple of 64.
    let n = (size + (N-1)) / N
    words = [Word](repeating: 0, count: n)
  }

  <clipped>

In a language where sizeof exists as a compile time constant operator, I would have set N to sizeof(Word) * 8 or rather MemoryLayout<UInt64>.size * 8 rather than the "magic number" 64. I admit, its not very magic here, but the point stands if only just for making it semantically clear whats going on.

Further, I noted the family of functions, for which the same questions applies (ref).

static func size(ofValue value: T) -> Int
static func stride(ofValue value: T) -> Int
static func alignment(ofValue value: T) -> Int

Edit: Adding some disassembly from generic/not generic version of functions.

Non-generic swift:

func getSizeOfInt() -> Int {
  return MemoryLayout<UInt64>.size
}

Produces this disassembly:

(lldb) disassemble --frame
MemoryLayout`getSizeOfInt() -> Int:
    0x1000013c0 <+0>:  pushq  %rbp
    0x1000013c1 <+1>:  movq   %rsp, %rbp
    0x1000013c4 <+4>:  movl   $0x8, %eax
->  0x1000013c9 <+9>:  popq   %rbp
    0x1000013ca <+10>: retq   

Based on the 0x8 constant, this looks like a compile time constant based on @Charles Srstka's answer.

How about the generic swift implementation?

func getSizeOf<T>(_ t:T) -> Int {
  return MemoryLayout<T>.size
}

Produced this disassembly:

(lldb) disassemble --frame
MemoryLayout`getSizeOf<A> (A) -> Int:
    0x100001390 <+0>:  pushq  %rbp
    0x100001391 <+1>:  movq   %rsp, %rbp
    0x100001394 <+4>:  subq   $0x20, %rsp
    0x100001398 <+8>:  movq   %rsi, -0x8(%rbp)
    0x10000139c <+12>: movq   %rdi, -0x10(%rbp)
->  0x1000013a0 <+16>: movq   -0x8(%rsi), %rax
    0x1000013a4 <+20>: movq   0x88(%rax), %rcx
    0x1000013ab <+27>: movq   %rcx, -0x18(%rbp)
    0x1000013af <+31>: callq  *0x20(%rax)
    0x1000013b2 <+34>: movq   -0x18(%rbp), %rax
    0x1000013b6 <+38>: addq   $0x20, %rsp
    0x1000013ba <+42>: popq   %rbp
    0x1000013bb <+43>: retq   
    0x1000013bc <+44>: nopl   (%rax)

The above doesn't look compile time... ? I am not familiar yet with assembler on mac/lldb.

1

There are 1 answers

2
Charles Srstka On BEST ANSWER

This code:

func getSizeOfInt64() -> Int {
    return MemoryLayout<Int64>.size
}

generates this assembly:

MyApp`getSizeOfInt64():
    0x1000015a0 <+0>:  pushq  %rbp
    0x1000015a1 <+1>:  movq   %rsp, %rbp
    0x1000015a4 <+4>:  movl   $0x8, %eax
    0x1000015a9 <+9>:  popq   %rbp
    0x1000015aa <+10>: retq  

From this, it appears that MemoryLayout<Int64>.size is indeed compile-time.

Checking the assembly for stride and alignment is left to the reader, but they give similar results (actually identical, in the case of Int64).

EDIT:

If we're talking about generic functions, obviously more work has to be done since the function will not know the type of which it's getting the size at compile time, and thus can't just put in a constant. But, if you define your function to take a type instead of an instance of the type, it does a little less work than in your example:

func getSizeOf<T>(_: T.Type) -> Int {
    return MemoryLayout<T>.size
}

called like: getSizeOf(UInt64.self)

generates this assembly:

MyApp`getSizeOf<A>(_:):
    0x100001590 <+0>:  pushq  %rbp
    0x100001591 <+1>:  movq   %rsp, %rbp
    0x100001594 <+4>:  movq   %rsi, -0x8(%rbp)
    0x100001598 <+8>:  movq   %rdi, -0x10(%rbp)
->  0x10000159c <+12>: movq   -0x8(%rsi), %rsi
    0x1000015a0 <+16>: movq   0x88(%rsi), %rax
    0x1000015a7 <+23>: popq   %rbp
    0x1000015a8 <+24>: retq