Making Smalltalk On A Raspberry Pi

Today, you probably don’t think much about object-oriented programming, it’s just part of the landscape. But decades ago, it was strange and obscure technology. While there were several languages that led up to the current object-oriented tools we use today, one of the most influential was Xerox PARC’s Smalltalk language. [Michael Engel] took a C++ implementation of the Smalltalk VM, some byte code for a complete Smalltalk system, a Raspberry Pi “bare metal” library, and produced a Smalltalk workstation running on a bare Raspberry Pi — even a Pi Zero. The code is on GitHub and is admittedly a work in progress.

Smalltalk was interesting — and sometimes annoying — because everything was an object. Literally everything. The system took over the entire machine. It provided the GUI, the compiler, and the run time libraries. That’s probably why it was easy for [Michael] to forego the usual Linux OS for his project.

If you don’t want to use a spare flash card to boot into the system, there are Smalltalk 80 versions that run on normal operating systems. The tutorial in that program’s user manual might be helpful to you if you haven’t done Smalltalk before.

Even a lowly integer is a full-scale object in Smalltalk. When you say “3+2” you are actually saying, in Smalltalk, that you have an integer object with value 3 that receives a + message with an integer argument of 2. If you were trying to wrap your mind around object-oriented principles, this was very confusing, although with a few decades of hindsight, it makes more sense. Smalltalk also did a lot to popularize the model/view/controller design for graphical user interface software.

We’ve looked at object-oriented for state machines before, which is a nice use case. If you want to see just how well PARC predicted the future, check out the mother of all demos.

