Code style always important. There are a lot of various rules which if followed can greatly improve the code quality - S.O.L.I.D., GRASP, KISS, DRY, OAOO, YAGNI, etc.
But why is so important? Does the compiler care about the quality of the code?
To answer this question we should dive a bit into the process of how the compiler work.
SIL
Below the scheme shows how swift code transformed into machine code:
One of the most interesting things here is SIL - Swift Intermediate Language. SIL can be used for analyzing data flow, optimization, generic specification, etc.
In another world - code is converted to a specific representation, that is much easier for the compiler to optimize. And this is a perfect place to review how exactly our code (with good quality or without it) is processed.
SIL use Static Single Assignment notation. SSA allows representing program code as a graph. Every value is assigned only once, all is simplified and optimized. As shown above, SIL can be represented as RAW SIL (swiftc -O -emit-silgen FILE.swift > OUTPUT.rawsil) and as Canonical SIL (swiftc -O -emit-sil FILE.swift > OUTPUT.sil).
SIL:
Represents program semantics
Specially created for code generation and analysis
The place where SIL is created/used - is in an optimized position for SIL main tasks
Bridges source and LLVM
Code to investigate
To explore SIL and all components let’s create a very simple code example.
Let’s create some functions that replace optional values with the default value.
The very first version can look like this:
We can improve this by adding additional abstractions and supportive features from the Swift language. We can add:
If we generate SIL from this code, we can get a set of instructions, that can help us to demystify and answer the question
SIL output
As u can see, this is SIL canonical output, thus we used -emit-sil params.
sil_stage canonical
The next part - is imports of required components:
and declaration of all components:
In the next part, we can see @main - the entry point. As u know, swift code can be written in the empty file at global scope, SIL generates the entry point anyway - all code from our program is situated inside this function.
The code inside tells us next: at the left side there are pseudo-register values and at the right side instruction with various params and comments that contains the result from the instruction.
As u can see all functions and params have slightly different names with a prefix, suffix, and some other additions - they are mangled. More about this process can be found in official doc.
For example, from generated code u can see that value from %11 will be stored to %3:
where %3 is a reference to the address of a global variable initialized in pres step:
as u know optional is just a enum with 2 cases - .none and .some(T). To get more info - check out the header file.
All other instructions in the @main function declare and create all values that are needed for our code.
Next in the generated code is a function. All functions in SIL starts within sil keyword and has a mangled name:
where
@$s4test14valueORDefault0B012defaultValuexxSgyKXK_xyKXKtKlF is name for valueORDefault<A>
Mangled name needed for creating a unique name for function in diff modules with the same name and for overloaded functions (that’s why u can find the name of the input arguments in the function name).
After we can see a few basic blocks - bb.
From the official doc: A function body consists of one or more basic blocks that correspond to the nodes of the function’s control flow graph. Each basic block contains one or more instructions and ends with terminator instruction. The function’s entry point is always the first basic block in its body.
The content of this block is not important for us right now. The most interesting part is how our functions that use the same logic but various logic implementation are translated into SIL.
and
In both cases, we have 4 basic blocks - bb0-bb3.
bb0:
create a reference to the address of a global variable
begins access to the target memory
loads the value at a specific address from memory
ends an access
conditionally branches to one of several destinations basic blocks
bb1:
unsafely extracts the payload data for an enum
unconditionally transfers control from the current basic block to the block labeled
bb2:
create a reference to the address of a global variable
loads the value at a specific address from memory
extracts a physical field from a loadable struct value
extracts a physical field from a loadable struct value
extracts a physical field from a loadable struct value
increases the strong retain count of the heap object referenced by specific ref
unconditionally transfers control from the current basic block to the block labeled
bb3:
retains a loadable value, which simply retains any references it holds at a specific ref
exits the current function and returns control to the calling function
Now, let’s compare the result of the simple nil-coalescing operator and our well-crafted function:
As u can see - they are identical, except for the values. Remember - we are at SIL level - and according to swift compiler there are a lot of additional steps until we can get an assembly - machine code.
Let’s do this: run swiftc -O -emit-assembly INPUT.swift > OUTPUT.asm.
This will generate the assembly file. For our example it may look like next:
Whole output
But the most interesting part - is how our function converted into instructions. For the function test__1, we can find
This means, that both functions - with and without abstraction will be treated by the compiler in the same way.
The compiler doesn’t care - is u’r code is in a good shape or not. (at least at some level)
Conclusion
Does it mean that we should skip all these patterns, principles, and other stuff, thus for the machine it looks in the same way? My opinion - NO. If u have some doubts - NO, NO, and NO.
The quality of the code is a key principle for any program. Scalability, maintainability, and more other differents *ability is the key concept for any programmer.
We wrote code that will be supported and read by humans, not by machines, this means that code style is very important. All existing articles and principles, discussions, and forums dedicated to the quality of the code and its important role are right.
To those, who think that Customer doesn’t care about the quality of the code, machine don’t care and other stuff like this, I can tell that first of all, we write code for other programmers, humans and they care, a lot!
Code style, principles, and patterns - are some of the key concepts of a good programmer, good program, good code.