Qual è la procedura (se presente) per selezionare i byte per rappresentare gli opcode?

5

TL; DR

Quale procedura viene seguita quando si selezionano i byte per rappresentare gli opcode? I byte per gli opcode sono scelti casualmente e sono mappati su mnemonici?

Recentemente ho appreso da questo ha risposto che il bytecode di solito consiste di istruzioni che avere un codice operativo, che consiste in un numero fisso di byte e operandi. In particolare questo frammento di maniaco del cricchetto risponde:

The bytecode itself is very often a very simple syntax. Where the first few bytes indicate what operation has to be performed and what operands are needed. The bytecode will be designed so that when reading byte per byte there is a unambiguous interpretation of the instructions.

Ho seguito questo consiglio e ho iniziato a progettare il mio set di istruzioni bytecode. Ma presto ho avuto un problema. Prima di porre la domanda , avevo provato a creare opcode usando metodi come :

# pseudo code

opcodes = {
    'PUSH': convertToBytes(5),
    'POP': convertToBytes(10),
    'ADD': converToBytes(15),
     etc...
}

Come puoi probabilmente sapere, nell'esempio precedente ho usato interi che erano multipli di cinque e li ho convertiti in forma di byte. Stavo cercando di creare un modo in cui potevo mappare ordinatamente i miei codici operativi, a qualcosa come numeri interi. Questo, naturalmente, non ha funzionato perché ogni singolo numero è diventato più grande, così ha fatto il numero di byte relativo a ciascun intero. Il che significava che i miei codici operativi sarebbero stati di lunghezza variabile.

Poi ho iniziato a chiedermi se stavo andando in questo modo nel modo sbagliato. Ho fatto qualche ricerca per vedere come altri linguaggi progettano gli opcode.

Ho trovato questa pagina web relativa ai codici operativi della CPU che dicevano:

The x86 CPU has a set of 8-16 bit codes that it recognizes and responds to. Each different code causes a different operation to take place inside the registers of the CPU or on the buses of the system board.

Here are three examples showing the bit patterns of three actual x86 opcodes, each followed by their one or more bytes of operands:

E continua a fare un esempio:

  Bit Pattern  ; operation performed by CPU
  -----------  -------------------------------------------------------
1. 10111000    ; MOVe the next two bytes into 16-bit register AX
2. 00000101    ; ...the LSB of the number (goes in AL)
3. 00000000    ; ...the MSB of the number (goes in AH)

1. 00000001    ; ADD to the BX register
2. 11000011    ; ...the contents of the AX register

1. 10001001    ; (2-byte opcode!) MOVe the contents of BX to
2. 00011110    ; ...the memory location pointed to
3. 00100000    ; ...by these last
4. 00000001    ; ...two bytes

Questo mi porta alla mia domanda: Quale procedura viene seguita quando si selezionano i byte per rappresentare gli opcode? . Come nell'esempio precedente, ogni istruzione consiste di un byte. Come, tuttavia, è stato lo schema specifico di un byte selezionato? . I byte per gli opcode sono scelti casualmente e sono mappati su mnemonici? ad esempio:

# pseudo code

opcodes = {
    'PUSH': selectRandomByte(),
    'POP': selectRandomByte(),
    'ADD': selectRandomByte(),
     etc...
}

Nota: mi permetta di chiarire: Quando dico opcode, mi riferisco agli opcode trovati in Virtual Machine bytecode , non alla CPU. Mi scuso se non era chiaro prima. L'esempio che ho fornito con i codici opzionali della CPU era solo a scopo illustrativo.

Fonti

posta Christian Dean 10.11.2016 - 22:33
fonte

3 risposte

9

Non è casuale, ma potrebbe non essere immediatamente evidente. E potrebbe essersi evoluto nel tempo: l'architettura x86, per esempio, è stata con noi per quasi 40 anni (1977), e si è evoluta da 16-bit a 32, a 64, con operazioni aggiuntive (come MMX e SSE) ) aggiunto in quel momento.

