HN user

lgg

345 karma

I work on linkers (dyld, ld) at Apple. Views expressed in my comments are mine and not those of my employer

Posts0
Comments106
View on HN
No posts found.

I mostly agree, but if you go further back in the supply chain there are a number of common inputs/tools. For example, as 2025 the new LPDDR lines are using EUV systems, which means for new fab production both DRAM and logic producers are competing for machines from ASML.

It doesn't change your point that these lines are different and the immediate price spike is not about them competing for capacity in the same facilities, but the fact that manufacturers are committing to large enough future purchases to drive new fab construction means that future pricing outlook (which does impact the current prices to extent) does involve some amount of competition between different types of semiconductor products.

That is ABSOLUTELY incorrect. SSDs have enormous amounts of error detection and correction builtin explicitly because errors on the raw medium are so common that without it you would never be able to read correct data from the device.

It has been years since I was familiar enough with the insides of SSDs to tell you exactly what they are doing now, but even ~10-15 years ago it was normal for each raw 2k block to actually be ~2176+ bytes and use at least 128 bytes for LDPC codes. Since then the block sizes have gone up (which reduces the number of bytes you need to achieve equivalent protection) and the lithography has shrunk (which increases the raw error rate).

Where exactly the error correction is implemented (individual dies, SSD controller, etc) and how it is reported can vary depending on the application, but I can say with assurance that there is no chance your OS sees uncorrected bits from your flash dies.

It really is not the same. Amazon was not profitable because it was building out logistics, and then AWS data centers. There were defensible moats around their business that their growth facilitated. Google built out data centers and fiber, again tangible assets they had that competitors did not.

OpenAI's spending is mostly buying compute from other people. In other words, OpenAI's growth is paying for Microsoft's data centers. The only real asset OpenAI is building are their models. While they may be the best models available today it is unclear if that those any durable advantage since everyone in the industry is advancing very quickly, and it is unclear if they can really monetize effectively them without the infrastructure to run them.

From https://ladybird.org (not some subpage, but literally on the main page):

Why build a new browser in C++ when safer and more modern languages are available?

Ladybird started as a component of the SerenityOS hobby project, which only allows C++. The choice of language was not so much a technical decision, but more one of personal convenience. Andreas was most comfortable with C++ when creating SerenityOS, and now we have almost half a million lines of modern C++ to maintain.

However, now that Ladybird has forked and become its own independent project, all constraints previously imposed by SerenityOS are no longer in effect. We are actively evaluating a number of alternatives and will be adding a mature successor language to the project in the near future. This process is already quite far along, and prototypes exist in multiple languages.

From: https://support.apple.com/guide/security/secure-enclave-sec5...

“A randomly generated UID is fused into the SoC at manufacturing time. Starting with A9 SoCs, the UID is generated by the Secure Enclave TRNG during manufacturing and written to the fuses using a software process that runs entirely in the Secure Enclave. This process protects the UID from being visible outside the device during manufacturing and therefore isn’t available for access or storage by Apple or any of its suppliers.“

Absolutely, I just think "when adhering to this convention" is a high risk. Admittedly I mostly work on macOS so I don't have a nearly as deep of an experience with ELF, but in my experience even when a system looks to be well maintained that you often find surprising numbers of projects being "clever" and breaking conventions as soon as you try to do something that depends on everyone actually globally following the convention.

Not really... symbol versioning is a form of namespacing, but it is somewhat orthogonal to this.

