MMIX LOGO

MMIX Style Guide

Table of Content

Content

Style Guide for MMIX Assembler Programming

This is an evolving collection of hints how to write readable programs in MMIX assembly language.

Names

  • You can use all lower case names. For example i, count, or xptr. This is the preferred way to name registers. Keep these names short but not too short.
  • You can use all UPPER case to name constants. For example NEXT, LINK, or DATA. This is the prefer way of naming constant offsets to a base address.
  • You can use CamelCase. For example StdOut, BinaryRead, NextChar or Main. CamelCase is the preferred way to write program labels.
  • You can use underscores to make long names more readable. For example Data_Segment or lring_size. This is the preferred way of naming entities that are not used very often and therefore require longer, more descriptive names.
  • You should use register numbers like $0 or $1 only for short program fragments. For complete procedures, assign names to the registers using the IS operator. As an exception to this rule, you may use $255 to hold the parameter value for a TRAP instruction or $0 to receive the return value of a subroutine.

Subroutines

Comments

All subroutines should start with a short comment explaining the code to follow. After that, use comments only if the comment adds information, that is not yet obvious from the code. It is better to have readable code without comments than unreadable code with comments.

The Stack Frame

A subroutine starts with setting up its stack frame. The stack frame contains parameters and local variables. For most subroutines, it is sufficient to name the appropriate registers.

Therefore, the code should start naming the parameters and local register variables.

Often you need a temporary variable, just to move data from one instruction to the next. One temporary variable is almost always enough. If you need more than one, think about naming them appropriately. It is good practice to make the temporary variable the last of the local variables (see Nested Subroutines below).

You may use $0 just before a POP 1,0 to receive the function return value. For example:

% converting a number to an ASCII digit
d       IS      $0                parameter
ToAscii ADD     $0,d,'0'
        POP     1,0
or
% computing the sum of an array a of n tetras
a       IS      $0	parameters
n       IS      $1

s       IS      $2      local variables
tmp     IS      $3       

Sum	SET	s,0
	JMP	2F

1H	LDT     tmp,a,0
	ADD	s,s,tmp
	ADD	a,a,4
	SUB	n,n,1	
2H      BP	n,1B

	SET	$0,s
	POP	1,0

Local Namespace

If you use multiple subroutines, name conflicts will be inevitable, unless you use the PREFIX pseudo instruction. In most cases you have a separate namespace for each subroutine. It can have the same name as the subroutine itself. The entry point of the subroutine is usually the only "global" name defined by a subroutine.

Example:

        PREFIX  :Sum:
% computing the sum of an array a of n tetras
a       IS      $0	parameters
n       IS      $1

s       IS      $2      local variables
tmp     IS      $3       

:Sum	SET	s,0
	JMP	2F

1H	LDT     tmp,a,0
	ADD	s,s,tmp
	ADD	a,a,4
	SUB	n,n,1	
2H      BP	n,1B

	SET	$0,s
	POP	1,0
In the example above, the subroutine is referenced in the rest of the program by :Sum.

Calling Subroutines

The most convenient way to pass parameters to a subroutine is the following:
  • Define a local variable tmp as the last local variable (the one with the highest register number) and call the subroutine using a PUSHJ tmp,YZ instruction. This will ensure that none of the other local variables or parameters will be modified by the subroutine.
  • Use tmp+1, tmp+2 to name the registers containing the arguments for the subroutine call. This naming of the arguments remains invariant when reordering or renumbering the local registers in the enclosing subroutine.
  • Use the names tmp, tmp+1, tmp+2 to access the return values.
Example: Calling the above function :Sum, we can write:
	
        LDA     tmp+1,my_array
        SET     tmp+2,my_size
        PUSHJ   tmp,:Sum
Note that assigning a different register number to tmp will automatically renumber the corresponding registers for the argument values. Further, all local variables and parameters (having local register numbers smaller than tmp) will keep their values during the subroutine call.

Nested Subroutines

If one subroutine calls another subroutine, this is called nested subroutines. Two things need to be done: saving the return address and passing parameters.

The most common error when doing so is not saving and restoring the rJ register, which contains the return address for the POP instruction. There are two preferred places to save and restore rJ

  • Start the subroutine with a GET instruction, saving rJ in a local register, and end the subroutine with a PUT instruction, restoring rJ, immediately preceding the POP instruction that terminates the subroutine.
  • If the subroutine contains only a single PUSHJ instruction, you can save rJ immediately before the PUSHJ and restore it immediately after the PUSHJ.

Example: Using the above function :Sum, we can write the function :Average.

        PREFIX  :Average:
% compute the average of an array a of n tetras
a       IS      $0	parameters
n       IS      $1

return  IS      $2      local variables
tmp     IS      $3       

:Sum	GET	return,:rJ
	SET	tmp+1,a
	SET	tmp+2,n
	PUSHJ	tmp,:Sum
	FLOT	tmp,tmp
	FLOT	n,n
	FDIV	$0,tmp,n
	PUT	:rJ,return
	POP	1,0
or
        PREFIX  :Average:
% compute the average of an array a of n tetras
a       IS      $0	parameters
n       IS      $1

return  IS      $2      local variables
tmp     IS      $3       

:Sum	SET	tmp+1,a
	SET	tmp+2,n
	GET	return,:rJ
	PUSHJ	tmp,:Sum
	PUT	:rJ,return
	FLOT	tmp,tmp
	FLOT	n,n
	FDIV	$0,tmp,n
	POP	1,0

Please help to keep this site up to date! If you want to point out important material or projects that are not listed here, if you find errors or want to suggest improvements, please send email to email