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
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"
34 #include "pretty-print.h"
35 #include "diagnostic-core.h"
36 #include "fold-const.h"
37 #include "internal-fn.h"
38 #include "langhooks.h"
40 #include "gimple-iterator.h"
41 #include "gimplify-me.h"
42 #include "gimple-walk.h"
43 #include "gimple-fold.h"
45 #include "tree-into-ssa.h"
46 #include "tree-ssa-propagate.h"
47 #include "gimple-pretty-print.h"
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. */
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
)
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;
77 tree decl
= gimple_call_fndecl (stmt
);
78 if (!decl
|| !fndecl_built_in_p (decl
, BUILT_IN_NORMAL
))
81 /* The remaining builtins implement the library interfaces to the coro
83 unsigned call_idx
= 0;
85 switch (DECL_FUNCTION_CODE (decl
))
89 case BUILT_IN_CORO_PROMISE
:
91 /* If we are discarding this, then skip it; the function has no
93 tree lhs
= gimple_call_lhs (stmt
);
96 gsi_remove (gsi
, true);
97 *handled_ops_p
= true;
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
,
123 gassign
*grpl
= gimple_build_assign (lhs
, repl
);
124 gsi_replace (gsi
, grpl
, true);
125 *handled_ops_p
= true;
128 case BUILT_IN_CORO_DESTROY
:
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;
149 case BUILT_IN_CORO_DONE
:
151 /* If we are discarding this, then skip it; the function has no
153 tree lhs
= gimple_call_lhs (stmt
);
156 gsi_remove (gsi
, true);
157 *handled_ops_p
= true;
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
);
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
,
170 gassign
*get_res
= gimple_build_assign (lhs
, done
);
171 gsi_replace (gsi
, get_res
, true);
172 *handled_ops_p
= true;
179 /* Main entry point for lowering coroutine FE builtins. */
182 execute_lower_coro_builtins (void)
184 struct walk_stmt_info wi
;
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
);
197 const pass_data pass_data_coroutine_lower_builtins
= {
198 GIMPLE_PASS
, /* type */
199 "coro-lower-builtins", /* name */
200 OPTGROUP_NONE
, /* optinfo_flags */
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
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
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.
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
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. */
274 move_edge_and_update (edge e
, basic_block old_bb
, basic_block new_bb
)
277 fprintf (dump_file
, "redirecting edge from bb %u to bb %u\n", old_bb
->index
,
280 e
= redirect_edge_and_branch (e
, new_bb
);
282 fprintf (dump_file
, "failed to redirect edge .. \n");
284 /* Die if we failed. */
285 gcc_checking_assert (e
);
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
;
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");
316 if (!is_gimple_call (stmt
) || !gimple_call_internal_p (stmt
))
321 switch (gimple_call_internal_fn (stmt
))
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);
340 actor_worklist
.safe_push (gsi
); /* Save for later. */
346 /* .CO_YIELD (NUM, FINAL, RES_LAB, DEST_LAB, FRAME_PTR);
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. */
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
);
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
)
364 "duplicate YIELD RESUME point (" HOST_WIDE_INT_PRINT_DEC
367 print_gimple_stmt (dump_file
, stmt
, 0, TDF_VOPS
|TDF_MEMSYMS
);
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
)
377 "duplicate YIELD DESTROY point (" HOST_WIDE_INT_PRINT_DEC
380 print_gimple_stmt (dump_file
, stmt
, 0, TDF_VOPS
|TDF_MEMSYMS
);
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
);
399 else if (gcond
*gif
= dyn_cast
<gcond
*> (stmt
))
401 if (gimple_cond_code (gif
) == EQ_EXPR
)
402 gimple_cond_make_true (gif
);
404 gimple_cond_make_false (gif
);
408 print_gimple_stmt (dump_file
, stmt
, 0, TDF_VOPS
|TDF_MEMSYMS
);
422 fprintf (dump_file
, "coro: nothing to do\n");
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
);
434 HOST_WIDE_INT idx
= TREE_INT_CST_LOW (gimple_call_arg (stmt
, 0));
435 tree
*seen
= destinations
.get (idx
);
439 fprintf (dump_file
, "saw CO_ACTOR in BB %u\n", bb
->index
);
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. */
447 fprintf (dump_file
, "yield point " HOST_WIDE_INT_PRINT_DEC
448 " not used, removing it .. \n", idx
);
449 gsi_remove (&gsi
, true);
454 /* So we need to switch the target of this switch case to the
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);
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
;
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 */
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
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
513 make_pass_coroutine_early_expand_ifns (gcc::context
*ctxt
)
515 return new pass_coroutine_early_expand_ifns (ctxt
);