Skip to content

Optimize [U]Int.init(bitPattern:) for optional pointer conversions #81902

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

oscbyspro
Copy link
Contributor

The [U]Int.init(bitPattern:) initializers for optional pointer types currently result in suboptimal machine code. These conversions are semantically simple—reinterpreting the bit pattern of an optional pointer as an integer—but the current implementation introduces conditional branching, which penalizes idiomatic use in performance-sensitive code. Swift’s current runtime representation uses a zero bit pattern to represent Optional<Unsafe*Pointer>.none. If this representation is stable and guaranteed, we can use unsafeBitCast(_:to:) to eliminate branching entirely.

Benchmark: Compiler Explorer Outputs

This section shows some assembly for the current (foo) and the proposed (bar) approach:

func foo(pointer: Optional<UnsafeRawPointer>, plus distance: Int) -> Optional<UnsafeRawPointer> {
    let x  = Int(bitPattern: pointer)
    return UnsafeRawPointer(bitPattern: x &+ distance)
}

func bar(pointer: Optional<UnsafeRawPointer>, plus distance: Int) -> Optional<UnsafeRawPointer> {
    let x = Swift.unsafeBitCast(pointer, to: Int.self)
    return UnsafeRawPointer(bitPattern: x &+ distance)
}

aarch64 swiftc 6.1:

output.foo(pointer: Swift.UnsafeRawPointer?, plus: Swift.Int) -> Swift.UnsafeRawPointer?:
        mov     x8, x0
        mov     x0, x1
        cbz     x8, .LBB1_2
        adds    x0, x0, x8
.LBB1_2:
        ret

output.bar(pointer: Swift.UnsafeRawPointer?, plus: Swift.Int) -> Swift.UnsafeRawPointer?:
        add     x0, x1, x0
        ret

x86-64 swiftc 6.1:

output.foo(pointer: Swift.UnsafeRawPointer?, plus: Swift.Int) -> Swift.UnsafeRawPointer?:
        mov     rax, rsi
        test    rdi, rdi
        je      .LBB1_1
        add     rax, rdi
        je      .LBB1_3
.LBB1_4:
        ret
.LBB1_1:
        test    rax, rax
        jne     .LBB1_4
.LBB1_3:
        xor     eax, eax
        ret

output.bar(pointer: Swift.UnsafeRawPointer?, plus: Swift.Int) -> Swift.UnsafeRawPointer?:
        lea     rax, [rdi + rsi]
        ret

Risk & Open Questions

The key question is whether the zero bit pattern representation of Optional<Unsafe*Pointer>.none is a formal guarantee of the Swift ABI or an implementation detail subject to change. If this layout is not formally guaranteed by the Swift ABI, then this optimization might introduce undefined behavior in future Swift versions.

…n:)`.

The branchy set `0` if `nil` approach doesn't translate well to machine code.
@oscbyspro oscbyspro requested a review from a team as a code owner June 2, 2025 14:27
@stephentyrone
Copy link
Contributor

stephentyrone commented Jun 2, 2025

Can you expand on what the "idiomatic use in performance-sensitive code" for this operation is with some examples? What does that use case expect to happen when the optional pointer is nil? Why is hoisting the nil check and working with a non-optional pointer not the idiomatic solution?

Separately, I'm slightly surprised that LLVM doesn't fuse these branches anyway, any ideas what information isn't getting passed forward from SIL, @meg-gupta?

@oscbyspro
Copy link
Contributor Author

oscbyspro commented Jun 2, 2025

[U]Int.init(bitPattern:) is supposed to return 0 when the pointer is nil. Just like it does today. The code-gen example includes an addition operation because that better represents the operation that prompted the proposal.

The short version of the story is that: I updated one of my text processing algorithms to accommodate nil pointers (because it makes no difference whether the pointer is nil or not, as long as the count is 0 when it is nil). In doing so, the performance of the algorithm dropped off a cliff. Overloading [U]Int.init(bitPattern:) with the proposed implementation fixes the performance issue. In my opinion, this is best handled by the standard library.

@oscbyspro
Copy link
Contributor Author

Why is hoisting the nil check and working with a non-optional pointer not the idiomatic solution?

I meant that Swift has two ways of solving my problem in an intentionally performant way: [U]Int.init(bitPattern:) and unsafeBitCast(_:to:). The former exists, to my understanding, as a convenience for the latter because it is a special case known to be safe at compile time. Given a choice between the two, [U]Int.init(bitPattern:) is more idiomatic.

@oscbyspro
Copy link
Contributor Author

oscbyspro commented Jun 4, 2025

Can you expand on what the "idiomatic use in performance-sensitive code" for this operation is with some examples?

Imagine an UnsafeRawBufferPointer or any other buffer such that baseAddress == nil implies count == 0. Let's say you want to slice the buffer from an 0 <= offset <= count. The bounds check is solely responsible for validation. A performance-sensitive algorithm wouldn't dance around the nil-ness of the baseAddress because checking the nil-ness is neither sufficient nor required to prove the safety of the operation. The operation should proceed unhindered when the baseAddress is nil. When the baseAddress is nil the bit pattern could be whatever because slicing the buffer from offset == 0 doesn't advance the baseAddress. This example only requires that Int.init(bitPattern:) and UnsafeRawPointer.init(bitPattern:) are round-trippable.

@oscbyspro
Copy link
Contributor Author

I may note that you can apply this optimization to each of the following *Span methods:

  • *Span/_extracting(last:)
  • *Span/_extracting(droppingFirst:)
  • *Span/_extracting(unchecked:)

I'll happily submit the corresponding changes, assuming bit-casting nil pointers is OK.

CC: @glessard

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants