Programs with functions and procedures

From now on we will no longer have to use C code as kind of a pseudocode description for our assembler programs. Instead we can use the C code as an equivalent, more convenient formulation of our programs. Following some rules the C code can be rewritten (or compiled) into assembly code. Later this will be done by a tool, a C compiler, at the moment you have to do it. By doing so you learn the C programming language by example and you will get an idea how a C compiler works.

Function main and the subprogram _start

By convention every program has a function main that returns an integer. When a program gets executed it behaves as if function main is the first function that gets executed. The return value of main defines the exit code of the program. If main has no explicit return statement this function returns 0 by default. Note that such a default return value is only guarantied for function main.

For example, the following program only defines a function main which explicitly returns 42:

1
2
3
4
5
int
main()
{
    return 42;
}

Compile this and check the exit code:

theon$ gcc -o main main.c
theon$ main; echo $?
42
theon$ 

In this even more minimalistic program function main has an empty function body, i.e.

1
2
3
4
int
main()
{
}

By convention function main it implicitly returns 0:

theon$ gcc -o main_no_return main_no_return.c
theon$ main_no_return; echo $?
0
theon$ 

The subprogram _start

In C you are not allowed to use the identifier _start for a function, this identifier is reserved. The gory details are that in general a program has to communicate with the operating system for receiving arguments and returning an exit code. This communication happens through so called system calls, and different operating systems have different system calls even if they run on the same hardware. So _start is reserved for being the name of a function that actually gets called first and itself calls function main. Platform depended system calls can be done in _start before and after main gets called. In the above example the implementation of function _start was added by the linker. With the command nm you can display the entries of a program's symbol table. You can use nm to see that the compiled programs have the symbol _start:

theon$ nm main | grep start
0000000000600b10 B __bss_start
                 U __fpstart@@SYSVABI_1.3
0000000000400660 T __start_crt
0000000000400600 T _start
theon$ 

The ULM does not have an operating system but we use _start to guarantee that the stack is initialized before function main gets called.

Equivalent assembly programs

The following assembly program is equivalent to the above C program in main.c (which was returning 42 in function main). It also contains the implementation of the _start function.

Until the linker gets covered in the next session we write all into the same file but in a way that we later can split this single source file into separate compile units. Hence for each function the directives for arguments etc. are repeated:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
        #NOTE: relevant for the function call convention
        .equ    FP,     1
        .equ    SP,     2
        .equ    RET,    3

        .text

//------------------------------------------------------------------------------
// SUBPROGRAM start_
//------------------------------------------------------------------------------
/*
        void
        start_()
        {
*/
        .equ    ret,            0
        .equ    fp,             8
        .equ    rval,           16

        .equ    proc_arg0,      16
        .equ    func_arg0,      24

_start:
        // initialize stack
        ldzwq   0,              %SP

        // call main() and store return value in %4
        subq    24,             %SP,            %SP
        ldzwq   main,           %4
        jmp     %4,             %RET
        movq    rval(%SP),      %4
        addq    24,             %SP,            %SP

/*
        (only) Exit point
*/
        halt    %4

/*
        }
*/

//------------------------------------------------------------------------------
// FUNCTION main
//------------------------------------------------------------------------------
        .equ    ret,            0
        .equ    fp,             8
        .equ    rval,           16

        .equ    proc_arg0,      16
        .equ    func_arg0,      24

/*
        int
        main()
        {
*/
        .text
main:
        // function prologue
        movq    %RET,           ret(%SP)
        movq    %FP,            fp(%SP)
        addq    0,              %SP,            %FP

        // begin of the function body

        /*
                return 42;
        */
        ldzwq   42,             %4
        movq    %4,             rval(%FP)

        // end of the function body

        // function epilogue
        addq    0,              %FP,            %SP
        movq    fp(%SP),        %FP
        movq    ret(%SP),       %RET
        jmp     %RET,           %0

/*
        }
*/

Use the debugger to see what is going on behind behind the scene when you run the program:

theon$ ulmas -o main main.s
theon$ ulm main; echo $?
42
theon$ 

