Skip to content

Commit 7977bf9

Browse files
authored
Merge pull request #110 from ibrahimcesar/claude/translate-chapter-5-01LMhP6qfXdGxx1oQRD2dw99
Translate Chapter 5
2 parents ef7b1de + b09f461 commit 7977bf9

7 files changed

Lines changed: 235 additions & 0 deletions

File tree

docs/chapter-5/5.0.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# 5 Computação com Máquinas de Registradores
2+
3+
> Meu objetivo é mostrar que a máquina celestial não é uma espécie de ser divino e vivo, mas uma espécie de mecanismo de relojoaria (e aquele que acredita que um relógio tem alma atribui a glória do criador à obra), na medida em que quase todos os múltiplos movimentos são causados por uma força muito simples e material, assim como todos os movimentos do relógio são causados por um único peso.
4+
>
5+
> — Johannes Kepler, letter to Herwart von Hohenburg, 1605
6+
7+
Começamos este livro estudando processos e descrevendo processos em termos de funções escritas em JavaScript. Para explicar os significados dessas funções, usamos uma sucessão de modelos de avaliação: o modelo de substituição do capítulo 1, o modelo de ambiente do capítulo 3, e o avaliador metacircular do capítulo 4. Nosso exame do avaliador metacircular, em particular, dissipou muito do mistério de como linguagens semelhantes ao JavaScript são interpretadas. Mas mesmo o avaliador metacircular deixa questões importantes sem resposta, porque ele falha em elucidar os mecanismos de controle em um sistema JavaScript. Por exemplo, o avaliador não explica como a avaliação de uma subexpressão consegue retornar um valor para a expressão que usa esse valor. Além disso, o avaliador não explica como algumas funções recursivas podem gerar processos iterativos (ou seja, serem avaliadas usando espaço constante) enquanto outras funções recursivas gerarão processos recursivos.[^1] Este capítulo aborda ambas essas questões.
8+
9+
Descreveremos processos em termos da operação passo a passo de um computador tradicional. Tal computador, ou *máquina de registradores*, executa sequencialmente *instruções* que manipulam o conteúdo de um conjunto fixo de elementos de armazenamento chamados *registradores*. Uma instrução típica de máquina de registradores aplica uma operação primitiva ao conteúdo de alguns registradores e atribui o resultado a outro registrador. Nossas descrições de processos executados por máquinas de registradores se parecerão muito com programas em "linguagem de máquina" para computadores tradicionais. No entanto, em vez de focar na linguagem de máquina de qualquer computador específico, examinaremos várias funções JavaScript e projetaremos uma máquina de registradores específica para executar cada função. Assim, abordaremos nossa tarefa da perspectiva de um arquiteto de hardware em vez da de um programador de computador em linguagem de máquina. Ao projetar máquinas de registradores, desenvolveremos mecanismos para implementar construções de programação importantes, como recursão. Também apresentaremos uma linguagem para descrever projetos de máquinas de registradores. Na seção 5.2 implementaremos um programa JavaScript que usa essas descrições para simular as máquinas que projetamos.
10+
11+
A maioria das operações primitivas de nossas máquinas de registradores são muito simples. Por exemplo, uma operação pode adicionar os números obtidos de dois registradores, produzindo um resultado a ser armazenado em um terceiro registrador. Tal operação pode ser realizada por hardware facilmente descrito. No entanto, para lidar com estruturas de lista, também usaremos as operações de memória `head`, `tail` e `pair`, que requerem um mecanismo elaborado de alocação de armazenamento. Na seção 5.3 estudamos sua implementação em termos de operações mais elementares.
12+
13+
Na seção 5.4, após termos acumulado experiência formulando funções simples como máquinas de registradores, projetaremos uma máquina que executa o algoritmo descrito pelo avaliador metacircular da seção 4.1. Isso preencherá a lacuna em nosso entendimento de como programas JavaScript são interpretados, fornecendo um modelo explícito para os mecanismos de controle no avaliador. Na seção 5.5 estudaremos um compilador simples que traduz programas JavaScript em sequências de instruções que podem ser executadas diretamente com os registradores e operações da máquina de registradores do avaliador.
14+
15+
[^1]: Com nosso avaliador metacircular, uma função recursiva sempre dá origem a um processo recursivo, mesmo quando o processo deveria ser iterativo de acordo com a distinção da seção 1.2.1. Veja a nota de rodapé 4 na seção 4.1.1.

