摘要
EVM portable IR 后端不应该长期从 ProofForge portable IR 直接降到低层 Yul 语法节点。ProofForge 现在已经有ProofForge.Compiler.Yul.AST,LCNF
的 EmitYul 路径和 portable IR EVM 后端最终都会通过 Yul.Printer
渲染这个 AST。这个 AST 很有价值,但它是语法 AST:它描述 Yul object、
block、statement、expression、function 和 literal。
缺少的是 portable IR 和这个语法 AST 之间的一层 target-semantic EVM
plan。
EVM target 的产品流水线应当是:
Lean.Compiler.LCNF → ProofForge.Compiler.LCNF.EmitYul 路径是
Lean-native experimental route,不是产品流水线。它仍可用于研究和等价性实验,
但新的 EVM 工作、CI gates 和文档应遵循上面的 portable-IR → EVM-plan → Yul
pipeline。
本 RFC 定义这层 EVM semantic plan 的边界和迁移路径。它保留现有 Yul AST
作为最终语法层,但不再让 ABI dispatch、storage layout、helper discovery、
event ABI、cross-call ABI 和 metadata 构造全部交织在同一个 lowering 过程里。
动机
当前 EVM portable IR 后端已经通过不断补能力,覆盖了 ABI entrypoint、标量和聚合表达式、storage slot、mapping、array、struct、event、cross-contract call、artifact metadata 和 diagnostic。这个进展也暴露了结构性问题:很多不同职责都挤在同一个 lowering 模块里:- 类型检查与 unsupported-capability diagnostic。
- EVM storage layout 分配。
- ABI selector dispatch 和 calldata guard。
- Return-data encoding。
- Event signature 与 topic/data encoding。
- Helper function discovery 和 emission。
- Cross-contract call 的 calldata/returndata packing。
- Artifact metadata 和 deploy manifest 输入。
- 低层 Yul expression/statement 构造。
当前状态
当前代码已经有真正的 Yul 语法 AST:ProofForge.Compiler.Yul.ASTProofForge.Compiler.Yul.Printer
ProofForge.Backend.Evm.IR.lowerModule : Module -> Except LowerError Yul.ObjectProofForge.Backend.Evm.IR.renderModule : Module -> Except LowerError String
设计目标
- 在分阶段迁移时保持现有生成的 Yul、bytecode、metadata、diagnostic 和 smoke test。
- 让 ABI、storage、helper、event、cross-call 和 metadata 在最终 Yul 语法生成前成为一等计划产物。
- 保留现有
Lean.Compiler.Yul.Object,作为传给Yul.Printer的最终语法 AST。 - 让 unsupported capability 在 validation 或 semantic planning 阶段失败,而不是拖到语法渲染阶段。
- 让未来 optimizer、audit 和 metadata pass 能检查 EVM plan,而不用解析 Yul 文本或反向理解通用 Yul statement。
- 让这个设计保持 target-specific。它不是替代 portable IR 的新全局 IR。
非目标
- 本 RFC 不替换
ProofForge.Compiler.Yul.AST。 - 它不引入第二套 portable IR。
- 它不要求改变面向用户的合约语言。
- 它不要求一次性重写
ProofForge.Backend.Evm.IR。 - 它不定义 Solana、Wasm、Move 或 Psy 后端的 plan 结构;这些目标可以选择不同的 target-plan 层。
模块形态
EVM 后端应逐步拆成:IR.lean 可以在迁移期间继续作为公共入口。最终
IR.lowerModule 应当变为:
renderModule 可以保持:
EVM plan 模型
plan 应该用 EVM 语义描述目标合约,而不是用通用 Yul 语法描述。草案如下:mapValueSlot 表示“这是一个 EVM mapping slot path”,而不是“调用两次 __proof_forge_map_slot”。具体使用 helper 还是 inline Yul 形式,由 ToYul pass 决定。
计划边界
Validation
Validation 负责:- 类型一致性。
- target-specific supported/unsupported capability 检查。
- unsupported shape 的显式 diagnostic。
- ABI-facing 类型限制。
- Storage path 合法性。
Semantic lowering
Lower pass 负责:
- 分配 storage layout。
- 把 portable effect 转成 EVM plan statement。
- 解析 helper requirement。
- 构建带 selector、calldata guard 和 return plan 的 entrypoint plan。
- 构建 event 和 crosscall plan。
- 记录 capability id 和 metadata 输入。
Yul generation
ToYul pass 负责:
- 把 plan statement 转成
Lean.Compiler.Yul.Statement。 - 根据
HelperSet发射 helper function。 - 发射 dispatcher
switch。 - 为 calldata、returndata、event、hash 和 call 生成 memory layout。
- 生成最终
Lean.Compiler.Yul.Object。
ToYul,就应当已经是合法的 EVM 计划。
Metadata
metadata pass 应该消费ModulePlan,而不是从渲染后的 Yul 里重新发现事实。这对这些字段尤其重要:
abi.entrypointsabi.events- capability list
- constructor metadata
- bytecode/Yul hash
- deploy manifest 字段
为什么比 portable IR 直接到 Yul AST 更好
现有低层 Yul AST 仍然必要,但它对后端架构来说太低层。semantic EVM plan 给 ProofForge 带来:- 稳定的 EVM 语义 review 面。
- 在打印 Yul 之前测试 storage layout 和 ABI plan 的位置。
- artifact metadata 生成的干净输入。
- helper discovery 的干净位置。
- 未来 optimizer 可以理解 EVM 概念,而不是原始 Yul 语法。
- 更清晰的路径,用来比较旧 SDK/LCNF EVM 路径和 portable IR EVM 路径的等价性。
迁移计划
迁移应分阶段,并保持行为不变。Stage 1:引入 plan 数据结构
新增ProofForge.Backend.Evm.Plan,先为最窄表面提供 semantic structure 和构造器:
- scalar value
- storage scalar read/write
- map value/presence slot
- entrypoint selector metadata
- helper requirement
Stage 2:迁移 storage layout planning
把 slot assignment 和 storage-path planning 从IR.lean 移到 Plan/Lower。第一个验收目标应该是 map 和 scalar storage,因为它们已经有强 golden Yul 和 Foundry 原始 slot 验证。
Stage 3:迁移 entrypoint 和 ABI planning
用 plan 表达 dispatcher case、calldata guard、return encoder 和结构化abi.entrypoints metadata。现有 ABI scalar 和 aggregate smoke 应保持字节级稳定,除非有明确的 printer 改动。
Stage 4:迁移 helper discovery
用 plan 中的HelperSet 替代分散的 helper accumulation。ToYul 应从这个集合确定性地发射 helper。
Stage 5:迁移 events 和 crosscalls
在降到 Yul 前,用 plan node 表达 event signature、topic/data field layout 和 crosscall ABI packing。这是最复杂的表面,应在 storage 和 ABI entrypoint 证明模式可行后再迁移。Stage 6:增加 plan-level tests
添加直接检查ModulePlan 的测试:
- storage slot formula
- selected helper
- entrypoint selector 和 ABI word count
- event signature 和 topic encoding
- unsupported diagnostic
验收标准
一个迁移 slice 只有在满足以下条件时才算接受:- 现有 golden Yul 仍然可复现,或者 diff 是有意且已 review 的。
solc --strict-assembly仍然接受生成的 Yul。- 对应 Foundry smoke 仍然验证运行时行为。
proof-forge-artifact.jsonmetadata 仍然通过验证。- unsupported node 仍然有显式 diagnostic。
- 对迁移的 capability,可以用 focused Lean test 或 smoke 检查 plan 数据。
开放问题
- plan tests 应该直接用 Lean structure equality,还是渲染稳定的
.evm-plan.jsonsnapshot 方便 review? HelperSet应该是带确定性顺序的 inductive set,还是从 plan traversal 计算出的 summary?- storage layout 应该在全部 type validation 之前计算,还是只在 validation 得到 typed module 后计算?
- 现有 LCNF
EmitYul路径是否要共享同一层 semantic EVM plan?如果要,共享到什么程度?
初始实现建议
先从 storage planning 开始,不要先动 ABI 或 event。 第一个具体 slice:- 添加
ProofForge.Backend.Evm.Plan。 - 建模
StorageLayout、StorageSlotPlan和HelperSet。 - 把 scalar storage 和 map storage path 降到 plan node。
- 把这些 plan node 转成现有 Yul AST,并保持生成的 Yul 不变。
- 为 nested map value 和 presence slot 添加 focused plan test。
- 保持
just evm-smoke map、just evm-smoke typed-map、just evm-diagnostics和just evm-coverage作为验证门禁。