Example with a procedure

This C program implements it's own puts function for printing a string and uses a function putchar for printing a single character:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void
puts(char *str)
{
    while (*str) {
        putchar(*str++);
    }
}

int
main()
{
    puts("hello, world!\n");
}

The function body of procedure puts can be described by a flow chart and it can accordingly be rewritten with spaghetti code (i.e. it contains goto statements) for reflecting more closely how you can implement it in assembly:

1
2
3
while (*str) {
    putchar(*str++);
}
1
2
3
4
5
6
7
8
puts_while:
    if (*str == 0)
        goto puts_done;
    putchar(*str);
    str = str + 1;
    goto puts_while;
puts_done:
    ;

For deriving an assembly implementation we actually consider this spaghetti code C program:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void
puts(char *str)
{
puts_while:
    if (*str == 0)
        goto puts_done;
    putchar(*str);
    str = str + 1;
    goto puts_while;
puts_done:
    ;
}

int
main()
{
    puts("hello, world!\n");
}

Before going into the syntactical meaning of this code let's compile both and check that you get the same result:

theon$ gcc -w -o example_puts example_puts.c
theon$ example_puts
hello, world!
theon$ gcc -w -o example_puts_archaic example_puts_archaic.c
theon$ example_puts_archaic
hello, world!
theon$ 

Step by step explanation what this C code describes

Let's begin with what you see in function main:

1
2
3
4
5
int
main()
{
    puts("hello, world!\n");
}

Function main has no local variables and is implicitly returning zero. So applying the standard recipe we already have the following skeleton:

