要求一致对齐的 data stack 还允许一些遍历控制流图的算法(包括代码验证和编译)在连接处中断循环,从而再次防止二次路径爆炸。 当遍历到达先前访问过的 PC 时,它要么位于循环的开始处,要么位于函数的入口处。 由于该 PC 处的堆栈高度是恒定的,因此我们知道循环不会增长堆栈,并且子程序的参数数量将始终相同——可能不需要再次遍历该路径。
2. 将返回地址保存在数据堆栈中。 这种设计经常被寄存器机器使用,包括来自 CDC、IBM、DEC、Intel 和 ARM 的机器。 寄存器主要用于计算,堆栈维护返回地址、参数和局部变量的调用帧。 在 EVM 上,没有用于计算的寄存器,因此将堆栈用于这两个目的可能会导致我们在下面看到的这种效率低下。 Pascal p 代码确实使用了这种设计,但它是具有专用寄存器的复杂调用约定的一部分。
我们更喜欢专用的返回堆栈。
它保持了计算和控制流之间的清晰分离:
数据堆栈没有易受攻击的返回地址,并且
无法覆盖返回堆栈。
它可以提高效率:
它使用本机算术而不是 256 位 EVM 指令来处理返回地址,
不占用 data stack 插槽来处理返回地址,并且
需要在堆栈上较少移动 256 位数据。
效率
我们在此说明与使用 JUMP 相比,如何使用子程序指令来降低普通和优化子程序调用的复杂性和 Gas 成本。
简单子程序调用
考虑一下一个相当小的子程序的这些示例,包括调用它的代码。
子程序调用,使用 RJUMPSUB:
SQUARE:
dup1 ; 3 gas
mul ; 5 gas
returnsub ; 3 gas
CALL_SQUARE:
push 0x02 ; 3 gas
rjumpsub SQUARE ; 5 gas
总 Gas:19
子程序调用,使用 JUMP:
SQUARE:
jumpdest ; 1 gas
swap1 ; 3 gas
dup1 ; 3 gas
mul ; 5 gas
swap1 ; 3 gas
jump ; 8 gas
CALL_SQUARE:
jumpdest ; 1 gas
push 0x02 ; 3 gas
push RTN_CALL: ; 3 gas
push SQUARE ; 3 gas
jump ; 8 gas
RTN_CALL:
jumpdest ; 1 gas
dup1 ; 3 gas
mul ; 5 gas
returnsub ; 3 gas
CALL_SQUARE:
push 0x02 ; 3 gas
rjump SQUARE ; 3 gas
总计:17 Gas
尾调用优化,使用 JUMP:
SQUARE:
jumpdest ; 1 gas
swap1 ; 3 gas
dup1 ; 3 gas
mul ; 5 gas
swap2 ; 3 gas
jump ; 8 gas
CALL_SQUARE:
jumpdest ; 1 gas
push 0x02 ; 3 gas
push SQUARE ; 3 gas
jump ; 8 gas
# 如果代码有效则返回 true
def validate_code(code: bytes, pc: int, sp: int, bp: int) -> boolean:
continuations = []
do
while pc < len(code):
opcode = code[pc]
if !is_valid(opcode):
return false
if is_terminator(opcode):
return true
# check stack height and return if we have been here before
# 检查堆栈高度,如果之前来过这里则返回
stack_height = sp - bp
if stack_height > 1024
return false
if pos in stack_heights:
if stack_height != stack_heights[pos]:
return false
return true
else:
stack_heights[pos] = stack_height
if opcode == RJUMP:
# reset pc to destination of jump
# 将 pc 重置为跳转的目标
jumpdest = immediate_data(pc)
pc += jumpdest
if !is_valid_jumpdest(pc)
return false
elif opcode == RJUMPI:
jumpdest = pc + immediate_data(pc)
if !is_valid_jumpdest(pc)
return false
# continue true side of conditional later
# 稍后继续条件的真侧
continations.push((jumpdest, sp, bp))
# continue false side of conditional now
# 现在继续条件的假侧
elif opcode == RJUMPSUB:
# will enter subroutine at destination
# 将在目标处进入子程序
bp = sp
# push return address and reset pc to destination
# 推送返回地址并将 pc 重置为目标
jumpdest = pc + immediate_data(pc)
if !is_valid_jumpdest(pc)
return false
push(return_stack, pc + 3)
pc = jumpdest
continue
elif opcode == RETURNSUB:
# will return to subroutine at destination
# 将返回到目标处的子程序
bp = sp
# pop return address and check for preceding call
# 弹出返回地址并检查前面的调用
pc = pop(return_stack)
if code[pc - 3] != RJUMPSUB:
return false
# apply instructions to stack
# 将指令应用于堆栈
sp -= removed_items(opcode)
if sp < 0
return false
sp += added_items(opcode)
# Skip opcode and immediate data
# 跳过操作码和立即数据
pc += 1 + immediate_size(opcode)
while (pc, sp, bp) = continuations.pop()
return true