From: Kyrylo Tkachov <[email protected]>
AArch64 addr_diff_vec entries are relative to a synthetic text label. The
casesi output sequence currently defines that label at each dispatch. If
several dispatches share one table, they define the same assembler symbol more
than once.
The base label belongs to the table:
dispatch A --\
dispatch B ----> .LrtxN: -> table N
dispatch C --/
Add TARGET_ASM_JUMP_TABLE_BASE and call it once while final still has the
function section selected. Add TARGET_CAN_SHARE_JUMP_TABLE_P to decide
whether a tablejump can share its table before duplication. Its default
returns whether the output hook is defined. Targets can override it to
inspect the instruction form and current function's options. Accepted forms
must reach the shared table without using their own jump address as the
table-entry base. Output and late verification do not query this predicate.
Preserve the existing AArch64 path for an unshared table. Its casesi output
still defines the base label and uses ADR. Once a table is shared, the hook
emits the base label and each dispatch refers to it. Shared copies can be more
than ADR's range from the base label, so use ADRP and ADD outside the tiny code
model and account for the extra instruction in the length attribute.
Other targets remain disabled because they do not define the hook.
gcc/ChangeLog:
PR middle-end/126236
* target.def (asm_out.jump_table_base): New hook.
(can_share_jump_table_p): Likewise.
* targhooks.cc (default_can_share_jump_table_p): New function.
* targhooks.h (default_can_share_jump_table_p): Declare.
* doc/tm.texi.in: Document the hooks.
* doc/tm.texi: Regenerate.
* final.cc (final_scan_insn_1): Call the jump-table base hook before
switching away from the function section or invoking target table
output.
* config/aarch64/aarch64.cc (aarch64_far_jump_table_base_p): New
function.
(aarch64_output_casesi): Emit the base label here for an unshared
table. Use ADRP and ADD for a shared table outside the tiny code
model.
(aarch64_casesi_far_base_p): New function.
(aarch64_asm_jump_table_base): New function.
(TARGET_ASM_JUMP_TABLE_BASE): Define.
* config/aarch64/aarch64-protos.h (aarch64_casesi_far_base_p):
Declare.
* config/aarch64/aarch64.md (length): Account for the longer base
sequence.
Signed-off-by: Kyrylo Tkachov <[email protected]>
---
gcc/config/aarch64/aarch64-protos.h | 1 +
gcc/config/aarch64/aarch64.cc | 68 +++++++++++++++++++++++++++--
gcc/config/aarch64/aarch64.md | 8 ++++
gcc/doc/tm.texi | 34 +++++++++++++++
gcc/doc/tm.texi.in | 4 ++
gcc/final.cc | 9 ++++
gcc/target.def | 41 +++++++++++++++++
gcc/targhooks.cc | 8 ++++
gcc/targhooks.h | 1 +
9 files changed, 170 insertions(+), 4 deletions(-)
diff --git a/gcc/config/aarch64/aarch64-protos.h
b/gcc/config/aarch64/aarch64-protos.h
index e8ae3d42794..67ce63bfd37 100644
--- a/gcc/config/aarch64/aarch64-protos.h
+++ b/gcc/config/aarch64/aarch64-protos.h
@@ -995,6 +995,7 @@ bool aarch64_is_mov_xn_imm (unsigned HOST_WIDE_INT);
bool aarch64_use_return_insn_p (void);
bool aarch64_use_simple_return_insn_p (void);
const char *aarch64_output_casesi (rtx *);
+bool aarch64_casesi_far_base_p (rtx_insn *);
const char *aarch64_output_load_tp (rtx);
const char *aarch64_output_sme_zero_za (rtx);
const char *aarch64_output_move_imm (rtx);
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index f9d906f449f..479e8c7f5b5 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -14419,6 +14419,21 @@ aarch64_elf_asm_destructor (rtx symbol, int priority)
}
}
+/* True if the base of a jump table whose body is DIFF_VEC has to be
+ materialised with ADRP and ADD rather than ADR. Once other jumps dispatch
+ through the same table the base is defined once and the copies are spread
+ over the whole function, which ADR does not cover. The tiny code model
+ bounds the whole program by 1MB, so ADR always reaches there. Under the
+ other models the base is still a label in this function, whose text is
+ limited to 4GB, so the page-relative form is enough. */
+
+static bool
+aarch64_far_jump_table_base_p (rtx diff_vec)
+{
+ return (JUMP_TABLE_SHARED_P (diff_vec)
+ && aarch64_cmodel != AARCH64_CMODEL_TINY);
+}
+
const char*
aarch64_output_casesi (rtx *operands)
{
@@ -14459,17 +14474,59 @@ aarch64_output_casesi (rtx *operands)
/* Need to implement table size reduction, by changing the code below. */
output_asm_insn (patterns[index][0], operands);
ASM_GENERATE_INTERNAL_LABEL (label, "Lrtx", CODE_LABEL_NUMBER (operands[2]));
- snprintf (buf, sizeof (buf),
- "adr\t%%4, %s", targetm.strip_name_encoding (label));
- output_asm_insn (buf, operands);
+ const char *name = targetm.strip_name_encoding (label);
+ if (aarch64_far_jump_table_base_p (diff_vec))
+ {
+ /* Several jumps dispatch through this table and there is one base,
+ so the distance to it is bounded only by the size of the function.
+ ADR only reaches 1MB. */
+ snprintf (buf, sizeof (buf), "adrp\t%%4, %s", name);
+ output_asm_insn (buf, operands);
+ snprintf (buf, sizeof (buf), "add\t%%4, %%4, :lo12:%s", name);
+ output_asm_insn (buf, operands);
+ }
+ else
+ {
+ snprintf (buf, sizeof (buf), "adr\t%%4, %s", name);
+ output_asm_insn (buf, operands);
+ }
output_asm_insn (patterns[index][1], operands);
output_asm_insn ("br\t%3", operands);
output_asm_insn (aarch64_sls_barrier (aarch64_harden_sls_retbr_p ()),
operands);
- assemble_label (asm_out_file, label);
+ if (!JUMP_TABLE_SHARED_P (diff_vec))
+ assemble_label (asm_out_file, label);
return "";
}
+/* Return true if the dispatch INSN needs the longer base sequence. Used by
+ the length attribute of the dispatch pattern. */
+
+bool
+aarch64_casesi_far_base_p (rtx_insn *insn)
+{
+ rtx_jump_table_data *table;
+
+ return (tablejump_p (insn, NULL, &table)
+ && aarch64_far_jump_table_base_p (PATTERN (table)));
+}
+
+/* Implement TARGET_ASM_JUMP_TABLE_BASE. The entries of a table are the
+ distance from the base to the case handler, so the base has to be a
+ text label. Emit it here, just before the table's own label, rather than
+ from the output template of the dispatching jump: several jumps may
+ dispatch through the one table, and the base has to be defined once. */
+
+static void
+aarch64_asm_jump_table_base (FILE *stream, rtx_jump_table_data *,
+ unsigned long labelno)
+{
+ char label[100];
+
+ ASM_GENERATE_INTERNAL_LABEL (label, "Lrtx", labelno);
+ assemble_label (stream, label);
+}
+
/* Return the asm string for an SME ZERO instruction whose 8-bit mask
operand is MASK. */
const char *
@@ -34170,6 +34227,9 @@ aarch64_run_selftests (void)
#undef TARGET_ASM_ALIGNED_HI_OP
#define TARGET_ASM_ALIGNED_HI_OP "\t.hword\t"
+#undef TARGET_ASM_JUMP_TABLE_BASE
+#define TARGET_ASM_JUMP_TABLE_BASE aarch64_asm_jump_table_base
+
#undef TARGET_ASM_ALIGNED_SI_OP
#define TARGET_ASM_ALIGNED_SI_OP "\t.word\t"
diff --git a/gcc/config/aarch64/aarch64.md b/gcc/config/aarch64/aarch64.md
index 40f7e8e2881..98e16a51b44 100644
--- a/gcc/config/aarch64/aarch64.md
+++ b/gcc/config/aarch64/aarch64.md
@@ -644,6 +644,14 @@
(match_test "TARGET_SB") (const_int 8)]
(const_int 12))
+ ;; A shared jump table may have to reach its base with ADRP and
+ ;; ADD rather than ADR, which is one instruction more.
+ (and (eq_attr "sls_length" "casesi")
+ (match_test "aarch64_casesi_far_base_p (insn)"))
+ (cond [(match_test "!aarch64_harden_sls_retbr_p ()") (const_int 20)
+ (match_test "TARGET_SB") (const_int 24)]
+ (const_int 28))
+
(eq_attr "sls_length" "casesi")
(cond [(match_test "!aarch64_harden_sls_retbr_p ()") (const_int 16)
(match_test "TARGET_SB") (const_int 20)]
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index ca68bdc3e61..fee5cb4f245 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -9303,6 +9303,40 @@ when it is necessary to output a label differently when
its address is
being taken.
@end defmac
+@deftypefn {Target Hook} void TARGET_ASM_JUMP_TABLE_BASE (FILE *@var{stream},
rtx_jump_table_data *@var{table}, unsigned long @var{labelno})
+This hook is required when @code{TARGET_CAN_SHARE_JUMP_TABLE_P} can return
+true. It is called once for a shared table, with the function's own section
+still selected, immediately before the label of the
+jump table @var{table}, whose label number is @var{labelno}. It should write
+to the stdio stream @var{stream} whatever the entries of the table are
+computed from and would otherwise be emitted once per dispatching jump,
+typically a text label for a PC-relative table. A target whose table holds
+absolute addresses has nothing to emit and may leave the body empty.
+
+Defining this hook enables shared jump tables by default. Define
+@code{TARGET_CAN_SHARE_JUMP_TABLE_P} to restrict support to particular
+instruction forms or target configurations.
+@end deftypefn
+
+@deftypefn {Target Hook} bool TARGET_CAN_SHARE_JUMP_TABLE_P (rtx_insn
*@var{insn})
+Return true if the tablejump instruction @var{insn} can share its jump
+table with copies of itself. The hook is called before duplication, with
+the current function's target options active. It can inspect the instruction
+form and these options, but must not modify RTL or emit assembly.
+
+Returning true requires @code{TARGET_ASM_JUMP_TABLE_BASE} to be defined.
+The accepted instruction must record its table label in RTL, be repeatable,
+and not use its own jump address as the table-entry base. It must be able
+to reach the shared table under the current target configuration, including
+the code model and section layout. The target must support the resulting
+shared table through final output. This hook only permits creating shared
+tables. It is not called to control their later output or verification.
+
+The default implementation returns true if
+@code{TARGET_ASM_JUMP_TABLE_BASE} is defined. Other duplication
+restrictions, including @code{TARGET_CANNOT_COPY_INSN_P}, still apply.
+@end deftypefn
+
@deftypefn {Target Hook} void TARGET_ASM_INTERNAL_LABEL (FILE *@var{stream},
const char *@var{prefix}, unsigned long @var{labelno})
A function to output to the stdio stream @var{stream} a label whose
name is made from the string @var{prefix} and the number @var{labelno}.
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 532e95ef216..5e5eaa3c9ea 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -5877,6 +5877,10 @@ when it is necessary to output a label differently when
its address is
being taken.
@end defmac
+@hook TARGET_ASM_JUMP_TABLE_BASE
+
+@hook TARGET_CAN_SHARE_JUMP_TABLE_P
+
@hook TARGET_ASM_INTERNAL_LABEL
@defmac ASM_OUTPUT_DEBUG_LABEL (@var{stream}, @var{prefix}, @var{num})
diff --git a/gcc/final.cc b/gcc/final.cc
index b6be9be99b7..4b51d59f01c 100644
--- a/gcc/final.cc
+++ b/gcc/final.cc
@@ -2427,6 +2427,15 @@ final_scan_insn_1 (rtx_insn *insn, FILE *file, int
optimize_p ATTRIBUTE_UNUSED,
the label in the read-only section. Also possibly write the
label and jump table together. */
table = jump_table_for_label (as_a <rtx_code_label *> (insn));
+
+ /* Still in the function's own section here, which is where the base
+ label of a PC-relative table has to go. */
+ if (table
+ && JUMP_TABLE_SHARED_P (PATTERN (table))
+ && targetm.asm_out.jump_table_base)
+ targetm.asm_out.jump_table_base (file, table,
+ CODE_LABEL_NUMBER (insn));
+
if (table)
{
#if defined(ASM_OUTPUT_ADDR_VEC) || defined(ASM_OUTPUT_ADDR_DIFF_VEC)
diff --git a/gcc/target.def b/gcc/target.def
index 66c20a9c9d2..7de60d91475 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -233,6 +233,25 @@ DEFHOOK_UNDOC
void, (char *buf, const char *prefix, unsigned long labelno),
default_generate_internal_label)
+/* Emit whatever a shared jump table's entries are relative to, once per
+ table, in the function's own section. */
+DEFHOOK
+(jump_table_base,
+ "This hook is required when @code{TARGET_CAN_SHARE_JUMP_TABLE_P} can return\n\
+true. It is called once for a shared table, with the function's own section\n\
+still selected, immediately before the label of the\n\
+jump table @var{table}, whose label number is @var{labelno}. It should
write\n\
+to the stdio stream @var{stream} whatever the entries of the table are\n\
+computed from and would otherwise be emitted once per dispatching jump,\n\
+typically a text label for a PC-relative table. A target whose table holds\n\
+absolute addresses has nothing to emit and may leave the body empty.\n\
+\n\
+Defining this hook enables shared jump tables by default. Define\n\
+@code{TARGET_CAN_SHARE_JUMP_TABLE_P} to restrict support to particular\n\
+instruction forms or target configurations.",
+ void, (FILE *stream, rtx_jump_table_data *table, unsigned long labelno),
+ NULL)
+
/* Output an internal label. */
DEFHOOK
(internal_label,
@@ -2810,6 +2829,28 @@ follow through a hot/cold partitioning.",
bool, (const rtx_insn *follower, const rtx_insn *followee),
hook_bool_const_rtx_insn_const_rtx_insn_true)
+/* Return true if INSN can share its jump table with copies of itself. */
+DEFHOOK
+(can_share_jump_table_p,
+ "Return true if the tablejump instruction @var{insn} can share its jump\n\
+table with copies of itself. The hook is called before duplication, with\n\
+the current function's target options active. It can inspect the
instruction\n\
+form and these options, but must not modify RTL or emit assembly.\n\
+\n\
+Returning true requires @code{TARGET_ASM_JUMP_TABLE_BASE} to be defined.\n\
+The accepted instruction must record its table label in RTL, be repeatable,\n\
+and not use its own jump address as the table-entry base. It must be able\n\
+to reach the shared table under the current target configuration, including\n\
+the code model and section layout. The target must support the resulting\n\
+shared table through final output. This hook only permits creating shared\n\
+tables. It is not called to control their later output or verification.\n\
+\n\
+The default implementation returns true if\n\
+@code{TARGET_ASM_JUMP_TABLE_BASE} is defined. Other duplication\n\
+restrictions, including @code{TARGET_CANNOT_COPY_INSN_P}, still apply.",
+ bool, (rtx_insn *insn),
+ default_can_share_jump_table_p)
+
/* Return true if the target supports conditional execution. */
DEFHOOK
(have_conditional_execution,
diff --git a/gcc/targhooks.cc b/gcc/targhooks.cc
index 388f696c8aa..c8f6742b24f 100644
--- a/gcc/targhooks.cc
+++ b/gcc/targhooks.cc
@@ -1948,6 +1948,14 @@ default_case_values_threshold (void)
return (targetm.have_casesi () ? 4 : 5);
}
+/* Return true if the target can emit a shared table for tablejump INSN. */
+
+bool
+default_can_share_jump_table_p (rtx_insn *)
+{
+ return targetm.asm_out.jump_table_base != NULL;
+}
+
bool
default_have_conditional_execution (void)
{
diff --git a/gcc/targhooks.h b/gcc/targhooks.h
index 86de6ef8a69..a7344874e58 100644
--- a/gcc/targhooks.h
+++ b/gcc/targhooks.h
@@ -220,6 +220,7 @@ extern addr_space_t
default_addr_space_for_artificial_rodata (tree,
artificial_rodata);
extern rtx default_addr_space_convert (rtx, tree, tree);
extern unsigned int default_case_values_threshold (void);
+extern bool default_can_share_jump_table_p (rtx_insn *);
extern bool default_have_conditional_execution (void);
extern bool default_have_ccmp (void);
--
2.50.1 (Apple Git-155)