当前位置: 首页 > news >正文

基于虚拟机源码分析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:函数结束,返回

相关文章:

  • C++_跨平台编译_cmakefile中_添加内容
  • 《QT实用小工具·四十》显示帧率的控件
  • portaudio 怎么调用获取输出流
  • 信息系统项目管理师论文考察范围预测
  • 《生成式AI导论》学习笔记
  • 基于vscode的c++开发(Windows)
  • 【二】【SQL】去重表数据及分组聚合查询
  • 动态获取权限,文件管理器选择文件,I/O流
  • 在autodl搭建stable-diffusion-webui+sadTalker
  • Python爬虫实战入门:爬取360模拟翻译(仅实验)
  • 低功耗设计——门控时钟
  • 2024-02-23(Spark)
  • 【sklearn】模型融合_投票法
  • ASP.NET MVC会计教学管理端项目系列--Log4Net日志组件
  • AD域帐户密码过期,终端802.1x认证自动重连导致AD账号被锁,员工无法上网、办公怎么办?
  • iOS小技能:跳转到地图APP(navForIOSMap)
  • Unreal Engine源代码下载方法
  • JavaScript简识
  • 【正点原子STM32连载】第五十一章 视频播放器实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
  • 下一个(全)排列
  • 读懂MEV链上套利操作
  • Mac 电脑下载 AppStore 中的 ipa 软件包详细流程
  • Pycharm Runtime Error R6034解决方法
  • LQ0100 人物相关性分析【文本处理】
  • 虚拟形象制作该如何进行?带你深入了解虚拟形象制作
  • 一文读懂TDengine3.0中的事务机制
  • Java入门刷题篇 基础语法->>基本数据类型->>Java1类型转换
  • 入门学python(三)
  • 湖北住建厅七大员报考条件和取证流程
  • 字节码指令 || JVM类加载与字节码技术
  • 哪个开源工作流引擎更好?Flowable or Camunda ?
  • 牛客网专项练习30天Pytnon篇第17天