Skip to content

Thoughts about engine.md (Guido)

Guido van Rossum edited this page Jan 4, 2024 · 2 revisions

(This is just personal notes notes -- I am trying something new and using the GitHub Wiki.)

Overview

How can the copy-and-patch (JIT) compiler be considered an "execution engine"? It's a compiler, so by definition (unlike an interpreter) it doesn't execute anything. Its output is executable though. Maybe we need more terminology so the distinction is clearer? As written this leaves me just confused.

Entry to and exit from superblocks -- is this from Tier 1?

"Performance of jumping [...] is also important. Memory consumption is also important." Yeah, yeah. :-)

Either interpreter or compiler. Not both.

Fine. As clarified off-line, both may exist in the binary, but we decide very early on which one to use, and stick to that one.

I am guessing we might also decide to use neither. Or only one or the other may exist in the binary (e.g. the JIT is not always present).

Superblocks and executors

"An executor is the runtime object that is the executable representation of a superblock." -- how does this relate to execution engines? Is the executor passed to the engine for execution? Or how otherwise is the engine invoked or the executor executed?

Creating executors

"Creating executors from superblocks is the first job of the execution engine." -- This leaves me more confused about the relationship between superblocks, executors, and engines. It seems the mapping between artifacts in the code (like functions) and concepts in this document is a bit fuzzy; I'd like it to be as crisp as possible (with instructions for how to split things up if the code conflates concepts).

Exits from executors

Maybe we could start out by not worrying too much about this (either about the speed of hot exits or the space of cold ones) and iterate after we've got other aspects in place? Or is this of such importance that the entire architecture needs to be aware of this?

Linking executors

Similar -- we can iterate on this. Also, the process is somewhat convoluted, since before we can create the executor we have to have the superblock.

Making progress

Another possible way to avoid this problem would be to make exits where the executor hasn't done any work yet always go to Tier 1. We can statically know (during the translation from superblock to executor) whether an exit hasn't done any work yet: the exit can only be preceded by pure guards (which we can mark in bytecodes.c). On the surface, at least, this is not the same as (1), since it's not about which executors are special, but about which exits; and it's definitely not the same as (2). Reasoning about this seems straightforward.

Inter-superblock optimization

"Some superblocks can be quite short, but form a larger region of hot code" -- this confused me. Maybe "form" should be "combine into"? So several short superblocks combine into a larger region, right? This feels quite advanced right now. Maybe we can punt until we're farther along the project?

Making "hot" exits fast and "cold" exits small

That doesn't sound very helpful -- how do we do this?

The implementation

Since this is just one possible implementation, it still leaves room for a lot of misunderstandings about how various pieces fit together.

Making "hot" exits fast

To be clear, "IP" and "first instruction" here refer to the Tier 2 interpreter's IP, right? So at the C level, or to the hardware CPU, this is still several instructions. And this transfer is responsible for the INCREF/DECREF of the old/new executors.

And in the JIT case, I'd like to understand how a JIT executor and its function pointer relate (presumably the executor is still an object, so I guess it has to contain a function pointer as a piece of data), and who is responsible for the executor object INCREF/DECREF.

(Here I was interrupted and had to save my work. More later.)

Clone this wiki locally