diff --git a/external-crates/move/crates/move-cli/src/sandbox/commands/run.rs b/external-crates/move/crates/move-cli/src/sandbox/commands/run.rs index f69c5e70bf3ba..0b5a81d89d38a 100644 --- a/external-crates/move/crates/move-cli/src/sandbox/commands/run.rs +++ b/external-crates/move/crates/move-cli/src/sandbox/commands/run.rs @@ -66,12 +66,12 @@ pub fn run( .map_err(|e| anyhow!("Error deserializing module: {:?}", e))?; move_vm_profiler::tracing_feature_enabled! { use move_vm_profiler::GasProfiler; - use move_vm_types::gas::GasMeter; + use move_vm_runtime::shared::gas::GasMeter; let gas_rem: u64 = gas_status.remaining_gas().into(); gas_status.set_profiler(GasProfiler::init( - &session.vm_config().profiler_config, - function_name.to_owned(), + &runtime.vm_config().profiler_config, + function.to_owned(), gas_rem, )); } diff --git a/external-crates/move/crates/move-trace-format/src/format.rs b/external-crates/move/crates/move-trace-format/src/format.rs index 4d4f9698cbd89..43cd0c2c19b31 100644 --- a/external-crates/move/crates/move-trace-format/src/format.rs +++ b/external-crates/move/crates/move-trace-format/src/format.rs @@ -6,7 +6,7 @@ use crate::interface::{NopTracer, Tracer, Writer}; use move_binary_format::{ file_format::{Bytecode, FunctionDefinitionIndex as BinaryFunctionDefinitionIndex}, - file_format_common::instruction_opcode, + file_format_common::{instruction_opcode, Opcodes}, }; use move_core_types::{ annotated_value::MoveValue, @@ -282,20 +282,23 @@ impl MoveTraceBuilder { } /// Record an `Instruction` event in the trace along with the effects of the instruction. - pub fn instruction( + pub fn instruction>( &mut self, - instruction: &Bytecode, + instruction: T, type_parameters: Vec, effects: Vec, gas_left: u64, pc: u16, ) { + let opcode: Opcodes = instruction.into(); + self.push_event(TraceEvent::Instruction { type_parameters, pc, gas_left, - instruction: Box::new(format!("{:?}", instruction_opcode(instruction))), + instruction: Box::new(format!("{:?}", opcode)), }); + for effect in effects { self.push_event(TraceEvent::Effect(Box::new(effect))); } diff --git a/external-crates/move/crates/move-unit-test/src/test_runner.rs b/external-crates/move/crates/move-unit-test/src/test_runner.rs index d32572657fc48..8d181ae0ee1db 100644 --- a/external-crates/move/crates/move-unit-test/src/test_runner.rs +++ b/external-crates/move/crates/move-unit-test/src/test_runner.rs @@ -288,7 +288,7 @@ impl SharedTestingConfig { test_config: &SharedTestingConfig, gas_meter: &mut impl GasMeter, // TODO: Plumb tracing in - _tracer: Option<&mut MoveTraceBuilder>, + tracer: Option<&mut MoveTraceBuilder>, module_id: ModuleId, function_name: &str, arguments: Vec, @@ -304,13 +304,15 @@ impl SharedTestingConfig { let function_name = IdentStr::new(function_name).unwrap(); - let serialized_return_values_result = vm_instance.execute_function_bypass_visibility( - &module_id, - function_name, - vec![], /* no ty args for now */ - serialize_values(arguments.iter()), - gas_meter, - ); + let serialized_return_values_result = vm_instance + .execute_function_bypass_visibility_with_tracer_if_enabled( + &module_id, + function_name, + vec![], /* no ty args for now */ + serialize_values(arguments.iter()), + tracer, + gas_meter, + ); serialized_return_values_result.map(|res| { ( res.return_values @@ -332,7 +334,7 @@ impl SharedTestingConfig { let mut gas_meter = GasStatus::new(&self.cost_table, Gas::new(self.execution_bound)); move_vm_profiler::tracing_feature_enabled! { use move_vm_profiler::GasProfiler; - use move_vm_types::gas::GasMeter; + use move_vm_runtime::shared::gas::GasMeter; gas_meter.set_profiler(GasProfiler::init_default_cfg( function_name.to_owned(), self.execution_bound, diff --git a/external-crates/move/crates/move-vm-runtime/src/execution/interpreter/eval.rs b/external-crates/move/crates/move-vm-runtime/src/execution/interpreter/eval.rs index 1badb58fdfa86..7a6fe27cba97f 100644 --- a/external-crates/move/crates/move-vm-runtime/src/execution/interpreter/eval.rs +++ b/external-crates/move/crates/move-vm-runtime/src/execution/interpreter/eval.rs @@ -9,6 +9,7 @@ use crate::{ set_err_info, state::{CallStack, MachineState, ResolvableType}, }, + tracing::{trace, tracer::VMTracer}, values::{ IntegerValue, Reference, Struct, StructRef, VMValueCast, Value, Variant, VariantRef, Vector, VectorRef, @@ -43,13 +44,15 @@ enum StepStatus { Done, } -struct RunContext<'vm_cache, 'native, 'native_lifetimes> { +struct RunContext<'vm_cache, 'native, 'native_lifetimes, 'tracer, 'trace_builder> { vtables: &'vm_cache VMDispatchTables, vm_config: Arc, extensions: &'native mut NativeContextExtensions<'native_lifetimes>, + // TODO: consider making this `Option<&mut VMTracer<'_>>` and passing it like that everywhere? + tracer: &'tracer mut Option>, } -impl RunContext<'_, '_, '_> { +impl RunContext<'_, '_, '_, '_, '_> { // TODO: The Run Context should hold this, not go get it from the Loader. fn vm_config(&self) -> &VMConfig { &self.vm_config @@ -66,12 +69,14 @@ pub(super) fn run( vtables: &VMDispatchTables, vm_config: Arc, extensions: &mut NativeContextExtensions, + tracer: &mut Option>, gas_meter: &mut impl GasMeter, ) -> VMResult> { let mut run_context = RunContext { extensions, vtables, vm_config, + tracer, }; let mut state = start_state; @@ -137,6 +142,13 @@ fn step( }); profile_open_instr!(gas_meter, format!("{:?}", instruction)); + trace(run_context.tracer, |tracer| { + tracer.start_instruction( + run_context.vtables, + state, + &gas_meter.remaining_gas().into(), + ) + }); dbg_println!(flag: eval_step, "Instruction: {instruction:?}"); // These are split out because `PartialVMError` and `VMError` are different types. It's unclear // why as they hold identical data, but if we could combine them, we could entirely inline @@ -145,6 +157,14 @@ fn step( Bytecode::Ret => { let charge_result = gas_meter.charge_simple_instr(SimpleInstruction::Ret); profile_close_instr!(gas_meter, format!("{:?}", instruction)); + trace(run_context.tracer, |tracer| { + tracer.end_instruction( + run_context.vtables, + state, + &gas_meter.remaining_gas().into(), + None, + ) + }); partial_error_to_error(state, run_context, charge_result)?; let non_ref_vals = state @@ -161,9 +181,17 @@ fn step( profile_close_frame!( gas_meter, - arena::to_ref(current_frame.function).pretty_string() + state.call_stack.current_frame.function().pretty_string() ); - + trace(run_context.tracer, |tracer| { + tracer.exit_frame( + run_context.vtables, + state, + &gas_meter.remaining_gas().into(), + state.call_stack.current_frame.function(), + None, + ) + }); if state.can_pop_call_frame() { state.pop_call_frame()?; // Note: the caller will find the callee's return values at the top of the shared operand stack @@ -176,6 +204,14 @@ fn step( } Bytecode::CallGeneric(idx) => { profile_close_instr!(gas_meter, format!("{:?}", instruction)); + trace(run_context.tracer, |tracer| { + tracer.end_instruction( + run_context.vtables, + state, + &gas_meter.remaining_gas().into(), + None, + ) + }); let ty_args = state .call_stack .current_frame @@ -194,6 +230,14 @@ fn step( } Bytecode::VirtualCall(vtable_key) => { profile_close_instr!(gas_meter, format!("{:?}", instruction)); + trace(run_context.tracer, |tracer| { + tracer.end_instruction( + run_context.vtables, + state, + &gas_meter.remaining_gas().into(), + None, + ) + }); let function = run_context .vtables .resolve_function(vtable_key) @@ -203,11 +247,27 @@ fn step( } Bytecode::DirectCall(function) => { profile_close_instr!(gas_meter, format!("{:?}", instruction)); + trace(run_context.tracer, |tracer| { + tracer.end_instruction( + run_context.vtables, + state, + &gas_meter.remaining_gas().into(), + None, + ) + }); call_function(state, run_context, gas_meter, *function, vec![])?; Ok(StepStatus::Running) } _ => { let step_result = op_step_impl(state, run_context, gas_meter, instruction); + trace(run_context.tracer, |tracer| { + tracer.end_instruction( + run_context.vtables, + state, + &gas_meter.remaining_gas().into(), + step_result.as_ref().err(), + ) + }); partial_error_to_error(state, run_context, step_result)?; Ok(StepStatus::Running) } @@ -883,7 +943,16 @@ fn call_function( ty_args: Vec, ) -> VMResult<()> { let fun_ref = function.to_ref(); - profile_open_frame!(gas_meter, func_name.clone()); + trace(run_context.tracer, |tracer| { + tracer.enter_frame( + run_context.vtables, + state, + &gas_meter.remaining_gas().into(), + fun_ref, + &ty_args, + ) + }); + profile_open_frame!(gas_meter, fun_ref.name().to_string()); // Charge gas let module_id = fun_ref.module_id(); @@ -918,11 +987,22 @@ fn call_function( } if fun_ref.is_native() { - call_native(state, run_context, gas_meter, fun_ref, ty_args)?; + let native_result = call_native(state, run_context, gas_meter, fun_ref, ty_args); - state.call_stack.current_frame.pc += 1; // advance past the Call instruction in the caller + // NB: Pass any error into the tracer before raising it. + trace(run_context.tracer, |tracer| { + tracer.exit_frame( + run_context.vtables, + state, + &gas_meter.remaining_gas().into(), + function.to_ref(), + native_result.as_ref().err(), + ) + }); - profile_close_frame!(gas_meter, func_name.clone()); + native_result?; + state.call_stack.current_frame.pc += 1; // advance past the Call instruction in the caller + profile_close_frame!(gas_meter, fun_ref.name().to_string()); } else { // Note: the caller will find the callee's return values at the top of the shared // operand stack when the new frame returns. diff --git a/external-crates/move/crates/move-vm-runtime/src/execution/interpreter/mod.rs b/external-crates/move/crates/move-vm-runtime/src/execution/interpreter/mod.rs index 819375b2609fd..3557465b8e1bd 100644 --- a/external-crates/move/crates/move-vm-runtime/src/execution/interpreter/mod.rs +++ b/external-crates/move/crates/move-vm-runtime/src/execution/interpreter/mod.rs @@ -5,6 +5,7 @@ use crate::{ execution::{ dispatch_tables::VMDispatchTables, interpreter::state::{CallStack, MachineState, ModuleDefinitionResolver}, + tracing::trace, values::Value, }, jit::execution::ast::{Function, Type}, @@ -23,19 +24,29 @@ pub(crate) mod state; /// Entrypoint into the interpreter. All external calls need to be routed through this /// function. pub(crate) fn run( - function: VMPointer, - ty_args: Vec, - args: Vec, vtables: &VMDispatchTables, vm_config: Arc, extensions: &mut NativeContextExtensions, + tracer: &mut Option>, gas_meter: &mut impl GasMeter, + function: VMPointer, + ty_args: Vec, + args: Vec, ) -> VMResult> { let fun_ref = function.to_ref(); + trace(tracer, |tracer| { + tracer.enter_initial_frame( + vtables, + &gas_meter.remaining_gas().into(), + function.ptr_clone().to_ref(), + &ty_args, + &args, + ) + }); profile_open_frame!(gas_meter, fun_ref.pretty_string()); if fun_ref.is_native() { - let return_values = eval::call_native_with_args( + let return_result = eval::call_native_with_args( None, vtables, gas_meter, @@ -48,11 +59,12 @@ pub(crate) fn run( .map_err(|e| { e.at_code_offset(fun_ref.index(), 0) .finish(Location::Module(fun_ref.module_id().clone())) - })?; - + }); + trace(tracer, |tracer| { + tracer.exit_initial_native_frame(&return_result, &gas_meter.remaining_gas().into()) + }); profile_close_frame!(gas_meter, fun_ref.pretty_string()); - - Ok(return_values.into_iter().collect()) + return_result.map(|values| values.into_iter().collect()) } else { let module_id = function.to_ref().module_id(); let resolver = ModuleDefinitionResolver::new(vtables, module_id) @@ -62,7 +74,7 @@ pub(crate) fn run( .finish(Location::Module(fun_ref.module_id().clone())) })?; let state = MachineState::new(call_stack); - eval::run(state, vtables, vm_config, extensions, gas_meter) + eval::run(state, vtables, vm_config, extensions, tracer, gas_meter) } } @@ -75,3 +87,5 @@ macro_rules! set_err_info { } pub(crate) use set_err_info; + +use super::tracing::tracer::VMTracer; diff --git a/external-crates/move/crates/move-vm-runtime/src/execution/interpreter/state.rs b/external-crates/move/crates/move-vm-runtime/src/execution/interpreter/state.rs index 325d392eb7662..16dda7a5133cc 100644 --- a/external-crates/move/crates/move-vm-runtime/src/execution/interpreter/state.rs +++ b/external-crates/move/crates/move-vm-runtime/src/execution/interpreter/state.rs @@ -61,25 +61,25 @@ macro_rules! debug_writeln { /// An `MachineState` 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 MachineState { - pub(super) call_stack: CallStack, + pub(crate) call_stack: CallStack, /// Operand stack, where Move `Value`s are stored for stack operations. - pub(super) operand_stack: ValueStack, + pub(crate) operand_stack: ValueStack, } /// The operand stack. -pub(super) struct ValueStack { - pub(super) value: Vec, +pub(crate) struct ValueStack { + pub(crate) value: Vec, } /// A call stack. // #[derive(Debug)] -pub(super) struct CallStack { +pub(crate) struct CallStack { /// The current frame we are computing in. - pub(super) current_frame: CallFrame, + pub(crate) current_frame: CallFrame, /// The current heap. - pub(super) heap: MachineHeap, + pub(crate) heap: MachineHeap, /// The stack of active functions. - pub(super) frames: Vec, + pub(crate) frames: Vec, } // A Resolver is a simple and small structure allocated on the stack and used by the @@ -93,12 +93,12 @@ pub(crate) struct ModuleDefinitionResolver { /// A `Frame` is the execution context for a function. It holds the locals of the function and /// the function itself. #[derive(Debug)] -pub(super) struct CallFrame { - pub(super) pc: u16, - pub(super) function: VMPointer, - pub(super) resolver: ModuleDefinitionResolver, - pub(super) stack_frame: StackFrame, - pub(super) ty_args: Vec, +pub(crate) struct CallFrame { + pub(crate) pc: u16, + pub(crate) function: VMPointer, + pub(crate) resolver: ModuleDefinitionResolver, + pub(crate) stack_frame: StackFrame, + pub(crate) ty_args: Vec, } pub(super) struct ResolvableType<'a, 'b> { @@ -425,6 +425,14 @@ impl ValueStack { } Ok(self.value[(self.value.len() - n)..].iter()) } + + pub(crate) fn len(&self) -> usize { + self.value.len() + } + + pub(crate) fn value_at(&self, n: usize) -> Option<&Value> { + self.value.get(n) + } } impl CallStack { @@ -547,6 +555,10 @@ impl ModuleDefinitionResolver { &self.module.function_instantiation_at(idx.0).handle } + // + // Type lookup and instantiation + // + pub(crate) fn instantiate_generic_function( &self, idx: FunctionInstantiationIndex, diff --git a/external-crates/move/crates/move-vm-runtime/src/execution/mod.rs b/external-crates/move/crates/move-vm-runtime/src/execution/mod.rs index eb3b031342599..201f9f14e2ded 100644 --- a/external-crates/move/crates/move-vm-runtime/src/execution/mod.rs +++ b/external-crates/move/crates/move-vm-runtime/src/execution/mod.rs @@ -3,5 +3,6 @@ pub mod dispatch_tables; pub mod interpreter; +pub mod tracing; pub mod values; pub mod vm; diff --git a/external-crates/move/crates/move-vm-runtime/src/execution/values/values_impl.rs b/external-crates/move/crates/move-vm-runtime/src/execution/values/values_impl.rs index 5f3c883a6edb4..e78cb5cca2b71 100644 --- a/external-crates/move/crates/move-vm-runtime/src/execution/values/values_impl.rs +++ b/external-crates/move/crates/move-vm-runtime/src/execution/values/values_impl.rs @@ -2850,8 +2850,10 @@ impl<'d, 'a> serde::de::Visitor<'d> for EnumFieldVisitor<'a> { A: serde::de::SeqAccess<'d>, { let tag = match seq.next_element_seed(&MoveTypeLayout::U8)? { - Some(MoveValue::U8(tag)) if tag as u64 <= VARIANT_COUNT_MAX => tag as u16, - Some(MoveValue::U8(tag)) => return Err(A::Error::invalid_length(tag as usize, &self)), + Some(RuntimeValue::U8(tag)) if tag as u64 <= VARIANT_COUNT_MAX => tag as u16, + Some(RuntimeValue::U8(tag)) => { + return Err(A::Error::invalid_length(tag as usize, &self)) + } Some(val) => { return Err(A::Error::invalid_type( serde::de::Unexpected::Other(&format!("{val:?}")), @@ -3318,21 +3320,23 @@ pub mod prop { } } -use move_core_types::runtime_value::{MoveStruct, MoveValue, MoveVariant}; +use move_core_types::runtime_value::{ + MoveStruct as RuntimeStruct, MoveValue as RuntimeValue, MoveVariant as RuntimeVariant, +}; impl Value { - pub fn as_move_value(&self, layout: &MoveTypeLayout) -> MoveValue { + pub fn as_move_value(&self, layout: &MoveTypeLayout) -> RuntimeValue { use MoveTypeLayout as L; match (layout, self) { - (L::U8, Value::U8(x)) => MoveValue::U8(*x), - (L::U16, Value::U16(x)) => MoveValue::U16(*x), - (L::U32, Value::U32(x)) => MoveValue::U32(*x), - (L::U64, Value::U64(x)) => MoveValue::U64(*x), - (L::U128, Value::U128(x)) => MoveValue::U128(**x), - (L::U256, Value::U256(x)) => MoveValue::U256(**x), - (L::Bool, Value::Bool(x)) => MoveValue::Bool(*x), - (L::Address, Value::Address(x)) => MoveValue::Address(**x), + (L::U8, Value::U8(x)) => RuntimeValue::U8(*x), + (L::U16, Value::U16(x)) => RuntimeValue::U16(*x), + (L::U32, Value::U32(x)) => RuntimeValue::U32(*x), + (L::U64, Value::U64(x)) => RuntimeValue::U64(*x), + (L::U128, Value::U128(x)) => RuntimeValue::U128(**x), + (L::U256, Value::U256(x)) => RuntimeValue::U256(**x), + (L::Bool, Value::Bool(x)) => RuntimeValue::Bool(*x), + (L::Address, Value::Address(x)) => RuntimeValue::Address(**x), // Enum variant case with dereferencing the Box. (L::Enum(enum_layout), Value::Container(container)) => { @@ -3345,7 +3349,7 @@ impl Value { for (v, field_layout) in values.iter().zip(field_layouts) { fields.push(v.as_move_value(field_layout)); } - MoveValue::Variant(MoveVariant { tag, fields }) + RuntimeValue::Variant(RuntimeVariant { tag, fields }) } else { panic!("Expected Enum, got non-variant container"); } @@ -3358,7 +3362,7 @@ impl Value { for (v, field_layout) in r.iter().zip(struct_layout.fields().iter()) { fields.push(v.as_move_value(field_layout)); } - MoveValue::Struct(MoveStruct::new(fields)) + RuntimeValue::Struct(RuntimeStruct::new(fields)) } else { panic!("Expected Struct, got non-struct container"); } @@ -3366,15 +3370,17 @@ impl Value { // Vector case with handling different container types (L::Vector(inner_layout), Value::Container(container)) => { - MoveValue::Vector(match &**container { - Container::VecU8(r) => r.iter().map(|u| MoveValue::U8(*u)).collect(), - Container::VecU16(r) => r.iter().map(|u| MoveValue::U16(*u)).collect(), - Container::VecU32(r) => r.iter().map(|u| MoveValue::U32(*u)).collect(), - Container::VecU64(r) => r.iter().map(|u| MoveValue::U64(*u)).collect(), - Container::VecU128(r) => r.iter().map(|u| MoveValue::U128(*u)).collect(), - Container::VecU256(r) => r.iter().map(|u| MoveValue::U256(*u)).collect(), - Container::VecBool(r) => r.iter().map(|u| MoveValue::Bool(*u)).collect(), - Container::VecAddress(r) => r.iter().map(|u| MoveValue::Address(*u)).collect(), + RuntimeValue::Vector(match &**container { + Container::VecU8(r) => r.iter().map(|u| RuntimeValue::U8(*u)).collect(), + Container::VecU16(r) => r.iter().map(|u| RuntimeValue::U16(*u)).collect(), + Container::VecU32(r) => r.iter().map(|u| RuntimeValue::U32(*u)).collect(), + Container::VecU64(r) => r.iter().map(|u| RuntimeValue::U64(*u)).collect(), + Container::VecU128(r) => r.iter().map(|u| RuntimeValue::U128(*u)).collect(), + Container::VecU256(r) => r.iter().map(|u| RuntimeValue::U256(*u)).collect(), + Container::VecBool(r) => r.iter().map(|u| RuntimeValue::Bool(*u)).collect(), + Container::VecAddress(r) => { + r.iter().map(|u| RuntimeValue::Address(*u)).collect() + } Container::Vec(r) => r .iter() .map(|v| v.as_move_value(inner_layout.as_ref())) @@ -3393,7 +3399,7 @@ impl Value { panic!("Unexpected signer layout: {:?}", r); } match &r[0] { - Value::Address(a) => MoveValue::Signer(**a), + Value::Address(a) => RuntimeValue::Signer(**a), v => panic!("Unexpected non-address while converting signer: {:?}", v), } } else { @@ -3405,3 +3411,144 @@ impl Value { } } } + +use move_core_types::annotated_value::{ + MoveEnumLayout as AnnEnumLayout, MoveStruct as AnnStruct, MoveTypeLayout as AnnTypeLayout, + MoveValue as AnnValue, MoveVariant as AnnVariant, +}; + +impl Value { + /// Converts the value to an annotated move value. This is only needed for tracing and care + /// should be taken when using this function as it can possibly inflate the size of the value. + pub(crate) fn as_annotated_move_value(&self, layout: &AnnTypeLayout) -> Option { + use AnnTypeLayout as L; + match (layout, self) { + (L::U8, Value::U8(x)) => Some(AnnValue::U8(*x)), + (L::U16, Value::U16(x)) => Some(AnnValue::U16(*x)), + (L::U32, Value::U32(x)) => Some(AnnValue::U32(*x)), + (L::U64, Value::U64(x)) => Some(AnnValue::U64(*x)), + (L::U128, Value::U128(x)) => Some(AnnValue::U128(**x)), + (L::U256, Value::U256(x)) => Some(AnnValue::U256(**x)), + (L::Bool, Value::Bool(x)) => Some(AnnValue::Bool(*x)), + (L::Address, Value::Address(x)) => Some(AnnValue::Address(**x)), + (layout, Value::Container(container)) => container.as_annotated_move_value(layout), + (layout, Value::Reference(ref_)) => ref_.as_annotated_move_value(layout), + (_, _) => None, + } + } +} + +impl Container { + /// Converts the value to an annotated move value. This is only needed for tracing and care + /// should be taken when using this function as it can possibly inflate the size of the value. + pub(crate) fn as_annotated_move_value(&self, layout: &AnnTypeLayout) -> Option { + use AnnTypeLayout as L; + match (layout, self) { + (L::Vector(elem_layout), Container::Vec(entries)) => Some(AnnValue::Vector( + entries + .iter() + .map(|v| v.as_annotated_move_value(elem_layout)) + .collect::>()?, + )), + (L::Vector(_inner), Container::VecU8(entries)) => Some(AnnValue::Vector( + entries.iter().map(|n| AnnValue::U8(*n)).collect(), + )), + (L::Vector(_inner), Container::VecU16(entries)) => Some(AnnValue::Vector( + entries.iter().map(|n| AnnValue::U16(*n)).collect(), + )), + (L::Vector(_inner), Container::VecU32(entries)) => Some(AnnValue::Vector( + entries.iter().map(|n| AnnValue::U32(*n)).collect(), + )), + (L::Vector(_inner), Container::VecU64(entries)) => Some(AnnValue::Vector( + entries.iter().map(|n| AnnValue::U64(*n)).collect(), + )), + (L::Vector(_inner), Container::VecU128(entries)) => Some(AnnValue::Vector( + entries.iter().map(|n| AnnValue::U128(*n)).collect(), + )), + (L::Vector(_inner), Container::VecU256(entries)) => Some(AnnValue::Vector( + entries.iter().map(|n| AnnValue::U256(*n)).collect(), + )), + (L::Vector(_inner), Container::VecBool(entries)) => Some(AnnValue::Vector( + entries.iter().map(|n| AnnValue::Bool(*n)).collect(), + )), + (L::Vector(_inner), Container::VecAddress(entries)) => Some(AnnValue::Vector( + entries.iter().map(|n| AnnValue::Address(*n)).collect(), + )), + (L::Signer, Container::Struct(struct_)) => { + let values = &struct_.0; + let [Value::Address(ref addr)] = **values else { + return None; + }; + Some(AnnValue::Signer(**addr)) + } + (L::Struct(struct_layout), Container::Struct(struct_)) => { + let fields = struct_ + .0 + .iter() + .zip(struct_layout.fields.iter()) + .map(|(value, layout)| { + let value = value.as_annotated_move_value(&layout.layout)?; + let field_name = layout.name.clone(); + Some((field_name, value)) + }) + .collect::>>()?; + let struct_ = AnnStruct::new(struct_layout.type_.clone(), fields); + Some(AnnValue::Struct(struct_)) + } + (L::Enum(enum_layout), Container::Variant(variant)) => { + let AnnEnumLayout { type_, variants } = enum_layout.as_ref(); + let (tag, values) = variant.as_ref(); + let ((variant_name, _), field_layouts) = + variants.iter().find(|((_, vtag), _)| vtag == tag)?; + + let fields = values + .0 + .iter() + .zip(field_layouts.iter()) + .map(|(value, layout)| { + let value = value.as_annotated_move_value(&layout.layout)?; + let field_name = layout.name.clone(); + Some((field_name, value)) + }) + .collect::>>()?; + + let tag = tag.clone(); + let type_ = type_.clone(); + let variant_name = variant_name.clone(); + let variant = AnnVariant { + tag, + fields, + type_, + variant_name, + }; + Some(AnnValue::Variant(variant)) + } + (_, _) => None, + } + } +} + +impl Reference { + /// Converts the value to an annotated move value. This is only needed for tracing and care + /// should be taken when using this function as it can possibly inflate the size of the value. + pub(crate) fn as_annotated_move_value(&self, layout: &AnnTypeLayout) -> Option { + use AnnTypeLayout as L; + match (layout, self) { + (L::U8, Reference::U8(value)) => Some(AnnValue::U8(*value.to_ref())), + (L::U16, Reference::U16(value)) => Some(AnnValue::U16(*value.to_ref())), + (L::U32, Reference::U32(value)) => Some(AnnValue::U32(*value.to_ref())), + (L::U64, Reference::U64(value)) => Some(AnnValue::U64(*value.to_ref())), + (L::U128, Reference::U128(value)) => Some(AnnValue::U128(*value.to_ref())), + (L::U256, Reference::U256(value)) => Some(AnnValue::U256(*value.to_ref())), + (L::Address, Reference::Address(value)) => Some(AnnValue::Address(*value.to_ref())), + (L::Bool, Reference::Bool(value)) => Some(AnnValue::Bool(*value.to_ref())), + (layout, Reference::Container(container)) => { + container.to_ref().as_annotated_move_value(layout) + } + (layout, Reference::Global(global_ref)) => { + global_ref.value.to_ref().as_annotated_move_value(layout) + } + (_, _) => None, + } + } +} diff --git a/external-crates/move/crates/move-vm-runtime/src/execution/vm.rs b/external-crates/move/crates/move-vm-runtime/src/execution/vm.rs index 112520ed3341f..8abf8b8e7d39b 100644 --- a/external-crates/move/crates/move-vm-runtime/src/execution/vm.rs +++ b/external-crates/move/crates/move-vm-runtime/src/execution/vm.rs @@ -26,10 +26,14 @@ use move_core_types::{ language_storage::{ModuleId, TypeTag}, vm_status::StatusCode, }; +use move_trace_format::format::MoveTraceBuilder; use move_vm_config::runtime::VMConfig; use std::{borrow::Borrow, sync::Arc}; -use super::dispatch_tables::{IntraPackageKey, VirtualTableKey}; +use super::{ + dispatch_tables::{IntraPackageKey, VirtualTableKey}, + tracing::tracer::VMTracer, +}; // ------------------------------------------------------------------------------------------------- // Types @@ -105,6 +109,7 @@ impl<'extensions> MoveVM<'extensions> { function_name, ty_args, args, + None, gas_meter, bypass_declared_entry_check, ) @@ -137,6 +142,47 @@ impl<'extensions> MoveVM<'extensions> { function_name, ty_args, args, + None, + gas_meter, + bypass_declared_entry_check, + ) + } + + /// Similar to execute_entry_function, but it bypasses visibility checks and accepts a tracer + pub fn execute_function_bypass_visibility_with_tracer_if_enabled( + &mut self, + module: &ModuleId, + function_name: &IdentStr, + ty_args: Vec, + args: Vec>, + tracer: Option<&mut MoveTraceBuilder>, + gas_meter: &mut impl GasMeter, + ) -> VMResult { + move_vm_profiler::tracing_feature_enabled! { + use move_vm_profiler::GasProfiler; + if gas_meter.get_profiler_mut().is_none() { + gas_meter.set_profiler(GasProfiler::init_default_cfg( + function_name.to_string(), + gas_meter.remaining_gas().into(), + )); + } + } + + let tracer = if cfg!(feature = "tracing") { + tracer + } else { + None + }; + + dbg_println!("running {module}::{function_name}"); + dbg_println!("tables: {:#?}", self.virtual_tables.loaded_packages); + let bypass_declared_entry_check = true; + self.execute_function( + module, + function_name, + ty_args, + args, + tracer, gas_meter, bypass_declared_entry_check, ) @@ -163,6 +209,7 @@ impl<'extensions> MoveVM<'extensions> { function_name: &IdentStr, type_arguments: Vec, serialized_args: Vec>, + tracer: Option<&mut MoveTraceBuilder>, gas_meter: &mut impl GasMeter, bypass_declared_entry_check: bool, ) -> VMResult { @@ -187,6 +234,7 @@ impl<'extensions> MoveVM<'extensions> { parameters, return_type, serialized_args, + &mut tracer.map(VMTracer::new), gas_meter, ) } @@ -255,6 +303,7 @@ impl<'extensions> MoveVM<'extensions> { param_types: Vec, return_types: Vec, serialized_args: Vec>, + tracer: &mut Option>, gas_meter: &mut impl GasMeter, ) -> VMResult { let arg_types = param_types @@ -289,13 +338,14 @@ impl<'extensions> MoveVM<'extensions> { .map_err(|err| err.finish(Location::Undefined))?; let return_values = interpreter::run( - func, - ty_args, - deserialized_args, &self.virtual_tables, self.vm_config.clone(), &mut self.native_extensions, + tracer, gas_meter, + func, + ty_args, + deserialized_args, )?; let serialized_return_values = serialize_return_values( diff --git a/external-crates/move/crates/move-vm-runtime/src/jit/execution/ast.rs b/external-crates/move/crates/move-vm-runtime/src/jit/execution/ast.rs index 4d9ff670b3a8b..6b724adf2aedf 100644 --- a/external-crates/move/crates/move-vm-runtime/src/jit/execution/ast.rs +++ b/external-crates/move/crates/move-vm-runtime/src/jit/execution/ast.rs @@ -27,6 +27,7 @@ use move_binary_format::{ VariantInstantiationHandle, VariantInstantiationHandleIndex, VariantJumpTable, VariantJumpTableIndex, VariantTag, }, + file_format_common::Opcodes, }; use move_core_types::{ gas_algebra::AbstractMemorySize, identifier::Identifier, language_storage::ModuleId, @@ -127,6 +128,7 @@ pub struct Function { pub index: FunctionDefinitionIndex, pub code: *const [Bytecode], pub parameters: Vec, + pub locals: Vec, pub return_: Vec, pub type_parameters: Vec, pub native: Option, @@ -1128,9 +1130,97 @@ impl Type { } } -//************************************************************************************************** +// ------------------------------------------------------------------------------------------------- +// Into +// ------------------------------------------------------------------------------------------------- + +impl Into for &Bytecode { + fn into(self) -> Opcodes { + match self { + Bytecode::Pop => Opcodes::POP, + Bytecode::Ret => Opcodes::RET, + Bytecode::BrTrue(_) => Opcodes::BR_TRUE, + Bytecode::BrFalse(_) => Opcodes::BR_FALSE, + Bytecode::Branch(_) => Opcodes::BRANCH, + Bytecode::LdU8(_) => Opcodes::LD_U8, + Bytecode::LdU64(_) => Opcodes::LD_U64, + Bytecode::LdU128(_) => Opcodes::LD_U128, + Bytecode::CastU8 => Opcodes::CAST_U8, + Bytecode::CastU64 => Opcodes::CAST_U64, + Bytecode::CastU128 => Opcodes::CAST_U128, + Bytecode::LdConst(_) => Opcodes::LD_CONST, + Bytecode::LdTrue => Opcodes::LD_TRUE, + Bytecode::LdFalse => Opcodes::LD_FALSE, + Bytecode::CopyLoc(_) => Opcodes::COPY_LOC, + Bytecode::MoveLoc(_) => Opcodes::MOVE_LOC, + Bytecode::StLoc(_) => Opcodes::ST_LOC, + Bytecode::DirectCall(_) => Opcodes::CALL, + Bytecode::VirtualCall(_) => Opcodes::CALL, + Bytecode::CallGeneric(_) => Opcodes::CALL_GENERIC, + Bytecode::Pack(_) => Opcodes::PACK, + Bytecode::PackGeneric(_) => Opcodes::PACK_GENERIC, + Bytecode::Unpack(_) => Opcodes::UNPACK, + Bytecode::UnpackGeneric(_) => Opcodes::UNPACK_GENERIC, + Bytecode::ReadRef => Opcodes::READ_REF, + Bytecode::WriteRef => Opcodes::WRITE_REF, + Bytecode::FreezeRef => Opcodes::FREEZE_REF, + Bytecode::MutBorrowLoc(_) => Opcodes::MUT_BORROW_LOC, + Bytecode::ImmBorrowLoc(_) => Opcodes::IMM_BORROW_LOC, + Bytecode::MutBorrowField(_) => Opcodes::MUT_BORROW_FIELD, + Bytecode::MutBorrowFieldGeneric(_) => Opcodes::MUT_BORROW_FIELD_GENERIC, + Bytecode::ImmBorrowField(_) => Opcodes::IMM_BORROW_FIELD, + Bytecode::ImmBorrowFieldGeneric(_) => Opcodes::IMM_BORROW_FIELD_GENERIC, + Bytecode::Add => Opcodes::ADD, + Bytecode::Sub => Opcodes::SUB, + Bytecode::Mul => Opcodes::MUL, + Bytecode::Mod => Opcodes::MOD, + Bytecode::Div => Opcodes::DIV, + Bytecode::BitOr => Opcodes::BIT_OR, + Bytecode::BitAnd => Opcodes::BIT_AND, + Bytecode::Xor => Opcodes::XOR, + Bytecode::Shl => Opcodes::SHL, + Bytecode::Shr => Opcodes::SHR, + Bytecode::Or => Opcodes::OR, + Bytecode::And => Opcodes::AND, + Bytecode::Not => Opcodes::NOT, + Bytecode::Eq => Opcodes::EQ, + Bytecode::Neq => Opcodes::NEQ, + Bytecode::Lt => Opcodes::LT, + Bytecode::Gt => Opcodes::GT, + Bytecode::Le => Opcodes::LE, + Bytecode::Ge => Opcodes::GE, + Bytecode::Abort => Opcodes::ABORT, + Bytecode::Nop => Opcodes::NOP, + Bytecode::VecPack(..) => Opcodes::VEC_PACK, + Bytecode::VecLen(_) => Opcodes::VEC_LEN, + Bytecode::VecImmBorrow(_) => Opcodes::VEC_IMM_BORROW, + Bytecode::VecMutBorrow(_) => Opcodes::VEC_MUT_BORROW, + Bytecode::VecPushBack(_) => Opcodes::VEC_PUSH_BACK, + Bytecode::VecPopBack(_) => Opcodes::VEC_POP_BACK, + Bytecode::VecUnpack(..) => Opcodes::VEC_UNPACK, + Bytecode::VecSwap(_) => Opcodes::VEC_SWAP, + Bytecode::LdU16(_) => Opcodes::LD_U16, + Bytecode::LdU32(_) => Opcodes::LD_U32, + Bytecode::LdU256(_) => Opcodes::LD_U256, + Bytecode::CastU16 => Opcodes::CAST_U16, + Bytecode::CastU32 => Opcodes::CAST_U32, + Bytecode::CastU256 => Opcodes::CAST_U256, + Bytecode::PackVariant(_) => Opcodes::PACK_VARIANT, + Bytecode::PackVariantGeneric(_) => Opcodes::PACK_VARIANT_GENERIC, + Bytecode::UnpackVariant(_) => Opcodes::UNPACK_VARIANT, + Bytecode::UnpackVariantImmRef(_) => Opcodes::UNPACK_VARIANT_IMM_REF, + Bytecode::UnpackVariantMutRef(_) => Opcodes::UNPACK_VARIANT_MUT_REF, + Bytecode::UnpackVariantGeneric(_) => Opcodes::UNPACK_VARIANT_GENERIC, + Bytecode::UnpackVariantGenericImmRef(_) => Opcodes::UNPACK_VARIANT_GENERIC_IMM_REF, + Bytecode::UnpackVariantGenericMutRef(_) => Opcodes::UNPACK_VARIANT_GENERIC_MUT_REF, + Bytecode::VariantSwitch(_) => Opcodes::VARIANT_SWITCH, + } + } +} + +// ------------------------------------------------------------------------------------------------- // Debug -//************************************************************************************************** +// ------------------------------------------------------------------------------------------------- impl ::std::fmt::Debug for Function { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/external-crates/move/crates/move-vm-runtime/src/jit/execution/translate.rs b/external-crates/move/crates/move-vm-runtime/src/jit/execution/translate.rs index 32cf562f2d010..b81f32230622a 100644 --- a/external-crates/move/crates/move-vm-runtime/src/jit/execution/translate.rs +++ b/external-crates/move/crates/move-vm-runtime/src/jit/execution/translate.rs @@ -561,12 +561,18 @@ fn alloc_function( .map(|tok| make_type(module, tok)) .collect::>>()?; // Native functions do not have a code unit - let (locals_len, jump_tables) = match &def.code { + let (locals_len, locals, jump_tables) = match &def.code { Some(code) => ( parameters.len() + module.signature_at(code.locals).0.len(), + module + .signature_at(code.locals) + .0 + .iter() + .map(|tok| make_type(module, tok)) + .collect::>>()?, code.jump_tables.clone(), ), - None => (0, vec![]), + None => (0, vec![], vec![]), }; let return_ = module .signature_at(handle.return_) @@ -581,6 +587,7 @@ fn alloc_function( is_entry, code: vm_pointer::null_ptr(), parameters, + locals, return_, type_parameters, native, diff --git a/external-crates/move/crates/move-vm-runtime/src/unit_tests/publish_tests.rs b/external-crates/move/crates/move-vm-runtime/src/unit_tests/publish_tests.rs index bd158d8e5f404..67f874a5454ba 100644 --- a/external-crates/move/crates/move-vm-runtime/src/unit_tests/publish_tests.rs +++ b/external-crates/move/crates/move-vm-runtime/src/unit_tests/publish_tests.rs @@ -7,16 +7,13 @@ use crate::{ dev_utils::{ - compilation_utils::compile_packages_in_file, - in_memory_test_adapter::InMemoryTestAdapter, + compilation_utils::compile_packages_in_file, in_memory_test_adapter::InMemoryTestAdapter, vm_test_adapter::VMTestAdapter, }, shared::gas::UnmeteredGasMeter, }; use move_core_types::{ - account_address::AccountAddress, - identifier::Identifier, - language_storage::ModuleId, + account_address::AccountAddress, identifier::Identifier, language_storage::ModuleId, }; #[test]