Перейти к содержанию
    

Борьба с компилятором

Надоело вылавливать баги связанные с тем что компилятор просто выбрасывает важные части кода.
За gcc ничего подобного вроде бы не замечалось. Видимо, ошибки в коде.

Вот код полностью
А можно вместо дизасма показать непосредственно сгенерированный компилятором ассемблерный текст? А то в процессе ассемблирования и последующего дизассемблирования информация теряется, трудно что-либо понять в этом коде. Там даже меток нет! Ну и хотелось бы видеть Ваши комментарии - в каком месте по-вашему генерится неверный код - "ожидалось вот так, а получилось эдак", чтобы весь код от начала до конца не анализировать...

 

uint8_t buff[16];
volatile uint8_t rd, wr;

С переменной wr проблем быть не должно - она кроме как в условии цикла нигде больше не читается. Непонятно насчет rd - она действительно может модифицироваться где-либо кроме read()? Если нет, то почему она объявлена глобальной и волатильной? Если же да - то я не понимаю, как такой код может работать... У меня подобный код выглядел бы так:

uint8_t buff[16];
volatile uint8_t wr;

uint8_t read()
{
    static uint8_t rd;

    while (rd == wr);
    uint8_t val = buff[rd];
    rd = (rd + 1) & 15;
  
    return val;
}

И вот что получается после компиляции:

read:
        lds r25,rd.1072
.L2:
        lds r24,wr
        cp r25,r24
        breq .L2
        mov r30,r25
        ldi r31,lo8(0)
        subi r30,lo8(-(buff))
        sbci r31,hi8(-(buff))
        ld r24,Z
        subi r25,lo8(-(1))
        andi r25,lo8(15)
        sts rd.1072,r25
        ret

Как видите, rd читается и пишется ровно по одному разу, wr читается только в цикле.

Изменено пользователем alx2

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вот от компилятора, но тут есть ещё изменения, cli/sei убраны,

.global	thread_global_shedule
.type	thread_global_shedule, @function
thread_global_shedule:
/* prologue: naked */
/* frame size = 0 */
sts (th_self)+1,__zero_reg__
sts th_self,__zero_reg__
ldi r17,lo8(64)
.L73:
in r18,82-0x20
out 82-0x20,__zero_reg__
out 92-0x20,r17
lds r28,th_sleep
lds r29,(th_sleep)+1
sbiw r28,0
breq .L65
lds r24,tov
lds r25,(tov)+1
add r24,r18
adc r25,__zero_reg__
sts (tov)+1,__zero_reg__
sts tov,__zero_reg__
ldd r18,Y+35
ldd r19,Y+36
cp r24,r18
cpc r25,r19
brlo .+2
rjmp .L66
sub r18,r24
sbc r19,r25
std Y+36,r19
std Y+35,r18
.L65:
ldi r24,lo8(1)
ldi r25,hi8(1)
.L72:
movw r30,r24
lsl r30
rol r31
subi r30,lo8(-(th_run))
sbci r31,hi8(-(th_run))
ld r26,Z
ldd r27,Z+1
sbiw r26,0
brne .+2
rjmp .L69
movw r28,r26
ldd r24,Y+41
ldd r25,Y+42
std Z+1,r25
st Z,r24
/* вот это здесь должно быть но его нет */
sts (th_self)+1,r25
sts th_self,r24
/* конец */
/* #APP */
;  32 "thread.c" 1
lds	r30, th_self		
lds	r31, th_self+1		
ldd	r16, Z+32		
out	__SREG__, r16		
ldd	r16, Z+33		
ldd	r17, Z+34		
out	__SP_L__, r16		
out	__SP_H__, r17		
ldd	r16, Z+30		
ldd	r17, Z+31		
sts	0x045E, r16		
sts	0x045F, r17		
ld	r0, Z+			
ld	r1, Z+			
ld	r2, Z+			
ld	r3, Z+			
ld	r4, Z+			
ld	r5, Z+			
ld	r6, Z+			
ld	r7, Z+			
ld	r8, Z+			
ld	r9, Z+			
ld	r10, Z+			
ld	r11, Z+			
ld	r12, Z+			
ld	r13, Z+			
ld	r14, Z+			
ld	r15, Z+			
ld	r16, Z+			
ld	r17, Z+			
ld	r18, Z+			
ld	r19, Z+			
ld	r20, Z+			
ld	r21, Z+			
ld	r22, Z+			
ld	r23, Z+			
ld	r24, Z+			
ld	r25, Z+			
ld	r26, Z+			
ld	r27, Z+			
ld	r28, Z+			
ld	r29, Z+			
lds	r30, 0x045E		
lds	r31, 0x045F		
reti				

