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.

Heap

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

Stack

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