8 ms·
A strange game. The only winning move is not to play. If you want memory safety guarantees from the compiler, don't use Forth. The paradigm in Forth is "corre
by astrobe_ 5y ago
A strange game. The only winning move is not to play.
If you want memory safety guarantees from the compiler, don't use Forth.
The paradigm in Forth is "correct by design", "fail-fast" and to fight complexity unguibus et rostro.
The paradigm in Forth is to increase the skills of the programmer rather than to increase the skills of the compiler. That's why it is a wonderful language for advanced training, IMO.
- kragen 5y agoThis is precisely the kind of self-aggrandizing macho nonsense I was criticizing in https://news.ycombinator.com/item?id=27159383 https://news.ycombinator.com/item?id=27159383. 1. What should you use if you want memory safety guarantees—whether because you're a novice, because you have better ways to spend your Saturday nights than disassembling core dumps, because you're building a platform for safely running untrusted code written by players who are competing against one another https://news.ycombinator.com/item?id=27160615 https://news.ycombinator.com/item?id=27160615, or because, given your own fallibility, you think it's the only way to be safe enough from ransomware gangs? Of course you're right that it's not ANS Forth or polyForth or machineForth, but what should it be? What's the lowest price in complexity we can get away with paying for that pearl? Surely we can do better than JavaScript. Or even PostScript. 2. You say the paradigm in Forth is "correct by design", "fail-fast" and to fight complexity uñas y rostro. Well, precisely what I want is to be fail-fast; standard Forth pointer arithmetic isn't, because indexing past the end of an array will just access some other object. That means that instead of failing fast it will usually produce incorrect results and possibly corrupt the data in that other object, producing errors downstream. To fail fast we need bounds-checking, and for bounds checking we need to distinguish pointers from integers. 3. You say, "The paradigm in Forth is to increase the skills of the programmer," which is to say, to be more difficult than necessary. You're free to make your Forth more difficult than necessary if you want, which is an enjoyable activity that will surely make you a better programmer, but that isn't what Forth was designed for. In fact it's close to the opposite of Forth's design principles, which are to fight complexity because complexity makes programming more difficult. That's why Forth programmers in the 01980s had IDEs with virtual memory, incremental recompilation, compile-time metaprogramming, reflection, and multithreading that fit on a floppy disk and could run in 64K of RAM. It's not because F-83 was written by superhumans; it's because Forth's design removed a lot of accidental complexity from the problems, allowing its implementors to focus on the essence. I mean, you're probably right that most people using Forth today are doing it, like Apollo, not because it is easy but because it is difficult. But that's not what Chuck Moore was looking for, it's not what Elizabeth Rather is looking for, and it's not what I'm looking for either.
- remexre 5y ago> because you're a novice, because you have better ways to spend your Saturday nights than disassembling core dumps My experience is that with a development methodology that emphasizes early-binding, incremental testing, and aggressive factoring, I just don't have the same kinds of memory issues I do in C. Strings having an explicit length helps a lot, and I suspect stack manipulation having a linear-types-like feeling helps somewhat too, since you think a lot more about "should this be removing this from the stack" than "should I be using this variable again some time, you know, later." > because you're building a platform for safely running untrusted code written by players who are competing against one another, or because, given your own fallibility, you think it's the only way to be safe enough from ransomware gangs My aarch64 Forth runs under QEMU, and this is easy to set up, and wiring a PL011 to stdio works great. If you're just trying to avoid buffer overruns, though (which I suppose meets the latter case but not really the former), I'd consider using a 64-bit Forth with 32-bit pointers and 32-bit offsets, storing the size of the allocation in the first word before the pointer, so you can check the offset in a special TAGGED!/TAGGED@. I suspect you'd still want the normal !/@ for speed, and implement TAGGED!/TAGGED@ in terms of them, though? Perhaps the MTE extensions would work, but I haven't investigated them (I don't have a laptop whose CPU that supports them yet).
- kragen 5y ago> I just don't have the same kinds of memory issues I do in C. Interesting! I definitely haven't reached that level—in Forth I have the same kinds of memory problems I have in C, and also parameter-passing bugs and type errors. Is there a codebase you think of as exemplary that I could learn from? Or, maybe better, a screencast of somebody writing some software in the development style? (Interactive testing of new words doesn't leave development artifacts, after all.) "Much better than C" might still be pretty bad compared to JS, Rust, or OCaml; have you tried them? > My aarch64 Forth runs under QEMU, and this is easy to set up, and wiring a [UART] to stdio works great. This is definitely a viable approach that can work in many circumstances! However, each trust domain in QEMU uses 16 megabytes of RAM and roughly a million microseconds of CPU time to start up and shut down, and if you're doing cross-architecture emulation so you can't use kvm, the CPU overhead is typically on the order of 300%. By contrast, each trust domain in Lucet takes about 16 kilobytes of RAM and 50 microseconds of CPU time to start up and shut down https://www.fastly.com/blog/lucet-performance-and-lifecycle https://www.fastly.com/blog/lucet-performance-and-lifecycle, and a new trust domain in a language that implements object-capability security could conceivably take 16 bytes of RAM and 0.1 microseconds. In both cases you can usually come within a few percent of native-code performance—which probably isn't in the cards if every memory access is preceded by a bounds check, as in Valgrind (typically 1000% CPU overhead) or this pseudo-Forth approach. Communications round-trip time might be 1 μs per ping-pong in QEMU and 0.1 μs in the other cases. So, there are cases where a secure-compiler approach can give you 3–6 orders of magnitude better performance than just using QEMU. If you have a multiplayer game where each player uploads code, you might be able to support a few hundred players on a PC with the QEMU approach, or a few hundred thousand players with the Lucet approach. (Not, probably, all connected over TCP at once.) > consider using a 64-bit Forth with 32-bit pointers and 32-bit offsets An interesting idea here is to use a 64-bit system with 64-bit pointers and 32-bit offsets, mapping each allocation 4 gibibytes and change apart. Basically using the high 32 bits of the pointer as an MTE address tag or segment ID. Bloated and probably slow, though, and of course the bounds checking isn't very precise—enough to ensure that you can't index into another allocation, but not enough to catch every off-by-one array indexing bug. There used to be a fat-pointer bounds-checking version of GCC that replaced pointers with essentially Golang slices, and you could of course implement that kind of scheme on top of a standard Forth, much as you suggest with TAGGED@.