Os escritores-compiladores realmente precisam 'entender' o código da máquina? [fechadas]

10

Pode ser uma pergunta estranha.

Um cara que escreve um compilador C ++ (ou qualquer outra linguagem que não seja a VM): ele precisa ser capaz de ler / escrever a linguagem de máquina bruta? Como isso funciona?

Edição: Refiro-me especificamente aos compiladores que compilam o código da máquina, não a alguma outra linguagem de programação.

Aviv Cohn
fonte
1
Não. Você não precisa mesmo de conhecê-lo, você pode apenas cega, copie sem pensar sua especificação ISA-alvo: dl.acm.org/citation.cfm?id=1706346
SK-lógica
1
Coffescript compila para javascript.
Kartik
@Kartik O compilador CoffeeScript que compila para Javascript também inclui um compilador Javascript que compila para qualquer Javascript que seja compilado? Ou ele apenas compila o código-fonte Javascript e nada mais?
Aviv Cohn
O compilador coffeescript simplesmente converte cofeescript em javascript. Javascript não é compilado, é manipulado pelo navegador. Eu queria dizer que você pode escrever um compilador que compila um idioma para outro, não precisa saber a linguagem de máquina para isso. Outro exemplo é o compilador 'SPL' que compila as reproduções de Shakespeare para C ++. shakespearelang.sourceforge.net
Kartik

Respostas:

15

Não, não mesmo. É perfeitamente possível (e geralmente até preferível) para o seu compilador emitir o código do assembly. O montador cuida da criação do código de máquina real.

A propósito, sua distinção entre implementação não VM e implementação VM não é útil.

  • Para iniciantes, usar uma VM ou pré-compilação no código da máquina são apenas maneiras diferentes de implementar uma linguagem; na maioria dos casos, um idioma pode ser implementado usando qualquer estratégia. Na verdade, tive que usar um intérprete de C ++ uma vez.

  • Além disso, muitas VMs como a JVM possuem um código de máquina binário e algum assembler, assim como uma arquitetura comum.

O LLVM (que é usado pelos compiladores Clang) merece menção especial aqui: define uma VM para a qual as instruções podem ser representadas como código de bytes, montagem textual ou estrutura de dados que facilita a emissão de um compilador. Portanto, embora seja útil para depuração (e para entender o que você está fazendo), você nem precisa saber sobre a linguagem assembly, apenas sobre a API LLVM.

O bom do LLVM é que sua VM é apenas uma abstração e que o código de bytes geralmente não é interpretado, mas JITted de forma transparente. Portanto, é perfeitamente possível escrever uma linguagem que seja efetivamente compilada, sem precisar saber sobre o conjunto de instruções da sua CPU.

amon
fonte
E outra propriedade interessante do LLVM é que não é necessário entender profundamente o ISA de destino para implementar um back-end eficiente. É bastante declarativo, então é possível quase copiar e colar uma especificação ISA em arquivos .td sem sequer tentar entendê-la.
SK-lógica
Obrigado por responder. Pergunta: Entendo pela sua resposta e pelas respostas de outras pessoas que o compilador-escritor não precisa entender o código da máquina, ele pode usar outra ferramenta que faz a conversão em código da máquina real para ele. No entanto, o cara que escreveu essa ferramenta precisava entender a linguagem de máquina, certo? O cara que escreveu o software que faz a conversão real de algum idioma para código de máquina precisa realmente entender a linguagem de máquina, certo?
Aviv Cohn
5
@ Pro sim. Se você criar uma camada de abstração, precisará entender apenas a camada abaixo de você. Embora seja útil ter um entendimento básico de todas as camadas, isso não é realmente necessário. Você não precisa entender a física quântica para usar um transistor. Você não precisa entender o design do chip para usar uma CPU. Você não precisa saber o código da máquina para escrever a montagem. Você não precisa conhecer o conjunto de instruções da plataforma ao usar uma VM, etc. Mas alguém teve que criar esses níveis de abstração abaixo do seu.
amon
1
@ SK-logic Não é verdade (pelo menos se você quiser um bom código) pelo que ouvi. As pessoas que implementaram o back-end Aarch64 para llvm tiveram alguns desafios (realocações para um, padrões de armazenamento de carga horríveis, ..). E isso é ignorar o maior elefante na sala: modelo de memória do ISA e do modelo de memória do idioma que você está em interessado Você pode trabalhar em um compilador, mas você não pode trabalhar em um backend sem compreender a arquitetura ...
Voo
1
Eu acrescentaria que realmente não há diferença substancial entre montagem e código de máquina, conceitualmente. Você não terá muito benefício, uma vez sabendo como usar uma instrução específica, qual é o código de operação dessa instrução. Se você sabe como usar o MOV, não importa se você não sabe que é a instrução 27, que eu acho que é semelhante ao que a @ SK-logic está descrevendo.
Whatsisname
9

