Крышеснос с avr-gcc

Что-то у меня вообще тупняк — не понимаю, что творит GCC
Есть у меня такая вот структура:

	struct t_samples {
		int16_t  i1;
		int16_t  i2;
		int16_t  i3;
		int16_t  i4;
		int16_t  i5;
		int16_t  i6;
	};

При попытке загрузить переменную этого типа в регистры вот таким вот кодом

int main()
{
	struct t_samples sample_A;
	struct t_samples sample_B;
	struct t_samples tmp_sample;
	while(1)
	{

		ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
		{
			if(flag & BIT1)
				tmp_sample = sample_B;
			else
				tmp_sample = sample_A;
<...вычисления>
		}
	}
}

avr-gcc творит что-то, что от чего у меня сносит крышу:

				if(flag & BIT1)
    16e0:	40 fc       	sbrc	r4, 0
    16e2:	87 c5       	rjmp	.+2830   	; 0x21f2 <main+0xfce>
    16e4:	c1 56       	subi	r28, 0x61	; 97
    16e6:	de 4f       	sbci	r29, 0xFE	; 254
    16e8:	28 81       	ld	r18, Y
    16ea:	cf 59       	subi	r28, 0x9F	; 159
    16ec:	d1 40       	sbci	r29, 0x01	; 1
    16ee:	c0 56       	subi	r28, 0x60	; 96
    16f0:	de 4f       	sbci	r29, 0xFE	; 254
    16f2:	38 81       	ld	r19, Y
    16f4:	c0 5a       	subi	r28, 0xA0	; 160
    16f6:	d1 40       	sbci	r29, 0x01	; 1
    16f8:	c9 01       	movw	r24, r18
    16fa:	7c 01       	movw	r14, r24
    16fc:	c3 56       	subi	r28, 0x63	; 99
    16fe:	de 4f       	sbci	r29, 0xFE	; 254
    1700:	28 81       	ld	r18, Y
    1702:	cd 59       	subi	r28, 0x9D	; 157
    1704:	d1 40       	sbci	r29, 0x01	; 1
    1706:	c2 56       	subi	r28, 0x62	; 98
    1708:	de 4f       	sbci	r29, 0xFE	; 254
    170a:	38 81       	ld	r19, Y
    170c:	ce 59       	subi	r28, 0x9E	; 158
    170e:	d1 40       	sbci	r29, 0x01	; 1
    1710:	c9 01       	movw	r24, r18
    1712:	8c 01       	movw	r16, r24
    1714:	c5 56       	subi	r28, 0x65	; 101
    1716:	de 4f       	sbci	r29, 0xFE	; 254
    1718:	28 81       	ld	r18, Y
    171a:	cb 59       	subi	r28, 0x9B	; 155
    171c:	d1 40       	sbci	r29, 0x01	; 1
    171e:	c4 56       	subi	r28, 0x64	; 100
    1720:	de 4f       	sbci	r29, 0xFE	; 254
    1722:	38 81       	ld	r19, Y
    1724:	cc 59       	subi	r28, 0x9C	; 156
    1726:	d1 40       	sbci	r29, 0x01	; 1
    1728:	c9 01       	movw	r24, r18
    172a:	dc 01       	movw	r26, r24
    172c:	cf 55       	subi	r28, 0x5F	; 95
    172e:	de 4f       	sbci	r29, 0xFE	; 254
    1730:	28 81       	ld	r18, Y
    1732:	c1 5a       	subi	r28, 0xA1	; 161
    1734:	d1 40       	sbci	r29, 0x01	; 1
    1736:	ce 55       	subi	r28, 0x5E	; 94
    1738:	de 4f       	sbci	r29, 0xFE	; 254
    173a:	38 81       	ld	r19, Y
    173c:	c2 5a       	subi	r28, 0xA2	; 162
    173e:	d1 40       	sbci	r29, 0x01	; 1
    1740:	c9 01       	movw	r24, r18
    1742:	bc 01       	movw	r22, r24
    1744:	cd 55       	subi	r28, 0x5D	; 93
    1746:	de 4f       	sbci	r29, 0xFE	; 254
    1748:	28 81       	ld	r18, Y
    174a:	c3 5a       	subi	r28, 0xA3	; 163
    174c:	d1 40       	sbci	r29, 0x01	; 1
    174e:	cc 55       	subi	r28, 0x5C	; 92
    1750:	de 4f       	sbci	r29, 0xFE	; 254
    1752:	38 81       	ld	r19, Y
    1754:	c4 5a       	subi	r28, 0xA4	; 164
    1756:	d1 40       	sbci	r29, 0x01	; 1
    1758:	c9 01       	movw	r24, r18
    175a:	ac 01       	movw	r20, r24
    175c:	cb 55       	subi	r28, 0x5B	; 91
    175e:	de 4f       	sbci	r29, 0xFE	; 254
    1760:	28 81       	ld	r18, Y
    1762:	c5 5a       	subi	r28, 0xA5	; 165
    1764:	d1 40       	sbci	r29, 0x01	; 1
    1766:	ca 55       	subi	r28, 0x5A	; 90
    1768:	de 4f       	sbci	r29, 0xFE	; 254
    176a:	38 81       	ld	r19, Y
    176c:	c6 5a       	subi	r28, 0xA6	; 166
    176e:	d1 40       	sbci	r29, 0x01	; 1
    1770:	c9 01       	movw	r24, r18
    1772:	9c 01       	movw	r18, r24
					tmp_sample = sample_B;
				else
					tmp_sample = sample_A;



