Skip to content

Commit

Permalink
Add initial support for argument capture of closures
Browse files Browse the repository at this point in the history
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
  • Loading branch information
philberty authored and dkm committed Jan 15, 2023
1 parent c0f826f commit bce8d1e
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 6 deletions.
47 changes: 47 additions & 0 deletions gcc/rust/backend/rust-compile-context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,52 @@ Context::type_hasher (tree type)
return hstate.end ();
}

void
Context::push_closure_context (HirId id)
{
auto it = closure_bindings.find (id);
rust_assert (it == closure_bindings.end ());

closure_bindings.insert ({id, {}});
closure_scope_bindings.push_back (id);
}

void
Context::pop_closure_context ()
{
rust_assert (!closure_scope_bindings.empty ());

HirId ref = closure_scope_bindings.back ();
closure_scope_bindings.pop_back ();
closure_bindings.erase (ref);
}

void
Context::insert_closure_binding (HirId id, tree expr)
{
rust_assert (!closure_scope_bindings.empty ());

HirId ref = closure_scope_bindings.back ();
closure_bindings[ref].insert ({id, expr});
}

bool
Context::lookup_closure_binding (HirId id, tree *expr)
{
if (closure_scope_bindings.empty ())
return false;

HirId ref = closure_scope_bindings.back ();
auto it = closure_bindings.find (ref);
rust_assert (it != closure_bindings.end ());

auto iy = it->second.find (id);
if (iy == it->second.end ())
return false;

*expr = iy->second;
return true;
}

} // namespace Compile
} // namespace Rust
9 changes: 9 additions & 0 deletions gcc/rust/backend/rust-compile-context.h
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,11 @@ class Context
return mangler.mangle_item (ty, path);
}

void push_closure_context (HirId id);
void pop_closure_context ();
void insert_closure_binding (HirId id, tree expr);
bool lookup_closure_binding (HirId id, tree *expr);

std::vector<tree> &get_type_decls () { return type_decls; }
std::vector<::Bvariable *> &get_var_decls () { return var_decls; }
std::vector<tree> &get_const_decls () { return const_decls; }
Expand Down Expand Up @@ -377,6 +382,10 @@ class Context
std::map<HirId, tree> implicit_pattern_bindings;
std::map<hashval_t, tree> main_variants;

// closure bindings
std::vector<HirId> closure_scope_bindings;
std::map<HirId, std::map<HirId, tree>> closure_bindings;

// To GCC middle-end
std::vector<tree> type_decls;
std::vector<::Bvariable *> var_decls;
Expand Down
50 changes: 45 additions & 5 deletions gcc/rust/backend/rust-compile-expr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2829,10 +2829,25 @@ CompileExpr::visit (HIR::ClosureExpr &expr)

// lets ignore state capture for now we need to instantiate the struct anyway
// then generate the function

std::vector<tree> vals;
// TODO
// setup argument captures based on the mode?
for (const auto &capture : closure_tyty->get_captures ())
{
// lookup the HirId
HirId ref = UNKNOWN_HIRID;
bool ok = ctx->get_mappings ()->lookup_node_to_hir (capture, &ref);
rust_assert (ok);

// lookup the var decl
Bvariable *var = nullptr;
bool found = ctx->lookup_var_decl (ref, &var);
rust_assert (found);

// FIXME
// this should bes based on the closure move-ability
tree var_expr = var->get_tree (expr.get_locus ());
tree val = address_expression (var_expr, expr.get_locus ());
vals.push_back (val);
}

