Ветвление: сборка не требуется
29 июня 2021 г. · 7 минут чтения
Эта статья является частью серии «Начальная загрузка» , в которой я начинаю с 512-байтного начального источника и пытаюсь загрузить реальную систему.
Предыдущий пост:
Нет веток? Нет проблем — Форт-ассемблер
Следующее сообщение:
Как Forth реализует исключения
В прошлый раз мы начали с barebones ядра Miniforth , 1 и реализовали ветки, написав дополнительные слова-примитивы на ассемблере. Из прагматических соображений именно этим путем я и буду следовать дальше, но я заметил, что в чистом Форте также можно реализовывать ветки. Я считаю, что этот подход довольно интересен, так что давайте отклонимся и посмотрим поближе.
Соответствующие 2 доступных примитива:
+ - ! @ c! c@ dup drop swap emit u. >r r> [ ] : ;
Кроме того, у нас есть реализации следующего из [предыдущего поста](https://habr.com/ru/articles/739012/ :
системные переменные
latest
,st
,base
,dp
,disk#
доступ к пространству словаря:
here
,allot
,,
,c,
определяющие слова
create
,variable
,constant
немного разного:
+!
,cell+
,cells
,compile
,immediate
,lit,
Вы можете запустить Miniforth, следуя инструкциям в репозитории GitHub . Если вы хотите попробовать реализовать ветки pure-Forth самостоятельно, сейчас самое время прекратить чтение. В противном случае мы будем изучать ветки на purity
ветке .
Безусловные ветви
Когда (branch)
or (0branch)
компилируется в слово, за ним сразу же следует адрес цели ветки:
Реализация безусловного перехода не так уж сложна — манипулируйте стеком возврата, чтобы перенаправить адрес возврата:
: (branch)
r> \ вывести адрес возврата, который указывает на ячейку, содержащую цель перехода
@ \ получить цель перехода
>r \ сделать это новым обратным адресом
;
Условные ветки
Ясно, что сложность условного перехода сводится к выбору между двумя возможными значениями адреса возврата. Это было бы довольно просто, если бы у нас было and
и 0=
— поскольку в Форте true
установлены все биты, мы можем and
с помощью логического флага выбирать между значением по нашему выбору и 0,3 .
Это проще всего использовать, если мы закодируем наши ветки как смещение, а не как абсолютный адрес. В этом случае реализация будет выглядеть так:
: (0branch)
0= ( должен перейти )
r> dup cell+ ( должен пкркйти addr-of-offset retaddr-if-false )
>r @ and r> ( offset|0 retaddr-if-false )
+ >r
;
К сожалению, у нас нет and
или 0=
. Тем не менее, это все еще полезная отправная точка. Можем ли мы как-то реализовать эти слова?
Сдвиг битов вокруг
Было бы неплохо, если бы мы могли извлекать отдельные биты из слова. Если бы у нас это было, мы могли бы реализовывать побитовые функции, сдвигая входные данные, вычисляя то, что нам нужно, по крупицам и сдвигая результат:
Сдвиг влево достаточно прост, так как это просто умножение на два:
: 2* dup + ;
Однако добраться до наименее значимой позиции немного сложнее. Однако, если мы используем доступ к памяти, мы можем извлечь старший байт значения: 4
variable x
: lobyte x ! x c@ ;
: hibyte x ! x 1 + c@ ;
По сути, это 8-битный сдвиг вправо. Давайте используем это, чтобы проверить, равно ли число нулю. Нам нужно ИЛИ все биты вместе, но у нас нет ни того, or
ни другого. Однако сложение несколько похоже, поэтому давайте посчитаем биты в значении.
s ( c v -- c' v' )
обрабатывает одну итерацию «цикла» — он немного сдвигается за пределы 8-битного значения v
и добавляет его к счетчику c
.
: s 2* dup >r hibyte + r> lobyte ;
Выполнение этого 8 раз будет подсчитывать биты в байте, вот что делает nb
( n
umber of its):b
: nb 0 swap s s s s s s s s drop ;
nbw
( n
число b
его в w
орде) делает то же самое для полного 16-битного значения, вызывая nb
для каждой половины:
: nbw dup hibyte nb swap lobyte nb + ;
Как нам превратить это в сравнение с нулем? Повторяем nb
несколько раз:
после
nbw
у вас будет значение не более 16,после
nbw nb
у вас будет значение не более 4,после
nbw nb nb
у вас будет значение не более 2,после
nbw nb nb nb
у вас будет значение либо 0, либо 1.
: 1bit nbw nb nb nb ;
Выбор между значениями
Хотя мы могли бы использовать аналогичную стратегию битового сдвига для реализации and
и выбора между двумя адресами возврата, используя это, есть более простой способ: использовать 1-битное значение, которое мы вычисляем, для индексации в массив. 5 Мы будем использовать массив из 2 записей, называемый буфером ветвления:
create bb 2 cells allot
: (0branch)
r> dup \ две копии
@ bb ! \ bb[0] = адрес возврата, если на стеке 0
cell+ bb cell+ ! \ bb[1] = адрес возврата, если на стеке есть что-то еще
1bit cells bb + @ >r
;
Другие решения: компромисс между временем и памятью
Несмотря на элегантность, наше решение довольно неэффективно, выполняя тысячи инструкций в каждой ветке. Хотя я не ожидаю наилучшей производительности, когда мы не ограничиваемся дополнительной сборкой, все же есть способы сделать это лучше.
Например, мы могли бы подготовить 256-байтовую таблицу поиска для файлов 1bit
. Поскольку у нас нет никакого способа зациклиться, нам нужно будет повторить все вручную. Поскольку 255 = 3 × 5 × 17, это может выглядеть так:
: x 1 c, ; \ записать 1 один
: y x x x ; \ записать 3
: z y y y y y ; \ записать 15
create tab 0 c,
z z z z z z z z z z z z z z z z z \ 17 times
: 1bit-half tab + c@ ;
: 1bit dup hibyte 1bit-half swap lobyte 1bit-half + 1bit-half ;
И это всё?
Да, мы закончили. Остальной код, необходимый для определения if
, then
и других слов потока управления, выглядит точно так же, как в предыдущем посте.
Вы можете спросить, это все, что нам нужно для полноты по Тьюрингу? 6 Возможно, есть какой-то примитив, который мы не сможем определить по какой-то причине? Я не думаю, что нам нужно беспокоиться. Наша ветвь может быть использована для реализации структуры управления циклом до нуля, и это все, что есть у brainfuck .
Таким образом, я закончу это отступление и продолжу начальную загрузку, не ограничивая искусственно использование ассемблера. Далее на повестке дня у нас есть механизмы обработки исключений Форта и способы их расширения для получения более качественных сообщений об ошибках, чем тот минимум, который обычно встречается в Форте.
Понравилась эта статья?
Возможно, вам понравятся и другие мои посты . Если вы хотите получать уведомления о новых, вы можете подписаться на меня в Твиттере или подписаться на RSS-канал .
Я хотел бы поблагодарить моих спонсоров GitHub за их поддержку: Michalina Sidor и Tijn Kersjes.
1
Слово «ядро» используется здесь в смысле языковой реализации, а не операционной системы. Если вы знаете термин получше этого, пожалуйста, дайте мне знать, так как в какой-то момент мне придется говорить об обоих вещах одновременно...↩
2
Я пропускаю load
и s:
, так как они не помогут, и их описание выходит за рамки этого поста. Я описал их в предыдущем посте , если вам интересно.↩
3
Этот подход, кажется, независимо изобретался как минимум трижды: мной , Цезарем Блюмом и Полом Слей .↩
4
Напомним, что x86 имеет прямой порядок следования байтов, и поэтому значение like 1234
хранится как 34 12
в памяти.↩
5
Кажется, я научился этой технике у M/o/Vfuscator .↩
6
Что ж, поскольку память конечна, все, что мы на самом деле когда-либо создавали, — это просто очень большая конечная машина. Я полагаю, что более близким понятием будет LBA-полнота, если мы будем педантичными, но я не стал бы надеяться на полностью формальное определение, которое отражает то, что мы обычно подразумеваем под «полным по Тьюрингу», когда говорим о вещах, которые действительно существуют.↩
arteast
Клепаете переводы, не приходя в сознание?
"b
ранчоb
" - это вот что? Как можно было такое написать? Ответ - никак, это машинный перевод."Ассемблер четвертого стиля" - как, переводя статью о Forth, можно было так перевести Forth-style assembler? Ответ - никак, это машинный перевод.
следующая строчка "Обычный синтаксис сборки выглядит так:" - сборки?! Мы про ассемблер говорим, а не сборочный автомат.
А по теме статьи - непонятно, почему автор не применил свеженаписанный ассемблер. and, or, shl, shr реализуются через одну-две операции, а не вот это вот все.
arteast
Отвечу сам себе по теме "по теме" - потому, что смысл статьи был именно в том, чтобы не применять ассемблер... Читать по диагонали вредно.
Что, кстати, показывает еще раз про перевод. Сравните "Ветвление: сборка не требуется" и "Branches: no assembly required".
forthuse Автор
Вот есть классическая статья как на Форт можно сделать ассемблер.
Brad Rodriguez: Как написать свой (кросс-)ассемблер