Session 18
Toward a Run-Time System in TM
Set Up
Next time, we begin our study of the run-time system that enables a compiled program to run. Your project targets a virtual machine, TM. Both the compiled program and the run-time system will run in TM.
Today, we do a sequence of exercises leading from the simplest TM program up to a TM program that calls two functions in the simplest fashion possible. The goals are to help you begin to familiarize yourself with the TM assembly language and to demystify what, for many, is the most complex part of code generation: calling functions. For each exercise, I give a solution to the exercise, along with some slides I used while discussing the code.
To prepare for today, you worked through a lab exercise in which you downloaded, compiled, and ran two versions of the TM simulator. You may want to put those TM simulators in your path... You will be using them a lot the rest of the semester.
Quick Introduction to TM
For today, you read a little bit about the TM simulator but perhaps not about TM itself. It has a small instruction set with:
- six register-only opcodes for I/O and arithmetic
- one register-only opcode for terminating execution
- four register-memory opcodes to manipulate memory
- six register-memory opcodes for branching
Exercise 1
Write a TM program that prints 1.
Solution:
Discussion
Slide 1: Lines are numbered in TM programs.
Slide 2: The order of the lines in the program file does not matter.
Slides 3-5: This program uses three instruction codes.
Note: All TM registers are initialized to 0.
Slide 6: This TM program is a simplified version of the shortest possible Klein program, which we discussed in Session 13:
function main(): integer 1
Every program must define a function named
main,
which is called first when the program runs.
We'll work our way up to doing this.
Exercise 2
Write a TM program that computes 3 + 4 and prints the result.
Solution:
Discussion
Slide 1: TM supports arithmetic only in registers.
Slide 2: This corresponds to a near-minimal Klein program:
function main(): integer 3 + 4
Exercise 3
Write a TM program that squares its command-line argument and prints the result.
Solution:
Before Starting
Where will our TM program find the command-line arguments?
The original version of TM did not have command-line arguments, only user input. Unfortunately for us, Klein does not have user input, only command-line arguments.
A home-grown extension to TM places command-line arguments in DMEM[1..n].
When the TM virtual machine starts, DMEM[0] holds the number of the last slot of DMEM, by default 1023. The rest of the slots hold 0.
Note: IMEM is distinct from DMEM.
Discussion
Slide 1: LD computes the address ([rX] + offset) and reads DMEM.
Even code this short may benefit from inline comments.
Slide 2: In this particular example, I use r0 to compute. From now on, though, I will use r0 as 0 — a TM idiom!
Slide 3: A program with two arguments doesn't look much different.
Slide 4: It benefits from using the R0 idiom.
s[tep] n — Execute n (default 1) TM instructions r[egs] — Print the contents of the registers d[Mem] b n — Print n dMem locations starting at b
Slide 5: The two-argument version corresponds to a Klein program with arguments:
function main(m: integer, n: integer): integer 3 + 4
But
— again. What about calling main as a
function?
Exercise 4
Solution:
Before Starting
Exercise Slide 5: What does it mean to "jump to an instruction"? The program transfers control to another location in the code, where it operates on some values, produces an answer, and branches back. The arguments coming in to the function, the value going out, and the place to branch back to must all be placed in locations that both the branching code and the computing code know about.
For now, these can be registers.
Exercise Slide 6: This idea led to one of the first design patterns in the history of programming: Subroutine. This pattern is implemented as a language primitive in nearly every programming language we use, so we rarely think of it as a pattern at all.
Exercise Slides 7-8: To write your Klein compiler, though, you will need to implement the pattern in TM assembly language.
Discussion
Slide 1: Code this long benefits from inline comments.
This program uses registers in place of slots stored in a stack frame. When we do use stack frames, they will be in DMEM.
In the code, note:
- Lines 2 and 7: the use R7 as the program counter
- Line 6: the use of ADD with R0 to move a value to a new register
Yes, we could have MULtiplied into R4 directly. In general, though, the program will have to move its result to the result slot on the stack.
Slides 2 and 3: Highlight the calling sequence in the calling function and the called function.
Slides 4 and 5: Highlight the return sequence in the called function and the calling function.
Slide 6: This code corresponds to a Klein program with a single argument.
function main(n: integer): integer n * n
Slide 7: What if square(n) is a
function, too?
function main(n: integer): integer square(n) function square(m: integer): integer m * m
Exercise 5
main(n) and
square(m).
Solution:
Discussion
We do not always get to the final exercise in class. If we did not get there, try it in the comfort of your own home, and only then look at the code and slides.
We now need more registers to store arguments, return values, and return addresses. We still have room(!), but this program shows how to use DMEM to save R6 in MAIN before calling SQUARE and how to return to that location at the end of MAIN.
Consider the calling and return sequences:
-
calling sequence, run-time to
main(Slides 2-3: caller and called) -
calling sequence,
maintosquare(Slides 4-5: caller and called) -
return sequence,
squaretomain(Slides 6-7: called and caller) -
return sequence,
mainto run-time (Slides 8-9: called and caller)
Wrap Up
You can download all the materials for today: code, slides, and notes, as a single archive.
If you have any questions, comments, or suggestions, please let me know.