alx2 0 31 июля, 2009 Опубликовано 31 июля, 2009 (изменено) · Жалоба Надоело вылавливать баги связанные с тем что компилятор просто выбрасывает важные части кода.За 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 читается только в цикле. Изменено 31 июля, 2009 пользователем alx2 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
amaora 25 31 июля, 2009 Опубликовано 31 июля, 2009 (изменено) · Жалоба Вот от компилятора, но тут есть ещё изменения, 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)) { то, тоже код генерируется нормально, для этого присвоения. Изменено 31 июля, 2009 пользователем amaora Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
sigmaN 0 31 июля, 2009 Опубликовано 31 июля, 2009 · Жалоба давайте исходить из того, что volatile нужен только для того, чтобы уведомить компилятор Си о том, что переменная может быть изменена вне кода Си, который анализируется оптимизатором и может быть существенно упрощён/сокращен. К примеру регистры переферии нужно объявлять как volatile, потому что их значения могут меняться без ведома компилятора. Также верно и обратное: компилятор не должен сокращать и иным образом упрощать выражения с volatile переменной, т.к. это может нарушить ход выполнения программы и привести к нежелательным результатам. По коду: uint8_t tqu; tqu = CONFIG_SHEDULE_TQUANT; далее tqu используется только с timer_setup(tqu); Компилятор сделает просто timer_setup(CONFIG_SHEDULE_TQUANT); tqu вы даже не найдете в стеке и это нормально! Первым делом смотреть надо на код, на вашу модель вообще(как что куда и зачем), а уже потом гнать на компилятор. :) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
alx2 0 31 июля, 2009 Опубликовано 31 июля, 2009 · Жалоба Вот от компилятора, но тут есть ещё изменения, 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; } Ассемблерный код не показываю, там все прозрачно... Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
amaora 25 1 августа, 2009 Опубликовано 1 августа, 2009 (изменено) · Жалоба Про присваивание *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 не знаю, ни когда с такой ({}) конструкцией не связывался. Правка: ну конечно можно параметром передать это очевидно. Изменено 1 августа, 2009 пользователем amaora Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
alx2 0 1 августа, 2009 Опубликовано 1 августа, 2009 · Жалоба Слишком сильное предположение,А где используется значение 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, то есть годится и для чтения, и для записи. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
amaora 25 1 августа, 2009 Опубликовано 1 августа, 2009 · Жалоба >#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) \ ) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
sigmaN 0 1 августа, 2009 Опубликовано 1 августа, 2009 · Жалоба >А, понял. По ассемблерному коду видно, что thread_regs_restore() - одна большая ассемблерная вставка, и th_self в ней действительно читается. Можно увидеть ее исходный код? Так дело то всё в том, что компилятор не понимает читается ли что-то в ассемблерной вставке или нет. Он анализирует только Си код :) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
amaora 25 1 августа, 2009 Опубликовано 1 августа, 2009 · Жалоба Сейчас вернул код в исходное состояние, в то, что я показал здесь, присвоение th_self заработало в обоих местах, странно, видимо как то повлияли другие изменения. Но вот что более инетересно, пробую вот такое изменение, thread_restore_helper() { - thread_regs_restore(); + //thread_regs_restore(); for (;;); } Второе присваивание, то что перед вызовом этой безвозвратной функции исчезло, значит компилятор все таки учитывает содержимое асм-вставки. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
sigmaN 0 1 августа, 2009 Опубликовано 1 августа, 2009 · Жалоба Смотря какая асм-вставка. Если это реализованная полностью на асм функция, оформленная в соответствии со всеми правилами сохранения/возврата регистров и т.д. - то это одно дело. Если же это асм типа asm(); да ещё и работающий с переменными задествованными в Си-коде - то это совсем другое. В общем и целом(не знаю как в gcc) у меня в техасовском компиляторе есть опция, которая говорит компилятору, что переменные могут быть модифицированы где-то ещё, кроме кода, предоставленного компилятору для сборки. Ещё как вариант, можно не изменять значение птакой-то переменной тупо из асм кода, а передать указатель на эту переменную как параметр асм-функции. Тогда код асм функции сможет делать всё то-же самое, но компилятор будет знать, что раз этот указатель передан в функцию и он не объявлен в прототипе как const - значит допускается, что функция будет работать с ним и на запись. Соответственно компилятор примет меры, чтобы это получилось:) Если же это asm() вставка, работающая с Си переменными - то так вообще делать ИМХО нельзя, ибо будет работать только пока не включишь оптимизацию. P.S. Опыта работы с gcc не имею...может быть там с asm() вставками дело налажено иначе(слыхал, что там как-то этот вопрос подругому стоит), но в общем, Си компилятор работает именно так(ложит на asm большой прибор и ничего он там не понимал и понимать не должен) :) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
amaora 25 1 августа, 2009 Опубликовано 1 августа, 2009 · Жалоба Проверил макрос, он имеет неожиданный эффект, #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 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
alx2 0 2 августа, 2009 Опубликовано 2 августа, 2009 (изменено) · Жалоба #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 не только допустимо, но в целом ряде случаев оправданно и очень эффективно. Только применять их надо правильно. Изменено 2 августа, 2009 пользователем alx2 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
sigmaN 0 2 августа, 2009 Опубликовано 2 августа, 2009 · Жалоба >Прекрасно понимает, если ему правильно объяснить. Использование ассемблерных inline-вставок в gcc не только допустимо, но в целом ряде >случаев оправданно и очень эффективно. Только применять их надо правильно. Ну вот, значит "слухи" подтвердились :) Я ж говорю, с gcc не общался, но старался чем-то помочь :) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться