Optional only set in conditional compilation block, does compiler remove statement entirely from release build?

Viewed 143

For example:

struct L {
    #if DEBUG
    static let og:((String) -> Void)? = { print($0) }
    #else
    static let og:((String) -> Void)? = nil
    #endif
}
L.og?("Howdy!")
print("Done.")

In a release build of this code, what does the Swift compiler do with the line L.og?("Howdy!")? Does it optimize away the line completely? Or is the expression L.og evaluated to nil at run time? How do I prove either answer?

2 Answers

I think I have finally answered my own question. This edit entirely replaces my previous answer because I think it was wrong.

Short answer

Compiler optimization does not remove the L.og?("Howdy!") line in Release builds. An unwrap of og? still happens at run time.

Proof

Define "Release"

In a typical Xcode project, what is the optimization for Release? In the target build settings it is SWIFT_OPTIMIZATION_LEVEL = -O.

Method

I ran swiftc -emit-assembly -O on 2 different programs.

Program A:

struct L {
    #if DEBUG
    static let og:((String) -> Void)? = { print($0) }
    #else
    static let og:((String) -> Void)? = nil
    #endif
}
L.og?("Howdy!")

Program B:

struct L {
    #if DEBUG
    static let og:((String) -> Void)? = { print($0) }
    #else
    static let og:((String) -> Void)? = nil
    #endif
}

In the assembly output, the main from A

_main:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    pushq   %r13
    pushq   %rax
    .cfi_offset %r13, -24
    cmpq    $-1, _globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0(%rip)
    jne LBB0_4
LBB0_1:
    movq    _$s4main1LV2ogySScSgvpZ(%rip), %rax
    testq   %rax, %rax
    je  LBB0_3
    movq    _$s4main1LV2ogySScSgvpZ+8(%rip), %r13
    movabsq $36805260308296, %rdi
    movabsq $-1873497444986126336, %rsi
    callq   *%rax
LBB0_3:
    xorl    %eax, %eax
    addq    $8, %rsp
    popq    %r13
    popq    %rbp
    retq
LBB0_4:
    leaq    _globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0(%rip), %rdi
    leaq    _globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0(%rip), %rsi
    callq   _swift_once
    jmp LBB0_1
    .cfi_endproc

    .p2align    4, 0x90

And B:

_main:
    pushq   %rbp
    movq    %rsp, %rbp
    xorl    %eax, %eax
    popq    %rbp
    retq

    .p2align    4, 0x90

If the compiler optimized away the entire line L.og?("Howdy!"), then I expect the main from B would be the same as A. It is not. Therefore, the complier leaves it in.

Non-optional result

A similar result is found for non-optional empty closure instead. The call to L.og("Howdy!") remains in the optimized assembly even though the call is to an empty function.

That is:

struct L {
    #if DEBUG
    static let og:((String) -> Void) = { print($0) }
    #else
    static let og:((String) -> Void) = { _ in }
    #endif
}
L.og("Howdy!")

Here is optimized main assembly:

_main:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    pushq   %r13
    pushq   %rax
    .cfi_offset %r13, -24
    cmpq    $-1, _globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0(%rip)
    jne LBB0_1
LBB0_2:
    movq    _$s4main1LV2ogyySScvpZ+8(%rip), %r13
    movabsq $36805260308296, %rdi
    movabsq $-1873497444986126336, %rsi
    callq   *_$s4main1LV2ogyySScvpZ(%rip)
    xorl    %eax, %eax
    addq    $8, %rsp
    popq    %r13
    popq    %rbp
    retq
LBB0_1:
    leaq    _globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0(%rip), %rdi
    leaq    _globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0(%rip), %rsi
    callq   _swift_once
    jmp LBB0_2
    .cfi_endproc

    .p2align    4, 0x90

With conditional compilation, the compiler evaluates the state of the flag at build time.

Code inside an #if block that evaluates as true is compiled, and code that is inside an #if block that evaluates as false is ignored. (It is stripped away and will not appear in the binary.) In your case, for a release build, it would be as if your code was:

struct L {
    static let og:((String) -> Void)? = nil
}
L.og?("Howdy!")
print("Done.")

Thus, the "optional chaining" in the L.og?("Howdy!") would evaluate L.og as nil, and that code would never do anything.

I don't know the answer to the final part of your question, about whether or not the compiler completely optimizes away the line since it can never do anything. The LLVM compiler that builds Swift code is quite smart, and in release mode it might well strip the code away completely, but I'm not sure. You'd have to look at the assembly output to be sure.

Related