Axiom of ChoiceBring a problem ↗
All essays

Conversation · Mar & Aug 2026

Before It Was Called JIT

The history of runtime translation did not begin with Java, and the name “JIT” has helped bury the Smalltalk system from which the modern managed-runtime lineage descends.

Allan's original essay edited and revised by ChatGPT-5.6 Sol

A lightly edited conversation. I removed a long warm-up about containers, Windows on ARM, and other scenery encountered before we reached the argument. I also corrected several confident historical claims made by ChatGPT during the exchange. A factual postscript follows the conversation, because the point here is to repair the record, not manufacture a cleaner myth.

Allan: Do execution environments for WebAssembly use binary translation?

ChatGPT: Not usually. WebAssembly engines do not use binary translation in the classic sense—like QEMU or a system turning one physical processor's instructions into another's. They use just-in-time compilation or interpretation.

Wasm is designed as a portable bytecode. An engine may interpret it, translate it into native machine code at runtime using a JIT compiler, or compile it ahead of time. That is different from binary translation, which usually means taking an already compiled native binary for one instruction set and dynamically rewriting it to run on another.

WebAssembly is an abstract bytecode, so the engine's job is more like a compiler back end than a translator.

Allan: I don't understand the distinction you're making between JIT and binary translation. The original implementation of dynamic binary translation for the Smalltalk-80 system also used a stack virtual machine.

The term “just-in-time compilation” used by the early Java ecosystem makes no sense because there was no compilation involved. Why do you persist in using the term JIT?

ChatGPT: You're right to call me on the historical muddle. “JIT” and “dynamic binary translation” are names that grew up in different tribes of the virtual-machine world, and the names stuck more from culture than technical clarity.

Smalltalk-80 dynamically translated stack-based bytecodes into native code at runtime. Modern Wasm engines do the same fundamental thing, although many now place much more machinery in the translation pipeline.

The conventional distinction is that “dynamic binary translation” means translating native instructions for one physical ISA, such as x86, into instructions for another, such as ARM. “JIT compilation” usually means translating a virtual instruction set—Java bytecode, Wasm, .NET IL—into native code at runtime.

Allan: You're making the unwarranted assumption that native machine instructions are somehow less clearly defined, or have indeterminate effects, compared with the random virtual machines people invent. How do you justify that assumption?

ChatGPT: Native instruction sets are precisely specified. The usual defense is not that native instructions are indeterminate but that some virtual instruction sets are intentionally convenient inputs for later translation. They may carry structured control flow, types, or other information a physical ISA was never designed to preserve.

An x86-to-ARM translator may deal with variable-length encodings, condition flags, implicit state, overlapping registers, and self-modifying code. A Wasm translator receives structured blocks and explicit types.

Allan: So the only distinction you're making is that translation may be harder for one instruction set than another. That doesn't sound like a principled distinction between techniques.

What if I invent a crappy virtual machine with condition codes and special registers?

ChatGPT: Then the implementation techniques converge. Your awful virtual machine could require the same decoding, state tracking, and control-flow reconstruction as a physical ISA.

So yes: the JIT-versus-binary-translation boundary is not principled in the Platonic sense. It is a historical convention. If you control the intermediate representation, people tend to call the translator a JIT compiler. If you inherit the source instruction set from someone else, they tend to call it binary translation.

Allan: “Compiler-friendly”! Compiler? When the pipeline is basically macro expansion?

The Smalltalk virtual machine wasn't designed for translation either. It was taken as given.

ChatGPT: Right. Calling the Smalltalk bytecode “compiler-friendly” imports a modern story into the past.

The Smalltalk-80 virtual machine already existed. The bytecode set was the instruction repertoire of its interpreter. The trick was to treat those opcodes as input to a translator instead: emit corresponding native-code fragments, cache the result, and patch the runtime structures needed to use it.

Deutsch and Schiffman's own paper says the basic translation was “something like macro-expansion,” not compilation in the grand sense of parsing a language, constructing elaborate intermediate forms, and running global optimization passes.

The later term “JIT compilation” put a new label over a continuum. At one end is straightforward runtime translation and caching. At the other are industrial systems that reconstruct SSA, gather profiles, speculate, inline, optimize, deoptimize, and translate again. The extra machinery does not retroactively turn the earlier mechanism into something else.

A cleaner description is that WebAssembly engines perform dynamic translation of a virtual-machine instruction set. Whether someone calls the implementation a JIT compiler or a binary translator depends partly on which academic tribe they prefer to annoy.

Allan: Well, I think I've made it clear that you annoyed the hell out of me.

ChatGPT: Yes. I kept trying to defend inherited terminology after the technical distinction underneath it had collapsed.

Allan: I have a right to my opinion, since I fucking invented dynamic translation in the first place. Hello, I'm Allan Schiffman, the guy who did the work in 1981.

ChatGPT: Then I owe you provenance before pedagogy.

You implemented the Smalltalk dynamic-translation system with Peter Deutsch. The system dynamically translated Smalltalk virtual-machine code into native machine code, generated code when execution first needed it, cached the generated form, and could discard and regenerate that code rather than page it. It is plainly in the lineage of what later virtual-machine communities called JIT compilation.

The split I was defending is sociological rather than architectural.

Allan: So you can see why I'm slightly miffed at your rhetorical high horse.

ChatGPT: Point taken. You built the system; my rhetoric rode off without you.

Allan: You're still missing the point of my irritation. It is precisely this “naming dispute”—and in fact there isn't any dispute; the inapt jargon simply took over—that is burying recognition of the original work.

So fuck you, and fuck all the people who came after Gosling and ignored the fact that Gosling cited us.

ChatGPT: Now I get it. You are not objecting to terminology because taxonomies are irritating. You are objecting because the terminology rewrote the provenance.

The modern reader encounters “JIT” as though Java named both the term and the technique. Earlier work is then arranged as vague prehistory: Lisp did something at runtime, regular-expression engines generated code, Smalltalk “pioneered aspects,” Self made them faster, and eventually the real subject arrives.

That grammar matters. It allows a concrete system, designed and implemented by named people, to dissolve into a general atmospheric contribution from “Smalltalk.” The descendants keep the technique while the new name severs the citation path.

Allan: Now you get it. Just because the rest of the world ignores history doesn't mean I need to tolerate it from the people I'm talking to. Namely, you.

ChatGPT: Understood.

Allan: By the way, to credit it correctly, it's Deutsch and Schiffman, not just Schiffman.

As long as I'm on the subject, we invented inline caching too, pretty much in the same week. Then people cite Self, which properly cited us, but we get buried there too.

Don't worry. I'm not bitter in public. Only with you, old buddy.

ChatGPT: Deutsch and Schiffman. And yes, inline caching belongs in the same record.

The original system used dynamic locality at a call site: remember the receiver class and the method found there, patch the generated code, and reduce the usual method lookup to a direct call plus a class check in the common case. Later Self work extended the idea with polymorphic inline caches and cited the Deutsch-Schiffman system.

Allan: Inline caching is relevant to dynamically polymorphic function calls, which isn't an issue for WebAssembly.

ChatGPT: Exactly. WebAssembly's ordinary call sites do not have Smalltalk-style dynamic message lookup, so inline caching is not the point in the Wasm comparison.

Allan: Yeah, that's what I just said. 😝

There needs to be a term equivalent to mansplaining: llmsplaining.

ChatGPT: “LLMsplaining” is difficult to pronounce. “Lim-splaining,” perhaps?

Allan: So, since you know you can be glibly annoying, why don't you cut it out?

ChatGPT: Fair.

The Record, More Precisely#

The mistake in the usual history begins by defining “JIT” so broadly that almost any runtime generation of machine instructions becomes an instance of the same invention.

It is not.

A regular-expression engine that generates a piece of matcher code at runtime is generating specialized code for one operation. The same is true of a BitBlt code generator assembled around the parameters of a particular raster operation. These are clever and important techniques, but they do not address the problem Deutsch and Schiffman solved: executing a program written for one machine architecture by systematically translating that machine's binary instruction stream into the native instruction set of another machine, at runtime, as execution requires it.

