From e13bab5a71e5c7274ffbe699673c38744940fe01 Mon Sep 17 00:00:00 2001 From: Alan Modra Date: Sat, 13 Dec 2003 08:23:05 +0000 Subject: [PATCH] * read.c: Remove unneeded prototypes. (s_comm): Split out code to.. (s_comm_internal): ..here. Tidy error returns. Rearrange so that "name" from input line may be used in more places. Merge code testing for valid size from elf_common. Merge code from s_lcomm_internal. Call comm_parse_extra. (bss_alloc): New function, split out of s_lcomm_internal and elf_common. (parse_align): Likewise. (s_lcomm_internal): Rewrite. (s_lcomm, s_lcomm_bytes): Use s_comm_internal. * read.h (bss_alloc, parse_align, s_comm_internal): Declare. * config/obj-elf.c (elf_common): Split out code to.. (elf_common_parse): ..here. Remove code common to s_comm_internal, parse_align and bss_alloc. Rearrange and Tidy. * config/tc-alpha.h (TC_IMPLICIT_LCOMM_ALIGNMENT): Define. --- gas/ChangeLog | 19 +++ gas/config/obj-elf.c | 202 +++++++---------------- gas/config/tc-alpha.h | 15 ++ gas/read.c | 365 +++++++++++++++++------------------------- gas/read.h | 3 + 5 files changed, 236 insertions(+), 368 deletions(-) diff --git a/gas/ChangeLog b/gas/ChangeLog index 677e0d01e4e..b4c156ae413 100644 --- a/gas/ChangeLog +++ b/gas/ChangeLog @@ -1,3 +1,22 @@ +2003-12-13 Alan Modra + + * read.c: Remove unneeded prototypes. + (s_comm): Split out code to.. + (s_comm_internal): ..here. Tidy error returns. Rearrange so that + "name" from input line may be used in more places. Merge code + testing for valid size from elf_common. Merge code from + s_lcomm_internal. Call comm_parse_extra. + (bss_alloc): New function, split out of s_lcomm_internal and + elf_common. + (parse_align): Likewise. + (s_lcomm_internal): Rewrite. + (s_lcomm, s_lcomm_bytes): Use s_comm_internal. + * read.h (bss_alloc, parse_align, s_comm_internal): Declare. + * config/obj-elf.c (elf_common): Split out code to.. + (elf_common_parse): ..here. Remove code common to s_comm_internal, + parse_align and bss_alloc. Rearrange and Tidy. + * config/tc-alpha.h (TC_IMPLICIT_LCOMM_ALIGNMENT): Define. + 2003-12-10 Zack Weinberg * tc-ppc.c (md_assemble): Rewrite comment about optional operands diff --git a/gas/config/obj-elf.c b/gas/config/obj-elf.c index 697dd4fc1d0..dd33ec444d3 100644 --- a/gas/config/obj-elf.c +++ b/gas/config/obj-elf.c @@ -63,7 +63,6 @@ static void adjust_stab_sections PARAMS ((bfd *, asection *, PTR)); static void build_group_lists PARAMS ((bfd *, asection *, PTR)); static int elf_separate_stab_sections PARAMS ((void)); static void elf_init_stab_section PARAMS ((segT)); -static symbolS *elf_common PARAMS ((int)); #ifdef NEED_ECOFF_DEBUG static bfd_boolean elf_get_extr PARAMS ((asymbol *, EXTR *)); @@ -287,185 +286,96 @@ elf_file_symbol (s) #endif } +/* Called from read.c:s_comm after we've parsed .comm symbol, size. + Parse a possible alignment value. */ + static symbolS * -elf_common (is_common) - int is_common; +elf_common_parse (int ignore ATTRIBUTE_UNUSED, symbolS *symbolP, addressT size) { - char *name; - char c; - char *p; - offsetT temp, size, sign; - symbolS *symbolP; - int have_align; - expressionS exp; + addressT align = 0; + int is_local = symbol_get_obj (symbolP)->local; - if (flag_mri && is_common) + if (*input_line_pointer == ',') { - s_mri_common (0); - return NULL; - } + char *save = input_line_pointer; - name = input_line_pointer; - c = get_symbol_end (); - /* just after name is now '\0' */ - p = input_line_pointer; - *p = c; - SKIP_WHITESPACE (); - if (*input_line_pointer != ',') - { - as_bad (_("expected comma after symbol-name")); - ignore_rest_of_line (); - return NULL; - } - input_line_pointer++; /* skip ',' */ - temp = get_absolute_expr (&exp); - sign = (offsetT) 1 << (stdoutput->arch_info->bits_per_address - 1); - size = temp & ((sign << 1) - 1); - if (temp != size || !exp.X_unsigned) - { - as_bad (_(".COMMon length (%ld) out of range, ignored."), (long) temp); - ignore_rest_of_line (); - return NULL; - } - *p = 0; - symbolP = symbol_find_or_make (name); - *p = c; - if (S_IS_DEFINED (symbolP) && ! S_IS_COMMON (symbolP)) - { - as_bad (_("symbol `%s' is already defined"), S_GET_NAME (symbolP)); - ignore_rest_of_line (); - return NULL; - } - if (S_GET_VALUE (symbolP) != 0) - { - if (S_GET_VALUE (symbolP) != (valueT) size) - { - as_warn (_("length of .comm \"%s\" is already %ld; not changed to %ld"), - S_GET_NAME (symbolP), (long) S_GET_VALUE (symbolP), - (long) size); - } - } - know (symbolP->sy_frag == &zero_address_frag); - if (*input_line_pointer != ',') - have_align = 0; - else - { - have_align = 1; input_line_pointer++; SKIP_WHITESPACE (); - } - if (! have_align || *input_line_pointer != '"') - { - if (! have_align) - temp = 0; - else - { - temp = get_absolute_expr (&exp); - if (!exp.X_unsigned) - { - temp = 0; - as_warn (_("common alignment negative; 0 assumed")); - } - } - if (symbol_get_obj (symbolP)->local) + + if (*input_line_pointer == '"') { - segT old_sec; - int old_subsec; - char *pfrag; - int align; - - /* allocate_bss: */ - old_sec = now_seg; - old_subsec = now_subseg; - if (temp) + /* For sparc. Accept .common symbol, length, "bss" */ + input_line_pointer++; + /* Some use the dot, some don't. */ + if (*input_line_pointer == '.') + input_line_pointer++; + /* Some say data, some say bss. */ + if (strncmp (input_line_pointer, "bss\"", 4) == 0) + input_line_pointer += 4; + else if (strncmp (input_line_pointer, "data\"", 5) == 0) + input_line_pointer += 5; + else { - /* convert to a power of 2 alignment */ - for (align = 0; (temp & 1) == 0; temp >>= 1, ++align); - if (temp != 1) - { - as_bad (_("common alignment not a power of 2")); - ignore_rest_of_line (); - return NULL; - } + char *p = input_line_pointer; + char c; + + while (*--p != '"') + ; + while (!is_end_of_line[(unsigned char) *input_line_pointer]) + if (*input_line_pointer++ == '"') + break; + c = *input_line_pointer; + *input_line_pointer = '\0'; + as_bad (_("bad .common segment %s"), p); + *input_line_pointer = c; + ignore_rest_of_line (); + return NULL; } - else - align = 0; - record_alignment (bss_section, align); - subseg_set (bss_section, 0); - if (align) - frag_align (align, 0, 0); - if (S_GET_SEGMENT (symbolP) == bss_section) - symbol_get_frag (symbolP)->fr_symbol = 0; - symbol_set_frag (symbolP, frag_now); - pfrag = frag_var (rs_org, 1, 1, (relax_substateT) 0, symbolP, - (offsetT) size, (char *) 0); - *pfrag = 0; - S_SET_SIZE (symbolP, size); - S_SET_SEGMENT (symbolP, bss_section); - S_CLEAR_EXTERNAL (symbolP); - subseg_set (old_sec, old_subsec); + /* ??? Don't ask me why these are always global. */ + is_local = 0; } else { - allocate_common: - S_SET_VALUE (symbolP, (valueT) size); - S_SET_ALIGN (symbolP, temp); - S_SET_EXTERNAL (symbolP); - S_SET_SEGMENT (symbolP, bfd_com_section_ptr); + input_line_pointer = save; + align = parse_align (is_local); + if (align == (addressT) -1) + return NULL; } } + + if (is_local) + { + bss_alloc (symbolP, size, align); + S_CLEAR_EXTERNAL (symbolP); + } else { - input_line_pointer++; - /* @@ Some use the dot, some don't. Can we get some consistency?? */ - if (*input_line_pointer == '.') - input_line_pointer++; - /* @@ Some say data, some say bss. */ - if (strncmp (input_line_pointer, "bss\"", 4) - && strncmp (input_line_pointer, "data\"", 5)) - { - while (*--input_line_pointer != '"') - ; - input_line_pointer--; - goto bad_common_segment; - } - while (*input_line_pointer++ != '"') - ; - goto allocate_common; + S_SET_VALUE (symbolP, size); + S_SET_ALIGN (symbolP, align); + S_SET_EXTERNAL (symbolP); + S_SET_SEGMENT (symbolP, bfd_com_section_ptr); } symbol_get_bfdsym (symbolP)->flags |= BSF_OBJECT; - demand_empty_rest_of_line (); return symbolP; - - { - bad_common_segment: - p = input_line_pointer; - while (*p && *p != '\n') - p++; - c = *p; - *p = '\0'; - as_bad (_("bad .common segment %s"), input_line_pointer + 1); - *p = c; - input_line_pointer = p; - ignore_rest_of_line (); - return NULL; - } } void obj_elf_common (is_common) int is_common; { - elf_common (is_common); + if (flag_mri && is_common) + s_mri_common (0); + else + s_comm_internal (0, elf_common_parse); } static void obj_elf_tls_common (ignore) int ignore ATTRIBUTE_UNUSED; { - symbolS *symbolP = elf_common (0); + symbolS *symbolP = s_comm_internal (0, elf_common_parse); if (symbolP) symbol_get_bfdsym (symbolP)->flags |= BSF_THREAD_LOCAL; diff --git a/gas/config/tc-alpha.h b/gas/config/tc-alpha.h index 52876866c87..939a14f296e 100644 --- a/gas/config/tc-alpha.h +++ b/gas/config/tc-alpha.h @@ -80,6 +80,21 @@ extern valueT alpha_gp_value; : BFD_RELOC_ALPHA_LINKAGE); #endif +#ifndef VMS +#define TC_IMPLICIT_LCOMM_ALIGNMENT(size, align) \ + do \ + { \ + align = 0; \ + if (size > 1) \ + { \ + addressT temp = 1; \ + while ((size & temp) == 0) \ + ++align, temp <<= 1; \ + } \ + } \ + while (0) +#endif + #define md_number_to_chars number_to_chars_littleendian extern int tc_get_register PARAMS ((int frame)); diff --git a/gas/read.c b/gas/read.c index 186d28b70c9..55a68bea7f3 100644 --- a/gas/read.c +++ b/gas/read.c @@ -214,22 +214,9 @@ static int dwarf_file_string; #endif #endif -static void cons_worker (int, int); -static int scrub_from_string (char *, int); static void do_align (int, char *, int, int); static void s_align (int, int); -static void s_lcomm_internal (int, int); static int hex_float (int, char *); -static inline int sizeof_sleb128 (offsetT); -static inline int sizeof_uleb128 (valueT); -static inline int output_sleb128 (char *, offsetT); -static inline int output_uleb128 (char *, valueT); -static inline int output_big_sleb128 (char *, LITTLENUM_TYPE *, int); -static inline int output_big_uleb128 (char *, LITTLENUM_TYPE *, int); -static int output_big_leb128 (char *, LITTLENUM_TYPE *, int, int); -static void do_org (segT, expressionS *, int); -char *demand_copy_string (int *lenP); -static segT get_segmented_expression (expressionS *expP); static segT get_known_segmented_expression (expressionS * expP); static void pobegin (void); static int get_line_sb (sb *); @@ -1328,16 +1315,18 @@ s_align_ptwo (int arg) s_align (arg, 0); } -void -s_comm (int ignore ATTRIBUTE_UNUSED) +symbolS * +s_comm_internal (int param, + symbolS *(*comm_parse_extra) (int, symbolS *, addressT)) { - register char *name; - register char c; - register char *p; - offsetT temp; - register symbolS *symbolP; + char *name; + char c; + char *p; + offsetT temp, size; + symbolS *symbolP = NULL; char *stop = NULL; char stopc; + expressionS exp; if (flag_mri) stop = mri_comment_field (&stopc); @@ -1352,75 +1341,83 @@ s_comm (int ignore ATTRIBUTE_UNUSED) { as_bad (_("expected symbol name")); discard_rest_of_line (); - return; + goto out; } SKIP_WHITESPACE (); - if (*input_line_pointer != ',') + /* Accept an optional comma after the name. The comma used to be + required, but Irix 5 cc does not generate it for .lcomm. */ + if (*input_line_pointer == ',') + input_line_pointer++; + + *p = 0; + temp = get_absolute_expr (&exp); + size = temp; +#ifdef BFD_ASSEMBLER + size &= ((offsetT) 2 << (stdoutput->arch_info->bits_per_address - 1)) - 1; +#endif + if (exp.X_op == O_absent) { - *p = 0; - as_bad (_("expected comma after \"%s\""), name); + as_bad (_("missing size expression")); *p = c; ignore_rest_of_line (); - if (flag_mri) - mri_comment_end (stop, stopc); - return; + goto out; } - - input_line_pointer++; /* skip ',' */ - - if ((temp = get_absolute_expression ()) < 0) + else if (temp != size || !exp.X_unsigned) { - as_warn (_(".COMMon length (%lu) out of range ignored"), - (unsigned long) temp); + as_warn (_("size (%ld) out of range, ignored"), (long) temp); + *p = c; ignore_rest_of_line (); - if (flag_mri) - mri_comment_end (stop, stopc); - return; + goto out; } - *p = 0; symbolP = symbol_find_or_make (name); - *p = c; - if (S_IS_DEFINED (symbolP) && !S_IS_COMMON (symbolP)) { - as_bad (_("symbol `%s' is already defined"), - S_GET_NAME (symbolP)); + symbolP = NULL; + as_bad (_("symbol `%s' is already defined"), name); + *p = c; ignore_rest_of_line (); - if (flag_mri) - mri_comment_end (stop, stopc); - return; + goto out; } - if (S_GET_VALUE (symbolP)) - { - if (S_GET_VALUE (symbolP) != (valueT) temp) - as_bad (_("length of .comm \"%s\" is already %ld; not changing to %ld"), - S_GET_NAME (symbolP), - (long) S_GET_VALUE (symbolP), - (long) temp); - } + size = S_GET_VALUE (symbolP); + if (size == 0) + size = temp; + else if (size != temp) + as_warn (_("size of \"%s\" is already %ld; not changing to %ld"), + name, (long) size, (long) temp); + + *p = c; + if (comm_parse_extra != NULL) + symbolP = (*comm_parse_extra) (param, symbolP, size); else { - S_SET_VALUE (symbolP, (valueT) temp); + S_SET_VALUE (symbolP, (valueT) size); S_SET_EXTERNAL (symbolP); - } #ifdef OBJ_VMS - { - extern int flag_one; - if (!temp || !flag_one) - S_GET_OTHER(symbolP) = const_flag; - } -#endif /* not OBJ_VMS */ - know (symbolP->sy_frag == &zero_address_frag); + { + extern int flag_one; + if (size == 0 || !flag_one) + S_GET_OTHER (symbolP) = const_flag; + } +#endif + } + know (symbolP == NULL || symbolP->sy_frag == &zero_address_frag); demand_empty_rest_of_line (); - + out: if (flag_mri) mri_comment_end (stop, stopc); -} /* s_comm() */ + return symbolP; +} + +void +s_comm (int ignore) +{ + s_comm_internal (ignore, NULL); +} /* The MRI COMMON pseudo-op. We handle this by creating a common symbol with the appropriate name. We make s_space do the right @@ -1917,67 +1914,20 @@ s_linkonce (int ignore ATTRIBUTE_UNUSED) demand_empty_rest_of_line (); } -static void -s_lcomm_internal (/* 1 if this was a ".bss" directive, which may - require a 3rd argument (alignment); 0 if it was - an ".lcomm" (2 args only). */ - int needs_align, - /* 1 if the alignment value should be interpreted as - the byte boundary, rather than the power of 2. */ - int bytes_p) +void +bss_alloc (symbolS *symbolP, addressT size, int align) { - register char *name; - register char c; - register char *p; - register int temp; - register symbolS *symbolP; + char *pfrag; segT current_seg = now_seg; subsegT current_subseg = now_subseg; - const int max_alignment = 15; - int align = 0; segT bss_seg = bss_section; - name = input_line_pointer; - c = get_symbol_end (); - p = input_line_pointer; - *p = c; - - if (name == p) - { - as_bad (_("expected symbol name")); - discard_rest_of_line (); - return; - } - - SKIP_WHITESPACE (); - - /* Accept an optional comma after the name. The comma used to be - required, but Irix 5 cc does not generate it. */ - if (*input_line_pointer == ',') - { - ++input_line_pointer; - SKIP_WHITESPACE (); - } - - if (is_end_of_line[(unsigned char) *input_line_pointer]) - { - as_bad (_("missing size expression")); - return; - } - - if ((temp = get_absolute_expression ()) < 0) - { - as_warn (_("BSS length (%d) < 0 ignored"), temp); - ignore_rest_of_line (); - return; - } - #if defined (TC_MIPS) || defined (TC_ALPHA) if (OUTPUT_FLAVOR == bfd_target_ecoff_flavour || OUTPUT_FLAVOR == bfd_target_elf_flavour) { /* For MIPS and Alpha ECOFF or ELF, small objects are put in .sbss. */ - if ((unsigned) temp <= bfd_get_gp_size (stdoutput)) + if (size <= bfd_get_gp_size (stdoutput)) { bss_seg = subseg_new (".sbss", 1); seg_info (bss_seg)->bss = 1; @@ -1989,147 +1939,118 @@ s_lcomm_internal (/* 1 if this was a ".bss" directive, which may } } #endif + subseg_set (bss_seg, 1); - if (!needs_align) + if (align) { - TC_IMPLICIT_LCOMM_ALIGNMENT (temp, align); - - /* Still zero unless TC_IMPLICIT_LCOMM_ALIGNMENT set it. */ - if (align) - record_alignment (bss_seg, align); + record_alignment (bss_seg, align); + frag_align (align, 0, 0); } - if (needs_align) - { - align = 0; - SKIP_WHITESPACE (); + /* Detach from old frag. */ + if (S_GET_SEGMENT (symbolP) == bss_seg) + symbol_get_frag (symbolP)->fr_symbol = NULL; - if (*input_line_pointer != ',') - { - as_bad (_("expected comma after size")); - ignore_rest_of_line (); - return; - } + symbol_set_frag (symbolP, frag_now); + pfrag = frag_var (rs_org, 1, 1, 0, symbolP, size, NULL); + *pfrag = 0; - input_line_pointer++; - SKIP_WHITESPACE (); +#ifdef S_SET_SIZE + S_SET_SIZE (symbolP, size); +#endif + S_SET_SEGMENT (symbolP, bss_seg); - if (is_end_of_line[(unsigned char) *input_line_pointer]) - { - as_bad (_("missing alignment")); - return; - } +#ifdef OBJ_COFF + /* The symbol may already have been created with a preceding + ".globl" directive -- be careful not to step on storage class + in that case. Otherwise, set it to static. */ + if (S_GET_STORAGE_CLASS (symbolP) != C_EXT) + S_SET_STORAGE_CLASS (symbolP, C_STAT); +#endif /* OBJ_COFF */ - align = get_absolute_expression (); + subseg_set (current_seg, current_subseg); +} - if (bytes_p) - { - /* Convert to a power of 2. */ - if (align != 0) - { - unsigned int i; +offsetT +parse_align (int align_bytes) +{ + expressionS exp; + addressT align; - for (i = 0; (align & 1) == 0; align >>= 1, ++i) - ; - if (align != 1) - as_bad (_("alignment not a power of 2")); - align = i; - } - } + SKIP_WHITESPACE (); + if (*input_line_pointer != ',') + { + no_align: + as_bad (_("expected alignment after size")); + ignore_rest_of_line (); + return -1; + } - if (align > max_alignment) - { - align = max_alignment; - as_warn (_("alignment too large; %d assumed"), align); - } - else if (align < 0) - { - align = 0; - as_warn (_("alignment negative; 0 assumed")); - } + input_line_pointer++; + SKIP_WHITESPACE (); - record_alignment (bss_seg, align); + align = get_absolute_expr (&exp); + if (exp.X_op == O_absent) + goto no_align; + + if (!exp.X_unsigned) + { + as_warn (_("alignment negative; 0 assumed")); + align = 0; } - else + + if (align_bytes && align != 0) { - /* Assume some objects may require alignment on some systems. */ -#if defined (TC_ALPHA) && ! defined (VMS) - if (temp > 1) + /* convert to a power of 2 alignment */ + unsigned int alignp2 = 0; + while ((align & 1) == 0) + align >>= 1, ++alignp2; + if (align != 1) { - align = ffs (temp) - 1; - if (temp % (1 << align)) - abort (); + as_bad (_("alignment not a power of 2")); + ignore_rest_of_line (); + return -1; } -#endif + align = alignp2; } + return align; +} - *p = 0; - symbolP = symbol_find_or_make (name); - *p = c; - - if ( -#if (defined (OBJ_AOUT) || defined (OBJ_MAYBE_AOUT) \ - || defined (OBJ_BOUT) || defined (OBJ_MAYBE_BOUT)) -#ifdef BFD_ASSEMBLER - (OUTPUT_FLAVOR != bfd_target_aout_flavour - || (S_GET_OTHER (symbolP) == 0 && S_GET_DESC (symbolP) == 0)) && -#else - (S_GET_OTHER (symbolP) == 0 && S_GET_DESC (symbolP) == 0) && -#endif -#endif - (S_GET_SEGMENT (symbolP) == bss_seg - || (!S_IS_DEFINED (symbolP) && S_GET_VALUE (symbolP) == 0))) - { - char *pfrag; - - subseg_set (bss_seg, 1); - - if (align) - frag_align (align, 0, 0); - - /* Detach from old frag. */ - if (S_GET_SEGMENT (symbolP) == bss_seg) - symbol_get_frag (symbolP)->fr_symbol = NULL; - - symbol_set_frag (symbolP, frag_now); - pfrag = frag_var (rs_org, 1, 1, (relax_substateT) 0, symbolP, - (offsetT) temp, (char *) 0); - *pfrag = 0; - - S_SET_SEGMENT (symbolP, bss_seg); +/* Called from s_comm_internal after symbol name and size have been + parsed. NEEDS_ALIGN is 0 if it was an ".lcomm" (2 args only), + 1 if this was a ".bss" directive which has a 3rd argument + (alignment as a power of 2), or 2 if this was a ".bss" directive + with alignment in bytes. */ -#ifdef OBJ_COFF - /* The symbol may already have been created with a preceding - ".globl" directive -- be careful not to step on storage class - in that case. Otherwise, set it to static. */ - if (S_GET_STORAGE_CLASS (symbolP) != C_EXT) - { - S_SET_STORAGE_CLASS (symbolP, C_STAT); - } -#endif /* OBJ_COFF */ +static symbolS * +s_lcomm_internal (int needs_align, symbolS *symbolP, addressT size) +{ + addressT align = 0; -#ifdef S_SET_SIZE - S_SET_SIZE (symbolP, temp); -#endif + if (needs_align) + { + align = parse_align (needs_align - 1); + if (align == (addressT) -1) + return NULL; } else - as_bad (_("symbol `%s' is already defined"), S_GET_NAME (symbolP)); + /* Assume some objects may require alignment on some systems. */ + TC_IMPLICIT_LCOMM_ALIGNMENT (size, align); - subseg_set (current_seg, current_subseg); - - demand_empty_rest_of_line (); + bss_alloc (symbolP, size, align); + return symbolP; } void s_lcomm (int needs_align) { - s_lcomm_internal (needs_align, 0); + s_comm_internal (needs_align, s_lcomm_internal); } void s_lcomm_bytes (int needs_align) { - s_lcomm_internal (needs_align, 1); + s_comm_internal (needs_align * 2, s_lcomm_internal); } void diff --git a/gas/read.h b/gas/read.h index 9c0959901c6..614d5700022 100644 --- a/gas/read.h +++ b/gas/read.h @@ -139,6 +139,9 @@ extern void generate_lineno_debug (void); extern void s_abort (int) ATTRIBUTE_NORETURN; extern void s_align_bytes (int arg); extern void s_align_ptwo (int); +extern void bss_alloc (symbolS *, addressT, int); +extern offsetT parse_align (int); +extern symbolS *s_comm_internal (int, symbolS *(*) (int, symbolS *, addressT)); extern void s_app_file_string (char *); extern void s_app_file (int); extern void s_app_line (int); -- 2.30.2