Earlier quoted context omitted.
Ok, thanks, that makes sense. QBE looks interesting, but I'm missing 32 bit support. So currently I'm trying to reuse the TCC backend, which is far from trivial.
There seems to have been a little progress on that recently, I saw someone working on a ppc 32bit port here: https://bsd.network/@tobhe/111756322928965195 and I also saw somewhere someone working on a 68000 port, but I don't remember where. I'd like an arm 32bit port, which might be a bit easier with this ppc port as an example of 32bit qbe. It'd be nice to try run some hare programs on a gameboy advance :)
Oasis – a small, statically-linked Linux system
271–280 of 288 posts
Re: Oasis – a small, statically-linked Linux system
#272Earlier quoted context omitted.
Michaelforney has also built croc [1], a qbe based C compiler. Really impressive! [1]: https://github.com/michaelforney/cproc
Not as "impressive" as TCC, I'd say. Why? TCC has its own backend, and it has the preprocessor built in. (But QBE is indeed impressive.)
Re: Oasis – a small, statically-linked Linux system
#273Earlier quoted context omitted.
> Static linking gives you better instruction cache utilization as you are executing local code linearly rather than going through indirection with more boilerplate. No, it does not, it worsens it. For example, «strlen», if it comes from a dynamic library, will be loaded into the physical memory once and only once, and it will be mapped into each process's address space as many times as there are processes. Since «st…
The compiler has built-ins for parts of libc exactly because dynamic linkage is ridiculous for performance, but they cannot statically link with a dynamic libc. It's a hack to make dynamic linked libc have at least somewhat acceptable performance. If your libc was statically linked, you would not need the built-in - the strlen impl from your libc would get inlined by LTO. The chances of a particular routine being in…
The argument is entirely contrived and has no root in facts. Compiler built-ins appeared in GNU C/C++ compilers as an attempt to replace the non-portable inline assembly with portable primitives – across compilers and across different architectures as well. The rationale is well documented in the GNU C/C++ compiler v2.3 circa documentation, and it has nothing to do with the dynamic linking.
The use of the compiler built-ins increased once the C/C++ compilers gained the interprocedural, in-file, holistic optimisation capabilities – to improve the quality of the generated code. Moreover, compiler built-ins had been present in some form even in the 32-bit Watcom C compiler for MS-DOS and MS-DOS had no shared libraries or whatsoever.
> The chances of a particular routine being in L1 is absolutely miniscule - it's hard enough to keep a single process and it's data in L1 and L2 […]
CPU caches work at addresses being accessed level, not at the process level. The CPU knows nothing about processes – the CPU is a code interpreter.
One copy of «strlen» in a single memory page at a single physical memory address shared across all processes has a much better chance of staying in the cache for a longer time as opposed to 10k copies of the same «strlen» implementation in 10k memory pages strewn across 10k distinct physical addresses. A single page that is accessed frequently has a higher hit rate and, thus, fewer chance of getting evicted from the cache – these are the basics one can't go against. CPU's other than Intel CPU's have larger or large I-caches, too, therefore very frequently used code has higher chances of survival in the CPU cache. Most importantly, however, the CPU cache (L1/L2) size is not the bottleneck, the TLB size is – a frequently accessed address is better from the TLB perspective than 10k distinct addresses as it will result in a fewer number of the TLB flushes.
Lastly, the shared library cache I was referring to has nothing to do with the CPU execution time. It is the cache where the shared libraries are «pre-linked» to reduce the startup, the GOT fixup and the dynamic library initialisation times – to improve the user experience, not performance.
Re: Oasis – a small, statically-linked Linux system
#274Earlier quoted context omitted.
> A linker typically only includes the parts of the library it needs for each binary […] It is exactly the same with the dynamic linking due to the demand paging available in all modern UNIX systems: the dynamic library is not loaded into memory in its entirety, it is mapped into the process's virtual address space. Initially, there is no code from the dynamic library loaded into memory until the process attempts to…
> It is a one-off step, though. Yes, but often a one off step that sets all your calls to call through a pointer, so each call site in a dynamic executable is slower due to an extra indirection. > For large, very large and frequently used dynamic libraries, caching can be employed to reduce such overhead. The cache is not unlimited nor laid out obviously in userspace, and if you have a bunch of calls into a library t…
That is true, however in tight loops or in hot code paths it is unwise to instigate a jump anyway (even into a subroutine in the close locality). If the overhead of invoking a function in the performance sensitive or critical code is considered high, the code has to be rewritten to do away with it, and it is called microoptimisation. This will also be true in the case of the static linking.
Dynamic libraries do not cater for microoptimisations (which are rare) anyway. They offer greater convenience with a trade-off over the maximum code peformance gains.
> The cache is not unlimited nor laid out obviously in userspace […]
I should have made myself clearer. I was referring to the pre-linked shared library cache, not the CPU cache. The pre-linked shared library cache reduces the process startup time and offer better user experience. The cache has nothing to do with performance.
> So you suffer more page faults than you otherwise have to in order to load one function in a page and ignore the rest.
I will experience significantly fewer page faults if my «strlen» code comes from a single address in a single memory page from 10k processes invoking it (the dynamic library case) as opposed to 10k copies of the same «strlen» sprawled across 10k distinct memory pages at 10k distinct memory addresses (the static linking case).
Re: Oasis – a small, statically-linked Linux system
#275Wouldn't it be better to pile on OpenWRT?
Re: Oasis – a small, statically-linked Linux system
#276Earlier quoted context omitted.
> Agreed, and that is already totally possible How? Take for instance OpenSSL mentioned above. I have a software to distribute for multiple Debian versions, starting from Bullseye which uses OpenSSL 1.x and libicu67. Bookworm the more recent has icu72 and OpenSSL 3.x which are binary-incompatible. My requirement is that I do only one build, not one per distro as i do not have the manpower or CI availability for this.…
> How? Well you build OpenSSL as a static library, and you use that... > Take for instance OpenSSL mentioned above. However for something like OpenSSL on a distro like Debian, I really don't get why one would want it: it is most definitely distributed by Debian in the core repo. But yeah, I do link OpenSSL statically for Android and iOS (where anyway the system does not provide it). That's fairly straightforward, I j…
I mean yes that's what I do but see my comment, I was asking specifically about dynamic linking mentioned by the parent (OpenSSL is definitely a "core library")
> I think there is fundamentally no way (and that's by definition) to support two explicitly incompatible versions in the same build.
Yes, that's my point - in the end static linking is the only thing that will work reliably when you have to ship across an array of distros even for core libraries... The only exceptions in my mind is libgl & other drivers
Re: Oasis – a small, statically-linked Linux system
#277Earlier quoted context omitted.
You sound like the perfect Nix cult memb… erm, user. It’s everything you describe and more (plus the language is incredibly powerful compared with starlark). But you speak from sufficient experience that I presume Nix is a “been there, done that” thing for you. What gives?
Nix isn't as fine-grained as Bazel as I understand it? I don't think it's incremental within a package, which is presumably what dijit achieved.
Re: Oasis – a small, statically-linked Linux system
#278Earlier quoted context omitted.
The compiler has built-ins for parts of libc exactly because dynamic linkage is ridiculous for performance, but they cannot statically link with a dynamic libc. It's a hack to make dynamic linked libc have at least somewhat acceptable performance. If your libc was statically linked, you would not need the built-in - the strlen impl from your libc would get inlined by LTO. The chances of a particular routine being in…
> The compiler has built-ins for parts of libc exactly because dynamic linkage is ridiculous for performance […] The argument is entirely contrived and has no root in facts. Compiler built-ins appeared in GNU C/C++ compilers as an attempt to replace the non-portable inline assembly with portable primitives – across compilers and across different architectures as well. The rationale is well documented in the GNU C/C++…
This is missing the point entirely.
GCC needs to emit e.g. memory copies. Before, this was inline assembly replicated over and over. Now, it's a call to __builtin_memcpy.
The point missed is that GCC always considered the idea of calling memcpy entirely unacceptable as the performance would be horrible over an inline implementation.
The proof of this intent lies in later optimizations: Not only would GCC never want to emit such slow calls, it replaces your explicit libc calls with builtins because obviously you wouldn't want to do something as slow as a dynamic linkeage call.
With static linking and LTO, the libc implementation becomes as good as the builtin, rendering the latter pointless. GCC just cannot assume this to be the case.
> CPU caches work at addresses being accessed level, not at the process level.
No, CPU caches do not work on addresses, they work on tags to be pedantic. Either way, I never said that caches are process level. I said that they do not survive across multiple processes - not because of flushing, but because of trashing. I.e., if you have three processes, A, B and C, where A and C run shared code while B something else, and you switch A -> kernel -> B -> kernel -> C, then by the time you made it form A to C your cache is has been trashed by both B and the kernel.
Now, instead of 3 processes and one routine, make it thousands of threads and gigabytes of shared libraries.
> One copy of «strlen» in a single memory page at a single physical memory address shared across all processes
Again, strlen is a terrible example: 10k copies of strlen being a handful of bytes in the current instruction stream, prefetched and branch predicted will outperform that shared page to an outright ridiculous extent and might even be smaller in total: a 10k copies of a handful of bytes vs. 10k calls and PLT indirections + the un-inlined function. Because it is literally less memory, it also trashes the TLB less.
Even in more realistic cases, remember the TLB hit of the PLT table in each application, not to mention the many more pages consumed by the bulkier implementation. In fact, let's focus a bit on the TLB. The most basic Gtk app links at least 80 libraries worth over 90 megabytes on my system. An L1 TLB has about 64 entries, the L2 around a thousand or so - so it can reference ~16MB worth of memory or thereabout. In other words, even the L2 TLb is about 6 times too small to keep the libraries of the simplest possible gtk app cached.
Heck, take just libicudata at 30MB. Of course, I wouldn't suggest statically linking that, but just pointing out that a single dependency of a Gtk app is enough to fill up the TLB twice, nullifying the idea of any cache benefit to using these libraries.
"Yes but at least they can have libicudata in L3!" - yeah, no - not only would it compete with other dynamic dependencies (for this and other processes), but more importantly the applications also need to process data. A single Gtk app on a 4k monitor will, for example, be managing at least two 32MB framebuffers (3840x2160x4, x2 for double buffering), so that's most of your cache gone during draw before you even consider the input to the draw or any actual functionality of the app!
The best-case for dynamic linkage performance is cases where call cost is irrelevant, e.g. when calling compute routines. There is no point whatsoever in considering CPU caches outside the scope of the currently running process.
Re: Oasis – a small, statically-linked Linux system
#279Earlier quoted context omitted.
> How? Well you build OpenSSL as a static library, and you use that... > Take for instance OpenSSL mentioned above. However for something like OpenSSL on a distro like Debian, I really don't get why one would want it: it is most definitely distributed by Debian in the core repo. But yeah, I do link OpenSSL statically for Android and iOS (where anyway the system does not provide it). That's fairly straightforward, I j…
> Well you build OpenSSL as a static library, and you use that... I mean yes that's what I do but see my comment, I was asking specifically about dynamic linking mentioned by the parent (OpenSSL is definitely a "core library") > I think there is fundamentally no way (and that's by definition) to support two explicitly incompatible versions in the same build. Yes, that's my point - in the end static linking is the onl…
Really, that's the job of the distro/package maintainers. As a developer, you provide the sources of your project. If people want to use it on their respective distro, they write and maintain a package for it, or ask their distro maintainers to do it. That is the whole point of the distro!
Re: Oasis – a small, statically-linked Linux system
#280Earlier quoted context omitted.
> If Linux dependency management worked well, there would be no need or appetite for docker. I kindly disagree here. Linux dependency management does work well. The problem is the bad libraries that don't do semver properly, and the users who still decide to use bad libraries. If people stopped using libraries that break ABI compatibility, then the authors of those libraries would have to do it properly, and it would…
Semver only controls API compatibility, not ABI compatibility. You can make an ABI break in a Semver minor (or patch) version update. Semver is nice, but it's not enough for ensuring compatibility when dynamic linking.