(func $SHL
    (local $sp i32)
    (local $x1 i64)
    (local $x2 i64)
    (local $x3 i64)
    (local $x4 i64)
    (local $y1 i64)
    (local $y2 i64)
    (local $y3 i64)
    (local $y4 i64)

    (local $z1 i64)
    (local $z2 i64)
    (local $z3 i64)
    (local $z4 i64)
    (local $t i64)
    (local $r i64)

    ;; load args from the stack
    (local.set $x1 (i64.load (i32.add (global.get $sp) (i32.const 24))))
    (local.set $x2 (i64.load (i32.add (global.get $sp) (i32.const 16))))
    (local.set $x3 (i64.load (i32.add (global.get $sp) (i32.const 8))))
    (local.set $x4 (i64.load (global.get $sp)))

    (local.set $sp (i32.sub (global.get $sp) (i32.const 32)))

    (local.set $y1 (i64.load (i32.add (local.get $sp) (i32.const 24))))
    (local.set $y2 (i64.load (i32.add (local.get $sp) (i32.const 16))))
    (local.set $y3 (i64.load (i32.add (local.get $sp) (i32.const 8))))
    (local.set $y4 (i64.load (local.get $sp)))

    (block
        (if (i32.and (i64.eqz (local.get $x1)) (i64.eqz (local.get $x2))) (then
            (if (i64.eqz (local.get $x3)) (then
                (if (i64.eqz (local.get $x4))
                    (then
                        (local.set $z1 (local.get $y1))
                        (local.set $z2 (local.get $y2))
                        (local.set $z3 (local.get $y3))
                        (local.set $z4 (local.get $y4))
                    )
                    (else
                        (if (i64.lt_u (local.get $x4) (i64.const 256)) (then
                            (if (i64.ge_u (local.get $x4) (i64.const 128)) (then
                                (local.set $y1 (local.get $y3))
                                (local.set $y2 (local.get $y4))
                                (local.set $y3 (i64.const 0))
                                (local.set $y4 (i64.const 0))
                                (local.set $x4 (i64.sub (local.get $x4) (i64.const 128)))
                            ))
                            (if (i64.ge_u (local.get $x4) (i64.const 64)) (then
                                (local.set $y1 (local.get $y2))
                                (local.set $y2 (local.get $y3))
                                (local.set $y3 (local.get $y4))
                                (local.set $y4 (i64.const 0))
                                (local.set $x4 (i64.sub (local.get $x4) (i64.const 64)))
                            ))
                            (nop)
                            (block
                                (local.set $t (call $shl_single (local.get $y4) (local.get $x4)))
                                (local.set $z4 (global.get $global_))

                            )
                            (block
                                (local.set $r (call $shl_single (local.get $y3) (local.get $x4)))
                                (local.set $z3 (global.get $global_))

                            )
                            (local.set $z3 (i64.or (local.get $z3) (local.get $t)))
                            (block
                                (local.set $t (call $shl_single (local.get $y2) (local.get $x4)))
                                (local.set $z2 (global.get $global_))

                            )
                            (local.set $z2 (i64.or (local.get $z2) (local.get $r)))
                            (block
                                (local.set $r (call $shl_single (local.get $y1) (local.get $x4)))
                                (local.set $z1 (global.get $global_))

                            )
                            (local.set $z1 (i64.or (local.get $z1) (local.get $t)))
                        ))
                    )
                )
            ))
        ))

    )
    (i64.store (i32.add (local.get $sp) (i32.const 24)) (local.get $z1))
    (i64.store (i32.add (local.get $sp) (i32.const 16)) (local.get $z2))
    (i64.store (i32.add (local.get $sp) (i32.const  8)) (local.get $z3))
    (i64.store          (local.get $sp)                 (local.get $z4))
)

(func $shl_single
    (param $a i64)
    (param $amount i64)
    (result i64)
    (local $x i64)
    (local $y i64)
    (block
        (local.set $x (i64.shr_u (local.get $a) (i64.sub (i64.const 64) (local.get $amount))))
        (local.set $y (i64.shl (local.get $a) (local.get $amount)))

    )
    (global.set $global_ (local.get $y))
    (local.get $x)
)
