Ветвление: сборка не требуется
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( number 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: Как написать свой (кросс-)ассемблер