Computer Science 3 - 2017

Programming Language Translation


Practical for Week 2, beginning 24 July 2017 - Solutions

There were some very good solutions submitted, and some energetic ones too - clearly a lot of students had put in many hours developing their code. This is very encouraging, but there was also evidence of "sharing" out the tasks, not really working together a proper group, and not developing an interpreter that was up to the later tasks. And do learn to put your names into the introductory comments of programs that you write.

Full source for the solutions summarized here can be found in the ZIP file on the servers - PRAC2A.ZIP

Task 3 involved reading some Parva code for a simple algorithm and then adding suitable commentary. It is highly recommended that you adopt the style shown below, where the higher level code acts as commentary, rather than adopting a line by line explanation of each mnemonic/opcode.

                    ; Demonstrate de Morgan's Laws
                    ; P.D. Terry, Rhodes University, 2017

   0 DSP    2       ; bool x is v0, y is v1
   2 PRNS   "   X      Y    (X.Y)\' X\'+Y\'  (X+Y)\' X\'.Y\'\n\n"
   4 LDA    0       ;                                47 OR             ;
   6 LDC    0       ;                                48 NOT            ;
   8 STO            ; x = false;                     49 PRNI           ;     write(!(x || y));
   9 LDA    1       ; repeat                         50 LDA      0     ;
  11 LDC    0       ;                                52 LDV            ;
  13 STO            ;   y = false;                   53 NOT            ;
  14 LDA    0       ;   repeat                       54 LDA      1     ;
  16 LDV            ;                                56 LDV            ;
  17 PRNI           ;     write(x);                  57 NOT            ;
  18 LDA    1       ;                                58 AND            ;
  20 LDV            ;                                59 PRNI           ;     write(!x && !y);
  21 PRNI           ;     write(y);                  60 PRNS     "\n"  ;     writeLine();
  22 LDA    0       ;                                62 LDA      1     ;
  24 LDV            ;                                64 LDA      1     ;
  25 LDA    1       ;                                66 LDV            ;
  27 LDV            ;                                67 NOT            ;
  28 AND            ;                                68 STO            ;     y = !y;
  29 NOT            ;                                69 LDA      1     ;
  30 PRNI           ;     write(!(x && y));          71 LDV            ;
  31 LDA    0       ;                                72 NOT            ;
  33 LDV            ;                                73 BZE      14    ;   until (!y);
  34 NOT            ;                                75 LDA      0     ;
  35 LDA    1       ;                                77 LDA      0     ;
  37 LDV            ;                                79 LDV            ;
  38 NOT            ;                                80 NOT            ;
  39 OR             ;                                81 STO            ;   x = !x;
  40 PRNI           ;     write(!x || !y);           82 LDA      0     ;
  41 LDA    0       ;                                84 LDV            ;
  43 LDV            ;                                85 NOT            ;
  44 LDA    1       ;                                86 BZE      9     ; until (!x);
  46 LDV            ;                                88 HALT           ; System.Exit();

It is easy to see that this does not use short circuit evaluation of Boolean expressions, as it uses AND and OR, which are infix operators that requires their two operands both to have been evaluated and pushed onto the expression stack. However, it is easy to eliminate the AND and OR by introducing "jumping code" as it is sometimes called. We rely on the idea that for short-circuit semantics to hold we can write the following logical identities:

         x AND y ≡  if x  then y    else false
         x OR y  ≡  if x  then true else y                                   ...

If we apply them to an analysis of various Boolean expressions in the algorithm we could also use

         !x AND !y ≡  if !x  then !y   else false
         !x OR !y  ≡  if !x  then true else !y                                   ...

