Tagged Pointers Swift Package

Hey everyone,

I needed tagged pointers recently for a Swift project, so I decided to make my solution into a general Swift Package! Come take a look GitHub - joehinkle11/SwiftTaggedPointer: Tagged pointers in Swift

This particular implementation gives you 20 extra bits!

Here's some sample code of how to use it:

let x = 5
withUnsafePointer(to: x) { p in
    var tp = TaggedPointer(p)
    tp.bitTag0 = true
    tp.bitTag1 = true
    tp.bitTag2 = true
    tp.dataInt17 = 23
    assert(tp.getPointer() == p)
}

And here's a visual of the memory laid out.


               Raw storage:  |-----------------------------64 bits----------------------------|
           Pointer anatomy:  |--16 bits--|------1 bit-----|---- 44 bits ---|------3 bits------|
                             |unused bits|is kernel space?|the pointer guts|alignment artifact|
                             |   all 0s  |       0        |      ???       |      all 0s      |
               Raw storage:  |-----------------------------64 bits----------------------------|
Simplified Pointer anatomy:  |-----17 bits-----|--------------44 bits--------------|--3 bits--|
                             |     all 0s      |    significant bits of pointer    |  all 0s  |
                             |----------------------------------------------------------------|


          Raw storage:  |-----------------------------64 bits----------------------------|
TaggedPointer anatomy:  |-------17 bits-------|------44 bits-------|--------3 bits-------|
                        | custom 17 bit data  |    pointer data    |  custom 3 bit tag   |
                        |                     |    `getPointer`    |                     |
                        |                     |    `setPointer`    |                     |
                        |----------------------------------------------------------------|

                        |-----------------------------64 bits----------------------------|
                        |--------61 bits-------|-----------------3 bits------------------|
                        |                      |               `tagUInt3`                |
                        |                      |----1 bit----|----1 bit----|----1 bit----|
                        |                      |   `bitTag2` |  `bitTag1`  |  `bitTag0`  |
                        |----------------------------------------------------------------|

                        |-----------------------------64 bits----------------------------|
                        |------------17 bits----------|--------------47 bits-------------|
                        |          `dataInt17`        |                                  |
                        |-----16 bits-----|---1 bit---|                                  |
                        |   magnitude     | `signBit` |                                  |
                        |----------------------------------------------------------------|

                        |-----------------------------64 bits----------------------------|
                        |-----16 bits-----|---------------------48 bits------------------|
                        |   `dataUInt16`  |                                              |
                        |   `dataInt16`   |                                              |
                        |----------------------------------------------------------------|
3 Likes

Just FYI: The compiler does this automatically to an extent.

For instance, if you have an enum and its payload contains a reference to a Swift object, it will use the spare bits of the pointer to store the enum's discriminator:

enum Test {
    case a(Bool, Bool, UInt8)
    case b(SomeClass)
}

class SomeClass {}

print(MemoryLayout<Test>.size) // 8

Unfortunately it doesn't do that for arbitrary pointers (I guess it can't assume which bits are unused -- you might already have some kind of tagged pointer):

enum Test {
    case a(Bool, Bool, UInt8)
    case b(UnsafePointer<Int>)
}

print(MemoryLayout<Test>.size) // 9

Also, it doesn't do it for structs:

struct Test {
    var a: SomeClass
    var b: Bool
}

class SomeClass {}

print(MemoryLayout<Test>.size) // 9

I think we do want to add layout optimisations for structs at some point, which is why Swift has avoided making guarantees about struct layout so far. That may get closer to covering the use-case shown in your example.

4 Likes

Very cool! However it looks like Swift in that enum example couldn’t fit it into 8 bytes. I needed tagged pointers which were guaranteed to not be bigger than a word