Skip to content

Ассемблер

VBrazhnik edited this page Jan 3, 2019 · 2 revisions

Синтаксис ассемблера

Язык ассемблера подчиняется правилу «В одной строке — одна инструкция».

Инструкция или оператор (англ. statement) — наименьшая автономная часть языка программирования; команда или набор команд. Программа обычно представляет собой последовательность инструкций.

Источник: Оператор (программирование) — Википедия

Пустые строки, комментарии, как и лишние tab'ы или пробелы, игнорируются.

Комментарий

В header'е op.h есть константа COMMENT_CHAR. Она определяет какой символ, обозначает начало комментария.

В предоставленном файле это октоторп — #.

То есть в качестве комментария будет восприниматься всё между символом # и концом строки.

Комментарий может располагаться в любой части файла.

Пример #1:

# UNIT Factory
# is a programming school

Пример #2:

ld %0, r2    # And it is located in UNIT City

Альтернативный комментарий

В предоставленном архиве vm_champs.tar по пути champs/examples можно найти файл bee_gees.s с кодом чемпиона, который оригинальная программа asm транслирует в байт-код без ошибок.

В коде этого чемпиона встречаются два вида комментариев:

  • стандартный, который был рассмотрен выше;
  • альтернативный, о котором нет информации в subject'e.

Этот альтернативный вид от стандартного и описанного в subject'е отличается лишь символом начала комментария. Вместо октоторпа (#) здесь выступает ;.

Пример использования комментария данного вида:

sti r1, %:live, %1    ; UNIT City is placed in Kyiv, Ukraine

Как с этим жить?

Этот вид комментария не описывается в subject'е, но поддерживается оригинальным транслятором. Поэтому мы, скорее всего, не обязаны его обрабатывать.

Но все же добавим его поддержку в наш проект. Для этого в заголовочный файл op.h будет внесена следующая строчка:

# define ALT_COMMENT_CHAR    ';'

