Jump to content

    

Rst7

Модераторы
  • Content Count

    4481
  • Joined

  • Last visited

Posts posted by Rst7


  1. 58 minutes ago, AHTOXA said:

    он может делать это областями до 2К размером.

    И вот это они зря на откуп реализации отдали, да. Вот, например, я приводил пример кода, который дергает блок из списка, а добавить блок в этот список малость усложняется - сначала надо обычным чтением прочитать указатель на голову, заполнить им поле next в блоке, а затем уже после LDREX указателя на голову проверить, что он не изменился (и если изменился, то сделать CLREX и начинать сначала), и только после этого сохранить указатель на новый блок.

    Походу, и доставание блока из списка тоже должно выглядеть по другому. Сначала берем p, и p->next, а уже потом LDREX/STREX. Некрасиво, но действительно portable.

  2. 14 hours ago, AHTOXA said:

    Ну хорошо. Вот что там сказано про локальный монитор:

     

    Ну? Про локальный сказано, что он может либо адрес отслеживать, либо только выполнение LDREX/STREX. Т.е. STR в произвольный адрес не приведет к сбросу монитора.

    В общем произвольный STR не в мониторящийся адрес безопасен между LDREX/STREX.

     

    А вот зачем этот идиотизм со сбросом монитора при эксепшне - мне нифига непонятно. От него только хуже.

  3. 2 hours ago, lamer0k said:

    при каждом входе и выходе из прерывания выполняется CLREX.

    С чего бы? Просто в C-M таки эксепшн сбрасывает монитор. Но только в -M

    1 hour ago, AHTOXA said:

    речь о глобальном мониторе, но не суть. 

    Суть. Глобальный - он для многопроцессорной системы.

    Кроме того, все равно запись в тот же адрес нужна, а не произвольный STR.

  4. 2 hours ago, AHTOXA said:

    - верно описывает результат. Хотя причины объясняет неверно :-)

    Верно описывает результат только для Cortex-M. Потому что объяснение неверное.

    3 hours ago, Kabdim said:

    Эээ т.е. если просто записать в тот же адрес STR'ом вся схема сломается?

    А зачем писать в переменную, к которой Вы организуете эксклюзивный доступ, обычным способом?

    2 hours ago, Kabdim said:

    Жесть, насколько же x86 в этом аспекте прямолинейно правильней.

    Только менее универсальный. Например, взять элемент из списка на ARM выглядит так:

    #define ExAddr(VAL) ((volatile unsigned int *)(&(VAL)))
    
    typedef struct LLITEM
    {
      struct LLITEM *next;
      unsigned char data[100];
    }LLITEM;
    
    volatile LLITEM *FreeBlocks;
    
    LLITEM *AllocBlock(void)
    {
      LLITEM *p;
      LLITEM *next;
      do
      {
        p=(LLITEM*)__LDREX(ExAddr(FreeBlocks));
        if (!p) {__CLREX(); return p;}
        next=p->next;
      }while(__STREX((unsigned long)next,ExAddr(FreeBlocks)));
      return p;
    }
    Spoiler
    
        163          LLITEM *AllocBlock(void)
        164          {
       \                     AllocBlock: (+1)
       \        0x0   0x.... 0x....      LDR.W    R2,??DataTable6_1
        165            LLITEM *p;
        166            LLITEM *next;
        167            do
        168            {
        169              p=(LLITEM*)__LDREX(ExAddr(FreeBlocks));
       \                     ??AllocBlock_0: (+1)
       \        0x4   0xE852 0x0F00      LDREX    R0,[R2]
        170              if (!p) {__CLREX(); return p;}
       \        0x8   0xB128             CBZ.N    R0,??AllocBlock_1
        171              next=p->next;
        172            }while(__STREX((unsigned long)next,ExAddr(FreeBlocks)));
       \        0xA   0x6803             LDR      R3,[R0, #+0]
       \        0xC   0xE842 0x3100      STREX    R1,R3,[R2]
       \       0x10   0x2900             CMP      R1,#+0
       \       0x12   0xD1F7             BNE.N    ??AllocBlock_0
        173            return p;
       \       0x14   0x4770             BX       LR               ;; return
       \                     ??AllocBlock_1: (+1)
       \       0x16   0xF3BF 0x8F2F      CLREX    
       \       0x1A   0x4770             BX       LR
        174          }

     

    А на x86 с его единственным lock'ом надо извращаться.

  5. 17 minutes ago, AHTOXA said:

    что прерывания таки сбрасывают флаг экслюзивного доступа, даже если в обработчике ничего не делается.

    Я не об этом. Обычная запись в память (или чтение) между LDREX/STREX не сбрасывает флаг.

    volatile unsigned int testvar;
    volatile unsigned int testvar2;
    void TestEX(void)
    {
      unsigned int tv;
      tv=__LDREX(&testvar);
      __no_operation();
      __no_operation();
      __no_operation();
      testvar2=1;
      testvar2;
      __no_operation();
      __no_operation();
      __no_operation();
      tv=__STREX(tv,&testvar);
      DEBUG_PRINTF("__STREX result %d\r\n",tv);
    }

    Такой код печатает "__STREX result 0", несмотря на наличие LDR/STR между LDREX/STREX.

    То, что заход в прерывания сбрасывают флаг- это какой-то отдельный тонкий момент, с ним надо разбираться, потому что в теории так быть не должно.

     

    Ага, вот в чем дело (из мануала на CM4)

    Quote

    The processor removes its exclusive access tag if:

    • It executes a CLREX instruction.

    • It executes a Store-Exclusive instruction, regardless of whether the write succeeds.

    • An exception occurs. This means the processor can resolve semaphore conflicts between different threads.

    Т.к. прерывания - это тоже эксепшн, то вот и ответ.

  6. 4 minutes ago, esaulenka said:

    Объясните, пожалуйста.

    Например непонимание работы exclusive monitor'а ядра тут:

    Quote

    Если между LDREX и STREX произошло прерывание и оно что-то записало в память (а оно обязательно хоть регистры, да сохранит в стек), то STREX ничего не запишет в память и в выходной регистре будет записана 1.

     

  7. Какие-то очень сложные у вас всех TFTP. У меня от LPC1768 загрузчик весит

    Quote

      6 614 bytes of readonly  code memory
         20 bytes of readwrite code memory
         74 bytes of readonly  data memory
      4 180 bytes of readwrite data memory (+ 263 absolute)
     

    Из которых 1132 байта - это библиотечный printf, который можно выбросить, если сильно прижмет.

    Кстати, я его где-то выкладывал тут.

  8. 1 hour ago, AlexandrY said:

    RT1020 устарел не родившись.
    Теперь все только и говорят о Cortex-M55 совместно с Ethos-U55. 
    Готовтесь перепиливать свой RNDIS под новую архитектуру. :bye:

    *facepalm.jpg*

     

  9. 6 hours ago, haker_fox said:

    Но уважаемый @lamerok вообще-то является членом нашего клуба с давних времён. И как-то некрасиво говорить в адрес своего коллеги такие вещи.

    Очень жаль, что участник нашего форума оказался таким провальным. Если бы он это все высказывал тут в дискуссиях - то пофиг. А вот то, что он это несет в неокрепшие умы (я же не просто так цитату принес) - вот за это нет прощения.

  10. 26 minutes ago, AlexandrY said:

    В i.MX RT USB стеке всего 66 файлов объемом 1,87 Мегабайт.

    Давайте, кстати, проверим на вшивость Ваш прекрасный дорогой софт.

    Что у Вас в заголовочнике с регистрами от RT1020 описано для USB_USBCMD_ATDTW_MASK и USB_USBCMD_ATDTW_SHIFT?

  11. 3 minutes ago, AlexandrY said:

    Вы просто купились на халявный, но откровенно слабый софт.

    А причем тут "слабый софт"?

    6 minutes ago, AlexandrY said:

    Одно дело трудность освоения чужого кода, и совсем другое поддержка своего middleware. Последнее не оправдывается по нынешним временам. 

    Это зачем мне чужой код осваивать? Я за свое могу нести ответственность, а за чужое - пардон. Чтобы за чужое нести ответственность - надо его досконально изучить, а это сравнимо с написанием своего.

  12. 25 minutes ago, one_eight_seven said:

    Что-то хорошее там есть? Просто за первые две минуты у меня пошла кровь из глаз, и я больше не хочу иметь зрение.

    Там еще и дичь про LDREX/STREX написана. Я бы вообще дал этому дьяволу по лицу. Именно за вот это:

    Quote

    Как я уже говорил, я обучаю студентов разработке ПО для измерительных устройств. Работа в университете — это мое хобби, основное место работы с университетом не связано, но тоже коррелирует с разработкой встроенного софта, в том числе и для высоко-надежных систем.

     

  13. Вот прям могу с пылу с жару рассказать про борьбу с USB-стеком. Камень - iMX RT1020. Имею принцип в жизни - писать самому, готовое не использовать, максимум - подглядеть в примеры какие-то опущенные в даташитах подробности.

     

    Spoiler

    Сразу скажу, что на камень омерзительная документация. Куча умолчаний, куча неочевидных вещей, разбросанных по всей доке, с отсутствием прямых указаний, где поглядеть. И куча банальных недосмотров типа обозначения пунктов в алгоритме буквами a, b, c..., а ссылки в этом алгоритме типа "go to step 5". В свое время приходилось работать с мотороловскими процами во времена 680x0 - тогда у них документация была просто песней. Ну и у NXP тоже всегда была отличная. Я не ожидал, что так будет провально.

     

    Я не особо часто бодался с USB, последний раз лет эдак 15 назад делал эмулятор FT232 на AT90USB162. А теперь пришлось сделать ECM/RNDIS устройство. Официальные примеры для этих iMX RT есть, но со страшным нагромождением уровней абстракции, разбросано по куче файлов, местами - плюсы на Си врукопашную, в общем - все прелести.

    Довольно быстро все поехало в простом варианте типа суперцикла, без всяких прерываний, просто с проверкой флагов. Затем начал переделывать на вменяемую работу, т.е. прерывания, обеспечение zero copy, минимальные простои и так далее. И в конце концов наступил на мертвое зависание алгоритма добавления нового буфера в цепочку буферов USB DMA на ходу. Алгоритм описан в даташите, более того, именно его реализацию (не без индусских делов) я обнаружил при изучении примеров (после того, как моя реализация не заработала). Убито было несколько дней. Оказалось, что в заголовочном файле с регистрами периферии был неверно описан один из битов в регистрах (не 1<<14, а 1<<12). Обнаружил это совершенно случайно, когда уже начал проверять совсем все. Заголовочный файл с регистрами был взят именно из официальных примеров. Так что официальные примеры, конечно, компилируются, но вот никто их на таргете никогда не запускал и не проверял. Вообще оказалось, что во всей этой iMX-линейке вроде бы и один и тот же USB, но этот бит пляшет произвольно от типа к типу, в каких-то - 12й, в каких-то - 14й, в некоторых это считалось ошибкой и описано в errata, в некоторых нет, в общем - полный хаос.

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

  14. 16 minutes ago, haker_fox said:

    Кстати, а как вы делаете Abort(). Не используете SVC и MPU?

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

  15. 5 minutes ago, haker_fox said:

    Кстати, а что вы под этим подразумеваете? Стиль кодирования?

    Конкретно в данном случае - без использования CMSIS.

    Теперь о рукопашности этого ассерта и вообще кривости конкретно данного кода.

    Правильно писать вот так, если не отключать ассерты:

      if (portNum>=8)
        return LPC_GPIO_PORT->PIN[portNum];
      else
        Abort();

    Потому что ветка true - это более частая ветка, скорее всего с вероятностью 100%.

  16. Just now, haker_fox said:

    Добавляется запись в аварийный журнал в энергонезависимую память.

    Я не об этом. А о том, что куча таких же кривых проверок и насилия над стеком есть и в других процедурах библиотеки. Или у Вас только GPIO через библиотеку, а упомянутая остальная периферия - вся аккуратненько врукопашную?

  17. 1 minute ago, haker_fox said:

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

    Там же небось такой рукопашный assert один на всех. И прекрасно живет и во всех остальных библиотечных процедурах, в том числе и тех, которые

    5 minutes ago, haker_fox said:

    Далее работают DMA, SSP, SGPIO (LPC4337) и прочая периферия.

     

  18. 26 minutes ago, haker_fox said:

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

    На каждое чтение порта вот такое вот? Зачем тогда сама тема про анализ листингов? Смысл их анализировать, если быстродействие релиза реально проваливается просто отказом от удаления отладочных пирогов?

    И да, неужели настолько сложно написать работу с портами с предсказуемыми последствиями?

  19. 3 hours ago, haker_fox said:

    Но в целом компилятор не упрекнёшь) Функция dprintf не будет вызвана.

    Тут главное не забыть в релизе сделать так:

    #define CHECK_PARAM(expr)

    Чтобы функция приняла вменяемый вид без насилия над стеком:

       \                                 In section .text, align 2, keep-with-next
        137          uint32_t GPIO_ReadValue( uint8_t portNum ) 
        138          {
        139              CHECK_PARAM(portNum < 8);
        140              return LPC_GPIO_PORT->PIN[portNum];
       \                     GPIO_ReadValue: (+1)
       \        0x0   0x....             LDR.N    R1,??DataTable4  ;; 0x400f6100
       \        0x2   0xF851 0x0020      LDR      R0,[R1, R0, LSL #+2]
       \        0x6   0x4770             BX       LR               ;; return

    Ну и, к сожалению, inline - это вообще редкая птица во всяких этих библиотеках для не умеющих прочитать содержимое порта сразу по месту в собственном коде.

  20. 7 minutes ago, jcxz said:

    Так ведь они (intrinsic'и) - это уже собственно ассемблер. Завуалированный.

    Ну все-таки не совсем. Если бы я делал asm("UQSUB16 %0,%0,%1":"+r"(max):"r"(*_s++)); - вот тогда да, ассемблер. А так пока никакого выхода за границу разумного для чистого ЯВУшника - ну подумаешь, есть функция __uqsub16(), которая хитро вычитает половинки long'ов.

    Приведу пример с большого брата (да, Intel'у отдельный котел в аду черти растопили за то, что в SIMD-инструкциях нет знакового сложения с сатурацией для пачки 32хбитных операндов, приходится вот такое изобретать):

    static inline __m128i selectb(__m128i s, __m128i a, __m128i b) {
    #if 1   // SSE4.1 supported
    	return _mm_blendv_epi8(b, a, s);
    #else
    	return _mm_or_si128(
    		_mm_and_si128(s, a),
    		_mm_andnot_si128(s, b));
    #endif
    }
    
    static inline __m128i add_saturated(__m128i a, __m128i b) {
    	__m128i sum = _mm_add_epi32(a, b);                  // a + b
    	__m128i axb = _mm_xor_si128(a, b);                  // check if a and b have different sign
    	__m128i axs = _mm_xor_si128(a, sum);                // check if a and sum have different sign
    	__m128i overf1 = _mm_andnot_si128(axb, axs);            // check if sum has wrong sign
    	__m128i overf2 = _mm_srai_epi32(overf1, 31);            // -1 if overflow
    	__m128i asign = _mm_srli_epi32(a, 31);                 // 1  if a < 0
    	__m128i sat1 = _mm_srli_epi32(overf2, 1);             // 7FFFFFFF if overflow
    	__m128i sat2 = _mm_add_epi32(sat1, asign);            // 7FFFFFFF if positive overflow 80000000 if negative overflow
    	return  selectb(overf2, sat2, sum);                      // sum if not overflow, else sat2
    }

    Это asm или не asm?

  21. 20 minutes ago, Rst7 said:

    невозможность загрузить сразу кучу переменных при помощи LDM

    Если нельзя, но очень хочется - то можно:

    #define PROCESS2(V) max=__UQSUB16(max,V)+V;
    
    unsigned long findmaxv2(unsigned short *_s)
    {
      unsigned long max=0;
      unsigned long a0,a1,a2,a3,a4,a5,a6,a7;
      for(unsigned long i=0; i<512/(2*8); i++)
      {
        asm("LDMIA %0!,{%1,%2,%3,%4,%5,%6,%7,%8}":
              "+r"(_s),
              "=r"(a0),"=r"(a1),"=r"(a2),"=r"(a3),
              "=r"(a4),"=r"(a5),"=r"(a6),"=r"(a7));
        PROCESS2(a0);
        PROCESS2(a1);
        PROCESS2(a2);
        PROCESS2(a3);
        PROCESS2(a4);
        PROCESS2(a5);
        PROCESS2(a6);
        PROCESS2(a7);
      }
      if ((max>>16)>(max&0xFFFF)) return max>>16;
      return max&0xFFFF;
    }
    Spoiler
    
         80          unsigned long findmaxv2(unsigned short *_s)
         81          {
       \                     findmaxv2: (+1)
       \        0x0   0xE92D 0x41F0      PUSH     {R4-R8,LR}
       \        0x4   0x4606             MOV      R6,R0
         82            unsigned long max=0;
       \        0x6   0x2700             MOVS     R7,#+0
         83            unsigned long a0,a1,a2,a3,a4,a5,a6,a7;
         84            for(unsigned long i=0; i<512/(2*8); i++)
       \        0x8   0xF05F 0x0520      MOVS.W   R5,#+32
         85            {
         86              asm("LDMIA %0!,{%1,%2,%3,%4,%5,%6,%7,%8}":
                                                                ^
    Warning[Og014]: Warning in inline assembly: "Registers are not specified in
              ascending order"
         87                    "+r"(_s),
         88                    "=r"(a0),"=r"(a1),"=r"(a2),"=r"(a3),
         89                    "=r"(a4),"=r"(a5),"=r"(a6),"=r"(a7));
       \                     ??findmaxv2_0: (+1)
       \        0xC   0xE8B6 0x511F      LDMIA R6!,{R8,LR,R12,R0,R1,R2,R3,R4}
         90              PROCESS2(a0);
         91              PROCESS2(a1);
         92              PROCESS2(a2);
         93              PROCESS2(a3);
         94              PROCESS2(a4);
         95              PROCESS2(a5);
         96              PROCESS2(a6);
         97              PROCESS2(a7);
       \       0x10   0xFAD7 0xF758      UQSUB16  R7,R7,R8
       \       0x14   0x44B8             ADD      R8,R8,R7
       \       0x16   0xFAD8 0xF75E      UQSUB16  R7,R8,LR
       \       0x1A   0x44BE             ADD      LR,LR,R7
       \       0x1C   0xFADE 0xF75C      UQSUB16  R7,LR,R12
       \       0x20   0x44BC             ADD      R12,R12,R7
       \       0x22   0xFADC 0xF750      UQSUB16  R7,R12,R0
       \       0x26   0x19C0             ADDS     R0,R0,R7
       \       0x28   0xFAD0 0xF051      UQSUB16  R0,R0,R1
       \       0x2C   0x1809             ADDS     R1,R1,R0
       \       0x2E   0xFAD1 0xF052      UQSUB16  R0,R1,R2
       \       0x32   0x1812             ADDS     R2,R2,R0
       \       0x34   0xFAD2 0xF053      UQSUB16  R0,R2,R3
       \       0x38   0x181B             ADDS     R3,R3,R0
       \       0x3A   0xFAD3 0xF754      UQSUB16  R7,R3,R4
       \       0x3E   0x19E7             ADDS     R7,R4,R7
         98            }
       \       0x40   0x1E6D             SUBS     R5,R5,#+1
       \       0x42   0xD1E3             BNE.N    ??findmaxv2_0
         99            if ((max>>16)>(max&0xFFFF)) return max>>16;
       \       0x44   0x0C38             LSRS     R0,R7,#+16
       \       0x46   0xB2BF             UXTH     R7,R7
       \       0x48   0x4287             CMP      R7,R0
       \       0x4A   0xBF28             IT       CS 
       \       0x4C   0x4638             MOVCS    R0,R7
        100            return max&0xFFFF;
       \       0x4E   0xE8BD 0x81F0      POP      {R4-R8,PC}       ;; return
        101          }
    

     

    Конечно, в реальной жизни надо подавить warning, который понятно почему возникает. И цикл минимально развернуть.

    Понятное дело, без знания asm'а такое не напишешь, но по сравнению с чистым asm'ом - не надо самому корячиться с прологами/эпилогами и всей остальной сопутствующей шляпой.

  22. Ну допустим так:

    #include <intrinsics.h>
    
    #define PROCESS4() acc=*s++; max=__UQSUB16(max,acc)+acc; max=__UQSUB16(max,acc>>32)+(acc>>32)
    
    unsigned long findmax(unsigned short *_s)
    {
      unsigned long max=0;
      unsigned long long acc;
      unsigned long long *s=(unsigned long long *)_s;
      for(unsigned long i=0; i<512/(4*4); i++)
      {
        PROCESS4();
        PROCESS4();
        PROCESS4();
        PROCESS4();
      }
      if ((max>>16)>(max&0xFFFF)) return max>>16;
      return max&0xFFFF;
    }
    Spoiler

         62          unsigned long findmax(unsigned short *_s)
    		     63          {
    		   \                     findmax: (+1)
    		   \        0x0   0xB470             PUSH     {R4-R6}
    		     64            unsigned long max=0;
    		     65            unsigned long long acc;
    		     66            unsigned long long *s=(unsigned long long *)_s;
    		     67            for(unsigned long i=0; i<512/(4*4); i++)
    		   \        0x2   0x4601             MOV      R1,R0
    		   \        0x4   0x2600             MOVS     R6,#+0
    		   \        0x6   0x2020             MOVS     R0,#+32
    		     68            {
    		     69              PROCESS4();
    		   \                     ??findmax_0: (+1)
    		   \        0x8   0xE8F1 0x2302      LDRD     R2,R3,[R1], #+8
    		   \        0xC   0xFAD6 0xF652      UQSUB16  R6,R6,R2
    		   \       0x10   0x1996             ADDS     R6,R2,R6
    		     70              PROCESS4();
    		   \       0x12   0xE8F1 0x4502      LDRD     R4,R5,[R1], #+8
    		   \       0x16   0xFAD6 0xF253      UQSUB16  R2,R6,R3
    		   \       0x1A   0x18D2             ADDS     R2,R2,R3
    		   \       0x1C   0xFAD2 0xF254      UQSUB16  R2,R2,R4
    		   \       0x20   0x18A2             ADDS     R2,R4,R2
    		   \       0x22   0xFAD2 0xF255      UQSUB16  R2,R2,R5
    		   \       0x26   0x1952             ADDS     R2,R2,R5
    		     71              PROCESS4();
    		   \       0x28   0xE8F1 0x4502      LDRD     R4,R5,[R1], #+8
    		   \       0x2C   0xFAD2 0xF254      UQSUB16  R2,R2,R4
    		   \       0x30   0x18A2             ADDS     R2,R4,R2
    		   \       0x32   0xFAD2 0xF255      UQSUB16  R2,R2,R5
    		   \       0x36   0x1952             ADDS     R2,R2,R5
    		     72              PROCESS4();
    		   \       0x38   0xE8F1 0x4502      LDRD     R4,R5,[R1], #+8
    		   \       0x3C   0xFAD2 0xF254      UQSUB16  R2,R2,R4
    		   \       0x40   0x18A2             ADDS     R2,R4,R2
    		   \       0x42   0xFAD2 0xF255      UQSUB16  R2,R2,R5
    		   \       0x46   0x1956             ADDS     R6,R2,R5
    		     73            }
    		   \       0x48   0x1E40             SUBS     R0,R0,#+1
    		   \       0x4A   0xD1DD             BNE.N    ??findmax_0
    		     74            if ((max>>16)>(max&0xFFFF)) return max>>16;
    		   \       0x4C   0x0C30             LSRS     R0,R6,#+16
    		   \       0x4E   0xB2B6             UXTH     R6,R6
    		   \       0x50   0x4286             CMP      R6,R0
    		   \       0x52   0xBF28             IT       CS 
    		   \       0x54   0x4630             MOVCS    R0,R6
    		     75            return max&0xFFFF;
    		   \       0x56   0xBC70             POP      {R4-R6}
    		   \       0x58   0x4770             BX       LR               ;; return
    		     76          }

    Понятное дело, что развернуть можно чуть больше, если сильно надо. Основной провал по сравнению с asm-вариантом - невозможность загрузить сразу кучу переменных при помощи LDM, потому LDRD в качестве оптимизации. Но это IAR такой, насколько я знаю, GCC вроде бы умеет.

     

    А так, конечно, да, по сравнению с тем, что может накалякать какой-нибудь залетный формошлеп - это магия ;)