Нет, я, конечно, понял, что я не прав, и что вся фигня из-за локальности переменных — если убрать атомик_блок, код становится лучше в два раза, а если переменные сделать глобальными — то вообще всё замечательно

    16bc:	a0 90 42 03 	lds	r10, 0x0342
    16c0:	b0 90 43 03 	lds	r11, 0x0343
    16c4:	00 91 40 03 	lds	r16, 0x0340
    16c8:	10 91 41 03 	lds	r17, 0x0341
    16cc:	e0 91 3e 03 	lds	r30, 0x033E
    16d0:	f0 91 3f 03 	lds	r31, 0x033F
    16d4:	60 91 44 03 	lds	r22, 0x0344
    16d8:	70 91 45 03 	lds	r23, 0x0345
    16dc:	40 91 46 03 	lds	r20, 0x0346
    16e0:	50 91 47 03 	lds	r21, 0x0347
    16e4:	20 91 48 03 	lds	r18, 0x0348
    16e8:	30 91 49 03 	lds	r19, 0x0349


НО! Объясните мне люди, ЧТО компилятор пытался сделать? Я честно не понимаю половины махинаций из первого листинга…
Кстати, во втором листинге тоже немного не понятно — из каких соображений он, интересно, память не по порядку читает?
  • 0
  • 26 мая 2011, 19:32
  • Alatar

Комментарии (21)

RSS свернуть / развернуть

if(flag & BIT1)
                                tmp_sample = sample_B;
                        else
                                tmp_sample = sample_A;



… в старой версии языка Си, запрещалось присваивание структур, но с оговоркой что эти ограничения будут сняты. Раз gcc не выдает ошибки при компиляции, значит ограничения сняты — но хорошоб узнать про процедуру присваивания структур. Ща поищем, самому интересно :))
0
… Керниган и Ричи:
В языке «C» существует ряд ограничений на использование структур. Обязательные правила заключаются в том, что единственные операции, которые вы можете проводить со структурами, состоят в определении ее адреса с помощью операции & и доступе к одному из ее членов ("."). Это влечет за собой то, что структуры нельзя присваивать или копировать как целое, и что они не могут быть переданы функциям или возвращены ими. (В последующих версиях эти ограничения будут сняты).
0
Ну Керниган и Ричи — это, конечно, очень старый формат, там много чего нельзя. В C89 уже таких проблем нет. Я использую C99 — там вообще есть некоторые возможности, которых нет в C++.
0
первый листинг такой громадный потому как локальные переменные. компилятор на каждом шаге вычисляет адрес структуры, укоротить код можно включением оптимизации.
очерёдность чтения полей непринципиальна в авр, время доступа постоянно. в каком порядке компилятор «смог освободить» регистры, в том и присваивал.
+1
Ну да, забыл сказать — оптимизация -O2, переменные не volatile. Зачем так хитро вычислять адрес структуры? Зачем гонять данные из регистра в регистр? Как вообще это вычисление адреса работает?
0
А какая версия компилятора?
У меня версия 4.4.5 генерирует примерно следущее:

