Uma máquina, virtual ou não, precisa de um modelo de computação que descreva como a computação é realizada nela. Por definição, assim que calcula, implementa algum modelo de computação. A questão então é: qual modelo devemos escolher para nossa VM? Máquinas físicas são limitadas pelo que pode ser feito de maneira eficaz e eficiente em hardware. Mas, como você observa, as máquinas virtuais não têm essas restrições, elas são definidas no software usando linguagens arbitrariamente de alto nível.
De fato, existem máquinas virtuais de alto nível, como você descreve. Eles são chamados de linguagens de programação . O padrão C, por exemplo, dedica a maior parte de suas páginas à definição de um modelo para a chamada "máquina abstrata C", que descreve como o programa C se comporta e, por extensão (como regra geral), como um compilador (ou intérprete) compatível com C Deve se comportar.
Obviamente, geralmente não chamamos isso de máquina virtual. Geralmente, uma VM significa algo de nível inferior, mais próximo ao hardware, que não se destina a ser diretamente programado, projetado para ser executado com eficiência. Esse viés de seleção significa que algo que aceita código composível de alto nível (como o que você descreve) não seria considerado uma VM porque executa código de alto nível.
Mas, para ir direto ao ponto, aqui estão alguns motivos para criar uma VM (como em algo direcionado por um compilador de bytecode) com base em registro ou algo semelhante. Máquinas de empilhar e registrar são extremamente simples. Há uma sequência de instruções, algum estado e semântica para cada instrução (uma função Estado -> Estado). Sem reduções complexas de árvores, sem precedência do operador. Analisar, analisar e executar é muito simples, porque é uma linguagem mínima (o açúcar sintático é compilado) e projetado para ser lido por máquina em vez de humano.
Por outro lado, analisar até mesmo as linguagens C mais simples é bastante difícil e executá-lo exige análises não locais, como verificação e propagação de tipos, resolução de sobrecargas, manutenção de uma tabela de símbolos, resolução de identificadores de sequência , transformando texto linear em um AST baseado em precedência , e assim por diante. Ele se baseia em conceitos que são naturais para os seres humanos, mas precisam ser minuciosamente projetados de maneira reversa pelas máquinas.
O bytecode da JVM, por exemplo, é emitido por javac
. Praticamente nunca precisa ser lido ou escrito por seres humanos, por isso é natural orientá-lo para o consumo pelas máquinas. Se você é otimizado para os seres humanos, a JVM seria apenas em cada partida ler o código, analisá-lo, analisar é, em seguida, convertê-lo em uma representação intermediária semelhante a uma simplificada tal modelo de máquina de qualquer maneira . Poderia muito bem cortar o intermediário.