You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Next we'll write the Frame object. The frame is a collection of attributes with no methods. As mentioned above, the attributes include `f_code`, the code object (created by the compiler) corresponding to the frame; the local, global, and builtin namespaces (`f_globals`, `f_locals`, and `f_builtins`); a reference to the previous frame, `f_back`; a data stack, a block stack, and the last instruction executed (`f_lasti`). (We have to do a little extra work to get to the builtin namespace because Python treats this namespace differently in different modules; this detail is not important to the virtual machine.)
431
-
#TODO: fix the naming here
430
+
Next we'll write the Frame object. The frame is a collection of attributes with no methods. As mentioned above, the attributes include the code object created by the compiler; the local, global, and builtin namespaces; a reference to the previous frame; a data stack, a block stack, and the last instruction executed. (We have to do a little extra work to get to the builtin namespace because Python treats this namespace differently in different modules; this detail is not important to the virtual machine.)
self.f_back = f_back # Reference to previous frame
447
-
self.stack = [] # Data stack
448
-
self.block_stack = [] # Block stack
449
-
450
-
self.f_lineno = f_code.co_firstlineno # First line number in original source code
451
-
self.f_lasti = 0 # Last executed bytecode instruction
447
+
self.last_instruction = 0
448
+
self.block_stack = []
452
449
```
453
450
454
451
Next, we'll add frame manipulation to the virtual machine. There are three helper functions for frames: one to create new frames (which is responsible for sorting out the new frame's namespacing) and one each to push and pop frames from the frame stack. A fourth function, `run_frame`, does the main work of executing a frame. We'll come back to this momentarily.
@@ -458,26 +455,26 @@ class VirtualMachine(object):
The Function object appears below. The implementation is somewhat twisty, and most of the details aren't critical to understanding the interpreter. The important thing to notice is that calling a function - invoking the `__call__` method - creates a new Frame object and starts running it. (The implementation is twisty because this is one place where our Python interpreter interacts with the real Python compiler
495
-
TODO: TRUE?.)
491
+
The Function object appears below. The implementation is somewhat twisty, and most of the details aren't critical to understanding the interpreter. The important thing to notice is that calling a function - invoking the `__call__` method - creates a new Frame object and starts running it. (The implementation is twisty because TODO: why? Ask Ned.)
496
492
497
493
```
498
494
class Function(object):
@@ -570,7 +566,7 @@ Before we get to running a frame, we need two more methods. The first, `parse_by
570
566
571
567
A single instruction is one byte long if it doesn't have an argument, or three bytes if it does have an argument, where the last two bytes are the argument. The meaning of the argument to each instruction depends on which instruction it is. For example, as mentioned above, for `POP_JUMP_IF_FALSE`, the argument to the instruction is the jump target. For `BUILD_LIST`, the argument is the number of elements in the list. For `LOAD_CONST`, it's an index into the list of constants.
572
568
573
-
Some instructions use simple numbers as their arguments. For others, the virtual machine has to do a little work to discover what the arguments mean. The `dis` module in the standard library exposes a cheatsheet to what arguments have what meaning, which makes our code more compact. For example, the list `dis.hasname` tells us that the arguments to `LOAD_NAME`, `IMPORT_NAME`, `LOAD_GLOBAL`, and nine other instructions have the same meaing: in each case, the argument represents an index into the list of names on the code object.
569
+
Some instructions use simple numbers as their arguments. For others, the virtual machine has to do a little work to discover what the arguments mean. The `dis` module in the standard library exposes a cheatsheet to what arguments have what meaning, which makes our code more compact. For example, the list `dis.hasname` tells us that the arguments to `LOAD_NAME`, `IMPORT_NAME`, `LOAD_GLOBAL`, and nine other instructions have the same meaing: for these twelve instructions, the argument represents an index into the list of names on the code object.
574
570
575
571
576
572
TODO: fix frame naming
@@ -579,32 +575,30 @@ class VirtualMachine(object):
579
575
[... snip ...]
580
576
581
577
def parse_byte_and_args(self):
582
-
""" Parse 1 - 3 bytes of bytecode into
583
-
an instruction and maybe arguments."""
584
578
f = self.frame
585
-
opoffset = f.f_lasti
586
-
byteCode = f.f_code.co_code[opoffset]
587
-
f.f_lasti += 1
588
-
byteName = dis.opname[byteCode]
589
-
arg = None
590
-
arguments = []
579
+
opoffset = f.last_instruction
580
+
byteCode = f.code_obj.co_code[opoffset]
581
+
f.last_instruction += 1
582
+
byte_name = dis.opname[byteCode]
591
583
if byteCode >= dis.HAVE_ARGUMENT:
592
-
arg = f.f_code.co_code[f.f_lasti:f.f_lasti+2]
593
-
f.f_lasti += 2
584
+
arg = f.code_obj.co_code[f.last_instruction:f.last_instruction+2] # index into the bytecode
585
+
f.last_instruction += 2 # advance the instruction pointer
594
586
arg_val = arg[0] + (arg[1] << 8)
595
587
if byteCode in dis.hasconst: # Look up a constant
596
-
arg = f.f_code.co_consts[arg_val]
588
+
arg = f.code_obj.co_consts[arg_val]
597
589
elif byteCode in dis.hasname: # Look up a name
598
-
arg = f.f_code.co_names[arg_val]
590
+
arg = f.code_obj.co_names[arg_val]
599
591
elif byteCode in dis.haslocal: # Look up a local name
600
-
arg = f.f_code.co_varnames[arg_val]
592
+
arg = f.code_obj.co_varnames[arg_val]
601
593
elif byteCode in dis.hasjrel: # Calculate a relative jump
602
-
arg = f.f_lasti + arg_val
594
+
arg = f.last_instruction + arg_val
603
595
else:
604
596
arg = arg_val
605
-
arguments = [arg]
597
+
argument = [arg]
598
+
else:
599
+
argument = []
606
600
607
-
return byteName, arguments
601
+
return byte_name, argument
608
602
```
609
603
610
604
The next method is `dispatch`, which looks up the operations for a given instruction and executes them. In the CPython interpreter, this dispatch is done with a giant switch statement that spans 1,500 lines! Luckily, since we're writing Python, we can be more compact. We'll define a method for each byte name and then use `getattr` to look it up. If our bytecode was named `FOO_BAR`, the corresponding method would be named `byte_FOO_BAR`. For the moment, we'll leave the content of these methods as a black box. Each bytecode method will return either `None` or a `why` string to be passed to block management. The return values of the individual bytecode methods are used only as internal indicators of interpreter state - don't confuse these with return values from executing frames.
@@ -614,27 +608,26 @@ The next method is `dispatch`, which looks up the operations for a given instruc
614
608
class VirtualMachine(object):
615
609
[... snip ...]
616
610
617
-
def dispatch(self, byteName, arguments):
618
-
""" Dispatch by bytename to the corresponding method and call it.
619
-
Exceptions are caught and set on the virtual machine."""
611
+
def dispatch(self, byte_name, argument):
612
+
""" Dispatch by bytename to the corresponding methods.
613
+
Exceptions are caught and set on the virtual machine."""
@@ -1055,7 +1047,7 @@ class VirtualMachine(object):
1055
1047
1056
1048
### What can we actually do with this? Pick an interesting example and scope the code to just those bytecode instructions, maybe?
1057
1049
1058
-
### Somehow conclude
1050
+
### With byterun, you have a compact Python interpreter that's easier to understand than CPython. I encourage you to disassemble your own programs and run them using Byterun. (You'll eventually run into instructions that this shorter version of Byterun doesn't implement. The full implementation can be found at github.com/nedbat/byterun. )
0 commit comments