下一代编程语言出炉

简介: Mio 是一款由 C++ 实现的新兴编程语言,主打极致编译速度——大型项目编译比 C++ 快近 10 倍。语法简洁现代(支持模板、类、模块化导入、宏与条件编译),采用上下文无关 CFG 设计,语义分析阶段完成模板实例化,并基于 LLVM 生成高效代码。开源地址:github.com/mioLanguage。

省流:我用 c++ 实现了一款编译大型项目比 c++ 快近 $10$ 倍的编程语言。

组织地址 | 仓库地址。
想听干货的到下一章。

Mio 语言简介

灵感

一次偶然的机会,我知道到了 python,对它的原理特别感兴趣,研究过一段时间,写了个简易解释器。保存的 U 盘被一个 xxs 借走然后弄丢了,本来就没画多少时间写,于是就懒得重写了。

机遇

我在 zsjn 中学冬令营和同学吹牛,聊到了以前写的解释器,于是 ld^1 就复现了一个解释器?然后它集成汇编的难写和 python 的巨慢,不过在我们努力地调屎下,我们实现了图灵完备性^2。

ld 百思苦想给变量赋值的指令,随手打出了 mio 这三个字符,然后用这三个非常有意义的字符给我们的语言命名了。

重写

今年暑假前夕,我突然翻到我们在 github 上留下的 mio,觉得当时写的过于简陋,于是开始了深入学习,下面讲分享我半个暑假的成果。

语法简介

可能写的不好,以 github 仓库上的为准。

注释

# 后面的内容为注释,直到行尾:

# 这是一行注释
var x=10;  # 行末注释

导入

关键字:import

使用方法:导入 mio 头文件,必须写在文件顶层。可以用-I命令指定头文件路径。

mioc hello.mio -I ./include
import stdio;              # 导入 C 标准库
import "mylib.mio";          # 导入 mio 文件(用引号包裹)
import stdio,"lib";   # 同时导入多个

宏与条件编译

关键字:macro,@[if,elif,else,end]

使用方法:定义宏来替换代码和条件编译防止重复引入。可以用 -D 命令定义宏。

mioc hello.mio -D DEBUG
macro DEBUG;
macro RELEASE;
@if DEBUG
    printf("DEBUG\n");
@elif RELEASE
    printf("RELEASE\n");
@else
    printf("UNKNOWN\n");
@end

变量

关键字:var(可变)、const(常量)

使用方法:

var 变量名: 类型=初始值;      # 变量
const 常量名: 类型=初始值;    # 常量(不可修改)

基本类型

类型 说明
i8 / i16 / i32 / i64 / i128 有符号整数
u8 / u16 / u32 / u64 / u128 无符号整数
isize / usize 指针宽度整数(32 / 64 位自适应)
f32 / f64 浮点数
bool 布尔值(true / false)
char 单个字符
T[N] 长度为 $N$ 的数组
*T、**T… 指针类型
&T 可变引用类型
&&T 不可变可变引用类型,在模板里可以隐式转换成 &T

示例

var x: i32=42;
const PI: f64=3.14;

# 同时声明多个
var a: i64=1,b: i32=2;

# 数组
var arr: i32[3]={1,2,3};

自动补全类型

有初始值时可以省略类型,编译器自动推导:

var x=42;          # i32
var pi=3.14;       # f64
var flag=true;     # bool
var nums={1,2,3};  # i32[3]

字符串与字符

var s: char*="hello";    # 字符串字面量
var ch: char='A';        # 字符字面量

全局变量与命名空间访问

用 :: 前缀访问全局变量(避免与局部变量同名冲突):

var x: i32=10;
void foo() {
    var x: i32=20;
    printf("%d\n",::x);   # 访问全局 x,输出 10
    printf("%d\n",x);     # 访问局部 x,输出 20
}

控制流(if)

关键字:if、else

基本用法

条件必须用括号括起来

if (条件) {
    代码块
} else if (条件) {
    代码块
} else {
    代码块
}

示例

if (score >= 90) {
    printf("A\n");
} else if (score >= 80) {
    printf("B\n");
} else {
    printf("C\n");
}

单语句简写

单语句可以省略大括号:

if (x > 0) printf("正数\n");

循环

