quinta-feira, maio 14, 2009

Compilador Erra?

Muitas vezes encontramos alguém reclamando que o "compilador fez besteira". Na maioria dos casos, o problema não estava no compilador, mas sim no código fonte. Considero o caso que relato a seguir como uma rara exceção.

O meu filho resolver tentar dar uma mexida no código do Spoke-o-dometer que estou desenvolvendo. Para isto instalei o sistema de desenvolvimento no micro dele e copiei a versão mais recente do código (que está aqui). Como primeiro teste, ele compilou o programa do jeito que estava e gravou no microcontrolador. E os LEDs piscaram de forma incorreta.

Descobri então que a versão que instalei no micro dele é mais recente que a que está instalada no meu. Concluí então que eu tinha feito alguma besteira no código fonte, que por sorte funcionava no compilador antigo. Após examinar o código sem encontrar nada estranho, resolvi gerar a listagem do código fonte entremeado com o código assembly gerado nas duas versões, para ver se tinha alguma pista.

Acabei chegando à linha abaixo:

P1OUT = (P1OUT & 0xC1) | (valor & 0x3E);

Este é um código tipo de quem precisa "brincar" com os bits em um microcontrolador. P1OUT é um registrador que controla algumas das saídas digitais; um bit em 1 corresponde a uma saída em nível alto e um bit em 0 corresponde a uma saída em nível baixo. No meu hardware, os bits 0 a 5 controlam 5 LEDs, o bit 0 é um LED verde que uso para indicar a detecção do ima e os demais são usados para mostrar os caracteres. 'valor' contém a nova programação dos LEDs (a coluna atual do caracter a ser apresentado), nos bits 7 a 1. Para colocar esta nova programação é preciso trocar o valor dos bits 5 a 1 de P1OUT, sem alterar os demais. É isto que a expressão booleana acima faz:

Na versão antiga do compilador (MSP430 C/C++ Compiler V3.42A/W32) o código gerado é bastante simples:
  MOV.B   R15, R14    ; R14 recebe o valor
AND.B #0x3e, R14 ; R14 contem valor & 0x3E
MOV.B &0x21, R13 ; 0x21 é o endereço de P1OUT
AND.B #0xc1, R13 ; R13 contem P1OUT & 0xC1
BIS.B R14, R13 ; R13 recebe R13 | R14
MOV.B R13, &0x21 ; atualiza P1OUT
Já a versão nova (IAR MSP430 C/C++ Compiler V4.11B/W32) gera esta pequena monstruosidade:
   MOV.B   R15, R14         ; R14 recebe o valor
CLRC
RRC.B R14 ; roda R14 para a direita
BIT.B #0x1, R14
JC ??Timer_A_TO_4 ; Desvia se (R14 & 0x01) != 0
BIC.B #0x2, &0x21 ; Desliga bit 1 de P1OUT
JMP ??Timer_A_TO_5
Timer_A_TO_4:
BIS.B #0x2, &0x21 ; Liga bita 1 de P1OUT
??Timer_A_TO_5:
ou seja, corresponde a algo como
if (valor & 0x02)
P1OUT |= 2;
else
P1OUT &= 0xFD;
Se alguém conseguir explicar porque este código foi gerado, eu agradeço. Por enquanto eu considero um bug do compilador.

Para contornar este problema a solução foi quebrar a expressão em duas linhas:
P1OUT = (P1OUT & 0xC1);
P1OUT = P1OUT | (valor & 0x3E);
o que gera o bem razoável
    AND.B   #0xc1, &0x21
MOV.B R15, R14
AND.B #0x3e, R14
BIS.B R14, &0x21
Reparar que a quebra em duas linhas causou duas escritas em P1OUT, o que não é problema neste caso. Se fosse problema, bastava montar o novo valor em uma variável auxiliar.

4 comentários:

Wanderley Caloni disse...

Olá, DQ.

Aparentemente, ele tentou criar um código mais simples e burro, mas parou na primeira "interação". Veja se existe alguma continuação abaixo. Se não existir, esse é o bug:

Vendo em binário, conclui-se que P1OUT zera cinco bits, começando do 1, e seta os mesmos cinco bits, de acordo com o valor de "valor", que só leva em conta os mesmos cinco bits:

P1OUT = (P1OUT & 11000001 ) | ( valor & 00111110 )

P1OUT = ( valor & 00000010 ) ?
P1OUT | 00000010
: P1OUT & 11111101

Isso me parece o início de um código que funcionaria, se ele tivesse feito a mesma instrução para cada um dos cinco bits:

P1OUT = ( valor & 00000010 ) ?
P1OUT | 00000010
: P1OUT & 11111101

P1OUT = ( valor & 00000100 ) ?
P1OUT | 00000100
: P1OUT & 11111011

P1OUT = ( valor & 00001000 ) ?
P1OUT | 00001000
: P1OUT & 11110111

P1OUT = ( valor & 00010000 ) ?
P1OUT | 00010000
: P1OUT & 11101111

P1OUT = ( valor & 00100000 ) ?
P1OUT | 00100000
: P1OUT & 11011111

Deve existir algum "motivo interno" para o compilador fazer esse tipo de código (flags de desempenho x tamanho do código, por exemplo).

[]s

Daniel Quadros disse...

Caloni,

Não tinha nenhuma continuação, era só isto mesmo. Está na lista fazer alguns testes mudando as opções de otimização (estava com o default) e instalando uma versão ainda mais recente. O que é estranho é que é uma construção bastante comum. Se o problema se reproduzir com a versão mais recente vou reportar ao fabricante e ver se dão algum retorno (lembrando que esta é uma versão gratuita e sem suporte).

Adriano Caye disse...

Eu não tenho experiência com o compilador da IAR, uso o ambiente da CrossStudio, que funciona muito bem. A variável "valor" foi declarada como sendo sem sinal (unsigned)? Isso pode fazer a diferença para o compilador.

Daniel Quadros disse...

Adriano,

Esta foi a minha primeira desconfiança. Entretanto, 'valor' está declarado como 'byte', que é typedef para 'unsigned char'. Nos includes do compilador 'P1OPUT' também é definido como 'unsigned char'. Além disso, o código não está testando o bit mais significativo (que seria o sinal).