CS 301 Lab: Macros and the Stack


        In this lab you will learn how to use a common macro tool, m4, to create
        custom macro operations to simplify repetitive tasks. You will also learn
        basic stack operations. You will then practice writing and using macros to 
        perform system calls and stack based operations.

Macros

When you program in any language you will often find yourself repeating a simple or complex sequence of commands over and over again. This is especially true in assembly language programming. If you have ever looked through your code, copied and pasted a segment, then changed only variable (or register) names you might want to consider making a macro.

Macros are similar to functions - they can hide an operation behind a simple descriptive name. However, macros are not functions. A macro call is replaced by text - every time you call the macro your program gets a little longer. A function call always jumps to the same piece of code – the only extra code you will need is the few lines it takes to set up the parameters (and save register states – we'll talk about that next lab).

Here's an example of a code fragment you might want to make into a macro:

	la $a0, MyString	#Get the address of a string
	li $v0, 4 		#Call code to print a string
	syscall 		#Print the string

This is one of those things you will type over and over again in an interactive program. It's not that hard to remember, but the following is even easier:

	#Print String From Label Macro
 	printStrLab(MyString)

You'll never forget the syscall or use the wrong call code.

Using the m4 Macro Processor

m4 Reference Materials:

Your textbook may suggest that SPIM supports user defined macros. However, SPIM has no built-in macro support. You can use m4 instead. The m4 macro language is a common unix tool. Examples of its use include:

James R. Larus, the man who wrote the SPIM interpreter, has this to say about why SPIM doesn't support macros:

"If macros and conditional assembly are useful, why do assemblers for unix systems rarely, if ever, provide them? One ... reason is that other tools on unix—such as cpp, the C preprocessor, or m4, a general macro processor—can provide macros and conditional assembly language programs."

m4 is available to you from the command line on the PCs in CL134.5 or on Hercules. It can be used interactively if you type m4 and then press enter. You can learn a lot about its behaviour in this mode, but it is not practical for real use. To exit interactive mode hit +c

To use m4 macros in a program you can simply write m4 commands directly into the file you want to use macros in. Usually you will keep macro definitions in macro libraries and include them in your program as needed.

In Lab Exercise: Using a Macro Library

Download this file and save it to c:\workarea:  printLib.m4

The contents of this file are not very important right now. All you need to know is that it defines 4 string printing macros:

Macro Description
printStrLab Prints a string using a label to get the address. eg:
  printStrLab(str1)
printStrReg Prints a string from an address stored in a register - may be used to print a portion of a string.  eg:
  printStrReg($t1)
printStrLit Prints a literal string. eg:
  printStrLit("I will be printed to the console\n")
printStr This is a wrapper macro that tries to guess what type of argument is being passed.
If the first character is a " it uses printStrLit,
if the first character is a $ it uses printStrReg,
otherwise it uses printStrLab.
eg:
  printStr(str1)
  printStr($t1)
  printStr("I will be printed to the console\n")

There are also some utility macros in there, but you can ignore them for now.

Here is a sample program that uses all of those macros. Copy and paste it into notepad and save it as printDemo.m4.
##
## Program name: printDemo.m4
##
## This program demonstrates how to include and use m4 macros
##
## Several strings are printed to the console
##

include(printLib.m4)		#include a needed macro library

	.text
	.globl __start
__start:
				#this string will be labeled str1
	printStrLit("I'm a literal string.\n")
				#the next will be labeled str2
	printStr("I am too.\n\n\n")

	printStrLab(myStr)
	printStr(str2)

	printStr("Registers can be  used to point to different parts of strings\n")
	la $t1, myStr
	addi $t1, $t1, 13	#Point somewhere else in the string
	printStrReg($t1)
	addi $t1, 11 		#Point a little farther in the same string
	printStr($t1)
	
	li $v0, 10
	syscall

	.data
myStr: .asciiz "I'm a string defined in the data section\n"

To turn this file into a MIPS assembly program pass it through the m4 macro processor. m4 is a command line tool so you'll need to open a command shell. Click Start>run... type cmd then click OK. In the window that appears type:
c:
cd \workarea
m4 printDemo.m4 > printDemo.s

Take a look at printDemo.s. The expansion is considerably longer – 71 lines vs. 32 lines.

You can run printDemo.s from the command line by typing:

pcspim printDemo.s
Your output should look like this:
I'm a literal string.
I am too.


I'm a string defined in the data section
I am too.


These two strings were printed from registers.
defined in the data section
the data section

Writing Your Own Macros

m4 has two kinds of macros: builtin and composite. For simplicity we will call builtins "commands" and composites "macros". A macro is defined with the define command. define takes two arguments, the first is the new macro's name and the second is its output. This is a basic macro:

define(`MyMacro',`Hello I am your macro!')
MyMacro #calls the MyMacro macro you defined

The ` mark (located at the upper left of your keyboard) and the ' mark (located next to the Enter key) indicate quoted text. It is usually a good idea to quote the arguments to an m4 macro, but it is not always necessary. Consult the documentation to learn more. To test it out create a new text file called firstmac.m4, copy the code above into it—for this example be careful not to leave a blank line at the end. Save the file and exit. Run m4 by typing:

m4 firstmac.m4

You should get this output:


Hello I am your macro! #calls the MyMacro macro you defined

In this format macros are perfect for creating mnemonics for values you use in your program. You could, for example, define PI and us it where needed.

The # mark in the program indicates the beginning of a comment. Any macros found after the comment symbol on a line are not expanded. Hence MyMacro is left in the comment part of the output but expanded to "Hello I am your macro!" at the beginning of the line.

Controlling Macro Expansion

Beware of unwanted macro expansion. The # mark is one tool you can use. An empty quote, `', in the middle of a macro name can be used to prevent it from being expanded, or it can be used to separate two macros without adding a blank space to the output.

Putting quotes around a macro can delay its expansion. This is useful when passing a macro as an argument to a macro or command.

It is important to be aware of what commands and macros are already defined. Some will expand without any arguments and produce unwanted results. Click here for a list of commands to watch out for.

Controlling Whitespace

Notice that there is an extra blank line in the output from the last example. Any text that is not inside an m4 command is printed, including blank spaces and new lines. To prevent or control output you can use these commands:

Command Effect
dnl Suppress the rest of the line up to and including the newline character.
divert(-1) Send all output after this command to nowhere.
divert(0) Send all output after this command to stdout

An m4 library usually uses these commands together to prevent extra white space from appearing in the output.

Leading whitespace sent as an argument to a macro or command is ignored unless it is inside quotes. Trailing whitespace is always sent. Notice the difference between these two examples:

divert(-1)
define(`Hi',    `tight quotes'   )
define(`HiQ',`   loose quotes')
divert(0)dnl
Hi with tight quotes
HiQ with loose quotes
H`'iQ won't expand dnl and I won't print

which becomes:

tight quotes    with tight quotes
   loose quotes with loose quotes
HiQ won't expand

Argument Substitution

Now you need to know how to send arguments to your macros. All macros can receive any number of arguments. They are not declared when the macro is defined. Instead they are used when they are asked for in the macro body. The arguments are labelled $0, $1, ... $n. To see an example look at the printStrLab macro in stringLib.m4. It looks like this:

define(printStrLab,
       `#Print the string from label $1
        li $v0, 4
        la $a0, $1
        syscall
')

Advanced Macros

m4 can act like a real programming language with variables, branching, loops, and recursive functions. Here you will learn only the basics. To learn more consult the reference materials listed at the beginning of this section

The Stack

Memory allocated to your program is divided into three segments text, data, and stack. The text and data segments are located near the beginning of your program's memory and grow up in memory address. The stack segment begins in the middle of your memory space and grows back toward the data segment.

Fig. 1

The three pointers on the left all have special purposes when referring to memory.

The stack is used to temporarily store information. It is most often used for function calls. The contents of registers a calling program wants to preserve are stored on the stack prior to calling and restored afterwards. Registers that the called function needs to use but has agreed to preserve are saved by that function. When all the work is done, the function restores saved registers to their original states. Then the calling program restores the registers it saved and processing continues.

Fig. 2

The hardware stack is similar to the stack ADT. It grows only at one end. We usually interact with it using push and pop operations. A push stores one word, and a pop removes one word.

Some hardware has built-in push and pop operations. MIPS does not have them. Instead we directly manipulate the stack pointer and access the memory next to it.

To push and pop $s1 you would use this code:

	#push 
	addi	$sp, $sp, -4 	#increment the stack pointer by one word
	sw	$s1, 0($sp)	#store information in new space on the stack

	#Do some stuff

	#pop
	lw	$s1, 0($sp)	#load information off the stack
	addi	$sp, $sp, 4	#shrink the stack

These operations produce an effect like this:

If your program is the only one that will be running you could reverse the order of the lw and addi instructions. If your program will be running on a modern OS it could be interrupted between the two instructions. Then the topmost datum could be overwritten before it is popped. This really happens and is hard to diagnose in large programs.

Unlike a normal stack ADT the hardware stack supports random access; you can read or write any item in the stack. This can be used to your advantage. Simply make a hole in the stack, remember where it is, and you've got temporary memory to play with. This is used for storing parameters to functions and setting up local variables (more on this next lab). It can also be used to optimize stack operations.

Suppose you need to store 5 registers. You can use normal push and pop operations. Or you can make a hole in the stack big enough for the 5 registers and then use the stack pointer to do immediate memory access to store stuff in the stack. Eg.:

	#push 5 registers 
	addi	$sp, $sp, -4 	
	sw	$s1, 0($sp)	
	addi	$sp, $sp, -4 	
	sw	$s2, 0($sp)	
	addi	$sp, $sp, -4 	
	sw	$s3, 0($sp)
	addi	$sp, $sp, -4 
	sw	$s4, 0($sp)
	addi	$sp, $sp, -4 
	sw	$s5, 0($sp)

	#pop 5 registers 
	lw	$s5, 0($sp)
	addi	$sp, $sp, 4
	lw	$s4, 0($sp)
	addi	$sp, $sp, 4
	lw	$s3, 0($sp)
	addi	$sp, $sp, 4
	lw	$s2, 0($sp)
	addi	$sp, $sp, 4
	lw	$s1, 0($sp)
	addi	$sp, $sp, 4
vs.
	#push 5 registers at once
	addi	$sp, $sp, -20 	
	sw	$s1, 16(sp)	
	sw	$s2, 12(sp)	
	sw	$s3, 8(sp)
	sw	$s4, 4(sp)
	sw	$s5, 0(sp)

	#pop 5 registers at once
	lw	$s5, 0($sp)
	lw	$s4, 4($sp)
	lw	$s3, 8($sp)
	lw	$s2, 12($sp)
	lw	$s1, 16($sp)
	addi	$sp, $sp, 20

That made the push/pop set run in 12 clocks instead of 20. Not bad.

The push and pop stack operations are ideal candidates for macros. The instructions used to implement them are prone to error and they are used frequently. The foreach macro could be used to push multiple registers at once. You would need to invoke it twice: once to count the registers sent as an argument, and again to store or load them.

Remember:Things pop off the stack in the reverse order that you push them on.


Lab Assignment

Use the stack to reverse input. Implement and use macros for I/O and stack operations.

This page last modified:
Accessed     times.

CS Dept Home Page
CS Dept Class Files
CS115 Lab Files

Copyright: Department of Computer Science, University of Regina.