while 循环

关键字:while

条件必须用括号括起来

var i: i32=0;
while (i < 5) {
    printf("%d\n",i);
    i=i+1;
}

for 循环

关键字:for

格式:for (初始化; 条件; 更新) { 代码块 }

var sum: i32=0;
for (i=0; i < 10; i=i+1) {
    sum=sum+i;
}

省略部分

var i: i32=0;
for (; i < 5; i=i+1) {   # 省略初始化
    printf("%d\n",i);
}

break 与 continue

while (true) {
    if (x > 100) {
        break;       # 跳出循环
    }
    if (x % 2 == 0) {
        x=x+1;
        continue;    # 跳过本次迭代剩余部分
    }
    x=x+1;
}

跳转(goto)

关键字:goto

定义标签::标签名

var i: i32=0;
:loop
    printf("%d\n",i);
    i=i+1;
    if (i < 5) {
        goto loop;
    }

运算符

算术运算符

运算符 说明
+ - * / % 加减乘除取模
- 取负(一元)
~ 按位取反

比较运算符

运算符 说明
== != 等于 / 不等于
< > <= >= 小于 / 大于 / 小于等于 / 大于等于

逻辑运算符

运算符 说明
&& `\ \ `! 逻辑与 / 逻辑或 / 逻辑非

位运算符

运算符 说明
& `\ `^ 按位与 / 按位或 / 按位异或
<< >> 左移 / 右移

赋值运算符

运算符 说明
= 赋值
+= -= *= /= %= 复合赋值
&= `\ =`^= <<= >>= 位复合赋值

运算符重载(结构体 / 类)

在结构体或类中定义 operator+、operator- 等方法实现运算符重载:

struct Point {
    x: f64;
    y: f64;
    Point operator+(other: Point) {
        return Point(this.x+other.x,this.y+other.y);
    }
}

类型转换

使用 C 风格的类型转换语法:

var x: f64=3.14;
var y: i32=i32(x);        # 浮点数转整数(截断)
var z: i64=i64(y);        # 整数扩展
var n: u32=u32(-1);       # 有符号转无符号

函数

定义格式

返回类型 函数名(参数名: 参数类型,...) {
    函数体
}

示例

# 显式返回
i32 add(a: i32,b: i32) {
    return a+b;
}

# 隐式返回(最后一行不加分号)
i32 add(a: i32,b: i32) {
    a+b
}

# 无返回值
void say_hello() {
    printf("hello\n");
}

# 静态函数(仅当前文件可见)
static void helper() {
    printf("helper\n");
}

# 仅声明函数(其他文件定义)
extern i32 printf(fmt: char*,...);

函数调用

var result=add(10,20);
say_hello();

类

关键字:class

类与结构体类似,但支持继承、虚函数和访问控制。类默认使用引用语义(通过指针操作)。

定义

class 类名 {
    访问控制:
    字段名: 类型;
    方法定义...
}

访问控制

关键字 说明
public: 公开成员,外部可访问
private: 私有成员,仅类内部可访问
protected: 受保护成员,类及其子类可访问

构造函数与析构函数

构造函数名与类名相同,析构函数以 ~ 开头:

class Animal {
public:
    name: char*;

    Animal(name: char*) {
        this.name=name;
    }
    ~Animal() {
        printf("Animal destroyed\n");
    }
}

继承

使用 类名(父类:访问控制) 语法继承父类:

class Dog(Animal:public) {
public:
    Dog(name: char*) {
        this.name=name;
    }
}

虚函数与重写

用 virtual 声明虚函数,子类用 override 重写:

class Animal {
public:
    virtual void speak() {
        printf("Animal speak\n");
    }
};

class Dog(Animal:public) {
public:
    override void speak() {
        printf("Dog: woof!\n");
    }
};

完整示例

class Animal {
public:
    name: char*;

    Animal(name: char*) {
        this.name=name;
        printf("Animal ctor: %s\n",name);
    }
    ~Animal() {
        printf("Animal dtor: %s\n",this.name);
    }
    virtual void speak() {
        printf("Animal speak\n");
    }
};

class Cat(Animal:public) {
public:
    Cat(name: char*) {
        this.name=name;
        printf("Cat ctor: %s\n",name);
    }
    override void speak() {
        printf("Cat %s: meow!\n",this.name);
    }
};

