最近在审计Aptos公链,我主要负责mempool和execute的审计,里面用到了Move虚拟机,所以对Move虚拟机的原理进行了一定的探索。本系列文章尝试通过对照move虚拟机执行阶段的源码,来分析move合约实际的工作原理,如果不了解move语言,可以先阅读官方文档:move book
合约一
module test_05::test_move{
public fun test_local_variable(){
let i = 0;
}
}
这是一个最简单的合约,其中只有一个方法test_local_variable,这个方法只会分配一个本地变量i,下面我们通过下面的命令执行反编译:
move disassemble --name test_move
得到的move汇编指令如下:
// Move bytecode v5
module f2.test_move {
public test_local_variable() {
L0: i: u64
B0:
0: LdU64(0)
1: Pop
2: Ret
}
}
Move虚拟机最终是针对这些汇编指令进行执行。Move虚拟机使用interpreter.rs进行指令的执行,执行的时候主要依赖两个个数据结构:
/// `Interpreter` instances can execute Move functions.
///
/// An `Interpreter` instance is a stand alone execution context for a function.
/// It mimics execution on a single thread, with an call stack and an operand stack.
pub(crate) struct Interpreter {
/// Operand stack, where Move `Value`s are stored for stack operations.
operand_stack: Stack,
/// The stack of active functions.
call_stack: CallStack,
}
operand_stack是一个栈,用来暂存执行指令时的本地数据,call_stack是个列表,用来存放Frame:
struct CallStack(Vec);
这里的Frame是栈帧,执行一次Function会创建一个栈帧,所以当一个Function中调用另一个Function的时候,需要把当前Function的上下文保存起来,call_stack就是用来保存当前Function的。
下面是执行入口代码:
/// Main loop for the execution of a function.
///
/// This function sets up a `Frame` and calls `execute_code_unit` to execute code of the
/// function represented by the frame. Control comes back to this function on return or
/// on call. When that happens the frame is changes to a new one (call) or to the one
/// at the top of the stack (return). If the call stack is empty execution is completed.
// REVIEW: create account will be removed in favor of a native function (no opcode) and
// we can simplify this code quite a bit.
fn execute_main(
&mut self,
loader: &Loader,
data_store: &mut impl DataStore,
gas_meter: &mut impl GasMeter,
extensions: &mut NativeContextExtensions,
function: Arc,
ty_args: Vec,
args: Vec,
) -> VMResult> {
let mut locals = Locals::new(function.local_count());
for (i, value) in args.into_iter().enumerate() {
locals
.store_loc(i, value)
.map_err(|e| self.set_location(e))?;
}
let mut current_frame = Frame::new(function, ty_args, locals);
loop {
let resolver = current_frame.resolver(loader);
let exit_code = current_frame //self
.execute_code(&resolver, self, data_store, gas_meter)
.map_err(|err| self.maybe_core_dump(err, ¤t_frame))?;
match exit_code {
ExitCode::Return => {
if let Some(frame) = self.call_stack.pop() {
current_frame = frame;
current_frame.pc += 1; // advance past the Call instruction in the caller
} else {
return Ok(mem::take(&mut self.operand_stack.0));
}
}
ExitCode::Call(fh_idx) => {
let func = resolver.function_from_handle(fh_idx);
// Charge gas
let module_id = func
.module_id()
.ok_or_else(|| {
PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
.with_message("Failed to get native function module id".to_string())
})
.map_err(|e| set_err_info!(current_frame, e))?;
gas_meter
.charge_call(
module_id,
func.name(),
self.operand_stack
.last_n(func.arg_count())
.map_err(|e| set_err_info!(current_frame, e))?,
)
.map_err(|e| set_err_info!(current_frame, e))?;
if func.is_native() {
self.call_native(
&resolver,
data_store,
gas_meter,
extensions,
func,
vec![],
)?;
current_frame.pc += 1; // advance past the Call instruction in the caller
continue;
}
let frame = self
.make_call_frame(func, vec![])
.map_err(|err| self.maybe_core_dump(err, ¤t_frame))?;
self.call_stack.push(current_frame).map_err(|frame| {
let err = PartialVMError::new(StatusCode::CALL_STACK_OVERFLOW);
let err = set_err_info!(frame, err);
self.maybe_core_dump(err, &frame)
})?;
current_frame = frame;
}
ExitCode::CallGeneric(idx) => {
// TODO(Gas): We should charge gas as we do type substitution...
let ty_args = resolver
.instantiate_generic_function(idx, current_frame.ty_args())
.map_err(|e| set_err_info!(current_frame, e))?;
let func = resolver.function_from_instantiation(idx);
// Charge gas
let module_id = func
.module_id()
.ok_or_else(|| {
PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
.with_message("Failed to get native function module id".to_string())
})
.map_err(|e| set_err_info!(current_frame, e))?;
gas_meter
.charge_call_generic(
module_id,
func.name(),
ty_args.iter().map(|ty| TypeWithLoader { ty, loader }),
self.operand_stack
.last_n(func.arg_count())
.map_err(|e| set_err_info!(current_frame, e))?,
)
.map_err(|e| set_err_info!(current_frame, e))?;
if func.is_native() {
self.call_native(
&resolver, data_store, gas_meter, extensions, func, ty_args,
)?;
current_frame.pc += 1; // advance past the Call instruction in the caller
continue;
}
let frame = self
.make_call_frame(func, ty_args)
.map_err(|err| self.maybe_core_dump(err, ¤t_frame))?;
self.call_stack.push(current_frame).map_err(|frame| {
let err = PartialVMError::new(StatusCode::CALL_STACK_OVERFLOW);
let err = set_err_info!(frame, err);
self.maybe_core_dump(err, &frame)
})?;
current_frame = frame;
}
}
}
}
可以看到在执行的开始,会初始化一个Locals,这个数据结构可以看成是寄存器,用来临时存放从栈上获取的数据。接下来一个for循环会把用户的入参存入寄存器。然后针对当前Function创建一个Frame,然后进入loop循环,通过Frame的execute_code方法执行这个Function,返回的结果有三种:
1. Return
此时走的是第一个分支,这时候有两种情况,一种是call_stack上还有Frame,说明这个方法是被其他方法调用的,此时需要把current_frame更新,然后继续循环,这相当于方法内部调用完其他方法后,继续执行。另外一种就是所有方法都执行完了,这时候需要把operand_stack上的最后一个值当成返回值返回回去。
2. Call
当Function A执行过程中需要调用Function B的时候,在汇编层面其实是Call指令,下面是它的处理:
Bytecode::Call(idx) => {
return Ok(ExitCode::Call(*idx));
}
这个分支会把Function B先解析出来,如果是native方法的话就直接执行,因为native方法是rust语言写的,因此对指令的处理无法使用Move自己定义的那套规则来处理,所以会有一整套native的逻辑进行处理,但本质都是对数据进行处理然后更新到operand_stack上。如果是Move方法,则针对Function B创建一个新的Frame,然后把当前Function A的Frame存到call_stack,最后把current_frame更新为Function B的Frame。然后进入下一个循环,又开始执行:
current_frame.execute_code(&resolver, self, data_store, gas_meter)
这时候执行了Function B。
当Function B执行完后,会返回Return:
Bytecode::Ret => {
gas_meter.charge_simple_instr(S::Ret)?;
return Ok(ExitCode::Return);
}
这时候就会进入上面的分支1,在分支1里发现call_stack还有Function A的Frame,于是把current_frame更新为Function A的Frame,然后进入下一个循环,继续执行Function A剩余的指令
3. CallGeneric
这个分支和分支2是一样的,区别在于调用的是泛型方法,具体过程就不赘述了。
接下来看一下Frame是如何执行指令的:
struct Frame {
pc: u16,
locals: Locals,
function: Arc,
ty_args: Vec,
}
impl Frame {
/// Create a new `Frame` given a `Function` and the function `Locals`.
///
/// The locals must be loaded before calling this.
fn new(function: Arc, ty_args: Vec, locals: Locals) -> Self {
Frame {
pc: 0,
locals,
function,
ty_args,
}
}
/// Execute a Move function until a return or a call opcode is found.
fn execute_code(
&mut self,
resolver: &Resolver,
interpreter: &mut Interpreter,
data_store: &mut impl DataStore,
gas_meter: &mut impl GasMeter,
) -> VMResult {
self.execute_code_impl(resolver, interpreter, data_store, gas_meter)
.map_err(|e| {
e.at_code_offset(self.function.index(), self.pc)
.finish(self.location())
})
}
fn execute_code_impl(
&mut self,
resolver: &Resolver,
interpreter: &mut Interpreter,
data_store: &mut impl DataStore,
gas_meter: &mut impl GasMeter,
) -> PartialVMResult {
use SimpleInstruction as S;
macro_rules! make_ty {
($ty: expr) => {
TypeWithLoader {
ty: $ty,
loader: resolver.loader(),
}
};
}
let code = self.function.code();
loop {
for instruction in &code[self.pc as usize..] {
trace!(
&self.function,
&self.locals,
self.pc,
instruction,
resolver,
interpreter
);
fail_point!("move_vm::interpreter_loop", |_| {
Err(
PartialVMError::new(StatusCode::VERIFIER_INVARIANT_VIOLATION).with_message(
"Injected move_vm::interpreter verifier failure".to_owned(),
),
)
});
match instruction {
Bytecode::Pop => {
gas_meter.charge_simple_instr(S::Pop)?;
interpreter.operand_stack.pop()?;
}
Bytecode::Ret => {
gas_meter.charge_simple_instr(S::Ret)?;
return Ok(ExitCode::Return);
}
Bytecode::BrTrue(offset) => {
gas_meter.charge_simple_instr(S::BrTrue)?;
if interpreter.operand_stack.pop_as::()? {
self.pc = *offset;
break;
}
}
Bytecode::BrFalse(offset) => {
gas_meter.charge_simple_instr(S::BrFalse)?;
if !interpreter.operand_stack.pop_as::()? {
self.pc = *offset;
break;
}
}
Bytecode::Branch(offset) => {
gas_meter.charge_simple_instr(S::Branch)?;
self.pc = *offset;
break;
}
Bytecode::LdU8(int_const) => {
gas_meter.charge_simple_instr(S::LdU8)?;
interpreter.operand_stack.push(Value::u8(*int_const))?;
}
Bytecode::LdU64(int_const) => {
gas_meter.charge_simple_instr(S::LdU64)?;
interpreter.operand_stack.push(Value::u64(*int_const))?;
}
Bytecode::LdU128(int_const) => {
gas_meter.charge_simple_instr(S::LdU128)?;
interpreter.operand_stack.push(Value::u128(*int_const))?;
}
Bytecode::LdConst(idx) => {
let constant = resolver.constant_at(*idx);
gas_meter.charge_ld_const(NumBytes::new(constant.data.len() as u64))?;
interpreter.operand_stack.push(
Value::deserialize_constant(constant).ok_or_else(|| {
PartialVMError::new(StatusCode::VERIFIER_INVARIANT_VIOLATION)
.with_message(
"Verifier failed to verify the deserialization of constants"
.to_owned(),
)
})?,
)?
}
Bytecode::LdTrue => {
gas_meter.charge_simple_instr(S::LdTrue)?;
interpreter.operand_stack.push(Value::bool(true))?;
}
Bytecode::LdFalse => {
gas_meter.charge_simple_instr(S::LdFalse)?;
interpreter.operand_stack.push(Value::bool(false))?;
}
Bytecode::CopyLoc(idx) => {
// TODO(Gas): We should charge gas before copying the value.
let local = self.locals.copy_loc(*idx as usize)?;
gas_meter.charge_copy_loc(&local)?;
interpreter.operand_stack.push(local)?;
}
Bytecode::MoveLoc(idx) => {
let local = self.locals.move_loc(*idx as usize)?;
gas_meter.charge_move_loc(&local)?;
interpreter.operand_stack.push(local)?;
}
Bytecode::StLoc(idx) => {
let value_to_store = interpreter.operand_stack.pop()?;
gas_meter.charge_store_loc(&value_to_store)?;
self.locals.store_loc(*idx as usize, value_to_store)?;
}
Bytecode::Call(idx) => {
return Ok(ExitCode::Call(*idx));
}
Bytecode::CallGeneric(idx) => {
return Ok(ExitCode::CallGeneric(*idx));
}
Bytecode::MutBorrowLoc(idx) | Bytecode::ImmBorrowLoc(idx) => {
let instr = match instruction {
Bytecode::MutBorrowLoc(_) => S::MutBorrowLoc,
_ => S::ImmBorrowLoc,
};
gas_meter.charge_simple_instr(instr)?;
interpreter
.operand_stack
.push(self.locals.borrow_loc(*idx as usize)?)?;
}
Bytecode::ImmBorrowField(fh_idx) | Bytecode::MutBorrowField(fh_idx) => {
let instr = match instruction {
Bytecode::MutBorrowField(_) => S::MutBorrowField,
_ => S::ImmBorrowField,
};
gas_meter.charge_simple_instr(instr)?;
let reference = interpreter.operand_stack.pop_as::()?;
let offset = resolver.field_offset(*fh_idx);
let field_ref = reference.borrow_field(offset)?;
interpreter.operand_stack.push(field_ref)?;
}
Bytecode::ImmBorrowFieldGeneric(fi_idx)
| Bytecode::MutBorrowFieldGeneric(fi_idx) => {
let instr = match instruction {
Bytecode::MutBorrowField(_) => S::MutBorrowFieldGeneric,
_ => S::ImmBorrowFieldGeneric,
};
gas_meter.charge_simple_instr(instr)?;
let reference = interpreter.operand_stack.pop_as::()?;
let offset = resolver.field_instantiation_offset(*fi_idx);
let field_ref = reference.borrow_field(offset)?;
interpreter.operand_stack.push(field_ref)?;
}
Bytecode::Pack(sd_idx) => {
let field_count = resolver.field_count(*sd_idx);
gas_meter.charge_pack(
false,
interpreter.operand_stack.last_n(field_count as usize)?,
)?;
let args = interpreter.operand_stack.popn(field_count)?;
interpreter
.operand_stack
.push(Value::struct_(Struct::pack(args)))?;
}
Bytecode::PackGeneric(si_idx) => {
let field_count = resolver.field_instantiation_count(*si_idx);
gas_meter.charge_pack(
true,
interpreter.operand_stack.last_n(field_count as usize)?,
)?;
let args = interpreter.operand_stack.popn(field_count)?;
interpreter
.operand_stack
.push(Value::struct_(Struct::pack(args)))?;
}
Bytecode::Unpack(_sd_idx) => {
let struct_ = interpreter.operand_stack.pop_as::()?;
gas_meter.charge_unpack(false, struct_.field_views())?;
for value in struct_.unpack()? {
interpreter.operand_stack.push(value)?;
}
}
Bytecode::UnpackGeneric(_si_idx) => {
let struct_ = interpreter.operand_stack.pop_as::()?;
gas_meter.charge_unpack(true, struct_.field_views())?;
// TODO: Whether or not we want this gas metering in the loop is
// questionable. However, if we don't have it in the loop we could wind up
// doing a fair bit of work before charging for it.
for value in struct_.unpack()? {
interpreter.operand_stack.push(value)?;
}
}
Bytecode::ReadRef => {
let reference = interpreter.operand_stack.pop_as::()?;
gas_meter.charge_read_ref(reference.value_view())?;
let value = reference.read_ref()?;
interpreter.operand_stack.push(value)?;
}
Bytecode::WriteRef => {
let reference = interpreter.operand_stack.pop_as::()?;
let value = interpreter.operand_stack.pop()?;
gas_meter.charge_write_ref(&value)?;
reference.write_ref(value)?;
}
Bytecode::CastU8 => {
gas_meter.charge_simple_instr(S::CastU8)?;
let integer_value = interpreter.operand_stack.pop_as::()?;
interpreter
.operand_stack
.push(Value::u8(integer_value.cast_u8()?))?;
}
Bytecode::CastU64 => {
gas_meter.charge_simple_instr(S::CastU64)?;
let integer_value = interpreter.operand_stack.pop_as::()?;
interpreter
.operand_stack
.push(Value::u64(integer_value.cast_u64()?))?;
}
Bytecode::CastU128 => {
gas_meter.charge_simple_instr(S::CastU128)?;
let integer_value = interpreter.operand_stack.pop_as::()?;
interpreter
.operand_stack
.push(Value::u128(integer_value.cast_u128()?))?;
}
// Arithmetic Operations
Bytecode::Add => {
gas_meter.charge_simple_instr(S::Add)?;
interpreter.binop_int(IntegerValue::add_checked)?
}
Bytecode::Sub => {
gas_meter.charge_simple_instr(S::Sub)?;
interpreter.binop_int(IntegerValue::sub_checked)?
}
Bytecode::Mul => {
gas_meter.charge_simple_instr(S::Mul)?;
interpreter.binop_int(IntegerValue::mul_checked)?
}
Bytecode::Mod => {
gas_meter.charge_simple_instr(S::Mod)?;
interpreter.binop_int(IntegerValue::rem_checked)?
}
Bytecode::Div => {
gas_meter.charge_simple_instr(S::Div)?;
interpreter.binop_int(IntegerValue::div_checked)?
}
Bytecode::BitOr => {
gas_meter.charge_simple_instr(S::BitOr)?;
interpreter.binop_int(IntegerValue::bit_or)?
}
Bytecode::BitAnd => {
gas_meter.charge_simple_instr(S::BitAnd)?;
interpreter.binop_int(IntegerValue::bit_and)?
}
Bytecode::Xor => {
gas_meter.charge_simple_instr(S::Xor)?;
interpreter.binop_int(IntegerValue::bit_xor)?
}
Bytecode::Shl => {
gas_meter.charge_simple_instr(S::Shl)?;
let rhs = interpreter.operand_stack.pop_as::()?;
let lhs = interpreter.operand_stack.pop_as::()?;
interpreter
.operand_stack
.push(lhs.shl_checked(rhs)?.into_value())?;
}
Bytecode::Shr => {
gas_meter.charge_simple_instr(S::Shr)?;
let rhs = interpreter.operand_stack.pop_as::()?;
let lhs = interpreter.operand_stack.pop_as::()?;
interpreter
.operand_stack
.push(lhs.shr_checked(rhs)?.into_value())?;
}
Bytecode::Or => {
gas_meter.charge_simple_instr(S::Or)?;
interpreter.binop_bool(|l, r| Ok(l || r))?
}
Bytecode::And => {
gas_meter.charge_simple_instr(S::And)?;
interpreter.binop_bool(|l, r| Ok(l && r))?
}
Bytecode::Lt => {
gas_meter.charge_simple_instr(S::Lt)?;
interpreter.binop_bool(IntegerValue::lt)?
}
Bytecode::Gt => {
gas_meter.charge_simple_instr(S::Gt)?;
interpreter.binop_bool(IntegerValue::gt)?
}
Bytecode::Le => {
gas_meter.charge_simple_instr(S::Le)?;
interpreter.binop_bool(IntegerValue::le)?
}
Bytecode::Ge => {
gas_meter.charge_simple_instr(S::Ge)?;
interpreter.binop_bool(IntegerValue::ge)?
}
Bytecode::Abort => {
gas_meter.charge_simple_instr(S::Abort)?;
let error_code = interpreter.operand_stack.pop_as::()?;
let error = PartialVMError::new(StatusCode::ABORTED)
.with_sub_status(error_code)
.with_message(format!(
"{} at offset {}",
self.function.pretty_string(),
self.pc,
));
if cfg!(feature = "testing") || cfg!(feature = "stacktrace") {
return Err(error.with_exec_state(interpreter.get_internal_state()));
} else {
return Err(error);
}
}
Bytecode::Eq => {
let lhs = interpreter.operand_stack.pop()?;
let rhs = interpreter.operand_stack.pop()?;
gas_meter.charge_eq(&lhs, &rhs)?;
interpreter
.operand_stack
.push(Value::bool(lhs.equals(&rhs)?))?;
}
Bytecode::Neq => {
let lhs = interpreter.operand_stack.pop()?;
let rhs = interpreter.operand_stack.pop()?;
gas_meter.charge_neq(&lhs, &rhs)?;
interpreter
.operand_stack
.push(Value::bool(!lhs.equals(&rhs)?))?;
}
Bytecode::MutBorrowGlobal(sd_idx) | Bytecode::ImmBorrowGlobal(sd_idx) => {
let is_mut = matches!(instruction, Bytecode::MutBorrowGlobal(_));
let addr = interpreter.operand_stack.pop_as::()?;
let ty = resolver.get_struct_type(*sd_idx);
interpreter.borrow_global(
is_mut,
false,
resolver.loader(),
gas_meter,
data_store,
addr,
&ty,
)?;
}
Bytecode::MutBorrowGlobalGeneric(si_idx)
| Bytecode::ImmBorrowGlobalGeneric(si_idx) => {
let is_mut = matches!(instruction, Bytecode::MutBorrowGlobalGeneric(_));
let addr = interpreter.operand_stack.pop_as::()?;
let ty = resolver.instantiate_generic_type(*si_idx, self.ty_args())?;
interpreter.borrow_global(
is_mut,
true,
resolver.loader(),
gas_meter,
data_store,
addr,
&ty,
)?;
}
Bytecode::Exists(sd_idx) => {
let addr = interpreter.operand_stack.pop_as::()?;
let ty = resolver.get_struct_type(*sd_idx);
interpreter.exists(
false,
resolver.loader(),
gas_meter,
data_store,
addr,
&ty,
)?;
}
Bytecode::ExistsGeneric(si_idx) => {
let addr = interpreter.operand_stack.pop_as::()?;
let ty = resolver.instantiate_generic_type(*si_idx, self.ty_args())?;
interpreter.exists(
true,
resolver.loader(),
gas_meter,
data_store,
addr,
&ty,
)?;
}
Bytecode::MoveFrom(sd_idx) => {
let addr = interpreter.operand_stack.pop_as::()?;
let ty = resolver.get_struct_type(*sd_idx);
interpreter.move_from(
false,
resolver.loader(),
gas_meter,
data_store,
addr,
&ty,
)?;
}
Bytecode::MoveFromGeneric(si_idx) => {
let addr = interpreter.operand_stack.pop_as::()?;
let ty = resolver.instantiate_generic_type(*si_idx, self.ty_args())?;
interpreter.move_from(
true,
resolver.loader(),
gas_meter,
data_store,
addr,
&ty,
)?;
}
Bytecode::MoveTo(sd_idx) => {
let resource = interpreter.operand_stack.pop()?;
let signer_reference = interpreter.operand_stack.pop_as::()?;
let addr = signer_reference
.borrow_field(0)?
.value_as::()?
.read_ref()?
.value_as::()?;
let ty = resolver.get_struct_type(*sd_idx);
// REVIEW: Can we simplify Interpreter::move_to?
interpreter.move_to(
false,
resolver.loader(),
gas_meter,
data_store,
addr,
&ty,
resource,
)?;
}
Bytecode::MoveToGeneric(si_idx) => {
let resource = interpreter.operand_stack.pop()?;
let signer_reference = interpreter.operand_stack.pop_as::()?;
let addr = signer_reference
.borrow_field(0)?
.value_as::()?
.read_ref()?
.value_as::()?;
let ty = resolver.instantiate_generic_type(*si_idx, self.ty_args())?;
interpreter.move_to(
true,
resolver.loader(),
gas_meter,
data_store,
addr,
&ty,
resource,
)?;
}
Bytecode::FreezeRef => {
gas_meter.charge_simple_instr(S::FreezeRef)?;
// FreezeRef should just be a null op as we don't distinguish between mut
// and immut ref at runtime.
}
Bytecode::Not => {
gas_meter.charge_simple_instr(S::Not)?;
let value = !interpreter.operand_stack.pop_as::()?;
interpreter.operand_stack.push(Value::bool(value))?;
}
Bytecode::Nop => {
gas_meter.charge_simple_instr(S::Nop)?;
}
Bytecode::VecPack(si, num) => {
let ty = resolver.instantiate_single_type(*si, self.ty_args())?;
gas_meter.charge_vec_pack(
make_ty!(&ty),
interpreter.operand_stack.last_n(*num as usize)?,
)?;
let elements = interpreter.operand_stack.popn(*num as u16)?;
let value = Vector::pack(&ty, elements)?;
interpreter.operand_stack.push(value)?;
}
Bytecode::VecLen(si) => {
let vec_ref = interpreter.operand_stack.pop_as::()?;
let ty = &resolver.instantiate_single_type(*si, self.ty_args())?;
gas_meter.charge_vec_len(TypeWithLoader {
ty,
loader: resolver.loader(),
})?;
let value = vec_ref.len(ty)?;
interpreter.operand_stack.push(value)?;
}
Bytecode::VecImmBorrow(si) => {
let idx = interpreter.operand_stack.pop_as::()? as usize;
let vec_ref = interpreter.operand_stack.pop_as::()?;
let ty = resolver.instantiate_single_type(*si, self.ty_args())?;
let res = vec_ref.borrow_elem(idx, &ty);
gas_meter.charge_vec_borrow(false, make_ty!(&ty), res.is_ok())?;
interpreter.operand_stack.push(res?)?;
}
Bytecode::VecMutBorrow(si) => {
let idx = interpreter.operand_stack.pop_as::()? as usize;
let vec_ref = interpreter.operand_stack.pop_as::()?;
let ty = &resolver.instantiate_single_type(*si, self.ty_args())?;
let res = vec_ref.borrow_elem(idx, ty);
gas_meter.charge_vec_borrow(true, make_ty!(ty), res.is_ok())?;
interpreter.operand_stack.push(res?)?;
}
Bytecode::VecPushBack(si) => {
let elem = interpreter.operand_stack.pop()?;
let vec_ref = interpreter.operand_stack.pop_as::()?;
let ty = &resolver.instantiate_single_type(*si, self.ty_args())?;
gas_meter.charge_vec_push_back(make_ty!(ty), &elem)?;
vec_ref.push_back(elem, ty)?;
}
Bytecode::VecPopBack(si) => {
let vec_ref = interpreter.operand_stack.pop_as::()?;
let ty = &resolver.instantiate_single_type(*si, self.ty_args())?;
let res = vec_ref.pop(ty);
gas_meter.charge_vec_pop_back(make_ty!(ty), res.as_ref().ok())?;
interpreter.operand_stack.push(res?)?;
}
Bytecode::VecUnpack(si, num) => {
let vec_val = interpreter.operand_stack.pop_as::()?;
let ty = &resolver.instantiate_single_type(*si, self.ty_args())?;
gas_meter.charge_vec_unpack(make_ty!(ty), NumArgs::new(*num))?;
let elements = vec_val.unpack(ty, *num)?;
for value in elements {
interpreter.operand_stack.push(value)?;
}
}
Bytecode::VecSwap(si) => {
let idx2 = interpreter.operand_stack.pop_as::()? as usize;
let idx1 = interpreter.operand_stack.pop_as::()? as usize;
let vec_ref = interpreter.operand_stack.pop_as::()?;
let ty = &resolver.instantiate_single_type(*si, self.ty_args())?;
gas_meter.charge_vec_swap(make_ty!(ty))?;
vec_ref.swap(idx1, idx2, ty)?;
}
}
// invariant: advance to pc +1 is iff instruction at pc executed without aborting
self.pc += 1;
}
// ok we are out, it's a branch, check the pc for good luck
// TODO: re-work the logic here. Tests should have a more
// natural way to plug in
if self.pc as usize >= code.len() {
if cfg!(test) {
// In order to test the behavior of an instruction stream, hitting end of the
// code should report no error so that we can check the
// locals.
return Ok(ExitCode::Return);
} else {
return Err(PartialVMError::new(StatusCode::PC_OVERFLOW));
}
}
}
}
fn ty_args(&self) -> &[Type] {
&self.ty_args
}
fn resolver<'a>(&self, loader: &'a Loader) -> Resolver<'a> {
self.function.get_resolver(loader)
}
fn location(&self) -> Location {
match self.function.module_id() {
None => Location::Script,
Some(id) => Location::Module(id.clone()),
}
}
}
看着代码很多,其实逻辑比较清晰,首先从Function里拿到所有的指令,然后循环执行,根据指令的不同,会进行不同的操作。对于本地变量的存取,主要依赖operand_stack和Locals两个数据结构,针对需要存储到链上的数据,则需要依赖DataStore。接下来的一系列文章,我会根据Move合约反汇编出来的指令,对照着上面的代码进行解读。