1/* coroutine expansion and optimisation passes.
2
3 Copyright (C) 2018-2023 Free Software Foundation, Inc.
4
5 Contributed by Iain Sandoe <iain@sandoe.co.uk> under contract to Facebook.
6
7This file is part of GCC.
8
9GCC is free software; you can redistribute it and/or modify it under
10the terms of the GNU General Public License as published by the Free
11Software Foundation; either version 3, or (at your option) any later
12version.
13
14GCC is distributed in the hope that it will be useful, but WITHOUT ANY
15WARRANTY; without even the implied warranty of MERCHANTABILITY or
16FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17for more details.
18
19You should have received a copy of the GNU General Public License
20along with GCC; see the file COPYING3. If not see
21<http://www.gnu.org/licenses/>. */
22
23#include "config.h"
24#include "system.h"
25#include "coretypes.h"
26#include "backend.h"
27#include "target.h"
28#include "tree.h"
29#include "gimple.h"
30#include "tree-pass.h"
31#include "ssa.h"
32#include "cgraph.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"
38#include "gimplify.h"
39#include "gimple-iterator.h"
40#include "gimplify-me.h"
41#include "gimple-walk.h"
42#include "gimple-fold.h"
43#include "tree-cfg.h"
44#include "tree-into-ssa.h"
45#include "tree-ssa-propagate.h"
46#include "gimple-pretty-print.h"
47#include "cfghooks.h"
48
49/* Here we:
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. */
53
54static tree
55lower_coro_builtin (gimple_stmt_iterator *gsi, bool *handled_ops_p,
56 struct walk_stmt_info *wi ATTRIBUTE_UNUSED)
57{
58 gimple *stmt = gsi_stmt (i: *gsi);
59 *handled_ops_p = !gimple_has_substatements (g: stmt);
60
61 if (gimple_code (g: stmt) != GIMPLE_CALL)
62 return NULL_TREE;
63
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 (gs: stmt)
67 && gimple_call_internal_fn (gs: stmt) == IFN_CO_SUSPN)
68 {
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;
73 return NULL_TREE;
74 }
75
76 tree decl = gimple_call_fndecl (gs: stmt);
77 if (!decl || !fndecl_built_in_p (node: decl, klass: BUILT_IN_NORMAL))
78 return NULL_TREE;
79
80 /* The remaining builtins implement the library interfaces to the coro
81 frame. */
82 unsigned call_idx = 0;
83
84 switch (DECL_FUNCTION_CODE (decl))
85 {
86 default:
87 break;
88 case BUILT_IN_CORO_PROMISE:
89 {
90 /* If we are discarding this, then skip it; the function has no
91 side-effects. */
92 tree lhs = gimple_call_lhs (gs: stmt);
93 if (!lhs)
94 {
95 gsi_remove (gsi, true);
96 *handled_ops_p = true;
97 return NULL_TREE;
98 }
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 (gs: stmt, index: 0);
107 tree align_t = gimple_call_arg (gs: stmt, index: 1);
108 tree from = gimple_call_arg (gs: stmt, index: 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 (t: 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,
121 size_int (offs));
122 gassign *grpl = gimple_build_assign (lhs, repl);
123 gsi_replace (gsi, grpl, true);
124 *handled_ops_p = true;
125 }
126 break;
127 case BUILT_IN_CORO_DESTROY:
128 call_idx = 1;
129 /* FALLTHROUGH */
130 case BUILT_IN_CORO_RESUME:
131 {
132 tree ptr = gimple_call_arg (gs: stmt, index: 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 (gs: static_cast<gcall *> (stmt), fn: f_ptr_tmp);
145 *handled_ops_p = true;
146 }
147 break;
148 case BUILT_IN_CORO_DONE:
149 {
150 /* If we are discarding this, then skip it; the function has no
151 side-effects. */
152 tree lhs = gimple_call_lhs (gs: stmt);
153 if (!lhs)
154 {
155 gsi_remove (gsi, true);
156 *handled_ops_p = true;
157 return NULL_TREE;
158 }
159 /* When we're done, the resume fn is set to NULL. */
160 tree ptr = gimple_call_arg (gs: stmt, index: 0); /* frame ptr. */
161 tree vpp = build_pointer_type (ptr_type_node);
162 tree indirect
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,
168 null_pointer_node);
169 gassign *get_res = gimple_build_assign (lhs, done);
170 gsi_replace (gsi, get_res, true);
171 *handled_ops_p = true;
172 }
173 break;
174 }
175 return NULL_TREE;
176}
177
178/* Main entry point for lowering coroutine FE builtins. */
179
180static unsigned int
181execute_lower_coro_builtins (void)
182{
183 struct walk_stmt_info wi;
184 gimple_seq body;
185
186 body = gimple_body (current_function_decl);
187 memset (s: &wi, c: 0, n: sizeof (wi));
188 walk_gimple_seq_mod (&body, lower_coro_builtin, NULL, &wi);
189 gimple_set_body (current_function_decl, body);
190
191 return 0;
192}
193
194namespace {
195
196const pass_data pass_data_coroutine_lower_builtins = {
197 .type: GIMPLE_PASS, /* type */
198 .name: "coro-lower-builtins", /* name */
199 .optinfo_flags: OPTGROUP_NONE, /* optinfo_flags */
200 .tv_id: TV_NONE, /* tv_id */
201 .properties_required: 0, /* properties_required */
202 .properties_provided: 0, /* properties_provided */
203 .properties_destroyed: 0, /* properties_destroyed */
204 .todo_flags_start: 0, /* todo_flags_start */
205 .todo_flags_finish: 0 /* todo_flags_finish */
206};
207
208class pass_coroutine_lower_builtins : public gimple_opt_pass
209{
210public:
211 pass_coroutine_lower_builtins (gcc::context *ctxt)
212 : gimple_opt_pass (pass_data_coroutine_lower_builtins, ctxt)
213 {}
214
215 /* opt_pass methods: */
216 bool gate (function *) final override { return flag_coroutines; };
217
218 unsigned int execute (function *f ATTRIBUTE_UNUSED) final override
219 {
220 return execute_lower_coro_builtins ();
221 }
222
223}; // class pass_coroutine_lower_builtins
224
225} // namespace
226
227gimple_opt_pass *
228make_pass_coroutine_lower_builtins (gcc::context *ctxt)
229{
230 return new pass_coroutine_lower_builtins (ctxt);
231}
232
233/* Expand the remaining coroutine IFNs.
234
235 In the front end we construct a single actor function that contains
236 the coroutine state machine.
237
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.
242
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).
246
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.
249
250 So, if we have:
251
252 CO_YIELD (NUM, FINAL, RES_LAB, DEST_LAB, FRAME_PTR)
253
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.
256
257 We expect to find a CO_ACTOR (NUM) in the resume dispatcher and a
258 CO_ACTOR (NUM+1) in the destroy dispatcher.
259
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
263 were earlier.
264
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.
268
269 Once we have remade the connections to their correct postions, we elide
270 the labels that the front end inserted. */
271
272static void
273move_edge_and_update (edge e, basic_block old_bb, basic_block new_bb)
274{
275 if (dump_file)
276 fprintf (stream: dump_file, format: "redirecting edge from bb %u to bb %u\n", old_bb->index,
277 new_bb->index);
278
279 e = redirect_edge_and_branch (e, new_bb);
280 if (!e && dump_file)
281 fprintf (stream: dump_file, format: "failed to redirect edge .. \n");
282
283 /* Die if we failed. */
284 gcc_checking_assert (e);
285}
286
287static unsigned int
288execute_early_expand_coro_ifns (void)
289{
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;
301 basic_block bb;
302 gimple_stmt_iterator gsi;
303
304 FOR_EACH_BB_FN (bb, cfun)
305 for (gsi = gsi_start_bb (bb); !gsi_end_p (i: gsi);)
306 {
307 gimple *stmt = gsi_stmt (i: gsi);
308
309 if (!is_gimple_call (gs: stmt) || !gimple_call_internal_p (gs: stmt))
310 {
311 gsi_next (i: &gsi);
312 continue;
313 }
314 switch (gimple_call_internal_fn (gs: stmt))
315 {
316 case IFN_CO_FRAME:
317 {
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 (gs: stmt);
323 tree size = gimple_call_arg (gs: stmt, index: 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);
328 gsi_next (i: &gsi);
329 }
330 break;
331 case IFN_CO_ACTOR:
332 changed = true;
333 actor_worklist.safe_push (obj: gsi); /* Save for later. */
334 gsi_next (i: &gsi);
335 break;
336 case IFN_CO_YIELD:
337 {
338 changed = true;
339 /* .CO_YIELD (NUM, FINAL, RES_LAB, DEST_LAB, FRAME_PTR);
340 NUM = await number.
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. */
346 if (dump_file)
347 fprintf (stream: dump_file, format: "saw CO_YIELD in BB %u\n", bb->index);
348 tree num = gimple_call_arg (gs: stmt, index: 0); /* yield point. */
349 HOST_WIDE_INT idx = TREE_INT_CST_LOW (num);
350 bool existed;
351 tree res_tgt = TREE_OPERAND (gimple_call_arg (stmt, 2), 0);
352 tree &res_dest = destinations.get_or_insert (k: idx, existed: &existed);
353 if (existed && dump_file)
354 {
355 fprintf (
356 stream: dump_file,
357 format: "duplicate YIELD RESUME point (" HOST_WIDE_INT_PRINT_DEC
358 ") ?\n",
359 idx);
360 print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS);
361 }
362 else
363 res_dest = res_tgt;
364 tree dst_tgt = TREE_OPERAND (gimple_call_arg (stmt, 3), 0);
365 tree &dst_dest = destinations.get_or_insert (k: idx + 1, existed: &existed);
366 if (existed && dump_file)
367 {
368 fprintf (
369 stream: dump_file,
370 format: "duplicate YIELD DESTROY point (" HOST_WIDE_INT_PRINT_DEC
371 ") ?\n",
372 idx + 1);
373 print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS);
374 }
375 else
376 dst_dest = dst_tgt;
377 to_remove.add (k: res_tgt);
378 to_remove.add (k: dst_tgt);
379 /* lose the co_yield. */
380 gsi_remove (&gsi, true);
381 stmt = gsi_stmt (i: gsi); /* next. */
382 /* lose the copy present at O0. */
383 if (is_gimple_assign (gs: stmt))
384 {
385 gsi_remove (&gsi, true);
386 stmt = gsi_stmt (i: gsi);
387 }
388 /* Simplify the switch or if following. */
389 if (gswitch *gsw = dyn_cast<gswitch *> (p: stmt))
390 {
391 gimple_switch_set_index (gs: gsw, integer_zero_node);
392 fold_stmt (&gsi);
393 }
394 else if (gcond *gif = dyn_cast<gcond *> (p: stmt))
395 {
396 if (gimple_cond_code (gs: gif) == EQ_EXPR)
397 gimple_cond_make_true (gs: gif);
398 else
399 gimple_cond_make_false (gs: gif);
400 fold_stmt (&gsi);
401 }
402 else if (dump_file)
403 print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS);
404 if (gsi_end_p (i: gsi))
405 break;
406 continue;
407 }
408 default:
409 gsi_next (i: &gsi);
410 break;
411 }
412 }
413
414 if (!changed)
415 {
416 if (dump_file)
417 fprintf (stream: dump_file, format: "coro: nothing to do\n");
418 return todoflags;
419 }
420
421 while (!actor_worklist.is_empty ())
422 {
423 gsi = actor_worklist.pop ();
424 gimple *stmt = gsi_stmt (i: gsi);
425 gcc_checking_assert (is_gimple_call (stmt)
426 && gimple_call_internal_p (stmt)
427 && gimple_call_internal_fn (stmt) == IFN_CO_ACTOR);
428 bb = gsi_bb (i: gsi);
429 HOST_WIDE_INT idx = TREE_INT_CST_LOW (gimple_call_arg (stmt, 0));
430 tree *seen = destinations.get (k: idx);
431 changed = true;
432
433 if (dump_file)
434 fprintf (stream: dump_file, format: "saw CO_ACTOR in BB %u\n", bb->index);
435
436 if (!seen)
437 {
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. */
441 if (dump_file)
442 fprintf (stream: dump_file, format: "yield point " HOST_WIDE_INT_PRINT_DEC
443 " not used, removing it .. \n", idx);
444 gsi_remove (&gsi, true);
445 release_defs (stmt);
446 }
447 else
448 {
449 /* So we need to switch the target of this switch case to the
450 relevant BB. */
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);
455 edge e;
456 edge_iterator ei;
457 FOR_EACH_EDGE (e, ei, bb->succs)
458 {
459 basic_block old_bb = e->dest;
460 move_edge_and_update (e, old_bb, new_bb);
461 }
462 gsi_remove (&gsi, true);
463 }
464 }
465
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 (i: gsi);)
470 {
471 gimple *stmt = gsi_stmt (i: gsi);
472 if (glabel *glab = dyn_cast<glabel *> (p: stmt))
473 {
474 tree rem = gimple_label_label (gs: glab);
475 if (to_remove.contains (k: rem))
476 {
477 gsi_remove (&gsi, true);
478 to_remove.remove (k: rem);
479 continue; /* We already moved to the next insn. */
480 }
481 }
482 else
483 break;
484 gsi_next (i: &gsi);
485 }
486
487 /* Changed the CFG. */
488 todoflags |= TODO_cleanup_cfg;
489 return todoflags;
490}
491
492namespace {
493
494const pass_data pass_data_coroutine_early_expand_ifns = {
495 .type: GIMPLE_PASS, /* type */
496 .name: "coro-early-expand-ifns", /* name */
497 .optinfo_flags: OPTGROUP_NONE, /* optinfo_flags */
498 .tv_id: TV_NONE, /* tv_id */
499 .properties_required: (PROP_cfg), /* properties_required */
500 .properties_provided: 0, /* properties_provided */
501 .properties_destroyed: 0, /* properties_destroyed */
502 .todo_flags_start: 0, /* todo_flags_start */
503 .todo_flags_finish: 0 /* todo_flags_finish, set this in the fn. */
504};
505
506class pass_coroutine_early_expand_ifns : public gimple_opt_pass
507{
508public:
509 pass_coroutine_early_expand_ifns (gcc::context *ctxt)
510 : gimple_opt_pass (pass_data_coroutine_early_expand_ifns, ctxt)
511 {}
512
513 /* opt_pass methods: */
514 bool gate (function *f) final override
515 {
516 return flag_coroutines && f->coroutine_component;
517 }
518
519 unsigned int execute (function *f ATTRIBUTE_UNUSED) final override
520 {
521 return execute_early_expand_coro_ifns ();
522 }
523
524}; // class pass_coroutine_expand_ifns
525
526} // namespace
527
528gimple_opt_pass *
529make_pass_coroutine_early_expand_ifns (gcc::context *ctxt)
530{
531 return new pass_coroutine_early_expand_ifns (ctxt);
532}
533

source code of gcc/coroutine-passes.cc