高级语言里一个函数有签名、有边界、有清晰的进出规则;翻到EVM字节码这一层,这些全消失了。所谓”子程序”只是一串被 JUMP 抵达的指令,谁负责压参数、返回值放哪个槽、局部变量划在内存哪一段,全靠编译器之间的君子协定,字节码本身不携带任何声明。EIP-4573在2021年12月想把”过程”(procedure)这个词的实体真正放进指令集:命名的、有固定接口、只能在初始化时被验证过的代码块。提案由Greg Colvin执笔,状态停在Stagnant,是EOF家族里偏 compiler 视角的一件展品。
五条指令的分工
提案引入五条指令构成完整闭环:ENTERPROC 声明一个过程的入口和它期望的栈与内存接口,LEAVEPROC 标记代码段结束,CALLPROC 按过程编号跳入(编号在代码验证阶段解析成地址),RETURNPROC 带着约定的返回槽离开,FRAMEADDRESS 则回答”我这个调用帧的内存起点在哪”——配合栈式寻址,过程的局部变量不再需要编译器手工排布整块内存。作者特意借用了Intel的术语分工:subroutine 只是”知道回家的路”的跳转,那由同系列的EIP-2315负责;procedure 是更高一层的概念,带接口声明、带自动分配的调用帧。接口校验全部前置到合约初始化时,为的是守住EIP-3779立下的安全承诺:合法程序除非Gas耗尽或递归栈溢出,绝不会以异常姿态停住。
为什么它总是要等一堆前提
4573的Requires一栏挂着五个邻居:EIP-2315(子程序与返回栈)、EIP-3540(EOF容器)、EIP-3670(代码验证)、EIP-3779(安全属性)、EIP-4200(静态跳转)。这几乎是EIP生态里最典型的”结构性提案困境”:它想要的每一分好处——接口检查、帧管理、静态解析——都建立在”字节码有结构”这个前提上,而这个前提本身(EOF)多年进不了主网。2023年EIP-2315被正式Withdrawn之后,4573的根更加悬空;随着EOF系列整体转入Stagnant,过程指令自然也一起搁了浅。
它想解决的问题并没有消失
今天编译器仍然用纯软件约定模拟函数:Solidity在内存上手写函数选择器、自己维护自由内存指针。这套办法能跑,但每次跨合约边界的调试都要靠工具链脑补边界,字节码层面的结构审计做不了。4573留下的真正遗产是提问方式:如果字节码自己声明”这里是一个函数、收三个参数、留一个返回值”,验证器、调试器和形式化工具能多做多少静态工作。
快速问答
问:这跟EVM的”函数选择器”是一回事吗?
答:不是。选择器发生在合约与合约之间(calldata前四字节路由),4573针对的是同一份字节码内部的函数边界。
问:调用帧与调用栈有什么区别?
答:栈是Gas计费的调用深度,帧是每个过程在内存里自动划走的私有地盘;提案里每帧有上限,具体尺寸在规范参数里。
问:为什么作者强调”只能在定义的接口处进入”?
答:禁止 JUMP 跳进过程中段,才能静态保证入口栈高恰好匹配,把一类栈错位漏洞消灭在验证期。
一份没有契约的雇佣
用一个类比收拢这份提案的动机。今天的字节码像一群没有合同的公司间合作:调用方与被调方靠口口相传约定”你给我栈顶放三个数,我给你还回来一个”,任何一方违约,事故要到运行时炸栈才暴露。4573要做的,是把雇佣关系写成合同并交给劳动仲裁——部署时验证器逐条核对:这个过程声明收三个数,所有调用它的 CALLPROC 是否真的准备了三个数;它的局部空间是否与别人的帧互不越界;入口出口是否配对完整。合同有瑕疵,字节码根本发布不了。这就是所谓”把运行时灾难兑换成部署期报错”。它最终没能上船,但船上至今缺的仍是这份合同:每一次编译器栈错位事故的完整复盘,都会绕回”字节码不会自证结构”这个原点。
风险提示:本文为字节码提案史科普,不构成投资建议;请勿把提案语义与任何已部署链的实际行为混同。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。