Hi, as mentioned today at the plumbers toolchain track, here is my approach to tackling constant-folding issues regarding eBPF CORE and runtime structure layout randomization. I developed it to support the next iteration of Selfpatch-SLR and its linux kernel version:
https://lore.kernel.org/all/[email protected]/ The problem this addresses is that the frontend folds e.g. offsetof-builtin calls to constants, erasing any information on what fields contributed to the constant, and even that the constant depends on field offsets at all. This is obviously a problem when you want to finalize field offsets at a later time. For marked structures, this patch makes the frontend construct DEFERRED_CST_COMPONENT_REF nodes instead of normal COMPONENT_REFs. Deferred cst in this context means that something would usually be a compile-time constant and still behaves as one at runtime, but is not yet known. When fold_offsetof encounters these deferred cst component refs, it does not just return a constant integer, but a DEFERRED_CST_EXPR around a tree that still holds the relevant semantic information. In this tree, each contributing deferred cst field offset is represented by a call to a corresponding IFN. Additionally, I adapted c_fully_fold to merge true surrounding constants into the DEFERRED_CST_EXPRs. For example, "4 + DEFERRED_CST_EXPR<x>" becomes "DEFERRED_CST_EXPR<4 + x>". When checking for constness in some contexts, like in static initializers, DEFERRED_CST_EXPRs act as opaque and report as valid in that context if -fdeferred-cst-static is set. This allows code such as: struct A { int x; } __attribute__((deferred_cst_layout)); static const size_t x_offset = offsetof(struct A, x); Or: struct cred *init_cred_ptr = &init_task.cred; Which would usually break when transformed to expressions that are not compile-time constants. Deferred constants are supported up to the deferred_cst_barrier pass, which runs at the very beginning of the small IPA passes, immediately after lowering. By default, the barrier will replace all deferred constants in gimple and static initializers with their true compile-time values. If -fdeferred-cst-barrier-hard is set, the barrier will error instead, if any deferred constants still exist. The big idea here is that an IPA pass right before the barrier pass can iterate over all functions and static initializers and turn all deferred constant nodes into whatever is needed. For static initializers specifically, that pass can generate functions that recalculate and update the static data at runtime, by essentially copy pasting the trees under the DEFERRED_CST_EXPR and generating the according MODIFY_EXPRs. In the case of CORE, I guess you could even generate info for the JIT on how to calculate the static values and fill them in. Anyways, after this actual instrumentation pass there should be no deferred constant nodes left so the hard barrier is happy. The way this currently applies to field offsets and offsetof specifically, I believe this could be expanded to also work for enum values, by producing something along the lines of: DEFERRED_CST_EXPR<IFN_DEFERRED_CST_ENUM_VALUE(...)> I am looking forward for your input on this, and feel free to approach me at plumbers any time to talk about this. Jasper Signed-off-by: York Jasper Niebuhr <[email protected]> --- gcc/Makefile.in | 4 +- gcc/alias.cc | 1 + gcc/c-family/c-attribs.cc | 30 +- gcc/c-family/c-common.cc | 107 ++++++- gcc/c/c-decl.cc | 1 + gcc/c/c-fold.cc | 157 +++++++++- gcc/c/c-typeck.cc | 40 ++- gcc/common.opt | 8 + gcc/deferred-cst.cc | 626 ++++++++++++++++++++++++++++++++++++++ gcc/deferred-cst.h | 39 +++ gcc/expr.cc | 35 ++- gcc/fold-const.cc | 14 + gcc/gimple-expr.cc | 5 + gcc/gimple.cc | 3 +- gcc/gimplify.cc | 54 +++- gcc/internal-fn.cc | 9 + gcc/internal-fn.def | 3 + gcc/passes.def | 1 + gcc/tree-pass.h | 1 + gcc/tree-pretty-print.cc | 31 +- gcc/tree.cc | 19 +- gcc/tree.def | 13 + gcc/tree.h | 1 + gcc/varasm.cc | 18 +- 24 files changed, 1176 insertions(+), 44 deletions(-) create mode 100644 gcc/deferred-cst.cc create mode 100644 gcc/deferred-cst.h diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 8ecef4ccdc7..e70fe8dc21b 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1464,6 +1464,7 @@ OBJS = \ dce.o \ ddg.o \ debug.o \ + deferred-cst.o \ hierarchical_discriminator.o \ dep-fusion.o \ df-core.o \ @@ -3163,6 +3164,7 @@ GTFILES = $(CPPLIB_H) $(srcdir)/input.h $(srcdir)/coretypes.h \ $(srcdir)/internal-fn.h \ $(srcdir)/calls.cc \ $(srcdir)/analyzer/analyzer-language.cc \ + $(srcdir)/deferred-cst.cc \ @all_gtfiles@ # Compute the list of GT header files from the corresponding C sources, @@ -4173,7 +4175,7 @@ PLUGIN_HEADERS = $(TREE_H) $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) \ lcm.h cfgloopmanip.h file-prefix-map.h builtins.def $(INSN_ATTR_H) \ pass-instances.def params.list $(srcdir)/../include/gomp-constants.h \ $(EXPR_H) $(srcdir)/analyzer/*.h $(srcdir)/diagnostics/*.h \ - $(srcdir)/text-art/*.h $(srcdir)/topics/*.h + $(srcdir)/text-art/*.h $(srcdir)/topics/*.h deferred-cst.h # generate the 'build fragment' b-header-vars s-header-vars: Makefile diff --git a/gcc/alias.cc b/gcc/alias.cc index 6e16b21327f..2d5d672a520 100644 --- a/gcc/alias.cc +++ b/gcc/alias.cc @@ -618,6 +618,7 @@ ends_tbaa_access_path_p (const_tree t) switch (TREE_CODE (t)) { case COMPONENT_REF: + case DEFERRED_CST_COMPONENT_REF: if (DECL_NONADDRESSABLE_P (TREE_OPERAND (t, 1))) return true; /* Permit type-punning when accessing a union, provided the access diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc index d437c55285e..a8dae5e7f52 100644 --- a/gcc/c-family/c-attribs.cc +++ b/gcc/c-family/c-attribs.cc @@ -193,6 +193,8 @@ static tree handle_null_terminated_string_arg_attribute (tree *, tree, tree, int static tree handle_btf_decl_tag_attribute (tree *, tree, tree, int, bool *); static tree handle_btf_type_tag_attribute (tree *, tree, tree, int, bool *); +static tree handle_deferred_cst_access_attribute (tree *, tree, tree, int, bool *); + /* Helper to define attribute exclusions. */ #define ATTR_EXCL(name, function, type, variable) \ { name, function, type, variable } @@ -667,7 +669,9 @@ const struct attribute_spec c_common_gnu_attributes[] = { "btf_type_tag", 1, 1, false, true, false, false, handle_btf_type_tag_attribute, NULL}, { "btf_decl_tag", 1, 1, true, false, false, false, - handle_btf_decl_tag_attribute, NULL} + handle_btf_decl_tag_attribute, NULL}, + { "deferred_cst_access", 0, 0, false, true, false, false, + handle_deferred_cst_access_attribute, NULL } }; const struct scoped_attribute_specs c_common_gnu_attribute_table = @@ -5292,6 +5296,30 @@ handle_btf_type_tag_attribute (tree *node, tree name, tree args, return NULL_TREE; } +// TODO + +/* Handle the "deferred_cst_access" struct attribute. */ + +static tree +handle_deferred_cst_access_attribute (tree *node, + tree name, + tree ARG_UNUSED (args), + int ARG_UNUSED (flags), + bool *no_add_attrs) +{ + tree type = *node; + + if (TREE_CODE (type) != RECORD_TYPE) + { + warning (OPT_Wattributes, + "%qE attribute only applies to struct types", + name); + *no_add_attrs = true; + } + + return NULL_TREE; +} + /* Handle the "nonstring" variable attribute. */ static tree diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc index 5acb221d31f..34a4ea7a0de 100644 --- a/gcc/c-family/c-common.cc +++ b/gcc/c-family/c-common.cc @@ -57,6 +57,7 @@ along with GCC; see the file COPYING3. If not see #include "gcc-rich-location.h" #include "gcc-urlifier.h" #include "diagnostics/diagnostics-selftests.h" +#include "deferred-cst.h" cpp_reader *parse_in; /* Declared in c-pragma.h. */ @@ -7217,13 +7218,32 @@ c_common_to_target_charset (HOST_WIDE_INT c) return uc; } +/* Helper for fold_offsetof_internal. Add OFFSET to the ACCUMULATOR + tree. */ +static void +accumulate_offset (tree *accumulator, tree offset) +{ + gcc_assert (accumulator); + gcc_assert (offset); + + offset = convert (sizetype, offset); + + if (*accumulator == NULL_TREE) + *accumulator = offset; + else + *accumulator = size_binop_loc ( + input_location, PLUS_EXPR, *accumulator, offset); +} + /* Fold an offsetof-like expression. EXPR is a nested sequence of component references with an INDIRECT_REF of a constant at the bottom; much like the traditional rendering of offsetof as a macro. TYPE is the desired type of - the whole expression. Return the folded result. */ + the whole expression. Return the folded result. NON_CST is set to the + sum of all non-constant portions of the offset. DEFERRED is set to the + deferred constant portion of the offset. */ -tree -fold_offsetof (tree expr, tree type, enum tree_code ctx) +static tree +fold_offsetof_internal (tree expr, tree type, enum tree_code ctx, tree *non_cst, tree *deferred) { tree base, off, t; tree_code code = TREE_CODE (expr); @@ -7233,10 +7253,10 @@ fold_offsetof (tree expr, tree type, enum tree_code ctx) return expr; case REALPART_EXPR: - return fold_offsetof (TREE_OPERAND (expr, 0), type, code); + return fold_offsetof_internal (TREE_OPERAND (expr, 0), type, code, non_cst, deferred); case IMAGPART_EXPR: - base = fold_offsetof (TREE_OPERAND (expr, 0), type, code); + base = fold_offsetof_internal (TREE_OPERAND (expr, 0), type, code, non_cst, deferred); if (base == error_mark_node) return base; off = TYPE_SIZE_UNIT (TREE_TYPE (TREE_TYPE (TREE_OPERAND (expr, 0)))); @@ -7261,7 +7281,7 @@ fold_offsetof (tree expr, tree type, enum tree_code ctx) return convert (type, TREE_OPERAND (expr, 0)); case COMPONENT_REF: - base = fold_offsetof (TREE_OPERAND (expr, 0), type, code); + base = fold_offsetof_internal (TREE_OPERAND (expr, 0), type, code, non_cst, deferred); if (base == error_mark_node) return base; @@ -7277,8 +7297,24 @@ fold_offsetof (tree expr, tree type, enum tree_code ctx) / BITS_PER_UNIT)); break; + case DEFERRED_CST_COMPONENT_REF: + base = fold_offsetof_internal (TREE_OPERAND (expr, 0), type, code, non_cst, deferred); + if (base == error_mark_node) + return base; + + t = TREE_OPERAND (expr, 1); + if (DECL_C_BIT_FIELD (t)) + { + error_at (EXPR_LOCATION (expr), + "attempt to take address of bit-field structure member"); + return error_mark_node; + } + t = deferred_cst_call_ifn_field_offset (EXPR_LOCATION (expr), t); + accumulate_offset (deferred, t); + return base; + case ARRAY_REF: - base = fold_offsetof (TREE_OPERAND (expr, 0), type, code); + base = fold_offsetof_internal (TREE_OPERAND (expr, 0), type, code, non_cst, deferred); if (base == error_mark_node) return base; @@ -7330,13 +7366,26 @@ fold_offsetof (tree expr, tree type, enum tree_code ctx) t = convert (sizetype, t); off = size_binop (MULT_EXPR, TYPE_SIZE_UNIT (TREE_TYPE (expr)), t); + + /* For classification as cst/non-cst, the array index has to be + fully folded. */ + off = c_fully_fold(off, false, NULL); + + if (off == error_mark_node) + return off; + + if (!TREE_CONSTANT (off)) + { + accumulate_offset (non_cst, off); + return base; + } break; case COMPOUND_EXPR: /* Handle static members of volatile structs. */ t = TREE_OPERAND (expr, 1); gcc_checking_assert (VAR_P (get_base_address (t))); - return fold_offsetof (t, type); + return fold_offsetof_internal (t, type, ERROR_MARK, non_cst, deferred); default: gcc_unreachable (); @@ -7346,6 +7395,48 @@ fold_offsetof (tree expr, tree type, enum tree_code ctx) return size_binop (PLUS_EXPR, base, convert (type, off)); return fold_build_pointer_plus (base, off); } + +/* Like fold_offsetof_internal, but integrates deferred constant components + into the result. If such components exist, the result is wrapped in a + DEFERRED_CST_EXPR node. */ + +tree +fold_offsetof (tree expr, tree type, enum tree_code ctx) +{ + tree res = NULL_TREE; + tree non_cst = NULL_TREE; + tree deferred = NULL_TREE; + tree cst = fold_offsetof_internal (expr, type, ctx, &non_cst, &deferred); + + res = cst; + + if (res == error_mark_node) + return res; + + /* DEFERRED_CST_EXPRs contain only ordinary constants and deferred + constant components. */ + if (deferred != NULL_TREE) + { + if (POINTER_TYPE_P (type)) + res = fold_build_pointer_plus (res, convert (sizetype, deferred)); + else + res = size_binop (PLUS_EXPR, res, convert (type, deferred)); + + res = deferred_cst_build_expr (res); + } + + /* Runtime-dependent terms are added only after the deferred + boundary has been constructed. */ + if (non_cst != NULL_TREE) + { + if (POINTER_TYPE_P (type)) + res = fold_build_pointer_plus (res, convert (type, non_cst)); + else + res = size_binop (PLUS_EXPR, res, convert (type, non_cst)); + } + + return res; +} /* *PTYPE is an incomplete array. Complete it with a domain based on INITIAL_VALUE. If INITIAL_VALUE is not present, use 1 if DO_DEFAULT diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc index 8a894de3a1b..62da65e0efb 100644 --- a/gcc/c/c-decl.cc +++ b/gcc/c/c-decl.cc @@ -62,6 +62,7 @@ along with GCC; see the file COPYING3. If not see #include "omp-offload.h" /* For offload_vars. */ #include "c-parser.h" #include "gcc-urlifier.h" +#include "deferred-cst.h" #include "tree-pretty-print.h" diff --git a/gcc/c/c-fold.cc b/gcc/c/c-fold.cc index 122049daf87..f333a33153c 100644 --- a/gcc/c/c-fold.cc +++ b/gcc/c/c-fold.cc @@ -26,9 +26,11 @@ along with GCC; see the file COPYING3. If not see #include "c-tree.h" #include "intl.h" #include "gimplify.h" +#include "deferred-cst.h" static tree c_fully_fold_internal (tree expr, bool, bool *, bool *, bool, bool); +static tree c_fully_fold_deferred_cst_internal (tree expr); /* If DISABLE is true, stop issuing warnings. This is used when parsing code that we know will not be executed. This function may @@ -125,6 +127,7 @@ c_fully_fold (tree expr, bool in_init, bool *maybe_const, bool lval) } ret = c_fully_fold_internal (expr, in_init, maybe_const, &maybe_const_itself, false, lval); + ret = c_fully_fold_deferred_cst_internal (ret); if (eptype) ret = fold_convert_loc (loc, eptype, ret); *maybe_const &= maybe_const_itself; @@ -258,14 +261,22 @@ c_fully_fold_internal (tree expr, bool in_init, bool *maybe_const_operands, goto out; case COMPONENT_REF: + case DEFERRED_CST_COMPONENT_REF: orig_op0 = op0 = TREE_OPERAND (expr, 0); op1 = TREE_OPERAND (expr, 1); - op2 = TREE_OPERAND (expr, 2); + if (code == COMPONENT_REF) + op2 = TREE_OPERAND (expr, 2); op0 = c_fully_fold_internal (op0, in_init, maybe_const_operands, maybe_const_itself, for_int_const, lval); STRIP_TYPE_NOPS (op0); if (op0 != orig_op0) - ret = build3 (COMPONENT_REF, TREE_TYPE (expr), op0, op1, op2); + { + if (code == COMPONENT_REF) + ret = build3 (COMPONENT_REF, TREE_TYPE (expr), op0, op1, op2); + else + ret = deferred_cst_build_component_ref (TREE_TYPE (expr), + op0, op1); + } if (ret != expr) { TREE_READONLY (ret) = TREE_READONLY (expr); @@ -720,6 +731,148 @@ c_fully_fold_internal (tree expr, bool in_init, bool *maybe_const_operands, return ret; } +/* Internal helper for c_fully_fold. Fold surrounding constant + operations into DEFERRED_CST_EXPR nodes. + + An operation is absorbed when: + + - its tree code is explicitly supported by + deferred_cst_operator_p; + - every operand is constant-like; and + - at least one operand is a DEFERRED_CST_EXPR. + + Existing deferred wrappers are removed when an operation is absorbed, + giving a single outer DEFERRED_CST_EXPR. For example: + + DEFERRED_CST_EXPR (A) + 1 + -> DEFERRED_CST_EXPR (A + 1) + + (DEFERRED_CST_EXPR (A) + 1) * 2 + -> DEFERRED_CST_EXPR ((A + 1) * 2) + + DEFERRED_CST_EXPR (A) + DEFERRED_CST_EXPR (B) + -> DEFERRED_CST_EXPR (A + B) + + DEFERRED_CST_EXPR (C) ? 1 : 2 + -> DEFERRED_CST_EXPR (C ? 1 : 2) + + An operation involving a nonconstant operand is not absorbed: + + X + DEFERRED_CST_EXPR (A) + -> X + DEFERRED_CST_EXPR (A) */ + +static tree +c_fully_fold_deferred_cst_internal (tree expr) +{ + if (expr == NULL_TREE) + return NULL_TREE; + + enum tree_code code = TREE_CODE (expr); + + /* The true constants inside deferred constants can be folded here, + while the deferred components are represented as calls to + internal functions and thus can not and should not be folded + here. */ + if (code == DEFERRED_CST_EXPR) + { + tree old_inner = TREE_OPERAND (expr, 0); + + bool maybe_const_operands = true; + bool maybe_const_itself = true; + + tree inner + = c_fully_fold_internal (old_inner, false, + &maybe_const_operands, + &maybe_const_itself, + false, false); + + if (inner == old_inner) + return expr; + + return deferred_cst_build_expr (inner); + } + + /* Unsupported operations form a boundary for deferred folding. */ + if (!deferred_cst_operator_p (code)) + return expr; + + int nops = TREE_OPERAND_LENGTH (expr); + + /* Every currently-supported operator has at most three operands. */ + gcc_assert (nops >= 1 && nops <= 3); + + tree ops[3]; + bool changed = false; + bool has_deferred = false; + bool all_constant = true; + bool has_side_effects = false; + + for (int i = 0; i < nops; ++i) + { + tree old_op = TREE_OPERAND (expr, i); + tree op = c_fully_fold_deferred_cst_internal (old_op); + + ops[i] = op; + + if (op != old_op) + changed = true; + + if (TREE_CODE (op) == DEFERRED_CST_EXPR) + has_deferred = true; + else if (!TREE_CONSTANT (op)) + all_constant = false; + + if (TREE_SIDE_EFFECTS (op)) + has_side_effects = true; + } + + /* There is no deferred expression directly participating in this + operation, or there are non-constant operands or operands with + side effects involved, so the operation itself must not become + deferred. + + We may nevertheless need to propagate a transformation made in one + of the operands. */ + if (!has_deferred || !all_constant || has_side_effects) + { + if (!changed) + return expr; + + tree copy = copy_node (expr); + for (int i = 0; i < nops; i++) + TREE_OPERAND (copy, i) = ops[i]; + + return copy; + } + + /* Every operand is constant-like and at least one operand is + deferred. Move this complete operation inside one deferred + wrapper. */ + + tree inner = copy_node (expr); + for (int i = 0; i < nops; i++) + { + bool op_deferred_cst = ops[i] != NULL_TREE + && TREE_CODE (ops[i]) == DEFERRED_CST_EXPR; + TREE_OPERAND (inner, i) = op_deferred_cst + ? TREE_OPERAND (ops[i], 0) : ops[i]; + } + + bool maybe_const_operands = true; + bool maybe_const_itself = true; + + /* The true constants inside deferred constants can be folded here, + while the deferred components are represented as calls to + internal functions and thus can not and should not be folded + here. */ + inner = c_fully_fold_internal (inner, false, + &maybe_const_operands, + &maybe_const_itself, + false, false); + + return deferred_cst_build_expr (inner); +} + /* Fold X for consideration by one of the warning functions when checking whether an expression has a constant value. */ diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc index aa368a6e95a..4d9c0179b29 100644 --- a/gcc/c/c-typeck.cc +++ b/gcc/c/c-typeck.cc @@ -55,6 +55,7 @@ along with GCC; see the file COPYING3. If not see #include "realmpfr.h" #include "tree-pretty-print-markup.h" #include "gcc-urlifier.h" +#include "deferred-cst.h" /* Possible cases of implicit conversions. Used to select diagnostic messages and control folding initializers in convert_for_assignment. */ @@ -2290,6 +2291,19 @@ c_size_in_bytes (const_tree type) / BITS_PER_UNIT)); } +/* Return true if INIT is a valid deferred constant initializer. */ +static bool +deferred_constant_initializer_p (tree init) +{ + if (!flag_deferred_cst_static + || !init + || !deferred_cst_present_p (init)) + return false; + + return initializer_constant_valid_p (init, TREE_TYPE (init)) + != NULL_TREE; +} + /* Return either DECL or its known constant value (if it has one). */ tree @@ -2304,7 +2318,8 @@ decl_constant_value_1 (tree decl, bool in_init) /* This is invalid if initial value is not constant. If it has either a function call, a memory reference, or a variable, then re-evaluating it could give different results. */ - && TREE_CONSTANT (DECL_INITIAL (decl)) + && (TREE_CONSTANT (DECL_INITIAL (decl)) + || deferred_constant_initializer_p (DECL_INITIAL (decl))) /* Check for cases where this is sub-optimal, even though valid. */ && (in_init || TREE_CODE (DECL_INITIAL (decl)) != CONSTRUCTOR)) return DECL_INITIAL (decl); @@ -3199,9 +3214,14 @@ build_counted_by_ref (tree datum, tree subdatum, /* Get the TYPE of the counted_by field. */ *counted_by_type = TREE_TYPE (counted_by_subdatum); - counted_by_ref - = build3 (COMPONENT_REF, TREE_TYPE (counted_by_subdatum), - datum, counted_by_subdatum, NULL_TREE); + if (deferred_cst_field_p (counted_by_subdatum)) + counted_by_ref + = deferred_cst_build_component_ref (TREE_TYPE (counted_by_subdatum), + datum, counted_by_subdatum); + else + counted_by_ref + = build3 (COMPONENT_REF, TREE_TYPE (counted_by_subdatum), + datum, counted_by_subdatum, NULL_TREE); datum = counted_by_ref; counted_by_field = TREE_CHAIN (counted_by_field); @@ -3422,8 +3442,11 @@ build_component_ref (location_t loc, tree datum, tree component, quals |= TYPE_QUALS (TREE_TYPE (datum)); subtype = c_build_qualified_type (TREE_TYPE (subdatum), quals); - ref = build3 (COMPONENT_REF, subtype, datum, subdatum, - NULL_TREE); + if (deferred_cst_field_p (subdatum)) + ref = deferred_cst_build_component_ref (subtype, datum, subdatum); + else + ref = build3 (COMPONENT_REF, subtype, datum, subdatum, + NULL_TREE); SET_EXPR_LOCATION (ref, loc); check_counted_by_attribute (loc, ref); @@ -6364,6 +6387,7 @@ lvalue_p (const_tree ref) case REALPART_EXPR: case IMAGPART_EXPR: case COMPONENT_REF: + case DEFERRED_CST_COMPONENT_REF: return lvalue_p (TREE_OPERAND (ref, 0)); case C_MAYBE_CONST_EXPR: @@ -10209,7 +10233,9 @@ digest_init (location_t init_loc, tree decl, tree type, tree init, /* Check to see if we have already given an error message. */ if (inside_init == error_mark_node) ; - else if (require_constant && !TREE_CONSTANT (inside_init)) + else if (require_constant + && !TREE_CONSTANT (inside_init) + && !deferred_constant_initializer_p (inside_init)) { error_init (init_loc, "initializer element is not constant"); inside_init = error_mark_node; diff --git a/gcc/common.opt b/gcc/common.opt index 5d0e255a40d..648b0cb193f 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -3675,6 +3675,14 @@ Enum(zero_init_padding_bits_kind) String(unions) Value(ZERO_INIT_PADDING_BITS_UN EnumValue Enum(zero_init_padding_bits_kind) String(all) Value(ZERO_INIT_PADDING_BITS_ALL) +fdeferred-cst-static +Common Var(flag_deferred_cst_static) Init(0) +Allow deferred constants to appear in static data. + +fdeferred-cst-barrier-hard +Common Var(flag_deferred_cst_barrier_hard) Init(0) +Make deferred-cst-barrier ICE if deferred constants remain. + g Common Driver RejectNegative JoinedOrMissing Generate debug information in default format. diff --git a/gcc/deferred-cst.cc b/gcc/deferred-cst.cc new file mode 100644 index 00000000000..bec14508484 --- /dev/null +++ b/gcc/deferred-cst.cc @@ -0,0 +1,626 @@ +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "stringpool.h" +#include "fold-const.h" +#include "internal-fn.h" +#include "attribs.h" +#include "tree-pass.h" +#include "context.h" +#include "cgraph.h" +#include "function.h" +#include "cfg.h" +#include "basic-block.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "gimple-walk.h" +#include "diagnostic.h" + +#include "deferred-cst.h" + +/* Weak mapping from STRING_CST tokens used as arguments to + IFN_DEFERRED_CST_FIELD_OFFSET to the FIELD_DECL whose byte offset + the call represents. + + For an ordinary field the value is that field's FIELD_DECL. + + For a bit-field the value is DECL_BIT_FIELD_REPRESENTATIVE (field), + since IFN_DEFERRED_CST_FIELD_OFFSET describes the byte offset of + the whole bit-field representative rather than the individual + field. */ +static GTY((cache)) tree_cache_map *field_offset_field_decls; + +/* Used only to make STRING_CST contents unique. The actual string value has + no semantic meaning; the STRING_CST node itself is the metadata token. */ +static unsigned HOST_WIDE_INT next_field_offset_token = 0; + +/* Return true if TYPE is a record carrying + __attribute__((deferred_cst_access)). */ +bool +deferred_cst_access_type_p (tree type) +{ + return (type + && TREE_CODE (type) == RECORD_TYPE + && lookup_attribute ("deferred_cst_access", + TYPE_ATTRIBUTES (type)) != NULL_TREE); +} + +bool +deferred_cst_field_p (tree field) +{ + if (!field || TREE_CODE (field) != FIELD_DECL) + return false; + + return deferred_cst_access_type_p (DECL_CONTEXT (field)); +} + +/* Return true if CODE is an operation which may be absorbed into a + DEFERRED_CST_EXPR. + + This is deliberately an explicit whitelist. In particular, do not + absorb arbitrary unary/binary/reference tree codes merely because all + of their operands happen to be constant. */ +bool +deferred_cst_operator_p (enum tree_code code) +{ + switch (code) + { + /* Binary arithmetic operations. */ + case PLUS_EXPR: + case MINUS_EXPR: + case MULT_EXPR: + case POINTER_PLUS_EXPR: + case POINTER_DIFF_EXPR: + case TRUNC_DIV_EXPR: + case CEIL_DIV_EXPR: + case FLOOR_DIV_EXPR: + case TRUNC_MOD_EXPR: + case RDIV_EXPR: + case EXACT_DIV_EXPR: + case LSHIFT_EXPR: + case RSHIFT_EXPR: + case LROTATE_EXPR: + case RROTATE_EXPR: + case BIT_IOR_EXPR: + case BIT_XOR_EXPR: + case BIT_AND_EXPR: + return true; + + /* Comparisons. */ + case LT_EXPR: + case LE_EXPR: + case GT_EXPR: + case GE_EXPR: + case EQ_EXPR: + case NE_EXPR: + case UNORDERED_EXPR: + case ORDERED_EXPR: + case UNLT_EXPR: + case UNLE_EXPR: + case UNGT_EXPR: + case UNGE_EXPR: + case UNEQ_EXPR: + return true; + + /* Boolean operations. + + TRUTH_ANDIF_EXPR and TRUTH_ORIF_EXPR retain their short-circuit + semantics inside the deferred expression. Nothing is evaluated + when absorbing expressions into deferred constants. */ + case TRUTH_AND_EXPR: + case TRUTH_OR_EXPR: + case TRUTH_XOR_EXPR: + case TRUTH_ANDIF_EXPR: + case TRUTH_ORIF_EXPR: + return true; + + /* Unary arithmetic and conversions. */ + case FIX_TRUNC_EXPR: + case FLOAT_EXPR: + CASE_CONVERT: + case ADDR_SPACE_CONVERT_EXPR: + case NON_LVALUE_EXPR: + case NEGATE_EXPR: + case BIT_NOT_EXPR: + case TRUTH_NOT_EXPR: + case CONJ_EXPR: + case REALPART_EXPR: + case IMAGPART_EXPR: + case VIEW_CONVERT_EXPR: + case PAREN_EXPR: + return true; + + case COMPLEX_EXPR: + return true; + + /* Conditional expressions. These are absorbed only when the + condition and both alternatives are all constant-like. */ + case COND_EXPR: + case VEC_COND_EXPR: + return true; + + default: + return false; + } +} + +tree +deferred_cst_build_component_ref (tree type, tree base, tree field) +{ + gcc_assert (field); + gcc_assert (TREE_CODE (field) == FIELD_DECL); + + return build2 (DEFERRED_CST_COMPONENT_REF, type, base, field); +} + +tree +deferred_cst_build_expr (tree expr) +{ + /* TODO assert that expr is actually a deferred constant */ + return build1_loc (EXPR_LOCATION (expr), DEFERRED_CST_EXPR, + TREE_TYPE (expr), expr); +} + +/* Create a unique STRING_CST to use as a metadata token. */ +static tree +make_token (const char *name, unsigned HOST_WIDE_INT id) +{ + char buf[128]; + + int len = snprintf (buf, sizeof (buf), + "__deferred_cst_token_%s_" + HOST_WIDE_INT_PRINT_UNSIGNED, + name, id); + + gcc_assert (len > 0); + gcc_assert ((unsigned int) len < sizeof (buf)); + + /* Include the trailing NUL. */ + unsigned int string_len = len + 1; + + tree token = build_string (string_len, buf); + + TREE_TYPE (token) + = build_array_type_nelts (char_type_node, string_len); + + return token; +} + +/* Generate a new key token into field_offset_field_decls and store + the FIELD_DECL FIELD under it. Return the new token. */ +static tree +register_field_offset_field_decl (tree field) +{ + tree token = make_token ("field_offset", next_field_offset_token++); + + if (!field_offset_field_decls) + field_offset_field_decls = tree_cache_map::create_ggc (); + + bool existed = field_offset_field_decls->put (token, field); + + /* Every token created above is unique. */ + gcc_assert (!existed); + + return token; +} + +/* Look up the field declaration associated with a token argument of + IFN_DEFERRED_CST_FIELD_OFFSET. */ +static tree +lookup_field_offset_field_decl (tree token) +{ + if (!token) + return NULL_TREE; + + tree *slot + = field_offset_field_decls + ? field_offset_field_decls->get (token) + : NULL; + + return slot ? *slot : NULL_TREE; +} + +/* Create a call to IFN_DEFERRED_CST_FIELD_OFFSET for the physical, + addressable FIELD whose byte offset is deferred. + + FIELD must not be a bit-field. Callers handling bit-fields must pass + DECL_BIT_FIELD_REPRESENTATIVE (field). + + Internally, these call hold the field declarations in a side-map + keyed by tokens used as call arguments. */ +tree +deferred_cst_call_ifn_field_offset (location_t loc, tree field) +{ + gcc_assert (field); + gcc_assert (TREE_CODE (field) == FIELD_DECL); + gcc_assert (!DECL_BIT_FIELD (field)); + + tree token = register_field_offset_field_decl (field); + + return build_call_expr_internal_loc (loc, + IFN_DEFERRED_CST_FIELD_OFFSET, + sizetype, 1, token); +} + +tree +deferred_cst_ifn_field_offset_field (tree token) +{ + if (!token) + return NULL_TREE; + + return lookup_field_offset_field_decl (token); +} + +static tree +find_deferred_cst_node (tree *tp, int *walk_subtrees, void *) +{ + tree node = *tp; + + if (!node) + return NULL_TREE; + + switch (TREE_CODE (node)) + { + case DEFERRED_CST_EXPR: + case DEFERRED_CST_COMPONENT_REF: + *walk_subtrees = 0; + return node; + + default: + break; + } + + /* Declarations and types are metadata here. Do not walk into their + contexts, types, or initializers. */ + if (DECL_P (node) || TYPE_P (node)) + *walk_subtrees = 0; + + return NULL_TREE; +} + +bool +deferred_cst_present_p (tree expr) +{ + if (!expr) + return false; + + tree root = expr; + + return walk_tree_without_duplicates ( + &root, + find_deferred_cst_node, + NULL) + != NULL_TREE; +} + +/* Replace deferred cst components under DEFERRED_CST_EXPR nodes with + their compile-time values. + + Calls to IFN_DEFERRED_CST_FIELD_OFFSET become the compile-time + field offsets. + + Return NULL_TREE if no operation took place. ICE if a substitution + failed. */ +static tree +substitute_deferred_cst_component (tree expr) +{ + if (!expr) + return NULL_TREE; + + location_t loc = EXPR_LOCATION (expr); + + if (TREE_CODE (expr) == CALL_EXPR) + { + if (CALL_EXPR_IFN (expr) == IFN_DEFERRED_CST_FIELD_OFFSET) + { + gcc_assert (call_expr_nargs (expr) == 1); + tree field + = deferred_cst_ifn_field_offset_field ( + CALL_EXPR_ARG (expr, 0)); + + gcc_assert (field && TREE_CODE (field) == FIELD_DECL); + + tree offset = byte_position (field); + return fold_convert_loc (loc, TREE_TYPE (expr), offset); + } + } + + return NULL_TREE; +} + +/* Replace all deferred cst components with their compile-time values + and return the fully folded result. */ +static tree +substitute_deferred_cst_components (tree expr) +{ + if (CONSTANT_CLASS_P (expr)) + return expr; + + tree deferred_component = substitute_deferred_cst_component (expr); + if (deferred_component) + return deferred_component; + + gcc_assert (deferred_cst_operator_p (TREE_CODE (expr))); + + switch (TREE_OPERAND_LENGTH (expr)) + { + case 1: + { + tree op0 + = substitute_deferred_cst_components (TREE_OPERAND (expr, 0)); + + return fold_build1_loc (EXPR_LOCATION (expr), + TREE_CODE (expr), + TREE_TYPE (expr), + op0); + } + + case 2: + { + tree op0 + = substitute_deferred_cst_components (TREE_OPERAND (expr, 0)); + tree op1 + = substitute_deferred_cst_components (TREE_OPERAND (expr, 1)); + + return fold_build2_loc (EXPR_LOCATION (expr), + TREE_CODE (expr), + TREE_TYPE (expr), + op0, op1); + } + + case 3: + { + tree op0 + = substitute_deferred_cst_components (TREE_OPERAND (expr, 0)); + tree op1 + = substitute_deferred_cst_components (TREE_OPERAND (expr, 1)); + tree op2 + = substitute_deferred_cst_components (TREE_OPERAND (expr, 2)); + + return fold_build3_loc (EXPR_LOCATION (expr), + TREE_CODE (expr), + TREE_TYPE (expr), + op0, op1, op2); + } + + default: + gcc_unreachable (); + } +} + +/* Replace all deferred components in EXPR with their compile-time + values and evaluate the result. Return an actual constant. */ +static tree +evaluate_deferred_cst_now (tree expr) +{ + tree result = substitute_deferred_cst_components (expr); + result = fold (result); + + if (!CONSTANT_CLASS_P (result)) + internal_error ("invalid compile-time value of deferred constant"); + + return result; +} + +/* Returns true if the given tree node may not exist past the deferred + constant barrier pass. */ +static bool +deferred_cst_barrier_applies (tree node) +{ + if (!node) + return false; + + switch (TREE_CODE (node)) + { + case DEFERRED_CST_EXPR: + case DEFERRED_CST_COMPONENT_REF: + return true; + + default: + return false; + } +} + +/* On the deferred constant barrier pass, return the replacement for + a tree node that may not exist beyond it. + + DEFERRED_CST_COMPONENT_REFs are simpy replaced with a standard + COMPONENT_REF node. + + All deferred components under a DEFERRED_CST_EXPR are replaced with + their actual compile-time values. Afterwards, the expression is + fully evaluated to replace NODE. */ +static tree +deferred_cst_barrier_apply (tree node) +{ + gcc_assert (deferred_cst_barrier_applies (node)); + + if (flag_deferred_cst_barrier_hard) + internal_error ( + "unresolved deferred constant reached hard barrier"); + + switch (TREE_CODE (node)) + { + case DEFERRED_CST_EXPR: + return evaluate_deferred_cst_now (TREE_OPERAND (node, 0)); + + case DEFERRED_CST_COMPONENT_REF: + { + tree ref + = build3_loc (EXPR_LOCATION (node), + COMPONENT_REF, + TREE_TYPE (node), + TREE_OPERAND (node, 0), + TREE_OPERAND (node, 1), + NULL_TREE); + + /* Preserve expression properties that may have been attached + to the deferred reference during lowering. */ + TREE_READONLY (ref) = TREE_READONLY (node); + TREE_THIS_VOLATILE (ref) = TREE_THIS_VOLATILE (node); + TREE_SIDE_EFFECTS (ref) = TREE_SIDE_EFFECTS (node); + + return ref; + } + + default: + gcc_unreachable (); + } +} + +/* Walker to go through functions and initializers. It applies the + deferred constant barrier to all deferred constant tree nodes. */ +static tree +deferred_cst_barrier_walk (tree *tp, int *walk_subtrees, void *data) +{ + tree node = *tp; + + if (!node) + return NULL_TREE; + + if (TYPE_P (node) || DECL_P (node)) + { + *walk_subtrees = 0; + return NULL_TREE; + } + + if (!deferred_cst_barrier_applies (node)) + return NULL_TREE; + + /* + A deferred component can have another deferred component in its base. + Clean the base explicitly before replacing the outer node. Operand 1 + is a FIELD_DECL and must not be walked. + */ + if (TREE_CODE (node) == DEFERRED_CST_COMPONENT_REF) + { + tree base = TREE_OPERAND (node, 0); + + walk_tree (&base, + deferred_cst_barrier_walk, + data, + NULL); + + TREE_OPERAND (node, 0) = base; + } + + *tp = deferred_cst_barrier_apply (node); + *walk_subtrees = 0; + + return NULL_TREE; +} + +static void +deferred_cst_barrier_function (function *fn) +{ + basic_block bb; + + FOR_EACH_BB_FN (bb, fn) + { + for (gimple_stmt_iterator gsi = gsi_start_bb (bb); + !gsi_end_p (gsi); + gsi_next (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + + for (unsigned int i = 0; i < gimple_num_ops (stmt); ++i) + { + tree *operand = gimple_op_ptr (stmt, i); + + if (!operand || !*operand) + continue; + + walk_tree (operand, + deferred_cst_barrier_walk, + NULL, + NULL); + } + } + } +} + +static tree +deferred_cst_barrier_init (tree init) +{ + if (!init) + return init; + + walk_tree (&init, deferred_cst_barrier_walk, NULL, NULL); + + return init; +} + +static void +deferred_cst_barrier_functions () +{ + cgraph_node *node; + + FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) + { + function *fn = DECL_STRUCT_FUNCTION (node->decl); + gcc_assert (fn); + + push_cfun (fn); + deferred_cst_barrier_function (fn); + pop_cfun (); + } +} + +static void +deferred_cst_barrier_inits () +{ + varpool_node *vnode; + + FOR_EACH_VARIABLE (vnode) + { + tree decl = vnode->decl; + tree init = DECL_INITIAL (decl); + + if (!init || init == error_mark_node) + continue; + + tree clean_init = deferred_cst_barrier_init (init); + if (clean_init != init) + DECL_INITIAL (decl) = clean_init; + } +} + +namespace { + +const pass_data pass_data_deferred_cst_barrier = +{ + SIMPLE_IPA_PASS, + "deferred-cst-barrier", + OPTGROUP_NONE, + TV_NONE, + 0, 0, 0, 0, 0 +}; + +class pass_deferred_cst_barrier : public simple_ipa_opt_pass +{ +public: + pass_deferred_cst_barrier (gcc::context *ctxt) + : simple_ipa_opt_pass (pass_data_deferred_cst_barrier, ctxt) + {} + + unsigned int execute (function *) final override; +}; + +unsigned int +pass_deferred_cst_barrier::execute (function *) +{ + deferred_cst_barrier_functions (); + deferred_cst_barrier_inits (); + return 0; +} + +} // anonymous namespace + +simple_ipa_opt_pass * +make_pass_deferred_cst_barrier (gcc::context *ctxt) +{ + return new pass_deferred_cst_barrier (ctxt); +} + +#include "gt-deferred-cst.h" diff --git a/gcc/deferred-cst.h b/gcc/deferred-cst.h new file mode 100644 index 00000000000..d475030de01 --- /dev/null +++ b/gcc/deferred-cst.h @@ -0,0 +1,39 @@ +#ifndef GCC_DEFERRED_CST_H +#define GCC_DEFERRED_CST_H + +/* Returns true of accesses to fields of this type should be based on + deferred cst offsets. This is the case on RECORD_TYPEs with + + __attribute__((deferred_cst_access)) */ +extern bool deferred_cst_access_type_p (tree type); + +/* Returns true if FIELD is part of a type that satisfies + deferred_cst_access_type_p. */ +extern bool deferred_cst_field_p (tree field); + +/* Return true if CODE is an operation which may be absorbed into a + DEFERRED_CST_EXPR. */ +extern bool deferred_cst_operator_p (enum tree_code code); + +/* Build DEFERRED_CST_COMPONENT_REF to FIELD in BASE. */ +extern tree deferred_cst_build_component_ref (tree type, tree base, tree field); + +/* Build a DEFERRED_CST_EXPR around EXPR. */ +extern tree deferred_cst_build_expr (tree expr); + +/* Create a call to IFN_DEFERRED_CST_FIELD_OFFSET for the physical, + addressable FIELD whose byte offset is deferred. + + FIELD must not be a bit-field. Callers handling bit-fields must pass + DECL_BIT_FIELD_REPRESENTATIVE (field). */ +extern tree deferred_cst_call_ifn_field_offset (location_t loc, tree field); + +/* Get the FIELD_DECL associated with the argument TOKEN to a call of + IFN_DEFERRED_CST_FIELD_OFFSET. */ +extern tree deferred_cst_ifn_field_offset_field (tree token); + +/* Return true if EXPR contains a DEFERRED_CST_EXPR or + DEFERRED_CST_COMPONENT_REF. */ +extern bool deferred_cst_present_p (tree expr); + +#endif diff --git a/gcc/expr.cc b/gcc/expr.cc index 7ca5dd84ce9..d0925557cef 100644 --- a/gcc/expr.cc +++ b/gcc/expr.cc @@ -66,6 +66,7 @@ along with GCC; see the file COPYING3. If not see #include "tree-pretty-print.h" #include "flags.h" #include "internal-fn.h" +#include "deferred-cst.h" /* If this is nonzero, we do not bother generating VOLATILE @@ -8460,6 +8461,7 @@ store_field (rtx target, poly_int64 bitsize, poly_int64 bitpos, } } + /* Given an expression EXP that may be a COMPONENT_REF, a BIT_FIELD_REF, an ARRAY_REF, or an ARRAY_RANGE_REF, look for nested operations of these codes and find the ultimate containing object, which we return. @@ -8497,7 +8499,8 @@ get_inner_reference (tree exp, poly_int64 *pbitsize, /* First get the mode, signedness, storage order and size. We do this from just the outermost expression. */ *pbitsize = -1; - if (TREE_CODE (exp) == COMPONENT_REF) + if (TREE_CODE (exp) == COMPONENT_REF + || TREE_CODE (exp) == DEFERRED_CST_COMPONENT_REF) { tree field = TREE_OPERAND (exp, 1); size_tree = DECL_SIZE (field); @@ -8585,6 +8588,36 @@ get_inner_reference (tree exp, poly_int64 *pbitsize, } break; + case DEFERRED_CST_COMPONENT_REF: + { + tree field = TREE_OPERAND (exp, 1); + tree deferred_cst_field = field; + + if (DECL_BIT_FIELD (field)) + { + deferred_cst_field = DECL_BIT_FIELD_REPRESENTATIVE (field); + + gcc_assert (deferred_cst_field); + gcc_assert (TREE_CODE (deferred_cst_field) == FIELD_DECL); + gcc_assert (!DECL_BIT_FIELD (deferred_cst_field)); + + tree relative_bit_offset + = size_binop (MINUS_EXPR, + bit_position (field), + bit_position (deferred_cst_field)); + + gcc_assert (poly_int_tree_p (relative_bit_offset)); + bit_offset += wi::to_poly_offset (relative_bit_offset); + } + + tree this_offset = deferred_cst_call_ifn_field_offset ( + EXPR_LOCATION (exp), + deferred_cst_field); + this_offset = deferred_cst_build_expr (this_offset); + offset = size_binop (PLUS_EXPR, offset, this_offset); + } + break; + case ARRAY_REF: case ARRAY_RANGE_REF: { diff --git a/gcc/fold-const.cc b/gcc/fold-const.cc index 56503e570bc..2fb0287b927 100644 --- a/gcc/fold-const.cc +++ b/gcc/fold-const.cc @@ -11139,6 +11139,20 @@ fold_binary_loc (location_t loc, enum tree_code code, tree type, switch (code) { + case DEFERRED_CST_COMPONENT_REF: + if (TREE_CODE (arg0) == CONSTRUCTOR + && ! type_contains_placeholder_p (TREE_TYPE (arg0))) + { + unsigned HOST_WIDE_INT idx; + tree field; + tree value; + + FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (arg0), idx, field, value) + if (field == arg1) + return value; + } + return NULL_TREE; + case MEM_REF: /* MEM[&MEM[p, CST1], CST2] -> MEM[p, CST1 + CST2]. */ if (TREE_CODE (arg0) == ADDR_EXPR diff --git a/gcc/gimple-expr.cc b/gcc/gimple-expr.cc index 96f9fc720af..fabd6e10ea1 100644 --- a/gcc/gimple-expr.cc +++ b/gcc/gimple-expr.cc @@ -846,6 +846,11 @@ is_gimple_val (tree t) || TREE_CODE (t) == OMP_TARGET_DEVICE_MATCHES) return true; + /* Technically, deferred constant expressions act as min invariants, + but they do not survice to where that would be relevant. */ + if (TREE_CODE (t) == DEFERRED_CST_EXPR) + return true; + return (is_gimple_variable (t) || is_gimple_min_invariant (t)); } diff --git a/gcc/gimple.cc b/gcc/gimple.cc index c791e041243..7918552fce1 100644 --- a/gcc/gimple.cc +++ b/gcc/gimple.cc @@ -2564,7 +2564,8 @@ get_gimple_rhs_num_ops (enum tree_code code) || (SYM) == WITH_SIZE_EXPR \ || (SYM) == SSA_NAME \ || (SYM) == OMP_NEXT_VARIANT \ - || (SYM) == OMP_TARGET_DEVICE_MATCHES) ? GIMPLE_SINGLE_RHS \ + || (SYM) == OMP_TARGET_DEVICE_MATCHES \ + || (SYM) == DEFERRED_CST_EXPR) ? GIMPLE_SINGLE_RHS \ : GIMPLE_INVALID_RHS), #define END_OF_BASE_TREE_CODES (unsigned char) GIMPLE_INVALID_RHS, diff --git a/gcc/gimplify.cc b/gcc/gimplify.cc index 8bfc71315f9..6f55340dc34 100644 --- a/gcc/gimplify.cc +++ b/gcc/gimplify.cc @@ -75,6 +75,7 @@ along with GCC; see the file COPYING3. If not see #include "recog.h" #include "output.h" #include "gimplify_reg_info.h" +#include "deferred-cst.h" /* Identifier for a basic condition, mapping it to other basic conditions of its Boolean expression. Basic conditions given the same uid (in the same @@ -3497,8 +3498,8 @@ recalculate_side_effects (tree t) } } -/* Gimplify the COMPONENT_REF, ARRAY_REF, REALPART_EXPR or IMAGPART_EXPR - node *EXPR_P. +/* Gimplify the COMPONENT_REF, DEFERRED_CST_COMPONENT_REF, ARRAY_REF, + REALPART_EXPR or IMAGPART_EXPR node *EXPR_P. compound_lval : min_lval '[' val ']' @@ -6086,7 +6087,7 @@ gimplify_init_ctor_preeval (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p, already been taken care of for us, in gimplify_init_ctor_preeval(). */ static void gimplify_init_ctor_eval (tree, vec<constructor_elt, va_gc> *, - gimple_seq *, bool); + gimple_seq *, bool, bool); static void gimplify_init_ctor_eval_range (tree object, tree lower, tree upper, @@ -6116,10 +6117,15 @@ gimplify_init_ctor_eval_range (tree object, tree lower, tree upper, the store. Otherwise just assign value to the reference. */ if (TREE_CODE (value) == CONSTRUCTOR) - /* NB we might have to call ourself recursively through - gimplify_init_ctor_eval if the value is a constructor. */ - gimplify_init_ctor_eval (cref, CONSTRUCTOR_ELTS (value), - pre_p, cleared); + { + /* NB we might have to call ourself recursively through + gimplify_init_ctor_eval if the value is a constructor. */ + bool deferred_cst_value + = deferred_cst_access_type_p (TREE_TYPE (value)); + + gimplify_init_ctor_eval (cref, CONSTRUCTOR_ELTS (value), + pre_p, cleared, deferred_cst_value); + } else { if (gimplify_expr (&value, pre_p, NULL, is_gimple_val, fb_rvalue) @@ -6154,7 +6160,8 @@ gimplify_init_ctor_eval_range (tree object, tree lower, tree upper, static void gimplify_init_ctor_eval (tree object, vec<constructor_elt, va_gc> *elts, - gimple_seq *pre_p, bool cleared) + gimple_seq *pre_p, bool cleared, + bool deferred_cst) { tree array_elt_type = NULL; unsigned HOST_WIDE_INT ix; @@ -6220,14 +6227,25 @@ gimplify_init_ctor_eval (tree object, vec<constructor_elt, va_gc> *elts, else { gcc_assert (TREE_CODE (purpose) == FIELD_DECL); - cref = build3 (COMPONENT_REF, TREE_TYPE (purpose), - unshare_expr (object), purpose, NULL_TREE); + tree base = unshare_expr (object); + + if (deferred_cst) + cref = deferred_cst_build_component_ref (TREE_TYPE (purpose), + base, purpose); + else + cref = build3 (COMPONENT_REF, TREE_TYPE (purpose), + base, purpose, NULL_TREE); } if (TREE_CODE (value) == CONSTRUCTOR && TREE_CODE (TREE_TYPE (value)) != VECTOR_TYPE) - gimplify_init_ctor_eval (cref, CONSTRUCTOR_ELTS (value), - pre_p, cleared); + { + bool value_deferred_cst + = deferred_cst_access_type_p (TREE_TYPE (value)); + + gimplify_init_ctor_eval (cref, CONSTRUCTOR_ELTS (value), + pre_p, cleared, value_deferred_cst); + } else if (TREE_CODE (value) == RAW_DATA_CST) { if (RAW_DATA_LENGTH (value) <= 32) @@ -6418,6 +6436,7 @@ gimplify_init_constructor (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p, bool cleared = false; bool is_empty_ctor = false; bool is_init_expr = (TREE_CODE (*expr_p) == INIT_EXPR); + bool deferred_cst = false; gcc_assert (TREE_CODE (TREE_OPERAND (*expr_p, 1)) == CONSTRUCTOR); @@ -6434,6 +6453,8 @@ gimplify_init_constructor (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p, = optimize_compound_literals_in_ctor (TREE_OPERAND (*expr_p, 1)); type = TREE_TYPE (ctor); elts = CONSTRUCTOR_ELTS (ctor); + deferred_cst + = deferred_cst_access_type_p (TREE_TYPE (ctor)); ret = GS_ALL_DONE; switch (TREE_CODE (type)) @@ -6698,7 +6719,8 @@ gimplify_init_constructor (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p, if (!cleared || num_nonzero_elements > 0 || ctor_has_side_effects_p) - gimplify_init_ctor_eval (object, elts, pre_p, cleared); + gimplify_init_ctor_eval (object, elts, pre_p, cleared, + deferred_cst); *expr_p = NULL_TREE; } @@ -20425,6 +20447,7 @@ gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p, case REALPART_EXPR: case IMAGPART_EXPR: case COMPONENT_REF: + case DEFERRED_CST_COMPONENT_REF: ret = gimplify_compound_lval (expr_p, pre_p, post_p, fallback ? fallback : fb_rvalue); break; @@ -20658,6 +20681,11 @@ gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p, ret = GS_ALL_DONE; break; + case DEFERRED_CST_EXPR: + /* DEFERRED_CST_EXPRs act as min invariants. */ + ret = GS_ALL_DONE; + break; + case CONST_DECL: /* If we require an lvalue, such as for ADDR_EXPR, retain the CONST_DECL node. Otherwise the decl is replaceable by its diff --git a/gcc/internal-fn.cc b/gcc/internal-fn.cc index 65be5aed35c..88a7334dadb 100644 --- a/gcc/internal-fn.cc +++ b/gcc/internal-fn.cc @@ -4266,6 +4266,15 @@ expand_reduc_sbool_optab_fn (internal_fn fn, gcall *stmt, direct_optab optab) assign_call_lhs (lhs, lhs_rtx, &ops[0]); } +/* IFN_DEFERRED_CST_FIELD_OFFSET should not survice GIMPLE. */ + +void +expand_DEFERRED_CST_FIELD_OFFSET (internal_fn, gcall *) +{ + internal_error ( + "IFN_DEFERRED_CST_FIELD_OFFSET reached RTL expansion without being lowered"); +} + /* Expanders for optabs that can use expand_direct_optab_fn. */ #define expand_unary_optab_fn(FN, STMT, OPTAB) \ diff --git a/gcc/internal-fn.def b/gcc/internal-fn.def index 084a9271631..ce8aacf803f 100644 --- a/gcc/internal-fn.def +++ b/gcc/internal-fn.def @@ -622,6 +622,9 @@ DEF_INTERNAL_FN (BITINTTOFLOAT, ECF_PURE | ECF_LEAF, ". R . ") DEF_INTERNAL_OPTAB_FN (BIT_ANDN, ECF_CONST, andn, binary) DEF_INTERNAL_OPTAB_FN (BIT_IORN, ECF_CONST, iorn, binary) +/* Deferred constant functions. */ +DEF_INTERNAL_FN (DEFERRED_CST_FIELD_OFFSET, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) + #undef DEF_INTERNAL_WIDENING_OPTAB_FN #undef DEF_INTERNAL_SIGNED_COND_FN #undef DEF_INTERNAL_COND_FN diff --git a/gcc/passes.def b/gcc/passes.def index cdddb87302f..2b14864d200 100644 --- a/gcc/passes.def +++ b/gcc/passes.def @@ -47,6 +47,7 @@ along with GCC; see the file COPYING3. If not see /* Interprocedural optimization passes. */ INSERT_PASSES_AFTER (all_small_ipa_passes) + NEXT_PASS (pass_deferred_cst_barrier); /* auto_profile_offline uses dwarf_name hook that only works before free_lang_data. */ NEXT_PASS (pass_ipa_auto_profile_offline); diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h index b3c97658a8f..eddec889dc2 100644 --- a/gcc/tree-pass.h +++ b/gcc/tree-pass.h @@ -513,6 +513,7 @@ extern gimple_opt_pass *make_pass_coroutine_early_expand_ifns (gcc::context *ctx extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt); /* IPA Passes */ +extern simple_ipa_opt_pass *make_pass_deferred_cst_barrier (gcc::context *ctxt); extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt); extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt); extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt); diff --git a/gcc/tree-pretty-print.cc b/gcc/tree-pretty-print.cc index 79e0a734dbb..79251619fbe 100644 --- a/gcc/tree-pretty-print.cc +++ b/gcc/tree-pretty-print.cc @@ -2837,6 +2837,7 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags, break; case COMPONENT_REF: + case DEFERRED_CST_COMPONENT_REF: op0 = TREE_OPERAND (node, 0); str = "."; if (op0 @@ -2873,16 +2874,21 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags, if (op_prio (op0) < op_prio (node)) pp_right_paren (pp); pp_string (pp, str); + if (code == DEFERRED_CST_COMPONENT_REF) + pp_string (pp, "DEFERRED<"); op1 = TREE_OPERAND (node, 1); dump_generic_node (pp, op1, spc, flags, false); - if (DECL_P (op1)) /* Not always a decl in the C++ FE. */ - if (tree off = component_ref_field_offset (node)) - if (TREE_CODE (off) != INTEGER_CST) - { - pp_string (pp, "{off: "); - dump_generic_node (pp, off, spc, flags, false); - pp_right_brace (pp); - } + if (code == DEFERRED_CST_COMPONENT_REF) + pp_string (pp, ">"); + else + if (DECL_P (op1)) /* Not always a decl in the C++ FE. */ + if (tree off = component_ref_field_offset (node)) + if (TREE_CODE (off) != INTEGER_CST) + { + pp_string (pp, "{off: "); + dump_generic_node (pp, off, spc, flags, false); + pp_right_brace (pp); + } break; case BIT_FIELD_REF: @@ -4344,6 +4350,13 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags, } break; + case DEFERRED_CST_EXPR: + pp_string (pp, "DEFERRED_CST_EXPR <"); + dump_generic_node (pp, TREE_OPERAND (node, 0), + spc, flags, false); + pp_character (pp, '>'); + break; + case OMP_TARGET_DEVICE_MATCHES: pp_string (pp, "OMP_TARGET_DEVICE_MATCHES <"); dump_generic_node (pp, OMP_TARGET_DEVICE_MATCHES_SELECTOR (node), spc, @@ -4790,6 +4803,7 @@ op_code_prio (enum tree_code code) case ARRAY_REF: case ARRAY_RANGE_REF: case COMPONENT_REF: + case DEFERRED_CST_COMPONENT_REF: return 15; /* Special expressions. */ @@ -5052,6 +5066,7 @@ print_call_name (pretty_printer *pp, tree node, dump_flags_t flags) } /* Fallthru. */ case COMPONENT_REF: + case DEFERRED_CST_COMPONENT_REF: case SSA_NAME: case OBJ_TYPE_REF: dump_generic_node (pp, op0, 0, flags, false); diff --git a/gcc/tree.cc b/gcc/tree.cc index 2868cb0867d..158c99b9aed 100644 --- a/gcc/tree.cc +++ b/gcc/tree.cc @@ -76,6 +76,7 @@ along with GCC; see the file COPYING3. If not see #include "asan.h" #include "ubsan.h" #include "attr-callback.h" +#include "deferred-cst.h" /* Names of tree components. Used for printing out the tree and error messages. */ @@ -3908,7 +3909,13 @@ staticp (tree arg) ? arg : NULL); case CONSTRUCTOR: - return TREE_STATIC (arg) ? arg : NULL; + { + if (!flag_deferred_cst_static + && deferred_cst_access_type_p (TREE_TYPE (arg))) + return NULL; + + return TREE_STATIC (arg) ? arg : NULL; + } case LABEL_DECL: case STRING_CST: @@ -3926,6 +3933,16 @@ staticp (tree arg) return staticp (TREE_OPERAND (arg, 0)); + case DEFERRED_CST_COMPONENT_REF: + { + /* Deferred cst references are only compatible with static data if + explicitly permitted by -fdeferred-cst-static */ + if (flag_deferred_cst_static) + return staticp (TREE_OPERAND (arg, 0)); + else + return NULL; + } + case BIT_FIELD_REF: return NULL; diff --git a/gcc/tree.def b/gcc/tree.def index ad8e676e942..43e94d0c361 100644 --- a/gcc/tree.def +++ b/gcc/tree.def @@ -443,6 +443,11 @@ DEFTREECODE (TRANSLATION_UNIT_DECL, "translation_unit_decl",\ in units of DECL_OFFSET_ALIGN / BITS_PER_UNIT. */ DEFTREECODE (COMPONENT_REF, "component_ref", tcc_reference, 3) +/* Value is a structure component with a deferred constant offset. + Operand 0 is the structure expression. + Operand 1 is the field (a node of type FIELD_DECL). */ +DEFTREECODE (DEFERRED_CST_COMPONENT_REF, "deferred_cst_component_ref", tcc_reference, 2) + /* Reference to a group of bits within an object. Similar to COMPONENT_REF except the position is given explicitly rather than via a FIELD_DECL. Operand 0 is the structure or union expression; @@ -1610,6 +1615,14 @@ DEFTREECODE (TARGET_OPTION_NODE, "target_option_node", tcc_exceptional, 0) Operand 2 is additional data. */ DEFTREECODE (ANNOTATE_EXPR, "annotate_expr", tcc_expression, 3) +/* DEFERRED_CST_EXPR. + Operand 0 is an expression with deferred const-ness. + Such expressions are not constant at compile-time, but become + constant before the normal runtime. For example, a + DEFERRED_CST_EXPR may wrap offsetof-like expressions that depend + on DEFERRED_CST_COMPONENT_REFs. */ +DEFTREECODE (DEFERRED_CST_EXPR, "deferred_cst_expr", tcc_expression, 1) + /* Local variables: mode:c diff --git a/gcc/tree.h b/gcc/tree.h index f8b2d718b7e..386a62051a8 100644 --- a/gcc/tree.h +++ b/gcc/tree.h @@ -5545,6 +5545,7 @@ handled_component_p (const_tree t) switch (TREE_CODE (t)) { case COMPONENT_REF: + case DEFERRED_CST_COMPONENT_REF: case BIT_FIELD_REF: case ARRAY_REF: case ARRAY_RANGE_REF: diff --git a/gcc/varasm.cc b/gcc/varasm.cc index 5999f1e5420..c955670c7e7 100644 --- a/gcc/varasm.cc +++ b/gcc/varasm.cc @@ -64,6 +64,7 @@ along with GCC; see the file COPYING3. If not see #include "asan.h" #include "recog.h" #include "gimple-expr.h" +#include "deferred-cst.h" /* The (assembler) name of the first globally-visible object output. */ extern GTY(()) const char *first_global_object_name; @@ -5056,7 +5057,12 @@ initializer_constant_valid_p_1 (tree value, tree endtype, tree *cache) switch (TREE_CODE (value)) { case CONSTRUCTOR: - if (constructor_static_from_elts_p (value)) + if (!flag_deferred_cst_static + && deferred_cst_access_type_p (TREE_TYPE (value))) + return NULL_TREE; + + if (constructor_static_from_elts_p (value) + || flag_deferred_cst_static) { unsigned HOST_WIDE_INT idx; tree elt; @@ -5107,6 +5113,16 @@ initializer_constant_valid_p_1 (tree value, tree endtype, tree *cache) case RAW_DATA_CST: return null_pointer_node; + case DEFERRED_CST_EXPR: + { + /* Deferred constants are only allowed in static initializers + if explicitly permitted by -fdeferred-cst-static */ + if (flag_deferred_cst_static) + return null_pointer_node; + else + return NULL_TREE; + } + case ADDR_EXPR: case FDESC_EXPR: { -- 2.43.0
