В компьютерном программировании, A р-машинный код ( портативный машинный код) представляет собой виртуальная машина предназначена для выполнения п-коды (на языке ассемблера или машинный код гипотетического центрального процессора (CPU)). Этот термин применяется как в целом ко всем таким машинам (таким как виртуальная машина Java (JVM) и предварительно скомпилированный код MATLAB ), так и к конкретным реализациям, наиболее известной из которых является p-Machine системы Pascal-P, в частности, UCSD Pascal. реализация, среди разработчиков которой p в p-коде толковалось как псевдокод чаще, чем переносимый, таким образом псевдокод означал инструкции для псевдо-машины.
Хотя концепция была впервые реализована примерно в 1966 году (как O-код для базового комбинированного языка программирования ( BCPL ) и P-код для языка Euler ), термин p-код впервые появился в начале 1970-х годов. Двумя ранними компиляторами, генерирующими p-код, были компилятор Pascal-P в 1973 году, созданный Кесавом В. Нори, Урсом Амманном, Кэтлин Йенсен, Ханс-Генрихом Нэгели и Кристианом Якоби, и компилятор Pascal-S в 1975 году Никлаусом Виртом.
Программы, которые были переведены в p-код, могут либо интерпретироваться программой, которая имитирует поведение гипотетического ЦП, либо транслироваться в машинный код ЦП, на котором программа должна запускаться и затем выполняться. Если есть достаточный коммерческий интерес, может быть построена аппаратная реализация спецификации ЦП (например, Pascal MicroEngine или версия процессора Java ).
По сравнению с прямым переводом в машинный код, двухэтапный подход, включающий перевод в p-код и выполнение посредством интерпретации или JIT -компиляции, предлагает несколько преимуществ.
Одним из существенных недостатков p-кода является скорость выполнения, которую иногда можно исправить с помощью JIT-компиляции. P-код часто легче реконструировать, чем собственный код.
В начале 1980-х по крайней мере две операционные системы достигли машинной независимости благодаря широкому использованию p-кода. Операционная система Бизнес (BOS) была операционная система кросс-платформа, разработанная для запуска программ, р-код исключительно. UCSD р-система, разработанная в Университете Калифорнии, Сан - Диего, была самостоятельной компиляцией и самостоятельный хостингом операционной системой на основе р-кода, оптимизированную для генерации на Pascal языка.
В 1990-х годах перевод в p-код стал популярной стратегией для реализации таких языков, как Python, Microsoft P-Code в Visual Basic и байт-код Java в Java.
В языке Go используется универсальная переносимая сборка как форма p-кода, реализованная Кеном Томпсоном как расширение работы Bell Labs над Plan 9. В отличие от байт-кода Common Language Runtime (CLR) или байт-кода JVM, здесь нет стабильной спецификации, а инструменты сборки Go не генерируют формат байт-кода, который будет использоваться в более позднее время. Ассемблер Go использует общий язык ассемблера в качестве промежуточного представления, а исполняемые файлы Go - это машинно- зависимые статически связанные двоичные файлы.
Как и многие другие машины с p-кодом, p-машина UCSD является стековой, что означает, что большинство инструкций берут свои операнды из стека и помещают результаты обратно в стек. Таким образом, add
инструкция заменяет два самых верхних элемента стека их суммой. Несколько инструкций требуют немедленного аргумента. Как и Pascal, p-код строго типизирован и изначально поддерживает типы данных boolean (b), character (c), integer (i), real (r), set (s) и указатель (a).
Несколько простых инструкций:
Insn. Stack Stack Description before after adi i1 i2 i1+i2 add two integers adr r1 r2 r1+r2 add two reals inn i1 s1 is1 set membership; b1 = whether i1 is a member of s1 ldi i1 i1 i1 load integer constant mov a1 a2 a2 move not b1 b1 -b1 boolean negation
В отличие от других сред на основе стека (таких как Forth и виртуальная машина Java ), но очень похожих на реальный целевой ЦП, p-System имеет только один стек, совместно используемый кадрами стека процедур (с указанием адреса возврата и т. Д.), И аргументы для местные инструкции. Три машины регистров указывают в стек (который растет вверх):
Также присутствует постоянная область, а под ней куча, растущая вниз по направлению к стопке. Регистр NP ( новый указатель ) указывает на верхний (самый низкий используемый адрес) кучи. Когда EP становится больше NP, память машины исчерпана.
Пятый регистр, PC, указывает на текущую инструкцию в области кода.
Фреймы стека выглядят так:
EP -gt; local stack SP -gt;... locals... parameters... return address (previous PC) previous EP dynamic link (previous MP) static link (MP of surrounding procedure) MP -gt; function return value
Последовательность вызова процедуры работает следующим образом: вызов вводится с помощью
mst n
где n
указывает разницу в уровнях вложенности (помните, что Паскаль поддерживает вложенные процедуры). Эта инструкция пометит стек, то есть зарезервирует первые пять ячеек указанного выше кадра стека и инициализирует предыдущую EP, динамическую и статическую ссылку. Затем вызывающий абонент вычисляет и передает любые параметры для процедуры, а затем выдает
cup n, p
для вызова пользовательской процедуры ( n
количество параметров, p
адрес процедуры). Это сохранит ПК в ячейке обратного адреса и установит адрес процедуры как новый ПК.
Пользовательские процедуры начинаются с двух инструкций
ent 1, i ent 2, j
Первый устанавливает SP в MP + i
, второй устанавливает EP в SP + j
. Таким образом, по i
сути, указывается пространство, зарезервированное для локальных пользователей (плюс количество параметров плюс 5), и j
указывается количество записей, необходимых локально для стека. На этом этапе проверяется нехватка памяти.
Возврат к звонящему осуществляется через
retC
с C
указанием типа возврата (i, r, c, b, a, как указано выше, и p, если значение не возвращается). Возвращаемое значение должно быть предварительно сохранено в соответствующей ячейке. Для всех типов, кроме p, при возврате это значение останется в стеке.
Вместо вызова пользовательской процедуры (чашки) q
можно вызвать стандартную процедуру с помощью
csp q
Эти стандартные процедуры являются процедурами Паскаля, такими как readln()
( csp rln
), sin()
( csp sin
) и т. Д. Особенностью eof()
является инструкция p-кода.
Никлаус Вирт определил простую машину с p-кодом в книге 1976 года « Алгоритмы + структуры данных = программы». У машины было 3 регистра - счетчик программ p, базовый регистр b и регистр вершины стека t. Всего было 8 инструкций:
Это код машины, написанный на Паскале:
const amax=2047; {maximum address} levmax=3; {maximum depth of block nesting} cxmax=200; {size of code array} type fct=(lit,opr,lod,sto,cal,int,jmp,jpc); instruction=packed record f:fct; l:0..levmax; a:0..amax; end; var code: array [0..cxmax] of instruction; procedure interpret; const stacksize = 500; var p, b, t: integer; {program-, base-, topstack-registers} i: instruction; {instruction register} s: array [1..stacksize] of integer; {datastore} function base(l: integer): integer; var b1: integer; begin b1:= b; {find base l levels down} while l gt; 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: begin t:= t - 1; s[t]:= s[t] + s[t + 1] end; 3: begin t:= t - 1; s[t]:= s[t] - s[t + 1] end; 4: begin t:= t - 1; s[t]:= s[t] * s[t + 1] end; 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] lt;gt; s[t + 1]) end; 10: begin t:= t - 1; s[t]:= ord(s[t] lt; s[t + 1]) end; 11: begin t:= t - 1; s[t]:= ord(s[t] gt;= s[t + 1]) end; 12: begin t:= t - 1; s[t]:= ord(s[t] gt; s[t + 1]) end; 13: begin t:= t - 1; s[t]:= ord(s[t] lt;= s[t + 1]) end; end; lod: begin t:= t + 1; s[t]:= s[base(l) + a] end; sto: begin s[base(l)+a]:= s[t]; writeln(s[t]); t:= t - 1 end; cal: begin {generate new block mark} s[t + 1]:= base(l); s[t + 2]:= b; s[t + 3]:= p; b:= t + 1; p:= a end; 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 Вирта, компилятора подмножества Паскаля, используемого для обучения разработке компиляторов.