...
  be:	08 c0       	rjmp	.+16     	; 0xd0 <main+0x3e>
  c0:	d9 01       	movw	r26, r18
  c2:	fa 01       	movw	r30, r20
  c4:	8c e0       	ldi	r24, 0x0C	; 12
  c6:	01 90       	ld	r0, Z+
  c8:	0d 92       	st	X+, r0
  ca:	81 50       	subi	r24, 0x01	; 1
  cc:	e1 f7       	brne	.-8      	; 0xc6 <main+0x34>
  ce:	07 c0       	rjmp	.+14     	; 0xde <main+0x4c>
  d0:	d9 01       	movw	r26, r18
  d2:	fb 01       	movw	r30, r22
  d4:	8c e0       	ldi	r24, 0x0C	; 12
  d6:	01 90       	ld	r0, Z+
  d8:	0d 92       	st	X+, r0
  da:	81 50       	subi	r24, 0x01	; 1
  dc:	e1 f7       	brne	.-8      	; 0xd6 <main+0x44>

...


А смысл этих манипуляций в том, что три структуры по 12 байт не помещаются в регистрах и размещены в стеке по неизвестным во время компиляции адресам. А со стеком компилятор действительно перемудрил… может регистров ему всё таки не хватило.
0
>>А какая версия компилятора?
На вскидку не скажу, но что-то из четвёртой ветки, где-то с год назад собирал. Завтра уточню.
>>У меня версия 4.4.5 генерирует примерно следущее:
Ну тут, вероятно, от остального контекста неслабо зависит
>>А смысл этих манипуляций в том, что три структуры по 12 байт не помещаются в регистрах
А три структуры мне и не нужны — моя цель выгрузить из памяти в регистры одну из двух структур, с которой я буду работать дальше
>>и размещены в стеке по неизвестным во время компиляции адресам.
а вот это интересно, по идее, если это так, то должна помочь Link-time оптимизация из GCC 4.6. Надо бы поподробнее почитать, как работает стек локальных переменных, желательно в применении как avr-gcc.
0
А зачем вся структура на 12 байт нужна в регистрах одновременно? Какие-то объёмные вычисления, которые используют сразу все элементы структуры? Помоему в данном случае удобней обращаться к нужной структуре по указателю.

struct t_samples *tmp_sample;

if(flag & BIT1)
tmp_sample = &sample_B;
else
tmp_sample = &sample_A;
0
Ну просто дальнейшие манипуляции со структурой (да, используются все поля) должны уложиться в жёсткий тайминг, так что иметь операции обращения к памяти внутри критичной части кода не хочется. А так да, обычно с указателями работаю.
0
Копирование структуры почти всегда порождает жуткий код, а на деле же это просто копирование блока памяти —
memcpy
лучше, что собственно и показывает листинг асма сгенереного 4.4.5 — копирование же :). А если сделать копирование с указателями, как предлагает уважаемый neiver и я комментом ниже, то код еще и меньше по размеру будет.
0
Что-то обсуждение куда-то не в ту степь пошло. Вопрос не в том КАК исправить (это я уже сделал), а в том ЧТО пытается сделать компилятор.
И да, работать с указателями я умею ;) Но в данном случае мне КОПИРОВАНИЕ не нужно вообще — я загружаю структуру в регистры перед критичной секцией и выгружаю её после по ТОМУ ЖЕ адресу. При правильном расположении объявления переменных под tmp_sample память не выделяется — только регистры, на то она и tmp =). И желаемого поведения я добился, так что как лечить меня не интересует.
0
Присвоение чего либо — это копирование, за исключением случая использования reference. Так что во всех случаях компилятор именно копирует, что вы ему кодом и говорите :).
0
К.О.? Мы вообще о чём? Кстати, в C, вроде бы, нету ссылок, или я что-то пропустил?
Хорошо, не нравится слово «копирование», объясню на пальцах:
допустим есть такой код

int a, b;
<...>
b = a;
<...>

Здесь компилятор сначала считает из памяти значение по адресу «a» в регистр, а затем из регистра запишет по адресу «b».
Теперь другой код:

int a, tmp;
<...>
tmp = a;
tmp ++;
a = tmp;
<...>

