main
to be defined and procedures other than
printInt
to be called.
You should do the portion of this lab headed "the basics" first, after which you can work on the remaining parts in almost any order. I would like to see everyone achieve at least a couple of the non-basic parts.
Remember to test your work frequently. You don't need to complete this whole section in order to conduct useful tests; each paragraph can be tested.
If you haven't already done this, revise the printInt
statements so they don't neeed to have that one specific name before
the left parenthesis; they can have any name. Whatever name is used
should wind up in the jal
instruction in the generated
assembly code. This gives you relatively general procedure call
statements, though they all still require one integer argument.
Change the syntax of a program so that there can be any number of
procedure definitions (each with any name), rather than always just a
single definition of main
. Be sure to reset the
register allocators between generating code for each procedure and the
next, so that each procedure has the full range of registers
available.
Because each procedure call passes in one argument, it would be
nice if each procedure were expecting an argument. The syntax of
a procedure definition should no longer have an empty pair of
parentheses. Instead, include the keyword int
and
a variable name. This should be treated just like the corresponding
local variable declaration would, except that the assembly code for
the procedure should also include a move
instruction that
copies the argument value from $a0
into the variable's
$s
register.
$a0
through $a3
.
return
statement, which can translate into a
j
instruction that jumps to a label just before the
saved registers are restored from the stack. (Keep in mind that each
procedure will need its own label.)
printInt
as predefined
with one argument.) Also, check that no two procedure definitions use
the same name.
Unless you want every procedure to return an integer, you might
want to differentiate those that do from those that don't by starting
each definition with the keyword int
or
void
.
You will need a return
statement that includes an
expression whose value should be returned. This expression's value
should be moved into the $v0
register. After that, the
code should jump to the tail end of the procedure where registers are
restored from the stack.
You will want to allow procedure calls to serve as expressions. The generated assembly can be the same as for procedure call statements, provided that you limit the contexts in which procedure call expressions are allowed, as described in the following paragraph.
To keep the assembly code for procedure call expressions just as
simple as for procedure call statements, you will need to set up your
grammar so that calls can only appear as top-level expressions. That
is, a procedure call can show up as the right hand side of an
assignment statement, as the test expression in a while
or if
statement, or as the expression in a return
statement. A procedure call can't show up as a subexpression inside
an arithmetic expression, nor as an argument to another procedure
call. (Actually, the first argument would be OK, but there is no
point in getting too fancy.) Subject to this limitation, the same
assembly language can be generated as for a call statement. The
expression's value will wind up in $v0
.
If you are really adventuresome, you could allow procedure call
expressions to serve as subexpressions, rather than only as top-level
expressions. However, this requires that you save any $t
and $a
registers before the call and restore them
afterward. Moreover, you can't leave the return value in
$v0
because the same overall expression might have more
than one procedure call, each with its own return value.