¿Por qué las máquinas virtuales deben ser "máquinas de pila" o "máquinas de registro", etc.?

48

(Esta es una pregunta extremadamente novata).

He estado estudiando un poco sobre máquinas virtuales.

Resulta que muchos de ellos están diseñados de manera muy similar a las computadoras físicas o teóricas.

Leí que la JVM, por ejemplo, es una 'máquina de pila'. Lo que eso significa (y corrígeme si me equivoco) es que almacena toda su 'memoria temporal' en una pila, y realiza operaciones en esta pila para todos sus códigos de operación.

Por ejemplo, el código fuente 2 + 3se traducirá a bytecode similar a:

push 2
push 3
add

Mi pregunta es esta:

Las JVM probablemente se escriben usando C / C ++ y tal. Si es así, ¿por qué la JVM no ejecuta el siguiente código C: 2 + 3..? Quiero decir, ¿por qué necesita una pila, o en otros 'registros' de máquinas virtuales, como en una computadora física?

La CPU física subyacente se encarga de todo esto. ¿Por qué los escritores de VM simplemente no ejecutan el bytecode interpretado con instrucciones 'habituales' en el idioma con el que está programada la VM?

¿Por qué las máquinas virtuales necesitan emular hardware, cuando el hardware real ya lo hace por nosotros?

De nuevo, preguntas muy novatas. Gracias por tu ayuda

Aviv Cohn
fuente
55
¿Ha considerado en qué se basan las máquinas no virtuales?
55
@MichaelT ¿Te refieres a máquinas físicas?
Aviv Cohn
Por supuesto, la mayoría de las máquinas virtuales de Javascript no son máquinas apiladas o máquinas de registro: V8 / IonMonkey / Chakra / etc. son máquinas virtuales que implementan Javascript. Una "VM" es solo un intérprete o un compilador JIT que puede implementar cualquier lenguaje que el diseñador lo desee.
Billy ONeal
@BillyONeal Entonces, por ejemplo, si estoy escribiendo una VM para algún lenguaje y la estoy escribiendo en C: la VM analiza la línea de bytcode 'print "hi"' y ejecuta printf("hi");: ¿se considera esto una VM? No tiene 'pila' ni 'registros' ni nada.
Aviv Cohn
@Prog: Sí, eso es correcto.
Billy ONeal

Respuestas:

51

Una máquina, virtual o no, necesita un modelo de computación que describa cómo se realiza la computación en ella. Por definición, tan pronto como computa, implementa algún modelo de computación. La pregunta entonces es: ¿Qué modelo deberíamos elegir para nuestra VM? Las máquinas físicas están limitadas por lo que se puede hacer de manera efectiva y eficiente en el hardware. Pero, como observa, las máquinas virtuales no tienen tales restricciones, están definidas en software que utiliza lenguajes arbitrariamente de alto nivel.

De hecho, hay máquinas virtuales que son de alto nivel como usted describe. Se llaman lenguajes de programación . El estándar C, por ejemplo, dedica la mayor parte de sus páginas a definir un modelo para la llamada "máquina abstracta C", que describe cómo se comportan los programas C y, por extensión (como si fuera la regla), cómo un compilador (o intérprete) de C conforme debería comportarse.

Por supuesto, generalmente no lo llamamos máquina virtual. Una VM generalmente significa algo de nivel inferior, más cercano al hardware, que no está destinado a ser programado directamente, diseñado para ejecutarse de manera eficiente. Este sesgo de selección significa que algo que acepta código componible de alto nivel (como lo que usted describe) no se consideraría una VM porque se ejecuta código de alto nivel.

Pero para ir al grano, aquí hay algunas razones para hacer que una VM (como en, algo dirigido por un compilador de bytecode) esté basada en registros o similares. Las máquinas de apilar y registrar son extremadamente simples. Hay una secuencia de instrucciones, algunos estados y semántica para cada instrucción (una función Estado -> Estado). Sin reducciones complejas de árboles, sin precedencia de operadores. Analizarlo, analizarlo y ejecutarlo es muy simple, porque es un lenguaje mínimo (el azúcar sintáctico se compila) y está diseñado para ser leído en máquina en lugar de ser leído por humanos.

Por el contrario, analizar incluso los lenguajes tipo C más simples es bastante difícil, y su ejecución requiere análisis no locales como verificar y propagar tipos, resolver sobrecargas, mantener una tabla de símbolos, resolver identificadores de cadena , convertir texto lineal en un AST basado en precedencia , y así. Se basa en conceptos que son naturales para los humanos pero que deben ser cuidadosamente diseñados por máquinas.

El bytecode de JVM, por ejemplo, es emitido por javac. Prácticamente nunca necesita ser leído o escrito por humanos, por lo que es natural orientarlo hacia el consumo de las máquinas. Si lo optimiza para humanos, la JVM solo en cada inicio leería el código, lo analizaría, analizaría y luego lo convertiría en una representación intermedia que se pareciera a un modelo de máquina simplificado de todos modos . También podría eliminar al intermediario.


