Skip to content

Commit

Permalink
[move-compiler-v2] add parser code for lambda types (#14792)
Browse files Browse the repository at this point in the history
Add parser code for function values and non-inline lambdas

 Extend syntax
 - for lambda: `move |args| body with copy+store`
- Add function types: `|T1, T2| T3 with copy+store`
- curry construction:
    - from lambda: `|a, b| f(x, 3, a, b)`
    - `EarlyBind` operation takes a function value and values for any number of leading parameters.
- arbitrary function calls:
    - `(|x, y| x + y)(2, 3)`
- suppress printing common abilities (all functions have drop)

Modify exp_builder and lambda_lifter to generate function values, model to track "used" functions in addition to "called" functions.

Attaches an `AbilitySet` to `Type::Fun` and `Exp::Lambda` based on
source.  Adds a new `ExpCall` operation in parser/expansion ASTs to be
able to carry more generalized function calls through to move-model,
which already can support this through `Invoke`, which previously was
underutilized.  Added basic type checking for function abilities.

Added more lambda tests under `move-compiler-v2/tests/lambda/` which
are run "with" and "without" lambda features enabled.  Currently, many
things pass through to hit "not yet implemented" errors in bytecode
gen, etc.

Tricky features are:
- In some cases, abilities may not be inferred well yet, so examples may need to be over-annotated with abilities.
- in `ty.rs`: unification checks `Fun` abilities as well as other things.
- in `lambda_lifter.rs`, we (1) reject lambdas without `move` free-var handling, (2) try to reduce lambda to curry, by checking for a simple function call with simple args, the last of which are identical to the lambda parameters.
  • Loading branch information
brmataptos authored Dec 1, 2024
1 parent 1e926ed commit 5d87d94
Show file tree
Hide file tree
Showing 147 changed files with 5,915 additions and 5,496 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion third_party/move/evm/move-to-yul/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@ impl<'a> Context<'a> {
Tuple(_)
| TypeParameter(_)
| Reference(_, _)
| Fun(_, _)
| Fun(..)
| TypeDomain(_)
| ResourceDomain(_, _, _)
| Error
Expand Down
2 changes: 1 addition & 1 deletion third_party/move/evm/move-to-yul/src/solidity_ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ impl SolidityType {
},
TypeParameter(_)
| Reference(_, _)
| Fun(_, _)
| Fun(..)
| TypeDomain(_)
| ResourceDomain(_, _, _)
| Error
Expand Down
10 changes: 9 additions & 1 deletion third_party/move/move-binary-format/src/file_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -851,11 +851,19 @@ impl AbilitySet {
);
/// The empty ability set
pub const EMPTY: Self = Self(0);
/// Abilities for `Functions`
/// Minimal abilities for all `Functions`
pub const FUNCTIONS: AbilitySet = Self(Ability::Drop as u8);
/// Maximal abilities for all `Functions`. This is used for identity when unifying function types.
pub const MAXIMAL_FUNCTIONS: AbilitySet = Self::PUBLIC_FUNCTIONS;
/// Abilities for `Bool`, `U8`, `U64`, `U128`, and `Address`
pub const PRIMITIVES: AbilitySet =
Self((Ability::Copy as u8) | (Ability::Drop as u8) | (Ability::Store as u8));
/// Abilities for `private` user-defined/"primitive" functions (not closures).
/// These can be be changed in module upgrades, so should not be stored
pub const PRIVATE_FUNCTIONS: AbilitySet = Self((Ability::Copy as u8) | (Ability::Drop as u8));
/// Abilities for `public` user-defined/"primitive" functions (not closures)
pub const PUBLIC_FUNCTIONS: AbilitySet =
Self((Ability::Copy as u8) | (Ability::Drop as u8) | (Ability::Store as u8));
/// Abilities for `Reference` and `MutableReference`
pub const REFERENCES: AbilitySet = Self((Ability::Copy as u8) | (Ability::Drop as u8));
/// Abilities for `Signer`
Expand Down
23 changes: 14 additions & 9 deletions third_party/move/move-compiler-v2/src/bytecode_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,13 +480,13 @@ impl<'env> Generator<'env> {
self.emit_with(*id, |attr| Bytecode::SpecBlock(attr, spec));
},
// TODO(LAMBDA)
ExpData::Lambda(id, _, _) => self.error(
ExpData::Lambda(id, _, _, _, _) => self.error(
*id,
"Function-typed values not yet supported except as parameters to calls to inline functions",
),
// TODO(LAMBDA)
ExpData::Invoke(_, exp, _) => self.error(
exp.as_ref().node_id(),
ExpData::Invoke(id, _exp, _) => self.error(
*id,
"Calls to function values other than inline function parameters not yet supported",
),
ExpData::Quant(id, _, _, _, _, _) => {
Expand Down Expand Up @@ -564,6 +564,12 @@ impl<'env> Generator<'env> {
Constant::Bool(false)
}
},
Value::Function(_mid, _fid) => {
self.error(
id,
"Function-typed values not yet supported except as parameters to calls to inline functions");
Constant::Bool(false)
},
}
}
}
Expand Down Expand Up @@ -785,6 +791,11 @@ impl<'env> Generator<'env> {
Operation::MoveFunction(m, f) => {
self.gen_function_call(targets, id, m.qualified(*f), args)
},
// TODO(LAMBDA)
Operation::EarlyBind => self.error(
id,
"Function-typed values not yet supported except as parameters to calls to inline functions",
),
Operation::TestVariants(mid, sid, variants) => {
self.gen_test_variants(targets, id, mid.qualified(*sid), variants, args)
},
Expand Down Expand Up @@ -813,12 +824,6 @@ impl<'env> Generator<'env> {

Operation::NoOp => {}, // do nothing

// TODO(LAMBDA)
Operation::Closure(..) => self.error(
id,
"Function-typed values not yet supported except as parameters to calls to inline functions",
),

// Non-supported specification related operations
Operation::Exists(Some(_))
| Operation::SpecFunction(_, _, _)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ fn find_possibly_modified_vars(
_ => {},
}
},
Lambda(node_id, pat, _) => {
Lambda(node_id, pat, _, _, _) => {
// Define a new scope for bound vars, and turn off `modifying` within.
match pos {
VisitorPosition::Pre => {
Expand Down Expand Up @@ -978,7 +978,8 @@ impl<'env> ExpRewriterFunctions for SimplifierRewriter<'env> {
let ability_set = self
.env()
.type_abilities(&ty, self.func_env.get_type_parameters_ref());
ability_set.has_ability(Ability::Drop)
// Don't drop a function-valued expression so we don't lose errors.
!ty.has_function() && ability_set.has_ability(Ability::Drop)
} else {
// We're missing type info, be conservative
false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ impl<'env, 'params> SymbolVisitor<'env, 'params> {
Pre | Post | BeforeBody | MidMutate | BeforeThen | BeforeElse
| PreSequenceValue => {},
},
Lambda(_, pat, _) => {
Lambda(_, pat, _, _, _) => {
match position {
Pre => self.seen_uses.enter_scope(),
Post => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use move_model::{
model::{FunId, FunctionEnv, GlobalEnv, Loc, ModuleEnv, NodeId, Parameter, QualifiedId},
ty::Type,
};
use std::{collections::BTreeSet, iter::Iterator, ops::Deref, vec::Vec};
use std::{collections::BTreeSet, iter::Iterator, vec::Vec};

type QualifiedFunId = QualifiedId<FunId>;

Expand All @@ -20,8 +20,8 @@ fn identify_function_types_with_functions_in_args(func_types: Vec<Type>) -> Vec<
func_types
.into_iter()
.filter_map(|ty| {
if let Type::Fun(argt, _) = &ty {
if argt.deref().has_function() {
if let Type::Fun(args, _, _) = &ty {
if args.as_ref().has_function() {
Some(ty)
} else {
None
Expand All @@ -41,8 +41,8 @@ fn identify_function_typed_params_with_functions_in_rets(
func_types
.iter()
.filter_map(|param| {
if let Type::Fun(_argt, rest) = &param.1 {
let rest_unboxed = rest.deref();
if let Type::Fun(_args, result, _) = &param.1 {
let rest_unboxed = result.as_ref();
if rest_unboxed.has_function() {
Some((*param, rest_unboxed))
} else {
Expand Down Expand Up @@ -270,7 +270,7 @@ fn check_privileged_operations_on_structs(env: &GlobalEnv, fun_env: &FunctionEnv
},
ExpData::Assign(_, pat, _)
| ExpData::Block(_, pat, _, _)
| ExpData::Lambda(_, pat, _) => {
| ExpData::Lambda(_, pat, _, _, _) => {
pat.visit_pre_post(&mut |_, pat| {
if let Pattern::Struct(id, str, _, _) = pat {
let module_id = str.module_id;
Expand Down Expand Up @@ -344,7 +344,7 @@ pub fn check_access_and_use(env: &mut GlobalEnv, before_inlining: bool) {

// Check that functions being called are accessible.
if let Some(def) = caller_func.get_def() {
let callees_with_sites = def.called_funs_with_callsites();
let callees_with_sites = def.used_funs_with_uses();
for (callee, sites) in &callees_with_sites {
let callee_func = env.get_function(*callee);
// Check visibility.
Expand Down
4 changes: 2 additions & 2 deletions third_party/move/move-compiler-v2/src/env_pipeline/inliner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pub fn run_inlining(env: &mut GlobalEnv, scope: RewritingScope, keep_inline_func
let mut visited_targets = BTreeSet::new();
while let Some(target) = todo.pop_first() {
if visited_targets.insert(target.clone()) {
let callees_with_sites = target.called_funs_with_call_sites(env);
let callees_with_sites = target.used_funs_with_uses(env);
for (callee, sites) in callees_with_sites {
todo.insert(RewriteTarget::MoveFun(callee));
targets.entry(RewriteTarget::MoveFun(callee));
Expand Down Expand Up @@ -1161,7 +1161,7 @@ impl<'env, 'rewriter> ExpRewriterFunctions for InlinedRewriter<'env, 'rewriter>
};
let call_loc = self.env.get_node_loc(id);
if let Some(lambda_target) = optional_lambda_target {
if let ExpData::Lambda(_, pat, body) = lambda_target.as_ref() {
if let ExpData::Lambda(_, pat, body, _, _) = lambda_target.as_ref() {
let args_vec: Vec<Exp> = args.to_vec();
Some(InlinedRewriter::construct_inlined_call_expression(
self.env,
Expand Down
Loading

0 comments on commit 5d87d94

Please sign in to comment.