Once again, the runtime is a thin layer over actual JS runtime. The compiled output includes a bundle.js
which is executed using eval
.
But since bundle.js
is just a bunch of AsyncGenerators
, its execution needs to be managed. And this is what our runtime does.
It yields
given AsyncGenerators
and maintains the state of execution.
This is a simple class, tasked to “hold” class instances. Every time a class is instantiated, it registers itself on the heap. And the heap “allocates” a address to it.
The class gains an address and in-fact stores it in a special field called __starting_address
This is done to provide a unique identity to each instantiated class, and the heap abstraction helps us down the line when we start visualising.
https://github.com/metz-sh/simulacrum/blob/main/src/runtime/heap.ts
An equally simple class, tasked with capturing “loading” and “unloading” methods on the stack.
Whenever a methods starts executing, the first thing it does is to yield
a LOAD instruction.
This instruction tells the runtime to load the metadata related to the method on the stack. That metadata being:
{
startingAddress: string, // Provided by heap, unique for each instance.
offset: string, // The method name.
}
Similarly, when a method’s execution is over, it yields
an UNLOAD instruction. Which tells the runtime to pop from the stack.
And once you have a stack of this information, you are pretty much set to visualise method calls.
https://github.com/metz-sh/simulacrum/blob/main/src/runtime/execution-stack.ts
https://app.metz.sh/play/60b5ee3d6afe46c4a635e488fdbe5bc7?minimal=true