É possível criar um intérprete "autoinicializado" independente do intérprete original?

21

Segundo a Wikipedia, o termo "inicialização" no contexto de escrever compiladores significa isso :

Na ciência da computação, o bootstrap é o processo de escrever um compilador (ou montador) na linguagem de programação de origem que ele pretende compilar. A aplicação desta técnica leva a um compilador auto-hospedado.

E eu posso entender como isso funcionaria. No entanto, a história parece ser um pouco diferente para os intérpretes. Agora, é claro, é possível escrever um intérprete auto-hospedado. Não é isso que estou perguntando. Na verdade, estou perguntando: é possível tornar um intérprete auto-hospedado independente do primeiro intérprete original . Para explicar o que quero dizer, considere este exemplo:

Você escreve sua primeira versão intérprete em linguagem X , eo intérprete é para uma nova linguagem que você está criando, chamado Y . Você primeiro usa o compilador da linguagem X para criar um executável. Você agora pode interpretar arquivos escritos em seu novo idioma Y usando o intérprete escrito em linguagem X .

Agora, tanto quanto eu entendo, para ser capaz de "bootstrap" o intérprete que você escreveu no idioma X , seria necessário para reescrever o intérprete em linguagem Y . Mas aqui está o problema: mesmo se você reescrever todo o intérprete em linguagem Y , você ainda vai precisar do original intérprete que você escreveu no idioma X . Como para executar o intérprete no idioma Y , você precisará interpretar os arquivos de origem. Mas o que exatamente vai interpretar os arquivos de origem? Bem, não pode ser nada, é claro, então você é forçado a usar o primeiro intérprete.

Não importa quantos novos intérpretes você escreva no idioma Y , você sempre precisará usar o primeiro intérprete escrito em X para interpretar os intérpretes subsequentes. Isso parece ser um problema simplesmente devido à natureza dos intérpretes.

No entanto , por outro lado, este artigo da Wikipedia sobre intérpretes fala sobre intérpretes auto-hospedados . Aqui está um pequeno trecho que é relevante:

Um auto-intérprete é um intérprete de linguagem de programação escrito em uma linguagem de programação que pode se interpretar; um exemplo é um intérprete BASIC escrito em BASIC. Os auto-intérpretes estão relacionados aos compiladores de hospedagem automática.

Se não existir nenhum compilador para a linguagem ser interpretada, a criação de um auto-intérprete requer a implementação da linguagem em uma linguagem host (que pode ser outra linguagem de programação ou assembler). Por ter um primeiro intérprete como esse, o sistema é inicializado e novas versões do intérprete podem ser desenvolvidas no próprio idioma

Ainda não está claro para mim, como exatamente isso seria feito. Parece que não importa o quê, você sempre será forçado a usar a primeira versão do seu intérprete escrita no idioma do host.

Agora, o artigo mencionado acima tem um link para outro artigo no qual a Wikipedia fornece alguns exemplos de supostos intérpretes de hospedagem própria . Após uma inspeção mais detalhada, porém, parece que a parte principal de "interpretação" de muitos desses intérpretes de hospedagem automática (especialmente alguns dos mais comuns, como PyPy ou Rubinius), é realmente escrita em outros idiomas, como C ++ ou C.

Então, o que eu descrevo acima é possível? Um intérprete auto-hospedado pode ser independente do host original? Se sim, como exatamente isso seria feito?

Christian Dean
fonte

Respostas:

24

A resposta curta é: você está certo em sua suspeita, sempre precisa de outro intérprete escrito em X ou um compilador de Y para outro idioma para o qual você já tenha um intérprete. Os intérpretes são executados, os compiladores são traduzidos apenas de um idioma para outro. Em algum momento do sistema, deve haver um intérprete ... mesmo que seja apenas a CPU.

Não importa quantos novos intérpretes você escreva no idioma Y , você sempre precisará usar o primeiro intérprete escrito em X para interpretar os intérpretes subsequentes. Isso parece ser um problema simplesmente devido à natureza dos intérpretes.

Corrigir. O que você pode fazer é escrever um compilador de Y para X (ou outro idioma para o qual você tem um intérprete), e você pode até fazer isso em Y . Em seguida, você pode executar o compilador Y escrito em Y no intérprete Y escrito em X (ou no intérprete Y escrito em Y executando no interpretador Y escrito em X ou no intérprete Y escrito em Y executando no interpretador Y escrito em Y rodando no Yintérprete escrito em X ou… ad infinitum) para compilar seu intérprete Y escrito em Y a X , para que você possa executá-lo em um intérprete X. Dessa forma, você se livrou do seu intérprete Y escrito em X , mas agora você precisa do intérprete X (sabemos que já temos um, pois, caso contrário, não poderíamos executar o intérprete X escrito em Y ), e você teve que escrever um compilador Y- para- X primeiro.