Não. O ponto principal da sua pergunta é que a compilação é um termo extremamente amplo. A compilação pode acontecer de qualquer idioma para qualquer idioma. E o código de montagem / máquina é apenas um dos muitos idiomas para o destino de compilação. Por exemplo, linguagens Java e .NET como C #, F # e VB.NET são compiladas para algum tipo de código intermediário, em vez de código específico da máquina. Não importa se é executado na VM, o idioma ainda está compilado. Também há uma opção para compilar em outro idioma, como C. C é realmente um destino de compilação bastante popular e muitas ferramentas fazem isso. E, finalmente, você pode usar alguma ferramenta ou biblioteca para fazer o trabalho duro de produzir código de máquina para você. há, por exemplo, LLVM que pode reduzir o esforço necessário para criar um compilador independente.

Além disso, sua edição não faz nenhum sentido. É como perguntar "Todo engenheiro precisa entender como o motor funciona? E eu estou perguntando sobre engenheiros trabalhando em motores". Se você estiver trabalhando em um programa ou biblioteca que emita um código de máquina, é necessário entendê-lo. O ponto é que você não precisa fazer isso ao escrever o compilador. Muitas pessoas fizeram isso antes de você, então você precisa ter motivos sérios para fazê-lo novamente.

Eufórico
fonte
E a pessoa que está escrevendo a ferramenta ou biblioteca que efetua a conversão para a linguagem de máquina precisa entender completamente a linguagem de máquina, certo?
Aviv Cohn
3
@Prog Você precisa entender completamente uma linguagem de programação para programar nela? Não, mas você possivelmente escreverá um código abaixo do ideal e não poderá fazer certas coisas que outras pessoas possam fazer. Você precisa entender completamente a linguagem de máquina se escrever um compilador que seja traduzido para isso. Não, mas seu compilador será abaixo do ideal e incapaz de fazer certas coisas.
usar o seguinte código
@ Sumurai8: embora, até certo ponto, você possa "entender" a linguagem da máquina por partes, a fim de escrever um emissor de código de máquina que entenda tudo melhor do que você. Por exemplo, se você escrever uma boa estrutura, poderá configurar a definição de cada código de operação, juntamente com seus custos e considerações de pipelining, e então sua estrutura poderá escrever código de máquina otimizado, mesmo que você não tenha experiência em otimizar essa máquina específica. Ser capaz de programar esse código de máquina com competência provavelmente não faz mal.
21714 Steve Jobs (
@SteveJessop Se você entende cada código de operação a um ponto em que pode aprender uma máquina como encadear esse código de operação junto com outros códigos de operação para expressar um conceito de nível superior, você entende completamente a linguagem de máquina. Está, então, apenas com preguiça de encontrar a melhor solução para todos os problemas lá fora ;-)
Sumurai8
@ Sumurai8: hmm, mas pelo menos em princípio eu poderia "entender" cada código de operação brevemente pelos cinco minutos que levo para configurá-lo, e depois o esqueceria quando eu "entender" o código de operação depois da próxima. Provavelmente não é isso que o questionador quer dizer com "ser capaz de ler / escrever linguagem de máquina bruta". É claro que estou assumindo uma estrutura bastante boa aqui, configurável o suficiente para definir e usar todas as informações úteis sobre cada código de operação do conjunto de instruções. O LLVM visa um pouco a isso, mas de acordo com o "Voo" (em um comentário abaixo) não o atingiu.
21714 Steve Jobs (
3

Classicamente, um compilador possui três partes: análise lexical, análise e geração de código. A análise lexical divide o texto do programa em palavras-chave, nomes e valores do idioma. A análise mostra como os tokens provenientes da análise lexical são combinados em declarações sintaticamente corretas para o idioma. A geração de código pega as estruturas de dados produzidas pelo analisador e as converte em código de máquina ou alguma outra representação. Atualmente, a análise e análise lexical podem ser combinadas em uma única etapa.

Claramente, a pessoa que escreve o gerador de código precisa entender o código da máquina de destino em um nível muito profundo, incluindo conjuntos de instruções, pipelines de processador e comportamento do cache. Caso contrário, os programas produzidos pelo compilador seriam lentos e ineficientes. Eles podem muito bem ler e escrever código de máquina como representado por números octais ou hexadecimais, mas geralmente escrevem funções para gerar o código de máquina, consultando internamente as tabelas de instruções da máquina. Teoricamente, as pessoas que escrevem o lexer e o analisador podem não saber nada sobre a geração do código da máquina. De fato, alguns compiladores modernos permitem que você conecte suas próprias rotinas de geração de código, que podem emitir código de máquina para alguma CPU de que os gravadores lexer e analisador nunca ouviram falar.

No entanto, na prática, os escritores do compilador em cada etapa conhecem muito sobre diferentes arquiteturas de processador, e isso os ajuda a projetar as estruturas de dados que a etapa de geração de código precisará.

Charles E. Grant
fonte
2

Há muito tempo, escrevi um compilador que foi convertido entre dois scripts shell diferentes. Não chegou nem perto do código da máquina.

Uma gravação do compilador precisa entender sua saída , mas isso geralmente não é um código de máquina.

A maioria dos programadores nunca escreve um compilador que gera código de máquina ou código de montagem, mas compiladores personalizados podem ser muito úteis em muitos projetos para produzir outros resultados.

O YACC é um desses compiladores que não gera código de máquina….

Ian
fonte
0

Você não precisa começar com um conhecimento detalhado da semântica de suas linguagens de entrada e saída, mas é melhor terminar com um conhecimento requintadamente detalhado de ambos, caso contrário, seu compilador será inutilmente incorreto. Portanto, se sua entrada for C ++ e sua saída for uma linguagem de máquina específica, você precisará conhecer a semântica de ambas.

Aqui estão algumas das sutilezas na compilação de C ++ para código de máquina: (bem no alto da minha cabeça, tenho certeza de que há mais coisas que estou esquecendo.)

  1. Qual será o tamanho int? A escolha "correta" aqui é uma arte, baseada no tamanho natural do ponteiro da máquina, no desempenho da ALU para vários tamanhos de operações aritméticas e nas escolhas feitas pelos compiladores existentes para a máquina. A máquina possui aritmética de 64 bits? Caso contrário, a adição de números inteiros de 32 bits deve ser convertida em uma instrução, enquanto a adição de números inteiros de 64 bits deve ser convertida em uma chamada de função para fazer a adição de 64 bits. A máquina possui operações de adição de 8 e 16 bits ou você precisa simular aquelas com operações e mascaramento de 32 bits (por exemplo, o DEC Alpha 21064)?

  2. Qual é a convenção de chamada usada por outros compiladores, bibliotecas e idiomas na máquina? Os parâmetros são pressionados na pilha da direita para a esquerda ou da esquerda para a direita? Alguns parâmetros entram nos registradores enquanto outros ficam na pilha? Ints e floats estão em diferentes espaços de registro? Os parâmetros alocados do registro precisam ser tratados especialmente em chamadas varargs? Quais registros são salvos pelo chamador e quais são salvos pelo chamado? Você pode executar otimizações de chamada em folha?

  3. O que cada uma das instruções de turno da máquina faz? Se você pedir para mudar um número inteiro de 64 bits por 65 bits, qual é o resultado? (Em muitas máquinas, o resultado é o mesmo que mudar 1 bit; em outras, o resultado é "0".)

  4. Quais são as semânticas de consistência de memória da máquina? O C ++ 11 possui uma semântica de memória muito bem definida que impõe restrições a algumas otimizações em alguns casos, mas permite otimizações em outros casos. Se você estiver compilando uma linguagem que não possui semânticas de memória bem definidas (como todas as versões do C / C ++ anteriores ao C ++ 11 e muitas outras linguagens imperativas), precisará inventar a semântica de memória à medida que avança e, geralmente, você desejará inventar a semântica da memória que melhor corresponda à semântica da sua máquina.

Lógica Errante
fonte