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

Closure types #195

Closed
7 tasks done
philberty opened this issue Jan 27, 2021 · 10 comments · Fixed by #1611
Closed
7 tasks done

Closure types #195

philberty opened this issue Jan 27, 2021 · 10 comments · Fixed by #1611
Assignees

Comments

@philberty
Copy link
Member

philberty commented Jan 27, 2021

https://doc.rust-lang.org/reference/types/closure.html
https://doc.rust-lang.org/rust-by-example/fn/closures.html
https://doc.rust-lang.org/nomicon/hrtb.html

I think there is stuff in GCC we can take inspiration from the C++ implementation of closures for example:

int test() {
    int capture = 456;
    auto a = [&](int a) -> int {
        return a + capture;
    };
    return a(123);
}

will give us

int test ()
{
  int D.2400;
  int capture;
  struct ._anon_0 a;
  typedef struct ._anon_0 ._anon_0;

  try
    {
      capture = 456;
      a.__capture = &capture;
      D.2400 = test()::<lambda(int)>::operator() (&a, 123);
      return D.2400;
    }
  finally
    {
      capture = {CLOBBER};
      a = {CLOBBER};
    }
}

int test()::<lambda(int)>::operator() (const struct ._anon_0 * const __closure, int a)
{
  int D.2403;
  int & capture [value-expr: __closure->__capture];

  _1 = __closure->__capture;
  _2 = *_1;
  D.2403 = a + _2;
  return D.2403;
}

The task list breaks down into:

I am currently blocked on this until we get lang-item support into the compiler from my operator overloading branch,

@bjorn3
Copy link

bjorn3 commented Jan 27, 2021

Closures are interesting. They get a separate MIR body and DefId in rustc, but typechecking handles them as part of the parent function.

@philberty
Copy link
Member Author

Useful comment from @dkm #523 (comment)

@bjorn3
Copy link

bjorn3 commented Jun 24, 2021

Maybe similar to the GNU extension for nested function in C https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html

No, nested functions in C require the stack to be executable to put a trampoline. In addition such nested functions are not allowed to escape the current stack frame. In Rust closures are conceptually implemented like the following:

fn foo() {
    let a = 0;
    let b = || a;
    b();
}

// -->

fn foo() {
    struct MyClosure<'a> {
        a: &i32,
    }
    impl<'a> Fn<()> for MyClosure<'a> {
        type Output = i32;
        extern "rust-call" fn call(&self, args: ()) -> i32 {
            *self.a
        }
    }
    let a = 0;
    let b = MyClosure { a: &a };
    b();
}

The "rust-call" abi here is a workaround for the lack of variadic generics in rust. It is basically equivalent to the default "Rust" abi except that the last argument must be a tuple and this tuple will be passed as if every element was listed as argument individually. So eg "extern "rust-call" fn foo(&self, args: (i32, i64)) is equivalent to fn foo(&self, arg1: i32, arg2: i64).

@dkm
Copy link
Member

dkm commented Jun 24, 2021

Not sure the requirement for executable stack still holds for all targets as described in https://gcc.gnu.org/onlinedocs/gccint/Trampolines.html

What do you mean by "not allowed to escape current stack frame"? I think you can take a pointer to the nested function and use it in a function call, also shown in the previous link :

hack (int *array, int size)
{
  void store (int index, int value)
    { array[index] = value; }

  intermediate (store, size);
}

Maybe I don't understand your point?

@bjorn3
Copy link

bjorn3 commented Jun 24, 2021

For example

fn foo() -> impl FnMut() -> i32 {
    let mut bar = 0;
    move || { bar += 1; bar }
}

is valid. bar gets moved into the closure and then the closure gets returned. The closure escapes the foo function.

@philberty
Copy link
Member Author

philberty commented Jun 24, 2021

Yeah this kind of thing is supported in C++ with std::function and in go. It will be interesting to disect the implementation used from those

@dkm
Copy link
Member

dkm commented Jun 24, 2021

For example

fn foo() -> impl FnMut() -> i32 {
    let mut bar = 0;
    move || { bar += 1; bar }
}

is valid. bar gets moved into the closure and then the closure gets returned. The closure escapes the foo function.

Ok, the closure can be used even after the enclosing function has exited, didn't know that...

@philberty
Copy link
Member Author

I could be wrong but i remember reading about go and from my experience with go when you define a closure, it seemed to take a copy of any enclosed names at that time and make them implicit parameters to that closure when it is defined. It caused a few subtle bugs in the product I was working on.

@bjorn3
Copy link

bjorn3 commented Jun 24, 2021

