[toc]

# 概述

书接上回
http://146.56.209.11:90/2023/12/30/% e3%80%90% e7% bc%96% e8% af%91% e5%8e%9f% e7%90%86% e3%80%91-% e9%98% b6% e6% ae% b5% e6%80% a7% e5% b0%8f% e7% bb%93% e5%89%8d% e7% ab% af/ 编译器之后即是后端,这里主要是记录下学习后端的体系化知识

# 指令

通常语言在抽象语法树之后都会根据抽象语法树生成一套指令,通常指令都是使用
指令 操作数 A, 操作数 B…
这样的方式存储的 判断大小端的方式:
即随便搞一个整数,然后看看这个整数的高 16 位是否在高 16 位上

1
2
3
4
5
6
7
8
9
Instruction::ByteOrder Instruction::byteorder() {
int n = 0x12345678;
char str[4] = { 0 };
memcpy(str, &n, sizeof(4));
if (str[0] == 0x12) {
return Instruction::ByteOrder::BigEndian;
}
return Instruction::ByteOrder::LittleEndian;
}

# 编译器

ast 需要通过编译器来将高级语言转换成中间语言
中间语言进一步通过虚拟机和 C++ 编译器会转换成机器语言 compiler 通过仿照前端的 evaluator 阶段对 ast 进行遍历,然后生成对应的中间语言指令

# 虚拟机

编译器阶段生成的中间代码直接输入到虚拟机,然后类似于计算等操作是在虚拟机的栈上进行的,寄存器主要负责的是 A+B=C 这种 实际计算 的数据存储,但是最终计算的结果还是会存放在虚拟机的栈上 (计算结束会从栈上弹出),常量数组 / 函数等也会存储在虚拟机中
虚拟机不需要考虑编译期阶段复杂的遍历,只需要一条一条的处理指令即可。且需要存储一个指令指针。 类似于下面这种,通过 m_ip(指令指针) 对中间语言 (指令) 进行遍历与执行即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void VM::run() {
while (m_ip < m_instructions.size())
{
auto ins = m_instructions[m_ip++];
auto op = ins->opcode();
switch (op)
{
case pi::code::Code::OP_LOAD:
{
uint16_t val = 0;
ins->read(val);
push(m_constants[val]);
break;
}
case pi::code::Code::OP_POP:
{
pop();
break;
}
case pi::code::Code::OP_ADD:
case pi::code::Code::OP_SUB:
case pi::code::Code::OP_MUL:
case pi::code::Code::OP_DIV:
{
run_binary_operation(op);
break;
}
default:
{
auto code = Code::lookup(op);
std::cout << "operation undifined: opcode"<<std::endl;
}
}
}
}