Do alto-nível ao assembly Compiladores Cristina C. Vieira 1
Viagem Como são implementadas as estruturas computacionais em assembly? Revisão dos conceitos relacionados com a programação em assembly para o MIPS R3000 (ver disciplina de Arquitectura de Computadores) 2
Do alto-nível ao assembly Alvo: MIPS R3000 int sum(int A[], int N) { int i, sum = 0; for(i=0; i<n; i++) { sum = sum + A[i]; return sum; # $a0 armazena o endereço de A[0] # $a1 armazena o valor de N Sum: addi $t0, $0, 0 # i = 0 addi $v0, $0, 0 # sum = 0 Loop: beq $t0, $a1, End # if(i == N) goto End; add $t1, $t0, $t0 # 2*i add $t1, $t1, $t1 # 2*(2*i) = 4*i add $t1, $t1, $a0 # 4*i + base(a) lw $t2, 0($t1) # load A[i] add $v0, $v0, $t2 # sum = sum + A[i] addi $t0, $t0, 1 # i++ j Loop # goto Loop; End: jr $ra # return 3
Do alto-nível ao assembly int a, b, c;... fun(...) {... Variáveis globais: Armazenadas na memória Para cada uso de uma variável global o compilador tem de gerar instruções load/store Memória a b c 4
Variáveis Globais.data 0x10000000 a:.space 4 b:.space 4 c:.space 4 Alocação de memória int a, b, c; void fun() { c = a + b; la $t1, a lw $t1, 0($t1) la $t2, b lw $t2, 0($t2) add $t3, $t2, $t1 la $t4, c sw $t3, 0($t4) a b c Memória 0x10000008 0x10000004 0x10000000 5
Do alto-nível ao assembly void fun() { int a, b, c;... c = a + b;... Conceito de chamada a procedimentos Cada procedimento tem estados Variáveis locais Endereço de retorno Estado é guardado na área de memória designada por pilha de chamadas (é utilizado um registo para apontar para a posição actual da pilha) Pilha de chamadas A pilha de chamadas encontra-se no topo da memória A pilha cresce para baixo c b a Memória $sp Registos 6
Variáveis Locais void fun() { int a, b, c;... c = a + b;... Exemplo: c b a Memória fun: addi $sp, $sp, -12 lw $t1, 0($sp) lw $t2, 4($sp) add $t3, $t2, $t1 sw $t3, 8($sp) addi $sp, $sp, 12 jr $ra $sp Reserva espaço na pilha Load a Load b a + b store c liberta espaço na pilha 7
Variáveis Locais Acesso aos registos internos do processador é muito mais rápido Mas os registos internos são em número limitado E por isso nem todas as variáveis locais podem ser armazenadas nesses registos No passado a atribuição de registos internos do processador a variáveis locais era feita pelo programador: A linguagem C tem uma palavra reservada para orientar o compilador: register (e.g., register int c;) Hoje os compiladores são muito mais eficientes Essa atribuição é lhe inteiramente delegada 8
Variáveis Locais Utilização de registos internos void fun() { int a, b, c;... c = a + b;... $t1 $t2 $t3 Ficheiro de Registos a b c fun: add $t3, $t2, $t1 jr $ra 9
Do alto-nível ao assembly typedef struct { int x, y, z; foo; foo *p; Implementar Registos Registos contêm vários campos Cada estrutura é armazenada em posições contíguas de memória z y x p Memória 10
Do alto-nível ao assembly typedef struct { int x, y, z; foo; fun() { foo *p; p->x = p->y + p->z; Exemplo com estrutura local: z y x p Memória fun: addi $sp, $sp, -16 lw $t1, 0($sp) addi $t1, $t1, 8 lw $t2, 0($t1) lw $t1, 0($sp) addi $t1, $t1, 12 lw $t3, 0($t1) add $t3, $t2, $t3 lw $t1, 0($sp) addi $t1, $t1, 4 sw $t3, 0($t1) addi $sp, $sp, 16 jr $ra Reserva espaço na pilha Endereço de p address p->y Load p->y Endereço de p address p->z Load p->z p->y + p->z Endereço de p address p->x store em p->x liberta espaço na pilha 11
Do alto-nível ao assembly typedef struct { int x, y, z; foo; fun() { foo *p; p->x = p->y + p->z; Exemplo com estrutura local (optimizado): z y x p Memória fun: addi $sp, $sp, -16 lw $t2, 8($sp) lw $t3, 12($sp) add $t3, $t2, $t3 sw $t3, 4($sp) addi $sp, $sp, 16 Reserva espaço na pilha Load p->y Load p->z p->y + p->z store em p->x liberta espaço na pilha 12
Do alto-nível ao assembly Compiladores Cristina C. Vieira 13
Alinhamento, empacotamento e enchimento Requisitos de alinhamento: Inteiros tipo int (4 bytes) a começar em endereços com os 2 LSBs == 00 Inteiros tipo short (2 bytes) a começar em endereços com o LSB == 0 Alinhamento requer: Enchimento (padding) entre campos para assegurar o alinhamento Empacotamento de campos para assegurar a utilização de memória 14
Alinhamento typedef struct { int w; char x; int y; char z; foo; z y x w Organização ingénua Memória y x, z w Organização Empacotada (poupa 4 bytes) Memória foo *p; 15 livre ocupado p 32 bits p 32 bits
Arrays Afectação de posições de memória para os elementos do array Elementos são armazenados contiguamente a[3] a[2] a[1] a[0] int a[4]; Memória short a[4]; Memória a[0] a[2] a[1] a[3] 32 bits 32 bits 16
Arrays Utilizando registos do processador para armazenar as variáveis i e j: int a[4]; proc() { int i, j; i = a[j];.data A:.space 16 Proc:.text la $t0, A addi $t2, $0, 4 mult $t1, $t2 mflo $t2 add $t3, $t2, $t0 lw $t4, 0($t3) Endereço de a[j] = endereço de a[0] + (4 j) = a + (4 j) 17
Expressões a = b * c + d e; a em $t4; b em $t0; c em $t1; d em $t2; e em $t3 mult $t0, $t1 mflo $t4 sub $t5, $t2, $t3 add $t4, $t4, $t5 mult $t0, $t1 mflo $t4 add $t4, $t4, $t2 sub $t4, $t4, $t3 18
Estruturas condicionais If(a == 1) b = 2; a em $t0; b em $t1 addi $t2, $0, 1 bne $t2, $t0, skip_if addi $t1, $0, 2 skip_if: If(a == 1) b = 2; else b = 1; a em $t2; b em $t1 addi $t2, $0, 1 bne $t2, $t0, else addi $t1, $0, 2 j skip_if else: addi $t1, $0, 1 skip_if: 19
Estruturas condicionais Branch-delay O processador executa sempre a instrução a seguir a uma instrução de salto (quer o salto seja realizado ou não) Quando não é possível deslocar uma instrução para depois da instrução de salto, tem de se introduzir uma instrução nop if(a == 1) b = 2; c = a+1; a em $t0; b em $t1 addi $t2, $0, 1 bne $t2, $t0, skip_if addi $t3, $t0, 1 addi $t1, $0, 2 skip_if: 20
Ciclos int sum(int A[], int N) { int i, sum = 0; for(i=0; i<n; i++) { sum = sum + A[i]; return sum; Transformar o fluxo de controlo (while, for, do while, etc.) em saltos 21 # $a0 armazena o endereço de A[0] # $a1 armazena o valor de N Sum: addi $t0, $0, 0 # i = 0 addi $v0, $0, 0 # sum = 0 Loop: beq $t0, $a1, End # if(i == N) goto End; add $t1, $t0, $t0 # 2*i add $t1, $t1, $t1 # 2*(2*i) = 4*i add $t1, $t1, $a0 # 4*i + base(a) lw $t2, 0($t1) # load A[i] add $v0, $v0, $t2 # sum = sum + A[i] addi $t0, $t0, 1 # i++ j Loop # goto Loop; End: jr $ra # return
Ciclos Otimizações Manter i e endereço de a[i] em registos Determinar endereço de a[0] antes do corpo do ciclo, e incrementar 4 (no caso de serem acessos a palavras de 32 bits) no corpo do ciclo Caso o ciclo execute pelo menos uma iteração (N > 0) passar salto do ciclo para o fim do corpo 22
Ciclos Código após as optimizações int sum(int A[], int N) { int i, sum = 0; for(i=0; i<n; i++) { sum = sum + A[i]; return sum; # $a0 armazena o endereço de A[0] # $a1 armazena o valor de N Sum: Addi $t0, $0, 0 # i = 0 Addi $v0, $0, 0 # sum = 0 Loop: Lw $t2, 0($a0) # load A[i] Add $v0, $v0, $t2 # sum = sum + A[i] addi $a0, $a0, 4 # add 4 to address Addi $t0, $t0, 1 # i++ bne $t0, $a1, Loop # if(i!= N) goto Loop; End: jr $ra # return 23
Procedimentos Protocolo entre os procedimentos que invocam e os procedimentos invocados Dependente do processador No MIPS: Procedimento espera argumentos nos registos $a0-$a3 Coloca valores a retornar nos registos $v0-$v1 Outras formas de passagem de parâmetros utilizam a pilha de chamadas (por exemplo, sempre que o número de argumentos ultrapassa o número de registos para utilizar como argumentos) 24
Sumário Quais as responsabilidades do compilador? Esconder do programador conceitos baixo-nível da máquina Produzir rapidamente código eficiente Afetar variáveis a registos locais ou posições de memória Cálculo de expressões com constantes Manter funcionalidade inicial Geração de instruções de forma a suportar as chamadas aos procedimentos utilizadas no programa Otimizações 25