An attempt to articulate Forth's practical strengths and eternal usefulness
Posted by todsacerdoti 7 days ago
Comments
Comment by ofalkaed 13 hours ago
What I see as the real strength of Forth is that if you write your program in source files, there is no abstraction. You stick your word definitions in those source files and let the application you are writing dictate the words you define instead of relying on the dictionary you built up on REPL and things quickly start becoming easy and the code remains readable. It might seem like a lot of work and endlessly reinventing the wheel but if you start with a featureful Forth like gforth it does not take that much time or effort once you have the basics down; you can build complex applications with gforth as easily as you can build up a full Forth with that minimal Forth that fits in your boot sector.
The main thing is learning that application development with Forth is top down design and bottom up writing. You break the application up into its major functions, break those into smaller functions, break those into words and then break those words into those simple sort words that everyone in the Forth community says you should write. Then you start writing code. I am just starting to get the hang of Forth and it is surprisingly quick and powerful once you start getting the sense of it.
Comment by kunley 1 hour ago
Comment by chuckadams 30 minutes ago
Comment by xelxebar 10 hours ago
Also, you're excited by Forth and Lisp, you might like Forsp[1]. It uses a call-by-push-value evaluation strategy in a way that really makes the language feel like a true hybrid of the two. The implementation is also brilliantly simple.
Anyway, thank you for the article. I lurk on the DuskOS mailing list and wish I could find out where the Forthers gather IRL so I could osmote more of their world into my own.
Comment by binary132 1 hour ago
Comment by ebiederm 10 hours ago
The early B compiler was reported to generate threaded code (like Forth). The threaded code was abandoned fairly early in the port to the PDP11 from the PDP7 as it was deemed to slow to write an operating system in.
At which point unix and C lost a very interesting size optimization. With the net result that Forth was more portable and portable between machines circa 1970 and Unix had to wait until circa 1975 with UnixV6.
I have been trying to go back through the history to see if I could understand why threaded code was deemed too slow. Today the most part of executables is code that is not run often and would probably benefit from a smaller representation (less memory and cache space making the system faster overall). So this is a practical question even today.
I found a copy of unix for the PDP7. Reproduced from old printouts typed in. If I have read the assembly correctly the B compiler was not using an efficient form of threaded code at all.
The PDP7 is an interesting machine. It's cells were 18 bits wide. The adress bus was 12bits wide. Which meant there was room for an opcode and a full address in every cell.
As I read the B compiler it was using a form of token threading with everything packed into a single 18 bit cell. The basic operations of B were tokens and an if token encoded with a full address in the cell. Every token had to be decoded via a jump table, the address of the target code was then plugged into a jump instruction which was immediately run.
Given the width of the cells, I wonder what the conclusions about performance of B would have been if subroutine threading or a similar technique using jmp instructions would have been.
Does anyone know if Forth suffers measurably in inner loops from have to call words that perform basic operations?
Is this where a Forth programmer would be accustomed to write the inner loop in assembly to avoid the performance penalty?
Comment by rerdavies 6 hours ago
The overhead of threading seems pretty obvious: call and return instructions are expensive compared to the cost of the one equivalent instruction that would have been executed in a compiled implementation. And placing arguments on a stack means that all operands have to to be read from and written to memory, incurring additional ferocious overhead, whereas a compiler would enregister values, particularly in performance-critical code. Not unreasonable to expect that Forth code is going to run at least an order of magnitude slower than compiled code.
Comment by Someone 4 hours ago
- returns can run in as good as zero time
- when calling a word, the CPU could prefetch the cache line containing the next word to be called, on the assumption that it would be called soon (an assumption that would be correct for many “almost leaf” calls)
- the top of the stack could be kept in register-speed memory.
For an example, see https://users.ece.cmu.edu/~koopman/stack_computers/sec4_4.ht...:
“The internal structure of the NC4016 is designed for single clock cycle instruction execution. All primitive operations except memory fetch, memory store, and long literal fetch execute in a single clock cycle. This requires many more on-chip interconnection paths than are present on the Canonical Stack Machine, but provides much better performance.
[…]
The NC4016 subroutine return bit allows combining a subroutine return with other instructions in a similar manner. This results in most subroutine exit instructions executing "for free" in combination with other instructions. An optimization that is performed by NC4016 compilers is tail-end recursion elimination. Tail-end recursion elimination involves replacing a subroutine call/subroutine exit instruction pair by an unconditional branch to the subroutine that would have been called.”
(¿Almost?) all modern hardware is designed for other language paradigms, though, so these won’t help with hardware you can buy.
Comment by jacquesm 9 hours ago
Which was still incredibly fast for the day, given that Forth was compiled to an intermediary format with the Forth interpreter acting as a very primitive virtual machine. This interpretation step had considerable overhead, especially in inner loops with few instructions the overhead would be massive. For every one instruction doing actual work you'd have a whole slew of them assigned to bookkeeping and stack management. What in C would compile to a few machine instructions (which a competent assembly programmer of the time would be able to significantly improve upon) would result in endless calls to lower and lower levels.
There were later Forth implementations that improved on this by compiling to native code but I never had access to those when I was still doing this.
For a lark I wrote a Forth in C rather than bootstrapping it through assembly and it performed quite well, Forth is ridiculously easy to bring up, it is essentially a few afternoons work to go from zero to highway speeds on a brand new board that you have a compiler for. Which is one of the reasons it is still a favorite for initial board bring-up.
One area where Forth usually beat out C by a comfortable margin was code size, Forth code tends to be extremely compact (and devoid of any luxury). On even the smallest micro controllers (8051 for instance, and later, MicroChip and such) you could get real work done in Forth.
Comment by forgotpwd16 4 hours ago
Seems people have/are more fun/interested implementing Forth interpreters/compilers than actually using Forth. Same with CHIP-8. It's all about making emulators.
Comment by DonHopkins 4 hours ago
Here's some of Mitch Bradley's beautiful code from OpenFirmware, his Forth kernel meta-compiler written in Forth, which supports 8, 16, 32, an 64 bit, big-endian and little-endian architectures, as well as direct, indirect, and token threaded code, with or without headers, etc:
kernel.fth: https://github.com/MitchBradley/openfirmware/blob/master/for...
metacompile.fth: https://github.com/MitchBradley/openfirmware/blob/master/for...
The OpenFirmware kernel is a Forth meta-compiler, which can compile itself on any architecture, and also cross-compile for different target architectures.
It has cross-architecture extensions to FORTH (like \16 \32 comments and /n /n* generically typed words) that make it possible to write platform, word size, byte order, and threading independent code, and compile images (stripped or with headers) for embedded systems (like the OLPC boot ROMs) and new CPU architectures (like Sun's transition from 68K to SPARC), and share code with a more powerful development environments.
Comment by aperrien 10 hours ago
Comment by throwaway81523 6 hours ago
Comment by bvrmn 6 hours ago
Code as structure could be more conveniently expressed as language data structures as structure nowdays.
Comment by Joker_vD 6 hours ago
Besides, once a C compiler is written for one platform, porting it to another one takes significantly less time than writing from scratch (especially if the compiler is written with portability in mind).
Comment by danparsonson 59 minutes ago
I didn't calculate it but if you're just dividing one number by the other then you're assuming the final code arrived fully-formed in one go - don't forget about refactoring, debugging, testing, etc. etc.
Comment by ErroneousBosh 5 hours ago
Why would you think that's different for any other language, like oh for example Forth?
Comment by Joker_vD 5 hours ago
Comment by kragen 12 hours ago
Comment by ofalkaed 10 hours ago
Howo? Or would you agree that value is perhaps a more suitable word than importance? For me I think these articles have such a tendency to fixate on the strengths of Forth to the extent that they have reduced Forth to those strengths in the eyes of many. TFA does a fair job of avoiding this and shows Forth more as a powerful and flexible general purpose language than a very niche language, but I think it still focuses a bit much on the strengths at cost of the general.
You are a Forth expert compared to me and probably most of HN, so try and keep that in mind with your response if you could.
Edit: I am probably asking for insight into your workflow with Forth, but maybe not?
Comment by Surac 7 hours ago
Comment by graboid 7 hours ago
Comment by optimalsolver 6 hours ago
It has the most charming illustrations I've ever seen in a text book.
Comment by vdupras 11 hours ago
Fairly counting SLOC is a tricky problem, but my count is 1119 lines of code for the C compiler (written in Forth of course), that's less than 8x the count of chibicc, described as the smallest.
Comment by fallat 9 hours ago
note the point of that section was really that anyone using gcc or clang should ack. the real cost when using them.
Comment by kragen 11 hours ago
Comment by entaloneralie 11 hours ago
https://git.sr.ht/~vdupras/duskos/tree/master/item/fs/doc/co...
Comment by kragen 11 hours ago
Comment by entaloneralie 11 hours ago
Comment by NooneAtAll3 10 hours ago
is shortcutting different from short circuiting?
Comment by vdupras 2 hours ago
Comment by norir 10 hours ago
Comment by fuhsnn 10 hours ago
Comment by vdupras 11 hours ago
So, Dusk's compiler is not apple-to-apple comparable to the other, but comparable enough to give a ballpark idea that its code density compares very, very favorably.
Comment by kragen 11 hours ago
Comment by vdupras 11 hours ago
Comment by kragen 11 hours ago
Comment by entaloneralie 11 hours ago
Comment by anthk 3 hours ago
https://howerj.github.io/subleq.htm
https://howerj.github.io/subleq.htm
The same, but multiplexing instructions (it runs much faster):