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.
compiler
machine-code
Aviv Cohn
fonte
fonte
Respostas:
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.
fonte
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.
fonte
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á.
fonte
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….
fonte
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.)
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)?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?
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".)
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.
fonte