No entanto , por outro lado, o artigo da Wikipedia sobre intérpretes fala sobre intérpretes auto-hospedados. Aqui está um pequeno trecho que é relevante:

Um auto-intérprete é um intérprete de linguagem de programação escrito em uma linguagem de programação que pode se interpretar; um exemplo é um intérprete BASIC escrito em BASIC. Os auto-intérpretes estão relacionados aos compiladores de hospedagem automática.

Se não existir nenhum compilador para a linguagem ser interpretada, a criação de um auto-intérprete requer a implementação da linguagem em uma linguagem host (que pode ser outra linguagem de programação ou assembler). Por ter um primeiro intérprete como esse, o sistema é inicializado e novas versões do intérprete podem ser desenvolvidas no próprio idioma

Ainda não está claro para mim, como exatamente isso seria feito. Parece que não importa o quê, você sempre será forçado a usar a primeira versão do seu intérprete escrita no idioma do host.

Corrigir. Observe que o artigo da Wikipedia diz explicitamente que você precisa de uma segunda implementação do seu idioma e não diz que pode se livrar da primeira.

Agora, o artigo mencionado acima tem um link para outro artigo no qual a Wikipedia fornece alguns exemplos de supostos intérpretes de hospedagem própria. Após uma inspeção mais minuciosa, parece que a principal parte "de interpretação" de muitos desses intérpretes de hospedagem automática (especialmente alguns dos mais comuns, como PyPy ou Rubinius), é realmente escrita em outros idiomas, como C ++ ou C.

Mais uma vez, correto. Esses são realmente exemplos ruins. Veja Rubinius, por exemplo. Sim, é verdade que a parte Ruby do Rubinius é auto-hospedada, mas é um compilador, não um intérprete: compila no código-fonte do Ruby o código de bytes do Rubinius. A parte do intérprete OTOH não é auto-hospedada: interpreta o bytecode do Rubinius, mas é escrita em C ++. Portanto, chamar Rubinius de "intérprete auto-hospedado" está errado: a parte auto-hospedada não é um intérprete e a parte intérprete não é auto-hospedada .

O PyPy é semelhante, mas ainda mais incorreto: nem mesmo está escrito em Python, mas em RPython, que é uma linguagem diferente. É sintaticamente semelhante ao Python, semanticamente um "subconjunto estendido", mas na verdade é uma linguagem de tipo estatístico aproximadamente no mesmo nível de abstração que Java, e sua implementação é um compilador com vários back-end que compila RPython para código-fonte C, ECMAScript código-fonte, código de byte CIL, bytecode da JVM ou código-fonte Python.

Então, o que eu descrevo acima é possível? Um intérprete de host próprio pode ser independente do host original? Se sim, como exatamente isso seria feito?

Não, não por si só. Você precisaria manter o intérprete original ou escrever um compilador e compilar seu auto-intérprete.

Não são alguns meta-circular VMs, como Klein (escrito em auto ) e Maxine (escrito em Java). Observe, no entanto, que aqui a definição de "meta-circular" ainda é diferente: essas VMs não são escritas na linguagem que executam: Klein executa o auto bytecode, mas é escrito em Self, Maxine executa o bytecode da JVM, mas é escrito em Java. No entanto, o código-fonte Self / Java da VM é realmente compilado no bytecode Self / JVM e, em seguida, executado pela VM; portanto, quando a VM é executada, ela está no idioma que é executado. Ufa.

Observe também que isso é diferente das VMs, como o SqueakVM e o Jikes RVM . O Jikes é escrito em Java, e o SqueakVM é escrito em Slang (um subconjunto sintático e semântico do Smalltalk estaticamente tipificado, aproximadamente no mesmo nível de abstração que um assembler de alto nível) e ambos são compilados estaticamente no código nativo antes de serem executados. Eles não correm dentro de si mesmos. No entanto, você pode executá-los por cima deles mesmos (ou por cima de outra VM / JVM Smalltalk). Mas isso não é "meta-circular" nesse sentido.

Maxine e Klein, OTOH fazemcorrer dentro de si mesmos; eles executam seu próprio bytecode usando sua própria implementação. Isso é realmente impressionante! Ele permite algumas oportunidades interessantes de otimização, por exemplo, como a VM se executa em conjunto com o programa do usuário, pode integrar chamadas do programa do usuário para a VM e vice-versa, por exemplo, chamar o coletor de lixo ou o alocador de memória pode ser incorporado ao usuário código e retornos de chamada reflexivos no código do usuário podem ser incorporados na VM. Além disso, todos os truques inteligentes de otimização que as VMs modernas fazem, onde assistem ao programa em execução e o otimizam, dependendo da carga de trabalho e dos dados reais, a VM pode aplicar esses mesmos truques a si mesma enquanto executa o programa do usuário enquanto o programa do usuário está executando a carga de trabalho específica. Em outras palavras, a VM se especializou muito para issoprograma específico executando essa carga de trabalho específica.

