1 /* coroutine expansion and optimisation passes.
3 Copyright (C) 2018-2021 Free Software Foundation, Inc.
5 Contributed by Iain Sandoe <iain@sandoe.co.uk> under contract to Facebook.
7 This file is part of GCC.
9 GCC is free software; you can redistribute it and/or modify it under
10 the terms of the GNU General Public License as published by the Free
11 Software Foundation; either version 3, or (at your option) any later
14 GCC is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19 You should have received a copy of the GNU General Public License
20 along with GCC; see the file COPYING3. If not see
21 <http://www.gnu.org/licenses/>. */
25 #include "coretypes.h"
30 #include "tree-pass.h"
33 #include "pretty-print.h"
34 #include "diagnostic-core.h"
35 #include "fold-const.h"
36 #include "internal-fn.h"
37 #include "langhooks.h"
39 #include "gimple-iterator.h"
40 #include "gimplify-me.h"
41 #include "gimple-walk.h"
42 #include "gimple-fold.h"
44 #include "tree-into-ssa.h"
45 #include "tree-ssa-propagate.h"
46 #include "gimple-pretty-print.h"
50 * lower the internal function that implements an exit from scope.
51 * expand the builtins that are used to implement the library
52 interfaces to the coroutine frame. */
55 lower_coro_builtin (gimple_stmt_iterator
*gsi
, bool *handled_ops_p
,
56 struct walk_stmt_info
*wi ATTRIBUTE_UNUSED
)
58 gimple
*stmt
= gsi_stmt (*gsi
);
59 *handled_ops_p
= !gimple_has_substatements (stmt
);
61 if (gimple_code (stmt
) != GIMPLE_CALL
)
64 /* This internal function implements an exit from scope without
65 performing any cleanups; it jumps directly to the label provided. */
66 if (gimple_call_internal_p (stmt
)
67 && gimple_call_internal_fn (stmt
) == IFN_CO_SUSPN
)
69 tree dest
= TREE_OPERAND (gimple_call_arg (stmt
, 0), 0);
70 ggoto
*g
= gimple_build_goto (dest
);
71 gsi_replace (gsi
, g
, /* do EH */ false);
72 *handled_ops_p
= true;
76 tree decl
= gimple_call_fndecl (stmt
);
77 if (!decl
|| !fndecl_built_in_p (decl
, BUILT_IN_NORMAL
))
80 /* The remaining builtins implement the library interfaces to the coro
82 unsigned call_idx
= 0;
84 switch (DECL_FUNCTION_CODE (decl
))
88 case BUILT_IN_CORO_PROMISE
:
90 /* If we are discarding this, then skip it; the function has no
92 tree lhs
= gimple_call_lhs (stmt
);
95 gsi_remove (gsi
, true);
96 *handled_ops_p
= true;
99 /* The coro frame starts with two pointers (to the resume and
100 destroy() functions). These are followed by the promise which
101 is aligned as per type [or user attribute].
102 The input pointer is the first argument.
103 The promise alignment is the second and the third is a bool
104 that is true when we are converting from a promise ptr to a
105 frame pointer, and false for the inverse. */
106 tree ptr
= gimple_call_arg (stmt
, 0);
107 tree align_t
= gimple_call_arg (stmt
, 1);
108 tree from
= gimple_call_arg (stmt
, 2);
109 gcc_checking_assert (TREE_CODE (align_t
) == INTEGER_CST
);
110 gcc_checking_assert (TREE_CODE (from
) == INTEGER_CST
);
111 bool dir
= wi::to_wide (from
) != 0;
112 HOST_WIDE_INT promise_align
= TREE_INT_CST_LOW (align_t
);
113 HOST_WIDE_INT psize
=
114 TREE_INT_CST_LOW (TYPE_SIZE_UNIT (ptr_type_node
));
115 HOST_WIDE_INT align
= TYPE_ALIGN_UNIT (ptr_type_node
);
116 align
= MAX (align
, promise_align
);
117 psize
*= 2; /* Start with two pointers. */
118 psize
= ROUND_UP (psize
, align
);
119 HOST_WIDE_INT offs
= dir
? -psize
: psize
;
120 tree repl
= build2 (POINTER_PLUS_EXPR
, ptr_type_node
, ptr
,
122 gassign
*grpl
= gimple_build_assign (lhs
, repl
);
123 gsi_replace (gsi
, grpl
, true);
124 *handled_ops_p
= true;
127 case BUILT_IN_CORO_DESTROY
:
130 case BUILT_IN_CORO_RESUME
:
132 tree ptr
= gimple_call_arg (stmt
, 0); /* frame ptr. */
133 HOST_WIDE_INT psize
=
134 TREE_INT_CST_LOW (TYPE_SIZE_UNIT (ptr_type_node
));
135 HOST_WIDE_INT offset
= call_idx
* psize
;
136 tree fntype
= TREE_TYPE (decl
);
137 tree fntype_ptr
= build_pointer_type (fntype
);
138 tree fntype_ppp
= build_pointer_type (fntype_ptr
);
139 tree indirect
= fold_build2 (MEM_REF
, fntype_ptr
, ptr
,
140 build_int_cst (fntype_ppp
, offset
));
141 tree f_ptr_tmp
= make_ssa_name (TYPE_MAIN_VARIANT (fntype_ptr
));
142 gassign
*get_fptr
= gimple_build_assign (f_ptr_tmp
, indirect
);
143 gsi_insert_before (gsi
, get_fptr
, GSI_SAME_STMT
);
144 gimple_call_set_fn (static_cast<gcall
*> (stmt
), f_ptr_tmp
);
145 *handled_ops_p
= true;
148 case BUILT_IN_CORO_DONE
:
150 /* If we are discarding this, then skip it; the function has no
152 tree lhs
= gimple_call_lhs (stmt
);
155 gsi_remove (gsi
, true);
156 *handled_ops_p
= true;
159 /* When we're done, the resume fn is set to NULL. */
160 tree ptr
= gimple_call_arg (stmt
, 0); /* frame ptr. */
161 tree vpp
= build_pointer_type (ptr_type_node
);
163 = fold_build2 (MEM_REF
, vpp
, ptr
, build_int_cst (vpp
, 0));
164 tree d_ptr_tmp
= make_ssa_name (ptr_type_node
);
165 gassign
*get_dptr
= gimple_build_assign (d_ptr_tmp
, indirect
);
166 gsi_insert_before (gsi
, get_dptr
, GSI_SAME_STMT
);
167 tree done
= fold_build2 (EQ_EXPR
, boolean_type_node
, d_ptr_tmp
,
169 gassign
*get_res
= gimple_build_assign (lhs
, done
);
170 gsi_replace (gsi
, get_res
, true);
171 *handled_ops_p
= true;
178 /* Main entry point for lowering coroutine FE builtins. */
181 execute_lower_coro_builtins (void)
183 struct walk_stmt_info wi
;
186 body
= gimple_body (current_function_decl
);
187 memset (&wi
, 0, sizeof (wi
));
188 walk_gimple_seq_mod (&body
, lower_coro_builtin
, NULL
, &wi
);
189 gimple_set_body (current_function_decl
, body
);
196 const pass_data pass_data_coroutine_lower_builtins
= {
197 GIMPLE_PASS
, /* type */
198 "coro-lower-builtins", /* name */
199 OPTGROUP_NONE
, /* optinfo_flags */
201 0, /* properties_required */
202 0, /* properties_provided */
203 0, /* properties_destroyed */
204 0, /* todo_flags_start */
205 0 /* todo_flags_finish */
208 class pass_coroutine_lower_builtins
: public gimple_opt_pass
211 pass_coroutine_lower_builtins (gcc::context
*ctxt
)
212 : gimple_opt_pass (pass_data_coroutine_lower_builtins
, ctxt
)
215 /* opt_pass methods: */
216 virtual bool gate (function
*) { return flag_coroutines
; };
218 virtual unsigned int execute (function
*f ATTRIBUTE_UNUSED
)
220 return execute_lower_coro_builtins ();
223 }; // class pass_coroutine_lower_builtins
228 make_pass_coroutine_lower_builtins (gcc::context
*ctxt
)
230 return new pass_coroutine_lower_builtins (ctxt
);
233 /* Expand the remaining coroutine IFNs.
235 In the front end we construct a single actor function that contains
236 the coroutine state machine.
238 The actor function has three entry conditions:
239 1. from the ramp, resume point 0 - to initial-suspend.
240 2. when resume () is executed (resume point N).
241 3. from the destroy () shim when that is executed.
243 The actor function begins with two dispatchers; one for resume and
244 one for destroy (where the initial entry from the ramp is a special-
245 case of resume point 0).
247 Each suspend point and each dispatch entry is marked with an IFN such
248 that we can connect the relevant dispatchers to their target labels.
252 CO_YIELD (NUM, FINAL, RES_LAB, DEST_LAB, FRAME_PTR)
254 This is await point NUM, and is the final await if FINAL is non-zero.
255 The resume point is RES_LAB, and the destroy point is DEST_LAB.
257 We expect to find a CO_ACTOR (NUM) in the resume dispatcher and a
258 CO_ACTOR (NUM+1) in the destroy dispatcher.
260 Initially, the intent of keeping the resume and destroy paths together
261 is that the conditionals controlling them are identical, and thus there
262 would be duplication of any optimisation of those paths if the split
265 Subsequent inlining of the actor (and DCE) is then able to extract the
266 resume and destroy paths as separate functions if that is found
267 profitable by the optimisers.
269 Once we have remade the connections to their correct postions, we elide
270 the labels that the front end inserted. */
273 move_edge_and_update (edge e
, basic_block old_bb
, basic_block new_bb
)
276 fprintf (dump_file
, "redirecting edge from bb %u to bb %u\n", old_bb
->index
,
279 e
= redirect_edge_and_branch (e
, new_bb
);
281 fprintf (dump_file
, "failed to redirect edge .. \n");
283 /* Die if we failed. */
284 gcc_checking_assert (e
);
288 execute_early_expand_coro_ifns (void)
290 /* Don't rebuild stuff unless we have to. */
291 unsigned int todoflags
= 0;
292 bool changed
= false;
293 /* Some of the possible YIELD points will hopefully have been removed by
294 earlier optimisations; record the ones that are still present. */
295 hash_map
<int_hash
<HOST_WIDE_INT
, -1, -2>, tree
> destinations
;
296 /* Labels we added to carry the CFG changes, we need to remove these to
297 avoid confusing EH. */
298 hash_set
<tree
> to_remove
;
299 /* List of dispatch points to update. */
300 auto_vec
<gimple_stmt_iterator
, 16> actor_worklist
;
302 gimple_stmt_iterator gsi
;
304 FOR_EACH_BB_FN (bb
, cfun
)
305 for (gsi
= gsi_start_bb (bb
); !gsi_end_p (gsi
);)
307 gimple
*stmt
= gsi_stmt (gsi
);
309 if (!is_gimple_call (stmt
) || !gimple_call_internal_p (stmt
))
314 switch (gimple_call_internal_fn (stmt
))
318 /* This internal function is a placeholder for the frame
319 size. In principle, we might lower it later (after some
320 optimisation had reduced the frame size). At present,
321 without any such optimisation, we just set it here. */
322 tree lhs
= gimple_call_lhs (stmt
);
323 tree size
= gimple_call_arg (stmt
, 0);
324 /* Right now, this is a trivial operation - copy through
325 the size computed during initial layout. */
326 gassign
*grpl
= gimple_build_assign (lhs
, size
);
327 gsi_replace (&gsi
, grpl
, true);
333 actor_worklist
.safe_push (gsi
); /* Save for later. */
339 /* .CO_YIELD (NUM, FINAL, RES_LAB, DEST_LAB, FRAME_PTR);
341 FINAL = 1 if this is the final_suspend() await.
342 RES_LAB = resume point label.
343 DEST_LAB = destroy point label.
344 FRAME_PTR = is a null pointer with the type of the coro
345 frame, so that we can resize, if needed. */
347 fprintf (dump_file
, "saw CO_YIELD in BB %u\n", bb
->index
);
348 tree num
= gimple_call_arg (stmt
, 0); /* yield point. */
349 HOST_WIDE_INT idx
= TREE_INT_CST_LOW (num
);
351 tree res_tgt
= TREE_OPERAND (gimple_call_arg (stmt
, 2), 0);
352 tree
&res_dest
= destinations
.get_or_insert (idx
, &existed
);
353 if (existed
&& dump_file
)
357 "duplicate YIELD RESUME point (" HOST_WIDE_INT_PRINT_DEC
360 print_gimple_stmt (dump_file
, stmt
, 0, TDF_VOPS
|TDF_MEMSYMS
);
364 tree dst_tgt
= TREE_OPERAND (gimple_call_arg (stmt
, 3), 0);
365 tree
&dst_dest
= destinations
.get_or_insert (idx
+ 1, &existed
);
366 if (existed
&& dump_file
)
370 "duplicate YIELD DESTROY point (" HOST_WIDE_INT_PRINT_DEC
373 print_gimple_stmt (dump_file
, stmt
, 0, TDF_VOPS
|TDF_MEMSYMS
);
377 to_remove
.add (res_tgt
);
378 to_remove
.add (dst_tgt
);
379 /* lose the co_yield. */
380 gsi_remove (&gsi
, true);
381 stmt
= gsi_stmt (gsi
); /* next. */
382 /* lose the copy present at O0. */
383 if (is_gimple_assign (stmt
))
385 gsi_remove (&gsi
, true);
386 stmt
= gsi_stmt (gsi
);
388 /* Simplify the switch or if following. */
389 if (gswitch
*gsw
= dyn_cast
<gswitch
*> (stmt
))
391 gimple_switch_set_index (gsw
, integer_zero_node
);
394 else if (gcond
*gif
= dyn_cast
<gcond
*> (stmt
))
396 if (gimple_cond_code (gif
) == EQ_EXPR
)
397 gimple_cond_make_true (gif
);
399 gimple_cond_make_false (gif
);
403 print_gimple_stmt (dump_file
, stmt
, 0, TDF_VOPS
|TDF_MEMSYMS
);
417 fprintf (dump_file
, "coro: nothing to do\n");
421 while (!actor_worklist
.is_empty ())
423 gsi
= actor_worklist
.pop ();
424 gimple
*stmt
= gsi_stmt (gsi
);
425 gcc_checking_assert (is_gimple_call (stmt
)
426 && gimple_call_internal_p (stmt
)
427 && gimple_call_internal_fn (stmt
) == IFN_CO_ACTOR
);
429 HOST_WIDE_INT idx
= TREE_INT_CST_LOW (gimple_call_arg (stmt
, 0));
430 tree
*seen
= destinations
.get (idx
);
434 fprintf (dump_file
, "saw CO_ACTOR in BB %u\n", bb
->index
);
438 /* If we never saw this index, it means that the CO_YIELD
439 associated was elided during earlier optimisations, so we
440 don't need to fix up the switch targets. */
442 fprintf (dump_file
, "yield point " HOST_WIDE_INT_PRINT_DEC
443 " not used, removing it .. \n", idx
);
444 gsi_remove (&gsi
, true);
449 /* So we need to switch the target of this switch case to the
451 basic_block new_bb
= label_to_block (cfun
, *seen
);
452 /* We expect the block we're modifying to contain a single
453 CO_ACTOR() followed by a goto <switch default bb>. */
454 gcc_checking_assert (EDGE_COUNT (bb
->succs
) == 1);
457 FOR_EACH_EDGE (e
, ei
, bb
->succs
)
459 basic_block old_bb
= e
->dest
;
460 move_edge_and_update (e
, old_bb
, new_bb
);
462 gsi_remove (&gsi
, true);
466 /* Remove the labels we inserted to map our hidden CFG, this
467 avoids confusing block merges when there are also EH labels. */
468 FOR_EACH_BB_FN (bb
, cfun
)
469 for (gsi
= gsi_start_bb (bb
); !gsi_end_p (gsi
);)
471 gimple
*stmt
= gsi_stmt (gsi
);
472 if (glabel
*glab
= dyn_cast
<glabel
*> (stmt
))
474 tree rem
= gimple_label_label (glab
);
475 if (to_remove
.contains (rem
))
477 gsi_remove (&gsi
, true);
478 to_remove
.remove (rem
);
479 continue; /* We already moved to the next insn. */
487 /* Changed the CFG. */
488 todoflags
|= TODO_cleanup_cfg
;
494 const pass_data pass_data_coroutine_early_expand_ifns
= {
495 GIMPLE_PASS
, /* type */
496 "coro-early-expand-ifns", /* name */
497 OPTGROUP_NONE
, /* optinfo_flags */
499 (PROP_cfg
), /* properties_required */
500 0, /* properties_provided */
501 0, /* properties_destroyed */
502 0, /* todo_flags_start */
503 0 /* todo_flags_finish, set this in the fn. */
506 class pass_coroutine_early_expand_ifns
: public gimple_opt_pass
509 pass_coroutine_early_expand_ifns (gcc::context
*ctxt
)
510 : gimple_opt_pass (pass_data_coroutine_early_expand_ifns
, ctxt
)
513 /* opt_pass methods: */
514 virtual bool gate (function
*f
)
516 return flag_coroutines
&& f
->coroutine_component
;
519 virtual unsigned int execute (function
*f ATTRIBUTE_UNUSED
)
521 return execute_early_expand_coro_ifns ();
524 }; // class pass_coroutine_expand_ifns
529 make_pass_coroutine_early_expand_ifns (gcc::context
*ctxt
)
531 return new pass_coroutine_early_expand_ifns (ctxt
);