]> gcc.gnu.org Git - gcc.git/blame - gcc/gimple-harden-conditionals.cc
ipa/105166 - avoid modref queries with mismatching types
[gcc.git] / gcc / gimple-harden-conditionals.cc
CommitLineData
95bb87b2 1/* Harden conditionals.
7adcbafe 2 Copyright (C) 2021-2022 Free Software Foundation, Inc.
95bb87b2
AO
3 Contributed by Alexandre Oliva <oliva@adacore.com>.
4
5This file is part of GCC.
6
7GCC is free software; you can redistribute it and/or modify it under
8the terms of the GNU General Public License as published by the Free
9Software Foundation; either version 3, or (at your option) any later
10version.
11
12GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13WARRANTY; without even the implied warranty of MERCHANTABILITY or
14FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15for more details.
16
17You should have received a copy of the GNU General Public License
18along with GCC; see the file COPYING3. If not see
19<http://www.gnu.org/licenses/>. */
20
21#include "config.h"
22#include "system.h"
23#include "coretypes.h"
24#include "backend.h"
0485ce91
AO
25#include "target.h"
26#include "rtl.h"
95bb87b2
AO
27#include "tree.h"
28#include "fold-const.h"
29#include "gimple.h"
30#include "gimplify.h"
31#include "tree-pass.h"
32#include "ssa.h"
33#include "gimple-iterator.h"
34#include "tree-cfg.h"
35#include "basic-block.h"
36#include "cfghooks.h"
37#include "cfgloop.h"
2bff91f3 38#include "tree-eh.h"
95bb87b2
AO
39#include "diagnostic.h"
40#include "intl.h"
41
42namespace {
43
44/* These passes introduces redundant, but reversed conditionals at
45 compares, such as those used in conditional branches, and those
46 that compute boolean results. This doesn't make much sense for
47 abstract CPUs, but this kind of hardening may avoid undesirable
48 execution paths on actual CPUs under such attacks as of power
49 deprivation. */
50
51/* Define a pass to harden conditionals other than branches. */
52
53const pass_data pass_data_harden_compares = {
54 GIMPLE_PASS,
55 "hardcmp",
56 OPTGROUP_NONE,
57 TV_NONE,
58 PROP_cfg | PROP_ssa, // properties_required
59 0, // properties_provided
60 0, // properties_destroyed
61 0, // properties_start
62 TODO_update_ssa
63 | TODO_cleanup_cfg
64 | TODO_verify_il, // properties_finish
65};
66
67class pass_harden_compares : public gimple_opt_pass
68{
69public:
70 pass_harden_compares (gcc::context *ctxt)
71 : gimple_opt_pass (pass_data_harden_compares, ctxt)
72 {}
73 opt_pass *clone () { return new pass_harden_compares (m_ctxt); }
74 virtual bool gate (function *) {
75 return flag_harden_compares;
76 }
77 virtual unsigned int execute (function *);
78};
79
80/* Define a pass to harden conditionals in branches. This pass must
81 run after the above, otherwise it will re-harden the checks
82 introduced by the above. */
83
84const pass_data pass_data_harden_conditional_branches = {
85 GIMPLE_PASS,
86 "hardcbr",
87 OPTGROUP_NONE,
88 TV_NONE,
89 PROP_cfg | PROP_ssa, // properties_required
90 0, // properties_provided
91 0, // properties_destroyed
92 0, // properties_start
93 TODO_update_ssa
94 | TODO_cleanup_cfg
95 | TODO_verify_il, // properties_finish
96};
97
98class pass_harden_conditional_branches : public gimple_opt_pass
99{
100public:
101 pass_harden_conditional_branches (gcc::context *ctxt)
102 : gimple_opt_pass (pass_data_harden_conditional_branches, ctxt)
103 {}
104 opt_pass *clone () { return new pass_harden_conditional_branches (m_ctxt); }
105 virtual bool gate (function *) {
106 return flag_harden_conditional_branches;
107 }
108 virtual unsigned int execute (function *);
109};
110
111}
112
113/* If VAL is an SSA name, return an SSA name holding the same value,
114 but without the compiler's knowing that it holds the same value, so
115 that uses thereof can't be optimized the way VAL might. Insert
116 stmts that initialize it before *GSIP, with LOC.
117
118 Otherwise, VAL must be an invariant, returned unchanged. */
119
120static inline tree
121detach_value (location_t loc, gimple_stmt_iterator *gsip, tree val)
122{
123 if (TREE_CONSTANT (val) || TREE_CODE (val) != SSA_NAME)
124 {
125 gcc_checking_assert (is_gimple_min_invariant (val));
126 return val;
127 }
128
fb488cba
AO
129 /* Create a SSA "copy" of VAL. It would be nice to have it named
130 after the corresponding variable, but sharing the same decl is
131 problematic when VAL is a DECL_BY_REFERENCE RESULT_DECL, and
132 copying just the identifier hits -fcompare-debug failures. */
38e4a361 133 tree ret = make_ssa_name (TREE_TYPE (val));
95bb87b2 134
0485ce91
AO
135 /* Some modes won't fit in general regs, so we fall back to memory
136 for them. ??? It would be ideal to try to identify an alternate,
137 wider or more suitable register class, and use the corresponding
138 constraint, but there's no logic to go from register class to
139 constraint, even if there is a corresponding constraint, and even
140 if we could enumerate constraints, we can't get to their string
141 either. So this will do for now. */
142 bool need_memory = true;
143 enum machine_mode mode = TYPE_MODE (TREE_TYPE (val));
144 if (mode != BLKmode)
145 for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
146 if (TEST_HARD_REG_BIT (reg_class_contents[GENERAL_REGS], i)
147 && targetm.hard_regno_mode_ok (i, mode))
148 {
149 need_memory = false;
150 break;
151 }
152
153 tree asminput = val;
154 tree asmoutput = ret;
155 const char *constraint_out = need_memory ? "=m" : "=g";
156 const char *constraint_in = need_memory ? "m" : "0";
157
158 if (need_memory)
159 {
160 tree temp = create_tmp_var (TREE_TYPE (val), "dtch");
161 mark_addressable (temp);
162
163 gassign *copyin = gimple_build_assign (temp, asminput);
164 gimple_set_location (copyin, loc);
165 gsi_insert_before (gsip, copyin, GSI_SAME_STMT);
166
167 asminput = asmoutput = temp;
168 }
169
170 /* Output an asm statement with matching input and output. It does
171 nothing, but after it the compiler no longer knows the output
172 still holds the same value as the input. */
95bb87b2
AO
173 vec<tree, va_gc> *inputs = NULL;
174 vec<tree, va_gc> *outputs = NULL;
175 vec_safe_push (outputs,
176 build_tree_list
177 (build_tree_list
0485ce91
AO
178 (NULL_TREE, build_string (strlen (constraint_out),
179 constraint_out)),
180 asmoutput));
95bb87b2
AO
181 vec_safe_push (inputs,
182 build_tree_list
183 (build_tree_list
0485ce91
AO
184 (NULL_TREE, build_string (strlen (constraint_in),
185 constraint_in)),
186 asminput));
95bb87b2
AO
187 gasm *detach = gimple_build_asm_vec ("", inputs, outputs,
188 NULL, NULL);
189 gimple_set_location (detach, loc);
190 gsi_insert_before (gsip, detach, GSI_SAME_STMT);
191
0485ce91
AO
192 if (need_memory)
193 {
194 gassign *copyout = gimple_build_assign (ret, asmoutput);
195 gimple_set_location (copyout, loc);
196 gsi_insert_before (gsip, copyout, GSI_SAME_STMT);
197 SSA_NAME_DEF_STMT (ret) = copyout;
198
199 gassign *clobber = gimple_build_assign (asmoutput,
200 build_clobber
201 (TREE_TYPE (asmoutput)));
202 gimple_set_location (clobber, loc);
203 gsi_insert_before (gsip, clobber, GSI_SAME_STMT);
204 }
205 else
206 SSA_NAME_DEF_STMT (ret) = detach;
95bb87b2
AO
207
208 return ret;
209}
210
211/* Build a cond stmt out of COP, LHS, RHS, insert it before *GSIP with
212 location LOC. *GSIP must be at the end of a basic block. The succ
213 edge out of the block becomes the true or false edge opposite to
214 that in FLAGS. Create a new block with a single trap stmt, in the
215 cold partition if the function is partitioned,, and a new edge to
216 it as the other edge for the cond. */
217
218static inline void
219insert_check_and_trap (location_t loc, gimple_stmt_iterator *gsip,
220 int flags, enum tree_code cop, tree lhs, tree rhs)
221{
222 basic_block chk = gsi_bb (*gsip);
223
224 gcond *cond = gimple_build_cond (cop, lhs, rhs, NULL, NULL);
225 gimple_set_location (cond, loc);
226 gsi_insert_before (gsip, cond, GSI_SAME_STMT);
227
228 basic_block trp = create_empty_bb (chk);
229
230 gimple_stmt_iterator gsit = gsi_after_labels (trp);
231 gcall *trap = gimple_build_call (builtin_decl_explicit (BUILT_IN_TRAP), 0);
232 gimple_set_location (trap, loc);
233 gsi_insert_before (&gsit, trap, GSI_SAME_STMT);
234
235 if (dump_file)
236 fprintf (dump_file,
237 "Adding reversed compare to block %i, and trap to block %i\n",
238 chk->index, trp->index);
239
240 if (BB_PARTITION (chk))
241 BB_SET_PARTITION (trp, BB_COLD_PARTITION);
242
243 int true_false_flag = flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE);
244 gcc_assert (true_false_flag);
245 int neg_true_false_flag = (~flags) & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE);
246
247 /* Remove the fallthru bit, and set the truth value for the
248 preexisting edge and for the newly-created one. In hardcbr,
249 FLAGS is taken from the edge of the original cond expr that we're
250 dealing with, so the reversed compare is expected to yield the
251 negated result, and the same result calls for a trap. In
252 hardcmp, we're comparing the boolean results of the original and
253 of the reversed compare, so we're passed FLAGS to trap on
254 equality. */
255 single_succ_edge (chk)->flags &= ~EDGE_FALLTHRU;
256 single_succ_edge (chk)->flags |= neg_true_false_flag;
257 edge e = make_edge (chk, trp, true_false_flag);
258 e->goto_locus = loc;
259
260 if (dom_info_available_p (CDI_DOMINATORS))
261 set_immediate_dominator (CDI_DOMINATORS, trp, chk);
262 if (current_loops)
263 add_bb_to_loop (trp, current_loops->tree_root);
264}
265
266/* Split edge E, and insert_check_and_trap (see above) in the
267 newly-created block, using detached copies of LHS's and RHS's
268 values (see detach_value above) for the COP compare. */
269
270static inline void
271insert_edge_check_and_trap (location_t loc, edge e,
272 enum tree_code cop, tree lhs, tree rhs)
273{
274 int flags = e->flags;
275 basic_block src = e->src;
276 basic_block dest = e->dest;
277 location_t eloc = e->goto_locus;
278
279 basic_block chk = split_edge (e);
280 e = NULL;
281
282 single_pred_edge (chk)->goto_locus = loc;
283 single_succ_edge (chk)->goto_locus = eloc;
284
285 if (dump_file)
286 fprintf (dump_file,
287 "Splitting edge %i->%i into block %i\n",
288 src->index, dest->index, chk->index);
289
290 gimple_stmt_iterator gsik = gsi_after_labels (chk);
291
292 bool same_p = (lhs == rhs);
293 lhs = detach_value (loc, &gsik, lhs);
294 rhs = same_p ? lhs : detach_value (loc, &gsik, rhs);
295
296 insert_check_and_trap (loc, &gsik, flags, cop, lhs, rhs);
297}
298
299/* Harden cond stmts at the end of FUN's blocks. */
300
301unsigned int
302pass_harden_conditional_branches::execute (function *fun)
303{
304 basic_block bb;
305 FOR_EACH_BB_REVERSE_FN (bb, fun)
306 {
307 gimple_stmt_iterator gsi = gsi_last_bb (bb);
308
309 if (gsi_end_p (gsi))
310 continue;
311
312 gcond *cond = dyn_cast <gcond *> (gsi_stmt (gsi));
313 if (!cond)
314 continue;
315
316 /* Turn:
317
318 if (x op y) goto l1; else goto l2;
319
320 into:
321
322 if (x op y) goto l1'; else goto l2';
323 l1': if (x' cop y') goto l1'trap; else goto l1;
324 l1'trap: __builtin_trap ();
325 l2': if (x' cop y') goto l2; else goto l2'trap;
326 l2'trap: __builtin_trap ();
327
328 where cop is a complementary boolean operation to op; l1', l1'trap,
329 l2' and l2'trap are newly-created labels; and x' and y' hold the same
330 value as x and y, but in a way that does not enable the compiler to
331 optimize the redundant compare away.
332 */
333
334 enum tree_code op = gimple_cond_code (cond);
335 tree lhs = gimple_cond_lhs (cond);
336 tree rhs = gimple_cond_rhs (cond);
337 location_t loc = gimple_location (cond);
338
339 enum tree_code cop = invert_tree_comparison (op, HONOR_NANS (lhs));
340
341 if (cop == ERROR_MARK)
342 /* ??? Can we do better? */
343 continue;
344
345 insert_edge_check_and_trap (loc, EDGE_SUCC (bb, 0), cop, lhs, rhs);
346 insert_edge_check_and_trap (loc, EDGE_SUCC (bb, 1), cop, lhs, rhs);
347 }
348
349 return 0;
350}
351
352/* Instantiate a hardcbr pass. */
353
354gimple_opt_pass *
355make_pass_harden_conditional_branches (gcc::context *ctxt)
356{
357 return new pass_harden_conditional_branches (ctxt);
358}
359
2bff91f3 360/* Return the fallthru edge of a block whose other edge is an EH
e53bb196 361 edge. If EHP is not NULL, store the EH edge in it. */
2bff91f3 362static inline edge
e53bb196 363non_eh_succ_edge (basic_block bb, edge *ehp = NULL)
2bff91f3
AO
364{
365 gcc_checking_assert (EDGE_COUNT (bb->succs) == 2);
366
367 edge ret = find_fallthru_edge (bb->succs);
368
369 int eh_idx = EDGE_SUCC (bb, 0) == ret;
370 edge eh = EDGE_SUCC (bb, eh_idx);
371
372 gcc_checking_assert (!(ret->flags & EDGE_EH)
373 && (eh->flags & EDGE_EH));
374
e53bb196
AO
375 if (ehp)
376 *ehp = eh;
377
2bff91f3
AO
378 return ret;
379}
380
95bb87b2
AO
381/* Harden boolean-yielding compares in FUN. */
382
383unsigned int
384pass_harden_compares::execute (function *fun)
385{
386 basic_block bb;
387 /* Go backwards over BBs and stmts, so that, even if we split the
388 block multiple times to insert a cond_expr after each compare we
389 find, we remain in the same block, visiting every preexisting
390 stmt exactly once, and not visiting newly-added blocks or
391 stmts. */
392 FOR_EACH_BB_REVERSE_FN (bb, fun)
393 for (gimple_stmt_iterator gsi = gsi_last_bb (bb);
394 !gsi_end_p (gsi); gsi_prev (&gsi))
395 {
396 gassign *asgn = dyn_cast <gassign *> (gsi_stmt (gsi));
397 if (!asgn)
398 continue;
399
400 /* Turn:
401
402 z = x op y;
403
404 into:
405
406 z = x op y;
407 z' = x' cop y';
408 if (z == z') __builtin_trap ();
409
410 where cop is a complementary boolean operation to op; and x'
411 and y' hold the same value as x and y, but in a way that does
412 not enable the compiler to optimize the redundant compare
413 away.
414 */
415
416 enum tree_code op = gimple_assign_rhs_code (asgn);
417
418 enum tree_code cop;
419
420 switch (op)
421 {
422 case EQ_EXPR:
423 case NE_EXPR:
424 case GT_EXPR:
425 case GE_EXPR:
426 case LT_EXPR:
427 case LE_EXPR:
428 case LTGT_EXPR:
429 case UNEQ_EXPR:
430 case UNGT_EXPR:
431 case UNGE_EXPR:
432 case UNLT_EXPR:
433 case UNLE_EXPR:
434 case ORDERED_EXPR:
435 case UNORDERED_EXPR:
436 cop = invert_tree_comparison (op,
437 HONOR_NANS
438 (gimple_assign_rhs1 (asgn)));
439
440 if (cop == ERROR_MARK)
441 /* ??? Can we do better? */
442 continue;
443
444 break;
445
446 /* ??? Maybe handle these too? */
447 case TRUTH_NOT_EXPR:
448 /* ??? The code below assumes binary ops, it would have to
449 be adjusted for TRUTH_NOT_EXPR, since it's unary. */
450 case TRUTH_ANDIF_EXPR:
451 case TRUTH_ORIF_EXPR:
452 case TRUTH_AND_EXPR:
453 case TRUTH_OR_EXPR:
454 case TRUTH_XOR_EXPR:
455 default:
456 continue;
457 }
458
459 /* These are the operands for the verification. */
460 tree lhs = gimple_assign_lhs (asgn);
461 tree op1 = gimple_assign_rhs1 (asgn);
462 tree op2 = gimple_assign_rhs2 (asgn);
463 location_t loc = gimple_location (asgn);
464
465 /* Vector booleans can't be used in conditional branches. ???
466 Can we do better? How to reduce compare and
467 reversed-compare result vectors to a single boolean? */
468 if (VECTOR_TYPE_P (TREE_TYPE (op1)))
469 continue;
470
2bff91f3
AO
471 /* useless_type_conversion_p enables conversions from 1-bit
472 integer types to boolean to be discarded. */
473 gcc_checking_assert (TREE_CODE (TREE_TYPE (lhs)) == BOOLEAN_TYPE
474 || (INTEGRAL_TYPE_P (TREE_TYPE (lhs))
475 && TYPE_PRECISION (TREE_TYPE (lhs)) == 1));
95bb87b2
AO
476
477 tree rhs = copy_ssa_name (lhs);
478
479 gimple_stmt_iterator gsi_split = gsi;
480 /* Don't separate the original assignment from debug stmts
481 that might be associated with it, and arrange to split the
482 block after debug stmts, so as to make sure the split block
483 won't be debug stmts only. */
484 gsi_next_nondebug (&gsi_split);
485
2bff91f3
AO
486 bool throwing_compare_p = stmt_ends_bb_p (asgn);
487 if (throwing_compare_p)
488 {
489 basic_block nbb = split_edge (non_eh_succ_edge
490 (gimple_bb (asgn)));
491 gsi_split = gsi_start_bb (nbb);
492
493 if (dump_file)
494 fprintf (dump_file,
495 "Splitting non-EH edge from block %i into %i"
496 " after a throwing compare\n",
497 gimple_bb (asgn)->index, nbb->index);
498 }
499
95bb87b2
AO
500 bool same_p = (op1 == op2);
501 op1 = detach_value (loc, &gsi_split, op1);
502 op2 = same_p ? op1 : detach_value (loc, &gsi_split, op2);
503
504 gassign *asgnck = gimple_build_assign (rhs, cop, op1, op2);
505 gimple_set_location (asgnck, loc);
506 gsi_insert_before (&gsi_split, asgnck, GSI_SAME_STMT);
507
508 /* We wish to insert a cond_expr after the compare, so arrange
b8c4171e
AO
509 for it to be at the end of a block if it isn't, and for it
510 to have a single successor in case there's more than
511 one, as in PR104975. */
512 if (!gsi_end_p (gsi_split)
513 || !single_succ_p (gsi_bb (gsi_split)))
95bb87b2 514 {
b8c4171e
AO
515 if (!gsi_end_p (gsi_split))
516 gsi_prev (&gsi_split);
517 else
518 gsi_split = gsi_last_bb (gsi_bb (gsi_split));
2bff91f3
AO
519 basic_block obb = gsi_bb (gsi_split);
520 basic_block nbb = split_block (obb, gsi_stmt (gsi_split))->dest;
95bb87b2
AO
521 gsi_next (&gsi_split);
522 gcc_checking_assert (gsi_end_p (gsi_split));
523
524 single_succ_edge (bb)->goto_locus = loc;
525
526 if (dump_file)
2bff91f3
AO
527 fprintf (dump_file,
528 "Splitting block %i into %i"
529 " before the conditional trap branch\n",
530 obb->index, nbb->index);
531 }
532
533 /* If the check assignment must end a basic block, we can't
534 insert the conditional branch in the same block, so split
535 the block again, and prepare to insert the conditional
536 branch in the new block.
537
538 Also assign an EH region to the compare. Even though it's
539 unlikely that the hardening compare will throw after the
540 original compare didn't, the compiler won't even know that
541 it's the same compare operands, so add the EH edge anyway. */
542 if (throwing_compare_p)
543 {
544 add_stmt_to_eh_lp (asgnck, lookup_stmt_eh_lp (asgn));
545 make_eh_edges (asgnck);
546
e53bb196 547 edge ckeh;
2bff91f3 548 basic_block nbb = split_edge (non_eh_succ_edge
e53bb196 549 (gimple_bb (asgnck), &ckeh));
2bff91f3
AO
550 gsi_split = gsi_start_bb (nbb);
551
552 if (dump_file)
553 fprintf (dump_file,
554 "Splitting non-EH edge from block %i into %i after"
555 " the newly-inserted reversed throwing compare\n",
556 gimple_bb (asgnck)->index, nbb->index);
e53bb196
AO
557
558 if (!gimple_seq_empty_p (phi_nodes (ckeh->dest)))
559 {
560 edge aseh;
561 non_eh_succ_edge (gimple_bb (asgn), &aseh);
562
563 gcc_checking_assert (aseh->dest == ckeh->dest);
564
565 for (gphi_iterator psi = gsi_start_phis (ckeh->dest);
566 !gsi_end_p (psi); gsi_next (&psi))
567 {
568 gphi *phi = psi.phi ();
569 add_phi_arg (phi, PHI_ARG_DEF_FROM_EDGE (phi, aseh), ckeh,
570 gimple_phi_arg_location_from_edge (phi, aseh));
571 }
572
573 if (dump_file)
574 fprintf (dump_file,
575 "Copying PHI args in EH block %i from %i to %i\n",
576 aseh->dest->index, aseh->src->index, ckeh->src->index);
577 }
95bb87b2
AO
578 }
579
2bff91f3 580 gcc_checking_assert (single_succ_p (gsi_bb (gsi_split)));
95bb87b2
AO
581
582 insert_check_and_trap (loc, &gsi_split, EDGE_TRUE_VALUE,
583 EQ_EXPR, lhs, rhs);
584 }
585
586 return 0;
587}
588
589/* Instantiate a hardcmp pass. */
590
591gimple_opt_pass *
592make_pass_harden_compares (gcc::context *ctxt)
593{
594 return new pass_harden_compares (ctxt);
595}
This page took 0.32973 seconds and 5 git commands to generate.