The absolute prefix requirements (similar to Nix, and I guess for the same reason) mean this probably can't be used where it would be most useful - on machines that you don't control.
I wonder if anyone will ever make something like this but where all the software has to be relocatable (no hard-coded absolute paths).
I guess a lot of software would be excluded or need patching, but I think it would be useful and maybe it would encourage Linux developers to stop bloody hard-coding absolute paths like it's still the 90s!
I've used OCaml a bit and found various issues with it:
* Terrible Windows support. With OCaml 5 it's upgraded to "pretty bad".
* The syntax is hard to parse for humans. Often it turns into a word soup, without any helpful punctuation to tell you what things are. It's like reading a book with no paragraphs, capitalisation or punctuation.
* The syntax isn't recoverable. Sometimes you can add a single character and the error message is essentially "syntax error in these 1000 lines".
* Ocamlfmt is pretty bad. It thinks it is writing prose. It will even put complex `match`es on one line if they fit. Really hurts readability.
* The documentation is super terse. Very few examples.
* OPAM. In theory... I feel like it should be great. But in practice I find it to be incomprehensible, full of surprising behaviours, and also surprisingly buggy. I still can't believe the bug where it can't find `curl` if you're in more than 32 Unix groups.
* Optional type annotation for function signatures throws away a significant benefit of static typing - documentation/understanding and nice error messages.
* Tiny ecosystem. Rust gets flak for its small standard library, but OCaml doesn't even have a built in function to copy files.
* Like all FP languages it has a weird obsession with singly linked lists, which are actually a pretty awful data structure.
It's not all bad though, and I'd definitely take it over C and Python. Definitely wouldn't pick it over Rust though, unless I was really worried about compile times.
I would suggest that people interested in using OCaml on Windows (or indeed, OCaml at all) try F# instead. It is still an ML-family language. Incidentally, by targeting the CLR, F# is considerably more deployable (both to users and to developers) than OCaml is. Plus, any old NuGet library written in C# or VB.NET can be used almost trivially in F#. This also solves the problem OP listed about a tiny ecosystem, because the C#/.NET ecosystem is massive.
I couldn't agree more with the parent commenter about OCaml documentation. Functional programmers appear to love terseness to an almost extreme degree. Things like `first` are abbreviated to `fst`, which is just odd. Especially now that good IntelliSense means there is no real functional (heh) difference between typing `.fi` and pressing Tab, and typing `.fs` and pressing Tab.
The F# documentation is comparatively very spiffy and detailed, with plenty of examples[1][2][3].
To decide on a language for a math research project, I implemented a toy problem in many languages: What is the distribution of cycle counts for signed permutations on n letters? Use all cores in parallel.
C++ 100 19.57s
Rust 96 20.40s
F# 95 20.52s
Nim 75 26.04s
Julia 64 30.40s
Ocaml 48 41.07s
Haskell 41 47.64s
Chez 39 49.53s
Swift 33 58.46s
Lean 7 278.88s
Tarjan, n = 10
Nyx - Apple M4 Max - 12 performance and 4 efficiency cores
n! * 2^n = 3,715,891,200 signed permutations
score = gops normalized so best language averages 100
time = running time in seconds
This had me briefly smitten with F#, till I realized the extent that rusty .NET bed springs were poking through. Same as the JVM and Clojure, or Erlang and Elixir. The F# JIT compiler is nevertheless pretty amazing.
I nearly settled on OCaml. After AI warning me that proper work-stealing parallelism is a massive, sophisticated project to code properly, the 40 lines of OCaml code I wrote that beat available libraries is my favorite code file in years.
Nevertheless, once one understands lazy evaluation in Haskell, it's hard to use any other language. The log slowdown for conventional use of a functional data structure becomes a linear speedup once one exploits persistence.
F# is a nice language, but it depends on .NET, so a direct comparison to OCaml is not possible. The .NET runtime dependency often makes things unnecessarily complex and fragile. There must be a reason why Golang (no VM runtime) is successful despite entering the market very late after Java and C# had already established clear dominance.
Easier deployment helped, but nowadays it’s common to put a single Go executable in a container anyway. .net core doesn’t have much if any disadvantage there.
Go was made by Google flawed, but with clarity of purpose and consistently usable libraries.
.NET spent years forked into framework and core and unclear open source future.
Java struggled to scale down and Oracle caused similar distrust as Microsoft.
While I agree with most points, .NET or JRE introduce additional complexity that may not be welcome in every setting. I've worked for several companies where upgrading to a newer version of .NET was always a major pain point, especially for applications that weren't very actively developed. There is limited security support for each major .NET version, so at some point, you and your customers need to upgrade. Such problems are non-existent if you don't have a separate VM runtime dependency.
> I've worked for several companies where upgrading to a newer version of .NET was always a major pain point,
This was so difficult for the teams I work with that the person who became the team specialist in MSBuild, dotnet, and C# basically punched his ticket and received a couple of promotions. The work is so miserable and challenging that basically no one else Will take it.
I've struggled to understand why .net upgrades are so painful and laborious despite the maturity of the languages and ecosystems. I had assumed that, as with Powershell, C# and .net behind the scenes are undergoing enormous changes as Microsoft attempts to improve or add support for historically neglected platforms and hardware, and so I had assumed also that these were temporary problems, just badly managed growing pains on a path to major improvement, but I gather from your comment that the problem has a long history and is systemic. Awful.
MSBuild has been a huge source of the suffering I've experienced while working on these projects. The documentation is, in my experience, a nightmare: exhaustingly verbose, full of jargon, and uncanny in its ability to talk around an issue, concept, or functionality without clarifying anything conceptually or practically. I find this throughout Microsoft docs.
Upgrading a modern .NET (Core) version often means changing just versions in the project file. Upgrading from the old .NET Framework is bit more challenging, but we have also successfully upgraded a couple of Framework projects to modern .NET without too many issues.
Huh, maybe I misunderstood the nature of the .net work the guy I mentioned was doing. All I know directly is the pain of MSBuild itself. Thanks for the correction.
I don't think so. Upgrading to a newer major version of the C++ runtime essentially involves recompiling, unless you're dealing with an application that's 15 years or older. You can even instruct the compiler to use an older C++ standard if your code won't compile with the newer one. There is also an option to compile the runtime statically, though it is obviously not recommended.
F# is indeed an ML-family language, but at this point it doesn't have all that much in common with OCaml specifically: it doesn't have the latter's structurally typed object model with inferred row-polymorphic types, nor its extremely powerful module system, not even many convenience features like open variants.
F# has plenty of features that OCaml doesn't have like proper cross-platform support, proper deployment, active patterns, units of measure, computation expressions, type providers, interfaces on unions and records, etc. F# is probably faster as well and definitely has a larger ecosystem through .NET.
F# is worse because the type inferencing isn't as good. You need to type annotate in more places. It's a drag, because it feels like a missed opportunity to let the machine do work for you.
Additionally, one of the most pleasant and unique features of OCaml, strong named arguments, doesn't exist in F# (except in methods or whatever). Most programming languages don't have this (or it's hamfisted like in more dynamic languages) so it doesn't seem like a loss, unless you are very cozy with them in OCaml.
(I'm bitter about this one because named arguments give safety and convenience. But when you combine them with currying it's like 4 or 5 design patterns that are elegantly provided for you. You just do it implicitly, without ever having to study a book about using them.)
Finally, F# brings back the NULL problem that OCaml worked so hard to eradicate.
Nearly the same as with positional argument: Partially applying a function to a named argument creates a closure with this argument filled in, and you can apply the argument in whichever order you want:
let f a ~x ~y b = a + x + y + b
let g = f ~y:1 (* g is closure with the argument named ~y filled in *)
let h = g 2 (* another closure with the first positional argument filled in *)
let int_15 = h 8 ~x:4 (* = g 2 8 ~y:4 = f ~y:1 2 8 ~x:4 *)
The more complex interaction is rather with type inference and currying, or the interaction between currying and optional arguments.
> * OPAM. In theory... I feel like it should be great. But in practice I find it to be incomprehensible, full of surprising behaviours, and also surprisingly buggy. I still can't believe the bug where it can't find `curl` if you're in more than 32 Unix groups.
A lot of these are learning curve issues, but once you scale them you still permanently suffer with a very deficient, fragmented ecosystem. Until you have built up your own curated set of dependencies for your business stack, anyway.
> * Ocamlfmt is pretty bad. It thinks it is writing prose. It will even put complex `match`es on one line if they fit. Really hurts readability.
I suggest configuring ocamlformat to use the janestreet profile for better defaults.
> * Optional type annotation for function signatures throws away a significant benefit of static typing - documentation/understanding and nice error messages.
People should be providing .mli files, but don't. That said, an IDE with type hints helps this enormously. The VS Code plugin for OCaml is the best experience for noobs, hands down.
> * Like all FP languages it has a weird obsession with singly linked lists, which are actually a pretty awful data structure
This made me chuckle. I've had that thought before, shouldn't the default be a vector on modern devices? Of course other collection types are available.
The reason why functional languages like linked lists so much is because they are very easy to make immutable without a lot of pain all around. If you implement an immutable vector in a straightforward way, though, you basically need to do a lot of copying for any operation that needs to construct one (the rigmarole with immutable strings, and existence of hacks such as StringBuilder, is a good illustration of the problem).
But you can have a data structure that is more like vector under the hood while still supporting efficient copy-with-modifications. Clojure vectors, for example.
That makes sense. But LinkedIn lists are horrible for cache efficient things no? I think there was an article on HN from a Golang perspective padding structs or something such that they are efficient wrt to cache hits.
LinkedIn lists will tell you they contain one new element, but you have to click on a link and scroll through their suggested new elements to see what it is
I'd say that functional programming is probably one of the only domains where linked lists actually make sense. Vectors certainty have their use in more places though.
A good compiler will make the lists disappear in many cases. No runtime overhead. I actually love single linked lists as a way to break down sequences of problem steps.
I believe it's possible in theory - Koka has a whole "functional but in-place" set of compiler optimisations that essentially translate functional algorithms into imperative ones. But I think that's also possible in Koka in part because of its ownership rules that track where objects are created and freed, so might also not be feasible for OCaml.
I mean, the set of valid deforestation transformations you could do to an OCaml program is not literally the empty set, but OCaml functions can do I/O, update refs, and throw exceptions, as well as failing to terminate, so you would have to be sure that none of the code you were running in the wrong order did any of those things. I don't think the garbage collection issues you mention are a problem, though maybe I don't understand them?
Part of what Koka's functional-but-in-place system relies on is the Perceus program analysis, which, as I understand it, is kind of like a limited Rust-like lifetime analysis which can determine statically the lifetime of different objects and whether they can be reused or discarded or whatever. That way, if you're, say, mapping over a linked list, rather than construct a whole new list for the new entries, the Koka compiler simply mutates the old list with the new values. You write a pure, functional algorithm, and Koka converts it to the imperative equivalent.
That said, I think this is somewhat unrelated to the idea of making linked lists disappear - Koka is still using linked lists, but optimising their allocation and deallocation, whereas Haskell can convert a linked list to an array and do a different set of optimisations there.
Why would a specific way of structuring data in memory be relevant to breaking down sequences of problem steps?
If what you mean is the ability to think in terms of "first" and "rest", that's just an interface that doesn't have to be backed by a linked list implementation.
Not GP but bump allocation (OCaml's GC uses a bump allocator into the young heap) mitigates this somewhat, list nodes tend to be allocated near each other. It is worse than the guaranteed contiguous access patterns of a vector, but it's not completely scattered either.
+10 for bad windows support, i think this is a key and weirdly underestimated reason
just to give an idea how bad, until recently, you could not just go to ocaml.org and download ocaml for windows, you had to either download one for mingw or wsl
so for many it was just not installable, i.e. for many we didnt have ocaml for windows, until very very recently
The Windows support is bad because OCaml is a PL designed by people who are deep in Linux life. Windows is not something that keeps them up at night. (Which isn't to say they didn't try, just, you know, not as much as it takes)
One of the things people often neglect to mention in their love letters to the language (except for Anil Madhavapeddy) is that it actually feels UNIXy. It feels like home.
If that's what you use as your yardstick of what's Unixy, then I guess you don't consider "find" to be Unixy, in spite of being one of the early Programmer's Workbench tools.
Short options were a compromise to deal with the limits of the input hardware at the time. Double dashes were a workaround for the post-dash option car crash traditional Unix tooling allows because teletypes were so slow. There is nothing particularly Unixy about any of these options other than the leading hyphen convention.
Caml was developed in 01985, Linux was first released in 01991, and Caml was extended into OCaml in 01996. I don't think the developers were using Linux at the time; SunOS 4 would be my best guess. I didn't work on it, but I was in the sort of internetty environment that OCaml came from, and I was using Solaris at work and got my first Linux box at home. As another commenter mentioned, the command line follows Unix conventions rather than the Linux conventions from GNU.
It's just sort of anachronistic. Caml was no more designed by people deep in Linux life than James Clerk Maxwell was into aviation or Christopher Columbus liked to visit the Massachusetts Bay Colony. They probably would have been, but the timelines didn't allow it.
Sure. The larger point was about Windows. You would or would not agree that people doing PL research on SunOS workstations (or whatever UNIX) would also not give a shit about Windows?
Windows didn't exist in 01985. I mean, Microsoft did release a product by that name at the end of the year, but it wasn't an operating system.
I remember that in 01998 or 01999 I asked Andrew Tanenbaum, a different European CS professor, what he thought about Linux. His impression was still that it was some kind of hobbyist project for people who (paraphrasing here and reading between the lines) couldn't afford a real computer. So I suspect that, even when Caml became OCaml, its developers saw Linux as belonging to the same crowd as Microsoft Windows, rather than to the systems they were used to.
But even that Windows/Unix OS dichotomy didn't exist when the programming language was designed. They might have been thinking about Unix vs. VMS, or Unix vs. GCOS, or Unix vs. Oberon, or BSD vs. System V, but definitely not Linux vs. Windows.
I don't think you would be flagged. Rust definitely has flaws:
* Compile time is only ok. On par with C++.
* Async has a surprisingly number of footguns and ergonomic issues.
* There's no good solution to self-borrowing or partial borrows.
* While using macros is fine, writing them is pretty awful. Fortunately you rarely need to do that. Relatedly it is missing introspection support.
* Sometimes the types and lifetimes get very complex.
But overall I still much prefer it to OCaml. The syntax is much nicer, it's easier to read, the ecosystem and tooling are much better, the documentation is much better, and it actively hates linked lists!
* Crates.io is an unmoderated wasteland of infinite transitive dependency chaos
But my "favourite":
* It's 2025 and allocator-api (or its competitors) is still in nightly only and no sign of stabilizing
I work almost exclusively in Rust, and I like the language but in many respects a) Rust is the language that tokio ate, and b) it has been deluged with people coming from the NodeJS/NPM ecosystem doing Web Scale Development(tm) and its terrible practices around dependency management.
> Crates.io is an unmoderated wasteland of infinite transitive dependency chaos
Well, I'm not sure I agree with that. If by "unmoderated" you mean anyone can upload a crate without going through bureaucratic approvals processes then that seems like a good thing to me!
But you did remind me of one other majorly annoying thing about the crate system: the global namespace. It doesn't have the same security issues as Python's global package namespace, but it is really really annoying because you pretty much have to break large projects down into multiple crates for compile time reasons, and then good luck publishing those on crates.io. Plus you end up with a load of crate names like `myproject-foo`, `myproject-bar`, which is very verbose.
Oh and yeah, the Tokio situation is a bit sad.
Also the proliferation of some types of crates like error handling. This should really be in the standard library by now (or at least fairly soon). Any large project ends up with like 5 different error handling libraries just because dependencies made different choices about which to use. It's not a major issue but it is silly.
Overall though, still my favourite language by far.
I think the error type stuff should be less prominent, and really shouldn't be front-and-center in the standard library, or any library. Error handling is undoubtedly important, but its expression in the API is a different matter. I don't have fully formed opinions, but I do feel that the common recommendations are bad.
On the topic of allocators, I noticed some PRs to add allocator support to more types recently...great, now we just need to stabil—oh, wait. Although I care more about not stabilizing a bad API than eternal unstability, if it comes down to that.
The problem for me is that instead of focusing on the problem at hand and the business logic, I have to focus on the Rust memory model and the borrow checker. And seems very verbose.
Thing is, if I sketch something in pseudocode, I should be able to translate it to any mainstream programming languages. With Rust I can't just do that, I have to bend the problem to fit the way the language works.
That third issue is very annoying. C++ in visual studio often has that and I'll help someone who has like 2,000 weird errors and my response is usually "looks like a missing semicolon to me" and they're like "????" Why wouldn't it be able to just say that.
This was my problem as well, the object oriented related syntax is just too much. ML of which caml is a version of, has really tight syntax. The “o” in ocaml ruins it imo.
No offense but the only time I have encountered objects in Ocaml was while using LablGTK a long time ago and the object syntax was pretty clean and not far from the module one. They are also structurally typed which is very handy.
Considering it only impacts a fairly small subset of the language, could you explain how it supposedly ruins everything?
> The documentation is super terse. Very few examples.
I agree. OCaml is a complex language with very beginner-unfriendly documentation. In fact, I would even say it's unfriendly to engineers (as developers). The OCaml community prefers to see this language as an academic affair and doesn't see the need to attract the masses. E.g. Rust is an example of the opposite. It's a complex language, but it's pushing hard to become mainstream.
It's interesting reading many of the associated comments, because there is a genuinely active effort to address many of the pain points of the language:
* Windows support has improved to the point where you can just download opam, and it will configure and set up a working compiler and language tools for you[^1]. The compiler team treat Windows as an first tier target. opam repository maintainers ensure new libraries and library versions added to the opam repository are compiled and tested for Windows compatibility, and authors are encouraged to fix it before making a release if its reasonably straightforward
* debugger support with gdb (and lldb) is slowly being improved thanks to efforts at Tarides
* opam is relatively stable (I've never found it "buggy and surprising"), but
there are aspects (like switches that behave more like python venvs) which don't provide the most modern behaviour. dune package management (which is still in the works) will simplify this considerably, but opam continues to see active development and improvement from release to release.
* the platform team (again) are working on improving documentation with worked recipes and examples for popular uses cases (outside of the usual compiler and code generation cases) with the OCaml Cookbook: https://ocaml.org/cookbook
There are other things I find frustrating or that I work around, or are more misperceptions:
* there isn't a builtin way to copy files because the standard library is deliberately very small (like Rust), but there is a significant ecosystem of packages (this is different to other languages which cram a lot into their standard library). The result is a lot of friction for newcomers who have to install something to get what they need done, but that's valued by more experienced developers who don't want the whole kitchen sink in their binary and all its supply chain issues.[^2]
* the type inference can be a bit of a love/hate thing. Many people find it frustrating because of the way it works, and start annotating everything to short-circuit it. I've personally found it requires a bit of work to understand what it is doing, and when to rely on it, and when not to (essentially not trying to make it do things it simply will never be able to do).[^3]
* most people use singly-linked lists because they work reasonably well for their use cases and don't get in their way. There are other data structures, they work well and have better performance (for where it is needed). The language is pragmatic enough to offer mutable and immutable versions.
* ocamlformat is designed to work without defaults (but some of them I find annoying and reconfigure)
Please don't take this as an apology for its shortcomings - any language used in the wild has its frustrations, and more "niche" languages like OCaml have more than a few. But for me it's amazing how much the language has been modernised (effects-based runtime, multicore, etc) without breaking compatibility or adding reams of complexity to the language. Many of these things have taken a long time, but the result is usually much cleaner and better thought out than if they were rushed.
[^1] This in itself is not enough, and still "too slow". It will improve with efforts like relocatable OCaml (enabling binary distribution instead of compiling from source everywhere) and disentangling the build system from Unixisms that require Cygwin.
[^2] I particularly appreciate that the opam repository is actively tested (all new package releases are tested in a CI for dependency compatibility and working tests), curated (if its too small to be library, it will probably be rejected) and pruned (unmaintained packages are now being archived)
[^3] OCaml sets expectations around its type inference ("no annotations!") very high, but the reality is that it relies on a very tightly designed and internally coherent set of language constructs in order to achieve a high level of type inference / low level of annotation, but these are very different to how type inference works in other languages. For example, I try and avoid using the same field name in a module because of the "flat namespace" of field names used to infer record types, but this isn't always possible (e.g. generated code), so I find myself compensating by moving things into separate modules (which are relatively cheap and don't pollute the scope as much).
> RISC-V has either little-endian or big-endian byte order.
Yeah though for instruction fetch it's always little endian. I honestly think they should remove support for big endian from the spec. As far as I know nobody has implemented it, the justification in the ISA manual is very dubious, and it adds unneeded complexity to the spec and to reference models.
Plus it's embarrassing (see Linus's rant which I fully agree with).
The rundown on this is that CodeThink added Big Endian RISC-V because of a half-baked optimized networking scenario where somehow the harts (RISC-V speak for a cpu core) don't have Zbb byte manipulation instructions. Linus shuts down efforts made in mainline Kernel (!!) because these issues are extremely flimsy at best and don't have technical merit for complicating the kernel's RISC-V code and already extreme RISC-V fragmentation.
I've looked at more reasons that CodeThink came up with for Big Endian RISC-V, and trust me, that's the best that they have to present.
> somehow the harts don't have Zbb byte manipulation instructions
More specifically, it relies on a hypothetical scenario where building a big-endian / bi-endian core from scratch would be easier than adding the Zbb extension to a little-endian core.
> So when a little-endian system needs to inspect or modify a network packet, it has to swap the big-endian values to little-endian and back, a process that can take as many as 10-20 instructions on a RISC-V target which doesn’t implement the Zbb extension.
See this justification doesn't make any sense to me. The motivation is that it makes high performance network routing faster, but only in situations where a) you don't implement Zbb (which is a real no-brainer extension to implement), and b) you don't do the packet processing in hardware.
I'm happy to be proven wrong but that sounds like an illogical design space. If you're willing to design a custom chip that supports big endian for your network appliance (because none of the COTS chips do) then why would you not be willing to add a custom peripheral or even custom instructions for packet processing?
Half the point of RISC-V is that it's customisable for niche applications, yet this one niche application somehow was allowed and now it forces all spec writers and reference model authors to think about how things will work with big endian. And it uses up 3 precious bits in mstatus.
I guess it maybe is too big of a breaking change to say "actually no" even if nobody has ever actually manufactured a big endian RISC-V chip, so I'm not super seriously suggesting it is removed.
Perhaps we can all take a solemn vow to never implement it and then it will be de facto removed.
Did he make the same rant about ARMv8 which can (if implemented) even switch endianness on the fly? What about POWER, SPARC, MIPS, Alpha, etc which all support big-endian?
Once you leave x86-land, the ISA including optional big-endian is the rule rather than the exception.
The problem is that it's relatively easy to add "supports both endiannesses" in hardware and architecture but the ongoing effect on the software stack is massive. You need a separate toolchain for it; you need support in the kernel for it; you need distros to build all their stuff two different ways; everybody has to add a load of extra test cases and setups. That's a lot of ongoing maintenance work for a very niche use case, and the other problem is that typically almost nobody actually uses the nonstandard endianness config and so it's very prone to bitrotting, because nobody has the hardware to run it.
Architectures with only one supported endianness are less painful. "Supports both and both are widely used" would also be OK (I think mips was here for a while?) but I think that has a tendency to collapse into "one is popular and the other is niche" over time.
Relatedly, "x32" style "32 bit pointers on a 64 bit architecture" ABIs are not difficult to define but they also add a lot of extra complexity in the software stack for something niche. And they demonstrate how hard it is to get rid of something once it's nominally supported: x32 is still in Linux because last time they tried to dump it a handful of people said they still used it. Luckily the Arm ILP32 handling never got accepted upstream in the first place, or it would probably also still be there sucking up maintenance effort for almost no users.
Major difference between "we won't support big endian" and calling RISC-V out as stupid for adding optional support.
The academic argument Linus himself made is alone reason enough that big-endian SHOULD be included in the ISA. When you are trying to grasp the fundamentals in class, adding little endian's "partially backward, but partially forward" increases complexity and mistakes without meaningfully increasing knowledge of the course fundamentals.
No zbb support is also a valid use. Very small implementations may want to avoid adding zbb, but still maximize performance. These implementations almost certainly won't be large enough to run Linux and wouldn't be Linus' problem anyway.
While I've found myself almost always agreeing with Linus (even on most of his notably controversial rants), he's simply not correct about this one and has no reason to go past the polite, but firm "Linux has no plans to support a second endianness on RISC-V".
> Relatedly, "x32" style "32 bit pointers on a 64 bit architecture" ABIs are not difficult to define but they also add a lot of extra complexity in the software stack for something niche.
I'm not sure that there's much undue complexity, at least on the kernel side. You just need to ensure that the process running with 32-bit pointers can avoid having to deal with addresses outside the bottom 32-bit address space. That looks potentially doable. You need to do this anyway for other restricted virtual address spaces that arise as a result of memory paging schemes, such as 48-bit on new x86-64 hardware where software may be playing tricks with pointer values and thus be unable to support virtual addresses outside the bottom 48-bit range.
In practice it seems like it's not as simple as that; see this lkml post from a few years back pointing out some of the weird x32 specific syscall stuff they ended up with: https://lkml.org/lkml/2018/12/10/1145
But my main point is that the complexity is not in the one-off "here's a patch to the kernel/compiler to add this", but in the way you now have an entire extra config that needs to be maintained and tested all the way through the software stack by the kernel, toolchain, distros and potentially random other software with inline asm or target specific ifdefs. That's ongoing work for decades for many groups of people.
Then the real question is whether this bespoke syscall mechanism will be needed going forward, especially as things like time_t adopt 64-bit values anyway. Can't we just define a new "almost 32-bit" ABI that just has 64-bit clean struct layouts throughout for all communication with the kernel (and potentially with system-wide daemons, writing out binary data, etc. so there's no real gratuitous breakage there, either), but sticks with 32-bit pointers at a systems level otherwise? Wouldn't this still be a massive performance gain for most code?
You could definitely do better than x32 did (IIRC it is a bit of an outlier even among "32-bit compat ABI" setups). But even if the kernel changes were done more cleanly that still leaves the whole software stack with the ongoing maintenance burden. The fact that approximately nobody has taken up x32 suggests that the performance gain is not worth it in practice for most people and codebases.
Defining yet another 32-bit-on-64-bit x86 ABI would be even worse, because now everybody would have to support x32 for the niche users who are still using that, plus your new 32-bit ABI as well.
But that maintenance burden has been paid off for things like 64-bit time_t on 32-bit ABI's. One couod argue that this changes the calculus of whether it's worth it to deprecate the old x32 (as has been proposed already) but also propose more general "ABI-like" ways of letting a process only deal with a limited range of virtual address space, be that 32-bit, 48-bit or whatever - which is, arguably, where most of the gain in "x32" is.
If you read the LKML thread with Linus' rant, you would know that big endian ARM* is a problematic part of the Linux kernel that the maintainers are removing due to lack of testing let alone receiving bug fixes. It's also implied that big endian causes problems elsewhere beyond ARM, but no examples are given.
Later on in the thread, Linus states that he has no problem with historically Big Endian architectures, it's just that nothing new should be added for absolutely no reason.
*ARMv3+ is bi endian, but only for data, all instructions are little endian.
The fact that essentially every major ISA except x86 has support for big-endian including the two latest big-name entries (ARMv8 and RISC-V) contradicts this assertion.
Most importantly, big-endian numbers have overwhelmingly won the human factor. If I write 4567, you don't interpret it as 7 thousand 6 hundred and fifty-four. Even an "inverted big-endian" (writing the entire sequence backward rather than partially forward and partially backward like little endian) makes more sense and would be much more at home with right-to-left readers more than little endian too.
Is that so? I know that MIPS used to be very popular for embedded devices, especially routers and switches, but it seems that everything has moved to ARM.
I think assembly is probably a pretty bad choice for a RISC-V emulator. It's not portable, a nightmare to maintain, and not even as fast as binary translation.
What kind of performance do you get?
I guess it would be a great way to learn about the differences between x86 and RISC-V though!
I am not looking for performance (it will run natively on rv64 hardware), I am looking to protect the code against computer language syntax/compiler planned obsolescence (usually cycles of 5-10 years).
Have a look a little bit below in the comments where I give a reference to another one, written by the creator of ffmpeg and q-emu.
Honestly, assembly language bitrots far faster than other programming languages. In my lifetime, the only thing that really comes close to qualifying as "compiler language syntax/compiler planned obsolescence" is Python 2 to Python 3. In contrast, with x86 alone, there's three separate generations of assembly language to go through in that same timeframe.
Look, I work on compilers, and I have no idea what you're even trying to refer with "planned obsolescence" here.
And 5/10 years is a very short time in compiler development planning! Prototypeless-functions in C were deprecated for longer than some committee members were alive before they removed from the standard, and they will remain supported in C compilers probably for longer than I myself will be alive.
// Chromium uses a minimum timer interval of 4ms. We'd like to go
// lower; however, there are poorly coded websites out there which do
// create CPU-spinning loops. Using 4ms prevents the CPU from
// spinning too busily and provides a balance between CPU spinning and
// the smallest possible interval timer.
At the time at least the 4ms only kicks in after 5 levels of nesting, as mentioned in the article, but there was still a 1ms limit before that.
Seems like it has been removed though based on jayflux's comment.
I have a lot of experience with pairwise testing so I can explain this.
The reason there isn't an "equal" option is because it's impossible to calibrate. How close do the two options have to be before the average person considers them "equal"? You can't really say.
The other problem is when two things are very close, if you provide an "equal" option you lose the very slight preference information. One test I did was getting people to say which of two greyscale colours is lighter. With enough comparisons you can easily get the correct ordering even down to 8 bits (i.e. people can distinguish 0x808080 and 0x818181), but they really look the same if you just look at a pair of them (unless they are directly adjacent, which wasn't the case in my test).
The "polluted by randomness" issue isn't a problem with sufficient comparisons because you show the things in a random order so it eventually gets cancelled out. Imagine throwing a very slightly weighted coin; it's mostly random but with enough throws you can see the bias.
...
On the other hand, 16 comparisons isn't very many at all, and also I did implement an ad-hoc "they look the same" option for my tests and it did actually perform significantly better, even if it isn't quite as mathematically rigorous.
Also player skill ranking systems like Elo or TrueSkill have to deal with draws (in games that allow them), and really most of these ranking algorithms are totally ad-hoc anyway (e.g. why does Bradley-Terry use a sigmoid model?), so it's not really a big deal to add more ad-hocness into your model.
Ordering isn't necessarily the most valuable signal to rank models where much stronger degrees of preference between some of the answers exist though. "I don't mind either of these answers but I do have a clear preference for this one" is sometimes a more valuable signal than a forced choice". And A model x which is consistently subtly preferred to model y in the common case where both models yield acceptable outputs but manages to be universally disfavoured for being wrong or bad more often is going to be a worse model for most use cases.
Also depends what the pairwise comparisons are measuring of course. If it's shades of grey, is the statistical preference identifying a small fraction of the public that's able to discern a subtle mismatch in shading between adjacent boxes, or is it purely subjective colour preference confounded by far greater variation in monitor output? If it's LLM responses, I wonder whether regular LLM users have subtle biases against recognisable phrasing quirks of well-known models which aren't necessarily more prominent or less appropriate than the less familiar phrasing quirks of a less-familiar model. Heavy use of em-dashes, "not x but y" constructions and bullet points were perceived as clear, well-structured communication before they were seen as stereotypical, artificial AI responses.
It's a really nice language - especially the lightweight dependent types. Basically it has dependent types for integers and bit-vector lengths so you can have some really nice guarantees. E.g. in this example https://github.com/Timmmm/sail_demo/blob/master/src/079_page...
we have this function type
Which basically means it returns a tuple of 2 integers, and they must sum to the input integer. The type system knows this. Then when we do this:
let (width0, width1) = splitAccessWidths(vaddr, width);
let val0 = mem_read_contiguous(paddr0, width0);
let val1 = mem_read_contiguous(paddr1, width1);
val1 @ val0
The type system knows that `length(val0) + length(val1) == width`. When you concatenate them (@ is bit-vector concatenation; wouldn't have been my choice but it's heavily OCaml-inspired), the type system knows `length(val1 @ val0) == width`.
If you make a mistake and do `val1 @ val1` for example you'll get a type error.
The type `val count_ones : forall 'n, 'n >= 0. (bits('n)) -> range(0, 'n)` means that it's generic over any length of bit vector and the return type is an integer from 0 to the length of the bit vector.
I added it to Godbolt (slightly old version though) so you can try it out there.
It's not a general purpose language so it's really only useful for modelling hardware.
I see the RISC-V Sail repo mentions compiling to SystemVerilog. That would be amazing, if you could specify instruction semantics and have that transformed all the way into silicon.
It says they intend the backend to be used for FEC, so it's probably an untimed model that it outputs. Sail would probably generate bad SystemVerilog anyway even if it was timed/synthesizable because the language doesn't have any concepts to express things like pipelining or the necessary concurrency primitives or properly structured modules for the output netlists.
If you want a high-level RTL with some of the dependent features of Sail, but for hardware that can generate good SystemVerilog, I think Bluespec is probably the best complement.
It's still kind of experimental. Also the purpose is more for formal verification against a real design. The RISC-V model doesn't have any microarchitectural features you'd need for a real chip - not even pipelining - so it would be very slow.
Still... it is tantalisingly close to a really nice HDL for design purposes. I have considered trying to make a pipelined RISC-V chip in Sail with all the caches, branch predictor etc.
One feature that makes it a little awkward though is that there isn't really anything like a class or a SV module that you can reuse. If you want to have N of anything you pretty much have to copy & paste it N times.
I wonder if anyone will ever make something like this but where all the software has to be relocatable (no hard-coded absolute paths).
I guess a lot of software would be excluded or need patching, but I think it would be useful and maybe it would encourage Linux developers to stop bloody hard-coding absolute paths like it's still the 90s!