tech-userlevel archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: Trivial program size inflation

Robert Elz wrote in
 |    Date:        Sun, 2 Jul 2023 15:51:06 -0400 (EDT)
 |    From:        Mouse <mouse%Rodents-Montreal.ORG@localhost>
 |    Message-ID:  <202307021951.PAA07056%Stone.Rodents-Montreal.ORG@localhost>
 || For example, a program that calls printf but never uses any
 || floating-point values at all will not, in theory, need floating point
 || support.  But we do not have any mechanism by which anything can
 || discover that no floating-point printf formats are used and thus bring
 || in a printf variant that doesn't actually support floating point; this
 || means that a bunch of floating-point stuff will be brought in even
 || though it will never actually get used.
 |First, a different printf that doesn't support floats isn't needed,
 |printf (itself) has essentially no knowledge of anything related to
 |When everything used to be static (ie: back in my time...) a lot of

Mine around Y2K also, not _that_ long ago.

 |effort was expended making small programs stay small (both RAM for the
 |executing binary, and disc space for the executable file, were scarce
 |resources) by careful crafting of what was in libc.a and the order it

Even getting rid of libc was not that hard (especially on Linux
with its syscall() macros where one did not even need ASM shims at
all to go this way).  And not even "extern char **environ" with
the three argument form of main() which unfortunately is never
become standardized.

Isn't it just that it all went down a grazy route of comfort, with
(very smart) TLS (instead of hand-written "ThreadSpecificData";
which was still very fast with a fast thread_self()), automatic
ctors and dtors (which existed by then but who really uses that
possibility of injection auto-ctors, ie C++ instances, what
a mess).
ifuncs are also a smart thing, but then again dynamic local jumps
(ie computed gotos) in a statically linked function may be faster,
by then and maybe even more so today with all the massive (even
"multithreaded"?) speculation.  But yes they are smart.  Of course
that could be a standardized thing like dlopen/dlsym is so that
people could actually use it.

(This is all so much better than applying attributes, because all
of today's language designers do not care, for example i now have
to write for example OVRX(~a_md(void)) because

  # if __cplusplus +0 < 201103l
  #  define su_OVRX(X) virtual X
  # else
  #  define su_OVRX(X) X override
  # endif

ie the order of the attribute switched, but it is likely i am just
too stupid to understand why virtual is left and override is
right.  Right?  Yes the "virtual" above is just moron notation as
the virtuality is implied from a super class.  But, hey.)

And POSIX threads are "Thank you, Butenhof of HP"; they should
have required a pthread_enable_threading(&mt_enabled_main_fun) to
be called in order to make thread+ initialization an explicit
thing, instead of anything else.  It could even have been super
strict, "normally an application does not wildly switch back and
forth SMT and non-SMT", at least i have never seen that.

 |all appeared.   Keeping that correct took much work, and it was very easy
 |to end up with multiple symbol definition errors from linking an innocuous
 |(and correct) program that just happened to be slightly different than
 |had been expected.

My gut feeling always was that libc is best when it is not needed.

 |The issue above was solved by having dummy versions of the floating point
 |to string conversion routines (which did nothing, and so were very small).

I have a Plan9 dtoa.c file that is ~20KB and says

  /* derived from /netlib/fp/dtoa.c assuming IEEE, Standard C */
  /* kudos to, gripes to */

Uses a malloc though.  (And a global dtoa lock, optionally.)
And then the full gdtoa package, also from dmg@, aka David M. Gay.
That is a bit larger of course.

 |The compiler helped, by inserting a reference to a well known symbol, if
 |the program being compiled contained any float or double references.
 |The real floating conversion routines defined that symbol, the dummy ones
 |did not.   libc was constructed (as far as is relevant here) with the
 |real conversion routines first, then printf, then the dummy conversion
 |routines following.
 |If the program used any floating point then the compiler inserted undefined
 |reference to the magic symbol would cause the real conversion routines to
 |be linked (as they would be if explicitly called by the program, but that's
 |very unlikely).   Then printf would be linked (we're assuming the program
 |uses printf, or this issue isn't relevant).   If the floating point \
 |routines were already linked, they satisfy the undefined symbols in \
 |the printf
 |object file(s).   If they weren't, those remained undefined until the dummy
 |routines were encountered, later in libc, at which point they'd be loaded.
 |Since we know the program isn't using floating point to get to that point,
 |they'd never be called.

Very smart mess that can only come from academics or science.
(In that no little show can dare to depend that much on a third
party infrastructure.)  :-)

 |Note that this isn't quite "discover that no floating-point printf formats
 |are used" - there was never an attempt to do that, but if the program
 | printf("%f", x);
 |what is 'x' in a valid program?   What can it be that the compiler would
 |not know that the program is using floating point?
 |Even "*(double *)&long_var" is enough for floats to be considered used.
 |If you manage to call printf with a floating format, and pass it something
 |that the compiler does not believe is, or is to be treated as, any kind
 |of floating point data (even if it happens to be) and the program uses no
 |floats elsewhere, anywhere, then you loose...   Trivial to fix, you just
 |declare some float variable, somewhere.
 |I don't know if current compilers provide this kind of assistance, or not,
 |but they could.