;  0 "" 2
/* #NOAPP */
.L70:
rjmp .L70
.L69:
sbiw r24,1
brne .L71
ldi r24,lo8(0)
ldi r25,hi8(0)
rjmp .L72
.L66:
sub r24,r18
sbc r25,r19
sts (tov)+1,r25
sts tov,r24
ldd r18,Y+41
ldd r19,Y+42
ldd r24,Y+38
mov r26,r24
ldi r27,lo8(0)
lsl r26
rol r27
subi r26,lo8(-(th_run))
sbci r27,hi8(-(th_run))
ld r30,X+
ld r31,X
sbiw r26,1
sbiw r30,0
breq .L67
ldd r24,Z+41
ldd r25,Z+42
std Y+42,r25
std Y+41,r24
ld r30,X+
ld r31,X
sbiw r26,1
ldd __tmp_reg__,Z+41
ldd r31,Z+42
mov r30,__tmp_reg__
std Z+40,r29
std Z+39,r28
ld r24,X+
ld r25,X
sbiw r26,1
std Y+40,r25
std Y+39,r24
ld r30,X+
ld r31,X
std Z+42,r29
std Z+41,r28
.L68:
sts (th_sleep)+1,r19
sts th_sleep,r18
rjmp .L65
.L71:
call idle_go
rjmp .L73
.L67:
adiw r26,1
st X,r29
st -X,r28
std Y+40,r29
std Y+39,r28
ld r30,X+
ld r31,X
std Z+42,r31
std Z+41,r30
rjmp .L68
.size	thread_global_shedule, .-thread_global_shedule

 

Сейчас проверил, если убрать первое присвоение th_self = NULL; то второе не выбрасывается.

 

По поводу моего примера с read, там предполагается так же и write, в которой наоборот rd будет volatile а wr нет.

 

Если сделать вот так,

 

   th_head = th_run + (i--);
