-
Notifications
You must be signed in to change notification settings - Fork 162
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
Comments
Closures are interesting. They get a separate MIR body and |
Useful comment from @dkm #523 (comment) |
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 |
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 :
Maybe I don't understand your point? |
For example fn foo() -> impl FnMut() -> i32 {
let mut bar = 0;
move || { bar += 1; bar }
} is valid. |
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 |
Ok, the closure can be used even after the enclosing function has exited, didn't know that... |
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. |
If you don't use
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 |
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);
} |
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
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>
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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.
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.
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.
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.
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.
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.
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:
will give us
The task list breaks down into:
unboxed_closures
&fn_traits
feature) rust-lang/rust#29625I am currently blocked on this until we get lang-item support into the compiler from my operator overloading branch,
The text was updated successfully, but these errors were encountered: