3. Assembly Language

3.1. Overview of the JCoCo VM

_images/coco.png

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)
 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

Practice 3.1

The code in cocoio is a bit wasteful which often happens when compiling a program written in a higher level language. Optimize the code in cocoio so it contains fewer instructions.

You can check your answer(s) at the end of the chapter.

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
 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

1
2
3
4
  onelabel:  LOAD_FAST                     1
             STORE_FAST                    2
  twolabel:
threelabel: LOAD_GLOBAL                    0

Practice 3.2

Without touching the code that compares the two values, the assembly in cocoassembled 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.

You can check your answer(s) at the end of the chapter.

3.4.1. If-Then Statements

import disassembler

def main():
    x = 5
    y = 6
    if x > y:
        print(x)

    print(y)

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
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 cocoifthenasm 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.

You can check your answer(s) at the end of the chapter.

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.

You can check your answer(s) at the end of the chapter.

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.

You can check your answer(s) at the end of the chapter.

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.

You can check your answer(s) at the end of the chapter.

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.

You can check your answer(s) at the end of the chapter.

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)
 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

_images/nested.png

Fig 3.13 Execution of nested.casm

Practice 3.8

The program in nested-fun 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?

You can check your answer(s) at the end of the chapter.

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)
 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 cocorecursion is executed. Use figure 2 as a guide to how you draw this picture. Be sure to include the code, the values of n, and the PC values.

You can check your answer(s) at the end of the chapter.

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?

You can check your answer(s) at the end of the chapter.

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.

You can check your answer(s) at the end of the chapter.

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.

You can check your answer(s) at the end of the chapter.

3.14. Chapter Summary

3.15. Review Questions

  1. How do the Python virtual machine and JCoCo differ? Name three differences between the two implementations.
  2. What is a disassembler?
  3. What is an assembler?
  4. What is a stack frame? Where are they stored? What goes inside a stack frame?
  5. What is the purpose of the block stack and where is it stored?
  6. What is the purpose of the Program Counter?
  7. Name an instruction that is responsible for creating a list object and describe how it works.
  8. Describe the execution of the STORE_FAST and LOAD_FAST instructions.
  9. How can JCoCo read a line of input from the keyboard?
  10. What is the difference between a disassembled Python program and an assembled JCoCo program? Provide a short example and point out the differences.
  11. When a Python while loop is implemented in JCoCo, what is the last instruction of the loop and what is its purpose?
  12. What do exception handling and loops have in common in the JCoCo implementation?
  13. What is lazy evaluation and why is it important to Python and JCoCo?
  14. What is a closure and why are closures needed?
  15. How do you create an instance of a class in JCoCo? What instructions must be executed to create objects?
  16. Write a class, using JCoCo, and create some instances of the class.

3.16. Exercises

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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 cocoio 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.

_images/factorial.png

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.

  1. __init__ is mapped to the constructor code.
  2. eat is mapped to the eat method.
  3. speak is mapped to the speak method.
  4. food is mapped to an integer.
  5. 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.