swift tail-call-optimization

¿Swift implementa la optimización de llamadas de cola? ¿Y en caso de recursión mutua?



tail-call-optimization (2)

En particular si tengo el siguiente código:

func sum(n: Int, acc: Int) -> Int { if n == 0 { return acc } else { return sum(n - 1, acc + n) } }

¿El compilador Swift lo optimizará a un bucle? ¿Y es así en un caso más interesante a continuación?

func isOdd(n: Int) -> Bool { if n == 0 { return false; } else { return isEven(n - 1) } } func isEven(n: Int) -> Bool { if n == 0 { return true } else { return isOdd(n - 1) } }


La mejor manera de verificar es examinar el código de lenguaje ensamblador generado por el compilador. Tomé el código de arriba y lo compilé con:

swift -O3 -S tco.swift >tco.asm

La parte relevante de la salida.

.globl __TF3tco3sumFTSiSi_Si .align 4, 0x90 __TF3tco3sumFTSiSi_Si: pushq %rbp movq %rsp, %rbp testq %rdi, %rdi je LBB0_4 .align 4, 0x90 LBB0_1: movq %rdi, %rax decq %rax jo LBB0_5 addq %rdi, %rsi jo LBB0_5 testq %rax, %rax movq %rax, %rdi jne LBB0_1 LBB0_4: movq %rsi, %rax popq %rbp retq LBB0_5: ud2 .globl __TF3tco5isOddFSiSb .align 4, 0x90 __TF3tco5isOddFSiSb: pushq %rbp movq %rsp, %rbp testq %rdi, %rdi je LBB1_1 decq %rdi jo LBB1_9 movb $1, %al LBB1_5: testq %rdi, %rdi je LBB1_2 decq %rdi jo LBB1_9 testq %rdi, %rdi je LBB1_1 decq %rdi jno LBB1_5 LBB1_9: ud2 LBB1_1: xorl %eax, %eax LBB1_2: popq %rbp retq .globl __TF3tco6isEvenFSiSb .align 4, 0x90 __TF3tco6isEvenFSiSb: pushq %rbp movq %rsp, %rbp movb $1, %al LBB2_1: testq %rdi, %rdi je LBB2_5 decq %rdi jo LBB2_7 testq %rdi, %rdi je LBB2_4 decq %rdi jno LBB2_1 LBB2_7: ud2 LBB2_4: xorl %eax, %eax LBB2_5: popq %rbp retq

No hay instrucciones de llamada en el código generado, solo saltos condicionales ( je / jne / jo / jno ). Esto sugiere claramente que Swift hace optimizaciones de llamadas de cola en ambos casos.

Además, las funciones isOdd / isEven son interesantes porque el compilador no solo parece realizar el TCO, sino que también incorpora la otra función en cada caso.


Sí, el compilador swift realiza la optimización de la llamada de cola en algunos casos:

func sum(n: Int, acc: Int) -> Int { if n == 0 { return acc } else { return sum(n - 1, acc: acc + 1) } }

Como una función global, utilizará espacio de pila constante en el nivel de optimización "más rápido" ( -O ).

Si está dentro de una estructura, seguirá utilizando un espacio de pila constante. Sin embargo, dentro de una clase, el compilador no realiza tco porque el método podría ser anulado en tiempo de ejecución.

Clang también admite tco para Objective-C, pero a menudo el release llamadas ARC después de la llamada recursiva, lo que evita esta optimización, consulte este artículo de Jonathon Mah para obtener más detalles.

ARC también parece prevenir el TCO en Swift:

func sum(n: Int, acc: Int, s: String?) -> Int { if n == 0 { return acc } else { return sum(n - 1, acc + 1, s) } }

No se realizó TCO en mis pruebas.