docs/chapter-5/5.1.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
## 5.1 Projetando Máquinas de Registradores
2+
3+
Para projetar uma máquina de registradores, devemos projetar seus *caminhos de dados* (registradores e operações) e o *controlador* que sequencia essas operações. Para ilustrar o projeto de uma máquina de registradores simples, vamos examinar o Algoritmo de Euclides, que é usado para calcular o máximo divisor comum (MDC) de dois inteiros. Como vimos na seção anterior, o Algoritmo de Euclides pode ser executado por um processo iterativo, conforme especificado pela seguinte função:
4+
5+
```javascript
6+
function gcd(a, b) {
7+
return b === 0 ? a : gcd(b, a % b);
8+
}
9+
```
10+
11+
Uma máquina para executar este algoritmo deve acompanhar dois números, $a$ e $b$, então vamos assumir que esses números são armazenados em dois registradores com esses nomes. As operações básicas necessárias são testar se o conteúdo do registrador `b` é zero e calcular o resto do conteúdo do registrador `a` dividido pelo conteúdo do registrador `b`.
12+
13+
A operação de resto é um processo complexo, mas vamos assumir por enquanto que temos um dispositivo primitivo que calcula restos. Em cada ciclo do algoritmo MDC, o conteúdo do registrador `a` deve ser substituído pelo conteúdo do registrador `b`, e o conteúdo de `b` deve ser substituído pelo resto do conteúdo antigo de `a` dividido pelo conteúdo antigo de `b`. Seria conveniente se essas substituições pudessem ser feitas simultaneamente, mas em nosso modelo de máquinas de registradores vamos assumir que apenas um registrador pode receber um novo valor a cada passo. Para realizar as substituições, nossa máquina usará um terceiro registrador "temporário", que chamaremos de `t`. (Primeiro o resto será colocado em `t`, depois o conteúdo de `b` será colocado em `a`, e finalmente o resto armazenado em `t` será colocado em `b`.)
14+
15+
Podemos ilustrar os registradores e operações necessários para esta máquina usando o diagrama de caminho de dados mostrado na Figura 5.1. Neste diagrama, os registradores (`a`, `b` e `t`) são representados por retângulos. Cada forma de atribuir um valor a um registrador é indicada por uma seta com um botão—desenhado como $\otimes$—atrás da cabeça, apontando da fonte de dados para o registrador. Quando pressionado, o botão permite que o valor na fonte "flua" para o registrador designado. O rótulo ao lado de cada botão é o nome que usaremos para nos referir ao botão. Os nomes são arbitrários e podem ser escolhidos para ter valor mnemônico (por exemplo, `a<-b` denota pressionar o botão que atribui o conteúdo do registrador `b` ao registrador `a`). A fonte de dados para um registrador pode ser outro registrador (como na atribuição `a<-b`), um resultado de operação (como na atribuição `t<-r`), ou uma constante (um valor embutido que não pode ser alterado, representado em um diagrama de caminho de dados por um triângulo contendo a constante).
16+
17+
Uma operação que calcula um valor a partir de constantes e do conteúdo de registradores é representada em um diagrama de caminho de dados por um trapézio contendo um nome para a operação. Por exemplo, a caixa marcada `rem` na Figura 5.1 representa uma operação que calcula o resto do conteúdo dos registradores `a` e `b` aos quais está conectada. Setas (sem botões) apontam dos registradores de entrada e constantes para a caixa, e setas conectam o valor de saída da operação aos registradores. Um teste é representado por um círculo contendo um nome para o teste. Por exemplo, nossa máquina MDC tem uma operação que testa se o conteúdo do registrador `b` é zero. Um teste também tem setas de seus registradores de entrada e constantes, mas não tem setas de saída; seu valor é usado pelo controlador em vez dos caminhos de dados. No geral, o diagrama de caminho de dados mostra os registradores e operações que são necessários para a máquina e como eles devem ser conectados. Se visualizarmos as setas como fios e os botões $\otimes$ como interruptores, o diagrama de caminho de dados é muito parecido com o diagrama de fiação de uma máquina que poderia ser construída a partir de componentes elétricos.
18+
19+
![Figura 5.1](img_original/Fig5.1a.std.svg)
20+
**Figura 5.1:** Caminhos de dados para uma máquina MDC.
21+
22+
Para que os caminhos de dados realmente calculem MDCs, os botões devem ser pressionados na sequência correta. Descreveremos essa sequência em termos de um diagrama de controlador, conforme ilustrado na Figura 5.2. Os elementos do diagrama de controlador indicam como os componentes do caminho de dados devem ser operados. As caixas retangulares no diagrama de controlador identificam os botões do caminho de dados a serem pressionados, e as setas descrevem o sequenciamento de um passo para o próximo. O diamante no diagrama representa uma decisão. Uma das duas setas de sequenciamento será seguida, dependendo do valor do teste do caminho de dados identificado no diamante. Podemos interpretar o controlador em termos de uma analogia física: pense no diagrama como um labirinto no qual uma bola de gude está rolando. Quando a bola de gude rola para dentro de uma caixa, ela pressiona o botão do caminho de dados que é nomeado pela caixa. Quando a bola de gude rola para dentro de um nó de decisão (como o teste para `b` $= 0$), ela sai do nó pelo caminho determinado pelo resultado do teste indicado. Tomados em conjunto, os caminhos de dados e o controlador descrevem completamente uma máquina para calcular MDCs. Iniciamos o controlador (a bola de gude rolando) no lugar marcado `start`, depois de colocar números nos registradores `a` e `b`. Quando o controlador atinge `done`, encontraremos o valor do MDC no registrador `a`.
23+
24+
![Figura 5.2](img_original/Fig5.2.std.svg)
25+
**Figura 5.2:** Controlador para uma máquina MDC.
26+
27+
### Exercício 5.1
28+
29+
Projete uma máquina de registradores para calcular fatoriais usando o algoritmo iterativo especificado pela seguinte função. Desenhe diagramas de caminho de dados e controlador para esta máquina.
30+
31+
```javascript
32+
function factorial(n) {
33+
function iter(product, counter) {
34+
return counter > n
35+
? product
36+
: iter(counter * product,
37+
counter + 1);
38+
}
39+
return iter(1, 1);
40+
}
41+
```
42+
43+
**Solução:**
44+
45+
(Solução do usuário GitHub escolmebartlebooth)
46+
47+
Em `factorial(n)`, a função `iter(product, counter)` é inicializada com os argumentos `(1, 1)` e executa até `counter > n`, momento em que o produto é retornado. Diferentemente de `gcd(a, b)`, a máquina de registradores não precisa de um registrador temporário, assumindo que a ordem correta de atribuição de registradores seja seguida.
48+
49+
Para o caminho de dados, vemos três registradores: `product`, `counter` e `n`. Existem duas operações: `multiply` e `plus` com duas atribuições. Existe um teste: `greater than`.
50+
51+
![Figura 5.1 - Exercício](img_javascript/ex5-1-solution-1.png)
52+
53+
Para o controlador, começamos preenchendo os três registradores com o fatorial desejado `n` e o valor `1` (inicializando `product` e `counter`). A função `iter` executa e testa se `counter > n`. Se `counter <= n`, então `product` é atualizado para `product * counter`, seguido por counter sendo atualizado para `counter + 1` e o processo é repetido até `counter > n`. Uma vez que counter é > n, o produto é `factorial(n)` e pode ser retornado.
54+
55+
![Figura 5.2 - Exercício](img_javascript/ex5-1-solution-2.png)