i32 main() {
    var dog=Dog("Buddy");
    var cat=Cat("Kitty");
    dog.speak();   # Dog: woof!
    cat.speak();   # Cat: meow!
    return 0;
}

枚举

关键字:enum

定义

enum 枚举名 {
    变体名,
    变体名=初始值,
    ...
}

示例

enum Color {
    Red,
    Green,
    Blue
}

enum Status {
    Ok=0,
    Error=-1
}

使用

var c=Color.Red;
var s=Status.Ok;

联合体

关键字:union

定义

union 联合体名 {
    字段名: 类型;
    字段名: 类型;
    ...
}

示例

union Value {
    int_val: i32;
    float_val: f64;
    bool_val: bool;
}

使用

var v: Value;
v.int_val=42;
printf("%d\n",v.int_val);

v.float_val=3.14;
printf("%f\n",v.float_val);

命名空间

关键字:namespace

命名空间用于组织代码,避免名称冲突。命名空间可以嵌套。

namespace math {
    i32 add(a: i32,b: i32) {
        return a+b;
    }
    i32 sub(a: i32,b: i32) {
        return a-b;
    }
}

i32 main() {
    var x=math::add(10,20);   # 用 :: 访问命名空间成员
    return x;
}

模板

关键字:template、typename

模板用于编写泛型代码,支持多个模板参数、指定类型和默认值,以及自动类型推导和显式类型参数。支持模板函数和模板类,他们都是在语义解析的时候实例化。

语法

template$T:typename$
template$T:typename,len:i32=100$
  • T:typename — 类型参数(简写:T 等同于 T:typename)
  • len:i32=100 — 值参数,类型为 i32,默认值为 100

定义

template$T:typename$
T max(a: T,b: T) {
    if (a > b) {
        return a;
    }
    return b;
}

使用

i32 main() {
    var x=max(10,20);           # 自动推导 T=i32
    var y=max$f64$(3.14,2.71);  # 显式指定 T=f64
    return 0;
}

函数参数默认值

函数参数可以指定默认值,调用时可以省略有默认值的参数:

i32 add(a: i32,b: i32=10) {
    return a+b;
}

i32 main() {
    var x=add(5);       # x=15 (b 使用默认值 10)
    var y=add(5,20);   # y=25
    return 0;
}

作用域规则

顶层作用域(文件级别)

以下内容只能出现在文件顶层,不能在函数内部定义:

  • import
  • class
  • enum
  • union
  • namespace
  • template

块作用域(函数内部)

用 {} 包围的代码块可以嵌套,内部定义的变量在块结束后销毁:

void test() {
    var x=10;
    {
        var y=20;   # y 只在此块内有效
        var z=30;
    }
    # y 和 z 在这里不可见
}

编译器的底层实现逻辑

你是否好奇 Mio 是怎么编译的?一个 .mio 文本文件,如何变成可执行的机器指令?本章将深入剖析 Mio 编译器的完整工作流程 —— 从源码到可执行文件的全链路实现。

本章部分内容参考这篇文章,并由 AI 润色。在此保留创作权。

编译流程总览

Mio 编译器的整体架构遵循经典的编译器设计范式,但也有一些独特的实现选择。整个编译流程可以概括为五个核心阶段:词法分析、语法分析、语义分析、代码生成和链接。

第一阶段是词法分析,由 Lexer 类负责。它将源代码字符流拆分为有意义的 Token 序列(关键字、标识符、字面量、运算符等),为后续语法分析提供基础。词法分析器采用有限状态自动机实现,逐字符扫描并识别 Token 类型,同时记录每个 Token 的位置信息(行号、列号)以便错误报告。

第二阶段是语法分析,由 Parser 类负责。Parser 将 Token 流组织成抽象语法树(AST),采用递归下降解析法,为每个语法规则编写一个解析函数。表达式部分使用 Pratt 解析法处理运算符优先级。值得注意的是,Mio 的预处理功能(宏展开、文件导入、条件编译)并未独立成阶段,而是融入 Parser 中完成 —— 这是 Mio 与 C++ 等传统编译器的显著差异。

