On Wed, Dec 01, 2021 at 07:57:54PM +0530, Siddhesh Poyarekar wrote: > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c > @@ -0,0 +1,72 @@ > +/* { dg-do run } */ > +/* { dg-options "-O2" } */ > + > +typedef __SIZE_TYPE__ size_t; > +#define abort __builtin_abort > + > +size_t > +__attribute__ ((noinline)) > +test_builtin_malloc_condphi (int cond) > +{ > + void *ret; > + > + if (cond) > + ret = __builtin_malloc (32); > + else > + ret = __builtin_malloc (64); > + > + return __builtin_dynamic_object_size (ret, 0); > +} > + > +size_t > +__attribute__ ((noinline)) > +test_builtin_calloc_condphi (size_t cnt, size_t sz, int cond) > +{ > + struct > + { > + int a; > + char b; > + } bin[cnt]; > + > + char *ch = __builtin_calloc (cnt, sz); > + > + return __builtin_dynamic_object_size (cond ? ch : (void *) &bin, 0);
I think it would be nice if the testcases didn't leak memory, can you replace return ... with size_t ret = and add __builtin_free (ch); return ret; in both cases (in the first perhaps rename ret to ch first. > --- a/gcc/testsuite/gcc.dg/builtin-object-size-5.c > +++ b/gcc/testsuite/gcc.dg/builtin-object-size-5.c > @@ -1,5 +1,7 @@ > /* { dg-do compile { target i?86-*-linux* i?86-*-gnu* x86_64-*-linux* } } */ > /* { dg-options "-O2" } */ > +/* For dynamic object sizes we 'succeed' if the returned size is known for > + maximum object size. */ > > typedef __SIZE_TYPE__ size_t; > extern void abort (void); > @@ -13,7 +15,11 @@ test1 (size_t x) > > for (i = 0; i < x; ++i) > p = p + 4; > +#ifdef __builtin_object_size > + if (__builtin_object_size (p, 0) == -1) > +#else > if (__builtin_object_size (p, 0) != sizeof (buf) - 8) > +#endif > abort (); > } > > @@ -25,10 +31,15 @@ test2 (size_t x) > > for (i = 0; i < x; ++i) > p = p + 4; > +#ifdef __builtin_object_size > + if (__builtin_object_size (p, 1) == -1) > +#else > if (__builtin_object_size (p, 1) != sizeof (buf) - 8) > +#endif > abort (); > } I'd say for __bdos it would be better to rewrite the testcase as dg-do run, perhaps use somewhat smaller buffer (say 16 times smaller; and dg-additional-sources for a file that actually defines that buffer and main. Perhaps you can have those #ifdef __builtin_object_size if (__builtin_object_size (p, 0) != sizeof (buf) - 8 - 4 * x) #else in there, just in the wrapper that #define __builtin_object_size make it dg-do run and have dg-additional-sources (and #ifndef N #define N 0x40000000 #endif and use that as size of buf. > + gcc_checking_assert (is_gimple_variable (ret) This should be TREE_CODE (ret) == SSA_NAME The reason why is_gimple_variable accepts VAR_DECLs/PARM_DECLs/RESULT_DECLs is high vs. low gimple, but size_type_node sizes are gimple types and both objsz passes are run when in ssa form, so it should always be either a SSA_NAME or INTEGER_CST. > + || TREE_CODE (ret) == INTEGER_CST); > + } > + > + return ret; > } > > /* Set size for VARNO corresponding to OSI to VAL. */ > @@ -176,27 +218,113 @@ object_sizes_initialize (struct object_size_info *osi, > unsigned varno, > object_sizes[object_size_type][varno].wholesize = wholeval; > } > > +/* Return a MODIFY_EXPR for cases where SSA and EXPR have the same type. The > + TREE_VEC is returned only in case of PHI nodes. */ > + > +static tree > +bundle_sizes (tree ssa, tree expr) > +{ > + gcc_checking_assert (TREE_TYPE (ssa) == sizetype); > + > + if (!TREE_TYPE (expr)) > + { > + gcc_checking_assert (TREE_CODE (expr) == TREE_VEC); I think I'd prefer to do it the other way, condition on TREE_CODE (expr) == TREE_VEC and if needed assert it has NULL TREE_TYPE. > + TREE_VEC_ELT (expr, TREE_VEC_LENGTH (expr) - 1) = ssa; > + return expr; > + } > + > + gcc_checking_assert (types_compatible_p (TREE_TYPE (expr), sizetype)); > + return size_binop (MODIFY_EXPR, ssa, expr); This looks wrong. MODIFY_EXPR isn't a binary expression (tcc_binary/tcc_comparison), size_binop shouldn't be called on it. I think you even don't want to fold it, so return build2 (MODIFY_EXPR, sizetype, ssa, expr); ? Also, calling a parameter or var ssa is quite unusual, normally one calls a SSA_NAME either name, or ssa_name etc. > + gcc_checking_assert (size_initval_p (oldval, object_size_type)); > + gcc_checking_assert (size_initval_p (old_wholeval, > + object_size_type)); > + /* For dynamic object sizes, all object sizes that are not gimple > + variables will need to be gimplified. */ > + if (TREE_CODE (wholeval) != INTEGER_CST > + && !is_gimple_variable (wholeval)) > + { > + bitmap_set_bit (osi->reexamine, varno); > + wholeval = bundle_sizes (make_ssa_name (sizetype), wholeval); > + } > + if (TREE_CODE (val) != INTEGER_CST && !is_gimple_variable (val)) Again twice above. > +/* Set temporary SSA names for object size and whole size to resolve > dependency > + loops in dynamic size computation. */ > + > +static inline void > +object_sizes_set_temp (struct object_size_info *osi, unsigned varno) > +{ > + tree val = object_sizes_get (osi, varno); > + > + if (size_initval_p (val, osi->object_size_type)) > + object_sizes_set (osi, varno, > + make_ssa_name (sizetype), > + make_ssa_name (sizetype)); This makes me a little bit worried. Do you compute the wholesize SSA_NAME at runtime always, or only when it is really needed and known not to always be equal to the size? I mean, e.g. for the cases where there is just const char *p = malloc (size); and the pointer is never increased size == wholesize. For __bos it will just be 2 different INTEGER_CSTs, but if it would at runtime mean we compute something twice and hope we eventually find out during later passes it is the same, it would be bad. > + tree phires = TREE_VEC_ELT (wholesize, TREE_VEC_LENGTH (wholesize) - 1); > + gphi *wholephi = create_phi_node (phires, gimple_bb (stmt)); > + phires = TREE_VEC_ELT (size, TREE_VEC_LENGTH (size) - 1); > + gphi *phi = create_phi_node (phires, gimple_bb (stmt)); > + gphi *obj_phi = as_a <gphi *> (stmt); Just one space instead of 2 above. And the above shows that you actually create 2 PHIs unconditionally, rather than trying to do that only if 1) wholesize is ever actually different from size 2) something needs wholesize. > + /* If we built an expression, we will need to build statements > + and insert them on the edge right away. */ > + if (!is_gimple_variable (wsz)) Again, above comments about is_gimple_variable. > + wsz = force_gimple_operand (wsz, &seq, true, NULL); > + if (!is_gimple_variable (sz)) > + { > + gimple_seq s; > + sz = force_gimple_operand (sz, &s, true, NULL); > + gimple_seq_add_seq (&seq, s); > + } > + > + if (seq) > + { > + edge e = gimple_phi_arg_edge (obj_phi, i); > + > + /* Put the size definition before the last statement of the source > + block of the PHI edge. This ensures that any branches at the end > + of the source block remain the last statement. We are OK even if > + the last statement is the definition of the object since it will > + succeed any definitions that contribute to its size and the size > + expression will succeed them too. */ > + gimple_stmt_iterator gsi = gsi_last_bb (e->src); > + gsi_insert_seq_before (&gsi, seq, GSI_CONTINUE_LINKING); This looks problematic. The last stmt in the bb might not exist at all, or can be one that needs to terminate the bb (stmt_ends_bb_p), or can be some other normal stmt that is just last in the bb, or it can be a debug stmt. E.g. for -fcompare-debug sanity, inserting before the last stmt in the block is wrong, because without -g it could be some random stmt and with -g it can be a debug stmt, so the resulting stmts will then differ between -g and -g0. Or the last stmt could actually be computing something you use in the expressions? I think generally you want gsi_insert_seq_on_edge, just be prepared that it doesn't always work - one can't insert on EH or ABNORMAL edges. For EH/ABNORMAL edges not really sure what can be done, punt, force just __bos behavior for it, or perhaps insert before the last stmt in that case, but beware that it would need to be SSA_NAME_OCCURS_IN_ABNORMAL_PHI SSA_NAME which I think needs to have underlying SSA_NAME_VAR and needs to follow rules such that out of SSA can handle it. Jakub