<- previous    index    next ->

Lecture 7 Subroutines


Pass integer parameters: Also  pass address of array and string:
  in  rdi   first
  in  rsi   second
  in  rdx   third
  in  rcx   fourth

return integer from function in  rax

Pass float dd, double dq parameters:
  in  xmm0   first
  in  xmm1   second
  in  xmm2   third

return float or double in xmm0


Pass an array and change the array in assembly labguage.
Be safe, use a header file, .h, in the "C" code.
test_call1_64.c
test_call1_64.s
call1_64.h
call1_64.c
call1_64.s
call1_64.asm
test_call1_64.out

// test_call1_64.c   test  call1_64.asm 
// nasm -f elf64 -l call1_64.lst call1_64.asm
// gcc -m64 -o test_call1_64 test_call1_64.c call1_64.o
// ./test_call1_64 > test_call1_64.out
 #include "call1_64.h"
 #include <stdio.h>
 int main()
 {
   long int L[2];
   printf("test_call1_64.c using call1_64.asm\n");
   L[0]=1;
   L[1]=2;
   printf("address of L=L[0]=%ld, L[1]=%ld \n", &L, &L[1]);
   call1_64(L); // add 3 to L[0], add 4 to L[1]
   printf("L[0]=%ld, L[1]=%ld \n", L[0], L[1]);
   return 0;
 }

; call1_64.asm  a basic structure for a subroutine to be called from "C"
;
; Parameter:   long int *L
; Result: L[0]=L[0]+3  L[1]=L[1]+4

        global call1_64		; linker must know name of subroutine

        extern	printf		; the C function, to be called for demo

        SECTION .data		; Data section, initialized variables
fmt1:    db "rdi=%ld, L[0]=%ld", 10, 0	; The printf format, "\n",'0'
fmt2:    db "rdi=%ld, L[1]=%ld", 10, 0	; The printf format, "\n",'0'

	SECTION .bss
a:	resq	1		; temp for printing

	SECTION .text           ; Code section.

call1_64:			; name must appear as a nasm label
        push	rbp		; save rbp
        mov	rbp, rsp	; rbp is callers stack
        push	rdx		; save registers
        push	rdi
	push	rsi

	mov	rax,rdi		; first, only, in parameter
	mov	[a],rdi		; save for later use

	mov	rdi,fmt1	; format for printf debug, demo
	mov	rsi,rax         ; first parameter for printf
	mov	rdx,[rax]	; second parameter for printf
	mov	rax,0		; no xmm registers
        call    printf		; Call C function	

	mov	rax,[a]		; first, only, in parameter, demo
	mov	rdi,fmt2	; format for printf
	mov	rsi,rax         ; first parameter for printf
	mov	rdx,[rax+8]	; second parameter for printf
	mov	rax,0		; no xmm registers
        call    printf		; Call C function	

	mov	rax,[a]		; add 3 to L[0]
	mov	rdx,[rax]	; get L[0]
	add	rdx,3		; add
	mov	[rax],rdx	; store sum for caller

	mov	rdx,[rax+8]	; get L[1]
	add	rdx,4		; add
	mov	[rax+8],rdx	; store sum for caller

        pop	rsi		; restore registers
	pop	rdi		; in reverse order
        pop	rdx
        mov	rsp,rbp		; restore callers stack frame
        pop	rbp
        ret			; return

Output of execution:
  test_call1_64.c using call1_64.asm
  address of L=L[0]=140732670621824, L[1]=140732670621832 
  rdi=140732670621824, L[0]=1
  rdi=140732670621832, L[1]=2
  L[0]=4, L[1]=6 


A small change to use a double array, floating point
test_callf1_64.c
callf1_64.h
callf1_64.c
callf1_64.asm
test_callf1_64.out

; callf1_64.asm  a basic structure for a subroutine to be called from "C"
;
; Parameters:   double *L
; Result: L[0]=L[0]+3.0  L[1]=L[1]+4.0

        global callf1_64	; linker must know name of subroutine

        extern	printf		; the C function, to be called for demo

        SECTION .data		; Data section, initialized variables