第三阶段是语义分析,由 SemanticAnalyzer 类负责。这一阶段分为两步:先注册所有声明(收集符号表、检测重复定义),再分析代码(类型检查、引用解析、模板实例化)。Mio 的模板在语义分析阶段实例化,实例化后的 AST 被缓存供后续阶段使用。

第四阶段是代码生成,由 Compiler 类负责。它遍历语义分析后的 AST,将其转换为 LLVM IR(中间表示)。这一阶段完成类型映射(Mio 类型 → LLVM 类型)、变量管理(通过 alloca/load/store 模拟 SSA)、类布局(struct + vtable)、函数生成和控制流转换等工作。

第五阶段是后端处理与链接,借助 LLVM 完成优化(根据 -O0 ~ -O3 选择优化 Pass)、目标代码生成(汇编 / 目标文件),最后由链接器将多个目标文件和库合并为可执行文件。

词法分析阶段(Lexer)

做什么

词法分析器将预处理后的字符流拆分成一个个词素(Token)—— 编译器能识别的最小语义单元,并记录每个 Token 的位置信息(行号、列号)以便错误报告。

Token 的分类

Mio 的 Token 分为以下类别:

  • 关键字:var,const,if,class,import,macro 等
  • 标识符:用户定义的变量名、函数名、类名等
  • 字面量:整数(42)、浮点数(3.14)、字符串("hello")、字符('A')、布尔值(true/false)
  • 运算符:+,-,*,/,%,=,==,!=,&&,||,+=,-=,<<,>> 等
  • 分隔符:;,{ ,},(,),[,],,,:,:: 等
  • 特殊标记:$(模板参数标记)、@if/@elif/@else/@end(条件编译指令)

怎么做

Mio 的 Lexer 采用有限状态自动机逐字符扫描:

扫描流程:

  1. 跳过空白和注释:skipWhitespace() 函数处理空格、制表符、换行符和 # 注释
  2. 识别 Token 类型:根据当前字符类型进入不同处理分支
    • 字母或下划线 → ident()(标识符 / 关键字)
    • 数字 → number()(整数 / 浮点数)
    • 双引号 → stringLit()(字符串)
    • 单引号 → charLit()(字符)
    • 运算符字符 → 直接匹配(优先匹配多字符运算符,如 += 优先于 +)
  3. 生成 Token:记录类型、词素文本、行号、列号,字面量 Token 额外存储值

关键词匹配:ident() 函数先读取完整标识符,然后查关键字表判断是否为关键字,若不是则标记为 TOK_IDENT。

最长匹配原则:运算符匹配时优先匹配更长的有效序列,例如 += 识别为单个复合赋值运算符,而非 + 和 = 两个 Token。

关键问题与解决方案

问题 解决方案
>> 是右移还是模板结束符 由于没有历史包袱,于是使用 $ 而不是尖括号
非法字符(中文符号等) 记录错误并跳过,继续分析后续内容
多字符运算符歧义 贪心匹配最长的有效运算符序列

语法分析阶段(Parser)

做什么

语法分析器将词法分析输出的 Token 流,根据 Mio 的语法规则,组织成抽象语法树(AST),并以树形结构表示程序的逻辑结构。

抽象语法树(AST)

AST 是编译器内部的核心数据结构,它以树形结构表示程序的逻辑结构,过滤掉冗余的语法符号(如括号、分号等),仅保留必要的语义信息。

示例:var x = 2 * 3 + 4;

AST 结构:

VarDeclNode
├─ name: "x"
├─ type: (自动推导)
└─ init: BinaryExpr(+)
   ├─ left: BinaryExpr(*)
   │  ├─ left: IntLiteral(2)
   │  └─ right: IntLiteral(3)
   └─ right: IntLiteral(4)

怎么做

Mio 采用递归下降解析法,为每个语法规则编写一个解析函数,由顶层函数递归调用:

