CompOrg Spring 2005 Lab

Lab #6 - Subroutines, strings and arrays

3/2/2005

This lab involves making changes to IA32 assembly language programs, compiling, testing and debugging the resulting code.

There is an IA32 instruction set reference here: IA32 Quick Reference (pdf). All the files listed here are also in ~hollingd/public/lab6 on monte.

We haven't covered subroutines yet! There are some quick notes about subroutines at the end of this document.

Exercise 1: factorial subroutine

Write an assembly language factorial subroutine. Here is C code and the corresponding assembly to get you started:


p1.s p1.c
	.file	"p1.c"
	.text
.globl factorial
	.type	factorial,@function
factorial:
	pushl	%ebp
	movl	%esp, %ebp
	movl	$0, %eax
	leave
	ret
.Lfe1:
	.size	factorial,.Lfe1-factorial
	.section	.rodata
.LC0:
	.string	"%d! = %d\n"
	.align 32
.LC1:
	.string	"You have to give me a number!\n"
	.text
.globl main
	.type	main,@function
main:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$8, %esp
	andl	$-16, %esp
	movl	$0, %eax
	subl	%eax, %esp
	cmpl	$1, 8(%ebp)
	jle	.L3
	subl	$12, %esp
	movl	12(%ebp), %eax
	addl	$4, %eax
	pushl	(%eax)
	call	atoi
	addl	$16, %esp
	movl	%eax, -4(%ebp)
	subl	$4, %esp
	subl	$8, %esp
	pushl	-4(%ebp)
	call	factorial
	addl	$12, %esp
	pushl	%eax
	pushl	-4(%ebp)
	pushl	$.LC0
	call	printf
	addl	$16, %esp
	jmp	.L4
.L3:
	subl	$12, %esp
	pushl	$.LC1
	call	printf
	addl	$16, %esp
.L4:
	movl	$1, %eax
	leave
	ret
.Lfe2:
	.size	main,.Lfe2-main

#include <stdio.h>

int factorial(int x) {
  return(0);  /* This needs to be written in assembly! */
}


int main(int argc, char **argv) {
  int num;
  if (argc>1) {
	num = atoi(argv[1]);
	printf("%d! = %d\n",num,factorial(num));
  } else {
	printf("You have to give me a number!\n");
  }
  return(1);
}

Exercise 2: Strings and a strlen subroutine

Write an assembly language subroutine that returns the length of a string. Some issues:

Below is some C code that might help you test your code (gcc -S to generate assembly):

p2.c
#include <stdio.h>

int mystrlen(char *s) {
  return(0); /* you need to write this in assembly */
}

int main(int argc, char **argv) {
  int num;
  if (argc>1) {
	printf("length is %d\n",mystrlen(argv[1]));
  } else {
	printf("You have to give me something!\n");
  }
  return(1);
}


Exercise 3: Arrays

Write an assembly language subroutine that returns the sum of all integers in an array of int. Some issues:

Below is some C code that might help you test your code (gcc -S to generate assembly):

p3.c
#include <stdio.h>

int asum(int *p,int len) {
  return(0); /* you need to write this in assembly */
}

int main(int argc, char **argv) {
  int nums[] = { 1,2,3,4,3,2,1 };
  printf("Sum  is %d\n",asum(nums,sizeof(nums) / sizeof(int)));
  return(1);
}


Exercise 4: Speed and code size

Write an assembly language subroutine that reverses an array of ints. Prizes awarded in two categories: speed and code size. Code size is measured as the number of instructions in the reverse subroutine (including all standard subroutine overhead).

Below is some test code. The numbers to beat (using the below test code on monte) are:

Code size:18 lines
User time:.72 seconds (on monte)
p4.c
#include <stdio.h>

void reverse(int *p,int len) {
  /* write this in assembly */
}

#define SIZE 1000
#define LOOPS 1000000
  
/* expects either a 't or 'l' on the command line
   t runs a test (prints the array before and after)
   l runs a measureable loop 
*/
                   
int main(int argc, char **argv) {
  int nums[SIZE];
  int i;

  for (i=0;i<SIZE;i++) nums[i]=i;

  if (*argv[1] == 't') {
  	printf("Before: ");
	for (i=0;i<SIZE;i++)  printf("%d ",nums[i]);
	printf("\n");

	reverse(nums,SIZE);

	printf("After: ");
	for (i=0;i<SIZE;i++)  printf("%d ",nums[i]);
	printf("\n");
  } else if (*argv[1] == 'l') {
	for (i=0;i<LOOPS;i++) {
	  reverse(nums,SIZE);
	}
  } else {
	printf(" use t to test, l to loop\n");
  }
  return(1);
}


IA32 Subroutines Overview

Stack setup: IA32 subroutines that adhere to the calling convention used by gcc (this includes all the code we've looked at) require that the current stack frame pointer %ebp be saved on the stack so that it can be restored when the subroutine returns to the caller. This requires the instruction push %ebp which should be the first instruction of any subroutine you write. The next instruction should establish the stack frame for this subroutine by setting the stack frame pointer %ebp to point to the current "top of the stack". This second instruction is movl %esp,%ebp (always!).

Returing from the subroutine: Typically a subroutine will compute something and return a value. Return values are simply placed in register %eax before the subroutine jumps back to the caller. Once the right value is in %eax, the subroutine must restore the stack frame pointer %ebp to point to the stack frame for the calling subroutine - this requires first setting the stack pointer to the top of the current stack frame: movl %ebp,%esp and then poping the old stack frame pointer off the stack: pop %ebp. Now the stack frame is restored to the caller's stack frame, and the top of stack contains the return address (the instruction back in the calling subrouting that we should jump to). The ret instruction pops the return address off the stack and jumps there.

NOTE: The IA32 leave instruction does the work of both: movl %ebp,%esp and pop %ebp, and is often used instead of both. This means your subroutines should always end with:

leave
ret

Getting at parameter values: Parameter values are passed on the stack. Since the stack pointer can change inside the body of a subroutine (for example, if the subroutine pushes something on the stack), we record the address of the top of the stack when the subroutine starts (before anything else messes with the stack). This is what the "stack frame pointer", %ebp stuff is all about. Saving the stack frame pointer in %ebp allows us (or a compiler) to access parameters without worrying about whether we have moved the stack pointer. Assuming you always set up the stack frame pointer as described above: the following is how you access parameter values:

Parameter address of the value example
First parameter value %ebp+8 movl 8(%ebp),%eax # put first parameter in eax
Second parameter value %ebp+12 movl 12(%ebp),%eax # put second parameter in eax
Third parameter value %ebp+16 movl 0x10(%ebp),%eax # put third parameter in eax
... and so on ...

Calling a subroutine: To call a subroutine, you must first push the parameter values on the stack (with the pushl instruction) in reverse order. The last pushl will always be the first parameter (by first, I mean leftmost parameter in the C prototype for the function). Then issue the call instruction, this puts the return address on the stack and jumps to the subroutine. When the subrouting returns, it will come back to the instruction immediately following the call.