Pregunta

Implementaré una máquina virtual en x86 y me pregunto qué tipo de diseño produciría los mejores resultados. ¿En qué debería concentrarme para aplastar el jugo? Voy a implementar toda la máquina virtual en el ensamblaje x86.

No tengo muchas instrucciones y puedo elegir su forma. Las instrucciones se proyectan directamente en la sintaxis de smalltalk en bloques. Entrego el diseño de instrucciones en el que estaba pensando:

^ ...       # return
^null     # return nothing
object    # address to object
... selector: ... # message pass (in this case arity:1 selector: #selector:)
var := ... # set
var # get

El tipo de VM en el que estaba pensando:

mov eax, [esi]
add esi, 2
mov ecx, eax
and eax, 0xff
and ecx, 0xff00 # *256
shr ecx, 5          # *8
jmp [ecx*4 + operations]
align 8:
    operations:
dd retnull
dd ret
# so on...
    retnull:          # jumps here at retnul
# ... retnull action
    ret:
# ... ret action
#etc.

No empiece a preguntar por qué necesito otra implementación de máquina virtual. Las rutinas interpretativas no son cosas de stock que solo recoges cuando las necesitas. La mayoría de las máquinas virtuales que propone en otros lugares están orientadas a la portabilidad con el costo del rendimiento. Mi objetivo no es la portabilidad, mi objetivo es el rendimiento.

La razón por la que se necesita este intérprete es porque los bloques smalltalk no terminan siendo interpretados de la misma manera:

A := B subclass: [
    def a:x [^ x*x]
    clmet b [...]
    def c [...]
    def d [...]
]

[ 2 < x ] whileTrue: [...]

(i isNeat) ifTrue: [...] ifFalse: [...]

List fromBlock: [
    "carrots"
    "apples"
    "oranges" toUpper
]

Necesito el beneficio real que proviene de las rutinas interpretativas, esa es la elección del contexto en el que leer el programa. Por supuesto, un buen compilador debería compilar la mayoría de las veces los casos obvios como: 'ifTrue: ifFalse' o 'whileTrue:', o el ejemplo de la lista. La necesidad de un intérprete no solo desaparece porque siempre puede llegar a un caso en el que no puede estar seguro de que el bloqueo reciba el tratamiento que espera.

¿Fue útil?

Solución

Veo que hay cierta confusión acerca de la portabilidad aquí, así que me siento obligado a aclarar algunas cosas. Estas son mis humildes opiniones, así que, por supuesto, eres libre de objetar en contra de ellas.

Supongo que se encontró con http://www.complang.tuwien.ac .at / forth / threading / si considera escribir una VM en serio, por lo que no me detendré en las técnicas descritas.

Ya mencionado, apuntar a una VM tiene algunas ventajas, como tamaño de código reducido, complejidad reducida del compilador (a menudo se traduce en una compilación más rápida), portabilidad (tenga en cuenta que el punto de una VM es la portabilidad del idioma , por lo que no importa si la VM en sí no es portátil).

Teniendo en cuenta la naturaleza dinámica de su ejemplo, su VM se parecerá más a un compilador JIT que a otros más populares. Entonces, aunque S.Lott perdió el punto en este caso, su mención de Forth está muy en el lugar. Si tuviera que diseñar una VM para un lenguaje muy dinámico, separaría la interpretación en dos etapas;

  1. Una etapa de productor que consulta un flujo AST a pedido y lo transforma en una forma más significativa (por ejemplo, tomar un bloque, decidir si debe ejecutarse de inmediato o almacenarse en algún lugar para su posterior ejecución) posiblemente introduciendo Nuevos tipos de tokens. Esencialmente recupera información sensible al contexto que puede perderse al analizar aquí.

  2. Una etapa de consumo que obtiene el flujo generado desde 1 y lo ejecuta ciegamente como cualquier otra máquina. Si lo hace como Forth, simplemente puede empujar una secuencia almacenada y terminar con ella en lugar de saltar el puntero de instrucciones.

Como usted dice, simplemente imitar cómo funciona el maldito procesador de otra manera no logra el dinamismo (o cualquier otra característica que valga la pena, como la seguridad) que necesita. De lo contrario, estaría escribiendo un compilador.

Por supuesto, puede agregar optimizaciones arbitrariamente comlex en la etapa 1.

Otros consejos

Si quiere algo realmente rápido, intente usar LLVM . Puede generar código nativo para la mayoría de los procesadores a partir de una descripción de programa de alto nivel. Puede utilizar su propio lenguaje ensamblador o generar la estructura llvm omitiendo la fase de ensamblaje, según lo que le resulte más conveniente.

No estoy seguro de si es lo mejor para su problema, pero definitivamente es lo que usaría si realizara una ejecución crítica de código de rendimiento que no se puede compilar con el resto del programa.

El objetivo de un intérprete es la portabilidad, la mayoría de las veces. El enfoque más rápido que se me ocurre es generar código x86 en la memoria directamente, al igual que los compiladores JIT, pero luego, por supuesto, ya no tienes un intérprete. Tienes un compilador.

Sin embargo, no estoy seguro de que escribir el intérprete en ensamblador le brinde el mejor rendimiento (a menos que sea un gurú de ensambladores y su proyecto tenga un alcance muy limitado). El uso de un lenguaje de nivel superior puede ayudarlo a centrarse en mejores algoritmos para, por ejemplo, la búsqueda de símbolos y las estrategias de asignación de registros.

puede acelerar su rutina de despacho con una instrucción no codificada establecida en:

mov eax, [esi]
add esi, 4
add eax, pOpcodeTable
jmp eax

que debería tener una sobrecarga < 4 ciclos para cada envío en cpu's & Gt; Pentium 4.

Además, por razones de rendimiento, es mejor incrementar ESI (IP) en cada rutina primitiva porque hay muchas posibilidades de que el incremento se pueda combinar con otras instrucciones:

mov eax, [esi]
add eax, pOpcodeTable
jmp eax

~ 1-2 cylces por encima.

Tengo que preguntar, ¿por qué crear una máquina virtual con un enfoque en el rendimiento? ¿Por qué no simplemente escribir código x86 directamente? Nada puede ser más rápido.

Si desea un lenguaje de muy interpretación rápida, mire Adelante . Su diseño es muy ordenado y muy fácil de copiar.

Si no le gusta JIT y su objetivo no es la portabilidad. Creo que puede interesarse en el proyecto NativeClient de Google. Lo hacen analista estático, sandboxing y otros. Permiten que el host ejecute instrucciones RAW x86.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top