1
2
3
4
5
6
int
main()
{
    /* statements */
    return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    // ... directives for arguments, locals, etc.

    .text
main:
    // ... function prologue ...
    movq      %RET,           ret(%SP)
    movq      %FP,            fp(%SP)
    addq      0,              %SP,            %FP

    // reserve space for local variables.
    subq      0*8,            %SP,            %SP

    /*
        statements
    */

    /*
        return 0;
    */
    ldzwq   0,              %4
    movq    %4,             rval(%FP)

    // ... function epilogue ...
    addq    0,              %FP,            %SP
    movq    fp(%SP),        %FP
    movq    ret(%SP),       %RET
    jmp     %RET,           %0

Whenever you see a string literal in a C program it means the compiler will generate in the data segment a corresponding .string directive with a unique label. So for before we consider how to call puts we generate the string with some unique label:

1
"hello, word!\n"
1
2
3
    .data
.main.L0:         // some unique label
    .string "hello, word!\n"

When you pass a string to a function you only pass a pointer to the string as argument. Function puts does not return a value, hence we applying the recipe for calling a procedure with one argument (which is the pointer to the string literal) we have

1
puts("hello, world!\n");
1
2
3
4
5
6
7
8
9
/*
    puts("hello, word!\n");
*/
subq    24,             %SP,            %SP
ldzwq   .main.L0,       %4
movq    %4,             proc_arg0(%SP)
ldzwq   puts,           %4
jmp     %4,             %RET
addq    24,             %SP,            %SP

So in total we have

1
2
3
4
5
6
int
main()
{
    puts("hello, world!\n");
    return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    // ... directives for arguments, locals, etc.
    .data
.main.L0:         // some unique label
    .string "hello, word!\n"

    .text
main:
    // ... function prologue ...
    movq      %RET,           ret(%SP)
    movq      %FP,            fp(%SP)
    addq      0,              %SP,            %FP

    // reserve space for local variables.
    subq      0*8,            %SP,            %SP

    /*
        puts("hello, word!\n");
    */
    subq    24,             %SP,            %SP
    ldzwq   .main.L0,       %4
    movq    %4,             proc_arg0(%SP)
    ldzwq   puts,           %4
    jmp     %4,             %RET
    addq    24,             %SP,            %SP

    /*
        return 0;
    */
    ldzwq   0,              %4
    movq    %4,             rval(%FP)

    // ... function epilogue ...
    addq    0,              %FP,            %SP
    movq    fp(%SP),        %FP
    movq    ret(%SP),       %RET
    jmp     %RET,           %0

Next comes the implementation of puts which is described by:

1
2
3
4
5
6
7
void
puts(char *str)
{
    while (*str) {
        putchar(*str++);
    }
}

This procedure expects one parameter str which should be a pointer to a character, i.e. str should be the address of the string's first character. In C terminology that is expressed by declaring str as a variable of type char *, and you see this declaration (bookkeeping information) in the lines

1
2
void
puts(char *str )

When you use the variable in a statement *str denotes the value at the end of the pointer, in this case a character.

We can now dig into the details of implementing function puts. It does not return a value so we use the recipe for implementing a procedures:

1
2
3
4
5
void
puts(char *str)
{
    /* .... */
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    // ... directives for arguments, locals, etc.

    .equ      str,        proc_arg0
    .text
puts:
    // function prologue
    movq      %RET,       ret(%SP)
    movq      %FP,        fp(%SP)
    addq      0,          %SP,        %FP

    // reserve space for local variables.
    subq      0*8,        %SP,        %SP

    // begin of the function body

    /*

      Implementation of the function or procedure

    */

    // end of the function body

    // function epilogue
    addq      0,          %FP,        %SP
    movq      fp(%SP),    %FP
    movq      ret(%SP),   %RET
    jmp       %RET,       %0

The implementation of the body was described by the flow chart above. And we implement this chart node by node.

Conditional jump if (*str == 0)

One thing I dislike in C, and many other programming languages, is that the comparison for equality is expressed with “==” and the assignment with “=”. I would prefer a single equal sign for comparison and something like “:=” for assignments. But knowing the meaning of “==” in

is that *str (the character at the end of pointer str) is compared with zero. So you first have to load the pointer str, i.e. the address stored in the argument str into a register

1
    movq    str(%FP),   %4

and then the character at the end of the pointer:

1
    movzbq  (%4),       %4

after that you can check if %4 contains zero and jump in that case. The code for this node is

1
2
3
puts_while:
    if (*str == 0)
        goto puts_done;
1
2
3
4
5
puts_while:
    movq    str(%FP), %4
    movzbq  (%4),     %4
    subq    0,        %4,     %0
    jz      puts_done

the label puts_while is needed so that we can later jump back to it, and puts_done is a label to jump to the end of the function body.

Printing the character *str

It would be a shame to call here a function putchar to print a single character. So we just load *str and use the putc instruction:

1
putchar(*str);
1
2
3
    movq    str(%FP), %4
    movzbq  (%4),     %4
    putc    %4

Of course it is also a shame that we reload *str into %4. Because before *str was already fetched into %4. But at the moment we just blindly implement the flow chart without much thinking. We can care about optimizations another time.

Incrementing the pointer str

Now we increment str so that it points to the next character

1
str = str + 1;
1
2
3
    movq    str(%FP), %4
    addq    1,        %4,     %4
    movq    %4,       str(%FP)

Unconditional jump and label to break the loop

The unconditional jump is a single instruction

1
goto puts_while;
1
    jmp     puts_while

and the empty statement is just a label after the unconditional jump

1
2
puts_done:
    ;
1
puts_done:

Hence in total we have

1
2
3
4
5
6
7
void
puts(char *str)
{
    while (*str) {
        putchar(*str++);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    .equ      str,            proc_arg0

    .text
puts:
    // function prologue
    movq      %RET,           ret(%SP)
    movq      %FP,            fp(%SP)
    addq      0,              %SP,            %FP

    // begin of the function body

puts_while:
    /*
        if (*str == 0) goto puts_done;
    */
    movq      str(%FP),       %4
    movzbq    (%4),           %4
    subq    0,                %4,             %0
    jz      puts_done

    /*
        putchar(*str);
    */
    movq      str(%FP),       %4
    movzbq    (%4),           %4
    putc      %4

    /*
        str = str + 1;
    */
    movq      str(%FP),       %4
    addq      1,              %4,             %4
    movq      %4,             str(%FP)

    /*
        goto puts_while;
    */
    jmp       puts_while

puts_done:
    // end of the function body

    // function epilogue
    addq      0,              %FP,            %SP
    movq      fp(%SP),        %FP
    movq      ret(%SP),       %RET
    jmp       %RET,           %0

Complete assembly implementation for this C code

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
        #NOTE: relevant for the function call convention
        .equ    FP,     1
        .equ    SP,     2
        .equ    RET,    3

        .text

//------------------------------------------------------------------------------
// SUBPROGRAM start_
//------------------------------------------------------------------------------
        .equ    ret,            0
        .equ    fp,             8
        .equ    rval,           16

        .equ    proc_arg0,      16
        .equ    func_arg0,      24

/*
        void
        start_()
        {
*/

_start:
        #NOTE: relevant for the convention
        ldzwq   0,              %SP

        subq    24,             %SP,            %SP
        ldzwq   main,           %4
        jmp     %4,             %RET
        movq    rval(%SP),      %4
        addq    24,             %SP,            %SP

/*
        (only) Exit point
*/
        halt    %4

/*
        }
*/

//------------------------------------------------------------------------------
// FUNCTION main
//------------------------------------------------------------------------------
        .equ    ret,            0
        .equ    fp,             8
        .equ    rval,           16

        .equ    proc_arg0,      16
        .equ    func_arg0,      24

/*
        char msg[] = "hello, word!\n";          // string literals are global
*/

        .data
.main.L0:
        .string "hello, word!\n"

/*
        int
        main()
        {
*/
        .text
main:
        // function prologue
        movq    %RET,           ret(%SP)
        movq    %FP,            fp(%SP)
        addq    0,              %SP,            %FP

        // begin of the function body

        /*
                puts("hello, word!\n");
        */
        subq    24,             %SP,            %SP
        ldzwq   .main.L0,       %4
        movq    %4,             proc_arg0(%SP)
        ldzwq   puts,           %4
        jmp     %4,             %RET
        addq    24,             %SP,            %SP

        /*
                return 0;
        */
        ldzwq   0,              %4
        movq    %4,             rval(%FP)

        // end of the function body

        // function epilogue
        addq    0,              %FP,            %SP
        movq    fp(%SP),        %FP
        movq    ret(%SP),       %RET
        jmp     %RET,           %0
/*
        }
*/

//------------------------------------------------------------------------------
// PROCEDURE puts
//------------------------------------------------------------------------------
        .equ    ret,            0
        .equ    fp,             8
        .equ    rval,           16

        .equ    proc_arg0,      16
        .equ    func_arg0,      24

/*
        void
        puts(char *str)
        {
*/
        .equ    str,            proc_arg0

        .text
puts:
        // function prologue
        movq    %RET,           ret(%SP)
        movq    %FP,            fp(%SP)
        addq    0,              %SP,            %FP

        // begin of the function body

puts_while:
        /*
           if (*str == 0) goto puts_done;
        */
        movq    str(%FP),       %4
        movzbq  (%4),           %4
        subq    0,              %4,             %0
        jz      puts_done

        /*
           putchar(*str);
        */
        movq    str(%FP),       %4
        movzbq  (%4),           %4
        putc    %4

        /*
           str = str + 1;
        */
        movq    str(%FP),       %4
        addq    1,              %4,             %4
        movq    %4,             str(%FP)

        /*
           goto puts_while;
        */
        jmp     puts_while

puts_done:
        // end of the function body

        // function epilogue
        addq    0,              %FP,            %SP
        movq    fp(%SP),        %FP
        movq    ret(%SP),       %RET
        jmp     %RET,           %0
/*
        }
*/
theon$ ulmas -o example_puts example_puts.s
theon$ ulm example_puts
hello, word!
theon$ 

Example with a function

This C program implements it's own strlen function for determining the length of a string. For the sake of simplicity the program just returns the string length in main:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
typedef unsigned long uint64_t;     // unsigned long is 64 bit wide on theon

uint64_t
strlen(char *str)
{
    char *ch = str;
    while (*ch) {
        ++ch;
    }
    return ch - str;
}

int
main()
{
    return strlen("hello, world!\n");
}
theon$ gcc -o example_strlen example_strlen.c
theon$ example_strlen; echo $?
14
theon$ 

Step by step explanation what this C code describes

We again begin with function main:

1
2
3
4
5
int
main()
{
    return strlen("hello, world!\n");
}

As the code contains a string literal we choose some unique label and generate the string in the data segment. For function main we use the skeleton for a function:

1
2
3
4
5
int
main()
{
    /* statements */
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    // ... directives for arguments, locals, etc.

    .text
main:
    // ... function prologue ...
    movq      %RET,           ret(%SP)
    movq      %FP,            fp(%SP)
    addq      0,              %SP,            %FP

    // reserve space for local variables.
    subq      0*8,            %SP,            %SP

    /*
        statements
    */

    // ... function epilogue ...
    addq    0,              %FP,            %SP
    movq    fp(%SP),        %FP
    movq    ret(%SP),       %RET
    jmp     %RET,           %0

Function main only contains a return statement. The return value is an expression which in turn is defined as the return value of a function call. So we call function strlen, store the return value on the stack and jump to the epilogue of main:

1
return strlen("hello, world!\n");
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    /*
        return strlen("hello, word!\n");
    */
    subq      32,         %SP,            %SP
    ldzwq     .main.L0,   %4
    movq      %4,         func_arg0(%SP)
    ldzwq     strlen,     %4
    jmp       %4,         %RET
    movq      rval(%SP),  %4
    movq      %4,         rval(%FP)
    addq      32,         %SP,            %SP
    // define a label before the epilogue
    jmp     .main.leave                          

Note that in this case the jump to the epilogue is an unnecessary instruction as the epilogue follows immediately. You see that when we put things together:

1
2
3
4
5
int
main()
{
    return strlen("hello, world!\n");
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    // ... directives for arguments, locals, etc.

    .text
main:
    // ... function prologue ...
    movq      %RET,       ret(%SP)
    movq      %FP,        fp(%SP)
    addq      0,          %SP,            %FP

    // reserve space for local variables.
    subq      0*8,        %SP,            %SP

    /*
        return strlen("hello, word!\n");
    */
    subq      32,         %SP,            %SP
    ldzwq     .main.L0,   %4
    movq      %4,         func_arg0(%SP)
    ldzwq     strlen,     %4
    jmp       %4,         %RET
    movq      rval(%SP),  %4
    movq      %4,         rval(%FP)
    addq      32,         %SP,            %SP
    // define a label before the epilogue
    jmp       .main.leave                        

.main.leave:
    // ... function epilogue ...
    addq      0,          %FP,            %SP
    movq      fp(%SP),    %FP
    movq      ret(%SP),   %RET
    jmp       %RET,       %0

In general a return statement can occur in the middle of a compound statement. Using this “jump to the epilogue pattern” allows us to implement return statement in a mindless way that always works. For example in cases like this:

1
2
3
4
5
if (condition) {
    return a;
} else {
    return b;
}

Again, optimizing the assembly code is something we can do afterwards. First of all we need something that just does the job, and a method to derive such a working solution.

But let's not digress, the next thing where we need a working solution is function strlen. In a first step we just care about the coarse structure, i.e. what arguments and local variables does the functions. The initialization of local variable is uninteresting in this case so we rewrite:

1
2
3
4
5
6
7
8
9
uint64_t
strlen(char *str)
{
    char *ch = str;
    while (*ch) {
        ++ch;
    }
    return ch - str;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
uint64_t
strlen(char *str)
{
    char *ch;
    ch = str;
    while (*ch) {
        +ch;
    }
    return ch - str;
}

Now it is more obvious that the function has one argument (named str) and one local variable (named ch):

1
2
3
4
5
6
uint64_t
strlen(char *str)
{
    char *ch;
    /* statements */
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    // ... directives for arguments, locals, etc.

    .equ      str,        func_arg0
    .equ      ch,         local0
    .text
puts:
    // function prologue
    movq      %RET,       ret(%SP)
    movq      %FP,        fp(%SP)
    addq      0,          %SP,        %FP

    // reserve space for local variables.
    subq      1*8,        %SP,        %SP     // for ch

    // begin of the function body

    /*

      Implementation of the function or procedure

    */

    // end of the function body

    // function epilogue
    addq      0,          %FP,        %SP
    movq      fp(%SP),    %FP
    movq      ret(%SP),   %RET
    jmp       %RET,       %0

In your bookkeeping you have to note that variables str and ch are supposed to be pointer to a character. That means each of these variables stores a 64 bit address of a character. That means *str and *ch in the C code refer to a character at the end of the pointer.

Initialization of the local variable

We simply fetch the value of variable str and store it at the memory location for variable ch. The bookkeeping note tell us that both variables have the size of 8 bytes, so we use movq for fetching and storing:

1
ch = str;
1
2
3
4
5
      /*
         ch = str
      */
      movq    str(%FP),   %4
      movq    %4,         ch(%FP)

While loop

We rewrite the while loop with spaghetti code and also resolve the meaning of the post-increment:

1
2
3
while (*ch) {
    ++ch;
}
1
2
3
4
5
6
7
strlen_while:
    if (*ch == 0)
        goto strlen_done;
    ch = ch + 1;
    goto strlen_while;
strlen_done:
    ;

Looking at the bookkeeping notes we recall that *ch refers to the byte with the address stored in the local variable ch. This byte gets compared against zero in the conditional jump:

1
2
if (*ch == 0)
    goto strlen_done;
1
2
3
4
5
6
7
8
    /*
        if (*ch == 0)
            goto strlen_done;
    */
    movq    ch(%FP),  %4
    movzbq  (%4),     %4
    subq    0,        %4,       %0
    jz      strlen_done

The next statement just increments the pointer to the next byte:

1
ch = ch + 1;
1
2
3
4
5
6
    /*
        ch = ch + 1;
    */
    movq    ch(%FP),  %4
    addq    1,        %4,     %4
    movq    %4,       ch(%FP)

The goto statement is just a unconditional jump:

1
goto strlen_while;
1
2
3
4
    /*
        goto strlen_while;
    */
    jmp strlen_while

For leaving the loop we formally need the empty statement in C which becomes just a label in the assembly code:

1
2
strlen_done:
    ;
1
strlen_done:

Return statement

For the return value we simply subtract the value of ch from the value of str and store it on the proper stack location. Then we jump to the epilogue:

1
return ch - str;
1
2
3
4
5
6
7
8
9
    /*
        return ch - str
    */
    movq    str(%FP),       %4
    movq    ch(%FP),        %5
    subq    %4,             %5,             %5
    movq    %5,             rval(%FP)
    // define a label before the epilogue
    jmp     .strlen.leave                 

Hence in total the implementation of function strlen is given by

1
2
3
4
5
6
7
8
uint64_t
strlen(char *str)
{
    char *ch = str;
    while (*ch++) {
    }
    return ch - str;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/*
      uint64_t
      strlen(char *str)
      {
*/
      .equ    str,            func_arg0
      .equ    ch,             local0

      .text
strlen:
      // function prologue
      movq    %RET,           ret(%SP)
      movq    %FP,            fp(%SP)
      addq    0,              %SP,            %FP
      subq    1*8,            %SP,            %SP

      // begin of the function body
      /*
         ch = str
      */
      movq    str(%FP),       %4
      movq    %4,             ch(%FP)

strlen_while
      /*
         if (*ch == 0)
             goto .puts.done;
      */
      movq    ch(%FP),        %4
      movzbq  (%4),           %4
      subq    0,              %4,             %0
      jz      strlen_done

      /*
         ++ch
      */
      movq    ch(%FP),        %4
      addq    1,              %4,             %4
      movq    %4,             ch(%FP)

      /*
          goto strlen_while;
      */
      jmp     strlen_while

strlen_done:
      /*
          return ch - str
      */
      movq    str(%FP),       %4
      movq    ch(%FP),        %5
      subq    %4,             %5,             %5
      movq    %5,             rval(%FP)
      jmp     .strlen.leave

      // end of the function body

.strlen.leave

      // function epilogue
      addq    0,              %FP,            %SP
      movq    fp(%SP),        %FP
      movq    ret(%SP),       %RET
      jmp     %RET,           %0
/*
      }
*/

Complete assembly implementation for this C code

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
        #NOTE: relevant for the function call convention
        .equ    FP,     1
        .equ    SP,     2
        .equ    RET,    3

        .text

//------------------------------------------------------------------------------
// SUBPROGRAM start_
//------------------------------------------------------------------------------
        .equ    ret,            0
        .equ    fp,             8
        .equ    rval,           16

        .equ    proc_arg0,      16
        .equ    func_arg0,      24

/*
        void
        start_()
        {
*/

_start:
        #NOTE: relevant for the convention
        ldzwq   0,              %SP

        subq    24,             %SP,            %SP
        ldzwq   main,           %4
        jmp     %4,             %RET
        movq    rval(%SP),      %4
        addq    24,             %SP,            %SP

/*
        (only) Exit point
*/
        halt    %4

/*
        }
*/

//------------------------------------------------------------------------------
// FUNCTION main
//------------------------------------------------------------------------------
        .equ    ret,            0
        .equ    fp,             8
        .equ    rval,           16

        .equ    proc_arg0,      16
        .equ    func_arg0,      24
        .equ    local0,         -8

/*
        char msg[] = "hello, word!\n";          // string literals are global
*/

        .data
.main.L0:
        .string "hello, world!\n"

/*
        int
        main()
        {
*/
        .text
main:
        // function prologue
        movq    %RET,           ret(%SP)
        movq    %FP,            fp(%SP)
        addq    0,              %SP,            %FP

        // begin of the function body

        /*
                return strlen("hello, word!\n");
        */
        subq    32,             %SP,            %SP
        ldzwq   .main.L0,       %4
        movq    %4,             func_arg0(%SP)
        ldzwq   strlen,         %4
        jmp     %4,             %RET
        movq    rval(%SP),      %4
        movq    %4,             rval(%FP)
        addq    32,             %SP,            %SP
        jmp     .main.leave                        

        // end of the function body

.main.leave:
        // function epilogue
        addq    0,              %FP,            %SP
        movq    fp(%SP),        %FP
        movq    ret(%SP),       %RET
        jmp     %RET,           %0
/*
        }
*/

//------------------------------------------------------------------------------
// FUNCTION strlen
//------------------------------------------------------------------------------
        .equ    ret,            0
        .equ    fp,             8
        .equ    rval,           16

        .equ    proc_arg0,      16
        .equ    func_arg0,      24
        .equ    local0,         -8

/*
        uint64_t
        strlen(char *str)
        {
*/
        .equ    str,            func_arg0
        .equ    ch,             local0

        .text
strlen:
        // function prologue
        movq    %RET,           ret(%SP)
        movq    %FP,            fp(%SP)
        addq    0,              %SP,            %FP
        subq    1*8,            %SP,            %SP

        // begin of the function body
        /*
           ch = str
        */
        movq    str(%FP),       %4
        movq    %4,             ch(%FP)

strlen_while
        /*
           if (*ch == 0) goto .puts.done;
        */
        movq    ch(%FP),        %4
        movzbq  (%4),           %4
        subq    0,              %4,             %0
        jz      strlen_done

        /*
           ++ch
        */
        movq    ch(%FP),        %4
        addq    1,              %4,             %4
        movq    %4,             ch(%FP)

        /*
            goto strlen_while;
        */
        jmp     strlen_while

strlen_done:
        /*
            return ch - str
        */
        movq    str(%FP),       %4
        movq    ch(%FP),        %5
        subq    %4,             %5,             %5
        movq    %5,             rval(%FP)

        // end of the function body

        // function epilogue
        addq    0,              %FP,            %SP
        movq    fp(%SP),        %FP
        movq    ret(%SP),       %RET
        jmp     %RET,           %0
/*
        }
*/
theon$ ulmas -o example_strlen example_strlen.s
theon$ ulm example_strlen; echo $?
14
theon$