跟我一起写个虚拟机 .Net 7(一)

主要参考和借鉴 silan-liu 微微笑的蜗牛 的 《听说你想写个虚拟机》系列

最近《中文编程语言——青语言》发布了,我表示即震惊又惭愧,几年前就开始准备也想自己写个中文编程,奈何每次自己都想的大而全导致遗落在某个角落。

在看了青语言的源码后,我感觉我又可以了。

为了避免自己好高骛远,还是从最底层的来,不要想着一下就搞定,可以多借鉴和学习已有的模式,从而真正积累起来。

最起码我还在路上。

虚拟机

虚拟机就像名字说的那种,它就是一个虚拟机,像VMware一样的虚拟机,但是,这个系列写下来,顶多写一个类似C#运行时(CLR) 或者 java的虚拟机(JVM)。

当然,肯定比CLR小很多就是了,主要还是偏向虚拟机 VMware的概念,偏向于用软件来模拟硬件的CPU,寄存器,堆栈等设备信息,来实现指令的执行,这样的虚拟机。

还是比较基础的,对计算机编译基础想有简单动手能力的,可以跟我一起,我动手能力有点差,正好适合我。

依稀记得在大学时期用汇编写51的时钟案例了,可惜太早了,都忘求了,现在,慢慢捡起来。

同时,也能让你理解一门语言是如何实现跨平台的(实际上就是每个平台运行了相应的虚拟机 [ 运行时/解释器 ] )

今天的任务

主要是通过最少的指令来实现一个加法,并发结果输出到控制台上。

大概有以下几个指令

指令集

/// <summary>
/// 指令集
/// </summary>
public enum InstructionSet
{
    /// <summary>
    /// PUSH 5; 
    /// 将数据放入栈中
    /// </summary>
    PUSH,
    /// <summary>
    /// 加法
    /// 取出栈中的两个操作数,执行加法操作,然后,放入栈中
    /// </summary>
    ADD,
    /// <summary>
    /// 取栈顶数据
    /// </summary>
    POP,
    /// <summary>
    /// 虚拟机停止运行
    /// </summary>
    HALT
}
指令集

这里有一个重点,我提一下那就是指令集是CPU带的,比如ARM指令集,Intel 指令集,以及RISC(精简指令集)。

指令集对应的其实就是汇编集,比如 ARM汇编,指令(机器码)与汇编(指令码)是一一对应的。

指令就是CPU对外提供的它能支持的各种操作,比如,加减乘除,跳转,判断等,而CPU与CPU之间是有差异的,它们对外提供的指令集不同。但是,大部分是相似的。

所以,真正执行指令的是CPU,而映射到虚拟机这个概念,执行的就是虚拟机本身。

目前提供了4条最简单的指令。

栈结构

栈结构很容易理解,相对于队列来讲。
队列是先进先出。

从图上可以看到,从左侧进入,右侧出去,里面是有序的,从右到左排列,就像去超市结账排队一样。

栈是后进先出。

从口里进去,然后,从口里出去,就像堆了一堆砖头,你总得从最上边把砖头拿走(最下边的不好抽出来)。

理解了堆栈的结构,就有助于理解,接下来的操作了。

逻辑

PUSH,就是堆栈的进操作。堆栈指针要在执行后+1;(累加),它有一个操作数,比如:PUSH 5 ; 就是把5压到堆栈上。

POP,就是堆栈的出操作。堆栈指针要在执行后-1;(累减),它只能获取一个数据,比如:POP ; 就弹出栈顶的数据。

ADD,就是对加数与被加数进行加操作,它只能从栈里获取两个数据,并把结果PUSH到堆栈上。

HALT ,没有操作数,就是任务执行完毕的意思。

寄存器相关

/// <summary>
/// 指令指针寄存器
/// </summary>
public int IP { get; private set; } = 0;
/// <summary>
/// 栈指针寄存器
/// </summary>
public int StackPointer { get; private set; } = -1;
/// <summary>
/// 栈
/// </summary>
public int[] Stack { get; private set; } = new int[256];

主要是IP,指令指针寄存器和栈指针寄存器以及我们所定义的栈。

控制指令指针IP,就可以让CPU(虚拟机)从所设定的位置,继续往下执行我们给它的指令集合(程序)。

而,栈指针寄存器(sp)是控制和表现Stack栈结构的辅助索引。

虚拟机相关

public class VM
{
    public bool Runing { get; private set; }
    /// <summary>
    /// 指令指针寄存器
    /// </summary>
    public int IP { get; private set; } = 0;
    /// <summary>
    /// 栈指针寄存器
    /// </summary>
    public int StackPointer { get; private set; } = -1;
    /// <summary>
    /// 栈
    /// </summary>
    public int[] Stack { get; private set; } = new int[256];
    public void Run(int[] Instructions)
    {
        Start();
        while (Runing)
        {
            int instruction = Instructions[IP];
            Eval((InstructionSet)instruction, Instructions);
            IP++;
        }
        Console.WriteLine("VM 虚拟机 退出运行!");
    }
    private void Eval(InstructionSet instruction, int[] Instructions)
    {
        switch (instruction)
        {
            case InstructionSet.HALT:
                Runing = false;
                Console.WriteLine("虚拟机停止");
                break;
            case InstructionSet.PUSH:
                StackPointer++;
                ++IP;
                Stack[StackPointer] = Instructions[IP];
                break;
            case InstructionSet.POP:
                int popValue = Stack[StackPointer];
                StackPointer--;
                Console.WriteLine($"poped {popValue}");
                break;
            case InstructionSet.ADD:
                //从栈中取出两个操作数,相加,然后,保存在栈中
                int a = Stack[StackPointer];
                StackPointer--;
                int b = Stack[StackPointer];
                StackPointer--;
                int sum = a + b;
                StackPointer++;
                Stack[StackPointer] = sum;
                break;
        }
    }
    public void Start()
    {
        Runing = true;
        IP = 0;
        Stack = new int[256];
        StackPointer = -1;
    }
}

整体逻辑就是,先初始化,start,恢复初始环境,相当于重启,然后,根据程序,依次往下执行,直到遇到停止的指令,才会退出执行。

主函数

static void Main(string[] args)
{
    VM VM = new VM();
    var program = new List<int>()
    {
        (int)InstructionSet.PUSH,5,
        (int)InstructionSet.PUSH,6,
        (int)InstructionSet.ADD,
        (int)InstructionSet.POP,
        (int)InstructionSet.HALT
    };
    VM.Run(program.ToArray());
    Console.ReadLine();
}

因为我的操作码是枚举来的,所以,在C#里需要转码一下,当然,后期的操作码和操作数应该能合成一条指令,类似字节码。

program就是定义的程序(汇编),压入了5,压入了6,执行加法,然后,弹出结果,停止虚拟机的执行。

结果如下

弹出了结果,11,结果正确。

然后,依次各个退出,顺利完成极简版的虚拟机。

敬请期待下篇《跟我一起写个虚拟机 .Net 7(二)》

代码地址

https://github.com/kesshei/VirtualMachineDemo.git

https://gitee.com/kesshei/VirtualMachineDemo.git

致谢

我主要是看 silan-liu 微微笑的蜗牛 的 《听说你想写个虚拟机》系列。

作者写的真不错,有喜欢的,可以去支持一下。

一键三连呦!,感谢大佬的支持,您的支持就是我的动力!

posted @ 2023-06-08 23:16  蓝创精英团队  阅读(7)  评论(0)    收藏  举报  来源