On Fri, 12 Jul 2024, Jakub Jelinek wrote: > Hi! > > Nick has implemented a new .base64 directive in gas (to be shipped in > the upcoming binutils 2.43; big thanks for that). > See https://sourceware.org/bugzilla/show_bug.cgi?id=31964 > > The following patch adjusts default_elf_asm_output_ascii (i.e. > ASM_OUTPUT_ASCII elfos.h implementation) to use it if it detects binary > data and gas supports it. > > Without this patch, we emit stuff like: > .string "\177ELF\002\001\001\003" > .string "" > .string "" > .string "" > .string "" > .string "" > .string "" > .string "" > .string "\002" > .string ">" > ... > .string "\324\001\236 > 0FS\202\002E\n0@\203\004\005&\202\021\337)\021\203C\020A\300\220I\004\t\b\206(\234\0132l\004b\300\bK\006\220$0\303\020P$\233\211\002D\f" > etc., with this patch more compact > .base64 > "f0VMRgIBAQMAAAAAAAAAAAIAPgABAAAAABf3AAAAAABAAAAAAAAAAACneB0AAAAAAAAAAEAAOAAOAEAALAArAAYAAAAEAAAAQAAAAAAAAABAAEAAAAAAAEAAQAAAAAAAEAMAAAAAAAAQAwAAAAAAAAgAAAAAAAAAAwAAAAQAAABQAwAAAAAAAFADQAAAAAAAUANAAAAAAAAcAAAAAAAAABwAAAAAAAAAAQAAAAAAAAABAAAABAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAADBwOQAAAAAAMHA5AAAAAAAAEAAAAAAAAAEAAAAFAAAAAIA5AAAAAAAAgHkAAAAA" > .base64 > "AACAeQAAAAAAxSSgAgAAAADFJKACAAAAAAAQAAAAAAAAAQAAAAQAAAAAsNkCAAAAAACwGQMAAAAAALAZAwAAAADMtc0AAAAAAMy1zQAAAAAAABAAAAAAAAABAAAABgAAAGhmpwMAAAAAaHbnAwAAAABoducDAAAAAOAMAQAAAAAA4MEeAAAAAAAAEAAAAAAAAAIAAAAGAAAAkH2nAwAAAACQjecDAAAAAJCN5wMAAAAAQAIAAAAAAABAAgAAAAAAAAgAAAAAAAAABAAAAAQAAABwAwAAAAAAAHADQAAAAAAAcANAAAAAAABAAAAAAAAAAEAAAAAAAAAACAAAAAAA" > .base64 > "AAAEAAAABAAAALADAAAAAAAAsANAAAAAAACwA0AAAAAAACAAAAAAAAAAIAAAAAAAAAAEAAAAAAAAAAcAAAAEAAAAaGanAwAAAABoducDAAAAAGh25wMAAAAAAAAAAAAAAAAQAAAAAAAAAAgAAAAAAAAAU+V0ZAQAAABwAwAAAAAAAHADQAAAAAAAcANAAAAAAABAAAAAAAAAAEAAAAAAAAAACAAAAAAAAABQ5XRkBAAAAAw/WAMAAAAADD+YAwAAAAAMP5gDAAAAAPy7CgAAAAAA/LsKAAAAAAAEAAAAAAAAAFHldGQGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" > .base64 > "AAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAUuV0ZAQAAABoZqcDAAAAAGh25wMAAAAAaHbnAwAAAACYGQAAAAAAAJgZAAAAAAAAAQAAAAAAAAAvbGliNjQvbGQtbGludXgteDg2LTY0LnNvLjIAAAAAAAQAAAAwAAAABQAAAEdOVQACgADABAAAAAEAAAAAAAAAAQABwAQAAAAJAAAAAAAAAAIAAcAEAAAAAwAAAAAAAAAEAAAAEAAAAAEAAABHTlUAAAAAAAMAAAACAAAAAAAAAAOAAACsqAAAgS0AAOJWAAAjNwAAXjAAAAAAAAAAAAAAF1gAAHsxAABBBwAA" > .base64 > "G0kAALGmAACwoAAAAAAAAAAAAACQhAAAAAAAAOw1AACNYgAAAAAAAFQoAAAAAAAAx3UAALZAAAAAAAAAiIUAALGeAABBlAAAWEsAAPmRAACmOgAAAAAAADh3AAAAAAAAlCAAAAAAAABymgAAaosAAMIjAAAKMQAAMkIAADU0AAAAAAAA5ZwAAAAAAAAAAAAAAAAAAFIdAAAIGQAAAAAAAMFbAAAoTQAAGDcAAIRgAAA6HgAAlxwAAAAAAADOlgAAAAAAAEhPAAARiwAAMGgAAOVtAADMFgAAAAAAAAAAAACrjgAAYl4AACZVAAA/HgAAAAAAAAAAAABqPwAAAAAA" > The patch attempts to juggle between readability and compactness, so > if it detects some hunk of the initializer that would be shorter to be > emitted as .string/.ascii directive, it does so, but if it previously > used .base64 directive it switches mode only if there is a 16+ char > ASCII-ish string. > > On my #embed testcase from yesterday > unsigned char a[] = { > #embed "cc1plus" > }; > without this patch it emits 2.4GB of assembly, while with this > patch 649M. > Compile times (trunk, so yes,rtl,extra checking) are: > time ./xgcc -B ./ -S -std=c23 -O2 embed-11.c > > > > > > real 0m13.647s > > > user 0m7.157s > > > sys 0m2.597s > > > time ./xgcc -B ./ -c -std=c23 -O2 embed-11.c > > > > > > real 0m28.649s > > > user 0m26.653s > > > sys 0m1.958s > > > without the patch and > time ./xgcc -B ./ -S -std=c23 -O2 embed-11.c > > real 0m4.283s > user 0m2.288s > sys 0m0.859s > time ./xgcc -B ./ -c -std=c23 -O2 embed-11.c > > real 0m6.888s > user 0m5.876s > sys 0m1.002s > with the patch, so that feels like significant improvement. > The resulting embed-11.o is identical between the two ways of expressing > the mostly binary data in the assembly. But note that there are portions > like: > .base64 > "nAAAAAAAAAAvZRcAIgAOAFAzMwEAAAAABgAAAAAAAACEQBgAEgAOAFBHcAIAAAAA7AAAAAAAAAAAX19nbXB6X2dldF9zaQBtcGZyX3NldF9zaV8yZXhwAG1wZnJfY29zaABtcGZyX3RhbmgAbXBmcl9zZXRfbmFuAG1wZnJfc3ViAG1wZnJfdGFuAG1wZnJfc3RydG9mcgBfX2dtcHpfc3ViX3VpAF9fZ21wX2dldF9tZW1vcnlfZnVuY3Rpb25zAF9fZ21wel9zZXRfdWkAbXBmcl9wb3cAX19nbXB6X3N1YgBfX2dtcHpfZml0c19zbG9uZ19wAG1wZnJfYXRh" > .base64 > "bjIAX19nbXB6X2RpdmV4YWN0AG1wZnJfc2V0X2VtaW4AX19nbXB6X3NldABfX2dtcHpfbXVsAG1wZnJfY2xlYXIAbXBmcl9sb2cAbXBmcl9hdGFuaABfX2dtcHpfc3dhcABtcGZyX2FzaW5oAG1wZnJfYXNpbgBtcGZyX2NsZWFycwBfX2dtcHpfbXVsXzJleHAAX19nbXB6X2FkZG11bABtcGZyX3NpbmgAX19nbXB6X2FkZF91aQBfX2dtcHFfY2xlYXIAX19nbW9uX3N0YXJ0X18AbXBmcl9hY29zAG1wZnJfc2V0X2VtYXgAbXBmcl9jb3MAbXBmcl9zaW4A" > .string "__gmpz_ui_pow_ui" > .string "mpfr_get_str" > .string "mpfr_acosh" > .string "mpfr_sub_ui" > .string "__gmpq_set_ui" > .string "mpfr_set_inf" > ... > .string "GLIBC_2.14" > .string "GLIBC_2.11" > .base64 > "AAABAAIAAQADAAMAAwADAAMAAwAEAAUABgADAAEAAQADAAMABwABAAEAAwADAAMAAwAIAAEAAwADAAEAAwABAAMAAwABAAMAAQADAAMAAwADAAMAAwADAAYAAwADAAEAAQAIAAMAAwADAAMAAwABAAMAAQADAAMAAQABAAEAAwAIAAEAAwADAAEAAwABAAMAAQADAAEABgADAAMAAQAHAAMAAwADAAMAAwABAAMAAQABAAMAAwADAAkAAQABAAEAAwAKAAEAAwADAAMAAQABAAMAAwALAAEAAwADAAEAAQADAAMAAwABAAMAAwABAAEAAwADAAMABwABAAMAAwAB" > .base64 > "AAEAAwADAAEAAwABAAMAAQADAAMAAwADAAEAAQABAAEAAwADAAMAAQABAAEAAQABAAEAAQADAAMAAwADAAMAAQABAAwAAwADAA0AAwADAAMAAwADAAEAAQADAAMAAQABAAMAAwADAAEAAwADAAEAAwAIAAMAAwADAAMABgABAA4ACwAGAAEAAQADAAEAAQADAAEAAwABAAMAAwABAAEAAwABAAMAAwABAAEAAwADAAMAAwABAAMAAQABAAEAAQABAAMADwABAAMAAQADAAMAAwABAAEAAQAIAAEADAADAAMAAQABAAMAAwADAAEAAQABAAEAAQADAAEAAwADAAEA" > .base64 > "AwABAAMAAQADAAMAAQABAAEAAwADAAMAAwADAAMAAQADAAMACAAQAA8AAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQA=" > so it isn't all just totally unreadable stuff. > > Tested on top of the #embed patchset (just to get the above > data) and then with full bootstrap/regtest on top of vanilla trunk, > on x86_64-linux against trunk binutils prepended into $PATH (HAVE_GAS_BASE64 > is true) and i686-linux (against older binutils, HAVE_GAS_BASE64 not > defined), ok for trunk?
OK. Do we want to have a command-line switch controlling the use of .base64? It might be the 16+ char limit is too large for the readability purpose and/or with wide-strings, etc. .base64 is unwanted to some people at least when debugging? (does gas support sth like .wstring or UTF-8/16/32 input?) I don't have a good suggestion for the flag name, a quick scan for assembler related options doesn't reveal a nice "pattern"; -f[no-]base64-in-asm? Thanks, Richard. > 2024-07-12 Jakub Jelinek <ja...@redhat.com> > > * configure.ac (HAVE_GAS_BASE64): New check. > * config/elfos.h (BASE64_ASM_OP): Define if HAVE_GAS_BASE64 is > defined. > * varasm.cc (assemble_string): Bump maximum from 2000 to 16384 if > BASE64_ASM_OP is defined. > (default_elf_asm_output_limited_string): Emit opening '"' together > with STRING_ASM_OP. > (default_elf_asm_output_ascii): Use BASE64_ASM_OP if defined and > beneficial. Remove UB when last_null is NULL. > * configure: Regenerate. > * config.in: Regenerate. > > --- gcc/configure.ac.jj 2024-07-01 11:28:22.435241431 +0200 > +++ gcc/configure.ac 2024-07-12 10:28:38.038804308 +0200 > @@ -3054,6 +3054,11 @@ case "${target}" in > ;; > esac > > +gcc_GAS_CHECK_FEATURE([.base64], gcc_cv_as_base64,, > + [ .section .rodata > + .base64 > "Tm9uIHB1ZG9yIGVzdCBuaWwgc2NpcmUsIHB1ZG9yIG5pbCBkaXNjZXJlIHZlbGxlLgo="],, > +[AC_DEFINE(HAVE_GAS_BASE64, 1, [Define if your assembler supports > .base64.])]) > + > # gnu_indirect_function type is an extension proposed at > # http://groups.google/com/group/generic-abi/files. It allows dynamic runtime > # selection of function implementation > --- gcc/config/elfos.h.jj 2024-01-03 11:51:43.966558606 +0100 > +++ gcc/config/elfos.h 2024-07-12 10:40:59.721200959 +0200 > @@ -444,6 +444,10 @@ see the files COPYING3 and COPYING.RUNTI > > #define STRING_ASM_OP "\t.string\t" > > +#ifdef HAVE_GAS_BASE64 > +#define BASE64_ASM_OP "\t.base64\t" > +#endif > + > /* The routine used to output NUL terminated strings. We use a special > version of this for most svr4 targets because doing so makes the > generated assembly code more compact (and thus faster to assemble) > --- gcc/varasm.cc.jj 2024-07-08 11:00:54.723980651 +0200 > +++ gcc/varasm.cc 2024-07-12 13:28:39.934619478 +0200 > @@ -2101,7 +2101,19 @@ void > assemble_string (const char *p, int size) > { > int pos = 0; > +#if defined(BASE64_ASM_OP) \ > + && BITS_PER_UNIT == 8 \ > + && CHAR_BIT == 8 \ > + && 'A' == 65 \ > + && 'a' == 97 \ > + && '0' == 48 \ > + && '+' == 43 \ > + && '/' == 47 \ > + && '=' == 61 > + int maximum = 16384; > +#else > int maximum = 2000; > +#endif > > /* If the string is very long, split it up. */ > > @@ -8469,8 +8481,7 @@ default_elf_asm_output_limited_string (F > int escape; > unsigned char c; > > - fputs (STRING_ASM_OP, f); > - putc ('"', f); > + fputs (STRING_ASM_OP "\"", f); > while (*s != '\0') > { > c = *s; > @@ -8504,9 +8515,11 @@ default_elf_asm_output_ascii (FILE *f, c > { > const char *limit = s + len; > const char *last_null = NULL; > + const char *last_base64 = s; > unsigned bytes_in_chunk = 0; > unsigned char c; > int escape; > + bool prev_base64 = false; > > for (; s < limit; s++) > { > @@ -8519,7 +8532,7 @@ default_elf_asm_output_ascii (FILE *f, c > bytes_in_chunk = 0; > } > > - if (s > last_null) > + if ((uintptr_t) s > (uintptr_t) last_null) > { > for (p = s; p < limit && *p != '\0'; p++) > continue; > @@ -8528,6 +8541,104 @@ default_elf_asm_output_ascii (FILE *f, c > else > p = last_null; > > +#if defined(BASE64_ASM_OP) \ > + && BITS_PER_UNIT == 8 \ > + && CHAR_BIT == 8 \ > + && 'A' == 65 \ > + && 'a' == 97 \ > + && '0' == 48 \ > + && '+' == 43 \ > + && '/' == 47 \ > + && '=' == 61 > + if (s >= last_base64) > + { > + unsigned cnt = 0; > + const char *t; > + for (t = s; t < limit && (t - s) < (long) ELF_STRING_LIMIT - 1; t++) > + { > + if (t == p && t != s) > + { > + if (cnt <= (t - s + 1 + 2) / 3 * 4 > + && (!prev_base64 || (t - s) >= 16) > + && ((t - s) > 1 || cnt <= 2)) > + { > + last_base64 = p; > + goto no_base64; > + } > + } > + c = *t; > + escape = ELF_ASCII_ESCAPES[c]; > + switch (escape) > + { > + case 0: > + ++cnt; > + break; > + case 1: > + if (c == 0) > + cnt += 2 + strlen (STRING_ASM_OP) + 1; > + else > + cnt += 4; > + break; > + default: > + cnt += 2; > + break; > + } > + } > + if (cnt > (t - s + 2) / 3 * 4 && (t - s) >= 3) > + { > + if (bytes_in_chunk > 0) > + { > + putc ('\"', f); > + putc ('\n', f); > + bytes_in_chunk = 0; > + } > + > + unsigned char buf[(ELF_STRING_LIMIT + 2) / 3 * 4 + 3]; > + unsigned j = 0; > + static const char base64_enc[] = > + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" > + "0123456789+/"; > + > + fputs (BASE64_ASM_OP "\"", f); > + while (s < t) > + { > + unsigned char a = *s; > + unsigned char b = 0, c = 0; > + if (s < t - 1) > + b = s[1]; > + if (s < t - 2) > + c = s[2]; > + unsigned long v = ((((unsigned long) a) << 16) > + | (((unsigned long) b) << 8) > + | c); > + buf[j++] = base64_enc[(v >> 18) & 63]; > + buf[j++] = base64_enc[(v >> 12) & 63]; > + buf[j++] = base64_enc[(v >> 6) & 63]; > + buf[j++] = base64_enc[v & 63]; > + if (s >= t - 2) > + { > + buf[j - 1] = '='; > + if (s >= t - 1) > + buf[j - 2] = '='; > + break; > + } > + s += 3; > + } > + memcpy (buf + j, "\"\n", 3); > + fputs ((const char *) buf, f); > + s = t - 1; > + prev_base64 = true; > + continue; > + } > + last_base64 = t; > + no_base64: > + prev_base64 = false; > + } > +#else > + (void) last_base64; > + (void) prev_base64; > +#endif > + > if (p < limit && (p - s) <= (long) ELF_STRING_LIMIT) > { > if (bytes_in_chunk > 0) > --- gcc/configure.jj 2024-07-01 11:28:22.432241469 +0200 > +++ gcc/configure 2024-07-12 10:28:44.843716198 +0200 > @@ -25859,6 +25859,39 @@ case "${target}" in > ;; > esac > > +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking assembler for .base64" >&5 > +$as_echo_n "checking assembler for .base64... " >&6; } > +if ${gcc_cv_as_base64+:} false; then : > + $as_echo_n "(cached) " >&6 > +else > + gcc_cv_as_base64=no > + if test x$gcc_cv_as != x; then > + $as_echo ' .section .rodata > + .base64 > "Tm9uIHB1ZG9yIGVzdCBuaWwgc2NpcmUsIHB1ZG9yIG5pbCBkaXNjZXJlIHZlbGxlLgo="' > > conftest.s > + if { ac_try='$gcc_cv_as $gcc_cv_as_flags -o conftest.o conftest.s >&5' > + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5 > + (eval $ac_try) 2>&5 > + ac_status=$? > + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 > + test $ac_status = 0; }; } > + then > + gcc_cv_as_base64=yes > + else > + echo "configure: failed program was" >&5 > + cat conftest.s >&5 > + fi > + rm -f conftest.o conftest.s > + fi > +fi > +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $gcc_cv_as_base64" >&5 > +$as_echo "$gcc_cv_as_base64" >&6; } > +if test $gcc_cv_as_base64 = yes; then > + > +$as_echo "#define HAVE_GAS_BASE64 1" >>confdefs.h > + > +fi > + > + > # gnu_indirect_function type is an extension proposed at > # http://groups.google/com/group/generic-abi/files. It allows dynamic runtime > # selection of function implementation > --- gcc/config.in.jj 2024-06-17 18:50:45.791187189 +0200 > +++ gcc/config.in 2024-07-12 10:28:41.994753122 +0200 > @@ -1431,6 +1431,12 @@ > #endif > > > +/* Define if your assembler supports .base64. */ > +#ifndef USED_FOR_TARGET > +#undef HAVE_GAS_BASE64 > +#endif > + > + > /* Define 0/1 if your assembler supports CFI directives. */ > #undef HAVE_GAS_CFI_DIRECTIVE > > > Jakub > > -- Richard Biener <rguent...@suse.de> SUSE Software Solutions Germany GmbH, Frankenstrasse 146, 90461 Nuernberg, Germany; GF: Ivo Totev, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)