Symbol versioning allows you to have multiple symbols with the same name namespaced by version, but you still have no control over what library in the search path they will be found in. So it does not improve the speed of the runtime searching (since they could be in any library an the search path and you still need to search for them in order), and it does not provide the the same binary compatibility support and dylib hijacking protection (since again, any dylibs earlier in the search path could declare a symbol with he same name.

One could use symbol versioning to construct a system where you had the same binary protection guarantees, but it would involve every library declaring a unique version string, and guaranteeing there are no collisions. The obvious way to do that would be to use the file path as the symbol version, at which point you have reinvented mach-o install names, except:

1. You still do not get the runtime speed ups unless you change the dynamic linker behavior to use the version string as the search path, which would require ecosystem wide changes.

2. You can't actually use symbol versioning to do versioned symbols any more, since you overloaded the use of version strings (mach-o binaries end up accomplishing symbol versioning through header tricks with `asmname`, so it is not completely intractable to do even without explicit support).

Windows and macOS both use a form of two level name-spacing, which does the same sort of direct binding to a target library for each symbol. Retrofitting that into a binary format is pretty simple, but retrofitting it into an ecosystem that depends on the existing flat namespace look up semantics is not. I think it is pretty clever that the author noticed the static nature of the nix store allows them to statically evaluate the symbol resolutions and get the launch time benefits of two level namespaces.

I do wonder if it might make more sense to rewrite the binaries to use Direct Binding[1]. That is an existing encoding of library targets for symbols in ELF that has been used by Solaris for a number of years.

1: https://en.wikipedia.org/wiki/Direct_binding

Conceptually not much has changed since the book was written, but in practice there has been a lot of advancement. For example, ASLR and the increase in the number of libraries has greatly increased the pressure to make relocations efficient, modern architecture including PC relative load/store and branch instructions has greatly reduced the cost of PIC code, and code signing has made mutating program text to apply relocations problematic.

On Darwin we redesigned our fixup format so it can be efficiently applied during page in. That did in include adding a few new load commands to describe the new data, as well as a change in how we store pointers in the data segments, but those are not really properties of mach-o so much as the runtime.

I generally find that a lot of things attribute to the file format are actually more about how it is used rather than what it supports. Back when Mac OS X first shipped people argued about PEF vs mach-o, but what all the arguments all boiled down to was the calling convention (TOC vs GOT), either of which could have been support by either format.

Another example is symbol lookup. Darwin uses two level namespaces (where binds include both the symbol name and the library it is expected to be resolved from), and Linux uses flat namespaces (where binds only include the symbol name which is then searched for in all the available libraries). People often act as though that is a file format difference, but mach-o supports both (though the higher level parts of the Darwin OS stack depend on two level namespaces, the low level parts can work with a flat namespace, which is important since a lot of CLI tools that are primarily developed on Linux depend on that). Likewise, ELF also supports both, Solaris uses two level namespaces (they call it ELF Direct Binding).

There is a bug here... Clearly the author intended to cache the value of nextmalloc to avoid calling dlsym() on every malloc. The correct code should be:

  static void *(*nextmalloc)(size_t) = NULL;
  if (!nextmalloc) 
    nextmalloc = dlsym(RTLD_NEXT, "malloc");
  }
Somehow the fact that the optimization is incorrectly missed here feels appropriate ;-)

It really isn't unavoidable on macOS. @rpath is designed specifically to handle this sort of thing, and is how all the command line tools distributed with Xcode link to libraries embedded in Xcode and continue working even when you drag Xcode to a new location.

Admittedly supporting that would require updating how all the tools are built and not just defaulting to Whatever Linux Does™, which is probably too much effort to justify in this case, but it is hardly an unsolvable (or even an unsolved) problem.

I have been out of this area for almost a decade now, but I have very fond memories of the nRF5 SDK. When I was evaluating the (then new) Nordic BLE SoC's for future products it was so much nicer than the TI CC2540 we had used in our first BLE device.

Analogue 3D 3 years ago

That is only true as the speed of the system performing the emulation approaches infinity ;-)

Yes, you can do all the same things in software, in fact it is trivial, just take the same output from you EDA tools and run it in a simulator. Of course that is so slow it cannot interface with (most) real external HW like CRTs and accessories, but in some technical sense it is software taking the exact same set of inputs as an FPGA, and generating the exact same outputs (just much, much slower).

If we accept that as the premise then then we can consider emulators an optimization where instead of using the simulated verilog we try to manually write code that performs equivalent operations, but can run fast enough to hit the original timing constraints of the HW we are replacing. The thing is that the code is constrained by the limits of the modern HW it is running on, and sometimes the modern HW just cannot do what legacy HW did.

An NES does not have a frame buffer (it does not even have enough ram to hold ~5% of a rendered frame of its output!). To cope with that the games generate their output line by line as that the video signal is being generated. What that means is that you click a button on the controller it can change the output of the scanline that is currently writing to the screen (and you can release it updating the output before the frame is being generated, changing subsequent lines). IOW, the input latency is less than a single frame of input. That is not true with modern computers where we render into a memory mapped frame buffer which is then transmitted to the screen with a complex series of of chips including the GPU and DC, and ultimately synchronized on the blanking intervals.

On an FPGA you can design a display pipeline that matches that of legacy consoles, and get the same latency. Of course you could also do the same thing in software emulation on a computer if you clock it so high that it renders and outputs one frame of video for each scanline of output on the original, but given the NES had a framerate of ~60 (59.94) fps and vertical resolution of 240p that comes out to a framerate of ~14,400 fps to hit the latency target for accurate emulation.

