2

In ObjC, a bool's bit pattern could be retrieved by casting it to a UInt8.

e.g.

  • true => 0x01
  • false => 0x00

This bit pattern could then be used in further bit manipulation operations.


Now I want to do the same in Swift.

What I got working so far is

UInt8(UInt(boolValue))

but this doesn't look like it is the preferred approach.

I also need the conversion in O(1) without data-dependent branching. So, stuff like the following is not allowed.

boolValue ? 1 : 0

Also, is there some documentation about the way the UInt8 and UInt initializers are implemented? e.g. if the UInt initializer to convert from bool uses data-dependent branching, I can't use it either.

Of course, the fallback is always to use further bitwise operations to avoid the bool value altogether (e.g. Check if a number is non zero using bitwise operators in C).


  • Does Swift offer an elegant way to access the bit pattern of a Bool / convert it to UInt8, in O(1) without data-dependent branching?
Community
  • 1
  • 1
Etan
  • 17,014
  • 17
  • 89
  • 148

2 Answers2

5

When in doubt, have a look at the generated assembly code :)

func foo(someBool : Bool) -> UInt8 {
    let x = UInt8(UInt(someBool))
    return x
}

compiled with ("-O" = "Compile with optimizations")

xcrun -sdk macosx swiftc -emit-assembly -O main.swift

gives

    .globl  __TF4main3fooFSbVSs5UInt8
    .align  4, 0x90
__TF4main3fooFSbVSs5UInt8:
    .cfi_startproc
    pushq   %rbp
Ltmp2:
    .cfi_def_cfa_offset 16
Ltmp3:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp4:
    .cfi_def_cfa_register %rbp
    callq   __TFE10FoundationSb19_bridgeToObjectiveCfSbFT_CSo8NSNumber
    movq    %rax, %rdi
    callq   __TFE10FoundationSuCfMSuFCSo8NSNumberSu
    movzbl  %al, %ecx
    cmpq    %rcx, %rax
    jne LBB0_2
    popq    %rbp
    retq

The function names can be demangled with

$ xcrun -sdk macosx swift-demangle __TFE10FoundationSb19_bridgeToObjectiveCfSbFT_CSo8NSNumber __TFE10FoundationSuCfMSuFCSo8NSNumberSu
_TFE10FoundationSb19_bridgeToObjectiveCfSbFT_CSo8NSNumber ---> ext.Foundation.Swift.Bool._bridgeToObjectiveC (Swift.Bool)() -> ObjectiveC.NSNumber
_TFE10FoundationSuCfMSuFCSo8NSNumberSu ---> ext.Foundation.Swift.UInt.init (Swift.UInt.Type)(ObjectiveC.NSNumber) -> Swift.UInt

There is no UInt initializer that takes a Bool argument. So the smart compiler has used the automatic conversion between Swift and Foundation types and generated some code like

let x = UInt8(NSNumber(bool: someBool).unsignedLongValue)

Probably not very efficient with two function calls. (And it does not compile if you only import Swift, without Foundation.)

Now the other method where you assumed data-dependent branching:

func bar(someBool : Bool) -> UInt8 {
    let x = UInt8(someBool ? 1 : 0)
    return x
}

The assembly code is

    .globl  __TF4main3barFSbVSs5UInt8
    .align  4, 0x90
__TF4main3barFSbVSs5UInt8:
    pushq   %rbp
    movq    %rsp, %rbp
    andb    $1, %dil
    movb    %dil, %al
    popq    %rbp
    retq

No branching, just an "AND" operation with 0x01!

Therefore I do not see a reason not to use this "straight-forward" conversion. You can then profile with Instruments to check if it is a bottleneck for your app.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
2

@martin-r’s answer is more fun :-), but this can be done in a playground.

// first check this is true or you’ll be sorry...
sizeof(Bool) == sizeof(UInt8)

let t = unsafeBitCast(true, UInt8.self)   // = 1
let f = unsafeBitCast(false, UInt8.self)  // = 0
Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118
  • 3
    I checked that as well, and the generated assembly code is *completely identical* to `UInt8(someBool ? 1 : 0)`. That's why I limited by answer to "safe" methods :) – Martin R Mar 14 '15 at 11:29
  • 1
    Interesting, I’m conflicted on which syntax I prefer, since the bit cast clearly signals intent of the code so easy to read, but then again is unsafe... – Airspeed Velocity Mar 14 '15 at 12:00
  • It is indeed interesting. I would have bet that unsafeBitCast just copies the bytes. But an unsafeBitCast of an enum with 5 cases to UInt8 actually does an AND with 0x07, so the compiler uses the information which bits are really used. – Martin R Mar 14 '15 at 12:35
  • Kind of prefer this syntax, because the compiler may change the way it processes the first answer. While unsafe, this solution is more explicit. Still, the first answer provides insight in how to actually check how compilation may not work as assumed. Will accept this answer because Airspeed has less reputation. – Etan Mar 14 '15 at 12:53