No entanto, observe que eu evitei o uso da palavra "intérprete" acima e sempre usei "executar"? Bem, essas VMs não são construídas em torno de intérpretes, elas são construídas em torno de compiladores (JIT). Houve um intérprete adicionado ao Maxine posteriormente, mas você sempre precisa do compilador: é necessário executar a VM uma vez em cima de outra VM (por exemplo, Oracle HotSpot no caso de Maxine), para que a VM possa (JIT) se compilar. No caso de Maxine, o JIT compila sua própria fase de inicialização, depois serializa o código nativo compilado em uma imagem de VM de inicialização e coloca um carregador de inicialização muito simples na frente (o único componente da VM escrito em C, embora isso seja apenas por conveniência) , também pode estar em Java). Agora você pode usar o Maxine para se executar.

Jörg W Mittag
fonte
Yeesh . Eu nunca soube que o mundo dos intérpretes autônomos era tão complicado! Obrigado por fornecer uma boa visão geral.
Christian Dean
1
Haha, bem, por que o mundo deveria ser menos preocupante do que o conceito? ;-)
Jörg W Mittag
3
Acho que um dos problemas é que as pessoas costumam jogar rápido e solto com os idiomas envolvidos. Por exemplo, Rubinius é geralmente chamado de "Ruby in Ruby", mas isso é apenas metade da história. Sim, estritamente falando , o compilador Ruby no Rubinius é escrito em Ruby, mas a VM que executa o bytecode não é. E ainda pior: o PyPy costuma ser chamado de "Python em Python", exceto que na verdade não existe uma única linha de Python lá. A coisa toda está escrita em RPython, que é projetado para ser familiar aos programadores de Python, mas não é Python . Da mesma forma SqueakVM: não está escrito em Smalltalk, é…
Jörg W Mittag
… Está escrito em Gíria, que de acordo com as pessoas que realmente o codificaram, é ainda pior que C em suas capacidades de abstração. A única vantagem da Slang é que é um subconjunto adequado do Smalltalk, o que significa que você pode desenvolvê-lo (e executar e, o mais importante, depurar a VM) um poderoso IDE do Smalltalk.
Jörg W Mittag
2
Apenas para adicionar outra complicação: Algumas linguagens interpretadas (por exemplo, FORTH e possivelmente TeX) são capazes de gravar uma imagem de memória carregável do sistema em execução, como um arquivo executável. Nesse sentido, esses sistemas podem ser executados sem o intérprete original. Por exemplo, uma vez eu escrevi um interpretador FORTH usando uma versão de 16 bits do FORTH para "interpretar de forma cruzada" uma versão de 32 bits para uma CPU diferente e executar em um sistema operacional diferente. (Note, a linguagem FORTH inclui seu próprio montador, de modo a "ADIANTE VM" (que é normalmente apenas 10 ou 20 instruções de código de máquina) pode ser escrita em si mesmo diante.)
alephzero
7

Você está certo ao observar que um intérprete de hospedagem automática ainda exige que um intérprete seja executado por si mesmo e não pode ser inicializado no mesmo sentido que um compilador.

No entanto, um idioma auto-hospedado não é a mesma coisa que um intérprete auto-hospedado. Geralmente é mais fácil criar um intérprete do que criar um compilador. Portanto, para implementar um novo idioma, podemos primeiro implementar um intérprete em um idioma não relacionado. Em seguida, podemos usar esse intérprete para desenvolver um compilador para a nossa linguagem. O idioma é auto-hospedado, pois o compilador é interpretado. O compilador pode então se compilar e pode ser considerado totalmente inicializado.

Um caso especial disso é um tempo de execução de compilação de JIT auto-hospedado. Ele pode começar com um intérprete em um idioma host, que usa o novo idioma para implementar a compilação JIT, após o qual o compilador JIT pode se compilar. Parece um intérprete auto-hospedado, mas evita o problema dos infinitos intérpretes. Essa abordagem é usada, mas ainda não é muito comum.

Outra técnica relacionada é um intérprete extensível, onde podemos criar extensões no idioma que está sendo interpretado. Por exemplo, podemos implementar novos códigos de operação no idioma. Isso pode transformar um intérprete básico em um intérprete rico em recursos, desde que evitemos dependências circulares.

Um caso que ocorre com bastante frequência é a capacidade do idioma de influenciar sua própria análise, por exemplo, como macros avaliadas no tempo de análise. Como o idioma da macro é o mesmo que o idioma que está sendo processado, ele tende a ser muito mais rico em recursos do que os idiomas de macro dedicados ou restritos. No entanto, é correto observar que o idioma que executa a extensão é um idioma ligeiramente diferente do idioma após a extensão.

Quando são usados ​​intérpretes auto-hospedados "reais", isso geralmente é feito por razões de educação ou pesquisa. Por exemplo, implementar um intérprete para o Scheme dentro do Scheme é uma maneira legal de ensinar linguagens de programação (consulte SICP).

amon
fonte