Это будет вторым (первое — приведение к Norm'е) и последним изменением, которое мы внесем в файл op.h.

Имя чемпиона

В файле с кодом чемпиона должно быть определено его имя. Для этого в ассемблере существует команда, имя которой определено в константе NAME_CMD_STRING. В предоставленном файле op.h это .name.

То есть после команды .name должна следовать строка с именем нашего чемпиона:

.name    "Batman"

Длина строки не должна превышать число заданное в константе PROG_NAME_LENGTH. В предоставленном файле оно равно 128.

Кстати, в качестве имени чемпиона также может выступать пустая строка:

.name    ""

Но полное отсутствие строки это уже ошибка:

.name    

Комментарий чемпиона

Также в файле с расширением .s должен быть определен комментарий чемпиона.

Команда, которая поможет это сделать, содержится в константе COMMENT_CMD_STRING файла op.h. В предоставленном файле это .comment.

Длина строки-комментария ограничена константой COMMENT_LENGTH. В предоставленном файле op.h её значение равно 2048.

По своей сути команда .comment очень похожа на .name и ведет себя аналогичным образом в случаях с пустой строкой и в случаях с её полным отсутствием.

Другие команды

В некоторых файлах с расширением .s, которые были предоставлены нам в качестве примера, встречалась такая команда как .extend.

Эта команда, как и любые другие отличные от .name и .comment, не описана в subject'e и оригинальным транслятором определяется как ошибочная.

Таким же образом с подобными командами будем обращаться и мы.

Исполняемый код

Исполняемый код чемпиона состоит из инструкций.

Для языка ассемблера действует правило «Одна строка — одна инструкция». И символом окончания инструкции в этом языке является перевод строки. То есть вместо привычного в языке С символа ; здесь выступает символ \n.

Опираясь на это правило мы должны помнить, что даже после последней инструкции должен следовать перевод строки. Иначе, asm выведет сообщение об ошибке.

Каждая инструкция состоит из нескольких компонентов:

Метка

Метка состоит из символов, которые были определены в константе LABEL_CHARS. В файле-примере это abcdefghijklmnopqrstuvwxyz_0123456789.

То есть в составе метки не могут присутствовать символы, которые не указаны в LABEL_CHARS.

А за самой меткой должен следовать символ определенный в константе LABEL_CHAR. В файле-примере это символ :.

Зачем нужны метки?

Метка указывает на операцию, которая следует прямо за ней. Именно на одну операцию, а не на их блок.

.name       "Batman"
.comment    "This city needs me"

loop:
        sti r1, %:live, %1    # <-- На эту операцию указывает метка loop
live:
        live %0               # <-- На эту операцию указывает метка live
        ld %0, r2             # <-- А на эту операцию никакая метка не указывает
        zjmp %:loop

Задача меток состоит в том, чтобы упростить нашу жизнь, а точнее процесс написания кода.

Чтобы в полной мере осознать их роль, давайте представим мир без меток.

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

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

В рассматриваемом случае нам нужно после каждой итерации цикла снова возвращаться на операцию live. Вот только сколько это байтов назад? Чтобы это выяснить нужно узнать сколько байтов в байт-коде займет код операции и её аргумент.

Как мы узнаем позже, код операции live занимает 1 байт, а её единственному аргументу необходимо 4 байта.

Получается нам нужно вернуться на 5 байтов назад:

live %1
zjmp %-5

Не так уж сложно, но все же подобные расчеты занимают время. И гораздо проще было бы просто написать «перейти на операцию live». Для этого и существуют метки.

Мы просто создаем метку на нужную нам операцию live и передаем её операции zjmp:

loop:    live %1
         zjmp %:loop

С точки зрения трансляции кода на ассемблере в байт-код оба примера абсолютно идентичны. Во время работы программа asm высчитает на сколько байтов назад указывает метка loop и подменит её на число -5.

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

Формы записи

Для записи метки существует несколько подходов:

marker:
        live %0
marker:


        live %0
marker: live %0

Все приведенные выше примеры для программы-транслятора означают одно и тоже.

Поэтому можно выбрать любой из представленных вариантов.

Много меток для одной операции

Также возможна такая форма записи:

marker:

label:
        live %0

Это означает, что и метка marker, и метка label указывают на одну и ту же операцию.

Без операции

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

marker: 
# Конец файла

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

Главное, чтобы в конце линии, на которой она расположена, находился \n. Иначе транслятор сообщит об ошибке.

Операции и их аргументы

Язык ассемблера имеет определенный набор из 16 операций. Каждая из которых принимает от одного до трех аргументов.

Информация об имени операции, её коде, а также об аргументах, которые она принимает, приведена в предоставленном заданием файле op.c.

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
1 live T_DIR
2 ld T_DIR / T_IND T_REG
3 st T_REG T_REG / T_IND
4 add T_REG T_REG T_REG
5 sub T_REG T_REG T_REG
6 and T_REG / T_DIR / T_IND T_REG / T_DIR / T_IND T_REG
7 or T_REG / T_DIR / T_IND T_REG / T_DIR / T_IND T_REG
8 xor T_REG / T_DIR / T_IND T_REG / T_DIR / T_IND T_REG
9 zjmp T_DIR
10 ldi T_REG / T_DIR / T_IND T_REG / T_DIR T_REG
11 sti T_REG T_REG / T_DIR / T_IND T_REG / T_DIR
12 fork T_DIR
13 lld T_DIR / T_IND T_REG
14 lldi T_REG / T_DIR / T_IND T_REG / T_DIR T_REG
15 lfork T_DIR
16 aff T_REG

Разобраться какова роль каждой операции и как она интерпретирует аргументы разных типов это самая важная задача для понимания основ проекта «Corewar».

Операции и их аргументы

Аргументы

Каждый аргумент соответствует одному из трех типов:

1. Регистр — Registry — T_REG

Регистр это такая переменная, где мы можем хранить какие-либо данные. Размер этой переменной в октетах обозначен в константе REG_SIZE, которая в файле-примере op.h инициализирована значением 4.

Октет в информатике — восемь двоичных разрядов. В русском языке октет обычно называют байтом.

Источник: Октет (информатика) — Википедия

Количество регистров ограничено числом указанным в константе REG_NUMBER. В примере — 16.

То есть доступные нам регистры это r1, r2, r3 ... r16.

Значения регистров

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

В r1 будет записан номер игрока-чемпиона. Только со знаком минус.

Данный номер уникален в рамках игры и нужен операции live, чтобы сообщить, что конкретный игрок жив.

То есть каретка, которая будет размещена на начале кода игрока под номером 2, получит значение r1 равным -2.

В случае, если операция live будет выполнена с аргументом -2, виртуальная машина засчитает, что данный игрок жив:

live %-2

2. Прямой — Direct — T_DIR

Прямой аргумент состоит из двух частей: символ, который задан в константе DIRECT_CHAR (%) + число или метка, которые представляют прямое значение.

В случае, если речь идет о метке, то перед её именем также должен быть указан символ из переменной LABEL_CHAR (:):

sti r1, %:marker, %1

Что такое прямое и непрямое значение?

Чтобы осознать разницу между прямым и непрямым значением, стоит рассмотреть один очень простой пример.

Представим, что у нас есть число 5. В своем прямом значении оно представляет самого себя. То есть число 5 это число 5.

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

Метка в прямом и непрямом значении

Если с числами в прямом и непрямом значении все понятно, то как быть с метками? В чем тут разница?

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

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

Поэтому метки это те же числа. Только записанные в другой форме

Процесс замены меток на числа описан в главе «Зачем нужны метки?».

3. Непрямой — Indirect — T_IND

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

Если в качестве аргумента типа T_IND выступает число, то никаких дополнительных символов не нужно:

ld    5, r7

Если таким аргументом является метка, то перед её именем должен быть указан символ из переменной LABEL_CHAR (:):

ld    :label, r7

Символ-разделитель

Для того, чтобы отделить один аргумент от другого в рамках одной операции, в ассемблере используется специальный символ-разделитель. Он определяется константой препроцессора SEPARATOR_CHAR и в файле-примере op.h это символ ,:

ld    21, r7

Операции

Операция live

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
1 live T_DIR

Описание

На операцию live возложены две функции:

  1. Она засчитывает, что каретка, которая выполняет операцию live, жива.

  2. Если указанный в качестве аргумента операции live номер совпадает с номером игрока, то она засчитывает, что это игрок жив. Например, если значение аргумента равно -2, значит игрок с номером 2 жив.

Что такое каретка?

Подробное объяснение этого термина будет дано в разделе «Виртуальная машина», поскольку каретка относится именно к этой части проекта «Corewar».

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

Каретка это процесс, выполняющий операцию, на которой стоит.

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

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

3 чемпиона. 3 участка памяти с размещенными на них исполняемыми кодами. 3 каретки.

Каждая каретка содержит в себе несколько важных элементов:

  • PC (Program Counter)

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

  • Регистры

Те самые регистры, количество которых определяет константа REG_NUMBER.

  • Флаг carry

Специальная переменная, которая влияет на работу функции zjmp и может принимать одно из двух значений: 1 или 0 (true или false).

  • Номер цикла, в котором данной кареткой в последний раз выполнялась операция live

Эта информация нужна, чтобы определить жива ли каретка.

На самом деле каретка содержит в себе гораздо больше элементов, но они будут рассмотрены позже.

Операция ld

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
2 ld T_DIR / T_IND T_REG

Описание

Задача этой операции состоит в загрузке значения в регистр. Но её поведение отличается в зависимости от типа первого аргумента:

  • Аргумент #1 — T_DIR

Если тип первого аргумента это T_DIR, то число переданное в качестве аргумента будет воспринято «как есть».

Задачи операции:

  1. Записать полученное число в регистр, который был передан в качестве второго аргумента.

  2. Если в регистр записали число 0, то установить значение carry в 1. Если было записано не нулевое значение, то установить carry в 0.

  • Аргумент #1 — T_IND

Если тип первого аргумента это T_IND, то в данном случае число представляет собой адрес.

В случае получения аргумента такого типа он обрезается по модулю — <ПЕРВЫЙ_АРГУМЕНТ> % IDX_MOD.

Что такое IDX_MOD?

IDX_MOD это еще одна константа из файла op.h. Её значение определяется с помощью выражения (MEM_SIZE / 8), где MEM_SIZE определяет объем памяти в байтах. В виртуальной машине на участке памяти размером с MEM_SIZE и будут биться чемпионы.

Так для чего же нужна константа IDX_MOD? Она нужна для того, чтобы каретка не могла прыгать в памяти на слишком большие расстояния. В файле-примере константа MEM_SIZE была инициализирована значением (4 * 1024). Поэтому значение IDX_MOD в этом случае соответствует 512.

Получается, что каретка не сможет за один прыжок переместиться дальше, чем на 512 байт.

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

Задача операции ld:

  1. Определить адрес — текущая позиция + <ПЕРВЫЙ_АРГУМЕНТ> % IDX_MOD.

  2. С полученного адреса необходимо считать 4 байта.

  3. Записать считанное число в регистр, который был передан в качестве второго параметра.

  4. Если в регистр записали число 0, то устанавливаем значение carry в 1. Если было записано не нулевое значение, то устанавливаем carry в 0.

Почему мы считываем именно 4 байта?

Как мы знаем размер каждого регистра составляет 4 байта. А точнее то количество байт, которое определено в файле op.h константой REG_SIZE.

В том же файле определен размер аргумента типа T_DIR:

# define REG_SIZE    4
# define DIR_SIZE    REG_SIZE

И он тоже составляет 4 байта.

Мы переходим по адресу, который задан с помощью аргумента типа T_IND, чтобы считать значение. Считать число «как оно есть». То есть получить число типа T_DIR. И это самое число мы должны записать в регистр. Для того, чтобы запись прошла успешно и считанное число поместилось в регистр, размеры этого числа и размеры регистра должны быть совместимы.

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

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

В общем, мы считываем столько байт, сколько может вместить регистр.

Операция st

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
3 st T_REG T_REG / T_IND

Описание

Эта операция записывает значение из регистра, который был передан как первый параметр. А вот куда данная операция его записывает, зависит от типа второго аргумента:

  • Аргумент #2 — T_REG

Если второй аргумент соответствует типу T_REG, то значение записывается в регистр.

Например, в данном случае значение из регистра номер 7 записывается в регистр с номером 11:

st    r7, r11
  • Аргумент #2 — T_IND

Как мы помним аргументы типа T_IND это об относительных адресах. Поэтому в данном случае порядок работы операции st такой:

  1. Усечь значение второго аргумента по модулю IDX_MOD.

  2. Определить адрес — текущая позиция + <ВТОРОЙ_АРГУМЕНТ> % IDX_MOD

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

Операция add

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
4 add T_REG T_REG T_REG

Описание

К счастью у данной операции все аргументы имеют один и тот же тип. Поэтому с ней все просто.

Задача операции add:

  1. Просуммировать значение из регистра, который был передан как первый аргумент, с значением регистра, который был передан как второй аргумент.

  2. Записать полученный результат в регистр, который был передан как третий аргумент.

  3. Если полученная сумма, которую мы записали в третий аргумент была равна нулю, то устанавливаем carry в 1. А если сумма была не нулевой — в 0.

Операция sub

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
5 sub T_REG T_REG T_REG

Описание

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

Её задачи:

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

  2. Полученный результат записать в регистр, который был передан в качестве третьего аргумента.

  3. Если записанный результат был равен нулю, то значение carry сделать равным 1. Если результат был не нулевой, то сделать равным 0.

Операция and

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
6 and T_REG / T_DIR / T_IND T_REG / T_DIR / T_IND T_REG

Описание

and выполняет операцию «побитовое И» для значений первых двух аргументов и записывает полученный результат в регистр, переданный в качестве третьего аргумента.

Если записанный результат был равен нулю, то значение carry нужно установить равным 1. Если результат был не нулевой, то — равным 0.

Поскольку первый и второй аргумент могут быть одного из трех типов, мы рассмотрим как получить значение каждого из них:

  • Аргумент #1 / Аргумент #2 — T_REG

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

  • Аргумент #1 / Аргумент #2 — T_DIR

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

  • Аргумент #1 / Аргумент #2 — T_IND

Если тип аргумента T_IND, то необходимо установить адрес, с которого будет считано 4 байта.

Адрес определяется следующим образом — текущая позиция + <АРГУМЕНТ> % IDX_MOD.

Считанное по этому адресу 4-байтовое число и будет требуемым значением.

Операция or

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
7 or T_REG / T_DIR / T_IND T_REG / T_DIR / T_IND T_REG

Описание

По своей сути эта операция полностью аналогична операции and. Только в данном случае «побитовое И» заменяется на «побитовое ИЛИ».

Операция xor

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
8 xor T_REG / T_DIR / T_IND T_REG / T_DIR / T_IND T_REG

Описание

По своей сути эта операция полностью аналогична операции and. Только в данном случае «побитовое И» заменяется на «побитовое исключающее ИЛИ».

Как работает «побитовое исключающее ИЛИ» (XOR) ?

A B A ^ B
0 0 0
0 1 1
1 0 1
1 1 0

Операция zjmp

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
9 zjmp T_DIR

Описание

Эта та самая функция, на работу которой влияет значение флага carry.

Если оно равно 1, то функция обновляет значение PC на адрес — текущая позиция + <ПЕРВЫЙ_АРГУМЕНТ> % IDX_MOD.

То есть zjmp устанавливает куда должна переместиться каретка для выполнения следующей операции. Это позволяет нам перепрыгивать в памяти на нужную позицию, а не выполнять всё по порядку.

Если значение carry равно нулю, перемещение не выполняется.

Операция ldi

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
10 ldi T_REG / T_DIR / T_IND T_REG / T_DIR T_REG

Описание

Данная операция записывает значение в регистр, который был ей передан как третий параметр. Значением, которая она записывает, являются 4 байта. Эти 4 байта она считывает по адресу, который формируется по следующему принципу: текущая позиция + (<ЗНАЧЕНИЕ_ПЕРВОГО_АРГУМЕНТА> + <ЗНАЧЕНИЕ_ВТОРОГО_АРГУМЕНТА>) % IDX_MOD.

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

  • Аргумент #1 / Аргумент #2 — T_REG

Значение содержится в регистре, который был передан в качестве параметра.

  • Аргумент #1 / Аргумент #2 — T_DIR

В данном случае у нас аргумент уже содержит свое значение.

  • Аргумент #1 — T_IND

Чтобы получить значение этого аргумента, нужно считать 4 байта по адресу — текущая позиция + <ПЕРВЫЙ_АРГУМЕНТ> % IDX_MOD.

Операция sti

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
11 sti T_REG T_REG / T_DIR / T_IND T_REG / T_DIR

Описание

Эта операция записывает значение регистра, переданного в качестве первого параметра, по адресу — текущая позиция + (<ЗНАЧЕНИЕ_ВТОРОГО_АРГУМЕНТА> + <ЗНАЧЕНИЕ_ТРЕТЕГО_АРГУМЕНТА>) % IDX_MOD.

Как получить значение для каждого типа аргумента описано выше.

Операция fork

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
12 fork T_DIR

Описание

Операция fork делает копию каретки. И эту копию размещает по адресу <ПЕРВЫЙ_АРГУМЕНТ> % IDX_MOD.

Какие данные копируются?

  • Значения всех регистров

  • Значение carry

  • Номер цикла, в котором в последний раз выполнялась операция live

  • И кое-что еще, но об этом позже.

Операция lld

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
13 lld T_DIR / T_IND T_REG

Описание

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

Единственное различие между этими двумя операциями состоит в применении усечения по модулю.

Если первый аргумент имеет тип T_IND, то в этой операции мы будем считывать 4 байта значения по адресу — текущая позиция + <ПЕРВЫЙ_АРГУМЕНТ>. Не применяя усечение по модулю.

Проблемы оригинальной виртуальной машины

Оригинальная виртуальная машина corewar, к сожалению, работает неправильно. И считывает 2 байта, а не 4. Возможно подобный баг объясняется теми же строками, что и проблемы в предоставленных файлах:

... we might have mistaken a bottle of water for a bottle of vodka.

Для аргумента типа T_DIR ничего не меняется.

Операция lldi

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
14 lldi T_REG / T_DIR / T_IND T_REG / T_DIR T_REG

Описание

По своей сути данная операция аналогична операции ldi.

Она записывает значение в регистр, который был передан ей как третий параметр. Значением, которая эта операция записывает, являются считанные 4 байта.

Они считываются по адресу, который формируется по следующему принципу: текущая позиция + (<ЗНАЧЕНИЕ_ПЕРВОГО_АРГУМЕНТА> + <ЗНАЧЕНИЕ_ВТОРОГО_АРГУМЕНТА>).

В отличие от операции ldi в этом случае при формировании адреса не нужно делать усечение по модулю IDX_MOD.

Для аргументов типа T_IND все остается по старому

Если в качестве первого или второго аргумента мы получаем аргумент типа T_IND, то все так же считываем 4 байта значения по адресу — текущая позиция + <АРГУМЕНТ> % IDX_MOD. Здесь усечение по модулю сохраняется.

Операция lfork

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
15 lfork T_DIR

Описание

По своей сути эта операция аналогична операции fork.

За исключением того факта, что новая каретка в этом случае создается по адресу — текущая позиция + <ПЕРВЫЙ_АРГУМЕНТ>. В операции lfork усечение по модулю делать не нужно.

Операция aff

Код операции Имя операции Аргумент #1 Аргумент #2 Аргумент #3
16 aff T_REG

Описание

Эта операция берет значение из регистра, который был передан в качестве единственного аргумента. Приводит его к типу char. И выводит на экран как ASCII-символ.

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

aff: The opcode is 10 in the hexadecimal. There is an argument’s coding byte, even if it’s a bit silly because there is only 1 argument that is a registry, which is a registry, and its content is interpreted by the character’s ASCII value to display on the standard output. The code is modulo 256.

Но на самом деле выполнять дополнительное усечение по модулю 256 нет необходимости.

Ведь результат работы этих двух расчетов будет идентичен:

(char)(value % 256)
(char)(value)

Режим отображения вывода aff в оригинальном corewar

В оригинальной виртуальной машине corewar по умолчанию режим отображения вывода операции aff выключен. Чтобы его включить нужно воспользоваться флагом -a.

Обновленная таблица операций

Код Имя Аргумент #1 Аргумент #2 Аргумент #3 Изменяет carry Описание
1 live T_DIR Нет alive
2 ld T_DIR / T_IND T_REG Да load
3 st T_REG T_REG / T_IND Нет store
4 add T_REG T_REG T_REG Да addition
5 sub T_REG T_REG T_REG Да subtraction
6 and T_REG / T_DIR / T_IND T_REG / T_DIR / T_IND T_REG Да bitwise AND (&)
7 or T_REG / T_DIR / T_IND T_REG / T_DIR / T_IND T_REG Да bitwise OR (|)
8 xor T_REG / T_DIR / T_IND T_REG / T_DIR / T_IND T_REG Да bitwise XOR (^)
9 zjmp T_DIR Нет jump if non-zero
10 ldi T_REG / T_DIR / T_IND T_REG / T_DIR T_REG Нет load index
11 sti T_REG T_REG / T_DIR / T_IND T_REG / T_DIR Нет store index
12 fork T_DIR Нет fork
13 lld T_DIR / T_IND T_REG Да long load
14 lldi T_REG / T_DIR / T_IND T_REG / T_DIR T_REG Да long load index
15 lfork T_DIR Нет long fork
16 aff T_REG Нет aff

Циклы до исполнения

Но это еще не всё, что необходимо знать об операциях.

Есть еще один важный параметр — циклы до исполнения.

Это количество циклов, которое каретка должна подождать перед тем, как начать выполнять операцию.

Примером, оказавшись на операции fork следующие 800 циклов она проведет в ожидании. А для операции ld ожидание составит всего 5 циклов.

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

Код Имя Циклы до исполнения
1 live 10
2 ld 5
3 st 5
4 add 10
5 sub 10
6 and 6
7 or 6
8 xor 6
9 zjmp 20
10 ldi 25
11 sti 25
12 fork 800
13 lld 10
14 lldi 50
15 lfork 1000
16 aff 2

Полную таблицу операций можно просмотреть на сервисе Google Sheets.

Clone this wiki locally