Seminário final INE 5309 - Assembly Language

Propaganda
Seminário final INE 5309 - Assembly Language
February 26, 2007
Luiz Henrique Bincoletto Tomazella 0513238-0
Marcelo de Aguiar Patricio 0513231-2
1
1
Introdução
Sparc (Scalable Processo Architecture) é um arquiterura RISC desenvolvida
e licenciada por SPARC International Inc. um consórcio de construtores de
computadores que controlam o design. O Escalable do nome significa que o
design permite a compatibilidade com programas futuros. Ele foi desenvolvido
ao mesmo tempo que a arquitetura MIPS. mas em Berkeley em vez de Stanford.
Como MIPS, a arquitetura é aberta (não proprietária) e o nome SPARC é lincenciado por SPARC International para os fabricantes que quiserem implementar
a arquitetura. Alguns dos fabricantes SPARC incluem Sun Microsystem, Texas
Instruments, Toshiba, Fujitsy, Cypress, e Tatung, entre outros. Essas companias usam o chip para uma faixa de aplicação muito grande, desde laptops até
super computadores.
SPARC teve sua primeira implementação pela SUN em 1987, usando a versão
7 da arquitetura SPARC, desde então existiram 2 mudanças principais: SPARC
v8 em 1990, e a ultima impletamentação SPARC v9 em 1994, que introduziu
suporte a endereçamento de 64-bits, e é compativel com aplicações de 32-bits.
figura 1: Diagrama de Blocos
2
2
Instruction Set
As instruções da linguagem assembly do SPARC podem ser dos seguintes tipos:
• Aritimética: Adição ou subtração, com instruções adicionais para cada
conjunto de códigos e instruções especiais para rotinas de multiplicação
e para ler e escrever do registrador Y (um registrador especial usado na
multiplicação e divisão)
• Lógico: ands, ors, nors e xors, com instruçãoes adicionais para cara cojunto de códigos.
• Shift: logical left, logical right e aritimético right.
• Load: loads para bytes com e sem sinal, halfwords com e sem sinal, words
e doublewords.
• Store: Store para low-order byte ou low-order half do registrador, registrador (word), 2 registradores (doubleword) ou swap (load e store ao
mesmo tempo).
• Integer branch: SPARC tem um um registrador de condição que armazena
o resultados de comparções e outras operações (hi, lo, less than, etc). Instruções de desvio leêm o código de condição respectivo de cada comando.
Os branchs equal, not equal, less than, less or equal, greater than or equal,
greater than e são aplicados sobre unsigned integer. Também há branchs
para overflow de signed integer.
• Control: Esses são comandos que chamam sub-rotinas ou ajudam na
chamada. Call usa um jump incondicional para qualquer endereço na
memória. Jumps faz a mesma coisa mas com registrador e cálculo de
deslocamento para determinar o endereço alvo. Uma instrução de retorno
retorna uma routina trap e restaura o estado do usuário antes da chamda
do trap. Uma instrução “sethi” é incluida para criar uma constante imediata para outras instruções. Comandos Save e Restore salvam e restauram
a janela de registradores sobre chamadas de funções.
• Floating-Point: SPARC inclui separados conjuntos de comandos sobre
ponto flutuante para load, store, adds, multiplications, divisions, compares
e branches.
• Pseudo-Instruction: SPARC inclui também pseudos instruções para melhorar a legibilidade onde a operação que está sendo executada não pôde
parecerem óbvias.
3
3
Instruction types
Como muitas máquinas RISC, SPARC e MIPS tem muitas instruções dividas
em poucos tipos de instruções. Tendo poucos tipos de instruções, a arquitetura
das máquinas é mais fácil de planejar e otimizar.
Ambos SPARC e MIPS usam instruções de 32 bits e tem 3 tipos de instruções. No MIPS são chamadas de R-format, I-format e J-format e no SPARC
são chamadas de tipo 1, tipo 2 e tipo 3.
As duas máquinas tem tipos para instruções aritiméticas (R-format e tipo
3), para instruções branchs (I-format e tipo 2) e para instruções de jump (Jformat e tipo 1). Diferentemente do MIPS , o SPARC as instruções de load e
store são do tipo 3 e também adiciona ao tipo 2 instrução chamada sethi que é
silimiar a instrução lui do MIPS.
3.1
Tipo 1 - Instruções de jump
.
+————————————————————-+
| 01 | 30 bit
|
+————————————————————-+
Como a instrução Jump do MIPS (J-format), há somente uma única instrução no SPARC que é do tipo 1 chamda de CALL. Quando essa instrução
é encontrada, o controle é transferido imediatamente para a nova localização
dada pelos 30 bits da constante da instrução. Para determinar a localização a
constante é deslocada 2 bits a esquerda para gerar um enderço de 32-bit e o PC
é setado com esse valor mais o PC corrente. A mudança de endereço é relativa
ao PC para permitir o programa ser movido na memória sem aftor os endereços
especificados pelas instruções de chamada.
3.2
3.2.1
Tipo 2 - Branch e Sethi instructions
Primeiro formato:
.
+—————————————————————–+
| 00 |a(1)| cond(4) | op2(3)| 22 bit constant
|
+—————————————————————–+
Diferentemente do MIPS, o SPARC fornece muitos tipos de instruções de
branch. O tipo do branch é determinado pelos bits cond. Note que é permitidos
desvios até 221 , para desvios maiores um tipo especial de é necessário.
3.2.2
Segundo formato:
.
+————————————————————-+
| 00 |rd (5) |100| 22 bit constant
|
4
+————————————————————-+
Esse formato é utilizado para instrução sethi. Essa instrução é usada para
carregar (load) uma instrução de 32-bit em um registrador. Para fazer o load,
essa instrução carregara os 22 bits mais significativos e então uma instrução “or”
é chamada para carregar os outros 10 bits da palavra.
3.3
Tipo 3 - Algebric Instructions
Essas são as instruções mais comuns. São elas instruções algébricas, load/store
(exeto sethi).
Elas tem uma registrador de destino chamado rd, um especificador de instrução chamado op3 e registrador fonte, chamado rs1, o outro registrador fonte
é chamado de rs2, ou pode ser um imediato de 13 bits.
Note que como esse formato inclui load/store, Há um problema potencial
pois o endereçamento de memória ficará limitado a um imediato de 13 bits ou
213 . Para esses casos, o endereço de instruções é relativo ao frame pointer. Comparado ao MIPS, carregar da memória será mais lento, uma vez que no MIPS
instruções do I-format podem naturalmente se dirigir a uma faixa maior da
memória e consequentemente não necessitará de uma instrução adicional para
carregar um endereço no registrador antes da instrução load (como o SPARC
necessita fazer).
Two Source Register Instruction:
+———————————————————————+
| 10 | rd (5) | op3 (6) | rs1 (5) |0| unused (8) | rs2 (5) |
+———————————————————————+
One Source Register and Constant Instruction:
+———————————————————————+
| 11 | rd (5) | op3 (6) | rs1 (5) |1| signed 13 bit const |
+———————————————————————+
Como podemos ver , o opcode 10 é relacionado a instruções algébricas com
2 operandos e um registrador de destino. O opcod 11 é relacionado a operações
com imediatos e load/store.
5
4
Register layout
Tipicamente microprocessadores RISC são projetados para ter um pequeno
número de instruções que pode ser decodificados e executado rapidamente. Para
fazer as instruções executarem rapidamente, o número de acessos a memória
tem que ser o menor possível. Para ajudar a limitar o número de acesso a
memória, máquinas RISC tem um grande número de registradores para o programador/compilador usar. No MIPS, há 32 registradores que o prorama pode
usar também no SPARC há 32 registradores que o programa pode usar. Desde
a otimização do SPARC para chamada de sub-rotinas, o número atual de registradores no microprocessador é muito maior que 32 (frequentemente existem
124 registradores), mas apenas 32 registradores são visíveis para o programa em
um dado momento (mais detalhes em 5. Subroutine Process).
Registradores podem ser organizados pela sua função, pois eles são criados
com algum propósito, se um registrador não é utilizado para uma finalidade que
ele é projetado, ele está disponível para ser usado por qualquer instrução.
Os registradores mais gerais são os temporários. Esses registradores existem
para armazenar valores carregados da memória ou calculados pela ALU antes de
ser salvos na memória. Eles não são preservados entre chamadas de sub-rotinas.
O MIPS tem 10 registradores temporários que são $t0-$t9. O SPARC possui
somente 8 registradores temporários que são %l0 - %l7, mas caso seja preciso
outros registradores podem ser usados como temporários.
Para acesso a memória e para chamadas de sub-rotinas, tanto o MIPS quanto
o SPARC fornecem alguns poucos registradores especializados. Tanto o MIPS
quanto o SPARC tem um stack point $sp e %sp, frame pointer $fp e %fp. MIPS
reserva o registrador $at para o compilador, os registradores $k0 e $k1 para o
sistema operacional e um registrador $gp para o global memory pointer. O
MIPS tem 4 registradores reservados que o programa não pode contar para uso
geral e o SPARC não tem essa limitação.
Embora o SPARC tenha mais instruções de branch que o MIPS, ainda assim
muitas comparações precisam ser feitas com a constante zero. Consquentemente,
ambos reservam um registrador para essa constante. Esse registrador é $zero
no MIPS e %g0 no SPARC.
Para fornecer chamadas de sub-rotinas alguns registradores precisam ser
designados para a passagem de parâmetros e outros para o retorno das funções.
O MIPS tem 4 registradores com a função de serem parâmetros de funções
($a0, $a3) e 2 registradores para valores de retorno das funções ($v0 - $v1) e um
registrador para armazenar o endereço de memória que a função deve retornar o
PC quando a sub-rotina acabar. O SPARC tem 6 registradores para parâmetros
de funções (%i0 - %i5), e 6 para valores de retorno das funções (%o0 - %o5) e
para o endereço de retorno a função o registrador %i7.
O SPARC tem 7 registradores que o MIPS não possui chamados de %g1 %g7. Esses registradores são “globais” e seus valores não são alterados entre
chamadas de sub-rotinas. Atualmente, todas sub-rotinas usam os mesmos registradores globais, consequentemente o SPARC não possui o registrador $gp,
que o MIPS possiu. Caso seja necessário mais registrados globais a memória é
6
usada.
Nas duas arquiteturas caso seja necessário mais argumentos do que os registradores disponíveis a pilha é usada.
O SPARC tem mais registradores disponíveis para o programa em um certo
momento e tem mais otimizações para chamadas de sub-rotinas. SPARC tem
uma clara vantagem sobre MIPS no projeto dos registradores.
figura 2: Layout dos registradores
Comparação entre os registradores do MIPS e SPARC:
7
Number
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
MIPS
$zero
$at
$v0
$v1
$a0
$a1
$a2
$a3
$t0
$t1
$t2
$t3
$t4
$t5
$t6
$t7
$s0
$s1
$s2
$s3
$s4
$s5
$s6
$s7
$t8
$t9
$k0
$k1
$gp
$sp
$fp
$ra
Always returns zero
Reserved for Assembler
Func Return 1
Func Return 2
Func Arg 1
Func Arg 2
Func Arg 3
Func Arg 4
Temp 1
Temp 2
Temp 3
Temp 4
Temp 5
Temp 6
Temp 7
Temp 8
Saved 1
Saved 2
Saved 3
Saved 4
Saved 5
Saved 6
Saved 7
Saved 8
Temp 9
Temp 10
OS Kernel 1
OS Kernel 2
Ptr To Global Mem
Stack Pointer
Frame Pointer
Func Return Address
8
SPARC
%g0
%g1
%g2
%g3
%g4
%g5
%g6
%g7
%o0
%o1
%o2
%o3
%o4
%o5
%sp
%o7
%l0
%l1
%l2
%l3
%l4
%l5
%l6
%l7
%i0
%i1
%i2
%i3
%i4
%i5
%fp
%i7
Always returns zero
Global 1
Global 2
Global 3
Global 4
Global 5
Global 6
Global 7
For Sub Arg 1
For Sub Arg 2
For Sub Arg 3
For Sub Arg 4
For Sub Arg 5
For Sub Arg 6
Stack Pointer
Subroutine Return Address
Local 1
Local 2
Local 3
Local 4
Local 5
Local 6
Local 7
Local 8
Func Arg 1
Func Arg 2
Func Arg 3
Func Arg 4
Func Arg 5
Local 6
Frame Pointer
Func Return Address
5
Subroutine Register Allocation
Se uma sub-rotina é chamada o programa precisa salvar o estado da máquina
naquele momento. Em particular, um certo número de registradores precisam
ser salvos. Isso inclui stack pointer, frame pointer, program counter e um número
de registradores de uso geral.
No MIPS, o processador depende exclusivamente do programa para salvar os
registrados que serão usados pela sub-rotina. Consequentemente, quando uma
sub-rotina é chamada, o programa tem a obrigação de ajustar o stack pointer
para criar espaço para os registradores que precisam ser salvos e então colocar
os registradores na pilha.
Um programa em MIPS irá colocar o $gp, o $ra, e os registrados $s0 $s7 na pilha quando entrar em uma sub-rotina. Então quando a sub-rotina é
completada, os registradores que foram salvos devem ser restaurados e a subrotina deve voltar para o endereço de retorno, $ra.
Para garantir que os registradores salvos serão restaurados, a máquina MIPS
gasta alguns ciclos de clock para salvar e depois alguns ciclos de clock para
restaurar os registrados, isso sempre que uma sub-rotina é chamada, consequentemente gastando uma quantidade significante de ciclos de clock.
A SUN, criou um número grande de registradores para o seu processador,
mas uma sub-rotina em particular só pode ver 32 registradores. Esses registradores consistem em in (%i0 - %i7), out (%o0 - %07), local (%l0 - %l7) e
global (%g0 - %g7). Para criar o “register file”, o SPARC pode salvar o estado
da máquina sem pedir ao programa para salvar o estado de seus registradores
em pilha. Para fazer isso é criado uma janela sobre os registradores.
Quando uma sub-rotina é chamada, o processador renomeia os registrados
out (%o0 - %07), antes que a sub-rotina processe os registradores in (%i0 - %i7),
e cria novos registradores locais (%l0 - %l7) e out (%o0 - %07) para a sub-rotina.
Esses novos registradores out pode se transformar em registradores in para uma
sub-rotina chamada dentro dessa sub-rotina.
9
figura 3
Desse modo, o SPARC garante uma rápida chamada a uma sub-rotina se a
sub-rotina tiver menos que 8 argumentos e o “register file” não estiver cheio. Se
a sub-rotina tem muitos argumentos, o processador deve confiar ao programa
para salvar o estado da máquina na pilha como no MIPS. Se o processador não
consegue mais alocar registradores no “register file”, então a sub-rotina chamada
a mais tempo é salva na pilha para liberar mais 32 registradores para a nova
sub-rotina.
10
figura 4
11
6
Pipelining
Arquitetura SPARC suporta pipelining. Isso evita hazards de 2 maneiras: load
delay slots e branch delay slots. Isso faz com que não exista forward de cálculo
de endereços e nem de valores aritiméticos como no MIPS, aparentemente tendo
um ciclo a menos no pipeline. Uma CPU SPARC usa 4 passos por instrução, os
passos são Fetch, Execute, Memory e Write Back. Durante o Execute parte da
instrução é executada, a seguinte instrução será o Fecth, e quando a segunda
instrução precisar do cálculo da primeira, o calculo é efetuado.
6.1
Load delay slots
Sabesse que uma instrução load fornece dados para uma instrução posterior.
Supondo que há uma instrução load precedendo uma instrução de adição e que
o valor carregado será usado na soma. O valor necessário para soma não foi
carregado quando a adição for utilizá-lo. O load estará acessando a memória
quando a soma precisar do valor. Para resolver esse problema, o SPARC adiciona um “load delay slot” para atrasar a execução da instrução add.
CPU cycle:
F E M W
ld %r10+offset,%r11 [load to r11]
F E M W add %r11,%r12,%r13 [add r11+r12]
0 1 2
3
4
tabela 1 - Time in machine cycles
CPU cycle:
F E M W
D D D
F
E
0 1
2
3
tabela 2 - Time in
ld %r10+offset,%r11 [load to r11]
D D
[Delay]
M W add %r11,%r12,%r13 [add r11+r12]
4
machine cycles
Agora o valores necessários para fazer a soma estaram disponíveis no tempo
correto, não ocorrendo mais problemas. O MIPS faz algo parecido, porém ele
possui forwarding para resolver esse problema.
6.2
Branch delay slots
Instruções branch chamam um sub-programa ou outro programa. No SPARC,
branches leêm um código de condição setado anteriormente por uma comparação e então o branch é executado, no MIPS a comparação é feita no branch e
então calculado o endereço do desvio.
CPU cycle:
12
F
E M W
[branch]
F E M W [próxima instrução]
0 1 2
3
4
tabela 3 - Time in machine cycles
O endereço que a instrução branch está sendo executada, a máquina carregará a instrução seguinte a primeira instrução, como esse endereço ainda não
está disponível quando a segunda instrução está sendo carregada isso por causar
problemas.
A arquitetura de SPARC pede que o programador compartilhe de alguma
da responsabilidade para este problema. O programador pode inserir uma instrução que será executada depois do branch ou inserir um nop (no operation),
para efetivamente atrasar a chamada da sub-rotina ou programa enquanto o
branch calcula o endereço. Essa é uma responsabilidade incoveniente para o
programador ou compilador, pois tem que decidir entre uma instrução ou um
nop. Isso é diferente no MIPS, que insere delays para o programador.
O que a máquina pode fazer pelo programador é manter dois contadores
para o programa, o contador adicional é chamado de %npc ou “next program
counter”. A instrução começa a ser executada, F-E-M-W em um ciclo que é
apontado por %pc, e a outra instrução que está sendo buscada (F) é apontada
por %npc. O %pc é sempre carregado com o endereço do %npc. Assim a
instrução que vem depois do branch será sempre executada. Mas caso o branch
seja executado, o %npc é atualizado com o endereço do desvio.
13
7
Exercícios
Como foi pedido, foi feita a reimplementação dos dois exercícios. O multiplicador e o salvamento de contexto.
Porem os dois exercícios foram feitos no mesmo programa, que é listado
abaixo:.
7.1
Código
.file "mult.c"
.section ".rodata"
.align 8
.LLC0:
.asciz "****************\noverflow\n****************\n\n"
.section ".text"
.align 4
.global multiplicacao
.type multiplicacao, #function
.proc 04
multiplicacao:
!#PROLOGUE# 0
save
%sp, -112, %sp
!#PROLOGUE# 1
smul %i0, %i1, %i0
mov %i0, %l1
sra %l1, 31, %l1
rd %y, %l0
cmp %l0, %l1
be .LL2
sethi %hi(.LLC0), %o0
call printf, 0
or %o0, %lo(.LLC0), %o0
.LL2:
ret
restore
.size multiplicacao, .-multiplicacao
.section ".rodata"
.align 8
.LLC1:
.asciz "Insira um numero: "
.align 8
.LLC2:
.asciz "%d"
.align 8
14
.LLC3:
.asciz "Insira outro numero: "
.align 8
.LLC4:
.asciz "\n = Resultado: %d\n"
.section ".text"
.align 4
.global main
.type main, #function
.proc 04
main:
!#PROLOGUE# 0
save %sp, -120, %sp
!#PROLOGUE# 1
st %g0, [%fp-20]
st %g0, [%fp-24]
sethi %hi(.LLC1), %o0
call printf, 0
or %o0, %lo(.LLC1), %o0
sethi %hi(.LLC2), %l0
or %l0, %lo(.LLC2), %o0
call scanf, 0
add %fp, -20, %o1
sethi %hi(.LLC3), %o0
call printf, 0
or %o0, %lo(.LLC3), %o0
or %l0, %lo(.LLC2), %o0
call scanf, 0
add %fp, -24, %o1
ld [%fp-20], %o0
call multiplicacao, 0
ld [%fp-24], %o1
mov %o0, %o1
sethi %hi(.LLC4), %o0
call printf, 0
or %o0, %lo(.LLC4), %o0
ret
restore %g0, 0, %o0
.size main, .-main
.ident "GCC: (GNU) 3.4.2"
7.2
Considerações sobre o desenvolvimento do exercício:
Para auxiliar na criação da estrutura do programa foi criado um programa em
c e gerado um arquivo em assembly com o seguinte comando: gcc -S -O mult.c.
15
Uma vez criado o arquivo em assembly as alterações foram feitas nesse arquivo,
chamado mult.s. Uma vez o arquivo em assembly finalizado ele foi compilado
com o comando: gcc mult.s -o mult, assim gerando um arquivo binário para ser
executado. O if (z>0) ..... foi feito simplesmente para auxiliar na implementação
em assembly.
Código do programa em c:
int multiplicacao (int a, int b){
int z;
z=a*b;
if (z>0) {printf("overflow");}
return(z);
}
int main(){
int x = 0;
int y = 0;
printf("Insira um numero: ");
scanf("%d",&x);
printf("Insira outro numero: ");
scanf("%d",&y);
printf("Resultado: %d\n",multiplicacao(x,y));
return(0);
}
Foi utilizada a máquina VENUS da rede da inf, utilizando processador Sparcr
Ultra I V9 (informações fornecidas).
Para fazer a multiplicação fui utilizado a instrução smul, que faz a multiplicação de dois números de 32 bits. Existe uma instrução chamada mulx, que
faz a multiplicação entre 64 bits, mais essa instrução não foi aceita pelo processador o que nos faz concluir que essa instrução só está disponível em versões
mais novas do processador.
O SPARC possui um código de condição que indica quando houve overflow
(V) e existe uma instrução bvs (branch on overflow set) que faz o desvio se o
bit de condição (V) estiver setado. Isso facilitaria muito a implementação do
exercício mais infelizmente ele também não pode ser implementado, não por
um problema de não reconhecimento da instrução como no caso anterior, mas
na falta de uma instrução que setasse o bit de condição. Analisando o manual
encontramos as seguintes definições para as instruções de multiplicação de 32
bits.
16
Opcode
op3
Operation
UMUL
00 1010 Unsigned Integer Multiply
SMUL
00 1011 Signed Integer Multiply
UMULcc 01 1010 Unsigned Integer Multiply and modify cc’s
SMULcc 01 1011 Signed Integer Multiply and modify cc’s
tabela 4 - Instruções para multiplicação 32 bits.
Bit
icc.n
icc.z
icc.v
icc.c
xcc.n
xcc.z
xcc.v
xcc.c
tabela 5
UMULcc / SMULcc
Set to 1 if product{31} = 1; otherwise, set to 0
Set to 1 if product{31:0}= 0; otherwise, set to 0
Set to 0
Set to 0
Set to 1 if product{63} = 1; otherwise, set to 0
Set to 1 if product{63:0} = 0; otherwise, set to 0
Set to 0
Set to 0
- Alteração nos bits de condição
Como se pode ver na tabela 5, nenhuma das instruções seta o bit de condição
(V) assim optamos por utilizar a instrução smul, que trabalha com número com
sinal mais não seta nenhum bit de condição.
Assim tivemos que pesquisar outra maneira de detectar overflow na multiplicação. Consultando o manual encontramos a seguinte definição.
• 32-bit overflow after UMUL/UMULcc is indicated by Y <> 0.
• 32-bit overflow after SMUL/SMULcc is indicated by Y <> (R[rd] > >
31), where > > indicates 32-bit arithmetic rightshift.
17
Figura 5: Esquema da multiplicação
Então implementamos a segunda opção que diz respeito a instrução escolhida.
Para implementar o exercício de salvamento de contexto foi mais simples,
uma vez que noo arquivo mult.s gerado pelo gcc essa tarefa já estava feita, pois
o SPARC facilita essa implementação. A única coisa que precisa ser feita é
chamar a instrução save %sp, imediato, %sp no início da função e restore no
fim.
A instrução save tem um imediato que é o numero de bytes que serão salvos,
ou melhor o número de registrados a serem movidos no “register file”. Como o
arquivo foi complidado com a opção de otimização então o compilador otimizou
o salvamento de contexto, porém foram feitos testes e foi verificado que se não
for complidado com otimização todo ínicio de sub-rotina tem uma instrução
save %sp, -120, %sp, aonde 120 corresponde a 30 registradores 120 / 4 = 30.
Assim tem uma diferença entre os 32 registradores disponíveis e os 30 movidos,
mas não conseguimos chegar a uma conclusão sobre essa diferença.
18
Download