解析流程:

  1. parse() 作为入口,循环调用 parse_decl() 直到文件结束
  2. parse_decl() 根据当前 Token 判断声明类型并分发
    • var/const → parse_var_decl() 解析变量声明
    • 返回类型 + 标识符 → parse_func_def() 解析函数声明
    • class → parse_class_def() 解析类声明
    • import → parse_import() 解析导入指令
    • macro → parse_macro_def() 解析宏定义
    • template → parse_template_def() 解析模板定义
    • enum/union → 解析枚举 / 联合体
    • namespace → 解析命名空间
  3. parse_stmt() 解析语句(if/while/for/return/break/continue/goto/block)
  4. parse_expr() 解析表达式(见下方 Pratt 解析法)
  5. 各解析函数递归调用并构建 AST 节点

Pratt 解析法(表达式):

表达式解析采用 Pratt 解析法(也称"优先级爬升法")处理运算符优先级:

层级 解析函数 运算符
最低 parse_assignment() =,+=,-=,*=,/=,%=,&=,`\ =,^=,<<=,>>=`
parse_logical_or() `\ \ `
parse_logical_and() &&
parse_bit_or() `\ `
parse_bit_xor() ^
parse_bit_and() &
parse_equality() ==,!=
parse_relational() <,>,<=,>=
parse_shift() <<,>>
parse_additive() +,-
parse_multiplicative() *,/,%
parse_unary() -,!,~,*,&
最高 parse_postfix() (),[],.,->
parse_primary() 字面量、标识符、括号表达式

预处理功能的融入:

Mio 的预处理不在独立阶段完成,而是在 Parser 解析过程中动态处理:

  • 宏展开:parse_macro_def() 将宏名和值存入 macros 列表,后续遇到宏名时替换
  • 文件导入:parse_import() 递归创建新 Lexer 解析导入文件,AST 合并到当前 AST
  • 条件编译:parse_cond_comp() 根据宏定义状态决定是否保留代码块

AST 节点数据结构:

class AstNode {
   
    AstNodeKind kind;           // 节点类型(VAR_DECL/FUNC_DEF/BINARY_EXPR...)
    MioType* type;              // 语义分析后填充的类型信息
    const std::string* filename;
    int line,col;

    // 不同节点类型的数据存储
    struct {
    std::string name; MioType* var_type; AstNode* init; } var_decl;
    struct {
    std::string name; MioType* return_type; ... } func_def;
    struct {
    AstNode* left; TokenKind op; AstNode* right; } binary;
    // ...
};

关键问题与解决方案

问题 解决方案
运算符优先级 Pratt 解析法,通过优先级数值控制解析顺序
语法错误恢复 记录错误后跳过部分 Token,继续解析尽可能多地报告错误
循环导入 imported_files 列表检测已导入文件,直接跳过

语义分析阶段(SemanticAnalyzer)

做什么

语义分析阶段检查程序的语义正确性 —— 代码的意义是否正确,完成符号表建立、类型检查、建立 a-ast、模板实例化和名称修饰等工作。

两阶段设计

Mio 的语义分析分为注册和分析两个阶段,这种设计允许前向引用:

第一阶段:注册(registerPass)

遍历 AST,收集所有声明并建立符号表:

  • 函数声明 → 存入 funcDecls
  • 变量声明 → 存入 varDecls
  • 类定义 → 存入 classTypes,记录字段和方法
  • 枚举 / 联合体 → 存入 enumNames/unionNames
  • 模板定义 → 存入 templateMap/classTemplateMap
  • 命名空间 → 递归处理

第二阶段:分析(analyze)

再次遍历 AST,进行类型检查和引用解析:

  • analyzeDecl() → 分析顶层声明
  • analyzeStmt() → 分析语句(变量声明、条件、循环等)
  • checkExpr() → 分析表达式(类型检查、引用查找)
  • checkType() → 检查类型合法性,触发模板实例化

符号表实现

SemanticAnalyzer 使用多个哈希表存储符号信息:

// 符号集合
std::unordered_set<std::string> funcDecls;      // 已声明的函数
std::unordered_set<std::string> varDecls;       // 已声明的变量
std::unordered_set<std::string> classTypes;     // 已定义的类
std::unordered_set<std::string> enumNames;      // 已定义的枚举
std::unordered_set<std::string> unionNames;     // 已定义的联合体

// 类详细信息
std::unordered_map<std::string,std::unordered_set<std::string>> classFields;
std::unordered_map<std::string,std::unordered_map<std::string,MioType*>> classFieldTypes;
std::unordered_map<std::string,std::unordered_set<std::string>> classMethodSet;
std::unordered_map<std::string,std::unordered_set<std::string>> classPureVirtuals;