+   th_self = *th_head;

            if (likely(*th_head)) {

 

то, тоже код генерируется нормально, для этого присвоения.

Изменено пользователем amaora

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

давайте исходить из того, что volatile нужен только для того, чтобы уведомить компилятор Си о том, что переменная может быть изменена вне кода Си, который анализируется оптимизатором и может быть существенно упрощён/сокращен. К примеру регистры переферии нужно объявлять как volatile, потому что их значения могут меняться без ведома компилятора. Также верно и обратное: компилятор не должен сокращать и иным образом упрощать выражения с volatile переменной, т.к. это может нарушить ход выполнения программы и привести к нежелательным результатам.

 

По коду:

uint8_t tqu;

tqu = CONFIG_SHEDULE_TQUANT;

далее tqu используется только с timer_setup(tqu);

Компилятор сделает просто timer_setup(CONFIG_SHEDULE_TQUANT); tqu вы даже не найдете в стеке и это нормально!

 

Первым делом смотреть надо на код, на вашу модель вообще(как что куда и зачем), а уже потом гнать на компилятор. :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вот от компилятора, но тут есть ещё изменения, cli/sei убраны,
Я гляжу, и вызов dbg_write() пропал... Но это фигня...

/* вот это здесь должно быть но его нет */
sts (th_self)+1,r25
    sts th_self,r24
/* конец */

Мы вообще-то о пропадании какого присваивания говорим? До сих пор я думал, что вот этого: *th_head = (*th_head)->next; Тогда при чем тут th_self?

Если же речь о вот этом присваивании: th_self = *th_head; (то есть строчкой ниже), то ИМХО компилятор выкинул его правильно. Дело в том, что еще строчкой ниже вызывается thread_restore_helper(), объявленная как noreturn. В теле thread_restore_helper() th_self не используется, управление вызывающей функции она не возвращает - видимо поэтому компилятор разумно посчитал, что присваивание th_self не имеет эффекта.

 

По поводу моего примера с read, там предполагается так же и write, в которой наоборот rd будет volatile а wr нет.
То есть это что-то типа пайпа между двумя равноправными тредами? Мне такого делать не приходилось, но думаю, тут помогут автоматические переменные, типа вот:

uint8_t read()
{
    uint8_t _rd = rd;
    while (_rd == wr);
    uint8_t val = buff[rd];
    rd = (_rd + 1) & 15;
    return val;
}

void write(uint8_t c)
{
    uint8_t _wr = wr;
    uint8_t next_wr = (_wr + 1) & 15;
    while(rd == next_wr);
    buff[_wr] = c;
    wr = next_wr;
}

Ассемблерный код не показываю, там все прозрачно...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Про присваивание *th_head ошибся, но я уже писал об этом.

 

>В теле thread_restore_helper() th_self не используется, управление вызывающей

> функции она не возвращает - видимо поэтому компилятор разумно посчитал, что

> присваивание th_self не имеет эффекта.

 

Слишком сильное предположение, так и предыдущее присваивание можно отбросить. Да и вот ещё раз повторю,

 

>Сейчас проверил, если убрать первое присвоение th_self = NULL; то второе не

> выбрасывается.

 

почему?

 

>То есть это что-то типа пайпа между двумя равноправными тредами? Мне такого

> делать не приходилось, но думаю, тут помогут автоматические переменные, типа

> вот:

 

Я так и делаю, но это работа которую хочется преложить на компилтор,

 

uint8_t read() {
    while (rd == volatile_read(wr));
    uint8_t val = buff[rd];
    rd = (rd + 1) & 15;
    return val;
}

 

Это похоже можно сделать вот так,

#define volatile_read(op) \
    ({ volatile typeof (op) *__op = &(op); *__op; })

 

Как сделать volatile_write не знаю, ни когда с такой ({}) конструкцией не связывался. Правка: ну конечно можно параметром передать это очевидно.

Изменено пользователем amaora

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Слишком сильное предположение,
А где используется значение th_self после присваивания? Я вижу только одно место:

 

struct thread * volatile * th_self_v = &th_self;

 

Но оно выполняется раньше, в самом начале thread_global_shedule(). Разве что она еще используется в функции thread_regs_restore()? Ее код почему-то не приведен...

 

А, понял. По ассемблерному коду видно, что thread_regs_restore() - одна большая ассемблерная вставка, и th_self в ней действительно читается. Можно увидеть ее исходный код?

 

так и предыдущее присваивание можно отбросить.
Согласен с Вами. th_head не используется даже в thread_regs_restore().

 

Да и вот ещё раз повторю,

>Сейчас проверил, если убрать первое присвоение th_self = NULL; то второе не

> выбрасывается.

почему?

Наверное потому что качество оптимизатора не бесконечно. Не сообразил он...

 

#define volatile_read(op) \

({ volatile typeof (op) *__op = &(op); *__op; })

Хм. Ну тогда вот так:

#define volatile_var(x) (*(volatile typeof(x) *)&x)

В отличие от предыдущего этот макрос может быть lvalue, то есть годится и для чтения, и для записи.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

>#define volatile_var(x) (*(volatile typeof(x) *)&x)

 

Действительно, а я все только усложнил. Спасибо, с этим ясно.

 

th_self используется в следующем коде, и далее другим кодом в том контексте который востановил этот код, th_self это указатель на структуру задачи которая выполняется в данный момент.

 

#define thread_regs_restore() \
__asm__ __volatile__ (				\
"lds	r30, th_self		\n\t"	\
"lds	r31, th_self+1		\n\t"	\
"ldd	r16, Z+32		\n\t"	\
"out	__SREG__, r16		\n\t"	\
"ldd	r16, Z+33		\n\t"	\
"ldd	r17, Z+34		\n\t"	\
"out	__SP_L__, r16		\n\t"	\
"out	__SP_H__, r17		\n\t"	\
"ldd	r16, Z+30		\n\t"	\
"ldd	r17, Z+31		\n\t"	\
"sts	(%0-1), r16		\n\t"	\
"sts	(%0-0), r17		\n\t"	\
"ld	r0, Z+			\n\t"	\
"ld	r1, Z+			\n\t"	\
"ld	r2, Z+			\n\t"	\
"ld	r3, Z+			\n\t"	\
"ld	r4, Z+			\n\t"	\
"ld	r5, Z+			\n\t"	\
"ld	r6, Z+			\n\t"	\
"ld	r7, Z+			\n\t"	\
"ld	r8, Z+			\n\t"	\
"ld	r9, Z+			\n\t"	\
"ld	r10, Z+			\n\t"	\
"ld	r11, Z+			\n\t"	\
"ld	r12, Z+			\n\t"	\
"ld	r13, Z+			\n\t"	\
"ld	r14, Z+			\n\t"	\
"ld	r15, Z+			\n\t"	\
"ld	r16, Z+			\n\t"	\
"ld	r17, Z+			\n\t"	\
"ld	r18, Z+			\n\t"	\
"ld	r19, Z+			\n\t"	\
"ld	r20, Z+			\n\t"	\
"ld	r21, Z+			\n\t"	\
"ld	r22, Z+			\n\t"	\
"ld	r23, Z+			\n\t"	\
"ld	r24, Z+			\n\t"	\
"ld	r25, Z+			\n\t"	\
"ld	r26, Z+			\n\t"	\
"ld	r27, Z+			\n\t"	\
"ld	r28, Z+			\n\t"	\
"ld	r29, Z+			\n\t"	\
"lds	r30, (%0-1)		\n\t"	\
"lds	r31, (%0-0)		\n\t"	\
"reti				\n\t"	\
: :	"X" (THREAD_TEMP_ADDR)		\
)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

>А, понял. По ассемблерному коду видно, что thread_regs_restore() - одна большая ассемблерная вставка, и th_self в ней действительно читается. Можно увидеть ее исходный код?

 

Так дело то всё в том, что компилятор не понимает читается ли что-то в ассемблерной вставке или нет. Он анализирует только Си код :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Сейчас вернул код в исходное состояние, в то, что я показал здесь, присвоение th_self заработало в обоих местах, странно, видимо как то повлияли другие изменения. Но вот что более инетересно, пробую вот такое изменение,

 

thread_restore_helper()
{
- thread_regs_restore();
+    //thread_regs_restore();
    for (;;);
}

 

Второе присваивание, то что перед вызовом этой безвозвратной функции исчезло, значит компилятор все таки учитывает содержимое асм-вставки.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Смотря какая асм-вставка. Если это реализованная полностью на асм функция, оформленная в соответствии со всеми правилами сохранения/возврата регистров и т.д. - то это одно дело. Если же это асм типа asm(); да ещё и работающий с переменными задествованными в Си-коде - то это совсем другое. В общем и целом(не знаю как в gcc) у меня в техасовском компиляторе есть опция, которая говорит компилятору, что переменные могут быть модифицированы где-то ещё, кроме кода, предоставленного компилятору для сборки.

Ещё как вариант, можно не изменять значение птакой-то переменной тупо из асм кода, а передать указатель на эту переменную как параметр асм-функции. Тогда код асм функции сможет делать всё то-же самое, но компилятор будет знать, что раз этот указатель передан в функцию и он не объявлен в прототипе как const - значит допускается, что функция будет работать с ним и на запись. Соответственно компилятор примет меры, чтобы это получилось:)