Admittedly this has quite a lot more code than the binary operator code in the original. However, short-circuited Boolean evaluation is so much better that it is worth developing special opcodes to achieve it, as we shall see later in the course.

                    ; Demonstrate de Morgan's Laws
                    ; P.D. Terry, Rhodes University, 2017
                    ; using short-circuit semantics
   0 DSP    2       ; bool x is v0, y is v1
   2 PRNS   "   X      Y    (X.Y)\' X\'+Y\'  (X+Y)\' X\'.Y\'\n\n"
   4 LDA    0       ;
   6 LDC    0       ;                                58 BRN   63       ;
   8 STO            ; x = false;                     60 LDA    1       ;
   9 LDA    1       ; repeat                         62 LDV            ;
  11 LDC    0       ;                                63 NOT            ;
  13 STO            ;   y = false;                   64 PRNI           ;     write(!(x || y));
  14 LDA    0       ;   repeat                       65 LDA      0     ;
  16 LDV            ;                                67 LDV            ;
  17 PRNI           ;     write(x);                  68 BZE     74     ;
  18 LDA    1       ;                                70 LDC      0     ;
  20 LDV            ;                                72 BRN     78     ;
  21 PRNI           ;     write(y);                  74 LDA      1     ;
  22 LDA    0       ;                                76 LDV            ;
  24 LDV            ;                                77 NOT            ;
  25 BZE   32       ;                                78 PRNI           ;     write(!x && !y);
  27 LDA    1       ;                                79 PRNS     "\n"  ;     writeLine();
  29 LDV            ;                                81 LDA      1     ;
  30 BRN   34       ;                                83 LDA      1     ;
  32 LDC    0       ;                                85 LDV            ;
  34 NOT            ;                                86 NOT            ;
  35 PRNI           ;     write(!(x && y));          87 STO            ;     y = !y;
  36 LDA    0       ;                                88 LDA      1     ;
  38 LDV            ;                                90 LDV            ;
  39 NOT            ;                                91 NOT            ;
  40 BZE   46       ;                                92 BZE      14    ;   until (!y);
  43 LDC    1       ;                                94 LDA      0     ;
  44 BRN   50       ;                                96 LDA      0     ;
  46 LDA    1       ;                                98 LDV            ;
  48 LDV            ;                                99 NOT            ;
  49 NOT            ;                               100 STO            ;   x = !x;
  50 PRNI           ;     write(!x || !y);          101 LDA      0     ;
  51 LDA    0       ;                               103 LDV            ;
  53 LDV            ;                               104 NOT            ;
  54 BZE    60      ;                               105 BZE      9     ; until (!x);
  56 LDC    1       ;                               107 HALT           ; System.Exit();

It might be possible to manipulate these logical expressions to make for an even shorter solution, and you might like to puzzle out how this can be done. See also the discussion on the course web site.


Task 4 - Execution overheads - part one

See discussion of Task 10 below.


Task 5 - Coding the hard way

Task 5 was to hand-compile the Factorial program into PVM code. Most people got a long way towards this. Once again, look at how I have commented this, using "high level" code.

   0 DSP    3       ; n is v0, f is v1, i is v2      42 MUL
   2 LDA    0                                        43 STO
   4 LDC    1                                        44 LDA    2       ;     f = f * i;
   6 STO            ; n = 1;                         46 LDA    2
   7 LDA    0                                        48 LDV
   9 LDV                                             49 LDC    1
  10 LDC    20      ; // max = 20, constant          51 SUB
  12 CLE            ; while (n <= max) {             52 STO            ;     i = i - 1;
  13 BZE    78                                       53 BRN    26      ;   }
  15 LDA    1                                        55 LDA    0
  17 LDC    1                                        57 LDV
  19 STO            ;   f = 1;                       58 PRNI           ;   write(n);
  20 LDA    2                                        59 PRNS   "! = "  ;   write("! = ");
  22 LDA    0                                        61 LDA    1
  24 LDV                                             63 LDV
  25 STO            ;   i = n;                       64 PRNI           ;   write(f);
  26 LDA    2                                        65 PRNS   "\n"    ;   write("\n") (or use PRNL)
  28 LDV                                             67 LDA    0
  29 LDC    0                                        69 LDA    0
  31 CGT            ;   while (i > 0) {              71 LDV
  32 BZE    55                                       72 LDC    1
  34 LDA    1                                        74 ADD
  36 LDA    1                                        75 STO            ;   n = n + 1;
  38 LDV                                             76 BRN    7       ; }
  39 LDA    2                                        78 HALT
  41 LDV

Note that max is a constant, not a variable. There is no need to assign it a variable location and store 20 into this - simply build the value of 20 into the instructions that need to use it. And why on earth redraft the whole algorithm into one that uses a do-while loop (or a repeat-until loop) in place of the suggested while loop?