// 模板
std::unordered_map<std::string,std::pair<std::vector<TemplateParam>,AstNode*>> templateMap;
std::unordered_map<std::string,std::pair<std::vector<TemplateParam>,AstNode*>> classTemplateMap;
std::unordered_map<std::string,AstNode*> instantiatedClasses;  // 实例化缓存

作用域管理:

语义分析时维护 locals 和 localMioTypes 映射,进入函数 / 块时保存当前作用域,退出时恢复:

auto savedLocals = locals;
auto savedMioTypes = localMioTypes;

// 处理函数参数和局部变量
for(auto& p : node->func_def.params) {
   
    locals[p.name] = p.type;
    localMioTypes[p.name] = p.type;
}

analyzeBlock(node->func_def.body);

locals = savedLocals;
localMioTypes = savedMioTypes;

类型检查

checkType():

  1. 检查类型是否合法(如不能定义"引用的引用")
  2. 解析类名(处理命名空间前缀)
  3. 如果是模板类,触发模板实例化
  4. 递归检查基类型

checkExpr():

  • 二元运算:检查左右操作数类型是否兼容,支持类运算符重载查找
  • 一元运算:检查操作数是否合法(如不能对 void* 解引用)
  • 函数调用:检查函数是否存在,参数类型是否匹配
  • 成员访问:检查类中是否存在该字段 / 方法
  • 标识符:从符号表(locals → varDecls → funcDecls → 导入的命名空间)逐级查找

类型推导(resolveExprMioType):

  • 字面量 → 对应内置类型
  • 二元运算 → 统一操作数类型(整数提升、整数→浮点数)
  • 成员访问 → 从 classFieldTypes 查找字段类型
  • 标识符 → 从 localMioTypes 查找

模板实例化

Mio 的模板在语义分析阶段实例化:

触发条件:checkType() 遇到 MioTypeKind::CLASS 且 param_types 非空

实例化流程:

  1. 生成实例化名称:ClassName_Arg1Type_Arg2Type...
  2. 检查缓存(instantiatedClasses),若已实例化则直接复用
  3. 从 classTemplateMap 查找模板定义
  4. 构建类型替换表(typeSubst):模板参数名 → 实际类型
  5. 克隆 AST 并替换所有类型引用
  6. 将实例化后的类 AST 存入 instantiatedClasses
  7. 递归注册实例化后的类成员(字段、方法、构造函数)

函数模板实例化:类似流程,Compiler 中 genTemplateInstantiation() 完成,结果缓存在 templateInstances

关键问题与解决方案

问题 解决方案
前向引用(使用在前,声明在后) 两阶段设计:先注册所有声明,再分析引用
模板递归实例化 实例化缓存 + 名称去重
类型不匹配 统一操作数类型,若无法统一则报错
命名冲突 符号表检测重复定义

代码生成阶段(Compiler)

做什么

语义分析后的 A - AST 被转换为 LLVM IR(中间表示),这是编译器前端和后端之间的桥梁。LLVM IR 采用 ** 静态单赋值(SSA)** 形式,每个变量只能赋值一次,便于数据流分析和优化。

整体架构

class Compiler {
   
    llvm::LLVMContext ctx;
    std::unique_ptr<llvm::Module> mod;
    llvm::IRBuilder<> b;
    llvm::Function* curFn;
    llvm::BasicBlock* curBB;

    // 符号映射(变量 → LLVM 内存位置)
    std::unordered_map<std::string,llvm::AllocaInst*> locals;
    std::unordered_map<std::string,MioType*> localMioTypes;
    std::unordered_map<std::string,llvm::Function*> funcDecls;
    std::unordered_map<std::string,llvm::GlobalVariable*> globalVars;

    // 类相关
    std::unordered_map<std::string,llvm::StructType*> classTypes;
    std::unordered_map<std::string,std::unordered_map<std::string,unsigned>> classFieldIdx;
    std::unordered_map<std::string,std::vector<std::pair<std::string,llvm::Function*>>> classVTable;
};

怎么做

