В программировании на компьютере машина P-кода ( переносная машина кода [1] ) — это виртуальная машина, предназначенная для выполнения P-кода, языка ассемблера или машинного кода гипотетического центрального процессора (ЦП). Термин «машина P-кода» применяется в общем смысле ко всем таким машинам (таким как виртуальная машина Java (JVM) и предварительно скомпилированный код MATLAB ), а также к конкретным реализациям, использующим эти машины. Одним из наиболее заметных применений машин P-кода является P-машина системы Pascal-P . Разработчики реализации Pascal UCSD в этой системе истолковывали P в P-коде как псевдо чаще, чем переносимый; они приняли уникальную метку для псевдокода, означающего инструкции для псевдомашины.
Хотя эта концепция была впервые реализована около 1966 года как O-код для Basic Combined Programming Language ( BCPL ) и P-код для языка Euler , [2] термин P -код впервые появился в начале 1970-х годов. Двумя ранними компиляторами, генерирующими P-код, были компилятор Pascal-P в 1973 году, созданный Кесавом В. Нори, Урсом Амманном, Кэтлин Йенсен, Хансом-Генрихом Нэгели и Кристианом Якоби, [3] и компилятор Pascal-S в 1975 году, созданный Никлаусом Виртом .
Программы, которые были переведены в P-код, могут быть либо интерпретированы программой, которая эмулирует поведение гипотетического ЦП, либо переведены в машинный код ЦП, на котором программа должна работать, а затем выполнены. Если есть достаточный коммерческий интерес, может быть построена аппаратная реализация спецификации ЦП (например, Pascal MicroEngine или версия процессора Java ).
В то время как типичная модель компилятора направлена на перевод программного кода в машинный код , идея машины P-кода следует двухэтапному подходу, включающему перевод в P-код и выполнение путем интерпретации или оперативной компиляции (JIT) через машину P-кода.
Такое разделение позволяет отделить разработку интерпретатора P-кода от базового компилятора машинного кода, который должен учитывать машинно-зависимое поведение при генерации своего байт-кода . Таким образом, интерпретатор P-кода также может быть реализован быстрее, а возможность интерпретировать код во время выполнения позволяет проводить дополнительные проверки во время выполнения , которые могут быть недоступны в машинном коде. Кроме того, поскольку P-код основан на идеальной виртуальной машине, программа P-кода часто может быть меньше той же программы, переведенной в машинный код. И наоборот, двухэтапная интерпретация программы на основе P-кода приводит к более медленной скорости выполнения, хотя иногда это можно решить с помощью компиляции just-in-time , а ее более простую структуру легче подвергнуть обратному проектированию, чем машинный код.
В начале 1980-х годов по крайней мере две операционные системы достигли машинной независимости посредством широкого использования P-кода [ требуется ссылка ] . Business Operating System (BOS) была кроссплатформенной операционной системой, разработанной для запуска программ исключительно на P-коде. UCSD p-System , разработанная в Калифорнийском университете в Сан-Диего, была самокомпилирующейся и саморазмещающейся операционной системой на основе P-кода, оптимизированной для генерации языком Pascal .
В 1990-х годах перевод в p-код стал популярной стратегией для реализаций таких языков, как Python , Microsoft P-Code в Visual Basic и байт-код Java в Java .
Язык Go использует универсальную, переносимую сборку как форму p-кода, реализованную Кеном Томпсоном как расширение работы над Plan 9 из Bell Labs . В отличие от байт-кода Common Language Runtime (CLR) или байт-кода JVM, здесь нет стабильной спецификации, а инструменты сборки Go не выдают формат байт-кода для использования в более позднее время. Ассемблер Go использует универсальную сборку как промежуточное представление , а исполняемые файлы Go являются машинно-специфичными статически связанными двоичными файлами. [4]
Как и многие другие машины P-кода, UCSD P-Machine является стековой машиной , что означает, что большинство инструкций берут свои операнды из стека и помещают результаты обратно в стек. Таким образом, add
инструкция заменяет два верхних элемента стека их суммой. Несколько инструкций принимают немедленный аргумент. Как и Pascal, P-код строго типизирован , поддерживая логические (b), символьные (c), целые (i), вещественные (r), множества (s) и указатели (a) типы данных изначально.
Несколько простых инструкций:
Стек Инн. Описание стека до после adi i1 i2 i1+i2 сложить два целых числаadr r1 r2 r1+r2 сложить два реальных числаinn i1 s1 b1 установить членство; b1 = является ли i1 членом s1ldi i1 i1 i1 загрузить целую константуmov a1 a2 a2 ходне b1 b1 -b1 логическое отрицание
Подобно реальному целевому ЦП, P-System имеет только один стек, который совместно используется кадрами стека процедур (предоставляя адрес возврата и т. д.) и аргументами локальных инструкций. Три регистра машины указывают на стек (который растет вверх):
Также присутствует постоянная область, а ниже — куча, растущая вниз по направлению к стеку. Регистр NP (новый указатель) указывает на вершину (самый низкий используемый адрес) кучи. Когда EP становится больше NP, память машины исчерпывается.
Пятый регистр, PC, указывает на текущую инструкцию в области кода.
Стековые фреймы выглядят так:
ЭП -> локальный стекСП -> ... местные жители ... параметры ... обратный адрес (предыдущий ПК) предыдущий EP динамическая ссылка (предыдущий MP) статическая ссылка (MP окружающей процедуры)MP -> возвращаемое значение функции
Последовательность вызова процедуры работает следующим образом: вызов начинается с
мст н
где n
указывает разницу в уровнях вложенности (помните, что Pascal поддерживает вложенные процедуры). Эта инструкция пометит стек , т.е. зарезервирует первые пять ячеек указанного выше стекового кадра и инициализирует предыдущую EP, динамическую и статическую ссылку. Затем вызывающий вычисляет и помещает любые параметры для процедуры, а затем выдает
чашка н, п
для вызова пользовательской процедуры ( n
где число параметров, p
адрес процедуры). Это сохранит PC в ячейке обратного адреса и установит адрес процедуры как новый PC.
Пользовательские процедуры начинаются с двух инструкций
ент 1, я ent 2, j
Первый устанавливает SP в MP + i
, второй устанавливает EP в SP + j
. Таким i
образом, по сути, указывается пространство, зарезервированное для локальных переменных (плюс количество параметров плюс 5), и j
указывается количество записей, необходимых локально для стека. На этом этапе проверяется исчерпание памяти.
Возврат к вызывающему абоненту осуществляется через
ретС
с C
указанием типа возвращаемого значения (i, r, c, b, a, как указано выше, и p для отсутствия возвращаемого значения). Возвращаемое значение должно быть предварительно сохранено в соответствующей ячейке. Для всех типов, кроме p, возврат оставит это значение в стеке.
Вместо вызова пользовательской процедуры (cup) q
можно вызвать стандартную процедуру с помощью
csp д
Эти стандартные процедуры представляют собой процедуры Pascal, такие как readln()
( csp rln
), sin()
( csp sin
) и т. д. Характерной особенностью eof()
является то, что вместо них используется инструкция p-Code.
Никлаус Вирт описал простую машину p-кода в книге 1976 года «Алгоритмы + Структуры данных = Программы» . Машина имела 3 регистра — счетчик программ p , базовый регистр b и регистр вершины стека t . Было 8 инструкций:
lit 0, a
: постоянная нагрузка aopr 0, a
: выполнить операцию a (13 операций: RETURN, 5 математических функций и 7 функций сравнения)lod l, a
: переменная нагрузки l , asto l, a
: сохранить переменную l , acal l, a
: вызов процедуры a на уровне lint 0, a
: увеличить t-регистр на ajmp 0, a
: перейти кjpc 0, a
: условный переход к [ 5]Это код машины, написанный на языке Паскаль:
const amax = 2047 ; {максимальный адрес} levmax = 3 ; {максимальная глубина вложенности блоков} cxmax = 200 ; {размер массива кода} тип fct = ( lit , opr , lod , sto , cal , int , jmp , jpc ) ; инструкция = упакованная запись f : fct ; l : 0 .. levmax ; a : 0 .. amax ; конец ; var code : массив [ 0 .. cxmax ] инструкций ; процедура интерпретации ; константный размер стека = 500 ; var p , b , t : целое число ; {регистры программы, базы, верхнего стека} i : инструкция ; {регистр инструкции} s : массив [ 1 .. stacksize ] целых чисел ; {хранилище данных} function base ( l : integer ) : integer ; var b1 : integer ; begin b1 := b ; {найти уровень base l вниз} while l > 0 do begin b1 := s [ b1 ] ; l := l - 1 end ; base := b1 end {base} ; begin writeln ( ' start pl/0' ) ; t := 0 ; b := 1 ; p := 0 ; s [ 1 ] := 0 ; s [ 2 ] := 0 ; s [ 3 ] := 0 ; repeat i := code [ p ] ; p := p + 1 ; with i do case f of lit : begin t := t + 1 ; s [ t ] := a end ; opr : case a of {operator} 0 : begin {return} t := b - 1 ; p := s [ t + 3 ] ; b := s [ t + 2 ] ; end ; 1 : s [ t ] := - s [ t ] ; 2 : начало t := t - 1 ; s [ t ] := s [ t ] + s [ t + 1 ] конец ; 3 : начало t := t - 1 ; s [ t ] := s [ t ] - s [ t + 1 ] конец ; 4 : начало t := t - 1 ; s [ t ] := s [ t ] * s [ t + 1 ] конец ; 5 : begin t := t - 1 ; s [ t ] := s [ t ] div s [ t + 1 ] end ; 6 : s [ t ] := ord ( odd ( s [ t ])) ; 8 : begin t := t - 1 ; s [ t ] := ord ( s [ t ] = s [ t + 1 ]) end ; 9 : begin t := t - 1 ; s [ t ] := ord ( s [ t ] <> s [ t + 1 ]) end ; 10 : begin t := t - 1 ; s [ t ] := ord ( s [ t ] < s [ t + 1 ]) end ; 11 : начало t := t - 1 ; s [ t ] := ord ( s [ t ] >= s [ t + 1 ]) конец ; 12 : начало t := t - 1 ; s [ t ] := ord ( s [ t ] > s [ t + 1 ]) конец ; 13 : начало t := t - 1 ; s [ t ] := ord ( s [ t ] <= s [ t + 1 ]) конец ; конец ; lod : begin t := t + 1 ; s [ t ] := s [ base ( l ) + a ] конец ; sto : begin s [ base ( l ) + a ] := s [ t ] ; writeln ( s [ t ]) ; t := t - 1 конец ; cal : begin {сгенерировать новую метку блока} s [ t + 1 ] := base ( l ) ; s [ t + 2 ] := b ; s [ t + 3 ] := p ; b := t + 1 ; p := a конец ; int : t := t + a ; jmp : p := a ; jpc : begin if s [ t ] = 0 then p := a ; t := t - 1 end end {with, case} until p = 0 ; writeln ( ' end pl/0' ) ; end {interpret} ;
Эта машина использовалась для запуска PL/0 Вирта , компилятора подмножества Паскаля, используемого для обучения разработке компиляторов. [6] [ проверка не удалась ]
P-код — это название нескольких фирменных промежуточных языков Microsoft . Они предоставили альтернативный двоичный формат машинному коду . В разное время Microsoft заявляла, что P-код — это сокращение от упакованного кода [7] или псевдокода [8] .
Microsoft P-code использовался в Visual C++ и Visual Basic . Как и другие реализации P-code, Microsoft P-code позволял создавать более компактный исполняемый файл за счет более медленного выполнения.