fuente
Entonces, ¿qué está diciendo es que compilar todo en instrucciones en la pila ( System.out.println("hi");es decir, se compila en alguna instrucción en una pila, int a = 7se compila en una instrucción en la pila, etc.) hace que la ejecución del programa sea simple y más eficiente?
Aviv Cohn
2
@Prog Básicamente, sí. Pero no solo ejecución, también análisis. Todo lo que se hace mediante programación.
Aún así, no entiendo por qué 2 + 3se compila push 2 push 3 add. El addpaso al final es ejecutado por la JVM de todos modos mediante la ejecución del código C 2 + 3. No hay otra forma para que los programadores de la JVM hagan esto. ¿Por qué no compilarlo 2 + 3y hacer que la JVM solo ejecute el código C 2 + 3(suponiendo que esté escrito en C) de inmediato?
Aviv Cohn
@Prog El autor de JVM no puede simplemente escribir 2 + 3el código fuente de JVM porque JVM tiene que trabajar con cualquier programa que realice cualquier operación en cualquier orden. Construir código fuente C y diferir a una implementación C simplemente empuja el mismo problema a la implementación C (y no se puede hacer fácilmente, y mucho menos eficientemente). Tiene que haber alguna estructura de datos que describa el programa, para que pueda ser interpretado y compilado JIT, y el "código fuente legible por humanos" es una elección horrible de estructura de datos por las razones descritas anteriormente.
77
@Prog Pareces demasiado concentrado en el caso específico de 2 + 3. ¿Qué pasa a + b? Entonces los valores a agregar no provienen i.argument{1,2}, se cargan desde variables locales. ¿Qué hay de frobnicate(x[i]) + (Foo.bar() * 2)? Con este diseño, solo hay una addoperación (para int) y funciona independientemente de cómo se calcularon los sumandos. Además, una instrucción que agrega solo literales enteros no tendría sentido: su resultado también podría calcularse previamente (es decir, en lugar de add(2,3)serlo push(5)).
20

Esta respuesta se centra en la JVM, pero de hecho se aplica a cualquier VM.

¿Por qué las máquinas virtuales necesitan emular hardware, cuando el hardware real ya lo hace por nosotros?

No lo hacen, pero hace que la VM sea mucho más simple y portátil: una VM que emula hardware puede usar el mismo modelo computacional que cualquier CPU de hardware.

La JVM en particular se construyó con la portabilidad en mente, de hecho, se construyó para que incluso se pudiera implementar en hardware (puede ser difícil de creer hoy, pero el origen de Java se encontraba en el mundo integrado, específicamente, los controladores para televisión interactiva )

Si tiene un objetivo como este, es deseable que la VM funcione lo más cerca posible de una máquina física, ya que la traducción al código real de la máquina se vuelve más fácil y, por lo tanto, más rápida. Una vez que tenga los códigos de operación de la VM, en teoría, todo lo que tiene que hacer es traducir a los códigos de operación de la CPU en la que realmente se ejecuta el programa. En la práctica no es exactamente así de simple.

Quiero decir, ¿por qué necesita una pila, o en otros 'registros' de máquinas virtuales, como en una computadora física?

El uso de un modelo de máquina virtual basado en pila tiene la ventaja de que puede transferirse fácilmente tanto a máquinas de registro como a máquinas de pila, mientras que lo contrario no es necesariamente cierto. Una máquina virtual basada en registros necesitaría hacer suposiciones sobre el número de registros, el tamaño de los registros, etc. Con una máquina de pila, no se necesitan tales suposiciones.

La CPU física subyacente se encarga de todo esto. ¿Por qué los escritores de VM simplemente no ejecutan el bytecode interpretado con instrucciones 'habituales' en el idioma con el que está programada la VM?

Bueno, eso es lo que hacen esas máquinas virtuales: interpretan el código de bytes. Incluso la JVM hace eso, al menos antes de que JIT (justo a tiempo) entre en funcionamiento: interpreta los códigos de bytes y ejecuta las declaraciones en el lenguaje en el que se escribió la JVM (generalmente C o C ++, pero incluso hay una escrita en JavaScript, Doppio ). Sin embargo, tenga en cuenta que incluso esas declaraciones fueron traducidas al código de máquina por un compilador y en realidad se parecen mucho a lo que produce el compilador de Java, es decir, usan registros y la pila para realizar su trabajo. Tenga en cuenta que el uso de lenguajes "interpretados" frente a "compilados" se vuelve algo borroso en este punto.

