Doing something useful

You probably are tired of writing programs for computing the factorial. So I wrote one for you that uses functions. Your job will be to write a program that computes the greatest common divisor (gcd).

Calling conventions for Quiz06

This is what caller and callee have to know about each other in this Quiz:

The symbols RET, CALLEE0, ..., CALLEE3 are defined and are used for functions calls as follows:

  • The caller uses %RET for the return address (so a callee can return with jmp %RET, %0).

  • Functions can receive at most four arguments. The callee expects to receive them as follows:

    • First argument in %CALLEE0

    • ...

    • Forth argument %CALLEE3

    If a function returns a value the callee will store it in %CALLEE0 before returning.

    The callee is only allowed to use the register %CALLEE0, ..., %CALLEE3. These registers are consecutive (which is important to know for using instructions like divq that operate on register pairs and register triplets).

Quiz06: Computing the greatest common divisor

Write a program gcd.s that computes for two 64-bit unsigned integers the greatest common divisor (gcd). Provide the user a nice experience, i.e. using the program looks like this:

1
2
3
4
5
6
7
8
theon$ ulm gcd
a = 18
b = 12
gcd(18, 12) = 6
theon$ ulm gcd
a = 350982
b = 822647
gcd(350982, 822647) = 527

Use the following algorithm for your implementation:

Submit your program with

1
submit hpc quiz06 gcd.s

Conditional jumps for equalities and inequalities

So far we only have covered the conditional jumps `jz and jnz`. These can be used for realizing conditions like $x = y$ or $x \neq y$:

  • Jump if \(y = x\) is realized by the pattern: compute \(y-x\) and check if the zero flag is set:

    1
    2
    subq  %X,     %Y,     %0
    jz    some_label
    
  • Jump if \(y \neq x\) is realized by the pattern: compute \(y-x\) and check if the zero flag is not set:

    1
    2
    subq  %X,     %Y,     %0
    jnz   some_label
    

For inequalities we proceed accordingly. But we have to distinguish whether the bit pattern are used for signed or unsigned integers:

  • For unsigned integers instructions ja (jump above), jae (jump above equal), jb (jump below), jbe (jump below equal) can be used. In all these cases first compute \(y-x\) and then choose what condition should cause a jump:

    • ja jumps if \(y-x > 0\).

    • jae jumps if \(y-x \geq 0\).

    • jb jumps if \(y-x < 0\).

    • jbe jumps if \(y-x \leq 0\).

  • For signed integers instructions jg (jump greater), jge (jump greater equal), jl (jump less), jle (jump less equal) can be used. In all these cases first compute \(y-x\) and then choose what condition should cause a jump:

    • jg jumps if \(y-x > 0\).

    • jge jumps if \(y-x \geq 0\).

    • jl jumps if \(y-x < 0\).

    • jle jumps if \(y-x \leq 0\).

Example for computing the factorial

This is actually the program for computing the factorial that I showed in a video before. If you use it interactively it looks and feels like that:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
theon$ ulm factorial
n = 20
20! = 2432902008176640000
theon$ ulm factorial
n = 0
0! = 1
theon$ ulm factorial
n = 3
3! = 6
theon$ ulm factorial
n = 10
10! = 3628800

Here the output when it is used non-interactive (by piping an echo output):

theon$ ulmas -o factorial factorial.s
theon$ echo 20 | ulm factorial
n = 20! = 2432902008176640000
theon$ echo 0 | ulm factorial
n = 0! = 1
theon$ echo 1 | ulm factorial
n = 1! = 1
theon$ echo 10 | ulm factorial
n = 10! = 3628800
theon$ 

For \(n>20\) we get \(n! \bmod 2^{64}\) as result (of course the ULM would provide instructions for computing that with 128 bits or more):

theon$ ulmas -o factorial factorial.s
theon$ echo 21 | ulm factorial
n = 21! = 14197454024290336768
theon$ 

Have a good look at it! Not just because it is nice but also because you can use ideas for the assignment ;-)

  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
        # Example for realizing leaf functions (recursive calls will fail!)


        # registers that a callee can use
        .equ    CALLEE0,        1
        .equ    CALLEE1,        CALLEE0 + 1
        .equ    CALLEE2,        CALLEE1 + 1
        .equ    CALLEE3,        CALLEE2 + 1

        # all other registers belong to the caller
        .equ    CALL,           CALLEE3 + 1     
        .equ    RET,            CALL + 1
        .equ    N,              RET + 1
        .equ    RES,            N + 1

        .data
