Вчера я обнаружил катастрофическую проблему в clang
, когда пытался скомпилировать код для arm (по крайней мере, в android arm-v7a).Посмотрите этот небольшой код:
void init_c_32(uint8_t *ptr)
{
uint32_t tmp[SIZE];
memcpy(tmp, ptr, 33);
}
вот сгенерированный код сборки для вызова memcpy
здесь:
0x7903d714 <+20>: ldr r0, [sp, #0x10]
0x7903d716 <+22>: add r3, sp, #0x14
0x7903d718 <+24>: mov.w r12, #0x20
0x7903d71c <+28>: str r0, [sp, #0xc]
0x7903d71e <+30>: mov r0, r3
0x7903d720 <+32>: ldr r3, [sp, #0xc]
0x7903d722 <+34>: str r1, [sp, #0x8]
0x7903d724 <+36>: mov r1, r3
0x7903d726 <+38>: str r2, [sp, #0x4]
0x7903d728 <+40>: mov r2, r12
0x7903d72a <+42>: blx 0x7903d658 ; symbol stub for: __aeabi_memcpy
, который использует __aeabi_memcpy
и все будет хорошо для любого ptr
адрес.Теперь, если мы изменим тип аргумента на uint32_t *
, сгенерированный код сборки изменится следующим образом:
void init_c_32(uint32_t *ptr)
{
uint32_t tmp[SIZE];
memcpy(tmp, ptr, 33);
}
0x790456dc <+20>: ldr r0, [sp, #0x8]
0x790456de <+22>: add r3, sp, #0xc
0x790456e0 <+24>: ldm.w r0!, {r4, r5, r12, lr}
0x790456e4 <+28>: stm.w r3!, {r4, r5, r12, lr}
0x790456e8 <+32>: ldm.w r0, {r4, r5, r12, lr}
0x790456ec <+36>: stm.w r3, {r4, r5, r12, lr}
Этот код много оптимизирован и использует ldm.w
и stm.w
вместо memcpy
.Результат намного быстрее кода, но есть недостаток.Этот код не будет работать правильно с нечетными ptr
адресами и создает исключение SIGBUS
, которое является правильным на основе сгенерированного кода сборки..w
адресация ограничивает адресацию модели четными значениями, но, возможно, мы можем сказать, что это сделано намеренно, потому что мы определили аргумент как unit32_t *
, и мы говорим, что этот аргумент должен быть выровнен.
Но здесь возникает основная проблема .Проверьте следующий код:
void init_c_32(__packed uint32_t *ptr)
{
uint32_t tmp[SIZE];
memcpy(tmp, ptr, 33);
}
, как видите, событие, хотя мы указали uint32_t *
в качестве входного параметра, мы использовали спецификатор __packed
.Как указывает standard , __packed
говорит, что:
объекты упакованного типа читаются или пишутся с использованием выравниваемого доступа.
Но когда мы видимсгенерированный ассемблерный код, мы видим следующее:
0x78ec56dc <+20>: ldr r0, [sp, #0x8]
0x78ec56de <+22>: add r3, sp, #0xc
0x78ec56e0 <+24>: ldm.w r0!, {r4, r5, r12, lr}
0x78ec56e4 <+28>: stm.w r3!, {r4, r5, r12, lr}
0x78ec56e8 <+32>: ldm.w r0, {r4, r5, r12, lr}
0x78ec56ec <+36>: stm.w r3, {r4, r5, r12, lr}
Как видите, сгенерированный код не отличается в режиме, отличном от __packed
, и это противоречит стандарту ARM
.Вы по-прежнему не можете использовать нечетные адреса для ссылок, и вы получите исключение SIGBUS
.Я думаю, что в этом случае сгенерированный код должен быть аналогичен тому, когда мы используем uint8_t *
в качестве аргумента.
Я думаю, что это очень серьезная ошибка и может привести к неожиданным результатам, и любое хорошее решениеприветствуется.
Я использовал ndk 16 для создания этой проблемы, которая использует clang 5.0.3
в качестве компилятора.
Текущий обходной путь использует uint8_t *
в качестве ввода все время, что создает правильный код.Но с точки зрения эффективности, будет лучше, если эта проблема будет решена.