If you don't use move Rust will take references to all captured variables. If you do use move all captured variables are moved into the closure and inaccessible outside if they are not Copy.

Yeah this kind of thing is supported in C++ with std::function and in go. It will be interesting to disect the implementation used from those

Unlike in Rust these have to use heap allocation I think to store all captures. In Rust every closure has a distinct type with a size depending on the captured variables. In addition if all captured variables implement Copy, so does the closure. Same for Clone.

@philberty philberty self-assigned this Sep 30, 2021
@philberty
Copy link
Member Author

This is a useful testcase from rust-lang/rust#45510:

#![feature(fn_traits)] 
#![feature(unboxed_closures)]

struct Ishmael;
struct Maybe;
struct CallMe;

impl FnOnce<(Ishmael,)> for CallMe {
    type Output = ();
    extern "rust-call" fn call_once(self, _args: (Ishmael,)) -> () {
        println!("Split your lungs with blood and thunder!");
    }
}

impl FnOnce<(Maybe,)> for CallMe {
    type Output = ();
    extern "rust-call" fn call_once(self, _args: (Maybe,)) -> () {
        println!("So we just met, and this is crazy");
    }
}

fn main() {
    CallMe(Ishmael);
    CallMe(Maybe);
}

philberty added a commit that referenced this issue Oct 14, 2021
This adds all the nesecary boiler plate to introduce the ClosureType. More
work is needed to be make this actually useable for type resolution.

Addresses #195
bors bot added a commit that referenced this issue Oct 15, 2021
740: Add boiler plate for TyTy::ClosureType r=philberty a=philberty

This simply adds in the boilerplate for closure types for type-checking.
The closure branch is blocked until we get lang-item support in next.

Addresses #195


Co-authored-by: Philip Herron <philip.herron@embecosm.com>
@philberty philberty removed this from the Control Flow 2 - Pattern Matching milestone Nov 26, 2021
@philberty philberty removed their assignment Feb 7, 2022
@philberty philberty removed the blocked label Oct 14, 2022
@philberty philberty self-assigned this Oct 14, 2022
@philberty philberty moved this to Additional sprint items in libcore 1.49 Oct 14, 2022
dkm pushed a commit that referenced this issue Dec 28, 2022
When we have a closure expression that captures a parent function's
variable we must setup the closure data to contain this. Ignoring
moveability and mutability requires for now, this patch creates the closure
structure with fields for each of the captured variables. When it comes to
compilation of the closure expression in order to support nested closures
we must setup a context of implicit mappings so that for all path
resolution we hit this implicit closure mappings lookups code before any
lookup_var_decl as this decl will not exist so the order here is important
during path resolution which is a similar problem to match expression
destructuring.

Fixes #195
dkm pushed a commit that referenced this issue Jan 2, 2023
When we have a closure block referencing variables in a parent function,
we must track what these are. We do this by having a context of closures
so if we have a variable reference and its declared in a rib whose node id
is less than the node id of the closure's node id we know it must be a
captured variable. We also need to iterate all possible closure contexts
as we might be in the case of a nested closure.

Addresses #195
dkm pushed a commit that referenced this issue Jan 2, 2023
When we have a closure expression that captures a parent function's
variable we must setup the closure data to contain this. Ignoring
moveability and mutability requires for now, this patch creates the closure
structure with fields for each of the captured variables. When it comes to
compilation of the closure expression in order to support nested closures
we must setup a context of implicit mappings so that for all path
resolution we hit this implicit closure mappings lookups code before any
lookup_var_decl as this decl will not exist so the order here is important
during path resolution which is a similar problem to match expression
destructuring.

Fixes #195
dkm pushed a commit that referenced this issue Jan 6, 2023
This patch adds a proxy class ItemWrapper to be a proxy allowing us to use
the same code paths for attribute handling as we have with normal items. We
need this so we can grab the fn trait associated type lang item's. Which
are being missed currently.

Addresses #195
dkm pushed a commit that referenced this issue Jan 6, 2023
In the AST we have ClosureExprInner and ClosureExprInnerTyped the first
is the closure expression of the form:

    let closure_inferred  = |i| i + 1;

The second is of the form:

    let closure_annotated = |i: i32| -> i32 { i + 1 };

Both of these can be seguared into a single HIR::ClosureExpr with an
optional return type and parameter types.

Addresses #195
dkm pushed a commit that referenced this issue Jan 6, 2023
This adds the type checking for a HIR::ClosureExpr which specifies a
TyTy::ClosureType whih inherits the FnOnce trait by default. The
specialisation of the trait bound needs to be determined by the the
mutability and argument capture and moveablity rules.