fmt1:	db "rdi=%ld, L[0]=%e", 10, 0	; The printf format, "\n",'0'
fmt2:	db "rdi=%ld, L[1]=%e", 10, 0	; The printf format, "\n",'0'
a3:	dq	3.0		; 64-bit variable a initialized to 3.0
a4:	dq	4.0		; 64-bit variable b initializes to 4.0

	SECTION .bss
a:	resq	1		; temp for saving address

	SECTION .text           ; Code section.

callf1_64:			; name must appear as a nasm label
        push	rbp		; save rbp
        mov	rbp, rsp	; rbp is callers stack
        push	rdx		; save registers
        push	rdi
	push	rsi

	mov	rax,rdi		; first, only, in parameter
	mov	[a],rdi		; save for later use

;	mov	rdi,fmt1	; format for printf debug, demo
;	mov	rsi,rax         ; first parameter for printf
;       movq    xmm0, qword [rax] ; second parameter for printf
;	mov	rax,1		; one xmm registers
;	call    printf		; Call C function	

;	mov	rax,[a]		; first, only, in parameter, demo
;	mov	rdi,fmt2	; format for printf
;	mov	rsi,rax         ; first parameter for printf
;	movq    xmm0, qword [rax+8] ; second parameter for printf
;	mov	rax,1		; one xmm registers
;	call    printf		; Call C function	

	mov	rax,[a]		; add 3.0 to L[0]
	fld	qword [rax] 	; load L[0] (pushed on flt pt stack, st0)
	fadd	qword [a3]	; floating add 3.0 (to st0)
	fstp	qword [rax]	; store into L[0] (pop flt pt stack)

	fld	qword [rax+8] 	; load L[1] (pushed on flt pt stack, st0)
	fadd	qword [a4]	; floating add 4.0 (to st0)
	fstp	qword [rax+8]	; store into L[1] (pop flt pt stack)

        pop	rsi		; restore registers
	pop	rdi		; in reverse order
        pop	rdx
        mov	rsp,rbp		; restore callers stack frame
        pop	rbp
        ret			; return


Output of execution:
  test_callf1_64.c using callf1_64.asm
  L[0]=4.000000e+00, L[1]=6.000000e+00 




Here is another basic subroutine (function, procedure, etc)
Note passing parameters.
Note saving and restoring the callers registers.
(Yes, this is needed for CMSC 313 project 2)

Now, to pass more arguments, call2_64.c
can be implemented as call2_64.asm
Both tested using test_call2_64.c
Using prototype call2_64.h
Output is test_call2_64.out

Note passing arrays including strings is via address,
     passing scalar values is via passing values.

; call2_64.asm  code call2_64.c for nasm for test_call2_64.c 
; // call2_64.c a very simple loop that will be coded for nasm
; void call2_64(long int *A, long int start, long int end, long int value)
; {
;   long int i;
;
;   for(i=start; i≤end; i++) A[i]=value;
;   return;
; }
;
; execution output is dd1[0]=5, dd1[1]=7, dd1[98]=7, dd1[99]=9
 
	section	.bss
i:	resd	1		; actually unused, kept in register rax

	section .text
	global	call2_64	; linker must know name of subroutine
call2_64:			; name must appear as a nasm label
        push	rbp		; save rbp
        mov	rbp, rsp	; rbp is callers stack
        push	rax		; save registers (overkill)
	push	rbx
	push	rcx
	push	rdx
				; know address or value from prototype
;	mov	rdi,rdi		; get address of A into rdi (default)
        mov	rax,rsi		; get value of start
        mov	rbx,rdx		; get value of end
        mov	rdx,rcx		; get value of value
	
loop1:	mov	[8*rax+rdi],rdx	; A[i]=value;
	add	rax,1		; i++;
	cmp	rax,rbx		; i≤end
	jle	loop1		; loop until i=end

	pop	rdx		; in reverse order
	pop	rcx
	pop	rbx
	pop	rax
        mov	rsp,rbp		; restore callers stack frame
        pop	rbp
	ret			; return

Outout of execution:
dd1[0]=5, dd1[1]=7, dd1[98]=7, dd1[99]=9


A simple program with a simple function, 
called and written in the same .asm file
intfunct_64.asm

