Rust: Ensine rustc a fazer chamadas de cauda

Criado em 27 jan. 2011  ·  18Comentários  ·  Fonte: rust-lang/rust

Rustc ainda não sabe como fazer chamadas de cauda ('be' em vez de 'ret'). Não deve ser muito difícil ensiná-lo como.

A-LLVM

Comentários muito úteis

Temos planos de implementar o TCO garantido, se possível. Até reservamos uma palavra-chave para isso, "tornar-se". Por favor, verifique o repositório de RFCs.

Em 3 de agosto de 2016, 19:46 -0400, Antoine PLASKOWSKI [email protected] , escreveu:

Sou notícia em ferrugem e estou muito triste. Eu tento uma função recursiva de cauda e empilho estouro tão rápido. pior quando compilo no release a mudança de comportamento. Ele simplesmente não chama minha função. Um amigo me disse que o LLVM otimiza para indefinir valor (o LLVM sabe que é uma cauda de recursão infinita?!?).

fn rec(i: i32) { rec(i + 1) } fn main() { println!("{}", rec(0)); }

Estou de acordo com o Boiethios, uma linguagem com recursos funcionais que não implementa a chamada de cauda perder algo.

Eu escrevo em C e C++, eles não garantem a chamada de cauda, ​​mas na verdade eles lidam muito bem com isso. Isso não vai me impedir de aprender ferrugem, agora eu sei que ferrugem não lida com isso, vou codificar sem, então não é um grande problema.

Mas acho que essa é uma característica muito boa para uma linguagem moderna.