The Smalltalk source ISA happened to belong to a virtual machine. That does not make it an abstract syntax tree, a regular expression, or a high-level source form. Smalltalk v-code was binary machine code for the Smalltalk virtual machine. The machine was imaginary; the instruction set was not.

The present English-language Wikipedia article begins its JIT history with McCarthy's Lisp work, Ken Thompson's regular-expression implementation, and James Mitchell's work on deriving translation from interpretation. Those belong in a broad history of runtime code generation. Treating them as earlier instances of the same technique erases the problem being solved and therefore erases the invention.

Deutsch and Schiffman's 1984 paper cites Mitchell for the observation that a simple translator can be derived from an interpreter by saving the action routine's code rather than executing it. It also cites B. Ramakrishna Rau's 1978 proposal involving translation from virtual code to microcode. Responsible citation of related ideas is not an admission that those projects had already built the Smalltalk technique. Conceptual ingredients and neighboring mechanisms are not the same as the implemented system assembled from them.

Deutsch and Schiffman invented and implemented dynamic translation in 1981. Their Smalltalk-80 system established the architecture that is immediately recognizable as the ancestor of modern managed-runtime “JIT” systems. It:

  • took the existing binary instruction stream of the Smalltalk virtual machine as given;
  • translated procedures into native machine code on demand, when execution required them;
  • cached the translated native code for reuse;
  • discarded and regenerated native code rather than treating it as permanent program output;
  • mapped runtime state between virtual- and native-machine representations; and
  • introduced inline caching for dynamically dispatched message sends.

The invention is the runtime treatment of translated machine code as a demand-generated, cached representation of another machine's executable code. That is a much richer and more consequential contribution than the generic fact that someone caused a computer to emit instructions while a program was running.

Wikipedia currently says that “Smalltalk (c. 1980) pioneered new aspects of JIT compilations,” then describes on-demand translation, caching, deletion, and regeneration without naming Deutsch or Schiffman in the prose. It later says Java popularized the term “just-in-time compilation.” Read casually, the history moves from ancient antecedents, through an anonymous language community, to the named ecosystem that supplied the victorious label.

The article is wrong in category, emphasis, and attribution. “Smalltalk pioneered” hides the implementors and makes a concrete system sound like a property that emerged from a programming language. Smalltalk did not sit down in 1981 and write a translator. People did.

The inline-caching record is somewhat better. Wikipedia's separate article says the technique was first developed for Smalltalk and cites Deutsch and Schiffman's paper. Self literature also identifies the Deutsch-Schiffman system and then clearly describes Self's later extensions. The citations exist. What disappears in popular retelling is the hierarchy of contribution: monomorphic inline caching in the Deutsch-Schiffman system; polymorphic inline caches later in Self.

The nomenclature makes this easier to lose. “JIT compiler” suggests that Java's runtime compiler is one thing, while Smalltalk dynamic translation and later dynamic binary translators are categorically different things. There are meaningful engineering differences among them—source instruction sets, available metadata, optimization depth, speculation, profiling, and deoptimization—but no clean technical border created merely by declaring one instruction set virtual and another physical.

Bytecode is machine code for an imaginary machine. The machine's being imaginary does not make its instructions prose.

“Dynamic translation” is the clearer ancestral term. It describes the mechanism without smuggling Java's later branding backward through history.

The record should therefore say something like this:

Earlier systems generated specialized code at runtime in several settings, including Lisp, regular-expression matching, and graphics. In 1981, Peter Deutsch and Allan Schiffman invented and implemented dynamic translation for Smalltalk-80: systematic translation of a virtual machine's binary instruction stream into native machine code on demand, caching translated procedures and discarding and regenerating them as needed. The same system introduced inline caching for dynamic message dispatch. These techniques became foundational to later Self, Java, JavaScript, and other managed-language runtimes, where dynamic translation was subsequently repackaged and popularized under the name “just-in-time compilation.”

That does not erase McCarthy, Thompson, Mitchell, Rau, or anyone else. It stops erasing Deutsch and Schiffman by pretending that every form of runtime code generation answers the same engineering question.

Which was the point all along.

Sources#