str0    .string "n = "
str1    .string "! = "
str2    .string "\n"

        .text
/*
        Entry point of the program
*/

        # puts(str0);
        ldzwq   puts,           %CALL
        ldzwq   str0,           %CALLEE0
        jmp     %CALL,          %RET

        # readuint();
        ldzwq   readuint,       %CALL
        jmp     %CALL,          %RET
        addq    %CALLEE0,       %0,             %N

        # %RES = factorial(%N);
        ldzwq   factorial,      %CALL
        addq    %N,             %0,             %CALLEE0
        jmp     %CALL,          %RET
        addq    %CALLEE0,       %0,             %RES

        # printuint(%N);
        ldzwq   printuint,      %CALL
        addq    %N,             %0,             %CALLEE0
        jmp     %CALL,          %RET

        # puts(str1);
        ldzwq   puts,           %CALL
        ldzwq   str1,           %CALLEE0
        jmp     %CALL,          %RET


        # printuint(%RES);
        ldzwq   printuint,      %CALL
        addq    %RES,           %0,             %CALLEE0
        jmp     %CALL,          %RET

        # puts(str2);
        ldzwq   puts,           %CALL
        ldzwq   str2,           %CALLEE0
        jmp     %CALL,          %RET
/*
        Exit point
*/
        halt    0

//------------------------------------------------------------------------------

/*
        void
        puts(const char *);
*/

puts:
.L0     movzbq  (%CALLEE0),     %CALLEE1
        subq    0,              %CALLEE1,       %0
        jz      .L1
        putc    %CALLEE1
        addq    1,              %CALLEE0,       %CALLEE0
        jmp     .L0
.L1     jmp     %RET,           %0

/*
        uint64_t
        readuint();
*/
readuint:
        ldzwq   0,              %CALLEE0
.L2     getc    %CALLEE1
        subq    '\n',           %CALLEE1,       %0
        jz      .L3
        subq    '0',            %CALLEE1,       %CALLEE1
        imulq   10,             %CALLEE0,       %CALLEE0
        addq    %CALLEE1,       %CALLEE0,       %CALLEE0
        jmp     .L2
.L3     jmp     %RET,           %0


/*
        void
        printuint(uint64_t);
*/
        .bss
.L4     .space  21

        .text
printuint:
        ldzwq   .L4,            %CALLEE3

        ldzwq   0,              %CALLEE1

.L5     divq    10,             %CALLEE0,       %CALLEE0
        addq    '0',            %CALLEE2,       %CALLEE2
        addq    1,              %CALLEE3,       %CALLEE3
        movb    %CALLEE2,       (%CALLEE3)
        subq    0,              %CALLEE0,       %0
        jnz     .L5

.L6     movzbq  (%CALLEE3),     %CALLEE2
        subq    0,              %CALLEE2,       %0
        jz      .L7
        putc    %CALLEE2
        subq    1,              %CALLEE3,       %CALLEE3
        jmp     .L6
.L7     jmp     %RET,           %0

/*
        uint64_t
        factorial(uint64_t);
*/
factorial:
        ldzwq   1,              %CALLEE1
loop    subq    0,              %CALLEE0,       %0
        jz      done
        imulq   %CALLEE0,       %CALLEE1,       %CALLEE1
        subq    1,              %CALLEE0,       %CALLEE0
        jmp     loop
done    addq    %CALLEE1,       %0,             %CALLEE0
        jmp     %RET,           %0

Skeleton for quiz06

theon$ ulmas -o gcd gcd_skeleton.s
theon$ (echo 350982; echo  822647) | ulm gcd
a = b = gcd(350982, 822647) = 350982
theon$ 
  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
        # Example for realizing leaf functions (recursive calls will fail!)


        # registers that a callee can use
        .equ    CALLEE0,        1
        .equ    CALLEE1,        CALLEE0 + 1
        .equ    CALLEE2,        CALLEE1 + 1
        .equ    CALLEE3,        CALLEE2 + 1

        # all other registers belong to the caller
        .equ    CALL,           CALLEE3 + 1     
        .equ    RET,            CALL + 1
        .equ    A,              RET + 1
        .equ    B,              A + 1
        .equ    RES,            B + 1

        .data
