macchina con codice p - p-code machine
Nella programmazione di computer , una macchina p-code ( macchina a codice portatile ) è una macchina virtuale progettata per eseguire p-code (il linguaggio assembly o il codice macchina di un'ipotetica unità di elaborazione centrale (CPU)). Questo termine viene applicato sia genericamente a tutte queste macchine (come la Java virtual machine (JVM) e il codice precompilato MATLAB ), sia a specifiche implementazioni, la più famosa è la p-Machine del sistema Pascal-P , in particolare la UCSD Pascal implementazione, tra i cui sviluppatori, la p in p-code è stata interpretata per significare pseudo più spesso di portable , quindi pseudo-codice significa istruzioni per una pseudo-macchina.
Sebbene il concetto sia stato implementato per la prima volta intorno al 1966 (come codice O per il linguaggio di programmazione combinato di base ( BCPL ) e codice P per il linguaggio Euler ), il termine codice p è apparso per la prima volta nei primi anni '70. Due dei primi compilatori che generano codice p furono il compilatore Pascal-P nel 1973, di Kesav V. Nori, Urs Ammann, Kathleen Jensen, Hans-Heinrich Nägeli e Christian Jacobi, e il compilatore Pascal-S nel 1975, di Niklaus Wirth .
I programmi che sono stati tradotti in p-code possono essere interpretati da un programma software che emula il comportamento dell'ipotetica CPU o tradotti nel codice macchina della CPU su cui il programma deve essere eseguito e quindi eseguito. Se c'è un interesse commerciale sufficiente, può essere costruita un'implementazione hardware della specifica della CPU (ad esempio, il Pascal MicroEngine o una versione di un processore Java ).
Vantaggi e punti deboli dell'implementazione del codice p
Rispetto alla traduzione diretta in codice macchina nativo , un approccio in due fasi che prevede la traduzione in codice p e l'esecuzione mediante interpretazione o compilazione just-in-time (JIT) offre diversi vantaggi.
- È molto più semplice scrivere un piccolo interprete p-code per una nuova macchina piuttosto che modificare un compilatore per generare codice nativo per la stessa macchina.
- La generazione del codice macchina è una delle parti più complicate della scrittura di un compilatore. In confronto, la generazione di p-code è molto più semplice perché nessun comportamento dipendente dalla macchina deve essere considerato nella generazione del bytecode . Questo lo rende utile per far funzionare rapidamente un compilatore.
- Poiché p-code si basa su una macchina virtuale ideale, un programma p-code è spesso molto più piccolo dello stesso programma tradotto in codice macchina.
- Quando il p-code viene interpretato, l'interprete può applicare controlli di runtime aggiuntivi che sono difficili da implementare con il codice nativo.
Uno degli svantaggi significativi del p-code è la velocità di esecuzione, che a volte può essere risolta tramite la compilazione JIT. Il codice P è spesso anche più facile da decodificare rispetto al codice nativo.
All'inizio degli anni '80, almeno due sistemi operativi hanno raggiunto l'indipendenza dalla macchina attraverso un uso estensivo del codice p. Il Business Operating System (BOS) era un sistema operativo multipiattaforma progettato per eseguire esclusivamente programmi p-code. L' UCSD p-System , sviluppato presso l'Università della California, San Diego, era un sistema operativo autocompilante e auto-hosting basato su codice p ottimizzato per la generazione dal linguaggio Pascal .
Negli anni '90, la traduzione in p-code è diventata una strategia popolare per le implementazioni di linguaggi come Python , Microsoft P-Code in Visual Basic e Java bytecode in Java .
Il linguaggio Go utilizza un assembly generico e portatile come forma di p-code, implementato da Ken Thompson come estensione del lavoro su Plan 9 dei Bell Labs . A differenza del bytecode CLR ( Common Language Runtime ) o del bytecode JVM, non esiste una specifica stabile e gli strumenti di compilazione Go non emettono un formato bytecode da utilizzare in un secondo momento. L'assemblatore Go usa il linguaggio assembly generico come rappresentazione intermedia e gli eseguibili Go sono binari collegati staticamente specifici della macchina .
UCSD p-Machine
Architettura
Come molte altre macchine p-code, la p-Machine UCSD è una macchina stack , il che significa che la maggior parte delle istruzioni prende i propri operandi da uno stack e rimette i risultati nello stack. Pertanto, l' addistruzione sostituisce i due elementi più in alto dello stack con la loro somma. Alcune istruzioni richiedono un argomento immediato. Come Pascal, il p-code è fortemente tipizzato , supportando in modo nativo i tipi di dati boolean (b), character (c), integer (i), real (r), set (s) e pointer (a) .
Alcune semplici istruzioni:
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
Ambiente
A differenza di altri ambienti basati su stack (come Forth e la macchina virtuale Java ) ma molto simile a una CPU di destinazione reale, il p-System ha un solo stack condiviso da frame di stack di procedure (che forniscono l' indirizzo di ritorno , ecc.) e gli argomenti per istruzioni locali. Tre dei registri della macchina puntano nello stack (che cresce verso l'alto):
- SP punta alla cima dello stack (il puntatore dello stack ).
- MP segna l'inizio dello stack frame attivo (il puntatore mark ).
- EP punta alla posizione dello stack più alta utilizzata nella procedura corrente (il puntatore estremo ).
È presente anche un'area costante e, al di sotto di essa, l' heap che cresce verso lo stack. Il registro NP (il nuovo puntatore ) punta alla parte superiore (l'indirizzo utilizzato più basso) dell'heap. Quando EP diventa maggiore di NP, la memoria della macchina è esaurita.
Il quinto registro, PC, punta all'istruzione corrente nell'area del codice.
Convenzioni di chiamata
I frame stack hanno questo aspetto:
EP ->
local stack
SP -> ...
locals
...
parameters
...
return address (previous PC)
previous EP
dynamic link (previous MP)
static link (MP of surrounding procedure)
MP -> function return value
La sequenza di chiamata della procedura funziona come segue: La chiamata viene introdotta con
mst n
dove nspecifica la differenza nei livelli di annidamento (ricordate che Pascal supporta le procedure annidate). Questa istruzione segnerà lo stack, cioè riserverà le prime cinque celle del precedente stack frame, e inizializzerà il precedente collegamento EP, dinamico e statico. Il chiamante quindi calcola e invia qualsiasi parametro per la procedura, quindi emette
cup n, p
per chiamare una procedura utente ( nessendo il numero di parametri, pl'indirizzo della procedura). Questo salverà il PC nella cella dell'indirizzo di ritorno e imposterà l'indirizzo della procedura come nuovo PC.
Le procedure per l'utente iniziano con le due istruzioni
ent 1, i ent 2, j
Il primo imposta SP su MP+ i, il secondo imposta EP su SP+ j. Quindi ispecifica essenzialmente lo spazio riservato ai locali (più il numero di parametri più 5) e jfornisce il numero di voci necessarie localmente per lo stack. A questo punto viene controllato l'esaurimento della memoria.
Il ritorno al chiamante avviene tramite
retC
con Cdando il tipo di ritorno (i, r, c, b, una come sopra, e p per nessun valore di ritorno). Il valore restituito deve essere precedentemente memorizzato nella cella appropriata. Su tutti i tipi tranne p, la restituzione lascerà questo valore nello stack.
Invece di chiamare una procedura utente (cup), la procedura standard qpuò essere chiamata con
csp q
Queste procedure standard sono procedure Pascal come readln()( csp rln), sin()( csp sin), ecc. Peculiarmente eof()è invece un'istruzione p-Code.
Esempio di macchina
Niklaus Wirth ha specificato una semplice macchina p-code nel libro del 1976 Algorithms + Data Structures = Programs . La macchina aveva 3 registri: un contatore di programma p , un registro di base b e un registro di cima dello stack t . C'erano 8 istruzioni:
- acceso 0, a : costante di carico a
- opr 0, a : esegue l'operazione a (13 operazioni: RETURN, 5 funzioni matematiche e 7 funzioni di confronto)
- lod l , a : variabile di carico l,a
- sto l , a : memorizza la variabile l, a
- cal l , una chiamata di procedura una a livello l
- int 0, a : incrementa il registro t di a
- jmp 0, a : salta ad a
- jpc 0, a : salto condizionato ad a
Questo è il codice della macchina, scritto in Pascal:
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 > 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] <> s[t + 1]) end;
10: begin t := t - 1; s[t] := ord(s[t] < s[t + 1]) end;
11: begin t := t - 1; s[t] := ord(s[t] >= s[t + 1]) end;
12: begin t := t - 1; s[t] := ord(s[t] > s[t + 1]) end;
13: begin t := t - 1; s[t] := ord(s[t] <= 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};
Questa macchina è stata utilizzata per eseguire PL/0 di Wirth , un compilatore di sottoinsiemi Pascal utilizzato per insegnare lo sviluppo del compilatore.
Guarda anche
- Mela intera dolce 16
- Bytecode
- Joel McCormack , progettista della versione NCR Corporation della macchina p-code
- LLVM IR
- Codice P Microsoft
- Sistema di runtime
- Filettatura di token
Riferimenti
Ulteriori letture
- Pemberton, Steven ; Daniele, Martin. Implementazione di Pascal: il compilatore e l'interprete P4 . John Wiley . ISBN 0-13-653031-1.
- Pemberton, Steven , ed. (2011-04-13). "Attuazione di Pascal: un libro e fonti" .(NB. Dispone di sorgenti Pascal del compilatore e interprete P4 , istruzioni per l'uso.)
- Pemberton, Steven , ed. (2011-04-13). "pcode del compilatore Pascal come compilato da solo" .(NB. Ha il p-code del compilatore P4 , generato da se stesso.)
- "La pagina del Jefferson Computer Museum sul p-System UCSD" .
- "Implementazione Open Source" ., inclusi pacchetti e binari precompilati; un'amichevole forchetta del Klebsch. "Attuazione Klebsch" .
- Terry, Pat (2005). Compilazione con C# e Java . P. 624. ISBN 0-321-26360-X.
- Wirth, Niklaus (1975). Algoritmi + Strutture Dati = Programmi . ISBN 0-13-022418-9.
- Wirth, Niklaus (1996). Costruzione del compilatore . ISBN 0-201-40353-6.
- Liffick, Blaise W., ed. (1979). Il Byte Book di Pascal . ISBN 0-07-037823-1.
- Barron, David William , ed. (1981). Pascal: Il linguaggio e la sua implementazione . ISBN 0-471-27835-1.(NB. Vedi in particolare gli articoli Pascal-P Implementation Notes e Pascal-S: A Subset and its Implementation .)