Если же это asm() вставка, работающая с Си переменными - то так вообще делать ИМХО нельзя, ибо будет работать только пока не включишь оптимизацию.

 

P.S. Опыта работы с gcc не имею...может быть там с asm() вставками дело налажено иначе(слыхал, что там как-то этот вопрос подругому стоит), но в общем, Си компилятор работает именно так(ложит на asm большой прибор и ничего он там не понимал и понимать не должен) :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Проверил макрос, он имеет неожиданный эффект,

 

#define surely(var)        (*(volatile __typeof__ (var) *) &(var))

if (likely(*th_head)) {
                interrupts_disable();
                surely(*th_head) = (*th_head)->next;
                surely(th_self) = *th_head;
                thread_restore_helper();
}

 

 6fe:    ed 01           movw    r28, r26
700:    89 a5           ldd    r24, Y+41; 0x29
702:    9a a5           ldd    r25, Y+42; 0x2a
704:    91 83           std    Z+1, r25; 0x01
706:    80 83           st    Z, r24
/* !!! */
708:    80 81           ld    r24, Z
70a:    91 81           ldd    r25, Z+1; 0x01
/* !!! */
70c:    90 93 ea 00     sts    0x00EA, r25
710:    80 93 e9 00     sts    0x00E9, r24
714:    e0 91 e9 00     lds    r30, 0x00E9
718:    f0 91 ea 00     lds    r31, 0x00EA
71c:    00 a1           ldd    r16, Z+32; 0x20

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

