3. Assembly Language¶
3.1. Overview of the JCoCo VM¶
Fig 3.1 The JCoCo Virtual Machine
3.2. Getting Started¶
3.2.1. Running the Disassembler¶
1 2 3 4 5 6 7 8 9 10 11 12 13 from disassembler import * import sys def main(): x=5 y=6 z=x+y print(z) if len(sys.argv) == 1: main() else: disassemble(main)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | MyComputer> python3.2 addtwo.py 1
Function: main/0
Constants: None, 5, 6
Locals: x, y, z
Globals: print
BEGIN
LOAD_CONST 1
STORE_FAST 0
LOAD_CONST 2
STORE_FAST 1
LOAD_FAST 0
LOAD_FAST 1
BINARY_ADD
STORE_FAST 2
LOAD_GLOBAL 0
LOAD_FAST 2
CALL_FUNCTION 1
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
MyComputer> python3.2 addtwo.py 1 > addtwo.casm
|
3.2.2. Running JCoCo¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | MyComputer> coco -v addtwo.casm
Function: main/0
Constants: None, 5, 6
Locals: x, y, z
Globals: print
BEGIN
LOAD_CONST 1
STORE_FAST 0
LOAD_CONST 2
STORE_FAST 1
LOAD_FAST 0
LOAD_FAST 1
BINARY_ADD
STORE_FAST 2
LOAD_GLOBAL 0
LOAD_FAST 2
CALL_FUNCTION 1
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
11
MyComputer> coco addtwo.casm
11
MyComputer>
|
MyComputer> python3.2 addtwo.py 1 > addtwo.casm
MyComputer> coco addtwo.casm
3.3. Input/Output¶
3.3.1. Python I/O¶
1 2 3 4 5 6 7 8 9 | import disassembler
def main():
name = input("Enter your name: ")
age = int(input("Enter your age: "))
print(name + ", a year from now you will be", age+1, "years old.")
#main()
disassembler.disassemble(main)
|
3.3.2. CoCo I/O¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | Function: main/0
Constants: None,
"Enter your name: ", "Enter your age: ",
", a year from now you will be",
1, "years old."
Locals: name, age
Globals: input, int, print
BEGIN
LOAD_GLOBAL 0
LOAD_CONST 1
CALL_FUNCTION 1
STORE_FAST 0
LOAD_GLOBAL 1
LOAD_GLOBAL 0
LOAD_CONST 2
CALL_FUNCTION 1
CALL_FUNCTION 1
STORE_FAST 1
LOAD_GLOBAL 2
LOAD_FAST 0
LOAD_CONST 3
BINARY_ADD
LOAD_FAST 1
LOAD_CONST 4
BINARY_ADD
LOAD_CONST 5
CALL_FUNCTION 3
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
|
Fig 3.2 JCoCo I/O
3.4. If-Then-Else Statements¶
1 2 3 4 5 6 7 8 9 10 11 12 13 | import disassembler
def main():
x = 5
y = 6
if x > y:
z = x
else:
z = y
print(z)
disassembler.disassemble(main)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Function: main/0
Constants: None, 5, 6
Locals: x, y, z
Globals: print
BEGIN
LOAD_CONST 1
STORE_FAST 0
LOAD_CONST 2
STORE_FAST 1
LOAD_FAST 0
LOAD_FAST 1
COMPARE_OP 4
POP_JUMP_IF_FALSE label00
LOAD_FAST 0
STORE_FAST 2
JUMP_FORWARD label01
label00: LOAD_FAST 1
STORE_FAST 2
label01: LOAD_GLOBAL 0
LOAD_FAST 2
CALL_FUNCTION 1
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
|
Fig 3.3 If-Then-Else assembly
1 2 3 4 | onelabel: LOAD_FAST 1
STORE_FAST 2
twolabel:
threelabel: LOAD_GLOBAL 0
|
3.4.1. Assembled CoCo¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Function: main/0
Constants: None, 5, 6
Locals: x, y, z
Globals: print
BEGIN
LOAD_CONST 1
STORE_FAST 0
LOAD_CONST 2
STORE_FAST 1
LOAD_FAST 0
LOAD_FAST 1
COMPARE_OP 4
POP_JUMP_IF_FALSE 11
LOAD_FAST 0
STORE_FAST 2
JUMP_FORWARD 13
LOAD_FAST 1
STORE_FAST 2
LOAD_GLOBAL 0
LOAD_FAST 2
CALL_FUNCTION 1
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
|
Fig 3.4 Assembled code
Practice 3.2
Without touching the code that compares the two values, the assembly in Assembled CoCo can be optimized to remove at least three instructions. Rewrite the code to remove at least three instructions from this code. With a little more work, five instructions could be removed.
3.4.2. If-Then Statements¶
import disassembler
def main():
x = 5
y = 6
if x > y:
print(x)
print(y)
disassembler.disassemble(main)
3.4.3. CoCo If-Then Assembly¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Function: main/0
Constants: None, 5, 6
Locals: x, y
Globals: print
BEGIN
LOAD_CONST 1
STORE_FAST 0
LOAD_CONST 2
STORE_FAST 1
LOAD_FAST 0
LOAD_FAST 1
COMPARE_OP 4
POP_JUMP_IF_FALSE label00
LOAD_GLOBAL 0
LOAD_FAST 0
CALL_FUNCTION 1
POP_TOP
JUMP_FORWARD label00
label00: LOAD_GLOBAL 0
LOAD_FAST 1
CALL_FUNCTION 1
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
|
Fig 3.5 If-Then assembly
Practice 3.3
Rewrite the code in CoCo If-Then Assembly so it executes with the same result using POP_JUMP_IF_TRUE instead of the jump if false instruction. Be sure to optimize your code when you write it so there are no unnecessary instructions.
3.5. While Loops¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import disassembler
def main():
f = 8
i = 1
j = 1
n = 1
while n < f:
n = n + 1
tmp = j
j = j + i
i = tmp
print("Fibonacci("+str(n)+") is",i)
disassembler.disassemble(main)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | Function: main/0
Constants: None, 8, 1, "Fibonacci(", ") is"
Locals: f, i, j, n, tmp
Globals: print, str
BEGIN
LOAD_CONST 1
STORE_FAST 0
LOAD_CONST 2
STORE_FAST 1
LOAD_CONST 2
STORE_FAST 2
LOAD_CONST 2
STORE_FAST 3
SETUP_LOOP label02
label00: LOAD_FAST 3
LOAD_FAST 0
COMPARE_OP 0
POP_JUMP_IF_FALSE label01
LOAD_FAST 3
LOAD_CONST 2
BINARY_ADD
STORE_FAST 3
LOAD_FAST 2
STORE_FAST 4
LOAD_FAST 2
LOAD_FAST 1
BINARY_ADD
STORE_FAST 2
LOAD_FAST 4
STORE_FAST 1
JUMP_ABSOLUTE label00
label01: POP_BLOCK
label02: LOAD_GLOBAL 0
LOAD_CONST 3
LOAD_GLOBAL 1
LOAD_FAST 3
CALL_FUNCTION 1
BINARY_ADD
LOAD_CONST 4
BINARY_ADD
LOAD_FAST 1
CALL_FUNCTION 2
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
|
Fig 3.6 While loop assembly
Practice 3.4
Write a short program that tests the use of the BREAK_LOOP instruction. You don’t have to write a while loop to test this. Simply write some code that uses a BREAK_LOOP and prints something to the screen to verify that it worked.
3.6. Exception Handling¶
1 2 3 4 5 6 7 8 9 10 11 12 | import disassembler
def main():
try:
x = float(input("Enter a number: "))
y = float(input("Enter a number: "))
z = x / y
print(x,"/",y,"=",z)
except Exception as ex:
print(ex)
disassembler.disassemble(main)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | Function: main/0
Constants: None,
"Enter a number: ", "/", "="
Locals: x, y, z, ex
Globals: float, input, print, Exception
BEGIN
SETUP_EXCEPT label00
LOAD_GLOBAL 0
LOAD_GLOBAL 1
LOAD_CONST 1
CALL_FUNCTION 1
CALL_FUNCTION 1
STORE_FAST 0
LOAD_GLOBAL 0
LOAD_GLOBAL 1
LOAD_CONST 1
CALL_FUNCTION 1
CALL_FUNCTION 1
STORE_FAST 1
LOAD_FAST 0
LOAD_FAST 1
BINARY_TRUE_DIVIDE
STORE_FAST 2
LOAD_GLOBAL 2
LOAD_FAST 0
LOAD_CONST 2
LOAD_FAST 1
LOAD_CONST 3
LOAD_FAST 2
CALL_FUNCTION 5
POP_TOP
POP_BLOCK
JUMP_FORWARD label03
label00: DUP_TOP
LOAD_GLOBAL 3
COMPARE_OP 10
POP_JUMP_IF_FALSE label02
POP_TOP
STORE_FAST 3
POP_TOP
SETUP_FINALLY label01
LOAD_GLOBAL 2
LOAD_FAST 3
CALL_FUNCTION 1
POP_TOP
POP_BLOCK
POP_EXCEPT
LOAD_CONST 0
label01: LOAD_CONST 0
STORE_FAST 3
DELETE_FAST 3
END_FINALLY
JUMP_FORWARD label03
label02: END_FINALLY
label03: LOAD_CONST 0
RETURN_VALUE
END
|
Fig 3.7 Exception handling assembly
Practice 3.5
Write a short program that tests creating an exception, raising it, and printing the handled exception. Write this as a JCoCo program without using the disassembler.
3.7. List Constants¶
1 2 3 4 5 6 7 | import disassembler
def main():
lst = ["hello","world"]
print(lst)
disassembler.disassemble(main)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Function: main/0
Constants: None, "hello", "world"
Locals: lst
Globals: print
BEGIN
LOAD_CONST 1
LOAD_CONST 2
BUILD_LIST 2
STORE_FAST 0
LOAD_GLOBAL 0
LOAD_FAST 0
CALL_FUNCTION 1
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
|
Fig 3.8 Assembly for building a list
3.8. Calling a Method¶
1 2 3 4 5 6 7 8 9 | import disassembler
def main():
s = input("Enter a list of integers:")
lst = s.split()
print(lst)
disassembler.disassemble(main)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | Function: main/0
Constants: None, "Enter a list of integers:"
Locals: s, lst
Globals: input, split, print
BEGIN
LOAD_GLOBAL 0
LOAD_CONST 1
CALL_FUNCTION 1
STORE_FAST 0
LOAD_FAST 0
LOAD_ATTR 1
CALL_FUNCTION 0
STORE_FAST 1
LOAD_GLOBAL 2
LOAD_FAST 1
CALL_FUNCTION 1
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
|
Fig 3.9 Assembly for calling a method
Practice 3.6
Normally, if you want to add to numbers together in Python, like 5 and 6, you write 5+6. This corresponds to using the BINARY_ADD instruction in JCoCo which in turn calls the magic method __add__ with the method call 5.__add__(6). Write a short JCoCo program where you add two integers together without using the BINARY_ADD instruction. Print the result to the screen.
3.9. Iterating Over a List¶
1 2 3 4 5 6 7 8 9 10 | from disassembler import *
def main():
x = input("Enter a list: ")
lst = x.split()
for b in lst:
print(b)
disassemble(main)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | Function: main/0
Constants: None, "Enter a list: "
Locals: x, lst, b
Globals: input, split, print
BEGIN
LOAD_GLOBAL 0
LOAD_CONST 1
CALL_FUNCTION 1
STORE_FAST 0
LOAD_FAST 0
LOAD_ATTR 1
CALL_FUNCTION 0
STORE_FAST 1
SETUP_LOOP label02
LOAD_FAST 1
GET_ITER
label00: FOR_ITER label01
STORE_FAST 2
LOAD_GLOBAL 2
LOAD_FAST 2
CALL_FUNCTION 1
POP_TOP
JUMP_ABSOLUTE label00
label01: POP_BLOCK
label02: LOAD_CONST 0
RETURN_VALUE
END
|
Fig 3.10 List iteration assembly
Practice 3.7
Write a JCoCo program that gets a string from the user and iterates over the characters of the string, printing them to the screen.
3.10. Range Objects and Lazy Evaluation¶
1 2 3 4 5 6 7 8 9 10 | from disassembler import *
def main():
x = input("Enter a list: ")
lst = x.split()
for i in range(len(lst)-1,-1,-1):
print(lst[i])
disassemble(main)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | Function: main/0
Constants: None, "Enter a list: ", 1, -1, -1
Locals: x, lst, i
Globals: input, split, range, len, print
BEGIN
LOAD_GLOBAL 0
LOAD_CONST 1
CALL_FUNCTION 1
STORE_FAST 0
LOAD_FAST 0
LOAD_ATTR 1
CALL_FUNCTION 0
STORE_FAST 1
SETUP_LOOP label02
LOAD_GLOBAL 2
LOAD_GLOBAL 3
LOAD_FAST 1
CALL_FUNCTION 1
LOAD_CONST 2
BINARY_SUBTRACT
LOAD_CONST 3
LOAD_CONST 4
CALL_FUNCTION 3
GET_ITER
label00: FOR_ITER label01
STORE_FAST 2
LOAD_GLOBAL 4
LOAD_FAST 1
LOAD_FAST 2
BINARY_SUBSCR
CALL_FUNCTION 1
POP_TOP
JUMP_ABSOLUTE label00
label01: POP_BLOCK
label02: LOAD_CONST 0
RETURN_VALUE
END
|
Fig 3.11 Range assembly
3.11. Functions and Closures¶
1 2 3 4 5 6 7 8 9 | def main():
x = 10
def f(x):
def g():
return x
return g
print(f(3)())
#main()
disassembler.disassemble(main)
|
3.11.1. Nested Functions Assembly¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | Function: main/0
Function: f/1
Function: g/0
Constants: None
FreeVars: x
BEGIN
LOAD_DEREF 0
RETURN_VALUE
END
Constants: None, code(g)
Locals: x, g
CellVars: x
BEGIN
LOAD_CLOSURE 0
BUILD_TUPLE 1
LOAD_CONST 1
MAKE_CLOSURE 0
STORE_FAST 1
LOAD_FAST 1
RETURN_VALUE
END
Constants: None, 10, code(f), 3
Locals: x, f
Globals: print
BEGIN
LOAD_CONST 1
STORE_FAST 0
LOAD_CONST 2
MAKE_FUNCTION 0
STORE_FAST 1
LOAD_GLOBAL 0
LOAD_FAST 1
LOAD_CONST 3
CALL_FUNCTION 1
CALL_FUNCTION 0
CALL_FUNCTION 1
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
|
Fig 3.12 Nested functions assembly
3.11.2. Nested Function¶
Fig 3.13 Execution of nested.casm
Practice 3.8
The program in Nested Functions Assembly would work just fine without the cell. The variable x could refer directly to the 3 in both the f and g functions without any ramifications. Yet, a cell variable is needed in some circumstances. Can you come up with an example where a cell variable is absolutely needed?
3.12. Recursion¶
1 2 3 4 5 6 7 8 9 10 11 12 13 | import disassembler
def factorial(n):
if n==0:
return 1
return n*factorial(n-1)
def main():
print(factorial(5))
disassembler.disassemble(factorial)
disassembler.disassemble(main)
|
3.12.1. CoCo Recursion Assembly¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | Function: factorial/1
Constants: None, 0, 1
Locals: n
Globals: factorial
BEGIN
LOAD_FAST 0
LOAD_CONST 1
COMPARE_OP 2
POP_JUMP_IF_FALSE label00
LOAD_CONST 2
RETURN_VALUE
label00: LOAD_FAST 0
LOAD_GLOBAL 0
LOAD_FAST 0
LOAD_CONST 2
BINARY_SUBTRACT
CALL_FUNCTION 1
BINARY_MULTIPLY
RETURN_VALUE
END
Function: main/0
Constants: None, 5
Globals: print, factorial
BEGIN
LOAD_GLOBAL 0
LOAD_GLOBAL 1
LOAD_CONST 1
CALL_FUNCTION 1
CALL_FUNCTION 1
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
|
Fig 3.14 Recursion assembly
Practice 3.9
Draw a picture of the run-time stack just before the instruction on line 11 of CoCo Recursion Assembly is executed. Use Nested Function as a guide to how you draw this picture. Be sure to include the code, the values of n, and the PC values.
3.13. Support for Classes and Objects¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import disassembler
class Dog:
def __init__(self):
self.food = 0
def eat(self):
self.food = self.food + 1
def speak(self):
if self.food > 2:
print("I am happy!")
else:
print("I am hungry!!!")
self.food=self.food - 1
def main():
mesa = Dog()
mesa.eat()
mesa.speak()
mesa.eat()
mesa.eat()
mesa.speak()
disassembler.disassemble(Dog)
disassembler.disassemble(main)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | Class: Dog
BEGIN
Function: eat/1
Constants: None, 1
Locals: self
Globals: food
BEGIN
LOAD_FAST 0
LOAD_ATTR 0
LOAD_CONST 1
BINARY_ADD
LOAD_FAST 0
STORE_ATTR 0
LOAD_CONST 0
RETURN_VALUE
END
Function: __init__/1
Constants: None, 0
Locals: self
Globals: food
BEGIN
LOAD_CONST 1
LOAD_FAST 0
STORE_ATTR 0
LOAD_CONST 0
RETURN_VALUE
END
# speak function omitted
END
Function: main/0
Constants: None
Locals: mesa
Globals: Dog, eat, speak
BEGIN
LOAD_GLOBAL 0
CALL_FUNCTION 0
STORE_FAST 0
LOAD_FAST 0
LOAD_ATTR 1
CALL_FUNCTION 0
POP_TOP
...
RETURN_VALUE
END
|
Fig 3.15 The dog class
Practice 3.10
In this section it was stated that every object consists of a dictionary which holds the attributes of the object. What is stored in the dictioinary of the object that mesa refers to in this section?
3.13.1. Inheritance¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import disassembler
class Dog:
def __init__(self):
self.food = 0
def eat(self):
self.food = self.food + 1
def speak(self):
if self.food > 2:
print("I am happy!")
else:
print("I am hungry!!!")
self.food=self.food - 1
def main():
mesa = Dog()
mesa.eat()
mesa.speak()
mesa.eat()
mesa.eat()
mesa.speak()
disassembler.disassemble(Dog)
disassembler.disassemble(main)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | Class: Animal
BEGIN
Function: __init__/2
Constants: None, 0
Locals: self, name
Globals: name, food
BEGIN
LOAD_FAST 1
LOAD_FAST 0
STORE_ATTR 0
LOAD_CONST 1
LOAD_FAST 0
STORE_ATTR 1
LOAD_CONST 0
RETURN_VALUE
END
Function: eat/1
Constants: None, 1
Locals: self
Globals: food
BEGIN
LOAD_FAST 0
...
RETURN_VALUE
END
Function: speak/1
Constants: None, "is an animal"
Locals: self
Globals: print, name
BEGIN
LOAD_GLOBAL 0
...
RETURN_VALUE
END
END
|
Fig 3.16 Inheritance in JCoCo - part 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | Class: Dog(Animal)
BEGIN
Function: __init__/2
Constants: None
Locals: self, name
FreeVars: __class__
Globals: super, __init__
BEGIN
LOAD_GLOBAL 0
CALL_FUNCTION 0
LOAD_ATTR 1
LOAD_FAST 1
CALL_FUNCTION 1
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
Function: speak/1
Constants: None, "says woof!"
Locals: self
Globals: print, name
BEGIN
LOAD_GLOBAL 0
...
RETURN_VALUE
END
END
Function: main/0
Constants: None, "Mesa"
Locals: mesa
Globals: Dog, eat, speak
BEGIN
LOAD_GLOBAL 0
LOAD_CONST 1
CALL_FUNCTION 1
STORE_FAST 0
...
RETURN_VALUE
END
|
Fig 3.17 Inheritance in JCoCo - part 2
Practice 3.11
Code was omitted in figures 3.16 and 3.17 for brevity in the chapter. Pick a method and complete the assembly code according to the original Python code from which it is derived.
3.13.2. Dynamically Created Classes¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import disassembler
def main():
class Dog:
dogNumber = 0
def __init__(self,name):
self.name = name
self.id = Dog.dogNumber
Dog.dogNumber += 1
def speak(self):
print("Dog number: ", self.id)
x = Dog("Mesa")
y = Dog("Sequoia")
x.speak()
y.speak()
disassembler.disassemble(main)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | Function: main/0
Function: Dog/1
Function: __init__/2
Constants: None, 1
Locals: self, name
FreeVars: Dog
Globals: name, dogNumber, id
BEGIN
LOAD_FAST 1
LOAD_FAST 0
STORE_ATTR 0
LOAD_DEREF 0
LOAD_ATTR 1
LOAD_FAST 0
STORE_ATTR 2
...
RETURN_VALUE
END
Function: speak/1
Constants: None, "Dog number: "
Locals: self
Globals: print, id
BEGIN
LOAD_GLOBAL 0
...
RETURN_VALUE
END
Constants: 0, code(__init__), code(speak), None
Locals: __locals__
FreeVars: Dog
Globals: __name__, __module__, dogNumber, __init__, speak
BEGIN
LOAD_FAST 0
STORE_LOCALS
LOAD_NAME 0
STORE_NAME 1
LOAD_CONST 0
STORE_NAME 2
LOAD_CLOSURE 0
BUILD_TUPLE 1
LOAD_CONST 1
MAKE_CLOSURE 0
STORE_NAME 3
LOAD_CONST 2
MAKE_FUNCTION 0
STORE_NAME 4
LOAD_CONST 3
RETURN_VALUE
END
Constants: None, code(Dog), "Dog", "Mesa", "Sequoia"
Locals: x, y
CellVars: Dog
Globals: speak
BEGIN
LOAD_BUILD_CLASS
LOAD_CLOSURE 0
BUILD_TUPLE 1
LOAD_CONST 1
MAKE_CLOSURE 0
LOAD_CONST 2
CALL_FUNCTION 2
STORE_DEREF 0
LOAD_DEREF 0
LOAD_CONST 3
CALL_FUNCTION 1
STORE_FAST 0
...
RETURN_VALUE
END
|
Fig 3.18 and Fig 3.19 Dynamically created class
Practice 3.12
In some detail, describe the contents of the operand stack before and after the built-in class builder function is called to create a class instance.
3.14. Chapter Summary¶
3.15. Review Questions¶
- How do the Python virtual machine and JCoCo differ? Name three differences between the two implementations.
- What is a disassembler?
- What is an assembler?
- What is a stack frame? Where are they stored? What goes inside a stack frame?
- What is the purpose of the block stack and where is it stored?
- What is the purpose of the Program Counter?
- Name an instruction that is responsible for creating a list object and describe how it works.
- Describe the execution of the STORE_FAST and LOAD_FAST instructions.
- How can JCoCo read a line of input from the keyboard?
- What is the difference between a disassembled Python program and an assembled JCoCo program? Provide a short example and point out the differences.
- When a Python while loop is implemented in JCoCo, what is the last instruction of the loop and what is its purpose?
- What do exception handling and loops have in common in the JCoCo implementation?
- What is lazy evaluation and why is it important to Python and JCoCo?
- What is a closure and why are closures needed?
- How do you create an instance of a class in JCoCo? What instructions must be executed to create objects?
- Write a class, using JCoCo, and create some instances of the class.
3.16. Exercises¶
Consulting the JCoCo assembly language program in the solution to exercise 3.2, provide the contents of the operand stack after each instruction is executed.
Write a JCoCo program which reads an integer from the user and then creates a list of all the even numbers from 0 up to and including that integer. The program should conclude printing the list to the screen. Test your program with JCoCo to be sure it works.
With as few instructions as possible add some exception handling to the previous exercise to print “You didn’t enter an integer!” if the user fails to enter an integer in their program.
In as few instructions as possible write a JCoCo program that computes the sum of the rst n integers where the non-negative n is read from the user.
Write a recursive JCoCo program that adds up the rst n integers where n is read from the user. Re- member, there must be a base case that comes rst in this function and the recursive case must be called on something smaller which is used in computing the solution to the whole problem.
Write a Rational class that can be used to add and multiply fractions together. A Rational number has an integer numerator and denominator. The __add__ method is needed to add together Rationals. The __mul__ method is for multiplication. To get fractions in reduced format you may want to find the greatest common divisor of the numerator and the denominator when creating a Rational number. Write this code in Python first, then disassemble it to get started with this assignment.
You may wish to write the greatest common divisor function, gcd, as part of the class although no self parameter is needed for this function. The greatest common divisor of two integers, 𝑥 and 𝑦, can be defined recursively. If 𝑦 is zero then 𝑥 is the greatest common divisor. Otherwise, the greatest common divisor of 𝑥 and 𝑦 is equal to the greatest common divisor of 𝑦 and the remainder 𝑥 divided by 𝑦. Write a recursive function called gcd to determine the greatest common divisor of 𝑥 and 𝑦.
Disassemble the program and then look for ways of shortening up the JCoCo assembly language program. Use the following main program in your code.
1 2 3 4 5 6 7 8 import disassembler def main (): x = Rational(1,2) y = Rational(2,3) print(x+y) print(x*y) disassembler.disassemble(Rational) disassembler.disassemble(main)From this code you should get the following output which matches the output you should get had this been a Python program. Remember to use Python 3.2 when disassembling your program. And, remember to turn in as short a program as possible while getting this output below from the main program given above.
1 7/6 1/3
3.17. Solutions to Practice Problems¶
These are solutions to the practice problems. You should only consult these answers after you have tried each of them for yourself first. Practice problems are meant to help reinforce the material you have just read so make use of them.
3.17.1. Solution to Practice Problem 3.1¶
The assembly code in CoCo I/O blindly pops the None at the end and then pushes None again before returning from main. This can be eliminated resulting in two fewer instructions. This would also mean that None is not needed in the constants, but this was not eliminated below.
Function: main/0
Constants: None,
"Enter your name: ", "Enter your age: ",
", a year from now you will be",
1, "years old."
Locals: name, age
Globals: input, int, print
BEGIN
LOAD_GLOBAL 0
LOAD_CONST 1
CALL_FUNCTION 1
STORE_FAST 0
LOAD_GLOBAL 1
LOAD_GLOBAL 0
LOAD_CONST 2
CALL_FUNCTION 1
CALL_FUNCTION 1
STORE_FAST 1
LOAD_GLOBAL 2
LOAD_FAST 0
LOAD_CONST 3
BINARY_ADD
LOAD_FAST 1
LOAD_CONST 4
BINARY_ADD
LOAD_CONST 5
CALL_FUNCTION 3
RETURN_VALUE
END
3.17.2. Solution to Practice Problem 3.2¶
As in practice 3.1 the POP_TOP and LOAD_CONST from the end can be eliminated. In the if-then-else code both the then part and the else part execute exactly the same STORE_FAST instruction. That can be moved after the if-then-else code and written just once, resulting in one less instruction and three less overall. Furthermore, if we move the LOAD_GLOBAL for the call to print before the if-then-else statement, we can avoid storing the maximum value in z at all and just leave the result on the top of the operand stack: either x or y. By leaving the bigger of x or y on the top of the stack, the call to print will print the correct value. This eliminates five instructions from the original code.
Function: main/0
Constants: None, 5, 6
Locals: x, y
Globals: print
BEGIN
LOAD_CONST 1
STORE_FAST 0
LOAD_CONST 2
STORE_FAST 1
LOAD_GLOBAL 0
LOAD_FAST 0
LOAD_FAST 1
COMPARE_OP 4
POP_JUMP_IF_FALSE label00
LOAD_FAST 0
JUMP_FORWARD label01
label00: LOAD_FAST 1
label01: CALL_FUNCTION 1
RETURN_VALUE
END
It is worth noting that the code above is exactly the disassembled code from this Python program.
import disassembler
def main():
x = 5
y = 6
print(x if x > y else y)
disassembler.disassemble(main)
When main is called, this code prints the result of a conditional expression. The if-then-else expression inside the print statement is different than an if-then-else statement. An if-then-else statement updates a variable or has some other side-effect. An if-then-else expression, or conditional expression as it is called in Python documentation, yields a value: either the then value or the else value. In the assembly language code we see that the yielded value is passed to the print function as its argument.
3.17.3. Solution to Practice Problem 3.3¶
Function: main/0
Constants: None, 5, 6
Locals: x, y
Globals: print
BEGIN
LOAD_CONST 1
STORE_FAST 0
LOAD_CONST 2
STORE_FAST 1
LOAD_FAST 0
LOAD_FAST 1
COMPARE_OP 1
POP_JUMP_IF_TRUE label00
LOAD_GLOBAL 0
LOAD_FAST 0
CALL_FUNCTION 1
POP_TOP
label00: LOAD_GLOBAL 0
LOAD_FAST 1
CALL_FUNCTION 1
RETURN_VALUE
END
3.17.4. Solution to Practice Problem 3.4¶
The following code behaves differently if the BREAK_LOOP instruction is removed from the program.
Function: main/0
Constants: None, 7, 6
Locals: x, y
Globals: print
BEGIN
SETUP_LOOP label01
LOAD_CONST 1
STORE_FAST 0
LOAD_CONST 2
STORE_FAST 1
LOAD_FAST 0
LOAD_FAST 1
COMPARE_OP 1
POP_JUMP_IF_TRUE label00
BREAK_LOOP
LOAD_GLOBAL 0
LOAD_FAST 0
CALL_FUNCTION 1
POP_TOP
label00: POP_BLOCK
label01: LOAD_GLOBAL 0
LOAD_FAST 1
CALL_FUNCTION 1
RETURN_VALUE
END
3.17.5. Solution to Practice Problem 3.5¶
This is the hello world program with exception handling used to raise and catch an exception. This solution does not include code for finally handling in case an exception happened while handling the exception. It also assumes the exception will match when thrown since JCoCo only supports one type of exception.
Function: main/0
Constants: None, "Hello World!"
Locals: ex
Globals: Exception, print
BEGIN
SETUP_EXCEPT label00
LOAD_GLOBAL 0
LOAD_CONST 1
CALL_FUNCTION 1
RAISE_VARARGS 1
POP_BLOCK
JUMP_FORWARD label01
label00: LOAD_GLOBAL 1
ROT_TWO
CALL_FUNCTION 1
POP_TOP
POP_EXCEPT
label01: LOAD_CONST 0
RETURN_VALUE
END
3.17.6. Solution to Practice Problem 3.6¶
This program adds 5 and 6 together using the __add__ magic method associated with integer objects. First 5 is loaded onto the operand stack. Then LOAD_ATTR is used to load the __add__ of the 5 object onto the stack. This is the function. The argument to __add__ is loaded next which is the 6. The 6 is loaded by the LOAD_CONST instruction. Then __add__ is called with one argument. The 11 is left on the operand stack after the function call. It is stored in x, the print is loaded, x is loaded onto the operand stack, and print is called to print the value. Since print leaves None on the stack, that value is returned from the main function.
Function: main/0
Constants: None, 5, 6
Locals: x
Globals: __add__, print
BEGIN
LOAD_CONST 1
LOAD_ATTR 0
LOAD_CONST 2
CALL_FUNCTION 1
STORE_FAST 0
LOAD_GLOBAL 1
LOAD_FAST 0
CALL_FUNCTION 1
RETURN_VALUE
END
3.17.7. Solution to Practice Problem 3.7¶
Function: main/0
Constants: None, "Enter a string: "
Locals: x, a
Globals: input, print
BEGIN
LOAD_GLOBAL 0
LOAD_CONST 1
CALL_FUNCTION 1
STORE_FAST 0
SETUP_LOOP label02
LOAD_FAST 0
GET_ITER
label00: FOR_ITER label01
STORE_FAST 1
LOAD_GLOBAL 1
LOAD_FAST 1
CALL_FUNCTION 1
POP_TOP
JUMP_ABSOLUTE label00
label01: POP_BLOCK
label02: LOAD_CONST 0
RETURN_VALUE
END
3.17.8. Solution to Practice Problem 3.8¶
A cell variable is needed if an inner function makes a modification to a variable that is located in the outer function. Consider the JCoCo program below. Without the cell the program below would print 10 to the screen and with the cell it prints 11. Why is that? Draw the run-time stack both ways to see what happens with and without the cell variable.
Function: f/1
Function: g/1
Constants: None, 1
Locals: y
FreeVars: x
BEGIN
LOAD_DEREF 0
LOAD_CONST 1
BINARY_ADD
STORE_DEREF 0
LOAD_DEREF 0
LOAD_FAST 0
BINARY_ADD
RETURN_VALUE
END
Constants: None, code(g)
Locals: x, g
CellVars: x
BEGIN
LOAD_CLOSURE 0
BUILD_TUPLE 1
LOAD_CONST 1
MAKE_CLOSURE 0
STORE_FAST 1
LOAD_FAST 1
LOAD_DEREF 0
CALL_FUNCTION 1
LOAD_DEREF 0
BINARY_ADD
RETURN_VALUE
END
Function: main/0
Constants: None, 3
Globals: print, f
BEGIN
LOAD_GLOBAL 0
LOAD_GLOBAL 1
LOAD_CONST 1
CALL_FUNCTION 1
CALL_FUNCTION 1
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
Interestingly, this program cannot be written in Python. The closest Python equivalent of this program is given below. However, it is not the equivalent of the program written above. In fact, the program below won’t even execute. There is an error on the line x = x + 1. The problem is that as soon as Python sees x = in the function g, it decides there is another x that is a local variable in g. But, then x = x + 1 results in an error because x in g has not yet been assigned a value.
def f(x):
def g(y):
x = x + 1
return x + y
return g(x) + x
def main():
print(f(3))
main()
3.17.9. Solution to Practice Problem 3.9¶
A couple things to notice in figure 3.20. The run-time stack contains one stack frame for every function call to factorial. Each of the stack frames, except the one for the main function, point at the factorial code. While there is only one copy of each function’s code, there may be multiple stack frames executing the code. This happens when a function is recursive. There also multiple n values, one for each stack frame. Again this is expected in a recursive function.
Fig 3.20 Execution of fact.casm
3.17.10. Solution to Practice Problem 3.10¶
Python is a very transparent language. It turns out there is function called dir that can be used to print the attributes of an object which are the keys of its dictionary. The dictionary maps names (i.e. strings) to the attributes of the object. The following strings map to their indicated values.
- __init__ is mapped to the constructor code.
- eat is mapped to the eat method.
- speak is mapped to the speak method.
- food is mapped to an integer.
- This is all that is mapped by JCoCo. However, if you try this in Python you will discover that a number of other methods are mapped to default implementations of magic methods in Python including a hash method, comparison methods like equality (i.e. __eq__), a repr method, a str method, and a number of others.
3.17.11. Solution to Practice Problem 3.11¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | Class: Animal
BEGIN
Function: eat/1
Constants: None, 1
Locals: self
Globals: food
BEGIN
LOAD_FAST 0
LOAD_ATTR 0
LOAD_CONST 1
BINARY_ADD
LOAD_FAST 0
STORE_ATTR 0
LOAD_CONST 0
RETURN_VALUE
END
Function: __init__/2
Constants: None, 0
Locals: self, name
Globals: name, food
BEGIN
LOAD_FAST 1
LOAD_FAST 0
STORE_ATTR 0
LOAD_CONST 1
LOAD_FAST 0
STORE_ATTR 1
LOAD_CONST 0
RETURN_VALUE
END
Function: speak/1
Constants: None, "is an animal"
Locals: self
Globals: print, name
BEGIN
LOAD_GLOBAL 0
LOAD_FAST 0
LOAD_ATTR 1
LOAD_CONST 1
CALL_FUNCTION 2
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
END
Class: Dog(Animal)
BEGIN
Function: __init__/2
Constants: None
Locals: self, name
FreeVars: __class__
Globals: super, __init__
BEGIN
LOAD_GLOBAL 0
CALL_FUNCTION 0
LOAD_ATTR 1
LOAD_FAST 1
CALL_FUNCTION 1
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
Function: speak/1
Constants: None, "says woof!"
Locals: self
Globals: print, name
BEGIN
LOAD_GLOBAL 0
LOAD_FAST 0
LOAD_ATTR 1
LOAD_CONST 1
CALL_FUNCTION 2
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
END
Function: main/0
Constants: None, "Mesa"
Locals: mesa
Globals: Dog, eat, speak
BEGIN
LOAD_GLOBAL 0
LOAD_CONST 1
CALL_FUNCTION 1
STORE_FAST 0
LOAD_FAST 0
LOAD_ATTR 1
CALL_FUNCTION 0
POP_TOP
LOAD_FAST 0
LOAD_ATTR 2
CALL_FUNCTION 0
POP_TOP
LOAD_CONST 0
RETURN_VALUE
END
|
3.17.12. Solution to Practice Problem 3.12¶
To get ready to execute the built-in class builder function the stack must contain the following in order from the top of the stack down: The name of the class is on the top of the operand stack. Below the name is the closure of the class initializing function and its environment. Below that is the built-in class builder function itself. The CALL_FUNCTION instruction is executed with two arguments indicated to call the class builder.
Upon its return, the two arguments and the class builder function have been popped from the stack and the instance of the class is left on the operand stack. This class instance may then be stored as a reference from some known location, likely by a STORE_FAST instruction.