The CallExpr is amended here so that we support CallExpr's for all
receivers that implement any of the FnTraits. This means closures and
generics that have the relevant type bound of FnTraits we get the same path
of type checking.

Addresses #195
dkm pushed a commit that referenced this issue Jan 6, 2023
Closures's need to generate their specific function and setup their
argument passing based on the signiture specified in libcore. We can get
this information based on the specified bound on the closure.

Addresses #195
dkm pushed a commit that referenced this issue Jan 6, 2023
dkm pushed a commit that referenced this issue Jan 6, 2023
When we have a closure block referencing variables in a parent function,
we must track what these are. We do this by having a context of closures
so if we have a variable reference and its declared in a rib whose node id
is less than the node id of the closure's node id we know it must be a
captured variable. We also need to iterate all possible closure contexts
as we might be in the case of a nested closure.

Addresses #195
dkm pushed a commit that referenced this issue Jan 6, 2023
When we have a closure expression that captures a parent function's
variable we must setup the closure data to contain this. Ignoring
moveability and mutability requires for now, this patch creates the closure
structure with fields for each of the captured variables. When it comes to
compilation of the closure expression in order to support nested closures
we must setup a context of implicit mappings so that for all path
resolution we hit this implicit closure mappings lookups code before any
lookup_var_decl as this decl will not exist so the order here is important
during path resolution which is a similar problem to match expression
destructuring.

Fixes #195
dkm pushed a commit that referenced this issue Jan 15, 2023
This patch adds a proxy class ItemWrapper to be a proxy allowing us to use
the same code paths for attribute handling as we have with normal items. We
need this so we can grab the fn trait associated type lang item's. Which
are being missed currently.

Addresses #195
dkm pushed a commit that referenced this issue Jan 15, 2023
In the AST we have ClosureExprInner and ClosureExprInnerTyped the first
is the closure expression of the form:

    let closure_inferred  = |i| i + 1;

The second is of the form:

    let closure_annotated = |i: i32| -> i32 { i + 1 };

Both of these can be seguared into a single HIR::ClosureExpr with an
optional return type and parameter types.

Addresses #195
dkm pushed a commit that referenced this issue Jan 15, 2023
This adds the type checking for a HIR::ClosureExpr which specifies a
TyTy::ClosureType whih inherits the FnOnce trait by default. The
specialisation of the trait bound needs to be determined by the the
mutability and argument capture and moveablity rules.

The CallExpr is amended here so that we support CallExpr's for all
receivers that implement any of the FnTraits. This means closures and
generics that have the relevant type bound of FnTraits we get the same path
of type checking.

Addresses #195
dkm pushed a commit that referenced this issue Jan 15, 2023
Closures's need to generate their specific function and setup their
argument passing based on the signiture specified in libcore. We can get
this information based on the specified bound on the closure.

Addresses #195
dkm pushed a commit that referenced this issue Jan 15, 2023
dkm pushed a commit that referenced this issue Jan 15, 2023
When we have a closure block referencing variables in a parent function,
we must track what these are. We do this by having a context of closures
so if we have a variable reference and its declared in a rib whose node id
is less than the node id of the closure's node id we know it must be a
captured variable. We also need to iterate all possible closure contexts
as we might be in the case of a nested closure.

Addresses #195
dkm pushed a commit that referenced this issue Jan 15, 2023
When we have a closure expression that captures a parent function's
variable we must setup the closure data to contain this. Ignoring
moveability and mutability requires for now, this patch creates the closure
structure with fields for each of the captured variables. When it comes to
compilation of the closure expression in order to support nested closures
we must setup a context of implicit mappings so that for all path
resolution we hit this implicit closure mappings lookups code before any
lookup_var_decl as this decl will not exist so the order here is important
during path resolution which is a similar problem to match expression
destructuring.

Fixes #195
CohenArthur pushed a commit that referenced this issue Jan 16, 2023
This patch adds a proxy class ItemWrapper to be a proxy allowing us to use
the same code paths for attribute handling as we have with normal items. We
need this so we can grab the fn trait associated type lang item's. Which
are being missed currently.

Addresses #195
CohenArthur pushed a commit that referenced this issue Jan 31, 2023
Add hir lowering of closure expressions

In the AST we have ClosureExprInner and ClosureExprInnerTyped the first
is the closure expression of the form:

    let closure_inferred  = |i| i + 1;

The second is of the form:

    let closure_annotated = |i: i32| -> i32 { i + 1 };

Both of these can be seguared into a single HIR::ClosureExpr with an
optional return type and parameter types.