Task 6 - Trapping overflow and other pitfalls

Checking for overflow in multiplication and division was not always well done. You cannot safely multiply and then try to check overflow (it is too late by then) - you have to detect it in a more subtle way. Here is one way of doing it - note the check to prevent a division by zero when handling multiplication. This does not use any precision greater than that of the simulated machine itself. I don't think many spotted that the PVM.rem opcode also involved division, and some people who thought of using a multiplication overflow check on these lines forgot that numbers to be multiplied can be negative.

An alternative, slightlier risky method is shown as a comment - risky because, if the emulator were written in a system that itself trapped multiplicative overflow, it would all blow up anyway.

       case PVM.mul:           // integer multiplication
         tos = Pop();
         sos = Pop();
         if (tos != 0 && Math.Abs(sos) > maxInt / Math.Abs(tos)) ps = badVal;
// riskier
//       if (tos != 0 && tos * sos / tos != sos) ps = badVal;
         else Push(sos * tos);
         break;
       case PVM.div:           // integer division (quotient)
         tos = Pop();
         if (tos == 0) ps = divZero;
         else Push(Pop() / tos);
         break;
       case PVM.rem:           // integer division (remainder)
         tos = Pop();
         if (tos == 0) ps = divZero;
         else Push(Pop() % tos);
         break;

or for the "inline" assembler

       case PVM.mul:           // integer multiplication
         tos = mem[cpu.sp++];
         if (tos != 0 && Math.Abs(mem[cpu.sp]) > maxInt / Math.Abs(tos)) ps = badVal;
// riskier
//       if (tos != 0 && tos * mem[cpu.sp] / tos != mem[cpu.sp]) ps = badVal;
         else mem[cpu.sp] *= tos;
         break;
       case PVM.div:           // integer division (quotient)
         tos = mem[cpu.sp++];
         if (tos != 0) mem[cpu.sp] /= tos;
         else ps = divZero;
         break;
       case PVM.rem:           // integer division (remainder)
         tos = mem[cpu.sp++];
         if (tos != 0) mem[cpu.sp] %= tos;
         else ps = divZero;
         break;

