From e0b3636e3dac335e089c77bc3ad0bd233e815a0a Mon Sep 17 00:00:00 2001 From: Wolfgang Grieskamp Date: Sat, 12 Aug 2023 11:50:47 -0700 Subject: [PATCH 1/2] [compiler v2] Fix bugs around compilation of vector code This fixes a few bugs and closes #9629 - Usage of precompiled stdlib in transactional tests: v2 compiler currently (and probably never) supports precompiled modules, working around this - Stack-based bytecode generator created wrong target on generic function calls - Needed to make implicit conversion from `&mut` to `&` explicit in generated bytecode via Freeze operation Added some additional tests while debugging this. --- .../src/bytecode_generator.rs | 42 ++++++++++- .../function_generator.rs | 2 +- .../pipeline/livevar_analysis_processor.rs | 3 + .../reference_conversion.exp | 46 ++++++++++++ .../reference_conversion.move | 15 ++++ .../file-format-generator/generic_call.exp | 58 +++++++++++++++ .../file-format-generator/generic_call.move | 9 +++ .../tests/control_flow/sorter.exp | 3 + .../tests/control_flow/sorter.move | 73 +++++++++++++++++++ .../tests/evaluation_order/arg_order.exp | 3 + .../tests/evaluation_order/arg_order.move | 18 +++++ .../src/framework.rs | 22 +++++- 12 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 third_party/move/move-compiler-v2/tests/bytecode-generator/reference_conversion.exp create mode 100644 third_party/move/move-compiler-v2/tests/bytecode-generator/reference_conversion.move create mode 100644 third_party/move/move-compiler-v2/tests/file-format-generator/generic_call.exp create mode 100644 third_party/move/move-compiler-v2/tests/file-format-generator/generic_call.move create mode 100644 third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.exp create mode 100644 third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.move create mode 100644 third_party/move/move-compiler-v2/transactional-tests/tests/evaluation_order/arg_order.exp create mode 100644 third_party/move/move-compiler-v2/transactional-tests/tests/evaluation_order/arg_order.move diff --git a/third_party/move/move-compiler-v2/src/bytecode_generator.rs b/third_party/move/move-compiler-v2/src/bytecode_generator.rs index e7eff285c27bd..37afa08c4624d 100644 --- a/third_party/move/move-compiler-v2/src/bytecode_generator.rs +++ b/third_party/move/move-compiler-v2/src/bytecode_generator.rs @@ -285,7 +285,6 @@ impl<'env> Generator<'env> { // Dispatcher impl<'env> Generator<'env> { - /// Generate code, for the given expression, and store the result in the given temporary. fn gen(&mut self, targets: Vec, exp: &Exp) { match exp.as_ref() { ExpData::Invalid(id) => self.internal_error(*id, "invalid expression"), @@ -720,7 +719,25 @@ impl<'env> Generator<'env> { .env() .get_node_instantiation_opt(id) .unwrap_or_default(); - let args = self.gen_arg_list(args); + // Function calls can have implicit conversion of &mut to &, need to compute implicit + // conversions. + let param_types: Vec = self + .env() + .get_function(fun) + .get_parameters() + .into_iter() + .map(|Parameter(_, ty)| ty.instantiate(&type_args)) + .collect(); + if args.len() != param_types.len() { + self.internal_error(id, "inconsistent type arity"); + return; + } + let args = args + .iter() + .zip(param_types.into_iter()) + .map(|(e, t)| self.maybe_convert(e, &t)) + .collect::>(); + let args = self.gen_arg_list(&args); self.emit_with(id, |attr| { Bytecode::Call( attr, @@ -732,6 +749,27 @@ impl<'env> Generator<'env> { }) } + /// Convert the expression so it matches the expected type. This is currently only needed + /// for `&mut` to `&` conversion, in which case we need to to introduce a Freeze operation. + fn maybe_convert(&self, exp: &Exp, expected_ty: &Type) -> Exp { + let id = exp.node_id(); + let exp_ty = self.env().get_node_type(id); + if let ( + Type::Reference(ReferenceKind::Mutable, _), + Type::Reference(ReferenceKind::Immutable, et), + ) = (exp_ty, expected_ty) + { + let freeze_id = self + .env() + .new_node(self.env().get_node_loc(id), expected_ty.clone()); + self.env() + .set_node_instantiation(freeze_id, vec![et.as_ref().clone()]); + ExpData::Call(freeze_id, Operation::Freeze, vec![exp.clone()]).into_exp() + } else { + exp.clone() + } + } + fn gen_arg_list(&mut self, exps: &[Exp]) -> Vec { exps.iter().map(|exp| self.gen_arg(exp)).collect() } diff --git a/third_party/move/move-compiler-v2/src/file_format_generator/function_generator.rs b/third_party/move/move-compiler-v2/src/file_format_generator/function_generator.rs index 38fa62cf2dbf1..4807ef09b55ac 100644 --- a/third_party/move/move-compiler-v2/src/file_format_generator/function_generator.rs +++ b/third_party/move/move-compiler-v2/src/file_format_generator/function_generator.rs @@ -523,7 +523,7 @@ impl<'a> FunctionGenerator<'a> { let idx = self.gen.function_instantiation_index( &fun_ctx.module, &fun_ctx.loc, - fun_ctx.fun.func_env, + &fun_ctx.module.env.get_function(id), inst.to_vec(), ); self.emit(FF::Bytecode::CallGeneric(idx)) diff --git a/third_party/move/move-compiler-v2/src/pipeline/livevar_analysis_processor.rs b/third_party/move/move-compiler-v2/src/pipeline/livevar_analysis_processor.rs index df0a3baaef812..53ba45026a7c7 100644 --- a/third_party/move/move-compiler-v2/src/pipeline/livevar_analysis_processor.rs +++ b/third_party/move/move-compiler-v2/src/pipeline/livevar_analysis_processor.rs @@ -22,6 +22,9 @@ impl FunctionTargetProcessor for LiveVarAnalysisProcessor { mut data: FunctionData, _scc_opt: Option<&[FunctionEnv]>, ) -> FunctionData { + if fun_env.is_native() { + return data; + } // Call the existing live-var analysis from the move-prover. let target = FunctionTarget::new(fun_env, &data); let offset_to_live_refs = livevar_analysis::LiveVarAnnotation::from_map( diff --git a/third_party/move/move-compiler-v2/tests/bytecode-generator/reference_conversion.exp b/third_party/move/move-compiler-v2/tests/bytecode-generator/reference_conversion.exp new file mode 100644 index 0000000000000..a9fcef47dcab8 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/bytecode-generator/reference_conversion.exp @@ -0,0 +1,46 @@ +// ---- Model Dump +module 0x42::reference_conversion { + private fun deref(r: &u64) { + Deref(r) + } + private fun use_it() { + { + let x: u64 = 42; + { + let r: &mut u64 = Borrow(Mutable)(x); + r = 43; + reference_conversion::deref(r) + } + } + } +} // end 0x42::reference_conversion + +============ initial bytecode ================ + +[variant baseline] +fun reference_conversion::deref($t0: &u64): u64 { + var $t1: u64 + 0: $t1 := read_ref($t0) + 1: return $t1 +} + + +[variant baseline] +fun reference_conversion::use_it(): u64 { + var $t0: u64 + var $t1: u64 + var $t2: u64 + var $t3: &mut u64 + var $t4: &mut u64 + var $t5: u64 + var $t6: &u64 + 0: $t2 := 42 + 1: $t1 := move($t2) + 2: $t4 := borrow_local($t1) + 3: $t3 := move($t4) + 4: $t5 := 43 + 5: write_ref($t3, $t5) + 6: $t6 := freeze_ref($t3) + 7: $t0 := reference_conversion::deref($t6) + 8: return $t0 +} diff --git a/third_party/move/move-compiler-v2/tests/bytecode-generator/reference_conversion.move b/third_party/move/move-compiler-v2/tests/bytecode-generator/reference_conversion.move new file mode 100644 index 0000000000000..d22d3d256f4c5 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/bytecode-generator/reference_conversion.move @@ -0,0 +1,15 @@ +module 0x42::reference_conversion { + + fun deref(r: &u64): u64 { + *r + } + + fun use_it(): u64 { + let x = 42; + let r = &mut x; + *r = 43; + deref(r) + } + + +} diff --git a/third_party/move/move-compiler-v2/tests/file-format-generator/generic_call.exp b/third_party/move/move-compiler-v2/tests/file-format-generator/generic_call.exp new file mode 100644 index 0000000000000..8c072a9559fe7 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/file-format-generator/generic_call.exp @@ -0,0 +1,58 @@ +============ initial bytecode ================ + +[variant baseline] +fun Test::foo($t0: u64): u64 { + var $t1: u64 + 0: $t1 := Test::identity($t0) + 1: return $t1 +} + + +[variant baseline] +fun Test::identity<#0>($t0: #0): #0 { + var $t1: #0 + 0: $t1 := move($t0) + 1: return $t1 +} + +============ after LiveVarAnalysisProcessor: ================ + +[variant baseline] +fun Test::foo($t0: u64): u64 { + var $t1: u64 + # live vars: $t0 + 0: $t1 := Test::identity($t0) + # live vars: $t1 + 1: return $t1 +} + + +[variant baseline] +fun Test::identity<#0>($t0: #0): #0 { + var $t1: #0 + # live vars: $t0 + 0: $t1 := move($t0) + # live vars: $t1 + 1: return $t1 +} + + +============ disassembled file-format ================== +// Move bytecode v6 +module 42.Test { + + +foo(Arg0: u64): u64 { +B0: + 0: MoveLoc[0](Arg0: u64) + 1: Call identity(u64): u64 + 2: Ret +} +identity(Arg0: Ty0): Ty0 { +B0: + 0: MoveLoc[0](Arg0: Ty0) + 1: StLoc[1](loc0: Ty0) + 2: MoveLoc[1](loc0: Ty0) + 3: Ret +} +} diff --git a/third_party/move/move-compiler-v2/tests/file-format-generator/generic_call.move b/third_party/move/move-compiler-v2/tests/file-format-generator/generic_call.move new file mode 100644 index 0000000000000..350acc32f1d41 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/file-format-generator/generic_call.move @@ -0,0 +1,9 @@ +module 0x42::Test { + fun identity(x: T): T { + x + } + + fun foo(x: u64): u64 { + identity(x) + } +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.exp new file mode 100644 index 0000000000000..a6db107b3b9ca --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.exp @@ -0,0 +1,3 @@ +processed 2 tasks + +==> Compiler v2 delivered same results! diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.move b/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.move new file mode 100644 index 0000000000000..b57f00f2ad851 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.move @@ -0,0 +1,73 @@ +//# publish +module 0x42::heap { + use std::vector; + + fun create1(): vector { + vector[3, 2, 1, 5, 8, 4] + } + + fun create2(): vector { + vector[1, 2, 3, 4, 5, 8] + } + + fun vcopy(x: &vector): vector { + let y : vector = vector::empty(); + let i : u64 = 0; + let l : u64 = vector::length(x); + while (i < l) { + vector::push_back(&mut y, *vector::borrow(x, i)); + i = i + 1; + }; + y + } + + fun sort(x: &mut vector) { + let i: u64 = 0; + while (i < vector::length(x)) { + let j: u64 = i + 1; + while (j < vector::length(x)) { + if (*vector::borrow(x, i) > *vector::borrow(x, j)) { + vector::swap(x, i, j) + }; + j = j + 1; + }; + i = i + 1; + } + } + + fun array_equals(x: &vector, y: &vector): bool { + let l1: u64 = vector::length(x); + let l2: u64 = vector::length(y); + if (l1 != l2) { + return false + }; + let i: u64 = 0; + while (i < l1) { + if (*vector::borrow(x, i) != *vector::borrow(y, i)) { + return false + }; + i = i + 1; + }; + true + } + + public fun main() { + let x: vector = create1(); + let y: vector = create2(); + let z: vector = vcopy(&x); + assert!(array_equals(&x, &z), 23); + assert!(array_equals(&y, &y), 29); + sort(&mut x); + assert!(array_equals(&y, &x), 31); + assert!(array_equals(&x, &y), 29); + assert!(!array_equals(&x, &z), 31); + } +} + +//# run +script { +use 0x42::heap::main; +fun mymain() { + main(); +} +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/evaluation_order/arg_order.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/evaluation_order/arg_order.exp new file mode 100644 index 0000000000000..a6db107b3b9ca --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/evaluation_order/arg_order.exp @@ -0,0 +1,3 @@ +processed 2 tasks + +==> Compiler v2 delivered same results! diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/evaluation_order/arg_order.move b/third_party/move/move-compiler-v2/transactional-tests/tests/evaluation_order/arg_order.move new file mode 100644 index 0000000000000..b51c62f6fff81 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/evaluation_order/arg_order.move @@ -0,0 +1,18 @@ +//# publish +module 0x42::test { + public fun two_args(x: u64, b: bool): u64 { + if (b) { + x + } else { + 0 + } + } +} + +//# run +script { + use 0x42::test::two_args; + fun mymain() { + assert!(two_args(42, true) == 42, 1); + } +} diff --git a/third_party/move/testing-infra/transactional-test-runner/src/framework.rs b/third_party/move/testing-infra/transactional-test-runner/src/framework.rs index 6001828b21949..30b1327b8ca83 100644 --- a/third_party/move/testing-infra/transactional-test-runner/src/framework.rs +++ b/third_party/move/testing-infra/transactional-test-runner/src/framework.rs @@ -242,6 +242,7 @@ pub trait MoveTestAdapter<'a>: Sized { // Run the V2 compiler if requested SyntaxChoice::Source if run_config == TestRunConfig::CompilerV2 => { let ((module, _), warning_opt) = compile_source_unit_v2( + state.pre_compiled_deps, state.named_address_mapping.clone(), &state.source_files().cloned().collect::>(), data_path.to_owned(), @@ -326,6 +327,7 @@ pub trait MoveTestAdapter<'a>: Sized { // Run the V2 compiler if requested. SyntaxChoice::Source if run_config == TestRunConfig::CompilerV2 => { let ((_, script), warning_opt) = compile_source_unit_v2( + state.pre_compiled_deps, state.named_address_mapping.clone(), &state.source_files().cloned().collect::>(), data_path.to_owned(), @@ -603,6 +605,7 @@ impl<'a> CompiledState<'a> { } fn compile_source_unit_v2( + pre_compiled_deps: Option<&FullyCompiledProgram>, named_address_mapping: BTreeMap, deps: &[String], path: String, @@ -610,9 +613,26 @@ fn compile_source_unit_v2( (Option, Option), Option, )> { + let deps = if let Some(p) = pre_compiled_deps { + // The v2 compiler does not (and perhaps never) supports precompiled programs, so + // compile from the sources again, computing the directories where they are found. + let mut dirs: BTreeSet<_> = p + .files + .iter() + .filter_map(|(_, (file_name, _))| { + Path::new(file_name.as_str()) + .parent() + .map(|p| p.to_string_lossy().to_string()) + }) + .collect(); + dirs.extend(deps.iter().cloned()); + dirs.into_iter().collect() + } else { + deps.to_vec() + }; let options = move_compiler_v2::Options { sources: vec![path], - dependencies: deps.to_vec(), + dependencies: deps, named_address_mapping: named_address_mapping .into_iter() .map(|(alias, addr)| format!("{}={}", alias, addr)) From f79054074856d21d917ef6df2ea5f76d32c8e223 Mon Sep 17 00:00:00 2001 From: Wolfgang Grieskamp Date: Sun, 13 Aug 2023 00:15:10 -0700 Subject: [PATCH 2/2] Adding a new option `--print-bytecode` which can be provided to the `//# publish` and `//# run` transactional test runner command. This is the applied (for now) to the `sorter` test case only. Also introduced logic to map well-known vector functions to the associated builtin opcodes. --- .../function_generator.rs | 11 +- .../file_format_generator/module_generator.rs | 38 ++ .../tests/control_flow/sorter.exp | 581 ++++++++++++++++++ .../tests/control_flow/sorter.move | 4 +- .../src/framework.rs | 91 ++- .../transactional-test-runner/src/tasks.rs | 4 + 6 files changed, 710 insertions(+), 19 deletions(-) diff --git a/third_party/move/move-compiler-v2/src/file_format_generator/function_generator.rs b/third_party/move/move-compiler-v2/src/file_format_generator/function_generator.rs index 4807ef09b55ac..2f76bab4e21a0 100644 --- a/third_party/move/move-compiler-v2/src/file_format_generator/function_generator.rs +++ b/third_party/move/move-compiler-v2/src/file_format_generator/function_generator.rs @@ -512,7 +512,16 @@ impl<'a> FunctionGenerator<'a> { ) { let fun_ctx = ctx.fun_ctx; self.abstract_push_args(ctx, source); - if inst.is_empty() { + if let Some(opcode) = ctx.fun_ctx.module.get_well_known_function_code( + &ctx.fun_ctx.loc, + id, + Some( + self.gen + .signature(&ctx.fun_ctx.module, &ctx.fun_ctx.loc, inst.to_vec()), + ), + ) { + self.emit(opcode) + } else if inst.is_empty() { let idx = self.gen.function_index( &fun_ctx.module, &fun_ctx.loc, diff --git a/third_party/move/move-compiler-v2/src/file_format_generator/module_generator.rs b/third_party/move/move-compiler-v2/src/file_format_generator/module_generator.rs index b0a1671a4d1ab..4c94c2b1d9620 100644 --- a/third_party/move/move-compiler-v2/src/file_format_generator/module_generator.rs +++ b/third_party/move/move-compiler-v2/src/file_format_generator/module_generator.rs @@ -16,6 +16,7 @@ use move_binary_format::{ }; use move_core_types::{account_address::AccountAddress, identifier::Identifier}; use move_model::{ + ast::Address, model::{ FieldEnv, FunId, FunctionEnv, GlobalEnv, Loc, ModuleEnv, ModuleId, Parameter, QualifiedId, StructEnv, StructId, TypeParameter, TypeParameterKind, @@ -623,4 +624,41 @@ impl<'env> ModuleContext<'env> { value as FF::TableIndex } } + + /// Get the file format opcode for a well-known function. This applies currently to a set + /// vector functions which have builtin opcodes. Gets passed an optional type instantiation + /// in form of a signature. + pub fn get_well_known_function_code( + &self, + loc: &Loc, + qid: QualifiedId, + inst_sign: Option, + ) -> Option { + let fun = self.env.get_function(qid); + let mod_name = fun.module_env.get_name(); + if mod_name.addr() != &Address::Numerical(AccountAddress::ONE) { + return None; + } + let pool = self.env.symbol_pool(); + if pool.string(mod_name.name()).as_str() == "vector" { + if let Some(inst) = inst_sign { + match pool.string(fun.get_name()).as_str() { + "empty" => Some(FF::Bytecode::VecPack(inst, 0)), + "length" => Some(FF::Bytecode::VecLen(inst)), + "borrow" => Some(FF::Bytecode::VecImmBorrow(inst)), + "borrow_mut" => Some(FF::Bytecode::VecMutBorrow(inst)), + "push_back" => Some(FF::Bytecode::VecPushBack(inst)), + "pop_back" => Some(FF::Bytecode::VecPopBack(inst)), + "destroy_empty" => Some(FF::Bytecode::VecUnpack(inst, 0)), + "swap" => Some(FF::Bytecode::VecSwap(inst)), + _ => None, + } + } else { + self.internal_error(loc, "expected type instantiation for vector operation"); + None + } + } else { + None + } + } } diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.exp index a6db107b3b9ca..2c4c770e675bf 100644 --- a/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.exp +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.exp @@ -1,3 +1,584 @@ processed 2 tasks +task 0 'publish'. lines 1-65: + + + ==> Compiler v2 delivered same results! + +>>> V1 Compiler { +== BEGIN Bytecode == +// Move bytecode v6 +module 42.heap { + + +array_equals(Arg0: &vector, Arg1: &vector): bool { +L0: loc2: u64 +B0: + 0: CopyLoc[0](Arg0: &vector) + 1: VecLen(7) + 2: StLoc[3](loc1: u64) + 3: CopyLoc[1](Arg1: &vector) + 4: VecLen(7) + 5: StLoc[4](loc2: u64) + 6: CopyLoc[3](loc1: u64) + 7: MoveLoc[4](loc2: u64) + 8: Neq + 9: BrFalse(16) +B1: + 10: MoveLoc[1](Arg1: &vector) + 11: Pop + 12: MoveLoc[0](Arg0: &vector) + 13: Pop + 14: LdFalse + 15: Ret +B2: + 16: LdU64(0) + 17: StLoc[2](loc0: u64) +B3: + 18: CopyLoc[2](loc0: u64) + 19: CopyLoc[3](loc1: u64) + 20: Lt + 21: BrFalse(44) +B4: + 22: Branch(23) +B5: + 23: CopyLoc[0](Arg0: &vector) + 24: CopyLoc[2](loc0: u64) + 25: VecImmBorrow(7) + 26: ReadRef + 27: CopyLoc[1](Arg1: &vector) + 28: CopyLoc[2](loc0: u64) + 29: VecImmBorrow(7) + 30: ReadRef + 31: Neq + 32: BrFalse(39) +B6: + 33: MoveLoc[1](Arg1: &vector) + 34: Pop + 35: MoveLoc[0](Arg0: &vector) + 36: Pop + 37: LdFalse + 38: Ret +B7: + 39: MoveLoc[2](loc0: u64) + 40: LdU64(1) + 41: Add + 42: StLoc[2](loc0: u64) + 43: Branch(18) +B8: + 44: MoveLoc[1](Arg1: &vector) + 45: Pop + 46: MoveLoc[0](Arg0: &vector) + 47: Pop + 48: LdTrue + 49: Ret +} +create1(): vector { +B0: + 0: LdConst[0](Vector(U64): [6, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0]) + 1: Ret +} +create2(): vector { +B0: + 0: LdConst[1](Vector(U64): [6, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0]) + 1: Ret +} +public main() { +L0: loc0: vector +L1: loc1: vector +L2: loc2: vector +B0: + 0: Call create1(): vector + 1: StLoc[0](loc0: vector) + 2: Call create2(): vector + 3: StLoc[1](loc1: vector) + 4: ImmBorrowLoc[0](loc0: vector) + 5: Call vcopy(&vector): vector + 6: StLoc[2](loc2: vector) + 7: ImmBorrowLoc[0](loc0: vector) + 8: ImmBorrowLoc[2](loc2: vector) + 9: Call array_equals(&vector, &vector): bool + 10: BrFalse(12) +B1: + 11: Branch(14) +B2: + 12: LdU64(23) + 13: Abort +B3: + 14: ImmBorrowLoc[1](loc1: vector) + 15: ImmBorrowLoc[1](loc1: vector) + 16: Call array_equals(&vector, &vector): bool + 17: BrFalse(19) +B4: + 18: Branch(21) +B5: + 19: LdU64(29) + 20: Abort +B6: + 21: MutBorrowLoc[0](loc0: vector) + 22: Call sort(&mut vector) + 23: ImmBorrowLoc[1](loc1: vector) + 24: ImmBorrowLoc[0](loc0: vector) + 25: Call array_equals(&vector, &vector): bool + 26: BrFalse(28) +B7: + 27: Branch(30) +B8: + 28: LdU64(31) + 29: Abort +B9: + 30: ImmBorrowLoc[0](loc0: vector) + 31: ImmBorrowLoc[1](loc1: vector) + 32: Call array_equals(&vector, &vector): bool + 33: BrFalse(35) +B10: + 34: Branch(37) +B11: + 35: LdU64(29) + 36: Abort +B12: + 37: ImmBorrowLoc[0](loc0: vector) + 38: ImmBorrowLoc[2](loc2: vector) + 39: Call array_equals(&vector, &vector): bool + 40: Not + 41: BrFalse(43) +B13: + 42: Branch(45) +B14: + 43: LdU64(31) + 44: Abort +B15: + 45: Ret +} +sort(Arg0: &mut vector) { +L0: loc1: u64 +L1: loc2: &mut vector +L2: loc3: u64 +L3: loc4: u64 +L4: loc5: u64 +B0: + 0: LdU64(0) + 1: StLoc[5](loc4: u64) +B1: + 2: CopyLoc[5](loc4: u64) + 3: CopyLoc[0](Arg0: &mut vector) + 4: FreezeRef + 5: VecLen(7) + 6: Lt + 7: BrFalse(54) +B2: + 8: Branch(9) +B3: + 9: CopyLoc[5](loc4: u64) + 10: LdU64(1) + 11: Add + 12: StLoc[6](loc5: u64) +B4: + 13: CopyLoc[6](loc5: u64) + 14: CopyLoc[0](Arg0: &mut vector) + 15: FreezeRef + 16: VecLen(7) + 17: Lt + 18: BrFalse(49) +B5: + 19: Branch(20) +B6: + 20: CopyLoc[0](Arg0: &mut vector) + 21: CopyLoc[5](loc4: u64) + 22: StLoc[2](loc1: u64) + 23: StLoc[1](loc0: &mut vector) + 24: CopyLoc[0](Arg0: &mut vector) + 25: CopyLoc[6](loc5: u64) + 26: StLoc[4](loc3: u64) + 27: StLoc[3](loc2: &mut vector) + 28: MoveLoc[1](loc0: &mut vector) + 29: FreezeRef + 30: MoveLoc[2](loc1: u64) + 31: VecImmBorrow(7) + 32: ReadRef + 33: MoveLoc[3](loc2: &mut vector) + 34: FreezeRef + 35: MoveLoc[4](loc3: u64) + 36: VecImmBorrow(7) + 37: ReadRef + 38: Gt + 39: BrFalse(44) +B7: + 40: CopyLoc[0](Arg0: &mut vector) + 41: CopyLoc[5](loc4: u64) + 42: CopyLoc[6](loc5: u64) + 43: VecSwap(7) +B8: + 44: MoveLoc[6](loc5: u64) + 45: LdU64(1) + 46: Add + 47: StLoc[6](loc5: u64) + 48: Branch(13) +B9: + 49: MoveLoc[5](loc4: u64) + 50: LdU64(1) + 51: Add + 52: StLoc[5](loc4: u64) + 53: Branch(2) +B10: + 54: MoveLoc[0](Arg0: &mut vector) + 55: Pop + 56: Ret +} +vcopy(Arg0: &vector): vector { +L0: loc1: u64 +L1: loc2: vector +B0: + 0: VecPack(7, 0) + 1: StLoc[3](loc2: vector) + 2: LdU64(0) + 3: StLoc[1](loc0: u64) + 4: CopyLoc[0](Arg0: &vector) + 5: VecLen(7) + 6: StLoc[2](loc1: u64) +B1: + 7: CopyLoc[1](loc0: u64) + 8: CopyLoc[2](loc1: u64) + 9: Lt + 10: BrFalse(23) +B2: + 11: Branch(12) +B3: + 12: MutBorrowLoc[3](loc2: vector) + 13: CopyLoc[0](Arg0: &vector) + 14: CopyLoc[1](loc0: u64) + 15: VecImmBorrow(7) + 16: ReadRef + 17: VecPushBack(7) + 18: MoveLoc[1](loc0: u64) + 19: LdU64(1) + 20: Add + 21: StLoc[1](loc0: u64) + 22: Branch(7) +B4: + 23: MoveLoc[0](Arg0: &vector) + 24: Pop + 25: MoveLoc[3](loc2: vector) + 26: Ret +} +} +== END Bytecode == + +task 1 'run'. lines 67-73: + +== BEGIN Bytecode == +// Move bytecode v6 +script { +use 0000000000000000000000000000000000000000000000000000000000000042::heap; + + + + +main() { +B0: + 0: Call heap::main() + 1: Ret +} +} +== END Bytecode == +} + +>>> V2 Compiler { +== BEGIN Bytecode == +// Move bytecode v6 +module 42.heap { + + +array_equals(Arg0: &vector, Arg1: &vector): bool { +L0: loc2: u64 +L1: loc3: u64 +B0: + 0: CopyLoc[0](Arg0: &vector) + 1: VecLen(2) + 2: StLoc[2](loc0: u64) + 3: CopyLoc[1](Arg1: &vector) + 4: VecLen(2) + 5: StLoc[3](loc1: u64) + 6: CopyLoc[2](loc0: u64) + 7: MoveLoc[3](loc1: u64) + 8: Neq + 9: BrFalse(13) +B1: + 10: LdConst[0](Bool: [0]) + 11: Ret +B2: + 12: Branch(13) +B3: + 13: LdConst[1](U64: [0, 0, 0, 0, 0, 0, 0, 0]) + 14: StLoc[4](loc2: u64) +B4: + 15: CopyLoc[4](loc2: u64) + 16: CopyLoc[2](loc0: u64) + 17: Lt + 18: BrFalse(39) +B5: + 19: CopyLoc[0](Arg0: &vector) + 20: CopyLoc[4](loc2: u64) + 21: VecImmBorrow(2) + 22: ReadRef + 23: CopyLoc[1](Arg1: &vector) + 24: CopyLoc[4](loc2: u64) + 25: VecImmBorrow(2) + 26: ReadRef + 27: Neq + 28: BrFalse(32) +B6: + 29: LdConst[0](Bool: [0]) + 30: Ret +B7: + 31: Branch(32) +B8: + 32: LdConst[2](U64: [1, 0, 0, 0, 0, 0, 0, 0]) + 33: StLoc[5](loc3: u64) + 34: MoveLoc[4](loc2: u64) + 35: MoveLoc[5](loc3: u64) + 36: Add + 37: StLoc[4](loc2: u64) + 38: Branch(40) +B9: + 39: Branch(41) +B10: + 40: Branch(15) +B11: + 41: LdConst[3](Bool: [1]) + 42: Ret +} +create1(): vector { +B0: + 0: LdConst[4](U64: [3, 0, 0, 0, 0, 0, 0, 0]) + 1: LdConst[5](U64: [2, 0, 0, 0, 0, 0, 0, 0]) + 2: LdConst[2](U64: [1, 0, 0, 0, 0, 0, 0, 0]) + 3: LdConst[6](U64: [5, 0, 0, 0, 0, 0, 0, 0]) + 4: LdConst[7](U64: [8, 0, 0, 0, 0, 0, 0, 0]) + 5: LdConst[8](U64: [4, 0, 0, 0, 0, 0, 0, 0]) + 6: VecPack(2, 6) + 7: Ret +} +create2(): vector { +B0: + 0: LdConst[2](U64: [1, 0, 0, 0, 0, 0, 0, 0]) + 1: LdConst[5](U64: [2, 0, 0, 0, 0, 0, 0, 0]) + 2: LdConst[4](U64: [3, 0, 0, 0, 0, 0, 0, 0]) + 3: LdConst[8](U64: [4, 0, 0, 0, 0, 0, 0, 0]) + 4: LdConst[6](U64: [5, 0, 0, 0, 0, 0, 0, 0]) + 5: LdConst[7](U64: [8, 0, 0, 0, 0, 0, 0, 0]) + 6: VecPack(2, 6) + 7: Ret +} +public main() { +L0: loc0: vector +L1: loc1: vector +L2: loc2: vector +B0: + 0: Call create1(): vector + 1: StLoc[0](loc0: vector) + 2: Call create2(): vector + 3: StLoc[1](loc1: vector) + 4: ImmBorrowLoc[0](loc0: vector) + 5: Call vcopy(&vector): vector + 6: StLoc[2](loc2: vector) + 7: ImmBorrowLoc[0](loc0: vector) + 8: ImmBorrowLoc[2](loc2: vector) + 9: Call array_equals(&vector, &vector): bool + 10: BrFalse(12) +B1: + 11: Branch(14) +B2: + 12: LdConst[9](U64: [23, 0, 0, 0, 0, 0, 0, 0]) + 13: Abort +B3: + 14: ImmBorrowLoc[1](loc1: vector) + 15: ImmBorrowLoc[1](loc1: vector) + 16: Call array_equals(&vector, &vector): bool + 17: BrFalse(19) +B4: + 18: Branch(21) +B5: + 19: LdConst[10](U64: [29, 0, 0, 0, 0, 0, 0, 0]) + 20: Abort +B6: + 21: MutBorrowLoc[0](loc0: vector) + 22: Call sort(&mut vector) + 23: ImmBorrowLoc[1](loc1: vector) + 24: ImmBorrowLoc[0](loc0: vector) + 25: Call array_equals(&vector, &vector): bool + 26: BrFalse(28) +B7: + 27: Branch(30) +B8: + 28: LdConst[11](U64: [31, 0, 0, 0, 0, 0, 0, 0]) + 29: Abort +B9: + 30: ImmBorrowLoc[0](loc0: vector) + 31: ImmBorrowLoc[1](loc1: vector) + 32: Call array_equals(&vector, &vector): bool + 33: BrFalse(35) +B10: + 34: Branch(37) +B11: + 35: LdConst[10](U64: [29, 0, 0, 0, 0, 0, 0, 0]) + 36: Abort +B12: + 37: ImmBorrowLoc[0](loc0: vector) + 38: ImmBorrowLoc[2](loc2: vector) + 39: Call array_equals(&vector, &vector): bool + 40: Not + 41: BrFalse(43) +B13: + 42: Branch(45) +B14: + 43: LdConst[11](U64: [31, 0, 0, 0, 0, 0, 0, 0]) + 44: Abort +B15: + 45: Ret +} +sort(Arg0: &mut vector) { +L0: loc1: u64 +L1: loc2: u64 +L2: loc3: u64 +L3: loc4: u64 +L4: loc5: u64 +L5: loc6: u64 +B0: + 0: LdConst[1](U64: [0, 0, 0, 0, 0, 0, 0, 0]) + 1: StLoc[1](loc0: u64) +B1: + 2: CopyLoc[0](Arg0: &mut vector) + 3: FreezeRef + 4: VecLen(2) + 5: StLoc[2](loc1: u64) + 6: CopyLoc[1](loc0: u64) + 7: MoveLoc[2](loc1: u64) + 8: Lt + 9: BrFalse(57) +B2: + 10: LdConst[2](U64: [1, 0, 0, 0, 0, 0, 0, 0]) + 11: StLoc[3](loc2: u64) + 12: CopyLoc[1](loc0: u64) + 13: MoveLoc[3](loc2: u64) + 14: Add + 15: StLoc[4](loc3: u64) +B3: + 16: CopyLoc[0](Arg0: &mut vector) + 17: FreezeRef + 18: VecLen(2) + 19: StLoc[5](loc4: u64) + 20: CopyLoc[4](loc3: u64) + 21: MoveLoc[5](loc4: u64) + 22: Lt + 23: BrFalse(48) +B4: + 24: CopyLoc[0](Arg0: &mut vector) + 25: FreezeRef + 26: CopyLoc[1](loc0: u64) + 27: VecImmBorrow(2) + 28: ReadRef + 29: CopyLoc[0](Arg0: &mut vector) + 30: FreezeRef + 31: CopyLoc[4](loc3: u64) + 32: VecImmBorrow(2) + 33: ReadRef + 34: Gt + 35: BrFalse(41) +B5: + 36: CopyLoc[0](Arg0: &mut vector) + 37: CopyLoc[1](loc0: u64) + 38: CopyLoc[4](loc3: u64) + 39: VecSwap(2) + 40: Branch(41) +B6: + 41: LdConst[2](U64: [1, 0, 0, 0, 0, 0, 0, 0]) + 42: StLoc[6](loc5: u64) + 43: MoveLoc[4](loc3: u64) + 44: MoveLoc[6](loc5: u64) + 45: Add + 46: StLoc[4](loc3: u64) + 47: Branch(49) +B7: + 48: Branch(50) +B8: + 49: Branch(16) +B9: + 50: LdConst[2](U64: [1, 0, 0, 0, 0, 0, 0, 0]) + 51: StLoc[7](loc6: u64) + 52: MoveLoc[1](loc0: u64) + 53: MoveLoc[7](loc6: u64) + 54: Add + 55: StLoc[1](loc0: u64) + 56: Branch(58) +B10: + 57: Branch(59) +B11: + 58: Branch(2) +B12: + 59: Ret +} +vcopy(Arg0: &vector): vector { +L0: loc1: u64 +L1: loc2: u64 +L2: loc3: u64 +L3: loc4: vector +B0: + 0: VecPack(2, 0) + 1: StLoc[1](loc0: vector) + 2: LdConst[1](U64: [0, 0, 0, 0, 0, 0, 0, 0]) + 3: StLoc[2](loc1: u64) + 4: CopyLoc[0](Arg0: &vector) + 5: VecLen(2) + 6: StLoc[3](loc2: u64) +B1: + 7: CopyLoc[2](loc1: u64) + 8: CopyLoc[3](loc2: u64) + 9: Lt + 10: BrFalse(24) +B2: + 11: MutBorrowLoc[1](loc0: vector) + 12: CopyLoc[0](Arg0: &vector) + 13: CopyLoc[2](loc1: u64) + 14: VecImmBorrow(2) + 15: ReadRef + 16: VecPushBack(2) + 17: LdConst[2](U64: [1, 0, 0, 0, 0, 0, 0, 0]) + 18: StLoc[4](loc3: u64) + 19: MoveLoc[2](loc1: u64) + 20: MoveLoc[4](loc3: u64) + 21: Add + 22: StLoc[2](loc1: u64) + 23: Branch(25) +B3: + 24: Branch(26) +B4: + 25: Branch(7) +B5: + 26: MoveLoc[1](loc0: vector) + 27: StLoc[5](loc4: vector) + 28: MoveLoc[5](loc4: vector) + 29: Ret +} +} +== END Bytecode == + +task 1 'run'. lines 67-73: + +== BEGIN Bytecode == +// Move bytecode v6 +script { +use 0000000000000000000000000000000000000000000000000000000000000042::heap; + + + + +main() { +B0: + 0: Call heap::main() + 1: Ret +} +} +== END Bytecode == +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.move b/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.move index b57f00f2ad851..b8095984b9f3b 100644 --- a/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.move +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/control_flow/sorter.move @@ -1,4 +1,4 @@ -//# publish +//# publish --print-bytecode module 0x42::heap { use std::vector; @@ -64,7 +64,7 @@ module 0x42::heap { } } -//# run +//# run --print-bytecode script { use 0x42::heap::main; fun mymain() { diff --git a/third_party/move/testing-infra/transactional-test-runner/src/framework.rs b/third_party/move/testing-infra/transactional-test-runner/src/framework.rs index 30b1327b8ca83..0908032d8b63f 100644 --- a/third_party/move/testing-infra/transactional-test-runner/src/framework.rs +++ b/third_party/move/testing-infra/transactional-test-runner/src/framework.rs @@ -44,7 +44,9 @@ use move_disassembler::disassembler::{Disassembler, DisassemblerOptions}; use move_ir_types::location::Spanned; use move_symbol_pool::Symbol; use move_vm_runtime::session::SerializedReturnValues; +use once_cell::sync::Lazy; use rayon::iter::Either; +use regex::Regex; use std::{ collections::{BTreeMap, BTreeSet, VecDeque}, fmt::{Debug, Write as FmtWrite}, @@ -215,18 +217,21 @@ pub trait MoveTestAdapter<'a>: Sized { Either::Right(compile_ir_module(state.dep_modules(), data_path)?) }, }; - let source_mapping = SourceMapping::new_from_view( - match &compiled { - Either::Left(script) => BinaryIndexedView::Script(script), - Either::Right(module) => BinaryIndexedView::Module(module), - }, - Spanned::unsafe_no_loc(()).loc, - ) - .expect("Unable to build dummy source mapping"); - let disassembler = Disassembler::new(source_mapping, DisassemblerOptions::new()); + let view = match &compiled { + Either::Left(script) => BinaryIndexedView::Script(script), + Either::Right(module) => BinaryIndexedView::Module(module), + }; + let disassembler = disassembler_for_view(view); Ok(Some(disassembler.disassemble()?)) }, - TaskCommand::Publish(PublishCommand { gas_budget, syntax }, extra_args) => { + TaskCommand::Publish( + PublishCommand { + gas_budget, + syntax, + print_bytecode, + }, + extra_args, + ) => { let syntax = syntax.unwrap_or_else(|| self.default_syntax()); let data = match data { Some(f) => f, @@ -282,12 +287,24 @@ pub trait MoveTestAdapter<'a>: Sized { (None, module, None) }, }; - let (output, module) = self.publish_module( + let printed = if print_bytecode { + let disassembler = disassembler_for_view(BinaryIndexedView::Module(&module)); + Some(format!( + "\n== BEGIN Bytecode ==\n{}\n== END Bytecode ==", + disassembler.disassemble()? + )) + } else { + None + }; + let (mut output, module) = self.publish_module( module, named_addr_opt.map(|s| Identifier::new(s.as_str()).unwrap()), gas_budget, extra_args, )?; + if print_bytecode { + output = merge_output(output, printed); + } match syntax { SyntaxChoice::Source => self.compiled_state().add_with_source_file( named_addr_opt, @@ -309,6 +326,7 @@ pub trait MoveTestAdapter<'a>: Sized { gas_budget, syntax, name: None, + print_bytecode, }, extra_args, ) => { @@ -356,15 +374,25 @@ pub trait MoveTestAdapter<'a>: Sized { }, SyntaxChoice::IR => (compile_ir_script(state.dep_modules(), data_path)?, None), }; + let printed = if print_bytecode { + let disassembler = disassembler_for_view(BinaryIndexedView::Script(&script)); + Some(format!( + "\n== BEGIN Bytecode ==\n{}\n== END Bytecode ==", + disassembler.disassemble()? + )) + } else { + None + }; let args = self.compiled_state().resolve_args(args)?; let type_args = self.compiled_state().resolve_type_args(type_args)?; - let (output, return_values) = + let (mut output, return_values) = self.execute_script(script, type_args, signers, args, gas_budget, extra_args)?; let rendered_return_value = display_return_values(return_values); - Ok(merge_output( - warning_opt, - merge_output(output, rendered_return_value), - )) + output = merge_output(output, rendered_return_value); + if print_bytecode { + output = merge_output(output, printed); + } + Ok(merge_output(warning_opt, output)) }, TaskCommand::Run( RunCommand { @@ -374,6 +402,7 @@ pub trait MoveTestAdapter<'a>: Sized { gas_budget, syntax, name: Some((raw_addr, module_name, name)), + print_bytecode: _, }, extra_args, ) => { @@ -429,6 +458,12 @@ pub trait MoveTestAdapter<'a>: Sized { } } +fn disassembler_for_view(view: BinaryIndexedView) -> Disassembler { + let source_mapping = + SourceMapping::new_from_view(view, Spanned::unsafe_no_loc(()).loc).expect("source mapping"); + Disassembler::new(source_mapping, DisassemblerOptions::new()) +} + fn display_return_values(return_values: SerializedReturnValues) -> Option { let SerializedReturnValues { mutable_reference_outputs, @@ -765,6 +800,7 @@ where (vec![config], false) // either V1 or V2 }; let mut last_output = String::new(); + let mut bytecode_print_output = BTreeMap::::new(); for run_config in runs { let mut output = String::new(); let mut tasks = taskify::< @@ -811,6 +847,17 @@ where for task in tasks { handle_known_task(&mut output, &mut adapter, task); } + // Extract any bytecode outputs, they should not be part of the diff. + static BYTECODE_REX: Lazy = Lazy::new(|| { + Regex::new("(?m)== BEGIN Bytecode ==(.|\n|\r)*== END Bytecode ==").unwrap() + }); + while let Some(m) = BYTECODE_REX.find(&output) { + bytecode_print_output + .entry(run_config) + .or_default() + .push_str(&output.drain(m.range()).collect::()); + } + // If there is a previous output, compare to that one if !last_output.is_empty() && last_output != output { let diff = format_diff_no_color(&last_output, &output); @@ -824,6 +871,18 @@ where // Indicate in output that we passed comparison test last_output += "\n==> Compiler v2 delivered same results!\n" } + // Dump printed bytecode at last + for (config, out) in bytecode_print_output { + last_output += &format!( + "\n>>> {} {{\n{}\n}}\n", + match config { + TestRunConfig::CompilerV1 => "V1 Compiler", + TestRunConfig::CompilerV2 => "V2 Compiler", + _ => panic!("unexpected test config"), + }, + out + ); + } handle_expected_output(path, last_output)?; Ok(()) } diff --git a/third_party/move/testing-infra/transactional-test-runner/src/tasks.rs b/third_party/move/testing-infra/transactional-test-runner/src/tasks.rs index 7fa7edaa864b6..76ac8f54c0cc3 100644 --- a/third_party/move/testing-infra/transactional-test-runner/src/tasks.rs +++ b/third_party/move/testing-infra/transactional-test-runner/src/tasks.rs @@ -233,6 +233,8 @@ pub struct PublishCommand { pub gas_budget: Option, #[clap(long = "syntax")] pub syntax: Option, + #[clap(long = "print-bytecode")] + pub print_bytecode: bool, } #[derive(Debug, Parser)] @@ -261,6 +263,8 @@ pub struct RunCommand { pub syntax: Option, #[clap(name = "NAME", value_parser = parse_qualified_module_access)] pub name: Option<(ParsedAddress, Identifier, Identifier)>, + #[clap(long = "print-bytecode")] + pub print_bytecode: bool, } #[derive(Debug, Parser)]