miraculixx
fuente
Por supuesto, cualquier cosa que pueda implementarse en software puede implementarse en hardware. Además, el JVM actualmente (punto de acceso) es un compilador JIT: no ejecuta las declaraciones en el idioma en que se escribió el JVM. Si lo hiciera, Java funcionaría terriblemente y no sería una plataforma tan viable como lo es hoy. . (Diablos, la mayoría de las implementaciones de Javascript serían más rápidas)
Billy ONeal
2
@BillyONeal "En lugar de compilar método por método, justo a tiempo, Java HotSpot VM ejecuta inmediatamente el programa utilizando un intérprete, y analiza el código mientras se ejecuta para detectar los puntos críticos críticos en el programa. Luego enfoca la atención de un optimizador global de código nativo en los puntos críticos ". Citado de oracle.com/technetwork/java/whitepaper-135217.html#2 , sección" Detección de puntos críticos "
miraculixx
Si. "Optimizador de código nativo" == compilación JIT. Hay una fase de intérprete para el código que no parece estar "activa" para evitar JITing cosas que rara vez se usan. Pero eso no significa que no se haga JITing en absoluto.
Billy ONeal
Gracias por responder. Lo que deduje de su respuesta es que las razones para emular el hardware en la VM (también conocido como 'pilas' o 'registros', etc.) es porque facilita la compilación posterior del código de bytes o el código fuente al código de máquina real de un CPU física. Sin embargo, aparte de eso, ¿hay algo que ganar emulando hardware en una VM? Todavía no entiendo por qué alguien que diseña una VM pensaría en términos de una 'máquina de pila' o 'máquina de registro', etc., cuando en realidad estamos hablando de software. ¿Me estoy perdiendo de algo?
Aviv Cohn
@Prog Ok, tienes un lenguaje de programación, di X. ¿Cómo ejecutarás sus programas? Puede interpretar la fuente o compilarla en código máquina, o compilarla en algún código intermedio. Ahora tiene otro lenguaje de programación, Y, y desea implementarlo usando X. Si ambas implementaciones son intérpretes, tendrá el intérprete de Y ejecutándose en el intérprete de X, y esto será muy lento.
18446744073709551615
11

¿Por qué las máquinas virtuales deben ser "máquinas de pila" o "máquinas de registro", etc.?

Ellos no. Si necesita una máquina virtual, puede ser cualquier cosa.

Las máquinas virtuales existentes han aparecido como soluciones a situaciones como: Me ha surgido una idea realmente brillante, ¡he inventado un nuevo lenguaje de programación! Pero tengo que generar código. (¡Qué tarea más aburrida!) Pero no quiero generar el código i8086 porque es feo, y no quiero generar el código 68k porque todos los demás están usando Intel. También hay VAX, pero no tengo ningún VAX, ni una computadora ni un libro VAX. Por lo tanto, generaré código para algún procesador que no existe físicamente e implementaré ese procesador en el software. La especificación de esa VM hará un capítulo en mi tesis. En teoría, será posible compilarlo en el código nativo de cualquier procesador, pero ese no seré yo.

Por otro lado, la notación como "2 + 3" probablemente no será utilizada por las máquinas virtuales en un futuro previsible porque implica hacer muchas transformaciones antes de que algo pueda ejecutarse.

18446744073709551615
fuente
Gracias por responder. Entonces, lo que deduje de su respuesta es que la motivación para diseñar una VM que emule CPU físicas es porque facilita la implementación posterior de compiladores que compilan el código de máquina real. Pero aparte de eso, ¿hay alguna ventaja en diseñar una VM en términos de una 'máquina de pila' o una 'máquina de registro', etc.?
Aviv Cohn
1
Los registros requieren algoritmos de asignación de registros, que necesitan teoría y depuración. Una máquina de pila (especialmente una de cero operando) puede simplemente colocar los datos en la pila. OTOH, el hardware generalmente implementa una cantidad limitada de registros en lugar de una pila de tamaño variable. Entonces, las pilas son más fáciles para el software, los registros son más fáciles para el hardware y, por lo tanto, probablemente un poco más rápidos.
18446744073709551615
-2

Para responder a la pregunta real que se hizo. El término "MÁQUINA virtual" significa que TODO el software / hardware se simula / emula. Si usa el software / hardware subyacente para ejecutar las instrucciones, entonces no tiene una VM, tiene un compilador / intérprete.

Kyrelel
fuente
¿Es esta simplemente su opinión o puede respaldarla de alguna manera?
mosquito
@ Kyrelel eso no es cierto. "TODO" el hardware se emula en la máquina virtual "sistema" o "completa". No todas las máquinas virtuales están llenas. Por ejemplo, la capa BSD VM se denomina "máquina virtual", a pesar de que el hardware no se emula allí.
Netch
No creo que la pregunta sea necesariamente sobre terminología, sino más bien por qué las máquinas virtuales implementan una funcionalidad que aparentemente ya está manejada por el hardware real
Ryan