Lets assume very simple example:
func square() -> Int {
var x = 5
defer { x = 10 }
return x
}
Why 5 is returned?
We know that defer is able to work only until rbp
is vanished. So defer executes until return.
That how it look like in assembly:
output.square() -> Swift.Int:
push rbp
mov rbp, rsp
sub rsp, 16
mov qword ptr [rbp - 8], 0
mov qword ptr [rbp - 8], 5
lea rdi, [rbp - 8]
call ($defer #1 () -> () in output.square() -> Swift.Int)
mov eax, 5
add rsp, 16
pop rbp
ret
$defer #1 () -> () in output.square() -> Swift.Int:
push rbp
mov rbp, rsp
mov qword ptr [rbp - 8], 0
mov qword ptr [rbp - 8], rdi
mov qword ptr [rdi], 10
pop rbp
ret
Do I got it right, that defer and return statements in example just use different registers.
eax
for return and rdi
for defer.
And what happens when we use reference type
output.square() -> output.X:
push rbp
mov rbp, rsp
push r13
sub rsp, 24
mov qword ptr [rbp - 16], 0
xor eax, eax
mov edi, eax
call (type metadata accessor for output.X)
mov r13, rax
call (output.X.__allocating_init() -> output.X)
mov rdi, rax
mov qword ptr [rbp - 24], rdi
call swift_retain@PLT
mov rax, qword ptr [rbp - 24]
mov qword ptr [rbp - 16], rax
lea rdi, [rbp - 16]
call ($defer #1 () -> () in output.square() -> output.X)
mov rdi, qword ptr [rbp - 16]
call swift_release@PLT
mov rax, qword ptr [rbp - 24]
add rsp, 24
pop r13
pop rbp
ret
type metadata accessor for output.X:
lea rax, [rip (full type metadata for output.X) 16]
xor ecx, ecx
mov edx, ecx
ret
$defer #1 () -> () in output.square() -> output.X:
push rbp
mov rbp, rsp
push r13
sub rsp, 24
mov qword ptr [rbp - 24], rdi
mov qword ptr [rbp - 16], 0
mov qword ptr [rbp - 16], rdi
xor eax, eax
mov edi, eax
call (type metadata accessor for output.X)
mov r13, rax
call (output.X.__allocating_init() -> output.X)
mov rcx, rax
mov rax, qword ptr [rbp - 24]
mov rdi, qword ptr [rax]
mov qword ptr [rax], rcx
call swift_release@PLT
add rsp, 24
pop r13
pop rbp
ret
CodePudding user response:
The assembly-language output is not particularly helpful in understanding what's happening here. Absent compiler bugs, the compiler generates assembly that matches the language's requirements, and is free to do it any way it likes. The specific registers involved do not matter to this analysis.
The behavior precisely matches Swift's value type semantics. x
is an Int, which is a value type. The line return x
returns a copy of x
. The current value of x
is 5, so that is copied for return. Later, defer
replaces x
with a copy of 10. x
is then thrown away.
There's no problem being interested in the assembly output, of course. It actually can be enlightening in this case. Here is the same code with optimizations:
output.square() -> Swift.Int:
mov eax, 5
ret
You'll note the 10 is nowhere to be found.