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 a nossa VM? As máquinas físicas são limitadas pelo que pode ser efetivamente e eficientemente feito em hardware. Mas, como você observou, as máquinas virtuais não têm essas restrições, elas são definidas no software usando linguagens arbitrariamente de alto nível.
Existem, na verdade, 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 os programas C se comportam e por extensão (como regra) como um compilador C (ou interpretador) deve se comportar.
Claro, geralmente não chamamos isso de máquina virtual. Uma VM é geralmente utilizada para significar algo de nível inferior, mais próximo do hardware, não destinado a ser diretamente programado, projetado para ser executado de forma eficiente. Esse viés de seleção significa que algo que aceita código composable de alto nível (como o que você descreve) não seria considerado uma VM porque é executado código de alto nível.
Mas para chegar ao ponto, aqui estão algumas razões para tornar uma VM (como em algo direcionado por um compilador bytecode) baseada em registro ou algo semelhante. As máquinas de pilha e registro são extremamente simples. Há uma sequência de instruções, algum estado e semântica para cada instrução (uma função State - > State). Nenhuma redução de árvore complexa, sem precedência de operador. Analisar, analisar e executar é muito simples, porque é uma linguagem mínima (o açúcar sintático é compilado) e projetado para ser lido em máquina em vez de leitura humana.
Por outro lado, analisar até mesmo as linguagens C mais simples é muito difícil, e executá-las requer análises não locais, como verificar e propagar tipos, resolver sobrecargas, manter uma tabela de símbolos, resolver identificadores string , transformando texto linear em uma AST orientada por precedência e assim por diante. Baseia-se em conceitos que são naturais aos humanos, mas que precisam ser meticulosamente submetidos a engenharia reversa por máquinas.
O bytecode da JVM, por exemplo, é emitido por javac
. Ele praticamente nunca precisa ser lido ou escrito por humanos, então é natural direcioná-lo para o consumo de máquinas. Se você otimizá-lo para humanos, a JVM iria apenas em cada inicialização ler o código, analisá-lo, analisar é e, em seguida, convertê-lo em uma representação intermediária semelhante a um modelo de máquina simplificado de qualquer maneira . Pode muito bem cortar o homem do meio.