; intfunct_64.asm  this is a main and a function in one file
;                  call integer function  long int sum(long int x, long int y) 
; compile:	nasm -f elf64 -l intfunct_64.lst intfunct_64.asm 
; link:		gcc -m64 -o intfunct_64 intfunct_64.o
; run:		./intfunct_64 > intfunct_64.out
; view:         cat intfunct_64.out
; result:	5 = sum(2,3)

	extern	printf
	section .data
x:	dq	2
y:	dq	3
fmt:	db	"%ld = sum(%ld,%ld)",10,0

	section .bss
z:	resq	1
	
	section .text
	global	main
main:	push	rbp		; set up stack

	mov	rdi, [x]	; pass arguments for sum
	mov	rsi, [y]
	call	sum		; coded below
	mov	[z],rax		; save result from sum

	mov	rdi, fmt	; print
	mov	rsi, [z]
	mov	rdx, [x]	; yes, rdx comes before rcx
	mov	rcx, [y]
	mov	rax, 0		; no float or double
	call	printf

	pop	rbp		; restore stack
	mov	rax,0
	ret
; end main

sum:	; function long int sum(long int x, long int y)
	; so simple, do not need to save anything

	mov	rax,rdi		; get argument x
	add	rax,rsi		; add argument y, x+y result in rax
	ret			; return value in rax
; end of function sum
; end intfunct_64.asm




A simple demonstration of using a double sin(double x) function
from the "C" math.h  fltfunct_64.asm
Note xmm 128-bit registers are used to pass parameters.

; fltfunct_64.asm  call math routine  double sin(double x) 
; compile:	nasm -f elf64 fltfunct_64.asm 
; link:		gcc -m64 -o fltfunct_64 fltfunct_64.o -lm # needs math library
; run:		./fltfunct_64 > fltfunct_64.out
; view:		cat fltfunct_64.out
	
	extern	sin		; must extern all library functions
	extern	printf
	section .data
x:	dq	0.7853975	; Pi/4 = 45 degrees
y:	dq	0.0		; should be about 7.07E-1
fmt:	db	"y= %e = sin(%e)",10,0

	section .text
	global	main
main:	push	rbp		; set up stack

	movq	xmm0, qword [x]	; pass argument to sin()
	call	sin		; all "C" math uses double
	movq	qword [y], xmm0	; save result

	mov	rdi, fmt	; print
	movq	xmm0, qword [y]
	movq	xmm1, qword [x]
	mov	rax,2		; 2 doubles
	call	printf

	pop	rbx		; restore stack
	mov	rax,0		; no error return
	ret			; return to operating system

; result: y= 7.071063e-01 = sin(7.853975e-01)

Now, if you want to see why I teach Nasm rather than gas,
See fltfunct_64.c
// fltfunct_64.c  call math routine  double sin(double x) 
 #include <stdio.h>
 #include <math.h>
 int main()
 {
   double x = 0.7853975;	// Pi/4 = 45 degrees
   double y;
   y = sin(x);
   printf("y= %e = sin(%e)\n", y, x);
   return 0;
 }

See gas assembly language, gcc -m64 -S fltfunct_64.c makes fltfunct_64.s
	.file	"fltfunct_64.c"
	.section	.rodata
.LC1:
	.string	"y= %e = sin(%e)\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$32, %rsp
	movabsq	$4605249451321951854, %rax
	movq	%rax, -8(%rbp)
	movq	-8(%rbp), %rax
	movq	%rax, -24(%rbp)
	movsd	-24(%rbp), %xmm0
	call	sin
	movsd	%xmm0, -24(%rbp)
	movq	-24(%rbp), %rax
	movq	%rax, -16(%rbp)
	movq	-8(%rbp), %rdx
	movq	-16(%rbp), %rax
	movq	%rdx, -24(%rbp)
	movsd	-24(%rbp), %xmm1
	movq	%rax, -24(%rbp)
	movsd	-24(%rbp), %xmm0
	movl	$.LC1, %edi
	movl	$2, %eax
	call	printf
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (GNU) 4.8.2 20140120 (Red Hat 4.8.2-16)"
	.section	.note.GNU-stack,"",@progbits


