# Tutorial on writing an OCaml bytecode interpreter from scratch in Rust?

**URL:** <https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899>\
**Category:** Learning\
**Created:** [April 6, 2023, 9:09pm UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899 "2023-04-06T21:09:52Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![zeroexcuses](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/zeroexcuses/32/1692_2.png) [@zeroexcuses](https://discuss.ocaml.org/u/zeroexcuses)\
**Post date:** [April 6, 2023, 9:09pm UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/1 "2023-04-06T21:09:52Z")

</div>

Has anyone here written an OCaml bytecode interpreter from scratch ?

Only the bytecode interpreter part. Not the parser / typechecker / compiler. No optimizations. Just a dumb bytecode interpreter.

I feel like many questions I have regarding (1) transpiling, (2) OCaml runtime, (3) OCaml ↔ other languages would be auto resolved if I had a correct mental model of the OCaml bytecode interpreter.

Has anyone written one from scratch? Any advice / insights ?

Thanks!

PS: I’m okay with using a dumb mark & sweep, global-stop-the-world GC. The main goal is educational, not production use.

---

<div class="post-metadata">

**Author:** ![Chet\_Murthy](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/chet_murthy/32/1501_2.png) [@Chet\_Murthy](https://discuss.ocaml.org/u/Chet_Murthy)\
**Post date:** [April 6, 2023, 9:34pm UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/2 "2023-04-06T21:34:34Z")

</div>

Have you read Xavier’s Master’s Thesis: [https://hal.inria.fr/inria-00070049/document](https://hal.inria.fr/inria-00070049/document)

?

---

<div class="post-metadata">

**Author:** ![zeroexcuses](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/zeroexcuses/32/1692_2.png) [@zeroexcuses](https://discuss.ocaml.org/u/zeroexcuses)\
**Post date:** [April 6, 2023, 10:18pm UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/3 "2023-04-06T22:18:20Z")

</div>

I have not, thanks for the reference !

---

<div class="post-metadata">

**Author:** ![c-cube](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/c-cube/32/1727_2.png) [@c-cube](https://discuss.ocaml.org/u/c-cube)\
**Post date:** [April 7, 2023, 3:48am UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/4 "2023-04-07T03:48:25Z")

</div>

I’m also curious about that, because the actual OCaml interpreter has an  
annoying limitation: it’s not reentrant, which means you can’t really use it as  
an embedded interpreter (and to use the OCaml toplevel you also need  
your program to itself be compiled to bytecode).

An alternative, no-global-state, simple to embed interpreter could be  
useful for programs extensible with/scriptable with OCaml, imho.

---

<div class="post-metadata">

**Author:** ![silene](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/silene/32/2707_2.png) [@silene](https://discuss.ocaml.org/u/silene)\
**Post date:** [April 7, 2023, 6:53am UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/5 "2023-04-07T06:53:08Z")

</div>

OCaml’s bytecode is a simple 1-register stack machine, so most of its instructions are trivial to implement. But here are a few things to look for:

- There are macro-instructions. For example, `PUSHACC` is actually the sequence of the instructions `PUSH` and `ACC`, i.e., it pushes the accumulator register on the stack and then loads it from a random position on the stack.
- The caller of a function might push more arguments on the stack than what the called function expects. The interpreter needs to keep track of their number. Indeed, if there is any extra argument, the `RETURN` instruction does not give control back to the caller. Instead, it assumes that the current accumulator is a closure and gives it immediate control.
- The `GRAB` instruction checks that the caller passed enough arguments onto the stack. If not, it creates a closure, stores all the available arguments in it, points the closure at the instruction just before `GRAB` (which is assumed to be a `RESTART` instruction). Then, it returns to the caller, as if executing a `RETURN` instruction.

---

<div class="post-metadata">

**Author:** ![ancolie](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/ancolie/32/6189_2.png) [@ancolie](https://discuss.ocaml.org/u/ancolie)\
**Post date:** [April 7, 2023, 8:57am UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/6 "2023-04-07T08:57:46Z")

</div>

In [camlboot](https://github.com/Ekdohibs/camlboot) there’s is an OCaml interpreter. It is not an OCaml _bytecode_ interpreter but it may help depending on what you’re trying to achieve.

---

<div class="post-metadata">

**Author:** ![zeroexcuses](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/zeroexcuses/32/1692_2.png) [@zeroexcuses](https://discuss.ocaml.org/u/zeroexcuses)\
**Post date:** [April 7, 2023, 3:02pm UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/7 "2023-04-07T15:02:00Z")

</div>

Quoting the bottom of the README

> Expect this to take some time: on an 8-core machine, it took about 16 hours of CPU time, and 4 hours of wall-clock time.

Is this a typo? WTF is going on? 1. How is this taking 4 hrs on 8 cores; and who has the patience to debug something that takes 4 hrs on 8 cores ?

---

<div class="post-metadata">

**Author:** ![ancolie](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/ancolie/32/6189_2.png) [@ancolie](https://discuss.ocaml.org/u/ancolie)\
**Post date:** [April 7, 2023, 4:00pm UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/8 "2023-04-07T16:00:45Z")

</div>

It’s the time needed to bootstrap the full OCaml compiler, see the [paper](https://arxiv.org/abs/2202.09231).

---

<div class="post-metadata">

**Author:** ![modlfo](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/modlfo/32/345_2.png) [@modlfo](https://discuss.ocaml.org/u/modlfo)\
**Post date:** [August 6, 2024, 5:25am UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/9 "2024-08-06T05:25:01Z")

</div>

I just found this topic.

Some years ago I wrote a basic bytecode interpreter in Rust. My interpreter is very incomplete. I just made it run an example program I had.

I took as reference the interpreter found in [obytelib](https://github.com/bvaugon/obytelib). For garbage collection, I wrote a simple mark & sweep. Another important part was a deserializer of ocaml values. The bytecode includes serialized constant values.

My objective was to have a small interpreter that could be embedded and having multiple instances of it, just like Lua.

As I mentioned, I did not finish the project, but if you are still interested, I could share the code.

---

<div class="post-metadata">

**Author:** ![Piero\_Furiesi](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/piero_furiesi/32/5482_2.png) [@Piero\_Furiesi](https://discuss.ocaml.org/u/Piero_Furiesi)\
**Post date:** [January 27, 2026, 5:26pm UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/10 "2026-01-27T17:26:27Z")

</div>

Hi,

This is a pretty old thread but it’s the only one I found about RESTART/GRAB, so…

…I’ve just looked at the code for `RESTART`/`GRAB` in [ocaml/runtime/interp.c](https://github.com/ocaml/ocaml/blob/4.14/runtime/interp.c) on github, branch 14.4. There are some things about this mechanism which I cannot fully understand.

Given an environment `ENV`=_env0_ with size _S_ and extra-arguments `XARGS`=_xargs0_,

- `RESTART` pushes _S-2_ elements from _env0[2]_ onward onto the stack, then sets `ENV :=`_env0[1]_ and increments `XARGS` by _S-2_;
- the following `GRAB required_args` checks whether `XARGS >= required_args`;  
if so, decrements `XARGS` by `required_args` and exits;  
otherwise, creates a closure in `accu` of `XARGS`_+3 = xargs0+S+1_ elements, sets `accu[0]` to _address(_`RESTART`_)_, `accu[1]` to _env0[1]_, and populates all other fields from `accu[2]` onward by popping all _S-2_ elements previously pushed by `RESTART` plus _xargs0_+1 more; finally it pops new values for `PC`, `ENV` and `XARGS`.

Wouldn’t it be more efficient to avoid _S-2_ pushes and _S-2_ pops by moving `ENV :=`_env0[1]_ from `RESTART` to `GRAB`, which could set the fields `accu[2]`…`accu[`_S+1_`]` straight from _env0_ and pop only the remaining ones off the stack ?

And more: why two distinct instructions (which require two fetch/decode/exec cycles) instead of a single `RESTART_GRAB`? Is this a consequence of the bytecode compilation method?

---

<div class="post-metadata">

**Author:** ![nojb](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/nojb/32/519_2.png) [@nojb](https://discuss.ocaml.org/u/nojb)\
**Post date:** [January 27, 2026, 8:23pm UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/11 "2026-01-27T20:23:38Z")

</div>

> [@Piero\_Furiesi](#):
>
> Wouldn’t it be more efficient to avoid _S-2_ pushes and _S-2_ pops by moving `ENV :=` _env0[1]_ from `RESTART` to `GRAB`, which could set the fields `accu[2]`…`accu[` _S+1_ `]` straight from _env0_ and pop only the remaining ones off the stack ?
> 
> And more: why two distinct instructions (which require two fetch/decode/exec cycles) instead of a single `RESTART_GRAB`? Is this a consequence of the bytecode compilation method?

I think you are missing one key point: when defining a function, a closure is created and the code pointer stored in the closure points to the `GRAB` instruction, not the `RESTART` instruction. When applying the function, if number of arguments \>= function arity, the `RESTART` instruction is not executed. It is only in the case of partial applications (number of arguments \< function arity) that a new intermediate closure is constructed with the code pointer pointing to the `RESTART` instruction immediately preceding the current `GRAB` instruction. When, later, more arguments are provided to this intermediate closure, the `RESTART` instruction is executed which pushes back onto the stack the arguments that had been provided originally (on top of the arguments that were provided more recently), and then execution flows to the `GRAB` instruction following the `RESTART`, which again tests if number of arguments \>= function arity, if yes, executes function body, otherwise creates intermediate closure, etc.

Makes sense?

Best wishes,  
Nicolas

---

<div class="post-metadata">

**Author:** ![Piero\_Furiesi](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/piero_furiesi/32/5482_2.png) [@Piero\_Furiesi](https://discuss.ocaml.org/u/Piero_Furiesi)\
**Post date:** [January 27, 2026, 9:17pm UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/12 "2026-01-27T21:17:43Z")

</div>

Thank you, Nicolas.

Yes, I was thinking only at “interpreter level”, totally forgetting why `RESTART` and `GRAB` exist… But I’m thinking if it might be possible to have `RESTART` and `GRAB n` implemented as I wrote before and introduce a new instruction, let’s say `START_GRAB n`.  
A function would be compiled as [`GRAB n`; `BRANCH` _to rest of code_; `START_GRAB n`; _rest of code_], its closure’s code pointer pointing to `START_GRAB`, which works like the current `GRAB`.  
Or am I missing another key point? 🥺

---

<div class="post-metadata">

**Author:** ![nojb](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/nojb/32/519_2.png) [@nojb](https://discuss.ocaml.org/u/nojb)\
**Post date:** [January 27, 2026, 9:33pm UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/13 "2026-01-27T21:33:48Z")

</div>

> [@Piero\_Furiesi](#):
>
> But I’m thinking if it might be possible to have `RESTART` and `GRAB n` implemented as I wrote before and introduce a new instruction, let’s say `START_GRAB n`.

Perhaps; but to be honest I don’t see clearly what is the objective sought here 🙂

Cheers,  
Nicolas

---

<div class="post-metadata">

**Author:** ![Piero\_Furiesi](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/piero_furiesi/32/5482_2.png) [@Piero\_Furiesi](https://discuss.ocaml.org/u/Piero_Furiesi)\
**Post date:** [January 27, 2026, 9:44pm UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/14 "2026-01-27T21:44:21Z")

</div>

Just to reduce pushes/pops operations… During Covid time I wrote an OCaml interpreter for the Commodore C64 (taking inspiration from a cute project by Benoît Vaugon, [GitHub - bvaugon/ocapic: OCaml for PIC microcontrollers](https://github.com/bvaugon/ocapic) ) and its 6510 CPU (1Mhz clock) suffered a lot when executing many “indirect indexed address mode” instructions to operate on the stack 😄

---

<div class="post-metadata">

**Author:** ![silene](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/silene/32/2707_2.png) [@silene](https://discuss.ocaml.org/u/silene)\
**Post date:** [January 28, 2026, 7:45am UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/15 "2026-01-28T07:45:47Z")

</div>

You are right that there is some useless memory churn, but there are two things to consider:

1. This situation only happens if an already partially applied closure is applied again in a non-fully way. This situation might be so rare, that it is not worth optimizing for it. You should first check if the overhead is noticeable.
2. You are not forced to consider `RESTART` and `GRAB` as two separate instructions. Your interpreter could handle `RESTART` as a 3-word instruction, which would then behave as your proposed `START_GRAB` instruction. The advantage is that this does not require any change to the compiler; in particular, no need for the extra jumps of your proposal.

---

<div class="post-metadata">

**Author:** ![Piero\_Furiesi](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/piero_furiesi/32/5482_2.png) [@Piero\_Furiesi](https://discuss.ocaml.org/u/Piero_Furiesi)\
**Post date:** [January 28, 2026, 8:42am UTC](https://discuss.ocaml.org/t/tutorial-on-writing-an-ocaml-bytecode-interpreter-from-scratch-in-rust/11899/16 "2026-01-28T08:42:56Z")

</div>

Thanks, silene.

Last night I modified my 5-year-old interpreter code: the first time a closure is applied, `GRAB` works as usual; but when `RESTART` is executed (intermediate closure application), it sets a flag to tell the next `GRAB` that the first _S-2_ arguments have to be read from `ENV` instead of the stack. It saves a few CPU cycles and seems to work well.

_OCaml for retrocomputing is_ (useless but) _fun._
