Thunk - Thunk
Em programação de computador , um thunk é uma sub - rotina usada para injetar um cálculo em outra sub-rotina. Thunks são usados principalmente para atrasar um cálculo até que seu resultado seja necessário ou para inserir operações no início ou no final da outra sub-rotina. Eles têm muitos outros aplicativos na geração de código do compilador e programação modular .
O termo se originou como uma forma irregular caprichosa de "pensar". Refere-se ao uso original de thunks em compiladores ALGOL, que exigia uma análise especial (pensamento) para determinar que tipo de rotina gerar.
Fundo
Os primeiros anos de pesquisa de compiladores testemunharam ampla experimentação com diferentes estratégias de avaliação . Uma questão chave era como compilar uma chamada de sub-rotina se os argumentos podem ser expressões matemáticas arbitrárias em vez de constantes. Uma abordagem, conhecida como " chamada por valor ", calcula todos os argumentos antes da chamada e, em seguida, passa os valores resultantes para a sub-rotina. Na abordagem rival " chamada pelo nome ", a sub-rotina recebe a expressão de argumento não avaliada e deve avaliá-la.
Uma implementação simples de "chamada por nome" pode substituir o código de uma expressão de argumento para cada aparência do parâmetro correspondente na sub-rotina, mas isso pode produzir várias versões da sub-rotina e várias cópias do código da expressão. Como uma melhoria, o compilador pode gerar uma sub-rotina auxiliar, chamada de conversão , que calcula o valor do argumento. O endereço e o ambiente dessa sub-rotina auxiliar são então passados para a sub-rotina original no lugar do argumento original, onde pode ser chamado quantas vezes forem necessárias. Peter Ingerman primeiro descreveu thunks em referência à linguagem de programação ALGOL 60 , que oferece suporte à avaliação de chamada por nome.
Formulários
Programação funcional
Embora a indústria de software tenha padronizado amplamente a avaliação chamada por valor e chamada por referência , o estudo ativo da chamada por nome continuou na comunidade de programação funcional . Esta pesquisa produziu uma série de linguagens de programação de avaliação preguiçosas em que alguma variante de chamada por nome é a estratégia de avaliação padrão. Compiladores para essas linguagens, como o Glasgow Haskell Compiler , contam muito com thunks, com o recurso adicional de que os thunks salvam seu resultado inicial para evitar recalculá-lo; isso é conhecido como memoização ou chamada por necessidade .
As linguagens de programação funcional também permitem que os programadores gerem thunks explicitamente. Isso é feito no código-fonte envolvendo uma expressão de argumento em uma função anônima que não possui parâmetros próprios. Isso evita que a expressão seja avaliada até que uma função receptora chame a função anônima, obtendo assim o mesmo efeito da chamada por nome. A adoção de funções anônimas em outras linguagens de programação tornou esse recurso amplamente disponível.
O que se segue é uma demonstração simples em JavaScript (ES6):
// 'hypot' is a binary function
const hypot = (x, y) => Math.sqrt(x * x + y * y);
// 'thunk' is a function that takes no arguments and, when invoked, performs a potentially expensive
// operation (computing a square root, in this example) and/or causes some side-effect to occur
const thunk = () => hypot(3, 4);
// the thunk can then be passed around without being evaluated...
doSomethingWithThunk(thunk);
// ...or evaluated
thunk(); // === 5
Programação orientada a objetos
Thunks são úteis em programação orientada a objeto plataformas que permitem que uma classe para várias interfaces de herdar , levando a situações em que o mesmo método pode ser chamado através de qualquer uma das várias interfaces. O código a seguir ilustra essa situação em C ++ .
class A {
public:
virtual int Access() const { return value_; }
private:
int value_;
};
class B {
public:
virtual int Access() const { return value_; }
private:
int value_;
};
class C : public A, public B {
public:
int Access() const override { return better_value_; }
private:
int better_value_;
};
int use(B *b) { return b->Access(); }
int main() {
// ...
B some_b;
use(&some_b);
C some_c;
use(&some_c);
}
Neste exemplo, o código gerado para cada uma das classes A, B e C incluirá uma tabela de despacho que pode ser usada para chamar Accessum objeto desse tipo, por meio de uma referência que tenha o mesmo tipo. A classe C terá uma tabela de despacho adicional, usada para chamar Accessum objeto do tipo C por meio de uma referência do tipo B. A expressão b->Access()usará a própria tabela de despacho de B ou a tabela C adicional, dependendo do tipo de objeto ao qual b se refere. Se se referir a um objeto do tipo C, o compilador deve garantir que a Accessimplementação de C receba um endereço de instância para todo o objeto C, em vez da parte B herdada desse objeto.
Como uma abordagem direta para este problema de ajuste de ponteiro, o compilador pode incluir um deslocamento de inteiro em cada entrada da tabela de despacho. Este deslocamento é a diferença entre o endereço da referência e o endereço exigido pela implementação do método. O código gerado para cada chamada por meio dessas tabelas de despacho deve então recuperar o deslocamento e usá-lo para ajustar o endereço da instância antes de chamar o método.
A solução que acabamos de descrever tem problemas semelhantes à implementação ingênua de call-by-name descrita anteriormente: o compilador gera várias cópias de código para calcular um argumento (o endereço da instância), enquanto também aumenta os tamanhos da tabela de despacho para conter os deslocamentos. Como alternativa, o compilador pode gerar uma conversão de ajustador junto com a implementação de C de Accessque ajusta o endereço da instância pela quantidade necessária e, em seguida, chama o método. A conversão pode aparecer na tabela de despacho de C para B, eliminando assim a necessidade de os chamadores ajustarem o endereço eles próprios.
Cálculos numéricos que requerem avaliações em vários pontos
Rotinas para cálculos, como integração, precisam calcular uma expressão em vários pontos. Chamada por nome era usada para esse propósito em linguagens que não suportavam fechamentos ou parâmetros de procedimento .
Interoperabilidade
Thunks têm sido amplamente usados para fornecer interoperabilidade entre módulos de software cujas rotinas não podem chamar umas às outras diretamente. Isso pode ocorrer porque as rotinas têm convenções de chamada diferentes , são executadas em diferentes modos de CPU ou espaços de endereço , ou pelo menos uma é executada em uma máquina virtual . Um compilador (ou outra ferramenta) pode resolver esse problema gerando uma conversão que automatiza as etapas adicionais necessárias para chamar a rotina de destino, seja transformando argumentos, copiando-os para outro local ou alternando o modo de CPU. Uma conversão bem-sucedida minimiza o trabalho extra que o chamador deve fazer em comparação com uma chamada normal.
Grande parte da literatura sobre thunks de interoperabilidade está relacionada a várias plataformas Wintel , incluindo MS-DOS , OS / 2 , Windows e .NET , e à transição do endereçamento de memória de 16 bits para 32 bits . Como os clientes migraram de uma plataforma para outra, thunks foram essenciais para oferecer suporte a software legado escrito para as plataformas mais antigas.
A transição do código de 32 bits para o código de 64 bits no x86 também usa uma forma de conversão (WoW64). No entanto, como o espaço de endereço x86-64 é maior do que o disponível para o código de 32 bits, o antigo mecanismo de "conversão genérica" não podia ser usado para chamar o código de 64 bits do código de 32 bits. O único caso de código de 32 bits chamando código de 64 bits é na conversão do WoW64 de APIs do Windows para 32 bits.
Sobreposições e links dinâmicos
Em sistemas que não possuem hardware de memória virtual automática , os thunks podem implementar uma forma limitada de memória virtual conhecida como overlays . Com as sobreposições, um desenvolvedor divide o código de um programa em segmentos que podem ser carregados e descarregados independentemente e identifica os pontos de entrada em cada segmento. Um segmento que chama outro segmento deve fazer isso indiretamente por meio de uma tabela de ramificação . Quando um segmento está na memória, as entradas da tabela de ramificação entram no segmento. Quando um segmento é descarregado, suas entradas são substituídas por "reload thunks" que podem recarregá-lo sob demanda.
Da mesma forma, os sistemas que vinculam módulos de um programa dinamicamente em tempo de execução podem usar thunks para conectar os módulos. Cada módulo pode chamar os outros por meio de uma tabela de thunks que o vinculador preenche ao carregar o módulo. Desta forma, os módulos podem interagir sem conhecimento prévio de onde estão localizados na memória.
Veja também
Tecnologias Thunk
- Interface de modo protegido DOS (DPMI)
- Serviços de modo protegido DOS (DPMS)
- J / Direct
- Microsoft Layer para Unicode
- Serviços de invocação de plataforma
- Win32s
- Windows no Windows
- WoW64
- libffi
Conceitos relacionados
- Função anônima
- Futuros e promessas
- Chamada de procedimento remoto
- Shim (computação)
- Trampolim (computação)
- Expressão redutível