以太坊虚拟机介绍4-按位运算指令

以太坊虚拟机按位运算指令

EVM定义了8条按位运算指令,见下表:

按位运算指令 操作码 说明
AND 0x16 按位与
OR 0x17 按位或
XOR 0x18 按位异或
NOT 0x19 按位取反
BYTE 0x1A 取第n个字节
SHL 0x1B 左移
SHR 0x1C 逻辑右移
SAR 0x1D 算术右移

下面是按位运算指令的操作码分布图:
以太坊虚拟机介绍4-按位运算指令_第1张图片

AND、OR、XOR、NOT

AND、OR、XOR指令从栈顶弹出两个元素,进行按位运算,然后把结果推入栈顶。以AND指令为例,下面是它的操作示意图:
以太坊虚拟机介绍4-按位运算指令_第2张图片

NOT指令将栈元素按位取反,下面是它的操作示意图:
以太坊虚拟机介绍4-按位运算指令_第3张图片

这四条指令分别与Solidity语言里的&|^~运算符直接对应,下面的Solidity代码演示了这四条指令的具体应用(读者可以运行solc --asm --opcodes bitwise_demo1.sol命令观察编译器生成的字节码):

// bitwise_demo1.sol 
pragma solidity ^0.4.24;

contract C {

    function test() public pure {
        int s1; int s2; int s3;
        uint u1; uint u2; uint u3;

        s3 = s1 & s2; // ADD
        s3 = s1 | s2; // OR
        s3 = s1 ^ s2; // XOR
        s3 = ~ s1;    // NOT

        u3 = u1 & u2; // ADD
        u3 = u1 | u2; // OR
        u3 = u1 ^ u2; // XOR
        u3 = ~ u1;    // NOT
    }

}

BYTE

BYTE指令先后从栈顶弹出n和x,取x的第n个字节并推入栈顶。由于EVM的字长是32个字节,所以n在[0, 31]区间内才有意义,否则BYTE的运算结果就是0。另外,字节是从左到右数的,因此第0个字节占据字的最高位8个比特。以n=1为例,下面是BYTE指令操作示意图:

以太坊虚拟机介绍4-按位运算指令_第4张图片

读者可以通过下面的Solidity代码观察BYTE指令的用法:

// byte_demo2.sol 
pragma solidity ^0.4.24;

contract C {

    function test() public pure {
        bytes32 a;
        bytes1 b = a[31]; // ... BYTE ...
    }

}

SHL、SHR、SAR

这三条位移指令是由EIP-145引入的,从Constantinople虚拟机开始支持。这三条指令都是先后从栈顶弹出两个数n和x,其中x是要进行位移操作顶数,n是位移比特数,然后把结果推入栈顶。以左移指令SHL为例,下面是它的操作示意图:
以太坊虚拟机介绍4-按位运算指令_第5张图片

SHR和SAR的区别在于,前者执行逻辑右移(空缺补0),后者执行算术右移(空缺补符号位)。下表总结了这三条位移指令对于操作数的解释,以及计算结果(这里^表示指数运算):

指令 x n 结果
SHL unsigned unsigned (arg2 * 2^arg1) mod 2^256
SHR unsigned unsigned floor(arg2 / 2^arg1)
SAR signed unsigned floor(arg2 / 2^arg1)

Solidity语言提供了<<>>运算符,下表总结了这两个运算符的含义(这里**表示指数运算):

运算符 解释
x >> n x * 2**y
x << n x / 2**y

在Constantinople之前,位移运算符使用EXP、MUL、DIV、SDIV等指令实现;从Constantinople开始,位移运算符可以使用位移指令实现。不过请读者注意,<<运算符可以直接编译成SHL指令,但是由于取整方式不同,所以>>运算符并不能直接编译成SAR指令,详见EIP-145和Solidity文档。读者可以通过下面的Solidity代码观察位移指令的用法(可以通过--evm-version选项告诉Solidity编译器目标EVM版本,例如solc --asm --opcodes --evm-version constantinople bitwise_demo2.sol,如不指定,默认是byzantium):

// bitwise_demo2.sol 
pragma solidity ^0.4.24;

contract C {

    function test() public pure {
        int s1; int s2;
        uint u1; uint u2;
        uint n;

        u2 = u1 << n; // SHL
        s2 = s1 << n; // SHL
        u2 = u1 >> n; // SHR
        s2 = s1 >> n; // EXP、SDIV
        //s2 = s1 >>> n; // SHR?
    }

}

总结

本文介绍了EVM按位运算指令,下一篇文章将介绍EVM比较操作指令。如果大家对编程语言虚拟机有更多的兴趣,请关注我写的《自己动手写Java虚拟机》,以及马上将要出版的《自己动手实现Lua:虚拟机、编译器、标准库》。

你可能感兴趣的:(EVM)