|
1 |
| -WORK IN PROGRESS |
2 |
| -=== |
3 | 1 | Swift Weekly - Issue 10 - The Swift Runtime (Part 8) - Switch Statement
|
4 | 2 | ===
|
5 | 3 | Vandad Nahavandipoor
|
@@ -318,20 +316,144 @@ __note__: throughout the asm code for our example method, `[ds:r12]` will remain
|
318 | 316 | ```
|
319 | 317 |
|
320 | 318 | that is basically... what? oh wait! that is the `println()` function. at the end of all the cases, we are doing a `println()` call and Swift understood that __all__ our cases are just calling the `println()` function so at the end of every case, it has an unconditional jump to `cs:0000000100001dc7` where the call to `println()` happens. this is a hell of an optimization. instead of putting the `println()` for every case, the compiler only placed it in the code once and then jumps to it unconditionally for every case. really cool!
|
| 319 | + |
| 320 | +Range Comparison |
| 321 | +=== |
| 322 | +Let's say that you have an `Int` value and you want to find out which range it lies on: |
| 323 | + |
| 324 | +```swift |
| 325 | +func randomInt() -> Int{ |
| 326 | + return Int(arc4random_uniform(UInt32.max)) |
| 327 | +} |
| 328 | + |
| 329 | +func example2(){ |
| 330 | + |
| 331 | + switch randomInt(){ |
| 332 | + case 0...100: |
| 333 | + println(0xabcdefa) |
| 334 | + case 101...200: |
| 335 | + println(0xabcdefb) |
| 336 | + default: |
| 337 | + println(0xabcdefc) |
| 338 | + } |
| 339 | + |
| 340 | +} |
| 341 | +``` |
| 342 | + |
| 343 | +output asm is this: |
| 344 | + |
| 345 | +```asm |
| 346 | +0000000100001cb0 push rbp ; Objective C Implementation defined at 0x1000052a0 (instance) |
| 347 | +0000000100001cb1 mov rbp, rsp |
| 348 | +0000000100001cb4 push r14 |
| 349 | +0000000100001cb6 push rbx |
| 350 | +0000000100001cb7 sub rsp, 0x20 |
| 351 | +0000000100001cbb mov rbx, rdi |
| 352 | +0000000100001cbe mov rax, qword [ds:imp___got__swift_isaMask] ; imp___got__swift_isaMask |
| 353 | +0000000100001cc5 mov rax, qword [ds:rax] |
| 354 | +0000000100001cc8 and rax, qword [ds:rbx] |
| 355 | +0000000100001ccb lea rcx, qword [ds:_OBJC_CLASS_$__TtC12swift_weekly14ViewController] ; _OBJC_CLASS_$__TtC12swift_weekly14ViewController |
| 356 | +0000000100001cd2 xor edi, edi |
| 357 | +0000000100001cd4 cmp rax, rcx |
| 358 | +0000000100001cd7 cmove rdi, rbx |
| 359 | +0000000100001cdb test rdi, rdi |
| 360 | +0000000100001cde je 0x100001cf3 |
| 361 | +
|
| 362 | +0000000100001ce5 mov edi, 0xffffffff ; argument "upper_bound" for method imp___stubs__arc4random_uniform |
| 363 | +0000000100001cea call imp___stubs__arc4random_uniform |
| 364 | +0000000100001cef mov eax, eax |
| 365 | +0000000100001cf1 jmp 0x100001d0d |
| 366 | +
|
| 367 | +0000000100001cf3 mov r14, qword [ds:rax+0x50] ; XREF=-[_TtC12swift_weekly14ViewController example2]+46 |
| 368 | +0000000100001d07 mov rdi, rbx |
| 369 | +0000000100001d0a call r14 |
| 370 | +
|
| 371 | +0000000100001d0d cmp rax, 0x64 ; XREF=-[_TtC12swift_weekly14ViewController example2]+65 |
| 372 | +0000000100001d11 ja 0x100001d2c |
| 373 | +
|
| 374 | +0000000100001d13 mov qword [ss:rbp+var_28], 0xabcdefa |
| 375 | +0000000100001d1b mov rsi, qword [ds:imp___got___TMdSi] ; imp___got___TMdSi |
| 376 | +0000000100001d22 add rsi, 0x8 |
| 377 | +0000000100001d26 lea rdi, qword [ss:rbp+var_28] |
| 378 | +0000000100001d2a jmp 0x100001d66 |
| 379 | +
|
| 380 | +0000000100001d2c add rax, 0xffffffffffffff9b ; XREF=-[_TtC12swift_weekly14ViewController example2]+97 |
| 381 | +0000000100001d30 cmp rax, 0x63 |
| 382 | +0000000100001d34 ja 0x100001d4f |
| 383 | +
|
| 384 | +0000000100001d36 mov qword [ss:rbp+var_20], 0xabcdefb |
| 385 | +0000000100001d3e mov rsi, qword [ds:imp___got___TMdSi] ; imp___got___TMdSi |
| 386 | +0000000100001d45 add rsi, 0x8 |
| 387 | +0000000100001d49 lea rdi, qword [ss:rbp+var_20] |
| 388 | +0000000100001d4d jmp 0x100001d66 |
| 389 | +
|
| 390 | +0000000100001d4f mov qword [ss:rbp+var_18], 0xabcdefc ; XREF=-[_TtC12swift_weekly14ViewController example2]+132 |
| 391 | +0000000100001d57 mov rsi, qword [ds:imp___got___TMdSi] ; imp___got___TMdSi |
| 392 | +0000000100001d5e add rsi, 0x8 |
| 393 | +0000000100001d62 lea rdi, qword [ss:rbp+var_18] |
| 394 | +
|
| 395 | +0000000100001d66 call imp___stubs___TFSs7printlnU__FQ_T_ ; XREF=-[_TtC12swift_weekly14ViewController example2]+122, -[_TtC12swift_weekly14ViewController example2]+157 |
| 396 | +0000000100001d73 add rsp, 0x20 |
| 397 | +0000000100001d77 pop rbx |
| 398 | +0000000100001d78 pop r14 |
| 399 | +0000000100001d7a pop rbp |
| 400 | +0000000100001d7b ret |
| 401 | +``` |
| 402 | + |
| 403 | +what's going on here? |
| 404 | + |
| 405 | +1. first a call is made to the `imp___stubs__arc4random_uniform` function. the upper bound (`0xffffffff`) is passed into the `rdi` register ([System V AMD64 ABI](http://goo.gl/mBdSoG)) since that's the value of `UInt32.max`. Swift just didn't call the `max` var really. this was directly placed in the code segment (ref: `static var max: UInt32 { get }`) |
| 406 | +2. then we have an unconditional jump to `cs:0x100001d0d` where our first case statement is evaluated: |
| 407 | + |
| 408 | + ```asm |
| 409 | + 0000000100001d0d cmp rax, 0x64 ; XREF=-[_TtC12swift_weekly14ViewController example2]+65 |
| 410 | + 0000000100001d11 ja 0x100001d2c |
| 411 | + ``` |
| 412 | + |
| 413 | + this means: compare the random number with 100 and jump to `cs:0x100001d2c` if the number is __above__ 100. let's assume that the number is __not__ above 100, and is in fact 50. we will __not__ jump in that case to `cs:0x100001d2c` and will just continue down the line. now we get the following code, assuming that our random number was 50: |
| 414 | + |
| 415 | + ```asm |
| 416 | + 0000000100001d13 mov qword [ss:rbp+var_28], 0xabcdefa |
| 417 | + 0000000100001d1b mov rsi, qword [ds:imp___got___TMdSi] ; imp___got___TMdSi |
| 418 | + 0000000100001d22 add rsi, 0x8 |
| 419 | + 0000000100001d26 lea rdi, qword [ss:rbp+var_28] |
| 420 | + 0000000100001d2a jmp 0x100001d66 |
| 421 | + ``` |
| 422 | + |
| 423 | + breaking it down into smaller pieces: |
321 | 424 |
|
| 425 | + * the value of `0xabcdefa` is placed inside the stack |
| 426 | + * the `rsi` register is set to `0x0000000109CFE9A8`. at this point, I have no idea why. I can just see that the memory that it points to contains `0x0000000000000001`. Is that a boolean value and if yes, what does that mean? what is this mysterious `imp___got___TMdSi` value that we keep seeing every now and then. A quick [search in Google](http://goo.gl/Xf4oav) reveals that the only person in the world who has ever written about this magical string is, well, me, in Swift Weekly. If you know what this is, please send a pull request and complete this article. |
| 427 | + * then we are jumping to `cs:0x100001d66` where the `println()` is occurring. |
| 428 | + |
| 429 | +3. now if we assume that our value was more than 100 (let's say it is 101, `0x65`), then we would jump to `cs:0x100001d2c`, we would reach this code: |
322 | 430 |
|
| 431 | + ```asm |
| 432 | + 00000001000017b4 add rax, 0xffffffffffffff9b ; XREF=-[_TtC10TestMacApp11AppDelegate example3:]+121 |
| 433 | + 00000001000017b8 cmp rax, 0x63 |
| 434 | + 00000001000017bc ja 0x1000017d7 |
| 435 | + ``` |
| 436 | + |
| 437 | + let's say that the random number that was generated was 101: |
| 438 | + |
| 439 | + * the program is adding `0xffffffffffffff9b` to our value of 101. the previous comparison was with the value of 100 or `0x64`, now subtract 0x64 from the biggest quad word of `0xffffffffffffffff` and we will get `0xffffffffffffff9b` by adding the value of `0xffffffffffffff9b`, Swift is effectively removing the range of the first `case` statement from our value. |
| 440 | + * assuming that our value is 101, after the `add` instruction, `rax` becomes 0. then we have a compare with the value of `0x63` which is 99 and that is the range that we have specified in our second case statement. the [range](http://goo.gl/4qnLNe) is the y-x where x is the first and x is the last number in the series. for us, x was 101 and y was 200 so that yields 99. |
323 | 441 |
|
| 442 | + what if our value was 200 (`0xc8`) though? with this logic, after `add rax, 0xffffffffffffff9b`, we would have the value of `0x63` left in `rax` which is 99 __which is our range__. This is some clever shit that Swift is doing right here. I wish i had thougth about this when I used to do assembly programming years ago. |
324 | 443 |
|
| 444 | +Having looked at the code together, we can conclude that `switch` and `case` are not magical statements. there are _not_ magical statements when it comes down to the nitty gritty assembly level code. having said that, there _are_ magical ways of doing things in assembly. Swift seems to have grown a lot since the first beta and the code that it outputs for Release builds is impressive for simple `switch` statements. I would like to go more into this but I feel like we will just be exploring other things in the asm, not the real `switch` statement which was what we set out to do in this article. |
325 | 445 |
|
326 | 446 | Conclusion
|
327 | 447 | ===
|
328 | 448 | 1. The `imp___stubs___TFE12CoreGraphicsVSC7CGPointCfMS0_FT1xSi1ySi_S0_` private function takes in an x and a y value in the `rdi` and the `rsi` registers to create a `CGPoint`.
|
329 | 449 | 2. Usually, Swift uses the `imp___stubs__objc_msgSend` to call a function on an instance of `NSObject`. for a property, such as the `frame` property of a `UIView` however, Swift uses the `imp___stubs__objc_msgSend_stret` function but still follows the [System V AMD64](http://goo.gl/mBdSoG) calling convention.
|
330 | 450 | 3. A Swift compiler optimization kicks in where the code for every one of the case statements inside your `switch` calls the same function, but perhaps with different values. For instance, if you call the `println()` function at the end of each case statement, and do nothing else, the compiler is intelligent enough to only put `println()` once in the output assembly and prep the GPRs before it jumps to that line of code where `println()` occurs, so that the correct value gets printed out. Some really cool optimization going on here.
|
| 451 | +4. The `case` statements inside a `switch` are optimized very well, it seems like it, under Release builds in Swift. Range checking specifically in Swift is done very smoothly using one conditional rather than 2 (in most cases, depending on the ranges). |
331 | 452 |
|
332 | 453 | References
|
333 | 454 | ===
|
334 | 455 | 1. [Tuples - The Swift Programming Language](http://goo.gl/6UmR8n)
|
335 | 456 | 2. [NASM - Writing 64-bit code](http://goo.gl/5wvq4R)
|
336 | 457 | 3. [System V AMD64 ABI](http://goo.gl/mBdSoG) on Wikipedia
|
337 |
| -4. [System V Application Binary Interface, AMD64 Architecture Processor Supplement, Draft Version 0.99.6](http://goo.gl/lCBYu) |
| 458 | +4. [System V Application Binary Interface, AMD64 Architecture Processor Supplement, Draft Version 0.99.6](http://goo.gl/lCBYu) |
| 459 | +5. [Range - Statistics](http://goo.gl/4qnLNe) |
0 commit comments