Conversão de AST para Koopa IR e Geração de Código RISC-V

A construção de um compilador envolve etapas fundamentais de transformação de representações. Após a estruturação da Árvore de Sintaxe Abstrata (AST), o próximo passo consiste na geração de uma Representação Intermediária (IR) — neste caso, a Koopa IR — e sua subsequente tradução para linguagem de montagem (Assembly) RISC-V.

Geração de Koopa IR via Travessia de AST

Para gerar a Koopa IR, percorremos a AST de forma recursiva, convertendo cada nó em sua respectiva representação textual. O uso da classe std::ostringstream em C++ facilita a concatenação de strings complexas durante esse processo.

Abaixo, um exemplo de implementação de uma classe para definição de funções, demonstrando como a estrutura da AST pode ser mapeada para o formato da Koopa IR:


#include <iostream>
#include <memory>
#include <sstream>
#include <string>

const char* TAB = "  ";

class NodeBase {
public:
    virtual ~NodeBase() = default;
    virtual void emitIR(std::ostream &stream, std::string indent) const = 0;
};

class FuncDeclNode : public NodeBase {
public:
    std::unique_ptr<NodeBase> return_type;
    std::string name;
    std::unique_ptr<NodeBase> body;

    void emitIR(std::ostream &stream, std::string indent) const override {
        stream << indent << "fun @" << name << "(): ";
        return_type->emitIR(stream, "");
        stream << " {\n";
        body->emitIR(stream, indent + TAB);
        stream << "\n" << indent << "}";
    }
};

Ao utilizar std::ostringstream, é importante atentar para o fato de que o método .str() retorna um objeto temporário. Para processar o conteúdo com a biblioteca libkoopa (usando koopa_parse_from_string), é necessário armazenar esse resultado em uma variável std::string persistnete para garantir que o ponteiro de caracteres permaneça válido durante o parsing.

Tradução de Koopa IR para RISC-V

Uma vez obtida a representação em Koopa IR, o compilador deve traduzir esses comandos para instruções de máquina. O processo de conversão para RISC-V segue uma lógica similar à geração da IR: percorre-se a estrutura de dados da koopa_raw_program_t (gerada pelo parser da biblioteca) e emite-se o código assembly correspondente para cada instrução, gerenciando registradores e pilha.

Gerenciamento de Argumentos via Linha de Comando

Para que o compilador seja funcional, ele deve aceitar parâmetros que definam o modo de operação (gerar IR ou gerar Assembly) e o caminho do arquivo de saída. A função getopt_long_only da biblioteca padrão C é ideal para tratar argumentos que utilizam apenas um hífen, como exigido em certas especificações acadêmicas.


#include <getopt.h>
#include <string>
#include <cassert>

struct Config {
    bool emit_ir_only = false;
    std::string output_file;
};

void parse_cli(int argc, char **argv, Config &config) {
    static struct option options[] = {
        {"koopa",  no_argument,       nullptr, 'k'},
        {"riscv",  no_argument,       nullptr, 'r'},
        {"output", required_argument, nullptr, 'o'},
        {nullptr,  0,                 nullptr,  0 }
    };

    int opt;
    while ((opt = getopt_long_only(argc, argv, "ko:", options, nullptr)) != -1) {
        switch (opt) {
            case 'k':
                config.emit_ir_only = true;
                break;
            case 'r':
                config.emit_ir_only = false;
                break;
            case 'o':
                config.output_file = optarg;
                break;
            case '?':
                exit(1);
            default:
                assert(false && "Opção não implementada");
        }
    }
}

Esta abordagem permite que o binário suporte chamadas como compiler -koopa input.sysy -o output.koopa, garantindo flexibilidade no fluxo de compilação.

Tags: C++ Compiladores RISC-V KoopaIR AbstractSyntaxTree

Publicado em 7-27 09:03