diff --git a/Cargo.lock b/Cargo.lock index dd5b632eb86..592b5d5788e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12680,6 +12680,8 @@ dependencies = [ "mono-move-alloc", "mono-move-gas", "move-core-types", + "parking_lot 0.12.5", + "shared-dsa", ] [[package]] @@ -12699,13 +12701,30 @@ dependencies = [ "fxhash", "mono-move-alloc", "mono-move-core", - "mono-move-gas", - "mono-move-runtime", - "mono-move-testsuite", - "move-asm", "move-binary-format", "move-core-types", "parking_lot 0.12.5", +] + +[[package]] +name = "mono-move-orchestrator" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap 4.5.60", + "codespan-reporting", + "datatest-stable", + "legacy-move-compiler", + "mono-move-alloc", + "mono-move-core", + "mono-move-global-context", + "move-asm", + "move-binary-format", + "move-compiler-v2", + "move-core-types", + "move-model", + "move-prover-test-utils", + "shared-dsa", "specializer", ] @@ -12752,6 +12771,7 @@ dependencies = [ "mono-move-core", "mono-move-gas", "mono-move-global-context", + "mono-move-orchestrator", "mono-move-runtime", "move-binary-format", "move-compiler-v2", @@ -18242,23 +18262,14 @@ name = "specializer" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.60", - "codespan-reporting", - "datatest-stable", - "legacy-move-compiler", "mono-move-core", "mono-move-gas", - "move-asm", + "mono-move-global-context", "move-binary-format", "move-bytecode-verifier", - "move-compiler-v2", "move-core-types", - "move-model", - "move-prover-test-utils", - "move-vm-types", "shared-dsa", "smallvec", - "triomphe", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e40c3a5236e..065b14a09e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -211,6 +211,7 @@ members = [ "third_party/move/mono-move/core", "third_party/move/mono-move/gas", "third_party/move/mono-move/global-context", + "third_party/move/mono-move/orchestrator", "third_party/move/mono-move/programs", "third_party/move/mono-move/runtime", "third_party/move/mono-move/specializer", @@ -882,6 +883,7 @@ z3tracer = "0.8.0" # MOVE DEPENDENCIES mono-move-alloc = { path = "third_party/move/mono-move/alloc" } +mono-move-orchestrator = { path = "third_party/move/mono-move/orchestrator" } mono-move-core = { path = "third_party/move/mono-move/core" } mono-move-gas = { path = "third_party/move/mono-move/gas" } mono-move-global-context = { path = "third_party/move/mono-move/global-context" } diff --git a/third_party/move/mono-move/alloc/src/global_arena.rs b/third_party/move/mono-move/alloc/src/global_arena.rs index 8f676d609f7..e7655bcebe2 100644 --- a/third_party/move/mono-move/alloc/src/global_arena.rs +++ b/third_party/move/mono-move/alloc/src/global_arena.rs @@ -25,8 +25,16 @@ impl GlobalArenaPtr { /// Unlike arena-allocated pointers, the result is never invalidated by /// arena reset or arena drop: the static data lives for the entire /// lifetime of a program. - pub fn from_static(data: &'static T) -> Self { - GlobalArenaPtr(NonNull::from(data)) + /// + /// `const fn` so that wrappers (e.g. interned primitive-type constants) + /// can be declared as compile-time constants without a runtime init. + pub const fn from_static(data: &'static T) -> Self { + // SAFETY: A `&'static T` is always non-null and well-aligned, so + // converting it to `NonNull` is sound. + // + // TODO: once the workspace MSRV is bumped to 1.89+, replace this + // with `NonNull::from_ref(data)` and drop the `unsafe` block. + GlobalArenaPtr(unsafe { NonNull::new_unchecked(data as *const T as *mut T) }) } /// Unsafely casts this arena pointer to a reference with the specified diff --git a/third_party/move/mono-move/core/Cargo.toml b/third_party/move/mono-move/core/Cargo.toml index 79e57590dfd..732665be166 100644 --- a/third_party/move/mono-move/core/Cargo.toml +++ b/third_party/move/mono-move/core/Cargo.toml @@ -15,3 +15,5 @@ rust-version = { workspace = true } mono-move-alloc = { workspace = true } mono-move-gas = { workspace = true } move-core-types = { workspace = true } +parking_lot = { workspace = true } +shared-dsa = { workspace = true } diff --git a/third_party/move/mono-move/core/src/executable.rs b/third_party/move/mono-move/core/src/executable.rs index 2938b29d4f5..fc7c49a7244 100644 --- a/third_party/move/mono-move/core/src/executable.rs +++ b/third_party/move/mono-move/core/src/executable.rs @@ -1,8 +1,14 @@ // 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 -use mono_move_alloc::GlobalArenaPtr; +use crate::{ + types::{InternedType, Type}, + Function, +}; +use mono_move_alloc::{ExecutableArena, ExecutableArenaPtr, GlobalArenaPtr}; use move_core_types::account_address::AccountAddress; +use parking_lot::Mutex; +use shared_dsa::UnorderedMap; /// Identifies an executable (module or script) by its address and name. /// - For modules, constructed from module address and name. @@ -36,3 +42,137 @@ impl ExecutableId { self.name } } + +// ================================================================================================ +// Executable and supporting types +// ================================================================================================ + +/// Struct type metadata in an executable. +pub struct StructType { + /// Struct type signature. Invariant: stored type is always + /// [`Type::Struct`]. + ty: InternedType, +} + +impl StructType { + /// Creates a new struct type entry. + pub fn new(ty: InternedType) -> Self { + Self { ty } + } + + /// Returns the underlying type pointer. + pub fn ty(&self) -> InternedType { + self.ty + } +} + +/// Enum type metadata in an executable. +pub struct EnumType { + /// Enum type signature. Invariant: stored type is always + /// [`Type::Enum`]. + ty: InternedType, + /// Per-variant field types, indexed by variant tag. + #[allow(dead_code)] + variants: ExecutableArenaPtr<[VariantFields]>, +} + +impl EnumType { + /// Creates a new enum type entry. + pub fn new(ty: InternedType, variants: ExecutableArenaPtr<[VariantFields]>) -> Self { + Self { ty, variants } + } + + /// Returns the underlying type pointer. + pub fn ty(&self) -> InternedType { + self.ty + } +} + +/// Field types for a single enum variant. +#[derive(Copy, Clone)] +pub struct VariantFields { + #[allow(dead_code)] + fields: ExecutableArenaPtr<[InternedType]>, +} + +impl VariantFields { + /// Creates a new variant fields entry. + pub fn new(fields: ExecutableArenaPtr<[InternedType]>) -> Self { + Self { fields } + } +} + +/// A loaded executable (from module or script). +pub struct Executable { + data: ExecutableData, + + /// Arena where data is allocated for this executable. **Must** be the + /// last field so that it is dropped after any data structure that holds + /// pointers into it. + #[allow(dead_code)] + arena: Mutex, +} + +struct ExecutableData { + /// Executable ID which uniquely identifies this executable. + id: GlobalArenaPtr, + /// Non-generic struct definitions. + structs: UnorderedMap, StructType>, + /// Non-generic enum definitions. + #[allow(dead_code)] + enums: UnorderedMap, EnumType>, + /// Non-generic functions. + functions: UnorderedMap, ExecutableArenaPtr>, +} + +impl Executable { + /// Creates a new executable. + // TODO: the current constructor accepts pre-populated maps whose values + // are pointers into `arena` (and into the global arena), but there is + // nothing at the type level tying each pointer to the arena it came + // from. Consider replacing this with a builder API that owns the + // `ExecutableArena` internally and exposes `add_struct`/`add_enum`/ + // `add_function` entry points, so external callers cannot smuggle in + // pointers backed by a different arena. + pub fn new( + id: GlobalArenaPtr, + structs: UnorderedMap, StructType>, + enums: UnorderedMap, EnumType>, + functions: UnorderedMap, ExecutableArenaPtr>, + arena: ExecutableArena, + ) -> Box { + Box::new(Self { + data: ExecutableData { + id, + structs, + enums, + functions, + }, + arena: Mutex::new(arena), + }) + } + + /// Returns a non-generic function from this executable. Returns [`None`] + /// if such function does not exist. + pub fn get_function(&self, name: GlobalArenaPtr) -> Option<&Function> { + self.data.functions.get(&name).map(|ptr| { + // SAFETY: Because executable is alive, all its allocations are + // still valid. + unsafe { ptr.as_ref_unchecked() } + }) + } + + /// Returns a non-generic struct type from this executable. Returns [`None`] + /// if such struct does not exist. + pub fn get_struct(&self, name: GlobalArenaPtr) -> Option<&Type> { + self.data.structs.get(&name).map(|st| { + // SAFETY: Types must be still valid + unsafe { st.ty.as_ref_unchecked() } + }) + } + + /// Returns the executable ID pointer. + pub fn id(&self) -> GlobalArenaPtr { + self.data.id + } +} diff --git a/third_party/move/mono-move/core/src/lib.rs b/third_party/move/mono-move/core/src/lib.rs index 8efe294437d..597bddb6bd3 100644 --- a/third_party/move/mono-move/core/src/lib.rs +++ b/third_party/move/mono-move/core/src/lib.rs @@ -5,8 +5,9 @@ mod executable; mod function; mod instruction; mod transaction_context; +pub mod types; -pub use executable::ExecutableId; +pub use executable::{EnumType, Executable, ExecutableId, StructType, VariantFields}; pub use function::{FrameLayoutInfo, Function, SafePointEntry, SortedSafePointEntries}; pub use instruction::{ CodeOffset, DescriptorId, FrameOffset, MicroOp, MicroOpGasSchedule, ENUM_DATA_OFFSET, diff --git a/third_party/move/mono-move/core/src/types.rs b/third_party/move/mono-move/core/src/types.rs new file mode 100644 index 00000000000..e8a357c130c --- /dev/null +++ b/third_party/move/mono-move/core/src/types.rs @@ -0,0 +1,399 @@ +// 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 + +//! Runtime type model and layout metadata. +//! +//! A single **type graph** lives in the global arena: a DAG of [`Type`] nodes, +//! deduplicated by interning so that pointer equality implies structural +//! equality. Composite types (vectors, references, etc.) reference their +//! children via [`GlobalArenaPtr`]. +//! +//! ## Primitives +//! +//! Primitives (boolean, integer types, etc.) are pre-allocated as statics. No +//! arena allocation needed. Layout, size and alignment can be deduced from the +//! type. +//! +//! ## Type parameters +//! +//! Type parameters are interned and allocated in arena as [`GlobalArenaPtr`]. +//! During type substitution, pointers are replaced, and the whole type is re- +//! canonicalized. +//! TODO: This is currently not supported. +//! +//! ## Vectors +//! +//! Vectors are arena-allocated composite types with their inner +//! types interned recursively. +//! +//! In flat memory, vectors have 8-byte size and 8-byte alignment. +//! +//! ## References +//! +//! References are arena-allocated composite types with their inner +//! pointee types interned recursively. +//! +//! Size of references is 16 bytes (fat pointers). Alignment is also 16 bytes. +//! +//! ## Fully-instantiated structs +//! +//! Struct types are arena-allocated, and store executable ID, name and type +//! arguments that uniquely identify the type. Additionally, fully-instantiated +//! structs cache their layout information (size, alignment and field offsets). +//! +//! ## Enums +//! +//! Enums are simply pointers to arena-allocated executable IDs, identifiers +//! and type arguments that uniquely identify the type. Enum layouts are not +//! cached in the type graph because enum definitions can change on module +//! upgrade (new variants added). Instead, variant field layouts are stored +//! per-executable and resolved at runtime. +//! +//! ## Generic structs +//! +//! TODO: support substitution + +use crate::ExecutableId; +use mono_move_alloc::GlobalArenaPtr; +use move_core_types::ability::AbilitySet; + +// ================================================================================================ +// Layout types +// ================================================================================================ + +/// Total size of the type in flat memory including padding and any alignment. +pub type Size = u32; + +/// When [`Type`] is stored in flat memory, the start address needs to be +/// this many bytes aligned. +pub type Alignment = u32; + +/// Offset in bytes of struct fields in flat memory. +pub type FieldOffset = u32; + +/// Pointer to an arena-interned [`Type`]. Pointer equality implies structural +/// equality because the global interner deduplicates types. The alias hides +/// the raw `GlobalArenaPtr` form throughout the codebase. +pub type InternedType = GlobalArenaPtr; + +/// Pointer to an arena-interned list of [`InternedType`]s (e.g., function +/// parameter/return types, generic type arguments). The list itself is also +/// interned and deduplicated. +pub type InternedTypeList = GlobalArenaPtr<[InternedType]>; + +// ================================================================================================ +// View helpers for arena-interned pointers +// ================================================================================================ +// +// These free functions wrap the raw `unsafe { ptr.as_ref_unchecked() }` deref +// pattern behind a safe-looking API. +// +// # Safety contract (applies to every `view_*` helper below) +// +// The returned reference aliases arena memory. Callers must ensure the arena +// is alive for as long as the reference is used. In practice this holds +// whenever: +// +// - The caller is reachable only during the execution phase (i.e., some +// `ExecutionGuard` is alive on the call stack). +// - The caller holds a value that transitively stores arena pointers (like +// `ModuleIR` or `FunctionIR`), whose very existence implies the arena is +// live. +// +// The helpers return `&'static` references, which is an intentional lifetime +// widening: the *real* lifetime is "until the next maintenance phase," but +// Rust has no way to spell that. Callers must not store these references +// beyond the scope where the above invariants hold. +// +// TODO (design follow-up): the `&'static` widening makes these references +// effectively raw pointers at the type level — the "arena is alive" proof is +// carried only in docs, not in the types. Consider tying the returned +// reference to a witness value instead: +// +// - Parameterize these helpers by a borrow of an `ExecutionGuard` (or a +// lightweight `&ArenaLive<'a>` token) so the returned reference gets +// lifetime `'a` instead of `'static`. That statically prevents callers +// from stashing the reference across a maintenance phase. +// - Alternatively, make `InternedType` / `GlobalArenaPtr` carry a +// phantom lifetime and remove the free `view_*` functions in favor of +// `InternedType::view(&guard)`-style methods, so the compiler enforces +// that every deref is witnessed by a live guard. + +/// Returns a reference to the arena-interned [`Type`] behind `ptr`. +pub fn view_type(ptr: InternedType) -> &'static Type { + // SAFETY: see module-level contract above. + unsafe { ptr.as_ref_unchecked() } +} + +/// Returns a reference to the arena-interned list of [`InternedType`]s +/// behind `ptr`. +pub fn view_type_list(ptr: InternedTypeList) -> &'static [InternedType] { + // SAFETY: see module-level contract above. + unsafe { ptr.as_ref_unchecked() } +} + +/// Returns a reference to the arena-interned identifier string behind `ptr`. +pub fn view_name(ptr: GlobalArenaPtr) -> &'static str { + // SAFETY: see module-level contract above. + unsafe { ptr.as_ref_unchecked() } +} + +/// Layout for struct fields: +/// - Offset of the field in flat memory representation. +/// - Pointer to the field's type for traversals (e.g., serialization). +#[derive(Copy, Clone)] +pub struct FieldLayout { + pub offset: FieldOffset, + #[allow(dead_code)] + ty: InternedType, +} + +impl FieldLayout { + /// Creates a new field layout entry. + pub fn new(offset: FieldOffset, ty: InternedType) -> Self { + Self { offset, ty } + } +} + +/// Struct layout information: total size, alignment and information about the +/// field layouts. +pub struct StructLayout { + /// Total size of the struct. Includes necessary padding based on the + /// alignment requirements. + pub size: Size, + pub align: Alignment, + fields: GlobalArenaPtr<[FieldLayout]>, +} + +impl StructLayout { + /// Creates a new struct layout entry. + pub fn new(size: Size, align: Alignment, fields: GlobalArenaPtr<[FieldLayout]>) -> Self { + Self { + size, + align, + fields, + } + } + + // TODO: This API is test-only for now, will change, so ignore safety. + pub fn field_layouts(&self) -> &[FieldLayout] { + unsafe { self.fields.as_ref_unchecked() } + } +} + +// ================================================================================================ +// Type enum +// ================================================================================================ + +/// A canonical type node in the arena-allocated canonical type DAG. Each node +/// is unique within the global arena: pointer equality implies structural +/// equality (interning guarantee). +pub enum Type { + Bool, + U8, + U16, + U32, + U64, + U128, + U256, + I8, + I16, + I32, + I64, + I128, + I256, + Address, + Signer, + /// Immutable reference to a type; stores a pointer to canonicalized + /// pointee type. + ImmutRef { + inner: InternedType, + }, + /// Mutable reference to a type; stores a pointer to canonicalized pointee + /// type. + MutRef { + inner: InternedType, + }, + /// Variable-length vector; stores a pointer to canonicalized element type. + Vector { + elem: InternedType, + }, + /// Named struct with its layout. Layout is only set for fully-instantiated + /// structs. + Struct { + // TODO: Make this a pointer to struct type struct which holds these pointers. + executable_id: GlobalArenaPtr, + name: GlobalArenaPtr, + ty_args: InternedTypeList, + layout: Option, + }, + /// Named enum. Does not store any layout information as it may change (new + /// variant can be added during module upgrade). Enum layouts are always + /// resolved through the executable where they are defined. + Enum { + // TODO: Make this a pointer to enum type struct which holds these pointers. + executable_id: GlobalArenaPtr, + name: GlobalArenaPtr, + ty_args: InternedTypeList, + // TODO: Optional layout for enums with fixed size (frozen). + }, + /// Function type with argument types, result types and abilities. + Function { + args: InternedTypeList, + results: InternedTypeList, + abilities: AbilitySet, + }, + /// Unresolved generic type parameter placeholder (index into the enclosing + /// type-argument list). Note that pointer equality of type parameters does + /// not guarantee anything. For example, for + /// ```text + /// struct A { } // T is 0. + /// + /// struct B { + /// x: A, // T1 is 0. + /// y: A, // T2 is 1. + /// } + /// ``` + /// `p: A` and `q: B` satisfy p == q.x, which is meaningless. + TypeParam { + idx: u16, + }, +} + +impl Type { + /// Returns the size and alignment of this type. Returns [`None`] if the + /// size or alignment cannot be computed: + /// - If the type is a generic struct. + /// - If the type is an unresolved type parameter. + /// In both cases, type substitution must run first. + pub fn size_and_align(&self) -> Option<(Size, Alignment)> { + Some(match self { + // Primitives. + Type::Bool | Type::U8 | Type::I8 => (1, 1), + Type::U16 | Type::I16 => (2, 2), + Type::U32 | Type::I32 => (4, 4), + Type::U64 | Type::I64 => (8, 8), + Type::U128 | Type::I128 => (16, 16), + Type::U256 | Type::I256 | Type::Address | Type::Signer => (32, 32), + + // Vectors: pointer to the heap which stores vector metadata such + // as length, capacity. + Type::Vector { .. } => (8, 8), + + // References are 16-byte fat pointers. + Type::ImmutRef { .. } | Type::MutRef { .. } => (16, 16), + + // Enums: always heap pointers because of upgradability. + Type::Enum { .. } => (8, 8), + + // Function values - TODO: for now use heap pointer values. + Type::Function { .. } => (8, 8), + + // Structs: the layout must be pre-computed for all fields inline. + Type::Struct { layout, .. } => { + match layout { + Some(layout) => (layout.size, layout.align), + None => { + // INVARIANT: If layout is unset, this struct contains + // generic type arguments. + return None; + }, + } + }, + + // Need type substitution to calculate the size and alignment. + Type::TypeParam { .. } => { + return None; + }, + }) + } + + /// Returns layout for a struct type, or [`None`] for non-struct types or + /// generic structs without a computed layout. + pub fn struct_layout(&self) -> Option<&StructLayout> { + match self { + Type::Struct { layout, .. } => layout.as_ref(), + Type::Bool + | Type::U8 + | Type::U16 + | Type::U32 + | Type::U64 + | Type::U128 + | Type::U256 + | Type::I8 + | Type::I16 + | Type::I32 + | Type::I64 + | Type::I128 + | Type::I256 + | Type::Address + | Type::Signer + | Type::ImmutRef { .. } + | Type::MutRef { .. } + | Type::Vector { .. } + | Type::Enum { .. } + | Type::Function { .. } + | Type::TypeParam { .. } => None, + } + } +} + +// ================================================================================================ +// Static primitive type instances +// ================================================================================================ + +pub static BOOL: Type = Type::Bool; +pub static U8: Type = Type::U8; +pub static U16: Type = Type::U16; +pub static U32: Type = Type::U32; +pub static U64: Type = Type::U64; +pub static U128: Type = Type::U128; +pub static U256: Type = Type::U256; +pub static I8: Type = Type::I8; +pub static I16: Type = Type::I16; +pub static I32: Type = Type::I32; +pub static I64: Type = Type::I64; +pub static I128: Type = Type::I128; +pub static I256: Type = Type::I256; +pub static ADDRESS: Type = Type::Address; +pub static SIGNER: Type = Type::Signer; + +// TODO: placeholder. +pub static EMPTY_LIST: [InternedType; 0] = []; + +// ================================================================================================ +// Interned-type constants for primitives +// +// These are the preferred way to spell "the interned type for this primitive" +// at call sites. They hide the underlying `GlobalArenaPtr::from_static` call. +// ================================================================================================ + +pub const BOOL_TY: InternedType = GlobalArenaPtr::from_static(&BOOL); +pub const U8_TY: InternedType = GlobalArenaPtr::from_static(&U8); +pub const U16_TY: InternedType = GlobalArenaPtr::from_static(&U16); +pub const U32_TY: InternedType = GlobalArenaPtr::from_static(&U32); +pub const U64_TY: InternedType = GlobalArenaPtr::from_static(&U64); +pub const U128_TY: InternedType = GlobalArenaPtr::from_static(&U128); +pub const U256_TY: InternedType = GlobalArenaPtr::from_static(&U256); +pub const I8_TY: InternedType = GlobalArenaPtr::from_static(&I8); +pub const I16_TY: InternedType = GlobalArenaPtr::from_static(&I16); +pub const I32_TY: InternedType = GlobalArenaPtr::from_static(&I32); +pub const I64_TY: InternedType = GlobalArenaPtr::from_static(&I64); +pub const I128_TY: InternedType = GlobalArenaPtr::from_static(&I128); +pub const I256_TY: InternedType = GlobalArenaPtr::from_static(&I256); +pub const ADDRESS_TY: InternedType = GlobalArenaPtr::from_static(&ADDRESS); +pub const SIGNER_TY: InternedType = GlobalArenaPtr::from_static(&SIGNER); + +pub const EMPTY_TYPE_LIST: InternedTypeList = GlobalArenaPtr::from_static(&EMPTY_LIST); + +// ================================================================================================ +// Alignment utility +// ================================================================================================ + +/// Rounds a byte offset up to the next multiple of `align`. +/// +/// **Pre-condition:** `align` is non-zero and is a power of two. +pub fn align_up(offset: u32, align: u32) -> u32 { + debug_assert!(align > 0 && align.is_power_of_two()); + (offset + align - 1) & !(align - 1) +} diff --git a/third_party/move/mono-move/global-context/Cargo.toml b/third_party/move/mono-move/global-context/Cargo.toml index d20ae6d7a71..e022149d758 100644 --- a/third_party/move/mono-move/global-context/Cargo.toml +++ b/third_party/move/mono-move/global-context/Cargo.toml @@ -21,10 +21,3 @@ mono-move-core = { workspace = true } move-binary-format = { workspace = true } move-core-types = { workspace = true } parking_lot = { workspace = true } -specializer = { workspace = true } - -[dev-dependencies] -mono-move-gas = { workspace = true } -mono-move-runtime = { workspace = true } -mono-move-testsuite = { workspace = true } -move-asm = { workspace = true } diff --git a/third_party/move/mono-move/global-context/src/context.rs b/third_party/move/mono-move/global-context/src/context.rs index 45a42bea640..e5510b4762a 100644 --- a/third_party/move/mono-move/global-context/src/context.rs +++ b/third_party/move/mono-move/global-context/src/context.rs @@ -33,9 +33,11 @@ //! - Trade-off: minor memory waste for lower lock contention. use crate::maintenance_config::MaintenanceConfig; +use anyhow::{bail, Result}; use dashmap::DashMap; use mono_move_alloc::{GlobalArenaPool, GlobalArenaPtr, GlobalArenaShard}; -use mono_move_core::ExecutableId; +use mono_move_core::{types::StructLayout, ExecutableId}; +use move_binary_format::{file_format::SignatureToken, CompiledModule}; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{ hash::{Hash, Hasher}, @@ -47,12 +49,16 @@ mod identifiers; use identifiers::IdentifierInternerKey; mod executable_ids; use executable_ids::ExecutableIdInternerKey; -mod executable; -pub use executable::{Executable, ExecutableBuilder}; +pub use mono_move_core::Executable; mod executable_cache; use executable_cache::ExecutableCache; +mod sig_walk; +pub use sig_walk::{walk_sig_token, StructResolver}; mod types; -pub use types::{FieldLayout, Type}; +pub use types::{ + struct_info_at, try_as_primitive_type, view_name, view_type, view_type_list, FieldLayout, + InternedType, InternedTypeList, Type, +}; use types::{TypeInternerKey, TypeListInternerKey}; /// Global execution context with a two-phase state machine. @@ -89,9 +95,8 @@ struct Context { identifiers: DashMap, ahash::RandomState>, executable_ids: DashMap, ahash::RandomState>, - types: DashMap, ahash::RandomState>, - type_lists: - DashMap]>, ahash::RandomState>, + types: DashMap, + type_lists: DashMap, executable_cache: ExecutableCache, } @@ -318,6 +323,136 @@ impl<'ctx> ExecutionGuard<'ctx> { // the execution phase (guard is alive). Some(unsafe { ptr.as_ref_unchecked() }) } + + // ==================================================================== + // Public type construction helpers + // ==================================================================== + + /// Interns an immutable reference type wrapping `inner`. + pub fn intern_immut_ref(&self, inner: InternedType) -> InternedType { + let ty = self.global_arena.alloc(Type::ImmutRef { inner }); + self.insert_allocated_type_pointer_internal(ty) + } + + /// Interns a mutable reference type wrapping `inner`. + pub fn intern_mut_ref(&self, inner: InternedType) -> InternedType { + let ty = self.global_arena.alloc(Type::MutRef { inner }); + self.insert_allocated_type_pointer_internal(ty) + } + + /// Converts `&mut T` to `&T` by interning the immutable counterpart. + /// Errors if `mut_ref` is not a mutable reference type. + pub fn convert_mut_to_immut_ref(&self, mut_ref: InternedType) -> Result { + let Type::MutRef { inner } = self.type_data(mut_ref) else { + bail!("convert_mut_to_immut_ref: expected MutRef"); + }; + Ok(self.intern_immut_ref(*inner)) + } + + /// Strips the reference from `&T` or `&mut T`, returning `T`. + /// Errors if `ref_ty` is not a reference type. + pub fn strip_ref(&self, ref_ty: InternedType) -> Result { + let (Type::ImmutRef { inner } | Type::MutRef { inner }) = self.type_data(ref_ty) else { + bail!("strip_ref: expected reference type"); + }; + Ok(*inner) + } + + /// Interns a vector type with element type `elem`. + pub fn intern_vector(&self, elem: InternedType) -> InternedType { + let ty = self.global_arena.alloc(Type::Vector { elem }); + self.insert_allocated_type_pointer_internal(ty) + } + + /// Interns a type parameter with the given index. + pub fn intern_type_param(&self, idx: u16) -> InternedType { + let ty = self.global_arena.alloc(Type::TypeParam { idx }); + self.insert_allocated_type_pointer_internal(ty) + } + + /// Interns a function type. + pub fn intern_function_type( + &self, + args: InternedTypeList, + results: InternedTypeList, + abilities: move_core_types::ability::AbilitySet, + ) -> InternedType { + let ty = self.global_arena.alloc(Type::Function { + args, + results, + abilities, + }); + self.insert_allocated_type_pointer_internal(ty) + } + + /// Interns a list of type pointers. Empty lists return a static pointer. + pub fn intern_type_list(&self, types: &[InternedType]) -> InternedTypeList { + if types.is_empty() { + return types::EMPTY_TYPE_LIST; + } + let ptr = self.global_arena.alloc_slice_copy(types); + self.insert_allocated_type_list_internal(ptr) + } + + /// Safely dereferences a type pointer. Returns a reference to the + /// underlying [`Type`] data. The reference is valid for the guard's + /// lifetime. + /// + /// This localizes `unsafe` to this method. + pub fn type_data(&self, ptr: InternedType) -> &Type { + // SAFETY: All type pointers are valid during the execution phase + // because the arena is not reset while any ExecutionGuard is alive. + unsafe { ptr.as_ref_unchecked() } + } + + /// Interns a struct type with its layout. The field-layout slice is + /// allocated in the global arena. + pub fn intern_struct_type( + &self, + executable_id: GlobalArenaPtr, + name: GlobalArenaPtr, + ty_args: InternedTypeList, + size: u32, + align: u32, + fields: &[FieldLayout], + ) -> InternedType { + let fields_ptr = self.global_arena.alloc_slice_copy(fields); + let layout = StructLayout::new(size, align, fields_ptr); + let ty = self.global_arena.alloc(Type::Struct { + executable_id, + name, + ty_args, + layout: Some(layout), + }); + self.insert_allocated_type_pointer_internal(ty) + } + + /// Interns an enum type. Enum size/align is fixed metadata, so no + /// field-layout slice is carried on the type. + pub fn intern_enum_type( + &self, + executable_id: GlobalArenaPtr, + name: GlobalArenaPtr, + ty_args: InternedTypeList, + ) -> InternedType { + let ty = self.global_arena.alloc(Type::Enum { + executable_id, + name, + ty_args, + }); + self.insert_allocated_type_pointer_internal(ty) + } + + /// Looks up a type previously interned from a signature token of `module`. + /// Returns `None` if the token has not yet been interned in this module's + /// context. + pub fn try_intern_for_module( + &self, + token: &SignatureToken, + module: &CompiledModule, + ) -> Option { + self.get_interned_type_pointer_internal(token, module) + } } // @@ -384,7 +519,7 @@ impl<'ctx> ExecutionGuard<'ctx> { impl<'guard, T: ?Sized> ArenaRef<'guard, T> { /// Returns the underlying [`GlobalArenaPtr`] for this arena reference. - pub(crate) fn into_global_arena_ptr(self) -> GlobalArenaPtr { + pub fn into_global_arena_ptr(self) -> GlobalArenaPtr { self.ptr } diff --git a/third_party/move/mono-move/global-context/src/context/executable.rs b/third_party/move/mono-move/global-context/src/context/executable.rs deleted file mode 100644 index f69e071a77d..00000000000 --- a/third_party/move/mono-move/global-context/src/context/executable.rs +++ /dev/null @@ -1,510 +0,0 @@ -// 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 - -//! Types for executables (compiled modules / scripts). - -use crate::{ - context::types::{ - struct_info_at, try_as_primitive_type, Alignment, FieldLayout, FieldOffset, StructLayout, - Type, EMPTY_LIST, - }, - ArenaRef, ExecutionGuard, -}; -use anyhow::{anyhow, bail}; -use fxhash::FxBuildHasher; -use mono_move_alloc::{ExecutableArena, ExecutableArenaPtr, GlobalArenaPtr}; -use mono_move_core::{ExecutableId, FrameLayoutInfo, Function, MicroOp, SortedSafePointEntries}; -use move_binary_format::{ - access::ModuleAccess, - file_format::{ - FunctionHandleIndex, SignatureToken, StructDefinition, StructFieldInformation, - StructHandleIndex, - }, - CompiledModule, -}; -use parking_lot::Mutex; -use std::collections::HashMap; - -pub struct StructType { - /// Struct type signature. Invariant: stored type is always - /// [`Type::Struct`]. - ty: GlobalArenaPtr, -} - -pub struct EnumType { - /// Enum type signature. Invariant: stored type is always - /// [`Type::Enum`]. - ty: GlobalArenaPtr, - /// Per-variant field types, indexed by variant tag. - #[allow(dead_code)] - variants: ExecutableArenaPtr<[VariantFields]>, -} - -/// Field types for a single enum variant. -#[derive(Copy, Clone)] -pub struct VariantFields { - #[allow(dead_code)] - fields: ExecutableArenaPtr<[GlobalArenaPtr]>, -} - -/// A loaded executable (from module or script). -pub struct Executable { - data: ExecutableData, - - /// Arena where data is allocated for this executable. **Must** be the - /// last field so that it is dropped after any data structure that holds - /// pointers into it. - #[allow(dead_code)] - arena: Mutex, -} - -struct ExecutableData { - /// Executable ID which uniquely identifies this executable. - id: GlobalArenaPtr, - /// Non-generic struct definitions. Invariant: stored type is always - /// [`Type::Struct`]. - structs: HashMap, StructType, FxBuildHasher>, - /// Non-generic enum definitions. - enums: HashMap, EnumType, FxBuildHasher>, - /// Non-generic functions. - functions: HashMap, ExecutableArenaPtr, FxBuildHasher>, -} - -impl Executable { - /// Returns a non-generic function from this executable. Returns [`None`] - /// if such function does not exist. - pub fn get_function(&self, name: ArenaRef<'_, str>) -> Option<&Function> { - self.data - .functions - .get(&name.into_global_arena_ptr()) - .map(|ptr| { - // SAFETY: Because executable is alive, all its allocations are - // still valid. - unsafe { ptr.as_ref_unchecked() } - }) - } - - /// Returns a non-generic struct from this executable. Returns [`None`] - /// if such struct does not exist. - pub fn get_struct(&self, name: ArenaRef<'_, str>) -> Option<&Type> { - self.data - .structs - .get(&name.into_global_arena_ptr()) - .map(|ptr| { - // SAFETY: Types must be still valid - unsafe { ptr.ty.as_ref_unchecked() } - }) - } -} - -// TODO: this is likely to change. Placeholder. -// TODO: refactor to own CompiledModule instead of borrowing it (needed for ModuleIR cache). -// Split mutable state into a separate struct to avoid borrow conflicts with self.module. -#[allow(dead_code)] -pub struct ExecutableBuilder<'a, 'guard, 'ctx> { - // TODO: support scripts. - module: &'a CompiledModule, - /// Maps struct handle indices to struct definition indices. - struct_def_idx: HashMap, - - /// Stores data for executable that is being built. - data: ExecutableData, - /// Stores all allocations for this executable. - arena: ExecutableArena, - /// Context for interning. - guard: &'guard ExecutionGuard<'ctx>, -} - -impl<'ctx> ExecutionGuard<'ctx> { - /// Returns a new builder to transform [`CompiledModule`] into an - /// [`Executable`]. - pub fn executable_builder_for_module<'a, 'guard>( - &'guard self, - module: &'a CompiledModule, - ) -> ExecutableBuilder<'a, 'guard, 'ctx> - where - 'ctx: 'guard, - { - let struct_def_idx = module - .struct_defs() - .iter() - .enumerate() - .map(|(i, def)| (def.struct_handle, i)) - .collect::>(); - - let id = self.intern_address_name_internal(*module.self_addr(), module.self_name()); - let data = ExecutableData { - id, - structs: HashMap::with_hasher(FxBuildHasher::default()), - enums: HashMap::with_hasher(FxBuildHasher::default()), - functions: HashMap::with_hasher(FxBuildHasher::default()), - }; - - ExecutableBuilder { - module, - struct_def_idx, - data, - arena: ExecutableArena::new(), - guard: self, - } - } -} - -impl<'a, 'guard, 'ctx> ExecutableBuilder<'a, 'guard, 'ctx> { - /// Builds an executable from the provided compiled module. - pub fn build(mut self) -> anyhow::Result> { - // Process struct definitions first (type layout is needed by lowering - // functions). - for struct_def in &self.module.struct_defs { - self.resolve_struct_def(struct_def)?; - } - - let lowered = specializer::destack_and_lower_module(self.module.clone())?; - - // Handle-indexed function pointer table. Local definitions get - // Some(ptr); cross-module and generic handles remain None. - let mut func_ptrs = vec![None; self.module.function_handles.len()]; - for (def_idx, lowered_fn) in lowered.functions.into_iter().enumerate() { - if let Some(lf) = lowered_fn { - let name = self - .guard - .intern_identifier_internal(self.module.identifier_at(lf.name_idx)); - let code = self.arena.alloc_slice_fill_iter(lf.code); - let func = Function { - name, - code, - args_size: lf.args_size, - args_and_locals_size: lf.args_and_locals_size, - extended_frame_size: lf.extended_frame_size, - // TODO: hardcoded for now. - zero_frame: false, - frame_layout: FrameLayoutInfo::empty(&self.arena), - safe_point_layouts: SortedSafePointEntries::empty(&self.arena), - }; - let ptr = self.arena.alloc(func); - self.data.functions.insert(name, ptr); - let handle_idx = self.module.function_defs[def_idx].function.0 as usize; - func_ptrs[handle_idx] = Some(ptr); - } - } - - // Resolve all CallFunc instructions in a single pass: - // - Local calls (entry in func_ptrs) → CallDirect - // - Cross-module calls (no entry in func_ptrs) → CallIndirect - let self_module_handle_idx = self.module.self_module_handle_idx; - for func_ptr in &func_ptrs { - let Some(mut func_ptr) = *func_ptr else { - continue; - }; - // SAFETY: We have exclusive access — the executable is being built - // and no concurrent readers exist. The arena outlives the executable. - let func = unsafe { func_ptr.as_mut_unchecked() }; - let code = unsafe { func.code.as_mut_unchecked() }; - for op in code.iter_mut() { - if let MicroOp::CallFunc { func_id } = *op { - if let Some(ptr) = func_ptrs[func_id as usize] { - *op = MicroOp::CallDirect { ptr }; - } else { - let function = self - .module - .function_handle_at(FunctionHandleIndex(func_id as u16)); - if function.module == self_module_handle_idx { - bail!("unresolved local function handle {}", func_id); - } - let module = self.module.module_handle_at(function.module); - let executable_id = self.guard.intern_address_name_internal( - *self.module.address_identifier_at(module.address), - self.module.identifier_at(module.name), - ); - let func_name = self - .guard - .intern_identifier_internal(self.module.identifier_at(function.name)); - *op = MicroOp::CallIndirect { - executable_id, - func_name, - }; - } - } - } - } - - Ok(Box::new(Executable { - data: self.data, - arena: Mutex::new(self.arena), - })) - } -} - -// -// Only private APIs below. -// ------------------------ - -impl<'a, 'guard, 'ctx> ExecutableBuilder<'a, 'guard, 'ctx> { - /// Resolves a struct or enum definition. - /// - /// For structs, computes layouts **eagerly** by interning each field type - /// recursively and computing offsets inline. For now, this implements - /// C-style struct layout. For enums, only variant field types are interned - /// (enum type-level size is always fixed). - fn resolve_struct_def( - &mut self, - struct_def: &StructDefinition, - ) -> anyhow::Result> { - let handle = self.module.struct_handle_at(struct_def.struct_handle); - if !handle.type_parameters.is_empty() { - todo!("Generic structs / enums not yet supported"); - } - - let name = self - .guard - .intern_identifier_internal(self.module.identifier_at(handle.name)); - match &struct_def.field_information { - StructFieldInformation::Native => bail!("Native fields are deprecated"), - StructFieldInformation::Declared(field_defs) => { - // Check if already visited. For example, if we have structs: - // - // struct A { x: u64 } - // struct B { x: A } - // - // we do not need to recompute A's type information and can use - // cached data. - if let Some(ptr) = self.data.structs.get(&name) { - return Ok(ptr.ty); - } - - // If not yet processed, it is possible that struct type is - // cached in global arena (because it is not changing under - // upgrades). - let tok = SignatureToken::Struct(struct_def.struct_handle); - if let Some(ptr) = self - .guard - .get_interned_type_pointer_internal(&tok, self.module) - { - self.data.structs.insert(name, StructType { ty: ptr }); - return Ok(ptr); - } - - // Intern each field type and compute layout metadata inline. - let mut fields = Vec::with_capacity(field_defs.len()); - let mut offset = 0; - let mut align = 1; - - for field in field_defs { - let field_ty = self.intern_signature_token(&field.signature.0)?; - - // SAFETY: Type was just interned, pointer is valid (arena - // has not been reset during execution phase). - let field_ty_ref = unsafe { field_ty.as_ref_unchecked() }; - - let (field_size, field_align) = - field_ty_ref.size_and_align().ok_or_else(|| { - anyhow!("Size and alignment is set for non-generic types") - })?; - offset = align_up(offset, field_align); - align = align.max(field_align); - - fields.push(FieldLayout::new(offset, field_ty)); - offset += field_size; - } - - let size = align_up(offset, align); - // Note: fields are not interned, but they are also not used - // for equality or hash - but simply cache layouts in-place. - let fields = self.guard.global_arena.alloc_slice_copy(&fields); - let layout = StructLayout::new(size, align, fields); - - let ty = self.guard.global_arena.alloc(Type::Struct { - executable_id: self.data.id, - name, - ty_args: GlobalArenaPtr::from_static(&EMPTY_LIST), - layout: Some(layout), - }); - let ptr = self.guard.insert_allocated_type_pointer_internal(ty); - - self.data.structs.insert(name, StructType { ty: ptr }); - Ok(ptr) - }, - StructFieldInformation::DeclaredVariants(variant_defs) => { - if let Some(enum_def) = self.data.enums.get(&name) { - return Ok(enum_def.ty); - } - - // If not yet processed, it is possible that enum type is - // cached in global arena/ - let tok = SignatureToken::Struct(struct_def.struct_handle); - let ty = match self - .guard - .get_interned_type_pointer_internal(&tok, self.module) - { - Some(ptr) => ptr, - None => { - let ty = self.guard.global_arena.alloc(Type::Enum { - executable_id: self.data.id, - name, - ty_args: GlobalArenaPtr::from_static(&EMPTY_LIST), - }); - self.guard.insert_allocated_type_pointer_internal(ty) - }, - }; - - let mut variants = Vec::with_capacity(variant_defs.len()); - for variant_def in variant_defs { - let mut fields = Vec::with_capacity(variant_def.fields.len()); - for field in &variant_def.fields { - let field_ty = self.intern_signature_token(&field.signature.0)?; - fields.push(field_ty); - } - let fields = self.arena.alloc_slice_copy(&fields); - variants.push(VariantFields { fields }); - } - let variants = self.arena.alloc_slice_copy(&variants); - self.data.enums.insert(name, EnumType { ty, variants }); - Ok(ty) - }, - } - } - - /// Interns a signature token as a [`Type`]. Primitives return static - /// pointers. Composite types are arena-allocated and deduplicated. Because - /// allocation is outside the lock, it is possible that some allocations - /// will not be used. This is a design choice, as the memory waste is - /// bounded by available concurrency and the number of unique types. - fn intern_signature_token( - &mut self, - token: &SignatureToken, - // TODO: - // In the future, we need to pass type arguments so we can resolve - // field layouts of fully-instantiated generics. - ) -> anyhow::Result> { - // Primitives are static allocations - fast path. - if let Some(ptr) = try_as_primitive_type(token) { - return Ok(ptr); - } - - // Special case for structs / enums because we need to resolve local - // and non-local definitions. - if let Some((idx, _ty_args)) = token.struct_idx_and_ty_args() { - return match self.struct_def_idx.get(idx).copied() { - // TODO: handle type arguments for generic structs! - Some(idx) => self.resolve_struct_def(&self.module.struct_defs[idx]), - None => { - // TODO: - // If this type is a struct or an enum that is non-local, - // assume it must be interned & resolved. In the future, - // this case need to load executable dependency first. - self.guard - .get_interned_type_pointer_internal(token, self.module) - .ok_or_else(|| { - let (address, module_name, struct_name) = struct_info_at(self.module, *idx); - anyhow!( - "Non-local type not yet interned (transitive dependency not loaded): {}::{}::{}", - address, - module_name, - struct_name - ) - }) - }, - }; - } - - // Otherwise, this is a composite type. Check if it has been interned - // before first. - if let Some(ptr) = self - .guard - .get_interned_type_pointer_internal(token, self.module) - { - return Ok(ptr); - } - - // TODO: non-recursive implementation. - - // Cache miss: need to allocate and intern. Allocation is outside the - // lock to avoid contention and may recurse into inner type interning. - let ptr = match token { - SignatureToken::Reference(inner) => { - let inner = self.intern_signature_token(inner.as_ref())?; - self.guard.global_arena.alloc(Type::ImmutRef { inner }) - }, - SignatureToken::MutableReference(inner) => { - let inner = self.intern_signature_token(inner.as_ref())?; - self.guard.global_arena.alloc(Type::MutRef { inner }) - }, - SignatureToken::Vector(elem_token) => { - let elem = self.intern_signature_token(elem_token.as_ref())?; - self.guard.global_arena.alloc(Type::Vector { elem }) - }, - SignatureToken::Function(arg_tokens, result_tokens, abilities) => { - let args = self.intern_signature_token_list(arg_tokens)?; - let results = self.intern_signature_token_list(result_tokens)?; - self.guard.global_arena.alloc(Type::Function { - args, - results, - abilities: *abilities, - }) - }, - SignatureToken::TypeParameter(idx) => { - self.guard.global_arena.alloc(Type::TypeParam { idx: *idx }) - }, - - // Primitives handled above. - SignatureToken::Bool - | SignatureToken::U8 - | SignatureToken::U16 - | SignatureToken::U32 - | SignatureToken::U64 - | SignatureToken::U128 - | SignatureToken::U256 - | SignatureToken::I8 - | SignatureToken::I16 - | SignatureToken::I32 - | SignatureToken::I64 - | SignatureToken::I128 - | SignatureToken::I256 - | SignatureToken::Address - | SignatureToken::Signer - | SignatureToken::Struct(..) - | SignatureToken::StructInstantiation(..) => bail!("Must be already handled"), - }; - - // Insert and deduplicate the pointer, to ensure uniqueness even if - // there are race conditions. - Ok(self.guard.insert_allocated_type_pointer_internal(ptr)) - } - - /// Interns a list of signature tokens as a canonical type list. Empty - /// lists return a static pointer (no allocations). Non-empty lists are - /// arena-allocated and deduplicated. Because allocation is outside the - /// lock, it is possible that some allocations will not be used. This is - /// a design choice - the memory waste is bounded by available concurrency - /// and the number of unique type lists. - fn intern_signature_token_list( - &mut self, - tokens: &[SignatureToken], - ) -> anyhow::Result]>> { - if tokens.is_empty() { - return Ok(GlobalArenaPtr::from_static(&EMPTY_LIST)); - } - - if let Some(ptr) = self - .guard - .get_interned_type_list_internal(tokens, self.module) - { - return Ok(ptr); - } - - let mut types = Vec::with_capacity(tokens.len()); - for token in tokens { - types.push(self.intern_signature_token(token)?); - } - let ptr = self.guard.global_arena.alloc_slice_copy(&types); - Ok(self.guard.insert_allocated_type_list_internal(ptr)) - } -} - -/// Rounds the value up to the next multiple of alignment. -/// -/// **Pre-condition:** Align is non-zero and is a power of two. -fn align_up(offset: FieldOffset, align: Alignment) -> Alignment { - debug_assert!(align > 0 && align.is_power_of_two()); - (offset + align - 1) & !(align - 1) -} diff --git a/third_party/move/mono-move/global-context/src/context/executable_cache.rs b/third_party/move/mono-move/global-context/src/context/executable_cache.rs index cc31a877c72..123c59c5cf1 100644 --- a/third_party/move/mono-move/global-context/src/context/executable_cache.rs +++ b/third_party/move/mono-move/global-context/src/context/executable_cache.rs @@ -8,10 +8,9 @@ //! cached executables are freed before the global arena (which backs the keys) //! is reset. -use crate::context::executable::Executable; use dashmap::DashMap; use mono_move_alloc::{GlobalArenaPtr, LeakedBoxPtr}; -use mono_move_core::ExecutableId; +use mono_move_core::{Executable, ExecutableId}; /// Concurrent long-living executable cache. /// diff --git a/third_party/move/mono-move/global-context/src/context/sig_walk.rs b/third_party/move/mono-move/global-context/src/context/sig_walk.rs new file mode 100644 index 00000000000..2cb481ecf08 --- /dev/null +++ b/third_party/move/mono-move/global-context/src/context/sig_walk.rs @@ -0,0 +1,101 @@ +// 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 + +//! Walker over [`SignatureToken`] that produces interned [`InternedType`]. +//! Struct/enum resolution is pluggable via [`StructResolver`], so different +//! call sites (executable building vs. function body lowering) can supply +//! their own struct-handle lookup while sharing the walk over the rest of +//! the variants. + +use crate::ExecutionGuard; +use mono_move_core::types::{self as ty, InternedType}; +use move_binary_format::file_format::{SignatureToken, StructHandleIndex}; + +/// Resolves a struct handle (with its type arguments) to an interned type +/// pointer. Implementations may pre-build a table (specializer) or resolve +/// on demand (executable builder). +pub trait StructResolver { + fn resolve_struct( + &mut self, + struct_handle: StructHandleIndex, + ty_args: &[SignatureToken], + ) -> anyhow::Result; +} + +/// Recursively interns `token` into the global type arena. Composite leaves +/// go through `guard`'s public intern helpers; struct/enum tokens delegate +/// to `resolver`. +/// +/// TODO: non-recursive implementation. Coordinate with the similar TODO on +/// `TypeInternerKey`'s `Hash` impl in `types.rs`. +/// +/// TODO (perf): probe-before-allocate for composite tokens. +/// +/// Right now, every composite variant (Vector, Reference, MutableReference, +/// Function, and the StructInstantiation path through the resolver) allocates a +/// fresh `Type` node in the arena and then hands it to +/// `insert_allocated_type_pointer_internal`, which discards the new allocation +/// whenever an equivalent entry already exists. For modules with shared +/// signatures (common: many handles reference the same `SignatureIndex`, and +/// `vector` / `&T` appear repeatedly), this means the fast path pays one +/// arena allocation + a dedup probe per occurrence instead of a single probe. +/// +/// The dedup map already supports a cheaper key: `SignatureTokenKey` implements +/// `Equivalent`, so we can look up by `(token, module)` +/// without allocating. To take advantage, thread a `&CompiledModule` through +/// this walker and, for each composite token, call +/// `get_interned_type_pointer_internal` first; only recurse into children and +/// allocate on a miss. +pub fn walk_sig_token( + token: &SignatureToken, + guard: &ExecutionGuard<'_>, + resolver: &mut R, +) -> anyhow::Result { + Ok(match token { + SignatureToken::Bool => ty::BOOL_TY, + SignatureToken::U8 => ty::U8_TY, + SignatureToken::U16 => ty::U16_TY, + SignatureToken::U32 => ty::U32_TY, + SignatureToken::U64 => ty::U64_TY, + SignatureToken::U128 => ty::U128_TY, + SignatureToken::U256 => ty::U256_TY, + SignatureToken::I8 => ty::I8_TY, + SignatureToken::I16 => ty::I16_TY, + SignatureToken::I32 => ty::I32_TY, + SignatureToken::I64 => ty::I64_TY, + SignatureToken::I128 => ty::I128_TY, + SignatureToken::I256 => ty::I256_TY, + SignatureToken::Address => ty::ADDRESS_TY, + SignatureToken::Signer => ty::SIGNER_TY, + SignatureToken::TypeParameter(idx) => guard.intern_type_param(*idx), + SignatureToken::Vector(inner) => { + let elem = walk_sig_token(inner, guard, resolver)?; + guard.intern_vector(elem) + }, + SignatureToken::Reference(inner) => { + let inner = walk_sig_token(inner, guard, resolver)?; + guard.intern_immut_ref(inner) + }, + SignatureToken::MutableReference(inner) => { + let inner = walk_sig_token(inner, guard, resolver)?; + guard.intern_mut_ref(inner) + }, + SignatureToken::Function(args, results, abilities) => { + let arg_ptrs: Vec = args + .iter() + .map(|t| walk_sig_token(t, guard, resolver)) + .collect::>()?; + let result_ptrs: Vec = results + .iter() + .map(|t| walk_sig_token(t, guard, resolver)) + .collect::>()?; + let args = guard.intern_type_list(&arg_ptrs); + let results = guard.intern_type_list(&result_ptrs); + guard.intern_function_type(args, results, *abilities) + }, + SignatureToken::Struct(sh_idx) => resolver.resolve_struct(*sh_idx, &[])?, + SignatureToken::StructInstantiation(sh_idx, tys) => { + resolver.resolve_struct(*sh_idx, tys)? + }, + }) +} diff --git a/third_party/move/mono-move/global-context/src/context/types.rs b/third_party/move/mono-move/global-context/src/context/types.rs index 2c2acf95d53..9601b1e6b02 100644 --- a/third_party/move/mono-move/global-context/src/context/types.rs +++ b/third_party/move/mono-move/global-context/src/context/types.rs @@ -1,290 +1,26 @@ // 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 -//! Type interning and layout metadata. +//! Type interning infrastructure. //! -//! A single **type graph** lives in the global arena: a DAG of [`Type`] nodes, -//! deduplicated by interning so that pointer equality implies structural -//! equality. Composite types (vectors, references, etc.) reference their -//! children via [`GlobalArenaPtr`]. -//! -//! ## Primitives -//! -//! Primitives (boolean, integer types, etc.) are pre-allocated as statics. No -//! arena allocation needed. Layout, size and alignment can be deduced from the -//! type. -//! -//! ## Type parameters -//! -//! Type parameters are interned and allocated in arena as [`GlobalArenaPtr`]. -//! During type substitution, pointers are replaced, and the whole type is re- -//! canonicalized. -//! TODO: This is currently not supported. -//! -//! ## Vectors -//! -//! Vectors are arena-allocated composite types with their inner -//! types interned recursively. -//! -//! In flat memory, vectors have 8-byte size and 8-byte alignment. -//! -//! ## References -//! -//! References are arena-allocated composite types with their inner -//! pointee types interned recursively. -//! -//! Size of references is 16 bytes (fat pointers). Alignment is also 16 bytes. -//! -//! ## Fully-instantiated structs -//! -//! Struct types are arena-allocated, and store executable ID, name and type -//! arguments that uniquely identify the type. Additionally, fully-instantiated -//! structs cache their layout information (size, alignment and field offsets). -//! -//! ## Enums -//! -//! Enums are simply pointers to arena-allocated executable IDs, identifiers -//! and type arguments that uniquely identify the type. Enum layouts are not -//! cached in the type graph because enum definitions can change on module -//! upgrade (new variants added). Instead, variant field layouts are stored -//! per-executable and resolved at runtime. -//! -//! ## Generic structs -//! -//! TODO: support substitution +//! The pure type model ([`Type`], [`FieldLayout`], [`StructLayout`], etc.) +//! lives in `mono_move_core::types`. This module provides the interning +//! machinery that deduplicates types in the global arena, plus cross-format +//! hashing/equality between [`SignatureToken`]s and interned [`Type`]s. +// Re-export the pure type model from mono-move-core so existing consumers +// within this crate can continue to use `crate::context::types::Type` etc. use crate::ExecutionGuard; use dashmap::Equivalent; -use mono_move_alloc::GlobalArenaPtr; -use mono_move_core::ExecutableId; +pub use mono_move_core::types::*; use move_binary_format::{ access::ModuleAccess, file_format::{SignatureToken, StructHandleIndex}, CompiledModule, }; -use move_core_types::{ability::AbilitySet, account_address::AccountAddress, identifier::IdentStr}; +use move_core_types::{account_address::AccountAddress, identifier::IdentStr}; use std::hash::{Hash, Hasher}; -/// Total size of the type in flat memory including padding and any alignment. -pub type Size = u32; - -/// When [`Type`] is stored in flat memory, the start address needs to be -/// this many bytes aligned. -pub type Alignment = u32; - -/// Offset in bytes of struct fields in flat memory. -pub type FieldOffset = u32; - -/// Layout for struct fields: -/// - Offset of the field in flat memory representation. -/// - Pointer to the field's type for traversals (e.g., serialization). -#[derive(Copy, Clone)] -pub struct FieldLayout { - pub offset: FieldOffset, - #[allow(dead_code)] - ty: GlobalArenaPtr, -} - -/// Struct layout information: total size, alignment and information about the -/// field layouts. -pub struct StructLayout { - /// Total size of the struct. Includes necessary padding based on the - /// alignment requirements. - pub size: Size, - pub align: Alignment, - fields: GlobalArenaPtr<[FieldLayout]>, -} - -/// A canonical type node in the arena-allocated canonical type DAG. Each node -/// is unique within the global arena: pointer equality implies structural -/// equality (interning guarantee). -pub enum Type { - Bool, - U8, - U16, - U32, - U64, - U128, - U256, - I8, - I16, - I32, - I64, - I128, - I256, - Address, - Signer, - /// Immutable reference to a type; stores a pointer to canonicalized - /// pointee type. - ImmutRef { - inner: GlobalArenaPtr, - }, - /// Mutable reference to a type; stores a pointer to canonicalized pointee - /// type. - MutRef { - inner: GlobalArenaPtr, - }, - /// Variable-length vector; stores a pointer to canonicalized element type. - Vector { - elem: GlobalArenaPtr, - }, - /// Named struct with its layout. Layout is only set for fully-instantiated - /// structs. - Struct { - // TODO: Make this a pointer to struct type struct which holds these pointers. - executable_id: GlobalArenaPtr, - name: GlobalArenaPtr, - ty_args: GlobalArenaPtr<[GlobalArenaPtr]>, - layout: Option, - }, - /// Named enum. Does not store any layout information as it may change (new - /// variant can be added during module upgrade). Enum layouts are always - /// resolved through the executable where they are defined. - Enum { - // TODO: Make this a pointer to enum type struct which holds these pointers. - executable_id: GlobalArenaPtr, - name: GlobalArenaPtr, - ty_args: GlobalArenaPtr<[GlobalArenaPtr]>, - // TODO: Optional layout for enums with fixed size (frozen). - }, - /// Function type with argument types, result types and abilities. - Function { - args: GlobalArenaPtr<[GlobalArenaPtr]>, - results: GlobalArenaPtr<[GlobalArenaPtr]>, - abilities: AbilitySet, - }, - /// Unresolved generic type parameter placeholder (index into the enclosing - /// type-argument list). Note that pointer equality of type parameters does - /// not guarantee anything. For example, for - /// ```text - /// struct A { } // T is 0. - /// - /// struct B { - /// x: A, // T1 is 0. - /// y: A, // T2 is 1. - /// } - /// ``` - /// `p: A` and `q: B` satisfy p == q.x, which is meaningless. - TypeParam { - idx: u16, - }, -} - -impl Type { - /// Returns the size and alignment of this type. Returns [`None`] if the - /// size or alignment cannot be computed: - /// - If the type is a generic struct. - /// - If the type is an unresolved type parameter. - /// In both cases, type substitution must run first. - pub fn size_and_align(&self) -> Option<(Size, Alignment)> { - Some(match self { - // Primitives. - Type::Bool | Type::U8 | Type::I8 => (1, 1), - Type::U16 | Type::I16 => (2, 2), - Type::U32 | Type::I32 => (4, 4), - Type::U64 | Type::I64 => (8, 8), - Type::U128 | Type::I128 => (16, 16), - Type::U256 | Type::I256 | Type::Address | Type::Signer => (32, 32), - - // Vectors: pointer to the heap which stores vector metadata such - // as length, capacity. - Type::Vector { .. } => (8, 8), - - // References are 16-byte fat pointers. - Type::ImmutRef { .. } | Type::MutRef { .. } => (16, 16), - - // Enums: always heap pointers because of upgradability. - Type::Enum { .. } => (8, 8), - - // Function values - TODO: for now use heap pointer values. - Type::Function { .. } => (8, 8), - - // Structs: the layout must be pre-computed for all fields inline. - Type::Struct { layout, .. } => { - match layout { - Some(layout) => (layout.size, layout.align), - None => { - // INVARIANT: If layout is unset, this struct contains - // generic type arguments. - return None; - }, - } - }, - - // Need type substitution to calculate the size and alignment. - Type::TypeParam { .. } => { - return None; - }, - }) - } - - /// Returns layout for a struct type, or [`None`] for non-struct types or - /// generic structs without a computed layout. - pub fn struct_layout(&self) -> Option<&StructLayout> { - match self { - Type::Struct { layout, .. } => layout.as_ref(), - Type::Bool - | Type::U8 - | Type::U16 - | Type::U32 - | Type::U64 - | Type::U128 - | Type::U256 - | Type::I8 - | Type::I16 - | Type::I32 - | Type::I64 - | Type::I128 - | Type::I256 - | Type::Address - | Type::Signer - | Type::ImmutRef { .. } - | Type::MutRef { .. } - | Type::Vector { .. } - | Type::Enum { .. } - | Type::Function { .. } - | Type::TypeParam { .. } => None, - } - } -} - -impl StructLayout { - // TODO: This API is test-only for now, will change, so ignore safety. - pub fn field_layouts(&self) -> &[FieldLayout] { - unsafe { self.fields.as_ref_unchecked() } - } -} - -// -// Only private APIs below. -// ------------------------ - -impl FieldLayout { - /// Returns field layout information. - /// - /// # Safety - /// - /// The caller must ensure that the type pointer is valid. - pub(super) fn new(offset: FieldOffset, ty: GlobalArenaPtr) -> Self { - Self { offset, ty } - } -} - -impl StructLayout { - /// Returns struct layout information. - /// - /// # Safety - /// - /// The caller must ensure that the field layouts pointer is valid. - pub(super) fn new(size: Size, align: Alignment, fields: GlobalArenaPtr<[FieldLayout]>) -> Self { - Self { - size, - align, - fields, - } - } -} - impl<'ctx> ExecutionGuard<'ctx> { /// Returns the type pointer corresponding to the token if it has been /// interned before, or [`None`] otherwise. @@ -296,7 +32,7 @@ impl<'ctx> ExecutionGuard<'ctx> { &self, token: &SignatureToken, module: &CompiledModule, - ) -> Option> { + ) -> Option { // SAFETY: All existing keys/values are valid pointers because the map // is guaranteed to be cleared on arena's reset. self.ctx @@ -314,10 +50,7 @@ impl<'ctx> ExecutionGuard<'ctx> { /// /// 1. The caller must ensure that the inserted pointer is alive. /// 2. For any pointer that exists in the map, it must be still alive. - pub(super) fn insert_allocated_type_pointer_internal( - &self, - ptr: GlobalArenaPtr, - ) -> GlobalArenaPtr { + pub(super) fn insert_allocated_type_pointer_internal(&self, ptr: InternedType) -> InternedType { // SAFETY: We have just allocated the pointer, hence it is safe to wrap // it as a key and compute hash / equality. All existing keys are also // valid pointers because the map is cleared on arena's reset. @@ -336,8 +69,8 @@ impl<'ctx> ExecutionGuard<'ctx> { /// 3. For any pointer that exists in the map, it must be still alive. pub(super) fn insert_allocated_type_list_internal( &self, - ptr: GlobalArenaPtr<[GlobalArenaPtr]>, - ) -> GlobalArenaPtr<[GlobalArenaPtr]> { + ptr: InternedTypeList, + ) -> InternedTypeList { // SAFETY: We have just allocated the pointer, hence it is safe to wrap // it as a key and compute hash / equality. All existing keys are also // valid pointers because the map is cleared on arena's reset. @@ -347,68 +80,29 @@ impl<'ctx> ExecutionGuard<'ctx> { .entry(TypeListInternerKey(ptr)) .or_insert(ptr) } - - /// Returns the interned type list pointer if it has been interned before, - /// or [`None`] otherwise. Looks up directly from signature tokens without - /// interning each element first. - /// - /// # Safety - /// - /// For any pointer that exists in the map, it must be still alive. - pub(super) fn get_interned_type_list_internal( - &self, - tokens: &[SignatureToken], - module: &CompiledModule, - ) -> Option]>> { - // SAFETY: All existing keys/values are valid pointers because the map - // is guaranteed to be cleared on arena's reset. - self.ctx - .type_lists - .get(&SignatureTokenListKey(tokens, module)) - .map(|entry| *entry.value()) - } } -static BOOL: Type = Type::Bool; -static U8: Type = Type::U8; -static U16: Type = Type::U16; -static U32: Type = Type::U32; -static U64: Type = Type::U64; -static U128: Type = Type::U128; -static U256: Type = Type::U256; -static I8: Type = Type::I8; -static I16: Type = Type::I16; -static I32: Type = Type::I32; -static I64: Type = Type::I64; -static I128: Type = Type::I128; -static I256: Type = Type::I256; -static ADDRESS: Type = Type::Address; -static SIGNER: Type = Type::Signer; - -// TODO: placeholder. -pub(super) static EMPTY_LIST: [GlobalArenaPtr; 0] = []; - /// Returns a static primitive type pointer for primitive signature tokens, /// or [`None`] for composite types that require arena allocation. -pub(super) fn try_as_primitive_type(token: &SignatureToken) -> Option> { +pub fn try_as_primitive_type(token: &SignatureToken) -> Option { use SignatureToken as S; match token { - S::Bool => Some(GlobalArenaPtr::from_static(&BOOL)), - S::U8 => Some(GlobalArenaPtr::from_static(&U8)), - S::U16 => Some(GlobalArenaPtr::from_static(&U16)), - S::U32 => Some(GlobalArenaPtr::from_static(&U32)), - S::U64 => Some(GlobalArenaPtr::from_static(&U64)), - S::U128 => Some(GlobalArenaPtr::from_static(&U128)), - S::U256 => Some(GlobalArenaPtr::from_static(&U256)), - S::I8 => Some(GlobalArenaPtr::from_static(&I8)), - S::I16 => Some(GlobalArenaPtr::from_static(&I16)), - S::I32 => Some(GlobalArenaPtr::from_static(&I32)), - S::I64 => Some(GlobalArenaPtr::from_static(&I64)), - S::I128 => Some(GlobalArenaPtr::from_static(&I128)), - S::I256 => Some(GlobalArenaPtr::from_static(&I256)), - S::Address => Some(GlobalArenaPtr::from_static(&ADDRESS)), - S::Signer => Some(GlobalArenaPtr::from_static(&SIGNER)), + S::Bool => Some(BOOL_TY), + S::U8 => Some(U8_TY), + S::U16 => Some(U16_TY), + S::U32 => Some(U32_TY), + S::U64 => Some(U64_TY), + S::U128 => Some(U128_TY), + S::U256 => Some(U256_TY), + S::I8 => Some(I8_TY), + S::I16 => Some(I16_TY), + S::I32 => Some(I32_TY), + S::I64 => Some(I64_TY), + S::I128 => Some(I128_TY), + S::I256 => Some(I256_TY), + S::Address => Some(ADDRESS_TY), + S::Signer => Some(SIGNER_TY), S::Vector(_) | S::Function(_, _, _) | S::Struct(_) @@ -451,7 +145,7 @@ mod type_discriminant { /// /// Constructor must enforce the pointer points to the valid data and can be /// safely dereferenced. -pub(super) struct TypeInternerKey(GlobalArenaPtr); +pub(super) struct TypeInternerKey(InternedType); impl Hash for TypeInternerKey { fn hash(&self, state: &mut H) { @@ -582,6 +276,11 @@ impl Hash for TypeInternerKey { } } +// Inner `InternedType` fields are compared via `==`, which dispatches to +// `GlobalArenaPtr::PartialEq` (pointer equality). This is sound because the +// interner only stores canonical pointers — structurally equal inner types are +// deduplicated at insertion, so pointer equality coincides with structural +// equality. impl PartialEq for TypeInternerKey { fn eq(&self, other: &Self) -> bool { use Type::*; @@ -611,8 +310,6 @@ impl PartialEq for TypeInternerKey { Signer => matches!(other, Signer), ImmutRef { inner } => { if let ImmutRef { inner: other_inner } = other { - // SAFETY: Inner pointers are already canonical pointers, - // so it is safe to compare by pointer equality. *inner == *other_inner } else { false @@ -620,8 +317,6 @@ impl PartialEq for TypeInternerKey { }, MutRef { inner } => { if let MutRef { inner: other_inner } = other { - // SAFETY: Inner pointers are already canonical pointers, - // so it is safe to compare by pointer equality. *inner == *other_inner } else { false @@ -629,8 +324,6 @@ impl PartialEq for TypeInternerKey { }, Vector { elem } => { if let Vector { elem: other_elem } = other { - // SAFETY: Inner pointers are already canonical pointers, - // so it is safe to compare by pointer equality. *elem == *other_elem } else { false @@ -649,8 +342,6 @@ impl PartialEq for TypeInternerKey { .. } = other { - // SAFETY: Inner pointers are already canonical pointers, - // so it is safe to compare by pointer equality. executable_id == other_executable_id && name == other_name && ty_args == other_ty_args @@ -669,8 +360,6 @@ impl PartialEq for TypeInternerKey { ty_args: other_ty_args, } = other { - // SAFETY: Inner pointers are already canonical pointers, - // so it is safe to compare by pointer equality. executable_id == other_executable_id && name == other_name && ty_args == other_ty_args @@ -689,9 +378,6 @@ impl PartialEq for TypeInternerKey { abilities: other_abilities, } = other { - // SAFETY: Argument and return pointers are already - // canonical pointers, so it is safe to compare by pointer - // equality. args == other_args && results == other_results && abilities == other_abilities } else { false @@ -982,7 +668,7 @@ impl Equivalent for SignatureTokenKey<'_> { /// Returns struct information (module address, name and struct name) per given /// index. The index must come from the given compiled module. -pub(super) fn struct_info_at( +pub fn struct_info_at( module: &CompiledModule, idx: StructHandleIndex, ) -> (&AccountAddress, &IdentStr, &IdentStr) { @@ -1001,7 +687,7 @@ pub(super) fn struct_info_at( /// /// Constructor must enforce the pointer points to the valid data and can be /// safely dereferenced. -pub(super) struct TypeListInternerKey(GlobalArenaPtr<[GlobalArenaPtr]>); +pub(super) struct TypeListInternerKey(InternedTypeList); impl Hash for TypeListInternerKey { fn hash(&self, state: &mut H) { @@ -1035,32 +721,3 @@ impl PartialEq for TypeListInternerKey { // PartialEq implementation above is a full equivalence relation. impl Eq for TypeListInternerKey {} - -/// Wrapper around a slice of [`SignatureToken`]s and the owning -/// [`CompiledModule`] that is equivalent to [`TypeListInternerKey`] and -/// implements the same structural hashing. -struct SignatureTokenListKey<'a>(&'a [SignatureToken], &'a CompiledModule); - -impl Hash for SignatureTokenListKey<'_> { - fn hash(&self, state: &mut H) { - self.0.len().hash(state); - for token in self.0 { - SignatureTokenKey(token, self.1).hash(state); - } - } -} - -impl Equivalent for SignatureTokenListKey<'_> { - fn equivalent(&self, key: &TypeListInternerKey) -> bool { - // SAFETY: It is safe to dereference the pointer because the caller - // ensures it remains valid during the lifetime of the key. - let key = unsafe { key.0.as_ref_unchecked() }; - if self.0.len() != key.len() { - return false; - } - self.0 - .iter() - .zip(key.iter()) - .all(|(tok, key)| SignatureTokenKey(tok, self.1).equivalent(&TypeInternerKey(*key))) - } -} diff --git a/third_party/move/mono-move/global-context/src/lib.rs b/third_party/move/mono-move/global-context/src/lib.rs index 58e531a2b21..52506e62dfb 100644 --- a/third_party/move/mono-move/global-context/src/lib.rs +++ b/third_party/move/mono-move/global-context/src/lib.rs @@ -5,8 +5,9 @@ mod context; mod transaction_context; pub use context::{ - ArenaRef, Executable, ExecutableBuilder, ExecutionGuard, FieldLayout, GlobalContext, - MaintenanceGuard, Type, + struct_info_at, try_as_primitive_type, view_name, view_type, view_type_list, walk_sig_token, + ArenaRef, Executable, ExecutionGuard, FieldLayout, GlobalContext, InternedType, + InternedTypeList, MaintenanceGuard, StructResolver, Type, }; pub mod maintenance_config; pub use transaction_context::PlaceholderContext; diff --git a/third_party/move/mono-move/orchestrator/Cargo.toml b/third_party/move/mono-move/orchestrator/Cargo.toml new file mode 100644 index 00000000000..4010b4dc7e3 --- /dev/null +++ b/third_party/move/mono-move/orchestrator/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "mono-move-orchestrator" +version = "0.1.0" + +# Workspace inherited keys +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +publish = { workspace = true } +repository = { workspace = true } +rust-version = { workspace = true } + +[dependencies] +anyhow = { workspace = true } +clap = { workspace = true } +mono-move-alloc = { workspace = true } +mono-move-core = { workspace = true } +mono-move-global-context = { workspace = true } +move-binary-format = { workspace = true } +shared-dsa = { workspace = true } +specializer = { workspace = true } + +[[bin]] +name = "mseir-compiler" +path = "src/bin/mseir-compiler.rs" + +[dev-dependencies] +codespan-reporting = { workspace = true } +datatest-stable = { workspace = true } +legacy-move-compiler = { workspace = true } +move-asm = { workspace = true } +move-compiler-v2 = { workspace = true } +move-core-types = { workspace = true } +move-model = { workspace = true } +move-prover-test-utils = { workspace = true } + +[[test]] +name = "testsuite" +harness = false + +[lib] +doctest = false diff --git a/third_party/move/mono-move/specializer/src/bin/mseir-compiler.rs b/third_party/move/mono-move/orchestrator/src/bin/mseir-compiler.rs similarity index 87% rename from third_party/move/mono-move/specializer/src/bin/mseir-compiler.rs rename to third_party/move/mono-move/orchestrator/src/bin/mseir-compiler.rs index 408df155c9d..c031966f41a 100644 --- a/third_party/move/mono-move/specializer/src/bin/mseir-compiler.rs +++ b/third_party/move/mono-move/orchestrator/src/bin/mseir-compiler.rs @@ -6,8 +6,9 @@ use anyhow::{Context, Result}; use clap::Parser; +use mono_move_global_context::GlobalContext; +use mono_move_orchestrator::ExecutableBuilder; use move_binary_format::{access::ModuleAccess, file_format::CompiledModule}; -use move_vm_types::loaded_data::struct_name_indexing::StructNameIndex; use specializer::destack; use std::path::PathBuf; @@ -35,11 +36,16 @@ fn main() -> Result<()> { let module = CompiledModule::deserialize(&bytes) .map_err(|e| anyhow::anyhow!("failed to deserialize module: {:?}", e))?; - let struct_name_table: Vec = (0..module.struct_handles.len()) - .map(|i| StructNameIndex::new(i as u32)) - .collect(); + let ctx = GlobalContext::with_num_execution_workers(1); + let guard = ctx.try_execution_context(0).unwrap(); - let module_ir = destack(module, &struct_name_table)?; + let struct_types = { + let mut builder = ExecutableBuilder::new(&guard, &module); + builder.resolve_types()?; + builder.struct_type_table() + }; + + let module_ir = destack(module, &guard, &struct_types)?; if args.verbose { print_stats(&module_ir); diff --git a/third_party/move/mono-move/orchestrator/src/builder.rs b/third_party/move/mono-move/orchestrator/src/builder.rs new file mode 100644 index 00000000000..0ad16464413 --- /dev/null +++ b/third_party/move/mono-move/orchestrator/src/builder.rs @@ -0,0 +1,387 @@ +// 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 + +//! Builds an [`Executable`] from a [`CompiledModule`] by resolving its +//! struct/enum types and accumulating lowered functions. +//! +//! Type interning primitives live in `mono-move-global-context`; this module +//! orchestrates the module-level walk (struct defs, enum defs, layout +//! computation) and the final assembly into an `Executable`. + +use anyhow::{anyhow, bail}; +use mono_move_alloc::{ExecutableArena, ExecutableArenaPtr, GlobalArenaPtr}; +use mono_move_core::{ + types::{align_up, EMPTY_TYPE_LIST}, + EnumType, Executable, ExecutableId, FrameLayoutInfo, Function, MicroOp, SortedSafePointEntries, + StructType, VariantFields, +}; +use mono_move_global_context::{ + struct_info_at, walk_sig_token, ExecutionGuard, FieldLayout, InternedType, StructResolver, +}; +use move_binary_format::{ + access::ModuleAccess, + file_format::{ + FunctionHandleIndex, SignatureToken, StructDefinition, StructFieldInformation, + StructHandleIndex, + }, + CompiledModule, +}; +use shared_dsa::UnorderedMap; +use specializer::LoweredFunction; + +// TODO: this is likely to change. Placeholder. +// TODO: refactor to own CompiledModule instead of borrowing it (needed for ModuleIR cache). +// Split mutable state into a separate struct to avoid borrow conflicts with self.module. +#[allow(dead_code)] +pub struct ExecutableBuilder<'a, 'guard, 'ctx> { + // TODO: support scripts. + module: &'a CompiledModule, + /// Maps struct handle indices to struct definition indices. + struct_def_idx: UnorderedMap, + + /// Executable ID. + id: GlobalArenaPtr, + /// Non-generic struct definitions being built. + structs: UnorderedMap, StructType>, + /// Non-generic enum definitions being built. + enums: UnorderedMap, EnumType>, + /// Non-generic functions being built. + functions: UnorderedMap, ExecutableArenaPtr>, + /// Function pointers indexed by `FunctionHandleIndex`, for call + /// patching. Local definitions fill entries; cross-module handles stay + /// `None`. + func_ptrs: Vec>>, + /// Stores all allocations for this executable. + arena: ExecutableArena, + /// Context for interning. + guard: &'guard ExecutionGuard<'ctx>, +} + +impl<'a, 'guard, 'ctx> ExecutableBuilder<'a, 'guard, 'ctx> { + /// Creates a new builder for transforming `module` into an [`Executable`]. + pub fn new(guard: &'guard ExecutionGuard<'ctx>, module: &'a CompiledModule) -> Self + where + 'ctx: 'guard, + { + let struct_def_idx = module + .struct_defs() + .iter() + .enumerate() + .map(|(i, def)| (def.struct_handle, i)) + .collect::>(); + + let id = guard + .intern_address_name(module.self_addr(), module.self_name()) + .into_global_arena_ptr(); + + ExecutableBuilder { + module, + struct_def_idx, + id, + structs: UnorderedMap::new(), + enums: UnorderedMap::new(), + functions: UnorderedMap::new(), + func_ptrs: vec![None; module.function_handles.len()], + arena: ExecutableArena::new(), + guard, + } + } + + /// Resolve all struct and enum type definitions in the module. + /// Must be called before `finish()`. + pub fn resolve_types(&mut self) -> anyhow::Result<()> { + for struct_def in &self.module.struct_defs { + self.resolve_struct_def(struct_def)?; + } + Ok(()) + } + + /// Returns the module being built. + pub fn module(&self) -> &CompiledModule { + self.module + } + + /// Returns a struct type table mapping `StructHandleIndex` ordinals to + /// interned type pointers. Call after `resolve_types()`. + /// + /// Locally-defined, non-generic struct/enum handles resolve to `Some(ty)`. + /// Every other handle — imported from another module, or generic — + /// remains `None`. Downstream consumers must treat `None` as an + /// unresolved reference rather than a usable type; any attempt to lower + /// code that touches such a handle should fail loudly. See the TODO in + /// `specializer::destack::type_conversion::TableResolver`. + pub fn struct_type_table(&self) -> Vec> { + let num_handles = self.module.struct_handles.len(); + let mut table = vec![None; num_handles]; + for struct_def in &self.module.struct_defs { + let handle = self.module.struct_handle_at(struct_def.struct_handle); + let name = self + .guard + .intern_identifier(self.module.identifier_at(handle.name)) + .into_global_arena_ptr(); + if let Some(st) = self.structs.get(&name) { + table[struct_def.struct_handle.0 as usize] = Some(st.ty()); + } else if let Some(et) = self.enums.get(&name) { + table[struct_def.struct_handle.0 as usize] = Some(et.ty()); + } + } + table + } + + /// Records a lowered function. Functions can be added in any order; the + /// handle index carried by `lowered` determines where it lands in the + /// call-patching table. + pub fn add_function(&mut self, lowered: LoweredFunction) { + let name = self + .guard + .intern_identifier(self.module.identifier_at(lowered.name_idx)) + .into_global_arena_ptr(); + let code = self.arena.alloc_slice_fill_iter(lowered.code); + let func = Function { + name, + code, + args_size: lowered.args_size, + args_and_locals_size: lowered.args_and_locals_size, + extended_frame_size: lowered.extended_frame_size, + // TODO: hardcoded for now. + zero_frame: false, + frame_layout: FrameLayoutInfo::empty(&self.arena), + safe_point_layouts: SortedSafePointEntries::empty(&self.arena), + }; + let ptr = self.arena.alloc(func); + self.functions.insert(name, ptr); + self.func_ptrs[lowered.handle_idx.0 as usize] = Some(ptr); + } + + /// Finishes building the executable. Call after every function definition + /// has been recorded via `add_function`. + pub fn finish(self) -> anyhow::Result> { + self.resolve_all_calls()?; + + Ok(Executable::new( + self.id, + self.structs, + self.enums, + self.functions, + self.arena, + )) + } + + /// Rewrites every `CallFunc` in every local function's code: + /// - Handles that resolve to a local `Function` pointer become + /// `CallDirect`. + /// - Cross-module handles become `CallIndirect` keyed by the callee's + /// interned `(executable_id, func_name)` pair, to be dispatched at + /// runtime. + fn resolve_all_calls(&self) -> anyhow::Result<()> { + let self_module_handle_idx = self.module.self_module_handle_idx; + for func_ptr in &self.func_ptrs { + let Some(mut func_ptr) = *func_ptr else { + continue; + }; + // SAFETY: We have exclusive access during build — no concurrent + // readers exist yet. The arena is alive because `self` still owns + // it until `Executable::new` is called below. + let func = unsafe { func_ptr.as_mut_unchecked() }; + let code = unsafe { func.code.as_mut_unchecked() }; + for op in code.iter_mut() { + let MicroOp::CallFunc { func_id } = *op else { + continue; + }; + if let Some(ptr) = self.func_ptrs[func_id as usize] { + *op = MicroOp::CallDirect { ptr }; + continue; + } + let callee_handle = self + .module + .function_handle_at(FunctionHandleIndex(func_id as u16)); + if callee_handle.module == self_module_handle_idx { + bail!("unresolved local function handle {}", func_id); + } + let callee_module = self.module.module_handle_at(callee_handle.module); + let executable_id = self + .guard + .intern_address_name( + self.module.address_identifier_at(callee_module.address), + self.module.identifier_at(callee_module.name), + ) + .into_global_arena_ptr(); + let func_name = self + .guard + .intern_identifier(self.module.identifier_at(callee_handle.name)) + .into_global_arena_ptr(); + *op = MicroOp::CallIndirect { + executable_id, + func_name, + }; + } + } + Ok(()) + } +} + +// +// Only private APIs below. +// ------------------------ + +impl<'a, 'guard, 'ctx> ExecutableBuilder<'a, 'guard, 'ctx> { + /// Resolves a struct or enum definition. + /// + /// For structs, computes layouts **eagerly** by interning each field type + /// and computing offsets inline. For now, this implements C-style struct + /// layout. For enums, only variant field types are interned (enum + /// type-level size is always fixed). + fn resolve_struct_def( + &mut self, + struct_def: &StructDefinition, + ) -> anyhow::Result { + let handle = self.module.struct_handle_at(struct_def.struct_handle); + if !handle.type_parameters.is_empty() { + bail!("Generic structs / enums not yet supported"); + } + + let name = self + .guard + .intern_identifier(self.module.identifier_at(handle.name)) + .into_global_arena_ptr(); + match &struct_def.field_information { + StructFieldInformation::Native => bail!("Native fields are deprecated"), + StructFieldInformation::Declared(field_defs) => { + // Check if already visited. For example, if we have structs: + // + // struct A { x: u64 } + // struct B { x: A } + // + // we do not need to recompute A's type information and can use + // cached data. + if let Some(st) = self.structs.get(&name) { + return Ok(st.ty()); + } + + // If not yet processed, the struct type may already be cached + // in the global arena (because it is not changing under + // upgrades). + let tok = SignatureToken::Struct(struct_def.struct_handle); + if let Some(ptr) = self.guard.try_intern_for_module(&tok, self.module) { + self.structs.insert(name, StructType::new(ptr)); + return Ok(ptr); + } + + // Intern each field type and compute layout metadata inline. + let mut fields = Vec::with_capacity(field_defs.len()); + let mut offset = 0; + let mut align = 1; + + for field in field_defs { + let field_ty = self.intern_signature_token(&field.signature.0)?; + + let (field_size, field_align) = self + .guard + .type_data(field_ty) + .size_and_align() + .ok_or_else(|| { + anyhow!("Size and alignment is set for non-generic types") + })?; + offset = align_up(offset, field_align); + align = align.max(field_align); + + fields.push(FieldLayout::new(offset, field_ty)); + offset += field_size; + } + + let size = align_up(offset, align); + let ptr = self.guard.intern_struct_type( + self.id, + name, + EMPTY_TYPE_LIST, + size, + align, + &fields, + ); + + self.structs.insert(name, StructType::new(ptr)); + Ok(ptr) + }, + StructFieldInformation::DeclaredVariants(variant_defs) => { + if let Some(enum_def) = self.enums.get(&name) { + return Ok(enum_def.ty()); + } + + // If not yet processed, the enum type may already be cached + // in the global arena. + let tok = SignatureToken::Struct(struct_def.struct_handle); + let ty = self + .guard + .try_intern_for_module(&tok, self.module) + .unwrap_or_else(|| self.guard.intern_enum_type(self.id, name, EMPTY_TYPE_LIST)); + + let mut variants = Vec::with_capacity(variant_defs.len()); + for variant_def in variant_defs { + let mut fields = Vec::with_capacity(variant_def.fields.len()); + for field in &variant_def.fields { + let field_ty = self.intern_signature_token(&field.signature.0)?; + fields.push(field_ty); + } + let fields = self.arena.alloc_slice_copy(&fields); + variants.push(VariantFields::new(fields)); + } + let variants = self.arena.alloc_slice_copy(&variants); + self.enums.insert(name, EnumType::new(ty, variants)); + Ok(ty) + }, + } + } + + /// Interns a signature token as a [`Type`], delegating composite variants + /// to [`walk_sig_token`]. This wrapper keeps the per-token cache fast + /// path (avoids re-walking tokens already seen during this module's + /// resolution). + fn intern_signature_token( + &mut self, + token: &SignatureToken, + // TODO: + // In the future, we need to pass type arguments so we can resolve + // field layouts of fully-instantiated generics. + ) -> anyhow::Result { + if let Some(ptr) = self.guard.try_intern_for_module(token, self.module) { + return Ok(ptr); + } + walk_sig_token(token, self.guard, self) + } +} + +impl<'a, 'guard, 'ctx> StructResolver for ExecutableBuilder<'a, 'guard, 'ctx> { + fn resolve_struct( + &mut self, + struct_handle: StructHandleIndex, + ty_args: &[SignatureToken], + ) -> anyhow::Result { + // TODO: handle type arguments for generic structs! + match self.struct_def_idx.get(&struct_handle).copied() { + Some(def_idx) => self.resolve_struct_def(&self.module.struct_defs[def_idx]), + None => { + // TODO: + // If this type is a struct or an enum that is non-local, + // assume it must be interned & resolved. In the future, + // this case need to load executable dependency first. + let token = if ty_args.is_empty() { + SignatureToken::Struct(struct_handle) + } else { + SignatureToken::StructInstantiation(struct_handle, ty_args.to_vec()) + }; + self.guard + .try_intern_for_module(&token, self.module) + .ok_or_else(|| { + let (address, module_name, struct_name) = + struct_info_at(self.module, struct_handle); + anyhow!( + "Non-local type not yet interned (transitive dependency not loaded): {}::{}::{}", + address, + module_name, + struct_name + ) + }) + }, + } + } +} diff --git a/third_party/move/mono-move/orchestrator/src/lib.rs b/third_party/move/mono-move/orchestrator/src/lib.rs new file mode 100644 index 00000000000..3be37b34d7a --- /dev/null +++ b/third_party/move/mono-move/orchestrator/src/lib.rs @@ -0,0 +1,35 @@ +// 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 + +//! Orchestrator: drives module loading by resolving types, running the +//! specializer, and assembling executables. + +mod builder; + +use anyhow::Result; +pub use builder::ExecutableBuilder; +use mono_move_core::Executable; +use mono_move_global_context::ExecutionGuard; +use move_binary_format::CompiledModule; + +/// Build an executable from a compiled module. +/// +/// Orchestrates the full pipeline: +/// 1. Resolve struct/enum types via the execution guard's interner +/// 2. Run the specializer (destack → lower → gas instrument) +/// 3. Assemble the executable from lowered output +pub fn build_executable( + guard: &ExecutionGuard<'_>, + module: &CompiledModule, +) -> Result> { + let mut builder = ExecutableBuilder::new(guard, module); + builder.resolve_types()?; + let struct_types = builder.struct_type_table(); + let lowered = specializer::destack_and_lower_module(module.clone(), guard, &struct_types)?; + + for lowered_fn in lowered.functions { + builder.add_function(lowered_fn); + } + + builder.finish() +} diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/arg_regs.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/arg_regs.exp similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/arg_regs.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/arg_regs.exp diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/arg_regs.masm b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/arg_regs.masm similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/arg_regs.masm rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/arg_regs.masm diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/arithmetic.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/arithmetic.exp similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/arithmetic.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/arithmetic.exp diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/arithmetic.masm b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/arithmetic.masm similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/arithmetic.masm rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/arithmetic.masm diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/basic_load.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/basic_load.exp similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/basic_load.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/basic_load.exp diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/basic_load.masm b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/basic_load.masm similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/basic_load.masm rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/basic_load.masm diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/borrow_soundness.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/borrow_soundness.exp similarity index 88% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/borrow_soundness.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/borrow_soundness.exp index 53645250f11..88c2a67d89a 100644 --- a/third_party/move/mono-move/specializer/tests/test_cases/masm/borrow_soundness.exp +++ b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/borrow_soundness.exp @@ -62,10 +62,10 @@ fun mut_borrow_kills_reverse(r0): u64 { === micro-ops === === Module 0x66::borrow_soundness === -fun borrow_loc_no_source_subst(): skipped (lowering: instruction ImmBorrowLoc(Home(2), Home(1)) not yet lowered) +fun borrow_loc_no_source_subst(): skipped (lowering: instruction ImmBorrowLoc not yet lowered) -fun imm_borrow_value_prop(): skipped (lowering: instruction ImmBorrowLoc(Home(2), Home(1)) not yet lowered) +fun imm_borrow_value_prop(): skipped (lowering: instruction ImmBorrowLoc not yet lowered) -fun mut_borrow_kills_subst(): skipped (lowering: instruction MutBorrowLoc(Home(3), Home(1)) not yet lowered) +fun mut_borrow_kills_subst(): skipped (lowering: instruction MutBorrowLoc not yet lowered) -fun mut_borrow_kills_reverse(): skipped (lowering: instruction MutBorrowLoc(Home(3), Home(0)) not yet lowered) +fun mut_borrow_kills_reverse(): skipped (lowering: instruction MutBorrowLoc not yet lowered) diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/borrow_soundness.masm b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/borrow_soundness.masm similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/borrow_soundness.masm rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/borrow_soundness.masm diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/calls.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/calls.exp similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/calls.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/calls.exp diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/calls.masm b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/calls.masm similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/calls.masm rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/calls.masm diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/control_flow.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/control_flow.exp similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/control_flow.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/control_flow.exp diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/control_flow.masm b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/control_flow.masm similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/control_flow.masm rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/control_flow.masm diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/field_fusion.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/field_fusion.exp similarity index 79% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/field_fusion.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/field_fusion.exp index c21ba658e86..fc3161f063d 100644 --- a/third_party/move/mono-move/specializer/tests/test_cases/masm/field_fusion.exp +++ b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/field_fusion.exp @@ -88,12 +88,12 @@ fun identity() { 0: Return } -fun add_two_fields(): skipped (not all types are concrete) +fun add_two_fields(): skipped (lowering: instruction ImmBorrowLoc not yet lowered) -fun field_to_call(): skipped (not all types are concrete) +fun field_to_call(): skipped (lowering: instruction ImmBorrowLoc not yet lowered) -fun two_fields_to_call(): skipped (not all types are concrete) +fun two_fields_to_call(): skipped (lowering: instruction ImmBorrowLoc not yet lowered) -fun field_plus_constant(): skipped (not all types are concrete) +fun field_plus_constant(): skipped (lowering: instruction ImmBorrowLoc not yet lowered) -fun increment_field(): skipped (lowering: instruction ReadField(Home(1), FieldHandleIndex(0), Home(0)) not yet lowered) +fun increment_field(): skipped (lowering: instruction ReadField not yet lowered) diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/field_fusion.masm b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/field_fusion.masm similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/field_fusion.masm rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/field_fusion.masm diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/locals_soundness.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/locals_soundness.exp similarity index 95% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/locals_soundness.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/locals_soundness.exp index e6e351431ab..b31d800115e 100644 --- a/third_party/move/mono-move/specializer/tests/test_cases/masm/locals_soundness.exp +++ b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/locals_soundness.exp @@ -46,4 +46,4 @@ fun local_across_blocks() { 6: Return } -fun borrow_written_local(): skipped (lowering: instruction ImmBorrowLoc(Home(2), Home(1)) not yet lowered) +fun borrow_written_local(): skipped (lowering: instruction ImmBorrowLoc not yet lowered) diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/locals_soundness.masm b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/locals_soundness.masm similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/locals_soundness.masm rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/locals_soundness.masm diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/references.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/references.exp similarity index 75% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/references.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/references.exp index 09e99c1f526..d51a886cd38 100644 --- a/third_party/move/mono-move/specializer/tests/test_cases/masm/references.exp +++ b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/references.exp @@ -43,8 +43,8 @@ fun read_field_ref_twice(r0): u64 { === micro-ops === === Module 0x66::test === -fun read_field_val(): skipped (not all types are concrete) +fun read_field_val(): skipped (lowering: instruction ImmBorrowLoc not yet lowered) -fun read_and_add(): skipped (lowering: instruction ReadRef(Home(1), Home(0)) not yet lowered) +fun read_and_add(): skipped (lowering: instruction ReadRef not yet lowered) -fun read_field_ref_twice(): skipped (not all types are concrete) +fun read_field_ref_twice(): skipped (lowering: instruction ImmBorrowLoc not yet lowered) diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/references.masm b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/references.masm similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/references.masm rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/references.masm diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/struct_ops.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/struct_ops.exp similarity index 80% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/struct_ops.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/struct_ops.exp index beed21a5ea9..3e403e2e76d 100644 --- a/third_party/move/mono-move/specializer/tests/test_cases/masm/struct_ops.exp +++ b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/struct_ops.exp @@ -17,4 +17,4 @@ fun pack_and_unpack(): u64 { === micro-ops === === Module 0x66::test === -fun pack_and_unpack(): skipped (not all types are concrete) +fun pack_and_unpack(): skipped (lowering: instruction Pack not yet lowered) diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/struct_ops.masm b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/struct_ops.masm similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/struct_ops.masm rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/struct_ops.masm diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/v2_leftovers.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/v2_leftovers.exp similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/v2_leftovers.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/v2_leftovers.exp diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/v2_leftovers.masm b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/v2_leftovers.masm similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/v2_leftovers.masm rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/v2_leftovers.masm diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/vid_type_reset.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/vid_type_reset.exp similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/vid_type_reset.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/vid_type_reset.exp diff --git a/third_party/move/mono-move/specializer/tests/test_cases/masm/vid_type_reset.masm b/third_party/move/mono-move/orchestrator/tests/test_cases/masm/vid_type_reset.masm similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/masm/vid_type_reset.masm rename to third_party/move/mono-move/orchestrator/tests/test_cases/masm/vid_type_reset.masm diff --git a/third_party/move/mono-move/specializer/tests/test_cases/move/basic.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/move/basic.exp similarity index 90% rename from third_party/move/mono-move/specializer/tests/test_cases/move/basic.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/move/basic.exp index 528fb86450f..5d4be725acc 100644 --- a/third_party/move/mono-move/specializer/tests/test_cases/move/basic.exp +++ b/third_party/move/mono-move/orchestrator/tests/test_cases/move/basic.exp @@ -98,9 +98,9 @@ fun add() { 2: Return } -fun get_x(): skipped (lowering: instruction ReadField(Home(1), FieldHandleIndex(0), Home(0)) not yet lowered) +fun get_x(): skipped (lowering: instruction ReadField not yet lowered) -fun make_point(): skipped (not all types are concrete) +fun make_point(): skipped (lowering: instruction Pack not yet lowered) fun max() { frame_data_size: 16 diff --git a/third_party/move/mono-move/specializer/tests/test_cases/move/basic.move b/third_party/move/mono-move/orchestrator/tests/test_cases/move/basic.move similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/move/basic.move rename to third_party/move/mono-move/orchestrator/tests/test_cases/move/basic.move diff --git a/third_party/move/mono-move/specializer/tests/test_cases/move/fibonacci.exp b/third_party/move/mono-move/orchestrator/tests/test_cases/move/fibonacci.exp similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/move/fibonacci.exp rename to third_party/move/mono-move/orchestrator/tests/test_cases/move/fibonacci.exp diff --git a/third_party/move/mono-move/specializer/tests/test_cases/move/fibonacci.move b/third_party/move/mono-move/orchestrator/tests/test_cases/move/fibonacci.move similarity index 100% rename from third_party/move/mono-move/specializer/tests/test_cases/move/fibonacci.move rename to third_party/move/mono-move/orchestrator/tests/test_cases/move/fibonacci.move diff --git a/third_party/move/mono-move/specializer/tests/testsuite.rs b/third_party/move/mono-move/orchestrator/tests/testsuite.rs similarity index 73% rename from third_party/move/mono-move/specializer/tests/testsuite.rs rename to third_party/move/mono-move/orchestrator/tests/testsuite.rs index 63d93fc9dcf..5c66c7565ca 100644 --- a/third_party/move/mono-move/specializer/tests/testsuite.rs +++ b/third_party/move/mono-move/orchestrator/tests/testsuite.rs @@ -1,12 +1,30 @@ // 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 +//! End-to-end specializer + orchestrator baseline tests. +//! +//! Each `.masm` / `.move` input goes through the full pipeline: +//! +//! assemble/compile → format stackless IR → per-function micro-op lowering +//! +//! The resulting output is compared against a matching `.exp` baseline. +//! Running with `UPBL=1` refreshes baselines in place. +//! +//! Tests live here (rather than in the specializer crate) so that struct +//! references render with real names — resolved via the orchestrator's +//! [`ExecutableBuilder`] rather than a placeholder table. +//! +//! TODO: consider consolidating various kinds of tests into +//! `mono-move/testsuite` for reusability across components. + use codespan_reporting::term::termcolor::Buffer; use legacy_move_compiler::shared::known_attributes::KnownAttribute; +use mono_move_core::types::InternedType; +use mono_move_global_context::{ExecutionGuard, GlobalContext}; +use mono_move_orchestrator::ExecutableBuilder; use move_asm::assembler::{self, Options as AsmOptions}; use move_binary_format::{access::ModuleAccess, CompiledModule}; use move_model::metadata::LanguageVersion; -use move_vm_types::loaded_data::struct_name_indexing::StructNameIndex; use specializer::{ destack, lower::{lower_function, try_build_context, MicroOpsFunctionDisplay}, @@ -14,12 +32,6 @@ use specializer::{ }; use std::path::Path; -fn make_struct_name_table(module: &CompiledModule) -> Vec { - (0..module.struct_handles.len()) - .map(|i| StructNameIndex::new(i as u32)) - .collect() -} - fn format_micro_ops(module_ir: &ModuleIR) -> String { let module = &module_ir.module; let self_handle = module.module_handle_at(module.self_module_handle_idx); @@ -35,7 +47,7 @@ fn format_micro_ops(module_ir: &ModuleIR) -> String { for func_ir in module_ir.functions.iter().flatten() { let func_name = module.identifier_at(func_ir.name_idx).to_string(); - match try_build_context(module, func_ir) { + match try_build_context(module_ir, func_ir) { Err(e) => { out.push_str(&format!( "\nfun {}(): skipped (context: {})\n", @@ -69,6 +81,17 @@ fn format_micro_ops(module_ir: &ModuleIR) -> String { out } +/// Resolve types via the orchestrator's builder and return the +/// struct_type_table that the specializer expects. +fn resolve_struct_types( + guard: &ExecutionGuard<'_>, + module: &CompiledModule, +) -> Result>, String> { + let mut builder = ExecutableBuilder::new(guard, module); + builder.resolve_types().map_err(|e| format!("{:#}", e))?; + Ok(builder.struct_type_table()) +} + const EXP_EXT: &str = "exp"; datatest_stable::harness!( @@ -86,18 +109,21 @@ fn masm_runner(path: &Path) -> datatest_stable::Result<()> { let result = assembler::assemble(&options, &input, std::iter::empty()) .map_err(|e| format!("{:?}", e))?; let module = result.left().ok_or("expected module, got script")?; - let table = make_struct_name_table(&module); - let ir = destack(module, &table).map_err(|e| format!("{:#}", e))?; + let ctx = GlobalContext::with_num_execution_workers(1); + let guard = ctx.try_execution_context(0).unwrap(); + let struct_types = resolve_struct_types(&guard, &module)?; + + let ir = destack(module, &guard, &struct_types).map_err(|e| format!("{:#}", e))?; let mut output = format!("{}", ir); output.push_str("\n=== micro-ops ===\n"); output.push_str(&format_micro_ops(&ir)); + let baseline_path = path.with_extension(EXP_EXT); move_prover_test_utils::baseline_test::verify_or_update_baseline( baseline_path.as_path(), &output, )?; - Ok(()) } @@ -140,12 +166,16 @@ fn move_runner(path: &Path) -> datatest_stable::Result<()> { }) .collect(); + let ctx = GlobalContext::with_num_execution_workers(1); + let guard = ctx.try_execution_context(0).unwrap(); + let mut output = String::new(); for module in &modules { - let table = make_struct_name_table(module); + let struct_types = resolve_struct_types(&guard, module)?; let masm_output = move_asm::disassembler::disassemble_module(String::new(), module) .map_err(|e| format!("disassembly failed: {:#}", e))?; - let module_ir = destack(module.clone(), &table).map_err(|e| format!("{:#}", e))?; + let module_ir = + destack(module.clone(), &guard, &struct_types).map_err(|e| format!("{:#}", e))?; let ir_output = format!("{}", module_ir); output.push_str("=== masm ===\n"); output.push_str(&masm_output); @@ -159,6 +189,5 @@ fn move_runner(path: &Path) -> datatest_stable::Result<()> { baseline_path.as_path(), &output, )?; - Ok(()) } diff --git a/third_party/move/mono-move/global-context/tests/types_tests.rs b/third_party/move/mono-move/orchestrator/tests/types_tests.rs similarity index 89% rename from third_party/move/mono-move/global-context/tests/types_tests.rs rename to third_party/move/mono-move/orchestrator/tests/types_tests.rs index b1874b8e1b1..76f6735bbb7 100644 --- a/third_party/move/mono-move/global-context/tests/types_tests.rs +++ b/third_party/move/mono-move/orchestrator/tests/types_tests.rs @@ -15,9 +15,7 @@ fn add_executable<'guard>(guard: &'guard ExecutionGuard<'_>, source: &str) -> &' .left() .expect("Only modules are expected"); - let executable = guard - .executable_builder_for_module(&module) - .build() + let executable = mono_move_orchestrator::build_executable(guard, &module) .expect("Building an executable should always succeed"); let id = guard.intern_address_name(module.self_addr(), module.self_name()); @@ -64,7 +62,7 @@ struct D let name = guard.intern_identifier(ident_str!("B")); let layout = executable - .get_struct(name) + .get_struct(name.into_global_arena_ptr()) .unwrap() .struct_layout() .unwrap(); @@ -79,7 +77,7 @@ struct D let name = guard.intern_identifier(ident_str!("D")); let layout = executable - .get_struct(name) + .get_struct(name.into_global_arena_ptr()) .unwrap() .struct_layout() .unwrap(); @@ -93,5 +91,7 @@ struct D assert_eq!(offsets, vec![0, 1, 8, 16, 32, 48, 64, 160]); let name = guard.intern_identifier(ident_str!("E")); - assert!(executable.get_struct(name).is_none()); + assert!(executable + .get_struct(name.into_global_arena_ptr()) + .is_none()); } diff --git a/third_party/move/mono-move/specializer/Cargo.toml b/third_party/move/mono-move/specializer/Cargo.toml index 55c6d31c004..131d42cc44f 100644 --- a/third_party/move/mono-move/specializer/Cargo.toml +++ b/third_party/move/mono-move/specializer/Cargo.toml @@ -6,34 +6,14 @@ publish = false [dependencies] anyhow = { workspace = true } -clap = { workspace = true } mono-move-core = { workspace = true } mono-move-gas = { workspace = true } -move-asm = { workspace = true } +mono-move-global-context = { workspace = true } move-binary-format = { workspace = true } move-bytecode-verifier = { workspace = true } move-core-types = { workspace = true } -move-vm-types = { workspace = true, features = ["testing"] } shared-dsa = { workspace = true } smallvec = { workspace = true } -triomphe = { workspace = true } - -[dev-dependencies] -codespan-reporting = { workspace = true } -datatest-stable = { workspace = true } -legacy-move-compiler = { workspace = true } -move-compiler-v2 = { workspace = true } -move-model = { workspace = true } -move-prover-test-utils = { workspace = true } -move-vm-types = { workspace = true, features = ["testing"] } - -[[bin]] -name = "mseir-compiler" -path = "src/bin/mseir-compiler.rs" - -[[test]] -name = "testsuite" -harness = false [lib] doctest = false diff --git a/third_party/move/mono-move/specializer/TODO.md b/third_party/move/mono-move/specializer/TODO.md new file mode 100644 index 00000000000..5491115330d --- /dev/null +++ b/third_party/move/mono-move/specializer/TODO.md @@ -0,0 +1,3 @@ +# Specializer TODOs + +- Revisit the integer types everywhere in the specializer code base and reason through whether it is the correct width. \ No newline at end of file diff --git a/third_party/move/mono-move/specializer/src/destack/instr_utils.rs b/third_party/move/mono-move/specializer/src/destack/instr_utils.rs index 4f0c268330b..8e616f9fb8d 100644 --- a/third_party/move/mono-move/specializer/src/destack/instr_utils.rs +++ b/third_party/move/mono-move/specializer/src/destack/instr_utils.rs @@ -124,15 +124,15 @@ pub(crate) fn extract_imm_value(instr: &Instr) -> Option<(Slot, ImmValue)> { | Instr::BinaryOp(_, _, _, _) | Instr::BinaryOpImm(_, _, _, _) | Instr::Pack(_, _, _) - | Instr::PackGeneric(_, _, _) + | Instr::PackGeneric(_, _, _, _) | Instr::Unpack(_, _, _) - | Instr::UnpackGeneric(_, _, _) - | Instr::PackVariant(_, _, _) - | Instr::PackVariantGeneric(_, _, _) - | Instr::UnpackVariant(_, _, _) - | Instr::UnpackVariantGeneric(_, _, _) - | Instr::TestVariant(_, _, _) - | Instr::TestVariantGeneric(_, _, _) + | Instr::UnpackGeneric(_, _, _, _) + | Instr::PackVariant(_, _, _, _) + | Instr::PackVariantGeneric(_, _, _, _, _) + | Instr::UnpackVariant(_, _, _, _) + | Instr::UnpackVariantGeneric(_, _, _, _, _) + | Instr::TestVariant(_, _, _, _) + | Instr::TestVariantGeneric(_, _, _, _, _) | Instr::ImmBorrowLoc(_, _) | Instr::MutBorrowLoc(_, _) | Instr::ImmBorrowField(_, _, _) @@ -154,15 +154,15 @@ pub(crate) fn extract_imm_value(instr: &Instr) -> Option<(Slot, ImmValue)> { | Instr::WriteVariantField(_, _, _) | Instr::WriteVariantFieldGeneric(_, _, _) | Instr::Exists(_, _, _) - | Instr::ExistsGeneric(_, _, _) + | Instr::ExistsGeneric(_, _, _, _) | Instr::MoveFrom(_, _, _) - | Instr::MoveFromGeneric(_, _, _) + | Instr::MoveFromGeneric(_, _, _, _) | Instr::MoveTo(_, _, _) - | Instr::MoveToGeneric(_, _, _) + | Instr::MoveToGeneric(_, _, _, _) | Instr::ImmBorrowGlobal(_, _, _) - | Instr::ImmBorrowGlobalGeneric(_, _, _) + | Instr::ImmBorrowGlobalGeneric(_, _, _, _) | Instr::MutBorrowGlobal(_, _, _) - | Instr::MutBorrowGlobalGeneric(_, _, _) + | Instr::MutBorrowGlobalGeneric(_, _, _, _) | Instr::Call(_, _, _) | Instr::CallGeneric(_, _, _) | Instr::PackClosure(_, _, _, _) @@ -281,21 +281,35 @@ fn visit_slots( used::(*lhs, &mut f); }, - Instr::Pack(dst, _, fields) - | Instr::PackGeneric(dst, _, fields) - | Instr::PackVariant(dst, _, fields) - | Instr::PackVariantGeneric(dst, _, fields) => { + Instr::Pack(dst, _, fields) => { def::(*dst, &mut f); uses::(fields, &mut f); }, - Instr::Unpack(dsts, _, src) - | Instr::UnpackGeneric(dsts, _, src) - | Instr::UnpackVariant(dsts, _, src) - | Instr::UnpackVariantGeneric(dsts, _, src) => { + Instr::PackGeneric(dst, _, _, fields) | Instr::PackVariant(dst, _, _, fields) => { + def::(*dst, &mut f); + uses::(fields, &mut f); + }, + Instr::PackVariantGeneric(dst, _, _, _, fields) => { + def::(*dst, &mut f); + uses::(fields, &mut f); + }, + Instr::Unpack(dsts, _, src) => { + defs::(dsts, &mut f); + used::(*src, &mut f); + }, + Instr::UnpackGeneric(dsts, _, _, src) | Instr::UnpackVariant(dsts, _, _, src) => { + defs::(dsts, &mut f); + used::(*src, &mut f); + }, + Instr::UnpackVariantGeneric(dsts, _, _, _, src) => { defs::(dsts, &mut f); used::(*src, &mut f); }, - Instr::TestVariant(dst, _, src) | Instr::TestVariantGeneric(dst, _, src) => { + Instr::TestVariant(dst, _, _, src) => { + def::(*dst, &mut f); + used::(*src, &mut f); + }, + Instr::TestVariantGeneric(dst, _, _, _, src) => { def::(*dst, &mut f); used::(*src, &mut f); }, @@ -334,21 +348,28 @@ fn visit_slots( used::(*val, &mut f); }, - Instr::Exists(dst, _, addr) - | Instr::ExistsGeneric(dst, _, addr) - | Instr::MoveFrom(dst, _, addr) - | Instr::MoveFromGeneric(dst, _, addr) => { + Instr::Exists(dst, _, addr) | Instr::MoveFrom(dst, _, addr) => { def::(*dst, &mut f); used::(*addr, &mut f); }, - Instr::MoveTo(_, signer, val) | Instr::MoveToGeneric(_, signer, val) => { + Instr::ExistsGeneric(dst, _, _, addr) | Instr::MoveFromGeneric(dst, _, _, addr) => { + def::(*dst, &mut f); + used::(*addr, &mut f); + }, + Instr::MoveTo(_, signer, val) => { used::(*signer, &mut f); used::(*val, &mut f); }, - Instr::ImmBorrowGlobal(dst, _, addr) - | Instr::ImmBorrowGlobalGeneric(dst, _, addr) - | Instr::MutBorrowGlobal(dst, _, addr) - | Instr::MutBorrowGlobalGeneric(dst, _, addr) => { + Instr::MoveToGeneric(_, _, signer, val) => { + used::(*signer, &mut f); + used::(*val, &mut f); + }, + Instr::ImmBorrowGlobal(dst, _, addr) | Instr::MutBorrowGlobal(dst, _, addr) => { + def::(*dst, &mut f); + used::(*addr, &mut f); + }, + Instr::ImmBorrowGlobalGeneric(dst, _, _, addr) + | Instr::MutBorrowGlobalGeneric(dst, _, _, addr) => { def::(*dst, &mut f); used::(*addr, &mut f); }, @@ -483,10 +504,7 @@ fn rewrite_instr_slots { + Instr::Pack(dst, _, fields) => { if DEFS { rewrite_slot(dst, &mut f); } @@ -494,10 +512,39 @@ fn rewrite_instr_slots { + Instr::PackGeneric(dst, _, _, fields) | Instr::PackVariant(dst, _, _, fields) => { + if DEFS { + rewrite_slot(dst, &mut f); + } + if USES { + rewrite_slots(fields, &mut f); + } + }, + Instr::PackVariantGeneric(dst, _, _, _, fields) => { + if DEFS { + rewrite_slot(dst, &mut f); + } + if USES { + rewrite_slots(fields, &mut f); + } + }, + Instr::Unpack(dsts, _, src) => { + if DEFS { + rewrite_slots(dsts, &mut f); + } + if USES { + rewrite_slot(src, &mut f); + } + }, + Instr::UnpackGeneric(dsts, _, _, src) | Instr::UnpackVariant(dsts, _, _, src) => { + if DEFS { + rewrite_slots(dsts, &mut f); + } + if USES { + rewrite_slot(src, &mut f); + } + }, + Instr::UnpackVariantGeneric(dsts, _, _, _, src) => { if DEFS { rewrite_slots(dsts, &mut f); } @@ -505,7 +552,15 @@ fn rewrite_instr_slots { + Instr::TestVariant(dst, _, _, src) => { + if DEFS { + rewrite_slot(dst, &mut f); + } + if USES { + rewrite_slot(src, &mut f); + } + }, + Instr::TestVariantGeneric(dst, _, _, _, src) => { if DEFS { rewrite_slot(dst, &mut f); } @@ -568,10 +623,7 @@ fn rewrite_instr_slots { + Instr::Exists(dst, _, addr) | Instr::MoveFrom(dst, _, addr) => { if DEFS { rewrite_slot(dst, &mut f); } @@ -579,16 +631,36 @@ fn rewrite_instr_slots { + Instr::ExistsGeneric(dst, _, _, addr) | Instr::MoveFromGeneric(dst, _, _, addr) => { + if DEFS { + rewrite_slot(dst, &mut f); + } + if USES { + rewrite_slot(addr, &mut f); + } + }, + Instr::MoveTo(_, signer, val) => { if USES { rewrite_slot(signer, &mut f); rewrite_slot(val, &mut f); } }, - Instr::ImmBorrowGlobal(dst, _, addr) - | Instr::ImmBorrowGlobalGeneric(dst, _, addr) - | Instr::MutBorrowGlobal(dst, _, addr) - | Instr::MutBorrowGlobalGeneric(dst, _, addr) => { + Instr::MoveToGeneric(_, _, signer, val) => { + if USES { + rewrite_slot(signer, &mut f); + rewrite_slot(val, &mut f); + } + }, + Instr::ImmBorrowGlobal(dst, _, addr) | Instr::MutBorrowGlobal(dst, _, addr) => { + if DEFS { + rewrite_slot(dst, &mut f); + } + if USES { + rewrite_slot(addr, &mut f); + } + }, + Instr::ImmBorrowGlobalGeneric(dst, _, _, addr) + | Instr::MutBorrowGlobalGeneric(dst, _, _, addr) => { if DEFS { rewrite_slot(dst, &mut f); } diff --git a/third_party/move/mono-move/specializer/src/destack/mod.rs b/third_party/move/mono-move/specializer/src/destack/mod.rs index 35d3da10260..e83133dc556 100644 --- a/third_party/move/mono-move/specializer/src/destack/mod.rs +++ b/third_party/move/mono-move/specializer/src/destack/mod.rs @@ -14,20 +14,34 @@ mod type_conversion; use crate::stackless_exec_ir::ModuleIR; use anyhow::{bail, Result}; +use mono_move_core::types::InternedType; +use mono_move_global_context::ExecutionGuard; use move_binary_format::CompiledModule; -use move_vm_types::loaded_data::struct_name_indexing::StructNameIndex; /// Verify, convert, and optimize a compiled module into stackless execution IR. /// -/// `struct_name_table` maps `StructHandleIndex` ordinals to globally unique -/// `StructNameIndex` values, used to convert bytecode-level struct references -/// into runtime `Type` representations. -pub fn destack(module: CompiledModule, struct_name_table: &[StructNameIndex]) -> Result { +/// `struct_types` maps `StructHandleIndex` ordinals to pre-resolved interned +/// type pointers, used to convert bytecode-level struct references into +/// interned `Type` representations. `None` entries denote handles the +/// orchestrator could not resolve; touching one during conversion is an +/// error. +/// +/// TODO: consider wrapping the `(CompiledModule, struct_types)` pair in a +/// `CompiledModuleWithContext` that owns the module and the resolved +/// per-handle interned types (and, later, the full signature pool interned +/// up front). That would let every downstream pass take one argument instead +/// of threading the slice through, and keep the invariant that `struct_types` +/// indexes the module's own handle table encoded in the type. +pub fn destack( + module: CompiledModule, + guard: &ExecutionGuard<'_>, + struct_types: &[Option], +) -> Result { if let Err(e) = move_bytecode_verifier::verify_module(&module) { bail!("bytecode verification failed: {:#}", e); } - let mut module_ir = translate::translate_module(module, struct_name_table)?; + let mut module_ir = translate::translate_module(module, guard, struct_types)?; optimize::optimize_module(&mut module_ir); Ok(module_ir) } diff --git a/third_party/move/mono-move/specializer/src/destack/slot_alloc.rs b/third_party/move/mono-move/specializer/src/destack/slot_alloc.rs index 869e5593194..9233d5ebc95 100644 --- a/third_party/move/mono-move/specializer/src/destack/slot_alloc.rs +++ b/third_party/move/mono-move/specializer/src/destack/slot_alloc.rs @@ -13,7 +13,7 @@ use super::{ }; use crate::stackless_exec_ir::{BasicBlock, Instr, Slot}; use anyhow::{bail, Context, Result}; -use move_vm_types::loaded_data::runtime_types::Type; +use mono_move_core::types::InternedType; use shared_dsa::UnorderedMap; /// Output of slot allocation for a single function. @@ -21,7 +21,105 @@ pub(crate) struct AllocatedFunction { pub blocks: Vec, pub num_home_slots: u16, pub num_xfer_slots: u16, - pub home_slot_types: Vec, + pub home_slot_types: Vec, +} + +/// SSA `Vid` → real slot mapping with per-slot type tracking. +/// +/// Backed by two dense vectors indexed by ordinal: +/// - `real_slot_types[i]` — type of `Slot::Home(i)`. +/// - `vid_to_real[i]` — binding of `Slot::Vid(i)` (`None` if unbound). +/// +/// Invariant: every Home slot has a recorded type. Enforced by +/// [`Self::mint_fresh`] being the only slot-introduction path. Pinned-local +/// identity (`Home(i) → Home(i)` for `i < num_pinned`) is inferred, not stored. +struct SlotTable { + /// Pinned local count. Occupies `0..num_pinned` in `real_slot_types`. + num_pinned: u16, + /// Type per Home slot, indexed by ordinal. Grows only via `mint_fresh`. + real_slot_types: Vec, + /// Binding per Vid, indexed by ordinal. Cleared per block. + vid_to_real: Vec>, +} + +impl SlotTable { + fn new(local_types: &[InternedType]) -> Self { + Self { + num_pinned: local_types.len() as u16, + real_slot_types: local_types.to_vec(), + vid_to_real: Vec::new(), + } + } + + /// Resets per-block state. + fn start_block(&mut self) { + self.vid_to_real.clear(); + } + + /// Mints a fresh Home slot with the given type and binds `vid` to it. + /// The only path that introduces a new real slot. + fn mint_fresh(&mut self, vid: Slot, ty: InternedType) -> Slot { + let real = Slot::Home(self.real_slot_types.len() as u16); + self.real_slot_types.push(ty); + self.bind(vid, real); + real + } + + /// Binds `vid` to an existing real slot. No-op for non-Vid keys + /// (pinned identity is implicit; Xfer slots pass through). + fn bind(&mut self, vid: Slot, real: Slot) { + if let Slot::Vid(i) = vid { + let i = i as usize; + if self.vid_to_real.len() <= i { + self.vid_to_real.resize(i + 1, None); + } + self.vid_to_real[i] = Some(real); + } + } + + /// Returns `(real_slot, type)` for a vid bound to a Home slot, or + /// `None` if unbound or bound to an Xfer slot. Returning both pieces + /// atomically prevents callers from observing a slot without its type. + fn lookup(&self, vid: Slot) -> Option<(Slot, InternedType)> { + let real = self.real_of_opt(vid)?; + let Slot::Home(i) = real else { return None }; + let ty = *self.real_slot_types.get(i as usize)?; + Some((real, ty)) + } + + /// Whether `vid` is bound (pinned identity counts). + fn contains(&self, vid: &Slot) -> bool { + self.real_of_opt(*vid).is_some() + } + + /// Returns the real slot for `vid`, or `vid` unchanged if unbound. + fn real_of(&self, vid: Slot) -> Slot { + self.real_of_opt(vid).unwrap_or(vid) + } + + /// Whether `real` is poolable — a non-pinned Home slot. + fn is_poolable(&self, real: Slot) -> bool { + matches!(real, Slot::Home(i) if i >= self.num_pinned) + } + + fn next_slot(&self) -> u16 { + self.real_slot_types.len() as u16 + } + + /// Consumes the table and returns the per-Home-slot type vector + /// indexed by ordinal. + fn into_home_slot_types(self) -> Vec { + self.real_slot_types + } + + fn real_of_opt(&self, vid: Slot) -> Option { + match vid { + // Pinned locals are identity-mapped without an explicit entry. + Slot::Home(i) if i < self.num_pinned => Some(vid), + Slot::Vid(i) => self.vid_to_real.get(i as usize).copied().flatten(), + _ => None, + } + } } /// Map SSA `Vid`s to real slots across all blocks. @@ -32,60 +130,43 @@ pub(crate) struct AllocatedFunction { /// to its type at index `i`. /// Post: all `Vid`s replaced with real `Home`/`Xfer` slots. pub(crate) fn allocate_slots(ssa: SSAFunction) -> Result { - let num_pinned = ssa.local_types.len() as u16; + let mut table = SlotTable::new(&ssa.local_types); let mut result_blocks = Vec::with_capacity(ssa.blocks.len()); - let mut global_next_slot = num_pinned; let mut global_num_xfer_slots: u16 = 0; - let mut free_pool: UnorderedMap> = UnorderedMap::new(); - let mut real_slot_types: UnorderedMap = UnorderedMap::new(); - for (i, ty) in ssa.local_types.iter().enumerate() { - real_slot_types.insert(Slot::Home(i as u16), ty.clone()); - } + let mut free_pool: UnorderedMap> = UnorderedMap::new(); for mut block in ssa.blocks { let analysis = BlockAnalysis::analyze(&block.instrs); - let (block_max, block_xfer_slots, returned_pool) = allocate_block_in_place( + let (block_xfer_slots, returned_pool) = allocate_block_in_place( &mut block.instrs, - num_pinned, - global_next_slot, + &mut table, free_pool, &ssa.vid_types, - &mut real_slot_types, &analysis, )?; free_pool = returned_pool; - if block_max > global_next_slot { - global_next_slot = block_max; - } if block_xfer_slots > global_num_xfer_slots { global_num_xfer_slots = block_xfer_slots; } result_blocks.push(block); } - let mut home_slot_types = Vec::with_capacity(global_next_slot as usize); - for i in 0..global_next_slot { - home_slot_types.push( - real_slot_types - .get(&Slot::Home(i)) - .cloned() - .context("missing type for real slot")?, - ); - } + let num_home_slots = table.next_slot(); + let home_slot_types = table.into_home_slot_types(); Ok(AllocatedFunction { blocks: result_blocks, - num_home_slots: global_next_slot, + num_home_slots, num_xfer_slots: global_num_xfer_slots, home_slot_types, }) } -fn vid_type(vid: Slot, vid_types: &[Type]) -> Result { +fn vid_type(vid: Slot, vid_types: &[InternedType]) -> Result { match vid { Slot::Vid(i) => vid_types .get(i as usize) - .cloned() + .copied() .context("VID type not found during SSA allocation"), _ => bail!("vid_type called on non-Vid slot {:?}", vid), } @@ -96,27 +177,19 @@ fn vid_type(vid: Slot, vid_types: &[Type]) -> Result { /// For each instruction, in order: free last-use sources, allocate defs, remap, free dead defs. /// Allocation priority: xfer_precolor > stloc_targets > coalesce_to_local > type-keyed reuse > fresh. /// -/// Returns (next available slot index, xfer width, updated free pool). +/// Returns (xfer width, updated free pool). fn allocate_block_in_place( instrs: &mut [Instr], - num_pinned: u16, - start_slot: u16, - carry_pool: UnorderedMap>, - vid_types: &[Type], - real_slot_types: &mut UnorderedMap, + table: &mut SlotTable, + carry_pool: UnorderedMap>, + vid_types: &[InternedType], analysis: &BlockAnalysis, -) -> Result<(u16, u16, UnorderedMap>)> { +) -> Result<(u16, UnorderedMap>)> { if instrs.is_empty() { - return Ok((start_slot, 0, carry_pool)); - } - - // Identity mapping for pinned locals so remap_all_slots leaves them unchanged. - let mut vid_to_real: UnorderedMap = UnorderedMap::new(); - for r in 0..num_pinned { - vid_to_real.insert(Slot::Home(r), Slot::Home(r)); + return Ok((0, carry_pool)); } + table.start_block(); let mut free_pool = carry_pool; - let mut next_slot = start_slot; for (i, instr) in instrs.iter_mut().enumerate() { let (defs, uses) = collect_defs_and_uses(instr); @@ -124,63 +197,53 @@ fn allocate_block_in_place( // Phase 1: Free use-slots whose last use is this instruction. // Done BEFORE def allocation so the freed slot can be immediately reused. // Safe because sources are read before destinations are written. - // Only non-pinned Home slots are pooled (pinned locals and Xfer slots are not). for r in &uses { if r.is_vid() && analysis.last_use.get(r) == Some(&i) && !defs.contains(r) - && let Some(&real) = vid_to_real.get(r) - && matches!(real, Slot::Home(i) if i >= num_pinned) + && let Some((real, ty)) = table.lookup(*r) + && table.is_poolable(real) { - let ty = real_slot_types.get(&real).cloned().unwrap_or(Type::Bool); free_pool.entry(ty).or_default().push(real); } } // Phase 2: Allocate real slots for destination `Vid`s. for d in &defs { - if d.is_vid() && !vid_to_real.contains_key(d) { + if d.is_vid() && !table.contains(d) { if let Some(&xfer_r) = analysis.xfer_precolor.get(d) { - vid_to_real.insert(*d, xfer_r); + table.bind(*d, xfer_r); } else if let Some(&local_r) = analysis.stloc_targets.get(d) { - vid_to_real.insert(*d, local_r); + table.bind(*d, local_r); } else if let Some(&local_r) = analysis.coalesce_to_local.get(d) { - vid_to_real.insert(*d, local_r); + table.bind(*d, local_r); } else { // General case: reuse a same-typed slot from the pool, or mint a fresh one. let ty = vid_type(*d, vid_types)?; - let real = if let Some(slots) = free_pool.get_mut(&ty) { - slots.pop() + if let Some(real) = free_pool.get_mut(&ty).and_then(|slots| slots.pop()) { + table.bind(*d, real); } else { - None - }; - let real = real.unwrap_or_else(|| { - let r = Slot::Home(next_slot); - next_slot += 1; - real_slot_types.insert(r, ty); - r - }); - vid_to_real.insert(*d, real); + table.mint_fresh(*d, ty); + } } } } // Phase 3: Rewrite the instruction with real slots — in-place. - remap_all_slots_with(instr, |s| *vid_to_real.get(&s).unwrap_or(&s)); + remap_all_slots_with(instr, |s| table.real_of(s)); // Phase 4: Free slots for defs that are never used (last_use == def site). for d in &defs { if d.is_vid() && analysis.last_use.get(d) == Some(&i) && !uses.contains(d) - && let Some(&real) = vid_to_real.get(d) - && matches!(real, Slot::Home(i) if i >= num_pinned) + && let Some((real, ty)) = table.lookup(*d) + && table.is_poolable(real) { - let ty = real_slot_types.get(&real).cloned().unwrap_or(Type::Bool); free_pool.entry(ty).or_default().push(real); } } } - Ok((next_slot, analysis.max_xfer_width, free_pool)) + Ok((analysis.max_xfer_width, free_pool)) } diff --git a/third_party/move/mono-move/specializer/src/destack/ssa_conversion.rs b/third_party/move/mono-move/specializer/src/destack/ssa_conversion.rs index bc6a111e4c9..d4ae837bd02 100644 --- a/third_party/move/mono-move/specializer/src/destack/ssa_conversion.rs +++ b/third_party/move/mono-move/specializer/src/destack/ssa_conversion.rs @@ -13,15 +13,18 @@ use super::{ }; use crate::stackless_exec_ir::{BasicBlock, BinaryOp, CmpOp, Instr, Label, Slot, UnaryOp}; use anyhow::{bail, ensure, Context, Result}; +use mono_move_core::types::{self as ty, InternedType, InternedTypeList}; +use mono_move_global_context::ExecutionGuard; use move_binary_format::{ access::ModuleAccess, file_format::{ - Bytecode, CodeOffset, SignatureToken, StructDefInstantiationIndex, StructDefinitionIndex, - StructFieldInformation, VariantIndex, + Bytecode, CodeOffset, FieldHandleIndex, FieldInstantiationIndex, SignatureToken, + StructDefInstantiationIndex, StructDefinitionIndex, StructFieldInformation, + StructVariantInstantiationIndex, VariantFieldHandleIndex, VariantFieldInstantiationIndex, + VariantIndex, }, CompiledModule, }; -use move_vm_types::loaded_data::{runtime_types::Type, struct_name_indexing::StructNameIndex}; use shared_dsa::{Entry, UnorderedMap}; use std::ops::Range; @@ -74,11 +77,16 @@ pub(crate) struct SsaConverter<'a> { /// Simulated operand stack. stack: Vec, /// Types of all locals (params ++ declared locals). - local_types: Vec, + local_types: Vec, /// Types of value IDs, indexed directly by value ID number. - vid_types: Vec, - /// Struct name table for type conversion. - struct_name_table: &'a [StructNameIndex], + vid_types: Vec, + /// Execution guard for interning types. + guard: &'a ExecutionGuard<'a>, + /// Pre-resolved struct types, indexed by StructHandleIndex ordinal. + /// `None` entries denote handles the orchestrator could not resolve + /// (imported or generic); the converter must reject any code that + /// references them. + struct_types: &'a [Option], /// Completed basic blocks. blocks: Vec, /// Instructions for the current block being built. @@ -92,13 +100,18 @@ pub(crate) struct SsaConverter<'a> { } impl<'a> SsaConverter<'a> { - pub(crate) fn new(local_types: Vec, struct_name_table: &'a [StructNameIndex]) -> Self { + pub(crate) fn new( + local_types: Vec, + guard: &'a ExecutionGuard<'a>, + struct_types: &'a [Option], + ) -> Self { Self { next_vid: 0, stack: Vec::new(), local_types, vid_types: Vec::new(), - struct_name_table, + guard, + struct_types, blocks: Vec::new(), current_block_instrs: Vec::new(), current_block_label: None, @@ -107,7 +120,7 @@ impl<'a> SsaConverter<'a> { } } - fn alloc_vid(&mut self, ty: Type) -> Result { + fn alloc_vid(&mut self, ty: InternedType) -> Result { let vid = Slot::Vid(self.next_vid); self.next_vid = self .next_vid @@ -134,12 +147,12 @@ impl<'a> SsaConverter<'a> { /// Returns the type of a Vid slot by looking it up in `vid_types`. /// Invariant: only Vid slots appear on the operand stack. - fn vid_type(&self, slot: Slot) -> Result { + fn vid_type(&self, slot: Slot) -> Result { match slot { Slot::Vid(id) => self .vid_types .get(id as usize) - .cloned() + .copied() .with_context(|| format!("Vid id {} out of range", id)), other => bail!("expected Vid slot on operand stack, got {:?}", other), } @@ -172,28 +185,88 @@ impl<'a> SsaConverter<'a> { } // -------------------------------------------------------------------------------------------- - // Type helpers: these will all be replaced by cached type representations. + // Type helpers // -------------------------------------------------------------------------------------------- - fn struct_type(&self, module: &CompiledModule, idx: StructDefinitionIndex) -> Type { - let def = &module.struct_defs[idx.0 as usize]; - let tok = SignatureToken::Struct(def.struct_handle); - convert_sig_token(module, &tok, self.struct_name_table) + fn struct_type( + &self, + module: &CompiledModule, + idx: StructDefinitionIndex, + ) -> Result { + let handle = module.struct_defs[idx.0 as usize].struct_handle; + match self.struct_types.get(handle.0 as usize) { + Some(Some(ty)) => Ok(*ty), + Some(None) => bail!( + "unresolved struct handle {} for local struct definition {}", + handle.0, + idx.0 + ), + None => bail!("struct handle index {} out of bounds", handle.0), + } + } + + fn struct_inst_type( + &self, + module: &CompiledModule, + idx: StructDefInstantiationIndex, + ) -> Result { + // TODO: when generic-instantiation interning lands, return a + // distinct type for the instantiation. For now, the base struct + // type is what the rest of the pipeline uses. + let inst = &module.struct_def_instantiations[idx.0 as usize]; + self.struct_type(module, inst.def) } - fn struct_inst_type(&self, module: &CompiledModule, idx: StructDefInstantiationIndex) -> Type { + /// Returns `(base_struct_ty, ty_args_list)` for a generic struct + /// instantiation. `base_struct_ty` is the (uninstantiated) struct type; + /// `ty_args_list` is the interned list of type arguments. + /// + /// Because proper generic-instantiation interning is not yet implemented, + /// this pair preserves the information needed for later monomorphization + /// without producing a distinct instantiated struct type. + fn struct_inst_parts( + &self, + module: &CompiledModule, + idx: StructDefInstantiationIndex, + ) -> Result<(InternedType, InternedTypeList)> { let inst = &module.struct_def_instantiations[idx.0 as usize]; - let def = &module.struct_defs[inst.def.0 as usize]; - let type_params = module.signature_at(inst.type_parameters).0.clone(); - let tok = SignatureToken::StructInstantiation(def.struct_handle, type_params); - convert_sig_token(module, &tok, self.struct_name_table) + let base_ty = self.struct_type(module, inst.def)?; + let ty_args_toks = &module.signature_at(inst.type_parameters).0; + let ty_args = convert_sig_tokens(ty_args_toks, self.guard, self.struct_types)?; + let ty_args_ptr = self.guard.intern_type_list(&ty_args); + Ok((base_ty, ty_args_ptr)) } - fn field_type( + /// Returns `(enum_ty, variant_ordinal, ty_args_list)` for a generic + /// enum-variant instantiation. + fn variant_inst_parts( &self, module: &CompiledModule, - idx: move_binary_format::file_format::FieldHandleIndex, - ) -> Result { + idx: StructVariantInstantiationIndex, + ) -> Result<(InternedType, u16, InternedTypeList)> { + let inst = &module.struct_variant_instantiations[idx.0 as usize]; + let handle = &module.struct_variant_handles[inst.handle.0 as usize]; + let enum_ty = self.struct_type(module, handle.struct_index)?; + let ty_args_toks = &module.signature_at(inst.type_parameters).0; + let ty_args = convert_sig_tokens(ty_args_toks, self.guard, self.struct_types)?; + let ty_args_ptr = self.guard.intern_type_list(&ty_args); + Ok((enum_ty, handle.variant, ty_args_ptr)) + } + + // TODO: reconsider field-type resolution. + // + // Current (walk on demand): + // + no extra builder pass or storage + // + pays cost only for fields actually accessed + // - re-walks the same field's signature on each access + // - field resolution scattered across destack; not a single-step lookup + // + // Alternative (pre-resolve into a table, indexed by field handle): + // + O(1) lookup here; mirrors how `struct_types` is handled + // + single authoritative resolution pass in the builder + // - extra builder pass + `Vec` storage per module + // - pays cost upfront, including for never-accessed fields + fn field_type(&self, module: &CompiledModule, idx: FieldHandleIndex) -> Result { let handle = &module.field_handles[idx.0 as usize]; let tok = match &module.struct_defs[handle.owner.0 as usize].field_information { StructFieldInformation::Declared(fields) => { @@ -201,14 +274,14 @@ impl<'a> SsaConverter<'a> { }, _ => bail!("field access on native/variant struct"), }; - Ok(convert_sig_token(module, &tok, self.struct_name_table)) + convert_sig_token(&tok, self.guard, self.struct_types) } fn field_inst_type( &self, module: &CompiledModule, - idx: move_binary_format::file_format::FieldInstantiationIndex, - ) -> Result { + idx: FieldInstantiationIndex, + ) -> Result { let inst = &module.field_instantiations[idx.0 as usize]; let handle = &module.field_handles[inst.handle.0 as usize]; let base_tok = match &module.struct_defs[handle.owner.0 as usize].field_information { @@ -219,14 +292,14 @@ impl<'a> SsaConverter<'a> { }; let type_params = &module.signature_at(inst.type_parameters).0; let tok = substitute_type_params(&base_tok, type_params); - Ok(convert_sig_token(module, &tok, self.struct_name_table)) + convert_sig_token(&tok, self.guard, self.struct_types) } fn variant_field_handle_type( &self, module: &CompiledModule, - idx: move_binary_format::file_format::VariantFieldHandleIndex, - ) -> Result { + idx: VariantFieldHandleIndex, + ) -> Result { let handle = &module.variant_field_handles[idx.0 as usize]; let tok = match &module.struct_defs[handle.struct_index.0 as usize].field_information { StructFieldInformation::DeclaredVariants(variants) => { @@ -237,14 +310,14 @@ impl<'a> SsaConverter<'a> { }, _ => bail!("variant field access on non-variant struct"), }; - Ok(convert_sig_token(module, &tok, self.struct_name_table)) + convert_sig_token(&tok, self.guard, self.struct_types) } fn variant_field_inst_type( &self, module: &CompiledModule, - idx: move_binary_format::file_format::VariantFieldInstantiationIndex, - ) -> Result { + idx: VariantFieldInstantiationIndex, + ) -> Result { let inst = &module.variant_field_instantiations[idx.0 as usize]; let handle = &module.variant_field_handles[inst.handle.0 as usize]; let base_tok = match &module.struct_defs[handle.struct_index.0 as usize].field_information { @@ -258,7 +331,7 @@ impl<'a> SsaConverter<'a> { }; let type_params = &module.signature_at(inst.type_parameters).0; let tok = substitute_type_params(&base_tok, type_params); - Ok(convert_sig_token(module, &tok, self.struct_name_table)) + convert_sig_token(&tok, self.guard, self.struct_types) } // -------------------------------------------------------------------------------------------- @@ -325,79 +398,79 @@ impl<'a> SsaConverter<'a> { match bc { // --- Loads --- B::LdU8(v) => { - let dst = self.alloc_vid(Type::U8)?; + let dst = self.alloc_vid(ty::U8_TY)?; self.current_block_instrs.push(Instr::LdU8(dst, *v)); self.push_slot(dst); }, B::LdU16(v) => { - let dst = self.alloc_vid(Type::U16)?; + let dst = self.alloc_vid(ty::U16_TY)?; self.current_block_instrs.push(Instr::LdU16(dst, *v)); self.push_slot(dst); }, B::LdU32(v) => { - let dst = self.alloc_vid(Type::U32)?; + let dst = self.alloc_vid(ty::U32_TY)?; self.current_block_instrs.push(Instr::LdU32(dst, *v)); self.push_slot(dst); }, B::LdU64(v) => { - let dst = self.alloc_vid(Type::U64)?; + let dst = self.alloc_vid(ty::U64_TY)?; self.current_block_instrs.push(Instr::LdU64(dst, *v)); self.push_slot(dst); }, B::LdU128(v) => { - let dst = self.alloc_vid(Type::U128)?; + let dst = self.alloc_vid(ty::U128_TY)?; self.current_block_instrs.push(Instr::LdU128(dst, *v)); self.push_slot(dst); }, B::LdU256(v) => { - let dst = self.alloc_vid(Type::U256)?; + let dst = self.alloc_vid(ty::U256_TY)?; self.current_block_instrs.push(Instr::LdU256(dst, *v)); self.push_slot(dst); }, B::LdI8(v) => { - let dst = self.alloc_vid(Type::I8)?; + let dst = self.alloc_vid(ty::I8_TY)?; self.current_block_instrs.push(Instr::LdI8(dst, *v)); self.push_slot(dst); }, B::LdI16(v) => { - let dst = self.alloc_vid(Type::I16)?; + let dst = self.alloc_vid(ty::I16_TY)?; self.current_block_instrs.push(Instr::LdI16(dst, *v)); self.push_slot(dst); }, B::LdI32(v) => { - let dst = self.alloc_vid(Type::I32)?; + let dst = self.alloc_vid(ty::I32_TY)?; self.current_block_instrs.push(Instr::LdI32(dst, *v)); self.push_slot(dst); }, B::LdI64(v) => { - let dst = self.alloc_vid(Type::I64)?; + let dst = self.alloc_vid(ty::I64_TY)?; self.current_block_instrs.push(Instr::LdI64(dst, *v)); self.push_slot(dst); }, B::LdI128(v) => { - let dst = self.alloc_vid(Type::I128)?; + let dst = self.alloc_vid(ty::I128_TY)?; self.current_block_instrs.push(Instr::LdI128(dst, *v)); self.push_slot(dst); }, B::LdI256(v) => { - let dst = self.alloc_vid(Type::I256)?; + let dst = self.alloc_vid(ty::I256_TY)?; self.current_block_instrs.push(Instr::LdI256(dst, *v)); self.push_slot(dst); }, B::LdConst(idx) => { let tok = &module.constant_pool[idx.0 as usize].type_; - let ty = convert_sig_token(module, tok, self.struct_name_table); + let ty = convert_sig_token(tok, self.guard, self.struct_types)?; let dst = self.alloc_vid(ty)?; self.current_block_instrs.push(Instr::LdConst(dst, *idx)); self.push_slot(dst); }, B::LdTrue => { - let dst = self.alloc_vid(Type::Bool)?; + let dst = self.alloc_vid(ty::BOOL_TY)?; self.current_block_instrs.push(Instr::LdTrue(dst)); self.push_slot(dst); }, B::LdFalse => { - let dst = self.alloc_vid(Type::Bool)?; + let dst = self.alloc_vid(ty::BOOL_TY)?; self.current_block_instrs.push(Instr::LdFalse(dst)); self.push_slot(dst); }, @@ -405,14 +478,14 @@ impl<'a> SsaConverter<'a> { // --- Locals --- B::CopyLoc(idx) => { let src = Slot::Home(*idx as u16); - let ty = self.local_types[*idx as usize].clone(); + let ty = self.local_types[*idx as usize]; let dst = self.alloc_vid(ty)?; self.current_block_instrs.push(Instr::Copy(dst, src)); self.push_slot(dst); }, B::MoveLoc(idx) => { let src = Slot::Home(*idx as u16); - let ty = self.local_types[*idx as usize].clone(); + let ty = self.local_types[*idx as usize]; let dst = self.alloc_vid(ty)?; self.current_block_instrs.push(Instr::Move(dst, src)); self.push_slot(dst); @@ -450,19 +523,19 @@ impl<'a> SsaConverter<'a> { B::And => self.convert_binop(BinaryOp::And, true)?, // --- Unary ops (result type specified) --- - B::CastU8 => self.convert_unop(UnaryOp::CastU8, Type::U8)?, - B::CastU16 => self.convert_unop(UnaryOp::CastU16, Type::U16)?, - B::CastU32 => self.convert_unop(UnaryOp::CastU32, Type::U32)?, - B::CastU64 => self.convert_unop(UnaryOp::CastU64, Type::U64)?, - B::CastU128 => self.convert_unop(UnaryOp::CastU128, Type::U128)?, - B::CastU256 => self.convert_unop(UnaryOp::CastU256, Type::U256)?, - B::CastI8 => self.convert_unop(UnaryOp::CastI8, Type::I8)?, - B::CastI16 => self.convert_unop(UnaryOp::CastI16, Type::I16)?, - B::CastI32 => self.convert_unop(UnaryOp::CastI32, Type::I32)?, - B::CastI64 => self.convert_unop(UnaryOp::CastI64, Type::I64)?, - B::CastI128 => self.convert_unop(UnaryOp::CastI128, Type::I128)?, - B::CastI256 => self.convert_unop(UnaryOp::CastI256, Type::I256)?, - B::Not => self.convert_unop(UnaryOp::Not, Type::Bool)?, + B::CastU8 => self.convert_unop(UnaryOp::CastU8, ty::U8_TY)?, + B::CastU16 => self.convert_unop(UnaryOp::CastU16, ty::U16_TY)?, + B::CastU32 => self.convert_unop(UnaryOp::CastU32, ty::U32_TY)?, + B::CastU64 => self.convert_unop(UnaryOp::CastU64, ty::U64_TY)?, + B::CastU128 => self.convert_unop(UnaryOp::CastU128, ty::U128_TY)?, + B::CastU256 => self.convert_unop(UnaryOp::CastU256, ty::U256_TY)?, + B::CastI8 => self.convert_unop(UnaryOp::CastI8, ty::I8_TY)?, + B::CastI16 => self.convert_unop(UnaryOp::CastI16, ty::I16_TY)?, + B::CastI32 => self.convert_unop(UnaryOp::CastI32, ty::I32_TY)?, + B::CastI64 => self.convert_unop(UnaryOp::CastI64, ty::I64_TY)?, + B::CastI128 => self.convert_unop(UnaryOp::CastI128, ty::I128_TY)?, + B::CastI256 => self.convert_unop(UnaryOp::CastI256, ty::I256_TY)?, + B::Not => self.convert_unop(UnaryOp::Not, ty::BOOL_TY)?, // --- Unary ops (result type derived from operand) --- B::Negate => { let src_ty = self.vid_type(*self.stack.last().context("stack underflow")?)?; @@ -470,11 +543,8 @@ impl<'a> SsaConverter<'a> { }, B::FreezeRef => { let src_ty = self.vid_type(*self.stack.last().context("stack underflow")?)?; - let result_ty = match src_ty { - Type::MutableReference(inner) => Type::Reference(inner), - // The bytecode verifier guarantees the operand is &mut T. - other => bail!("FreezeRef on non-mutable-reference type {:?}", other), - }; + // The bytecode verifier guarantees the operand is &mut T. + let result_ty = self.guard.convert_mut_to_immut_ref(src_ty)?; self.convert_unop(UnaryOp::FreezeRef, result_ty)?; }, @@ -482,37 +552,40 @@ impl<'a> SsaConverter<'a> { B::Pack(idx) => { let n = struct_field_count(module, *idx); let fields = self.pop_n_reverse(n)?; - let result_ty = self.struct_type(module, *idx); + let result_ty = self.struct_type(module, *idx)?; let dst = self.alloc_vid(result_ty)?; self.current_block_instrs - .push(Instr::Pack(dst, *idx, fields)); + .push(Instr::Pack(dst, result_ty, fields)); self.push_slot(dst); }, B::PackGeneric(idx) => { let inst = &module.struct_def_instantiations[idx.0 as usize]; let n = struct_field_count(module, inst.def); let fields = self.pop_n_reverse(n)?; - let result_ty = self.struct_inst_type(module, *idx); + let result_ty = self.struct_inst_type(module, *idx)?; + let (base_ty, ty_args) = self.struct_inst_parts(module, *idx)?; let dst = self.alloc_vid(result_ty)?; self.current_block_instrs - .push(Instr::PackGeneric(dst, *idx, fields)); + .push(Instr::PackGeneric(dst, base_ty, ty_args, fields)); self.push_slot(dst); }, B::Unpack(idx) => { let src = self.pop_slot()?; let n = struct_field_count(module, *idx); let ftypes = struct_field_type_toks(module, *idx); - let ftypes: Vec = convert_sig_tokens(module, &ftypes, self.struct_name_table); + let ftypes: Vec = + convert_sig_tokens(&ftypes, self.guard, self.struct_types)?; let mut dsts = Vec::with_capacity(n); for i in 0..n { let fty = ftypes .get(i) - .cloned() + .copied() .context("field type index out of bounds")?; dsts.push(self.alloc_vid(fty)?); } + let struct_ty = self.struct_type(module, *idx)?; self.current_block_instrs - .push(Instr::Unpack(dsts.clone(), *idx, src)); + .push(Instr::Unpack(dsts.clone(), struct_ty, src)); for dst in dsts { self.push_slot(dst); } @@ -527,18 +600,23 @@ impl<'a> SsaConverter<'a> { .iter() .map(|ft| substitute_type_params(ft, &type_params)) .collect(); - let ftypes: Vec = - convert_sig_tokens(module, &ftypes_tok, self.struct_name_table); + let ftypes: Vec = + convert_sig_tokens(&ftypes_tok, self.guard, self.struct_types)?; let mut dsts = Vec::with_capacity(n); for i in 0..n { let fty = ftypes .get(i) - .cloned() + .copied() .context("field type index out of bounds")?; dsts.push(self.alloc_vid(fty)?); } - self.current_block_instrs - .push(Instr::UnpackGeneric(dsts.clone(), *idx, src)); + let (base_ty, ty_args) = self.struct_inst_parts(module, *idx)?; + self.current_block_instrs.push(Instr::UnpackGeneric( + dsts.clone(), + base_ty, + ty_args, + src, + )); for dst in dsts { self.push_slot(dst); } @@ -547,12 +625,13 @@ impl<'a> SsaConverter<'a> { // --- Variant ops --- B::PackVariant(idx) => { let handle = &module.struct_variant_handles[idx.0 as usize]; - let n = variant_field_count(module, handle.struct_index, handle.variant); + let variant = handle.variant; + let n = variant_field_count(module, handle.struct_index, variant); let fields = self.pop_n_reverse(n)?; - let result_ty = self.struct_type(module, handle.struct_index); + let result_ty = self.struct_type(module, handle.struct_index)?; let dst = self.alloc_vid(result_ty)?; self.current_block_instrs - .push(Instr::PackVariant(dst, *idx, fields)); + .push(Instr::PackVariant(dst, result_ty, variant, fields)); self.push_slot(dst); }, B::PackVariantGeneric(idx) => { @@ -563,30 +642,37 @@ impl<'a> SsaConverter<'a> { let type_params = module.signature_at(inst.type_parameters).0.clone(); let def = &module.struct_defs[handle.struct_index.0 as usize]; let tok = SignatureToken::StructInstantiation(def.struct_handle, type_params); - let result_ty = convert_sig_token(module, &tok, self.struct_name_table); + let result_ty = convert_sig_token(&tok, self.guard, self.struct_types)?; + let (enum_ty, variant, ty_args) = self.variant_inst_parts(module, *idx)?; let dst = self.alloc_vid(result_ty)?; - self.current_block_instrs - .push(Instr::PackVariantGeneric(dst, *idx, fields)); + self.current_block_instrs.push(Instr::PackVariantGeneric( + dst, enum_ty, variant, ty_args, fields, + )); self.push_slot(dst); }, B::UnpackVariant(idx) => { let src = self.pop_slot()?; let handle = &module.struct_variant_handles[idx.0 as usize]; - let n = variant_field_count(module, handle.struct_index, handle.variant); - let ftypes_tok = - variant_field_type_toks(module, handle.struct_index, handle.variant); - let ftypes: Vec = - convert_sig_tokens(module, &ftypes_tok, self.struct_name_table); + let variant = handle.variant; + let n = variant_field_count(module, handle.struct_index, variant); + let ftypes_tok = variant_field_type_toks(module, handle.struct_index, variant); + let ftypes: Vec = + convert_sig_tokens(&ftypes_tok, self.guard, self.struct_types)?; let mut dsts = Vec::with_capacity(n); for i in 0..n { let fty = ftypes .get(i) - .cloned() + .copied() .context("field type index out of bounds")?; dsts.push(self.alloc_vid(fty)?); } - self.current_block_instrs - .push(Instr::UnpackVariant(dsts.clone(), *idx, src)); + let enum_ty = self.struct_type(module, handle.struct_index)?; + self.current_block_instrs.push(Instr::UnpackVariant( + dsts.clone(), + enum_ty, + variant, + src, + )); for dst in dsts { self.push_slot(dst); } @@ -603,19 +689,22 @@ impl<'a> SsaConverter<'a> { .iter() .map(|ft| substitute_type_params(ft, &type_params)) .collect(); - let ftypes: Vec = - convert_sig_tokens(module, &ftypes_tok, self.struct_name_table); + let ftypes: Vec = + convert_sig_tokens(&ftypes_tok, self.guard, self.struct_types)?; let mut dsts = Vec::with_capacity(n); for i in 0..n { let fty = ftypes .get(i) - .cloned() + .copied() .context("field type index out of bounds")?; dsts.push(self.alloc_vid(fty)?); } + let (enum_ty, variant, ty_args) = self.variant_inst_parts(module, *idx)?; self.current_block_instrs.push(Instr::UnpackVariantGeneric( dsts.clone(), - *idx, + enum_ty, + variant, + ty_args, src, )); for dst in dsts { @@ -624,24 +713,29 @@ impl<'a> SsaConverter<'a> { }, B::TestVariant(idx) => { let src = self.pop_slot()?; - let dst = self.alloc_vid(Type::Bool)?; + let handle = &module.struct_variant_handles[idx.0 as usize]; + let variant = handle.variant; + let enum_ty = self.struct_type(module, handle.struct_index)?; + let dst = self.alloc_vid(ty::BOOL_TY)?; self.current_block_instrs - .push(Instr::TestVariant(dst, *idx, src)); + .push(Instr::TestVariant(dst, enum_ty, variant, src)); self.push_slot(dst); }, B::TestVariantGeneric(idx) => { let src = self.pop_slot()?; - let dst = self.alloc_vid(Type::Bool)?; - self.current_block_instrs - .push(Instr::TestVariantGeneric(dst, *idx, src)); + let (enum_ty, variant, ty_args) = self.variant_inst_parts(module, *idx)?; + let dst = self.alloc_vid(ty::BOOL_TY)?; + self.current_block_instrs.push(Instr::TestVariantGeneric( + dst, enum_ty, variant, ty_args, src, + )); self.push_slot(dst); }, // --- References --- B::ImmBorrowLoc(idx) => { let src = Slot::Home(*idx as u16); - let inner = self.local_types[*idx as usize].clone(); - let ty = Type::Reference(Box::new(inner)); + let inner = self.local_types[*idx as usize]; + let ty = self.guard.intern_immut_ref(inner); let dst = self.alloc_vid(ty)?; self.current_block_instrs .push(Instr::ImmBorrowLoc(dst, src)); @@ -649,8 +743,8 @@ impl<'a> SsaConverter<'a> { }, B::MutBorrowLoc(idx) => { let src = Slot::Home(*idx as u16); - let inner = self.local_types[*idx as usize].clone(); - let ty = Type::MutableReference(Box::new(inner)); + let inner = self.local_types[*idx as usize]; + let ty = self.guard.intern_mut_ref(inner); let dst = self.alloc_vid(ty)?; self.current_block_instrs .push(Instr::MutBorrowLoc(dst, src)); @@ -659,7 +753,7 @@ impl<'a> SsaConverter<'a> { B::ImmBorrowField(idx) => { let src = self.pop_slot()?; let fty = self.field_type(module, *idx)?; - let ty = Type::Reference(Box::new(fty)); + let ty = self.guard.intern_immut_ref(fty); let dst = self.alloc_vid(ty)?; self.current_block_instrs .push(Instr::ImmBorrowField(dst, *idx, src)); @@ -668,7 +762,7 @@ impl<'a> SsaConverter<'a> { B::MutBorrowField(idx) => { let src = self.pop_slot()?; let fty = self.field_type(module, *idx)?; - let ty = Type::MutableReference(Box::new(fty)); + let ty = self.guard.intern_mut_ref(fty); let dst = self.alloc_vid(ty)?; self.current_block_instrs .push(Instr::MutBorrowField(dst, *idx, src)); @@ -677,7 +771,7 @@ impl<'a> SsaConverter<'a> { B::ImmBorrowFieldGeneric(idx) => { let src = self.pop_slot()?; let fty = self.field_inst_type(module, *idx)?; - let ty = Type::Reference(Box::new(fty)); + let ty = self.guard.intern_immut_ref(fty); let dst = self.alloc_vid(ty)?; self.current_block_instrs .push(Instr::ImmBorrowFieldGeneric(dst, *idx, src)); @@ -686,7 +780,7 @@ impl<'a> SsaConverter<'a> { B::MutBorrowFieldGeneric(idx) => { let src = self.pop_slot()?; let fty = self.field_inst_type(module, *idx)?; - let ty = Type::MutableReference(Box::new(fty)); + let ty = self.guard.intern_mut_ref(fty); let dst = self.alloc_vid(ty)?; self.current_block_instrs .push(Instr::MutBorrowFieldGeneric(dst, *idx, src)); @@ -695,7 +789,7 @@ impl<'a> SsaConverter<'a> { B::ImmBorrowVariantField(idx) => { let src = self.pop_slot()?; let fty = self.variant_field_handle_type(module, *idx)?; - let ty = Type::Reference(Box::new(fty)); + let ty = self.guard.intern_immut_ref(fty); let dst = self.alloc_vid(ty)?; self.current_block_instrs .push(Instr::ImmBorrowVariantField(dst, *idx, src)); @@ -704,7 +798,7 @@ impl<'a> SsaConverter<'a> { B::MutBorrowVariantField(idx) => { let src = self.pop_slot()?; let fty = self.variant_field_handle_type(module, *idx)?; - let ty = Type::MutableReference(Box::new(fty)); + let ty = self.guard.intern_mut_ref(fty); let dst = self.alloc_vid(ty)?; self.current_block_instrs .push(Instr::MutBorrowVariantField(dst, *idx, src)); @@ -713,7 +807,7 @@ impl<'a> SsaConverter<'a> { B::ImmBorrowVariantFieldGeneric(idx) => { let src = self.pop_slot()?; let fty = self.variant_field_inst_type(module, *idx)?; - let ty = Type::Reference(Box::new(fty)); + let ty = self.guard.intern_immut_ref(fty); let dst = self.alloc_vid(ty)?; self.current_block_instrs .push(Instr::ImmBorrowVariantFieldGeneric(dst, *idx, src)); @@ -722,7 +816,7 @@ impl<'a> SsaConverter<'a> { B::MutBorrowVariantFieldGeneric(idx) => { let src = self.pop_slot()?; let fty = self.variant_field_inst_type(module, *idx)?; - let ty = Type::MutableReference(Box::new(fty)); + let ty = self.guard.intern_mut_ref(fty); let dst = self.alloc_vid(ty)?; self.current_block_instrs .push(Instr::MutBorrowVariantFieldGeneric(dst, *idx, src)); @@ -730,10 +824,9 @@ impl<'a> SsaConverter<'a> { }, B::ReadRef => { let src = self.pop_slot()?; - let ty = match self.vid_type(src)? { - Type::Reference(inner) | Type::MutableReference(inner) => *inner, - other => bail!("ReadRef on non-reference type {:?}", other), - }; + let src_ty = self.vid_type(src)?; + // The bytecode verifier guarantees the operand is `&T` or `&mut T`. + let ty = self.guard.strip_ref(src_ty)?; let dst = self.alloc_vid(ty)?; self.current_block_instrs.push(Instr::ReadRef(dst, src)); self.push_slot(dst); @@ -747,76 +840,87 @@ impl<'a> SsaConverter<'a> { // --- Globals --- B::Exists(idx) => { let addr = self.pop_slot()?; - let dst = self.alloc_vid(Type::Bool)?; + let struct_ty = self.struct_type(module, *idx)?; + let dst = self.alloc_vid(ty::BOOL_TY)?; self.current_block_instrs - .push(Instr::Exists(dst, *idx, addr)); + .push(Instr::Exists(dst, struct_ty, addr)); self.push_slot(dst); }, B::ExistsGeneric(idx) => { let addr = self.pop_slot()?; - let dst = self.alloc_vid(Type::Bool)?; + let (base_ty, ty_args) = self.struct_inst_parts(module, *idx)?; + let dst = self.alloc_vid(ty::BOOL_TY)?; self.current_block_instrs - .push(Instr::ExistsGeneric(dst, *idx, addr)); + .push(Instr::ExistsGeneric(dst, base_ty, ty_args, addr)); self.push_slot(dst); }, B::MoveFrom(idx) => { let addr = self.pop_slot()?; - let ty = self.struct_type(module, *idx); + let ty = self.struct_type(module, *idx)?; let dst = self.alloc_vid(ty)?; self.current_block_instrs - .push(Instr::MoveFrom(dst, *idx, addr)); + .push(Instr::MoveFrom(dst, ty, addr)); self.push_slot(dst); }, B::MoveFromGeneric(idx) => { let addr = self.pop_slot()?; - let ty = self.struct_inst_type(module, *idx); + let ty = self.struct_inst_type(module, *idx)?; + let (base_ty, ty_args) = self.struct_inst_parts(module, *idx)?; let dst = self.alloc_vid(ty)?; self.current_block_instrs - .push(Instr::MoveFromGeneric(dst, *idx, addr)); + .push(Instr::MoveFromGeneric(dst, base_ty, ty_args, addr)); self.push_slot(dst); }, B::MoveTo(idx) => { let val = self.pop_slot()?; let signer = self.pop_slot()?; + let struct_ty = self.struct_type(module, *idx)?; self.current_block_instrs - .push(Instr::MoveTo(*idx, signer, val)); + .push(Instr::MoveTo(struct_ty, signer, val)); }, B::MoveToGeneric(idx) => { let val = self.pop_slot()?; let signer = self.pop_slot()?; + let (base_ty, ty_args) = self.struct_inst_parts(module, *idx)?; self.current_block_instrs - .push(Instr::MoveToGeneric(*idx, signer, val)); + .push(Instr::MoveToGeneric(base_ty, ty_args, signer, val)); }, B::ImmBorrowGlobal(idx) => { let addr = self.pop_slot()?; - let ty = Type::Reference(Box::new(self.struct_type(module, *idx))); + let inner = self.struct_type(module, *idx)?; + let ty = self.guard.intern_immut_ref(inner); let dst = self.alloc_vid(ty)?; self.current_block_instrs - .push(Instr::ImmBorrowGlobal(dst, *idx, addr)); + .push(Instr::ImmBorrowGlobal(dst, inner, addr)); self.push_slot(dst); }, B::ImmBorrowGlobalGeneric(idx) => { let addr = self.pop_slot()?; - let ty = Type::Reference(Box::new(self.struct_inst_type(module, *idx))); + let inner = self.struct_inst_type(module, *idx)?; + let ty = self.guard.intern_immut_ref(inner); + let (base_ty, ty_args) = self.struct_inst_parts(module, *idx)?; let dst = self.alloc_vid(ty)?; self.current_block_instrs - .push(Instr::ImmBorrowGlobalGeneric(dst, *idx, addr)); + .push(Instr::ImmBorrowGlobalGeneric(dst, base_ty, ty_args, addr)); self.push_slot(dst); }, B::MutBorrowGlobal(idx) => { let addr = self.pop_slot()?; - let ty = Type::MutableReference(Box::new(self.struct_type(module, *idx))); + let inner = self.struct_type(module, *idx)?; + let ty = self.guard.intern_mut_ref(inner); let dst = self.alloc_vid(ty)?; self.current_block_instrs - .push(Instr::MutBorrowGlobal(dst, *idx, addr)); + .push(Instr::MutBorrowGlobal(dst, inner, addr)); self.push_slot(dst); }, B::MutBorrowGlobalGeneric(idx) => { let addr = self.pop_slot()?; - let ty = Type::MutableReference(Box::new(self.struct_inst_type(module, *idx))); + let inner = self.struct_inst_type(module, *idx)?; + let ty = self.guard.intern_mut_ref(inner); + let (base_ty, ty_args) = self.struct_inst_parts(module, *idx)?; let dst = self.alloc_vid(ty)?; self.current_block_instrs - .push(Instr::MutBorrowGlobalGeneric(dst, *idx, addr)); + .push(Instr::MutBorrowGlobalGeneric(dst, base_ty, ty_args, addr)); self.push_slot(dst); }, @@ -825,7 +929,7 @@ impl<'a> SsaConverter<'a> { let handle = module.function_handle_at(*idx); let num_args = module.signature_at(handle.parameters).0.len(); let ret_toks = &module.signature_at(handle.return_).0; - let ret_types = convert_sig_tokens(module, ret_toks, self.struct_name_table); + let ret_types = convert_sig_tokens(ret_toks, self.guard, self.struct_types)?; let args = self.pop_n_reverse(num_args)?; let mut rets = Vec::with_capacity(ret_types.len()); for rty in ret_types { @@ -847,7 +951,7 @@ impl<'a> SsaConverter<'a> { .iter() .map(|t| substitute_type_params(t, &type_params)) .collect(); - let ret_types = convert_sig_tokens(module, &ret_toks, self.struct_name_table); + let ret_types = convert_sig_tokens(&ret_toks, self.guard, self.struct_types)?; let args = self.pop_n_reverse(num_args)?; let mut rets = Vec::with_capacity(ret_types.len()); for rty in ret_types { @@ -872,7 +976,7 @@ impl<'a> SsaConverter<'a> { returns.clone(), move_core_types::ability::AbilitySet::EMPTY, ); - let ty = convert_sig_token(module, &tok, self.struct_name_table); + let ty = convert_sig_token(&tok, self.guard, self.struct_types)?; let dst = self.alloc_vid(ty)?; self.current_block_instrs .push(Instr::PackClosure(dst, *fhi, *mask, captured)); @@ -890,7 +994,7 @@ impl<'a> SsaConverter<'a> { returns.clone(), move_core_types::ability::AbilitySet::EMPTY, ); - let ty = convert_sig_token(module, &tok, self.struct_name_table); + let ty = convert_sig_token(&tok, self.guard, self.struct_types)?; let dst = self.alloc_vid(ty)?; self.current_block_instrs .push(Instr::PackClosureGeneric(dst, *fii, *mask, captured)); @@ -904,17 +1008,19 @@ impl<'a> SsaConverter<'a> { } else { bail!("CallClosure signature must start with a Function token") }; - let ret_types = convert_sig_tokens(module, &ret_toks, self.struct_name_table); + let ret_types = convert_sig_tokens(&ret_toks, self.guard, self.struct_types)?; let closure = self.pop_slot()?; let mut all_args = self.pop_n_reverse(num_args)?; all_args.push(closure); let mut rets = Vec::with_capacity(ret_types.len()); - for rty in ret_types { - rets.push(self.alloc_vid(rty)?); + for rty in &ret_types { + rets.push(self.alloc_vid(*rty)?); } + let sig_types_vec = convert_sig_tokens(&sig.0, self.guard, self.struct_types)?; + let signature_types = self.guard.intern_type_list(&sig_types_vec); self.current_block_instrs.push(Instr::CallClosure( rets.clone(), - *sig_idx, + signature_types, all_args, )); for r in rets { @@ -927,72 +1033,72 @@ impl<'a> SsaConverter<'a> { let count = *count as u16; let elems = self.pop_n_reverse(count as usize)?; let elem_tok = &module.signature_at(*sig_idx).0[0]; - let elem_ty = convert_sig_token(module, elem_tok, self.struct_name_table); - let ty = Type::Vector(triomphe::Arc::new(elem_ty)); + let elem_ty = convert_sig_token(elem_tok, self.guard, self.struct_types)?; + let ty = self.guard.intern_vector(elem_ty); let dst = self.alloc_vid(ty)?; self.current_block_instrs - .push(Instr::VecPack(dst, *sig_idx, count, elems)); + .push(Instr::VecPack(dst, elem_ty, count, elems)); self.push_slot(dst); }, B::VecLen(sig_idx) => { let vec_ref = self.pop_slot()?; - let dst = self.alloc_vid(Type::U64)?; + let elem_tok = &module.signature_at(*sig_idx).0[0]; + let elem_ty = convert_sig_token(elem_tok, self.guard, self.struct_types)?; + let dst = self.alloc_vid(ty::U64_TY)?; self.current_block_instrs - .push(Instr::VecLen(dst, *sig_idx, vec_ref)); + .push(Instr::VecLen(dst, elem_ty, vec_ref)); self.push_slot(dst); }, B::VecImmBorrow(sig_idx) => { let idx_r = self.pop_slot()?; let vec_ref = self.pop_slot()?; let elem_tok = &module.signature_at(*sig_idx).0[0]; - let elem_ty = convert_sig_token(module, elem_tok, self.struct_name_table); - let ty = Type::Reference(Box::new(elem_ty)); + let elem_ty = convert_sig_token(elem_tok, self.guard, self.struct_types)?; + let ty = self.guard.intern_immut_ref(elem_ty); let dst = self.alloc_vid(ty)?; self.current_block_instrs - .push(Instr::VecImmBorrow(dst, *sig_idx, vec_ref, idx_r)); + .push(Instr::VecImmBorrow(dst, elem_ty, vec_ref, idx_r)); self.push_slot(dst); }, B::VecMutBorrow(sig_idx) => { let idx_r = self.pop_slot()?; let vec_ref = self.pop_slot()?; let elem_tok = &module.signature_at(*sig_idx).0[0]; - let elem_ty = convert_sig_token(module, elem_tok, self.struct_name_table); - let ty = Type::MutableReference(Box::new(elem_ty)); + let elem_ty = convert_sig_token(elem_tok, self.guard, self.struct_types)?; + let ty = self.guard.intern_mut_ref(elem_ty); let dst = self.alloc_vid(ty)?; self.current_block_instrs - .push(Instr::VecMutBorrow(dst, *sig_idx, vec_ref, idx_r)); + .push(Instr::VecMutBorrow(dst, elem_ty, vec_ref, idx_r)); self.push_slot(dst); }, B::VecPushBack(sig_idx) => { let val = self.pop_slot()?; let vec_ref = self.pop_slot()?; + let elem_tok = &module.signature_at(*sig_idx).0[0]; + let elem_ty = convert_sig_token(elem_tok, self.guard, self.struct_types)?; self.current_block_instrs - .push(Instr::VecPushBack(*sig_idx, vec_ref, val)); + .push(Instr::VecPushBack(elem_ty, vec_ref, val)); }, B::VecPopBack(sig_idx) => { let vec_ref = self.pop_slot()?; let elem_tok = &module.signature_at(*sig_idx).0[0]; - let ty = convert_sig_token(module, elem_tok, self.struct_name_table); - let dst = self.alloc_vid(ty)?; + let elem_ty = convert_sig_token(elem_tok, self.guard, self.struct_types)?; + let dst = self.alloc_vid(elem_ty)?; self.current_block_instrs - .push(Instr::VecPopBack(dst, *sig_idx, vec_ref)); + .push(Instr::VecPopBack(dst, elem_ty, vec_ref)); self.push_slot(dst); }, B::VecUnpack(sig_idx, count) => { let count = *count as u16; let src = self.pop_slot()?; let elem_tok = &module.signature_at(*sig_idx).0[0]; - let elem_ty = convert_sig_token(module, elem_tok, self.struct_name_table); + let elem_ty = convert_sig_token(elem_tok, self.guard, self.struct_types)?; let mut dsts = Vec::with_capacity(count as usize); for _ in 0..count { - dsts.push(self.alloc_vid(elem_ty.clone())?); + dsts.push(self.alloc_vid(elem_ty)?); } - self.current_block_instrs.push(Instr::VecUnpack( - dsts.clone(), - *sig_idx, - count, - src, - )); + self.current_block_instrs + .push(Instr::VecUnpack(dsts.clone(), elem_ty, count, src)); for dst in dsts { self.push_slot(dst); } @@ -1001,8 +1107,10 @@ impl<'a> SsaConverter<'a> { let j = self.pop_slot()?; let i = self.pop_slot()?; let vec_ref = self.pop_slot()?; + let elem_tok = &module.signature_at(*sig_idx).0[0]; + let elem_ty = convert_sig_token(elem_tok, self.guard, self.struct_types)?; self.current_block_instrs - .push(Instr::VecSwap(*sig_idx, vec_ref, i, j)); + .push(Instr::VecSwap(elem_ty, vec_ref, i, j)); }, // --- Control flow --- @@ -1043,7 +1151,7 @@ impl<'a> SsaConverter<'a> { let rhs = self.pop_slot()?; let lhs = self.pop_slot()?; let result_ty = if result_is_bool { - Type::Bool + ty::BOOL_TY } else { self.vid_type(lhs)? }; @@ -1054,7 +1162,7 @@ impl<'a> SsaConverter<'a> { Ok(()) } - fn convert_unop(&mut self, op: UnaryOp, result_ty: Type) -> Result<()> { + fn convert_unop(&mut self, op: UnaryOp, result_ty: InternedType) -> Result<()> { let src = self.pop_slot()?; let dst = self.alloc_vid(result_ty)?; self.current_block_instrs.push(Instr::UnaryOp(dst, op, src)); 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 f27e119596f..d1d76752059 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 @@ -10,16 +10,16 @@ use super::instr_utils::{extract_imm_value, is_commutative}; use crate::stackless_exec_ir::{BasicBlock, BinaryOp, Instr}; -use move_vm_types::loaded_data::runtime_types::Type; +use mono_move_core::types::InternedType; /// Intermediate SSA representation of a single function, before slot allocation. pub(crate) struct SSAFunction { /// Basic blocks in SSA form. pub blocks: Vec, /// Type of each value ID, indexed directly by the value ID number. - pub vid_types: Vec, + pub vid_types: Vec, /// Types of all locals (params ++ declared locals). - pub local_types: Vec, + pub local_types: Vec, } impl SSAFunction { diff --git a/third_party/move/mono-move/specializer/src/destack/translate.rs b/third_party/move/mono-move/specializer/src/destack/translate.rs index 330d28f235a..efee7ff9c37 100644 --- a/third_party/move/mono-move/specializer/src/destack/translate.rs +++ b/third_party/move/mono-move/specializer/src/destack/translate.rs @@ -3,11 +3,15 @@ //! Conversion pipeline: Bytecode → SSA → instruction fusion → slot allocation. -use super::{ssa_conversion::SsaConverter, type_conversion::convert_sig_tokens}; -use crate::stackless_exec_ir::{FunctionIR, ModuleIR}; +use super::{ + ssa_conversion::SsaConverter, + type_conversion::{convert_sig_token, convert_sig_tokens}, +}; +use crate::stackless_exec_ir::{FuncSignature, FunctionIR, ModuleIR}; use anyhow::Result; -use move_binary_format::{access::ModuleAccess, file_format::SignatureToken, CompiledModule}; -use move_vm_types::loaded_data::struct_name_indexing::StructNameIndex; +use mono_move_core::types::InternedType; +use mono_move_global_context::ExecutionGuard; +use move_binary_format::{access::ModuleAccess, CompiledModule}; /// Convert an entire compiled module to stackless IR. /// @@ -39,7 +43,8 @@ use move_vm_types::loaded_data::struct_name_indexing::StructNameIndex; /// truly hold dead values, so type-keyed slot recycling is sound. pub fn translate_module( module: CompiledModule, - struct_name_table: &[StructNameIndex], + guard: &ExecutionGuard<'_>, + struct_types: &[Option], ) -> Result { let functions = module .function_defs @@ -55,17 +60,14 @@ pub fn translate_module( let local_sig_toks = &module.signature_at(code.locals).0; let num_params = param_sig_toks.len() as u16; let num_locals = local_sig_toks.len() as u16; - let all_sig_toks: Vec = param_sig_toks + let local_types: Vec = param_sig_toks .iter() .chain(local_sig_toks.iter()) - .cloned() - .collect(); - // [TODO]: we currently convert signature tokens into the runtime type representation, but - // this will change to use more efficient cached type representations. - let local_types = convert_sig_tokens(&module, &all_sig_toks, struct_name_table); + .map(|tok| convert_sig_token(tok, guard, struct_types)) + .collect::>()?; // Pass: Bytecode -> Intra-Block SSA -> Fusion - let converter = SsaConverter::new(local_types, struct_name_table); + let converter = SsaConverter::new(local_types, guard, struct_types); let ssa = converter .convert_function(&module, &code.code)? .with_fusion_passes(); @@ -86,5 +88,75 @@ pub fn translate_module( }) .collect::>>()?; - Ok(ModuleIR { module, functions }) + // Module-level signature caches. The lowering pass reads these directly + // instead of re-walking signature tokens per call site. + let handle_signatures = collect_handle_signatures(&module, guard, struct_types)?; + let instantiation_signatures = collect_instantiation_signatures(&module, guard, struct_types)?; + + Ok(ModuleIR { + module, + functions, + handle_signatures, + instantiation_signatures, + }) +} + +/// Pre-computes `FuncSignature` for every function handle in the module. +/// +/// TODO: convert the module's signature pool once and look up signatures by +/// index here instead of re-walking the same `SignatureToken` slices per +/// handle/instantiation. Handles that share a signature index currently hit the +/// interner repeatedly, which is both wasted work; a one-pass pool conversion +/// collapses this to a single interning per unique signature. +fn collect_handle_signatures( + module: &CompiledModule, + guard: &ExecutionGuard<'_>, + struct_types: &[Option], +) -> Result> { + module + .function_handles + .iter() + .map(|handle| { + let param_types = convert_sig_tokens( + &module.signature_at(handle.parameters).0, + guard, + struct_types, + )?; + let ret_types = + convert_sig_tokens(&module.signature_at(handle.return_).0, guard, struct_types)?; + Ok(FuncSignature { + param_types, + ret_types, + }) + }) + .collect() +} + +/// Pre-computes `FuncSignature` for every function instantiation in the module. +/// Today this mirrors the base handle's signature; future work (generic +/// instantiation substitution) will replace each entry with the concrete +/// substituted signature. +fn collect_instantiation_signatures( + module: &CompiledModule, + guard: &ExecutionGuard<'_>, + struct_types: &[Option], +) -> Result> { + module + .function_instantiations + .iter() + .map(|inst| { + let handle = module.function_handle_at(inst.handle); + let param_types = convert_sig_tokens( + &module.signature_at(handle.parameters).0, + guard, + struct_types, + )?; + let ret_types = + convert_sig_tokens(&module.signature_at(handle.return_).0, guard, struct_types)?; + Ok(FuncSignature { + param_types, + ret_types, + }) + }) + .collect() } diff --git a/third_party/move/mono-move/specializer/src/destack/type_conversion.rs b/third_party/move/mono-move/specializer/src/destack/type_conversion.rs index cba4097d3fb..464a83a75e8 100644 --- a/third_party/move/mono-move/specializer/src/destack/type_conversion.rs +++ b/third_party/move/mono-move/specializer/src/destack/type_conversion.rs @@ -1,119 +1,76 @@ // 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 -//! NOTE: This is a temporary placeholder until we have the new cached runtime type representation. +//! Conversion from [`SignatureToken`] to [`InternedType`] via the [`ExecutionGuard`]. //! -//! Local conversion from `SignatureToken` to the runtime `Type` enum. -//! -//! This replicates the logic from `move-vm-runtime/src/loader/type_loader.rs` -//! to avoid a dependency on `move-vm-runtime`. +//! Delegates to [`mono_move_global_context::walk_sig_token`], supplying a +//! [`StructResolver`] that looks up entries in a pre-built table. -use move_binary_format::{ - binary_views::BinaryIndexedView, file_format::SignatureToken, CompiledModule, -}; -use move_vm_types::loaded_data::{ - runtime_types::{AbilityInfo, Type}, - struct_name_indexing::StructNameIndex, -}; -use triomphe::Arc as TriompheArc; +use anyhow::{bail, Result}; +use mono_move_core::types::InternedType; +use mono_move_global_context::{walk_sig_token, ExecutionGuard, StructResolver}; +use move_binary_format::file_format::{SignatureToken, StructHandleIndex}; -/// Convert a single `SignatureToken` to a runtime `Type`. +/// Convert a single [`SignatureToken`] to [`InternedType`]. /// -/// `struct_name_table` maps `StructHandleIndex` ordinals to globally unique -/// `StructNameIndex` values. Type parameters become `TyParam(u16)`. +/// `struct_types` maps [`StructHandleIndex`] ordinals to pre-resolved +/// interned type pointers; unresolved entries are `None`. pub(crate) fn convert_sig_token( - module: &CompiledModule, tok: &SignatureToken, - struct_name_table: &[StructNameIndex], -) -> Type { - let view = BinaryIndexedView::Module(module); - convert_impl(&view, tok, struct_name_table) + guard: &ExecutionGuard<'_>, + struct_types: &[Option], +) -> Result { + let mut resolver = TableResolver { struct_types }; + walk_sig_token(tok, guard, &mut resolver) } -/// Convert a slice of `SignatureToken`s to `Vec`. +/// Convert a slice of [`SignatureToken`]s to [`Vec`]<[`InternedType`]>. pub(crate) fn convert_sig_tokens( - module: &CompiledModule, toks: &[SignatureToken], - struct_name_table: &[StructNameIndex], -) -> Vec { + guard: &ExecutionGuard<'_>, + struct_types: &[Option], +) -> Result> { toks.iter() - .map(|t| convert_sig_token(module, t, struct_name_table)) + .map(|t| convert_sig_token(t, guard, struct_types)) .collect() } -fn convert_impl( - view: &BinaryIndexedView, - tok: &SignatureToken, - struct_name_table: &[StructNameIndex], -) -> Type { - match tok { - SignatureToken::Bool => Type::Bool, - SignatureToken::U8 => Type::U8, - SignatureToken::U16 => Type::U16, - SignatureToken::U32 => Type::U32, - SignatureToken::U64 => Type::U64, - SignatureToken::U128 => Type::U128, - SignatureToken::U256 => Type::U256, - SignatureToken::I8 => Type::I8, - SignatureToken::I16 => Type::I16, - SignatureToken::I32 => Type::I32, - SignatureToken::I64 => Type::I64, - SignatureToken::I128 => Type::I128, - SignatureToken::I256 => Type::I256, - SignatureToken::Address => Type::Address, - SignatureToken::Signer => Type::Signer, - SignatureToken::TypeParameter(idx) => Type::TyParam(*idx), - SignatureToken::Vector(inner) => Type::Vector(TriompheArc::new(convert_impl( - view, - inner, - struct_name_table, - ))), - SignatureToken::Reference(inner) => { - Type::Reference(Box::new(convert_impl(view, inner, struct_name_table))) - }, - SignatureToken::MutableReference(inner) => { - Type::MutableReference(Box::new(convert_impl(view, inner, struct_name_table))) - }, - SignatureToken::Struct(sh_idx) => { - let struct_handle = view.struct_handle_at(*sh_idx); - Type::Struct { - idx: struct_name_table[sh_idx.0 as usize], - ability: AbilityInfo::struct_(struct_handle.abilities), - } - }, - SignatureToken::StructInstantiation(sh_idx, tys) => { - let type_args: Vec = tys - .iter() - .map(|t| convert_impl(view, t, struct_name_table)) - .collect(); - let struct_handle = view.struct_handle_at(*sh_idx); - Type::StructInstantiation { - idx: struct_name_table[sh_idx.0 as usize], - ty_args: TriompheArc::new(type_args), - ability: AbilityInfo::generic_struct( - struct_handle.abilities, - struct_handle - .type_parameters - .iter() - .map(|ty| ty.is_phantom) - .collect(), - ), - } - }, - SignatureToken::Function(args, results, abilities) => { - let args: Vec = args - .iter() - .map(|t| convert_impl(view, t, struct_name_table)) - .collect(); - let results: Vec = results - .iter() - .map(|t| convert_impl(view, t, struct_name_table)) - .collect(); - Type::Function { - args, - results, - abilities: *abilities, - } - }, +/// Resolves struct handles via direct table lookup. The table is indexed by +/// [`StructHandleIndex`] ordinal; `None` entries denote handles the +/// orchestrator could not resolve (imported from another module, or +/// generic). +struct TableResolver<'a> { + struct_types: &'a [Option], +} + +impl StructResolver for TableResolver<'_> { + fn resolve_struct( + &mut self, + struct_handle: StructHandleIndex, + _ty_args: &[SignatureToken], + ) -> Result { + // TODO: resolve cross-module struct references and intern generic + // instantiations properly. Today the orchestrator only populates + // entries for locally-defined, non-generic structs/enums; everything + // else is `None`. We also return the base struct type for both + // `SignatureToken::Struct` and `SignatureToken::StructInstantiation` + // because instantiation interning is not yet implemented. + // + // Root cause: the orchestrator's struct-type table is built by the + // layout pass, which can only handle fully-concrete, locally-defined + // structs. Splitting *interning* from *layout computation* into two + // phases would let us intern every type (local, cross-module, and + // generic) up front and compute layouts lazily when the type becomes + // fully concrete. That would also remove the `Option` + // shape of `struct_types` and simplify this resolver. + match self.struct_types.get(struct_handle.0 as usize) { + Some(Some(ty)) => Ok(*ty), + Some(None) => bail!( + "unresolved struct handle {}: cross-module or generic struct references are \ + not yet supported by the orchestrator's struct_type_table", + struct_handle.0 + ), + None => bail!("struct handle index {} out of bounds", struct_handle.0), + } } } 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 d771707c520..e22d35bc52a 100644 --- a/third_party/move/mono-move/specializer/src/lower/context.rs +++ b/third_party/move/mono-move/specializer/src/lower/context.rs @@ -6,95 +6,33 @@ //! Builds frame layout information (slot offsets/sizes) needed by the lowerer. //! All lookups are O(1) via indexed Vecs — no maps. -use crate::stackless_exec_ir::{FunctionIR, Instr}; +use crate::stackless_exec_ir::{FunctionIR, Instr, ModuleIR}; use anyhow::Result; -use mono_move_core::FRAME_METADATA_SIZE; -use move_binary_format::{access::ModuleAccess, file_format::SignatureToken, CompiledModule}; -use move_vm_types::loaded_data::runtime_types::Type; - -/// Returns the byte size of a concrete type, or None if the type is -/// not concrete (e.g., contains type parameters) or not yet handled. -pub fn type_size(ty: &Type) -> Option { - match ty { - Type::Bool - | Type::U8 - | Type::I8 - | Type::U16 - | Type::I16 - | Type::U32 - | Type::I32 - | Type::U64 - | Type::I64 - | Type::Address - | Type::Signer => Some(8), - Type::U128 | Type::I128 => Some(16), - Type::U256 | Type::I256 => Some(32), - Type::Vector(_) => Some(8), - Type::Reference(_) | Type::MutableReference(_) => Some(16), - Type::TyParam(_) => None, - Type::Struct { .. } => None, - Type::StructInstantiation { .. } => None, - Type::Function { .. } => None, - } -} - -fn sig_token_size(tok: &SignatureToken) -> Option { - match tok { - SignatureToken::Bool - | SignatureToken::U8 - | SignatureToken::I8 - | SignatureToken::U16 - | SignatureToken::I16 - | SignatureToken::U32 - | SignatureToken::I32 - | SignatureToken::U64 - | SignatureToken::I64 - | SignatureToken::Address - | SignatureToken::Signer => Some(8), - SignatureToken::U128 | SignatureToken::I128 => Some(16), - SignatureToken::U256 | SignatureToken::I256 => Some(32), - SignatureToken::Vector(_) => Some(8), - SignatureToken::Reference(_) | SignatureToken::MutableReference(_) => Some(16), - SignatureToken::TypeParameter(_) => None, - SignatureToken::Struct(_) => None, - SignatureToken::StructInstantiation(_, _) => None, - SignatureToken::Function(_, _, _) => None, - } -} - -fn sig_token_to_type(tok: &SignatureToken) -> Option { - match tok { - SignatureToken::Bool => Some(Type::Bool), - SignatureToken::U8 => Some(Type::U8), - SignatureToken::I8 => Some(Type::I8), - SignatureToken::U16 => Some(Type::U16), - SignatureToken::I16 => Some(Type::I16), - SignatureToken::U32 => Some(Type::U32), - SignatureToken::I32 => Some(Type::I32), - SignatureToken::U64 => Some(Type::U64), - SignatureToken::I64 => Some(Type::I64), - SignatureToken::U128 => Some(Type::U128), - SignatureToken::I128 => Some(Type::I128), - SignatureToken::U256 => Some(Type::U256), - SignatureToken::I256 => Some(Type::I256), - SignatureToken::Address => Some(Type::Address), - SignatureToken::Signer => Some(Type::Signer), - _ => None, - } +use mono_move_core::{ + types::{align_up, view_type, Alignment, InternedType, Size}, + FRAME_METADATA_SIZE, +}; + +/// Returns the (size, alignment) of a concrete interned type, or None if the +/// type is not concrete (e.g., contains type parameters or unresolved structs). +pub fn type_size_and_align(ty: InternedType) -> Option<(Size, Alignment)> { + view_type(ty).size_and_align() } #[derive(Clone, Copy, Debug)] pub struct SlotInfo { pub offset: u32, pub size: u32, + /// Byte alignment required by this slot's type. + pub align: u32, } pub struct CallSiteInfo { pub callee_func_id: u32, pub arg_write_slots: Vec, pub ret_read_slots: Vec, - pub param_types: Vec, - pub ret_types: Vec, + pub param_types: Vec, + pub ret_types: Vec, } pub struct LoweringContext { @@ -110,12 +48,12 @@ pub struct LoweringContext { /// Returns `Ok(None)` if any type is not concrete (e.g. type parameters, structs). /// Returns `Err` for unexpected failures. pub fn try_build_context( - module: &CompiledModule, + module_ir: &ModuleIR, func_ir: &FunctionIR, ) -> Result> { // Use an inner function that returns Option to keep `?` ergonomic for // non-concrete type checks, then wrap the result. - let inner = try_build_context_inner(module, func_ir); + let inner = try_build_context_inner(module_ir, func_ir); match inner { Some(result) => result.map(Some), None => Ok(None), @@ -125,86 +63,55 @@ pub fn try_build_context( /// Returns `None` if any type is not concrete. /// Returns `Some(Ok(ctx))` on success, `Some(Err(..))` on unexpected failure. fn try_build_context_inner( - module: &CompiledModule, + module_ir: &ModuleIR, func_ir: &FunctionIR, ) -> Option> { - // 1. Compute home slot layout - let mut home_slots = Vec::with_capacity(func_ir.num_home_slots as usize); - let mut offset: u32 = 0; - for slot_ty in &func_ir.home_slot_types { - let size = type_size(slot_ty)? as u32; - home_slots.push(SlotInfo { offset, size }); - offset += size; - } - let frame_data_size = offset; - - // 2. Build return_slots from this function's return signature - let handle = module.function_handle_at(func_ir.handle_idx); - let ret_sig = &module.signature_at(handle.return_).0; - let mut return_slots = Vec::with_capacity(ret_sig.len()); - let mut ret_offset: u32 = 0; - for tok in ret_sig { - let size = sig_token_size(tok)? as u32; - return_slots.push(SlotInfo { - offset: ret_offset, - size, - }); - ret_offset += size; - } - - // 3. Scan instructions for Call/CallGeneric to build call_sites + // 1. Compute home slot layout with proper alignment. + // + // Slots are laid out linearly in declaration order, padding each to its + // natural alignment. This can leave gaps between a small slot followed + // by a higher-aligned one. + // + // TODO: consider a smarter packing (e.g. sort by descending alignment, + // or bin-pack smaller slots into padding holes) to shrink frame size. + let home_slots = layout_slots(0, &func_ir.home_slot_types)?; + let frame_data_size = home_slots.last().map(|s| s.offset + s.size).unwrap_or(0); + + // 2. Build return_slots from this function's own signature. + let own_sig = &module_ir.handle_signatures[func_ir.handle_idx.0 as usize]; + let return_slots = layout_slots(0, &own_sig.ret_types)?; + + // 3. Walk Call/CallGeneric instructions, looking up each callee's sig + // in the module-level tables. let callee_base = frame_data_size + FRAME_METADATA_SIZE as u32; let mut call_sites = Vec::new(); for instr in func_ir.instrs() { - let handle_idx = match instr { - Instr::Call(_, idx, _) => *idx, + let (handle_idx, sig) = match instr { + Instr::Call(_, idx, _) => { + let sig = &module_ir.handle_signatures[idx.0 as usize]; + (*idx, sig) + }, Instr::CallGeneric(_, idx, _) => { - let inst = &module.function_instantiations[idx.0 as usize]; - inst.handle + let inst = &module_ir.module.function_instantiations[idx.0 as usize]; + let sig = &module_ir.instantiation_signatures[idx.0 as usize]; + (inst.handle, sig) }, _ => continue, }; - let callee_handle = module.function_handle_at(handle_idx); let callee_func_id = handle_idx.0 as u32; - // Param layout - let param_sig = &module.signature_at(callee_handle.parameters).0; - let mut arg_write_slots = Vec::with_capacity(param_sig.len()); - let mut param_types = Vec::with_capacity(param_sig.len()); - let mut arg_offset = callee_base; - for tok in param_sig { - let size = sig_token_size(tok)? as u32; - arg_write_slots.push(SlotInfo { - offset: arg_offset, - size, - }); - param_types.push(sig_token_to_type(tok)?); - arg_offset += size; - } - - // Return layout - let callee_ret_sig = &module.signature_at(callee_handle.return_).0; - let mut ret_read_slots = Vec::with_capacity(callee_ret_sig.len()); - let mut ret_types = Vec::with_capacity(callee_ret_sig.len()); - let mut callee_ret_offset = callee_base; - for tok in callee_ret_sig { - let size = sig_token_size(tok)? as u32; - ret_read_slots.push(SlotInfo { - offset: callee_ret_offset, - size, - }); - ret_types.push(sig_token_to_type(tok)?); - callee_ret_offset += size; - } + // Param and return areas share `callee_base` (callee's frame start). + let arg_write_slots = layout_slots(callee_base, &sig.param_types)?; + let ret_read_slots = layout_slots(callee_base, &sig.ret_types)?; call_sites.push(CallSiteInfo { callee_func_id, arg_write_slots, ret_read_slots, - param_types, - ret_types, + param_types: sig.param_types.clone(), + ret_types: sig.ret_types.clone(), }); } @@ -216,3 +123,21 @@ fn try_build_context_inner( num_xfer_slots: func_ir.num_xfer_slots, })) } + +/// Lays out a contiguous sequence of slots starting at `base`, padding each +/// to its natural alignment. Returns `None` if any type is not concrete. +fn layout_slots(base: u32, types: &[InternedType]) -> Option> { + let mut slots = Vec::with_capacity(types.len()); + let mut offset = base; + for ty in types { + let (size, alignment) = type_size_and_align(*ty)?; + offset = align_up(offset, alignment); + slots.push(SlotInfo { + offset, + size, + align: alignment, + }); + offset += size; + } + Some(slots) +} diff --git a/third_party/move/mono-move/specializer/src/lower/mod.rs b/third_party/move/mono-move/specializer/src/lower/mod.rs index 31515b46b94..1747aa31d29 100644 --- a/third_party/move/mono-move/specializer/src/lower/mod.rs +++ b/third_party/move/mono-move/specializer/src/lower/mod.rs @@ -10,7 +10,7 @@ mod translate; pub use context::{try_build_context, LoweringContext, SlotInfo}; pub use display::MicroOpsFunctionDisplay; use mono_move_core::MicroOp; -use move_binary_format::file_format::IdentifierIndex; +use move_binary_format::file_format::{FunctionHandleIndex, IdentifierIndex}; pub use translate::lower_function; /// Result of lowering a single non-generic function. @@ -18,6 +18,8 @@ pub use translate::lower_function; pub struct LoweredFunction { /// Function name, as an index into the module's identifier pool. pub name_idx: IdentifierIndex, + /// Handle index of this function in the defining module. + pub handle_idx: FunctionHandleIndex, /// Gas-instrumented micro-ops. pub code: Vec, /// Size of the argument region at the start of the frame. @@ -28,9 +30,10 @@ pub struct LoweredFunction { pub extended_frame_size: usize, } -/// Result of lowering an entire module. +/// Result of lowering an entire module. Only successfully lowered +/// functions appear; functions that were skipped (e.g., generic or +/// native) are omitted. Each entry carries its own `handle_idx`, so the +/// vector order is not meaningful. pub struct LoweredModule { - /// Per-definition-index results. `None` for functions that were - /// not lowered (e.g., generic functions). - pub functions: Vec>, + pub functions: Vec, } 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 5eda2127a0e..686199caa9c 100644 --- a/third_party/move/mono-move/specializer/src/lower/translate.rs +++ b/third_party/move/mono-move/specializer/src/lower/translate.rs @@ -6,8 +6,10 @@ use super::context::{LoweringContext, SlotInfo}; use crate::stackless_exec_ir::{BinaryOp, CmpOp, FunctionIR, ImmValue, Instr, Label, Slot}; use anyhow::{bail, Result}; -use mono_move_core::{CodeOffset, FrameOffset, MicroOp}; -use move_vm_types::loaded_data::runtime_types::Type; +use mono_move_core::{ + types::{view_type, InternedType, Type}, + CodeOffset, FrameOffset, MicroOp, +}; pub fn lower_function(func_ir: &FunctionIR, ctx: &LoweringContext) -> Result> { let mut state = LoweringState::new(func_ir, ctx); @@ -29,19 +31,30 @@ struct LoweringState<'a> { /// Indices into ops that need target patching branch_fixups: Vec, call_site_cursor: usize, - home_slot_types: &'a [Type], + home_slot_types: &'a [InternedType], /// Per-position slot info for Xfer slots, updated lazily. active_xfer_slots: Vec, /// Per-position types for Xfer slots, updated lazily. - active_xfer_types: Vec, + active_xfer_types: Vec, } impl<'a> LoweringState<'a> { fn new(func_ir: &'a FunctionIR, ctx: &'a LoweringContext) -> Self { - // Initialize active_xfer_slots/types from first callsite's arg_write_slots + // `active_xfer_slots` / `active_xfer_types` track the *current* meaning + // of each Xfer slot (which changes across call sites). Each position is + // overwritten by `def_slot` before it's read in valid IR, so the initial + // values below are benign placeholders that should never be observed. + // TODO: revisit this design. let num_xfer_slots = ctx.num_xfer_slots as usize; - let mut active_xfer_slots = vec![SlotInfo { offset: 0, size: 0 }; num_xfer_slots]; - let mut active_xfer_types = vec![Type::U64; num_xfer_slots]; + let mut active_xfer_slots = vec![ + SlotInfo { + offset: 0, + size: 0, + align: 1 + }; + num_xfer_slots + ]; + let mut active_xfer_types = vec![mono_move_core::types::U64_TY; num_xfer_slots]; if !ctx.call_sites.is_empty() { let first = &ctx.call_sites[0]; let n = num_xfer_slots.min(first.arg_write_slots.len()); @@ -78,7 +91,7 @@ impl<'a> LoweringState<'a> { Slot::Xfer(j) => { let cs = &self.ctx.call_sites[self.call_site_cursor]; let slot = cs.arg_write_slots[j as usize]; - let ty = cs.param_types[j as usize].clone(); + let ty = cs.param_types[j as usize]; self.active_xfer_slots[j as usize] = slot; self.active_xfer_types[j as usize] = ty; slot @@ -110,28 +123,22 @@ impl<'a> LoweringState<'a> { } fn slot_type(&self, slot: Slot) -> &Type { - match slot { - Slot::Home(i) => &self.home_slot_types[i as usize], - Slot::Xfer(j) => &self.active_xfer_types[j as usize], + let ptr = match slot { + Slot::Home(i) => self.home_slot_types[i as usize], + Slot::Xfer(j) => self.active_xfer_types[j as usize], Slot::Vid(_) => unreachable!("Vid slot in post-allocation IR"), - } + }; + view_type(ptr) } - fn is_u64_type(ty: &Type) -> bool { - matches!( - ty, - Type::U64 - | Type::Bool - | Type::U8 - | Type::I8 - | Type::U16 - | Type::I16 - | Type::U32 - | Type::I32 - | Type::I64 - | Type::Address - | Type::Signer - ) + /// Returns true if the type fits in 8 bytes or fewer (can use u64 micro-ops). + /// TODO: this is only a temporary heuristic until we have proper type-specific + /// micro-ops. + fn fits_in_u64(ty: &Type) -> bool { + match ty.size_and_align() { + Some((size, _)) => size <= 8, + None => false, + } } /// Lower one IR instruction. @@ -219,7 +226,7 @@ impl<'a> LoweringState<'a> { // --- Binary ops --- Instr::BinaryOp(dst, op, lhs, rhs) => { let lhs_ty = self.slot_type(*lhs); - if Self::is_u64_type(lhs_ty) { + if Self::fits_in_u64(lhs_ty) { let l = self.slot(*lhs); let r = self.slot(*rhs); let d = self.def_slot(*dst); @@ -239,7 +246,7 @@ impl<'a> LoweringState<'a> { // --- Binary ops with immediate --- Instr::BinaryOpImm(dst, op, src, imm) => { let src_ty = self.slot_type(*src); - if Self::is_u64_type(src_ty) { + if Self::fits_in_u64(src_ty) { let s = self.slot(*src); let d = self.def_slot(*dst); let v = imm_to_u64(imm); @@ -296,7 +303,7 @@ impl<'a> LoweringState<'a> { // --- Fused compare+branch --- Instr::BrCmp(Label(l), op, lhs, rhs) => { let lhs_ty = self.slot_type(*lhs); - if Self::is_u64_type(lhs_ty) { + if Self::fits_in_u64(lhs_ty) { let l_slot = self.slot(*lhs); let r_slot = self.slot(*rhs); let idx = self.ops.len(); @@ -339,7 +346,7 @@ impl<'a> LoweringState<'a> { }, Instr::BrCmpImm(Label(l), op, src, imm) => { let src_ty = self.slot_type(*src); - if Self::is_u64_type(src_ty) { + if Self::fits_in_u64(src_ty) { let s = self.slot(*src); let v = imm_to_u64(imm); let idx = self.ops.len(); @@ -392,7 +399,7 @@ impl<'a> LoweringState<'a> { self.emit(MicroOp::Return); }, - _ => bail!("instruction {:?} not yet lowered", instr), + _ => bail!("instruction {} not yet lowered", instr.opcode_name()), } Ok(()) } @@ -428,13 +435,13 @@ impl<'a> LoweringState<'a> { let next_cs = &self.ctx.call_sites[self.call_site_cursor]; if j < next_cs.arg_write_slots.len() { self.active_xfer_slots[j] = next_cs.arg_write_slots[j]; - self.active_xfer_types[j] = next_cs.param_types[j].clone(); + self.active_xfer_types[j] = next_cs.param_types[j]; resolved = true; } } if !resolved && k < prev_cs.ret_read_slots.len() { self.active_xfer_slots[j] = prev_cs.ret_read_slots[k]; - self.active_xfer_types[j] = prev_cs.ret_types[k].clone(); + self.active_xfer_types[j] = prev_cs.ret_types[k]; } } } diff --git a/third_party/move/mono-move/specializer/src/pipeline.rs b/third_party/move/mono-move/specializer/src/pipeline.rs index c0a34be8353..0eb489dc2b3 100644 --- a/third_party/move/mono-move/specializer/src/pipeline.rs +++ b/third_party/move/mono-move/specializer/src/pipeline.rs @@ -8,56 +8,55 @@ use crate::{ lower::{lower_function, try_build_context, LoweredFunction, LoweredModule}, }; use anyhow::Result; -use mono_move_core::{MicroOpGasSchedule, FRAME_METADATA_SIZE}; +use mono_move_core::{types::InternedType, MicroOpGasSchedule, FRAME_METADATA_SIZE}; use mono_move_gas::GasInstrumentor; +use mono_move_global_context::ExecutionGuard; use move_binary_format::CompiledModule; -use move_vm_types::loaded_data::struct_name_indexing::StructNameIndex; /// Run the full specializer pipeline: destack → lower → gas instrument → frame layout. // TODO: extend with additional passes (e.g., monomorphization, GC safe-point layout). -pub fn destack_and_lower_module(module: CompiledModule) -> Result { - // Identity mapping: valid when loading a single module in isolation. - let struct_name_table: Vec = (0..module.struct_handles.len()) - .map(|i| StructNameIndex::new(i as u32)) - .collect(); - let module_ir = destack(module, &struct_name_table)?; +pub fn destack_and_lower_module( + module: CompiledModule, + guard: &ExecutionGuard<'_>, + struct_types: &[Option], +) -> Result { + let module_ir = destack(module, guard, struct_types)?; let mut functions = Vec::with_capacity(module_ir.functions.len()); for func_ir in &module_ir.functions { let Some(func_ir) = func_ir else { - functions.push(None); continue; }; - let lowered = match try_build_context(&module_ir.module, func_ir)? { - Some(ctx) => { - let micro_ops = lower_function(func_ir, &ctx)?; - let code = GasInstrumentor::new(MicroOpGasSchedule).run(micro_ops); + let Some(ctx) = try_build_context(&module_ir, func_ir)? else { + continue; + }; + let micro_ops = lower_function(func_ir, &ctx)?; + let code = GasInstrumentor::new(MicroOpGasSchedule).run(micro_ops); - let args_size = ctx.home_slots[..func_ir.num_params as usize] - .iter() - .map(|s| s.size as usize) - .sum::(); - let args_and_locals_size = ctx.frame_data_size as usize; - let extended_frame_size = ctx - .call_sites - .iter() - .flat_map(|cs| cs.arg_write_slots.iter().chain(cs.ret_read_slots.iter())) - .map(|s| (s.offset + s.size) as usize) - .max() - // Leaf function: no callee slots needed beyond metadata. - .unwrap_or(args_and_locals_size + FRAME_METADATA_SIZE); + // End offset of the last param slot, including any inter-slot + // alignment padding. + let args_size = ctx.home_slots[..func_ir.num_params as usize] + .last() + .map(|s| (s.offset + s.size) as usize) + .unwrap_or(0); + let args_and_locals_size = ctx.frame_data_size as usize; + let extended_frame_size = ctx + .call_sites + .iter() + .flat_map(|cs| cs.arg_write_slots.iter().chain(cs.ret_read_slots.iter())) + .map(|s| (s.offset + s.size) as usize) + .max() + // Leaf function: no callee slots needed beyond metadata. + .unwrap_or(args_and_locals_size + FRAME_METADATA_SIZE); - Some(LoweredFunction { - name_idx: func_ir.name_idx, - code, - args_size, - args_and_locals_size, - extended_frame_size, - }) - }, - None => None, - }; - functions.push(lowered); + functions.push(LoweredFunction { + name_idx: func_ir.name_idx, + handle_idx: func_ir.handle_idx, + code, + args_size, + args_and_locals_size, + extended_frame_size, + }); } Ok(LoweredModule { functions }) 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 ad5526c4153..49090573ca1 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 @@ -1,21 +1,21 @@ // 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 -//! Human-readable display for the stackless execution IR. -//! Resolves pool indices using the CompiledModule for readable output. +//! Display for the stackless IR. Types render from their interned form; +//! entity handles (functions, fields, variants) resolve via `CompiledModule`. use super::{BinaryOp, CmpOp, FunctionIR, ImmValue, Instr, ModuleIR, Slot, UnaryOp}; +use mono_move_core::types::{ + view_name, view_type, view_type_list, InternedType, InternedTypeList, Type, +}; use move_binary_format::{ access::ModuleAccess, file_format::{ FieldHandleIndex, FieldInstantiationIndex, FunctionHandleIndex, FunctionInstantiationIndex, - SignatureToken, StructDefInstantiationIndex, StructDefinitionIndex, StructHandleIndex, - StructVariantHandleIndex, StructVariantInstantiationIndex, VariantFieldHandleIndex, - VariantFieldInstantiationIndex, + SignatureToken, VariantFieldHandleIndex, VariantFieldInstantiationIndex, }, CompiledModule, }; -use move_vm_types::loaded_data::runtime_types::Type; use std::fmt; impl fmt::Display for ModuleIR { @@ -86,9 +86,9 @@ fn display_function( // Display slot declarations with types for i in 0..func.num_home_slots { - let ty = &func.home_slot_types[i as usize]; + let ty = func.home_slot_types[i as usize]; write!(f, " r{}: ", i)?; - display_type(f, module, ty)?; + display_type(f, ty)?; writeln!(f)?; } writeln!(f, " code:")?; @@ -132,17 +132,6 @@ fn write_dsts(f: &mut fmt::Formatter<'_>, ds: &[Slot]) -> fmt::Result { write!(f, "{} := ", slot_names(ds)) } -fn struct_name(module: &CompiledModule, idx: StructDefinitionIndex) -> String { - let def = &module.struct_defs[idx.0 as usize]; - let handle = module.struct_handle_at(def.struct_handle); - module.identifier_at(handle.name).to_string() -} - -fn struct_inst_name(module: &CompiledModule, idx: StructDefInstantiationIndex) -> String { - let inst = &module.struct_def_instantiations[idx.0 as usize]; - struct_name(module, inst.def) -} - fn func_name(module: &CompiledModule, idx: FunctionHandleIndex) -> String { let handle = module.function_handle_at(idx); module.identifier_at(handle.name).to_string() @@ -172,27 +161,6 @@ fn field_inst_name(module: &CompiledModule, idx: FieldInstantiationIndex) -> Str field_name(module, inst.handle) } -fn variant_handle_name(module: &CompiledModule, idx: StructVariantHandleIndex) -> String { - let handle = &module.struct_variant_handles[idx.0 as usize]; - let def = &module.struct_defs[handle.struct_index.0 as usize]; - let struct_handle = module.struct_handle_at(def.struct_handle); - let sname = module.identifier_at(struct_handle.name); - let vname = match &def.field_information { - move_binary_format::file_format::StructFieldInformation::DeclaredVariants(variants) => { - module - .identifier_at(variants[handle.variant as usize].name) - .to_string() - }, - _ => format!("#{}", handle.variant), - }; - format!("{}::{}", sname, vname) -} - -fn variant_inst_name(module: &CompiledModule, idx: StructVariantInstantiationIndex) -> String { - let inst = &module.struct_variant_instantiations[idx.0 as usize]; - variant_handle_name(module, inst.handle) -} - fn variant_field_name(module: &CompiledModule, idx: VariantFieldHandleIndex) -> String { let handle = &module.variant_field_handles[idx.0 as usize]; let def = &module.struct_defs[handle.struct_index.0 as usize]; @@ -327,92 +295,72 @@ fn display_instr( }, // --- Struct --- - Instr::Pack(d, idx, fields) => { + Instr::Pack(d, ty, fields) => { write_dst(f, *d)?; - write!( - f, - "pack {}, {}", - struct_name(module, *idx), - slot_names(fields) - ) + write!(f, "pack ")?; + display_type(f, *ty)?; + write!(f, ", {}", slot_names(fields)) }, - Instr::PackGeneric(d, idx, fields) => { + Instr::PackGeneric(d, base_ty, ty_args, fields) => { write_dst(f, *d)?; - write!( - f, - "pack {}, {}", - struct_inst_name(module, *idx), - slot_names(fields) - ) + write!(f, "pack ")?; + display_type(f, *base_ty)?; + display_type_list(f, *ty_args)?; + write!(f, ", {}", slot_names(fields)) }, - Instr::Unpack(ds, idx, s) => { + Instr::Unpack(ds, ty, s) => { write_dsts(f, ds)?; - write!(f, "unpack {}, {}", struct_name(module, *idx), slot_name(*s)) + write!(f, "unpack ")?; + display_type(f, *ty)?; + write!(f, ", {}", slot_name(*s)) }, - Instr::UnpackGeneric(ds, idx, s) => { + Instr::UnpackGeneric(ds, base_ty, ty_args, s) => { write_dsts(f, ds)?; - write!( - f, - "unpack {}, {}", - struct_inst_name(module, *idx), - slot_name(*s) - ) + write!(f, "unpack ")?; + display_type(f, *base_ty)?; + display_type_list(f, *ty_args)?; + write!(f, ", {}", slot_name(*s)) }, // --- Variant --- - Instr::PackVariant(d, idx, fields) => { + Instr::PackVariant(d, ty, variant, fields) => { write_dst(f, *d)?; - write!( - f, - "pack_variant {}, {}", - variant_handle_name(module, *idx), - slot_names(fields) - ) + write!(f, "pack_variant ")?; + display_type(f, *ty)?; + write!(f, "@{}, {}", variant, slot_names(fields)) }, - Instr::PackVariantGeneric(d, idx, fields) => { + Instr::PackVariantGeneric(d, enum_ty, variant, ty_args, fields) => { write_dst(f, *d)?; - write!( - f, - "pack_variant {}, {}", - variant_inst_name(module, *idx), - slot_names(fields) - ) + write!(f, "pack_variant ")?; + display_type(f, *enum_ty)?; + display_type_list(f, *ty_args)?; + write!(f, "@{}, {}", variant, slot_names(fields)) }, - Instr::UnpackVariant(ds, idx, s) => { + Instr::UnpackVariant(ds, ty, variant, s) => { write_dsts(f, ds)?; - write!( - f, - "unpack_variant {}, {}", - variant_handle_name(module, *idx), - slot_name(*s) - ) + write!(f, "unpack_variant ")?; + display_type(f, *ty)?; + write!(f, "@{}, {}", variant, slot_name(*s)) }, - Instr::UnpackVariantGeneric(ds, idx, s) => { + Instr::UnpackVariantGeneric(ds, enum_ty, variant, ty_args, s) => { write_dsts(f, ds)?; - write!( - f, - "unpack_variant {}, {}", - variant_inst_name(module, *idx), - slot_name(*s) - ) + write!(f, "unpack_variant ")?; + display_type(f, *enum_ty)?; + display_type_list(f, *ty_args)?; + write!(f, "@{}, {}", variant, slot_name(*s)) }, - Instr::TestVariant(d, idx, s) => { + Instr::TestVariant(d, ty, variant, s) => { write_dst(f, *d)?; - write!( - f, - "test_variant {}, {}", - variant_handle_name(module, *idx), - slot_name(*s) - ) + write!(f, "test_variant ")?; + display_type(f, *ty)?; + write!(f, "@{}, {}", variant, slot_name(*s)) }, - Instr::TestVariantGeneric(d, idx, s) => { + Instr::TestVariantGeneric(d, enum_ty, variant, ty_args, s) => { write_dst(f, *d)?; - write!( - f, - "test_variant {}, {}", - variant_inst_name(module, *idx), - slot_name(*s) - ) + write!(f, "test_variant ")?; + display_type(f, *enum_ty)?; + display_type_list(f, *ty_args)?; + write!(f, "@{}, {}", variant, slot_name(*s)) }, // --- References --- @@ -578,91 +526,69 @@ fn display_instr( }, // --- Globals --- - Instr::Exists(d, idx, a) => { + Instr::Exists(d, ty, a) => { write_dst(f, *d)?; - write!(f, "exists {}, {}", struct_name(module, *idx), slot_name(*a)) + write!(f, "exists ")?; + display_type(f, *ty)?; + write!(f, ", {}", slot_name(*a)) }, - Instr::ExistsGeneric(d, idx, a) => { + Instr::ExistsGeneric(d, base_ty, ty_args, a) => { write_dst(f, *d)?; - write!( - f, - "exists {}, {}", - struct_inst_name(module, *idx), - slot_name(*a) - ) + write!(f, "exists ")?; + display_type(f, *base_ty)?; + display_type_list(f, *ty_args)?; + write!(f, ", {}", slot_name(*a)) }, - Instr::MoveFrom(d, idx, a) => { + Instr::MoveFrom(d, ty, a) => { write_dst(f, *d)?; - write!( - f, - "move_from {}, {}", - struct_name(module, *idx), - slot_name(*a) - ) + write!(f, "move_from ")?; + display_type(f, *ty)?; + write!(f, ", {}", slot_name(*a)) }, - Instr::MoveFromGeneric(d, idx, a) => { + Instr::MoveFromGeneric(d, base_ty, ty_args, a) => { write_dst(f, *d)?; - write!( - f, - "move_from {}, {}", - struct_inst_name(module, *idx), - slot_name(*a) - ) + write!(f, "move_from ")?; + display_type(f, *base_ty)?; + display_type_list(f, *ty_args)?; + write!(f, ", {}", slot_name(*a)) }, // MoveTo has no destination (side-effect) - Instr::MoveTo(idx, s, v) => { - write!( - f, - "move_to {}, {}, {}", - struct_name(module, *idx), - slot_name(*s), - slot_name(*v) - ) + Instr::MoveTo(ty, s, v) => { + write!(f, "move_to ")?; + display_type(f, *ty)?; + write!(f, ", {}, {}", slot_name(*s), slot_name(*v)) }, - Instr::MoveToGeneric(idx, s, v) => { - write!( - f, - "move_to {}, {}, {}", - struct_inst_name(module, *idx), - slot_name(*s), - slot_name(*v) - ) + Instr::MoveToGeneric(base_ty, ty_args, s, v) => { + write!(f, "move_to ")?; + display_type(f, *base_ty)?; + display_type_list(f, *ty_args)?; + write!(f, ", {}, {}", slot_name(*s), slot_name(*v)) }, - Instr::ImmBorrowGlobal(d, idx, a) => { + Instr::ImmBorrowGlobal(d, ty, a) => { write_dst(f, *d)?; - write!( - f, - "imm_borrow_global {}, {}", - struct_name(module, *idx), - slot_name(*a) - ) + write!(f, "imm_borrow_global ")?; + display_type(f, *ty)?; + write!(f, ", {}", slot_name(*a)) }, - Instr::ImmBorrowGlobalGeneric(d, idx, a) => { + Instr::ImmBorrowGlobalGeneric(d, base_ty, ty_args, a) => { write_dst(f, *d)?; - write!( - f, - "imm_borrow_global {}, {}", - struct_inst_name(module, *idx), - slot_name(*a) - ) + write!(f, "imm_borrow_global ")?; + display_type(f, *base_ty)?; + display_type_list(f, *ty_args)?; + write!(f, ", {}", slot_name(*a)) }, - Instr::MutBorrowGlobal(d, idx, a) => { + Instr::MutBorrowGlobal(d, ty, a) => { write_dst(f, *d)?; - write!( - f, - "mut_borrow_global {}, {}", - struct_name(module, *idx), - slot_name(*a) - ) + write!(f, "mut_borrow_global ")?; + display_type(f, *ty)?; + write!(f, ", {}", slot_name(*a)) }, - Instr::MutBorrowGlobalGeneric(d, idx, a) => { + Instr::MutBorrowGlobalGeneric(d, base_ty, ty_args, a) => { write_dst(f, *d)?; - write!( - f, - "mut_borrow_global {}, {}", - struct_inst_name(module, *idx), - slot_name(*a) - ) + write!(f, "mut_borrow_global ")?; + display_type(f, *base_ty)?; + display_type_list(f, *ty_args)?; + write!(f, ", {}", slot_name(*a)) }, // --- Calls --- @@ -701,64 +627,63 @@ fn display_instr( slot_names(captured) ) }, - Instr::CallClosure(rets, sig_idx, args) => { + Instr::CallClosure(rets, sig_types, args) => { write_dsts(f, rets)?; - write!(f, "call_closure #{}, {}", sig_idx.0, slot_names(args)) + write!(f, "call_closure ")?; + display_type_list(f, *sig_types)?; + write!(f, ", {}", slot_names(args)) }, // --- Vector --- - Instr::VecPack(d, sig, count, elems) => { + Instr::VecPack(d, elem_ty, count, elems) => { write_dst(f, *d)?; - write!(f, "vec_pack #{}, {}, {}", sig.0, count, slot_names(elems)) + write!(f, "vec_pack ")?; + display_type(f, *elem_ty)?; + write!(f, ", {}, {}", count, slot_names(elems)) }, - Instr::VecLen(d, sig, s) => { + Instr::VecLen(d, elem_ty, s) => { write_dst(f, *d)?; - write!(f, "vec_len #{}, {}", sig.0, slot_name(*s)) + write!(f, "vec_len ")?; + display_type(f, *elem_ty)?; + write!(f, ", {}", slot_name(*s)) }, - Instr::VecImmBorrow(d, sig, v, i) => { + Instr::VecImmBorrow(d, elem_ty, v, i) => { write_dst(f, *d)?; - write!( - f, - "vec_imm_borrow #{}, {}, {}", - sig.0, - slot_name(*v), - slot_name(*i) - ) + write!(f, "vec_imm_borrow ")?; + display_type(f, *elem_ty)?; + write!(f, ", {}, {}", slot_name(*v), slot_name(*i)) }, - Instr::VecMutBorrow(d, sig, v, i) => { + Instr::VecMutBorrow(d, elem_ty, v, i) => { write_dst(f, *d)?; - write!( - f, - "vec_mut_borrow #{}, {}, {}", - sig.0, - slot_name(*v), - slot_name(*i) - ) + write!(f, "vec_mut_borrow ")?; + display_type(f, *elem_ty)?; + write!(f, ", {}, {}", slot_name(*v), slot_name(*i)) }, // VecPushBack has no destination - Instr::VecPushBack(sig, v, val) => { - write!( - f, - "vec_push_back #{}, {}, {}", - sig.0, - slot_name(*v), - slot_name(*val) - ) + Instr::VecPushBack(elem_ty, v, val) => { + write!(f, "vec_push_back ")?; + display_type(f, *elem_ty)?; + write!(f, ", {}, {}", slot_name(*v), slot_name(*val)) }, - Instr::VecPopBack(d, sig, s) => { + Instr::VecPopBack(d, elem_ty, s) => { write_dst(f, *d)?; - write!(f, "vec_pop_back #{}, {}", sig.0, slot_name(*s)) + write!(f, "vec_pop_back ")?; + display_type(f, *elem_ty)?; + write!(f, ", {}", slot_name(*s)) }, - Instr::VecUnpack(ds, sig, count, s) => { + Instr::VecUnpack(ds, elem_ty, count, s) => { write_dsts(f, ds)?; - write!(f, "vec_unpack #{}, {}, {}", sig.0, count, slot_name(*s)) + write!(f, "vec_unpack ")?; + display_type(f, *elem_ty)?; + write!(f, ", {}, {}", count, slot_name(*s)) }, // VecSwap has no destination - Instr::VecSwap(sig, v, i, j) => { + Instr::VecSwap(elem_ty, v, i, j) => { + write!(f, "vec_swap ")?; + display_type(f, *elem_ty)?; write!( f, - "vec_swap #{}, {}, {}, {}", - sig.0, + ", {}, {}, {}", slot_name(*v), slot_name(*i), slot_name(*j) @@ -855,12 +780,22 @@ fn imm_value(imm: &ImmValue) -> String { } } -/// Display a runtime `Type` with struct names resolved via the module. -/// -/// `StructNameIndex` values are assumed to be ordinals matching `StructHandleIndex`, -/// which is how this crate's `type_conversion` builds them. -fn display_type(f: &mut fmt::Formatter<'_>, module: &CompiledModule, ty: &Type) -> fmt::Result { - match ty { +/// Display an interned type list as `[T0, T1, ...]`. +fn display_type_list(f: &mut fmt::Formatter<'_>, types: InternedTypeList) -> fmt::Result { + write!(f, "[")?; + for (i, ty) in view_type_list(types).iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + display_type(f, *ty)?; + } + write!(f, "]") +} + +/// Display an interned `Type`. Names are carried on the interned type itself, +/// so no module lookup is needed. +fn display_type(f: &mut fmt::Formatter<'_>, ty: InternedType) -> fmt::Result { + match view_type(ty) { Type::Bool => write!(f, "bool"), Type::U8 => write!(f, "u8"), Type::U16 => write!(f, "u16"), @@ -876,51 +811,39 @@ fn display_type(f: &mut fmt::Formatter<'_>, module: &CompiledModule, ty: &Type) Type::I256 => write!(f, "i256"), Type::Address => write!(f, "address"), Type::Signer => write!(f, "signer"), - Type::TyParam(idx) => write!(f, "_{}", idx), - Type::Vector(inner) => { + Type::TypeParam { idx } => write!(f, "_{}", idx), + Type::Vector { elem } => { write!(f, "vector<")?; - display_type(f, module, inner)?; + display_type(f, *elem)?; write!(f, ">") }, - Type::Reference(inner) => { + Type::ImmutRef { inner } => { write!(f, "&")?; - display_type(f, module, inner) + display_type(f, *inner) }, - Type::MutableReference(inner) => { + Type::MutRef { inner } => { write!(f, "&mut ")?; - display_type(f, module, inner) - }, - Type::Struct { idx, .. } => { - let sh_idx = StructHandleIndex(idx.to_string().parse::().unwrap()); - let handle = module.struct_handle_at(sh_idx); - write!(f, "{}", module.identifier_at(handle.name)) + display_type(f, *inner) }, - Type::StructInstantiation { idx, ty_args, .. } => { - let sh_idx = StructHandleIndex(idx.to_string().parse::().unwrap()); - let handle = module.struct_handle_at(sh_idx); - write!(f, "{}<", module.identifier_at(handle.name))?; - for (i, arg) in ty_args.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - display_type(f, module, arg)?; - } - write!(f, ">") + Type::Struct { name, .. } | Type::Enum { name, .. } => { + write!(f, "{}", view_name(*name)) }, Type::Function { args, results, .. } => { + let args = view_type_list(*args); + let results = view_type_list(*results); write!(f, "|")?; for (i, arg) in args.iter().enumerate() { if i > 0 { write!(f, ", ")?; } - display_type(f, module, arg)?; + display_type(f, *arg)?; } write!(f, "|")?; for (i, r) in results.iter().enumerate() { if i > 0 { write!(f, ", ")?; } - display_type(f, module, r)?; + display_type(f, *r)?; } Ok(()) }, 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 0327c6c3941..f4dcd3352d9 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 @@ -8,12 +8,12 @@ mod display; +use mono_move_core::types::{InternedType, InternedTypeList}; use move_binary_format::{ file_format::{ ConstantPoolIndex, FieldHandleIndex, FieldInstantiationIndex, FunctionHandleIndex, - FunctionInstantiationIndex, IdentifierIndex, SignatureIndex, StructDefInstantiationIndex, - StructDefinitionIndex, StructVariantHandleIndex, StructVariantInstantiationIndex, - VariantFieldHandleIndex, VariantFieldInstantiationIndex, + FunctionInstantiationIndex, IdentifierIndex, VariantFieldHandleIndex, + VariantFieldInstantiationIndex, }, CompiledModule, }; @@ -21,7 +21,6 @@ use move_core_types::{ function::ClosureMask, int256::{I256, U256}, }; -use move_vm_types::loaded_data::runtime_types::Type; /// Named slot operand. /// @@ -140,7 +139,10 @@ pub enum ImmValue { } /// A stackless IR instruction with explicit named-slot operands. -#[derive(Clone, Debug)] +/// +/// TODO: convert variants to struct-style (named fields) so call sites read +/// `Instr::Pack { dst, ty, args }` rather than positional tuples. +#[derive(Clone)] pub enum Instr { // --- Loads --- LdConst(Slot, ConstantPoolIndex), @@ -173,19 +175,24 @@ pub enum Instr { /// `dst = op(lhs_slot, immediate)` — binary op with immediate right operand BinaryOpImm(Slot, BinaryOp, Slot, ImmValue), - // --- Struct --- - Pack(Slot, StructDefinitionIndex, Vec), - PackGeneric(Slot, StructDefInstantiationIndex, Vec), - Unpack(Vec, StructDefinitionIndex, Slot), - UnpackGeneric(Vec, StructDefInstantiationIndex, Slot), + // --- Struct (second field is the interned struct `Type`; generic + // variants additionally carry an interned type-argument list) --- + // + // TODO: depending on how we pre-intern types, we may be able to unify + // some of instructions here. + Pack(Slot, InternedType, Vec), + PackGeneric(Slot, InternedType, InternedTypeList, Vec), + Unpack(Vec, InternedType, Slot), + UnpackGeneric(Vec, InternedType, InternedTypeList, Slot), - // --- Variant --- - PackVariant(Slot, StructVariantHandleIndex, Vec), - PackVariantGeneric(Slot, StructVariantInstantiationIndex, Vec), - UnpackVariant(Vec, StructVariantHandleIndex, Slot), - UnpackVariantGeneric(Vec, StructVariantInstantiationIndex, Slot), - TestVariant(Slot, StructVariantHandleIndex, Slot), - TestVariantGeneric(Slot, StructVariantInstantiationIndex, Slot), + // --- Variant (enum type + variant ordinal; generic variants also + // carry an interned type-argument list) --- + PackVariant(Slot, InternedType, u16, Vec), + PackVariantGeneric(Slot, InternedType, u16, InternedTypeList, Vec), + UnpackVariant(Vec, InternedType, u16, Slot), + UnpackVariantGeneric(Vec, InternedType, u16, InternedTypeList, Slot), + TestVariant(Slot, InternedType, u16, Slot), + TestVariantGeneric(Slot, InternedType, u16, InternedTypeList, Slot), // --- References --- ImmBorrowLoc(Slot, Slot), @@ -214,18 +221,19 @@ pub enum Instr { WriteVariantField(VariantFieldHandleIndex, Slot, Slot), WriteVariantFieldGeneric(VariantFieldInstantiationIndex, Slot, Slot), - // --- Globals --- - Exists(Slot, StructDefinitionIndex, Slot), - ExistsGeneric(Slot, StructDefInstantiationIndex, Slot), - MoveFrom(Slot, StructDefinitionIndex, Slot), - MoveFromGeneric(Slot, StructDefInstantiationIndex, Slot), - /// `(def, signer, val)` - MoveTo(StructDefinitionIndex, Slot, Slot), - MoveToGeneric(StructDefInstantiationIndex, Slot, Slot), - ImmBorrowGlobal(Slot, StructDefinitionIndex, Slot), - ImmBorrowGlobalGeneric(Slot, StructDefInstantiationIndex, Slot), - MutBorrowGlobal(Slot, StructDefinitionIndex, Slot), - MutBorrowGlobalGeneric(Slot, StructDefInstantiationIndex, Slot), + // --- Globals (struct type is the interned `Type` for the named resource; + // generic variants additionally carry an interned type-argument list) --- + Exists(Slot, InternedType, Slot), + ExistsGeneric(Slot, InternedType, InternedTypeList, Slot), + MoveFrom(Slot, InternedType, Slot), + MoveFromGeneric(Slot, InternedType, InternedTypeList, Slot), + /// `(struct_ty, signer, val)` + MoveTo(InternedType, Slot, Slot), + MoveToGeneric(InternedType, InternedTypeList, Slot, Slot), + ImmBorrowGlobal(Slot, InternedType, Slot), + ImmBorrowGlobalGeneric(Slot, InternedType, InternedTypeList, Slot), + MutBorrowGlobal(Slot, InternedType, Slot), + MutBorrowGlobalGeneric(Slot, InternedType, InternedTypeList, Slot), // --- Calls --- Call(Vec, FunctionHandleIndex, Vec), @@ -234,17 +242,20 @@ pub enum Instr { // --- Closures --- PackClosure(Slot, FunctionHandleIndex, ClosureMask, Vec), PackClosureGeneric(Slot, FunctionInstantiationIndex, ClosureMask, Vec), - CallClosure(Vec, SignatureIndex, Vec), + /// `CallClosure(rets, signature_types, args)` — `signature_types` is the + /// interned list of types from the closure's signature (arg types followed + /// by result types, matching the source `SignatureIndex`). + CallClosure(Vec, InternedTypeList, Vec), - // --- Vector --- - VecPack(Slot, SignatureIndex, u16, Vec), - VecLen(Slot, SignatureIndex, Slot), - VecImmBorrow(Slot, SignatureIndex, Slot, Slot), - VecMutBorrow(Slot, SignatureIndex, Slot, Slot), - VecPushBack(SignatureIndex, Slot, Slot), - VecPopBack(Slot, SignatureIndex, Slot), - VecUnpack(Vec, SignatureIndex, u16, Slot), - VecSwap(SignatureIndex, Slot, Slot, Slot), + // --- Vector (second field is the vector's element type) --- + VecPack(Slot, InternedType, u16, Vec), + VecLen(Slot, InternedType, Slot), + VecImmBorrow(Slot, InternedType, Slot, Slot), + VecMutBorrow(Slot, InternedType, Slot, Slot), + VecPushBack(InternedType, Slot, Slot), + VecPopBack(Slot, InternedType, Slot), + VecUnpack(Vec, InternedType, u16, Slot), + VecSwap(InternedType, Slot, Slot, Slot), // --- Control flow --- Branch(Label), @@ -259,6 +270,96 @@ pub enum Instr { AbortMsg(Slot, Slot), } +impl Instr { + /// Returns the variant tag as a static string. Useful for terse error + /// messages that don't need the full operand dump. + pub fn opcode_name(&self) -> &'static str { + match self { + Instr::LdConst(..) => "LdConst", + Instr::LdTrue(..) => "LdTrue", + Instr::LdFalse(..) => "LdFalse", + Instr::LdU8(..) => "LdU8", + Instr::LdU16(..) => "LdU16", + Instr::LdU32(..) => "LdU32", + Instr::LdU64(..) => "LdU64", + Instr::LdU128(..) => "LdU128", + Instr::LdU256(..) => "LdU256", + Instr::LdI8(..) => "LdI8", + Instr::LdI16(..) => "LdI16", + Instr::LdI32(..) => "LdI32", + Instr::LdI64(..) => "LdI64", + Instr::LdI128(..) => "LdI128", + Instr::LdI256(..) => "LdI256", + Instr::Copy(..) => "Copy", + Instr::Move(..) => "Move", + Instr::UnaryOp(..) => "UnaryOp", + Instr::BinaryOp(..) => "BinaryOp", + Instr::BinaryOpImm(..) => "BinaryOpImm", + Instr::Pack(..) => "Pack", + Instr::PackGeneric(..) => "PackGeneric", + Instr::Unpack(..) => "Unpack", + Instr::UnpackGeneric(..) => "UnpackGeneric", + Instr::PackVariant(..) => "PackVariant", + Instr::PackVariantGeneric(..) => "PackVariantGeneric", + Instr::UnpackVariant(..) => "UnpackVariant", + Instr::UnpackVariantGeneric(..) => "UnpackVariantGeneric", + Instr::TestVariant(..) => "TestVariant", + Instr::TestVariantGeneric(..) => "TestVariantGeneric", + Instr::ImmBorrowLoc(..) => "ImmBorrowLoc", + Instr::MutBorrowLoc(..) => "MutBorrowLoc", + Instr::ImmBorrowField(..) => "ImmBorrowField", + Instr::MutBorrowField(..) => "MutBorrowField", + Instr::ImmBorrowFieldGeneric(..) => "ImmBorrowFieldGeneric", + Instr::MutBorrowFieldGeneric(..) => "MutBorrowFieldGeneric", + Instr::ImmBorrowVariantField(..) => "ImmBorrowVariantField", + Instr::MutBorrowVariantField(..) => "MutBorrowVariantField", + Instr::ImmBorrowVariantFieldGeneric(..) => "ImmBorrowVariantFieldGeneric", + Instr::MutBorrowVariantFieldGeneric(..) => "MutBorrowVariantFieldGeneric", + Instr::ReadRef(..) => "ReadRef", + Instr::WriteRef(..) => "WriteRef", + Instr::ReadField(..) => "ReadField", + Instr::ReadFieldGeneric(..) => "ReadFieldGeneric", + Instr::WriteField(..) => "WriteField", + Instr::WriteFieldGeneric(..) => "WriteFieldGeneric", + Instr::ReadVariantField(..) => "ReadVariantField", + Instr::ReadVariantFieldGeneric(..) => "ReadVariantFieldGeneric", + Instr::WriteVariantField(..) => "WriteVariantField", + Instr::WriteVariantFieldGeneric(..) => "WriteVariantFieldGeneric", + Instr::Exists(..) => "Exists", + Instr::ExistsGeneric(..) => "ExistsGeneric", + Instr::MoveFrom(..) => "MoveFrom", + Instr::MoveFromGeneric(..) => "MoveFromGeneric", + Instr::MoveTo(..) => "MoveTo", + Instr::MoveToGeneric(..) => "MoveToGeneric", + Instr::ImmBorrowGlobal(..) => "ImmBorrowGlobal", + Instr::ImmBorrowGlobalGeneric(..) => "ImmBorrowGlobalGeneric", + Instr::MutBorrowGlobal(..) => "MutBorrowGlobal", + Instr::MutBorrowGlobalGeneric(..) => "MutBorrowGlobalGeneric", + Instr::Call(..) => "Call", + Instr::CallGeneric(..) => "CallGeneric", + Instr::PackClosure(..) => "PackClosure", + Instr::PackClosureGeneric(..) => "PackClosureGeneric", + Instr::CallClosure(..) => "CallClosure", + Instr::VecPack(..) => "VecPack", + Instr::VecLen(..) => "VecLen", + Instr::VecImmBorrow(..) => "VecImmBorrow", + Instr::VecMutBorrow(..) => "VecMutBorrow", + Instr::VecPushBack(..) => "VecPushBack", + Instr::VecPopBack(..) => "VecPopBack", + Instr::VecUnpack(..) => "VecUnpack", + Instr::VecSwap(..) => "VecSwap", + Instr::Branch(..) => "Branch", + Instr::BrTrue(..) => "BrTrue", + Instr::BrFalse(..) => "BrFalse", + Instr::BrCmp(..) => "BrCmp", + Instr::BrCmpImm(..) => "BrCmpImm", + Instr::Ret(..) => "Ret", + Instr::Abort(..) => "Abort", + Instr::AbortMsg(..) => "AbortMsg", + } + } +} + /// A basic block of instructions. /// /// Every block has a label. The last instruction is a terminator. @@ -270,6 +371,13 @@ pub struct BasicBlock { pub instrs: Vec, } +/// Parameter and return types of a function (or function instantiation), +/// resolved to [`InternedType`]. +pub struct FuncSignature { + pub param_types: Vec, + pub ret_types: Vec, +} + /// IR for a single function. pub struct FunctionIR { /// Function name in identifier pool. @@ -288,7 +396,7 @@ pub struct FunctionIR { pub blocks: Vec, /// Type of each Home slot (indexed by Home slot index, 0..num_home_slots-1). /// Xfer slots have no entry here — their types are inferred from call signatures. - pub home_slot_types: Vec, + pub home_slot_types: Vec, } impl FunctionIR { @@ -309,4 +417,10 @@ pub struct ModuleIR { pub module: CompiledModule, /// Indexed by `FunctionDefinitionIndex`. `None` for native functions. pub functions: Vec>, + /// Signatures for non-generic calls, indexed by `FunctionHandleIndex`. + pub handle_signatures: Vec, + /// Signatures for generic calls, indexed by `FunctionInstantiationIndex`. + /// Today these mirror the base handle's signature; when proper generic + /// instantiation substitution lands, they will hold substituted types. + pub instantiation_signatures: Vec, } diff --git a/third_party/move/mono-move/testsuite/Cargo.toml b/third_party/move/mono-move/testsuite/Cargo.toml index ac0d93b0626..8f5d38837ec 100644 --- a/third_party/move/mono-move/testsuite/Cargo.toml +++ b/third_party/move/mono-move/testsuite/Cargo.toml @@ -18,6 +18,7 @@ legacy-move-compiler = { workspace = true } mono-move-core = { workspace = true } mono-move-gas = { workspace = true } mono-move-global-context = { workspace = true } +mono-move-orchestrator = { workspace = true } mono-move-runtime = { workspace = true } move-binary-format = { workspace = true } move-compiler-v2 = { workspace = true } diff --git a/third_party/move/mono-move/testsuite/src/runner.rs b/third_party/move/mono-move/testsuite/src/runner.rs index 2980840cb8b..b35c3ad5ec0 100644 --- a/third_party/move/mono-move/testsuite/src/runner.rs +++ b/third_party/move/mono-move/testsuite/src/runner.rs @@ -59,9 +59,7 @@ pub fn run_test(steps: Vec) -> anyhow::Result<()> { // V2 path. let id = guard.intern_address_name(module.self_addr(), module.self_name()); - let executable = guard - .executable_builder_for_module(module) - .build() + let executable = mono_move_orchestrator::build_executable(&guard, module) .map_err(|err| anyhow!("Failed to build executable: {}", err))?; guard.insert_executable(id, executable); } @@ -219,7 +217,7 @@ fn execute_function_v2( let function_name = guard.intern_identifier(function_name); let function = guard .get_executable(id) - .and_then(|executable| executable.get_function(function_name)) + .and_then(|executable| executable.get_function(function_name.into_global_arena_ptr())) .unwrap_or_else(|| panic!("Failed to load function or find executable")); let txn_ctx = NoopTransactionContext; diff --git a/third_party/move/mono-move/global-context/tests/gas_test.rs b/third_party/move/mono-move/testsuite/tests/gas_test.rs similarity index 92% rename from third_party/move/mono-move/global-context/tests/gas_test.rs rename to third_party/move/mono-move/testsuite/tests/gas_test.rs index f8bef6b0486..f651db3ac5d 100644 --- a/third_party/move/mono-move/global-context/tests/gas_test.rs +++ b/third_party/move/mono-move/testsuite/tests/gas_test.rs @@ -14,9 +14,7 @@ fn add_executable(guard: &ExecutionGuard<'_>, source: &str) { let modules = mono_move_testsuite::compile_move_modules(source); for module in &modules { let id = guard.intern_address_name(module.self_addr(), module.self_name()); - let executable = guard - .executable_builder_for_module(module) - .build() + let executable = mono_move_orchestrator::build_executable(guard, module) .expect("Building an executable should always succeed"); guard.insert_executable(id, executable); } @@ -42,7 +40,7 @@ module 0x1::test { let fib_name = guard.intern_identifier(ident_str!("fib")); let fib = guard .get_executable(id) - .and_then(|e| e.get_function(fib_name)) + .and_then(|e| e.get_function(fib_name.into_global_arena_ptr())) .expect("fib should exist"); let txn_ctx = NoopTransactionContext;