docs/chapter-5/5.2.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
## 5.2 Um Simulador de Máquina de Registradores
2+
3+
Para obter uma boa compreensão do design de máquinas de registradores, devemos testar as máquinas que projetamos para ver se elas funcionam como esperado. Uma maneira de testar um design é simular manualmente a operação do controlador, como no exercício 5.4. Mas isso é extremamente tedioso para todas as máquinas, exceto as mais simples. Nesta seção, construímos um simulador para máquinas descritas na linguagem de máquina de registradores. O simulador é um programa JavaScript com quatro funções de interface. A primeira usa uma descrição de uma máquina de registradores para construir um modelo da máquina (uma estrutura de dados cujas partes correspondem às partes da máquina a ser simulada), e as outras três nos permitem simular a máquina manipulando o modelo:
4+
5+
- `make_machine(`*register-names*`, `*operations*`, `*controller*`)`
6+
constrói e retorna um modelo da máquina com os registradores, operações e controlador fornecidos.
7+
8+
- `set_register_contents(`*machine-model*`, `*register-name*`, `*value*`)`
9+
armazena um valor em um registrador simulado na máquina fornecida.
10+
11+
- `get_register_contents(`*machine-model*`, `*register-name*`)`
12+
retorna o conteúdo de um registrador simulado na máquina fornecida.
13+
14+
- `start(`*machine-model*`)`
15+
simula a execução da máquina fornecida, começando do início da sequência do controlador e parando quando atinge o final da sequência.
16+
17+
Como exemplo de como essas funções são usadas, podemos definir `gcd_machine` como um modelo da máquina GCD da seção 5.1 da seguinte forma:
18+
19+
```javascript
20+
const gcd_machine =
21+
make_machine(
22+
list("a", "b", "t"),
23+
list(list("rem", (a, b) => a % b),
24+
list("=", (a, b) => a === b)),
25+
list(
26+
"test_b",
27+
test(list(op("="), reg("b"), constant(0))),
28+
branch(label("gcd_done")),
29+
assign("t", list(op("rem"), reg("a"), reg("b"))),
30+
assign("a", reg("b")),
31+
assign("b", reg("t")),
32+
go_to(label("test_b")),
33+
"gcd_done"));
34+
```
35+
36+
O primeiro argumento para `make_machine` é uma lista de nomes de registradores. O próximo argumento é uma tabela (uma lista de listas de dois elementos) que associa cada nome de operação com uma função JavaScript que implementa a operação (ou seja, produz o mesmo valor de saída dados os mesmos valores de entrada). O último argumento especifica o controlador como uma lista de rótulos e instruções de máquina, como na seção 5.1.
37+
38+
Para calcular MDCs com esta máquina, definimos os registradores de entrada, iniciamos a máquina e examinamos o resultado quando a simulação termina:
39+
40+
```javascript
41+
set_register_contents(gcd_machine, "a", 206);
42+
```
43+
```
44+
"done"
45+
```
46+
47+
```javascript
48+
set_register_contents(gcd_machine, "b", 40);
49+
```
50+
```
51+
"done"
52+
```
53+
54+
```javascript
55+
start(gcd_machine);
56+
```
57+
```
58+
"done"
59+
```
60+
61+
```javascript
62+
get_register_contents(gcd_machine, "a");
63+
```
64+
```
65+
2
66+
```
67+
68+
Esta computação será executada muito mais lentamente do que uma função `gcd` escrita em JavaScript, porque simularemos instruções de máquina de baixo nível, como `assign`, por operações muito mais complexas.
69+
70+
**Exercício 5.7:** Use o simulador para testar as máquinas que você projetou no exercício 5.4.

