diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2022-10-21 14:13:47 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-21 14:13:47 +0000 |
commit | 490aa25d5564313c1957bf28533fe902a0aaa1f2 (patch) | |
tree | 3fee40077e3efdb231b96772bd58c02522ce6f3a /gcc/rust/backend/rust-compile-expr.cc | |
parent | 60b21d2f58f46c93fc33f6192682abfed62d8dd9 (diff) | |
parent | 05bd0555fa398c171acc22b6cfa1d5974202a5c7 (diff) | |
download | gcc-490aa25d5564313c1957bf28533fe902a0aaa1f2.zip gcc-490aa25d5564313c1957bf28533fe902a0aaa1f2.tar.gz gcc-490aa25d5564313c1957bf28533fe902a0aaa1f2.tar.bz2 |
Merge #1608
1608: Initial support for closures r=philberty a=philberty
This patch series introduces initial support for closures. Rust's implementation
of closures is actually pretty clever, the function signatures for closures is driven
by the specific FnTrait that the closure implements, this means a CallExpr to a closure
becomes a method-call expr with the receiver type being the closure itself using the
normal autoderef mechanism to do method selection for an implicit impl block.
See https://github.com/rust-lang/rust/blob/7807a694c2f079fd3f395821bcc357eee8650071/library/core/src/ops/function.rs#L54-L71
The other implicit part of this is that arguments being passed to a closure _must_
be passed as a tuple. The down side of allowing libcore to specify the signatures
of the closure functions is that we are limited in how we pass arguments, but
using a tuple and then using similar machinery from the match-expr to restructure
the parameter access to become the tuple accessors makes it look seamless. For
example:
```rust
let closure_annotated = |i: i32| -> i32 { i + 123 };
let i = 1;
closure_annotated(i);
```
Wil generate a function and call-expr such as:
```c
i32 test::main::{{closure}} (struct {{closure}} $closure, struct (i32) args)
{
_1 = args.__0; // this is 'i'
return _1 + 123;
}
__attribute__((cdecl))
i32 test::main ()
{
struct
{
i32 __0;
} D.137;
i32 D.140;
const i32 a;
const struct{{closure}} closure_annotated;
const i32 i;
try
{
a = 1;
i = 1;
D.137.__0 = i;
_1 = test::main::{{closure}} (closure_annotated, D.137);
<...>
}
finally
{
closure_annotated = {CLOBBER(eol)};
}
}
```
Note this patch series does not implement the argument capture yet but this patch set is
good start to the implementation so far.
Addresses #195
Co-authored-by: Philip Herron <philip.herron@embecosm.com>
Diffstat (limited to 'gcc/rust/backend/rust-compile-expr.cc')
-rw-r--r-- | gcc/rust/backend/rust-compile-expr.cc | 306 |
1 files changed, 299 insertions, 7 deletions
diff --git a/gcc/rust/backend/rust-compile-expr.cc b/gcc/rust/backend/rust-compile-expr.cc index 9ff2be6..b077a12 100644 --- a/gcc/rust/backend/rust-compile-expr.cc +++ b/gcc/rust/backend/rust-compile-expr.cc @@ -1589,9 +1589,7 @@ CompileExpr::visit (HIR::CallExpr &expr) } // must be a tuple constructor - bool is_fn = tyty->get_kind () == TyTy::TypeKind::FNDEF - || tyty->get_kind () == TyTy::TypeKind::FNPTR; - bool is_adt_ctor = !is_fn; + bool is_adt_ctor = tyty->get_kind () == TyTy::TypeKind::ADT; if (is_adt_ctor) { rust_assert (tyty->get_kind () == TyTy::TypeKind::ADT); @@ -1692,6 +1690,14 @@ CompileExpr::visit (HIR::CallExpr &expr) return true; }; + auto fn_address = CompileExpr::Compile (expr.get_fnexpr (), ctx); + + // is this a closure call? + bool possible_trait_call + = generate_possible_fn_trait_call (expr, fn_address, &translated); + if (possible_trait_call) + return; + bool is_varadic = false; if (tyty->get_kind () == TyTy::TypeKind::FNDEF) { @@ -1699,13 +1705,13 @@ CompileExpr::visit (HIR::CallExpr &expr) is_varadic = fn->is_varadic (); } - size_t required_num_args; + size_t required_num_args = expr.get_arguments ().size (); if (tyty->get_kind () == TyTy::TypeKind::FNDEF) { const TyTy::FnType *fn = static_cast<const TyTy::FnType *> (tyty); required_num_args = fn->num_params (); } - else + else if (tyty->get_kind () == TyTy::TypeKind::FNPTR) { const TyTy::FnPtr *fn = static_cast<const TyTy::FnPtr *> (tyty); required_num_args = fn->num_params (); @@ -1746,8 +1752,7 @@ CompileExpr::visit (HIR::CallExpr &expr) args.push_back (rvalue); } - // must be a call to a function - auto fn_address = CompileExpr::Compile (expr.get_fnexpr (), ctx); + // must be a regular call to a function translated = ctx->get_backend ()->call_expression (fn_address, args, nullptr, expr.get_locus ()); } @@ -2798,5 +2803,292 @@ CompileExpr::visit (HIR::ArrayIndexExpr &expr) expr.get_locus ()); } +void +CompileExpr::visit (HIR::ClosureExpr &expr) +{ + TyTy::BaseType *closure_expr_ty = nullptr; + if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (), + &closure_expr_ty)) + { + rust_fatal_error (expr.get_locus (), + "did not resolve type for this ClosureExpr"); + return; + } + rust_assert (closure_expr_ty->get_kind () == TyTy::TypeKind::CLOSURE); + TyTy::ClosureType *closure_tyty + = static_cast<TyTy::ClosureType *> (closure_expr_ty); + tree compiled_closure_tyty = TyTyResolveCompile::compile (ctx, closure_tyty); + + // generate closure function + generate_closure_function (expr, *closure_tyty, compiled_closure_tyty); + + // 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? + + translated + = ctx->get_backend ()->constructor_expression (compiled_closure_tyty, false, + vals, -1, expr.get_locus ()); +} + +tree +CompileExpr::generate_closure_function (HIR::ClosureExpr &expr, + TyTy::ClosureType &closure_tyty, + tree compiled_closure_tyty) +{ + TyTy::FnType *fn_tyty = nullptr; + tree compiled_fn_type + = generate_closure_fntype (expr, closure_tyty, compiled_closure_tyty, + &fn_tyty); + if (compiled_fn_type == error_mark_node) + return error_mark_node; + + const Resolver::CanonicalPath &parent_canonical_path + = closure_tyty.get_ident ().path; + Resolver::CanonicalPath path = parent_canonical_path.append ( + Resolver::CanonicalPath::new_seg (UNKNOWN_NODEID, "{{closure}}")); + + std::string ir_symbol_name = path.get (); + std::string asm_name = ctx->mangle_item (&closure_tyty, path); + + unsigned int flags = 0; + tree fndecl + = ctx->get_backend ()->function (compiled_fn_type, ir_symbol_name, asm_name, + flags, expr.get_locus ()); + + // insert into the context + ctx->insert_function_decl (fn_tyty, fndecl); + ctx->insert_closure_decl (&closure_tyty, fndecl); + + // setup the parameters + std::vector<Bvariable *> param_vars; + + // closure self + Bvariable *self_param + = ctx->get_backend ()->parameter_variable (fndecl, "$closure", + compiled_closure_tyty, + expr.get_locus ()); + DECL_ARTIFICIAL (self_param->get_decl ()) = 1; + param_vars.push_back (self_param); + + // setup the implicit argument captures + // TODO + + // args tuple + tree args_type + = TyTyResolveCompile::compile (ctx, &closure_tyty.get_parameters ()); + Bvariable *args_param + = ctx->get_backend ()->parameter_variable (fndecl, "args", args_type, + expr.get_locus ()); + param_vars.push_back (args_param); + + // setup the implicit mappings for the arguments. Since argument passing to + // closure functions is done via passing a tuple but the closure body expects + // just normal arguments this means we need to destructure them similar to + // what we do in MatchExpr's. This means when we have a closure-param of a we + // actually setup the destructure to take from the args tuple + + tree args_param_expr = args_param->get_tree (expr.get_locus ()); + size_t i = 0; + for (auto &closure_param : expr.get_params ()) + { + tree compiled_param_var = ctx->get_backend ()->struct_field_expression ( + args_param_expr, i, closure_param.get_locus ()); + + const HIR::Pattern ¶m_pattern = *closure_param.get_pattern (); + ctx->insert_pattern_binding ( + param_pattern.get_pattern_mappings ().get_hirid (), compiled_param_var); + i++; + } + + if (!ctx->get_backend ()->function_set_parameters (fndecl, param_vars)) + return error_mark_node; + + // lookup locals + HIR::Expr *function_body = expr.get_expr ().get (); + auto body_mappings = function_body->get_mappings (); + Resolver::Rib *rib = nullptr; + bool ok + = ctx->get_resolver ()->find_name_rib (body_mappings.get_nodeid (), &rib); + rust_assert (ok); + + std::vector<Bvariable *> locals + = compile_locals_for_block (ctx, *rib, fndecl); + + tree enclosing_scope = NULL_TREE; + Location start_location = function_body->get_locus (); + Location end_location = function_body->get_locus (); + bool is_block_expr + = function_body->get_expression_type () == HIR::Expr::ExprType::Block; + if (is_block_expr) + { + HIR::BlockExpr *body = static_cast<HIR::BlockExpr *> (function_body); + start_location = body->get_locus (); + end_location = body->get_end_locus (); + } + + tree code_block = ctx->get_backend ()->block (fndecl, enclosing_scope, locals, + start_location, end_location); + ctx->push_block (code_block); + + TyTy::BaseType *tyret = &closure_tyty.get_result_type (); + bool function_has_return = !closure_tyty.get_result_type ().is_unit (); + Bvariable *return_address = nullptr; + if (function_has_return) + { + tree return_type = TyTyResolveCompile::compile (ctx, tyret); + + bool address_is_taken = false; + tree ret_var_stmt = NULL_TREE; + + return_address = ctx->get_backend ()->temporary_variable ( + fndecl, code_block, return_type, NULL, address_is_taken, + expr.get_locus (), &ret_var_stmt); + + ctx->add_statement (ret_var_stmt); + } + + ctx->push_fn (fndecl, return_address); + + if (is_block_expr) + { + HIR::BlockExpr *body = static_cast<HIR::BlockExpr *> (function_body); + compile_function_body (ctx, fndecl, *body, true); + } + else + { + tree value = CompileExpr::Compile (function_body, ctx); + tree return_expr + = ctx->get_backend ()->return_statement (fndecl, {value}, + function_body->get_locus ()); + ctx->add_statement (return_expr); + } + + tree bind_tree = ctx->pop_block (); + + gcc_assert (TREE_CODE (bind_tree) == BIND_EXPR); + DECL_SAVED_TREE (fndecl) = bind_tree; + + ctx->pop_fn (); + ctx->push_function (fndecl); + + return fndecl; +} + +tree +CompileExpr::generate_closure_fntype (HIR::ClosureExpr &expr, + const TyTy::ClosureType &closure_tyty, + tree compiled_closure_tyty, + TyTy::FnType **fn_tyty) +{ + // grab the specified_bound + rust_assert (closure_tyty.num_specified_bounds () == 1); + const TyTy::TypeBoundPredicate &predicate + = *closure_tyty.get_specified_bounds ().begin (); + + // ensure the fn_once_output associated type is set + closure_tyty.setup_fn_once_output (); + + // the function signature is based on the trait bound that the closure + // implements which is determined at the type resolution time + // + // https://github.com/rust-lang/rust/blob/7807a694c2f079fd3f395821bcc357eee8650071/library/core/src/ops/function.rs#L54-L71 + + TyTy::TypeBoundPredicateItem item = TyTy::TypeBoundPredicateItem::error (); + if (predicate.get_name ().compare ("FnOnce") == 0) + { + item = predicate.lookup_associated_item ("call_once"); + } + else if (predicate.get_name ().compare ("FnMut") == 0) + { + item = predicate.lookup_associated_item ("call_mut"); + } + else if (predicate.get_name ().compare ("Fn") == 0) + { + item = predicate.lookup_associated_item ("call"); + } + else + { + // FIXME error message? + gcc_unreachable (); + return error_mark_node; + } + + rust_assert (!item.is_error ()); + + TyTy::BaseType *item_tyty = item.get_tyty_for_receiver (&closure_tyty); + rust_assert (item_tyty->get_kind () == TyTy::TypeKind::FNDEF); + *fn_tyty = static_cast<TyTy::FnType *> (item_tyty); + return TyTyResolveCompile::compile (ctx, item_tyty); +} + +bool +CompileExpr::generate_possible_fn_trait_call (HIR::CallExpr &expr, + tree receiver, tree *result) +{ + TyTy::FnType *fn_sig = nullptr; + bool found_overload = ctx->get_tyctx ()->lookup_operator_overload ( + expr.get_mappings ().get_hirid (), &fn_sig); + if (!found_overload) + return false; + + auto id = fn_sig->get_ty_ref (); + auto dId = fn_sig->get_id (); + + tree function = error_mark_node; + bool found_closure = ctx->lookup_function_decl (id, &function, dId, fn_sig); + if (!found_closure) + { + // something went wrong we still return true as this was meant to be an fn + // trait call + *result = error_mark_node; + return true; + } + + // need to apply any autoderef's to the self argument + HirId autoderef_mappings_id = expr.get_mappings ().get_hirid (); + std::vector<Resolver::Adjustment> *adjustments = nullptr; + bool ok = ctx->get_tyctx ()->lookup_autoderef_mappings (autoderef_mappings_id, + &adjustments); + rust_assert (ok); + + // apply adjustments for the fn call + tree self = resolve_adjustements (*adjustments, receiver, expr.get_locus ()); + + // resolve the arguments + std::vector<tree> tuple_arg_vals; + for (auto &argument : expr.get_arguments ()) + { + auto rvalue = CompileExpr::Compile (argument.get (), ctx); + tuple_arg_vals.push_back (rvalue); + } + + // this is always the 2nd argument in the function signature + tree fnty = TREE_TYPE (function); + tree fn_arg_tys = TYPE_ARG_TYPES (fnty); + tree tuple_args_tyty_chain = TREE_CHAIN (fn_arg_tys); + tree tuple_args_tyty = TREE_VALUE (tuple_args_tyty_chain); + + tree tuple_args + = ctx->get_backend ()->constructor_expression (tuple_args_tyty, false, + tuple_arg_vals, -1, + expr.get_locus ()); + + // args are always self, and the tuple of the args we are passing where + // self is the path of the call-expr in this case the fn_address + std::vector<tree> args; + args.push_back (self); + args.push_back (tuple_args); + + tree call_address = address_expression (function, expr.get_locus ()); + *result = ctx->get_backend ()->call_expression (call_address, args, + nullptr /* static chain ?*/, + expr.get_locus ()); + return true; +} + } // namespace Compile } // namespace Rust |