diff --git a/third_party/move/mono-move/core/src/instruction/mod.rs b/third_party/move/mono-move/core/src/instruction/mod.rs index f0ac413f417..8a6ea968f6c 100644 --- a/third_party/move/mono-move/core/src/instruction/mod.rs +++ b/third_party/move/mono-move/core/src/instruction/mod.rs @@ -362,7 +362,9 @@ pub enum MicroOp { imm: Box<[u8; 32]>, }, - /// Copy 8 bytes from `src` to `dst`. + /// Copy 8 bytes from `src` to `dst`. Neither slot need be 8-aligned: a + /// size-8 aggregate (e.g. `{u32, u32}`, align 4) can sit at a 4-aligned + /// field offset. Move8 { dst: FrameOffset, src: FrameOffset, @@ -953,6 +955,25 @@ pub enum MicroOp { size: u32, }, + /// Read within a heap object at a static offset: `obj_ref` is a fat pointer + /// whose target holds the heap object pointer; copies `size` bytes at byte + /// `offset` within the object into `dst`. + HeapReadOffset { + dst: FrameOffset, + obj_ref: FrameOffset, + offset: u32, + size: u32, + }, + + /// Write within a heap object at a static offset: copies `size` bytes + /// from `src` to byte `offset` within the object. + HeapWriteOffset { + obj_ref: FrameOffset, + offset: u32, + src: FrameOffset, + size: u32, + }, + /// Copies the 16-byte fat pointer from `src_ref` to `dst_ref`, while /// adding `offset` to the offset part of the fat pointer. /// @@ -1177,12 +1198,13 @@ pub enum MicroOp { /// If `None` (the runtime variant does not declare the field), aborts with /// a variant mismatch (expected Move semantics). /// - /// `off` is the full object-relative offset (already includes - /// `ENUM_DATA_OFFSET`); indexing by tag handles a field shared across - /// variants that sits at different byte offsets in each. An access whose - /// handle covers every variant, all placing the field at one fixed offset, - /// uses the static `enum_borrow` (`HeapBorrow`) fast path instead. - EnumBorrowVariantField { + /// Each `off` is measured from the object's start — the `ENUM_DATA_OFFSET` + /// tag skip is folded in when the op is constructed. Indexing by tag + /// handles a field shared across variants that sits at different byte + /// offsets in each. An access whose handle covers every variant, all + /// placing the field at one fixed offset, uses the [`MicroOp::HeapBorrow`] + /// fast path instead. + EnumBorrowVariantFieldByTag { dst: FrameOffset, enum_ref: FrameOffset, // TODO(perf): the boxed slice is a separate allocation and a @@ -1215,38 +1237,33 @@ pub enum MicroOp { variant: VariantTag, }, - /// Read a variant field by value through an enum reference at a static - /// object-relative `offset`. The uniform-offset fast path: the access's - /// handle covers every variant, all placing the field at the same offset, - /// so no runtime tag dispatch or mismatch check is needed. Derefs the - /// 16-byte `enum_ref` fat pointer to the heap object (a double deref), then - /// copies `size` bytes from `obj + offset` into `dst`. Fuses the - /// `HeapBorrow` + `ReadRef` (the divergent path still routes through a - /// scratch reference). - /// - // TODO(cleanup): nothing here is enum-specific — this reads any field through a - // reference at a static offset. Rename to a non-enum-specific name as part - // of a naming-consistency pass. - EnumReadVariantField { + /// Read a variant field by value through an enum reference, selecting the + /// field's object byte offset by the runtime variant tag: derefs the + /// 16-byte `enum_ref` fat pointer to the heap object, reads the tag, and + /// if `offsets[tag]` is `Some(off)` copies `size` bytes at byte `off` + /// within the object into `dst`; a `None` hole (the runtime variant does + /// not declare the field) aborts with a variant mismatch. Each `off` is + /// measured from the object's start — the `ENUM_DATA_OFFSET` tag skip is + /// folded in when the op is constructed. When every variant places the + /// field at one offset, [`MicroOp::HeapReadOffset`] is used instead. + EnumReadVariantFieldByTag { dst: FrameOffset, enum_ref: FrameOffset, - offset: u32, + // See `EnumBorrowVariantFieldByTag::offsets` for the boxed-slice rationale + // and the `size_of::() == 48` constraint. + offsets: Box<[Option]>, size: u32, }, - /// Write a variant field by value through an enum reference at a static - /// object-relative `offset` (uniform-offset fast path; see - /// [`MicroOp::EnumReadVariantField`]). Derefs the 16-byte `enum_ref` fat - /// pointer to the heap object, then copies `size` bytes from `src` into - /// `obj + offset`. Fuses `HeapBorrow` + `WriteRef` (the divergent path - /// still routes through a scratch reference). - /// - // TODO(cleanup): nothing here is enum-specific — this writes any field through a - // reference at a static offset, and is reusable when fusing. Rename to a - // non-enum-specific name as part of a naming-consistency pass. - EnumWriteVariantField { + /// Write a variant field by value through an enum reference, selecting the + /// field's object byte offset by the runtime variant tag (symmetric + /// counterpart to [`MicroOp::EnumReadVariantFieldByTag`]). Aborts with a + /// variant mismatch when `offsets[tag]` is `None`. When every variant + /// places the field at one offset, [`MicroOp::HeapWriteOffset`] is used + /// instead. + EnumWriteVariantFieldByTag { enum_ref: FrameOffset, - offset: u32, + offsets: Box<[Option]>, src: FrameOffset, size: u32, }, @@ -1944,14 +1961,14 @@ impl fmt::Display for MicroOp { dst.0, enum_ref.0, variant ) }, - MicroOp::EnumBorrowVariantField { + MicroOp::EnumBorrowVariantFieldByTag { dst, enum_ref, offsets, } => { write!( f, - "EnumBorrowVariantField [{}] <- &(*[{}]) by tag {:?}", + "EnumBorrowVariantFieldByTag [{}] <- &(*[{}]) by tag {:?}", dst.0, enum_ref.0, offsets ) }, @@ -1973,28 +1990,52 @@ impl fmt::Display for MicroOp { dst.0, descriptor_id, variant ) }, - MicroOp::EnumReadVariantField { + MicroOp::HeapReadOffset { dst, - enum_ref, + obj_ref, offset, size, } => { write!( f, - "EnumReadVariantField({}) [{}] <- (*[{}]).{}", - size, dst.0, enum_ref.0, offset + "HeapReadOffset({}) [{}] <- (*[{}]).{}", + size, dst.0, obj_ref.0, offset ) }, - MicroOp::EnumWriteVariantField { - enum_ref, + MicroOp::HeapWriteOffset { + obj_ref, offset, src, size, } => { write!( f, - "EnumWriteVariantField({}) (*[{}]).{} <- [{}]", - size, enum_ref.0, offset, src.0 + "HeapWriteOffset({}) (*[{}]).{} <- [{}]", + size, obj_ref.0, offset, src.0 + ) + }, + MicroOp::EnumReadVariantFieldByTag { + dst, + enum_ref, + offsets, + size, + } => { + write!( + f, + "EnumReadVariantFieldByTag({}) [{}] <- (*[{}]) by tag {:?}", + size, dst.0, enum_ref.0, offsets + ) + }, + MicroOp::EnumWriteVariantFieldByTag { + enum_ref, + offsets, + src, + size, + } => { + write!( + f, + "EnumWriteVariantFieldByTag({}) (*[{}]) <- [{}] by tag {:?}", + size, enum_ref.0, src.0, offsets ) }, MicroOp::DeepCopyHeapPtrs { base, offsets } => { @@ -2267,10 +2308,12 @@ impl MicroOp { | MicroOp::BoolAnd { .. } | MicroOp::BoolOr { .. } | MicroOp::EnumTestTag { .. } - | MicroOp::EnumBorrowVariantField { .. } + | MicroOp::EnumBorrowVariantFieldByTag { .. } | MicroOp::EnumCheckVariant { .. } - | MicroOp::EnumReadVariantField { .. } - | MicroOp::EnumWriteVariantField { .. } => false, + | MicroOp::HeapReadOffset { .. } + | MicroOp::HeapWriteOffset { .. } + | MicroOp::EnumReadVariantFieldByTag { .. } + | MicroOp::EnumWriteVariantFieldByTag { .. } => false, } } @@ -2343,24 +2386,30 @@ impl MicroOp { } } + /// Shift each present data-region-relative offset by [`ENUM_DATA_OFFSET`] to + /// a full object-relative offset; `None` holes (variants lacking the field) + /// pass through. + fn to_object_relative_offsets(data_relative_offsets: &[Option]) -> Box<[Option]> { + data_relative_offsets + .iter() + .map(|maybe_offset| maybe_offset.map(|offset| ENUM_DATA_OFFSET as u32 + offset)) + .collect() + } + /// Tag-dispatched variant-field borrow. `data_relative_offsets[tag]` is the /// field's data-region-relative offset in that variant, or `None` if the /// variant does not declare the field (borrowing it aborts). Offsets are /// shifted by `ENUM_DATA_OFFSET` to full object-relative offsets, matching /// `enum_borrow`. - pub fn enum_borrow_variant_field( + pub fn enum_borrow_variant_field_by_tag( enum_ref: FrameOffset, data_relative_offsets: &[Option], dst: FrameOffset, ) -> Self { - let offsets = data_relative_offsets - .iter() - .map(|maybe_offset| maybe_offset.map(|offset| ENUM_DATA_OFFSET as u32 + offset)) - .collect(); - MicroOp::EnumBorrowVariantField { + MicroOp::EnumBorrowVariantFieldByTag { dst, enum_ref, - offsets, + offsets: Self::to_object_relative_offsets(data_relative_offsets), } } @@ -2380,14 +2429,14 @@ impl MicroOp { /// data-region-relative and shifted by `ENUM_DATA_OFFSET` to a full /// object-relative offset, matching [`MicroOp::enum_borrow`]. pub fn enum_read_variant_field( - enum_ref: FrameOffset, + obj_ref: FrameOffset, field_offset: u32, dst: FrameOffset, size: u32, ) -> Self { - MicroOp::EnumReadVariantField { + MicroOp::HeapReadOffset { dst, - enum_ref, + obj_ref, offset: ENUM_DATA_OFFSET as u32 + field_offset, size, } @@ -2396,19 +2445,54 @@ impl MicroOp { /// Uniform-offset variant-field write by value; offset handling mirrors /// [`MicroOp::enum_read_variant_field`]. pub fn enum_write_variant_field( - enum_ref: FrameOffset, + obj_ref: FrameOffset, field_offset: u32, src: FrameOffset, size: u32, ) -> Self { - MicroOp::EnumWriteVariantField { - enum_ref, + MicroOp::HeapWriteOffset { + obj_ref, offset: ENUM_DATA_OFFSET as u32 + field_offset, src, size, } } + /// Tag-dispatched variant-field read by value. `data_relative_offsets[tag]` + /// is the field's data-region-relative offset in that variant, or `None` + /// when the variant lacks the field (reading it aborts). Offsets are shifted + /// by `ENUM_DATA_OFFSET` to full object-relative offsets, matching + /// [`MicroOp::enum_borrow_variant_field_by_tag`]. + pub fn enum_read_variant_field_by_tag( + enum_ref: FrameOffset, + data_relative_offsets: &[Option], + dst: FrameOffset, + size: u32, + ) -> Self { + MicroOp::EnumReadVariantFieldByTag { + dst, + enum_ref, + offsets: Self::to_object_relative_offsets(data_relative_offsets), + size, + } + } + + /// Tag-dispatched variant-field write by value; offset handling mirrors + /// [`MicroOp::enum_read_variant_field_by_tag`]. + pub fn enum_write_variant_field_by_tag( + enum_ref: FrameOffset, + data_relative_offsets: &[Option], + src: FrameOffset, + size: u32, + ) -> Self { + MicroOp::EnumWriteVariantFieldByTag { + enum_ref, + offsets: Self::to_object_relative_offsets(data_relative_offsets), + src, + size, + } + } + /// Deep-copy the owned heap pointers at the given byte offsets within the /// value at `base`, making a freshly byte-copied value independent. pub fn deep_copy_heap_ptrs(base: FrameOffset, offsets: Box<[u32]>) -> Self { diff --git a/third_party/move/mono-move/core/src/vm_error.rs b/third_party/move/mono-move/core/src/vm_error.rs index 1d8459e034c..005d6693986 100644 --- a/third_party/move/mono-move/core/src/vm_error.rs +++ b/third_party/move/mono-move/core/src/vm_error.rs @@ -712,6 +712,9 @@ pub enum GasInstrumentationError { #[error("CallClosure signature must start with a Function type")] ClosureSignatureNotFunction, + + #[error("empty field chain")] + EmptyFieldChain, } impl IntoExecutionError for GasInstrumentationError { @@ -728,7 +731,8 @@ impl IntoExecutionError for GasInstrumentationError { | NotAnEnum | CallReturnNoSignatureType { .. } | ClosureSignatureEmpty - | ClosureSignatureNotFunction => ExecutionErrorKind::InvariantViolation, + | ClosureSignatureNotFunction + | EmptyFieldChain => ExecutionErrorKind::InvariantViolation, } } } @@ -907,8 +911,12 @@ pub enum LoweringError { #[error("{op}: dst/src aliases but no enum-pointer scratch reserved")] EnumPtrScratchMissing { op: &'static str }, - #[error("{op}: no scratch slot reserved")] - VariantFieldScratchMissing { op: &'static str }, + // ---- fused field chains ---- + #[error("empty field chain")] + EmptyFieldChain, + + #[error("field chain offset overflow")] + FieldChainOffsetOverflow, #[error("Xfer({xfer}) read without a prior def in this block")] XferReadWithoutDef { xfer: u16 }, @@ -972,7 +980,8 @@ impl IntoExecutionError for LoweringError { | VectorTypeNoDescriptor { .. } | VariantOrdinalOutOfRange { .. } | EnumPtrScratchMissing { .. } - | VariantFieldScratchMissing { .. } + | EmptyFieldChain + | FieldChainOffsetOverflow | XferReadWithoutDef { .. } => ExecutionErrorKind::InvariantViolation, } } diff --git a/third_party/move/mono-move/runtime/src/interpreter.rs b/third_party/move/mono-move/runtime/src/interpreter.rs index d8c8601822c..0f541492a48 100644 --- a/third_party/move/mono-move/runtime/src/interpreter.rs +++ b/third_party/move/mono-move/runtime/src/interpreter.rs @@ -666,6 +666,39 @@ fn u256_from_u8(x: u8) -> U256 { U256::from_le_bytes(bytes) } +/// Shared prologue of the tag-dispatched variant-field ops +/// (`EnumBorrowVariantFieldByTag`, `Enum{Read,Write}VariantFieldByTag`): deref +/// the enum fat pointer at `enum_ref` to the heap object, read the runtime +/// tag, and select the field's object-relative offset from the per-tag table. +/// A tag past the table is heap corruption (a well-formed enum's tag is always +/// in range) and surfaces as an invariant violation; a `None` hole means the +/// runtime variant does not declare the field, a user-level mismatch. +/// +/// # Safety +/// +/// `fp + enum_ref` must hold a valid fat pointer to a live enum heap object. +#[inline(always)] +unsafe fn variant_field_loc( + fp: *mut u8, + enum_ref: FrameOffset, + offsets: &[Option], +) -> RuntimeResult<(*mut u8, u32)> { + // SAFETY: forwarded from this function's contract. + let (ref_base, ref_off) = unsafe { read_fat_ptr(fp, enum_ref) }; + let obj_ptr = unsafe { read_ptr(ref_base, ref_off as usize) }; + let tag = unsafe { read_enum_tag(obj_ptr) }; + let Some(variant_offset) = offsets.get(tag as usize) else { + invariant_violation!(EnumTagOutOfRange { + tag, + variant_count: offsets.len(), + }); + }; + match variant_offset { + Some(offset) => Ok((obj_ptr, *offset)), + None => Err(RuntimeError::EnumVariantMismatch { tag }), + } +} + // Dispatch on an [`IntOperand`]: for each variant, invoke the caller's // `$action!` macro with three arguments — `($rust_ty, $sign, $rhs_value)`. // `$sign` is the literal token `unsigned` or `signed`, letting `$action!` @@ -1550,8 +1583,11 @@ impl InterpreterContext<'_> { }, MicroOp::Move8 { dst, src } => { - let v = read_u64(fp, src); - write_u64(fp, dst, v); + // The slots need not be 8-aligned (see `Move8`'s doc), + // so the accesses must be unaligned — same codegen as + // aligned loads on x86-64/aarch64. + let v = (fp.add(src.into()) as *const u64).read_unaligned(); + (fp.add(dst.into()) as *mut u64).write_unaligned(v); }, MicroOp::Move { dst, src, size } => { @@ -2022,32 +2058,15 @@ impl InterpreterContext<'_> { write_bool(fp, dst, tag == variant); }, - MicroOp::EnumBorrowVariantField { + MicroOp::EnumBorrowVariantFieldByTag { dst, enum_ref, ref offsets, } => { - // Deref the enum fat pointer to the heap object, read the - // tag, then borrow the field at that variant's offset. - let (ref_base, ref_off) = read_fat_ptr(fp, enum_ref); - let obj_ptr = read_ptr(ref_base, ref_off as usize); - let tag = read_enum_tag(obj_ptr); - let Some(variant_offset) = offsets.get(tag as usize) else { - // A tag past the variant table is heap corruption (a - // well-formed enum's tag is always in range), not a - // user-level mismatch. Surface it as an invariant - // violation. - invariant_violation!(EnumTagOutOfRange { - tag, - variant_count: offsets.len(), - }); - }; - match variant_offset { - Some(offset) => write_fat_ptr(fp, dst, obj_ptr, *offset as u64), - // Tag in range but this variant does not declare the - // field (move semantics for this is a runtime error). - None => return Err(RuntimeError::EnumVariantMismatch { tag }), - } + // The field reference written to `dst` is a fat pointer + // pairing the heap object with the tag-selected offset. + let (obj_ptr, offset) = variant_field_loc(fp, enum_ref, offsets)?; + write_fat_ptr(fp, dst, obj_ptr, offset as u64); }, MicroOp::EnumCheckVariant { enum_ptr, variant } => { @@ -2073,20 +2092,19 @@ impl InterpreterContext<'_> { write_ptr(fp, dst, obj_ptr); }, - MicroOp::EnumReadVariantField { + MicroOp::HeapReadOffset { dst, - enum_ref, + obj_ref, offset, size, } => { - // Double deref of the enum fat pointer to the heap object, - // then copy the field bytes directly — no intermediate - // scratch reference. The offset is a static uniform offset - // (no tag dispatch). - let (ref_base, ref_off) = read_fat_ptr(fp, enum_ref); + // Deref the fat pointer to the heap object, then copy + // the bytes at the static offset directly (no tag + // dispatch). + let (ref_base, ref_off) = read_fat_ptr(fp, obj_ref); let obj_ptr = read_ptr(ref_base, ref_off as usize); - // Non-overlapping: `dst` is a stack-region slot, the field - // is heap-object bytes. + // Non-overlapping: `dst` is a stack-region slot, the + // source is heap-object bytes. std::ptr::copy_nonoverlapping( obj_ptr.add(offset as usize), fp.add(dst.into()), @@ -2094,16 +2112,50 @@ impl InterpreterContext<'_> { ); }, - MicroOp::EnumWriteVariantField { - enum_ref, + MicroOp::HeapWriteOffset { + obj_ref, offset, src, size, } => { - let (ref_base, ref_off) = read_fat_ptr(fp, enum_ref); + let (ref_base, ref_off) = read_fat_ptr(fp, obj_ref); let obj_ptr = read_ptr(ref_base, ref_off as usize); - // Non-overlapping: `src` is a stack-region slot, the field - // is heap-object bytes. + // Non-overlapping: `src` is a stack-region slot, the + // destination is heap-object bytes. + std::ptr::copy_nonoverlapping( + fp.add(src.into()), + obj_ptr.add(offset as usize), + size as usize, + ); + }, + + MicroOp::EnumReadVariantFieldByTag { + dst, + enum_ref, + ref offsets, + size, + } => { + // Fuses the tag-dispatched borrow and the read, no + // scratch. Non-overlapping: `dst` is a stack-region + // slot, the field is heap-object bytes. + let (obj_ptr, offset) = variant_field_loc(fp, enum_ref, offsets)?; + std::ptr::copy_nonoverlapping( + obj_ptr.add(offset as usize), + fp.add(dst.into()), + size as usize, + ); + }, + + MicroOp::EnumWriteVariantFieldByTag { + enum_ref, + ref offsets, + src, + size, + } => { + // Fuses the tag-dispatched borrow and the write, no + // scratch. Non-overlapping: `src` is a stack-region + // slot, the field is heap-object bytes. + let (obj_ptr, offset) = variant_field_loc(fp, enum_ref, offsets)?; std::ptr::copy_nonoverlapping( fp.add(src.into()), obj_ptr.add(offset as usize), diff --git a/third_party/move/mono-move/runtime/src/verifier.rs b/third_party/move/mono-move/runtime/src/verifier.rs index 1d5337de4e7..078695aaffd 100644 --- a/third_party/move/mono-move/runtime/src/verifier.rs +++ b/third_party/move/mono-move/runtime/src/verifier.rs @@ -558,7 +558,7 @@ impl FunctionVerifier<'_, P> { self.check_frame_access_1(pc, dst); }, - MicroOp::EnumBorrowVariantField { dst, enum_ref, .. } => { + MicroOp::EnumBorrowVariantFieldByTag { dst, enum_ref, .. } => { self.check_frame_access(Some(pc), enum_ref, 16); self.check_frame_access(Some(pc), dst, 16); }, @@ -576,28 +576,48 @@ impl FunctionVerifier<'_, P> { self.check_enum_new(pc, descriptor_id, variant); }, - MicroOp::EnumReadVariantField { - dst, - enum_ref, + // Read's `dst` and write's `src` are both the size-wide frame slot + // the value moves to/from; the checks are otherwise identical. + MicroOp::HeapReadOffset { + dst: value_slot, + obj_ref, + offset, + size, + } + | MicroOp::HeapWriteOffset { + obj_ref, offset, + src: value_slot, size, } => { - self.check_frame_access(Some(pc), enum_ref, 16); + self.check_frame_access(Some(pc), obj_ref, 16); self.check_nonzero_size(pc, size); self.check_ref_offset_size_no_overflow(pc, offset, size); - self.check_frame_access(Some(pc), dst, size); + self.check_frame_access(Some(pc), value_slot, size); }, - MicroOp::EnumWriteVariantField { + // Read's `dst` and write's `src` are both the size-wide frame slot + // the field value moves to/from; the checks are otherwise identical. + MicroOp::EnumReadVariantFieldByTag { + dst: value_slot, enum_ref, - offset, - src, + ref offsets, + size, + } + | MicroOp::EnumWriteVariantFieldByTag { + src: value_slot, + enum_ref, + ref offsets, size, } => { self.check_frame_access(Some(pc), enum_ref, 16); self.check_nonzero_size(pc, size); - self.check_ref_offset_size_no_overflow(pc, offset, size); - self.check_frame_access(Some(pc), src, size); + // Any tag may be selected at runtime, so every present offset + // must keep `offset + size` within `u32`. + for offset in offsets.iter().flatten() { + self.check_ref_offset_size_no_overflow(pc, *offset, size); + } + self.check_frame_access(Some(pc), value_slot, size); }, // Each owned heap pointer at `base + off` is an 8-byte frame slot. diff --git a/third_party/move/mono-move/specializer/src/destack/analysis.rs b/third_party/move/mono-move/specializer/src/destack/analysis.rs index 051ccb3ac9b..f5f637730ee 100644 --- a/third_party/move/mono-move/specializer/src/destack/analysis.rs +++ b/third_party/move/mono-move/specializer/src/destack/analysis.rs @@ -82,7 +82,7 @@ use crate::error::{XferVerifierError, XferVerifierResult}; #[cfg(debug_assertions)] use crate::stackless_exec_ir::instr_utils::for_each_value_use; use crate::stackless_exec_ir::{ - instr_utils::{clobbers_xfer, for_each_def, for_each_use}, + instr_utils::{clobbers_xfer, for_each_def, for_each_use, mut_local_borrow_target}, Instr, Slot, }; use shared_dsa::{UnorderedMap, UnorderedSet}; @@ -176,11 +176,10 @@ impl BlockAnalysis { // cannot appear }, }); - // Both shapes yield a `&mut` into the local's storage. - if let Instr::MutBorrowLoc(_, local @ Slot::Home(_)) - | Instr::MutBorrowLocField(_, _, _, local @ Slot::Home(_)) = instr - { - home_mut_borrow_pos.entry(*local).or_default().push(i); + // A `&mut` into a home local's storage can defer a write through + // the returned reference, conflicting with coalescing. + if let Some(local @ Slot::Home(_)) = mut_local_borrow_target(instr) { + home_mut_borrow_pos.entry(local).or_default().push(i); } } diff --git a/third_party/move/mono-move/specializer/src/destack/optimize.rs b/third_party/move/mono-move/specializer/src/destack/optimize.rs index 2b6a714ced8..fcf30c75701 100644 --- a/third_party/move/mono-move/specializer/src/destack/optimize.rs +++ b/third_party/move/mono-move/specializer/src/destack/optimize.rs @@ -10,7 +10,8 @@ use crate::stackless_exec_ir::{ instr_utils::{ - for_each_def, for_each_slot, for_each_use, remap_all_slots_with, remap_source_slots_with, + for_each_def, for_each_slot, for_each_use, mut_local_borrow_target, remap_all_slots_with, + remap_source_slots_with, }, FunctionIR, Instr, ModuleIR, Slot, }; @@ -75,15 +76,20 @@ fn copy_propagation(func: &mut FunctionIR) { for instr in &mut block.instrs { remap_source_slots_with(instr, |s| *subst.get(&s).unwrap_or(&s)); - // Kill subst for locals whose storage may be mutated without - // a full def: mut borrows (writes go through the ref) and - // `WriteLocalField` (partial write). - if let Instr::MutBorrowLoc(_, src) - | Instr::MutBorrowLocField(_, _, _, src) - | Instr::WriteLocalField(_, _, src, _) = instr - { - subst.remove(src); - subst.retain(|_, v| v != src); + // Kill subst for locals whose storage may be mutated without a + // full def: mut borrows (writes go through the ref) and + // `WriteLocalField` (partial write). `WriteLocalFieldChain` + // reports its root local as a def, so it is covered by the + // `for_each_def` kill below; the ref-rooted `WriteFieldChain` + // writes through a reference whose originating mut borrow was + // already killed here. + let mut hidden_write = mut_local_borrow_target(instr); + if let Instr::WriteLocalField(_, _, local, _) = instr { + hidden_write = Some(*local); + } + if let Some(src) = hidden_write { + subst.remove(&src); + subst.retain(|_, v| *v != src); } for_each_def(instr, |d| { diff --git a/third_party/move/mono-move/specializer/src/destack/ssa_function.rs b/third_party/move/mono-move/specializer/src/destack/ssa_function.rs index cdc628c8452..97ac9177223 100644 --- a/third_party/move/mono-move/specializer/src/destack/ssa_function.rs +++ b/third_party/move/mono-move/specializer/src/destack/ssa_function.rs @@ -1,7 +1,7 @@ // Copyright (c) Aptos Foundation // Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE -//! Intermediate SSA representation and pre-allocation fusion passes. +//! Intermediate SSA representation and pre-slot-allocation fusion passes. //! //! SSA is intra-block and applies only to value IDs, not to params or locals //! (which are mutable across blocks). Because the operand stack is empty at @@ -9,10 +9,11 @@ //! once within its block and never crosses a block boundary. use crate::stackless_exec_ir::{ - instr_utils::{extract_imm_value, is_commutative}, - BasicBlock, BinaryOp, Instr, + instr_utils::{extract_imm_value, for_each_value_use, is_commutative}, + BasicBlock, BinaryOp, FieldPath, Instr, Slot, }; use mono_move_core::types::InternedType; +use shared_dsa::UnorderedMap; /// Intermediate SSA representation of a single function, before slot allocation. pub(crate) struct SSAFunction { @@ -25,12 +26,15 @@ pub(crate) struct SSAFunction { } impl SSAFunction { - /// Run all pre-allocation instruction fusion passes. + /// Run all instruction fusion passes; they precede slot allocation, so + /// fused-away value IDs never receive frame slots. pub(crate) fn with_fusion_passes(mut self) -> Self { // TODO(perf): right now, we have each different fusion operation to be a separate pass. // This is easier to reason about, but we could make it more efficient by // combining the passes. for block in &mut self.blocks { + // Collapse depth-N inline-field chains first, on the raw borrow chain. + fuse_field_chains(&mut block.instrs); fuse_pairs(&mut block.instrs, try_fuse_field_access); // Consumes ReadField/WriteField produced above, maintain this // ordering between fusion passes. @@ -161,6 +165,269 @@ fn try_fuse_compare_branch(first: &Instr, second: &Instr) -> Option { } } +/// Where a fused field chain is rooted. +enum ChainRoot { + /// A by-value inline-struct local (the `*BorrowLoc` is absorbed). + Local(Slot), + /// A reference (any non-chain ref value). + Ref(Slot), +} + +/// How a chain ends. +enum ChainTerminal { + /// `ReadRef` of the deepest reference — a by-value field read. `dst` is + /// the `ReadRef`'s destination, receiving the field's bytes. + Read { dst: Slot }, + /// `WriteRef` through the deepest reference — a field write. `val` is the + /// `WriteRef`'s value operand, whose bytes are stored into the field. + Write { val: Slot }, + /// The deepest reference escapes (returned, passed, frozen, ...) — a + /// borrow. `dst` is that reference itself, which the fused instruction + /// takes over defining; its consumers stay unchanged. + Borrow { dst: Slot }, +} + +/// A fused chain and how it rewrites the instruction stream. +struct FusedChain { + /// The fused instruction. + instr: Instr, + /// The single position that survives the rewrite: `instr` overwrites the + /// original instruction here (the terminal read/write, or the last borrow + /// for a borrow chain); every other absorbed position is deleted. + place_at: usize, + /// End (exclusive) of the contiguous borrow run starting at the scan + /// position; the run's positions other than `place_at` are deleted, and + /// scanning resumes here. + borrow_end: usize, +} + +/// Minimum number of field borrows a fused chain collapses; shorter runs are +/// left to the pairwise passes. Shared by `fuse_field_chains`'s pre-scan gate +/// and `try_build_chain`'s depth check. +const MIN_CHAIN_DEPTH: usize = 2; + +/// Pre-slot-allocation pass: collapse a run of inline-struct field selections +/// whose intermediate references are dead after the chain into one fused chain +/// instruction (the erased references never receive frame slots). Runs on the +/// raw borrow chain before the pairwise field fusions. +/// +/// A run is fused only when every erased intermediate reference is single-use +/// (so removing its def is sound). The borrow links are contiguous; the +/// terminal read/write may sit a few instructions later (e.g. after a write's +/// right-hand side is evaluated). Sinking the borrow chain's address compute to +/// that terminal is sound because a reference is an address compute +/// (`base + offset`), not a value snapshot, and Move forbids modifying the root +/// while it stays borrowed. Depth-1 runs are left to the pairwise passes. +fn fuse_field_chains(instrs: &mut Vec) { + // Every fusable chain contains at least `MIN_CHAIN_DEPTH` struct field + // borrows; skip the analysis allocations for blocks that cannot hold one + // (the common case). + let field_borrows = instrs + .iter() + .filter(|instr| matches!(instr, Instr::ImmBorrowField(..) | Instr::MutBorrowField(..))) + .take(MIN_CHAIN_DEPTH) + .count(); + if field_borrows < MIN_CHAIN_DEPTH { + return; + } + + let use_info = value_use_info(instrs); + let len = instrs.len(); + // Rewrite plan, allocated lazily on the first fused chain and applied by + // the compaction loop below: `placed[pos]` replaces the instruction at + // `pos` with the fused one, `removed[pos]` deletes it. `placed` stays + // empty when nothing fuses. + let mut placed: Vec> = Vec::new(); + let mut removed: Vec = Vec::new(); + + let mut start = 0; + while start < len { + // Skip a position holding an earlier chain's sunk terminal. + if !placed.is_empty() && placed[start].is_some() { + start += 1; + continue; + } + match try_build_chain(instrs, start, &use_info) { + Some(chain) => { + if placed.is_empty() { + placed = vec![None; len]; + removed = vec![false; len]; + } + // The absorbed positions are always free: the cursor never + // re-enters a fused run (it resumes at `borrow_end`), and an + // earlier chain's sunk terminal cannot lie inside this run (a + // run is contiguous borrows; a terminal is a read/write). + debug_assert!((start..chain.borrow_end).all(|idx| !removed[idx])); + debug_assert!(!removed[chain.place_at] && placed[chain.place_at].is_none()); + removed[start..chain.borrow_end].fill(true); + // No-op for a sunk terminal (`place_at >= borrow_end`). + removed[chain.place_at] = false; + placed[chain.place_at] = Some(chain.instr); + start = chain.borrow_end; + }, + None => start += 1, + } + } + if placed.is_empty() { + return; + } + + // In-place write-cursor compaction (same shape as `fuse_pairs`): fused + // instructions overwrite their `place_at` position, removed ones drop out. + let mut write = 0; + for read in 0..len { + if removed[read] { + continue; + } + if let Some(fused) = placed[read].take() { + instrs[write] = fused; + } else if write != read { + instrs.swap(write, read); + } + write += 1; + } + instrs.truncate(write); +} + +/// For each `Vid`, the `(use count, last consumer position)` pair. A reference +/// `Vid` is linkable into a chain only when its use count is exactly 1, in which +/// case the recorded position is that sole consumer's. +fn value_use_info(instrs: &[Instr]) -> UnorderedMap { + let mut info: UnorderedMap = UnorderedMap::new(); + for (pos, instr) in instrs.iter().enumerate() { + for_each_value_use(instr, |slot| { + if slot.is_vid() { + let entry = info.entry(slot).or_insert((0, pos)); + entry.0 += 1; + entry.1 = pos; + } + }); + } + info +} + +/// Try to build a fused field chain rooted at `instrs[start]`. +fn try_build_chain( + instrs: &[Instr], + start: usize, + use_info: &UnorderedMap, +) -> Option { + let linkable = |slot: Slot| slot.is_vid() && matches!(use_info.get(&slot), Some(&(1, _))); + + // Identify the head: the root, the reference the next struct field-borrow + // must consume, the next index, and the path so far. The borrow kind (`Imm` + // vs `Mut`) doesn't change how the head links, so derive it once and merge + // each Imm/Mut head pair. + let is_mut = matches!( + &instrs[start], + Instr::MutBorrowLoc(..) | Instr::MutBorrowField(..) + ); + let (root, mut cur_ref, mut idx, mut path) = match &instrs[start] { + Instr::ImmBorrowLoc(produced, local) | Instr::MutBorrowLoc(produced, local) => ( + ChainRoot::Local(*local), + *produced, + start + 1, + FieldPath::new(), + ), + Instr::ImmBorrowField(produced, owner, fh, src) + | Instr::MutBorrowField(produced, owner, fh, src) => { + (ChainRoot::Ref(*src), *produced, start + 1, vec![( + *owner, *fh, + )]) + }, + _ => return None, + }; + + // Extend through contiguous, single-use struct field-borrows of the matching + // borrow kind. (Field borrows are emitted back-to-back; only the terminal + // may be separated, by a write's right-hand-side computation.) + while linkable(cur_ref) { + match instrs.get(idx) { + Some(Instr::ImmBorrowField(produced, owner, fh, src)) if !is_mut && *src == cur_ref => { + path.push((*owner, *fh)); + cur_ref = *produced; + idx += 1; + }, + Some(Instr::MutBorrowField(produced, owner, fh, src)) if is_mut && *src == cur_ref => { + path.push((*owner, *fh)); + cur_ref = *produced; + idx += 1; + }, + _ => break, + } + } + // The absorbed borrows occupy `[start, borrow_end)`. + let borrow_end = idx; + + // Shorter runs are left to the pairwise passes (see `MIN_CHAIN_DEPTH`, + // which the pre-scan gate shares). + if path.len() < MIN_CHAIN_DEPTH { + return None; + } + + // Classify the terminal by the deepest reference's sole consumer. It is + // single-use exactly when it feeds a `ReadRef`/`WriteRef`; otherwise it + // escapes (returned, passed, frozen, used more than once) and ends a borrow. + // Field borrows are pure address computes (no abort), so the terminal may + // sink past intervening instructions freely. + let sole_use_pos = if cur_ref.is_vid() { + use_info + .get(&cur_ref) + .and_then(|&(count, pos)| (count == 1).then_some(pos)) + } else { + None + }; + let terminal_consumer = sole_use_pos.and_then(|term_idx| match &instrs[term_idx] { + Instr::ReadRef(dst, src) if *src == cur_ref => { + Some((ChainTerminal::Read { dst: *dst }, term_idx)) + }, + Instr::WriteRef(ref_slot, val) if *ref_slot == cur_ref => { + Some((ChainTerminal::Write { val: *val }, term_idx)) + }, + _ => None, + }); + + // The borrows are absorbed; the terminal (when separate) is replaced in + // place at `place_at` by the fused instruction. + let (terminal, place_at) = match terminal_consumer { + Some((terminal, term_idx)) => (terminal, term_idx), + // No read/write consumer: the deepest reference is the chain's result, + // produced at the last borrow's position. + None => (ChainTerminal::Borrow { dst: cur_ref }, borrow_end - 1), + }; + + Some(FusedChain { + instr: build_chain_instr(root, is_mut, path, terminal), + place_at, + borrow_end, + }) +} + +/// Construct the fused instruction for a classified chain. +fn build_chain_instr( + root: ChainRoot, + is_mut: bool, + path: FieldPath, + terminal: ChainTerminal, +) -> Instr { + match root { + ChainRoot::Local(local) => match terminal { + ChainTerminal::Read { dst } => Instr::ReadLocalFieldChain(dst, path, local), + ChainTerminal::Write { val } => Instr::WriteLocalFieldChain(path, local, val), + ChainTerminal::Borrow { dst } if is_mut => { + Instr::MutBorrowLocalFieldChain(dst, path, local) + }, + ChainTerminal::Borrow { dst } => Instr::ImmBorrowLocalFieldChain(dst, path, local), + }, + ChainRoot::Ref(src) => match terminal { + ChainTerminal::Read { dst } => Instr::ReadFieldChain(dst, path, src), + ChainTerminal::Write { val } => Instr::WriteFieldChain(path, src, val), + ChainTerminal::Borrow { dst } if is_mut => Instr::MutBorrowFieldChain(dst, path, src), + ChainTerminal::Borrow { dst } => Instr::ImmBorrowFieldChain(dst, path, src), + }, + } +} + /// Try to fuse a `Ld*` + `BinaryOp` pair into a `BinaryOpImm` instruction. fn try_fuse_immediate_binop(first: &Instr, second: &Instr) -> Option { let (tmp, imm) = extract_imm_value(first)?; diff --git a/third_party/move/mono-move/specializer/src/gas.rs b/third_party/move/mono-move/specializer/src/gas.rs index 6c834f5e346..656330866ff 100644 --- a/third_party/move/mono-move/specializer/src/gas.rs +++ b/third_party/move/mono-move/specializer/src/gas.rs @@ -186,6 +186,19 @@ impl Emitter<'_, I> { .subst_type(self.module.interned_field_type_at(fh), *ty_args)?) } + /// Type of a fused field chain's terminal field (its last `path` step). + /// The whole chain reaches one field, so its read/write cost scales with + /// that field's size — exactly like the single-field op. + fn chain_terminal_ty( + &self, + path: &[(InternedType, FieldHandleIndex)], + ) -> GasInstrumentationResult { + let (owner, fh) = path + .last() + .ok_or(GasInstrumentationError::EmptyFieldChain)?; + self.field_ty(*owner, *fh) + } + /// Field types of enum `enum_ty`'s variant `variant`, with the enum's type /// arguments applied. fn variant_field_tys( @@ -284,7 +297,9 @@ impl Emitter<'_, I> { | Instr::ImmBorrowField(..) | Instr::MutBorrowField(..) | Instr::ImmBorrowVariantField(..) - | Instr::MutBorrowVariantField(..) => b.add_constant(BORROW), + | Instr::MutBorrowVariantField(..) + | Instr::ImmBorrowFieldChain(..) + | Instr::MutBorrowFieldChain(..) => b.add_constant(BORROW), Instr::ReadRef(_, ref_src) => { b.add_sized(REF_RW_BASE, REF_RW_PER_BYTE, self.pointee_ty(*ref_src)?) }, @@ -292,11 +307,18 @@ impl Emitter<'_, I> { b.add_sized(REF_RW_BASE, REF_RW_PER_BYTE, self.pointee_ty(*ref_dst)?) }, - // --- Fused field access (borrow + read/write) --- + // --- Fused field access (borrow + read/write). A chain is charged + // as one access sized by the bytes it reads or writes, independent + // of depth: the intermediate borrows are summed into a single + // offset at lowering, so no per-level runtime work remains to + // meter. --- Instr::ReadField(_, owner, fh, _) => { b.add_sized(REF_RW_BASE, REF_RW_PER_BYTE, self.field_ty(*owner, *fh)?) }, - Instr::WriteField(_, _, _, val) => { + Instr::ReadFieldChain(_, path, _) => { + b.add_sized(REF_RW_BASE, REF_RW_PER_BYTE, self.chain_terminal_ty(path)?) + }, + Instr::WriteField(_, _, _, val) | Instr::WriteFieldChain(_, _, val) => { b.add_sized(REF_RW_BASE, REF_RW_PER_BYTE, self.slot_ty(*val)?) }, Instr::ReadVariantField(..) => b.add_constant(FIELD_READ), @@ -305,11 +327,17 @@ impl Emitter<'_, I> { }, // --- Fused inline-struct field access --- - Instr::ImmBorrowLocField(..) | Instr::MutBorrowLocField(..) => b.add_constant(BORROW), + Instr::ImmBorrowLocField(..) + | Instr::MutBorrowLocField(..) + | Instr::ImmBorrowLocalFieldChain(..) + | Instr::MutBorrowLocalFieldChain(..) => b.add_constant(BORROW), Instr::ReadLocalField(_, owner, fh, _) => { b.add_sized(MOVE_BASE, MOVE_PER_BYTE, self.field_ty(*owner, *fh)?) }, - Instr::WriteLocalField(_, _, _, val) => { + Instr::ReadLocalFieldChain(_, path, _) => { + b.add_sized(MOVE_BASE, MOVE_PER_BYTE, self.chain_terminal_ty(path)?) + }, + Instr::WriteLocalField(_, _, _, val) | Instr::WriteLocalFieldChain(_, _, val) => { b.add_sized(MOVE_BASE, MOVE_PER_BYTE, self.slot_ty(*val)?) }, diff --git a/third_party/move/mono-move/specializer/src/lower/context.rs b/third_party/move/mono-move/specializer/src/lower/context.rs index c6498302bea..dc88fc9b971 100644 --- a/third_party/move/mono-move/specializer/src/lower/context.rs +++ b/third_party/move/mono-move/specializer/src/lower/context.rs @@ -218,8 +218,7 @@ pub(crate) struct VariantFieldAccess { /// variant tag; length is the enum's variant count. pub offsets: Vec>, /// `Some(off)` when every variant declares the field at the same offset: - /// the static fast path (no tag dispatch, membership check, or scratch - /// reference needed). + /// the static fast path (no tag dispatch or membership check). pub uniform_offset: Option, } @@ -338,14 +337,6 @@ pub struct LoweringContext<'a> { /// deep copy for [`Instr::MoveFrom`]) collects *before* the write, so it /// does not need GC tracking. pub resource_box_slot: Option, - /// 16-byte scratch fat-pointer slot for by-reference variant-field - /// read/write (`ReadVariantField`/`WriteVariantField`): the `enum_borrow` - /// (a heap deref) writes the field reference here, then `ReadRef`/ - /// `WriteRef` consumes it. `None` when the function has no such ops. - /// - /// Invariant: like `scratch`, its live range (borrow → deref) never spans - /// an allocating micro-op, so it needs no GC tracking. - pub variant_field_scratch: Option, /// 8-byte scratch heap-pointer slot for enum `PackVariant`/`UnpackVariant` /// when the slot-allocator aliases the enum-pointer slot with a field slot. /// `PackVariant` stages the freshly allocated pointer here and publishes it @@ -585,9 +576,8 @@ pub fn try_build_context<'a>( let resource_box_slot = needs_resource_box_slot.then(|| reserve_slot(&mut frame_data_size, 8)); // 6. Single IR pass over the function's enum ops: (1) verify each one's enum - // layout was derivable during discovery, and (2) detect which scratch - // slots the lowering needs. - let mut needs_variant_field_scratch = false; + // layout was derivable during discovery, and (2) detect whether + // pack/unpack needs the enum-pointer scratch slot. let mut needs_enum_ptr_scratch = false; for instr in func_ir.instrs() { let Some((enum_ty, NominalKind::Enum)) = field_layout_nominal_in_instr(instr) else { @@ -600,18 +590,6 @@ pub fn try_build_context<'a>( // layout was not derivable. return Ok(BuildContextOutcome::Skipped("enum layout not derivable")); } - // A by-reference variant-field read/write may need the 16-byte scratch - // fat-pointer slot: only the divergent-offset path uses it (the - // tag-dispatched borrow writes the field reference there for the - // following `ReadRef`/`WriteRef`, both non-allocating, so it never spans - // a safe point), but reserve conservatively for any such op rather than - // re-resolving each access here just to learn whether it is uniform. - if matches!( - instr, - Instr::ReadVariantField(..) | Instr::WriteVariantField(..) - ) { - needs_variant_field_scratch = true; - } // Pack/unpack may need an 8-byte scratch to keep the enum pointer out of // an aliased field slot (resolved per-instruction in lowering; the exact // alias depends on final slot offsets, which aren't known yet here). @@ -619,8 +597,6 @@ pub fn try_build_context<'a>( needs_enum_ptr_scratch = true; } } - let variant_field_scratch = - needs_variant_field_scratch.then(|| reserve_slot(&mut frame_data_size, 16)); let enum_ptr_scratch = needs_enum_ptr_scratch.then(|| reserve_slot(&mut frame_data_size, 8)); // TODO(perf): we need to revisit the complexity and performance of this function @@ -775,7 +751,6 @@ pub fn try_build_context<'a>( num_xfer_positions: func_ir.num_xfer_positions, scratch, resource_box_slot, - variant_field_scratch, enum_ptr_scratch, descriptors: descriptors.vec, enum_layouts: descriptors.enum_layouts, diff --git a/third_party/move/mono-move/specializer/src/lower/translate.rs b/third_party/move/mono-move/specializer/src/lower/translate.rs index 8d34f8bfc4f..855949ef03e 100644 --- a/third_party/move/mono-move/specializer/src/lower/translate.rs +++ b/third_party/move/mono-move/specializer/src/lower/translate.rs @@ -241,18 +241,169 @@ impl<'a> LoweringState<'a> { } /// Resolve a `FieldHandleIndex` against the struct type `struct_ty` - /// and return `(field_byte_offset, field_byte_size)`. + /// and return `(field_byte_offset, field_byte_size)`. Indexes the single + /// field directly, resolving only its child layout — unlike + /// [`Self::struct_field_layouts`], which builds the all-fields table that + /// `Pack`/`Unpack` need. fn resolve_field( &self, struct_ty: InternedType, fh: FieldHandleIndex, ) -> LoweringResult<(u32, u32)> { - let fields = self.struct_field_layouts(struct_ty, "field access")?; + let op = "field access"; + let layout = self + .ctx + .layouts + .layout_by_ty(struct_ty) + .ok_or(LoweringError::StructLayoutNotPopulated { op })?; + let LayoutKind::Struct { fields } = &layout.kind else { + return Err(LoweringError::NominalTypeNotStruct { op }); + }; let pos = self.ctx.module.field_position_at(fh) as usize; - let (offset, size, _align) = *fields + let field = fields .get(pos) .ok_or(LoweringError::FieldIndexOutOfRange { pos })?; - Ok((offset, size)) + let child = self + .ctx + .layouts + .layout(field.id) + .ok_or(LoweringError::FieldLayoutIdUnresolved { op })?; + Ok((field.offset, child.size)) + } + + /// Resolve a fused inline-struct chain `path` into `(summed_field_offset, + /// terminal_field_size)`: the byte offset of the deepest field relative to + /// the chain's root, plus that field's byte size. Each step's owner is + /// substituted to a concrete type before resolution, so the fold is + /// instantiation-correct; the running offset is `checked_add`ed. + fn resolve_field_path( + &self, + path: &[(InternedType, FieldHandleIndex)], + ) -> LoweringResult<(u32, u32)> { + if path.is_empty() { + return Err(LoweringError::EmptyFieldChain); + } + let mut sum: u32 = 0; + let mut terminal_size: u32 = 0; + for (owner, fh) in path { + let (offset, size) = self.resolve_field(self.concrete_ty(*owner)?, *fh)?; + sum = sum + .checked_add(offset) + .ok_or(LoweringError::FieldChainOffsetOverflow)?; + terminal_size = size; + } + Ok((sum, terminal_size)) + } + + // --- Shared field-access emission (single-field and chain arms) --- + + /// Borrow of an inline-struct local's field: the field's absolute frame + /// offset is compile-time, so no fat pointer is materialized. + fn lower_local_field_borrow( + &mut self, + field_offset: u32, + local: Slot, + dst: Slot, + ) -> LoweringResult<()> { + let local_info = self.slot(local)?; + let dst_info = self.def_slot(dst)?; + self.emit(MicroOp::SlotBorrow { + dst: dst_info.offset, + local: FrameOffset(local_info.offset.0 + field_offset), + }) + } + + /// By-value read of an inline-struct local's field: a frame move, plus a + /// deep copy when the field is heap-backed. + fn lower_local_field_read( + &mut self, + field: (u32, u32), + local: Slot, + dst: Slot, + ) -> LoweringResult<()> { + let (field_offset, field_size) = field; + let local_info = self.slot(local)?; + let dst_info = self.def_typed_slot(dst)?; + let src = SizedSlot { + offset: FrameOffset(local_info.offset.0 + field_offset), + size: field_size, + align: local_info.align, + }; + self.emit_single_move(dst_info.slot.offset, src)?; + self.maybe_deep_copy(dst_info.ty, dst_info.slot.offset) + } + + /// By-value write into an inline-struct local's field: a frame move. + fn lower_local_field_write( + &mut self, + field: (u32, u32), + local: Slot, + val: Slot, + ) -> LoweringResult<()> { + let (field_offset, field_size) = field; + let local_info = self.slot(local)?; + let val_info = self.slot(val)?; + let src = SizedSlot { + offset: val_info.offset, + size: field_size, + align: val_info.align, + }; + self.emit_single_move(FrameOffset(local_info.offset.0 + field_offset), src) + } + + /// Borrow of a field through a reference: fold the field offset into the + /// address compute in a single dispatch. + fn lower_ref_field_borrow( + &mut self, + field_offset: u32, + src: Slot, + dst: Slot, + ) -> LoweringResult<()> { + let src_info = self.slot(src)?; + let dst_info = self.def_slot(dst)?; + self.emit(MicroOp::DeriveRefOffsetImm { + dst_ref: dst_info.offset, + src_ref: src_info.offset, + offset: field_offset, + }) + } + + /// By-value read of a field through a reference, plus a deep copy when + /// the field is heap-backed. + fn lower_ref_field_read( + &mut self, + field: (u32, u32), + src: Slot, + dst: Slot, + ) -> LoweringResult<()> { + let (field_offset, field_size) = field; + let src_info = self.slot(src)?; + let dst_info = self.def_typed_slot(dst)?; + self.emit(MicroOp::ReadRefOffset { + dst: dst_info.slot.offset, + ref_ptr: src_info.offset, + offset: field_offset, + size: field_size, + })?; + self.maybe_deep_copy(dst_info.ty, dst_info.slot.offset) + } + + /// By-value write into a field through a reference. + fn lower_ref_field_write( + &mut self, + field: (u32, u32), + dst_ref: Slot, + val: Slot, + ) -> LoweringResult<()> { + let (field_offset, field_size) = field; + let ref_info = self.slot(dst_ref)?; + let val_info = self.slot(val)?; + self.emit(MicroOp::WriteRefOffset { + ref_ptr: ref_info.offset, + offset: field_offset, + src: val_info.offset, + size: field_size, + }) } /// Resolve a `VariantFieldHandleIndex` against `enum_ty`'s derived @@ -265,27 +416,6 @@ impl<'a> LoweringState<'a> { resolve_variant_field_access(self.ctx.module, &self.ctx.enum_layouts, enum_ty, vfh) } - /// Emit the borrow of a variant field's reference into `dst_ref`. Uses the - /// static `enum_borrow` when the offset is variant-independent, else the - /// tag-dispatched `EnumBorrowVariantField` (which also aborts when the - /// runtime variant does not declare the field). - fn emit_variant_field_borrow( - &mut self, - access: &VariantFieldAccess, - enum_ref: FrameOffset, - dst_ref: FrameOffset, - ) -> LoweringResult<()> { - match access.uniform_offset { - Some(offset) => self.emit(MicroOp::enum_borrow(enum_ref, offset, dst_ref))?, - None => self.emit(MicroOp::enum_borrow_variant_field( - enum_ref, - &access.offsets, - dst_ref, - ))?, - } - Ok(()) - } - fn xfer_binding(&self, j: u16) -> LoweringResult { self.xfer_bindings[j as usize].ok_or(LoweringError::XferReadWithoutDef { xfer: j }) } @@ -516,6 +646,8 @@ impl<'a> LoweringState<'a> { if dst_offset == src.offset { return Ok(()); } + // `Move8` is sound at any offset alignment (it uses unaligned + // accesses), so dispatch purely on size. if src.size == 8 { self.emit(MicroOp::Move8 { dst: dst_offset, @@ -1452,37 +1584,15 @@ impl<'a> LoweringState<'a> { Instr::ImmBorrowLocField(dst, owner, fh, local) | Instr::MutBorrowLocField(dst, owner, fh, local) => { let (field_offset, _) = self.resolve_field(self.concrete_ty(*owner)?, *fh)?; - let local_info = self.slot(*local)?; - let dst_info = self.def_slot(*dst)?; - self.emit(MicroOp::SlotBorrow { - dst: dst_info.offset, - local: FrameOffset(local_info.offset.0 + field_offset), - })?; + self.lower_local_field_borrow(field_offset, *local, *dst)?; }, Instr::ReadLocalField(dst, owner, fh, local) => { - let (field_offset, field_size) = - self.resolve_field(self.concrete_ty(*owner)?, *fh)?; - let local_info = self.slot(*local)?; - let dst_info = self.def_typed_slot(*dst)?; - let src = SizedSlot { - offset: FrameOffset(local_info.offset.0 + field_offset), - size: field_size, - align: local_info.align, - }; - self.emit_single_move(dst_info.slot.offset, src)?; - self.maybe_deep_copy(dst_info.ty, dst_info.slot.offset)?; + let field = self.resolve_field(self.concrete_ty(*owner)?, *fh)?; + self.lower_local_field_read(field, *local, *dst)?; }, Instr::WriteLocalField(owner, fh, local, val) => { - let (field_offset, field_size) = - self.resolve_field(self.concrete_ty(*owner)?, *fh)?; - let local_info = self.slot(*local)?; - let val_info = self.slot(*val)?; - let src = SizedSlot { - offset: val_info.offset, - size: field_size, - align: val_info.align, - }; - self.emit_single_move(FrameOffset(local_info.offset.0 + field_offset), src)?; + let field = self.resolve_field(self.concrete_ty(*owner)?, *fh)?; + self.lower_local_field_write(field, *local, *val)?; }, // --- Inline-struct: by-ref --- @@ -1494,38 +1604,47 @@ impl<'a> LoweringState<'a> { Instr::ImmBorrowField(dst, owner, fh, src) | Instr::MutBorrowField(dst, owner, fh, src) => { let (field_offset, _) = self.resolve_field(self.concrete_ty(*owner)?, *fh)?; - let src_info = self.slot(*src)?; - let dst_info = self.def_slot(*dst)?; - self.emit(MicroOp::DeriveRefOffsetImm { - dst_ref: dst_info.offset, - src_ref: src_info.offset, - offset: field_offset, - })?; + self.lower_ref_field_borrow(field_offset, *src, *dst)?; }, Instr::ReadField(dst, owner, fh, src) => { - let (field_offset, field_size) = - self.resolve_field(self.concrete_ty(*owner)?, *fh)?; - let src_info = self.slot(*src)?; - let dst_info = self.def_typed_slot(*dst)?; - self.emit(MicroOp::ReadRefOffset { - dst: dst_info.slot.offset, - ref_ptr: src_info.offset, - offset: field_offset, - size: field_size, - })?; - self.maybe_deep_copy(dst_info.ty, dst_info.slot.offset)?; + let field = self.resolve_field(self.concrete_ty(*owner)?, *fh)?; + self.lower_ref_field_read(field, *src, *dst)?; }, Instr::WriteField(owner, fh, dst_ref, val) => { - let (field_offset, field_size) = - self.resolve_field(self.concrete_ty(*owner)?, *fh)?; - let ref_info = self.slot(*dst_ref)?; - let val_info = self.slot(*val)?; - self.emit(MicroOp::WriteRefOffset { - ref_ptr: ref_info.offset, - offset: field_offset, - src: val_info.offset, - size: field_size, - })?; + let field = self.resolve_field(self.concrete_ty(*owner)?, *fh)?; + self.lower_ref_field_write(field, *dst_ref, *val)?; + }, + + // --- Fused inline-struct field CHAINS --- + // + // The whole run reaches one field at `base(root) + Σ offsets`, so + // each lowers to exactly one micro-op (the same one the single-field + // op uses), with the summed offset and terminal size folded in. + Instr::ReadLocalFieldChain(dst, path, local) => { + let field = self.resolve_field_path(path)?; + self.lower_local_field_read(field, *local, *dst)?; + }, + Instr::WriteLocalFieldChain(path, local, val) => { + let field = self.resolve_field_path(path)?; + self.lower_local_field_write(field, *local, *val)?; + }, + Instr::ImmBorrowLocalFieldChain(dst, path, local) + | Instr::MutBorrowLocalFieldChain(dst, path, local) => { + let (field_offset, _) = self.resolve_field_path(path)?; + self.lower_local_field_borrow(field_offset, *local, *dst)?; + }, + Instr::ReadFieldChain(dst, path, src) => { + let field = self.resolve_field_path(path)?; + self.lower_ref_field_read(field, *src, *dst)?; + }, + Instr::WriteFieldChain(path, dst_ref, val) => { + let field = self.resolve_field_path(path)?; + self.lower_ref_field_write(field, *dst_ref, *val)?; + }, + Instr::ImmBorrowFieldChain(dst, path, src) + | Instr::MutBorrowFieldChain(dst, path, src) => { + let (field_offset, _) = self.resolve_field_path(path)?; + self.lower_ref_field_borrow(field_offset, *src, *dst)?; }, // --- Pack / Unpack: per-field byte copies between frame slots --- @@ -1934,13 +2053,10 @@ impl<'a> LoweringState<'a> { ))?; }, // By-reference field read/write: `src`/`dst_ref` is an enum fat - // pointer. The uniform-offset fast path fuses the heap deref and - // the value copy into one `Enum{Read,Write}VariantField` (no - // scratch reference). The divergent-offset path resolves the offset - // by tag: `emit_variant_field_borrow` materializes a field - // reference in the scratch slot, then `ReadRef`/`WriteRef` accesses - // it. Every op here is non-allocating, so the scratch reference - // never spans a safe point. + // pointer. The uniform-offset fast path fuses the heap deref and the + // value copy into one `Enum{Read,Write}VariantField`; the divergent + // path fuses the tag dispatch and the copy into one + // `Enum{Read,Write}VariantFieldByTag`. Both are non-allocating. Instr::ReadVariantField(dst, enum_ty, vfh, src) => { let access = self.resolve_variant_field(self.concrete_ty(*enum_ty)?, *vfh)?; let src_off = self.slot(*src)?.offset; @@ -1953,19 +2069,12 @@ impl<'a> LoweringState<'a> { dst_off, access.field_size, ))?, - None => { - let scratch = self.ctx.variant_field_scratch.ok_or( - LoweringError::VariantFieldScratchMissing { - op: "ReadVariantField", - }, - )?; - self.emit_variant_field_borrow(&access, src_off, scratch)?; - self.emit(MicroOp::ReadRef { - dst: dst_off, - ref_ptr: scratch, - size: access.field_size, - })?; - }, + None => self.emit(MicroOp::enum_read_variant_field_by_tag( + src_off, + &access.offsets, + dst_off, + access.field_size, + ))?, } // A by-value variant-field read materializes the field; if it is // itself heap-backed, make it independent. @@ -1982,30 +2091,31 @@ impl<'a> LoweringState<'a> { val_off, access.field_size, ))?, - None => { - let scratch = self.ctx.variant_field_scratch.ok_or( - LoweringError::VariantFieldScratchMissing { - op: "WriteVariantField", - }, - )?; - self.emit_variant_field_borrow(&access, ref_off, scratch)?; - self.emit(MicroOp::WriteRef { - ref_ptr: scratch, - src: val_off, - size: access.field_size, - })?; - }, + None => self.emit(MicroOp::enum_write_variant_field_by_tag( + ref_off, + &access.offsets, + val_off, + access.field_size, + ))?, } }, // Borrowing a variant field directly produces a field reference // into `dst`; the borrow derefs the enum and offsets into the heap - // object (statically or by runtime tag). + // object — statically when uniform, else by runtime tag (aborting + // when the variant does not declare the field). Instr::ImmBorrowVariantField(dst, enum_ty, vfh, src) | Instr::MutBorrowVariantField(dst, enum_ty, vfh, src) => { let access = self.resolve_variant_field(self.concrete_ty(*enum_ty)?, *vfh)?; let src_off = self.slot(*src)?.offset; let dst_off = self.def_slot(*dst)?.offset; - self.emit_variant_field_borrow(&access, src_off, dst_off)?; + match access.uniform_offset { + Some(offset) => self.emit(MicroOp::enum_borrow(src_off, offset, dst_off))?, + None => self.emit(MicroOp::enum_borrow_variant_field_by_tag( + src_off, + &access.offsets, + dst_off, + ))?, + } }, Instr::ForceGC => self.emit(MicroOp::ForceGC)?, diff --git a/third_party/move/mono-move/specializer/src/stackless_exec_ir/display.rs b/third_party/move/mono-move/specializer/src/stackless_exec_ir/display.rs index d8d09816f01..975a1102717 100644 --- a/third_party/move/mono-move/specializer/src/stackless_exec_ir/display.rs +++ b/third_party/move/mono-move/specializer/src/stackless_exec_ir/display.rs @@ -174,6 +174,14 @@ fn variant_field_name(module: &CompiledModule, idx: VariantFieldHandleIndex) -> format!("{}::{}::{}", sname, vname, fname) } +/// Render a fused field chain's path as `Owner0::f0.Owner1::f1...`. +fn field_path_name(module: &CompiledModule, path: &super::FieldPath) -> String { + path.iter() + .map(|step| field_name(module, step.1)) + .collect::>() + .join(".") +} + fn display_instr( f: &mut fmt::Formatter<'_>, module: &CompiledModule, @@ -443,6 +451,80 @@ fn display_instr( ) }, + // --- Fused field chains --- + Instr::ReadFieldChain(d, path, s) => { + write_dst(f, *d)?; + write!( + f, + "read_field_chain {}, {}", + field_path_name(module, path), + slot_name(*s) + ) + }, + Instr::WriteFieldChain(path, d, v) => { + write!( + f, + "write_field_chain {}, {}, {}", + field_path_name(module, path), + slot_name(*d), + slot_name(*v) + ) + }, + Instr::ImmBorrowFieldChain(d, path, s) => { + write_dst(f, *d)?; + write!( + f, + "imm_borrow_field_chain {}, {}", + field_path_name(module, path), + slot_name(*s) + ) + }, + Instr::MutBorrowFieldChain(d, path, s) => { + write_dst(f, *d)?; + write!( + f, + "mut_borrow_field_chain {}, {}", + field_path_name(module, path), + slot_name(*s) + ) + }, + Instr::ReadLocalFieldChain(d, path, local) => { + write_dst(f, *d)?; + write!( + f, + "read_local_field_chain {}, {}", + field_path_name(module, path), + slot_name(*local) + ) + }, + Instr::WriteLocalFieldChain(path, local, v) => { + write!( + f, + "write_local_field_chain {}, {}, {}", + field_path_name(module, path), + slot_name(*local), + slot_name(*v) + ) + }, + Instr::ImmBorrowLocalFieldChain(d, path, local) => { + write_dst(f, *d)?; + write!( + f, + "imm_borrow_local_field_chain {}, {}", + field_path_name(module, path), + slot_name(*local) + ) + }, + Instr::MutBorrowLocalFieldChain(d, path, local) => { + write_dst(f, *d)?; + write!( + f, + "mut_borrow_local_field_chain {}, {}", + field_path_name(module, path), + slot_name(*local) + ) + }, + // --- Globals --- Instr::Exists(d, ty, a) => { write_dst(f, *d)?; diff --git a/third_party/move/mono-move/specializer/src/stackless_exec_ir/instr_utils.rs b/third_party/move/mono-move/specializer/src/stackless_exec_ir/instr_utils.rs index a0d0059f039..8603a1de17a 100644 --- a/third_party/move/mono-move/specializer/src/stackless_exec_ir/instr_utils.rs +++ b/third_party/move/mono-move/specializer/src/stackless_exec_ir/instr_utils.rs @@ -123,6 +123,21 @@ pub(crate) fn clobbers_xfer(instr: &Instr) -> bool { matches!(instr, Instr::Call(..) | Instr::CallClosure(..)) } +/// The local whose storage `instr` mutably borrows, if any. A later write +/// through that borrow mutates the local without a def at the write site (a +/// hidden write), so coalescing and copy propagation both derive their +/// guards from this single predicate. +pub(crate) fn mut_local_borrow_target(instr: &Instr) -> Option { + if let Instr::MutBorrowLoc(_, local) + | Instr::MutBorrowLocField(_, _, _, local) + | Instr::MutBorrowLocalFieldChain(_, _, local) = instr + { + Some(*local) + } else { + None + } +} + /// Resource type carried by a global-storage instruction (`exists`, /// `move_from`, `move_to`, `borrow_global[_mut]`), if any. The returned type /// is the interned resource nominal embedded in the instruction; it may still @@ -151,6 +166,14 @@ pub(crate) fn resource_type_in_instr(instr: &Instr) -> Option { | Instr::MutBorrowLocField(..) | Instr::ReadLocalField(..) | Instr::WriteLocalField(..) + | Instr::ReadFieldChain(..) + | Instr::WriteFieldChain(..) + | Instr::ImmBorrowFieldChain(..) + | Instr::MutBorrowFieldChain(..) + | Instr::ReadLocalFieldChain(..) + | Instr::WriteLocalFieldChain(..) + | Instr::ImmBorrowLocalFieldChain(..) + | Instr::MutBorrowLocalFieldChain(..) | Instr::ImmBorrowVariantField(..) | Instr::MutBorrowVariantField(..) | Instr::ReadVariantField(..) @@ -235,6 +258,19 @@ pub(crate) fn field_layout_nominal_in_instr(instr: &Instr) -> Option<(InternedTy | Instr::ReadLocalField(_, owner, _, _) | Instr::WriteLocalField(owner, _, _, _) => Some((*owner, NominalKind::Struct)), + // A struct chain's first owner contains every later owner as an inline + // field, so discovering it transitively lays out the whole path. + Instr::ReadFieldChain(_, path, _) + | Instr::WriteFieldChain(path, _, _) + | Instr::ImmBorrowFieldChain(_, path, _) + | Instr::MutBorrowFieldChain(_, path, _) + | Instr::ReadLocalFieldChain(_, path, _) + | Instr::WriteLocalFieldChain(path, _, _) + | Instr::ImmBorrowLocalFieldChain(_, path, _) + | Instr::MutBorrowLocalFieldChain(_, path, _) => { + path.first().map(|(owner, _)| (*owner, NominalKind::Struct)) + }, + // Variant ops carry the instantiated enum type directly. Instr::PackVariant(_, ty, _, _) | Instr::UnpackVariant(_, ty, _, _) @@ -355,6 +391,14 @@ pub(crate) fn is_fallthrough_terminator(instr: &Instr) -> bool { | Instr::MutBorrowLocField(..) | Instr::ReadLocalField(..) | Instr::WriteLocalField(..) + | Instr::ReadFieldChain(..) + | Instr::WriteFieldChain(..) + | Instr::ImmBorrowFieldChain(..) + | Instr::MutBorrowFieldChain(..) + | Instr::ReadLocalFieldChain(..) + | Instr::WriteLocalFieldChain(..) + | Instr::ImmBorrowLocalFieldChain(..) + | Instr::MutBorrowLocalFieldChain(..) | Instr::Exists(..) | Instr::MoveFrom(..) | Instr::MoveTo(..) @@ -430,6 +474,14 @@ pub(crate) fn extract_imm_value(instr: &Instr) -> Option<(Slot, ImmValue)> { | Instr::MutBorrowLocField(_, _, _, _) | Instr::ReadLocalField(_, _, _, _) | Instr::WriteLocalField(_, _, _, _) + | Instr::ReadFieldChain(_, _, _) + | Instr::WriteFieldChain(_, _, _) + | Instr::ImmBorrowFieldChain(_, _, _) + | Instr::MutBorrowFieldChain(_, _, _) + | Instr::ReadLocalFieldChain(_, _, _) + | Instr::WriteLocalFieldChain(_, _, _) + | Instr::ImmBorrowLocalFieldChain(_, _, _) + | Instr::MutBorrowLocalFieldChain(_, _, _) | Instr::Exists(_, _, _) | Instr::MoveFrom(_, _, _) | Instr::MoveTo(_, _, _) @@ -580,10 +632,14 @@ fn visit_slots( def::(*dst, &mut f); storage_use::(*src, &mut f); }, + // Field chains share the slot shape of their single-field counterparts; + // only the carried `path` differs. Instr::ImmBorrowField(dst, _, _, src) | Instr::MutBorrowField(dst, _, _, src) | Instr::ImmBorrowVariantField(dst, _, _, src) | Instr::MutBorrowVariantField(dst, _, _, src) + | Instr::ImmBorrowFieldChain(dst, _, src) + | Instr::MutBorrowFieldChain(dst, _, src) | Instr::ReadRef(dst, src) => { def::(*dst, &mut f); used::(*src, &mut f); @@ -593,11 +649,15 @@ fn visit_slots( used::(*val, &mut f); }, - Instr::ReadField(dst, _, _, src) | Instr::ReadVariantField(dst, _, _, src) => { + Instr::ReadField(dst, _, _, src) + | Instr::ReadVariantField(dst, _, _, src) + | Instr::ReadFieldChain(dst, _, src) => { def::(*dst, &mut f); used::(*src, &mut f); }, - Instr::WriteField(_, _, ref_slot, val) | Instr::WriteVariantField(_, _, ref_slot, val) => { + Instr::WriteField(_, _, ref_slot, val) + | Instr::WriteVariantField(_, _, ref_slot, val) + | Instr::WriteFieldChain(_, ref_slot, val) => { used::(*ref_slot, &mut f); used::(*val, &mut f); }, @@ -606,14 +666,17 @@ fn visit_slots( // a place use. Instr::ImmBorrowLocField(dst, _, _, local) | Instr::MutBorrowLocField(dst, _, _, local) - | Instr::ReadLocalField(dst, _, _, local) => { + | Instr::ReadLocalField(dst, _, _, local) + | Instr::ImmBorrowLocalFieldChain(dst, _, local) + | Instr::MutBorrowLocalFieldChain(dst, _, local) + | Instr::ReadLocalFieldChain(dst, _, local) => { def::(*dst, &mut f); storage_use::(*local, &mut f); }, // `local` is both a def (a field is written in-place) and a // place use (the other fields persist, so the slot // stays live with the same type after the write). - Instr::WriteLocalField(_, _, local, val) => { + Instr::WriteLocalField(_, _, local, val) | Instr::WriteLocalFieldChain(_, local, val) => { def::(*local, &mut f); storage_use::(*local, &mut f); used::(*val, &mut f); @@ -795,10 +858,13 @@ fn rewrite_instr_slots { if DEFS { rewrite_slot(dst, &mut f); @@ -814,7 +880,9 @@ fn rewrite_instr_slots { + Instr::ReadField(dst, _, _, src) + | Instr::ReadVariantField(dst, _, _, src) + | Instr::ReadFieldChain(dst, _, src) => { if DEFS { rewrite_slot(dst, &mut f); } @@ -822,7 +890,9 @@ fn rewrite_instr_slots { + Instr::WriteField(_, _, ref_slot, val) + | Instr::WriteVariantField(_, _, ref_slot, val) + | Instr::WriteFieldChain(_, ref_slot, val) => { if USES { rewrite_slot(ref_slot, &mut f); rewrite_slot(val, &mut f); @@ -833,7 +903,10 @@ fn rewrite_instr_slots { + | Instr::ReadLocalField(dst, _, _, local) + | Instr::ImmBorrowLocalFieldChain(dst, _, local) + | Instr::MutBorrowLocalFieldChain(dst, _, local) + | Instr::ReadLocalFieldChain(dst, _, local) => { if DEFS { rewrite_slot(dst, &mut f); } @@ -841,7 +914,7 @@ fn rewrite_instr_slots { + Instr::WriteLocalField(_, _, local, val) | Instr::WriteLocalFieldChain(_, local, val) => { // `local` is both a def and a place use of one // operand: rewrite once when either role is active. Under // SKIP_PLACE_USE only the use side is suppressed. diff --git a/third_party/move/mono-move/specializer/src/stackless_exec_ir/mod.rs b/third_party/move/mono-move/specializer/src/stackless_exec_ir/mod.rs index c7ea1c6a1bc..40e34c144b2 100644 --- a/third_party/move/mono-move/specializer/src/stackless_exec_ir/mod.rs +++ b/third_party/move/mono-move/specializer/src/stackless_exec_ir/mod.rs @@ -115,6 +115,10 @@ pub enum ImmValue { // of the largest integer type. const _: () = assert!(std::mem::size_of::() == 16); +/// A chain of inline-struct field selections, each an `(instantiated owner +/// type, field handle)` pair. +pub type FieldPath = Vec<(InternedType, FieldHandleIndex)>; + /// A stackless IR instruction with explicit named-slot operands. /// /// TODO(cleanup): @@ -202,6 +206,35 @@ pub enum Instr { /// `local.field = src` (mut_borrow_loc + write_field on an inline struct local) WriteLocalField(InternedType, FieldHandleIndex, Slot, Slot), + // --- Fused inline-struct field CHAINS (depth >= 2) --- + // + // Collapse a run of `*BorrowField` selections (whose intermediate + // references are dead after the chain) into one micro-op. `path` is the + // non-empty list of `(owner, field)` steps; the deepest field's address is + // `base(root) + Σ offsets`. Borrow chains keep the `Imm`/`Mut` split of + // the single-field borrows, see [`Instr::MutBorrowLocalFieldChain`] why + // this is needed. + // + /// `dst = root_ref.path` (root is a reference). + ReadFieldChain(Slot, FieldPath, Slot), + /// `root_ref.path = val`. + WriteFieldChain(FieldPath, Slot, Slot), + /// `dst = &root_ref.path`. + ImmBorrowFieldChain(Slot, FieldPath, Slot), + /// `dst = &mut root_ref.path`. + MutBorrowFieldChain(Slot, FieldPath, Slot), + /// `dst = root_local.path` (root is a by-value inline-struct local). + ReadLocalFieldChain(Slot, FieldPath, Slot), + /// `root_local.path = val`. + WriteLocalFieldChain(FieldPath, Slot, Slot), + /// `dst = &root_local.path`. + ImmBorrowLocalFieldChain(Slot, FieldPath, Slot), + /// `dst = &mut root_local.path`. A later write through `dst` mutates the + /// local without a def at the write site, so passes that track the + /// local's value must recognize this variant — the reason borrow chains + /// keep the `Imm`/`Mut` split. + MutBorrowLocalFieldChain(Slot, FieldPath, Slot), + // --- Globals (struct type is the interned `Type` for the named // resource; same type contract as `Pack`/`Unpack`) --- Exists(Slot, InternedType, Slot), @@ -306,6 +339,14 @@ impl Instr { Instr::MutBorrowLocField(..) => "MutBorrowLocField", Instr::ReadLocalField(..) => "ReadLocalField", Instr::WriteLocalField(..) => "WriteLocalField", + Instr::ReadFieldChain(..) => "ReadFieldChain", + Instr::WriteFieldChain(..) => "WriteFieldChain", + Instr::ImmBorrowFieldChain(..) => "ImmBorrowFieldChain", + Instr::MutBorrowFieldChain(..) => "MutBorrowFieldChain", + Instr::ReadLocalFieldChain(..) => "ReadLocalFieldChain", + Instr::WriteLocalFieldChain(..) => "WriteLocalFieldChain", + Instr::ImmBorrowLocalFieldChain(..) => "ImmBorrowLocalFieldChain", + Instr::MutBorrowLocalFieldChain(..) => "MutBorrowLocalFieldChain", Instr::Exists(..) => "Exists", Instr::MoveFrom(..) => "MoveFrom", Instr::MoveTo(..) => "MoveTo", diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/chain_abort_order.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/chain_abort_order.exp new file mode 100644 index 00000000000..c3b882486ce --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/chain_abort_order.exp @@ -0,0 +1,76 @@ +=== stackless === +// module 0x55::chain_abort_order + +fun boom() { + slots: params(0), locals(0), temps(1) + r0: u64 + code: + L0: + 0: r0 := ld_u64 777 + 1: abort r0 +} + +fun read_after_call(r0): u64 { + slots: params(1), locals(0), temps(3) + r0: &0x55::chain_abort_order::E + r1: &0x55::chain_abort_order::Inner + r2: &u64 + r3: u64 + code: + L0: + 0: r1 := imm_borrow_variant_field E::Fb::y, r0 + 1: r2 := imm_borrow_field Inner::b, r1 + 2: [] := call boom, [] + 3: r3 := read_ref r2 + 4: ret [r3] +} + +fun run(): u64 { + slots: params(0), locals(1), temps(1), xfer(1) + r0: 0x55::chain_abort_order::E + r1: u64 + code: + L0: + 0: r1 := ld_u64 7 + 1: r0 := pack_variant 0x55::chain_abort_order::E@0, [r1] + 2: x0 := imm_borrow_loc r0 + 3: [x0] := call read_after_call, [x0] + 4: ret [x0] +} +=== micro-ops === +// module 0x55::chain_abort_order + +fun boom() { + frame_data_size: 8 + entry_gas: 4 + code: + 0: StoreImm8 [0] <- #777 + 1: Abort [0] +} + +fun read_after_call() { + frame_data_size: 56 + entry_gas: 94 + code: + 0: EnumBorrowVariantFieldByTag [16] <- &(*[0]) by tag [None, Some(16)] + 1: DeriveRefOffsetImm [32] <- [16]+8 + 2: CallIndirect 0x55::chain_abort_order::boom + 3: ReadRef [48] <- *[32] (size=8) + 4: Move8 [0] <- [48] + 5: Return +} + +fun run() { + frame_data_size: 24 + entry_gas: 152 + code: + 0: StoreImm8 [8] <- #7 + 1: EnumNew [0] desc=2 tag #0 + 2: HeapMoveTo8 [0]+8 <- [8] + 3: SlotBorrow [48] <- &[0] + 4: CallIndirect 0x55::chain_abort_order::read_after_call + 5: Move8 [0] <- [48] + 6: Return + safe_point_layouts: + 1: [] +} diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/chain_abort_order.masm b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/chain_abort_order.masm new file mode 100644 index 00000000000..919bf75d3b0 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/chain_abort_order.masm @@ -0,0 +1,40 @@ +// RUN: publish --print(stackless,micro-ops) +module 0x55::chain_abort_order + +struct Inner has drop+copy + a: u64 + b: u64 + +enum E has drop + Fa + x: u64 + Fb + x: u64 + y: Inner + +// Aborts with code 777, no args, no results. +fun boom() + ld_u64 777 + abort + +fun read_after_call(e: &E): u64 + move_loc e + borrow_variant_field E, Fb::y + borrow_field Inner, b + call boom + read_ref + ret + +fun run(): u64 + local e: E + ld_u64 7 + pack_variant E, Fa + st_loc e + borrow_loc e + call read_after_call + ret + +// Reading y on an Fa must abort with a variant mismatch on BOTH VMs, before +// boom() runs (not boom()'s code 777). +// RUN: execute 0x55::chain_abort_order::run +// CHECK-SUBSTR: STRUCT_VARIANT_MISMATCH diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/chained_variant_field_fusion.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/chained_variant_field_fusion.exp new file mode 100644 index 00000000000..ec711990e56 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/chained_variant_field_fusion.exp @@ -0,0 +1,334 @@ +=== stackless === +// module 0x99::chained_variant_field_fusion + +fun mk_e(r0, r1): E { + slots: params(2), locals(0), temps(5) + r0: u64 + r1: u8 + r2: u8 + r3: u8 + r4: 0x99::chained_variant_field_fusion::Inner + r5: 0x99::chained_variant_field_fusion::E + r6: u16 + code: + L2: + 0: br_neq L0, r0, #0 + L1: + 1: r2 := ld_u8 1 + 2: r3 := ld_u8 7 + 3: r4 := pack 0x99::chained_variant_field_fusion::Inner, [r3, r1] + 4: r5 := pack_variant 0x99::chained_variant_field_fusion::E@0, [r2, r4] + 5: ret [r5] + L0: + 6: r6 := ld_u16 2 + 7: r2 := ld_u8 7 + 8: r4 := pack 0x99::chained_variant_field_fusion::Inner, [r2, r1] + 9: r5 := pack_variant 0x99::chained_variant_field_fusion::E@1, [r6, r4] + 10: ret [r5] +} + +fun mk_u(r0, r1): U { + slots: params(2), locals(0), temps(3) + r0: u64 + r1: u8 + r2: u8 + r3: 0x99::chained_variant_field_fusion::Inner + r4: 0x99::chained_variant_field_fusion::U + code: + L2: + 0: br_neq L0, r0, #0 + L1: + 1: r2 := ld_u8 7 + 2: r3 := pack 0x99::chained_variant_field_fusion::Inner, [r2, r1] + 3: r4 := pack_variant 0x99::chained_variant_field_fusion::U@0, [r3] + 4: ret [r4] + L0: + 5: r2 := ld_u8 7 + 6: r3 := pack 0x99::chained_variant_field_fusion::Inner, [r2, r1] + 7: r4 := pack_variant 0x99::chained_variant_field_fusion::U@1, [r3] + 8: ret [r4] +} + +fun read_e(r0): u8 { + slots: params(1), locals(0), temps(2) + r0: &0x99::chained_variant_field_fusion::E + r1: &0x99::chained_variant_field_fusion::Inner + r2: u8 + code: + L0: + 0: r1 := imm_borrow_variant_field E::P0::p, r0 + 1: r2 := read_field Inner::b, r1 + 2: ret [r2] +} + +fun read_f(r0): u8 { + slots: params(1), locals(0), temps(2) + r0: &0x99::chained_variant_field_fusion::F + r1: &0x99::chained_variant_field_fusion::Inner + r2: u8 + code: + L0: + 0: r1 := imm_borrow_variant_field F::Fb::y, r0 + 1: r2 := read_field Inner::b, r1 + 2: ret [r2] +} + +fun read_u(r0): u8 { + slots: params(1), locals(0), temps(2) + r0: &0x99::chained_variant_field_fusion::U + r1: &0x99::chained_variant_field_fusion::Inner + r2: u8 + code: + L0: + 0: r1 := imm_borrow_variant_field U::Ua::p, r0 + 1: r2 := read_field Inner::b, r1 + 2: ret [r2] +} + +fun run_read_e(r0, r1): u8 { + slots: params(2), locals(1), temps(0), xfer(2) + r0: u64 + r1: u8 + r2: 0x99::chained_variant_field_fusion::E + code: + L0: + 0: [r2] := call mk_e, [r0, r1] + 1: x0 := imm_borrow_loc r2 + 2: [x0] := call read_e, [x0] + 3: ret [x0] +} + +fun run_read_f(r0): u8 { + slots: params(1), locals(1), temps(4), xfer(1) + r0: u64 + r1: 0x99::chained_variant_field_fusion::F + r2: u8 + r3: u8 + r4: u8 + r5: 0x99::chained_variant_field_fusion::Inner + code: + L3: + 0: br_neq L0, r0, #0 + L1: + 1: r2 := ld_u8 1 + 2: r1 := pack_variant 0x99::chained_variant_field_fusion::F@0, [r2] + L2: + 3: x0 := imm_borrow_loc r1 + 4: [x0] := call read_f, [x0] + 5: ret [x0] + L0: + 6: r2 := ld_u8 1 + 7: r3 := ld_u8 7 + 8: r4 := ld_u8 9 + 9: r5 := pack 0x99::chained_variant_field_fusion::Inner, [r3, r4] + 10: r1 := pack_variant 0x99::chained_variant_field_fusion::F@1, [r2, r5] + 11: branch L2 +} + +fun run_read_u(r0, r1): u8 { + slots: params(2), locals(1), temps(0), xfer(2) + r0: u64 + r1: u8 + r2: 0x99::chained_variant_field_fusion::U + code: + L0: + 0: [r2] := call mk_u, [r0, r1] + 1: x0 := imm_borrow_loc r2 + 2: [x0] := call read_u, [x0] + 3: ret [x0] +} + +fun run_write_e(r0, r1): u8 { + slots: params(2), locals(1), temps(0), xfer(2) + r0: u64 + r1: u8 + r2: 0x99::chained_variant_field_fusion::E + code: + L0: + 0: x1 := ld_u8 0 + 1: [r2] := call mk_e, [r0, x1] + 2: x0 := mut_borrow_loc r2 + 3: [] := call write_e, [x0, r1] + 4: x0 := imm_borrow_loc r2 + 5: [x0] := call read_e, [x0] + 6: ret [x0] +} + +fun write_e(r0, r1) { + slots: params(2), locals(0), temps(2) + r0: &mut 0x99::chained_variant_field_fusion::E + r1: u8 + r2: u8 + r3: &mut 0x99::chained_variant_field_fusion::Inner + code: + L0: + 0: r2 := add r1, #1u8 + 1: r3 := mut_borrow_variant_field E::P0::p, r0 + 2: write_field Inner::b, r3, r2 + 3: ret [] +} +=== micro-ops === +// module 0x99::chained_variant_field_fusion + +fun mk_e() { + frame_data_size: 40 + entry_gas: 3 + code: + 0: JumpIntCmp @10 [0] != u64 #0 gas_taken=90 gas_fallthrough=87 + 1: StoreImm1 [9] <- #1 + 2: StoreImm1 [10] <- #7 + 3: Move(1) [12] <- [8] + 4: Move(1) [11] <- [10] + 5: EnumNew [16] desc=2 tag #0 + 6: HeapMoveTo [16]+8 <- [9] (size=1) + 7: HeapMoveTo [16]+9 <- [11] (size=2) + 8: Move8 [0] <- [16] + 9: Return + 10: StoreImm2 [24] <- #2 + 11: StoreImm1 [9] <- #7 + 12: Move(1) [12] <- [8] + 13: Move(1) [11] <- [9] + 14: EnumNew [16] desc=2 tag #1 + 15: HeapMoveTo [16]+8 <- [24] (size=2) + 16: HeapMoveTo [16]+10 <- [11] (size=2) + 17: Move8 [0] <- [16] + 18: Return + safe_point_layouts: + 5: [] + 14: [] +} + +fun mk_u() { + frame_data_size: 32 + entry_gas: 3 + code: + 0: JumpIntCmp @8 [0] != u64 #0 gas_taken=80 gas_fallthrough=80 + 1: StoreImm1 [9] <- #7 + 2: Move(1) [11] <- [8] + 3: Move(1) [10] <- [9] + 4: EnumNew [16] desc=3 tag #0 + 5: HeapMoveTo [16]+8 <- [10] (size=2) + 6: Move8 [0] <- [16] + 7: Return + 8: StoreImm1 [9] <- #7 + 9: Move(1) [11] <- [8] + 10: Move(1) [10] <- [9] + 11: EnumNew [16] desc=3 tag #1 + 12: HeapMoveTo [16]+8 <- [10] (size=2) + 13: Move8 [0] <- [16] + 14: Return + safe_point_layouts: + 4: [] + 11: [] +} + +fun read_e() { + frame_data_size: 40 + entry_gas: 19 + code: + 0: EnumBorrowVariantFieldByTag [16] <- &(*[0]) by tag [Some(9), Some(10)] + 1: ReadRefOffset [32] <- *([16]+1) (size=1) + 2: Move(1) [0] <- [32] + 3: Return +} + +fun read_f() { + frame_data_size: 40 + entry_gas: 19 + code: + 0: EnumBorrowVariantFieldByTag [16] <- &(*[0]) by tag [None, Some(9)] + 1: ReadRefOffset [32] <- *([16]+1) (size=1) + 2: Move(1) [0] <- [32] + 3: Return +} + +fun read_u() { + frame_data_size: 40 + entry_gas: 19 + code: + 0: HeapBorrow [16] <- &[0]+8 + 1: ReadRefOffset [32] <- *([16]+1) (size=1) + 2: Move(1) [0] <- [32] + 3: Return +} + +fun run_read_e() { + frame_data_size: 24 + entry_gas: 141 + code: + 0: Move(1) [56] <- [8] + 1: Move8 [48] <- [0] + 2: CallIndirect 0x99::chained_variant_field_fusion::mk_e + 3: Move8 [16] <- [48] + 4: SlotBorrow [48] <- &[16] + 5: CallIndirect 0x99::chained_variant_field_fusion::read_e + 6: Move(1) [0] <- [48] + 7: Return +} + +fun run_read_f() { + frame_data_size: 32 + entry_gas: 3 + code: + 0: JumpIntCmp @8 [0] != u64 #0 gas_taken=37 gas_fallthrough=15 + 1: StoreImm1 [16] <- #1 + 2: EnumNew [8] desc=4 tag #0 + 3: HeapMoveTo [8]+8 <- [16] (size=1) + 4: SlotBorrow [56] <- &[8] + 5: CallIndirect 0x99::chained_variant_field_fusion::read_f + 6: Move(1) [0] <- [56] + 7: Return + 8: StoreImm1 [16] <- #1 + 9: StoreImm1 [17] <- #7 + 10: StoreImm1 [18] <- #9 + 11: Move(1) [20] <- [18] + 12: Move(1) [19] <- [17] + 13: EnumNew [8] desc=4 tag #1 + 14: HeapMoveTo [8]+8 <- [16] (size=1) + 15: HeapMoveTo [8]+9 <- [19] (size=2) + 16: Jump @4 gas=74 + safe_point_layouts: + 2: [] + 13: [] +} + +fun run_read_u() { + frame_data_size: 24 + entry_gas: 141 + code: + 0: Move(1) [56] <- [8] + 1: Move8 [48] <- [0] + 2: CallIndirect 0x99::chained_variant_field_fusion::mk_u + 3: Move8 [16] <- [48] + 4: SlotBorrow [48] <- &[16] + 5: CallIndirect 0x99::chained_variant_field_fusion::read_u + 6: Move(1) [0] <- [48] + 7: Return +} + +fun run_write_e() { + frame_data_size: 24 + entry_gas: 210 + code: + 0: StoreImm1 [56] <- #0 + 1: Move8 [48] <- [0] + 2: CallIndirect 0x99::chained_variant_field_fusion::mk_e + 3: Move8 [16] <- [48] + 4: SlotBorrow [48] <- &[16] + 5: Move(1) [64] <- [8] + 6: CallIndirect 0x99::chained_variant_field_fusion::write_e + 7: SlotBorrow [48] <- &[16] + 8: CallIndirect 0x99::chained_variant_field_fusion::read_e + 9: Move(1) [0] <- [48] + 10: Return +} + +fun write_e() { + frame_data_size: 40 + entry_gas: 14 + code: + 0: IntAdd [17] <- [16] + u8 #1 + 1: EnumBorrowVariantFieldByTag [24] <- &(*[0]) by tag [Some(9), Some(10)] + 2: WriteRefOffset *([24]+1) <- [17] (size=1) + 3: Return +} diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/chained_variant_field_fusion.move b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/chained_variant_field_fusion.move new file mode 100644 index 00000000000..e5de3d2cb3f --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/chained_variant_field_fusion.move @@ -0,0 +1,98 @@ +// RUN: publish --print(stackless,micro-ops) +module 0x99::chained_variant_field_fusion { + struct Inner has drop, copy { + a: u8, + b: u8, + } + + enum U has drop { + Ua { p: Inner }, + Ub { p: Inner }, + } + + enum E has drop { + P0 { lead: u8, p: Inner }, + P1 { lead: u16, p: Inner }, + } + + enum F has drop { + Fa { x: u8 }, + Fb { x: u8, y: Inner }, + } + + // Uniform enum-rooted chain. + fun read_u(u: &U): u8 { + u.p.b + } + + // Divergent enum-rooted chain read (both variants present). + fun read_e(e: &E): u8 { + e.p.b + } + + // Divergent enum-rooted chain write (both variants present). + fun write_e(e: &mut E, v: u8) { + e.p.b = v + 1; + } + + // Divergent chain that aborts when the runtime variant lacks the field. + fun read_f(f: &F): u8 { + f.y.b + } + + fun mk_u(sel: u64, b: u8): U { + if (sel == 0) { U::Ua { p: Inner { a: 7, b } } } else { U::Ub { p: Inner { a: 7, b } } } + } + + fun mk_e(sel: u64, b: u8): E { + if (sel == 0) { E::P0 { lead: 1, p: Inner { a: 7, b } } } else { + E::P1 { lead: 2, p: Inner { a: 7, b } } + } + } + + fun run_read_u(sel: u64, b: u8): u8 { + let u = mk_u(sel, b); + read_u(&u) + } + + fun run_read_e(sel: u64, b: u8): u8 { + let e = mk_e(sel, b); + read_e(&e) + } + + fun run_write_e(sel: u64, v: u8): u8 { + let e = mk_e(sel, 0); + write_e(&mut e, v); + read_e(&e) + } + + fun run_read_f(sel: u64): u8 { + let f = if (sel == 0) { F::Fa { x: 1 } } else { F::Fb { x: 1, y: Inner { a: 7, b: 9 } } }; + read_f(&f) + } +} + +// Uniform chain: same result on either variant. +// RUN: execute 0x99::chained_variant_field_fusion::run_read_u --args 0, 42 +// CHECK: results: 42 +// RUN: execute 0x99::chained_variant_field_fusion::run_read_u --args 1, 42 +// CHECK: results: 42 + +// Divergent chain read: the byte offset differs per variant, the value does not. +// RUN: execute 0x99::chained_variant_field_fusion::run_read_e --args 0, 55 +// CHECK: results: 55 +// RUN: execute 0x99::chained_variant_field_fusion::run_read_e --args 1, 55 +// CHECK: results: 55 + +// Divergent chain write through &mut, then read back. +// RUN: execute 0x99::chained_variant_field_fusion::run_write_e --args 0, 77 +// CHECK: results: 78 +// RUN: execute 0x99::chained_variant_field_fusion::run_write_e --args 1, 77 +// CHECK: results: 78 + +// Divergent chain that reads a field present only on Fb. +// RUN: execute 0x99::chained_variant_field_fusion::run_read_f --args 1 +// CHECK: results: 9 +// Reading f.y.b on an Fa aborts with a variant mismatch on both VMs. +// RUN: execute 0x99::chained_variant_field_fusion::run_read_f --args 0 +// CHECK-SUBSTR: STRUCT_VARIANT_MISMATCH diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/copy_lowering.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/copy_lowering.exp index 7dad6f2ce07..4670beec2fc 100644 --- a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/copy_lowering.exp +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/copy_lowering.exp @@ -2,7 +2,7 @@ // module 0x42::enum_copy_lowering fun enum_copy() { - frame_data_size: 72 + frame_data_size: 56 entry_gas: 135 code: 0: StoreImm8 [16] <- #1 @@ -11,9 +11,9 @@ fun enum_copy() { 3: Move8 [8] <- [0] 4: DeepCopyHeapPtrs [8] deep-copy ptrs at [0] 5: SlotBorrow [24] <- &[0] - 6: EnumReadVariantField(8) [16] <- (*[24]).8 + 6: HeapReadOffset(8) [16] <- (*[24]).8 7: SlotBorrow [24] <- &[8] - 8: EnumReadVariantField(8) [40] <- (*[24]).8 + 8: HeapReadOffset(8) [40] <- (*[24]).8 9: AddU64 [40] <- [16] + [40] 10: Move8 [0] <- [40] 11: Return @@ -31,7 +31,7 @@ fun scalar_copy() { } fun struct_copy() { - frame_data_size: 104 + frame_data_size: 88 entry_gas: 245 code: 0: StoreImm8 [32] <- #1 @@ -45,9 +45,9 @@ fun struct_copy() { 8: Move(16) [16] <- [0] 9: DeepCopyHeapPtrs [16] deep-copy ptrs at [0, 8] 10: SlotBorrow [56] <- &[0] - 11: EnumReadVariantField(8) [32] <- (*[56]).8 + 11: HeapReadOffset(8) [32] <- (*[56]).8 12: SlotBorrow [56] <- &[24] - 13: EnumReadVariantField(8) [72] <- (*[56]).8 + 13: HeapReadOffset(8) [72] <- (*[56]).8 14: AddU64 [72] <- [32] + [72] 15: Move8 [0] <- [72] 16: Return diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_by_ref_param.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_by_ref_param.exp index 806c5fe9444..49fd5a54e95 100644 --- a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_by_ref_param.exp +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_by_ref_param.exp @@ -2,13 +2,12 @@ // module 0x42::enum_by_ref_param fun read_x() { - frame_data_size: 40 + frame_data_size: 24 entry_gas: 17 code: - 0: EnumBorrowVariantField [24] <- &(*[0]) by tag [Some(9), Some(16)] - 1: ReadRef [16] <- *[24] (size=1) - 2: Move(1) [0] <- [16] - 3: Return + 0: EnumReadVariantFieldByTag(1) [16] <- (*[0]) by tag [Some(9), Some(16)] + 1: Move(1) [0] <- [16] + 2: Return } fun read_x_a() { diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_divergent_write.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_divergent_write.exp new file mode 100644 index 00000000000..f596890d2c0 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_divergent_write.exp @@ -0,0 +1,120 @@ +=== micro-ops === +// module 0x43::variant_field_divergent_write + +fun make_e() { + frame_data_size: 40 + entry_gas: 3 + code: + 0: JumpIntCmp @8 [0] != u64 #0 gas_taken=79 gas_fallthrough=76 + 1: StoreImm1 [8] <- #1 + 2: StoreImm1 [9] <- #0 + 3: EnumNew [16] desc=2 tag #0 + 4: HeapMoveTo [16]+8 <- [8] (size=1) + 5: HeapMoveTo [16]+9 <- [9] (size=1) + 6: Move8 [0] <- [16] + 7: Return + 8: StoreImm2 [24] <- #2 + 9: StoreImm1 [9] <- #0 + 10: EnumNew [16] desc=2 tag #1 + 11: HeapMoveTo [16]+8 <- [24] (size=2) + 12: HeapMoveTo [16]+10 <- [9] (size=1) + 13: Move8 [0] <- [16] + 14: Return + safe_point_layouts: + 3: [] + 10: [] +} + +fun make_f() { + frame_data_size: 40 + entry_gas: 3 + code: + 0: JumpIntCmp @6 [0] != u64 #0 gas_taken=76 gas_fallthrough=69 + 1: StoreImm1 [8] <- #1 + 2: EnumNew [16] desc=3 tag #0 + 3: HeapMoveTo [16]+8 <- [8] (size=1) + 4: Move8 [0] <- [16] + 5: Return + 6: StoreImm1 [8] <- #1 + 7: StoreImm1 [24] <- #7 + 8: EnumNew [16] desc=3 tag #1 + 9: HeapMoveTo [16]+8 <- [8] (size=1) + 10: HeapMoveTo [16]+9 <- [24] (size=1) + 11: Move8 [0] <- [16] + 12: Return + safe_point_layouts: + 2: [] + 8: [] +} + +fun read_y() { + frame_data_size: 24 + entry_gas: 17 + code: + 0: EnumReadVariantFieldByTag(1) [16] <- (*[0]) by tag [None, Some(9)] + 1: Move(1) [0] <- [16] + 2: Return +} + +fun run_read_y() { + frame_data_size: 16 + entry_gas: 136 + code: + 0: Move8 [40] <- [0] + 1: CallIndirect 0x43::variant_field_divergent_write::make_f + 2: Move8 [8] <- [40] + 3: SlotBorrow [40] <- &[8] + 4: CallIndirect 0x43::variant_field_divergent_write::read_y + 5: Move(1) [0] <- [40] + 6: Return +} + +fun run_write_y() { + frame_data_size: 24 + entry_gas: 203 + code: + 0: Move8 [48] <- [0] + 1: CallIndirect 0x43::variant_field_divergent_write::make_f + 2: Move8 [16] <- [48] + 3: SlotBorrow [48] <- &[16] + 4: Move(1) [64] <- [8] + 5: CallIndirect 0x43::variant_field_divergent_write::set_y + 6: SlotBorrow [48] <- &[16] + 7: CallIndirect 0x43::variant_field_divergent_write::read_y + 8: Move(1) [0] <- [48] + 9: Return +} + +fun set_x() { + frame_data_size: 24 + entry_gas: 12 + code: + 0: IntAdd [17] <- [16] + u8 #1 + 1: EnumWriteVariantFieldByTag(1) (*[0]) <- [17] by tag [Some(9), Some(10)] + 2: Return +} + +fun set_y() { + frame_data_size: 24 + entry_gas: 12 + code: + 0: IntAdd [17] <- [16] + u8 #1 + 1: EnumWriteVariantFieldByTag(1) (*[0]) <- [17] by tag [None, Some(9)] + 2: Return +} + +fun write_read() { + frame_data_size: 48 + entry_gas: 148 + code: + 0: Move8 [72] <- [0] + 1: CallIndirect 0x43::variant_field_divergent_write::make_e + 2: Move8 [16] <- [72] + 3: SlotBorrow [72] <- &[16] + 4: Move(1) [88] <- [8] + 5: CallIndirect 0x43::variant_field_divergent_write::set_x + 6: SlotBorrow [24] <- &[16] + 7: EnumReadVariantFieldByTag(1) [40] <- (*[24]) by tag [Some(9), Some(10)] + 8: Move(1) [0] <- [40] + 9: Return +} diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_divergent_write.move b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_divergent_write.move new file mode 100644 index 00000000000..99b735eaa88 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_divergent_write.move @@ -0,0 +1,77 @@ +// RUN: publish --print(micro-ops) +module 0x43::variant_field_divergent_write { + enum E has drop { + P0 { lead: u8, x: u8 }, + P1 { lead: u16, x: u8 }, + } + + enum F has drop { + Fa { x: u8 }, + Fb { x: u8, y: u8 }, + } + + fun make_e(sel: u64): E { + if (sel == 0) { + E::P0 { lead: 1, x: 0 } + } else { + E::P1 { lead: 2, x: 0 } + } + } + + fun set_x(e: &mut E, v: u8) { + e.x = v + 1; + } + + fun write_read(sel: u64, v: u8): u8 { + let e = make_e(sel); + set_x(&mut e, v); + e.x + } + + fun make_f(sel: u64): F { + if (sel == 0) { + F::Fa { x: 1 } + } else { + F::Fb { x: 1, y: 7 } + } + } + + // Divergent read: aborts on `Fa`. + fun read_y(f: &F): u8 { + f.y + } + + // Divergent write: aborts on `Fa`. + fun set_y(f: &mut F, v: u8) { + f.y = v + 1; + } + + fun run_read_y(sel: u64): u8 { + let f = make_f(sel); + read_y(&f) + } + + fun run_write_y(sel: u64, v: u8): u8 { + let f = make_f(sel); + set_y(&mut f, v); + read_y(&f) + } +} + +// Divergent write + read back on both variants. +// RUN: execute 0x43::variant_field_divergent_write::write_read --args 0, 41 +// CHECK: results: 42 +// RUN: execute 0x43::variant_field_divergent_write::write_read --args 1, 41 +// CHECK: results: 42 + +// Divergent read: value on Fb, variant-mismatch abort on Fa. +// RUN: execute 0x43::variant_field_divergent_write::run_read_y --args 1 +// CHECK: results: 7 +// RUN: execute 0x43::variant_field_divergent_write::run_read_y --args 0 +// CHECK-SUBSTR: STRUCT_VARIANT_MISMATCH + +// Divergent write: value on Fb, variant-mismatch abort on Fa. +// RUN: execute 0x43::variant_field_divergent_write::run_write_y --args 1, 9 +// CHECK: results: 10 +// RUN: execute 0x43::variant_field_divergent_write::run_write_y --args 0, 9 +// CHECK-SUBSTR: STRUCT_VARIANT_MISMATCH diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_tail_borrows.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_tail_borrows.exp new file mode 100644 index 00000000000..68d404809a2 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_tail_borrows.exp @@ -0,0 +1,170 @@ +=== stackless === +// module 0x77::variant_field_tail_borrows + +fun bump_via_mut_ref(r0, r1): u8 { + slots: params(2), locals(2), temps(1), xfer(2) + r0: u64 + r1: u8 + r2: 0x77::variant_field_tail_borrows::E + r3: &mut u8 + r4: u8 + code: + L0: + 0: [r2] := call mk, [r0, r1] + 1: x0 := mut_borrow_loc r2 + 2: [r3] := call mut_ref, [x0] + 3: r4 := read_ref r3 + 4: r4 := add r4, #1u8 + 5: write_ref r3, r4 + 6: r4 := read_ref r3 + 7: ret [r4] +} + +fun imm_ref(r0): &u8 { + slots: params(1), locals(0), temps(2) + r0: &0x77::variant_field_tail_borrows::E + r1: &0x77::variant_field_tail_borrows::Inner + r2: &u8 + code: + L0: + 0: r1 := imm_borrow_variant_field E::P0::p, r0 + 1: r2 := imm_borrow_field Inner::b, r1 + 2: ret [r2] +} + +fun mk(r0, r1): E { + slots: params(2), locals(0), temps(5) + r0: u64 + r1: u8 + r2: u8 + r3: u8 + r4: 0x77::variant_field_tail_borrows::Inner + r5: 0x77::variant_field_tail_borrows::E + r6: u16 + code: + L2: + 0: br_neq L0, r0, #0 + L1: + 1: r2 := ld_u8 1 + 2: r3 := ld_u8 7 + 3: r4 := pack 0x77::variant_field_tail_borrows::Inner, [r3, r1] + 4: r5 := pack_variant 0x77::variant_field_tail_borrows::E@0, [r2, r4] + 5: ret [r5] + L0: + 6: r6 := ld_u16 2 + 7: r2 := ld_u8 7 + 8: r4 := pack 0x77::variant_field_tail_borrows::Inner, [r2, r1] + 9: r5 := pack_variant 0x77::variant_field_tail_borrows::E@1, [r6, r4] + 10: ret [r5] +} + +fun mut_ref(r0): &mut u8 { + slots: params(1), locals(0), temps(2) + r0: &mut 0x77::variant_field_tail_borrows::E + r1: &mut 0x77::variant_field_tail_borrows::Inner + r2: &mut u8 + code: + L0: + 0: r1 := mut_borrow_variant_field E::P0::p, r0 + 1: r2 := mut_borrow_field Inner::b, r1 + 2: ret [r2] +} + +fun read_via_imm_ref(r0, r1): u8 { + slots: params(2), locals(1), temps(1), xfer(2) + r0: u64 + r1: u8 + r2: 0x77::variant_field_tail_borrows::E + r3: u8 + code: + L0: + 0: [r2] := call mk, [r0, r1] + 1: x0 := imm_borrow_loc r2 + 2: [x0] := call imm_ref, [x0] + 3: r3 := read_ref x0 + 4: ret [r3] +} +=== micro-ops === +// module 0x77::variant_field_tail_borrows + +fun bump_via_mut_ref() { + frame_data_size: 48 + entry_gas: 211 + code: + 0: Move(1) [80] <- [8] + 1: Move8 [72] <- [0] + 2: CallIndirect 0x77::variant_field_tail_borrows::mk + 3: Move8 [16] <- [72] + 4: SlotBorrow [72] <- &[16] + 5: CallIndirect 0x77::variant_field_tail_borrows::mut_ref + 6: Move(16) [24] <- [72] + 7: ReadRef [40] <- *[24] (size=1) + 8: IntAdd [40] <- [40] + u8 #1 + 9: WriteRef *[24] <- [40] (size=1) + 10: ReadRef [40] <- *[24] (size=1) + 11: Move(1) [0] <- [40] + 12: Return +} + +fun imm_ref() { + frame_data_size: 48 + entry_gas: 106 + code: + 0: EnumBorrowVariantFieldByTag [16] <- &(*[0]) by tag [Some(9), Some(10)] + 1: DeriveRefOffsetImm [32] <- [16]+1 + 2: Move(16) [0] <- [32] + 3: Return +} + +fun mk() { + frame_data_size: 40 + entry_gas: 3 + code: + 0: JumpIntCmp @10 [0] != u64 #0 gas_taken=90 gas_fallthrough=87 + 1: StoreImm1 [9] <- #1 + 2: StoreImm1 [10] <- #7 + 3: Move(1) [12] <- [8] + 4: Move(1) [11] <- [10] + 5: EnumNew [16] desc=2 tag #0 + 6: HeapMoveTo [16]+8 <- [9] (size=1) + 7: HeapMoveTo [16]+9 <- [11] (size=2) + 8: Move8 [0] <- [16] + 9: Return + 10: StoreImm2 [24] <- #2 + 11: StoreImm1 [9] <- #7 + 12: Move(1) [12] <- [8] + 13: Move(1) [11] <- [9] + 14: EnumNew [16] desc=2 tag #1 + 15: HeapMoveTo [16]+8 <- [24] (size=2) + 16: HeapMoveTo [16]+10 <- [11] (size=2) + 17: Move8 [0] <- [16] + 18: Return + safe_point_layouts: + 5: [] + 14: [] +} + +fun mut_ref() { + frame_data_size: 48 + entry_gas: 106 + code: + 0: EnumBorrowVariantFieldByTag [16] <- &(*[0]) by tag [Some(9), Some(10)] + 1: DeriveRefOffsetImm [32] <- [16]+1 + 2: Move(16) [0] <- [32] + 3: Return +} + +fun read_via_imm_ref() { + frame_data_size: 32 + entry_gas: 146 + code: + 0: Move(1) [64] <- [8] + 1: Move8 [56] <- [0] + 2: CallIndirect 0x77::variant_field_tail_borrows::mk + 3: Move8 [16] <- [56] + 4: SlotBorrow [56] <- &[16] + 5: CallIndirect 0x77::variant_field_tail_borrows::imm_ref + 6: ReadRef [24] <- *[56] (size=1) + 7: Move(1) [0] <- [24] + 8: Return +} diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_tail_borrows.move b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_tail_borrows.move new file mode 100644 index 00000000000..e5bee6a9e41 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_tail_borrows.move @@ -0,0 +1,50 @@ +// RUN: publish --print(stackless,micro-ops) +module 0x77::variant_field_tail_borrows { + struct Inner has drop, copy { + a: u8, + b: u8, + } + + enum E has drop { + P0 { lead: u8, p: Inner }, + P1 { lead: u16, p: Inner }, + } + + // &e.p.b escapes (returned): a divergent variant-field borrow, then an + // immutable borrow of the tail field. + fun imm_ref(e: &E): &u8 { + &e.p.b + } + + // &mut e.p.b escapes (returned): the mutable form of the same shape. + fun mut_ref(e: &mut E): &mut u8 { + &mut e.p.b + } + + fun mk(sel: u64, b: u8): E { + if (sel == 0) { E::P0 { lead: 1, p: Inner { a: 7, b } } } else { + E::P1 { lead: 2, p: Inner { a: 7, b } } + } + } + + fun read_via_imm_ref(sel: u64, b: u8): u8 { + let e = mk(sel, b); + *imm_ref(&e) + } + + fun bump_via_mut_ref(sel: u64, b: u8): u8 { + let e = mk(sel, b); + let r = mut_ref(&mut e); + *r = *r + 1; + *r + } +} + +// RUN: execute 0x77::variant_field_tail_borrows::read_via_imm_ref --args 0, 33 +// CHECK: results: 33 +// RUN: execute 0x77::variant_field_tail_borrows::read_via_imm_ref --args 1, 33 +// CHECK: results: 33 +// RUN: execute 0x77::variant_field_tail_borrows::bump_via_mut_ref --args 0, 40 +// CHECK: results: 41 +// RUN: execute 0x77::variant_field_tail_borrows::bump_via_mut_ref --args 1, 40 +// CHECK: results: 41 diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_uniform.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_uniform.exp index 5d5a3c7c0ac..24dd010ae8c 100644 --- a/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_uniform.exp +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/enums/variant_field_uniform.exp @@ -2,13 +2,13 @@ // module 0x42::enum_variant_field_uniform fun read_value() { - frame_data_size: 64 + frame_data_size: 48 entry_gas: 95 code: 0: EnumNew [8] desc=2 tag #0 1: HeapMoveTo8 [8]+8 <- [0] 2: SlotBorrow [16] <- &[8] - 3: EnumReadVariantField(8) [32] <- (*[16]).8 + 3: HeapReadOffset(8) [32] <- (*[16]).8 4: Move8 [0] <- [32] 5: Return safe_point_layouts: @@ -16,18 +16,18 @@ fun read_value() { } fun run_computed() { - frame_data_size: 72 + frame_data_size: 56 entry_gas: 211 code: 0: StoreImm8 [24] <- #0 1: EnumNew [16] desc=2 tag #1 2: HeapMoveTo8 [16]+8 <- [0] 3: HeapMoveTo8 [16]+16 <- [24] - 4: SlotBorrow [96] <- &[16] - 5: Move8 [112] <- [8] + 4: SlotBorrow [80] <- &[16] + 5: Move8 [96] <- [8] 6: CallIndirect 0x42::enum_variant_field_uniform::write_computed 7: SlotBorrow [32] <- &[16] - 8: EnumReadVariantField(8) [24] <- (*[32]).8 + 8: HeapReadOffset(8) [24] <- (*[32]).8 9: Move8 [0] <- [24] 10: Return safe_point_layouts: @@ -35,16 +35,16 @@ fun run_computed() { } fun run_simple() { - frame_data_size: 72 + frame_data_size: 56 entry_gas: 183 code: 0: EnumNew [16] desc=2 tag #0 1: HeapMoveTo8 [16]+8 <- [0] - 2: SlotBorrow [96] <- &[16] - 3: Move8 [112] <- [8] + 2: SlotBorrow [80] <- &[16] + 3: Move8 [96] <- [8] 4: CallIndirect 0x42::enum_variant_field_uniform::write_simple 5: SlotBorrow [24] <- &[16] - 6: EnumReadVariantField(8) [40] <- (*[24]).8 + 6: HeapReadOffset(8) [40] <- (*[24]).8 7: Move8 [0] <- [40] 8: Return safe_point_layouts: @@ -52,11 +52,11 @@ fun run_simple() { } fun write_computed() { - frame_data_size: 48 + frame_data_size: 32 entry_gas: 33 code: 0: AddU64Imm [24] <- [16] + #1 - 1: EnumWriteVariantField(8) (*[0]).8 <- [24] + 1: HeapWriteOffset(8) (*[0]).8 <- [24] 2: Return } diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/generics/chained_field_fusion_generic.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/generics/chained_field_fusion_generic.exp new file mode 100644 index 00000000000..316a1374559 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/generics/chained_field_fusion_generic.exp @@ -0,0 +1,87 @@ +=== stackless === +// module 0x77::chained_field_fusion_generic + +fun mk(r0, r1): S<_0> { + slots: params(2), locals(0), temps(3) + r0: _0 + r1: _0 + r2: 0x77::chained_field_fusion_generic::B<_0> + r3: 0x77::chained_field_fusion_generic::A<_0> + r4: 0x77::chained_field_fusion_generic::S<_0> + code: + L0: + 0: r2 := pack 0x77::chained_field_fusion_generic::B<_0>, [r1] + 1: r3 := pack 0x77::chained_field_fusion_generic::A<_0>, [r0, r2] + 2: r4 := pack 0x77::chained_field_fusion_generic::S<_0>, [r3] + 3: ret [r4] +} + +fun read_chain(r0): _0 { + slots: params(1), locals(0), temps(1) + r0: &0x77::chained_field_fusion_generic::S<_0> + r1: _0 + code: + L0: + 0: r1 := read_field_chain S::x.A::y.B::z, r0 + 1: ret [r1] +} + +fun run_u64(r0): u64 { + slots: params(1), locals(1), temps(0), xfer(2) + r0: u64 + r1: 0x77::chained_field_fusion_generic::S + code: + L0: + 0: x0 := ld_u64 0 + 1: [r1] := call mk, [x0, r0] + 2: x0 := imm_borrow_loc r1 + 3: [x0] := call read_chain, [x0] + 4: ret [x0] +} + +fun run_u8(r0): u8 { + slots: params(1), locals(1), temps(0), xfer(2) + r0: u8 + r1: 0x77::chained_field_fusion_generic::S + code: + L0: + 0: x0 := ld_u8 0 + 1: [r1] := call mk, [x0, r0] + 2: x0 := imm_borrow_loc r1 + 3: [x0] := call read_chain, [x0] + 4: ret [x0] +} +=== micro-ops === +// module 0x77::chained_field_fusion_generic + +fun mk(): skipped (generic function not instantiated) + +fun read_chain(): skipped (generic function not instantiated) + +fun run_u64() { + frame_data_size: 24 + entry_gas: 230 + code: + 0: StoreImm8 [48] <- #0 + 1: Move8 [56] <- [0] + 2: CallIndirect 0x77::chained_field_fusion_generic::mk + 3: Move(16) [8] <- [48] + 4: SlotBorrow [48] <- &[8] + 5: CallIndirect 0x77::chained_field_fusion_generic::read_chain + 6: Move8 [0] <- [48] + 7: Return +} + +fun run_u8() { + frame_data_size: 8 + entry_gas: 104 + code: + 0: StoreImm1 [32] <- #0 + 1: Move(1) [33] <- [0] + 2: CallIndirect 0x77::chained_field_fusion_generic::mk + 3: Move(2) [1] <- [32] + 4: SlotBorrow [32] <- &[1] + 5: CallIndirect 0x77::chained_field_fusion_generic::read_chain + 6: Move(1) [0] <- [32] + 7: Return +} diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/generics/chained_field_fusion_generic.move b/third_party/move/mono-move/testsuite/tests/test_cases/differential/generics/chained_field_fusion_generic.move new file mode 100644 index 00000000000..7e825db060a --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/generics/chained_field_fusion_generic.move @@ -0,0 +1,38 @@ +// RUN: publish --print(stackless,micro-ops) +module 0x77::chained_field_fusion_generic { + struct B has drop, copy { + z: T, + } + + struct A has drop, copy { + first: T, + y: B, + } + + struct S has drop, copy { + x: A, + } + + fun read_chain(s: &S): T { + s.x.y.z + } + + fun mk(first: T, z: T): S { + S { x: A { first, y: B { z } } } + } + + fun run_u64(z: u64): u64 { + let s = mk(0, z); + read_chain(&s) + } + + fun run_u8(z: u8): u8 { + let s = mk(0, z); + read_chain(&s) + } +} + +// RUN: execute 0x77::chained_field_fusion_generic::run_u64 --args 123 +// CHECK: results: 123 +// RUN: execute 0x77::chained_field_fusion_generic::run_u8 --args 250 +// CHECK: results: 250 diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/generics/generic_enum_variant_field.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/generics/generic_enum_variant_field.exp index c6bcb21681b..38d5e91c1d1 100644 --- a/third_party/move/mono-move/testsuite/tests/test_cases/differential/generics/generic_enum_variant_field.exp +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/generics/generic_enum_variant_field.exp @@ -13,11 +13,11 @@ fun divergent_u128() { 5: SlotBorrow [24] <- &[16] 6: EnumTestTag [72] <- (*[24]).tag == #0 7: JumpNotZeroByte @12 [72] gas_taken=4 gas_fallthrough=2 - 8: EnumBorrowVariantField [40] <- &(*[24]) by tag [None, Some(8)] + 8: EnumBorrowVariantFieldByTag [40] <- &(*[24]) by tag [None, Some(8)] 9: ReadRef [80] <- *[40] (size=8) 10: Move8 [0] <- [80] 11: Return - 12: EnumBorrowVariantField [40] <- &(*[24]) by tag [Some(24), None] + 12: EnumBorrowVariantFieldByTag [40] <- &(*[24]) by tag [Some(24), None] 13: Jump @9 gas=80 14: AddU64Imm [80] <- [8] + #100 15: EnumNew [16] desc=2 tag #1 @@ -40,11 +40,11 @@ fun divergent_u8() { 5: SlotBorrow [24] <- &[16] 6: EnumTestTag [57] <- (*[24]).tag == #0 7: JumpNotZeroByte @12 [57] gas_taken=4 gas_fallthrough=2 - 8: EnumBorrowVariantField [40] <- &(*[24]) by tag [None, Some(8)] + 8: EnumBorrowVariantFieldByTag [40] <- &(*[24]) by tag [None, Some(8)] 9: ReadRef [64] <- *[40] (size=8) 10: Move8 [0] <- [64] 11: Return - 12: EnumBorrowVariantField [40] <- &(*[24]) by tag [Some(16), None] + 12: EnumBorrowVariantFieldByTag [40] <- &(*[24]) by tag [Some(16), None] 13: Jump @9 gas=80 14: AddU64Imm [64] <- [8] + #100 15: EnumNew [16] desc=3 tag #1 @@ -56,7 +56,7 @@ fun divergent_u8() { } fun uniform_u128() { - frame_data_size: 88 + frame_data_size: 72 entry_gas: 3 code: 0: JumpIntCmp @9 [0] != u64 #0 gas_taken=93 gas_fallthrough=86 @@ -65,7 +65,7 @@ fun uniform_u128() { 3: HeapMoveTo [16]+8 <- [24] (size=16) 4: HeapMoveTo8 [16]+24 <- [8] 5: SlotBorrow [40] <- &[16] - 6: EnumReadVariantField(8) [56] <- (*[40]).24 + 6: HeapReadOffset(8) [56] <- (*[40]).24 7: Move8 [0] <- [56] 8: Return 9: StoreImm16 [24] <- #2 @@ -80,7 +80,7 @@ fun uniform_u128() { } fun uniform_u8() { - frame_data_size: 80 + frame_data_size: 64 entry_gas: 3 code: 0: JumpIntCmp @9 [0] != u64 #0 gas_taken=48 gas_fallthrough=41 @@ -89,7 +89,7 @@ fun uniform_u8() { 3: HeapMoveTo [16]+8 <- [24] (size=1) 4: HeapMoveTo8 [16]+16 <- [8] 5: SlotBorrow [32] <- &[16] - 6: EnumReadVariantField(8) [48] <- (*[32]).16 + 6: HeapReadOffset(8) [48] <- (*[32]).16 7: Move8 [0] <- [48] 8: Return 9: StoreImm1 [24] <- #2 @@ -104,7 +104,7 @@ fun uniform_u8() { } fun write_uniform() { - frame_data_size: 80 + frame_data_size: 64 entry_gas: 161 code: 0: StoreImm8 [32] <- #5 @@ -112,11 +112,11 @@ fun write_uniform() { 2: HeapMoveTo8 [8]+8 <- [32] 3: HeapMoveTo8 [8]+16 <- [0] 4: SlotBorrow [16] <- &[8] - 5: EnumReadVariantField(8) [32] <- (*[16]).16 + 5: HeapReadOffset(8) [32] <- (*[16]).16 6: AddU64Imm [32] <- [32] + #7 - 7: EnumWriteVariantField(8) (*[16]).16 <- [32] + 7: HeapWriteOffset(8) (*[16]).16 <- [32] 8: SlotBorrow [40] <- &[8] - 9: EnumReadVariantField(8) [32] <- (*[40]).16 + 9: HeapReadOffset(8) [32] <- (*[40]).16 10: Move8 [0] <- [32] 11: Return safe_point_layouts: diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/global_storage/basic.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/global_storage/basic.exp index 8629642f112..7d1cb04f9cd 100644 --- a/third_party/move/mono-move/testsuite/tests/test_cases/differential/global_storage/basic.exp +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/global_storage/basic.exp @@ -215,7 +215,7 @@ fun double_publish(r0) { } fun publish_and_read(r0, r1, r2): u64 { - slots: params(3), locals(1), temps(7) + slots: params(3), locals(1), temps(6) r0: signer r1: address r2: u64 @@ -226,7 +226,6 @@ fun publish_and_read(r0, r1, r2): u64 { r7: u64 r8: 0x42::globals::Inner r9: 0x42::globals::R - r10: &0x42::globals::Inner code: L0: 0: r4 := imm_borrow_loc r0 @@ -240,13 +239,11 @@ fun publish_and_read(r0, r1, r2): u64 { 8: r5 := read_field R::x, r3 9: r7 := read_field R::y, r3 10: r7 := add r5, r7 - 11: r10 := imm_borrow_field R::inner, r3 - 12: r5 := read_field Inner::a, r10 - 13: r5 := add r7, r5 - 14: r10 := imm_borrow_field R::inner, r3 - 15: r7 := read_field Inner::b, r10 - 16: r7 := add r5, r7 - 17: ret [r7] + 11: r5 := read_field_chain R::inner.Inner::a, r3 + 12: r5 := add r7, r5 + 13: r7 := read_field_chain R::inner.Inner::b, r3 + 14: r7 := add r5, r7 + 15: ret [r7] } fun publish_and_take(r0, r1, r2): u64 { @@ -281,7 +278,7 @@ fun publish_and_take(r0, r1, r2): u64 { } fun publish_mutate_read(r0, r1, r2): u64 { - slots: params(3), locals(1), temps(9) + slots: params(3), locals(1), temps(7) r0: signer r1: address r2: u64 @@ -292,9 +289,7 @@ fun publish_mutate_read(r0, r1, r2): u64 { r7: u64 r8: 0x42::globals::Inner r9: 0x42::globals::R - r10: &mut 0x42::globals::Inner - r11: &0x42::globals::R - r12: &0x42::globals::Inner + r10: &0x42::globals::R code: L0: 0: r4 := imm_borrow_loc r0 @@ -307,12 +302,10 @@ fun publish_mutate_read(r0, r1, r2): u64 { 7: r3 := mut_borrow_global 0x42::globals::R, r1 8: r5 := read_field R::x, r3 9: r5 := add r5, #1 - 10: r10 := mut_borrow_field R::inner, r3 - 11: write_field Inner::b, r10, r5 - 12: r11 := imm_borrow_global 0x42::globals::R, r1 - 13: r12 := imm_borrow_field R::inner, r11 - 14: r5 := read_field Inner::b, r12 - 15: ret [r5] + 10: write_field_chain R::inner.Inner::b, r3, r5 + 11: r10 := imm_borrow_global 0x42::globals::R, r1 + 12: r5 := read_field_chain R::inner.Inner::b, r10 + 13: ret [r5] } fun take_missing(r0): u64 { @@ -378,8 +371,8 @@ fun double_publish() { } fun publish_and_read() { - frame_data_size: 200 - entry_gas: 372 + frame_data_size: 184 + entry_gas: 368 code: 0: SlotBorrow [88] <- &[0] 1: AddU64Imm [104] <- [64] + #1 @@ -390,21 +383,19 @@ fun publish_and_read() { 6: Move(16) [160] <- [128] 7: Move8 [152] <- [104] 8: Move8 [144] <- [64] - 9: HeapNew [192] desc=2 - 10: HeapMoveTo [192]+0 <- [144] (size=32) - 11: MoveTo<0x42::globals::R> signer=[88], src=[192] + 9: HeapNew [176] desc=2 + 10: HeapMoveTo [176]+0 <- [144] (size=32) + 11: MoveTo<0x42::globals::R> signer=[88], src=[176] 12: BorrowGlobal<0x42::globals::R> [72] <- addr=[32], ty= 13: ReadRefOffset [104] <- *([72]+0) (size=8) 14: ReadRefOffset [120] <- *([72]+8) (size=8) 15: AddU64 [120] <- [104] + [120] - 16: DeriveRefOffsetImm [176] <- [72]+16 - 17: ReadRefOffset [104] <- *([176]+0) (size=8) - 18: AddU64 [104] <- [120] + [104] - 19: DeriveRefOffsetImm [176] <- [72]+16 - 20: ReadRefOffset [120] <- *([176]+8) (size=8) - 21: AddU64 [120] <- [104] + [120] - 22: Move8 [0] <- [120] - 23: Return + 16: ReadRefOffset [104] <- *([72]+16) (size=8) + 17: AddU64 [104] <- [120] + [104] + 18: ReadRefOffset [120] <- *([72]+24) (size=8) + 19: AddU64 [120] <- [104] + [120] + 20: Move8 [0] <- [120] + 21: Return safe_point_layouts: 9: [] } @@ -443,8 +434,8 @@ fun publish_and_take() { } fun publish_mutate_read() { - frame_data_size: 232 - entry_gas: 342 + frame_data_size: 200 + entry_gas: 338 code: 0: SlotBorrow [88] <- &[0] 1: StoreImm8 [104] <- #0 @@ -455,19 +446,17 @@ fun publish_mutate_read() { 6: Move(16) [160] <- [128] 7: Move8 [152] <- [104] 8: Move8 [144] <- [64] - 9: HeapNew [224] desc=2 - 10: HeapMoveTo [224]+0 <- [144] (size=32) - 11: MoveTo<0x42::globals::R> signer=[88], src=[224] + 9: HeapNew [192] desc=2 + 10: HeapMoveTo [192]+0 <- [144] (size=32) + 11: MoveTo<0x42::globals::R> signer=[88], src=[192] 12: BorrowGlobalMut<0x42::globals::R> [72] <- addr=[32], ty= 13: ReadRefOffset [104] <- *([72]+0) (size=8) 14: AddU64Imm [104] <- [104] + #1 - 15: DeriveRefOffsetImm [176] <- [72]+16 - 16: WriteRefOffset *([176]+8) <- [104] (size=8) - 17: BorrowGlobal<0x42::globals::R> [192] <- addr=[32], ty= - 18: DeriveRefOffsetImm [208] <- [192]+16 - 19: ReadRefOffset [104] <- *([208]+8) (size=8) - 20: Move8 [0] <- [104] - 21: Return + 15: WriteRefOffset *([72]+24) <- [104] (size=8) + 16: BorrowGlobal<0x42::globals::R> [176] <- addr=[32], ty= + 17: ReadRefOffset [104] <- *([176]+24) (size=8) + 18: Move8 [0] <- [104] + 19: Return safe_point_layouts: 9: [] 12: [] diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/global_storage/nested.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/global_storage/nested.exp index 0d5aa050d0f..8068590f024 100644 --- a/third_party/move/mono-move/testsuite/tests/test_cases/differential/global_storage/nested.exp +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/global_storage/nested.exp @@ -39,8 +39,8 @@ fun mutate_vec_push() { } fun publish_nested_read() { - frame_data_size: 224 - entry_gas: 308 + frame_data_size: 208 + entry_gas: 306 code: 0: VecNew [72] 1: SlotBorrow [96] <- &[72] @@ -53,19 +53,18 @@ fun publish_nested_read() { 8: Move8 [136] <- [72] 9: Move8 [152] <- [136] 10: Move8 [144] <- [112] - 11: HeapNew [216] desc=3 - 12: HeapMoveTo [216]+0 <- [144] (size=16) - 13: MoveTo<0x42::nested_globals::Outer> signer=[120], src=[216] + 11: HeapNew [200] desc=3 + 12: HeapMoveTo [200]+0 <- [144] (size=16) + 13: MoveTo<0x42::nested_globals::Outer> signer=[120], src=[200] 14: BorrowGlobal<0x42::nested_globals::Outer> [80] <- addr=[32], ty= 15: ReadRefOffset [112] <- *([80]+0) (size=8) 16: DeriveRefOffsetImm [160] <- [80]+8 - 17: DeriveRefOffsetImm [176] <- [160]+0 - 18: StoreImm8 [192] <- #1 - 19: VecBorrow [200] <- &[176][[192]] (elem_size=8) - 20: ReadRef [192] <- *[200] (size=8) - 21: AddU64 [192] <- [112] + [192] - 22: Move8 [0] <- [192] - 23: Return + 17: StoreImm8 [176] <- #1 + 18: VecBorrow [184] <- &[160][[176]] (elem_size=8) + 19: ReadRef [176] <- *[184] (size=8) + 20: AddU64 [176] <- [112] + [176] + 21: Move8 [0] <- [176] + 22: Return safe_point_layouts: 2: [] 5: [] @@ -137,6 +136,6 @@ fun publish_vec_take() { === frame-layout === // module 0x42::nested_globals fun mutate_vec_push: heap_ptr_offsets=[72, 80, 96, 112, 144, 152, 168, 184] zero_frame=true -fun publish_nested_read: heap_ptr_offsets=[72, 80, 96, 120, 136, 152, 160, 176, 200] zero_frame=true +fun publish_nested_read: heap_ptr_offsets=[72, 80, 96, 120, 136, 152, 160, 184] zero_frame=true fun publish_vec_read: heap_ptr_offsets=[72, 80, 96, 120, 144, 152, 176] zero_frame=true fun publish_vec_take: heap_ptr_offsets=[72, 80, 88, 104, 136, 144, 168] zero_frame=true diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/chained_field_fusion.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/chained_field_fusion.exp new file mode 100644 index 00000000000..9f8230420e1 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/chained_field_fusion.exp @@ -0,0 +1,302 @@ +=== stackless === +// module 0x66::chained_field_fusion + +fun mk(r0, r1): S { + slots: params(2), locals(0), temps(5) + r0: u64 + r1: u64 + r2: u64 + r3: u64 + r4: 0x66::chained_field_fusion::B + r5: 0x66::chained_field_fusion::A + r6: 0x66::chained_field_fusion::S + code: + L0: + 0: r2 := ld_u64 0 + 1: r3 := ld_u64 0 + 2: r4 := pack 0x66::chained_field_fusion::B, [r3, r0] + 3: r5 := pack 0x66::chained_field_fusion::A, [r2, r4, r1] + 4: r6 := pack 0x66::chained_field_fusion::S, [r5] + 5: ret [r6] +} + +fun read_by_value(r0): u64 { + slots: params(1), locals(0), temps(1) + r0: 0x66::chained_field_fusion::S + r1: u64 + code: + L0: + 0: r1 := read_local_field_chain S::x.A::y.B::z, r0 + 1: ret [r1] +} + +fun read_by_ref(r0): u64 { + slots: params(1), locals(0), temps(1) + r0: &0x66::chained_field_fusion::S + r1: u64 + code: + L0: + 0: r1 := read_field_chain S::x.A::y.B::z, r0 + 1: ret [r1] +} + +fun write_by_ref(r0, r1) { + slots: params(2), locals(0), temps(0) + r0: &mut 0x66::chained_field_fusion::S + r1: u64 + code: + L0: + 0: write_field_chain S::x.A::y.B::z, r0, r1 + 1: ret [] +} + +fun borrow_chain(r0): &u64 { + slots: params(1), locals(0), temps(1) + r0: &0x66::chained_field_fusion::S + r1: &u64 + code: + L0: + 0: r1 := imm_borrow_field_chain S::x.A::y.B::z, r0 + 1: ret [r1] +} + +fun use_borrow_chain(r0): u64 { + slots: params(1), locals(0), temps(1), xfer(1) + r0: &0x66::chained_field_fusion::S + r1: u64 + code: + L0: + 0: [x0] := call borrow_chain, [r0] + 1: r1 := read_ref x0 + 2: ret [r1] +} + +fun read_shared(r0): u64 { + slots: params(1), locals(1), temps(2) + r0: &0x66::chained_field_fusion::S + r1: &0x66::chained_field_fusion::A + r2: u64 + r3: u64 + code: + L0: + 0: r1 := imm_borrow_field S::x, r0 + 1: r2 := read_field_chain A::y.B::z, r1 + 2: r3 := read_field A::w, r1 + 3: r3 := add r2, r3 + 4: ret [r3] +} + +fun test_read_by_value(r0): u64 { + slots: params(1), locals(1), temps(0), xfer(2) + r0: u64 + r1: 0x66::chained_field_fusion::S + code: + L0: + 0: x1 := ld_u64 0 + 1: [r1] := call mk, [r0, x1] + 2: [x0] := call read_by_value, [r1] + 3: ret [x0] +} + +fun test_read_by_ref(r0): u64 { + slots: params(1), locals(1), temps(0), xfer(2) + r0: u64 + r1: 0x66::chained_field_fusion::S + code: + L0: + 0: x1 := ld_u64 0 + 1: [r1] := call mk, [r0, x1] + 2: x0 := imm_borrow_loc r1 + 3: [x0] := call read_by_ref, [x0] + 4: ret [x0] +} + +fun test_write_by_ref(r0, r1): u64 { + slots: params(2), locals(1), temps(0), xfer(2) + r0: u64 + r1: u64 + r2: 0x66::chained_field_fusion::S + code: + L0: + 0: x1 := ld_u64 0 + 1: [r2] := call mk, [r0, x1] + 2: x0 := mut_borrow_loc r2 + 3: [] := call write_by_ref, [x0, r1] + 4: [x0] := call read_by_value, [r2] + 5: ret [x0] +} + +fun test_borrow_chain(r0): u64 { + slots: params(1), locals(1), temps(0), xfer(2) + r0: u64 + r1: 0x66::chained_field_fusion::S + code: + L0: + 0: x1 := ld_u64 0 + 1: [r1] := call mk, [r0, x1] + 2: x0 := imm_borrow_loc r1 + 3: [x0] := call use_borrow_chain, [x0] + 4: ret [x0] +} + +fun test_read_shared(r0, r1): u64 { + slots: params(2), locals(1), temps(0), xfer(2) + r0: u64 + r1: u64 + r2: 0x66::chained_field_fusion::S + code: + L0: + 0: [r2] := call mk, [r0, r1] + 1: x0 := imm_borrow_loc r2 + 2: [x0] := call read_shared, [x0] + 3: ret [x0] +} +=== micro-ops === +// module 0x66::chained_field_fusion + +fun mk() { + frame_data_size: 112 + entry_gas: 448 + code: + 0: StoreImm8 [16] <- #0 + 1: StoreImm8 [24] <- #0 + 2: Move8 [40] <- [0] + 3: Move8 [32] <- [24] + 4: Move8 [72] <- [8] + 5: Move(16) [56] <- [32] + 6: Move8 [48] <- [16] + 7: Move(32) [80] <- [48] + 8: Move(32) [0] <- [80] + 9: Return +} + +fun read_by_value() { + frame_data_size: 40 + entry_gas: 80 + code: + 0: Move8 [32] <- [16] + 1: Move8 [0] <- [32] + 2: Return +} + +fun read_by_ref() { + frame_data_size: 24 + entry_gas: 80 + code: + 0: ReadRefOffset [16] <- *([0]+16) (size=8) + 1: Move8 [0] <- [16] + 2: Return +} + +fun write_by_ref() { + frame_data_size: 24 + entry_gas: 28 + code: + 0: WriteRefOffset *([0]+16) <- [16] (size=8) + 1: Return +} + +fun borrow_chain() { + frame_data_size: 32 + entry_gas: 104 + code: + 0: DeriveRefOffsetImm [16] <- [0]+16 + 1: Move(16) [0] <- [16] + 2: Return +} + +fun use_borrow_chain() { + frame_data_size: 24 + entry_gas: 140 + code: + 0: Move(16) [48] <- [0] + 1: CallIndirect 0x66::chained_field_fusion::borrow_chain + 2: ReadRef [16] <- *[48] (size=8) + 3: Move8 [0] <- [16] + 4: Return +} + +fun read_shared() { + frame_data_size: 48 + entry_gas: 113 + code: + 0: DeriveRefOffsetImm [16] <- [0]+0 + 1: ReadRefOffset [32] <- *([16]+16) (size=8) + 2: ReadRefOffset [40] <- *([16]+24) (size=8) + 3: AddU64 [40] <- [32] + [40] + 4: Move8 [0] <- [40] + 5: Return +} + +fun test_read_by_value() { + frame_data_size: 40 + entry_gas: 324 + code: + 0: StoreImm8 [72] <- #0 + 1: Move8 [64] <- [0] + 2: CallIndirect 0x66::chained_field_fusion::mk + 3: Move(32) [8] <- [64] + 4: Move(32) [64] <- [8] + 5: CallIndirect 0x66::chained_field_fusion::read_by_value + 6: Move8 [0] <- [64] + 7: Return +} + +fun test_read_by_ref() { + frame_data_size: 40 + entry_gas: 278 + code: + 0: StoreImm8 [72] <- #0 + 1: Move8 [64] <- [0] + 2: CallIndirect 0x66::chained_field_fusion::mk + 3: Move(32) [8] <- [64] + 4: SlotBorrow [64] <- &[8] + 5: CallIndirect 0x66::chained_field_fusion::read_by_ref + 6: Move8 [0] <- [64] + 7: Return +} + +fun test_write_by_ref() { + frame_data_size: 48 + entry_gas: 412 + code: + 0: StoreImm8 [80] <- #0 + 1: Move8 [72] <- [0] + 2: CallIndirect 0x66::chained_field_fusion::mk + 3: Move(32) [16] <- [72] + 4: SlotBorrow [72] <- &[16] + 5: Move8 [88] <- [8] + 6: CallIndirect 0x66::chained_field_fusion::write_by_ref + 7: Move(32) [72] <- [16] + 8: CallIndirect 0x66::chained_field_fusion::read_by_value + 9: Move8 [0] <- [72] + 10: Return +} + +fun test_borrow_chain() { + frame_data_size: 40 + entry_gas: 278 + code: + 0: StoreImm8 [72] <- #0 + 1: Move8 [64] <- [0] + 2: CallIndirect 0x66::chained_field_fusion::mk + 3: Move(32) [8] <- [64] + 4: SlotBorrow [64] <- &[8] + 5: CallIndirect 0x66::chained_field_fusion::use_borrow_chain + 6: Move8 [0] <- [64] + 7: Return +} + +fun test_read_shared() { + frame_data_size: 48 + entry_gas: 276 + code: + 0: Move8 [80] <- [8] + 1: Move8 [72] <- [0] + 2: CallIndirect 0x66::chained_field_fusion::mk + 3: Move(32) [16] <- [72] + 4: SlotBorrow [72] <- &[16] + 5: CallIndirect 0x66::chained_field_fusion::read_shared + 6: Move8 [0] <- [72] + 7: Return +} diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/chained_field_fusion.masm b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/chained_field_fusion.masm new file mode 100644 index 00000000000..3ba2480f544 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/chained_field_fusion.masm @@ -0,0 +1,150 @@ +// RUN: publish --print(stackless,micro-ops) +module 0x66::chained_field_fusion + +struct B has drop+copy + b_lead: u64 + z: u64 + +struct A has drop+copy + a_lead: u64 + y: B + w: u64 + +struct S has drop+copy + x: A + +// Build S { x: A { a_lead: 0, y: B { b_lead: 0, z }, w } }. +fun mk(z: u64, w: u64): S + ld_u64 0 + ld_u64 0 + move_loc z + pack B + move_loc w + pack A + pack S + ret + +// Read a 3-level chain from a by-value local. +fun read_by_value(s: S): u64 + borrow_loc s + borrow_field S, x + borrow_field A, y + borrow_field B, z + read_ref + ret + +// Read a 3-level chain from a &S. +fun read_by_ref(s: &S): u64 + move_loc s + borrow_field S, x + borrow_field A, y + borrow_field B, z + read_ref + ret + +// Write through a 3-level chain on a &mut S. +fun write_by_ref(s: &mut S, v: u64) + move_loc v + move_loc s + mut_borrow_field S, x + mut_borrow_field A, y + mut_borrow_field B, z + write_ref + ret + +// Pure-borrow chain: returns &s.x.y.z. +fun borrow_chain(s: &S): &u64 + move_loc s + borrow_field S, x + borrow_field A, y + borrow_field B, z + ret + +fun use_borrow_chain(s: &S): u64 + move_loc s + call borrow_chain + read_ref + ret + +fun read_shared(s: &S): u64 + local r: &A + move_loc s + borrow_field S, x + st_loc r + copy_loc r + borrow_field A, y + borrow_field B, z + read_ref + move_loc r + borrow_field A, w + read_ref + add + ret + +fun test_read_by_value(z: u64): u64 + local s: S + move_loc z + ld_u64 0 + call mk + st_loc s + move_loc s + call read_by_value + ret + +fun test_read_by_ref(z: u64): u64 + local s: S + move_loc z + ld_u64 0 + call mk + st_loc s + borrow_loc s + call read_by_ref + ret + +fun test_write_by_ref(z: u64, v: u64): u64 + local s: S + move_loc z + ld_u64 0 + call mk + st_loc s + mut_borrow_loc s + move_loc v + call write_by_ref + move_loc s + call read_by_value + ret + +fun test_borrow_chain(z: u64): u64 + local s: S + move_loc z + ld_u64 0 + call mk + st_loc s + borrow_loc s + call use_borrow_chain + ret + +fun test_read_shared(z: u64, w: u64): u64 + local s: S + move_loc z + move_loc w + call mk + st_loc s + borrow_loc s + call read_shared + ret + +// RUN: execute 0x66::chained_field_fusion::test_read_by_value --args 42 +// CHECK: results: 42 + +// RUN: execute 0x66::chained_field_fusion::test_read_by_ref --args 7 +// CHECK: results: 7 + +// RUN: execute 0x66::chained_field_fusion::test_write_by_ref --args 5, 99 +// CHECK: results: 99 + +// RUN: execute 0x66::chained_field_fusion::test_borrow_chain --args 13 +// CHECK: results: 13 + +// RUN: execute 0x66::chained_field_fusion::test_read_shared --args 3, 4 +// CHECK: results: 7 diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/field_chain_borrows.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/field_chain_borrows.exp new file mode 100644 index 00000000000..f3b3058c58e --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/field_chain_borrows.exp @@ -0,0 +1,188 @@ +=== stackless === +// module 0x88::field_chain_borrows + +fun build(r0): S { + slots: params(1), locals(0), temps(4) + r0: u64 + r1: u64 + r2: 0x88::field_chain_borrows::B + r3: 0x88::field_chain_borrows::A + r4: 0x88::field_chain_borrows::S + code: + L0: + 0: r1 := ld_u64 0 + 1: r2 := pack 0x88::field_chain_borrows::B, [r1, r0] + 2: r1 := ld_u64 0 + 3: r3 := pack 0x88::field_chain_borrows::A, [r2, r1] + 4: r4 := pack 0x88::field_chain_borrows::S, [r3] + 5: ret [r4] +} + +fun bump_via_deep_mut(r0): u64 { + slots: params(1), locals(2), temps(1), xfer(1) + r0: u64 + r1: 0x88::field_chain_borrows::S + r2: &mut u64 + r3: u64 + code: + L0: + 0: [r1] := call build, [r0] + 1: x0 := mut_borrow_loc r1 + 2: [r2] := call deep_mut, [x0] + 3: r3 := read_ref r2 + 4: r3 := add r3, #1 + 5: write_ref r2, r3 + 6: r3 := read_local_field_chain S::x.A::y.B::z, r1 + 7: ret [r3] +} + +fun deep_mut(r0): &mut u64 { + slots: params(1), locals(0), temps(1) + r0: &mut 0x88::field_chain_borrows::S + r1: &mut u64 + code: + L0: + 0: r1 := mut_borrow_field_chain S::x.A::y.B::z, r0 + 1: ret [r1] +} + +fun read_ref(r0): u64 { + slots: params(1), locals(0), temps(1) + r0: &u64 + r1: u64 + code: + L0: + 0: r1 := read_ref r0 + 1: ret [r1] +} + +fun read_via_call(r0): u64 { + slots: params(1), locals(1), temps(0), xfer(1) + r0: u64 + r1: 0x88::field_chain_borrows::S + code: + L0: + 0: [r1] := call build, [r0] + 1: x0 := imm_borrow_local_field_chain S::x.A::y.B::z, r1 + 2: [x0] := call read_ref, [x0] + 3: ret [x0] +} + +fun read_via_local_ref(r0): u64 { + slots: params(1), locals(1), temps(1), xfer(1) + r0: u64 + r1: 0x88::field_chain_borrows::S + r2: u64 + code: + L0: + 0: [r1] := call build, [r0] + 1: r2 := read_local_field_chain S::x.A::y.B::z, r1 + 2: ret [r2] +} + +fun write_via_local(r0): u64 { + slots: params(1), locals(1), temps(1), xfer(1) + r0: u64 + r1: 0x88::field_chain_borrows::S + r2: u64 + code: + L0: + 0: x0 := ld_u64 0 + 1: [r1] := call build, [x0] + 2: r2 := add r0, #1 + 3: write_local_field_chain S::x.A::y.B::z, r1, r2 + 4: r2 := read_local_field_chain S::x.A::y.B::z, r1 + 5: ret [r2] +} +=== micro-ops === +// module 0x88::field_chain_borrows + +fun build() { + frame_data_size: 80 + entry_gas: 352 + code: + 0: StoreImm8 [8] <- #0 + 1: Move8 [24] <- [0] + 2: Move8 [16] <- [8] + 3: StoreImm8 [8] <- #0 + 4: Move8 [48] <- [8] + 5: Move(16) [32] <- [16] + 6: Move(24) [56] <- [32] + 7: Move(24) [0] <- [56] + 8: Return +} + +fun bump_via_deep_mut() { + frame_data_size: 56 + entry_gas: 359 + code: + 0: Move8 [80] <- [0] + 1: CallIndirect 0x88::field_chain_borrows::build + 2: Move(24) [8] <- [80] + 3: SlotBorrow [80] <- &[8] + 4: CallIndirect 0x88::field_chain_borrows::deep_mut + 5: Move(16) [32] <- [80] + 6: ReadRef [48] <- *[32] (size=8) + 7: AddU64Imm [48] <- [48] + #1 + 8: WriteRef *[32] <- [48] (size=8) + 9: Move8 [48] <- [16] + 10: Move8 [0] <- [48] + 11: Return +} + +fun deep_mut() { + frame_data_size: 32 + entry_gas: 104 + code: + 0: DeriveRefOffsetImm [16] <- [0]+8 + 1: Move(16) [0] <- [16] + 2: Return +} + +fun read_ref() { + frame_data_size: 24 + entry_gas: 80 + code: + 0: ReadRef [16] <- *[0] (size=8) + 1: Move8 [0] <- [16] + 2: Return +} + +fun read_via_call() { + frame_data_size: 32 + entry_gas: 226 + code: + 0: Move8 [56] <- [0] + 1: CallIndirect 0x88::field_chain_borrows::build + 2: Move(24) [8] <- [56] + 3: SlotBorrow [56] <- &[16] + 4: CallIndirect 0x88::field_chain_borrows::read_ref + 5: Move8 [0] <- [56] + 6: Return +} + +fun read_via_local_ref() { + frame_data_size: 40 + entry_gas: 190 + code: + 0: Move8 [64] <- [0] + 1: CallIndirect 0x88::field_chain_borrows::build + 2: Move(24) [8] <- [64] + 3: Move8 [32] <- [16] + 4: Move8 [0] <- [32] + 5: Return +} + +fun write_via_local() { + frame_data_size: 40 + entry_gas: 223 + code: + 0: StoreImm8 [64] <- #0 + 1: CallIndirect 0x88::field_chain_borrows::build + 2: Move(24) [8] <- [64] + 3: AddU64Imm [32] <- [0] + #1 + 4: Move8 [16] <- [32] + 5: Move8 [32] <- [16] + 6: Move8 [0] <- [32] + 7: Return +} diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/field_chain_borrows.move b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/field_chain_borrows.move new file mode 100644 index 00000000000..e7b37cc8b80 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/field_chain_borrows.move @@ -0,0 +1,61 @@ +// RUN: publish --print(stackless,micro-ops) +module 0x88::field_chain_borrows { + struct B has drop, copy { + pad: u64, + z: u64, + } + + struct A has drop, copy { + y: B, + w: u64, + } + + struct S has drop, copy { + x: A, + } + + fun build(z: u64): S { + S { x: A { y: B { pad: 0, z }, w: 0 } } + } + + fun read_ref(r: &u64): u64 { + *r + } + + fun deep_mut(s: &mut S): &mut u64 { + &mut s.x.y.z + } + + fun read_via_call(z: u64): u64 { + let s = build(z); + read_ref(&s.x.y.z) + } + + fun read_via_local_ref(z: u64): u64 { + let s = build(z); + let r = &s.x.y.z; + *r + } + + fun write_via_local(z: u64): u64 { + let s = build(0); + s.x.y.z = z + 1; + s.x.y.z + } + + fun bump_via_deep_mut(z: u64): u64 { + let s = build(z); + let r = deep_mut(&mut s); + *r = *r + 1; + s.x.y.z + } +} + +// RUN: execute 0x88::field_chain_borrows::read_via_local_ref --args 5 +// CHECK: results: 5 +// RUN: execute 0x88::field_chain_borrows::read_via_call --args 6 +// CHECK: results: 6 +// RUN: execute 0x88::field_chain_borrows::write_via_local --args 9 +// CHECK: results: 10 +// RUN: execute 0x88::field_chain_borrows::bump_via_deep_mut --args 7 +// CHECK: results: 8 diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/field_chain_gap_terminal.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/field_chain_gap_terminal.exp new file mode 100644 index 00000000000..7a22698abe7 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/field_chain_gap_terminal.exp @@ -0,0 +1,136 @@ +=== stackless === +// module 0x67::field_chain_gap_terminal + +fun mk(r0): S { + slots: params(1), locals(0), temps(5) + r0: u64 + r1: u64 + r2: u64 + r3: 0x67::field_chain_gap_terminal::B + r4: 0x67::field_chain_gap_terminal::A + r5: 0x67::field_chain_gap_terminal::S + code: + L0: + 0: r1 := ld_u64 0 + 1: r2 := ld_u64 0 + 2: r3 := pack 0x67::field_chain_gap_terminal::B, [r2, r0] + 3: r4 := pack 0x67::field_chain_gap_terminal::A, [r1, r3] + 4: r5 := pack 0x67::field_chain_gap_terminal::S, [r4] + 5: ret [r5] +} + +fun read_gap(r0): u64 { + slots: params(1), locals(1), temps(1) + r0: &0x67::field_chain_gap_terminal::S + r1: u64 + r2: u64 + code: + L0: + 0: r1 := ld_u64 5 + 1: r2 := read_field_chain S::x.A::y.B::z, r0 + 2: ret [r2] +} + +fun write_gap(r0, r1) { + slots: params(2), locals(1), temps(0) + r0: &mut 0x67::field_chain_gap_terminal::S + r1: u64 + r2: u64 + code: + L0: + 0: r2 := ld_u64 9 + 1: write_field_chain S::x.A::y.B::z, r0, r1 + 2: ret [] +} + +fun test_read_gap(r0): u64 { + slots: params(1), locals(1), temps(0), xfer(1) + r0: u64 + r1: 0x67::field_chain_gap_terminal::S + code: + L0: + 0: [r1] := call mk, [r0] + 1: x0 := imm_borrow_loc r1 + 2: [x0] := call read_gap, [x0] + 3: ret [x0] +} + +fun test_write_gap(r0, r1): u64 { + slots: params(2), locals(1), temps(0), xfer(2) + r0: u64 + r1: u64 + r2: 0x67::field_chain_gap_terminal::S + code: + L0: + 0: [r2] := call mk, [r0] + 1: x0 := mut_borrow_loc r2 + 2: [] := call write_gap, [x0, r1] + 3: x0 := imm_borrow_loc r2 + 4: [x0] := call read_gap, [x0] + 5: ret [x0] +} +=== micro-ops === +// module 0x67::field_chain_gap_terminal + +fun mk() { + frame_data_size: 88 + entry_gas: 352 + code: + 0: StoreImm8 [8] <- #0 + 1: StoreImm8 [16] <- #0 + 2: Move8 [32] <- [0] + 3: Move8 [24] <- [16] + 4: Move(16) [48] <- [24] + 5: Move8 [40] <- [8] + 6: Move(24) [64] <- [40] + 7: Move(24) [0] <- [64] + 8: Return +} + +fun read_gap() { + frame_data_size: 32 + entry_gas: 82 + code: + 0: StoreImm8 [16] <- #5 + 1: ReadRefOffset [24] <- *([0]+16) (size=8) + 2: Move8 [0] <- [24] + 3: Return +} + +fun write_gap() { + frame_data_size: 32 + entry_gas: 30 + code: + 0: StoreImm8 [24] <- #9 + 1: WriteRefOffset *([0]+16) <- [16] (size=8) + 2: Return +} + +fun test_read_gap() { + frame_data_size: 32 + entry_gas: 226 + code: + 0: Move8 [56] <- [0] + 1: CallIndirect 0x67::field_chain_gap_terminal::mk + 2: Move(24) [8] <- [56] + 3: SlotBorrow [56] <- &[8] + 4: CallIndirect 0x67::field_chain_gap_terminal::read_gap + 5: Move8 [0] <- [56] + 6: Return +} + +fun test_write_gap() { + frame_data_size: 40 + entry_gas: 314 + code: + 0: Move8 [64] <- [0] + 1: CallIndirect 0x67::field_chain_gap_terminal::mk + 2: Move(24) [16] <- [64] + 3: SlotBorrow [64] <- &[16] + 4: Move8 [80] <- [8] + 5: CallIndirect 0x67::field_chain_gap_terminal::write_gap + 6: SlotBorrow [64] <- &[16] + 7: CallIndirect 0x67::field_chain_gap_terminal::read_gap + 8: Move8 [0] <- [64] + 9: Return +} diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/field_chain_gap_terminal.masm b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/field_chain_gap_terminal.masm new file mode 100644 index 00000000000..db0f8ff7f29 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/field_chain_gap_terminal.masm @@ -0,0 +1,77 @@ +// RUN: publish --print(stackless,micro-ops) +module 0x67::field_chain_gap_terminal + +struct B has drop+copy + b_lead: u64 + z: u64 + +struct A has drop+copy + a_lead: u64 + y: B + +struct S has drop+copy + x: A + +// Build S { x: A { a_lead: 0, y: B { b_lead: 0, z } } }. +fun mk(z: u64): S + ld_u64 0 + ld_u64 0 + move_loc z + pack B + pack A + pack S + ret + +// Read with a gap: the borrow run ends, two unrelated instructions execute +// (materialize + store a constant), then `read_ref` consumes the chain's ref. +fun read_gap(s: &S): u64 + local t: u64 + move_loc s + borrow_field S, x + borrow_field A, y + borrow_field B, z + ld_u64 5 + st_loc t + read_ref + ret + +// Write with a gap: the value and the borrow run are on the stack, two +// unrelated instructions execute, then `write_ref` consumes the chain's ref. +fun write_gap(s: &mut S, v: u64) + local t: u64 + move_loc v + move_loc s + mut_borrow_field S, x + mut_borrow_field A, y + mut_borrow_field B, z + ld_u64 9 + st_loc t + write_ref + ret + +fun test_read_gap(z: u64): u64 + local s: S + move_loc z + call mk + st_loc s + borrow_loc s + call read_gap + ret + +fun test_write_gap(z: u64, v: u64): u64 + local s: S + move_loc z + call mk + st_loc s + mut_borrow_loc s + move_loc v + call write_gap + borrow_loc s + call read_gap + ret + +// RUN: execute 0x67::field_chain_gap_terminal::test_read_gap --args 42 +// CHECK: results: 42 + +// RUN: execute 0x67::field_chain_gap_terminal::test_write_gap --args 5, 99 +// CHECK: results: 99 diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/mut_local_field_chain.exp b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/mut_local_field_chain.exp new file mode 100644 index 00000000000..b66b408d12e --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/mut_local_field_chain.exp @@ -0,0 +1,69 @@ +=== stackless === +// module 0x88::mut_local_field_chain + +fun build(r0): S { + slots: params(1), locals(0), temps(4) + r0: u64 + r1: u64 + r2: 0x88::mut_local_field_chain::B + r3: 0x88::mut_local_field_chain::A + r4: 0x88::mut_local_field_chain::S + code: + L0: + 0: r1 := ld_u64 0 + 1: r2 := pack 0x88::mut_local_field_chain::B, [r1, r0] + 2: r1 := ld_u64 0 + 3: r3 := pack 0x88::mut_local_field_chain::A, [r2, r1] + 4: r4 := pack 0x88::mut_local_field_chain::S, [r3] + 5: ret [r4] +} + +fun bump(r0): u64 { + slots: params(1), locals(2), temps(1), xfer(1) + r0: u64 + r1: 0x88::mut_local_field_chain::S + r2: &mut u64 + r3: u64 + code: + L0: + 0: [r1] := call build, [r0] + 1: r2 := mut_borrow_local_field_chain S::x.A::y.B::z, r1 + 2: r3 := read_ref r2 + 3: r3 := add r3, #1 + 4: write_ref r2, r3 + 5: r3 := read_local_field_chain S::x.A::y.B::z, r1 + 6: ret [r3] +} +=== micro-ops === +// module 0x88::mut_local_field_chain + +fun build() { + frame_data_size: 80 + entry_gas: 352 + code: + 0: StoreImm8 [8] <- #0 + 1: Move8 [24] <- [0] + 2: Move8 [16] <- [8] + 3: StoreImm8 [8] <- #0 + 4: Move8 [48] <- [8] + 5: Move(16) [32] <- [16] + 6: Move(24) [56] <- [32] + 7: Move(24) [0] <- [56] + 8: Return +} + +fun bump() { + frame_data_size: 56 + entry_gas: 249 + code: + 0: Move8 [80] <- [0] + 1: CallIndirect 0x88::mut_local_field_chain::build + 2: Move(24) [8] <- [80] + 3: SlotBorrow [32] <- &[16] + 4: ReadRef [48] <- *[32] (size=8) + 5: AddU64Imm [48] <- [48] + #1 + 6: WriteRef *[32] <- [48] (size=8) + 7: Move8 [48] <- [16] + 8: Move8 [0] <- [48] + 9: Return +} diff --git a/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/mut_local_field_chain.move b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/mut_local_field_chain.move new file mode 100644 index 00000000000..31bd306b608 --- /dev/null +++ b/third_party/move/mono-move/testsuite/tests/test_cases/differential/structs/mut_local_field_chain.move @@ -0,0 +1,31 @@ +// RUN: publish --print(stackless,micro-ops) +module 0x88::mut_local_field_chain { + struct B has drop, copy { + pad: u64, + z: u64, + } + + struct A has drop, copy { + y: B, + w: u64, + } + + struct S has drop, copy { + x: A, + } + + fun build(z: u64): S { + S { x: A { y: B { pad: 0, z }, w: 0 } } + } + + fun bump(z: u64): u64 { + let s = build(z); + let r = &mut s.x.y.z; + *r = *r + 1; + // Reads `s` again: must see the value written through `r`. + s.x.y.z + } +} + +// RUN: execute 0x88::mut_local_field_chain::bump --args 41 +// CHECK: results: 42