Sway 编译器内部架构解析:从源码到 Fuel VM 字节码的完整编译流水线
【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway
Sway 是面向 Fuel 区块链的智能合约语言,设计上深受 Rust 启发,目标是把现代语言工程能力与性能带进区块链生态。本文以docs/internals.md为骨架,结合本仓库源码,逐阶段剖析 Sway 编译器的内部结构:词法分析、语法分析(CST/AST)、语义分析(依赖图、命名空间、类型检查、控制流图、死代码分析)、IR 生成、优化、代码生成与部署执行,并深入到支撑全流程的Engines与ConcurrentSlab内存基础设施。读完本文,你将理解forc build背后完整的编译流水线,知道每个阶段在哪个源码文件中实现、产出什么中间结构,以及如何用--ast、--ir、--dca-graph等命令选项把中间产物"打印"出来供调试分析。
编译流程总览
Sway 的编译过程可以分解为几个关键阶段,整体流程如下:
- 词法分析(Lexer):将源码拆解为 token,识别关键字、标识符、字面量与运算符。
- 语法分析(Parser):检查 token 是否符合语言的语法规则,形成抽象语法树(AST)。
- 语义分析(Semantic Analysis):检查 AST 的语义正确性,包括类型检查与作用域规则。
- IR 生成(IR Generation):将验证过的 AST 翻译为中间表示(IR),便于后续操作。
- 优化(Optimization):可选步骤,用于提升性能或缩减体积。
- 代码生成(Code Generation):将优化后的 IR 翻译为目标代码,适合在区块链平台上执行。
- 部署与执行(Deployment and Execution):生成的代码作为智能合约部署到区块链上,在触发时执行。
下文逐一深入每个阶段。
词法分析(Lexer)
词法分析阶段(lexing)将源码拆解为 token——编程语言的最小组成单元,例如关键字、标识符、字面量和运算符。该过程起始于 lex(sway-parse/src/token.rs):
pub fn lex( handler: &Handler, src: Source, start: usize, end: usize, source_id: Option<SourceId>, ) -> Result<TokenStream> {lex会先调用lex_commented生成带注释的 token 流,再通过strip_comments剥离注释,最终返回一个 TokenStream(sway-ast/src/token.rs),它由一棵 token 树构成:
pub enum GenericTokenTree<T> { Punct(Punct), Ident(Ident), Group(GenericGroup<T>), Literal(Literal), DocComment(DocComment), }从源码可以看出,词法层通过GenericTokenTree区分五类元素:标点(Punct)、标识符(Ident)、分组(Group,即()/{}/[]等定界符包裹的内容)、字面量(Literal)与文档注释(DocComment)。TokenStream内部维护token_trees: Vec<TokenTree>与full_span: Span(见 sway-ast/src/token.rs)。
值得补充的是词法层还承担了标识符与路径的合法性校验。在 sway-parse/src/token.rs 中,is_valid_identifier_or_path按以下规则校验:
- 标识符不能为空,不能只是
_,不能以双下划线__开头; - 首字符必须是 Unicode XID_Start 或
_,其余字符必须是 Unicode XID_Continue; - 路径允许以
::开头,按::分段,任何不属于::的孤立冒号(如foo:、foo:::bar)都会被拒绝。
此外,词法层会识别括号类定界符((/)、{/}、[/])以及分号、冒号、逗号、星号、加减号、比较符、赋值号等各类标点种类,为语法分析提供结构化的输入。
语法分析(Parser)
语法分析阶段检查词法器产出的 token 是否符合语言文法,分两步进行:
- 解析器首先产生具体语法树(CST);
- 再将 CST 处理为抽象语法树(AST)。
该实现在 parse(sway-core/src/lib.rs) 中起始,返回CST与AST,分别由LexedProgram和ParseProgram类型表示:
pub fn parse( src: Source, handler: &Handler, engines: &Engines, config: Option<&BuildConfig>, experimental: ExperimentalFeatures, package_name: &str, ) -> Result<(lexed::LexedProgram, parsed::ParseProgram), ErrorEmitted> {注意当前仓库中的parse签名已扩展出experimental: ExperimentalFeatures与package_name参数;当传入BuildConfig时,模块源码中声明的mod子模块需要从其他文件解析(见 sway-core/src/lib.rs 中config分支的说明)。
具体语法树(Concrete Syntax Tree)
CST 由 LexedProgram(sway-core/src/language/lexed/program.rs) 与 LexedModule(sway-core/src/language/lexed/mod.rs) 类型实现,表示一棵源码模块树:根模块以及所有通过mod关键字引入的子模块。
/// A lexed, but not yet parsed or type-checked, Sway program. pub struct LexedProgram { pub kind: TreeType, pub root: LexedModule, } pub struct LexedModule { /// The content of this module in the form of a [Module]. pub tree: Annotated<Module>, /// Submodules introduced within this module using the `mod` syntax in order of declaration. pub submodules: Vec<(ModName, LexedSubmodule)>, } /// A library module that was declared as a `mod` of another module. pub struct LexedSubmodule { pub module: LexedModule, }每个 Module(sway-ast/src/module.rs) 包含一组语法项(items)的列表:
pub struct Module { pub kind: ModuleKind, pub items: Vec<Item>, ... }每个 item 对应语言语法中的一个具体语法项,由Item与 ItemKind(sway-ast/src/item/mod.rs) 表示:
pub enum ItemKind { Submodule(Submodule), Use(ItemUse), Struct(ItemStruct), Enum(ItemEnum), Fn(ItemFn), Trait(ItemTrait), Impl(ItemImpl), Abi(ItemAbi), Const(ItemConst), Storage(ItemStorage), Configurable(ItemConfigurable), TypeAlias(ItemTypeAlias), ... }从ItemKind可以看出 Sway 支持的顶层语法项:子模块、use导入、结构体、枚举、函数、trait、impl、ABI、常量、存储、configurable 配置项与类型别名等,覆盖了智能合约开发的核心语言特性。
抽象语法树(Abstract Syntax Tree)
AST 由 CST 转换而来,是一种更适合后续操作的结构化表示。转换在 convert_parse_tree(sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs) 中完成,核心入口包括convert_parse_tree、module_to_sway_parse_tree与item_to_ast_nodes:
pub fn convert_parse_tree( context: &mut Context, handler: &Handler, engines: &Engines, module: Module, ) -> Result<(TreeType, ParseTree), ErrorEmitted> { let tree_type = convert_module_kind(&module.kind); context.set_program_type(tree_type); let tree = module_to_sway_parse_tree(context, handler, engines, module)?; Ok((tree_type, tree)) }convert_module_kind把模块种类映射为TreeType(Script/Contract/Predicate/Library),module_to_sway_parse_tree遍历模块的 items 并逐个调用item_to_ast_nodes生成根节点列表;值得注意的是,源码中要求子模块(mod)必须位于文件开头其他 item 之前(见item_to_ast_nodes的item_can_be_submodule参数)。
AST 由 ParseProgram(sway-core/src/language/parsed/program.rs) 实现,它是一棵模块树(ParseModule(sway-core/src/language/parsed/module.rs)),每个模块由基于节点的层级结构(AstNode 与 AstNodeContent(sway-core/src/language/parsed/mod.rs))组成:
/// A parsed, but not yet type-checked, Sway program. pub struct ParseProgram { pub kind: TreeType, pub root: ParseModule, } /// A module and its submodules in the form of a tree. pub struct ParseModule { /// The content of this module. pub tree: ParseTree, /// Submodules introduced within this module. pub submodules: Vec<(ModName, ParseSubmodule)>, ... }/// Represents the various structures that constitute a Sway program. pub enum AstNodeContent { /// A statement of the form `use foo::bar;` or `use ::foo::bar;` UseStatement(UseStatement), /// Any type of declaration, of which there are quite a few. See [Declaration] for more details /// on the possible variants. Declaration(Declaration), /// Any type of expression, of which there are quite a few. See [Expression] for more details. Expression(Expression), /// A statement of the form `mod foo::bar;` which imports/includes another source file. IncludeStatement(IncludeStatement), ... }AST 还可以通过给forc build传入--ast选项以文本形式输出,便于检查解析产物是否符合预期。另外,sway-core/src/language/parsed/program.rs 中的TreeType枚举表明 Sway 程序分为四种类型:predicate、script、contract、library,其中通过mod声明的子模块只能是 library。
语义分析(Semantic Analysis)
语义分析通过检查类型不匹配、未声明变量等语义错误来验证代码含义,产出一个类型化 AST(typed AST),确保代码符合语言语义。整个过程起始于 parsed_to_ast(sway-core/src/lib.rs),返回完全类型化的 AST ty::TyProgram(sway-core/src/language/ty/program.rs):
pub fn parsed_to_ast( handler: &Handler, engines: &Engines, parse_program: &mut parsed::ParseProgram, initial_namespace: namespace::Package, build_config: Option<&BuildConfig>, package_name: &str, retrigger_compilation: Option<Arc<AtomicBool>>, experimental: ExperimentalFeatures, backtrace: Backtrace, ) -> Result<ty::TyProgram, TypeCheckFailed> {在 sway-core/src/lib.rs 的parsed_to_ast中可以看到内部先后执行了多个子步骤:先构建模块依赖图(build_module_dep_graph),再基于初始命名空间创建收集用的命名空间并调用ty::TyProgram::collect收集程序符号。下面逐个展开这些子阶段。
模块依赖图(Module Dependency Graph)
首先为源码模块构建模块依赖图,实现在 ty::TyModule::build_dep_graph(sway-core/src/semantic_analysis/module.rs):
impl ty::TyModule { /// Analyzes the given parsed module to produce a dependency graph. pub fn build_dep_graph( handler: &Handler, parsed: &ParseModule, ) -> Result<ModuleDepGraph, ErrorEmitted> }图中各模块是节点、依赖关系是边,通过执行拓扑排序即可计算出正确的模块求值顺序,该顺序随后被用于后续各遍(pass)对 AST 的求值。
符号收集(Symbol Collection)
接下来收集 AST 中出现的符号,实现在 ty::TyAstNode::collect(sway-core/src/semantic_analysis/ast_node/mod.rs):
impl ty::TyAstNode { pub(crate) fn collect( handler: &Handler, engines: &Engines, ctx: &mut SymbolCollectionContext, node: &AstNode, ) -> Result<(), ErrorEmitted> }收集的产物是一棵命名空间树,其中包含将基于字符串的标识符解析为对应声明链接所需的全部信息。
命名空间(Namespaces)
每个 Namespace(sway-core/src/semantic_analysis/namespace/namespace.rs) 对应一个模块,内含与源码作用域对应的词法作用域树。每个 LexicalScope(sway-core/src/semantic_analysis/namespace/lexical_scope.rs) 包含一个符号表,把标识符映射到声明:
pub struct Namespace { /// An immutable namespace that consists of the names that should always be present. init: Module, /// The `root` of the project namespace. pub(crate) root: Root, ... } pub struct Root { pub(crate) module: Module, } pub struct Module { /// Submodules of the current module represented as an ordered map. pub(crate) submodules: im::OrdMap<ModuleName, Module>, /// Keeps all lexical scopes associated with this module. pub lexical_scopes: Vec<LexicalScope>, ... } /// A `LexicalScope` contains a set of all items that exist within the lexical scope via declaration or /// importing, along with all its associated hierarchical scopes. pub struct LexicalScope { /// The set of symbols, implementations, synonyms and aliases present within this scope. pub items: Items, /// The set of available scopes defined inside this scope's hierarchy. pub children: Vec<LexicalScopeId>, /// The parent scope associated with this scope. Will be None for a root scope. pub parent: Option<LexicalScopeId>, } /// The set of items that exist within some lexical scope via declaration or importing. pub struct Items { /// A map from `Ident`s to their associated parsed declarations. pub(crate) parsed_symbols: ParsedSymbolMap, /// A map from `Ident`s to their associated typed declarations. pub(crate) symbols: SymbolMap, ... }命名空间结构分为三层:Namespace(含固定存在的init模块与项目根root)、Module(有序子模块表 + 词法作用域列表)、LexicalScope(符号集合 + 父子层级关系),这种设计使名称解析可以高效地沿作用域链向上查找。
类型检查(Type Checking)
类型检查阶段利用此前构建的命名空间解析名称,确保程序中使用的类型一致且正确。该过程在 ty::TyProgram::type_check(sway-core/src/semantic_analysis/program.rs) 中完成,检查状态被跟踪在 TypeCheckContext(sway-core/src/semantic_analysis/type_check_context.rs) 上下文类型中:
impl TyProgram { pub fn type_check( handler: &Handler, engines: &Engines, parsed: &ParseProgram, initial_namespace: namespace::Root, package_name: &str, build_config: Option<&BuildConfig>, ) -> Result<Self, ErrorEmitted> }与此同时还会执行单态化(monomorphization)——一种处理泛型的编译器技术,为每一组唯一的泛型类型组合生成特化代码,通过避免运行时开销来提升性能。相关实现位于 TypeCheckContext::monomorphize(sway-core/src/semantic_analysis/type_check_context.rs) 及关联的 TypeBinding(sway-core/src/type_system/ast_elements/binding.rs) 类型中。
控制流图(Control Flow Graph)
得到完整的类型化 AST 后,还可以做进一步分析。为此编译器构建控制流图(CFG)——程序内控制流的表示,展示控制如何从一条指令流向另一条指令。CFG 的节点表示基本块(basic block),有向边表示块间的控制流转移。
Sway 使用 CFG 分析返回路径,确保所有需要返回值路径都以正确类型返回值:它检查方法命名空间与函数命名空间中的每个函数声明,验证所有通向函数出口节点的路径返回相同类型;此外,若函数有返回类型,所有路径都必须通向出口节点。实现在 ControlFlowGraph(sway-core/src/control_flow_analysis/analyze_return_paths.rs)。
死代码分析(Dead Code Analysis)
死代码分析识别并消除程序中永远不会执行的代码段,帮助提升代码质量、可维护性与性能。死代码分析图(DCA graph)的工作原理如下:
- 节点:DCA 图中的节点表示程序内不同的代码段或块,可以是函数、循环、条件分支或其他逻辑分组。
- 边:节点间的边表示不同代码段间的控制流关系,例如一条边可连接条件语句与其真/假两个分支对应的代码块。
- 不可达代码:DCA 图识别出因条件语句、循环结构或其他控制流机制而不可达的代码段,这些与主控制流断开的段通常就是死代码。
实现在 ControlFlowGraph::find_dead_code(sway-core/src/control_flow_analysis/dead_code_analysis.rs)。DCA 图可通过给forc build传入--dca-graph选项以 DOT 格式输出。
中间表示(IR)生成
IR 生成阶段把验证过的、完全类型化的 AST 翻译为中间表示(IR)。IR 充当高级代码与目标代码生成阶段之间的桥梁,让优化更易执行。该过程起始于 compile_program(sway-core/src/ir_generation.rs):
pub fn compile_program<'a>( program: &ty::TyProgram, include_tests: bool, engines: &'a Engines, experimental: ExperimentalFlags, ) -> Result<Context<'a>, Vec<CompileError>>最终产出的 IR 文件形如:
script { fn main() -> bool { entry(): v0 = const u64 11 v1 = const u64 0 v2 = cmp eq v0 v1 br block0() block0(): v9 = const bool false ret bool v9 } }IR 指令可以通过给forc build传入--ir选项输出。
优化(Optimization)
优化阶段可选但收益显著:它对 IR 应用多种优化技术,提升结果代码的性能或缩减体积。常见优化包括常量折叠(constant folding)、死代码消除(dead code elimination)与循环优化(loop optimization)。
优化 pass 被组织为 PassManager(sway-ir/src/pass_manager.rs) 中不同的 pass 组,并在 compile_ast_to_ir_to_asm(sway-core/src/lib.rs) 中配置:
pass_group.append_pass(CONST_DEMOTION_NAME); pass_group.append_pass(ARG_DEMOTION_NAME); pass_group.append_pass(RET_DEMOTION_NAME); pass_group.append_pass(MISC_DEMOTION_NAME); // Convert loads and stores to mem_copies where possible. pass_group.append_pass(MEMCPYOPT_NAME); // Run a DCE and simplify-cfg to clean up any obsolete instructions. pass_group.append_pass(DCE_NAME); pass_group.append_pass(SIMPLIFY_CFG_NAME); match build_config.optimization_level { OptLevel::Opt1 => { pass_group.append_pass(SROA_NAME); pass_group.append_pass(MEM2REG_NAME); pass_group.append_pass(DCE_NAME); } OptLevel::Opt0 => {} }从这段配置可以看出优化级别的差异:Opt0只跑常量/实参/返回值降级(demotion)、memcpy 优化与基础的 DCE、CFG 简化;而Opt1在此基础上追加 SROA(标量替换聚合)与 mem2reg 提升,并再跑一轮 DCE,从而让代码更利于后续寄存器分配与消除冗余。随后执行这些 pass 返回优化后的 IR,供下一步代码生成使用:
// Run the passes. let res = if let Err(ir_error) = pass_mgr.run(&mut ir, &pass_group) {代码生成(Code Generation)
代码生成阶段把优化后的 IR 翻译为目标代码,使其适合在区块链平台上执行。该过程确保产出的字节码或机器码符合目标区块链环境的约束与要求。
/// Given an AST compilation result, try compiling to a `CompiledAsm`, /// containing the asm in opcode form (not raw bytes/bytecode). pub fn ast_to_asm( handler: &Handler, engines: &Engines, programs: &Programs, build_config: &BuildConfig, ) -> Result<CompiledAsm, ErrorEmitted> {这一步的输出是 Fuel VM 汇编代码,由 FuelAsmBuilder(sway-core/src/asm_generation/fuel/fuel_asm_builder.rs) 类型生成。从ast_to_asm的注释可以看到,产物先是操作码形式的汇编(CompiledAsm),而非原始字节;后续还需进一步把汇编汇编成最终字节码(本仓库 sway-core/src/asm_generation 目录下的 from_ir 等模块负责该收尾工作)。结合forc build的构建配置(sway-core/src/build_config.rs中导出的PrintAsm等选项,见 sway-core/src/lib.rs),可以推断存在输出汇编文本的调试途径,具体以forc build --help中实际可用的选项为准。
部署与执行(Deployment and Execution)
最终,生成的代码作为智能合约部署到 Fuel VM 上。智能合约由交易或外部事件触发执行,其行为由编译器生成的代码所决定。本仓库提供了大量可供实际部署运行的示例(examples 目录下的 counter、wallet_smart_contract、storage_example、upgradeable_proxy 等项目),是理解"编译产物如何落地为链上合约"的直观参照。
支撑全流程的内存基础设施:Engines 与 Concurrent Slab
Engines
编译器中的所有并发 slab 都包含在 Engines(sway-core/src/engine_threading.rs) 中,它是编译器传递内存上下文的主要类型。内部包含类型引擎、不同种类的声明引擎、查询/缓存系统以及源码文件 id 引擎:
pub struct Engines { type_engine: TypeEngine, decl_engine: DeclEngine, parsed_decl_engine: ParsedDeclEngine, query_engine: QueryEngine, source_engine: SourceEngine, obs_engine: Arc<ObservabilityEngine>, }当前仓库的Engines还额外包含了obs_engine: Arc<ObservabilityEngine>(可观测性引擎,见 sway-core/src/obs_engine.rs),并提供了te()/de()/pe()/qe()/se()/obs()等访问器以及按program_id清理数据的clear_program方法(用于垃圾回收)。这个类型会在编译器各处被传递,是贯穿全流程的核心上下文。
并发 Slab(Concurrent Slab)
编译器在节点内存管理上采用了内存区域(memory arena)模式:不对节点持有直接指针/引用,而是使用整数 id,之后用该 id 索引到存放某一类节点全部内存的向量中。这种方式简化了编译器的内存管理,允许安全并发,也便于表示所有树/图结构中常见的循环引用。
ConcurrentSlab(sway-core/src/concurrent_slab.rs) 的实现如下:
pub(crate) struct ConcurrentSlab<T> { pub inner: RwLock<Inner<T>>, } pub struct Inner<T> { pub items: Vec<Option<Arc<T>>>, pub free_list: Vec<usize>, }Inner用Vec<Option<Arc<T>>>存放节点(None表示槽位空闲),用free_list记录可复用的空闲槽位,配合RwLock实现并发安全。这与 Rust 社区中"惯用树"的内存管理思路一脉相承:通过稳定 id 而非裸指针引用节点,既能规避借用检查对自引用结构的限制,又能高效处理图中节点间的循环依赖。
小结
Sway 编译器是一条职责清晰、层次分明的流水线:词法分析(sway-parse/src/token.rs)产出 token 树,语法分析(sway-core/src/lib.rs 的parse)先后产出 CST(LexedProgram)与 AST(ParseProgram),语义分析(parsed_to_ast)经由模块依赖图、符号收集、命名空间解析、类型检查(含单态化)、控制流图与死代码分析得到类型化 AST,IR 生成(sway-core/src/ir_generation.rs)产出中间表示,优化(sway-ir/src/pass_manager.rs 的 pass 组)按优化级别精简 IR,最终由 sway-core/src/asm_generation/fuel/fuel_asm_builder.rs 生成 Fuel VM 汇编并汇编为可部署字节码。贯穿全程的Engines与ConcurrentSlab则保证了各阶段内存的安全共享与高效管理。
对编译中间产物感兴趣的开发者,可以借助forc build的--ast(输出 AST 文本)、--ir(输出 IR)、--dca-graph(以 DOT 格式输出死代码分析图)等选项逐层检视编译结果,配合本仓库源码与 examples 中的示例项目,即可快速建立从 Sway 源码到链上字节码的完整心智模型。
【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考