And a final example of a simple recursive function, factorial,
written in optimized assembly language following the "C" code.
main        test_factorial_long.c
"C" version factorial_long.c
"C" header  factorial_long.h
nasm code   factorial_long.asm
output      test_factorial_long.out

// test_factorial_long.c  the simplest example of a recursive function
//                   a recursive function is a function that calls itself
// external
// long int factorial_long(long int n) // n! is n factorial = 1*2*...*(n-1)*n
// {
//   if( n ≤ 1 ) return 1;            // must have a way to stop recursion
//   return n * factorial_long(n-1);   // factorial calls factorial with n-1
// }                                   // n * (n-1) * (n-2) * ... * (1) 
 #include "factorial_long.h"
 #include <stdio.h>

int main()
{
  printf("test_factorial_long.c using long int, note overflow\n");
  printf(" 0!=%ld \n", factorial_long(0));   // Yes, 0! is one
  printf(" 1!=%ld \n", factorial_long(1l));  // Yes, "C" would convert 1 to 1l
  printf(" 2!=%ld \n", factorial_long(2l));  // because of function prototype
  printf(" 3!=%ld \n", factorial_long(3l));  // coming from factorial_long.h
  printf(" 4!=%ld \n", factorial_long(4l));
  printf(" 5!=%ld \n", factorial_long(5l));
  printf(" 6!=%ld \n", factorial_long(6l));
  printf(" 7!=%ld \n", factorial_long(7l));
  printf(" 8!=%ld \n", factorial_long(8l));
  printf(" 9!=%ld \n", factorial_long(9l));
  printf("10!=%ld \n", factorial_long(10l));
  printf("11!=%ld \n", factorial_long(11l));
  printf("12!=%ld \n", factorial_long(12l));
  printf("13!=%ld \n", factorial_long(13l));
  printf("14!=%ld \n", factorial_long(14l));
  printf("15!=%ld \n", factorial_long(15l));
  printf("16!=%ld \n", factorial_long(16l));
  printf("17!=%ld \n", factorial_long(17l));
  printf("18!=%ld \n", factorial_long(18l));
  printf("19!=%ld \n", factorial_long(19l));
  printf("20!=%ld \n", factorial_long(20l));
  printf("21!=%ld \n", factorial_long(21l)); /* expect a problem with */
  printf("22!=%ld \n", factorial_long(22l)); /* integer overflow      */

  return 0;
}

/* output of execution, with comments, is:
test_factorial_long.c using long int, note overflow
 0!=1 
 1!=1 
 2!=2 
 3!=6 
 4!=24 
 5!=120 
 6!=720 
 7!=5040 
 8!=40320 
 9!=362880 
10!=3628800 
11!=39916800 
12!=479001600 
13!=6227020800 
14!=87178291200 
15!=1307674368000 
16!=20922789888000 
17!=355687428096000 
18!=6402373705728000 
19!=121645100408832000 
20!=2432902008176640000 
21!=-4249290049419214848 wrong and obvious if you check your results 
22!=-1250660718674968576 
*/

; factorial_long.asm  test with test_factorial_long.c main program
; // factorial_long.c  the simplest example of a recursive function
; //                   a recursive function is a function that calls itself
; long int factorial_long(long int n) // n! is n factorial = 1*2*...*(n-1)*n
; {
;   if( n ≤ 1 ) return 1;            // must have a way to stop recursion
;   return n * factorial_long(n-1);   // factorial calls factorial with n-1
; }				      // n * (n-1) * (n-2) * ... * (1)
;                                     // note: "C" makes 1 be 1l  long
	global	factorial_long
	section .text
factorial_long:			; extreamly efficient version
	mov	rax, 1		; default return
	cmp	rdi, 1		; compare n to 1
	jle	done

	; normal case n * factorial_long(n-1)
	push	rdi		; save n for multiply
	sub	rdi, 1		; n-1
	call	factorial_long	; rax has factorial_long(n-1)
	pop	rdi
	imul	rax,rdi		; ??
				; n*factorial_long(n-1) in rax
done:	ret			; return with result in rax
; end factorial_long.asm


Project 2 is assigned

    <- previous    index    next ->

Other links

Go to top