Le architetture che esistono da molto tempo hanno in genere una relazione tra gli opcode e i segnali elettrici effettivi usati per controllare la CPU.

In alcune architetture, come PDP-11 , esiste un piano chiaro per gli opcode. Tutti gli opcode si adattano a una parola di 16 bit, generalmente divisa in cifre ottali a 3 bit, con il seguente uso generale:

  • Bit 15: 1 per un'operazione a byte, 0 per un'operazione a parola
  • Bit 14-12: 0 indica un'istruzione che accetta un singolo operando, 1-6 indica un'istruzione che accetta due operandi, 7 è per operazioni che non seguono la codifica dell'operando standard.
  • Bit 11-6: operando sorgente per istruzioni a due operandi, altrimenti denota l'istruzione specifica per singolo operando.
  • Bit 5-0: operando di destinazione per istruzioni a due operandi, altrimenti l'unico operando per istruzioni a singolo operando.

Nel caso del codice bytecode Java , non vi è alcun elemento elettrico sottostante architettura, e nessuna vera ragione per mettere intelligenza all'interno della struttura del bytecode, quindi le operazioni correlate sono raggruppate e assegnate a numeri sequenziali. Quindi, anche se c'è una ragione per cui iconst_0 e iconst_1 hanno opcode adiacenti, non c'è (probabilmente) alcuna ragione per cui questi compaiono prima degli opcode di matematica interi.

    
risposta data 10.11.2016 - 23:33
fonte
3

Non ho mai progettato un bytecode, ma qui ci sono alcune cose che prenderei in considerazione se lo facessi:

  1. Penso che non importi molto. Se le persone vorranno lavorare con il tuo bytecode, lo faranno usando i mnemonici, non i valori dei byte. E qualsiasi schema tu possa escogitare probabilmente non reggerà quando aggiungi nuovi codici opzionali in futuro.

  2. Pianifica il futuro. Assicurati di poter aggiungere facilmente nuovi codici opzionali in futuro e di rimanere compatibili con le versioni precedenti.

  3. Se hai intenzione di implementare il set di istruzioni in hardware, potrebbe avere senso progettare i valori opcode in modo facile da decodificare per l'hardware. Ma penso che questo sia importante solo per le CPU più semplici.

  4. Raggruppa i codici operativi correlati insieme.

  5. Se hai alcune istruzioni che hanno valori incorporati (ad esempio ldc.i4.1 in CIL o iconst_1 in Java), potrebbe essere carino se il valore fosse visibile nel valore opcode. O almeno avere gli stessi codici opzionali solo con valori diversi ordinati l'uno accanto all'altro (il valore di iconst_2 dovrebbe essere uno più di inconst_1 ).

risposta data 10.11.2016 - 23:14
fonte
1

Se il tuo codice byte è solo per un interprete software, probabilmente non avrai bisogno di alcuna regola. Probabilmente ...

Originariamente i codici byte provenivano dalla progettazione hardware. E le regole erano dettate dall'hardware. Alcuni modelli di bit erano riservati, ad esempio, ai codici di derivazione, ai codici di caricamento, ai codici negozio, ecc. Nella maggior parte dei casi questo ha anche aiutato gli utenti a decodificare il codice macchina.

La regola precedente si adattava alla maggior parte degli opcode. Ma ovviamente anche nella gente dell'hardware è stato necessario applicare il patch design. Quindi alcuni codici non erano ortogonali, perché erano progettati dopo che la maggior parte dell'HW era già terminata.

Se è necessario leggere manualmente il codice byte dai byte, provare a creare un determinato modello che può aiutare a decifrarlo. Per il software che legge il tuo byte code non importa quale pattern di bit è usato e potresti sceglierne uno casuale.

    
risposta data 10.11.2016 - 23:27
fonte