Addresses #195
CohenArthur pushed a commit that referenced this issue Jan 31, 2023
This adds the type checking for a HIR::ClosureExpr which specifies a
TyTy::ClosureType whih inherits the FnOnce trait by default. The
specialisation of the trait bound needs to be determined by the the
mutability and argument capture and moveablity rules.

The CallExpr is amended here so that we support CallExpr's for all
receivers that implement any of the FnTraits. This means closures and
generics that have the relevant type bound of FnTraits we get the same path
of type checking.

Addresses #195
CohenArthur pushed a commit that referenced this issue Jan 31, 2023
Closures's need to generate their specific function and setup their
argument passing based on the signiture specified in libcore. We can get
this information based on the specified bound on the closure.

Addresses #195
CohenArthur pushed a commit that referenced this issue Jan 31, 2023
CohenArthur pushed a commit that referenced this issue Jan 31, 2023
When we have a closure block referencing variables in a parent function,
we must track what these are. We do this by having a context of closures
so if we have a variable reference and its declared in a rib whose node id
is less than the node id of the closure's node id we know it must be a
captured variable. We also need to iterate all possible closure contexts
as we might be in the case of a nested closure.

Addresses #195
CohenArthur pushed a commit that referenced this issue Jan 31, 2023
When we have a closure expression that captures a parent function's
variable we must setup the closure data to contain this. Ignoring
moveability and mutability requires for now, this patch creates the closure
structure with fields for each of the captured variables. When it comes to
compilation of the closure expression in order to support nested closures
we must setup a context of implicit mappings so that for all path
resolution we hit this implicit closure mappings lookups code before any
lookup_var_decl as this decl will not exist so the order here is important
during path resolution which is a similar problem to match expression
destructuring.

Fixes #195
CohenArthur pushed a commit to CohenArthur/gccrs that referenced this issue Feb 14, 2023
When we have a closure block referencing variables in a parent function,
we must track what these are. We do this by having a context of closures
so if we have a variable reference and its declared in a rib whose node id
is less than the node id of the closure's node id we know it must be a
captured variable. We also need to iterate all possible closure contexts
as we might be in the case of a nested closure.

Addresses Rust-GCC#195

gcc/rust/ChangeLog:

	* resolve/rust-ast-resolve-expr.cc (ResolveExpr::visit): Use proper closure
	contexts.
	* resolve/rust-name-resolver.cc (Scope::lookup_decl_type): New function.
	(Scope::lookup_rib_for_decl): Likewise.
	(Resolver::insert_resolved_name): Insert captured items.
	(Resolver::push_closure_context): New function.
	(Resolver::pop_closure_context): Likewise.
	(Resolver::insert_captured_item): Likewise.
	(Resolver::decl_needs_capture): Likewise.
	(Resolver::get_captures): Likewise.
	* resolve/rust-name-resolver.h: Declare new functions.
CohenArthur pushed a commit to CohenArthur/gccrs that referenced this issue Feb 14, 2023
When we have a closure expression that captures a parent function's
variable we must setup the closure data to contain this. Ignoring
moveability and mutability requires for now, this patch creates the closure
structure with fields for each of the captured variables. When it comes to
compilation of the closure expression in order to support nested closures
we must setup a context of implicit mappings so that for all path
resolution we hit this implicit closure mappings lookups code before any
lookup_var_decl as this decl will not exist so the order here is important
during path resolution which is a similar problem to match expression
destructuring.

Fixes Rust-GCC#195

gcc/rust/ChangeLog:

	* backend/rust-compile-context.cc (Context::push_closure_context): New function.
	(Context::pop_closure_context): Likewise.
	(Context::insert_closure_binding): Likewise.
	(Context::lookup_closure_binding): Likewise.
	* backend/rust-compile-context.h: Declare new functions and closure mappings.
	* backend/rust-compile-expr.cc (CompileExpr::visit): Visit captures properly.
	(CompileExpr::generate_closure_function): Compile captures properly.
	* backend/rust-compile-resolve-path.cc (ResolvePathRef::resolve): Check for
	closure bindings.
	* backend/rust-compile-type.cc (TyTyResolveCompile::visit): Compile capture list's
	types as well.

gcc/testsuite/ChangeLog:

	* rust/execute/torture/closure3.rs: New test.
CohenArthur pushed a commit to CohenArthur/gccrs that referenced this issue Feb 14, 2023
When we have a closure block referencing variables in a parent function,
we must track what these are. We do this by having a context of closures
so if we have a variable reference and its declared in a rib whose node id
is less than the node id of the closure's node id we know it must be a
captured variable. We also need to iterate all possible closure contexts
as we might be in the case of a nested closure.

