Ответ 1
Поддержка плавающей точки в дистрибутивах ARM Linux не является тривиальной. Из-за этого вы должны использовать инструментальную цепочку, соответствующую вашей системе, которая является операционной системой и оборудованием, и использовать правильные компиляционные ключи.
Прежде всего вам нужно понять соглашение о вызове ARM, которое составляет около , как аргументы передаются при вызове функции?. ARM, являющаяся архитектурой RISC, может работать только с регистрами. Нет никаких инструкций, непосредственно манипулирующих памятью. Если вам нужно изменить значение в памяти, вам сначала нужно загрузить его в регистр, изменить его, затем вам нужно сохранить его обратно в память.
Когда вы вызываете функцию, вам может потребоваться передать ему аргументы, вы можете поместить аргументы в стек (память), но поскольку ARM может работать только с регистрами, первое, что, вероятно, сделает ваша функция, будет загружать их обратно в регистры. Чтобы избежать этого, конвенция о вызове ARM использует регистры для передачи аргументов. Однако, поскольку ARM имеет ограниченное количество регистров, соглашение о вызове также диктует вам использовать только первые четыре (r0-r3) регистра для первых четырех аргументов, остальные должны по-прежнему использовать стек, который должен быть передан.
Во-вторых, ранние ядра ARM не имеют поддержки с плавающей запятой, операции, реализованные в программном обеспечении. (Это то, что по-прежнему поддерживается через gcc -mfloat-abi=soft
.)
Мы можем легко продемонстрировать, что это означает с помощью следующего фрагмента.
float pi2(float a) {
return a * 3.14f;
}
Компиляция с помощью -c -O3 -mfloat-abi=soft
и obdump
ing дает нам
00000000 <pi2>:
0: f24f 51c3 movw r1, #62915 ; 0xf5c3
4: b508 push {r3, lr}
6: f2c4 0148 movt r1, #16456 ; 0x4048
a: f7ff fffe bl 0 <__aeabi_fmul>
e: bd08 pop {r3, pc}
Как вы можете видеть (на самом деле это не видно:)) pi2
получает свой параметр в r0
, заполняет pi constant
на r1
и использует __aeabi_fmul
для их умножения и возврата результата в r0
, Поскольку __aeabi_fmul
также использует одно и то же соглашение о вызовах, информация о r0
не отображается. Вся наша функция позволяет заполнить r1
и передать ее на __aeabi_fmul
.
Когда плавающая поддержка аппаратного обеспечения добавлена в ARM (опять же из-за стиля архитектуры), у нее появился собственный набор регистров (s0, s1,...).
Если мы скомпилируем тот же фрагмент с -c -O3 -mfloat-abi=softfp
и дамп, мы получим
00000000 <pi2>:
0: eddf 7a04 vldr s15, [pc, #16] ; 14 <pi2+0x14>
4: ee07 0a10 vmov s14, r0
8: ee27 7a27 vmul.f32 s14, s14, s15
c: ee17 0a10 vmov r0, s14
10: 4770 bx lr
12: bf00 nop
14: 4048f5c3 .word 0x4048f5c3
Как вы теперь видите, компилятор не создает вызов __aeabi_fmul
, но вместо этого создает инструкцию vmul.f32
после перемещения аргумента, расположенного в r0
, на s14
и заполняет 3.14
на s15
, После команды умножения он возвращает результат, доступный в s14
, назад к r0
, поскольку любой вызывающий объект этой функции ожидает его из-за соглашения о вызовах.
Теперь, если вы думаете, что pi2
как библиотека, предоставленная вам какой-либо третьей стороной, вы можете понять, что и реализация soft и softfp делает то же самое для вас, и вы можете использовать их взаимозаменяемо. Если система предоставляет их вам, вам все равно, будет ли ваше приложение работать в системе с поддержкой аппаратных средств с плавающей запятой или нет. Это было неплохо, когда старое программное обеспечение работало на новом оборудовании.
Однако, сохраняя совместимость, этот подход вводит накладные расходы движущихся значений между регистрами ARM и регистрами FP. Это, очевидно, влияет на производительность и обращается с помощью нового соглашения о вызовах, называемого hard
на gcc
. Это новое соглашение гласит, что если у вас есть аргументы с плавающей запятой в вашей функции, вы можете использовать регистры с плавающей запятой, чередующиеся с нормальными, а также вы можете возвращать значения с плавающей запятой в регистр с плавающей запятой s0
.
Снова, если мы скомпилируем наш фрагмент с -c -O3 -mfloat-abi=hard
и дамп, мы получим
00000000 <pi2>:
0: eddf 7a02 vldr s15, [pc, #8] ; c <pi2+0xc>
4: ee20 0a27 vmul.f32 s0, s0, s15
8: 4770 bx lr
a: bf00 nop
c: 4048f5c3 .word 0x4048f5c3
Вы можете видеть, что регистров нет. Аргумент pi2
передается в s0
, компилятор создал код для заполнения 3.14
в s15
и использует vmul.f32 s0, s0, s15
для получения результата, который мы хотим в s0
.
Большая проблема с этим новым соглашением заключается в том, что вы улучшаете код, созданный компилятором, вы полностью убиваете совместимость. Вы не можете ожидать, что приложение, построенное с помощью соглашения hard
, будет работать с библиотеками, созданными для soft/softfp
, и приложение, созданное для softfp, не будет работать с библиотеками, созданными для жестких задач.
Для получения дополнительной информации о вызовах вы должны проверить сайт ARM.