Computacionales
Ejercicios resueltos y planteados
Mario Medina C.
Depto. Ing. Eléctrica Facultad de Ingeniería Universidad de Concepción 2017
Prefacio
Esta es una colección de ejercicios de sistemas computacionales que espero sea de utilidad a aquellos alumnos empeñados en desarrollar las habilidades y competencias asociadas a esta materia. Muchos de ellos aparecen en los textos enumerados en la bibliografía de este documento; otros han sido creados por el autor para ser usados en tareas y exámenes. Es mi opinión que la única forma de aprender es haciendo. Se espera que los ejercicios planteados sean desarrollados por Uds., los alumnos. Por ello, en la mayoría de éstos, sólo se indica la solución final.
Estoy siempre dispuesto a responder consultas sobre estos ejerci-cios, ya sea via correo electrónico o en persona. Asimismo, rogaría me hicieran llegar cualquier corrección o comentario a los ejercicios de este libro.
Asi que, buena suerte, y provecho! Mario Medina C.
1 Representación digital de información 1
2 Ensamblador Intel x86 y lenguaje C 12
3 Procesador Y86 45 4 Pipelining 55 5 Optimización de código 66 6 Sistemas de memoria 67 7 Planificación de procesos 82 iii
C
apítulo
1
Representación digital de
información
1.1 Suponga que un compilador C para un procesador de 32 bits repre-senta los números negativos utilizando complemento a 2, y que uti-liza desplazamientos aritméticos para los desplazamientos a la dere-cha. Suponga además que x e y son números enteros con signo arbitra-rios, y que ux y uy son números enteros sin signo arbitraarbitra-rios, tal que ux = (unsigned) xy que uy = (unsigned) y.Entonces, para cada una de las siguientes expresiones en lenguaje C, indique si la expresión siempre es verdadera. En caso contrario, indique posibles valores de x y/o y que la hagan falsa.
a) ((x + y) << 4) + y - x == 17*y + 15*x b) ~x + ~y == ~(x + y)
c) (int) (ux - uy) == -(y - x) d) ((x >> 1) << 1) <= x
Solución
a) Verdadera. 17*y equivale a y << 4 + y, y 15*x equivale a x << 4 − x. b) Falsa. Contraejemplo: ∼ x+ ∼ y = (−x −1)+(−y −1) = −(x +y)−2 =∼
(x + y) + 1.
c) Verdadera d) Verdadera
1.2 Suponga que se dispone de un procesador de 32 bits, que usa aritmé-tica de complemento a 2 para la representación de números con signo. Se utilizan desplazamientos lógicos para los enteros sin signo, y despla-zamientos aritméticos para los enteros con signo. Suponga además que se sabe que x e y son enteros con signo, y que ux y uy son enteros sin signo. Entonces, para cada una de las siguientes expresiones, indique si son verdaderas para todos los valores posibles de x e y. En caso que sean falsas, indique los valores de x y/o y para los cuales las expresiones no se cumplen. a) (x >= 0) || ((2*x) < 0) b) (x & 7) != 7 || (x << 30 < 0) c) (x * x) >= 0 d) x < 0 || -x <= 0 e) x > 0 || -x >= 0 f ) x*y == ux*uy g) ~x*y +ux*uy == -y Solución
a) Falsa. Sea x = −232. Entonces, 2*x = 0.
b) Verdadera. Si (x & 7) != 7 es falsa, entonces el bit x2 = 1. Al
desplazar x 30 bits a la izquierda, el bit x2 pasa a ser el bit de
signo.
c) Falsa. Si x = 0xFFFF, lo que equivale a 65535, entonces se tiene
que x*x = 0xFFFE0001, es decir, −131071.
d) Verdadera.
e) Falsa. Sea x = −232. Entonces, x y -x son negativas.
f ) Verdadera. g) Verdadera.
1.3 Suponga que las variables x, f y d son de tipos int, float y double, respectivamente. Suponga además que tanto f como d son diferentes de ∞, −∞ y N aN . Para cada una de las siguientes expresiones, indique si son verdaderas para todos los valores posibles de las variables involu-cradas. En caso que sean falsas, indique los valores de dichas variables para los cuales las expresiones no se cumplen.
a) x == (int)(float) x b) x == (int)(double) x c) f == (float)(double) f d) d == (float) d e) f == -(-f) f ) 2/3 == 2/3.0 g) (d >= 0.0) || ((d*2) < 0.0) h) (d + f) - d == f Solución a) Falso. Contraejemplo: x = 232. b) Verdadero. c) Verdadero. d) Falso. Contraejemplo: x = 1040. e) Verdadero. f ) Falso. g) Verdadero. h) Falso. Contrajemplo: d = +∞ y f = 1.
1.4 Sea un procesador de 32 bits que utiliza complemento a 2. Entonces, para las expresiones siguientes, rellene la siguiente tabla con el tipo del resultado (signed ó unsigned) y si la expresión es F ó V.
Expresión Tipo F ó V −2147483647 − 1 == 2147483648U −2147483647 − 1 < 2147483647 −2147483647 − 1U < 2147483647 −2147483647 − 1 < −2147483647 −2147483647 − 1U < −2147483647
Solución
La siguiente tabla muestra la solución.
Expresión Tipo F ó V
−2147483647 − 1 == 2147483648U unsigned true −2147483647 − 1 < 2147483647 signed true −2147483647 − 1U < 2147483647 unsigned false
−2147483647 − 1 < −2147483647 signed true −2147483647 − 1U < −2147483647 unsigned true 1.5 En una máquina de 32 bits, sean x, y y z enteros de valor arbitrario.
Entonces, dado el siguiente código, unsigned ux = (unsigned int) x; double dx = (double) x;
double dy = (double) y; double dz = (double) z;
indique cuáles de las siguientes expresiones siempre son verdaderas. En caso contrario, debe indicar un contraejemplo para recibir el puntaje asociado. a) (x >= 0) || (x < ux) b) dx + dy == (double) (y + x) c) dx + dy + dz == dz + dy + dx d) dx*dy*dz == dz*dy*dx Solución a) Verdadero b) Falso. Contraejemplo: x, y = 231− 1.
c) Falso. Contraejemplo: x = 3.14, y = DBL_MAX y z = −DBL_MAX. d) Falso. Contraejemplo: x = 0.01, y =√DBL_MAX y z =√DBL_MIN.
donde DBL_MIN y DBL_MAX son los valores mínimos y máximos re-presentables en un número de punto flotante de doble precisión.
1.6 Reescriba cada una de las siguientes expresiones válidas en C en su forma más simple. Suponga que a y b son enteros con signo en un pro-cesador de 32 bits, que MIN_INT es el mínimo entero representable en 32 bits y que MAX_INT es el máximo entero representable en 32 bits. Re-cuerde además que en C el operador ^ corresponde a un XOR lógico, y que ~ es la negación lógica de a.
a) ~(~a | (b^(MIN_INT + MAX_INT))) b) ((a^b) & ~b) | (~(a^b) & b) c) 1 + (a << 3) + ~a d) ((a < 0) ? (a + 3) : a) >> 2 e) ~((a >> 31) << 1) Solución a) {a*b b) a c) 7*a d) a/4 e) (a < 0) ? 1 : -1
1.7 Considere la siguiente representación de punto flotante de 16 bits ba-sada en el estándar IEEE-754:
El bit más significativo es el bit de signo
Los 7 bits siguientes son el exponente, que se almacena como e+63 Los últimos 8 bits son la mantisa, que se codifica con la técnica del 1 implícito.
Siguiendo reglas de representación similares a IEEE-754, indique las representaciones hexadecimales de los siguientes números:
a) −0
b) Menor número positivo mayor que 1 c) Mayor número positivo denormalizado d) −∞
Solución
La siguiente tabla muestra la solución del problema.
Número Signo Mantisa Exponente
−0 1 0x00 0x00
Menor número positivo mayor que 1 0 0x01 0x01
Mayor número positivo denormalizado 0 0xFF 0x00
−∞ 1 0x00 0x7F
NotANumber 0 0x01 0x7F
1.8 Considere la siguiente representación de punto flotante de 8 bits basa-da en el estánbasa-dar IEEE-754:
El bit más significativo es el bit de signo
Los 3 bits siguientes son el exponente E, que se almacena como
e + 3
Los últimos 4 bits son la mantisa M.
El valor del número se codifica como V = (−1)s× 1.M × 2E.
Siguiendo reglas de representación similares a IEEE-754, indique las representaciones binarias de los siguientes números:
a) −0
b) El mayor número positivo normalizado c) El menor número negativo denormalizado d) ∞
e) 1
f ) NotANumber
Además, indique qué valores son representados por las siguientes se-cuencias de bits:
a) 0 100 0101 b) 1 010 0110 c) 0 111 1110 d) 1 000 0000
Solución
La siguiente tabla muestra la solución del problema.
Número Signo Exponente Mantisa
−0 1 000 0000
Mayor número positivo normalizado 0 110 1111
Menor número negativo denormalizado 1 000 0001
∞ 0 111 0000 1 0 011 0000 NotANumber 0 111 0001 a) 0 100 0101 = 2.625 b) 1 010 0110 = 0.6875 c) 0 111 1110 = NotANumber d) 1 000 1111 = −0.1171875
1.9 La función clasifica_float() recibe como argumento un número de precisión simple de formato IEEE-754, y debe clasificarlo. Para ello, complete el código siguiente.
void clasifica_float(float f) {
// unsigned u tiene el mismo patron de bits que f unsigned u = *(unsigned *) &f;
// Identifica las partes de f
int signo = u >> 31; // Bit de signo int exp = ________________________;
int frac = _______________________;
// Expresiones siguientes dependen de signo, exp y frac if (___________________________)
printf("Cero positivo o negativo\n"); else if (___________________________)
printf("Numero denormalizado\n"); else if (___________________________)
printf("Infinito positivo o negativo\n"); else if (___________________________)
printf("Not A Number\n");
printf("Mayor que -1.0 y menor que 1.0\n"); else if (___________________________) printf("Menor o igual a -1.0\n"); else printf("Mayor o igual a 1.0\n"); } Solución
El siguiente código implementa la función pedida. void clasifica_float(float f)
{
// Unsigned u tiene el mismo patron de bits que float f unsigned u = *(unsigned *) &f;
//* Identifica las partes de f int signo = u >> 31;
int exp = (u >> 23) & 0xFF; int frac = u & 0x7FFFFF;
// Expresiones siguientes dependen de signo, exp y frac if (exp == 0x00 && frac == 0x00)
printf("Cero positivo o negativo\n"); else if (exp == 0x00 && frac != 0x00)
printf("Numero denormalizado\n"); else if (exp == 0xFF && frac == 0x00)
printf("Infinito positivo o negativo\n"); else if (exp == 0xFF && frac != 0x00)
printf("Not A Number\n"); else if (exp < 0x7F)
printf("Mayor que -1.0 y menor que 1.0\n"); else if (exp >= 0x7F && signo == 0x01)
printf("Menor o igual a -1.0\n"); else
printf("Mayor o igual a 1.0\n"); }
1.10 Suponga que Ud. está utilizando un sistema computacional descono-cido, y quiere saber si este sistema en particular respeta la convención que los desplazamientos aritméticos a la derecha mantienen el signo del dato.
Escriba una función en C llamada int shifts\_son\_aritmeticos() que retorne 1 si este sistema usa desplazamientos aritméticos y 0 si usa desplazamientos lógicos. Su función debe operar correctamente cual-quiera sea el tamaño de palabra del procesador.
Solución
A continuación, se muestra una posible implementación que funciona en procesadores de 16, 32 ó 64 bits. int shifts_son_aritmeticos(void) { int i = -1; if ((i >> 1) < 0) return 1; else return 0; }
1.11 Escriba una función en C llamada bool isLittleEndian(void) tal que retorne true si es compilada y ejecutada en un procesador de ar-quitectura Little Endian, y que retorne false si es compilada y ejecuta-da en un procesador de arquitectura Big Endian.
Solución
El siguiente código muestra una posible solución. bool isLittleEndian(void) { int x = 0x00000001; char *p = &x; return (*p == 1); }
1.12 Suponga que existen dos formatos de representación de números de punto flotante de 9 bits similares al estándar IEEE. Éstos son:
Formato A :
1 bit de signo s
5 bits de exponente e, el que se almacena como e + 15 3 bits de mantisa m
Formato B :
1 bit de signo s
4 bits de exponente e, el que se almacena como e + 7 4 bits de mantisa m
1) el mayor número positivo normalizado 2) el menor número positivo normalizado 3) el menor número positivo denormalizado 4) uno
5) infinito positivo
b) La siguiente tabla muestra 5 patrones de bit en formato A. Para
cada uno, indique:
1) Su valor, ya sea como entero, potencia o expresado como frac-ción
2) El patrón equivalente en formato B 3) El valor del patrón en formato B
Es posible que en algún caso, Ud. no pueda representar exacta-mente el valor pedido en formato B. En ese caso, aproxime siempre su resultado hacia +∞.
Formato A Formato B
Bits Valor Bits Valor
s e m s e m 1 01111 001 0 10110 011 1 00111 010 0 00000 111 1 11100 000 0 10111 100 Solución
a) La siguiente tabla muestra los patrones de bits solicitados y los
valores asociados.
Formato A Valor
Mayor número pos. normalizado 0 11110 111 (2 − 2−3)215
Menor número pos. normalizado 0 00001 000 2−14
Menor número pos. denormalizado 0 00000 001 2−17
Uno 0 01111 000 +1.0
Formato B Valor Mayor número pos. normalizado 0 1110 1111 (2 − 2−4)27
Menor número pos. normalizado 0 0001 0000 2−6
Menor número pos. denormalizado 0 0000 0001 2−10
Uno 0 0111 0000 +1.0
Infinito positivo 0 1111 0000 +∞
b) La siguiente tabla muestra los valores y patrones de bits
solici-tados. En la tercera línea, es necesario convertir un número nor-malizado en formato A a un número denornor-malizado en formato B. Asimismo, las líneas 4 y 5 requieren aproximar el resultado en formato B hacia +∞.
Formato A Formato B
Bits Valor Bits Valor
1 01111 001 −98 1 0111 0010 −98 0 10110 011 176 0 1110 0110 176 1 00111 010 −10245 1 0000 0101 −10245 0 00000 111 7 × 2−17 0 0000 0001 2−10 1 11100 000 -8192 1 1110 1111 −248 0 10111 100 384 0 1111 0000 +∞
Ensamblador Intel x86 y
lenguaje C
2.1 El lenguaje ensamblador Intel x86 posee la instrucción leal s, d, que almacena en la dirección destino d la dirección efectiva calculada para la dirección fuente s. Esta instrucción puede ser utilizada para realizar cálculos de la forma a << k + b, donde k puede tomar valores 1, 2 o 3. Indique todos los posibles múltiplos del registro %eax que pueden al-macenarse en el registro %edx utilizando esta instrucción, mostrando el formato de la instrucción leal s, d para cada uno.Solución
Los múltiplos del registro %eax que se pueden calcular con la instruc-ción leal s, d se muestran en la siguiente tabla.
Instrucción Acción
leal (%eax), %edx %edx ← %eax
leal (%eax, %eax), %edx %edx ← 2%eax leal (%eax, %eax, 2), %edx %edx ← 3%eax leal (,%eax, 4), %edx %edx ← 4%eax leal (%eax, %eax, 4), %edx %edx ← 5%eax leal (,%eax, 8), %edx %edx ← 8%eax leal (%eax, %eax, 8), %edx %edx ← 9%eax
2.2 La función void decode1(int *xp, int *yp, int *zp) es compila-da al siguiente código ensamblador.
movl 8(%ebp), %edi movl 12(%ebp), %ebx movl 16(%ebp), %esi movl (%edi), %eax movl (%ebx), %edx movl (%esi), %ecx movl %eax, (%ebx) movl %edx, (%esi) movl %ecx, (%edi)
Los parámetros xp, yp y zp están almacenados a 8, 12 y 16 bytes de la dirección de memoria apuntada por %ebp. Escriba el código C equiva-lente al código ensamblador mostrado.
Solución
El siguiente código muestra una posible solución. void decode1(int *xp, int *yp, int *zp) {
int tx = *xp; int ty = *yp; int tz = *zp; *yp = tx; *zp = ty; *xp = tz; }
2.3 Sea el siguiente código ensamblador X86: pushl %ebp
movl %esp, %ebp movl 8(%ebp), %edx movl 12(%ebp), %eax movl %ebp, %esp movl (%edx), %edx addl %edx, (%eax) movl %edx, %eax popl %ebp ret
Rellene el siguiente fragmento de código C para que sea equivalente al código ensamblador anterior.
int fun(int *ap, int *bp) { ______ = ______; ______ = ______; return ______; } Solución
El siguiente código C es equivalente al código ensamblador X86 mos-trado.
int fun(int *ap, int *bp) { int temp = *ap;
*bp += *ap; return temp; }
2.4 El siguiente código C
int dw_loop(int x, int y, int n) { do { x += n; y *= n; n--; } while ((n > 0) & (y < n)); return x; }
es traducido al siguiente código ensamblador, donde las variables x, y y n están a 8, 12 y 16 bytes de (%ebp), respectivamente.
movl 8(%ebp), %esi movl 12(%ebp), %ebx movl 16(%ebp), %ecx .L6: imull %ecx, %ebx
addl %ecx, %esi decl %ecx testl %ecx, %ecx setg %al
cmpl %ecx, %ebx setl %dl andl %edx, %eax testb $1, %al jne .L6
a) Genere una tabla de uso de registros b) Comente el código ensamblador
c) Identifique la condición y el cuerpo del ciclo do-while() y las
lí-neas correspondientes del código ensamblador. Solución
a) La tabla de registros es
Registro Variable Valor Inicial
%esi x x
%ebx y y
%ecx n n
b) El código ensamblador comentado es
movl 8(%ebp), %esi // Almacena x en %esi movl 12(%ebp), %ebx // Almacena y en %ebx movl 16(%ebp), %ecx // Almacena n en %ecx .L6: imull %ecx, %ebx // y *= n
addl %ecx, %esi // x += n
decl %ecx //
n--testl %ecx, %ecx // Test n
setg %al // n > 0
cmpl %ecx, %ebx // Compara y y n
setl %dl // y < n
andl %edx, %eax // (n > 0) & (y < n) testb $1, %al // Test LSB
jne .L6 // If != 0, ir a lazo
El compilador reconoce que las condiciones (n > 0) y (y < n) se evalúan sólo a 0 o 1, y que entonces sólo es necesario examinar el bit menos significativo del resultado para saber el valor de la condición compuesta.
c) El cuerpo del ciclo está en las líneas 4 a 6 del código C y en las
líneas 6 a 8 del código ensamblador. La condición a evaluar está en la línea 7 del código C y en las líneas 9 a 14 del código ensam-blador.
2.5 En el siguiente código C, se han omitido los operadores de comparación y los tipos de datos de los cast.
char ctest(int a, int b, int c) { char t1 = a __ b; char t2 = b __ ( ) a; char t3 = ( ) c __ ( ) a; char t4 = ( ) a __ ( ) c; char t5 = c __ b; char t6 = a __ 0; return t1 + t2 + t3 + t4 + t5 + t6; }
A partir de ese código, el compilador C genera el siguiente código en-samblador:
ctest: movl 8(%ebp), %ecx movl 12(%ebp), %esi cmpl %esi, %ecx setl %al cmpl %ecx, %esi setb -1(%ebp) cmpw %cx, 16(%ebp) setge -2(%ebp) movb %cl,%dl cmpb 16(%ebp),%dl setne %bl cmpl %esi, 16(%ebp) setg -3(%ebp) testl %ecx, %ecx setg %dl addb -1(%ebp),%al addb -2(%ebp),%al addb %bl, %al addb -3(%ebp),%al addb %dl, %al movsbl %al, %eax
Suponga que las variables a, b y c están a 8, 12 y 16 bytes de (%ebp), respectivamente. Indique las partes faltantes del código C.
Solución
char ctest(int a, int b, int c) {
char t1 = a < b;
char t2 = b < (unsigned) a; char t3 = (short) c >= (short) a; char t4 = (char) a != (char) c;
char t5 = c > b;
char t6 = a > 0;
return t1 + t2 + t3 + t4 + t5 + t6; }
2.6 Sea el siguiente programa en C: unsigned int c, v;
for (c = 0; v; c++) { v &= (v - 1); }
a) Identifique qué hace este programa.
b) Escriba código en ensamblador Intel X86 de 32 bits equivalente.
Suponga que el registro %ecx contiene la variable c, y que el regis-tro %ebx contiene la variable v. Su código debe retornar el resulta-do en el registro %eax.
Solución
a) El programa cuenta el número de bits en 1 del argumento v. b) A continuación, se muestra un programa en ensamblador que
rea-liza la misma función.
xorl %ecx, %ecx # Hace 0 el contador c cmpl $0, %ebx # Si v = 0, ir a fin je fin
lazo: movl %ebx, %eax
decl %eax # Calcula v - 1
incl %ecx # Incrementa el contador andl %eax, %ebx # Calcula v = v&(v - 1) jnz lazo # Si no es 0, repetir movl %ecx, %eax # Retorna c en %eax
2.7 Considere el siguiente código ensamblador Intel x86 de 32 bits: yolo: pushl %ebp
movl %esp, %ebp pushl %esi pushl %ebx
movl 8(%ebp), %ebx movl 12(%ebp), %esi xorl %edx, %edx xorl %ecx, %ecx cmpl %ebx, %edx jge .L25
.L27: movl (%esi, %ecx, 4), %eax cmpl %edx, %eax
jle .L28 movl %eax, %edx .L28: incl %edx
incl %ecx cmpl %ebx, %ecx jl .L27
.L25: movl %edx, %eax popl %ebx popl %esi leave ret
Se sabe que este código viene de ensamblar el siguiente código C: int yolo(int n, int *a) {
int i; int x = ____________; for (i = ____________; ____________; _____________) { if (____________) { ____________; } ____________; } return ____________; }
Basándose en el código ensamblador mostrado, complete el código C anterior utilizando sólo los nombres de las variables a, i, n y x. En ca-so de realizar acceca-sos a vectores, utilice notación vectorial basada en índices.
Solución
Como primer paso en el análisis, se comentará el código ensamblador. yolo: pushl %ebp # Crea el marco de activacion
movl %esp, %ebp
pushl %esi # Almacena valores anteriores de pushl %ebx # registros %esi y %ebx en la pila movl 8(%ebp), %ebx # Lee n en %ebx
movl 12(%ebp), %esi # Lee a en %esi
xorl %edx, %edx # Hace 0 la variable temporal x xorl %ecx, %ecx # Hace 0 la variable temporal i cmpl %ebx, %edx # Si (n < 0), ir a .L25
jge .L25
.L27: movl (%esi, %ecx, 4), %eax # Lee a[i] en %eax
cmpl %edx, %eax # Si (a[i] > x), ir a .L28 jle .L28
movl %eax, %edx # Copia a[i] en x
.L28: incl %edx # Incrementa x
incl %ecx # Incrementa i
cmpl %ebx, %ecx # Si (i < n), ir a .L27 jl .L27
.L25: movl %edx, %eax # Copia x en %eax
popl %ebx # Recupera valores anteriores de popl %esi # registros %esi y %ebx de la pila leave # Recupera el marco de activacion ret
En base a este código, se puede deducir el siguiente código C. int yolo(int n, int *a) {
int i; int x = 0; for (i = 0; i < n; i++) { if (a[i] > x) { x = a[i]; } x++; } return x; }
2.8 Considere el siguiente código ensamblador: lazo: pushl %ebp
movl %esp, %ebp movl 8(%ebp), %ecx movl 12(%ebp), %edx xorl %eax, %eax cmpl %edx, %ecx jle Fin
Otro: decl %ecx incl %edx incl %eax cmpl %edx, %ecx jg Otro
Fin: incl %eax leave ret
Se sabe que el código anterior corresponde a un código C de la forma: int lazo(int x, int y) {
int result; for(________; ________; result++) { ________; ________; } ________; return result; }
Reescriba el código C anterior, rellenando las líneas indicadas. Utilice sólo las variables x, y y result. No utilice los nombres de los registros! Solución
Para analizar el código ensamblador, comenzaremos por comentarlo: lazo: pushl %ebp # Crea el marco de activacion
movl %esp, %ebp
movl 8(%ebp), %ecx # Lee x en %ecx movl 12(%ebp), %edx # Lee y en %edx
xorl %eax, %eax # result en %eax es igual a 0 cmpl %edx, %ecx # Compara x con y (x - y) jle Fin # Salta si x es <= a y Otro: decl %ecx # Decrementa x
incl %edx # Incrementa y incl %eax # Incrementa result cmpl %edx, %ecx # Compara x con y (x - y) jg Otro # Repite si x > y
Fin: incl %eax # Incrementa result leave
ret # Elimina el marco de activacion
Dado que la convención de paso de parámetros de C dice que éstos se pasan a través de la pila de derecha a izquierda, es claro que x está en la posición -8(%ebp) y que y está en la posición -12(%ebp). Entonces, se puede ver que el código C equivalente es:
int lazo(int x, int y) { int result; for(result = 0; x > y; result++) { x--; y++; } result++; return result; }
2.9 Considere el siguiente código C, donde M y N son constantes definidas con #define en alguna parte del código.
int mat1[M][N]; int mat2[N][M];
int sumaElementos(int i, int j) { return mat1[i][j] + mat2[i][j]; }
Ese código C genera el siguiente código en lenguaje de ensamblador. Suponga que el argumento i está en 8(%ebp), y que el argumento j está en 12(%ebp). Suponga además que mat1 y mat2 corresponden a las direcciones iniciales en memoria de las matrices correspondientes. sumaElementos:
pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax movl 12(%ebp), %ecx
sall $2, %ecx
leal 0(, %eax, 8), %edx subl %eax, %edx
leal (%eax, %eax, 4), %eax movl mat2(%ecx, %eax, 4), %eax addl mat1(%ecx, %edx, 4), %eax leave
ret
Cuáles son los valores de M y N ? Solución
A continuación, se muestra el código ensamblador comentado.
1 pushl %ebp # Almacena %ebp en la pila
2 movl %esp, %ebp # Actualiza puntero base
3 movl 8(%ebp), %eax # %eax <- i
4 movl 12(%ebp), %ecx # %ecx <- j
5 sall $2, %ecx # %ecx <- 4j
6 leal 0(, %eax, 8), %edx # %edx <- 8i
7 subl %eax, %edx # %edx <- 8i - i = 7i
8 leal (%eax, %eax, 4), %eax # %eax <- 5*%eax = 5i
9 movl mat2(%ecx, %eax, 4), %eax # %eax <- M[mat2 + 4j + 20i]
10 addl mat1(%ecx, %edx, 4), %eax # %eax <- %eax*M[mat1 + 4j + 28i]
11 leave
12 ret
En las líneas 9 y 10, se accede a las posiciones mat1 + 28i + 4j y mat2 + 20i + 4j. Dado que cada elemento de la matriz es un entero de 32 bits, se deduce entonces que M = 5 y N = 7.
2.10 Considere el siguiente código C, donde M y N son constantes definidas con #define en alguna parte del código.
int mat1[M][N]; int mat2[N][M];
int copiaElemento(int i, int j) { return mat1[i][j] = mat2[j][i]; }
Ese código C genera el siguiente código en lenguaje de ensamblador. Recuerde que el lenguaje C almacena las matrices por filas. Supon-ga que el argumento i está en 8(%ebp), y que el argumento j está en
12(%ebp). Suponga además que mat1 y mat2 corresponden a las direc-ciones iniciales en memoria de las matrices correspondientes.
copiaElemento: pushl %ebp movl %esp, %ebp pushl %ebx
movl 8(%ebp), %ecx movl 12(%ebp), %ebx leal (%ecx,%ecx,8), %edx sall $2, %edx
movl %ebx, %eax sall $4, %eax subl %ebx, %eax sall $2, %eax
movl mat2(%eax, %ecx, 4), %eax movl %eax, mat1(%edx, %ebx, 4) popl %ebx
leave ret
Cuáles son los valores de M y N ? Solución
A continuación, se muestra el código ensamblador X86 comentado.
1 pushl %ebp // Forma marco de activacion
2 pushl %ebx // Almacena %ebx en la pila
3 movl 8(%ebp), %ecx // %ecx <- i
4 movl 12(%ebp), %ebx // %ebx <- j
5 leal (%ecx,%ecx,8), %edx // %edx <- 9i
6 sall $2, %edx // %edx <- 4*9i
7 movl %ebx, %eax // %eax <- j
8 sall $4, %eax // %eax <- 16*j
9 subl %ebx, %eax // %eax <- 15*j
10 sall $2, %eax // %eax <- 4*15j
11 movl mat2(%eax, %ecx, 4), %eax // %eax <- M[mat2 + 4*(15*j + i)]
12 movl %eax, mat1(%edx, %ebx, 4) // M[mat1 + 4*(9*i + j)] <- %eax
13 popl %ebx // Recupera %ebx de la pila
14 leave // Elimina marco de activacion
15 ret
De estudiar los códigos ensamblador y en lenguaje C, se ve que en las líneas 11 y 12 se accede a las posiciones mat2 + 4(15*j + i) y mat1 + 4*(9*i + j). Dado que cada elemento de la matriz es un en-tero de 32 bits, se tiene que M = 15 y N = 9.
2.11 El producto punto de vectores es una operación de reducción que opera sobre dos vectores de la forma que se indica:
a • b = a1b1+ a2b2+ ... + an−1bn−1+ anbn
La función int productoPunto(int A[], int B[], int n) en len-guaje C retorna el producto punto de dos vectores A y B de largo n. int productoPunto(int A[], int B[], int n) {
int i; int suma = 0; for (i = 0; i < n; i++) { suma += A[i]*B[i]; } return suma; }
Escriba una función en lenguaje ensamblador equivalente. Solución
Primero, debemos transformar el código C a una forma más apropiada para su implementación en lenguaje ensamblador. Es decir, transfor-mar el ciclo for a un ciclo while, y transfortransfor-mar los accesos a vector vía índice a notación de punteros.
int productoPunto(int *A, int *B, int n) { int i = 0;
int suma = 0; while (i < n) {
suma = suma + *(A + 4*i) * *(B + 4*i); i++;
}
return suma; }
Luego, traducimos este código C a ensamblador, como se muestra a con-tinuación.
productoPunto:
pushl %ebp // Prepara la pila
movl %esp, %ebp
pushl %esi // Almacena registro %esi pushl %edi // Almacena registro %edi
xorl %edx, %edx // i <- 0 xorl %eax, %eax // suma <- 0 movl 8(%ebp), %esi // %esi <- A movl 12(%ebp), %edi // %edi <- B Lazo: cmpl %edx, 16(%ebp) // Compara n con i
je Fin // Si son iguales, ir a Fin
movl (%esi, %edx, 4), %ecx // %ecx <- *(A + 4*i) imull (%edi, %edx, 4), %ecx // %ecx <- *(B + 4*i)*%ecx addl %ecx, %eax // suma <- %ecx
incl %edx // i <- i + 1
jmp Lazo // Repetir
Fin: popl %edi // Recupera registro %edi popl %esi // Recupera registro %esi
leave // Recupera valores de pila
ret
2.12 El Máximo Común Divisor entre dos números puede calcularse recursi-vamente como se muestra a continuación:
int mcd(int m, int n) { if (n == 0) { return m; } if (m == 0) { return n; } mcd(n, m%n); }
Escriba ahora una función equivalente en ensamblador. Solución
Se muestra una posible solución. Como sólo hace uso del registro %eax que es Caller Save y que no retiene valores importantes entre invocacio-nes recursivas, no es necesario salvarlo en la pila.
mcd: pushl %ebp // Prepara la pila movl %esp, %ebp
cmpl $0, 12(%ebp) // Compara n con 0 jne SIGUE1
movl 8(%ebp), %eax // Si n == 0, retornar m jmp FIN
SIGUE1: cmpl $0, 8(%ebp) // Compara m con 0 jne SIGUE2
movl 12(%ebp), %eax // Si m == 0, retornar n jmp FIN
SIGUE2: movl 12(%ebp), %eax // %eax <- n
ctld // Extiende m a 64 bits
idivl 8(%ebp) // Calcula m/n
pushl %edx // m%n se agrega a la pila movl 12(%ebp), %eax // %eax <- n
pushl %eax // Agrega n a la pila call mcd
FIN: leave ret
2.13 Sea la matriz A de dimensiones 3 ∗ 3, como se muestra:
A = a b c d e f g h i
El determinante |A| de dicha matriz está dado por |A| = aei −af h −bdi +
bf g + cdh− ceg.
Escriba una función en ensamblador Intel x86 det3x3(int A[][3]) que reciba como argumento la dirección inicial de la matriz en 8(%ebp) y retorne el determinante de dicha matriz en el registro %eax. Suponga además que el rango de los elementos de A es tal que le basta con usar la operación de multiplicación imull S, D.
Solución
Se muestra un posible código ensamblador para la función pedida. det3x3: pushl %ebp // Prepara la pila
movl %esp, %ebp
pushl %ebx // Almacena registro %ebx en pila movl 8(%ebp), %ebx // Lee direccion de matriz A movl 16(%ebx), %ecx // Lee valor e en %ecx imull 32(%ebx), %ecx // Calcula ei
movl 20(%ebx), %edx // Lee valor f en %edx imull 28(%ebx), %edx // Calcula fh
subl %edx, %ecx // Calcula ei - fh imull (%ebx), %ecx // Calcula a(ei - fh) movl %ecx, %eax // Acumula resultado en %eax movl 20(%ebx), %ecx // Lee valor f en %ecx imull 24(%ebx), %ecx // Calcula fg
movl 12(%ebx), %edx // Lee valor d en %edx imull 32(%ebx), %edx // Calcula di
subl %edx, %ecx // Calcula fg - di imull 4(%ebx), %ecx // Calcula b(fg - di) addl %ecx, %eax // Acumula resultado en %eax
movl 12(%ebx), %ecx // Lee valor d en %ecx imull 28(%ebx), %ecx // Calcula dh
movl 16(%ebx), %edx // Lee valor e en %edx imull 24(%ebx), %edx // Calcula eg
subl %edx, %ecx // Calcula dh - eg imull 8(%ebx), %ecx // Calcula c(dh - eg) addl %ecx, %eax // Acumula resultado en %eax popl %ebx // Recupera valor de %ebx leave
ret
2.14 Suponga que los 4 bytes de un entero int de 32 bits se numeran desde el menos significativo (0) al más significativo (3).
a) Escriba una función en C unsigned int reemplazaByte(unsigned
int x, unsigned int i, unsigned char b)que reemplace el i-ésimo byte del entero x por el byte b dado. Por ejemplo, escri-bir reemplazaByte(0x12345678, 2, 0xAB) deberá retornar el en-tero 0x12AB5678, mientras que reemplazaByte(0x12345678, 0, 0xAB)deberá retornar el entero 0x123456AB.
b) Escriba ahora una función equivalente en lenguaje ensamblador
x86. Recuerde que la convención de paso de argumentos de C es-tablece que éstos se almacenan en la pila de derecha a izquierda. No olvide respetar las convenciones Caller-Save y Callee-Save. Solución
a) El siguiente código C implementa la función pedida. Se utiliza una
máscara para anular los bits a reemplazar, y luego se suma el nue-vo byte, desplazado i bytes a la izquierda.
unsigned int
reemplazaBytes(unsigned int x, unsigned int i, unsigned char b) {
unsigned int mascara = 0xFF << (i*8); return (x & ~mascara) | (b << (i*8)); }
b) El siguiente código ensamblador implementa la función pedida.
Para acceder al byte a modificar, se utiliza la variable i como un desplazamiento dentro de la copia del entero x que está en la pila. Luego, esta versión modificada de x se lee al registro %eax para ser
usado como valor de retorno. Como los únicos registros utilizados son %eax y %ecx, y éstos son Caller-Save, no es necesario almace-narlos en la pila.
pushl %ebp # Crea marco de activacion movl %esp, %ebp
movb 16(%ebp), %al # Lee byte b a en %al movl 12(%ebp), %ecx # Lee i
movb %al, 8(%ebp, %ecx) # b -> i-esimo byte de x movl 8(%ebp), %eax # Lee valor modificado
leave # como valor de retorno
ret
2.15 Se desea obtener la traspuesta de una matriz cuadrada M de dimensio-nes N × N . Para ello, Ud. debe realizar las siguientes tareas:
a) Primero, escriba una función void swap(int *a, int *b) en C
que intercambie los contenidos de dos punteros a enteros.
b) Luego, escriba esta función en lenguaje de ensamblador x86.
Re-cuerde que la convención de paso de argumentos de C establece que éstos se almacenan en la pila de derecha a izquierda. No olvi-de respetar las convenciones Caller-Save y Callee-Save.
c) Luego, escriba una función en C llamada void traspuesta(int
*M, int N) que recibe dos argumentos. El primero es un puntero a un vector de N2 enteros, utilizado para simular el acceso a una
matriz de N × N enteros, tal como se vió en clases. El segundo argumento es la dimensión de la matriz. Su función debe utilizar la función swap() escrita anteriormente.
d) Implemente ahora esta función en lenguaje ensamblador x86.
Solución
a) El siguiente código C implementa la función deseada. No es la
implementación más eficiente, pero refleja la implementación en lenguaje ensamblador que se muestra a continuación.
void swap(int *a, int *b) { int aTemp = *a; int bTemp = *b; *a = bTemp; *b = aTemp; }
b) El siguiente código ensamblador implementa la función swap()}
del punto anterior. Utiliza el registro %ebx, el cual es Callee-Save. Por ello, el valor de este registro se almacena en la pila al comienzo de la función y se recupera al final.
swap: pushl %ebp # Crea marco de activacion movl %esp, %ebp
pushl %ebx # Almacena registro %ebx movl 8(%ebp), %eax # Lee a al registro %eax movl 12(%ebp), %edx # Lee b al registro %edx movl (%eax), %ecx # Lee *a al registro %ecx movl (%edx), %ebx # Lee *b al registro %ebx movl %ecx, (%edx) # Escribe *a a puntero b movl %ebx, (%eax) # Escribe *b a puntero a popl %ebx # Recupera registro %ebx leave
ret
c) A continuación, se muestra una implementación en C de la
fun-ción traspuesta() solicitada. void traspuesta(int *M, int N) {
int i, j;
for (i = 0; i < N; i++) { for (j = 0; j < i; j++) {
swap(&M[i*N + j], &M[j*N + i]); }
} }
d) El siguiente código muestra una posible implementación en
len-guaje ensamblador de esta función.
pushl %ebp # Crea marco de activacion movl %esp, %ebp
xorl %eax, %eax # Hace i igual a 0 lazo1: xorl %ecx, %ecx # Hace j igual a 0 lazo2: movl 12(%ebp), %edx # Lee N
mull %ecx, %edx # Multiplica j*N addl %eax, %edx # Calcula j*N + i sall $2, %edx # Multiplica 4*(j*N + i) addl 8(%ebp), %edx # Calcula M + 4*(j*N + i) pushl %edx # Empila M + 4*(j*N + i) movl 12(%ebp), %edx # Lee N
mull %eax, %edx # Multiplica i*N addl %ecx, %edx # Calcula i*N + j
sall $2, %edx # Multiplica 4*(i*N + j) addl 8(%ebp), %edx # Calcula M + 4*i*N + j) pushl %edx # Empila M + 4*(i*N + j) call swap # Invoca a funcion swap() addl $8, %esp # Recupera posicion incl %ecx # Incrementa j
cmpl %ecx, %eax # Compara j con i (i - j) jl lazo2 # Si j < i, ir a lazo2 incl %eax # Incrementa i
cmpl %eax, 12(%ebp) # Compara i con N (N - i) jl lazo1
leave ret
2.16 Escriba una función unsigned int unosConsecutivos(unsigned int x)en lenguaje ensamblador que retorne el largo de la máxima secuen-cia de 1s consecutivos presentes en el argumento x. Es decir, si el ar-gumento es 0xDBFF1F1C, la función debe retornar 10. No olvide respe-tar las convenciones Caller-Save y Callee-Save en su código. Sugerencia: piense en cómo escribiría esta función en C antes de comenzar a pro-gramar en ensamblador.
Solución
El siguiente código muestra una posible implementación de la función deseada en lenguaje de programación C.
unsigned int UnosConsecutivos(unsigned int x) {
int maxContador = 0; // Largo de maxima secuencia int contador = 0; // Contador de unos
while (x != 0) { if ((signed int) x < 0) { contador++; if (maxContador < contador) { maxContador = contador; } } else { contador = 0; } x = x << 1; } return maxContador; }
A su vez, el siguiente código muestra una implementación de la función anterior en lenguaje de ensamblador x86.
unosconsecutivos:
pushl %ebp # Almacena puntero base en la pila movl %esp, %ebp # Redefine el puntero base
movl 8(%ebp), %ecx # Lee el argumento x a %ecx xorl %eax, %eax # Hace 0 %eax (maxContador) xorl %edx, %edx # Hace 0 %edx (contador) lazo: cmpl $0, %ecx # Compara x con 0
jz fin # si x es cero, va a fin jns pos # Salta si x es positivo incl %edx # Si es negativo, contador++ cmpl %edx, %eax # Compara maxContador y contador jge shift # Sigue si maxContador >= contador movl %edx, %eax # Actualiza valor de maxContador jmp shift
shift: sall $1, %ecx # Desplaza x un bit a la izquierda jmp lazo
pos: xorl %edx, %edx # Limpia el contador jmp shift # Si i es 0, ir al fin
fin: movl %ebp, %esp # Recupera valores de %ebp y %esp popl %ebp
ret
2.17 Considere el siguiente código ensamblador Intel x86 de 32 bits: yolo: pushl %ebp
movl %esp, %ebp pushl %esi pushl %ebx
movl 8(%ebp), %ebx movl 12(%ebp), %esi xorl %edx, %edx xorl %ecx, %ecx cmpl %ebx, %edx jge .L25
.L27: movl (%esi, %ecx, 4), %eax cmpl %edx, %eax
jle .L28 movl %eax, %edx .L28: incl %edx
incl %ecx cmpl %ebx, %ecx jl .L27
.L25: movl %edx, %eax popl %ebx popl %esi leave ret
Se sabe que este código es producto de ensamblar el siguiente código C:
int yolo(int n, int *a) { int i; int x = ____________; for (i = ____________; ____________; _____________) { if (____________) { ____________; } ____________; } return ____________; }
Basándose en el código ensamblador mostrado, complete el código C anterior utilizando sólo los nombres de las variables a, i, n y x. En ca-so de realizar acceca-sos a vectores, utilice notación vectorial basada en índices.
Solución
Como primer paso en el análisis, se comentará el código ensamblador. yolo: pushl %ebp # Crea el marco de activacion
movl %esp, %ebp
pushl %esi # Almacena valores anteriores de pushl %ebx # registros %esi y %ebx en la pila movl 8(%ebp), %ebx # Lee n en %ebx
movl 12(%ebp), %esi # Lee a en %esi
xorl %edx, %edx # Hace 0 la variable temporal x xorl %ecx, %ecx # Hace 0 la variable temporal i cmpl %ebx, %edx # Si (n < 0), ir a .L25
jge .L25 #
.L27: movl (%esi, %ecx, 4), %eax # Lee a[i] en %eax
cmpl %edx, %eax # Si (a[i] > x), ir a .L28 jle .L28
movl %eax, %edx # Copia a[i] en x
incl %ecx # Incrementa i
cmpl %ebx, %ecx # Si (i < n), ir a .L27 jl .L27
.L25: movl %edx, %eax # Copia x en %eax
popl %ebx # Recupera valores anteriores de popl %esi # registros %esi y %ebx de la pila leave # Recupera el marco de activacion ret
En base al código ensamblador anterior, se puede deducir el siguiente código C.
int yolo(int n, int *a) { int i; int x = 0; for (i = 0; i < n; i++) { if (a[i] > x) { x = a[i]; } x++; } return x; }
2.18 Sea la siguiente función switcher en lenguaje C, que utiliza un coman-do switch.
int switcher(int a, int b, int c) { int result; switch(a) { case ________________: /* Caso A */ c = ________________; case ________________: /* Caso B */ result = ________________; break; case ________________: /* Caso C */ case ________________: /* Caso D */ result = ________________; break; case ________________: /* Caso E */ result = ________________; break; default: result = ________________;
}
return result; }
Esta función es traducida en lenguaje ensamblador como se muestra a continuación:
# Codigo de funcion switcher()
# Suponga a en %ebp + 8, b en %ebp + 12, c en %ebp + 16 movl 8(%ebp), %eax
cmpl $7, %eax ja .L2
jmp *.L7(, %eax, 4) .L2: movl 12(%ebp), %eax
jmp .L8 .L5: movl $4, %eax
jmp .L8
.L6: movl 12(%ebp), %eax xorl $15, %eax movl %eax, 16(%ebp) .L3: movl %16(%ebp), %eax
addl $112, %eax jmp .L8
.L4: movl 16(%ebp), %eax addl 12(%ebp), %eax sall $2, %eax .L8: # Tabla de saltos .L7: .long .L3 .long .L2 .long .L4 .long .L2 .long .L5 .long .L6 .long .L2 .long .L4
Complete entonces la función switcher mostrada. Solución
El siguiente código implementa la función pedida. int switcher(int a, int b, int c) {
int result; switch(a) {
c = b^15; case 0: /* Caso B */ result = c + 112; break; case 2: /* Caso C */ case 7: /* Caso D */ result = 4*(b + c); break; case 4: /* Caso E */ result = 4; break; default: result = b; } return result; }
2.19 Uno de los problemas intrínsecos de la representación de enteros en un número finito de bits es la ocurrencia de rebalses.
a) Escriba una función llamada sePuedenSumar(int x, int y) en
C que retorne 1 si los enteros con signo de 32 bits x e y se pueden sumar sin posibilidad de rebalse, y 0 en caso contrario.
b) Escriba ahora código en ensamblador Intel X86 de 32 bits
equiva-lente. Suponga que el registro %ecx contiene la variable x, y que el registro %ebx contiene la variable y. Su código debe retornar el resultado 0 ó 1 en el registro %eax.
Solución
Para determinar si dos números enteros se pueden sumar sin que ocu-rra un error de representación, una técnica sencilla es realizar la suma y ver el signo del resultado: si la suma de dos números positivos da un resultado negativo, entonces ocurrió un rebalse. Asimismo, si la suma de dos números negativos da un resultado positivo, entonces ocurrió un rebalse. La suma de un número positivo y un número negativo no puede dar un rebalse.
a) El siguiente código C implementa lo solicitado.
bool sePuedenSumar(int x, int y) { int suma = x + y;
int rebalse_neg = x < 0 && y < 0 && suma >= 0; int rebalse_pos = x >= 0 && y >= 0 && suma < 0;
return !rebalse_pos && rebalse_neg; }
b) El siguiente código Intel x86 implementa lo solicitado.
movl $1, %eax # Hace 1 el registro de resultado %eax movl %ecx, %esi # Copia x en %esi
movl %ebx, %edi # Copia y en %edi xorl %esi, %edi # Calcula x XOR y
js fin # Si < 0, x e y tienen distinto signo addl %ebx, %esi # Calcula suma = x + y
js sum_neg # Salta si la suma es negativa testl %ecx, %ecx # Si x < 0 e y < 0 y la suma js fin_no # es positiva, no se pueden sumar jmp fin # Si x < 0 e y < 0, no se pueden sumar sum_neg:testl %ecx, %ecx # Si x >= 0 e y >= 0, y suma es negativa jns fin_no # entonces no se pueden sumar
jmp fin # En caso contrario, se pueden sumar fin_no: xorl %eax, %eax # Hace 0 el registro resultado %eax
fin: # Fin del codigo solicitado
2.20 Escriba una función void invierteRistra(char *cp, unsigned int n) en lenguaje ensamblador que reciba como argumentos un puntero a una ristra de C y el número de caracteres de ésta, y que invierta esta ristra in situ, es decir, en las mismas direcciones de memoria. Supon-ga que los argumentos a la función están almacenados en 8(%ebp) y 12(%ebp), respectivamente. No olvide respetar las convenciones
Caller-Save y Callee-Caller-Save en su código. Sugerencia: piense en cómo escribiría
esta función en C antes de comenzar a programar en ensamblador. Solución
El siguiente código de ensamblador X86 muestra una posible solución. .type invierteRistra, @function
invierteRistra:
pushl %ebp # Almacena puntero base en la pila movl %esp, %ebp # Redefine el puntero base
movl 8(%ebp), %eax # Lee direccion de ristra en %eax movl 12(%ebp), %edx # Lee el largo de la ristra en %edx addl %eax, %edx # Calcula posicion del ultimo subl $1, %edx # caracter de la ristra lazo: cmpl %eax, %edx # Compara puntero con el final
js finLazo # de la ristra y salta si es menor movb (%eax), %ch # Intercambia caracteres de ristra movb (%edx), %cl # utilizando registros intermedios
movb %ch, (%edx) movb %cl, (%eax)
incl %eax # Incrementa puntero a ristra en 1 decl %edx # Decrementa fin de ristra en 1 jmp lazo
finLazo:leave ret
2.21 Complete el siguiente código C en base al código ensamblador genera-do.
int funcion(int x, int n) { int result = x;
switch(n) {
// Aqui falta el codigo que Ud. debe escribir }
return result; }
El código ensamblador Intel x86 generado se muestra a continuación, donde la columna de la izquierda indica la dirección en memoria de cada instrucción. Asimismo, la tabla de saltos está almacenada a partir de la dirección de memoria 2000.
1000: pushl %ebp 1001: movl %esp, %ebp 1003: movl 8(%ebp), %eax 1006: movl 12(%ebp), %edx 1009: subl $50, %edx 1012: cmpl $5, %edx 1015: ja 1040 1017: jmp *2000(, %edx, 4) 1024: shll $2, %eax 1027: jmp 1043 1029: sarl $2, %eax 1032: jmp 1043
1034: leal (%eax, %eax, 2), %eax 1037: imull %eax, %eax
1040: addl $10, %eax 1043: popl %ebp 1044: ret . . . . 2000: 1024 2004: 1040 2008: 1024
2012: 1029 2016: 1034 2020: 1037 Solución
El siguiente código muestra una implementación que cumple con lo solicitado.
int funcion(int x, int n) { int result = x; switch(n) { case 50: case 52: result <<= 2; break; case 53: result >>= 2; break; case 54: result *= 3; case 55: result *= result; default: result += 10; } return result; }
2.22 La función 91 es una función recursiva creada por John McCarthy que tiene la particularidad que se evalúa a 91 para todos los enteros n ≤ 100, y es igual a n−10 para n ≥ 101. Su implementación en C es como sigue: int f91(int n) { if (n > 100) { return n - 10; } else { return f91(f91(n + 11)); } }
Escriba esta función recursiva en lenguaje ensamblador X86. Suponga, como de costumbre, que esta función recibe un argumento entero en la pila, y que retorna su valor en el registro %eax.
Solución
El siguiente código muestra una posible implementación. .type f91, @function
f91: pushl %ebp # Almacena puntero base en la pila movl %esp, %ebp # Redefine el puntero base
movl 8(%ebp), %eax # Lee el argumento al registro %eax cmpl $100, %eax # Compara argumento con 100
jg fin_f91 # Si es 1, terminar
addl $11, %eax # En otro caso, incrementar %eax en 10 pushl %eax # Almacena en la pila
call f91 # Llama a f91
popl %ebx # Extrae argumento de la pila
pushl %eax # Almacena nuevo argumento en la pila call f91 # Llama a f91 de nuevo
popl %ebx # Extrae argumento de la pila jmp fin2
fin_f91:subl $10, %eax # Si n es > 100, retorna n - 10 fin2: movl %ebp, %esp # Recupera valores de %ebp y %esp
popl %ebp ret
2.23 La función char *strchr(const char *s, int c) de la biblioteca es-tándar C recibe como argumentos un puntero a una ristra s y un entero c, y retorna un puntero a la primera ocurrencia del caracter contenido en c en la ristra. Si este caracter no está presente en la ristra, enton-ces la función debe retornar 0. Nótese que el argumento c es un entero de 32 bits, pero sólo se busca el caracter ASCII contenido en los 8 bits inferiores.
La ristra s está terminada por un caracter \textbackslash 0, también llamado NULL. Entonces, si el argumento c es NULL, la función debe retornar un puntero al caracter NULL terminador de la ristra.
Escriba, entonces, un función en lenguaje ensamblador Intel x86 de 32 bits que implemente correctamente esta función.
Solución
La función strchr() de C puede ser implementada mediante un ciclo do-whilesi se considera el terminador \textbackslash 0 como parte de la ristra. Como se muestra en el código, se revisará la ristra hasta que el caracter examinado sea el terminador, y después de incrementará el puntero s.
char *strchr(const char *s, int c) { do { if (*s == (char) c) { return s; } } while (*s++ != 0); return 0; }
El siguiente código ensamblador implementa una posible solución. .type strchr, @function
strchr: pushl %ebp # Crea el marco de activacion movl %esp, %ebp
movl 8(%ebp), %eax # %eax contiene el puntero s movl 12(%ebp), %edx # %edx contiene el entero c decl %eax # Decrementa el puntero s lazo: incl %eax # Incrementa s
cmpb %dl, (%eax) # Compara *s con el caracter c
je fin # Si son iguales, %eax contiene la posicion cmpb $0, (%eax) # Verificar si llegamos al final
jne lazo # Si aun quedan caracteres, ir a lazo esNULL: xorl %eax, %eax # Si no se encontro c, retorna 0 fin: leave # Recuperar el marco de activacion
ret # Retorno de la funcion
2.24 La función char *strrchr(const char *s, int c) de la biblioteca estándar C recibe como argumentos un puntero a una ristra s y un entero c, y retorna un puntero a la última ocurrencia del caracter con-tenido en c en la ristra. Si el caracter c no está presente en la ristra, entonces la función debe retornar NULL. Nótese que el argumento c es un entero de 32 bits, pero sólo se busca el caracter ASCII contenido en los 8 bits inferiores.
La ristra s está terminada por un caracter \textbackslash 0, también llamado NULL. Entonces, si el argumento c es NULL, la función debe retornar un puntero al caracter NULL terminador de la ristra.
Escriba, entonces, un función en lenguaje ensamblador Intel x86 de 32 bits que implemente correctamente esta función
Solución
La función strrchr() de C puede realizarse como un ciclo do-while. Como se muestra en el siguiente código, se revisa la ristra hasta que el caracter examinado sea el terminador, almacenando en res la posición de los caracteres encontrados en s que son iguales a c.
char* strrchr(const char *s, int c) { char *res = NULL;
do { if (*s == (char) c) { res = s; } } while (*s++ != 0); return res; }
El siguiente código ensamblador implementa una posible solución. .type strrchr, @function
strrchr:pushl %ebp # Crea el marco de activacion movl %esp, %ebp
movl 8(%ebp), %ecx # %eax contiene el puntero s movl 12(%ebp), %edx # %edx contiene el entero c xorl %eax, %eax # Hace NULL el puntero res lazo: cmpb %dl, (%ecx) # Compara *s con el caracter s
jne incr # Si son diferentes, continua movl %ecx, %eax # Si son iguales, copia el puntero incr: incl %ecx # Incrementa el puntero s
cmpb $0, (%ecx) # Verificar si llegamos al final jne lazo # Si aun quedan caracteres, ir a lazo cmpb $0, %dl # Verifica si se esta buscando el NULL jne fin # Si no es asi, ir a fin
movl %ecx, %eax # Si lo es, retorna la direccion del NULL fin: leave # Recuperar el marco de activacion
ret # Retorno de la funcion
2.25 Se le ha pedido reconstruir un código escrito en C basado en algunas declaraciones de estructuras y uniones en C, y en el código Intel x86 generado para un procesador de 32 bits Suponga además que el com-pilador utilizado no realiza alineamiento de código. A continuación, se
muestra el código de dichas declaraciones. Recuerde que todas las va-riables de una unión comparten los mismos bytes de memoria, y que el tamaño de una unión es el tamaño de su miembro mayor.
struct s1 { char a[4]; union u1 b; int c; }; struct s2 { struct s1 *d; char e; int f[4]; struct s2 *g; }; union u1 { struct s1 *h; struct s2 *i; char j; };
a) Indique el tamaño de cada una de estas estructuras, e indique los
desplazamientos de cada uno de sus elementos
b) Complete el código C para que sea equivalente al código
ensam-blador mostrado proc1: pushl %ebp
movl %esp, %ebp movl 8(%ebp), %eax movl 13(%eax), %eax leave
ret
int proc1(struct s2 *x) {
return x->______________________________________; }
c) Complete el código C para que sea equivalente al código
ensam-blador mostrado proc2: pushl %ebp
movl %esp, %ebp movl 8(%ebp), %eax movl 4(%eax), %eax movl 21(%eax), %eax
leave ret
int proc2(struct s1 *x) {
return x->______________________________________; }
d) Complete el código C para que sea equivalente al código
ensam-blador mostrado proc3: pushl %ebp
movl %esp, %ebp movl 8(%ebp), %eax movl (%eax), %eax movsbl 4(%eax), %eax leave
ret
char proc3(union u1 *x) {
return x->______________________________________; }
e) Complete el código C para que sea equivalente al código
ensam-blador mostrado proc4: pushl %ebp
movl %esp, %ebp movl 8(%ebp), %eax movl (%eax), %eax movl 21(%eax), %eax movl (%eax), %eax movsbl 1(%eax), %eax leave ret char proc4(union u1 *x) { return x->______________________________________; } Solución
Esta solución está referida a un procesador Intel de 32 bits, y no se realiza alineamiento de datos.
a) Las estructuras y uniones dadas tienen largo sizeof(struct s1) = 12,
b) La estructura s1 tiene un largo de 12 bytes, y los offsets de sus
elementos son: 0x00 para el vector a, 0x04 para la unión b, y 0x08 para el entero c. La estructura s2 tiene un largo de 25 bytes, y los offsets de sus elementos son: 0x00 para el puntero a estructura d, 0x04 para el caracter e, 0x05 para el vector f, y 0x15 para el puntero g. Finalmente, la unión u1 tiene un largo de 4 bytes, y los
offsets de sus elementos son todos 0x00.
c) Una posible solución se muestra en el siguiente código
int proc1(struct s2 *x) { return x->f[2]; }
d) Una posible solución se muestra en el siguiente código
int proc2(struct s1 *x) { return x->b.i->g; }
e) Una posible solución se muestra en el siguiente código
char proc3(union u1 *x) { return x->i->e; }
f ) Una posible solución se muestra en el siguiente código
char proc4(union u1 *x) { return x->i->g->d->a[1]; }
C
apítulo
3
Procesador Y86
3.1 El algoritmo recursivo de Dijkstra para calcular el máximo comúndi-visor puede escribirse como:
mcd(m, n) = m, if m = n mcd(m− n,n), if (m > n) mcd(m, n− m), en otro caso
Recuerde que el máximo común divisor entre dos números enteros po-sitivos n y m es el mayor entero d que divide exactamente tanto n como
m, es decir, m mod d = n mod d = 0.
Escriba una función en int mcd(int m, int n) que implemente es-te algoritmo, y luego una versión en lenguaje ensamblador Y86. El si-guiente código muestra un esqueleto que le puede servir como comien-zo. Ejecute su código utilizando el simulador del procesador secuencial para calcular mcd(18, 14), mostrando la salida del simulador secuen-cial estándar en modo texto.
# Ejecucion comienza en la direccion 0x00 .pos 0
init: irmovl Pila, %esp # define puntero a la pila irmovl Pila, %ebp # define puntero base jmp Main # ejecuta programa principal Main: irmovl $14, %eax # %eax vale 14
pushl %eax # n es 14
irmovl $18, %eax # %eax vale 18
pushl %eax # m es 18
call mcd # llama a mcd(18, 14) halt
# int mcd(int n, int m) # INSERTE SU CODIGO AQUI
.pos 0x100
Pila: # pila del programa comienza aqui
3.2 Sea la función recursiva SumaRec(int *dato, int cuenta), que su-ma cuenta enteros a partir de la posición dada dato, cuyo código C se muestra a continuación. Escriba esta función en lenguaje ensamblador Y86, y luego utilice esta función para calcular la suma de un vector de 8 elementos. Ejecute su código utilizando el simulador del procesador secuencial.
int SumaRec(int *dato, int cuenta) { if (cuenta == 1) {
return *dato; }
return *dato + SumaRec(dato++, cuenta--); }
El siguiente código muestra un esqueleto que le puede servir como co-mienzo.
# Ejecucion comienza en la direccion 0x00 .pos 0
init: irmovl Pila, %esp # define puntero a la pila irmovl Pila, %ebp # define puntero base jmp Main # ejecuta programa principal # vector de elementos
.align 4 vector: .long 0x56ad
.long 0x1786 .long 0x3018 .long 0x0F41 .long 0x000c .long 0x2918 .long 0x1004 .long 0x0F0F
pushl %eax # cuenta es 8
irmovl vector, %edx # lee direccion de vector pushl %edx # dato es dir. vector call SumaRec # llama a SumaRec(vector, 8) halt
# int SumaRec(int *dato, int cuenta) # INSERTE SU CODIGO AQUI
.pos 0x150
Pila: # pila del programa comienza aqui
3.3 En los programas de ejemplo del procesador X86, se presenta la ins-trucción leave, y se indica que es equivalente a las instrucciones Y86 rrmovl %ebp, %espy popl %ebp. Agregue esta instrucción al conjunto de instrucciones del procesador Y86, usando la siguiente codificación.
0 1 2 3 4 5
D 0
Use la tabla para popl como guía. Solución
La tabla de acciones para esta instrucción se muestra a continuación.
Etapa Acciones
Fetch iCode : iFun← M1[PC]
valP← PC + 1
Decode valA← R[%ebp]
valB← R[%ebp]
Execute valE← valB + 4 Memory valM← M4[valA]
Write Back R[ %esp]← valE
R[ %ebp]← valM
PC Update PC← valP
3.4 En los programas de ejemplo del procesador Y86, hay ocasiones en que es necesario sumar un valor constante a un registro. Esto requiere usar
una instrucción irmovl para almacenar la constante en un registro, y luego una instrucción addl para sumar su valor al registro destino. Agregue entonces una instrucción iaddl con el siguiente formato:
0 1 2 3 4 5
C 0 8 rB V
Esta instrucción suma la constante V al registro rB. Describa las compu-taciones necesarias para implementar la instrucción. Use la tabla para irmovly OP como guía.
Solución
La tabla de acciones para esta instrucción se muestra a continuación.
Etapa Acciones
Fetch iCode : iFun← M1[PC]
rA : rB← M1[PC + 1]
valC← M4[PC + 2]
valP← PC + 6
Decode valB← R[rB] Execute valE← valB + valC
Memory
Write Back R[rB]← valE
PC Update PC← valP
3.5 La función recursiva de Takeuchi fue inventada por Ikeo Takeuchi en 1978 para medir el desempeño de compiladores LISP, mientras traba-jaba en los laboratorios de la Nippon Telephone and Telegraph Co. Esta función está definida como sigue:
T (x,y,z) = if x ≤ y then y else T (T (x − 1,y,z),T (y − 1,z,x),T (z − 1,x,y)) Una posible implementación en lenguaje C es:
int takeuchi(int x, int y, int z) {
if (x <= y) return y; else
return takeuchi(takeuchi(x - 1, y, z), takeuchi(y - 1, z, x), takeuchi(z - 1, x, y));
}
La invocación de la función takeuchi() con argumentos (3,2,1) resulta en 9 invocaciones sucesivas a la función, con diferentes argumentos, y un resultado final de 3.
Escriba una función en lenguaje ensamblador Y86 que implemen-te esta función. Recuerde que la convención de paso de parámetros de C dice que éstos se almacenan en la pila de derecha a izquierda. No olvide cumplir con las convenciones Caller-Save y Callee-Save. El siguiente esqueleto de código le puede servir de comienzo. # Ejecucion en la direccion 0x00
.pos 0
init: irmovl Pila, %esp # define puntero a la pila irmovl Pila, %ebp # define puntero base jmp Main # ejecuta programa principal Main: irmovl $1, %eax # %eax vale 1
pushl %eax # 3er argumento z es 1 irmovl $2, %eax # %eax vale 2
pushl %eax # 2do argumento y es 2 irmovl $2, %eax # %eax vale 3
pushl %eax # 1er argumento x es 3 call takeuchi # llama a takeuchi(3, 2, 1) halt
# int takeuchi (int x, int y, int z) # INSERTE SU CODIGO AQUI
.pos 0x200
Pila: # pila del programa comienza aqui
Simule la ejecución de su función para los argumentos (3,2,1) usando el simulador secuencial ssim-std.exe. Calcule el número de ciclos que demora la ejecución y la cantidad de memoria utili-zada, tanto por el programa como por la pila.
Simule ahora la ejecución de su función usando el simulador seg-mentado psim.exe. Indique el CPI obtenido, identificando las ins-trucciones que causan pérdida de ciclos en el pipeline.
Solución
El siguiente código muestra una posible implementación de la función de Takeuchi en lenguaje ensamblador Y86.
# Ejecucion comienza en la direccion 0x00 .pos 0
init: irmovl Pila, %esp # define puntero a la pila irmovl Pila, %ebp # define puntero base jmp Main # ejecuta programa principal Main: irmovl $1, %eax # %eax vale 1
pushl %eax # 3er argumento z es 1 irmovl $2, %eax # % eax vale 2
pushl %eax # 2do argumento y es 2 irmovl $3, %eax # %eax vale 3
pushl %eax # 3er argumento z es 3
call takeuchi # llama a takeuchi con argumentos (3, 2, 1) halt
# int takeuchi(int x, int y, int z) takeuchi:
pushl %ebp # almacena puntero base rrmovl %esp, %ebp # crea marco de activación mrmovl 8(%ebp), %ecx # lee x
mrmovl 12(%ebp), %eax # lee y subl %ecx, %eax # resta y - x jl Lazo
mrmovl 12(%ebp), %eax # lee y
rrmovl %ebp, %esp # recupera puntero a la pila popl %ebp # recupera puntero base ret
Lazo:
mrmovl 16(%ebp), %edx # lee z
irmovl $-1, %eax # almacena -1 en %eax addl %eax, %edx # calcula z - 1 mrmovl 12(%ebp), %eax # lee y
pushl %eax # 3er argumento es y pushl %ecx # 2do argumento es x pushl %edx # 1er argumento es z - 1 call takeuchi
irmovl $12, %edx # almacena 12 en %edx
addl %edx, %esp # ajusta el puntero a la pila en 12 pushl %eax # almacena resultado en la pila
mrmovl 8(%ebp), %ecx # lee x mrmovl 12(%ebp), %eax # lee y
irmovl $-1, %edx # almacena - 1 en %edx addl %edx, %eax # calcula y - 1 mrmovl 16(%ebp), %edx # lee z
pushl %ecx # 3er argumento es x pushl %edx # 2do argumento es z pushl %eax # 1er argumento es y - 1 call takeuchi
irmovl $12, %edx # almacena 12 en %edx
addl %edx, %esp # ajusta el puntero a la pila pushl %eax # almacena resultado en la pila mrmovl 8(%ebp), %ecx # lee x
irmovl $-1, %edx # almacena -1 en %edx addl %edx, %ecx # calcula x - 1 mrmovl 16(%ebp), %edx # lee z
mrmovl 12(%ebp), %eax # lee y
pushl %edx # 3er argumento es z pushl %eax # 2do argumento es y pushl %ecx # 1er argumento es x - 1 call takeuchi
irmovl $12, %edx # almacena 12 en %edx
addl %edx, %esp # ajusta el puntero a la pila pushl %eax # almacena resultado en la pila call takeuchi # llama a takeuchi de nuevo irmovl $12, %edx # almacena 12 en %edx
addl %edx, %esp # ajusta el puntero a la pila pushl %eax # almacena resultado en la pila rrmovl %ebp, %esp # recupera puntero a la pila popl %ebp # recupera puntero base ret
.pos 0x0200
Pila: # Pila del programa comienza aqui
Algunos comentarios sobre esta solución:
El programa anterior cumple con las convenciones Caller-Save y
Callee-Save, y con la convención de paso de parámetros de C a
tra-vés de la pila, de derecha a izquierda.
Se escribió el programa intentando minimizar el uso de memoria en la pila, para así facilitar la ejecución recursiva de la función.
El programa ocupa 236 bytes de memoria, y su ejecución ocupa 68 bytes en la pila. La función takeuchi() misma ocupa 189 bytes de memoria.
La simulación del programa anterior usando el simulador secuen-cial ssim-std.exe calcula la función takeuchi(3, 2, 1) en 177 ciclos de reloj.
La simulación del programa anterior usando el simulador seg-mentado psim.exe calcula la función takeuchi(3, 2, 1) en 229 ciclos de reloj, dando un CPI de 1.29
La función takeuchi() es invocada 9 veces. Cada invocación in-volucra una instrucción de retorno ret. Las penalidad asociada a dichas instrucciones es de 3 ciclos. Entonces, en total estas ciones generan 27 ciclos de penalidad. Asimismo, hay 9 instruc-ciones de salto condicional jl. La penalidad en caso de predicción errónea de salto es de 2 ciclos. Estudiando el comportamiento del algoritmo, puede verse que este salto es predicho erróneamente 7 veces, por lo que hay 14 ciclos de penalidad asociados a ese salto. Los ciclos de penalidad restantes se deben a peligros de lectura y uso. Por ejemplo, es necesario leer el argumento y de la función desde memoria usando mrmovl 12(%ebp), %eax, y escribirlo nue-vamente a la pila usando pushl %eax antes de invocar por primera vez a la función takeuchi() después del rótulo Lazo.
3.6 El procesador Intel 80386 posee una instrucción bsr S, D conocida como Bit Scan Reverse, que determina el índice del primer bit en 1 del registro S, revisando dicho registro de izquierda a derecha, y retorna dicho índice en el registro D. Esta instrucción no existe en el procesa-dor Y86, pero puede ser implementada como una función en lenguaje ensamblador.
a) Escriba, una función bsr en lenguaje ensamblador Y86 que opere
sobre el registro %eax y retorne en el registro %edx el índice del primer bit en 1 del registro %eax. Es decir, si %eax = 0x432FA708, al terminar su función el registro %edx debe contener el número 30, ya que ése es el mayor bit de %eax que tiene valor 1.
b) Cuál es el tamaño de su función en bytes?
c) Suponga que su función se ejecuta en el procesador secuencial
es-tudiado en clases. Cuántos ciclos de reloj demorará su ejecución para %eax = 0x12345678?
d) Suponga ahora que su función se ejecuta en el procesador con
pi-pelining y forwarding visto en clases. Recuerde que las penalida-des asociadas a este procesador son:
Peligro Penalidad
Lectura y uso 1
Predicción errada 2
Retorno de función 3
Cuántos ciclos de reloj demorará ahora la ejecución de su función en este procesador segmentado, para %eax = 0x12345678?
Solución
a) Una posible implementación de la operación bsr S, D se muestra
a continuación.
bsr: pushl %ebp # crea marco de activacion rrmovl %esp, %ebp
pushl %esi # almacena copia de registro %esi irmovl $1, %esi # inicializa %esi a 1
irmovl $31, %edx # inicializa %edx a 31 xorl %ecx, %ecx # inicializa %ecx a 0 Lazo: subl %ecx, %eax # resta %eax - 0
jl Fin # si resultado es negativo, ir a Fin addl %eax, %eax # si resultado es positivo, duplicar %eax subl %esi, %edx # decrementar %edx en 1
je Fin # si resultado es 0, ir a Fin jmp Lazo
Fin: popl %esi # recupera valor de registro %esi rrmovl %ebp, %esp # recupera marco de activacion popl %ebp
ret # retorno de funcion
b) Es sabido que las instrucciones irmovl ocupan 6 bytes de
memo-ria, las instrucciones ret ocupan 1 byte, que las instrucciones de salto ocupan 5 bytes y que el resto de las instrucciones ocupa 2 bytes de memoria. Puede calcularse, entonces, que el código mos-trado tiene un tamaño de 48 bytes.
c) La ejecución de la función mostrada en el procesador secuencial
demorará tantos ciclos como instrucciones se ejecuten. En el ca-so que el registro %eax toma el valor 0x12345678, se ejecutan 30 instrucciones y por lo tanto demora 30 ciclos.