Единственный способ получить данные между x87 и XMM - перенаправить их в память.например, movsd [rsp-8]
/ fld qword [rsp-8]
для использования красной зоны.
Но вам вообще не нужно использовать x87, и не стоит, если вы хотите, чтобы он был эффективным.
Если у вас SSE4.1, используйте roundsd
для округления до целого числа.
rint
: roundsd xmm0,xmm0, 0b0100
- текущий режим округления (бит 2 =1) , флаг неточного исключения устанавливается, если результат! = Вход (бит 3 = 0). nearbyint
: roundsd xmm0,xmm0, 0b1100
текущий режим округления (бит 2 = 1), неточное исключение исключено(бит 3 = 1). roundsd xmm0,xmm0, 0b1000
: переопределение режима округления (бит 2 = 0) до _MM_FROUND_TO_NEAREST_INT
(биты [1: 0] = 00) .См. roundpd
документы для таблицы.Неточное исключение исключено (бит 3 = 1).
Без SSE4.1 для roundsd , посмотрите, что glibc's rint
делает: добавляет 2^52
(битовая комбинация 0x43300000, 0x00000000
), делая число настолько большим, что ближайшие представимые double
с являются целыми числами.Таким образом, обычное округление FP до ближайшего представимого значения округляется до целого числа. IEEE binary64 double
имеет 52 явных битов мантиссы (иначе значимых), поэтому размер этого числа равен , а не .
(Для отрицательных входных данных этоиспользует -2^52
)
Вычитание, которое снова дает округленную версию вашего исходного числа.
Реализация glibc проверяет для некоторых особых случаев (таких как Inf и NaN) и для показателей меньше, чем0 (т. Е. Входы с магнитудой меньше 1,0) копирует в знаковый бит ввода.(Я полагаю, что -0,499 округляется до -0,0 вместо 0, и чтобы убедиться, что 0,499 будет округлять до +0, а не -0.)
Простой способ реализовать это с помощью SSE2 - изолироватьзнаковый бит ввода с pand xmm0, [signbit_mask]
, затем ИЛИ в битовой комбинации FP 0x43300000 ..., что дает вам +- 2^52
.
default rel
;; UNTESTED. IDK if the SIGNBIT_FIXUP does anything other than +-0.0
rint_sse2:
;movsd xmm1, [signbit_mask] ; can be cheaply constructed on the fly, unlike 2^52
;pand xmm1, xmm0
pcmpeqd xmm1, xmm1
psrlq xmm1, 1 ; 0x7FFF...
%ifdef SIGNBIT_FIXUP
movaps xmm2, xmm1 ; copy the mask
%endif
andnps xmm1, xmm0 ; isolate sign bit
%ifdef SIGNBIT_FIXUP
movaps xmm3, xmm1 ; save another copy of the sign bit
%endif
orps xmm1, [big_double] ; +-2^52
addsd xmm0, xmm1
subsd xmm0, xmm1
%ifdef SIGNBIT_FIXUP
andps xmm0, xmm2 ; clear the sign bit
orps xmm0, xmm3 ; apply the original sign
%endif
ret
section .rodata
align 16
big_double: dq 0x4330000000000000 ; 2^52
; a 16-byte load will grab whatever happens to be next
; if you want a packed rint(__m128d), use times 2 dq ....
Особенно, если вы пропустите SIGNBIT_FIXUP
, этодовольно дешевый, не намного дороже, чем roundsd
2 моп с точки зрения задержки FP.(roundsd
имеет то же время ожидания, что и addsd
+ subsd
на большинстве процессоров. Это почти наверняка не совпадение, но оно позволяет избежать отдельных мопов для сортировки знака).