docs/chapter-5/5.3.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## 5.3 Alocação de Armazenamento e Coleta de Lixo
2+
3+
Na seção 5.4, mostraremos como implementar um avaliador JavaScript como uma máquina de registradores. Para simplificar a discussão, assumiremos que nossas máquinas de registradores podem ser equipadas com uma **memória com estrutura de lista**, na qual as operações básicas para manipular dados com estrutura de lista são primitivas. Postular a existência de tal memória é uma abstração útil quando se está focando nos mecanismos de controle em um interpretador, mas isso não reflete uma visão realista das operações de dados primitivas reais de computadores contemporâneos. Para obter uma visão mais completa de como os sistemas podem suportar memória com estrutura de lista de forma eficiente, devemos investigar como a estrutura de lista pode ser representada de uma maneira que seja compatível com memórias de computador convencionais.
4+
5+
Existem duas considerações na implementação de estrutura de lista. A primeira é puramente uma questão de representação: como representar a estrutura de "caixa-e-ponteiro" de pares, usando apenas as capacidades de armazenamento e endereçamento de memórias de computador típicas. A segunda questão diz respeito ao gerenciamento de memória conforme uma computação prossegue. A operação de um sistema JavaScript depende crucialmente da capacidade de continuamente criar novos objetos de dados. Estes incluem objetos que são explicitamente criados pelas funções JavaScript sendo interpretadas, bem como estruturas criadas pelo próprio interpretador, como ambientes e listas de argumentos. Embora a criação constante de novos objetos de dados não representaria problema algum em um computador com uma quantidade infinita de memória rapidamente endereçável, as memórias de computador estão disponíveis apenas em tamanhos finitos (que pena). JavaScript assim fornece uma facilidade de **alocação automática de armazenamento** para suportar a ilusão de uma memória infinita. Quando um objeto de dados não é mais necessário, a memória alocada a ele é automaticamente reciclada e usada para construir novos objetos de dados. Existem várias técnicas para fornecer tal alocação automática de armazenamento. O método que discutiremos nesta seção é chamado de **coleta de lixo**.

0 commit comments

Comments
 (0)