It is possible to use an intermediate long variable (but don't forget the casting operations or the Abs function):

       case PVM.mul:           // integer multiplication
         tos = Pop();
         sos = Pop();
         long temp = (long) sos * (long) tos;
         if (Math.Abs(temp) > maxInt) ps = badVal;
         else Push(sos * tos);
         break;

If given too large an index for an array to handle, a PVM program will terminate with an array bounds error as correctly trapped by the Push/Pop assembler. The same error would not be trapped by the Inline system, which merrily allows the LDXA opcode to wander wheresoever it likes. To fix this requires the following changes to the PVMInline interpreter. This strategy is discussed in the textbook.

       case PVM.anew:          // heap array allocation
         int size = mem[cpu.sp];
         if (size <= 0 || size + 1 > cpu.sp - cpu.hp - 2)
           ps = badAll;
         else {
           mem[cpu.hp] = size;
           mem[cpu.sp] = cpu.hp;
           cpu.hp += size + 1;
         }
         break;

       case PVM.ldxa:          // heap array indexing
         int adr = mem[cpu.sp++];
         int heapPtr = mem[cpu.sp];
         if (heapPtr == 0) ps = nullRef;
         else if (heapPtr < heapBase || heapPtr >= cpu.hp) ps = badMem;
         else if (adr < 0 || adr >= mem[heapPtr]) ps = badInd;
         else mem[cpu.sp] = heapPtr + adr + 1;
         break;


Task 7 - Arrays

The code as supplied for tracking students' attendance at a practical suffered from various defects - a "student number" of zero is useless, even though it would be accepted quite happily, a student is able to clock in more than once, the constant StudentsInClass has a misleading value, and if a large negative number is supplied the program crashes. A few simple changes will fix some or all of these. I was happy to accept just one or two of these changes, but here is a rather radical rewrite that embraces them all, and uses the value 0 to terminate the program, just so that you can have a look at how this would have been translated. (STUDENTS1.PAV):

  void main () {
  // Track students as they clock in and out of a practical - improved version
  // P.D. Terry, Rhodes University, 2017
  // Improved version

    const StudentsInClass = 100;
    bool[] atWork = new bool[StudentsInClass + 1];

    int student = 1;                         // students are numbered 1 .. 100
    while (student <= StudentsInClass) {
      atWork[student] = false;               // nobody is at the practical to start with
      student = student + 1;
    }

    read("Student? (> 0 clocks in, < 0 clocks out, 0 terminates) ", student);
    while (student != 0) {
      bool clockingIn = true;                // distinguish "in" and "out" easily
      if (student < 0) {
        clockingIn = false;
        student = -student;                  // fix the number
      }
      if (student > StudentsInClass)
        write("Invalid student number\n");
      else if (clockingIn)
        if (atWork[student])  write(student, " has already clocked in!\n");
        else atWork[student] = true;
      else
        if (!atWork[student]) write(student, " has not yet clocked in!\n");
        else atWork[student] = false;
      read("Student? (> 0 clocks in, < 0 clocks out, 0 terminates) ", student);
    } // while

    write("The following students have still not clocked out\n");
    student = 1;
    while (student <= StudentsInClass) {
      if (atWork[student]) write(student);
      student = student + 1;
    } // while

  } // main

A translation into PVM code is a little tedious, and it is easy to leave some of the code out and get a corrupted solution:

                    ; Track students as they clock in and out pf a practical
                    ; P.D. Terry, Rhodes University, 2017
                    ; bool[] atwork is v0, int student is v1
   0 DSP      3     ;                                   106 LDXA           ;
   2 LDA      0     ;                                   107 LDV            ;
   4 LDC      100   ;                                   108 BZE      118   ;    if (atWork[student])
   6 LDC      1     ;                                   110 LDA      1     ;
   8 ADD            ;                                   112 LDV            ;      write (student)
   9 ANEW           ;                                   113 PRNI           ;
  10 STO            ; bool[] atWork =  new bool[...]    114 PRNS     " has already clocked in!\n"
  11 LDA      1     ;                                   116 BRN      128   ;
  13 LDC      1     ;                                   118 LDA      0     ;    else
  15 STO            ; int student = 1;                  120 LDV            ;
  16 LDA      1     ;                                   121 LDA      1     ;
  18 LDV            ;                                   123 LDV            ;
  19 LDC      100   ;                                   124 LDXA           ;
  21 CLE            ;                                   125 LDC      1     ;
  22 BZE      45    ; while (student <= 100) {          127 STO            ;      atWork[student] = true;
  24 LDA      0     ;                                   128 BRN      159   ;
  26 LDV            ;                                   130 LDA      0     ;   else
  27 LDA      1     ;                                   132 LDV            ;
  29 LDV            ;                                   133 LDA      1     ;
  30 LDXA           ;                                   135 LDV            ;
  31 LDC      0     ;                                   136 LDXA           ;
  33 STO            ;   atWork[Student] = false;        137 LDV            ;
  34 LDA      1     ;                                   138 NOT            ;
  36 LDA      1     ;                                   139 BZE      149   ;     if (!atWork[student]
  38 LDV            ;                                   141 LDA      1     ;
  39 LDC      1     ;                                   143 LDV            ;       write(student)
  41 ADD            ;   student = student + 1;          144 PRNI           ;
  42 STO            ;                                   145 PRNS     " has not yet clocked in!\n"
  43 BRN      16    ; }                                 147 BRN      159   ;
  45 PRNS     "Student? (> 0 clocks in, < 0  ...        149 LDA      0     ;
  47 LDA      1     ;                                   151 LDV            ;     else
  49 INPI           ; read(student);                    152 LDA      1     ;
  50 LDA      1     ;                                   154 LDV            ;
  52 LDV            ;                                   155 LDXA           ;
  53 LDC      0     ;                                   156 LDC      0     ;
  55 CNE            ;                                   158 STO            ;      atWork[student] = false];
  56 BZE      166   ; while (student != 0) {            159 PRNS     "Student? (> 0 clocks in, < 0 ...
  58 LDA      2     ;                                   161 LDA      1     ;
  60 LDC      1     ;                                   163 INPI           ;   read(student)
  62 STO            ;   bool clockingIn = true;         164 BRN      50    ; } // while (student != 0)
  63 LDA      1     ;                                   166 PRNS     "The following students have still not ...
  65 LDV            ;                                   168 LDA      1     ;
  66 LDC      0     ;                                   170 LDC      1     ;
  68 CLT            ;                                   172 STO            ; student = 1;
  69 BZE      83    ;  if (student < 0) {               173 LDA      1     ;
  71 LDA      2     ;                                   175 LDV            ;
  73 LDC      0     ;                                   176 LDC      100   ;
  75 STO            ;    clockingIn = false;            178 CLE            ;
  76 LDA      1     ;                                   179 BZE      206   ; while (student <= 100
  78 LDA      1     ;                                   181 LDA      0     ;
  80 LDV            ;                                   183 LDV            ;
  81 NEG            ;                                   184 LDA      1     ;
  82 STO            ;    student = - student            186 LDV            ;
  83 LDA      1     ;  }                                187 LDXA           ;
  85 LDV            ;                                   188 LDV            ;
  86 LDC      100   ;                                   189 BZE      195   ;   if (atWork[student])
  88 CGT            ;                                   191 LDA      1     ;
  89 BZE      95    ;  if (student > StudentsInClass)   193 LDV            ;
  91 PRNS     "Invalid student number"                  194 PRNI           ;     write(student);
  93 BRN      159   ;                                   195 LDA      1     ;
  95 LDA      2     ;                                   197 LDA      1     ;
  97 LDV            ;                                   199 LDV            ;
  98 BZE      130   ;  else if (clockingIn)             200 LDC      1     ;
 100 LDA      0     ;                                   202 ADD            ;
 102 LDV            ;                                   203 STO            ;     student = student + 1;
 103 LDA      1     ;                                   204 BRN      173   ; } // while (student <= 100
 105 LDV            ;                                   206 HALT           ; System.Exit()


Task 8 - Your lecturer is quite a character

To be able to deal with input and output of character data we need to add two new opcodes, modelled on the INPI and PRNI codes whose interpretation would be as below. All of the new opcodes require additions to the lists of opcodes in the assembler and interpreter (be careful of two-word opcodes; they crop up in several places).

Note that the output of numbers was arranged to have a leading space; this is not as pretty when you see i t a p p l i e d t o c h a r a c t e r s , i s i t - which is why the call to results.write uses a second argument of 1, not 0 (this argument could have been omitted). Note the use of the modulo arithmetic to make quite sure that only sensible ASCII characters will be printed:

       case PVM.inpc:          // character input
         adr = Pop();
         if (InBounds(adr)) {
           mem[adr] = data.ReadChar();
           if (data.error()) ps = badData;
         }
         break;
       case PVM.prnc:          // character output
         if (tracing) results.write(padding);
         results.Write((char) (Math.Abs(Pop()) % (maxChar + 1)), 1);
         if (tracing) results.WriteLine();
         break;

or for the "inline" assembler

       case PVM.inpc:          // character input
         mem[mem[cpu.sp++]] = data.ReadChar();
         break;
       case PVM.prnc:          // character output
           if (tracing) results.Write(padding);
         results.Write((char) (Math.Abs(mem[cpu.sp++]) % (maxChar + 1)), 1);
           if (tracing) results.WriteLine();
         break;

To build a really safe system there are further refinements we could make. It can be argued that we should not try to store a value outside of the range 0 .. 255 into a character variable. This suggests that we should have a range of STO type instructions that check the value on the top of stack before assigning it. One of these - STOC to act as a variation on STO - would be interpreted as follows; we would need another to handle STLC and so on (these have not yet been implemented in the solution kit).

       case PVM.stoc:          // character checked store
         tos = Pop(); adr = Pop();
         if (inBounds(adr))
           if (tos >= 0 && tos <= maxChar) mem[adr] = tos; else ps = badVal;
         break;

or for the "inline" assembler, omitting the checking

       case PVM.stoc:          // character (unchecked) store
         tos = mem[cpu.sp++]; mem[mem[cpu.sp++]] = tos;
         break;

Introducing opcodes to convert to lower or upper case is simply done by using the methods from the C# Char wrapper class (notice the need for casting operations as well, to satisfy the C# compiler):

       case PVM.low:           // toLowerCase
         Push(Char.ToLower((char) Pop()));
         break;
       case PVM.cap:           // toUpperCase
         Push(Char.ToUpper((char) Pop()));
         break;

or for the "inline" assembler - note that cpu.sp is left unaltered.

       case PVM.low:           // toLowerCase
         mem[cpu.sp] = Char.ToLower((char) mem[cpu.sp]);
         break;
       case PVM.cap:           // toUpperCase
         mem[cpu.sp] = Char.ToUpper((char) mem[cpu.sp]);
         break;

The INC and DEC operations are best performed by introducing opcodes that assume that an address has been planted on the top of stack for the variable (or array element) that needs to be incremented or decremented. This may not have been apparent to everyone, but consider (as hinted in the prac sheet) a statement like a[i+j]++;

       case PVM.inc:           // ++
         adr = Pop();
         if (inBounds(adr)) mem[adr]++;
         break;
       case PVM.dec:           // --
         adr = Pop();
         if (inBounds(adr)) mem[adr]--;
         break;

or for the "inline" assembler

       case PVM.inc:           // ++
         mem[mem[cpu.sp++]]++;
         break;
       case PVM.dec:           // --
         mem[mem[cpu.sp++]]--;
         break;

With all these in place the string reversal algorithm can be programmed as follows:

                 ; Read a sentence and reverse in UPPER CASE 30   LDC   1  ;
                 ; P.D. Terry, Rhodes University, 2017       32   SUB      ;
                 ; char[] sentence is v0; leng is v1         33   LDXA     ;
                 ; original opcode set                       34   LDV      ;
                                                             35   LDC   46 ;    // '.' is 46 in ASCII table
   0   DSP   2   ;                                           37   CEQ      ;
   2   LDA   0   ;                                           38   BZE   13 ; until (sentence[leng-1] = '.');
   4   LDC   256 ;                                           40   LDA   1  ;
   6   ANEW      ;                                           42   LDV      ;
   7   STO       ;  sentence = new char[256];                43   LDC   0  ;
   8   LDA   1   ;                                           45   CGT      ; while (leng > 0) {
  10   LDC   0   ;                                           46   BZE   63 ;
  12   STO       ;  leng = 0;                                48   LDA   1  ;
  13   LDA   0   ;  repeat {                                 50   DEC      ;   leng--;
  15   LDV       ;                                           51   LDA   0  ;
  16   LDA   1   ;                                           53   LDV      ;
  18   LDV       ;                                           54   LDA   1  ;
  19   LDXA      ;                                           56   LDV      ;
  20   INPC      ;    read(sentence[leng]);                  57   LDXA     ;
  21   LDA   1   ;                                           58   LDV      ;
  23   INC       ;    leng++;                                59   CAP      ;
  24   LDA   0   ;  }                                        60   PRNC     ;   write(upper(sentence[leng]);
  26   LDV       ;                                           61   BRN   40 ; }
  27   LDA   1   ;                                           63   HALT     ; System.Exit()
  29   LDV       ;


Task 9 - Improving the opcode set still further

Once again, adding the LDL N and STL N opcodes is very easy. Unfortunately, it is easy to leave some of the changes out and get a corrupted solution. The PVMAsm class requires modification in the switch statement that recognizes two-word opcodes:

   case PVM.brn:                            // all require numeric address field
   ...
   case PVM.ldc:
   case PVM.ldl:  // +++++++++++++++++  addition
   case PVM.stl:  // +++++++++++++++++  addition
     codeLen = (codeLen + 1) % PVM.memSize;
     if (ch == '\n')                        // no field could be found
       error("Missing address", codeLen);
     else {                                 // unpack it and store
       PVM.mem[codeLen] = src.ReadInt();
       if (src.Error()) error("Bad address", codeLen);
     }
     break;

The PVM class requires several additions. We must add to the switch statement in the Trace and ListCode methods (several submissions missed this):

    static void Trace(OutFile results, int pcNow, bool traceStack, bool traceHeap) {
      switch (cpu.ir) {
        ...
        case PVM.ldl:  // +++++++++++++++++  addition
        case PVM.stl:  // +++++++++++++++++  addition
      }
      results.WriteLine();
    }

and we must provide case arms for all the new opcodes. A selection of these follows; the rest can be seen in the solution kit. Notice that for consistency all the "inBounds" checks should really be performed on the new opcodes too (several submissions missed this, and they have been left out here too so that you can add them yourselves). Firstly the basic two-word ones:

          case PVM.ldl:           // push local value
            Push(mem[cpu.fp - 1 - Next()]);
            break;
          case PVM.stl:           // store local value
            mem[cpu.fp - 1 - Next()] = Pop();
            break;

or for the "inline" assembler where we can code it all into one statement:

          case PVM.ldl:           // push local value
            mem[--cpu.sp] = mem[cpu.fp - 1 - mem[cpu.pc++]];
            break;
          case PVM.stl:           // store local value
            mem[cpu.fp - 1 - mem[cpu.pc++]] = mem[cpu.sp++];
            break;

A great many submissions made a rather bizarre error. Part of the original kit read as follows - where the action for all the "missing" opcodes was to trap an error if they were encountered (by accident?)

          case PVM.lda_2:         // push local address 2
          case PVM.lda_3:         // push local address 3
          case PVM.ldl:           // push local value
          case PVM.ldl_0:         // push value of local variable 0
          case PVM.ldl_1:         // push value of local variable 1
          case PVM.ldl_2:         // push value of local variable 2
          case PVM.ldl_3:         // push value of local variable 3
          case PVM.stl:           // store local value

Incompletely modifying the code on the lines shown below would have had the effect of adding PVM.lda_2, PVM.lda_3 as "extra" labels to the PVM.ldl clause (and similarly for other cases)!

          case PVM.lda_2:         // push local address 2
          case PVM.lda_3:         // push local address 3
          case PVM.ldl:           // push local value
            mem[--cpu.sp] = mem[cpu.fp - 1 - mem[cpu.pc++]];
            break;
          case PVM.ldl_0:         // push value of local variable 0
          case PVM.ldl_1:         // push value of local variable 1
          case PVM.ldl_2:         // push value of local variable 2
          case PVM.ldl_3:         // push value of local variable 3
          case PVM.stl:           // store local value
            mem[cpu.fp - 1 - mem[cpu.pc++]] = mem[cpu.sp++];
            break;

In improving the string reversal program, some people forgot to introduce the LDL and STL wherever they could, did not incorporate CAP and INC/DEC and ran the last loop the wrong way! If one codes carefully, this program reduces to the code shown below:

                 ; Read a sentence and reverse in UPPER CASE 17   SUB       ;
                 ; P.D. Terry, Rhodes University, 2017       18   LDXA      ;
                 ; char[] sentence is v0; leng is v1         19   LDV       ;
                 ; extended opcode set                       20   LDC   46  ;   // '.' is 46 in ASCII table
                                                             22   CEQ       ;
   0   DSP   2   ;                                           23   BZE   8   ; until (sentence[leng-1] = '.');
   2   LDC   256 ;                                           25   LDL_1     ;
   4   ANEW      ;  sentence = new char[256];                26   LDC_0     ;
   5   STL_0     ;                                           27   CGT       ; while (leng > 0) {
   6   LDC_0     ;                                           28   BZE   40  ;
   7   STL_1     ;  leng = 0;                                30   LDA_1     ;
   8   LDL_0     ;  repeat {                                 31   DEC       ;   leng--;
   9   LDL_1     ;                                           32   LDL_0     ;
  10   LDXA      ;                                           33   LDL_1     ;
  11   INPC      ;    read(sentence[leng]);                  34   LDXA      ;
  12   LDA_1     ;                                           35   LDV       ;
  13   INC       ;    leng++;                                36   CAP       ;
  14   LDL_0     ;  }                                        37   PRNC      ;   write(upper(sentence[leng]);
  15   LDL_1     ;                                           38   BRN   25  ; }
  16   LDC_1     ;                                           40   HALT      ; System.Exit();


Task 10 - Execution overheads - part two

In the prac kit you were supplied with a second translation SIEVE2.PVM of a cut down version of the same prime-counting program SIEVE.PAV as was used in Task 4, but this time using the extended opcode set developed in the last task. The kit also included the code that could be executed if the PVM were extended still further on the lines of the suggestions on page 44 of the textbook.

Running SIEVE1.PVM through both of the original and modified assemblers, and SIEVE2.PVM and SIEVE3.PVM through both of the modified assemblers gave the following timings for the same limit (4000) and number of iterations (100) on my machines, one a laptop running Windows XP and one a desktop running Windows 7-32.

   ┌────────────────────────────────┬────────────────────────┬────────────────────────┬────────────────────────┐
   │ Desktop Machine (Win 7-32)     │  Sieve1.pvm   (1.00)   │  Sieve2.pvm   (0.78)   │  Sieve3.pvm  (0.75)    │
   │                                │                        │                        │                        │
   │ ASM1  (Push/Pop)               │      0.73              │      0.57              │      0.55              │
   │ ASM2  (Inline)                 │      0.30     (0.41)   │      0.20     (0.36)   │      0.13    (0.24)    │
   │                                │                        │                        │                        │
   └────────────────────────────────┴────────────────────────┴────────────────────────┴────────────────────────┘

   ┌────────────────────────────────┬────────────────────────┬────────────────────────┬────────────────────────┐
   │ Laptop machine (XP-32)         │  Sieve1.pvm   (1.00)   │  Sieve2.pvm   (0.75)   │  Sieve3.pvm  (0.67)    │
   │                                │                        │                        │                        │
   │ ASM1  (Push/Pop)               │      1.14              │      0.85              │      0.76              │
   │ ASM2  (Inline)                 │      0.52     (0.46)   │      0.30     (0.35)   │      0.27    (0.35)    │
   │                                │                        │                        │                        │
   └────────────────────────────────┴────────────────────────┴────────────────────────┴────────────────────────┘

The Desktop times were about 65% of those on the slower Laptop. The Inline times were about 40% of the Push/Pop system with the original limited opcode set. The Inline times were about 30% of the Push/Pop system with the extended opcode set,

The reasons are not hard to find. The InLine emulator makes very few function calls within the fetch-execute cycle, whereas the Push/Pop one makes a very large number, each carrying an extra overhead. Similarly, the introduction of the LDL and STL codes allowed for fewer opcodes to be interpreted to achieve the desired result.

If one wishes to improve the performance of the interpreter further it might make sense to get some idea of which opcodes are executed most often. Clearly this will depend on the application, and so a mix of applications might need to be analysed. It is not difficult to add a profiling facility to the interpreter, and this has been done in yet another interpreter that you can find in the solution kit. Running this on the Sieve files yielded some interesting results. For a start, there were enormous numbers of steps executed - probably more than you might think.

        Original opcodes           Extended opcode set          Extended opcode set
                                   LDL and STL used             LDL, STL, LDL_x STL_x

      39 494 323 operations.     27 070 118 operations. (68%)    (same op count)

        LDA     10824405           LDL      8186502             LDL_2    3821200
        LDV      9386302           LDC      4148705             LDL_1    2582600
        LDC      4948605           BZE      2182801             BZE      2182801
        STO      3165703           CLE      1782901             LDC_0    1910701
        BZE      2182801           BRN      1727700             LDC      1782902
        CLE      1782901           LDXA     1727600             CLE      1782901
        ADD      1782701           STO      1327700             BRN      1727700
        BRN      1727700           STL      1038103             LDL_0    1727600
        LDXA     1727600           ADD       982801             LDXA     1727600
        CGT       982800           AND       982800             STO      1327700
        AND       982800           CGT       982800             ADD       982801
        HALT           1           LDA       799900             STL_2     982800
        ANEW           1           INC       799900             CGT       982800
        PRNS           1           LDV       399900             AND       982800
        PRNI           1           DSP            1             INC       799900
        DSP            1           PRNI           1             LDA_1     799800
                                   PRNS           1             LDC_1     454902
                                   ANEW           1             LDV       399900
                                   HALT           1             STL        55101
                                                                LDL        55001
                                                                LDC_2        200
                                                                STL_1        200
                                                                LDL_3        101
                                                                LDA_3        100
                                                                STL_3          1
                                                                STL_0          1
                                                                HALT           1
                                                                ANEW           1
                                                                PRNS           1
                                                                PRNI           1
                                                                DSP            1


Home  © P.D. Terry