入口:generate(AstNode* program)

  1. 调用 SemanticAnalyzer::analyze() 完成语义分析
  2. 处理模板实例化类(为 instantiatedClasses 中的类创建前向声明)
  3. genDecl() 递归遍历 A - AST,分发到具体生成函数
  4. 验证 LLVM 模块

类型映射(convertType):

Mio 类型 LLVM 类型
void void
i8/u8/char i8
i32 i32
i64 i64
f32 float
f64 double
bool i1
*T T*
T[N] [N x T]
class Name %struct.Name

变量管理(SSA 模拟):

LLVM IR 使用 SSA 形式,Mio 通过 alloca 在栈上分配内存来模拟可变变量:

// 变量声明 → 分配栈空间
void genVarDecl(AstNode* decl) {
   
    auto* alloca = createEntryAlloca(curFn,name,ty);
    locals[name] = alloca;
    if(initExpr) {
   
        auto* val = genExpr(initExpr);
        b.CreateStore(val,alloca);
    }
}

// 读取变量 → load
llvm::Value* loadVariable(const std::string& name) {
   
    return b.CreateLoad(ty,locals[name]);
}

// 修改变量 → store
void storeVariable(const std::string& name,llvm::Value* val) {
   
    b.CreateStore(val,locals[name]);
}

控制流生成:

Mio 结构 LLVM IR 结构
if-else br 条件分支 + then/else 基本块 + merge 合并块
while cond 条件块 → body 循环体 → end 结束块(含 br 回跳)
for init → cond → body → update → 回跳 cond
break br 跳转到最近的循环 end 块
continue br 跳转到最近的循环 cond/update 块
return 清理栈上对象 + ret 指令

类布局:

  1. 收集所有字段,按声明顺序排列
  2. 检查是否有虚函数,若有则第一个字段为 VTable 指针
  3. 创建 llvm::StructType,记录每个字段的索引
  4. 成员访问通过 GEP(Get Element Pointer)指令计算偏移量

虚函数表(VTable)生成:

  1. genClassDef() 收集所有虚函数,存入 classVTableOrder
  2. genVTable() 创建 VTable 类型和全局变量
  3. 构造函数中设置对象的 VTable 指针
  4. 虚函数调用:从对象读取 VTable 指针 → 索引获取函数指针 → 间接调用

模板实例化的代码生成:

Compiler 中 genTemplateInstantiation() 处理函数模板:

  1. 查找模板定义(templateMap)
  2. 生成实例化名称(名称修饰)
  3. 检查缓存(templateInstances)
  4. 构建类型替换表
  5. 调用 instantiateTemplate() 克隆 AST 并替换类型
  6. 生成 IR(genFuncDef)
  7. 缓存结果

清理机制(析构函数调用):

cleanupStack 维护当前作用域中需要销毁的对象:

std::vector<std::pair<std::string,llvm::AllocaInst*>> cleanupStack;

// 变量声明时注册析构
if(mt->kind == MioTypeKind::CLASS) {
   
    auto dtor = findDestructor(mt->name);
    if(dtor) cleanupStack.push_back({
   mt->name,alloca});
}

// 离开作用域时逆序调用析构
void genCleanupAll() {
   
    for(int i = cleanupStack.size() - 1; i >= 0; i--) {
   
        auto dtor = findDestructor(entry.first);
        b.CreateCall(dtor,{
   ptr});
    }
}

输出模式

Compiler 支持四种输出模式:

模式 方法 输出
LLVM IR emitLLVM() .ll 文本文件
汇编 emitAssembly() .s 汇编文件
目标文件 emitObject() .o 目标文件
可执行文件 linkExecutable() .exe/ELF 可执行文件

后端与链接

LLVM 优化

Compiler 通过 LLVM 的 Pass 框架应用优化,优化级别由 -O0 ~ -O3 控制:

级别 说明
-O0 无优化,最快编译速度,适合调试
-O1 基础优化(常量折叠、死代码消除)
-O2 标准优化(循环优化、内联)
-O3 激进优化(向量化、循环展开)

优化通过 LLVM C API 的 LLVMTargetMachineEmitToFile 在生成目标文件时应用。

目标代码生成