Você está recebendo isso porque está inscrito neste tópico.
Responda a este e-mail diretamente, visualize-o no GitHub (https://github.com/rust-lang/rust/issues/217#issuecomment-237409642) ou silencie o tópico (https://github.com/notifications/unsubscribe) -auth/AABsipoedHrbnKDekmzCr-dl8M6g-Gojks5qcShKgaJpZM4AC-q_).

Todos 18 comentários

De acordo com Graydon, precisamos pensar mais sobre as convenções de chamada antes de implementar isso. O LLVM requer a convenção fastcc para implementar chamadas de cauda, ​​mas não faz exatamente o que queremos:

graydon: brson: a julgar pelos comentários em llvm-land, podemos estar com problemas. podemos compensar o que o fastcc está fazendo hoje, mas pode mudar de ideia amanhã.
graydon: Eu pensei que fastcc == x86_fastcall, mas estava errado.
graydon: essas são convenções de chamada diferentes. fastcc é "o que quer que llvm pareça esta semana"
graydon: precisamos mudar para x86_fastcall e então, a longo prazo, escrever nossa própria convenção de chamada.

Atualmente, isso está parcialmente implementado devido a algumas complicações na maneira como o LLVM trata as chamadas de cauda. Rustc analisa expressões 'be' e as traduz como pares call+ret, o que é suficiente para nos fazer 'trabalhar' em casos simples e não muito profundos. Pode ser o suficiente para inicializar.

Aparentemente, existe apenas um CC (fastcc) que suporta chamadas de cauda garantidas e, para nos adaptarmos a ele, precisamos alterar nossas suposições de ABI em vários lugares (ele se transforma em callee-restore quando você ativa o sinalizador -tailcallopt). Portanto, mesmo se anotarmos nossos pares call+ret com 'tail', conforme necessário, não podemos dizer ao LLVM para começar a realizar essa otimização ainda.

Acabou por não precisar de mais implementação do que é mostrado aqui para auto-hospedagem. Punting para o próximo marco.

Alguém sente que realmente vamos fazer mais isso?

Não parece que vai acontecer.

Qual é a situação com LLVM e chamadas de cauda? Se eles podem ser feitos para funcionar de forma confiável sem algumas coisas invasivas como declarar o chamado para ser chamado de cauda, ​​podemos definir um conjunto limitado de coisas que podem ser passadas para chamadas de cauda (argumentos do próprio chamador, escalares) e erro quando algo mais é passado. Essas chamadas de cauda podem não ser muito úteis na prática, no entanto.

Eles só funcionam com uma ABI de callee que é subótima em casos que não são de chamada final. Então, de certa forma, sim, eles exigem que o chamado seja declarado como chamado de cauda.

Poderíamos implementar isso, digamos, analisando uma caixa e quando encontramos uma função que é chamada de cauda, ​​compilando-a separadamente sob a ABI amigável para chamada de cauda, ​​ou compilando wrappers que alternam ABI na entrada ou algo assim. Ou, alternativamente, podemos alternar _every_ função em todos os lugares para usar a ABI amigável para chamadas de cauda. Não tenho certeza se estamos fazendo isso atualmente. Estávamos por um tempo, mas podemos ter parado. É a ABI "fastcall" do LLVM, que acho que não estamos mais usando.

Em qualquer caso, é um pouco confuso.

Temos otimização de chamadas de irmãos, agora. Estamos planejando tentar fazer mais?

Não vai acontecer, exceto na medida abordada pelo #2216. A solução para #2216 deve ser ampla o suficiente para suportar a codificação de máquina de estado geral.

Isso continua a surgir na conversa e reservamos be novamente. Reabertura.

Acho que o comentário de Graydon na lista de discussão:

https://mail.mozilla.org/pipermail/rust-dev/2013-April/003557.html

coloca um prego neste caixão. Reproduzido aqui:


Em 04/10/2013 05:43, Artella Coding escreveu:

Oi, ferrugem faz otimização de chamada de cauda? A razão pela qual estou perguntando é que
a seguinte implementação recursiva de chamada de cauda resulta em uma pilha
transbordar. Obrigado.

Não, e muito provavelmente não. Temos um bug de longa data sobre isso:

https://github.com/mozilla/rust/issues/217

bem como uma página wiki e vários tópicos de lista de discussão:

https://github.com/mozilla/rust/wiki/Bikeshed-tailcall
https://mail.mozilla.org/pipermail/rust-dev/2011-August/000689.html
https://mail.mozilla.org/pipermail/rust-dev/2012-January/001280.html
...

O resumo de tudo isso é:

  • Todos sabemos que as chamadas de cauda são um recurso de linguagem virtuosa.
    Não é necessária uma maior elaboração de suas virtudes. Muitos de nós
    tem fundos de lisp e ML e gostaria muito deles. Deles
    ausência é mágoa e tristeza, não alcançada de ânimo leve.
  • Tail chama "joga mal" com destruição determinista. Incluindo
    queda determinística de ~ caixas. Para não dizer que eles não são
    composable, mas as opções para compô-los são complicadas para a interface do usuário,
    penalizar o desempenho, complicar a semântica ou todos os itens acima.
  • As chamadas de cauda também "jogam mal" com suposições em ferramentas C, incluindo
    ABIs de plataforma e vinculação dinâmica.
  • As chamadas de cauda exigem uma convenção de chamada que é um sucesso de desempenho
    em relação à convenção C.
  • Encontramos a maioria dos casos de _recursion_ de cauda convertem razoavelmente bem para
    loops e a maioria dos casos de chamadas de cauda não recursivas codificam o estado
    máquinas que convertem razoavelmente bem em loops enrolados
    enumerações. Nenhum deles é _bastante_ tão bonito quanto o
    variantes usando tail-call, mas elas funcionam e são "tão rápidas"*,
    bem como idiomática para programadores C e C++ (que são nossos
    público principal).

Lamento dizer tudo isso, e é com o coração pesado, mas nós
tentou e não encontrou uma maneira de fazer as compensações associadas a eles
resumir a um argumento para a inclusão na ferrugem.

-Graydon

  • em termos de velocidade, uma máquina de estado switch-in-a-loop é um despacho indireto, então
    provavelmente será mais lento do que uma máquina de estado codificada por chamadas de cauda de
    estado para estado; por outro lado, se a maneira de obter esse desempenho
    "voltar" é ativar chamadas de cauda em todos os lugares, estaríamos negociando uma isolada
    caso de desempenho subótimo para um imposto de desempenho subótimo entre todos os programas. Nós
    não ache este comércio aceitável.

Talvez valha a pena notar que Haskell geralmente precisa de uma transformação de argumento estático para inlining, fusion, etc. http://stackoverflow.com/a/9660027/667457

Rust poderia suportar a transformação de argumento estático dizendo que as funções definidas dentro de outra função devem ser elegíveis para otimizações de tailcall. Ou simplesmente avise se tailcalls entre funções aninhadas dentro de outra função falharam na otimização de irmãos. Não tenho certeza da situação atual.

É triste que a recursão da cauda não seja implementada. Então eu tenho uma pergunta: por que criar uma sintaxe de linguagem que parece uma sintaxe de linguagem funcional se falta um recurso essencial de funcional?

Sou notícia em ferrugem e estou muito triste. Eu tento uma função recursiva de cauda e empilho estouro tão rápido. pior quando compilo no release a mudança de comportamento. Ele simplesmente não chama minha função. Um amigo me disse que o LLVM otimiza para valor indefinido (o LLVM sabe que é uma cauda de recursão infinita?!?).

fn rec(i: i32) {
  rec(i + 1)
}

fn main() {
  println!("{}", rec(0));
}

Concordo com Boiethios, uma linguagem com recursos funcionais que não implementa tail call miss alguma coisa.

Eu escrevo em C e C++, eles não garantem chamada de cauda, ​​mas na verdade eles lidam muito bem com isso. Não vai me impedir de aprender a ferrugem, agora eu sei que a ferrugem não aguenta. Vou codificar sem, então não é um grande problema.

Mas acho que essa é uma característica muito boa para uma linguagem moderna.

notar que

fn rec(i: i32) {
  println!("Hello");
  rec(i + 1)
}

estouro de pilha também no modo de liberação de carga

Temos planos de implementar o TCO garantido, se possível. Até reservamos uma palavra-chave para isso, "tornar-se". Por favor, verifique o repositório de RFCs.

Em 3 de agosto de 2016, 19:46 -0400, Antoine PLASKOWSKI [email protected] , escreveu:

Sou notícia em ferrugem e estou muito triste. Eu tento uma função recursiva de cauda e empilho estouro tão rápido. pior quando compilo no release a mudança de comportamento. Ele simplesmente não chama minha função. Um amigo me disse que o LLVM otimiza para indefinir valor (o LLVM sabe que é uma cauda de recursão infinita?!?).

fn rec(i: i32) { rec(i + 1) } fn main() { println!("{}", rec(0)); }

Estou de acordo com o Boiethios, uma linguagem com recursos funcionais que não implementa a chamada de cauda perder algo.

Eu escrevo em C e C++, eles não garantem a chamada de cauda, ​​mas na verdade eles lidam muito bem com isso. Isso não vai me impedir de aprender ferrugem, agora eu sei que ferrugem não lida com isso, vou codificar sem, então não é um grande problema.

Mas acho que essa é uma característica muito boa para uma linguagem moderna.


Você está recebendo isso porque está inscrito neste tópico.
Responda a este e-mail diretamente, visualize-o no GitHub (https://github.com/rust-lang/rust/issues/217#issuecomment-237409642) ou silencie o tópico (https://github.com/notifications/unsubscribe) -auth/AABsipoedHrbnKDekmzCr-dl8M6g-Gojks5qcShKgaJpZM4AC-q_).

Só uma pergunta: é hipoteticamente possível fazer análise de gráfico de chamadas e transformar chamadas de cauda em loops, pelo menos dentro de uma única caixa, mas é computacionalmente caro em tempo de compilação e bastante complicado de codificar. Houve alguma discussão sobre essa possibilidade? Isso ainda seria uma opção agora, sem levar em conta a escolha da convenção de chamada.

A abordagem do lbstanza é exigir anotação de funções para torná-las elegíveis para TCO (defn+ em vez de defn). Na ferrugem, isso mitigaria amplamente a sobrecarga extra de tempo de compilação para a sugestão do timthelion, desde que você não esteja usando chamadas de cauda literalmente em todos os lugares lol.

Esta página foi útil?
0 / 5 - 0 avaliações