基于虚拟机源码分析move合约(三):整数的位运算和强制转换
Move合约:
module test_05::test_move{
public fun test_integer(){
let i:u64 = 1;
let j = i&0;
let k = i|0;
let m = i^0;
let n = i<<2;
let s = i>>2;
let cast_1 = (i as u8)+1;
let cast_2 = (i as u64)+1;
let cast_3 = (i as u128)+1;
}
}
这个合约演示了整数的5种位运算和强制转换
下面我们通过下面的命令执行反编译:
move disassemble --name test_move
我们通过反编译可以得到如下指令:
// Move bytecode v5
module f2.test_move {
public test_integer() {
L0: cast_1: u8
L1: cast_2: u64
L2: cast_3: u128
L3: i: u64
L4: j: u64
L5: k: u64
L6: m: u64
L7: n: u64
L8: s: u64
B0:
0: LdU64(1)
1: StLoc[3](i: u64)
2: CopyLoc[3](i: u64)
3: LdU64(0)
4: BitAnd
5: Pop
6: CopyLoc[3](i: u64)
7: LdU64(0)
8: BitOr
9: Pop
10: CopyLoc[3](i: u64)
11: LdU64(0)
12: Xor
13: Pop
14: CopyLoc[3](i: u64)
15: LdU8(2)
16: Shl
17: Pop
18: CopyLoc[3](i: u64)
19: LdU8(2)
20: Shr
21: Pop
22: CopyLoc[3](i: u64)
23: CastU8
24: LdU8(1)
25: Add
26: Pop
27: CopyLoc[3](i: u64)
28: CastU64
29: LdU64(1)
30: Add
31: Pop
32: MoveLoc[3](i: u64)
33: CastU128
34: LdU128(1)
35: Add
36: Pop
37: Ret
}
}
LdU64(1): 加载一个整数1到栈上
StLoc[3](i: u64):从栈上弹出1,然后存入寄存器3
CopyLoc[3](i: u64):从寄存器3复制一个数据,存入栈上
LdU64(0):加载数据0到栈上
BitAnd
这个操作是执行与操作的汇编指令,下面我们看看实际代码实现:
Bytecode::BitAnd => {
gas_meter.charge_simple_instr(S::BitAnd)?;
interpreter.binop_int(IntegerValue::bit_and)?
}
实际使用了binop_int进行二元操作,传入的是操作函数,这里传入的是bit_and函数:
fn binop_int<F>(&mut self, f: F) -> PartialVMResult<()>
where
F: FnOnce(IntegerValue, IntegerValue) -> PartialVMResult<IntegerValue>,
{
self.binop(|lhs, rhs| {
Ok(match f(lhs, rhs)? {
IntegerValue::U8(x) => Value::u8(x),
IntegerValue::U64(x) => Value::u64(x),
IntegerValue::U128(x) => Value::u128(x),
})
})
}
fn binop<F, T>(&mut self, f: F) -> PartialVMResult<()>
where
Value: VMValueCast<T>,
F: FnOnce(T, T) -> PartialVMResult<Value>,
{
let rhs = self.operand_stack.pop_as::<T>()?;
let lhs = self.operand_stack.pop_as::<T>()?;
let result = f(lhs, rhs)?;
self.operand_stack.push(result)
}
pub fn bit_and(self, other: Self) -> PartialVMResult<Self> {
use IntegerValue::*;
Ok(match (self, other) {
(U8(l), U8(r)) => IntegerValue::U8(l & r),
(U64(l), U64(r)) => IntegerValue::U64(l & r),
(U128(l), U128(r)) => IntegerValue::U128(l & r),
(l, r) => {
let msg = format!("Cannot bit_and {:?} and {:?}", l, r);
return Err(PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR).with_message(msg));
}
})
}
可以看到分别针对u8、u64和u128进行与运算,如果类型不匹配(比如u8+u64),则会报错。
可以看到,最终会从栈上弹出两个值,分别是0和10,然后执行操作函数,结果会存入栈上。
Pop:与操作的结果没有被继续使用,因此生命周期结束,被从栈上删除
CopyLoc[3](i: u64):从寄存器3复制一个数据,存入栈上
LdU64(0):加载数据0到栈上
BitOr
这个操作是执行或操作的汇编指令,下面我们看看实际代码实现:
Bytecode::BitOr => {
gas_meter.charge_simple_instr(S::BitOr)?;
interpreter.binop_int(IntegerValue::bit_or)?
}
实际和与操作类似,都是使用了binop_int进行二元操作,区别是传入的操作函数是bit_or:
pub fn bit_or(self, other: Self) -> PartialVMResult<Self> {
use IntegerValue::*;
Ok(match (self, other) {
(U8(l), U8(r)) => IntegerValue::U8(l | r),
(U64(l), U64(r)) => IntegerValue::U64(l | r),
(U128(l), U128(r)) => IntegerValue::U128(l | r),
(l, r) => {
let msg = format!("Cannot bit_or {:?} and {:?}", l, r);
return Err(PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR).with_message(msg));
}
})
}
或操作也是会按数据类型进行分别计算,如果不匹配就会报错。
最终或操作的结果也会存入栈上。
Pop:或操作的结果没有被继续使用,因此生命周期结束,被从栈上删除
CopyLoc[3](i: u64):从寄存器3复制一个数据,存入栈上
LdU64(0):加载数据0到栈上
Xor
这个操作是执行异或操作的汇编指令,下面我们看看实际代码实现:
Bytecode::Xor => {
gas_meter.charge_simple_instr(S::Xor)?;
interpreter.binop_int(IntegerValue::bit_xor)?
}
实际和与操作类似,都是使用了binop_int进行二元操作,区别是传入的操作函数是bit_xor:
pub fn bit_xor(self, other: Self) -> PartialVMResult<Self> {
use IntegerValue::*;
Ok(match (self, other) {
(U8(l), U8(r)) => IntegerValue::U8(l ^ r),
(U64(l), U64(r)) => IntegerValue::U64(l ^ r),
(U128(l), U128(r)) => IntegerValue::U128(l ^ r),
(l, r) => {
let msg = format!("Cannot bit_xor {:?} and {:?}", l, r);
return Err(PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR).with_message(msg));
}
})
}
异或操作也是会按数据类型进行分别计算,如果不匹配就会报错。
最终或操作的结果也会存入栈上。
Pop:异或操作的结果没有被继续使用,因此生命周期结束,被从栈上删除
CopyLoc[3](i: u64):从寄存器3复制一个数据,存入栈上
LdU8(2):加载数据2到栈上,这里可以看到2是U8类型的而不是U64,猜测是被编译器优化过了
Shl
这个操作是执行左移操作的汇编指令,下面我们看看实际代码实现:
Bytecode::Shl => {
gas_meter.charge_simple_instr(S::Shl)?;
let rhs = interpreter.operand_stack.pop_as::<u8>()?;
let lhs = interpreter.operand_stack.pop_as::<IntegerValue>()?;
interpreter.operand_stack.push(lhs.shl_checked(rhs)?.into_value())?;
}
首先从栈上弹出两个数据,然后执行shl_checked这个函数:
pub fn shl_checked(self, n_bits: u8) -> PartialVMResult<Self> {
use IntegerValue::*;
Ok(match self {
U8(x) => {
if n_bits >= 8 {
return Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR));
}
IntegerValue::U8(x << n_bits)
}
U64(x) => {
if n_bits >= 64 {
return Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR));
}
IntegerValue::U64(x << n_bits)
}
U128(x) => {
if n_bits >= 128 {
return Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR));
}
IntegerValue::U128(x << n_bits)
}
})
}
针对不同类型的整数,分别会校验范围,然后进行左移操作。
最后把执行结果压入栈
Pop:左移操作的结果没有被继续使用,因此生命周期结束,被从栈上删除
CopyLoc[3](i: u64):从寄存器3复制一个数据,存入栈上
LdU8(2):加载数据2到栈上
Shr
这个操作是执行右移操作的汇编指令,下面我们看看实际代码实现:
Bytecode::Shr => {
gas_meter.charge_simple_instr(S::Shr)?;
let rhs = interpreter.operand_stack.pop_as::<u8>()?;
let lhs = interpreter.operand_stack.pop_as::<IntegerValue>()?;
interpreter.operand_stack.push(lhs.shr_checked(rhs)?.into_value())?;
}
首先从栈上弹出两个数据,然后执行shr_checked这个函数:
pub fn shr_checked(self, n_bits: u8) -> PartialVMResult<Self> {
use IntegerValue::*;
Ok(match self {
U8(x) => {
if n_bits >= 8 {
return Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR));
}
IntegerValue::U8(x >> n_bits)
}
U64(x) => {
if n_bits >= 64 {
return Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR));
}
IntegerValue::U64(x >> n_bits)
}
U128(x) => {
if n_bits >= 128 {
return Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR));
}
IntegerValue::U128(x >> n_bits)
}
})
}
针对不同类型的整数,分别会校验范围,然后进行右移操作。
最后把执行结果压入栈
Pop:右移操作的结果没有被继续使用,因此生命周期结束,被从栈上删除
CopyLoc[3](i: u64):从寄存器3复制一个数据,存入栈上
CastU8
这个是强制转换成U8类型的汇编指令,下面看下具体代码:
Bytecode::CastU8 => {
gas_meter.charge_simple_instr(S::CastU8)?;
let integer_value = interpreter.operand_stack.pop_as::<IntegerValue>()?;
interpreter.operand_stack.push(Value::u8(integer_value.cast_u8()?))?;
}
首先将栈上的数据弹出,也就是上面指令复制的那个数据,然后将调用这个数据的cast_u8()方法进行强制转换,然后重新压入栈。
pub fn cast_u8(self) -> PartialVMResult<u8> {
use IntegerValue::*;
match self {
U8(x) => Ok(x),
U64(x) => {
if x > (std::u8::MAX as u64) {
Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR)
.with_message(format!("Cannot cast u64({}) to u8", x)))
} else {
Ok(x as u8)
}
}
U128(x) => {
if x > (std::u8::MAX as u128) {
Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR)
.with_message(format!("Cannot cast u128({}) to u8", x)))
} else {
Ok(x as u8)
}
}
}
}
可以看到,强制转换也是分别进行计算,会校验范围,最后用到的是rust的as操作。
LdU8(1):加载数据1到栈上,这里用的U8,因为上面已经强制转换成了U8,要保持数据类型一致
Add:执行加法操作,具体可以看上一篇文章
Pop:因为生命周期结束,所以从栈上删除
CopyLoc[3](i: u64):从寄存器3复制一个数据,存入栈上
CastU64
这个是强制转换成U64类型的汇编指令,下面看下具体代码:
Bytecode::CastU64 => {
gas_meter.charge_simple_instr(S::CastU64)?;
let integer_value = interpreter.operand_stack.pop_as::<IntegerValue>()?;
interpreter.operand_stack.push(Value::u64(integer_value.cast_u64()?))?;
}
首先将栈上的数据弹出,也就是上面指令复制的那个数据,然后将调用这个数据的cast_u64()方法进行强制转换,然后重新压入栈。
pub fn cast_u64(self) -> PartialVMResult<u64> {
use IntegerValue::*;
match self {
U8(x) => Ok(x as u64),
U64(x) => Ok(x),
U128(x) => {
if x > (std::u64::MAX as u128) {
Err(PartialVMError::new(StatusCode::ARITHMETIC_ERROR)
.with_message(format!("Cannot cast u128({}) to u64", x)))
} else {
Ok(x as u64)
}
}
}
}
主要针对U128进行处理
LdU64(1):加载数据1到栈上,这里用的U64,因为上面已经强制转换成了U64,要保持数据类型一致
Add:执行加法操作,具体可以看上一篇文章
Pop:因为生命周期结束,所以从栈上删除
MoveLoc[3](i: u64):从寄存器3删除一个数据,存入栈上
CastU128
这个是强制转换成U64类型的汇编指令,下面看下具体代码:
Bytecode::CastU128 => {
gas_meter.charge_simple_instr(S::CastU128)?;
let integer_value = interpreter.operand_stack.pop_as::<IntegerValue>()?;
interpreter.operand_stack.push(Value::u128(integer_value.cast_u128()?))?;
}
首先将栈上的数据弹出,也就是上面指令复制的那个数据,然后将调用这个数据的cast_u128()方法进行强制转换,然后重新压入栈。
pub fn cast_u128(self) -> PartialVMResult<u128> {
use IntegerValue::*;
Ok(match self {
U8(x) => x as u128,
U64(x) => x as u128,
U128(x) => x,
})
}
直接使用rust的as操作,非常简单
LdU128(1):加载数据1到栈上,这里用的U128,因为上面已经强制转换成了U128,要保持数据类型一致
Add:执行加法操作,具体可以看上一篇文章
Pop:因为生命周期结束,所以从栈上删除
Ret:函数结束,返回