HaoLang 调试能力 & 语言架构设计方法论与约束规范
⚠️ 重要声明:本文档全部为设计规则、约束、方法论、检查清单;文档内出现的函数名、接口名、代码片段均仅作为示例参考,不代表强制实现。实际编码实现时可根据工程情况调整函数命名、内部实现细节,但必须遵守文档定义的原则、约束、禁止项与验收标准。
承接《高级语言 GC 设计全覆盖检查清单》,补充完整统一收口原则完整约束、Debug埋点设计规范;用于编译器开发设计依据、代码评审、验收校验。
一、整体顶层设计哲学(统一原则,所有模块都遵守)
- 分层解耦原则:前端、语义分析、IR生成、LLVM中端、后端、运行时、调试、包管理严格分层;层与层之间定义明确接口,禁止跨层直接访问内部数据。
- 可调试可观测原则:不管Debug/Release,关键错误必须能够映射回
.hao源码;禁止编译器静默吞掉错误,禁止丢失源码位置信息。 - 边界隔离原则:区分 用户源码 / 编译器内部代码 / LLVM库代码 / HaoLang运行时库;日志、堆栈、诊断输出要清晰区分。
- 统一收口原则:同一类行为,全部收敛到一套固定入口函数/模块;禁止业务逻辑、语法分支、表达式代码中散落同类逻辑,不在各处重复实现。所有调用方调用公共入口,埋点、断言、开关逻辑收敛在入口内部。目的:便于迭代修改、便于增加诊断埋点、便于开关诊断能力、便于问题排查。
- 不变量优先原则:任何阶段都存在可校验的不变量;Debug模式下开启大量断言校验不变量,Release关闭断言保留业务逻辑。
- 兼容迭代原则:语言语法、包格式、
haoproject.json向前兼容;旧版本项目文件可以被新版本编译器读取。 - 自举导向设计:编译器内部数据结构、API设计,要为未来使用HaoLang重写编译器做准备;避免大量C++独有的黑魔法。
- 平台一致性原则:Windows / Linux行为尽量一致;ABI、内存布局、GC行为差异由运行时层抹平,上层语言尽量感知不到。
二、编译器整体架构分层约束
分层结构
- 词法层(Lexer):源码→Token流
- 语法解析层(Parser):Token→AST;基于ANTLR4生成解析器,ANTLR只负责语法结构,不做语义处理
- 语义分析Sema层:符号表、作用域、类型检查、绑定、类型推导、泛型实例化;填充AST的类型、符号信息
- IRGen中间代码生成层:带类型信息的AST → LLVM IR;处理GC根、临时根、写屏障、安全点、调试元数据
- LLVM中端:LLVM IR优化;受GC约束,部分优化需要禁用/做防护
- LLVM后端:IR → 机器码;生成目标obj文件
- 链接层:调用LLD,合并目标文件、运行时库、处理pdb/dwarf调试信息,输出exe/elf
- 运行时Runtime:GC、异常栈展开、FFI、字符串、数组、协程、内置库;被编译产物静态链接进去
- 包管理工具层:处理
haoproject.json、依赖下载、全局缓存、版本解析、冲突处理,独立于编译器内核。
架构强制约束
- ANTLR只输出原始AST,禁止在ANTLR Visitor里面做类型检查、GC逻辑、IR生成;Visitor只做简单AST构造。
- 语义分析完成之后,AST才携带完整类型信息;IRGen禁止处理未经过Sema的AST。
- IRGen不修改原始AST;AST是只读,所有IR信息输出到LLVM数据结构。
- 符号表、作用域由Sema统一维护,IRGen只读取,不修改。
- 包管理模块与编译器内核解耦:
hao build命令先解析依赖,把源文件列表交给编译器内核;内核不感知包下载逻辑。 - 编译器内核不直接操作网络;拉取包逻辑全部放在包管理子命令。
禁止规则
- ❌ 禁止Parser/Visitor直接生成IR。
- ❌ 禁止IRGen修改AST节点。
- ❌ 禁止编译器内核内部写网络请求代码。
- ❌ 禁止把运行时业务逻辑硬编码写在IRGen代码,优先调用runtime函数。
三、统一收口原则完整约束
文档中接口名称仅为示例,实现可自行命名,核心是行为收敛,不是函数名字必须一模一样。
统一收口覆盖五大领域:GC行为收口、IR生成收口、编译期诊断报错收口、Debug埋点收口、崩溃现场收集收口。
3.1 GC模块统一收口约束
允许
- 对象分配:全部调用统一分配入口,不直接调用底层malloc。
- 写屏障:所有堆引用赋值,统一调用写屏障入口,不在多处重复手写屏障逻辑。
- 栈根/临时根:使用一套统一根管理接口;IRGen内部封装,AST/Parser层绝对不能调用根操作接口。
禁止
❌ 禁止在if/for/闭包/字面量分支直接手写GC操作。
❌ 禁止多处重复实现写屏障逻辑。
❌ 禁止绕过入口直接修改GC对象头、标记位。
Debug埋点收口:所有GC入口函数内部内置埋点;不要在每一处调用点到处增加日志。开启GC诊断模式仅修改入口内部逻辑,全部调用点自动生效。
3.2 IRGen模块统一收口约束
IRGen层禁止到处裸写LLVM原始IR指令,封装工具函数作为收口入口。
示例接口仅参考,实现可自定义。
- 分配GC对象入口
- 发射写屏障IR调用入口
- 发射安全点轮询IR入口
- 附加源码调试位置元数据入口
约束
- 任何需要附加调试位置元数据,全部调用统一封装入口,不重复手写LLVM Metadata构造代码。
- 所有GC相关IR,全部通过封装函数产出,不裸写IR指令。
- 所有临时根的新增、销毁,全部走IRGen内部封装接口,不在各个表达式分支重复实现。
禁止
❌ 各个AST visitor分支直接手动构造调试元数据Metadata。
❌ 多处重复手写写屏障IR指令。
3.3 编译期诊断报错统一收口
所有编译错误、警告、提示,全部走统一Diagnostic诊断模块,禁止直接printf / cout输出。
示例接口:
diag_error/diag_warn/diag_note,仅作为示例。
- 参数必须携带源码位置信息结构体(文件名、行、列)。
- 模块内部统一处理:格式化输出、颜色开关、标准输出/错误输出、IDE可用JSON格式输出、错误计数统计。
约束
- Lexer、Parser、Sema所有报错,只能调用这一组诊断入口。
- 禁止各模块直接打印控制台。
- Debug模式下,诊断模块内部埋点记录诊断日志,不需要修改各个报错调用位置。
禁止
❌ 在业务代码散落直接控制台打印输出。
3.4 Debug埋点完整设计规范
埋点分为四类:断言Assert、Trace跟踪日志、Metrics统计指标、崩溃现场收集;全部统一收口,不在业务代码到处实现埋点逻辑。接口名称仅示例,实现可自定义。
| 埋点类型 | 作用 | 生效时机 | 输出位置 |
|---|---|---|---|
| 不变量断言 Assert | 校验内部不变量,检测逻辑bug | 仅Debug编译;Release编译期完全剔除,无性能开销 | 程序终止,输出断言条件与位置 |
| 诊断Trace跟踪日志 | 流程日志:GC流程、安全点、写屏障、IR生成流程 | 全局开关控制,Debug/Release均可开启 | stderr / 日志文件 |
| Metrics统计指标 | 计数器:GC次数、分配字节数、安全点命中次数、句柄数量 | 运行时开关控制 | 日志、内置查询接口 |
| 崩溃现场Dump | 崩溃时输出上下文、堆快照、GC状态 | 异常终止信号触发 | dump文件、标准错误输出 |
核心收口规则:埋点逻辑全部封装在公共入口内部;业务代码仅调用入口,不做开关判断、不做格式化输出;修改调试行为只修改入口,全链路生效。
3.4.1 断言 Assert 收口约束
不直接使用系统原生assert,封装项目自身断言入口。
- Debug模式:条件失败,输出断言条件、C++源码位置,尽可能附带HaoLang源码位置,终止程序。
- Release版本:该宏直接空展开,无性能开销。
- 所有内部不变量校验统一使用本断言;断言只校验内部不变量,用户输入错误交给Diagnostic报错。
❌ 禁止业务代码到处手写if判断失败打印日志并退出。
3.4.2 Trace跟踪日志埋点收口
统一trace日志入口,业务代码禁止直接使用printf。
- 参数携带模块标识、日志级别、格式化参数。
- 开关判断逻辑全部收敛在trace函数内部;关闭时直接返回,不执行格式化。
- GC、安全点、写屏障、临时根操作、IR生成流程,全部在此入口埋点。
- 支持环境变量控制开关,无需重新编译程序。
❌ 禁止业务代码到处写 if(debug) printf(...)。
3.4.3 Metrics指标统计收口
统一metrics模块,对外提供计数器增减、数值设置接口。
- 业务代码只调用计数接口;开关判断逻辑在metrics模块内部。关闭状态调用直接返回,无开销。
- 提供接口导出全部指标,用于日志、内部调试查询。
❌ 禁止业务代码自行定义全局变量到处计数。
3.4.4 崩溃现场收集埋点收口
程序启动阶段一次性注册全局崩溃处理钩子;业务代码完全不需要关心dump逻辑。
钩子内部统一完成:捕获异常信号、收集GC状态、线程信息、HaoLang源码堆栈、输出崩溃日志、可选生成minidump/core dump。
约束:OOM、堆损坏等异常场景直接调用统一上报入口,不在各个业务位置重复实现dump逻辑。
❌ 禁止业务代码零散注册信号、异常处理钩子。
3.5 IR调试元数据收口约束
所有IR指令附加源码位置,调用统一封装入口。
- AST节点携带源码位置,生成IR指令之后调用统一接口挂载调试元数据。
- 入口内部处理空位置、Debug/Release模式差异。
❌ 禁止各个AST分支手动手动构造llvm::DILocation。
3.6 统一收口完整检查清单
- GC分配、写屏障、根操作全部收敛统一入口,调用点无零散GC逻辑。
- 编译期报错全部使用Diagnostic模块,禁止直接控制台打印。
- 内部不变量校验全部封装项目自定义断言,不零散使用原生assert。
- Trace日志全部走统一trace入口,业务代码不手写开关判断。
- Metrics统计全部走metrics模块接口,业务不自己维护计数器。
- 程序崩溃、dump逻辑全局钩子只注册一次,业务不散落dump代码。
- IR调试元数据挂载统一调用封装接口,不手动构造Metadata。
- 所有Debug开关全部集中埋点模块,支持环境变量控制,不用修改业务源码。
- Release模式关闭trace/assert,不引入额外性能开销。
3.7 统一收口相关禁止项汇总
❌ 禁止同一逻辑多处重复实现。
❌ 禁止业务代码散落 if(debug) { printf(...); }。
❌ 禁止Lexer/Sema/Parser直接打印输出。
❌ 禁止绕过公共入口直接操作GC底层对象头、标记。
❌ 禁止各个AST分支手动构造LLVM调试元数据。
❌ 禁止业务代码自己注册崩溃信号处理。
方法论总结:业务只调用公共入口;埋点、断言、日志、开关全部写在入口内部。调用点只关心业务逻辑,完全不关心调试埋点逻辑。修改调试行为,只改一处入口,全链路生效。
四、Debug & 调试体系设计约束
4.1 调试元数据生成规则
- 每个AST节点必须携带源码位置:
文件名、行号、列号;所有报错、IR调试元数据都来源于此。 - IRGen生成LLVM Metadata:
- Windows:生成CodeView,最终输出PDB;
- Linux:生成DWARF调试信息。
- Release模式行为:
- 用户可选择分离符号文件:exe本体不带符号,pdb/dwarf符号单独保存;
- 绝不丢弃源码位置元数据,只是剥离到外部文件。
- 内联函数、闭包、lambda、泛型实例化,必须保留原始源码位置,不能使用编译器内部生成的虚拟文件名。
- LLVM优化不允许抹除调试元数据;编译参数保证调试元数据跟随IR优化传递。
4.2 源码级异常与堆栈
- 运行时异常、panic,栈回溯优先展示HaoLang源码栈帧,过滤编译器内部、runtime内部帧;提供开关展示完整原始栈。
- FFI原生崩溃,同时展示原生调用栈 + 最近一次进入HaoLang的源码栈帧。
- 栈展开Unwind逻辑与GC根管理强绑定;异常抛出/捕获过程不能破坏GC根集合。
4.3 编译器自身Debug模式开关
编译器提供可配置命令行开关:
- 打印AST:输出格式化AST树,用于排查语法语义问题
- 打印IR:输出生成的LLVM IR文本,检查IRGen输出正确性
- GC诊断模式:输出安全点、写屏障、根枚举日志
- 堆校验模式:每次GC前后执行堆完整性检查
- 断言开关:Debug构建开启大量不变量断言;Release关闭。
4.4 调试器体验约束
- 支持Visual Studio / lldb/gdb断点;可以直接对
.hao源码打断点,单步调试。 - 调试器中可以查看HaoLang变量、值类型、GC引用对象;显示HaoLang层面类型,而不是原始LLVM结构体。
禁止项
❌ 禁止Release模式直接丢弃所有调试元数据,必须支持分离符号。
❌ 禁止运行时异常只输出机器地址,不映射回源码行。
❌ 禁止IR优化把调试元数据全部清除。
五、IRGen(AST转LLVM IR)模块约束
和GC清单编译优化与GC交互约束配套。
IRGen核心规则
- IRGen负责:变量分配、表达式求值、临时根管理、写屏障调用、安全点Statepoint插入、调试元数据挂载。
- 所有GC相关IR操作,不直接手写LLVM原始指令,封装为内部工具函数。
- 临时根、栈根严格按照作用域、表达式生命周期管理;不手动到处插入push/pop。
- 写屏障调用标记内存语义,设置
noinline,添加内存屏障,防止LLVM DCE、重排、LICM错误优化。 - 安全点严格限定允许插入位置;禁止在表达式中间、算术运算中间插入安全点。
- 每个函数生成对应StackMap/Statepoint元数据,保证精确GC扫描。
IRGen检查清单
- 每一条IR指令尽可能挂载对应源码位置元数据。
- GC对象分配统一调用runtime函数,不裸写malloc。
- 所有引用赋值路径全部调用写屏障;值类型赋值跳过。
- 函数调用实参临时对象全部加入临时根集合。
- return表达式临时根延迟清理,交给调用方。
- 控制流分支(break/continue/return/throw/goto)正确处理临时根、栈根生命周期。
- 闭包env结构体自动生成引用位图元数据。
- 结构体、数组、泛型实例,编译期生成引用位图。
IRGen禁止项
- ❌ 禁止绕过写屏障直接修改堆上GC引用字段。
- ❌ 禁止安全点插入在基本块中间、算术运算中间。
- ❌ 禁止直接在IRGen硬编码大量魔法数字,常量全部由runtime头文件定义。
- ❌ 禁止LLVM优化把写屏障调用消除。
六、语言语法 & 类型系统约束(HaoLang语言本身)
对标C#语法糖,Go包管理,Java目录结构 kotlin语言;
.hao后缀;编译器命令hao build / hao run / hao pkg。
- 类型严格区分:值类型 / GC引用类型 / 原生裸指针;类型系统层面做区分,编译期检查。
- 字符串:支持普通字符串、
@原始字符串、$模板字符串、@$原始插值字符串;Char字符类型。 - 包体系:目录对应包;包名与目录名规则;导入、导出可见性。
- 泛型:编译期实例化;实例化时正确生成引用位图,不能丢失GC引用信息。
- 闭包捕获:捕获变量区分值捕获/引用捕获;捕获到env堆对象,自动触发写屏障。
- 空安全:可空引用类型编译期检查;可空解引用生成运行时空检查。
- FFI:显式声明C外部函数;区分返回值是否为GC托管对象;禁止FFI直接返回裸GC指针。
七、构建、链接、交叉编译约束
- Windows平台:使用LLVM+MSVC工具链+LLD;输出PE exe,PDB调试符号。
- 交叉编译:依靠LLVM target triple + sysroot;sysroot提供目标平台头文件、系统库。
- 静态编译优先:类似Go,默认尽可能静态链接;允许动态链接可配置。
- 构建产物分离:build目录存放中间obj、ir;dist存放编译器发行版产物。
- 发行版打包:编译器
hao.exe连同依赖LLVM库、运行时库、内置标准库一起打包;分发到其他Windows机器可以直接编译.hao源码。
交叉编译关键约束
- Windows编译Linux目标,必须提供Linux sysroot,不能只靠LLVM工具。
- 不同目标平台ABI、DataLayout在编译期正确配置。
- GC运行时要针对目标平台编译一份,不能直接复用Windows运行时二进制。
八、包管理模块约束(haoproject.json)
- 项目配置文件:
haoproject.json,定义项目名称、版本、依赖、替换、仓库配置。 - 全局包缓存目录:由环境变量
HAO_PKG或者默认路径控制;多个项目共享缓存,避免每个项目复制一份依赖。 - 仓库优先级:配置文件registry > 环境变量 > 内置默认中央仓库。
- 支持依赖替换、排除子依赖、版本冲突解析。
- 子命令:
hao pkg add / hao pkg remove / hao pkg install。 - 拉取github资源支持镜像地址兼容,适配国内网络。
禁止
- ❌ 项目目录不复制完整依赖源码副本,优先引用全局缓存。
九、自举相关架构约束
当前阶段编译器C++实现;未来目标:用HaoLang重写编译器本身。
- 当前C++版本是引导编译器(bootstrap‑0);只作为启动用。
- 架构设计上,尽量把编译器核心逻辑抽象为数据结构,避免重度C++特性;方便后续移植到HaoLang。
- 自举条件:HaoLang语言具备:泛型、容器、闭包、FFI、足够强的类型系统;可以实现词法、语法、Sema、IRGen。
- 自举完成之后:产出
hao.exe是HaoLang写的编译器,仍然依赖LLVM库、sysroot,只是不再依赖C++引导编译器。
重点:自举 ≠ 脱离LLVM;只是编译器本身实现语言发生切换。
十、完整验收检查清单(架构&调试&统一收口)
编译器架构
- 分层清晰:Lexer → Parser → Sema → IRGen → LLVM → Link;层之间接口明确。
- ANTLR只构造AST,不做语义、IR逻辑。
- 包管理与编译器内核解耦。
- 所有报错诊断统一走Diagnostic接口。
统一收口 & Debug埋点
- GC、诊断、trace、metrics、崩溃dump全部收敛到公共入口。
- 业务代码无散落debug判断、printf打印。
- Release关闭断言与trace,无性能损耗。
IRGen
- GC分配、写屏障统一封装,不散落到处。
- 安全点只允许在规定位置插入。
- 每个函数生成StackMap/Statepoint元数据。
- 调试元数据挂载走统一入口。
Debug调试体系
- AST节点全部携带源码位置信息。
- Windows输出PDB,Linux输出DWARF;支持分离符号。
- 异常堆栈可以映射到
.hao源码行;区分用户栈帧与内部栈帧。 - 编译器提供输出AST、IR、GC诊断日志的命令行开关。
- 支持gdb/lldb/VS源码断点调试。
构建&交叉编译&包管理
- 全局包缓存,多个项目复用依赖。
- 支持环境变量配置仓库、缓存路径。
- 交叉编译正确使用sysroot。
- 可以打包编译器发行版,分发到其他Windows机器编译源码。
自举前置条件
- 编译器内部数据结构尽量少使用C++独有黑魔法。
- 语言本身能力完备:泛型、容器、闭包、FFI、字符串处理。
十一、顶层方法论总结
语法解析产出AST,语义分析补全类型;
IRGen负责翻译IR,GC逻辑统一收口;
调试元数据一路传递,Release不丢符号;
包管理只管依赖下载,内核专注编译;
埋点断言收敛入口,调用方不关心调试逻辑;
自举只是换编译器实现语言,不会脱离LLVM工具链。
再次提示:本文档全部为规则与方法论,示例代码、函数名称仅参考,实际实现可自由调整细节,但约束、禁止项、验收标准需要遵守。