translated
= ctx->get_backend ()->constructor_expression (compiled_closure_tyty, false,
Expand Down Expand Up @@ -2879,8 +2894,29 @@ CompileExpr::generate_closure_function (HIR::ClosureExpr &expr,
DECL_ARTIFICIAL (self_param->get_decl ()) = 1;
param_vars.push_back (self_param);

// push a new context
ctx->push_closure_context (expr.get_mappings ().get_hirid ());

// setup the implicit argument captures
// TODO
size_t idx = 0;
for (const auto &capture : closure_tyty.get_captures ())
{
// lookup the HirId
HirId ref = UNKNOWN_HIRID;
bool ok = ctx->get_mappings ()->lookup_node_to_hir (capture, &ref);
rust_assert (ok);

// get the assessor
tree binding = ctx->get_backend ()->struct_field_expression (
self_param->get_tree (expr.get_locus ()), idx, expr.get_locus ());
tree indirection = indirect_expression (binding, expr.get_locus ());

// insert bindings
ctx->insert_closure_binding (ref, indirection);

// continue
idx++;
}

// args tuple
tree args_type
Expand Down Expand Up @@ -2910,7 +2946,10 @@ CompileExpr::generate_closure_function (HIR::ClosureExpr &expr,
}

if (!ctx->get_backend ()->function_set_parameters (fndecl, param_vars))
return error_mark_node;
{
ctx->pop_closure_context ();
return error_mark_node;
}

// lookup locals
HIR::Expr *function_body = expr.get_expr ().get ();
Expand Down Expand Up @@ -2977,6 +3016,7 @@ CompileExpr::generate_closure_function (HIR::ClosureExpr &expr,
gcc_assert (TREE_CODE (bind_tree) == BIND_EXPR);
DECL_SAVED_TREE (fndecl) = bind_tree;

ctx->pop_closure_context ();
ctx->pop_fn ();
ctx->push_function (fndecl);

Expand Down
8 changes: 8 additions & 0 deletions gcc/rust/backend/rust-compile-resolve-path.cc
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ ResolvePathRef::resolve (const HIR::PathIdentSegment &final_segment,
return constant_expr;
}

// maybe closure binding
tree closure_binding = error_mark_node;
if (ctx->lookup_closure_binding (ref, &closure_binding))
{
TREE_USED (closure_binding) = 1;
return closure_binding;
}

// this might be a variable reference or a function reference
Bvariable *var = nullptr;
if (ctx->lookup_var_decl (ref, &var))
Expand Down
31 changes: 30 additions & 1 deletion gcc/rust/backend/rust-compile-type.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "rust-compile-type.h"
#include "rust-compile-expr.h"
#include "rust-constexpr.h"
#include "rust-gcc.h"

#include "tree.h"

Expand Down Expand Up @@ -99,11 +100,39 @@ TyTyResolveCompile::visit (const TyTy::InferType &)
void
TyTyResolveCompile::visit (const TyTy::ClosureType &type)
{
auto mappings = ctx->get_mappings ();

std::vector<Backend::typed_identifier> fields;

size_t i = 0;
for (const auto &capture : type.get_captures ())
{
// lookup the HirId
HirId ref = UNKNOWN_HIRID;
bool ok = mappings->lookup_node_to_hir (capture, &ref);
rust_assert (ok);

// lookup the var decl type
TyTy::BaseType *lookup = nullptr;
bool found = ctx->get_tyctx ()->lookup_type (ref, &lookup);
rust_assert (found);

// FIXME get the var pattern name
std::string mappings_name = "capture_" + std::to_string (i);

// FIXME
// this should be based on the closure move-ability
tree decl_type = TyTyResolveCompile::compile (ctx, lookup);
tree capture_type = build_reference_type (decl_type);
fields.push_back (Backend::typed_identifier (mappings_name, capture_type,
type.get_ident ().locus));
}

tree type_record = ctx->get_backend ()->struct_type (fields);
RS_CLOSURE_FLAG (type_record) = 1;

std::string named_struct_str = type.get_ident ().path.get () + "{{closure}}";
std::string named_struct_str
= type.get_ident ().path.get () + "::{{closure}}";
translated = ctx->get_backend ()->named_type (named_struct_str, type_record,
type.get_ident ().locus);
}
Expand Down
33 changes: 33 additions & 0 deletions gcc/testsuite/rust/execute/torture/closure3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// { dg-output "3\n" }
extern "C" {
fn printf(s: *const i8, ...);
}

#[lang = "fn_once"]
pub trait FnOnce<Args> {
#[lang = "fn_once_output"]
type Output;

extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

fn f<F: FnOnce(i32) -> i32>(g: F) {
let call = g(1);
unsafe {
let a = "%i\n\0";
let b = a as *const str;
let c = b as *const i8;

printf(c, call);
}
}

pub fn main() -> i32 {
let capture = 2;
let a = |i: i32| {
let b = i + capture;
b
};
f(a);
0
}

0 comments on commit bce8d1e

Please sign in to comment.