str0    .string "a = "
str1    .string "b = "
str2    .string "gcd("
str3    .string ", "
str4    .string ") = "
str5    .string "\n"

        .text
/*
        Entry point of the program
*/

        # puts(str0);
        ldzwq   puts,           %CALL
        ldzwq   str0,           %CALLEE0
        jmp     %CALL,          %RET

        # readuint();
        ldzwq   readuint,       %CALL
        jmp     %CALL,          %RET
        addq    %CALLEE0,       %0,             %A

        # puts(str1);
        ldzwq   puts,           %CALL
        ldzwq   str1,           %CALLEE0
        jmp     %CALL,          %RET

        # readuint();
        ldzwq   readuint,       %CALL
        jmp     %CALL,          %RET
        addq    %CALLEE0,       %0,             %B

        # %RES = gcd(%A, %B);
        ldzwq   gcd,            %CALL
        addq    %A,             %0,             %CALLEE0
        addq    %B,             %0,             %CALLEE1
        jmp     %CALL,          %RET
        addq    %CALLEE0,       %0,             %RES

        # puts(str2);
        ldzwq   puts,           %CALL
        ldzwq   str2,           %CALLEE0
        jmp     %CALL,          %RET


        # printuint(%A);
        ldzwq   printuint,      %CALL
        addq    %A,             %0,             %CALLEE0
        jmp     %CALL,          %RET

        # puts(str3);
        ldzwq   puts,           %CALL
        ldzwq   str3,           %CALLEE0
        jmp     %CALL,          %RET

        # printuint(%B);
        ldzwq   printuint,      %CALL
        addq    %B,             %0,             %CALLEE0
        jmp     %CALL,          %RET

        # puts(str4);
        ldzwq   puts,           %CALL
        ldzwq   str4,           %CALLEE0
        jmp     %CALL,          %RET

        # printuint(%RES);
        ldzwq   printuint,      %CALL
        addq    %RES,           %0,             %CALLEE0
        jmp     %CALL,          %RET

        # puts(str5);
        ldzwq   puts,           %CALL
        ldzwq   str5,           %CALLEE0
        jmp     %CALL,          %RET
/*
        Exit point
*/
        halt    0

//------------------------------------------------------------------------------

/*
        void
        puts(const char *)
*/

puts:
.L0     movzbq  (%CALLEE0),     %CALLEE1
        subq    0,              %CALLEE1,       %0
        jz      .L1
        putc    %CALLEE1
        addq    1,              %CALLEE0,       %CALLEE0
        jmp     .L0
.L1     jmp     %RET,           %0

/*
        uint64_t
        readuint();
*/
readuint:
        ldzwq   0,              %CALLEE0
.L2     getc    %CALLEE1
        subq    '\n',           %CALLEE1,       %0
        jz      .L3
        subq    '0',            %CALLEE1,       %CALLEE1
        imulq   10,             %CALLEE0,       %CALLEE0
        addq    %CALLEE1,       %CALLEE0,       %CALLEE0
        jmp     .L2
.L3     jmp     %RET,           %0


/*
        void
        printuint(uint64_t);
*/
        .bss
.L4     .space  21

        .text
printuint:
        ldzwq   .L4,            %CALLEE3

        ldzwq   0,              %CALLEE1

.L5     divq    10,             %CALLEE0,       %CALLEE0
        addq    '0',            %CALLEE2,       %CALLEE2
        addq    1,              %CALLEE3,       %CALLEE3
        movb    %CALLEE2,       (%CALLEE3)
        subq    0,              %CALLEE0,       %0
        jnz     .L5

.L6     movzbq  (%CALLEE3),     %CALLEE2
        subq    0,              %CALLEE2,       %0
        jz      .L7
        putc    %CALLEE2
        subq    1,              %CALLEE3,       %CALLEE3
        jmp     .L6
.L7     jmp     %RET,           %0

/*
        uint64_t
        gcd(uint64_t a, uint64_t b);

        a:      %CALLEE0
        b:      %CALLEE1
*/
gcd:

        #
        #       Your code here
        #

done    jmp     %RET,           %0