В информатике , резьбовой код представляет собой метод программирования , где код имеет форму , которая по существу полностью состоит из вызовов в подпрограммы . Он часто используется в компиляторах , которые могут генерировать код в этой форме или сами реализовывать в этой форме. Код может быть обработан интерпретатором или это может быть просто последовательность инструкций вызова машинного кода .
Потоковый код имеет лучшую плотность, чем код, сгенерированный альтернативными методами генерации и альтернативными соглашениями о вызовах . В кэшированных архитектурах он может выполняться немного медленнее. [ Править ] Тем не менее, программу, которая достаточно мало , чтобы поместиться в компьютерном процессоре s» кэш может работать быстрее , чем большая программа , которая страдает много промахов кэша . [1] Маленькие программы также могут быстрее переключаться между потоками, когда другие программы заполнили кэш.
Потоковый код наиболее известен тем, что он используется во многих компиляторах языков программирования , таких как Forth , многих реализациях BASIC , некоторых реализациях COBOL , ранних версиях B , [2] и других языках для небольших миникомпьютеров и для любительских радиоспутников . [ необходима цитата ]
История
Обычный способ создания компьютерных программ - использование компилятора для перевода исходного кода (написанного на каком-то символическом языке ) в машинный код . Получающийся в результате исполняемый файл обычно работает быстро, но, поскольку он специфичен для аппаратной платформы, его нельзя переносить. Другой подход - генерировать инструкции для виртуальной машины и использовать интерпретатор на каждой аппаратной платформе. Интерпретатор создает экземпляр среды виртуальной машины и выполняет инструкции. Таким образом, необходимо компилировать только интерпретатор.
У ранних компьютеров было относительно мало памяти. Например, в большинстве Data General Nova , IBM 1130 и многих первых микрокомпьютерах было установлено всего 4 КБ ОЗУ. Следовательно, много времени было потрачено на то, чтобы найти способы уменьшить размер программы, чтобы она уместилась в доступной памяти.
Одним из решений является использование интерпретатора, который читает символический язык понемногу и вызывает функции для выполнения действий. Поскольку исходный код обычно намного плотнее, чем результирующий машинный код, это может снизить общее использование памяти. Это было причиной того, что Microsoft BASIC является интерпретатором: [а] его собственный код должен был совместно использовать 4 КБ памяти таких машин, как Altair 8800, с исходным кодом пользователя. Компилятор выполняет перевод с исходного языка в машинный код, поэтому компилятор, исходный код и вывод должны одновременно находиться в памяти. В интерпретаторе нет вывода. Код создается построчно, выполняется, а затем отбрасывается.
Потоковый код - это стиль форматирования скомпилированного кода, который минимизирует использование памяти. Вместо того, чтобы записывать каждый шаг операции при каждом его появлении в программе, как это было обычно, например, в макроассемблерах , компилятор записывает каждый общий бит кода в подпрограмму. Таким образом, каждый бит существует только в одном месте в памяти (см. « Не повторяйся »). Приложение верхнего уровня в этих программах может состоять только из вызовов подпрограмм. Многие из этих подпрограмм, в свою очередь, также состоят только из вызовов подпрограмм нижнего уровня.
Мэйнфреймы и некоторые ранние микропроцессоры, такие как RCA 1802, требовали нескольких инструкций для вызова подпрограммы. В приложении верхнего уровня и во многих подпрограммах эта последовательность постоянно повторяется, и только адрес подпрограммы изменяется от одного вызова к другому. Это означает, что программа, состоящая из множества вызовов функций, также может иметь значительное количество повторяющегося кода.
Чтобы решить эту проблему, системы с многопоточным кодом использовали псевдокод для представления вызовов функций в одном операторе. Во время выполнения крошечный «интерпретатор» просматривает код верхнего уровня, извлекает адрес подпрограммы в памяти и вызывает ее. В других системах, эта же базовая концепция реализована в виде ветви таблицы , отправка таблицы или таблицы виртуальных методов , все из которых состоит из таблицы адресов подпрограмм.
В 1970-х разработчики оборудования приложили значительные усилия, чтобы сделать вызов подпрограмм более быстрым и простым. В улучшенных конструкциях для вызова подпрограммы затрачивается только одна инструкция, поэтому использование псевдо-инструкции не экономит места. [ необходима цитата ] Кроме того, выполнение этих вызовов почти не связано с дополнительными накладными расходами. Сегодня, хотя почти все языки программирования сосредоточены на изоляции кода в подпрограммах, они делают это для ясности кода и удобства сопровождения, а не для экономии места.
Системы с многопоточным кодом экономят место, заменяя этот список вызовов функций, где только адрес подпрограммы изменяется от одного вызова к другому, списком токенов выполнения, которые по сути являются вызовами функций с удаленным кодом операции вызова, оставляя позади только список адресов. [3] [4] [5] [6] [7]
За прошедшие годы программисты создали множество вариаций этого «интерпретатора» или «небольшого селектора». Конкретный адрес в списке адресов может быть извлечен с помощью индекса, регистра общего назначения или указателя . Адреса могут быть прямыми или косвенными, смежными или несмежными (связаны указателями), относительными или абсолютными, разрешенными во время компиляции или динамически построенными. Ни один вариант не является «лучшим» для всех ситуаций.
Разработка
Чтобы сэкономить место, программисты сжали списки вызовов подпрограмм в простые списки адресов подпрограмм и использовали небольшой цикл для вызова каждой подпрограммы по очереди. Например, следующий псевдокод использует эту технику для сложения двух чисел A и B. В этом примере список помечен как поток, а переменная ip (указатель инструкции) отслеживает наше место в списке. Другая переменная sp (указатель стека) содержит адрес в другом месте памяти, доступный для временного хранения значения.
start : ip = & thread // указывает на адрес '& pushA', а не на текстовую метку 'thread' top : jump * ip ++ // следуйте за ip по адресу в потоке, следуйте за этим адресом до подпрограммы, продвигайте ip thread : & pushA & pushB & add ... pushA : * sp ++ = A // следуйте sp до доступной памяти, сохраняйте там A, продвигайте sp к следующему началу перехода pushB : * sp ++ = B jump top add : addend1 = * - - sp // Извлечь верхнее значение из стека addend2 = * - sp // Извлечь второе значение из стека * sp ++ = addend1 + addend2 // Сложить два значения вместе и сохранить результат наверху стека прыгать сверху
Цикл вызова в top
настолько прост, что его можно повторить в конце каждой подпрограммы. Управление теперь перескакивает один раз, с конца подпрограммы на начало другой, вместо двойного перехода top
. Например:
начать : ф = & нить // IP указывает на & Пуща (что указывает на первой инструкции Пуща) скачка * внутрибрюшинно ++ // контроля посылки в первой инструкции PUSHA и заблаговременного ф к & pushB нить : & Пуща & pushB и добавить . .. pushA : * sp ++ = A // следуйте sp до доступной памяти, сохраните там A, продвиньте sp к следующему переходу * ip ++ // отправьте управление, где ip говорит (т.е. pushB) и продвиньте ip pushB : * sp ++ = B jump * ip ++ add : addend1 = * - sp // Извлечь верхнее значение из стека addend2 = * - sp // Извлечь второе значение из стека * sp ++ = addend1 + addend2 / / Складываем два значения вместе и сохраняем результат поверх скачка стека * ip ++
Это называется прямым последовательным кодом (DTC). Хотя этот метод и старше, первым широко распространенным использованием термина «многопоточный код», вероятно, является статья Джеймса Р. Белла 1973 года «Потоковый код». [8]
В 1970 году Чарльз Х. Мур изобрел более компактную структуру, непрямой многопоточный код (ITC), для своей виртуальной машины Forth. Мур пришел к такому решению, потому что миникомпьютеры Nova имели бит косвенного обращения в каждом адресе, что делало ИТЦ простым и быстрым. Позже он сказал, что нашел это настолько удобным, что распространил его на все более поздние разработки Форта. [9]
Сегодня некоторые компиляторы Forth генерируют код с прямым потоком, в то время как другие генерируют код с косвенным потоком. Исполняемые файлы в любом случае действуют одинаково.
Модели с резьбой
Практически весь исполняемый многопоточный код использует тот или иной из этих методов для вызова подпрограмм (каждый метод называется «потоковой моделью»).
Прямая резьба
Адреса в потоке - это адреса машинного языка. Эта форма проста, но может иметь накладные расходы, поскольку поток состоит только из машинных адресов, поэтому все остальные параметры должны загружаться косвенно из памяти. Некоторые системы Forth создают код с прямыми потоками. На многих машинах прямая распараллеливание выполняется быстрее, чем подпрограмма (см. Ссылку ниже).
Пример стековой машины может выполнять последовательность «нажать A, нажать B, добавить». Это может быть преобразовано в следующий поток и подпрограммы, где ip
инициализируется помеченным адресом thread
(т. Е. Адресом, где &pushA
хранится).
#define PUSH (x) (* sp ++ = (x)) #define POP () (* - sp) start : ip = & thread // ip указывает на & pushA (который указывает на первую инструкцию pushA) jump * ip ++ // отправляем управление первой инструкции pushA и продвигаем ip в поток & pushB : & pushA & pushB & add ... pushA : PUSH ( A ) jump * ip ++ // отправляем управление туда, где ip говорит (т.е. pushB ) и продвиньте ip pushB : PUSH ( B ) jump * ip ++ add : result = POP () + POP () PUSH ( результат ) jump * ip ++
В качестве альтернативы в поток могут быть включены операнды. Это может удалить некоторую косвенность, необходимую выше, но увеличивает поток:
#define PUSH (x) (* sp ++ = (x)) #define POP () (* - sp) start : ip = & thread jump * ip ++ thread : & push & A // адрес, где хранится A, не буквальный A & push & B & add ... push : variable_address = * ip ++ // должен переместить ip за адрес операнда, так как это не адрес подпрограммы PUSH ( * variable_address ) // Считываем значение из переменной и нажимаем скачок стека * ip ++ add : result = POP () + POP () PUSH ( результат ) jump * ip ++
Косвенная резьба
Косвенная многопоточность использует указатели на местоположения, которые, в свою очередь, указывают на машинный код. За косвенным указателем могут следовать операнды, которые хранятся в косвенном «блоке», а не повторяются в потоке. Таким образом, косвенный код часто более компактен, чем код с прямым потоком. Косвенное обращение обычно делает его медленнее, хотя обычно все же быстрее, чем интерпретаторы байт-кода. Если операнды обработчика включают в себя как значения, так и типы, экономия места по сравнению с кодом с прямыми потоками может быть значительной. Старые системы FORTH обычно производят непрямой многопоточный код.
Например, если цель состоит в том, чтобы выполнить «push A, push B, add», можно использовать следующее. Здесь ip
инициализируется адресом &thread
, каждый фрагмент кода ( push
, add
) обнаруживается двойным косвенным обращением через сквозной ip
и косвенный блок; и любые операнды фрагмента находятся в косвенном блоке, следующем за адресом фрагмента. Это требует сохранения текущей подпрограммы, в ip
отличие от всех предыдущих примеров, где она содержала следующую вызываемую подпрограмму.
start : ip = & thread // указывает на '& i_pushA' jump * ( * ip ) // следуйте указателям на 1-ю инструкцию 'push', НЕ продвигайте ip еще thread : & i_pushA & i_pushB & i_add ... i_pushA : & push & A i_pushB : & push & B i_add : & add push : * sp ++ = * ( * ip + 1 ) // смотреть на 1 после начала косвенного блока для перехода по адресу операнда * ( * ++ ip ) // вперед ip в потоке, перейти через следующий косвенный блок к следующей подпрограмме add : addend1 = * - sp addend2 = * - sp * sp ++ = addend1 + addend2 jump * ( * ++ ip )
Подпрограмма потоковой передачи
Так называемый «код с подпрограммой» (также «код с потоком вызовов») состоит из серии инструкций «вызова» на машинном языке (или адресов функций для «вызова», в отличие от использования «перехода» в прямом потоке. ). Ранние компиляторы для ALGOL , Fortran, Cobol и некоторых систем Forth часто создавали программный код. Код во многих из этих систем работал со стеком операндов «последним вошел - первым ушел» (LIFO), для которого теория компиляторов была хорошо развита. Большинство современных процессоров имеют специальную аппаратную поддержку для инструкций «вызова» и «возврата» подпрограмм, поэтому накладные расходы, связанные с одной дополнительной машинной инструкцией на отправку, несколько уменьшаются.
Антон Эртл, соавтор компилятора Gforth , заявил, что «в отличие от популярных мифов, потоки подпрограмм обычно медленнее, чем прямые потоки». [10] Однако самые последние тесты Ertl [1] показывают, что потоки подпрограмм быстрее, чем прямые потоки в 15 из 25 тестовых случаев. В частности, он обнаружил, что прямая многопоточность - самая быстрая модель на процессорах Xeon, Opteron и Athlon, непрямая - самая быстрая на процессорах Pentium M, а подпрограмма - на процессорах Pentium 4, Pentium III и PPC.
В качестве примера потоковой передачи вызовов для «push A, push B, add»:
thread : call pushA call pushB call add ret pushA : * sp ++ = A ret pushB : * sp ++ = B ret add : addend1 = * - sp addend2 = * - sp * sp ++ = addend1 + addend2 ret
Распределение токенов
Код с токен-потоком реализует поток как список индексов в таблице операций; ширина индекса, естественно, выбирается как можно меньшей для плотности и эффективности. 1 байт / 8 бит - это естественный выбор для простоты программирования, но могут использоваться меньшие размеры, например 4 бита, или более крупные, например, 12 или 16 бит, в зависимости от количества поддерживаемых операций. Если ширина индекса выбрана более узкой, чем машинный указатель, он, естественно, будет более компактным, чем другие типы потоков, без особых усилий со стороны программиста. Обычно это от половины до трех четвертей размера других потоков, которые сами по себе составляют от четверти до восьмой размера непоточного кода. Указатели таблицы могут быть косвенными или прямыми. Некоторые компиляторы Forth создают код с токен-нитями. Некоторые программисты считают « p-код », сгенерированный некоторыми компиляторами Паскаля , а также байт-коды, используемые .NET , Java , BASIC и некоторыми компиляторами C , потоками токенов.
Исторически распространенным подходом является байт-код , который обычно использует 8-битные коды операций с виртуальной машиной на основе стека. Типичный интерпретатор байт-кода известен как «интерпретатор декодирования и отправки» и имеет следующую форму:
начать : VPC = & нить отправка : адр = декодирование ( & VPC ) // Преобразовать следующую операцию байткода на указатель на машинный код, реализующих это операция // Любого между инструкций выполняются здесь (например , обновление глобального состояния, обработка событий, и т.д.) прыгать ADDR CODE_PTR декодирование ( BYTE_CODE ** р ) { // в более сложном кодировании, может быть несколько таблиц , чтобы выбрать между или контроль / режим флагов возврата таблица [ * ( * р ) ++ ]; } thread : / * Содержит байт-код, а не машинные адреса. Следовательно, он более компактный. * / 1 / * pushA * / 2 / * pushB * / 0 / * add * / table : & add / * table [0] = адрес машинного кода, реализующего байт-код 0 * / & pushA / * table [1]. .. * / & pushB / * таблица [2] ... * / Пуща : * зр ++ = скачок диспетчерская pushB : * зр ++ = B прыжок диспетчерская добавить : addend1 = * - зр addend2 = * - sp * sp ++ = addend1 + addend2 отправка скачка
Если виртуальная машина использует только инструкции байтового размера, decode()
это просто выборка из thread
, но часто используются обычно 1-байтовые инструкции плюс несколько менее распространенных многобайтовых инструкций (см. Компьютер со сложным набором инструкций ), и decode()
это более сложный случай . Декодирование однобайтовых кодов операций может быть очень просто и эффективно обработано таблицей переходов, использующей код операции непосредственно в качестве индекса.
Для инструкций, в которых отдельные операции просты, такие как «push» и «add», накладные расходы, связанные с принятием решения о том, что выполнять, превышают затраты на его фактическое выполнение, поэтому такие интерпретаторы часто намного медленнее, чем машинный код. Однако для более сложных («составных») инструкций процент служебных данных пропорционально менее значим.
Бывают случаи, когда токен-многопоточный код может работать быстрее, чем эквивалентный машинный код, когда этот машинный код оказывается слишком большим, чтобы поместиться в кеш-память L1 физического процессора. Более высокая плотность кода многопоточного кода, особенно кода с токенами, может позволить ему полностью поместиться в кэш L1, когда в противном случае он не смог бы избежать перегрузки кеша. Однако многопоточный код потребляет как кэш инструкций (для реализации каждой операции), так и кэш данных (для байт-кода и таблиц), в отличие от машинного кода, который потребляет только кэш инструкций; это означает, что многопоточный код будет съедать бюджет на объем данных, которые могут храниться для обработки ЦП в любой момент времени. В любом случае, если вычисляемая проблема включает применение большого количества операций к небольшому объему данных, то использование многопоточного кода может быть идеальной оптимизацией. [4]
Резьба по Хаффману
Многопоточный код Хаффмана состоит из списков токенов, хранящихся в виде кодов Хаффмана . Код Хаффмана - это строка битов переменной длины, которая идентифицирует уникальный токен. Потоковый интерпретатор Хаффмана находит подпрограммы, используя индексную таблицу или дерево указателей, по которым можно перемещаться с помощью кода Хаффмана. Многопоточный код Хаффмана - одно из самых компактных представлений компьютерных программ. Индекс и коды выбираются путем измерения частоты вызовов каждой подпрограммы в коде. При частых звонках даются кратчайшие коды. Операции с примерно равными частотами задаются кодами с примерно равной битовой длиной. Большинство потоковых систем Хаффмана были реализованы как Forth-системы с прямыми потоками и использовались для упаковки больших объемов медленно работающего кода в небольшие дешевые микроконтроллеры . Большинство опубликованных [11] применений относятся к смарт-картам, игрушкам, калькуляторам и часам. Бит-ориентированный токенизированный код, используемый в PBASIC, можно рассматривать как своего рода многопоточный код Хаффмана.
Меньше используемая резьба
Примером является распараллеливание строк, при котором операции идентифицируются строками, обычно поиск осуществляется с помощью хеш-таблицы. Это использовалось в самых ранних реализациях Forth Чарльза Х. Мура и в экспериментальном компьютерном языке с аппаратной интерпретацией Университета Иллинойса . Он также используется в Башфорте .
РПЛ
HP «s RPL , впервые в HP-18C калькуляторе в 1986 году, является одним из видов фирменного гибридного прямой нарезки и непрямого-резьбового резьбовой интерпретирован языка , который, в отличии от других TIL , позволяет встраивание RPL„объекты“в«runstream "т.е. Поток адресов, по которому продвигается указатель интерпретатора. «Объект» RPL можно рассматривать как особый тип данных, структура которого в памяти содержит адрес «пролога объекта» в начале объекта, а затем следуют данные или исполняемый код. Пролог объекта определяет, как тело объекта должно выполняться или обрабатываться. Использование «внутреннего цикла RPL» [12], который был изобретен и опубликован (и запатентован [13] ) Уильямом К. Виксом в 1986 г. и опубликован в «Средах программирования», Institute for Applied Forth Research, Inc., 1988 г., исполнение следует так:
- Разыменовать IP (указатель инструкции) и сохранить его в O (указатель текущего объекта)
- Увеличьте IP на длину одного указателя адреса
- Разыменовать O и сохранить его адрес в O_1 (это второй уровень косвенности)
- Передайте управление следующему указателю или встроенному объекту, установив ПК (счетчик программ) на O_1 плюс один указатель адреса
- Вернуться к шагу 1
Более точно это можно представить следующим образом:
O = [I] I = I + Δ ПК = [O] + Δ
Где выше, O - текущий указатель объекта, I - указатель интерпретатора, Δ - длина одного адресного слова, а оператор «[]» означает «разыменование».
Когда управление передается указателю объекта или встроенному объекту, выполнение продолжается следующим образом:
ПРОЛОГ -> ПРОЛОГ (адрес пролога в начале кода пролога указывает на себя) ЕСЛИ O + Δ = / = PC THEN GOTO INDIRECT (тест на прямое выполнение) O = I - Δ (исправьте O, чтобы указать на начало встроенного объекта) I = I + α (исправьте I, чтобы указать после встроенного объекта, где α - длина объекта) КОСВЕННО (остальная часть пролога)
В микропроцессорах HP Saturn , использующих RPL, есть третий уровень косвенного обращения, который стал возможным благодаря архитектурному / программному трюку, который обеспечивает более быстрое выполнение. [12]
ветви
Во всех интерпретаторах ветвь просто изменяет указатель потока (см. ip
Выше). Условная ветвь для перехода, если значение вершины стека равно нулю, может быть закодировано следующим образом. Обратите внимание, что &thread[123]
это место перехода, а не адрес обработчика. Таким образом, его необходимо пропустить ( ip++
) независимо от того, взята ли ветвь.
поток : ... & brz & thread [ 123 ] ... brz : tmp = ip ++ if ( * sp ++ == 0 ) ip = tmp jump * ip ++
Общие удобства
Разделение стеков данных и возврата на машине устраняет большую часть кода управления стеком, существенно уменьшая размер многопоточного кода. Принцип двойного стека возник трижды независимо: для больших систем Burroughs , Forth и PostScript . Он используется в некоторых виртуальных машинах Java .
В многопоточной виртуальной машине часто присутствуют три регистра . Другой существует для передачи данных между подпрограммами («слова»). Эти:
- ip или i ( указатель инструкции ) виртуальной машины (не путать с программным счетчиком базового оборудования, реализующего виртуальную машину )
- w (рабочий указатель)
- rp или r (возврат указателя стека )
- sp или s ( указатель стека параметров для передачи параметров между словами)
Часто многопоточные виртуальные машины , такие как реализации Forth, имеют в своей основе простую виртуальную машину, состоящую из трех примитивов . Это:
- гнездо , также называемое докол
- unnest , или semi_s (; s)
- следующий
В виртуальной машине с косвенными потоками, приведенной здесь, операции следующие:
далее : * ip ++ -> w jump ** w ++ nest : ip -> * rp ++ w -> ip next unnest : * - rp -> ip next
Это , возможно , [ править ] самый простой и быстрый интерпретатор или виртуальную машину.
Смотрите также
- Стиль передачи продолжения , при котором глобальная переменная заменяется
ip
параметром функции. - Своевременная компиляция
- Возвратно-ориентированное программирование : повторное открытие многопоточного кода для эксплуатации удаленных уязвимых систем.
- Хвостовая рекурсия
Заметки
- ^ Dartmouth BASIC , на котором в конечном итоге основана MS, был компилятором, который работал на мэйнфреймах.
Рекомендации
- ^ a b "Скорость различных методов диспетчеризации интерпретатора V2" .
- ^ Деннис М. Ритчи, «Развитие языка C» , 1993. Цитата: «Компилятор B на PDP-7 не генерировал машинные инструкции, а вместо этого генерировал« многопоточный код »...»
- ^ Дэвид Фрех. "Муфорт ридми" . раздел «Простой и хвосторекурсивный нативный компилятор».
- ^ а б Стив Хеллер. «Эффективное программирование на C / C ++: меньше, быстрее, лучше» . 2014. Глава 5: «Вам нужен переводчик?» п. 195.
- ^ Жан-Поль Трембле; П.Г. Соренсон. «Теория и практика написания компиляторов» . 1985. с. 527
- ^ «Беспроводной мир: электроника, радио, телевидение, Том 89» . п. 73.
- ^ «Байт, Том 5» . 1980. с. 212
- ^ Белл, Джеймс Р. (1973). «Резьбовой код». Коммуникации ACM . 16 (6): 370–372. DOI : 10.1145 / 362248.362270 .
- ↑ Мур, Чарльз Х., опубликовал комментарии в четвертом выпуске журнала Byte Magazine.
- ^ Эртл, Антон. "Что такое резьбовой код?" .
- ^ Латендресс, Марио; Фили, Марк. Генерация быстрых интерпретаторов для байт-кода, сжатого по Хаффману . Эльзевир. CiteSeerX 10.1.1.156.2546 .
- ^ а б Басби, Джонатан. «Объяснение внутреннего цикла РПЛ» , «Музей калькуляторов HP» , 7 сентября 2018 г., последнее посещение - 27 декабря 2019 г.
- ^ Викес, Уильям К. (30 мая 1986 г.). «Система и метод обработки данных для прямого и косвенного выполнения однородно структурированных типов объектов» . uspto.gov . Проверено 27 декабря 2019 года .
Внешние ссылки
- Пояснительная страница Антона Эртля Что такое нитевой код? описывает различные методы создания потоков и предоставляет дополнительные ссылки.
- Развитие C Язык по Dennis M. Ritchie описывает B (предшественник C) , как реализованный с использованием «многопоточного кода».
- Проект Thinking Forth включает основополагающую (но уже распродавшуюся) книгу Лео Броди Thinking Forth, опубликованную в 1984 году.
- Стартовая онлайн-версия FORTH книги Лео Броди « Начиная FORTH», вышедшей в 1981 году.
- Книга Брэда Родригеса " Moving FORTH: Part 1: Design Decisions in the Forth Kernel" подробно рассматривает методы создания потоков.
- История процессоров общего назначения
- Расширения GCC. Ярлыки как значения
- Хорн, Джозеф К. "Что такое РПЛ?" . Архивировано 17 сентября 2017 года . Проверено 17 сентября 2017 .(NB. Краткий обзор связанных языков, системного и пользовательского RPL, используемых в калькуляторах HP, таких как HP 48. )