/lasm

Отлаживаемый симулятор ассемблера

Primary LanguageTypeScriptGNU General Public License v3.0GPL-3.0

Симулятор учебного псевдоассемблера

Симулируемая машина

Симулируется Load-Store машина с регистрами общего назначения:

  • 16 целочисленных 32-битных регистров (R0 ... R15)
  • 16 вещественных 64-битных регистров (F0 ... F15)
  • Служебные регистры
    • FLAGS - хранит результат выполнения инструкции CMP и используется инструкциями условного перехода
    • IP - Instruction Pointer (также известный, как Program Counter / PC), хранит адрес следующей исполняемой инструкции
    • SP - Stack Pointer, указатель на адрес вершины стека

Режимы адресации

В данной реализации поддерживаются следующие режимы адресации:

  • Регистровый
    ADD R1, R2, R1
    
  • Со смещением
    ADD R1, [10+R2], R1
    
  • Косвенный
    ADD R1, [R2], R1
    
  • Индексный
    ADD R1, [R2+R3], R1
    
  • Абсолютный
    ADD R1, [1001], R1
    

Набор инструкций

Поддерживаемые инструкции:

  • Перемещение данных

    • Загрузка из адреса в регистр
      LD [addr], reg
      
    • Сохранение данных из регистра в память
      ST reg, [addr]
      
    • Перемещение данных из регистра R1 в регистр R2
      MOV R1, R2
      
  • Арифметическо-логические операции

    • Запись суммы значений регистров R1 и R2 в регистр R3
    ADD R1, R2, R3
    FADD R1, R2, R3
    
    • Запись разности значений регистров R1 и R2 в регистр R3
    SUB R1, R2, R3
    FSUB R1, R2, R3
    
    • Запись произведения значений регистров R1 и R2 в регистр R3
    MUL R1, R2, R3
    FMUL R1, R2, R3
    
    • Запись частного значений регистров R1 и R2 в регистр R3
    DIV R1, R2, R3
    FDIV R1, R2, R3
    
    • Записать побитовое "И" значений регистров R1 и R2 в регистр R3
    OR R1, R2, R3
    
    • Записать побитовое "ИСКЛЮЧАЮЩЕЕ ИЛИ" значений регистров R1 и R2 в регистр R3
    XOR R1, R2, R3
    
    • Записать побитовый сдвиг вправо значений регистров R1 и R2 в регистр R3
    SHR R1, R2, R3
    
    • Записать побитовый сдвиг влево значений регистров R1 и R2 в регистр R3
    SHL R1, R2, R3
    
    • Сравнить значения регистров R1 и R2
    CMP R1, R2
    
    • Инкремент значения регистра R1
    INC R1
    
    • Декремент значения регистра R1
    DEC R1
    
  • Управление порядком выполнения программы

    • Безусловный переход
      JMP [addr]
      
    • Equal - переход, если CMP указал на равенство значений операндов
      JE [addr]
      
    • Not Equal - переход, если CMP указал на неравенство значений операндов
      JNE [addr]
      
    • Less - переход, если CMP указал на то, что значение первого операнда меньше значения второго
      JL [addr]
      
    • Less or Equal - переход, если CMP указал на то, что значение первого операнда меньше или равно значению второго
      JLE [addr]
      
    • Greater - переход, если CMP указал на то, что значение первого операнда больше значения второго
      JG [addr]
      
    • Greater or Equal - переход, если CMP указал на то, что значение первого операнда больше или равно значению второго
      JGE [addr]
      
    • Вызов функции
      CALL [function name]
      
    • Возврат из функции
      RET
      

На данный момент поддержка функций не реализована полностью (см. ниже)

Симулятор

Парсер

Линейный синтаксис описанного языка позволяет использовать для разбора максимально примитивный алгоритм - язык не поддерживает ветвления, поэтому результатом парсинга является не дерево, а обычный список, в котором позиции инструкций используются как адреса.

При парсинге происходит разбор динамических адресов со смещением в более удобную для последующей обработки структуру, статические же адреса со смещением разрешаются в конечный адрес.

Непосредственно при разборе каждой инструкции выполняется минимальный статический анализ кода с учетом информации о структуре памяти.

О вызове функций и стековой памяти

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

Реализация стековой памяти по состоянию на момент публикации исходного кода также находится в незавершенном состоянии.

Среда выполнения

Выполнение кода происходит в созданном от набора инструкций и начального состояния памяти изолированном контексте.

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

Во избежание зацикливания, исполнение программы будет прервано по достижении максимальной глубины вызовов.

Ограничения

И целые, и вещественные числа в JavaScript имеют один и тот же тип, из-за чего возникает возможность неправильной работы симулятора при неправильно поданных вводных данных. Пользовательский интерфейс для предотвращения таких ситуаций накладывает рестрикции на формат введенных чисел в зависимости от указанного пользователем типа ячейки памяти, однако, сам движок, в случае непосредственного вызова, таких гарантий дать не может.

Также следует заметить, что в описанной машине целые числа занимают 32 бита, в то время, как вещественные - 64 бита, JavaScript же хранит все числа в 64 битах. Это нужно учитывать при разработке программ - обработка целочисленного переполнения симулятором не реализована (хотя реализовать это несложно), при работе с большими числами следует использовать вещественный тип для их хранения.

Примеры программ

В качестве примера, доступны две программы - возведение числа в степень и поиск максимума в массиве.

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