Skip to content

Commit

Permalink
lambda-compiler-completion
Browse files Browse the repository at this point in the history
  • Loading branch information
brmataptos committed Nov 22, 2024
1 parent 9b3a2af commit 3e3563e
Show file tree
Hide file tree
Showing 32 changed files with 6,804 additions and 401 deletions.
40 changes: 34 additions & 6 deletions third_party/move/move-compiler-v2/src/bytecode_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Parts of the project are originally copyright © Meta Platforms, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::{experiments::Experiment, Options};
use codespan_reporting::diagnostic::Severity;
use ethnum::U256;
use itertools::Itertools;
Expand Down Expand Up @@ -324,6 +325,14 @@ impl<'env> Generator<'env> {
let loc = env.get_node_loc(id);
env.diag(severity, &loc, msg.as_ref())
}

fn check_if_lambdas_enabled(&self) -> bool {
let options = self
.env()
.get_extension::<Options>()
.expect("Options is available");
options.experiment_on(Experiment::LAMBDA_VALUES)
}
}

// ======================================================================================
Expand Down Expand Up @@ -480,14 +489,23 @@ 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",
if self.check_if_lambdas_enabled() {
"Function-typed values not yet implemented except as parameters to calls to inline functions"
} else {
"Function-typed values not yet supported except as parameters to calls to inline functions"
}
),
// TODO(LAMBDA)
ExpData::Invoke(id, _exp, _) => self.error(
*id,
"Calls to function values other than inline function parameters not yet supported",
if self.check_if_lambdas_enabled() {
"Calls to function values other than inline function parameters not yet implemented"
} else {
"Calls to function values other than inline function parameters not yet supported"
}
),
ExpData::Quant(id, _, _, _, _, _) => {
self.internal_error(*id, "unsupported specification construct")
Expand Down Expand Up @@ -564,10 +582,16 @@ impl<'env> Generator<'env> {
Constant::Bool(false)
}
},
// TODO(LAMBDA)
Value::Function(_mid, _fid) => {
self.error(
id,
"Function-typed values not yet supported except as parameters to calls to inline functions");
if self.check_if_lambdas_enabled() {
"Function-typed values not yet implemented except as parameters to calls to inline functions"
} else {
"Function-typed values not yet supported except as parameters to calls to inline functions"
}
);
Constant::Bool(false)
},
}
Expand Down Expand Up @@ -792,9 +816,13 @@ impl<'env> Generator<'env> {
self.gen_function_call(targets, id, m.qualified(*f), args)
},
// TODO(LAMBDA)
Operation::Bind(_mask) => self.error(
Operation::EarlyBind(_mask) => self.error(
id,
"Function-typed values not yet supported except as parameters to calls to inline functions",
if self.check_if_lambdas_enabled() {
"Function-typed values not yet implemented except as parameters to calls to inline functions"
} else {
"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
68 changes: 51 additions & 17 deletions third_party/move/move-compiler-v2/src/env_pipeline/lambda_lifter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,18 @@
//! Also, lambda lifting is currently restricted to only lift lambdas which do
//! not modify free variables.
//!
//! Lambda lifting rewrites lambda expressions into construction
//! of *closures*. A closure refers to a function and contains a partial list
//! of arguments for that function, essentially currying it. We use the
//! `Bind` operation to construct a closure from a function and set of arguemnts,
//! qualified by a mask which allows early-bound arguments to be anywhere in
//! the function argument list.
//! Lambda lifting rewrites lambda expressions into construction of *closures* using the `EarlyBind`
//! operation. A closure refers to a function and contains a partial list of arguments for that
//! function, essentially currying it. We use the `EarlyBind` operation to construct a closure from
//! a function and set of arguemnts, qualified by a mask which allows early-bound arguments to be
//! anywhere in the function argument list.
//!
//! ```ignore
//! let c = 1;
//! vec.any(|x| x > c)
//! ==>
//! let c = 1;
//! vec.any(Bind(0x10u64)(lifted, c))
//! vec.any(EarlyBind(0x10u64)(lifted, c))
//! where
//! fun lifted(c: u64, x: u64): bool { x > c }
//! ```
Expand All @@ -37,7 +36,7 @@
//! vec.any(|S{x}| x > c)
//! ==>
//! let c = 1;
//! vec.any(Bind(0x10)(lifted, c))
//! vec.any(EarlyBind(0x10)(lifted, c))
//! where
//! fun lifted(c: u64, arg$2: S): bool { let S{x} = arg$2; x > y }
//! ```
Expand Down Expand Up @@ -205,7 +204,9 @@ impl<'a> LambdaLifter<'a> {
/// - `closure_args` = corresponding expressions to provide as actual arg for each param
/// - `param_index_mapping` = for each free var which is a Parameter from the enclosing function,
/// a mapping from index there to index in the params list
fn get_params_for_freevars(&mut self) -> (Vec<Parameter>, Vec<Exp>, BTreeMap<usize, usize>) {
fn get_params_for_freevars(
&mut self,
) -> Option<(Vec<Parameter>, Vec<Exp>, BTreeMap<usize, usize>)> {
let env = self.fun_env.module_env.env;
let mut closure_args = vec![];

Expand All @@ -214,6 +215,9 @@ impl<'a> LambdaLifter<'a> {
// functions (courtesy of #12317)
let mut param_index_mapping = BTreeMap::new();
let mut params = vec![];
let ty_params = self.fun_env.get_type_parameters_ref();
let ability_inferer = AbilityInferer::new(env, ty_params);
let mut saw_error = false;

for (used_param_count, (param, var_info)) in
mem::take(&mut self.free_params).into_iter().enumerate()
Expand All @@ -230,6 +234,18 @@ impl<'a> LambdaLifter<'a> {
name.display(env.symbol_pool())
),
);
saw_error = true;
}
let param_abilities = ability_inferer.infer_abilities(&ty).1;
if !param_abilities.has_copy() {
env.error(
&loc,
&format!(
"captured variable `{}` must have a value with `copy` ability", // TODO(LAMBDA)
name.display(env.symbol_pool())
),
);
saw_error = true;
}
params.push(Parameter(name, ty.clone(), loc.clone()));
let new_id = env.new_node(loc, ty);
Expand All @@ -253,6 +269,7 @@ impl<'a> LambdaLifter<'a> {
name.display(env.symbol_pool())
),
);
saw_error = true;
}
params.push(Parameter(name, ty.clone(), loc.clone()));
let new_id = env.new_node(loc, ty);
Expand All @@ -262,7 +279,11 @@ impl<'a> LambdaLifter<'a> {
closure_args.push(ExpData::LocalVar(new_id, name).into_exp())
}

(params, closure_args, param_index_mapping)
if !saw_error {
Some((params, closure_args, param_index_mapping))
} else {
None
}
}

fn get_arg_if_simple(arg: &Exp) -> Option<&Exp> {
Expand All @@ -288,7 +309,7 @@ impl<'a> LambdaLifter<'a> {
.filter_map(|exp| Self::get_arg_if_simple(exp))
.collect();
if result.len() == args.len() && result.len() <= 64 {
// Curry/Bind bitmask is only a u64, so we limit to 64 args here.
// EarlyBind bitmask is only a u64, so we limit to 64 args here.
Some(result)
} else {
None
Expand All @@ -299,15 +320,15 @@ impl<'a> LambdaLifter<'a> {
use ExpData::*;
match fn_exp.as_ref() {
Value(_, ast::Value::Function(..)) => Some(fn_exp.clone()),
Call(id, Operation::Bind(mask), args) => {
Call(id, Operation::EarlyBind(mask), args) => {
let mut args_iter = args.iter();
args_iter.next().and_then(|fn_exp| {
let fn_args: Vec<_> = args_iter.cloned().collect();
Self::get_args_if_simple(&fn_args).map(|simp_args| {
let mut bind_args: Vec<_> =
simp_args.iter().map(|expref| (*expref).clone()).collect();
bind_args.insert(0, fn_exp.clone());
ExpData::Call(*id, Operation::Bind(*mask), bind_args).into_exp()
ExpData::Call(*id, Operation::EarlyBind(*mask), bind_args).into_exp()
})
})
},
Expand Down Expand Up @@ -391,7 +412,7 @@ impl<'a> LambdaLifter<'a> {
match body.as_ref() {
Call(id, oper, args) => {
match oper {
Operation::Bind(_mask) => {
Operation::EarlyBind(_mask) => {
// TODO(LAMBDA): We may be able to do something with this,
// but skip for now because it will be complicated.
None
Expand Down Expand Up @@ -549,7 +570,7 @@ impl<'a> LambdaLifter<'a> {
bound_value_missing_abilities);
return None;
};
Some(ExpData::Call(id, Operation::Bind(mask), new_args).into_exp())
Some(ExpData::Call(id, Operation::EarlyBind(mask), new_args).into_exp())
} else {
None
}
Expand Down Expand Up @@ -690,7 +711,11 @@ impl<'a> ExpRewriterFunctions for LambdaLifter<'a> {
// param_index_mapping = for each free var which is a Parameter from the enclosing function,
// a mapping from index there to index in the params list; other free vars are
// substituted automatically by using the same symbol for the param
let (mut params, mut closure_args, param_index_mapping) = self.get_params_for_freevars();
let Some((mut params, mut closure_args, param_index_mapping)) =
self.get_params_for_freevars()
else {
return None;
};

// Some(ExpData::Invalid(env.clone_node(id)).into_exp());
// Add lambda args. For dealing with patterns in lambdas (`|S{..}|e`) we need
Expand Down Expand Up @@ -753,6 +778,15 @@ impl<'a> ExpRewriterFunctions for LambdaLifter<'a> {
let body = ExpRewriter::new(env, &mut replacer).rewrite_exp(body.clone());
let fun_id = FunId::new(fun_name);
let params_types = params.iter().map(|param| param.get_type()).collect();
if abilities.has_store() {
let loc = env.get_node_loc(id);
env.error(
&loc,
// TODO(LAMBDA)
"Lambdas expressions with `store` ability currently may only be a simple call to an existing `public` function. This lambda expression requires defining a `public` helper function, which might affect module upgradeability and is not yet supported."
);
return None;
};
self.lifted.push(ClosureFunction {
loc: lambda_loc.clone(),
fun_id,
Expand Down Expand Up @@ -790,7 +824,7 @@ impl<'a> ExpRewriterFunctions for LambdaLifter<'a> {
env.set_node_instantiation(id, inst);
}
closure_args.insert(0, fn_exp);
Some(ExpData::Call(id, Operation::Bind(bitmask), closure_args).into_exp())
Some(ExpData::Call(id, Operation::EarlyBind(bitmask), closure_args).into_exp())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ impl ModuleGenerator {
ctx.error(
loc,
format!(
"Unexpected type: {}",
"Unimplemented type: {}",
ty.display(&ctx.env.get_type_display_ctx())
),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

Diagnostics:
warning: Unused parameter `f`. Consider removing or prefixing with an underscore: `_f`
┌─ tests/checking/inlining/function_name_shadowing.move:8:28
8 │ public inline fun quux(f:|u64, u64|u64, a: u64, b: u64): u64 {
│ ^

// -- Model dump before bytecode pipeline
module 0x42::Test {
public fun f(a: u64,b: u64): u64 {
Mul<u64>(a, b)
}
public inline fun quux(f: |(u64, u64)|u64,a: u64,b: u64): u64 {
Test::f(a, b)
}
public fun test_shadowing(): u64 {
Test::f(10, 2)
}
} // end 0x42::Test

// -- Sourcified model before bytecode pipeline
module 0x42::Test {
public fun f(a: u64, b: u64): u64 {
a * b
}
public inline fun quux(f: |(u64, u64)|u64, a: u64, b: u64): u64 {
f(a, b)
}
public fun test_shadowing(): u64 {
f(10, 2)
}
}


============ bytecode verification succeeded ========
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//# publish
module 0x42::Test {

public fun f(a: u64, b: u64): u64 {
a * b
}

public inline fun quux(f:|u64, u64|u64, a: u64, b: u64): u64 {
f(a, b)
}

public fun test_shadowing(): u64 {
quux(|a, b| a - b, 10, 2)
}
}

//# run 0x42::Test::test_shadowing
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,3 @@ error: tuple type `()` is not allowed as a type argument (type was inferred)
│ ^
= required by instantiating type parameter `T` of function `foreach`

error: function type `|u64|u64` is not allowed as a field type
┌─ tests/checking/typing/lambda.move:81:12
81 │ f: |u64|u64, // expected lambda not allowed
│ ^^^^^^^^
= required by declaration of field `f`
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,3 @@ error: cannot pass `|&u64|u64 with copy+drop+store` to a function which expects
73 │ foreach(&v, |e: &u64| { sum = sum + *e; *e }) // expected to have wrong result type of lambda
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: function type `|u64|u64` is not allowed as a field type
┌─ tests/checking/typing/lambda_typed.move:81:12
81 │ f: |u64|u64, // expected lambda not allowed
│ ^^^^^^^^
= required by declaration of field `f`
Original file line number Diff line number Diff line change
Expand Up @@ -532,13 +532,13 @@ module 0x8675309::M {


Diagnostics:
error: Calls to function values other than inline function parameters not yet supported
error: Calls to function values other than inline function parameters not yet implemented
┌─ tests/lambda/inline-parity/subtype_args.move:24:9
24 │ f(&mut 0, &mut 0);
│ ^^^^^^^^^^^^^^^^^

error: Calls to function values other than inline function parameters not yet supported
error: Calls to function values other than inline function parameters not yet implemented
┌─ tests/lambda/inline-parity/subtype_args.move:25:9
25 │ f(&0, &mut 0);
Expand Down
8 changes: 0 additions & 8 deletions third_party/move/move-compiler-v2/tests/lambda/lambda.exp
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,3 @@ error: tuple type `()` is not allowed as a type argument (type was inferred)
│ ^
= required by instantiating type parameter `T` of function `foreach`

error: function type `|u64|u64` is not allowed as a field type
┌─ tests/lambda/lambda.move:81:12
81 │ f: |u64|u64, // expected lambda not allowed
│ ^^^^^^^^
= required by declaration of field `f`
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,3 @@ error: tuple type `()` is not allowed as a type argument (type was inferred)
│ ^
= required by instantiating type parameter `T` of function `foreach`

error: function type `|u64|u64` is not allowed as a field type
┌─ tests/lambda/lambda.move:81:12
81 │ f: |u64|u64, // expected lambda not allowed
│ ^^^^^^^^
= required by declaration of field `f`
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public fun M::fun_result_lambda_not_allowed(): |u64| {


Diagnostics:
error: Unexpected type: |u64|
error: Unimplemented type: |u64|
┌─ tests/lambda/lambda4.move:89:16
89 │ public fun fun_result_lambda_not_allowed(): |u64| { // expected lambda not allowed
Expand Down
Loading

0 comments on commit 3e3563e

Please sign in to comment.