The problem
Contents hide
#include <iostream> int globalFunc() { std::cout << "hello, world!\n"; return 42; } int globalVar = globalFunc();
int main(int argc, const char * argv[]) { return 0; }After compiling and running this program you’ll see “hello, world!” in the output, so I’ve tried the same trick with swift:
import Foundation func globalFunc() -> Int { println("hello, world!") return 42 } var globalVar = globalFunc()
// currently, this file is emptyBut had no luck… This program does nothing in swift.
Explanation
At the first glance, it looks like compiler optimisation, but what if we call this variable from debugger?(lldb) po globalVar hello, world! 42 (lldb)It works. Weird. But it works!Ok, so let’s access to this variable from
main.swift
:var localOne = globalVarIt works as well, so variable instantiates at the first call. But how it works and why? To understand how it works we definitely should dive deeper into the executable. Usually I’m using LLVM IR for such purposes, but this time I decided to use Hopper, ’cause it has a very useful feature: ‘Show pseudocode’After opening the executable via Hopper you may see that app also has function
main
as an entry point, this function calls _top_level_code
, which consists of main.swift
. Let’s look at pseudocode of _top_level_code
:function _top_level_code { rax = __TF10swift_testa9globalVarSi(); rax = *rax; *__Tv10swift_test8localOneSi = rax; return rax; }and get rid of ‘garbage’
v_localOne = F_globalVar();Instead of direct access to variable, it calls respective function; let’s look at it:
function __TF10swift_testa9globalVarSi { swift_once(_globalinit_token0, _globalinit_func0, 0x0); return __Tv10swift_test9globalVarSi; }it calls another function
swift_once
and returns actual variable globalVar
. Ok, let’s go deeper, currently we’re interested in the second parameter: _globalinit_func0
, it’s also function:function _globalinit_func0 { rax = __TF10swift_test10globalFuncFT_Si(); *__Tv10swift_test9globalVarSi = rax; return rax; }Caught it! Finally, we’ve found function which does exactly what we want:
v_globalVar = F_globalFunc()
Conclusion
Any global variable in Swift is being replaced with respective function call that initializes this variable on demandLet’s wrap up our small research!This codefunc globalFunc() -> Int { println("hello, world") return 42 } var globalVar = globalFunc()
var localOne = globalVartranslates into this one:
func F_globalFunc() -> Int { println("hello, world") return 42 } var v_globalVar_init = 0 var v_globalVar = zero func F_globalVar_init() { v_globalVar_init = 1 v_globalVar = F_globalFunc() } func F_globalVar() { if (v_globalVar_init == 0) { F_globalVar_init() } return v_globalVar } // .... var v_localOne = F_globalVar()So, now this behaviour is clear, but question ‘why such a decision was made?’ is still open.Happy hacking, guys, and be careful!