matter-labs / era-compiler-llvm

ZKsync fork of the LLVM framework.
Other
33 stars 16 forks source link

[EVM] Stackification cleans up stack too eagerly #728

Open akiramenai opened 3 weeks ago

akiramenai commented 3 weeks ago

It looks like stackification attempts to clean up the stack before doing computations. Consider the following example:

define i256 @no_manipulations_needed_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind {
; CHECK-LABEL: no_manipulations_needed_no_junk:
; CHECK:       ; %bb.0:
; CHECK-NEXT:    JUMPDEST
; CHECK-NEXT:    SWAP1
; CHECK-NEXT:    SWAP2
; CHECK-NEXT:    POP
; CHECK-NEXT:    SUB
; CHECK-NEXT:    SWAP1
; CHECK-NEXT:    JUMP
  %x1 = sub i256 %a1, %a2
  ret i256 %x1
}

Instead of moving %a3 on top of the stack, popping it, and then subtracting, subtracting and then cleaning up would result in better assembly:

JUMPDEST
SUB
SWAP1
POP
SWAP1
JUMP

Other examples showing the same pattern can be found in test/CodeGen/EVM/stack-ops.ll and test/CodeGen/EVM/stack-ops-commutable.ll (e.g. same_arg_alive_with_junk).

akiramenai commented 3 weeks ago

Note that sometimes, eager cleanup is preferable:

define i256 @swap_first_no_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) nounwind {
; CHECK-LABEL: swap_first_no_junk:
; CHECK:       ; %bb.0:
; CHECK-NEXT:    JUMPDEST
; CHECK-NEXT:    SWAP2
; CHECK-NEXT:    POP
; CHECK-NEXT:    POP
; CHECK-NEXT:    SUB
; CHECK-NEXT:    SWAP1
; CHECK-NEXT:    JUMP
  %x1 = sub i256 %a1, %a4
  ret i256 %x1
}

Here it's better to cleanup first and then subtract.

akiramenai commented 2 weeks ago

The best way to reproduce is

declare i256 @foo(i256, i256, i256, i256)
define i256 @eager_cleanup(i256 %a1, i256 %a2, i256 %a3, i256 %a4, i256 %junk) {
  %result =  call i256 @foo(i256 %a1, i256 %a2, i256 %a3, i256 %a4)
  ret i256 %result
}