Now in practice most of the time it is a non-issue and emulation is more than sufficient, but some old games do very funky things to exploit whatever they could on the limited HW they run on.

It is also worth noting that FPGAs are a lot more interesting for older systems. Once you get to more modern systems that look more like modern computers the strict timing becomes less important. In particular, once you get to consoles that have frame buffers the timing becomes much less sensitive because the frame buffer acts as a huge synchronization point where you can absorb and hide a lot of timing mismatches.

It is a pretty easy mistake to make if you are used to how fast new processors come out now, but you comparing an i386 from 1991 to to a 68020 from the mid 80s.

In 1985 when the 386 came out I believe the fastest speed you could get was 16MHz. They added higher speed variants for years afterwards. Intel made a 40MHz 386 in 1991 that was strictly aimed at embedded users who want more perf but were not ready to move to 486 based designs (386CX40), I doubt almost anyone used on in a PC. AMD made a Am386 at 40MHz which was a reverse engineered clone of the 386, but again that came out in the 90s (the big selling point was that you could reuse your existing 386 motherboards instead of replacing them like you needed to for a 486).

Generally speaking macOS does not guarantee syscall stability, and does not generally guarantee compatibility for any binaries not linked to `libSystem.dylib` (that is the supported ABI boundary)[1]. This has a number of implications, including (but not limited to):

* The most obvious is the commonly mentioned fact that syscalls may change. Here is an example where golang program broke because they were directly using the `gettimeofday()` syscalls[2].

* The interface between the kernel and the dynamic linker (which is required since ABI stability for statically linked executables is not guaranteed) is private and may change between versions. That means if your chroot contains a `dyld` from an OS version that is not the same as the host kernel it may not work.

* The format of the dyld shared cache changes most releases, which means you can't just use the old dyld that matches the host kernel in your chroot because it may not work with the dyld shared cache for the OS you are trying to run in the chroot.

* The system maintains a number of security policies around platform binaries, and those binaries are enumerated as part of the static trust cache[3]. Depending on what you are doing and what permissions it needs you may not be able to even run the system binaries from another release of macOS.

In practice you can often get away with a slight skew (~1 year), but you can rarely get away with skews of more than 2-3 years.

[1] https://developer.apple.com/library/archive/qa/qa1118/_index...

[2] https://github.com/golang/go/issues/16606

[3] https://support.apple.com/guide/security/trust-caches-sec7d3...

Nushell and Uutils 3 years ago

I presume the reason they do it is that the premise of Nushell is that it uses pipes of structured output instead of simple text streams. That means that they need all the tools to output datas in that form. They could include wrappers for all OS provided binaries and handle the conversion in those wrappers, but that makes you incredibly fragile to minor output or flags changes, and in many cases those wrappers would end up being more complex than the complex than the commands themselves.

I think you missed my point. It wasn’t just ROM limited, the ram controller they used in the LC did not have enough address lines to address more than 4MB per SIMM slot, end of story. No amount of firmware hacking can ever make it support 16MB SIMMs without what amounts to a total board redesign. Given the 2MB soldered to the board (which took the address lines for two of the slots) that meant the machine was physically limited to 10MB unless you wanted to break out a soldering iron. Yes, the ROM has a software limit, but it reflected the actual limits of the HW (and more likely was due how the ROM went about detecting the ram then any explicit intent to limit things… it is not shocking that the software only works with supported physical configurations and not board reworks).

The LCII on the other hand is a bit less excusable since it could physically hold 12MB but only 10 was usable. As I said I suspect the reason is that it was a fairly quick revision they squeezes in before the redesigned LCIII and they just didn’t rev those parts of the ROM, but it still seemed pretty bad).

If you want more info there is a pretty deep dive on this here: https://68kmla.org/bb/index.php?threads/technical-explanatio...

Not to say there has never been software based market segmentation, but this example is just not right.

First off the LC was in no way a threat to the Iici. The IIci had 32 bit data bus with a 25MHz 68030 and and supported a CPU cache. The LC with a 16MHz 68020 with a 16 bit bus. The Iici was conservatively twice as fast.

Second, the LC HW did not support nearly as much ram as the Iici. It shipped with 2MB soldered down (which logically you can think of 2 1MB SIMMs) and had 2 slots that each supported 4MB SIMMs, which were the highest density commonly available at the time. The (cheaper) memory controller used in the LC only supported 24 bits of physical addresses (and only in so many configurations), resulting in a maximum of ~16MB. Once you account for the soldered down two megabytes and how the slots had to be configured that left you with the ability to install 4MB into the each slot you get 10MB.