Actually Plan9 had format registration for the formatted I/O
series.  That is cool.  If you need floating-point in your
program, why not require people to call a function which registers
the formats and includes the conversion routines.

I did not even have standard I/O by default.  See the mess
everywhere.  If you really want it, call a function.  Then the
buffers are created normally, and synchronization devices, as
necessary, too.  What is wrong with that, use a template and fill
it in just as you need it.  Heck -- that seems to be even simpler
than filling in a commit message of the FreeBSD project!

 |Similar, but different case specific, work can be done to handle all of
 |the other (largish) systems ... eg: when a program exits, exit() or \
 |it calls, needs to make sure all stdio buffers are flushed (typically
 |by doing fclose() on each of them, but the close part isn't as important,
 |the exit sys call accomplishes that - but that cannot ensure than unwritten
 |buffered data has been flushed to files first).   That means that you get
 |large chunks of stdio linked, even if your program doesn't include \

What a terrible thing to make this automatic.
Btw i always had a "atexit" and a "atexit_final", like this core
stuff could use the latter.  How can ISO live without that?

 |or use any of it (and since stdio uses malloc() you get that as well).
 |You can attempt to avoid this by calling _exit() instead of exit(), but
 |as "falling off the end of main" is defined as a call of exit(0), the
 |run time support doesn't know that exit() won't be needed, and links it
 |anyway (even if the compiler knows the program will never simply fall
 |off the end of main()).
 |With enough work that can be handled as well.

If any handler is truly installed, a BSS symbol will be set to
a PTF, and that will then be called.  Only that symbol is always

 |And then on to the next problem ...   and the next ...
 |Since in practice, almost no-one uses static linking for almost anything
 |any more (except via crunchgen for /rescue, which has so much linked in
 |that the whole of libc is a drop in the bucket, and most of it is needed,
 |by something, anyway) there aren't many people willing to attempt to
 |manage all of this, and keep it working.    Believe me, while possible,
 |it isn't easy - and the smallest changes in the oddest of places can
 |require a lot of work, and playing around, to keep it all working properly.
 |For some of this you need linker/binary format support so the library
 |can have routines which define symbols, which resolve references in the
 |program if that routine is linked - but for which the presence of the
 |symbol is not advertised, so the routine will not be linked just because
 |the symbol is unreferenced and it is defined in that routine - something
 |else needs to cause the routine to be linked first.
 |Personally, I don't see any point, and I know I won't be working on that
 |kind of thing, ever again - had enough of that, back when it really \
 |long long ago.   If you link static binaries, without doing everything \
 |to avoid it, and if no-one has done the work to make the static libc \
 |able to
 |handle all of this, and you're not willing to mangle your source code with
 |all the dummy routines that others have been suggesting, so that the libc
 |versions never let linked, then you're going to get big binaries.  Live
 |with it, or do the work to fix it yourself.

Actually i am always stunned what NetBSD with that small team of
developers actually does support regarding all that.  (Even if
sometimes not on all platforms.)

That is the thing to me actually, i would never dare again.  You
have to integrate into the standard machinery, and keep on with
that pace.  Otherwise you fall back and simply loose.
Even if so many standard decisions are truly bogus.

|Der Kragenbaer,                The moon bear,
|der holt sich munter           he cheerfully and one by one
|einen nach dem anderen runter  wa.ks himself off
|(By Robert Gernhardt)

Home | Main Index | Thread Index | Old Index