Здесь компилятор загрузит из памяти по адресу «a» в регистр, сделает инкремент, а потом запишет результат по адресу «a». Под переменную «tmp» даже память не выделится (разумеется, если включена оптимизация и переменная tmp больше нигде не используется).
Так вот, причём тут указатели и ссылки? Не о том же речь…
0
Ссылки есть и в С, только они явные и называются указателями. Думаете зря в С++ для референс используется именно знак &? Второй пример обойдется без временных переменных только в случае использования интегральных типов (по крайней мере так написано в стандарте) и поэтому замена структуры на int в данном случае некорректна :). Хотя сработает в современных компиляторах :).
Указатели и ссылки притом, что за присваивание структуры целиком в С надо бить томиком Кернигана&Ричи по рукам ;), без обид. Не надо усложнять жизнь себе и компилятору, вот в чем смысл моих сентенций.
0
Кхм… Моя очередь сказать «без обид». Вы вообще читаете то, что я пишу? Оно работает именно так как я хочу, под tmp_samples память не выделяется, просто в некоторых случаях считывание значения из памяти в регистры происходит через ж., потому что, как правильно заметил ув. neiver, считываемые переменные находятся в стеке и компилятору это сносит крышу. Что там по этому поводу говорят Керниган&Ричи меня не волнует совершенно — мир уже давно живёт по C99. А, на минуточку, по Кернигану&Ричи конструкция ATOMIC_BLOCK вообще невозможна ;).

И да, интересно, если в C указатели называются ссылками, то что же тогда называется указателями в C++? И что там называется ссылками?
0
Обидеть меня сложно, дискуссия доставляет ;).
Во первых — не через ж., а вполне через индексный регистр ;), как и везде в компутерном мире — frame-based адресация, frame всегда создается при наличии локальных переменных. Вина компилятора в вашем гигантском листинге, что он не переставил инструкции и не заметил, что это на деле memcpy. Так что руки прочь от компилятора! Ничего ему не сносит :).
Теперь вступлюсь за K&R — конструкция ATOMIC_BLOCK по ним вполне возможна, это ж define обычный, так что и от них руки прочь!
Насчет терминологии: в языках программирование есть всего два способа передачи параметров (в том числе и в операторы — не будете спорить, что они тоже в чем-то функции?) — by value и by reference. В С передаче by reference соответствует использование указателей, а в C++ — указателей и reference, которые являются всего-лишь «синтаксическим сахаром», а на деле — те же указатели. Вот как-то так.
0
>>Обидеть меня сложно, дискуссия доставляет ;).
Я бы сказал «вставляет», или даже «вштыривает» =)
>>Вина компилятора в вашем гигантском листинге, что он не переставил инструкции и не заметил, что это на деле memcpy.
mymcpy — это копирование из одного блока памяти в другой, здесь же нет целевого блока памяти вообще, а вот то, что тут можно использовать косвенную адресацию с пост инкрементом бог бы и догадаться (LD Y+ и вперёд).
>>Так что руки прочь от компилятора! Ничего ему не сносит :).
ну да, а конструкции типа
16f8: c9 01 movw r24, r18
16fa: 7c 01 movw r14, r24
он в здравом уме пишет?
>>Теперь вступлюсь за K&R — конструкция ATOMIC_BLOCK по ним вполне возможна, это ж define обычный, так что и от них руки прочь!
Ну да, обычный дефайн, не спорю… А вот внутрь него Вы лазали? Там хитровывернутый for, внутри которого объявлена локальная переменная для хранения SREG. Керниган&Ричи же требуют объявлять все локальные переменные вначале функции, так что уже даже на этом ATOMIC_BLOCK споткнётся. А ведь это ещё не всё =)
>> есть всего два способа передачи параметров — by value и by reference.
Ну если с этой точки зрения смотреть, то да, соглашусь. С чисто синтаксической же точки зрения в C++ есть ссылки и указатели, а в C — только указатели.
0
что тут можно использовать косвенную адресацию с пост инкрементом бог бы и догадаться (LD Y+ и вперёд).Опять у нас расхождение в терминологии — я это и имел в виду под memcpy :). Регистры же у нас мапятся в память, так? Неудачно просто выразился.
Ну да, обычный дефайн, не спорю… А вот внутрь него Вы лазали?...А у K&R несколько изданий ;). В крайнем описан и C99, в том числе. Я еще первое читал, с объявлениями вида

void func(x, z) int x, char* z;
{
//а тут тело
}

Не надо их считать седой древностью ;).
0
Не хочу показаться занудным, но покажусь. В первом K&R небыло void и // в качестве комментария.
0
Точно, вы правы. Но оно же 78года издания ;), если смотреть английский вариант, а читал я его году эдак в 86м, имею право подзабыть. Русский был помнится в коричневой обложке, вроде 80го года издания.
Комментарий — это от меня (профпривычка :)), а вот про отсутствие void забыл, факт.
0
ИМХО, пример в посте демонстрирует ошибочный стиль программирования, не в обиду будет сказано. Зачем вообще копировать структуру? Пользуйте указатель, присваивая его адресу нужной и будет щасте и компилятору, и вам :).
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.