Mercurial > hg > CbC > CbC_gcc
diff gcc/coroutine-passes.cc @ 145:1830386684a0
gcc-9.2.0
author | anatofuz |
---|---|
date | Thu, 13 Feb 2020 11:34:05 +0900 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gcc/coroutine-passes.cc Thu Feb 13 11:34:05 2020 +0900 @@ -0,0 +1,532 @@ +/* coroutine expansion and optimisation passes. + + Copyright (C) 2018-2020 Free Software Foundation, Inc. + + Contributed by Iain Sandoe <iain@sandoe.co.uk> under contract to Facebook. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "backend.h" +#include "target.h" +#include "tree.h" +#include "gimple.h" +#include "tree-pass.h" +#include "ssa.h" +#include "cgraph.h" +#include "pretty-print.h" +#include "diagnostic-core.h" +#include "fold-const.h" +#include "internal-fn.h" +#include "langhooks.h" +#include "gimplify.h" +#include "gimple-iterator.h" +#include "gimplify-me.h" +#include "gimple-walk.h" +#include "gimple-fold.h" +#include "tree-cfg.h" +#include "tree-into-ssa.h" +#include "tree-ssa-propagate.h" +#include "gimple-pretty-print.h" +#include "cfghooks.h" + +/* Here we: + * lower the internal function that implements an exit from scope. + * expand the builtins that are used to implement the library + interfaces to the coroutine frame. */ + +static tree +lower_coro_builtin (gimple_stmt_iterator *gsi, bool *handled_ops_p, + struct walk_stmt_info *wi ATTRIBUTE_UNUSED) +{ + gimple *stmt = gsi_stmt (*gsi); + *handled_ops_p = !gimple_has_substatements (stmt); + + if (gimple_code (stmt) != GIMPLE_CALL) + return NULL_TREE; + + /* This internal function implements an exit from scope without + performing any cleanups; it jumps directly to the label provided. */ + if (gimple_call_internal_p (stmt) + && gimple_call_internal_fn (stmt) == IFN_CO_SUSPN) + { + tree dest = TREE_OPERAND (gimple_call_arg (stmt, 0), 0); + ggoto *g = gimple_build_goto (dest); + gsi_replace (gsi, g, /* do EH */ false); + *handled_ops_p = true; + return NULL_TREE; + } + + tree decl = gimple_call_fndecl (stmt); + if (!decl || !fndecl_built_in_p (decl, BUILT_IN_NORMAL)) + return NULL_TREE; + + /* The remaining builtins implement the library interfaces to the coro + frame. */ + unsigned call_idx = 0; + + switch (DECL_FUNCTION_CODE (decl)) + { + default: + break; + case BUILT_IN_CORO_PROMISE: + { + /* If we are discarding this, then skip it; the function has no + side-effects. */ + tree lhs = gimple_call_lhs (stmt); + if (!lhs) + { + gsi_remove (gsi, true); + *handled_ops_p = true; + return NULL_TREE; + } + /* The coro frame starts with two pointers (to the resume and + destroy() functions). These are followed by the promise which + is aligned as per type [or user attribute]. + The input pointer is the first argument. + The promise alignment is the second and the third is a bool + that is true when we are converting from a promise ptr to a + frame pointer, and false for the inverse. */ + tree ptr = gimple_call_arg (stmt, 0); + tree align_t = gimple_call_arg (stmt, 1); + tree from = gimple_call_arg (stmt, 2); + gcc_checking_assert (TREE_CODE (align_t) == INTEGER_CST); + gcc_checking_assert (TREE_CODE (from) == INTEGER_CST); + bool dir = wi::to_wide (from) != 0; + HOST_WIDE_INT promise_align = TREE_INT_CST_LOW (align_t); + HOST_WIDE_INT psize = + TREE_INT_CST_LOW (TYPE_SIZE_UNIT (ptr_type_node)); + HOST_WIDE_INT align = TYPE_ALIGN_UNIT (ptr_type_node); + align = MAX (align, promise_align); + psize *= 2; /* Start with two pointers. */ + psize = ROUND_UP (psize, align); + HOST_WIDE_INT offs = dir ? -psize : psize; + tree repl = build2 (POINTER_PLUS_EXPR, ptr_type_node, ptr, + size_int (offs)); + gassign *grpl = gimple_build_assign (lhs, repl); + gsi_replace (gsi, grpl, true); + *handled_ops_p = true; + } + break; + case BUILT_IN_CORO_DESTROY: + call_idx = 1; + /* FALLTHROUGH */ + case BUILT_IN_CORO_RESUME: + { + tree ptr = gimple_call_arg (stmt, 0); /* frame ptr. */ + HOST_WIDE_INT psize = + TREE_INT_CST_LOW (TYPE_SIZE_UNIT (ptr_type_node)); + HOST_WIDE_INT offset = call_idx * psize; + tree fntype = TREE_TYPE (decl); + tree fntype_ptr = build_pointer_type (fntype); + tree fntype_ppp = build_pointer_type (fntype_ptr); + tree indirect = fold_build2 (MEM_REF, fntype_ptr, ptr, + build_int_cst (fntype_ppp, offset)); + tree f_ptr_tmp = make_ssa_name (TYPE_MAIN_VARIANT (fntype_ptr)); + gassign *get_fptr = gimple_build_assign (f_ptr_tmp, indirect); + gsi_insert_before (gsi, get_fptr, GSI_SAME_STMT); + gimple_call_set_fn (static_cast<gcall *> (stmt), f_ptr_tmp); + *handled_ops_p = true; + } + break; + case BUILT_IN_CORO_DONE: + { + /* If we are discarding this, then skip it; the function has no + side-effects. */ + tree lhs = gimple_call_lhs (stmt); + if (!lhs) + { + gsi_remove (gsi, true); + *handled_ops_p = true; + return NULL_TREE; + } + /* When we're done, the resume fn is set to NULL. */ + tree ptr = gimple_call_arg (stmt, 0); /* frame ptr. */ + tree vpp = build_pointer_type (ptr_type_node); + tree indirect + = fold_build2 (MEM_REF, vpp, ptr, build_int_cst (vpp, 0)); + tree d_ptr_tmp = make_ssa_name (ptr_type_node); + gassign *get_dptr = gimple_build_assign (d_ptr_tmp, indirect); + gsi_insert_before (gsi, get_dptr, GSI_SAME_STMT); + tree done = fold_build2 (EQ_EXPR, boolean_type_node, d_ptr_tmp, + null_pointer_node); + gassign *get_res = gimple_build_assign (lhs, done); + gsi_replace (gsi, get_res, true); + *handled_ops_p = true; + } + break; + } + return NULL_TREE; +} + +/* Main entry point for lowering coroutine FE builtins. */ + +static unsigned int +execute_lower_coro_builtins (void) +{ + struct walk_stmt_info wi; + gimple_seq body; + + body = gimple_body (current_function_decl); + memset (&wi, 0, sizeof (wi)); + walk_gimple_seq_mod (&body, lower_coro_builtin, NULL, &wi); + gimple_set_body (current_function_decl, body); + + return 0; +} + +namespace { + +const pass_data pass_data_coroutine_lower_builtins = { + GIMPLE_PASS, /* type */ + "coro-lower-builtins", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_NONE, /* tv_id */ + 0, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0 /* todo_flags_finish */ +}; + +class pass_coroutine_lower_builtins : public gimple_opt_pass +{ +public: + pass_coroutine_lower_builtins (gcc::context *ctxt) + : gimple_opt_pass (pass_data_coroutine_lower_builtins, ctxt) + {} + + /* opt_pass methods: */ + virtual bool gate (function *) { return flag_coroutines; }; + + virtual unsigned int execute (function *f ATTRIBUTE_UNUSED) + { + return execute_lower_coro_builtins (); + } + +}; // class pass_coroutine_lower_builtins + +} // namespace + +gimple_opt_pass * +make_pass_coroutine_lower_builtins (gcc::context *ctxt) +{ + return new pass_coroutine_lower_builtins (ctxt); +} + +/* Expand the remaining coroutine IFNs. + + In the front end we construct a single actor function that contains + the coroutine state machine. + + The actor function has three entry conditions: + 1. from the ramp, resume point 0 - to initial-suspend. + 2. when resume () is executed (resume point N). + 3. from the destroy () shim when that is executed. + + The actor function begins with two dispatchers; one for resume and + one for destroy (where the initial entry from the ramp is a special- + case of resume point 0). + + Each suspend point and each dispatch entry is marked with an IFN such + that we can connect the relevant dispatchers to their target labels. + + So, if we have: + + CO_YIELD (NUM, FINAL, RES_LAB, DEST_LAB, FRAME_PTR) + + This is await point NUM, and is the final await if FINAL is non-zero. + The resume point is RES_LAB, and the destroy point is DEST_LAB. + + We expect to find a CO_ACTOR (NUM) in the resume dispatcher and a + CO_ACTOR (NUM+1) in the destroy dispatcher. + + Initially, the intent of keeping the resume and destroy paths together + is that the conditionals controlling them are identical, and thus there + would be duplication of any optimisation of those paths if the split + were earlier. + + Subsequent inlining of the actor (and DCE) is then able to extract the + resume and destroy paths as separate functions if that is found + profitable by the optimisers. + + Once we have remade the connections to their correct postions, we elide + the labels that the front end inserted. */ + +static void +move_edge_and_update (edge e, basic_block old_bb, basic_block new_bb) +{ + if (dump_file) + fprintf (dump_file, "redirecting edge from bb %u to bb %u\n", old_bb->index, + new_bb->index); + + e = redirect_edge_and_branch (e, new_bb); + if (!e && dump_file) + fprintf (dump_file, "failed to redirect edge .. \n"); + + /* Die if we failed. */ + gcc_checking_assert (e); +} + +static unsigned int +execute_early_expand_coro_ifns (void) +{ + /* Don't rebuild stuff unless we have to. */ + unsigned int todoflags = 0; + bool changed = false; + /* Some of the possible YIELD points will hopefully have been removed by + earlier optimisations; record the ones that are still present. */ + hash_map<int_hash<HOST_WIDE_INT, -1, -2>, tree> destinations; + /* Labels we added to carry the CFG changes, we need to remove these to + avoid confusing EH. */ + hash_set<tree> to_remove; + /* List of dispatch points to update. */ + auto_vec<gimple_stmt_iterator, 16> actor_worklist; + basic_block bb; + gimple_stmt_iterator gsi; + + FOR_EACH_BB_FN (bb, cfun) + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi);) + { + gimple *stmt = gsi_stmt (gsi); + + if (!is_gimple_call (stmt) || !gimple_call_internal_p (stmt)) + { + gsi_next (&gsi); + continue; + } + switch (gimple_call_internal_fn (stmt)) + { + case IFN_CO_FRAME: + { + /* This internal function is a placeholder for the frame + size. In principle, we might lower it later (after some + optimisation had reduced the frame size). At present, + without any such optimisation, we just set it here. */ + tree lhs = gimple_call_lhs (stmt); + tree size = gimple_call_arg (stmt, 0); + /* Right now, this is a trivial operation - copy through + the size computed during initial layout. */ + gassign *grpl = gimple_build_assign (lhs, size); + gsi_replace (&gsi, grpl, true); + gsi_next (&gsi); + } + break; + case IFN_CO_ACTOR: + changed = true; + actor_worklist.safe_push (gsi); /* Save for later. */ + gsi_next (&gsi); + break; + case IFN_CO_YIELD: + { + changed = true; + /* .CO_YIELD (NUM, FINAL, RES_LAB, DEST_LAB, FRAME_PTR); + NUM = await number. + FINAL = 1 if this is the final_suspend() await. + RES_LAB = resume point label. + DEST_LAB = destroy point label. + FRAME_PTR = is a null pointer with the type of the coro + frame, so that we can resize, if needed. */ + if (dump_file) + fprintf (dump_file, "saw CO_YIELD in BB %u\n", bb->index); + tree num = gimple_call_arg (stmt, 0); /* yield point. */ + HOST_WIDE_INT idx = TREE_INT_CST_LOW (num); + bool existed; + tree res_tgt = TREE_OPERAND (gimple_call_arg (stmt, 2), 0); + tree &res_dest = destinations.get_or_insert (idx, &existed); + if (existed && dump_file) + { + fprintf ( + dump_file, + "duplicate YIELD RESUME point (" HOST_WIDE_INT_PRINT_DEC + ") ?\n", + idx); + print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS); + } + else + res_dest = res_tgt; + tree dst_tgt = TREE_OPERAND (gimple_call_arg (stmt, 3), 0); + tree &dst_dest = destinations.get_or_insert (idx + 1, &existed); + if (existed && dump_file) + { + fprintf ( + dump_file, + "duplicate YIELD DESTROY point (" HOST_WIDE_INT_PRINT_DEC + ") ?\n", + idx + 1); + print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS); + } + else + dst_dest = dst_tgt; + to_remove.add (res_tgt); + to_remove.add (dst_tgt); + /* lose the co_yield. */ + gsi_remove (&gsi, true); + stmt = gsi_stmt (gsi); /* next. */ + /* lose the copy present at O0. */ + if (is_gimple_assign (stmt)) + { + gsi_remove (&gsi, true); + stmt = gsi_stmt (gsi); + } + /* Simplify the switch or if following. */ + if (gswitch *gsw = dyn_cast<gswitch *> (stmt)) + { + gimple_switch_set_index (gsw, integer_zero_node); + fold_stmt (&gsi); + } + else if (gcond *gif = dyn_cast<gcond *> (stmt)) + { + if (gimple_cond_code (gif) == EQ_EXPR) + gimple_cond_make_true (gif); + else + gimple_cond_make_false (gif); + fold_stmt (&gsi); + } + else if (dump_file) + print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS); + if (gsi_end_p (gsi)) + break; + continue; + } + default: + gsi_next (&gsi); + break; + } + } + + if (!changed) + { + if (dump_file) + fprintf (dump_file, "coro: nothing to do\n"); + return todoflags; + } + + while (!actor_worklist.is_empty ()) + { + gsi = actor_worklist.pop (); + gimple *stmt = gsi_stmt (gsi); + gcc_checking_assert (is_gimple_call (stmt) + && gimple_call_internal_p (stmt) + && gimple_call_internal_fn (stmt) == IFN_CO_ACTOR); + bb = gsi_bb (gsi); + HOST_WIDE_INT idx = TREE_INT_CST_LOW (gimple_call_arg (stmt, 0)); + tree *seen = destinations.get (idx); + changed = true; + + if (dump_file) + fprintf (dump_file, "saw CO_ACTOR in BB %u\n", bb->index); + + if (!seen) + { + /* If we never saw this index, it means that the CO_YIELD + associated was elided during earlier optimisations, so we + don't need to fix up the switch targets. */ + if (dump_file) + fprintf (dump_file, "yield point " HOST_WIDE_INT_PRINT_DEC + " not used, removing it .. \n", idx); + gsi_remove (&gsi, true); + release_defs (stmt); + } + else + { + /* So we need to switch the target of this switch case to the + relevant BB. */ + basic_block new_bb = label_to_block (cfun, *seen); + /* We expect the block we're modifying to contain a single + CO_ACTOR() followed by a goto <switch default bb>. */ + gcc_checking_assert (EDGE_COUNT (bb->succs) == 1); + edge e; + edge_iterator ei; + FOR_EACH_EDGE (e, ei, bb->succs) + { + basic_block old_bb = e->dest; + move_edge_and_update (e, old_bb, new_bb); + } + gsi_remove (&gsi, true); + } + } + + /* Remove the labels we inserted to map our hidden CFG, this + avoids confusing block merges when there are also EH labels. */ + FOR_EACH_BB_FN (bb, cfun) + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi);) + { + gimple *stmt = gsi_stmt (gsi); + if (glabel *glab = dyn_cast<glabel *> (stmt)) + { + tree rem = gimple_label_label (glab); + if (to_remove.contains (rem)) + { + gsi_remove (&gsi, true); + to_remove.remove (rem); + continue; /* We already moved to the next insn. */ + } + } + else + break; + gsi_next (&gsi); + } + + /* Changed the CFG. */ + todoflags |= TODO_cleanup_cfg; + return todoflags; +} + +namespace { + +const pass_data pass_data_coroutine_early_expand_ifns = { + GIMPLE_PASS, /* type */ + "coro-early-expand-ifns", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_NONE, /* tv_id */ + (PROP_cfg), /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0 /* todo_flags_finish, set this in the fn. */ +}; + +class pass_coroutine_early_expand_ifns : public gimple_opt_pass +{ +public: + pass_coroutine_early_expand_ifns (gcc::context *ctxt) + : gimple_opt_pass (pass_data_coroutine_early_expand_ifns, ctxt) + {} + + /* opt_pass methods: */ + virtual bool gate (function *f) + { + return flag_coroutines && f->coroutine_component; + } + + virtual unsigned int execute (function *f ATTRIBUTE_UNUSED) + { + return execute_early_expand_coro_ifns (); + } + +}; // class pass_coroutine_expand_ifns + +} // namespace + +gimple_opt_pass * +make_pass_coroutine_early_expand_ifns (gcc::context *ctxt) +{ + return new pass_coroutine_early_expand_ifns (ctxt); +}