#define thread_regs_restore() \
__asm__ __volatile__ (                \
    "lds    r30, th_self        \n\t"    \
    "lds    r31, th_self+1        \n\t"    \
    "ldd    r16, Z+32            \n\t"    \
    : :    "X" (THREAD_TEMP_ADDR)        \
)

Тут у Вас и кроется ошибка. Во-первых, Вы поместили обращение к th_self в строковый литерал, который просто помещается компилятором в выходной ассемблерный файл. И нет никаких указаний компилятору на использование этой переменной. Во-вторых, компилятору не сказали о том, что в ассемблерном коде модифицируется содержимое регистров. Хотя, в данном конкретном применении это, наверное, не имеет значения (т.к. дальше управление передается куда-то в другое место), но других случаях неуказание компилятору списка "испорченных" регистров закладывает знатные грабли... :)

Cледовало позволить компилятору самому загрузить регистровую пару нужным значением, например, так:

__asm__ __volatile__ (
        "ldd    r16, Z+32            \n\t"    \
        "...остальной ассемблерный код..."
        : : "z"(th_self));

Вообще выполнять в ассемблерных вставках явным образом загрузки и выгрузки данных - плохая идея, так как это снижает эффективность кода. Например, на момент выполнения вставки нужные данные уже могут находиться в регистрах, но процессор все равно будет заново вычитывать их из памяти, потому что команды загрузки прописаны в ассемблерной вставке. Компилятор же сам отследит, что у него в каком регистре, и лишний код генерить не будет...

 

Так дело то всё в том, что компилятор не понимает читается ли что-то в ассемблерной вставке или нет. Он анализирует только Си код :)
Прекрасно понимает, если ему правильно объяснить. Использование ассемблерных inline-вставок в gcc не только допустимо, но в целом ряде случаев оправданно и очень эффективно. Только применять их надо правильно.
Изменено пользователем alx2

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

>Прекрасно понимает, если ему правильно объяснить. Использование ассемблерных inline-вставок в gcc не только допустимо, но в целом ряде >случаев оправданно и очень эффективно. Только применять их надо правильно.

Ну вот, значит "слухи" подтвердились :) Я ж говорю, с gcc не общался, но старался чем-то помочь :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...