emitObject() 流程:

  1. 初始化 LLVM 目标(LLVMInitializeAllTargets())
  2. 获取当前平台的三元组(sys::getProcessTriple())
  3. 创建 TargetMachine
  4. 设置 DataLayout 和 TargetTriple
  5. 调用 LLVMTargetMachineEmitToFile() 生成目标文件

链接

linkExecutableFiles() 根据目标平台调用不同链接器:

平台 链接方式
Windows(COFF) 调用 lld::coff::link()
Linux(ELF) 调用系统 cc 命令
macOS(Mach-O) 调用 lld::macho::link()
bool linkExecutableFiles(const std::vector<std::string>& objPaths,...) {
   
    switch(t.getObjectFormat()){
   
        case llvm::Triple::COFF:
            return lld::coff::link(args,...);
        case llvm::Triple::ELF:
            // 调用 cc 命令
            return system(cmd.c_str()) == 0;
        case llvm::Triple::MachO:
            return lld::macho::link(args,...);
    }
}

Mio 实现的特殊之处 / 为什么下一代编程语言是 Mio

预处理融入 Parser

与 C++ 的独立预处理器不同,Mio 的宏展开、文件导入和条件编译都在 Parser 中完成,没有预处理机制,这让 Mio 的错误处理更加简洁。

两遍扫描语义分析

采用现代编译器的特有配置,先注册所有声明,再分析引用,允许前向引用。这样就不会出现 c++ 里需要先定义再声明的尴尬现象。

语义阶段的模板实例化

模板在语义分析阶段实例化,而非代码生成阶段,实例化结果缓存在 instantiatedClasses。这样方便管理。

名称修饰

Mio 使用简单编码方案,所以可以无缝兼容 c 的所有代码,可以使用所有 libc 库代码和系统交互,甚至可以兼容部分实现较好的扩展库。

CFG 语法设计

c++ 之所以编译慢是因为它的语法上下文有关,比如 A *B; 在 A 是类型的时候是定义一个指向 A 的指针,在 A 是标识符的时候,就是一个普通的乘法。mio 没有历史包袱,所以可以设计完全上下文无关的语法。

这是世界上第一个上下文无关的高级语言!

关于文中对 ast 树的讲解,可以自行查看 P1175 的题解参考。如果文中有讲解不够明细的可以在评论区指出。

本文从灵感到创作历时近 1 年。请点个赞吧。

相关文章
|
17天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
8478 24
|
16天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
2817 14
|
15天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
2021 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
14天前
|
人工智能 编解码 并行计算
MiniMax-H3 一键整合包技术文档:8G 显存运行 AI 漫剧制作 —— 角色替换 / 动作迁移 / 文图生视频部署与调参指南
MiniMax H3 是 MiniMax 开源的全模态视频生成模型,支持文/图/音/视多条件输入,输出最高2K、15秒带双声道音频视频。本文档详述其Int8量化版在8GB显存下的本地一键部署、三段式工作流(EDIT/REPLACE/CONTINUE)、参数调优及常见问题排查。(239字)
|
10天前
|
人工智能 Linux 开发者
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
Codex是OpenAI推出的AI编程智能体,可读取本地项目、理解需求并自动修改代码。支持桌面GUI、命令行(CLI)及VS Code/Cursor插件三种形态,覆盖可视化操作、终端高效开发与编辑器无缝集成场景,助开发者用自然语言驱动编码全流程。(239字)
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
|
4天前
|
人工智能 JSON Linux
【全网最详细】ComfyUI使用教程:下载+本地部署+配置+工作流搭建一篇搞定(2026最新版)
ComfyUI是一款免费开源的本地AI绘图工具,采用节点式工作流设计,支持文生图、图生图、局部重绘、放大、换脸等多种功能。可离线运行,依赖显卡加速,无需联网。支持自定义流程保存与分享,插件生态丰富,适合进阶用户。(239字)
|
10天前
|
人工智能 JSON 编解码
【2026最新版】ComfyUI本地部署教程,新手也能看懂!
ComfyUI是本地运行的AI绘画工具,采用节点式工作流设计:通过拖拽连接“加载模型”“提示词编码”“采样”“解码”等模块,实现高度可控的文生图。新手推荐使用秋叶整合包,一键启动、内置模型管理与插件安装器,轻松上手。(239字)

热门文章

最新文章