Aptos move-model Builder 模块解析:Legacy 模式与 Compiler 模式的双轨构建机制
【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core
导读
本文聚焦 Aptos 仓库中 Move 规范验证体系的核心前置模块——third_party/move/move-model/src/builder,它是将一组 Move 模块"构建(编译)"为全局环境(GlobalEnv)的入口。文章以 builder/README.md 定义的legacy 模式与compiler 模式两条主线展开,结合模块源码剖析两种模式在"源码信息与 bytecode 信息如何组合"上的本质差异、三阶段翻译流水线、符号表结构与关键 API 调用链,帮助读者理解 Move Prover / 规范检查工具从 Move 代码到可分析模型的完整构建路径。
builder 模块的定位:为 Move 模块构建全局环境
在 Aptos 的 move-model 中,builder是一个负责"前端构建"的子模块,其职责在 README 开头一句话即已点明:
This module handles building (compiling) a global environment for a set of Move modules.
这里的"构建"并不是指生成链上部署的字节码,而是指:把一组 Move 模块(可能同时包含 Move 源码与已编译的 bytecode)翻译成规范验证系统可以查询、遍历、做类型检查的全局环境GlobalEnv。GlobalEnv是 move-model 的核心数据结构(定义于 model.rs),承载模块、结构体、函数、规格(spec)、常量等全部模型信息,是 Move Prover 后续所有分析阶段的数据基础。
从目录结构看,builder由以下文件组成(见 builder 目录):
| 文件 | 职责 |
|---|---|
| mod.rs | 子模块声明与若干文本辅助函数(复数化等) |
| model_builder.rs | ModelBuilder主状态机:维护全部符号表,负责将各模块按无环依赖顺序登记进模型 |
| module_builder.rs | ModuleBuilder:单个模块的翻译器,实现声明分析、定义分析、环境填充三阶段 |
| exp_builder.rs | ExpTranslator:Move 表达式(含 spec 语言表达式)到模型 AST 的翻译与类型检查 |
| builtins.rs | 注册内建函数与操作符(ModelBuilder::new时调用builtins::declare_builtins) |
| binary_module_loader.rs | 二进制模块加载器:将CompiledModule+SourceMap载入环境 |
| xir_loader.rs | XIR 加载器(字节码中间表示相关) |
| macros.rs | 内建宏展开辅助 |
两种构建模式:README 定义的双轨机制
README 指出该模块可以以两种模式运行,这是全文的核心骨架:
- legacy 模式(遗留模式):合并 bytecode 与 Move 源码中代表"表达式语言构造"的部分。由此得到的模型同时具备源码信息与 bytecode 信息。
- compiler 模式(编译器模式):完整分析 Move 源码。由此得到的模型默认不包含 bytecode 相关信息;但 bytecode 可以在后续阶段通过
GlobalEnv::attach_compiled_module附加进来。
两种模式的差异本质,在于"模型的信息来源":legacy 模式以已编译的CompiledModule为主体、以源码补全表达式层信息;compiler 模式则以源码为主体、bytecode 是可选的后置附件。下文分别深入。
Legacy 模式:bytecode 为主、源码补充表达式信息
Legacy 模式的实现集中在 binary_module_loader.rs。其入口是GlobalEnv::load_compiled_module(binary_module_loader.rs):
pub fn load_compiled_module( &mut self, with_dep_closure: bool, module: CompiledModule, source_map: SourceMap, ) -> ModuleId { let mut loader = BinaryModuleLoader::new(self, with_dep_closure, &module, &source_map); loader.load(); let BinaryModuleLoader { module_id, .. } = loader; self.attach_compiled_module(module_id, module, source_map); self.update_loaded_modules(); module_id }其注释清楚地说明了实现策略:
we leverage the already existing and battle-tested
env.attach_compiled_modulefunction... This function here basically simulates populating the initial env from bytecode instead of AST, and then calls the bytecode attach.
即:legacy 模式先"假装"从 AST 填充环境(实际上从 bytecode 反推),再调用字节码附加逻辑,从而让最终模型同时拥有两类信息。几个值得注意的实现细节:
- 依赖处理:
with_dep_closure为false时,模块的全部依赖必须已存在于环境中;为true时,会按使用表为缺失依赖创建 "stub"(占位)定义,形成"部分"模块,并可在后续load_compiled_module调用中被逐步精化(binary_module_loader.rs)。 0x1::vector特殊处理:加载时若发现vector模块尚未加载,会调用add_well_known_vector_funs(binary_module_loader.rs)把empty、length、borrow、borrow_mut、push_back、pop_back、destroy_empty、swap等以字节码指令形式存在、没有 handle 的 vector 函数以 native public 函数的形式补进环境——这是"合并 bytecode 信息"的直接体现。- 结构体/函数签名一致性校验:若同一结构体或函数已被加载,
load过程会做逻辑签名比对(type_params_logical_equal、params_logical_equal),不一致时报错并保证环境在无错前提下自洽。
Compiler 模式:完全分析源码,bytecode 后置附加
Compiler 模式的入口在 lib.rs 的run_model_builder_in_compiler_mode:
Builds the Move model for the v2 compiler. This builds the model, compiling both code and specs from sources into typed-checked AST. No bytecode is attached to the model. This currently uses the v1 compiler as the parser (up to expansion AST), after that a new type checker.
其流程(见run_model_builder_with_options_and_compilation_flags,lib.rs)大致如下:
- 解析:用编译器(
Compiler::from_package_paths)运行到PASS_PARSER,收集源码文件、注释与命名地址映射。 - 扩展:运行到
PASS_EXPANSION,得到 expansion AST;期间把vector、cmp、string、string_utils、signer等隐式模块及其依赖闭包纳入编译集。 - 模型构建:调用
run_move_checker(lib.rs),这是 compiler 模式的核心——不做 bytecode 附加("The expansion AST will be type checked. No bytecode is attached.")。
因此,compiler 模式产出的模型只含源码层信息(结构体、函数签名、spec 块、宏展开结果等),bytecode 相关的派生数据(如字节码指令序列)默认缺失,需要在后续阶段调用GlobalEnv::attach_compiled_module(model.rs)手动附加。该方法要求module_data[module_id]已通过GlobalEnv::add初始化:
/// Attaches a bytecode module to the module in the environment. This functions expects /// the `self.module_data[module_id]` to be already initialized using the `self.add` /// function. pub fn attach_compiled_module( &mut self, module_id: ModuleId, module: CompiledModule, source_map: SourceMap, )这一设计的意义在于:compiler 模式可以在不依赖链上/本地字节码的情况下先行完成源码级检查(类型检查、spec 检查),需要字节码级分析(如字节码指令层面的验证)时再按需附加,实现了"源码检查"与"字节码分析"的解耦。
模块翻译的三阶段流水线
无论哪种模式,单个模块的翻译都由ModuleBuilder::translate驱动(module_builder.rs),分为三个阶段:
pub fn translate(&mut self, loc: Loc, module_def: EA::ModuleDefinition) { self.decl_ana(&module_def); // 阶段 1:声明分析 self.def_ana(&module_def); // 阶段 2:定义分析 self.synthesize_validity_slots(); self.collect_spec_block_infos(&module_def); let attrs = self.translate_attributes(&module_def.attributes); self.populate_and_finalize_env(loc, attrs); // 阶段 3:填充环境 }- 声明分析(declaration analysis):收集模块内结构体、函数、spec 函数、spec 变量与 schema 的全部声明信息,但暂不分析函数体、条件与不变量。原因在于全局声明是"顺序无关"的,且可能存在循环引用——必须先让所有声明可见,才能正确解析相互引用。
- 定义分析(definition analysis):回头处理阶段 1 跳过的定义体,对表达式与 schema 包含(inclusion)做完整分析与类型检查。此阶段由
ExpTranslator(exp_builder.rs)完成表达式到模型 AST 的翻译,并支持old表达式、ghost字段等规格语言特性。 - 填充阶段(population phase):将本模块的信息正式登记进
GlobalEnv,并完成环境级后处理(如常量访问器注入、package 可见性的 friend 声明补全等)。
在 compiler 模式下,run_move_checker会先把模块按dependency_order排序,保证依赖先于使用者被翻译;同时通过pre_register_lemma_decls(lib.rs)预注册所有模块的 lemma 声明,使跨模块 lemma 引用不依赖模块处理顺序即可解析。
ModelBuilder:环境构建的状态核心
ModelBuilder(model_builder.rs)是构建过程的"总账本",以多张符号表维护增量状态,每翻译一个模块就扩展一次。其核心字段包括:
spec_fun_table:spec 函数符号表,因支持重载,一项可对应多个函数;spec_var_table/spec_schema_table:spec 变量与 schema 符号表,后者附带unused_schema_set用于生成未使用 schema 的告警;struct_table/reverse_struct_table:结构体符号表及其反向映射(供错误消息中类型可视化);fun_table:函数符号表,并维护builtin_receiver_functions与receiver_functions用于 receiver 风格方法调用(如v.length()分发到0x1::vector::length);const_table:常量符号表,支持跨模块常量使用者追踪(sync_const_users);intrinsics:内建声明列表,populate_env时统一注册进GlobalEnv;lemma_decl_table:全局 lemma 查询表,首次扫描时预填充,保证跨模块引用可解析。
ModelBuilder::new(model_builder.rs)在初始化时会调用builtins::declare_builtins,把语言内建的操作符与 spec 函数预先登记,这是表达式翻译的前提。
两种模式的选择与适用场景
- 需要完整字节码信息的场景(legacy 模式):例如对已部署模块做验证、需要访问指令级信息的分析、或者依赖
SourceMap做源码↔字节码映射的场景。此时通过load_compiled_module(with_dep_closure, module, source_map)一次性获得"源码 + bytecode"双全模型。 - 纯源码分析场景(compiler 模式):例如在编译早期对包内源码做类型检查、spec 检查、宏展开验证。此时使用
run_model_builder_in_compiler_mode(或带选项/编译标志的run_model_builder_with_options_and_compilation_flags),产出纯源码模型;若后续阶段需要字节码,再调用GlobalEnv::attach_compiled_module按需附加。
从代码结构可以推断,两者并非互斥的封闭体系,而是共享同一套GlobalEnv/ModelBuilder/ModuleBuilder基础设施:legacy 模式通过BinaryModuleLoader从 bytecode 反推环境初始状态,compiler 模式则从 expansion AST 正向翻译;最终都收敛到attach_compiled_module所定义的"源码信息 + 字节码信息"统一模型表示上。
源码地图与延伸阅读
- 模式定义与模块定位:builder/README.md
- 环境构建主状态机:model_builder.rs
- 单模块三阶段翻译:module_builder.rs
- 表达式与 spec 翻译器:exp_builder.rs
- bytecode 加载(legacy 模式核心):binary_module_loader.rs
- 内建函数注册:builtins.rs
- Compiler 模式入口与
run_move_checker:lib.rs - 全局环境与
attach_compiled_module:model.rs
综上,builder模块以"legacy 合并 bytecode"与"compiler 纯源码分析"两种模式覆盖了从 Move 代码到可验证模型的全部构建路径,是理解 Aptos move-model 乃至 Move Prover 前端流水线的关键入口。
【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考