Skip to content

Commit a43fa57

Browse files
committed
Wrote issue 10, Switch Statements
1 parent 6e4edef commit a43fa57

File tree

9 files changed

+197
-198
lines changed

9 files changed

+197
-198
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ Here are the available issues:
1414
- [Issue 07 - The Swift Runtime (Part 5) - Operators](issue07/README.md)
1515
- [Issue 08 - The Swift Runtime (Part 6) - Type Casting](issue08/README.md)
1616
- [Issue 09 - The Swift Runtime (Part 7) - Subscripts](issue09/README.md)
17+
- [Issue 10 - The Swift Runtime (Part 8) - Switch Statement](issue10/README.md)

issue10/README.md

+125-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
WORK IN PROGRESS
2-
===
31
Swift Weekly - Issue 10 - The Swift Runtime (Part 8) - Switch Statement
42
===
53
Vandad Nahavandipoor
@@ -318,20 +316,144 @@ __note__: throughout the asm code for our example method, `[ds:r12]` will remain
318316
```
319317

320318
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:
321424

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:
322430

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.
323441

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.
324443

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.
325445

326446
Conclusion
327447
===
328448
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`.
329449
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.
330450
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).
331452

332453
References
333454
===
334455
1. [Tuples - The Swift Programming Language](http://goo.gl/6UmR8n)
335456
2. [NASM - Writing 64-bit code](http://goo.gl/5wvq4R)
336457
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)

issue10/exampleCode/swift-weekly/ViewController.swift

+16-59
Original file line numberDiff line numberDiff line change
@@ -29,68 +29,25 @@ class ViewController: UIViewController {
2929

3030
}
3131

32-
// func example2(){
33-
//
34-
// let age = 2
35-
//
36-
// switch age{
37-
// case 0...2:
38-
// println("toddler")
39-
// case 3...14:
40-
// println("child")
41-
// case 15...17:
42-
// println("teenager")
43-
// default:
44-
// println("adult")
45-
// }
46-
//
47-
// }
48-
//
49-
// func example3(){
50-
//
51-
// class Vehicle{
52-
// let name: String
53-
// let wheels: Int
54-
// init(name: String, wheels: Int){
55-
// self.name = name
56-
// self.wheels = wheels
57-
// }
58-
// }
59-
//
60-
// class Bicycle: Vehicle{
61-
// convenience init(){self.init(name: "Bicycle", wheels: 2)}
62-
// }
63-
//
64-
// class Car: Vehicle{
65-
// convenience init(){self.init(name: "Car", wheels: 4)}
66-
// }
67-
//
68-
// class AlienCar: Vehicle{
69-
// convenience init(){self.init(name: "Blabla", wheels: -1)} //-1 wheels!!!
70-
// }
71-
//
72-
// let vehicles = [Car(), Bicycle(), Car(), AlienCar()]
73-
// for v in vehicles{
74-
// switch (v.name, v.wheels){
75-
// case (_, let wheels) where wheels >= 5:
76-
// println("You are a vehicle with more than 4 wheels. Your name isn't important to me")
77-
// case (let name, 4):
78-
// println("You have 4 wheels and your name is \(name)")
79-
// case let (_, 2):
80-
// println("You have 2 wheels, you are a bicycle")
81-
// case (let name, 1):
82-
// println("You are a single wheeler and your name is \(name)")
83-
// default:
84-
// println("I have no clue what type of a vehicle you are and have no access to your name!")
85-
// }
86-
// }
87-
//
88-
// }
32+
func randomInt() -> Int{
33+
return Int(arc4random_uniform(UInt32.max))
34+
}
8935

36+
func example2(){
37+
38+
switch randomInt(){
39+
case 0...100:
40+
println(0xabcdefa)
41+
case 101...200:
42+
println(0xabcdefb)
43+
default:
44+
println(0xabcdefc)
45+
}
46+
47+
}
9048
override func viewDidLoad() {
9149
super.viewDidLoad()
92-
93-
example1()
50+
example2()
9451
}
9552

9653
}

0 commit comments

Comments
 (0)