Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lambda support in VM: Wolfgang's PR plus some simplifications (removing mask, clarifying ops) #15387

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions api/types/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ pub trait Bytecode {
mutable: true,
to: Box::new(self.new_move_type(t.borrow())),
},
SignatureToken::Function(..) => {
// TODO(LAMBDA)
unimplemented!("signature token function to API MoveType")
},
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ pub enum FeatureFlag {
CollectionOwner,
NativeMemoryOperations,
EnableLoaderV2,
EnableFunctionValues,
}

fn generate_features_blob(writer: &CodeWriter, data: &[u64]) {
Expand Down Expand Up @@ -353,6 +354,7 @@ impl From<FeatureFlag> for AptosFeatureFlag {
FeatureFlag::CollectionOwner => AptosFeatureFlag::COLLECTION_OWNER,
FeatureFlag::NativeMemoryOperations => AptosFeatureFlag::NATIVE_MEMORY_OPERATIONS,
FeatureFlag::EnableLoaderV2 => AptosFeatureFlag::ENABLE_LOADER_V2,
FeatureFlag::EnableFunctionValues => AptosFeatureFlag::ENABLE_FUNCTION_VALUES,
}
}
}
Expand Down Expand Up @@ -500,6 +502,7 @@ impl From<AptosFeatureFlag> for FeatureFlag {
AptosFeatureFlag::COLLECTION_OWNER => FeatureFlag::CollectionOwner,
AptosFeatureFlag::NATIVE_MEMORY_OPERATIONS => FeatureFlag::NativeMemoryOperations,
AptosFeatureFlag::ENABLE_LOADER_V2 => FeatureFlag::EnableLoaderV2,
AptosFeatureFlag::ENABLE_FUNCTION_VALUES => FeatureFlag::EnableFunctionValues,
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions aptos-move/aptos-vm-environment/src/prod_configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pub fn aptos_prod_verifier_config(features: &Features) -> VerifierConfig {
let enable_enum_types = features.is_enabled(FeatureFlag::ENABLE_ENUM_TYPES);
let enable_resource_access_control =
features.is_enabled(FeatureFlag::ENABLE_RESOURCE_ACCESS_CONTROL);
let enable_function_values = features.is_enabled(FeatureFlag::ENABLE_FUNCTION_VALUES);

VerifierConfig {
max_loop_depth: Some(5),
Expand All @@ -98,6 +99,7 @@ pub fn aptos_prod_verifier_config(features: &Features) -> VerifierConfig {
sig_checker_v2_fix_script_ty_param_count,
enable_enum_types,
enable_resource_access_control,
enable_function_values,
}
}

Expand Down
9 changes: 9 additions & 0 deletions aptos-move/framework/src/built_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,15 @@ impl BuildOptions {
}
}

pub fn move_2_2() -> Self {
BuildOptions {
bytecode_version: Some(VERSION_7),
language_version: Some(LanguageVersion::V2_2),
compiler_version: Some(CompilerVersion::latest_stable()),
..Self::default()
}
}

pub fn inferred_bytecode_version(&self) -> u32 {
self.language_version
.unwrap_or_default()
Expand Down
4 changes: 4 additions & 0 deletions aptos-move/script-composer/src/decompiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ impl LocalState {
SignatureToken::Vector(s) => {
TypeTag::Vector(Box::new(Self::type_tag_from_sig_token(script, s)?))
},
SignatureToken::Function(..) => {
// TODO(LAMBDA)
bail!("function types not yet implemented for script composer")
},
SignatureToken::Struct(s) => {
let module_handle = script.module_handle_at(script.struct_handle_at(*s).module);
TypeTag::Struct(Box::new(StructTag {
Expand Down
1 change: 1 addition & 0 deletions third_party/move/move-binary-format/src/binary_views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ impl<'a> BinaryIndexedView<'a> {
Vector(ty) => AbilitySet::polymorphic_abilities(AbilitySet::VECTOR, vec![false], vec![
self.abilities(ty, constraints)?,
]),
Function(_, _, abilities) => Ok(*abilities),
Struct(idx) => {
let sh = self.struct_handle_at(*idx);
Ok(sh.abilities)
Expand Down
16 changes: 12 additions & 4 deletions third_party/move/move-binary-format/src/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ impl CompiledScriptBuilder {
sig: &SignatureToken,
) -> PartialVMResult<SignatureToken> {
use SignatureToken::*;
let import_vec =
|s: &mut Self, v: &[SignatureToken]| -> PartialVMResult<Vec<SignatureToken>> {
v.iter()
.map(|sig| s.import_signature_token(module, sig))
.collect::<PartialVMResult<Vec<_>>>()
};
Ok(match sig {
U8 => U8,
U16 => U16,
Expand All @@ -229,13 +235,15 @@ impl CompiledScriptBuilder {
MutableReference(Box::new(self.import_signature_token(module, ty)?))
},
Vector(ty) => Vector(Box::new(self.import_signature_token(module, ty)?)),
Function(args, results, abilities) => Function(
import_vec(self, args)?,
import_vec(self, results)?,
*abilities,
),
Struct(idx) => Struct(self.import_struct(module, *idx)?),
StructInstantiation(idx, inst_tys) => StructInstantiation(
self.import_struct(module, *idx)?,
inst_tys
.iter()
.map(|sig| self.import_signature_token(module, sig))
.collect::<PartialVMResult<Vec<_>>>()?,
import_vec(self, inst_tys)?,
),
})
}
Expand Down
24 changes: 19 additions & 5 deletions third_party/move/move-binary-format/src/check_bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,12 +546,12 @@ impl<'a> BoundsChecker<'a> {
)?;
}
},
Call(idx) => self.check_code_unit_bounds_impl(
Call(idx) | LdFunction(idx) => self.check_code_unit_bounds_impl(
self.view.function_handles(),
*idx,
bytecode_offset,
)?,
CallGeneric(idx) => {
CallGeneric(idx) | LdFunctionGeneric(idx) => {
self.check_code_unit_bounds_impl(
self.view.function_instantiations(),
*idx,
Expand Down Expand Up @@ -657,7 +657,9 @@ impl<'a> BoundsChecker<'a> {
| VecPushBack(idx)
| VecPopBack(idx)
| VecUnpack(idx, _)
| VecSwap(idx) => {
| VecSwap(idx)
| InvokeFunction(idx)
| EarlyBindFunction(idx, _) => {
self.check_code_unit_bounds_impl(
self.view.signatures(),
*idx,
Expand All @@ -683,8 +685,20 @@ impl<'a> BoundsChecker<'a> {

for ty in ty.preorder_traversal() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we rename this ty to tok? It is annoying that we have tags, tokens and types and sometimes even layouts all as types....

match ty {
Bool | U8 | U16 | U32 | U64 | U128 | U256 | Address | Signer | TypeParameter(_)
| Reference(_) | MutableReference(_) | Vector(_) => (),
Bool
| U8
| U16
| U32
| U64
| U128
| U256
| Address
| Signer
| TypeParameter(_)
| Reference(_)
| MutableReference(_)
| Vector(_)
| Function { .. } => (),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be Function(..)?

Struct(idx) => {
check_bounds_impl(self.view.struct_handles(), *idx)?;
if let Some(sh) = self.view.struct_handles().get(idx.into_index()) {
Expand Down
23 changes: 19 additions & 4 deletions third_party/move/move-binary-format/src/check_complexity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,20 @@ impl<'a> BinaryComplexityMeter<'a> {
cost = cost.saturating_add(struct_name.len() as u64 * COST_PER_IDENT_BYTE);
cost = cost.saturating_add(moduel_name.len() as u64 * COST_PER_IDENT_BYTE);
},
U8 | U16 | U32 | U64 | U128 | U256 | Signer | Address | Bool | Vector(_)
| TypeParameter(_) | Reference(_) | MutableReference(_) => (),
U8
| U16
| U32
| U64
| U128
| U256
| Signer
| Address
| Bool
| Vector(_)
| Function { .. }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function(..)?

| TypeParameter(_)
| Reference(_)
| MutableReference(_) => (),
}
}

Expand Down Expand Up @@ -262,7 +274,7 @@ impl<'a> BinaryComplexityMeter<'a> {

for instr in &code.code {
match instr {
CallGeneric(idx) => {
CallGeneric(idx) | LdFunctionGeneric(idx, ..) => {
self.meter_function_instantiation(*idx)?;
},
PackGeneric(idx) | UnpackGeneric(idx) => {
Expand Down Expand Up @@ -291,7 +303,9 @@ impl<'a> BinaryComplexityMeter<'a> {
| VecPushBack(idx)
| VecPopBack(idx)
| VecUnpack(idx, _)
| VecSwap(idx) => {
| VecSwap(idx)
| InvokeFunction(idx)
| EarlyBindFunction(idx, _) => {
self.meter_signature(*idx)?;
},

Expand Down Expand Up @@ -323,6 +337,7 @@ impl<'a> BinaryComplexityMeter<'a> {
| PackVariant(_)
| UnpackVariant(_)
| TestVariant(_)
| LdFunction(_)
| ReadRef
| WriteRef
| FreezeRef
Expand Down
4 changes: 4 additions & 0 deletions third_party/move/move-binary-format/src/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ fn sig_to_ty(sig: &SignatureToken) -> Option<MoveTypeLayout> {
SignatureToken::U128 => Some(MoveTypeLayout::U128),
SignatureToken::U256 => Some(MoveTypeLayout::U256),
SignatureToken::Vector(v) => Some(MoveTypeLayout::Vector(Box::new(sig_to_ty(v.as_ref())?))),
SignatureToken::Function(..) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you return None here (one less TODO)? We do not expect function value constant any time soon. Let's also add a unit test for that?

// TODO(LAMBDA): do we need representation in MoveTypeLayout?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need it here? It's a good question however. MoveTypeLayout is used for ser/de in the VM so it would be dependent on our implementation there?

Copy link
Contributor

@georgemitenkov georgemitenkov Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not need it here (function values cannot be constants), but we do need it otherwise. I have a PR adding it: #15442

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible that we could allow function values as constants in the future. But yeah, maybe not worth it now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, for structs it is also possible but we did not do it yet, so None is a way to go :)

None
},
SignatureToken::Reference(_)
| SignatureToken::MutableReference(_)
| SignatureToken::Struct(_)
Expand Down
76 changes: 75 additions & 1 deletion third_party/move/move-binary-format/src/deserializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ impl Table {
}
}

fn read_u8_internal(cursor: &mut VersionedCursor) -> BinaryLoaderResult<u8> {
cursor.read_u8().map_err(|_| {
PartialVMError::new(StatusCode::MALFORMED).with_message("Unexpected EOF".to_string())
})
}

fn read_u16_internal(cursor: &mut VersionedCursor) -> BinaryLoaderResult<u16> {
let mut u16_bytes = [0; 2];
cursor
Expand Down Expand Up @@ -1131,6 +1137,13 @@ fn load_signature_token(cursor: &mut VersionedCursor) -> BinaryLoaderResult<Sign
arity: usize,
ty_args: Vec<SignatureToken>,
},
Function {
args_arity: u64,
res_arity: u64,
args: Vec<SignatureToken>,
results: Vec<SignatureToken>,
abilities: AbilitySet,
},
}

impl TypeBuilder {
Expand All @@ -1157,6 +1170,37 @@ fn load_signature_token(cursor: &mut VersionedCursor) -> BinaryLoaderResult<Sign
}
}
},
T::Function {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not familiar with part of code, but don't we need to make sure we are V8 bytecode to even see a function? I think cursor is versioned, so you can check if the version is >= 8?

args_arity,
res_arity,
mut args,
mut results,
abilities,
} => {
if args.len() >= args_arity as usize {
results.push(tok);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can results be empty? If res_arity is 0, this seems to push a token nevertheless?

if results.len() >= res_arity as usize {
T::Saturated(SignatureToken::Function(args, results, abilities))
} else {
T::Function {
args_arity,
res_arity,
args,
results,
abilities,
}
}
} else {
args.push(tok);
T::Function {
args_arity,
res_arity,
args,
results,
abilities,
}
}
},
_ => unreachable!("invalid type constructor application"),
}
}
Expand Down Expand Up @@ -1223,6 +1267,19 @@ fn load_signature_token(cursor: &mut VersionedCursor) -> BinaryLoaderResult<Sign
let idx = load_type_parameter_index(cursor)?;
T::Saturated(SignatureToken::TypeParameter(idx))
},
S::FUNCTION => {
let args_arity = load_signature_size(cursor)?;
let res_arity = load_signature_size(cursor)?;
let abilities =
load_ability_set(cursor, AbilitySetPosition::FunctionValueType)?;
T::Function {
args_arity,
res_arity,
abilities,
args: vec![],
results: vec![],
}
},
})
} else {
Err(PartialVMError::new(StatusCode::MALFORMED)
Expand Down Expand Up @@ -1257,6 +1314,7 @@ enum AbilitySetPosition {
FunctionTypeParameters,
StructTypeParameters,
StructHandle,
FunctionValueType,
}

fn load_ability_set(
Expand Down Expand Up @@ -1306,11 +1364,17 @@ fn load_ability_set(
DeprecatedKind::RESOURCE => AbilitySet::EMPTY | Ability::Key,
};
Ok(match pos {
AbilitySetPosition::StructHandle => unreachable!(),
AbilitySetPosition::StructHandle | AbilitySetPosition::FunctionValueType => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why these are unreachable? Adding a message would help.

unreachable!()
},
AbilitySetPosition::FunctionTypeParameters => set | Ability::Store,
AbilitySetPosition::StructTypeParameters => set,
})
},
AbilitySetPosition::FunctionValueType => {
// This is a new type, shouldn't show up here.
Err(PartialVMError::new(StatusCode::UNKNOWN_ABILITY))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But above we use unreachable? Should we use an error for safety there as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a message? e.g., "Unexpected function value type abilities" so we can debug? If this is unreachable, maybe invariant violation status is more appropriate?

},
}
} else {
// The uleb here doesn't really do anything as it is bounded currently to 0xF, but the
Expand Down Expand Up @@ -1814,6 +1878,16 @@ fn load_code(cursor: &mut VersionedCursor, code: &mut Vec<Bytecode>) -> BinaryLo
Opcodes::CAST_U16 => Bytecode::CastU16,
Opcodes::CAST_U32 => Bytecode::CastU32,
Opcodes::CAST_U256 => Bytecode::CastU256,

Opcodes::LD_FUNCTION => Bytecode::LdFunction(load_function_handle_index(cursor)?),
Opcodes::LD_FUNCTION_GENERIC => {
Bytecode::LdFunctionGeneric(load_function_inst_index(cursor)?)
},
Opcodes::INVOKE_FUNCTION => Bytecode::InvokeFunction(load_signature_index(cursor)?),
Opcodes::EARLY_BIND_FUNCTION => Bytecode::EarlyBindFunction(
load_signature_index(cursor)?,
read_u8_internal(cursor)?,
),
};
code.push(bytecode);
}
Expand Down
Loading
Loading