HaoLang 调试能力 & 语言架构设计方法论与约束规范

⚠️ 重要声明:本文档全部为设计规则、约束、方法论、检查清单;文档内出现的函数名、接口名、代码片段均仅作为示例参考,不代表强制实现。实际编码实现时可根据工程情况调整函数命名、内部实现细节,但必须遵守文档定义的原则、约束、禁止项与验收标准。

承接《高级语言 GC 设计全覆盖检查清单》,补充完整统一收口原则完整约束、Debug埋点设计规范;用于编译器开发设计依据、代码评审、验收校验。

一、整体顶层设计哲学(统一原则,所有模块都遵守)

  1. 分层解耦原则:前端、语义分析、IR生成、LLVM中端、后端、运行时、调试、包管理严格分层;层与层之间定义明确接口,禁止跨层直接访问内部数据。
  2. 可调试可观测原则:不管Debug/Release,关键错误必须能够映射回.hao源码;禁止编译器静默吞掉错误,禁止丢失源码位置信息。
  3. 边界隔离原则:区分 用户源码 / 编译器内部代码 / LLVM库代码 / HaoLang运行时库;日志、堆栈、诊断输出要清晰区分。
  4. 统一收口原则:同一类行为,全部收敛到一套固定入口函数/模块;禁止业务逻辑、语法分支、表达式代码中散落同类逻辑,不在各处重复实现。所有调用方调用公共入口,埋点、断言、开关逻辑收敛在入口内部。目的:便于迭代修改、便于增加诊断埋点、便于开关诊断能力、便于问题排查。
  5. 不变量优先原则:任何阶段都存在可校验的不变量;Debug模式下开启大量断言校验不变量,Release关闭断言保留业务逻辑。
  6. 兼容迭代原则:语言语法、包格式、haoproject.json向前兼容;旧版本项目文件可以被新版本编译器读取。
  7. 自举导向设计:编译器内部数据结构、API设计,要为未来使用HaoLang重写编译器做准备;避免大量C++独有的黑魔法。
  8. 平台一致性原则:Windows / Linux行为尽量一致;ABI、内存布局、GC行为差异由运行时层抹平,上层语言尽量感知不到。

二、编译器整体架构分层约束

分层结构

  1. 词法层(Lexer):源码→Token流
  2. 语法解析层(Parser):Token→AST;基于ANTLR4生成解析器,ANTLR只负责语法结构,不做语义处理
  3. 语义分析Sema层:符号表、作用域、类型检查、绑定、类型推导、泛型实例化;填充AST的类型、符号信息
  4. IRGen中间代码生成层:带类型信息的AST → LLVM IR;处理GC根、临时根、写屏障、安全点、调试元数据
  5. LLVM中端:LLVM IR优化;受GC约束,部分优化需要禁用/做防护
  6. LLVM后端:IR → 机器码;生成目标obj文件
  7. 链接层:调用LLD,合并目标文件、运行时库、处理pdb/dwarf调试信息,输出exe/elf
  8. 运行时Runtime:GC、异常栈展开、FFI、字符串、数组、协程、内置库;被编译产物静态链接进去
  9. 包管理工具层:处理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入口
  • 附加源码调试位置元数据入口

约束

  1. 任何需要附加调试位置元数据,全部调用统一封装入口,不重复手写LLVM Metadata构造代码。
  2. 所有GC相关IR,全部通过封装函数产出,不裸写IR指令。
  3. 所有临时根的新增、销毁,全部走IRGen内部封装接口,不在各个表达式分支重复实现。

禁止

❌ 各个AST visitor分支直接手动构造调试元数据Metadata。
❌ 多处重复手写写屏障IR指令。

3.3 编译期诊断报错统一收口

所有编译错误、警告、提示,全部走统一Diagnostic诊断模块,禁止直接printf / cout输出。

示例接口:diag_error / diag_warn / diag_note,仅作为示例。

  • 参数必须携带源码位置信息结构体(文件名、行、列)。
  • 模块内部统一处理:格式化输出、颜色开关、标准输出/错误输出、IDE可用JSON格式输出、错误计数统计。