Technically speaking it was probably possible to get it to support 12MB or 16MB with a ROM patch if you desoldered the builtin memory and soldered from the address lines on the controller to some custom memory board. But as shipped with the builtin RAM and the controller chip they included 10MB was the most it could reasonably use.

The LCII did up the builtin memory to 4MB and had a software limit of 10MB like the LC (which meant if you installed 4MB SIMMs you would be missing 2MB), but I suspect that was more a result of how quickly it came to market (it was essentially just an LC with a 68030 and 4MB of ram, both of which greatly improved the experience of using the machine with System 7, which shipped after the original Mac LC).

Within a year or so after the LCII the the LCIII shipped with a completely redesigned board, and it supported 36MB of ram.

Source: I owned a Mac LC, paid for and installed a 2Mb memory upgrade to get it to 4MB, then eventually did a motherboard swap to upgrade it to an LCIII. I can even still tell you how much each of those upgrades cost ;-)

iMessage is E2E even without ADP, even with groups and multiple devices. The details are complex, but they are publicly documented here[1]:

The issue (I think) you are referring to is that if you enable iCloud backup[2] or iCloud for Messages[3] (both of which move effectively move the storage of the messages to the cloud, either as part of the device backup or as the canonical representation that devices sync from respectively) then the messages decoded on device will be stored in blobs that iCloud has the keys to unless you enable Advanced Data Protection.

[1] https://support.apple.com/guide/security/how-imessage-sends-...

[2] https://support.apple.com/en-us/HT211228

[3] https://support.apple.com/guide/icloud/what-you-can-do-with-...

HW accelerated rendering was supported at least as far back as System 6.0 in 1990

Classic MacOS used an API called Quickdraw, which was implemented as a series of graphics primitives (originally written for the Lisa as part of LisaGraf). Quickdraw implemented support for things like drawing lines, rectangles, etc. The original implementation software rendered them onto the system frame buffer.

Essentially all drawing went through Quickdraw, which made it a natural chokepoint to introduce acceleration, which is exactly what happened when Apple shipped the Macintosh Display Card 8*24GC in 1990. The card included a separate Am29000 processor (which was often higher performance than the host CPU), which had its own memory and an implementation of Quickdraw. Its driver patched the Quickdraw calls in the OS to RPC them over the bus to the card, which would then render them on behalf of the host. It also supported off screen rendering and DMAing the results back to other cards.

You could argue that is still software rendering, just on another CPU, but at the end of the day that is sort of orthogonal, almost all GPUs have some programmable components you need to load firmware into in order to operate. The key point is that there was an abstract interface the OS could use to offload rendering to some other device besides the main application processor, and the UI used it.

I honestly can't recall how much of this was still in common use by the time Mac OS 9 came around. CPUs were also much faster by then, and the move to PCI meant it was possible to use fast off the shelf PCI GPUs which may have changed the cost benefit ratios enough that it was best to just take whatever the GPU vendors were offering and software render into their frame buffers even if it could not fully accelerate all the same operations a bespoke earlier design could.

There is a contemporary write up of the card available here: http://preserve.mactech.com/articles/develop/issue_03/824GC_...

posix_spawn() is specified such that it is possible (but does not required!) it to be implemented in terms of [v]fork() and exec().

On macOS it is a syscall and is generally faster than using vfork() and exec(). It also has a number of extensions like POSIX_SPAWN_CLOEXEC_DEFAULT, POSIX_SPAWN_SETEXEC, and `posix_spawn_file_actions_addchdir_np()` that allow it to actually be used in many cases where other systems need to resort to fork() and exec().

It is not fetishism to point out something behaves differently than Linux, especially in story about a technology introduced on Apple platforms.

Technologies don't exist in a vacuum. This thread pointed out a problem with dynamic libraries that cannot occur on Apple platforms because dynamic linking does not exist in a void but is part of an ecosystem that exists beyond the dynamic linker. The fact that in that context you keep insisting on ignoring any technology not present on Linux is sort of baffling.

