В настройках сборки для создания приложений UEFI отсутствует статическая библиотека вспомогательных функций, которые, как ожидается, MSVC code-gen будет доступна.Code-gen MSVC иногда вставляет вызовы вспомогательных функций, так же, как gcc делает для 64x64 умножения или деления на 32-битных платформах, или для различных других вещей.(например, popcount для целей без аппаратного popcnt.)
В этом случае удержание MSVC в менее глупом коде-гене (что само по себе хорошо) удаляет все виды использования вспомогательных функций для вашей кодовой базы.Это хорошо, но не исправляет ваши настройки сборки. Он может снова сломаться, если в будущем вы добавите код, которому нужен помощник .uint64_t shr(uint64_t a, unsigned c) { return a >> c; }
компилируется для включения вызова вспомогательной функции даже при -O2
.
Сдвиг на константу без оптимизации использует _aullshr
вместо встраивания как shrd
/ shr
. Эта точная проблема (неработающие -Od
сборки) повторяется с uint64_t x
;x >> 4
или что-то в вашем источнике.
(Я не знаю, где MSVC хранит свою библиотеку вспомогательных функций. Мы думаем, что это статическая библиотека, которую вы могли бы связать без введения зависимости DLL (невозможно)для UEFI), но мы не знаем, может ли он быть связан с каким-то кодом запуска CRT, с которым вам нужно избегать ссылок для UEFI.)
Проблема неоптимизированной и оптимизированной ясна сэтот пример.MSVC с оптимизацией не нуждается в вспомогательной функции, но его мозговой код -Od
code делает .
Для доступа к битовому полю MSVC, очевидно, использует правый сдвиг базового типачлен битового поля .В вашем случае вы сделали это 64-битным типом, а 32-битный x86 не имеет 64-битного целочисленного сдвига (за исключением использования MMX или SSE2).С -Od
даже при постоянных счетах он помещает данные в EDX: EAX, счетчик сдвигов в cl
(как для инструкций по сдвигу в x86) и вызывает __aullshr
.
__a
= ?? ull
= длинная строка без знака. shr
= сдвиг вправо (аналогично инструкции asm для x86 с тем же именем). - требуетсячисло сдвигов в
cl
, точно так же, как инструкции по сдвигу в x86.
Из проводника компилятора Godbolt, x86 MSVC 19.16 -Od
, с UINT64
в качестве члена битового поляtype.
;; from int Method1(unsigned __int64) PROC
...
; extract IssueStruct.Bits.field8
mov eax, DWORD PTR _IssueStruct$[ebp]
mov edx, DWORD PTR _IssueStruct$[ebp+4]
mov cl, 57 ; 00000039H
call __aullshr ; emulation of shr edx:eax, cl
and eax, 1
and edx, 0
;; then store that to memory and cmp/jcc both halves. Ultra braindead
Очевидно, что для постоянного сдвига и доступа только к 1 биту его легко оптимизировать, поэтому MSVC фактически не вызывает вспомогательную функцию при -O2
.Это все еще довольно неэффективно, хотя!Он не может полностью оптимизировать 64-разрядность базового типа, даже если ни одно из битовых полей не шире 32.
; x86 MSVC 19.16 -O2 with unsigned long long as the bitfield type
int Method1(unsigned __int64) PROC ; Method1, COMDAT
mov edx, DWORD PTR _Data$[esp] ; load the high half of the inputs arg
xor eax, eax ; zero the low half?!?
mov ecx, edx ; copy the high half
and ecx, 33554432 ; 02000000H ; isolate bit 57
or eax, ecx ; set flags from low |= high
je SHORT $LN2@Method1
and edx, 536870912 ; 20000000H ; isolate bit 61
xor eax, eax ; re-materialize low=0 ?!?
or eax, edx ; set flags from low |= high
je SHORT $LN2@Method1
mov eax, 1
ret 0
$LN2@Method1:
xor eax, eax
ret 0
int Method1(unsigned __int64) ENDP ; Method1
Очевидно, что это действительно глупо, материализуя 0
для младшей половины вместопросто игнорировать это. MSVC работает намного лучше, если мы изменим тип элемента битового поля на unsigned
.(В связи с Godbolt я изменил это значение на bf_t
, чтобы я мог использовать typedef отдельно от UINT64, оставив его для другого члена объединения.)
Со структурой, основанной на unsigned field : 1
членов битового поля, MSVC не нужен помощник на -Od
И он даже делает лучший код на -O2
, , так что вы обязательно должны сделать это в своемреальный производственный код. Используйте элементы uint64_t
или unsigned long long
только для полей, которые должны быть шире, чем 32-битные, , если вы заботитесь о производительности на MSVC, который, очевидно, имеет ошибку пропущенной оптимизации с 64-битными типами для элементов битовых полей.
;; MSVC -O2 with plain unsigned (or uint32_t) bitfield members
int Method1(unsigned __int64) PROC ; Method1, COMDAT
mov eax, DWORD PTR _Data$[esp]
test eax, 33554432 ; 02000000H
je SHORT $LN2@Method1
test eax, 536870912 ; 20000000H
je SHORT $LN2@Method1
mov eax, 1
ret 0
$LN2@Method1:
xor eax, eax
ret 0
int Method1(unsigned __int64) ENDP ; Method1
Я мог бы реализовать его без ветвей, как ((high >> 25) & (high >> 29)) & 1
с 2 shr
инструкциями и 2 and
инструкциями (и mov
).Однако, если это действительно предсказуемо, ветвление является разумным и нарушает зависимость данных.clang делает хорошую работу здесь, используя not
+ test
для проверки обоих битов одновременно.(И setcc
, чтобы снова получить результат как целое число).Это имеет большую задержку, чем моя идея, особенно на процессорах без MOV-устранения.У clang также нет пропущенной оптимизации для битовых полей на основе 64-битных типов.В любом случае мы получаем один и тот же код.
# clang7.0 -O3 -m32 regardless of bitfield member type
Method1(unsigned long long): # @Method1(unsigned long long)
mov ecx, dword ptr [esp + 8]
xor eax, eax # prepare for setcc
not ecx
test ecx, 570425344 # 0x22000000
sete al
ret
Стандарт кодирования UEFI:
Стандарт кодирования EDK II 5.6.3.4 Битовые поля говорит, что:
- Битовые поля могут быть только типа
INT32
, подписанный INT32
, UINT32
или имя определения типа, определенное как один из трех INT32
вариантов.
Я не знаю, почему они составляют эти имена "INT32", когдаУ С99 уже отлично получается int32_t
.Также неясно, почему они установили это ограничение.Возможно, из-за ошибки пропущенной оптимизации MSVC?Или, возможно, чтобы помочь пониманию программиста-человека, запретив некоторые «странные вещи».
gcc и clang не предупреждают о unsigned long long
как типе битового поля, даже в 32-битном режиме и с -Wall -Wextra -Wpedantic
вРежим C или C ++.Я не думаю, что у ISO C или ISO C ++ есть проблема с этим.
Далее, Следует ли препятствовать использованию битовых полей типа int? указывает, что обычный int
какТип битового поля не должен поощряться, потому что подпись определяется реализацией.И что в стандарте ISO C ++ обсуждаются типы битовых полей от char
до long long
.
Я думаю, что ваше предупреждение MSVC о битовых полях, отличных от int
, должно быть из какого-то пакета обеспечения соблюдения стандартов кодирования, потому чтообычный MSVC на Godbolt этого не делает даже с `-Wall.
предупреждение C4214: используется нестандартное расширение: типы битовых полей, отличные от int