约束

  1. Lexer、Parser、Sema所有报错,只能调用这一组诊断入口。
  2. 禁止各模块直接打印控制台。
  3. 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,封装项目自身断言入口。

  1. Debug模式:条件失败,输出断言条件、C++源码位置,尽可能附带HaoLang源码位置,终止程序。
  2. Release版本:该宏直接空展开,无性能开销。
  3. 所有内部不变量校验统一使用本断言;断言只校验内部不变量,用户输入错误交给Diagnostic报错

❌ 禁止业务代码到处手写if判断失败打印日志并退出。

3.4.2 Trace跟踪日志埋点收口

统一trace日志入口,业务代码禁止直接使用printf。

  1. 参数携带模块标识、日志级别、格式化参数。
  2. 开关判断逻辑全部收敛在trace函数内部;关闭时直接返回,不执行格式化。
  3. GC、安全点、写屏障、临时根操作、IR生成流程,全部在此入口埋点。
  4. 支持环境变量控制开关,无需重新编译程序。

❌ 禁止业务代码到处写 if(debug) printf(...)

3.4.3 Metrics指标统计收口

统一metrics模块,对外提供计数器增减、数值设置接口。

  1. 业务代码只调用计数接口;开关判断逻辑在metrics模块内部。关闭状态调用直接返回,无开销。
  2. 提供接口导出全部指标,用于日志、内部调试查询。

❌ 禁止业务代码自行定义全局变量到处计数。

3.4.4 崩溃现场收集埋点收口

程序启动阶段一次性注册全局崩溃处理钩子;业务代码完全不需要关心dump逻辑。
钩子内部统一完成:捕获异常信号、收集GC状态、线程信息、HaoLang源码堆栈、输出崩溃日志、可选生成minidump/core dump。

约束:OOM、堆损坏等异常场景直接调用统一上报入口,不在各个业务位置重复实现dump逻辑。

❌ 禁止业务代码零散注册信号、异常处理钩子。

3.5 IR调试元数据收口约束

所有IR指令附加源码位置,调用统一封装入口。

  1. AST节点携带源码位置,生成IR指令之后调用统一接口挂载调试元数据。
  2. 入口内部处理空位置、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 调试元数据生成规则

  1. 每个AST节点必须携带源码位置:文件名、行号、列号;所有报错、IR调试元数据都来源于此。
  2. IRGen生成LLVM Metadata:
    • Windows:生成CodeView,最终输出PDB;
    • Linux:生成DWARF调试信息。
  3. Release模式行为
    • 用户可选择分离符号文件:exe本体不带符号,pdb/dwarf符号单独保存;
    • 绝不丢弃源码位置元数据,只是剥离到外部文件。
  4. 内联函数、闭包、lambda、泛型实例化,必须保留原始源码位置,不能使用编译器内部生成的虚拟文件名。
  5. LLVM优化不允许抹除调试元数据;编译参数保证调试元数据跟随IR优化传递。

4.2 源码级异常与堆栈

  • 运行时异常、panic,栈回溯优先展示HaoLang源码栈帧,过滤编译器内部、runtime内部帧;提供开关展示完整原始栈。
  • FFI原生崩溃,同时展示原生调用栈 + 最近一次进入HaoLang的源码栈帧。
  • 栈展开Unwind逻辑与GC根管理强绑定;异常抛出/捕获过程不能破坏GC根集合。

4.3 编译器自身Debug模式开关

编译器提供可配置命令行开关:

  1. 打印AST:输出格式化AST树,用于排查语法语义问题
  2. 打印IR:输出生成的LLVM IR文本,检查IRGen输出正确性
  3. GC诊断模式:输出安全点、写屏障、根枚举日志
  4. 堆校验模式:每次GC前后执行堆完整性检查
  5. 断言开关:Debug构建开启大量不变量断言;Release关闭。

4.4 调试器体验约束

  • 支持Visual Studio / lldb/gdb断点;可以直接对.hao源码打断点,单步调试。
  • 调试器中可以查看HaoLang变量、值类型、GC引用对象;显示HaoLang层面类型,而不是原始LLVM结构体。