They explained an issue they had on Linux, which was that he installed a package with a library that broke ABI with a client. I explained how it is rarely an issue on macOS because the risks of it are limited by how the system is constructed (immutable base system, so no mix and match package issues) and how linking policy is configured (by default binaries can only link to the embedded dylibs referenced in their bundle's code signature or platform binaries). So yes, I phrased my response in the context of a specific subset of the issue (running code from the wrong library) because all of the rest of the ways that happened are (modulo bugs) prevented by construction.

Package managers do not modify the base system on macOS, and the binaries installed by them will not have code signatures will not be trusted by executables built with the default ecosystem's policy, so they cannot impact binaries outside of their control unless those binaries have been specifically opted into a reduced runtime mode, which makes the change of an ABI break way lower (but not 0) than on Linux... I don't even understand what is controversial about that statement, the systems are built with different goals and engineering trade offs.

And before you tell me about how some system upgrade broke homebrew... of course it did. Many (most?) homebrew packages are opted into policies much closer to the behavior on Linux (flat namespaces, undefined dynamic_lookup, opted out of hardened runtime) because they depend on Linux like semantics due to being multi-platform. That also means they get none of the protections afforded by the system to prevent these issues. If they adopted two level namespaces, @rpath, limiting usage to APIs available in public SDK headers, and using min deployment targets they would be fare more resilient to system upgrades, but that would also entail an ongoing maintenance burden for packages primarily developed on and for Linux.

IOW, if your point is dynamic linking provides primitives to build fragile systems, then sure, I agree. If your point is all dynamically linked systems are significantly more fragile than statically linked systems I disagree (though I can actually point out cases where ABI mismatches have occurred in both static and dynamic binaries). If your point is that a system that allows fragile behaviors to be opted into by power users is inherently more fragile for normal users, then I also disagree (though I concede it may be more fragile for those power users).

The GP specifically talked about an inadvertent dylib hijacking, which is prevented by the mechanisms I described. You are talking about a platform ABI break, which while unfortunate does occasionally happen due to significant technical issues (or sometimes by accident).

Apple does spend a significant amount of effort to avoid breaking supported ABIs. There have definitely been issues though, and especially early in Mac OS X while learning how to deal with upstream open source projects that don't care about ABI. In this specific case the result was Apple funding the development of libc++ and factoring it into libc++ and libc++abi specifically do prevent this sort of breakage in the future. Another example would be about a decade ago when Apple removed the ssl headers for the SDKs and told developers to either use SecureTransport or include their own SSL libraries, since depending on openssl's ABI was not feasible.

That may true on Linux, but it is not the case on Apple platforms. On Darwin the base system is immutable and the dylibs embedded in an application bundle cannot be changed without invalidating the codesignature. In order to have this sort of issue occur you need to opt out of multiple security settings that are enabled by default (such as the hardened runtime and library validation) AND then be sloppy with your use of relative paths or dlopen() calls.

There is a bit more to it than that. Yes, it was always possible to use a mess of build rules and shell scripts to make your debug and release builds swap between fully static and dynamic libraries, but it was a lot of work, and was difficult to maintain. The novelty of mergeable dylibs is that they now make it trivial to switch between the two without all of that work. In particular it solves two large problems people tended to run into:

1. Static archives and dynamic libraries have different semantics with respect to symbol lookup. In particular, due to two level namespaces multiple dylibs can exports the same symbol without it being a runtime collision since the binary importing them stores the library a symbol came from in addition to the name. This is different from static archives where you have sets of symbols brought in by .o file. That means it is often non-trivial to switch between dynamic libraries and static archives. Mergeable libraries solve this by allowing you to use the semantics of dynamic libraries and two level namespaces for content that will be statically linked.

2. Most people use frameworks, not raw dylibs. They do that for a lot of reasons, but the biggest one is to allow them to distribute non-code resources that are associated with the library. This is a common problem that has been solved in various ways (Windows embeds the resources in the DLL files, classic Mac OS depended on resource forks, etc). Mergeable dylibs are completely supported by the runtime in such a way that enough of the dylib's identity is preserved so that things like NSBundle continue to work as a way to find the bundles resources despite the code itself being merged.

This stuff is certainly pretty rarified these days. I remember when the PPC970 came out people were shocked how difficult it was to bootstrap. IBM didn't really care as POWER4 (from which it was derived) was not a merchant chip and they had management processors (and very high margins) in all their machines to handle it. Apple was the launch partner and even back then had a lot of in house expertise doing this sort of work. Everyone else who tried to use it was in for some real pain and most of them gave up. The guys doing the eval boards with support from IBM literally posted this: https://web.archive.org/web/20060715134515/http://www.970eva...

TL;DR, the last line is "Once all of the above is completed, the processor will be able to successfully fetch instructions from a boot source. You are now effectively at the same point you would have been 5 months ago, had this been a standard 750 bringup... Board bringup from this point should be very straightforward and follow established methods."