ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Sway 编译器内部架构解析:从源码到 Fuel VM 字节码的完整编译流水线

Sway 编译器内部架构解析:从源码到 Fuel VM 字节码的完整编译流水线 Sway 编译器内部架构解析从源码到 Fuel VM 字节码的完整编译流水线【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/swaySway 是面向 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——编程语言的最小组成单元例如关键字、标识符、字面量和运算符。该过程起始于 lexsway-parse/src/token.rspub fn lex( handler: Handler, src: Source, start: usize, end: usize, source_id: OptionSourceId, ) - ResultTokenStream {lex会先调用lex_commented生成带注释的 token 流再通过strip_comments剥离注释最终返回一个 TokenStreamsway-ast/src/token.rs它由一棵 token 树构成pub enum GenericTokenTreeT { Punct(Punct), Ident(Ident), Group(GenericGroupT), Literal(Literal), DocComment(DocComment), }从源码可以看出词法层通过GenericTokenTree区分五类元素标点Punct、标识符Ident、分组Group即()/{}/[]等定界符包裹的内容、字面量Literal与文档注释DocComment。TokenStream内部维护token_trees: VecTokenTree与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。该实现在 parsesway-core/src/lib.rs 中起始返回CST与AST分别由LexedProgram和ParseProgram类型表示pub fn parse( src: Source, handler: Handler, engines: Engines, config: OptionBuildConfig, 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 TreeCST 由 LexedProgramsway-core/src/language/lexed/program.rs 与 LexedModulesway-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: AnnotatedModule, /// 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, }每个 Modulesway-ast/src/module.rs 包含一组语法项items的列表pub struct Module { pub kind: ModuleKind, pub items: VecItem, ... }每个 item 对应语言语法中的一个具体语法项由Item与 ItemKindsway-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 TreeAST 由 CST 转换而来是一种更适合后续操作的结构化表示。转换在 convert_parse_treesway-core/src/transform/to_parsed_lang/convert_parse_tree.rs 中完成核心入口包括convert_parse_tree、module_to_sway_parse_tree与item_to_ast_nodespub 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把模块种类映射为TreeTypeScript/Contract/Predicate/Librarymodule_to_sway_parse_tree遍历模块的 items 并逐个调用item_to_ast_nodes生成根节点列表值得注意的是源码中要求子模块mod必须位于文件开头其他 item 之前见item_to_ast_nodes的item_can_be_submodule参数。AST 由 ParseProgramsway-core/src/language/parsed/program.rs 实现它是一棵模块树ParseModulesway-core/src/language/parsed/module.rs每个模块由基于节点的层级结构AstNode 与 AstNodeContentsway-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语义分析通过检查类型不匹配、未声明变量等语义错误来验证代码含义产出一个类型化 ASTtyped AST确保代码符合语言语义。整个过程起始于 parsed_to_astsway-core/src/lib.rs返回完全类型化的 AST ty::TyProgramsway-core/src/language/ty/program.rspub fn parsed_to_ast( handler: Handler, engines: Engines, parse_program: mut parsed::ParseProgram, initial_namespace: namespace::Package, build_config: OptionBuildConfig, package_name: str, retrigger_compilation: OptionArcAtomicBool, experimental: ExperimentalFeatures, backtrace: Backtrace, ) - Resultty::TyProgram, TypeCheckFailed {在 sway-core/src/lib.rs 的parsed_to_ast中可以看到内部先后执行了多个子步骤先构建模块依赖图build_module_dep_graph再基于初始命名空间创建收集用的命名空间并调用ty::TyProgram::collect收集程序符号。下面逐个展开这些子阶段。模块依赖图Module Dependency Graph首先为源码模块构建模块依赖图实现在 ty::TyModule::build_dep_graphsway-core/src/semantic_analysis/module.rsimpl ty::TyModule { /// Analyzes the given parsed module to produce a dependency graph. pub fn build_dep_graph( handler: Handler, parsed: ParseModule, ) - ResultModuleDepGraph, ErrorEmitted }图中各模块是节点、依赖关系是边通过执行拓扑排序即可计算出正确的模块求值顺序该顺序随后被用于后续各遍pass对 AST 的求值。符号收集Symbol Collection接下来收集 AST 中出现的符号实现在 ty::TyAstNode::collectsway-core/src/semantic_analysis/ast_node/mod.rsimpl ty::TyAstNode { pub(crate) fn collect( handler: Handler, engines: Engines, ctx: mut SymbolCollectionContext, node: AstNode, ) - Result(), ErrorEmitted }收集的产物是一棵命名空间树其中包含将基于字符串的标识符解析为对应声明链接所需的全部信息。命名空间Namespaces每个 Namespacesway-core/src/semantic_analysis/namespace/namespace.rs 对应一个模块内含与源码作用域对应的词法作用域树。每个 LexicalScopesway-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::OrdMapModuleName, Module, /// Keeps all lexical scopes associated with this module. pub lexical_scopes: VecLexicalScope, ... } /// 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 scopes hierarchy. pub children: VecLexicalScopeId, /// The parent scope associated with this scope. Will be None for a root scope. pub parent: OptionLexicalScopeId, } /// The set of items that exist within some lexical scope via declaration or importing. pub struct Items { /// A map from Idents to their associated parsed declarations. pub(crate) parsed_symbols: ParsedSymbolMap, /// A map from Idents to their associated typed declarations. pub(crate) symbols: SymbolMap, ... }命名空间结构分为三层Namespace含固定存在的init模块与项目根root、Module有序子模块表 词法作用域列表、LexicalScope符号集合 父子层级关系这种设计使名称解析可以高效地沿作用域链向上查找。类型检查Type Checking类型检查阶段利用此前构建的命名空间解析名称确保程序中使用的类型一致且正确。该过程在 ty::TyProgram::type_checksway-core/src/semantic_analysis/program.rs 中完成检查状态被跟踪在 TypeCheckContextsway-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: OptionBuildConfig, ) - ResultSelf, ErrorEmitted }与此同时还会执行单态化monomorphization——一种处理泛型的编译器技术为每一组唯一的泛型类型组合生成特化代码通过避免运行时开销来提升性能。相关实现位于 TypeCheckContext::monomorphizesway-core/src/semantic_analysis/type_check_context.rs 及关联的 TypeBindingsway-core/src/type_system/ast_elements/binding.rs 类型中。控制流图Control Flow Graph得到完整的类型化 AST 后还可以做进一步分析。为此编译器构建控制流图CFG——程序内控制流的表示展示控制如何从一条指令流向另一条指令。CFG 的节点表示基本块basic block有向边表示块间的控制流转移。Sway 使用 CFG 分析返回路径确保所有需要返回值路径都以正确类型返回值它检查方法命名空间与函数命名空间中的每个函数声明验证所有通向函数出口节点的路径返回相同类型此外若函数有返回类型所有路径都必须通向出口节点。实现在 ControlFlowGraphsway-core/src/control_flow_analysis/analyze_return_paths.rs。死代码分析Dead Code Analysis死代码分析识别并消除程序中永远不会执行的代码段帮助提升代码质量、可维护性与性能。死代码分析图DCA graph的工作原理如下节点DCA 图中的节点表示程序内不同的代码段或块可以是函数、循环、条件分支或其他逻辑分组。边节点间的边表示不同代码段间的控制流关系例如一条边可连接条件语句与其真/假两个分支对应的代码块。不可达代码DCA 图识别出因条件语句、循环结构或其他控制流机制而不可达的代码段这些与主控制流断开的段通常就是死代码。实现在 ControlFlowGraph::find_dead_codesway-core/src/control_flow_analysis/dead_code_analysis.rs。DCA 图可通过给forc build传入--dca-graph选项以 DOT 格式输出。中间表示IR生成IR 生成阶段把验证过的、完全类型化的 AST 翻译为中间表示IR。IR 充当高级代码与目标代码生成阶段之间的桥梁让优化更易执行。该过程起始于 compile_programsway-core/src/ir_generation.rspub fn compile_programa( program: ty::TyProgram, include_tests: bool, engines: a Engines, experimental: ExperimentalFlags, ) - ResultContexta, VecCompileError最终产出的 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 被组织为 PassManagersway-ir/src/pass_manager.rs 中不同的 pass 组并在 compile_ast_to_ir_to_asmsway-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, ) - ResultCompiledAsm, ErrorEmitted {这一步的输出是 Fuel VM 汇编代码由 FuelAsmBuildersway-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 SlabEngines编译器中的所有并发 slab 都包含在 Enginessway-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: ArcObservabilityEngine, }当前仓库的Engines还额外包含了obs_engine: ArcObservabilityEngine可观测性引擎见 sway-core/src/obs_engine.rs并提供了te()/de()/pe()/qe()/se()/obs()等访问器以及按program_id清理数据的clear_program方法用于垃圾回收。这个类型会在编译器各处被传递是贯穿全流程的核心上下文。并发 SlabConcurrent Slab编译器在节点内存管理上采用了内存区域memory arena模式不对节点持有直接指针/引用而是使用整数 id之后用该 id 索引到存放某一类节点全部内存的向量中。这种方式简化了编译器的内存管理允许安全并发也便于表示所有树/图结构中常见的循环引用。ConcurrentSlabsway-core/src/concurrent_slab.rs 的实现如下pub(crate) struct ConcurrentSlabT { pub inner: RwLockInnerT, } pub struct InnerT { pub items: VecOptionArcT, pub free_list: Vecusize, }Inner用VecOptionArcT存放节点None表示槽位空闲用free_list记录可复用的空闲槽位配合RwLock实现并发安全。这与 Rust 社区中惯用树的内存管理思路一脉相承通过稳定 id 而非裸指针引用节点既能规避借用检查对自引用结构的限制又能高效处理图中节点间的循环依赖。小结Sway 编译器是一条职责清晰、层次分明的流水线词法分析sway-parse/src/token.rs产出 token 树语法分析sway-core/src/lib.rs 的parse先后产出 CSTLexedProgram与 ASTParseProgram语义分析parsed_to_ast经由模块依赖图、符号收集、命名空间解析、类型检查含单态化、控制流图与死代码分析得到类型化 ASTIR 生成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),仅供参考
返回列表