1 | /* Language independent return value optimizations |
2 | Copyright (C) 2004-2023 Free Software Foundation, Inc. |
3 | |
4 | This file is part of GCC. |
5 | |
6 | GCC is free software; you can redistribute it and/or modify |
7 | it under the terms of the GNU General Public License as published by |
8 | the Free Software Foundation; either version 3, or (at your option) |
9 | any later version. |
10 | |
11 | GCC is distributed in the hope that it will be useful, |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | GNU General Public License for more details. |
15 | |
16 | You should have received a copy of the GNU General Public License |
17 | along with GCC; see the file COPYING3. If not see |
18 | <http://www.gnu.org/licenses/>. */ |
19 | |
20 | #include "config.h" |
21 | #include "system.h" |
22 | #include "coretypes.h" |
23 | #include "backend.h" |
24 | #include "tree.h" |
25 | #include "gimple.h" |
26 | #include "tree-pass.h" |
27 | #include "ssa.h" |
28 | #include "tree-pretty-print.h" |
29 | #include "gimple-iterator.h" |
30 | #include "gimple-walk.h" |
31 | #include "internal-fn.h" |
32 | |
33 | /* This file implements return value optimizations for functions which |
34 | return aggregate types. |
35 | |
36 | Basically this pass searches the function for return statements which |
37 | return a local aggregate. When converted to RTL such statements will |
38 | generate a copy from the local aggregate to final return value destination |
39 | mandated by the target's ABI. |
40 | |
41 | That copy can often be avoided by directly constructing the return value |
42 | into the final destination mandated by the target's ABI. |
43 | |
44 | This is basically a generic equivalent to the C++ front-end's |
45 | Named Return Value optimization. */ |
46 | |
47 | struct nrv_data_t |
48 | { |
49 | /* This is the temporary (a VAR_DECL) which appears in all of |
50 | this function's RETURN_EXPR statements. */ |
51 | tree var; |
52 | |
53 | /* This is the function's RESULT_DECL. We will replace all occurrences |
54 | of VAR with RESULT_DECL when we apply this optimization. */ |
55 | tree result; |
56 | int modified; |
57 | }; |
58 | |
59 | static tree finalize_nrv_r (tree *, int *, void *); |
60 | |
61 | /* Callback for the tree walker. |
62 | |
63 | If TP refers to a RETURN_EXPR, then set the expression being returned |
64 | to nrv_data->result. |
65 | |
66 | If TP refers to nrv_data->var, then replace nrv_data->var with |
67 | nrv_data->result. |
68 | |
69 | If we reach a node where we know all the subtrees are uninteresting, |
70 | then set *WALK_SUBTREES to zero. */ |
71 | |
72 | static tree |
73 | finalize_nrv_r (tree *tp, int *walk_subtrees, void *data) |
74 | { |
75 | struct walk_stmt_info *wi = (struct walk_stmt_info *) data; |
76 | struct nrv_data_t *dp = (struct nrv_data_t *) wi->info; |
77 | |
78 | /* No need to walk into types. */ |
79 | if (TYPE_P (*tp)) |
80 | *walk_subtrees = 0; |
81 | |
82 | /* Otherwise replace all occurrences of VAR with RESULT. */ |
83 | else if (*tp == dp->var) |
84 | { |
85 | *tp = dp->result; |
86 | dp->modified = 1; |
87 | } |
88 | |
89 | /* Keep iterating. */ |
90 | return NULL_TREE; |
91 | } |
92 | |
93 | /* Main entry point for return value optimizations. |
94 | |
95 | If this function always returns the same local variable, and that |
96 | local variable is an aggregate type, then replace the variable with |
97 | the function's DECL_RESULT. |
98 | |
99 | This is the equivalent of the C++ named return value optimization |
100 | applied to optimized trees in a language independent form. If we |
101 | ever encounter languages which prevent this kind of optimization, |
102 | then we could either have the languages register the optimization or |
103 | we could change the gating function to check the current language. */ |
104 | |
105 | namespace { |
106 | |
107 | const pass_data pass_data_nrv = |
108 | { |
109 | .type: GIMPLE_PASS, /* type */ |
110 | .name: "nrv" , /* name */ |
111 | .optinfo_flags: OPTGROUP_NONE, /* optinfo_flags */ |
112 | .tv_id: TV_TREE_NRV, /* tv_id */ |
113 | .properties_required: ( PROP_ssa | PROP_cfg ), /* properties_required */ |
114 | .properties_provided: 0, /* properties_provided */ |
115 | .properties_destroyed: 0, /* properties_destroyed */ |
116 | .todo_flags_start: 0, /* todo_flags_start */ |
117 | .todo_flags_finish: 0, /* todo_flags_finish */ |
118 | }; |
119 | |
120 | class pass_nrv : public gimple_opt_pass |
121 | { |
122 | public: |
123 | pass_nrv (gcc::context *ctxt) |
124 | : gimple_opt_pass (pass_data_nrv, ctxt) |
125 | {} |
126 | |
127 | /* opt_pass methods: */ |
128 | bool gate (function *) final override { return optimize > 0; } |
129 | |
130 | unsigned int execute (function *) final override; |
131 | |
132 | }; // class pass_nrv |
133 | |
134 | unsigned int |
135 | pass_nrv::execute (function *fun) |
136 | { |
137 | tree result = DECL_RESULT (current_function_decl); |
138 | tree result_type = TREE_TYPE (result); |
139 | tree found = NULL; |
140 | basic_block bb; |
141 | gimple_stmt_iterator gsi; |
142 | struct nrv_data_t data; |
143 | |
144 | /* If this function does not return an aggregate type in memory, then |
145 | there is nothing to do. */ |
146 | if (!aggregate_value_p (result, current_function_decl)) |
147 | return 0; |
148 | |
149 | /* If a GIMPLE type is returned in memory, finalize_nrv_r might create |
150 | non-GIMPLE. */ |
151 | if (is_gimple_reg_type (type: result_type)) |
152 | return 0; |
153 | |
154 | /* If the front end already did something like this, don't do it here. */ |
155 | if (DECL_NAME (result)) |
156 | return 0; |
157 | |
158 | /* If the result has its address taken then it might be modified |
159 | by means not detected in the following loop. Bail out in this |
160 | case. */ |
161 | if (TREE_ADDRESSABLE (result)) |
162 | return 0; |
163 | |
164 | /* Look through each block for assignments to the RESULT_DECL. */ |
165 | FOR_EACH_BB_FN (bb, fun) |
166 | { |
167 | for (gsi = gsi_start_bb (bb); !gsi_end_p (i: gsi); gsi_next (i: &gsi)) |
168 | { |
169 | gimple *stmt = gsi_stmt (i: gsi); |
170 | tree ret_val; |
171 | |
172 | if (greturn *return_stmt = dyn_cast <greturn *> (p: stmt)) |
173 | { |
174 | /* In a function with an aggregate return value, the |
175 | gimplifier has changed all non-empty RETURN_EXPRs to |
176 | return the RESULT_DECL. */ |
177 | ret_val = gimple_return_retval (gs: return_stmt); |
178 | if (ret_val) |
179 | gcc_assert (ret_val == result); |
180 | } |
181 | else if (gimple_has_lhs (stmt) |
182 | && gimple_get_lhs (stmt) == result) |
183 | { |
184 | tree rhs; |
185 | |
186 | if (!gimple_assign_copy_p (stmt)) |
187 | return 0; |
188 | |
189 | rhs = gimple_assign_rhs1 (gs: stmt); |
190 | |
191 | /* Now verify that this return statement uses the same value |
192 | as any previously encountered return statement. */ |
193 | if (found != NULL) |
194 | { |
195 | /* If we found a return statement using a different variable |
196 | than previous return statements, then we cannot perform |
197 | NRV optimizations. */ |
198 | if (found != rhs) |
199 | return 0; |
200 | } |
201 | else |
202 | found = rhs; |
203 | |
204 | /* The returned value must be a local automatic variable of the |
205 | same type and alignment as the function's result. */ |
206 | if (!VAR_P (found) |
207 | || TREE_THIS_VOLATILE (found) |
208 | || !auto_var_in_fn_p (found, current_function_decl) |
209 | || TREE_ADDRESSABLE (found) |
210 | || DECL_ALIGN (found) > DECL_ALIGN (result) |
211 | || !useless_type_conversion_p (result_type, |
212 | TREE_TYPE (found))) |
213 | return 0; |
214 | } |
215 | else if (gimple_has_lhs (stmt)) |
216 | { |
217 | tree addr = get_base_address (t: gimple_get_lhs (stmt)); |
218 | /* If there's any MODIFY of component of RESULT, |
219 | then bail out. */ |
220 | if (addr && addr == result) |
221 | return 0; |
222 | } |
223 | } |
224 | } |
225 | |
226 | if (!found) |
227 | return 0; |
228 | |
229 | /* If dumping details, then note once and only the NRV replacement. */ |
230 | if (dump_file && (dump_flags & TDF_DETAILS)) |
231 | { |
232 | fprintf (stream: dump_file, format: "NRV Replaced: " ); |
233 | print_generic_expr (dump_file, found, dump_flags); |
234 | fprintf (stream: dump_file, format: " with: " ); |
235 | print_generic_expr (dump_file, result, dump_flags); |
236 | fprintf (stream: dump_file, format: "\n" ); |
237 | } |
238 | |
239 | TREE_ADDRESSABLE (result) |= TREE_ADDRESSABLE (found); |
240 | |
241 | /* Now walk through the function changing all references to VAR to be |
242 | RESULT. */ |
243 | data.var = found; |
244 | data.result = result; |
245 | FOR_EACH_BB_FN (bb, fun) |
246 | { |
247 | for (gsi = gsi_start_bb (bb); !gsi_end_p (i: gsi); ) |
248 | { |
249 | gimple *stmt = gsi_stmt (i: gsi); |
250 | /* If this is a copy from VAR to RESULT, remove it. */ |
251 | if (gimple_assign_copy_p (stmt) |
252 | && gimple_assign_lhs (gs: stmt) == result |
253 | && gimple_assign_rhs1 (gs: stmt) == found) |
254 | { |
255 | unlink_stmt_vdef (stmt); |
256 | gsi_remove (&gsi, true); |
257 | release_defs (stmt); |
258 | } |
259 | else |
260 | { |
261 | struct walk_stmt_info wi; |
262 | memset (s: &wi, c: 0, n: sizeof (wi)); |
263 | wi.info = &data; |
264 | data.modified = 0; |
265 | walk_gimple_op (stmt, finalize_nrv_r, &wi); |
266 | if (data.modified) |
267 | { |
268 | /* If this is a CLOBBER of VAR, remove it. */ |
269 | if (gimple_clobber_p (s: stmt)) |
270 | { |
271 | unlink_stmt_vdef (stmt); |
272 | gsi_remove (&gsi, true); |
273 | release_defs (stmt); |
274 | continue; |
275 | } |
276 | update_stmt (s: stmt); |
277 | } |
278 | gsi_next (i: &gsi); |
279 | } |
280 | } |
281 | } |
282 | |
283 | SET_DECL_VALUE_EXPR (found, result); |
284 | DECL_HAS_VALUE_EXPR_P (found) = 1; |
285 | |
286 | return 0; |
287 | } |
288 | |
289 | } // anon namespace |
290 | |
291 | gimple_opt_pass * |
292 | make_pass_nrv (gcc::context *ctxt) |
293 | { |
294 | return new pass_nrv (ctxt); |
295 | } |
296 | |
297 | /* Determine (pessimistically) whether DEST is available for NRV |
298 | optimization, where DEST is expected to be the LHS of a modify |
299 | expression where the RHS is a function returning an aggregate. |
300 | |
301 | DEST is available if it is not clobbered or used by the call. */ |
302 | |
303 | static bool |
304 | dest_safe_for_nrv_p (gcall *call) |
305 | { |
306 | tree dest = gimple_call_lhs (gs: call); |
307 | |
308 | dest = get_base_address (t: dest); |
309 | if (! dest) |
310 | return false; |
311 | |
312 | if (TREE_CODE (dest) == SSA_NAME) |
313 | return true; |
314 | |
315 | if (call_may_clobber_ref_p (call, dest, false) |
316 | || ref_maybe_used_by_stmt_p (call, dest, false)) |
317 | return false; |
318 | |
319 | return true; |
320 | } |
321 | |
322 | /* Walk through the function looking for GIMPLE_ASSIGNs with calls that |
323 | return in memory on the RHS. For each of these, determine whether it is |
324 | safe to pass the address of the LHS as the return slot, and mark the |
325 | call appropriately if so. |
326 | |
327 | The NRV shares the return slot with a local variable in the callee; this |
328 | optimization shares the return slot with the target of the call within |
329 | the caller. If the NRV is performed (which we can't know in general), |
330 | this optimization is safe if the address of the target has not |
331 | escaped prior to the call. If it has, modifications to the local |
332 | variable will produce visible changes elsewhere, as in PR c++/19317. */ |
333 | |
334 | namespace { |
335 | |
336 | const pass_data pass_data_return_slot = |
337 | { |
338 | .type: GIMPLE_PASS, /* type */ |
339 | .name: "retslot" , /* name */ |
340 | .optinfo_flags: OPTGROUP_NONE, /* optinfo_flags */ |
341 | .tv_id: TV_NONE, /* tv_id */ |
342 | PROP_ssa, /* properties_required */ |
343 | .properties_provided: 0, /* properties_provided */ |
344 | .properties_destroyed: 0, /* properties_destroyed */ |
345 | .todo_flags_start: 0, /* todo_flags_start */ |
346 | .todo_flags_finish: 0, /* todo_flags_finish */ |
347 | }; |
348 | |
349 | class pass_return_slot : public gimple_opt_pass |
350 | { |
351 | public: |
352 | pass_return_slot (gcc::context *ctxt) |
353 | : gimple_opt_pass (pass_data_return_slot, ctxt) |
354 | {} |
355 | |
356 | /* opt_pass methods: */ |
357 | unsigned int execute (function *) final override; |
358 | |
359 | }; // class pass_return_slot |
360 | |
361 | unsigned int |
362 | pass_return_slot::execute (function *fun) |
363 | { |
364 | basic_block bb; |
365 | |
366 | FOR_EACH_BB_FN (bb, fun) |
367 | { |
368 | gimple_stmt_iterator gsi; |
369 | for (gsi = gsi_start_bb (bb); !gsi_end_p (i: gsi); gsi_next (i: &gsi)) |
370 | { |
371 | gcall *stmt; |
372 | bool slot_opt_p; |
373 | |
374 | stmt = dyn_cast <gcall *> (p: gsi_stmt (i: gsi)); |
375 | if (stmt |
376 | && gimple_call_lhs (gs: stmt) |
377 | && !gimple_call_return_slot_opt_p (s: stmt) |
378 | /* Ignore internal functions, those are expanded specially |
379 | and aggregate_value_p on their result might result in |
380 | undesirable warnings with some backends. */ |
381 | && !gimple_call_internal_p (gs: stmt) |
382 | && aggregate_value_p (TREE_TYPE (gimple_call_lhs (stmt)), |
383 | gimple_call_fndecl (gs: stmt))) |
384 | { |
385 | /* Check if the location being assigned to is |
386 | clobbered by the call. */ |
387 | slot_opt_p = dest_safe_for_nrv_p (call: stmt); |
388 | gimple_call_set_return_slot_opt (s: stmt, return_slot_opt_p: slot_opt_p); |
389 | } |
390 | } |
391 | } |
392 | return 0; |
393 | } |
394 | |
395 | } // anon namespace |
396 | |
397 | gimple_opt_pass * |
398 | make_pass_return_slot (gcc::context *ctxt) |
399 | { |
400 | return new pass_return_slot (ctxt); |
401 | } |
402 | |