https://gcc.gnu.org/g:eaf619a0f552d73068a06d959ea6a4b8a19ff674
commit r17-892-geaf619a0f552d73068a06d959ea6a4b8a19ff674 Author: Georg-Johann Lay <[email protected]> Date: Thu May 28 11:44:21 2026 +0200 AVR: Support [[len=<words]] notes in inline asm to specifty its size. This patch adds support for [[len=<words>]] in (the comments of) inline asm constructs. It serves several purposes: - Cases where the expanded asm is longer than determined from the number of physical and logical line breaks. Such cases can lead to errors when a jump that uses a too optimistic jump offset is crossing an asm. - Better code generation for jumps that are crossing an asm. The default length of an asm is (1 + NL) * 2 words, where NL denotes the sum of physical and logical line breaks. However, almost all AVR instructions occupy only one 16-bit word. The feature is implemented in ADJUST_INSN_LENGTH. The length of an asm is the sum over all [[len=<words>]] notes, except when an unrecognized construct is found or an error occurred. In the latter case, the default insn length is used. These <words> are supported: <words> = [0-9]+ Specifies a non-negative decimal integer. <words> = %[0-9]+ <words> = %[<name>] # Already resolved to %[0-9]+ by the middle-end. Refers to the respective asm operand, which must be CONST_INT. <words> = lds <words> = sts Specifies the length of a LDS or STS instruction, i.e. 1 word if AVR_TINY, and 2 words otherwise. <words> = %~ <words> = %~call <words> = %~jmp Specifies the length of a %~call resp. %~jmp instruction, i.e. 2 words if AVR_HAVE_JMP_CALL, and 1 word otherwise. In order to observe the assigned lengths, see -fdump-rtl-shorten or the ";; ADDR = ..." insn addresses in the asm output with -mlog=insn_addresses. The benefits of using magic comments are: - The feature is backwards compatible, and the target code can use the same asm syntax since only asm comments have to be adjusted. No #ifdef feature test macros are needed. The only case where the feature is not fully backwards compatible is when asm templates already contain invalid "[[len=" notes for some reason. In that case, -mno-asm-len-notes restores the old behavior. - Since the asm size is the sum over all notes, the final size can be stitched together from multiple annotations / parts of an asm template, and there is no need to support operations like plus. gcc/ * config/avr/avr.cc (avr_read_number, avr_length_of_asm) (avr_maybe_length_of_asm): New static functions. (avr_adjust_insn_length): Call avr_maybe_length_of_asm on unrecognized insns. * config/avr/avr.opt (-masm-len-notes, -Wasm-len-notes): New options. * doc/invoke.texi (AVR Options): Add -masm-len-notes, -Wasm-len-notes. * doc/extend.texi (Size of an asm): Add @subsubheading "Specifying the size of an asm on AVR". libgcc/config/avr/libf7/ * libf7.h: Add "[len=...]]" notes to all non-empty inline asm's. * libf7.c: Dito. Diff: --- gcc/config/avr/avr.cc | 247 +++++++++++++++++++++++++++++++++++++++- gcc/config/avr/avr.opt | 8 ++ gcc/doc/extend.texi | 68 +++++++++++ gcc/doc/invoke.texi | 24 +++- libgcc/config/avr/libf7/libf7.c | 13 ++- libgcc/config/avr/libf7/libf7.h | 14 +-- 6 files changed, 355 insertions(+), 19 deletions(-) diff --git a/gcc/config/avr/avr.cc b/gcc/config/avr/avr.cc index 25cb518b0f3e..c6443c03423a 100644 --- a/gcc/config/avr/avr.cc +++ b/gcc/config/avr/avr.cc @@ -11131,8 +11131,244 @@ avr_rotate_bytes (rtx operands[]) } +/* Read a non-negative decimal number from a string starting at START. + Set *END to one position past the last digit of the number. */ + +static int +avr_read_number (const char *start, const char **end) +{ + int num = 0; + for (*end = start; **end >= '0' && **end <= '9'; ++(*end)) + num = 10 * num + **end - '0'; + + return num; +} + + +/* Return the sum over all [[len=<words>]] annotations in inline asm INSN. + TPL is the asm template, N_XOP the number of operands, or -1 when the + asm has no operands (not even zero operands). XOP[] are these operands. + LOC is the location of the asm, and DEFAULT_LEN is the code length + in words as determined by the middle-end from the number of logical and + physical line breaks in TPL. + + When an invalid %-reference or an unrecognized [[len=... is found, + then return -1. Otherwise, return the sum over all [[len=<words>]] + annotations, where: + + <words> = [0-9]+ + Specifies a non-negative decimal integer. + + <words> = %[0-9]+ + <words> = %[<name>] # Already resolved to %[0-9]+ by the middle-end. + Refers to the respective asm operand, which must be CONST_INT. + + <words> = lds + <words> = sts + Specifies the length of a LDS or STS instruction, i.e. + 1 word if AVR_TINY, and 2 words otherwise. + + <words> = %~ + <words> = %~call + <words> = %~jmp + Specifies the length of a %~call resp. %~jmp instruction, i.e. + 2 words if AVR_HAVE_JMP_CALL, and 1 word otherwise. + + In order to observe the assigned lengths, see -fdump-rtl-shorten or the + asm output with -mlog=insn_addresses. */ + +static int +avr_length_of_asm (rtx_insn *insn, int default_len, const char *tpl, + int n_xop, rtx *xop, location_t loc) +{ + const char *const len_begin = "[[len="; + const char *const len_end = "]]"; + const char *pos = strstr (tpl, len_begin); + int len = 0; + + if (! pos) + return -1; + + avr_dump ("\n;;; Calculating length of asm insn (default=%d, warn=%d):\n" + "%r\n", default_len, avropt_warn_asmlen_notes, insn); + + for (; pos; pos = strstr (pos, len_begin)) + { + const char *const pos_begin = pos; + const char *const pos_end = strstr (pos, len_end); + + pos += strlen (len_begin); + const bool percent_p = pos[0] == '%' && pos[1] != '~'; + int inc = -1; + + if (startswith (pos, "%~") + // %-resolution only applies to asm with operands. + && n_xop >= 0) + { + // "%~" specifies the length of %~jmp / %~call. + inc = AVR_HAVE_JMP_CALL ? 2 : 1; + pos += 2; + + // Allow an optional suffix of "jmp" or "call". + if (startswith (pos, "jmp")) + pos += 3; + else if (startswith (pos, "call")) + pos += 4; + } + else if (startswith (pos, "lds") + || startswith (pos, "sts")) + { + // "lds" specifies the length of LDS or STS. + inc = AVR_TINY ? 1 : 2; + pos += 3; + } + else if (! percent_p + // %-resolution only applies to asm with operands. + || n_xop >= 0) + { + // A plain decimal number <num>, or %<num> to refer to the + // <num>-th asm operand. Notice that the middle-end has already + // resolved named operand references like %[name] to the respective + // operand number (provided such a named operand exists). + pos += percent_p; + const char *end; + const int num = avr_read_number (pos, &end); + inc = end > pos ? num : -1; + pos = end; + } + + /* Prepare a code quote so we have it handy in maybe diagnostics. */ + + // Only show the gist of an unrecognized annotation. + // Recognized notes are short, so show only the relevant + // part and display the rest as ...'s. + const char *dots = "..."; + const size_t l_pos_begin = strlen (pos_begin); + const size_t l_pos = pos_end ? pos_end - pos_begin : l_pos_begin; + const size_t l_show = pos - pos_begin + 5; + char *msg = XALLOCAVEC (char, 1 + strlen (tpl) + strlen (dots)); + strcpy (msg, pos_begin); + if (l_pos > l_show + 2 * strlen (dots)) + { + msg[l_show] = '\0'; + strcat (msg, dots); + if (pos_end) + strncat (msg, pos_end - 4, 4); + } + if (pos_end) + strcat (msg, len_end); + + /* Now use inc or diagnose a problem. */ + + if (inc >= 0 && pos == pos_end) + { + pos += strlen (len_end); + + avr_dump (";;; len = %s + %s%d", + len ? "len" : "0", percent_p ? "%" : "", inc); + if (percent_p) + { + // Map inc to the value of the inc-th asm operand, + // which must be CONST_INT. + + if (inc >= n_xop) + { + // For asm with >= 0 operands, the middle-end will issue + // an error message. + avr_dump ("\n;;; Ignored: op %d does not exist\n\n", inc); + return -1; + } + if (! CONST_INT_P (xop[inc])) + { + avr_dump ("\n;;; Ignored: op %d is not CONST_INT\n\n", inc); + warning_at (loc, OPT_Wasm_len_notes, "%<asm%> len annotation" + " ignored: operand %d is not a compile-time" + " integer constant: %qs", inc, msg); + return -1; + } + inc = UINTVAL (xop[inc]) > 1000000 ? 1000000 : INTVAL (xop[inc]); + avr_dump (" = %s + %d", len ? "len" : "0", inc); + } // % + + len += inc; + avr_dump (" = %d\n", len); + } + else // inc < 0 || pos != pos_end + { + // FIXME: Diagnosing is void in LTO mode, except when compiled + // with `-ffat-lto-objects' (in which case cc1[plus] is diagnosing, + // not lto1). + if (pos_end) + { + avr_dump (";;; Ignored: %s\n\n", msg); + warning_at (loc, OPT_Wasm_len_notes, "%<asm%> len annotation" + " ignored: %qs", msg); + } + else + { + avr_dump (";;; Ignored (no %s): %s\n\n", len_end, msg); + warning_at (loc, OPT_Wasm_len_notes, "%<asm%> len annotation" + " ignored: misses closing %qs: %qs", len_end, msg); + } + return -1; + } + } + avr_dump ("\n"); + + return len; +} + + +/* A helper for the function below. Recognize [[len=<words>]] annotations + in the inline asm template of INSN, so the user can specify the length + of an asm. This can solve two problems: + + - Cases where the expanded asm is longer than determined from the number + of physical and logical line breaks. Such cases can lead to errors + when a jump that uses a too optimistic jump offset is crossing an asm. + + - Better code generation for jumps that are crossing an asm. The default + length of an asm is (1 + NL) * 2 words, where NL denotes the sum of + physical and logical line breaks. However, almost all AVR instructions + occupy only one 16-bit word. + + LEN is the length in units of 16-bit words as determined by the middle-end. + When no annotation has been found or a diagnostic occurred, return -1. */ + +static int +avr_maybe_length_of_asm (rtx_insn *insn, int len) +{ + if (avropt_asmlen_notes + && NONDEBUG_INSN_P (insn)) + { + rtx body = PATTERN (insn); + + if (asm_noperands (body) >= 0) + { + // An `asm' construct with operands. + + location_t loc; + int n_ops = asm_noperands (body); + rtx *ops = XALLOCAVEC (rtx, n_ops); + const char *tpl = decode_asm_operands (body, ops, NULL, NULL, NULL, + &loc); + return avr_length_of_asm (insn, len, tpl, n_ops, ops, loc); + } + else if (GET_CODE (body) == ASM_INPUT) + { + // An `asm' construct without operands. + + return avr_length_of_asm (insn, len, XSTR (body, 0), -1, nullptr, + ASM_INPUT_SOURCE_LOCATION (body)); + } + } + + return -1; +} + + /* Worker function for `ADJUST_INSN_LENGTH'. */ -/* Modifies the length assigned to instruction INSN +/* Modifies the length assigned to instruction INSN. LEN is the initially computed length of the insn. */ int @@ -11151,9 +11387,14 @@ avr_adjust_insn_length (rtx_insn *insn, int len) It is easier to state this in an insn attribute "adjust_len" than to clutter up code here... */ - if (!NONDEBUG_INSN_P (insn) || recog_memoized (insn) == -1) + if (!NONDEBUG_INSN_P (insn)) + return len; + + if (recog_memoized (insn) == -1) { - return len; + // Let the user specify the length of the asm with [[len=<words>]]'s. + const int len_asm = avr_maybe_length_of_asm (insn, len); + return len_asm < 0 ? len : len_asm; } /* Read from insn attribute "adjust_len" if/how length is to be adjusted. */ diff --git a/gcc/config/avr/avr.opt b/gcc/config/avr/avr.opt index 8bc2f5f1739a..97d207726ba9 100644 --- a/gcc/config/avr/avr.opt +++ b/gcc/config/avr/avr.opt @@ -140,6 +140,14 @@ mfuse-add= Target Joined RejectNegative UInteger Var(avropt_fuse_add) Init(0) Optimization IntegerRange(0, 3) -mfuse-add=<0,2> Optimization. Split register additions from load/store instructions. Most useful on Reduced Tiny. +masm-len-notes +Target Var(avropt_asmlen_notes) Init(1) Save +Recognize [[len=<spec>]] notes to specify the size of inline asm constructs. Enabled by default. + +Wasm-len-notes +Warning C C++ Var(avropt_warn_asmlen_notes) Init(1) Save +Warn about unrecognized [[len=<spec>]] notes in inline asm constructs. Enabled by default. + Waddr-space-convert Warning C Var(avropt_warn_addr_space_convert) Init(0) Warn if the address space of an address is changed. diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index e14d033267dd..d9a6594d72a1 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -13464,6 +13464,74 @@ This size is also used for inlining decisions. If you use @code{asm inline} instead of just @code{asm}, then for inlining purposes the size of the asm is taken as the minimum size, ignoring how many instructions GCC thinks it is. +@subsubheading Specifying the size of an @code{asm} on AVR + +Apart from the default size calculation explained above, since GCC@tie{}17 +there is a way to specify the exact size of an @code{asm} by means of +@code{[[len=@var{spec}]]} annotations. The annotations must be placed +in assembly comments in the @code{asm} template: +In a @code{/* ... */} block comment, or after a @samp{;} that starts +a single-line comment. + +The following @code{@var{spec}}'s are supported. They specify the +size of the @code{asm} --- or parts of it --- in units of 16-bit words: + +@table @code +@item [0-9]+ +The decimal representation of a non-negative integer. + +@item %[0-9]+ +@itemx %[@var{name}] +The value of the referred operand, which must evaluate to a +compile-time integer constant. + +@item %~ +@itemx %~jmp +@itemx %~call +The length of a @code{%~call} or @code{%~jmp} instruction before relaxing. + +@item lds +@itemx sts +The length of a @code{lds} resp.@: @code{sts} instruction. +@end table + +The following rules apply: + +@itemize +@item All such annotations will be ignored with option +@ref{avr_masm_len_notes,@option{-mno-asm-len-notes}}. Diagnosing can be +adjusted with option @ref{avr_Wasm_len_notes,@option{-Wasm-len-notes}}. + +@item The length of the @code{asm} is the sum over all +@code{[[len=@var{spec}]]} notes in the @code{asm} template. When an invalid +or unrecognizable note is found, then the default length (as calculated from +the number of physical and logical line breaks) applies. + +@item No white-spaces are recognized inside @code{[[...]]} notes, and all +letters must be lower case. Otherwise, all notes in that asm are ignored, +and the default length of the @code{asm} is used. +@end itemize + +The actual instruction lengths can be inferred from @samp{;; ADDR = ...} +assembly comments as generated with, say +@option{-save-temps -mlog=insn_addresses}. +Length calculations are also shown in the RTL dump file(s) as generated with +@option{-fdump-rtl-shorten}. + +Lengths annotations can solve two issues: + +@itemize +@item Cases where the expanded assembly code is longer than determined from the +number of physical and logical line breaks. Such cases can lead to errors when +a jump that uses a too optimistic jump offset is crossing such assembly code. + +@item Better code generation for jumps that are crossing an @code{asm}. +The default length of an asm is (1 + @var{NL}) * 2 words, where @var{NL} +denotes the sum of physical and logical line breaks. However, almost +all AVR instructions occupy only a single 16-bit word. +@end itemize + + @node Syntax Extensions @section Other Extensions to C Syntax diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index b605defb3907..8ed1c851f598 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -984,8 +984,8 @@ Objective-C and Objective-C++ Dialects}. -mvectorize-with-neon-quad -mvectorize-with-neon-double} @emph{AVR Options} (@ref{AVR Options}) -@gccoptlist{-mmcu=@var{mcu} -mabsdata -maccumulate-args -mcvt --mbranch-cost=@var{cost} -mfuse-add=@var{level} -mfuse-move=@var{level} +@gccoptlist{-mmcu=@var{mcu} -mabsdata -maccumulate-args -masm-len-notes +-mcvt -mbranch-cost=@var{cost} -mfuse-add=@var{level} -mfuse-move=@var{level} -mfuse-move2 -mcall-prologues -mgas-isr-prologues -mint8 -mflmap -mdouble=@var{bits} -mlong-double=@var{bits} -mno-call-main -mn_flash=@var{size} -mfract-convert-truncate -mno-interrupts @@ -993,7 +993,7 @@ Objective-C and Objective-C++ Dialects}. -mrmw -mstrict-X -mtiny-stack -mrodata-in-ram -msplit-bit-shift -msplit-ldst -mshort-calls -mskip-bug -muse-nonzero-bits -nodevicelib -nodevicespecs --Waddr-space-convert -Wmisspelled-isr} +-Wasm-len-notes -Waddr-space-convert -Wmisspelled-isr} @emph{Blackfin Options} (@ref{Blackfin Options}) @gccoptlist{-mcpu=@var{cpu}@r{[}-@var{sirevision}@r{]} @@ -24314,6 +24314,15 @@ instructions. This option has only an effect on reduced Tiny devices like ATtiny40. See also the @code{absdata} @ref{AVR Attributes,variable attribute}. +@anchor{avr_masm_len_notes} +@opindex masm-len-notes +@item -masm-len-notes +@itemx -mno-asm-len-notes +Recognize resp.@: ignore @code{[[len=@var{spec}]]} notes that are placed in +(comments in) inline assembly code templates. They allow to specify +the exact @ref{Size of an asm,size of @code{asm} constructs}. +This option is on per default. + @opindex mcvt @item -mcvt Use a @emph{compact vector table}. Some devices support a CVT @@ -24468,6 +24477,15 @@ which contains a folder named @code{device-specs} which contains a specs file na Warn about conversions between address spaces in the case where the resulting address space is not contained in the incoming address space. +@anchor{avr_Wasm_len_notes} +@opindex Wasm-len-notes +@item -Wasm-len-notes +Warn about unrecognized @code{[[len=@var{spec}]]} +@ref{Size of an asm,length} annotations in inline assembly code templates. +This @ref{Warning Options,warning} is on per default. +Notice that in LTO mode, the code must be compiled with option +@option{-ffat-lto-objects} in order for these diagnoses to appear. + @opindex Wmisspelled-isr @opindex Wno-misspelled-isr @item -Wmisspelled-isr diff --git a/libgcc/config/avr/libf7/libf7.c b/libgcc/config/avr/libf7/libf7.c index a672e424dc8c..d258f2b5ab25 100644 --- a/libgcc/config/avr/libf7/libf7.c +++ b/libgcc/config/avr/libf7/libf7.c @@ -344,7 +344,7 @@ float f7_get_float (const f7_t *aa) __asm ("cbr %T0%t2, 1 << (7 & %2)" "\n\t" "or %C0, %A1" "\n\t" - "or %D0, %B1" + "or %D0, %B1 ; [[len=3]]" : "+d" (mant) : "r" (expo16), "n" (FLT_DIG_MANT)); @@ -410,7 +410,8 @@ uint64_t clr_r18 (void) { extern void __clr_8 (void); register uint64_t r18 __asm ("r18"); - __asm ("%~call %x[f]" : "=r" (r18) : [f] "i" (__clr_8)); + __asm ("%~call %x[f] ; [[len=%~call]]" + : "=r" (r18) : [f] "i" (__clr_8)); return r18; } @@ -459,7 +460,7 @@ f7_double_t f7_get_double (const f7_t *aa) // dex = Overflow ? 1 : 0. __asm ("bst %T[mant]%T[bitno]" "\n\t" "clr %0" "\n\t" - "bld %0,0" + "bld %0,0 ; [[len=3]]" : "=r" (dex), [mant] "+r" (r18) : [bitno] "n" (64 - 8)); @@ -504,7 +505,7 @@ f7_double_t f7_get_double (const f7_t *aa) r18 = mant; __asm ("cbr %T0%t2, 1 << (7 & %2)" "\n\t" "or %r0+6, %A1" "\n\t" - "or %r0+7, %B1" + "or %r0+7, %B1 ; [[len=3]]" : "+r" (r18) : "r" (expo16), "n" (DBL_DIG_MANT)); @@ -672,7 +673,7 @@ int8_t cmp_u8 (uint8_t a_class, uint8_t b_class, bool sign_a) "sbci %[c], -1" "\n\t" "sbrc %[s], 0" "\n\t" "neg %[c]" "\n\t" - "1:" + "1: ; [[len=6]]" : [c] "=d" (c) : [a] "0" (a_class), [b] "r" (b_class), [s] "r" (sign_a)); return c; @@ -944,7 +945,7 @@ void f7_fdim (f7_t *cc, const f7_t *aa, const f7_t *bb) #ifdef F7MOD_addsub_ static void return_with_sign (f7_t *cc, const f7_t *aa, int8_t c_sign) { - __asm (";;; return with sign"); + __asm (";;; return with sign ; [[len=0]]"); f7_copy (cc, aa); if (c_sign != -1) f7_set_sign (cc, c_sign); diff --git a/libgcc/config/avr/libf7/libf7.h b/libgcc/config/avr/libf7/libf7.h index fc9bcafe978c..825cb68142b9 100644 --- a/libgcc/config/avr/libf7/libf7.h +++ b/libgcc/config/avr/libf7/libf7.h @@ -159,7 +159,7 @@ uint8_t f7_classify (const f7_t *aa) { extern void f7_classify_asm (void); register uint8_t rclass __asm ("r24"); - __asm ("%~call %x[f]" + __asm ("%~call %x[f] ; [[len=%~call]]" : "=r" (rclass) : [f] "i" (f7_classify_asm), "z" (aa)); return rclass; @@ -268,7 +268,7 @@ static F7_INLINE void f7_clr (f7_t *cc) { extern void f7_clr_asm (void); - __asm ("%~call %x[f]" + __asm ("%~call %x[f] ; [[len=%~call]]" : : [f] "i" (f7_clr_asm), "z" (cc) : "memory"); @@ -278,7 +278,7 @@ static F7_INLINE f7_t* f7_copy (f7_t *cc, const f7_t *aa) { extern void f7_copy_asm (void); - __asm ("%~call %x[f]" + __asm ("%~call %x[f] ; [[len=%~call]]" : : [f] "i" (f7_copy_asm), "z" (cc), "x" (aa) : "memory"); @@ -289,7 +289,7 @@ static F7_INLINE f7_t* f7_copy_P (f7_t *cc, const f7_t *aa) { extern void f7_copy_P_asm (void); - __asm ("%~call %x[f]" + __asm ("%~call %x[f] ; [[len=%~call]]" : : [f] "i" (f7_copy_P_asm), "x" (cc), "z" (aa) : "memory"); @@ -300,7 +300,7 @@ static F7_INLINE void f7_copy_mant (f7_t *cc, const f7_t *aa) { extern void f7_copy_mant_asm (void); - __asm ("%~call %x[f]" + __asm ("%~call %x[f] ; [[len=%~call]]" : : [f] "i" (f7_copy_mant_asm), "z" (cc), "x" (aa) : "memory"); @@ -337,7 +337,7 @@ int8_t f7_cmp_mant (const f7_t *aa, const f7_t *bb) { extern void f7_cmp_mant_asm (void); register int8_t r24 __asm ("r24"); - __asm ("%~call %x[f] ;; %1 %3" + __asm ("%~call %x[f] ;; %1 %3 ; [[len=%~call]]" : "=r" (r24) : [f] "i" (f7_cmp_mant_asm), "x" (aa), "z" (bb)); return r24; @@ -349,7 +349,7 @@ bool f7_store_expo (f7_t *cc, int16_t expo) extern void f7_store_expo_asm (void); register bool r24 __asm ("r24"); register int16_t rexpo __asm ("r24") = expo; - __asm ("%~call %x[f] ;; %0 %2 %3" + __asm ("%~call %x[f] ;; %0 %2 %3 ; [[len=%~call]]" : "=r" (r24) : [f] "i" (f7_store_expo_asm), "z" (cc), "r" (rexpo)); return r24;
