Pergunta

A fundação enganosamente simples de geração de código dinâmico dentro da estrutura de um C / C ++ já foi coberto em outra pergunta . Há algum apresentações suaves em tópico com exemplos de código?

Meus olhos estão começando a sangrar olhando para compiladores JIT de código aberto altamente complexas quando as minhas necessidades são muito mais modesto.

Existem bons textos sobre o assunto que não assuma um doutorado em ciência da computação? Eu estou procurando assim padrões de gastos, coisas que atente para, considerações de desempenho, etc. eletrônico ou recursos baseados na árvore pode ser igualmente valioso. Você pode assumir um conhecimento prático de (não apenas x86) linguagem assembly.

Foi útil?

Solução

Bem, um padrão que eu usei em emuladores é algo como isto:

typedef void (*code_ptr)();
unsigned long instruction_pointer = entry_point;
std::map<unsigned long, code_ptr> code_map;


void execute_block() {
    code_ptr f;
    std::map<unsigned long, void *>::iterator it = code_map.find(instruction_pointer);
    if(it != code_map.end()) {
        f = it->second
    } else {
        f = generate_code_block();
        code_map[instruction_pointer] = f;
    }
    f();
    instruction_pointer = update_instruction_pointer();
}

void execute() {
    while(true) {
        execute_block();
    }
}

Esta é uma simplificação, mas a ideia está lá. Basicamente, cada vez que o motor é solicitado a executar um "bloco básico" (geralmente um tudo até próxima op controle de fluxo ou função inteira no possível), ele irá procurá-lo para ver se ele já foi criado. Se assim for, executá-lo, o mais criá-lo, adicioná-lo e, em seguida, executar.

lavagem de repetição:)

Quanto à geração de código, que fica um pouco complicado, mas a idéia é emitir uma "função" adequada, que faz o trabalho de seu bloco básico no contexto da sua VM.

EDIT: Note que eu não demonstraram quaisquer otimizações quer, mas você pediu uma "introdução suave"

EDIT 2: Eu esqueci de mencionar uma das velocidades mais imediatamente produtivos ups que você pode implementar com esse padrão. Basicamente, se você não remover um bloco de sua árvore (você pode trabalhar em torno dele se fazer, mas é maneira mais simples se você nunca fazer), então você pode blocos "cadeia" em conjunto para pesquisas Evitar. Aqui está o conceito. Sempre que você retornar de f () e está prestes a fazer o "update_instruction_pointer", se o bloco que você executado apenas terminou em qualquer uma chamada, salto incondicional, ou não terminou em controle de fluxo em tudo, então você pode "correção" a sua instrução RET com um jmp direto para o próximo bloco que vai executar (causa que vai ser sempre o mesmo) se você já emited-lo. Isso faz com que você está executando mais e mais frequentemente no VM e cada vez menos na função "execute_block".

Outras dicas

Eu não estou ciente de quaisquer fontes especificamente relacionadas com EIC, mas eu imagino que é muito bonito como um compilador normal, única simples se você não está preocupado com o desempenho.

A maneira mais fácil é começar com um intérprete de VM. Então, para cada instrução VM, gerar o código de montagem que o intérprete teria executado.

Para ir além disso, eu imagino que você iria analisar os códigos de byte VM e convertê-los em algum tipo de forma intermediária adequada (três código de endereço? SSA?) E, em seguida, otimizar e gerar código como em qualquer outro compilador.

Para uma pilha com base VM, ele pode ajudar a manter o controle da profundidade da pilha "atual" como você traduzir os códigos de byte em forma intermediária, e tratar cada local pilha como uma variável. Por exemplo, se você acha que a profundidade da pilha atual é 4, e você verá uma instrução "push", que você pode gerar uma atribuição para "stack_variable_5" e incrementar um contador pilha de tempo de compilação, ou algo parecido. Um "add", quando a profundidade da pilha é de 5 pode gerar o código "stack_variable_4 = stack_variable_4 + stack_variable_5" e diminuir o contador pilha de tempo de compilação.

Também é possível traduzir o código baseado em pilha em árvores de sintaxe. Manter uma pilha de tempo de compilação. Cada instrução "push" provoca uma representação da coisa que está sendo empurrado para ser armazenado na pilha. Operadores criar nós de árvore de sintaxe que incluem seus operandos. Por exemplo, "XY +" pode fazer com que a pilha para conter "var (X)", em seguida, "var var (X) (Y)" e, em seguida, o mais aparece ambas as referências var fora e empurra "plus (var (X), var (Y))".

Arranja uma cópia do livro de Joel Pobar em Rotor (quando ele está fora), e mergulhar através da origem para o SSCLI . Cuidado, mentiras insanidade dentro de:)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top