Why is this enum’s MemoryLayout so bad?

i have an enum that looks like:

@frozen public 
enum Node<Anchor> 
{
    @frozen public 
    enum Value
    {
        case anchor (Anchor)
        case inline ((UInt, UInt, UInt32, UInt16, UInt8))
        case raw    ([UInt8])
    }

    case value      (Value)
    case leaf       (UInt32, attributes:[Value])
    case container  (UInt32, attributes:[Value], content:[Self]) 
}

this really ought to fit in 3 words as long as Anchor doesn’t exceed 23 bytes. but swift lays it out so that it takes four words of storage:

print(MemoryLayout<Node<Int>>.stride)
32

if i specialize it manually, swift can pack it in 3 words of storage:

@frozen public 
enum Node2
{
    @frozen public 
    enum Value
    {
        case anchor (Int)
        case inline ((UInt, UInt, UInt32, UInt16, UInt8))
        case raw    ([UInt8])
    }

    case value      (Value)
    case leaf       (UInt32, attributes:[Value])
    case container  (UInt32, attributes:[Value], content:[Self]) 
}
print(MemoryLayout<Node2>.stride)
24

why is the generic layout so bad?

Swift's generic layout model doesn't allow for overlapping storage involving types that have dynamic layout, so your generic version of Node<Anchor>.Value will always use an extra byte for the enum tag, because it has a case of the generic parameter type Anchor, as will the outer Node<Anchor> since it uses Value as a case, instead of being able to salvage spare bits from padding. You could make case anchor be indirect so that it's always represented as a refcounted pointer inline.

11 Likes

Out of curiosity, I have tried all three cases. Didn't see any difference; they all yield 32 (on Xcode 13.4 (13F17a)).

Summary
//
//  EnumMemoryLayout.swift
//  SwiftForums
//
//  Created by ibex on 27/8/2022.
//

// [https://forums.swift.org/t/why-is-this-enum-s-memorylayout-so-bad/59891]

import Foundation

@main
struct EnumMemoryLayout {
    static func main () async {
        Original.show ()
        Specialized.show ()
        OriginalIndirect.show ()
    }
}

enum Original {
    static func show () {
        print ("\(type(of: self))", MemoryLayout <Node <Int>>.stride)
    }

    enum Node <Anchor> {
        @frozen public
        enum Value {
            case anchor (Anchor)
            case inline ((UInt, UInt, UInt32, UInt16, UInt8))
            case raw    ([UInt8])
        }

        case value      (Value)
        case leaf       (UInt32, attributes:[Value])
        case container  (UInt32, attributes:[Value], content:[Self])
    }
}

enum Specialized {
    static func show () {
        print ("\(type(of: self))", MemoryLayout <Node>.stride)
    }
    
    enum Node {
        @frozen public
        enum Value {
            case anchor (Int)
            case inline ((UInt, UInt, UInt32, UInt16, UInt8))
            case raw    ([UInt8])
        }

        case value      (Value)
        case leaf       (UInt32, attributes:[Value])
        case container  (UInt32, attributes:[Value], content:[Self])
    }
}

enum OriginalIndirect {
    static func show () {
        print ("\(type(of: self))", MemoryLayout <Node <Int>>.stride)
    }
    enum Node <Anchor> {
        @frozen public
        enum Value {
            indirect case anchor (Anchor)
            case inline ((UInt, UInt, UInt32, UInt16, UInt8))
            case raw    ([UInt8])
        }

        case value      (Value)
        case leaf       (UInt32, attributes:[Value])
        case container  (UInt32, attributes:[Value], content:[Self])
    }
}