33 thoughts on “Making Smalltalk On A Raspberry Pi

  1. While it’s true everything *behaves* like an object in Smalltalk, there are actually “primitives” that handle certain low-level details (like integer math), rather than actually sending math messages to Integer objects. But you still can “extend” Integers with behaviour you can add, so you can make an Integer understand new messages.

    I think Smalltalk is the perfect system for embedded processing. I worked at Tektronix when their TDS line of oscilloscopes all ran Smalltalk internally!

    1. I used a Squeak variant to test hardware and prototype UI ideas. Ran like a champ on bare bones 68000, PPC & ARM boards.

      The only critique I have of using Squeak out of the box is it’s original VM used polling for I/O. I’m sure there are VMs by now that don’t have this problem. And message dispatch is a bit of a pig in terms of power usage. But again, a properly JITted Smalltalk VM should be fine. I would LOVE IT if Smalltalk or Self became popular for embedded work.

      1. The current Squeak VM uses some polling for input becuase it is simple enough and functional enough and nobody has had the urge and/or time to change it – at least not since the last major change that I did around 2000. It really isn’t much of an issue.

        There have been some experiments in Squeak on linux to do actual signal driven input but one has to remember that at the time most PCs couldn’t do anything but poll – think back to Windows 95, Mac OS 9 etc.
        Older Smalltalk systems running bare-metal (or nearly) have used low to very low level input interfaces. The Active Book system Eliot & I worked on in the mid-80’s handled device hardware signals to get keypresses etc. It’s not magic or even difficult in principle; but it is very tricky to make portable.

        And as for “a properly JITted Smalltalk VM should be fine” – we’ve been doing that for a while y’know. Like *1984*. For Squeak the VM has been using dynaminc translation (sorry, ‘JITTED’ for the newcomers that think java invented anything useful) for 10 years now. To give you some idea of how not-a-problem the use of message sending semantics is when done properly, consider that on a Rapsberry Pi 4 the current Squeak system can do somewhere around 60 million send/returns per second, per core. Thats ~33 instruction cylces per. Compare that to the actual instructons required to do a function call, stack frame setup, reload registers and return. It’s not a huge multiplier and you get so much more functionality out of a message sned.

        1. There were a number of Smalltalk-specific designs proposed and built, including the SOAR (Smalltalk on a RISC) at Berkeley, see http://basis.desk.org:8080/ToytownStLab/Smalltalk+hardware and http://www.merlintec.com/swiki/hardware/26.html

          Urs Hölzle and David Ungar analyzed the benefits of specialized hardware to implement object-oriented languages for SELF, a Smalltalk-inspired language at Sun, in 1994. They found out that many specialized hardware features, such as tagged arithmetic instructions, only showed minimal benefits and one of the critical parts of the runtime implementation, the message dispatch overhead, could hardly be reduced: https://cs.ucsb.edu/research/tech-reports/1994-21

        2. There were chips for Forth? It seems silly to me, knowing that the inner loop is just two instructions on the 6809 (I built one back in the mid ’80s), and probably any decent 16-bitter too. After that there isn’t much to squeeze out of all the indirect fetches; better to have a real instruction set that you can compile native code for.

    2. Thanks for the clarification about the primitives, Jan! I think you can actually get by with a very small number of primitives, but implementing features such as integer arithmetic in Smalltalk code itself would make the system unbearably slow, so they are used as shortcuts to improve the system performance. The Smalltalk “green book” (available at http://stephane.ducasse.free.fr/FreeBooks/) has a number of interesting articles on early Smalltalk versions, including the one for Tektronix’ 68k-based workstations and one for the Apple Lisa/Mac. In addition, the “blue book” includes a description of the VM implementation, itself written in Smalltalk, from which many of the Smalltalk-80 implementations are derived.

      I found it fascinating to see how much Smalltalk-80 can do with so (relatively) little resources. Coming from an embedded/C/Unix background myself, I think it’s an eye-opening experience. Maybe we should have a bit more Smalltalk in our systems – see also Stephen Kell’s research in that area: https://www.cs.kent.ac.uk/people/staff/srk21/

      1. Smalltalk’s a very, very small language. The language itself has almost no semantics other than expression evaluation and method class — it doesn’t even have conditionals. This makes the interpreter absolutely miniscule. Timothy Budd’s A Little Smalltalk implements a Smalltalk subset in 2-3 kloc of C. Everything else is in Smalltalk itself. The object model is also very close to the bare metal: despite being a really high level language, there’s very little abstraction between you and the CPU, which most high level languages don’t have. It’s remarkable just how much it can do with so little. Smalltalk’s also intended from the ground up for dynamic modification. You don’t run programs, you work with an entire mutable system which you change on the fly to meet your needs. It’s very much like Forth, that way.

        A little while ago I did an experiment in a mutable system blatantly based on Smalltalk but using browser technologies. It worked stunningly well: https://www.youtube.com/watch?v=JDunc6Cr7YQ I’d love to see some of these qualities in embedded systems.

  2. Though technically, The Mother of All Demos was SRI, not PARC.

    Sorry to be “that person” that quibbles about such stuff, but PARC invented enough of the future I don’t think they’ll be jealous of the Engelbart’s lab at SRI to get credit for the work they did putting the demo together.

  3. Author here – this started off as a nice hacking project for a rainy weekend to see how much effort it would take and has grown a bit bigger… and it has been lots of fun so far.

    This Smalltalk port is part of a project to investigate the usefulness of a class of systems for applications in areas such as the IoT (together with my students here at NTNU). These systems – we are also looking at Plan9, Inferno and Oberon – fill the niche between a typical RTOS (dozens of kB memory, some MIPS computing power) and full-featured Linux/BSD systems, which nowadays have significantly higher resource requirements. The Raspberry port was just a stopgap, my aim is to provide ports to RISC V based (FPGA) systems with the vision of creating a system that a single person can understand in a reasonable amount of time – so open source for hard- and software and low code complexity are important here. One inspiration for this is Nikaus Wirth’s Project Oberon (http://www.projectoberon.com/).

    Beware, the current version of the Raspberry Smalltalk port is still in an early stage and certainly will have a number of bugs. I found out last night that the current version I put on github crashes when using floating-point operations on the original Raspberry Pi SoC (BCM2835 in the Raspberry Pi 1/Zero/CM1), probably related to some changes in the compiler setup I made. This is probably related to differenced in ARM ABIs for hard/soft FP. Oh, and please don’t be too harsh on my horrible C++ coding skills ;-).

    Also you should probably be aware of the limited usefulness of a Smalltalk-80 system. It is currently restricted to the features of the original Smalltalk-80, so there is no networking or sound, the VM is 16 bit-based (so the amount of usable memory is around 1 MB), and you are restricted to im/exporting files to the FAT partition of the Raspberry’s SD card. However, it’s perfectly fine to explore the system and develop code, so there’s perhaps some use to it apart from the pure hacking value.

    I’d be happy if you decide to give the system a try. Please feel free to get in touch with questions and comments.

  4. One feature of Smalltalk-80 worth noting is the so-called “symbol”, which is a name that stands on its own with no associated value, for example #bill or #employment.

    One could claim that what is now called a “hashtag” was invented at Xerox PARC in 1989 as part of this language.

    Note also the use of the MVC paradigm, which to this day lives on in modern software.

    1. Smalltalk symbols are what most other languages (like Lisp) call an atom, or Java calls an interned string. It’s just an label which is guaranteed to have a unique value, so that you can do cheap comparisons based on pointer. Smalltalk uses them for class and method names to make lookups cheap.

          1. Most definitely! I assume the explicit return, in the middle of closure, returns from the closure with the value returned. This is the case whether the enclosing method has exited or not. The closure is an executable chunk of code that may return. The activating method is the method in which we need the return value. So it sends #value to the block and grabs the return value for it to continue.

          2. (Weird, I can’t reply to rwithers68’s comment…)

            Smalltalk had two ways to return from blocks: a bare expression (`[ 1 ]`) caused the block to return a value in the normal way, but you could also do `[ ^1 ]`. This caused the enclosing method to return, with any stack frames between the method and the block to be discarded. I’m not actually sure how it was implemented, but as Smalltalk stack frames were heap-allocated objects I think it just did a longjmp into the enclosing block and then let the garbage collector eat the stack frames once they weren’t referenced by the thread context. It was incredibly useful, allowing things like `a ifNil: [ ^’nil’ ]. ^(a toNumber)`. I wish modern programming languages had this.

          3. (And I cannot reply to David’s post)

            You are speaking of a closure created and #valued within an enclosing method, before the enclosing method has returned. In this case, the non-local return, inside a closure evaluating within the enclosing method that created it, becomes a return from the enclosing method. Yes and all intermediate frames, activating the closure, get discarded and that non-local return is the return value of the enclosing method.

            I was thinking of closures created in an enclosing method which either register the closure somewhere or return the closure. Such that this closure is evaluated after the enclosing method has returned. In this case there is no enclosing method to be returned from and the block simply returns the specified value, inside the block.

            I.E. In ParrotTalk, I build closures and install them as Thinks in a Stack, which are activated in turn.

            PTCipherThunkMaker>>#makeThunkOn: secretBytesHolder incoming: incoming

            | downCipher upCipher |
            downCipher := self cipherOnSecretBytes: (secretBytesHolder at: 1) incoming: incoming mode: #ENCRYPT.
            upCipher := self cipherOnSecretBytes: (secretBytesHolder at: 1) incoming: incoming mode: #DECRYPT.
            secretBytesHolder at: 1 put: nil.
            ivHolder := Array new: 1.

            [^ (Thunk new)
            upThunk: [:frame |
            upCipher hasVector ifTrue: [upCipher initialVector: frame header ivSequence].
            upCipher decrypt: frame payload ];
            headerMakerThunk: self headerMakerThunk;
            downThunk: [:frame | | chunk |
            ivHolder at: 1 put: (downCipher hasVector ifTrue: [downCipher vector copy] ifFalse:[#[]]).
            chunk := downCipher encrypt: frame asByteArray.
            downCipher vector: (chunk copyFrom: (chunk size – downCipher blockSize + 1) to: chunk size).
            chunk ];
            yourself]
            on: Exception
            do: [ :ex | self error: ‘protocol install failure: ‘, ex ].

            Here you can see the upThink and downThunk both are set with closures, which expect a frame argument. In this case the enclosing method returns and the saved closures are activated as traffic passes up and down the network stack.

          4. Oops, none of those closures has a non-local return. Still the non-local return is just like a method return from the activation of the closure. I don’t think that this is confusing. It is the prior case of non-local return inside the enclosing method which is the special case.

  5. Started my career in OOP using Smalltalk from PARC/XSIS and then ParcPlace when it hatched – one point of order – the MVC was supposed to turn into a full book on MVC, but Adele never managed to write it – instead she (and I think Peter) put out a 50 page paper entitled “The black art of MVC programming” – I had this paper once, and then I lost it. I’ve been searching to find it for years, but the only footprints left seem to be mine – I did hunt down Trygve Reenskaug but unfortunately he had lost his copy too :-( tl/dr – we discovered that MVC had a collection of nasty architectural problems and spent a lot of time whining about it, which is what motivated Dave Ungar to come up with costumes (Morphic)

    1. Pharo has a lot of potential, and the last six months have shown a lot of progress – that said, I keep using Squeak cause Pharo is still working on ‘getting there’

  6. Smalltalk can run on top of VMs designed for other dynamic languages. IT is NOT required a custom smalltalk VM any more.
    We use S8 Smalltalk to develop native iOS, android, raspberry pi and other execution contexts, on top of javascript and lua VMs.
    On the pi we can run on top of node.js or lua VMs.
    The interaction to native components is fast when executed and also for the developer to bind to.

    The major repository for S8 Smalltalk is at http://u8.smalltalking.net
    Facebook group https://www.facebook.com/groups/s8smalltalk

    The same S8 Smalltalk kernel runs on windows, iOS, android (native), pi, web, node.js; on each platform we have access to OS components with direct binding to native objects (NO primitives are required, but the S8 Compiler can use pragmas to extend funcionality on any execution machinery).
    S8 Smalltalk is a modern smalltalk; can use and provide objects at the native layers of execution; making possible to use and provide functionality to traditional technology.
    The application is an open system interacting with traditional technology, so you have the power of both worlds (an object system without borders).

    All the code is MIT licensed, and acccessible from U8 Service.
    The proposed mode of development is social software development (people and organizations sharing experiences while working on real solutions with virtual objects).

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.