Mercurial > hg > CbC > CbC_gcc
comparison gcc/coroutine-passes.cc @ 145:1830386684a0
gcc-9.2.0
author | anatofuz |
---|---|
date | Thu, 13 Feb 2020 11:34:05 +0900 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
131:84e7813d76e9 | 145:1830386684a0 |
---|---|
1 /* coroutine expansion and optimisation passes. | |
2 | |
3 Copyright (C) 2018-2020 Free Software Foundation, Inc. | |
4 | |
5 Contributed by Iain Sandoe <iain@sandoe.co.uk> under contract to Facebook. | |
6 | |
7 This file is part of GCC. | |
8 | |
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. | |
13 | |
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. | |
18 | |
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/>. */ | |
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 | |
54 static tree | |
55 lower_coro_builtin (gimple_stmt_iterator *gsi, bool *handled_ops_p, | |
56 struct walk_stmt_info *wi ATTRIBUTE_UNUSED) | |
57 { | |
58 gimple *stmt = gsi_stmt (*gsi); | |
59 *handled_ops_p = !gimple_has_substatements (stmt); | |
60 | |
61 if (gimple_code (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 (stmt) | |
67 && gimple_call_internal_fn (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 (stmt); | |
77 if (!decl || !fndecl_built_in_p (decl, 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 (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 (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, | |
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 (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; | |
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 (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 (stmt, 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 | |
180 static unsigned int | |
181 execute_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 (&wi, 0, 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 | |
194 namespace { | |
195 | |
196 const pass_data pass_data_coroutine_lower_builtins = { | |
197 GIMPLE_PASS, /* type */ | |
198 "coro-lower-builtins", /* name */ | |
199 OPTGROUP_NONE, /* optinfo_flags */ | |
200 TV_NONE, /* tv_id */ | |
201 0, /* properties_required */ | |
202 0, /* properties_provided */ | |
203 0, /* properties_destroyed */ | |
204 0, /* todo_flags_start */ | |
205 0 /* todo_flags_finish */ | |
206 }; | |
207 | |
208 class pass_coroutine_lower_builtins : public gimple_opt_pass | |
209 { | |
210 public: | |
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 virtual bool gate (function *) { return flag_coroutines; }; | |
217 | |
218 virtual unsigned int execute (function *f ATTRIBUTE_UNUSED) | |
219 { | |
220 return execute_lower_coro_builtins (); | |
221 } | |
222 | |
223 }; // class pass_coroutine_lower_builtins | |
224 | |
225 } // namespace | |
226 | |
227 gimple_opt_pass * | |
228 make_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 | |
272 static void | |
273 move_edge_and_update (edge e, basic_block old_bb, basic_block new_bb) | |
274 { | |
275 if (dump_file) | |
276 fprintf (dump_file, "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 (dump_file, "failed to redirect edge .. \n"); | |
282 | |
283 /* Die if we failed. */ | |
284 gcc_checking_assert (e); | |
285 } | |
286 | |
287 static unsigned int | |
288 execute_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 (gsi);) | |
306 { | |
307 gimple *stmt = gsi_stmt (gsi); | |
308 | |
309 if (!is_gimple_call (stmt) || !gimple_call_internal_p (stmt)) | |
310 { | |
311 gsi_next (&gsi); | |
312 continue; | |
313 } | |
314 switch (gimple_call_internal_fn (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 (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); | |
328 gsi_next (&gsi); | |
329 } | |
330 break; | |
331 case IFN_CO_ACTOR: | |
332 changed = true; | |
333 actor_worklist.safe_push (gsi); /* Save for later. */ | |
334 gsi_next (&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 (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); | |
350 bool existed; | |
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) | |
354 { | |
355 fprintf ( | |
356 dump_file, | |
357 "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 (idx + 1, &existed); | |
366 if (existed && dump_file) | |
367 { | |
368 fprintf ( | |
369 dump_file, | |
370 "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 (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)) | |
384 { | |
385 gsi_remove (&gsi, true); | |
386 stmt = gsi_stmt (gsi); | |
387 } | |
388 /* Simplify the switch or if following. */ | |
389 if (gswitch *gsw = dyn_cast<gswitch *> (stmt)) | |
390 { | |
391 gimple_switch_set_index (gsw, integer_zero_node); | |
392 fold_stmt (&gsi); | |
393 } | |
394 else if (gcond *gif = dyn_cast<gcond *> (stmt)) | |
395 { | |
396 if (gimple_cond_code (gif) == EQ_EXPR) | |
397 gimple_cond_make_true (gif); | |
398 else | |
399 gimple_cond_make_false (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 (gsi)) | |
405 break; | |
406 continue; | |
407 } | |
408 default: | |
409 gsi_next (&gsi); | |
410 break; | |
411 } | |
412 } | |
413 | |
414 if (!changed) | |
415 { | |
416 if (dump_file) | |
417 fprintf (dump_file, "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 (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 (gsi); | |
429 HOST_WIDE_INT idx = TREE_INT_CST_LOW (gimple_call_arg (stmt, 0)); | |
430 tree *seen = destinations.get (idx); | |
431 changed = true; | |
432 | |
433 if (dump_file) | |
434 fprintf (dump_file, "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 (dump_file, "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 (gsi);) | |
470 { | |
471 gimple *stmt = gsi_stmt (gsi); | |
472 if (glabel *glab = dyn_cast<glabel *> (stmt)) | |
473 { | |
474 tree rem = gimple_label_label (glab); | |
475 if (to_remove.contains (rem)) | |
476 { | |
477 gsi_remove (&gsi, true); | |
478 to_remove.remove (rem); | |
479 continue; /* We already moved to the next insn. */ | |
480 } | |
481 } | |
482 else | |
483 break; | |
484 gsi_next (&gsi); | |
485 } | |
486 | |
487 /* Changed the CFG. */ | |
488 todoflags |= TODO_cleanup_cfg; | |
489 return todoflags; | |
490 } | |
491 | |
492 namespace { | |
493 | |
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 */ | |
498 TV_NONE, /* tv_id */ | |
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. */ | |
504 }; | |
505 | |
506 class pass_coroutine_early_expand_ifns : public gimple_opt_pass | |
507 { | |
508 public: | |
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 virtual bool gate (function *f) | |
515 { | |
516 return flag_coroutines && f->coroutine_component; | |
517 } | |
518 | |
519 virtual unsigned int execute (function *f ATTRIBUTE_UNUSED) | |
520 { | |
521 return execute_early_expand_coro_ifns (); | |
522 } | |
523 | |
524 }; // class pass_coroutine_expand_ifns | |
525 | |
526 } // namespace | |
527 | |
528 gimple_opt_pass * | |
529 make_pass_coroutine_early_expand_ifns (gcc::context *ctxt) | |
530 { | |
531 return new pass_coroutine_early_expand_ifns (ctxt); | |
532 } |