Addresses Rust-GCC#195

gcc/rust/ChangeLog:

	* resolve/rust-ast-resolve-expr.cc (ResolveExpr::visit): Use proper closure
	contexts.
	* resolve/rust-name-resolver.cc (Scope::lookup_decl_type): New function.
	(Scope::lookup_rib_for_decl): Likewise.
	(Resolver::insert_resolved_name): Insert captured items.
	(Resolver::push_closure_context): New function.
	(Resolver::pop_closure_context): Likewise.
	(Resolver::insert_captured_item): Likewise.
	(Resolver::decl_needs_capture): Likewise.
	(Resolver::get_captures): Likewise.
	* resolve/rust-name-resolver.h: Declare new functions.
CohenArthur pushed a commit to CohenArthur/gccrs that referenced this issue Feb 14, 2023
When we have a closure expression that captures a parent function's
variable we must setup the closure data to contain this. Ignoring
moveability and mutability requires for now, this patch creates the closure
structure with fields for each of the captured variables. When it comes to
compilation of the closure expression in order to support nested closures
we must setup a context of implicit mappings so that for all path
resolution we hit this implicit closure mappings lookups code before any
lookup_var_decl as this decl will not exist so the order here is important
during path resolution which is a similar problem to match expression
destructuring.

Fixes Rust-GCC#195

gcc/rust/ChangeLog:

	* backend/rust-compile-context.cc (Context::push_closure_context): New function.
	(Context::pop_closure_context): Likewise.
	(Context::insert_closure_binding): Likewise.
	(Context::lookup_closure_binding): Likewise.
	* backend/rust-compile-context.h: Declare new functions and closure mappings.
	* backend/rust-compile-expr.cc (CompileExpr::visit): Visit captures properly.
	(CompileExpr::generate_closure_function): Compile captures properly.
	* backend/rust-compile-resolve-path.cc (ResolvePathRef::resolve): Check for
	closure bindings.
	* backend/rust-compile-type.cc (TyTyResolveCompile::visit): Compile capture list's
	types as well.

gcc/testsuite/ChangeLog:

	* rust/execute/torture/closure3.rs: New test.
tschwinge pushed a commit that referenced this issue Feb 22, 2023
When we have a closure block referencing variables in a parent function,
we must track what these are. We do this by having a context of closures
so if we have a variable reference and its declared in a rib whose node id
is less than the node id of the closure's node id we know it must be a
captured variable. We also need to iterate all possible closure contexts
as we might be in the case of a nested closure.

Addresses #195

gcc/rust/ChangeLog:

	* resolve/rust-ast-resolve-expr.cc (ResolveExpr::visit): Use proper closure
	contexts.
	* resolve/rust-name-resolver.cc (Scope::lookup_decl_type): New function.
	(Scope::lookup_rib_for_decl): Likewise.
	(Resolver::insert_resolved_name): Insert captured items.
	(Resolver::push_closure_context): New function.
	(Resolver::pop_closure_context): Likewise.
	(Resolver::insert_captured_item): Likewise.
	(Resolver::decl_needs_capture): Likewise.
	(Resolver::get_captures): Likewise.
	* resolve/rust-name-resolver.h: Declare new functions.
tschwinge pushed a commit that referenced this issue Feb 22, 2023
When we have a closure expression that captures a parent function's
variable we must setup the closure data to contain this. Ignoring
moveability and mutability requires for now, this patch creates the closure
structure with fields for each of the captured variables. When it comes to
compilation of the closure expression in order to support nested closures
we must setup a context of implicit mappings so that for all path
resolution we hit this implicit closure mappings lookups code before any
lookup_var_decl as this decl will not exist so the order here is important
during path resolution which is a similar problem to match expression
destructuring.

Fixes #195

gcc/rust/ChangeLog:

	* backend/rust-compile-context.cc (Context::push_closure_context): New function.
	(Context::pop_closure_context): Likewise.
	(Context::insert_closure_binding): Likewise.
	(Context::lookup_closure_binding): Likewise.
	* backend/rust-compile-context.h: Declare new functions and closure mappings.
	* backend/rust-compile-expr.cc (CompileExpr::visit): Visit captures properly.
	(CompileExpr::generate_closure_function): Compile captures properly.
	* backend/rust-compile-resolve-path.cc (ResolvePathRef::resolve): Check for
	closure bindings.
	* backend/rust-compile-type.cc (TyTyResolveCompile::visit): Compile capture list's
	types as well.

gcc/testsuite/ChangeLog:

	* rust/execute/torture/closure3.rs: New test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

4 participants