禁止项

❌ 禁止Release模式直接丢弃所有调试元数据,必须支持分离符号。
❌ 禁止运行时异常只输出机器地址,不映射回源码行。
❌ 禁止IR优化把调试元数据全部清除。

五、IRGen(AST转LLVM IR)模块约束

和GC清单编译优化与GC交互约束配套。

IRGen核心规则

  1. IRGen负责:变量分配、表达式求值、临时根管理、写屏障调用、安全点Statepoint插入、调试元数据挂载。
  2. 所有GC相关IR操作,不直接手写LLVM原始指令,封装为内部工具函数。
  3. 临时根、栈根严格按照作用域、表达式生命周期管理;不手动到处插入push/pop。
  4. 写屏障调用标记内存语义,设置noinline,添加内存屏障,防止LLVM DCE、重排、LICM错误优化。
  5. 安全点严格限定允许插入位置;禁止在表达式中间、算术运算中间插入安全点。
  6. 每个函数生成对应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

  1. 类型严格区分:值类型 / GC引用类型 / 原生裸指针;类型系统层面做区分,编译期检查。
  2. 字符串:支持普通字符串、@原始字符串、$模板字符串、@$原始插值字符串;Char字符类型。
  3. 包体系:目录对应包;包名与目录名规则;导入、导出可见性。
  4. 泛型:编译期实例化;实例化时正确生成引用位图,不能丢失GC引用信息。
  5. 闭包捕获:捕获变量区分值捕获/引用捕获;捕获到env堆对象,自动触发写屏障。
  6. 空安全:可空引用类型编译期检查;可空解引用生成运行时空检查。
  7. FFI:显式声明C外部函数;区分返回值是否为GC托管对象;禁止FFI直接返回裸GC指针。

七、构建、链接、交叉编译约束

  1. Windows平台:使用LLVM+MSVC工具链+LLD;输出PE exe,PDB调试符号。
  2. 交叉编译:依靠LLVM target triple + sysroot;sysroot提供目标平台头文件、系统库。
  3. 静态编译优先:类似Go,默认尽可能静态链接;允许动态链接可配置。
  4. 构建产物分离:build目录存放中间obj、ir;dist存放编译器发行版产物。
  5. 发行版打包:编译器hao.exe连同依赖LLVM库、运行时库、内置标准库一起打包;分发到其他Windows机器可以直接编译.hao源码。

交叉编译关键约束

  • Windows编译Linux目标,必须提供Linux sysroot,不能只靠LLVM工具。
  • 不同目标平台ABI、DataLayout在编译期正确配置。
  • GC运行时要针对目标平台编译一份,不能直接复用Windows运行时二进制。

八、包管理模块约束(haoproject.json)

  1. 项目配置文件:haoproject.json,定义项目名称、版本、依赖、替换、仓库配置。
  2. 全局包缓存目录:由环境变量HAO_PKG或者默认路径控制;多个项目共享缓存,避免每个项目复制一份依赖。
  3. 仓库优先级:配置文件registry > 环境变量 > 内置默认中央仓库。
  4. 支持依赖替换、排除子依赖、版本冲突解析。
  5. 子命令:hao pkg add / hao pkg remove / hao pkg install
  6. 拉取github资源支持镜像地址兼容,适配国内网络。

禁止

  • ❌ 项目目录不复制完整依赖源码副本,优先引用全局缓存。

九、自举相关架构约束

当前阶段编译器C++实现;未来目标:用HaoLang重写编译器本身。

  1. 当前C++版本是引导编译器(bootstrap‑0);只作为启动用。
  2. 架构设计上,尽量把编译器核心逻辑抽象为数据结构,避免重度C++特性;方便后续移植到HaoLang。
  3. 自举条件:HaoLang语言具备:泛型、容器、闭包、FFI、足够强的类型系统;可以实现词法、语法、Sema、IRGen。
  4. 自举完成之后:产出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工具链。

再次提示:本文档全部为规则与方法论,示例代码、函数名称仅参考,实际实现可自由调整细节,但约束、禁止项、验收标准需要遵守。