Why the VM was so fast
Strongtalk's speed did not come from its types. It came from watching the program run.
Every value is a tagged word
In the 32-bit VM every value fits in one machine word. The low two bits say what it is: 00
for a SmallInteger, whose value is the other 30 bits, and 01 for a pointer to an object in the
heap. So the range of a SmallInteger is −536,870,912 to 536,870,911; anything bigger becomes a
LargeInteger, automatically and invisibly. Adding two SmallIntegers is a couple of machine instructions,
with no memory allocated.
Primitives with failure codes
Basic operations — arithmetic, indexing, creating objects — are primitives written inside the VM. Smalltalk code calls them with a special syntax and says what to do if they fail:
+ other <Number> ^<Number>
^{{self primitiveAdd: other
ifFail: [:err <Symbol> | self retry: #+ coercing: other]}}
A primitive that cannot do its job answers a symbol such as #SmallIntegerOverflow,
#DivisionByZero, #OutOfBounds or #FirstArgumentHasWrongType — the VM
defines 136 of them — and the Smalltalk code decides what happens next, for example redoing the sum with
LargeIntegers.
Interpret first, then optimize what matters
New code starts in a fast interpreter. Every message send site keeps an inline cache recording the classes of the objects it has seen and the method each one used. Counters notice methods that run often. When a method gets hot, the optimizing compiler reads those caches — the type feedback — and compiles specialised machine code: it inlines the methods actually used, replaces sends with direct calls and keeps the common case on a fast path, with a check that falls back if a surprise turns up.
The compiler also recognises the core control messages — ifTrue:, ifFalse:,
and:, or:, whileTrue:, to:by:do: and value — and
turns them into plain jumps when their arguments are literal blocks.
Deoptimization
Optimized code makes assumptions. When one stops holding — a class is redefined, a method changed — the VM turns optimized activations back into ordinary ones and carries on. That is what let Strongtalk keep Smalltalk's live, change-anything development style while running fast.
Why the types weren't used for speed
It sounds natural to use declared types to generate faster code, but Strongtalk deliberately didn't. Types are optional and describe protocols, not classes, so they would rarely say exactly which method will run. Run-time feedback says precisely that. Keeping the two separate meant the types could stay purely about correctness — and the same ideas could be carried into Java, where they became HotSpot.
This site's edition keeps the tagged-integer range, the primitives and their failure codes, and inlines the same control messages; it runs bytecode in an interpreter rather than generating machine code. See how this edition was made.