libcpp, c, middle-end: Optimize initializers using #embed in C
[official-gcc.git] / gcc / coroutine-passes.cc
blob0f8e24f8d5513e9b90312dd29c7f272f2d15afcc
1 /* coroutine expansion and optimisation passes.
3 Copyright (C) 2018-2024 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
12 version.
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
17 for more details.
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/>. */
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 "calls.h"
33 #include "cgraph.h"
34 #include "pretty-print.h"
35 #include "diagnostic-core.h"
36 #include "fold-const.h"
37 #include "internal-fn.h"
38 #include "langhooks.h"
39 #include "gimplify.h"
40 #include "gimple-iterator.h"
41 #include "gimplify-me.h"
42 #include "gimple-walk.h"
43 #include "gimple-fold.h"
44 #include "tree-cfg.h"
45 #include "tree-into-ssa.h"
46 #include "tree-ssa-propagate.h"
47 #include "gimple-pretty-print.h"
48 #include "cfghooks.h"
50 /* Here we:
51 * lower the internal function that implements an exit from scope.
52 * expand the builtins that are used to implement the library
53 interfaces to the coroutine frame. */
55 static tree
56 lower_coro_builtin (gimple_stmt_iterator *gsi, bool *handled_ops_p,
57 struct walk_stmt_info *wi ATTRIBUTE_UNUSED)
59 gimple *stmt = gsi_stmt (*gsi);
60 *handled_ops_p = !gimple_has_substatements (stmt);
62 if (gimple_code (stmt) != GIMPLE_CALL)
63 return NULL_TREE;
65 /* This internal function implements an exit from scope without
66 performing any cleanups; it jumps directly to the label provided. */
67 if (gimple_call_internal_p (stmt)
68 && gimple_call_internal_fn (stmt) == IFN_CO_SUSPN)
70 tree dest = TREE_OPERAND (gimple_call_arg (stmt, 0), 0);
71 ggoto *g = gimple_build_goto (dest);
72 gsi_replace (gsi, g, /* do EH */ false);
73 *handled_ops_p = true;
74 return NULL_TREE;
77 tree decl = gimple_call_fndecl (stmt);
78 if (!decl || !fndecl_built_in_p (decl, BUILT_IN_NORMAL))
79 return NULL_TREE;
81 /* The remaining builtins implement the library interfaces to the coro
82 frame. */
83 unsigned call_idx = 0;
85 switch (DECL_FUNCTION_CODE (decl))
87 default:
88 break;
89 case BUILT_IN_CORO_PROMISE:
91 /* If we are discarding this, then skip it; the function has no
92 side-effects. */
93 tree lhs = gimple_call_lhs (stmt);
94 if (!lhs)
96 gsi_remove (gsi, true);
97 *handled_ops_p = true;
98 return NULL_TREE;
100 /* The coro frame starts with two pointers (to the resume and
101 destroy() functions). These are followed by the promise which
102 is aligned as per type [or user attribute].
103 The input pointer is the first argument.
104 The promise alignment is the second and the third is a bool
105 that is true when we are converting from a promise ptr to a
106 frame pointer, and false for the inverse. */
107 tree ptr = gimple_call_arg (stmt, 0);
108 tree align_t = gimple_call_arg (stmt, 1);
109 tree from = gimple_call_arg (stmt, 2);
110 gcc_checking_assert (TREE_CODE (align_t) == INTEGER_CST);
111 gcc_checking_assert (TREE_CODE (from) == INTEGER_CST);
112 bool dir = wi::to_wide (from) != 0;
113 HOST_WIDE_INT promise_align = TREE_INT_CST_LOW (align_t);
114 HOST_WIDE_INT psize =
115 TREE_INT_CST_LOW (TYPE_SIZE_UNIT (ptr_type_node));
116 HOST_WIDE_INT align = TYPE_ALIGN_UNIT (ptr_type_node);
117 align = MAX (align, promise_align);
118 psize *= 2; /* Start with two pointers. */
119 psize = ROUND_UP (psize, align);
120 HOST_WIDE_INT offs = dir ? -psize : psize;
121 tree repl = build2 (POINTER_PLUS_EXPR, ptr_type_node, ptr,
122 size_int (offs));
123 gassign *grpl = gimple_build_assign (lhs, repl);
124 gsi_replace (gsi, grpl, true);
125 *handled_ops_p = true;
127 break;
128 case BUILT_IN_CORO_DESTROY:
129 call_idx = 1;
130 /* FALLTHROUGH */
131 case BUILT_IN_CORO_RESUME:
133 tree ptr = gimple_call_arg (stmt, 0); /* frame ptr. */
134 HOST_WIDE_INT psize =
135 TREE_INT_CST_LOW (TYPE_SIZE_UNIT (ptr_type_node));
136 HOST_WIDE_INT offset = call_idx * psize;
137 tree fntype = TREE_TYPE (decl);
138 tree fntype_ptr = build_pointer_type (fntype);
139 tree fntype_ppp = build_pointer_type (fntype_ptr);
140 tree indirect = fold_build2 (MEM_REF, fntype_ptr, ptr,
141 build_int_cst (fntype_ppp, offset));
142 tree f_ptr_tmp = make_ssa_name (TYPE_MAIN_VARIANT (fntype_ptr));
143 gassign *get_fptr = gimple_build_assign (f_ptr_tmp, indirect);
144 gsi_insert_before (gsi, get_fptr, GSI_SAME_STMT);
145 gimple_call_set_fn (static_cast<gcall *> (stmt), f_ptr_tmp);
146 *handled_ops_p = true;
148 break;
149 case BUILT_IN_CORO_DONE:
151 /* If we are discarding this, then skip it; the function has no
152 side-effects. */
153 tree lhs = gimple_call_lhs (stmt);
154 if (!lhs)
156 gsi_remove (gsi, true);
157 *handled_ops_p = true;
158 return NULL_TREE;
160 /* When we're done, the resume fn is set to NULL. */
161 tree ptr = gimple_call_arg (stmt, 0); /* frame ptr. */
162 tree vpp = build_pointer_type (ptr_type_node);
163 tree indirect
164 = fold_build2 (MEM_REF, vpp, ptr, build_int_cst (vpp, 0));
165 tree d_ptr_tmp = make_ssa_name (ptr_type_node);
166 gassign *get_dptr = gimple_build_assign (d_ptr_tmp, indirect);
167 gsi_insert_before (gsi, get_dptr, GSI_SAME_STMT);
168 tree done = fold_build2 (EQ_EXPR, boolean_type_node, d_ptr_tmp,
169 null_pointer_node);
170 gassign *get_res = gimple_build_assign (lhs, done);
171 gsi_replace (gsi, get_res, true);
172 *handled_ops_p = true;
174 break;
176 return NULL_TREE;
179 /* Main entry point for lowering coroutine FE builtins. */
181 static unsigned int
182 execute_lower_coro_builtins (void)
184 struct walk_stmt_info wi;
185 gimple_seq body;
187 body = gimple_body (current_function_decl);
188 memset (&wi, 0, sizeof (wi));
189 walk_gimple_seq_mod (&body, lower_coro_builtin, NULL, &wi);
190 gimple_set_body (current_function_decl, body);
192 return 0;
195 namespace {
197 const pass_data pass_data_coroutine_lower_builtins = {
198 GIMPLE_PASS, /* type */
199 "coro-lower-builtins", /* name */
200 OPTGROUP_NONE, /* optinfo_flags */
201 TV_NONE, /* tv_id */
202 0, /* properties_required */
203 0, /* properties_provided */
204 0, /* properties_destroyed */
205 0, /* todo_flags_start */
206 0 /* todo_flags_finish */
209 class pass_coroutine_lower_builtins : public gimple_opt_pass
211 public:
212 pass_coroutine_lower_builtins (gcc::context *ctxt)
213 : gimple_opt_pass (pass_data_coroutine_lower_builtins, ctxt)
216 /* opt_pass methods: */
217 bool gate (function *) final override { return flag_coroutines; };
219 unsigned int execute (function *f ATTRIBUTE_UNUSED) final override
221 return execute_lower_coro_builtins ();
224 }; // class pass_coroutine_lower_builtins
226 } // namespace
228 gimple_opt_pass *
229 make_pass_coroutine_lower_builtins (gcc::context *ctxt)
231 return new pass_coroutine_lower_builtins (ctxt);
234 /* Expand the remaining coroutine IFNs.
236 In the front end we construct a single actor function that contains
237 the coroutine state machine.
239 The actor function has three entry conditions:
240 1. from the ramp, resume point 0 - to initial-suspend.
241 2. when resume () is executed (resume point N).
242 3. from the destroy () shim when that is executed.
244 The actor function begins with two dispatchers; one for resume and
245 one for destroy (where the initial entry from the ramp is a special-
246 case of resume point 0).
248 Each suspend point and each dispatch entry is marked with an IFN such
249 that we can connect the relevant dispatchers to their target labels.
251 So, if we have:
253 CO_YIELD (NUM, FINAL, RES_LAB, DEST_LAB, FRAME_PTR)
255 This is await point NUM, and is the final await if FINAL is non-zero.
256 The resume point is RES_LAB, and the destroy point is DEST_LAB.
258 We expect to find a CO_ACTOR (NUM) in the resume dispatcher and a
259 CO_ACTOR (NUM+1) in the destroy dispatcher.
261 Initially, the intent of keeping the resume and destroy paths together
262 is that the conditionals controlling them are identical, and thus there
263 would be duplication of any optimisation of those paths if the split
264 were earlier.
266 Subsequent inlining of the actor (and DCE) is then able to extract the
267 resume and destroy paths as separate functions if that is found
268 profitable by the optimisers.
270 Once we have remade the connections to their correct postions, we elide
271 the labels that the front end inserted. */
273 static void
274 move_edge_and_update (edge e, basic_block old_bb, basic_block new_bb)
276 if (dump_file)
277 fprintf (dump_file, "redirecting edge from bb %u to bb %u\n", old_bb->index,
278 new_bb->index);
280 e = redirect_edge_and_branch (e, new_bb);
281 if (!e && dump_file)
282 fprintf (dump_file, "failed to redirect edge .. \n");
284 /* Die if we failed. */
285 gcc_checking_assert (e);
288 static unsigned int
289 execute_early_expand_coro_ifns (void)
291 /* Don't rebuild stuff unless we have to. */
292 unsigned int todoflags = 0;
293 bool changed = false;
294 /* Some of the possible YIELD points will hopefully have been removed by
295 earlier optimisations; record the ones that are still present. */
296 hash_map<int_hash<HOST_WIDE_INT, -1, -2>, tree> destinations;
297 /* List of dispatch points to update. */
298 auto_vec<gimple_stmt_iterator, 16> actor_worklist;
299 basic_block bb;
300 gimple_stmt_iterator gsi;
302 FOR_EACH_BB_FN (bb, cfun)
303 for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi);)
305 gimple *stmt = gsi_stmt (gsi);
307 /* Tell the user about 'alloca', we don't support it yet. */
308 if (gimple_alloca_call_p (stmt))
310 sorry_at (gimple_location (stmt),
311 "%<alloca%> is not yet supported in coroutines");
312 gsi_next (&gsi);
313 continue;
316 if (!is_gimple_call (stmt) || !gimple_call_internal_p (stmt))
318 gsi_next (&gsi);
319 continue;
321 switch (gimple_call_internal_fn (stmt))
323 case IFN_CO_FRAME:
325 /* This internal function is a placeholder for the frame
326 size. In principle, we might lower it later (after some
327 optimisation had reduced the frame size). At present,
328 without any such optimisation, we just set it here. */
329 tree lhs = gimple_call_lhs (stmt);
330 tree size = gimple_call_arg (stmt, 0);
331 /* Right now, this is a trivial operation - copy through
332 the size computed during initial layout. */
333 gassign *grpl = gimple_build_assign (lhs, size);
334 gsi_replace (&gsi, grpl, true);
335 gsi_next (&gsi);
337 break;
338 case IFN_CO_ACTOR:
339 changed = true;
340 actor_worklist.safe_push (gsi); /* Save for later. */
341 gsi_next (&gsi);
342 break;
343 case IFN_CO_YIELD:
345 changed = true;
346 /* .CO_YIELD (NUM, FINAL, RES_LAB, DEST_LAB, FRAME_PTR);
347 NUM = await number.
348 FINAL = 1 if this is the final_suspend() await.
349 RES_LAB = resume point label.
350 DEST_LAB = destroy point label.
351 FRAME_PTR = is a null pointer with the type of the coro
352 frame, so that we can resize, if needed. */
353 if (dump_file)
354 fprintf (dump_file, "saw CO_YIELD in BB %u\n", bb->index);
355 tree num = gimple_call_arg (stmt, 0); /* yield point. */
356 HOST_WIDE_INT idx = TREE_INT_CST_LOW (num);
357 bool existed;
358 tree res_tgt = TREE_OPERAND (gimple_call_arg (stmt, 2), 0);
359 tree &res_dest = destinations.get_or_insert (idx, &existed);
360 if (existed && dump_file)
362 fprintf (
363 dump_file,
364 "duplicate YIELD RESUME point (" HOST_WIDE_INT_PRINT_DEC
365 ") ?\n",
366 idx);
367 print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS);
369 else
370 res_dest = res_tgt;
371 tree dst_tgt = TREE_OPERAND (gimple_call_arg (stmt, 3), 0);
372 tree &dst_dest = destinations.get_or_insert (idx + 1, &existed);
373 if (existed && dump_file)
375 fprintf (
376 dump_file,
377 "duplicate YIELD DESTROY point (" HOST_WIDE_INT_PRINT_DEC
378 ") ?\n",
379 idx + 1);
380 print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS);
382 else
383 dst_dest = dst_tgt;
384 /* lose the co_yield. */
385 gsi_remove (&gsi, true);
386 stmt = gsi_stmt (gsi); /* next. */
387 /* lose the copy present at O0. */
388 if (is_gimple_assign (stmt))
390 gsi_remove (&gsi, true);
391 stmt = gsi_stmt (gsi);
393 /* Simplify the switch or if following. */
394 if (gswitch *gsw = dyn_cast<gswitch *> (stmt))
396 gimple_switch_set_index (gsw, integer_zero_node);
397 fold_stmt (&gsi);
399 else if (gcond *gif = dyn_cast<gcond *> (stmt))
401 if (gimple_cond_code (gif) == EQ_EXPR)
402 gimple_cond_make_true (gif);
403 else
404 gimple_cond_make_false (gif);
405 fold_stmt (&gsi);
407 else if (dump_file)
408 print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS);
409 if (gsi_end_p (gsi))
410 break;
411 continue;
413 default:
414 gsi_next (&gsi);
415 break;
419 if (!changed)
421 if (dump_file)
422 fprintf (dump_file, "coro: nothing to do\n");
423 return todoflags;
426 while (!actor_worklist.is_empty ())
428 gsi = actor_worklist.pop ();
429 gimple *stmt = gsi_stmt (gsi);
430 gcc_checking_assert (is_gimple_call (stmt)
431 && gimple_call_internal_p (stmt)
432 && gimple_call_internal_fn (stmt) == IFN_CO_ACTOR);
433 bb = gsi_bb (gsi);
434 HOST_WIDE_INT idx = TREE_INT_CST_LOW (gimple_call_arg (stmt, 0));
435 tree *seen = destinations.get (idx);
436 changed = true;
438 if (dump_file)
439 fprintf (dump_file, "saw CO_ACTOR in BB %u\n", bb->index);
441 if (!seen)
443 /* If we never saw this index, it means that the CO_YIELD
444 associated was elided during earlier optimisations, so we
445 don't need to fix up the switch targets. */
446 if (dump_file)
447 fprintf (dump_file, "yield point " HOST_WIDE_INT_PRINT_DEC
448 " not used, removing it .. \n", idx);
449 gsi_remove (&gsi, true);
450 release_defs (stmt);
452 else
454 /* So we need to switch the target of this switch case to the
455 relevant BB. */
456 basic_block new_bb = label_to_block (cfun, *seen);
457 /* We expect the block we're modifying to contain a single
458 CO_ACTOR() followed by a goto <switch default bb>. */
459 gcc_checking_assert (EDGE_COUNT (bb->succs) == 1);
460 edge e;
461 edge_iterator ei;
462 FOR_EACH_EDGE (e, ei, bb->succs)
464 basic_block old_bb = e->dest;
465 move_edge_and_update (e, old_bb, new_bb);
467 gsi_remove (&gsi, true);
471 /* Changed the CFG. */
472 todoflags |= TODO_cleanup_cfg;
473 return todoflags;
476 namespace {
478 const pass_data pass_data_coroutine_early_expand_ifns = {
479 GIMPLE_PASS, /* type */
480 "coro-early-expand-ifns", /* name */
481 OPTGROUP_NONE, /* optinfo_flags */
482 TV_NONE, /* tv_id */
483 (PROP_cfg), /* properties_required */
484 0, /* properties_provided */
485 0, /* properties_destroyed */
486 0, /* todo_flags_start */
487 0 /* todo_flags_finish, set this in the fn. */
490 class pass_coroutine_early_expand_ifns : public gimple_opt_pass
492 public:
493 pass_coroutine_early_expand_ifns (gcc::context *ctxt)
494 : gimple_opt_pass (pass_data_coroutine_early_expand_ifns, ctxt)
497 /* opt_pass methods: */
498 bool gate (function *f) final override
500 return flag_coroutines && f->coroutine_component;
503 unsigned int execute (function *f ATTRIBUTE_UNUSED) final override
505 return execute_early_expand_coro_ifns ();
508 }; // class pass_coroutine_expand_ifns
510 } // namespace
512 gimple_opt_pass *
513 make_pass_coroutine_early_expand_ifns (gcc::context *ctxt)
515 return new pass_coroutine_early_expand_ifns (ctxt);