Пишем упрощенный ассемблер и виртуальный процессор — часть 1

11 комментариев

В последнее время я очень интересовался компиляторами, низкоуровневыми языками программирования и архитектурой компьютера. Однако на хабре про компиляторы низкоуровневых языков, нашел очень мало информации. Поэтому я решил рассказать о ассемблере и написать свой. Весь код в статье написан на языке Си из-за быстроты программ, написанных на нем, и его простоты.

Набор команд и грамматика

Набор команд не должен быть большим. У меня получилось всего 18 команд. Теперь, давайте определимся что наш виртуальный процессор должен уметь делать. Прежде всего, это пересылка данных, работа со стеком и базовая арифметика.

Но как же процессор будет давать/получать данные о своей работе? Для это нам нужно будет сделать ввод-вывод и возврат результата выполнения. Все. Никаких функций, переменных и переходов. Только хардкор. Однако язык будет условным, как ARM condition language. Раз он условный, нужно вырабатывать условия, пусть это будут делать сравнения.

Теперь простейший пример программы на языке:

al mov r00, #0

al cmp r00, #400

av return #0

le mul r01, r00, r00
le swi #5
le inc r00
le mov r14, #1

Разбор кода:

  • al mov r00, #0 — в любом случае r00 = 0
  • al cmp r00, #400 — в любом случае сравним r00 и 400
  • av return 0 — если r00 больше, завершаем выполнение с кодом 0
  • le mul r01, r00, r00 — если r00 меньше или равно, r01 = r00 * r00
  • le swi #5 — если r00 меньше или равно, закрашиваем пиксель(r00, r01)
  • le inc r00 — если r00 меньше или равно, увеличим r00 на 1.
  • le mov r14, #1 — если r00 меньше или равно, счетчик программы = 1

Для начала напишем парсер, создающий из входной строки объектное (общее) представление кода. Самый простой способ это сделать — сохранить условие, команду и аргументы в структуру.

Заголовочный файл:

#ifndef PARSER_H
#define PARSER_H

struct operation {
    char cond[32]; // Условие выполнения
    char cmd[32];  // Сама команда
    char arg0[32]; // Первый аргумент
    char arg1[32]; // Второй аргумент
    char arg2[32]; // Третий агрумент
};

void dump_operation(struct operation *op); // Вывод данных операции
struct operation *parse_code(const char *code); // Парсит строку в общее представление кода

#endif // PARSER_H

Исходный файл:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "parser.h"

#define SON(x) (((x) && (*(x) != '\0'))?(x):"NONE") //Если строка пуста или указатель нулевой,  то выдаем NONE (для dump_operation)

void dump_operation(struct operation *op)
{
    if (!op) { // Если дан неверный указатель
        printf("OP: NULL\n");
        return;
    }

        // Если указатель в порядке
    printf("COND: \"%s\"\nOP: \"%s\"\nARGS: \"%s\", \"%s\", \"%s\"\n\n",
               SON(op->cond), SON(op->cmd), SON(op->arg0),
               SON(op->arg1), SON(op->arg2));
}

static inline const char *skip(const char *line, char *skips) // Пропуск строки в строке
{
    line += strlen(skips); // Увеличим указатель на пропарсиваемую строку на длину пропускаемой
    while ((*line == ' ') || (*line == ',')) // Пропустим ненужные символы
        line++;

    if ((*line == '\0') || (*line == '\n') || (*line == ';')) // Если конец строки 
        return 0; // Вернем нулевой указатель

    return line; // Иначе вернем полученный указатель
}

static inline void str_to_lower(char *str) // Переводит строку в нижний регистр
{
    unsigned int l = strlen(str);
    unsigned int i = 0;

    for (i = 0; i < l; i++)
        str[i] = (char)tolower(str[i]);
}

struct operation *parse_code(const char *code)
{
    struct operation *op = (struct operation*)calloc(1, sizeof(*op)); // Выделим память под результат
    char *tokens[5] = {op->cond, op->cmd, op->arg0, op->arg1, op->arg2};
    unsigned int i = 0;

    for (i = 0; i < 5; i++) {
            if (sscanf(code, "%[0-9a-zA-Z@#$]", tokens[i]) <= 0/*Получаем строку в текущий токен*/) { // Если встретили запрещенный символ
            fprintf(stderr, "Error!\nUnknown symbol!\n"); // Говорим об этом

            free((void *)op); // Освобождаем память
            return 0; // Возвращаем нулевой указатель
            }

        str_to_lower(tokens[i]); // Переводим строку в нижний регистр

        code = skip(code, tokens[i]); // Пропускаем полученный токен

        if (!code) // Если код закончился
                    break; // Завершаем цикл
    }

    return op; // Возвращаем объектное представление кода
}

В следующей части расскажу об анализе кода и переводе его в байт-код.

Литература по теме

  • Э. Таненбаум Т.Остин Архитектура компьютера А. Ахо, Р. Сети, Д.
  • Ульман Компиляторы: принципы, технологии и инструменты

P.S. Народ, комментируйте, что не нравится. Постараюсь исправить. Заранее спасибо!

Комментарии к статье: 11

Подождите, загружаются комментарии...

Возможность комментировать эту статью отключена автором. Возможно, во всем виновата её провокационная тематика или большое обилие флейма от предыдущих комментаторов.

Если у вас есть вопросы по содержанию статьи, рекомендуем вам обратиться за помощью на наш форум.