[Программирование, Assembler] Преобразование строки символов в число двойной точности (double)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Преобразование строки символов в число двойной точности на MASM64 без FPU на SSE4.1 форматированное по правилам ML64.EXE то есть длиной до 32 символов.
1. Настройки компиляции, адресации и соглашения о вызовах.
SPL
1.1. Передача параметров в процедуру и обратно.
SPL
В соответствии с x64 software conventions будем считать что указатель на начало Числовой строки подлежащие конвертированию расположено в RCX.
1.2. Адресация и размерность кода.
SPL
Будем использовать x64 битный код при x32 битной адресации. Такой способ адресации позволяет использовать преимущества обоих диалектов. Для установки указанного режима необходимо указать директиву /LARGEADDRESSAWARE:NO в линковщику.
2. Текстовые константы и псевдонимы.
SPL
2.1. Текстовые константы
SPL
Для удобства работы со стеком создаем текстовую константу которая по сути выполняет роль имени (идентификатора) локальной переменной не определенного типа и «произвольного» размера:
; псевдонимы операндов #region
BUFF_STR equ esp - xmmword * 4
; #endregion
2.2. Псевдонимы переменны и регистров.
SPL
Для удобства работы с регистрами создаем блок текстовых констант которые по сути будут представлять собой имена переменных неопределенного типа и размером в двойное слово DWORD или INT для тех кому более привычен синтаксис СРР которые не имеют своего собственного отображения в памяти, а все время своего существования размещаются в регистре с которым они ассоциированы, при этом некоторые «переменны» являются по сути «объединениями» и размещаются в одних и тех же регистрах присутствуя в них на разных этапах исполнения программы:
; псевдонимы регистров #region
CUR_CHAR equ ecx ; абсолютная позиция текущего символа
DOT_CHAR equ edx ; относительная позиция символа точки
HASH_STR equ r8d ; хеш символов Числовой строки
END_CHAR equ HASH_STR ; относительная позиция последнего символа
N_Z_CHAR equ r9d ; относительная позиция символ не нулевого числа
OFF_CHAR equ N_Z_CHAR ; смешение дробной части относительно начала Числовой строки
END_FRAC equ r10d ; относительное положение последнего символа Числовой строки
EXP_CHAR equ END_FRAC ; текущий относительный символ строки Экспоненты
LEN_NUMB equ r11d ; длина значимой части Числа
LEN_CELL equ LEN_NUMB ; длина целой части Числа
HASH_MUL equ ebx ; значение экспоненты в десятичной системе
MANT_ARG equ r8 ; мантисса аргумент множителя
LOGB_ARG equ r9d ; порядок аргумента множителя
MANT_MUL equ r10 ; мантисса множителя
LOGB_MUL equ r11d ; порядок множителя
; #endregion
3. Секция данных
SPL
Создаем секцию данных. Стоит отметить что самая «лучшая» секция данных это такая секция которая размещена а секции кода, то есть при любой возможности необходимо избегать создания секции данных и размещать их непосредственно в секции кода в аргументах содержащихся непосредственно в инструкциях, к сожалению SIMD команды не допускают непосредственной размещения данных в инструкциях секции кода, что вынуждает создавать секцию данных:
.data ; #region
Xmm_HT byte 10h dup (09h)
Xmm_CR byte 10h dup (0Dh)
Xmm_SP byte 10h dup (20h)
Xmm_SL byte 10h dup ('/')
Xmm_30 byte 10h dup ('0')
Xmm_39 byte 10h dup ('9')
Xmm_0001 word 8 dup (010Ah)
Xmm_0010 dword 4 dup (10064h)
Xmm_0100 qword 2 dup (100002710h)
Mask_001 word 0044h, 0944h, 0D44h, 2044h, 0046h, 0946h, 0D46h, 2046h
Mask_010 word 0064h, 0964h, 0D64h, 2064h, 0066h, 0966h, 0D66h, 2066h
Mul_0001 qword 0E8D4A51000h
Plus word 2B00h
; тестовая строка
string byte ' ', 0Dh, 0Ah, '+-0098765432109876540.09876e-0248 '
; #endregion
Назначение определенных констант будет пояснено ниже в ходе выполнения процедуры.
4. Секция кода.
SPL
4.1. Поиск начала Числовой подстроки.
SPL
4.1.1. Пропуск обобщенных пробелов
SPL
4.1.1.1. Вход в цикл пропуска обобщенного пробела.
SPL
— сравниваем байты регистра ХММ3 самими собой в результате чего все байт ХММ3 принимают значение -1.
— уменьшаем указатель адреса первого символа в CUR_CHAR на длину ХММ регистра.
— увеличиваем указатель адреса первого символа в CUR_CHAR на длину ХММ регистра.
pcmpeqb xmm3, xmm3
sub CUR_CHAR, xmmword
@@: add CUR_CHAR, xmmword
Таким образом при начальном входе в цикл обработки указатель текущего символа будет установлен на начало строки, при последующих входах в цикл он будет смещаться на длину ХММ-регистра, то есть на 15 байт. Такой способ организации начала цикла, при котором инкремент расположен в начале цикла, позволяет значительно упростить выход из цикла сведя его к команде проверки условия и условному переходу. В противном случае при размещении команда инкремента и проверки условий в конце цикла эти инструкции конфликтовали бы в части изменения флагов процессора что избыточно усложнило бы выход из цикла.
4.1.1.2. Проверка строки символов.
SPL
— загружаем строку символов в ХММ0 и копируем ее в ХММ1/ХММ2 получая три копии строки.
— сравниваем три копии строки содержащиеся в регистрах с тремя строками размещенными в памяти, равномерно заполненными символами пробел/табуляция/возврат каретки
— складываем полученные результаты в регистр ХММ0.
movdqu xmm0,[CUR_CHAR]
movdqa xmm1, xmm0
movdqa xmm2, xmm0
pcmpeqb xmm0, xmmword ptr Xmm_SP
pcmpeqb xmm1, xmmword ptr Xmm_HT
pcmpeqb xmm2, xmmword ptr Xmm_CR
paddb xmm0, xmm1
paddb xmm0, xmm2
В результате байты регистра ХММ0 равные любому из трех символов обобщенного пробела принимают значение -1 а не равные 0. Векторное сравнение позволяет многократно повысить скорость сканирования строки не только за счет параллельного сравнения но и за счет исключения множества условных переходов характерных для «классических» способов.
4.1.1.3. Проверка результата и выход из цикла.
SPL
— командой PTEST выполняет операцию AND над байтами ХММ0 и ХММ3 и в случае если все байты результата установлены в -1 устанавливаем флаг переноса CF=1.
— если флаг переноса CF=1 то следовательно в сканируемой строке отсутствуют символы отличные от обобщенного пробела и необходимо вернуться в начало цикла.
ptest xmm0, xmm3
jc @b ; повторный пропуск обобщенного пробела
; #endregion
В результате сканирование строки продолжается до тех пор пока не будет найден символ отличный от обобщенного пробела. Скорость сканирования можно дополнительно увеличить если разместить строки равномерно заполненные символами пробел/табуляция/возврат каретки в старших регистрах SIMD, но в соответствии с соглашением вызова х64 это потребует предварительно сохранить их значение в память, а при выходе из функции восстановить, что учитывая ожидаемое время сканирования в один проход будет не оправданно.
4.1.2. Позиция первого символа не равного обобщенному пробелу.
SPL
-копируем старшие биты всех байтов регистра ХММ0 в EAX, теперь все биты соответствующие символам обобщенного пробела установлены в значение 1.
— инвертируем EAX, теперь биты соответствующие символам не равным обобщенному пробелу установлен в значение 1.
— сканируем биты регистра EAX от младшего к старшему в поиске первого бита установлено в значение 1, и результат равный номеру бита, помещаем в этот же регистр.
— добавляем значение EAX к CUR_CHAR и получаем указатель на первый символ отличный от обобщенного пробела.
; Позиция первого символа не равного обобщенному пробелу #region
pmovmskb eax, xmm0
not eax
bsf eax, eax
add CUR_CHAR, eax
; #endregion
4.1.3. Проверка на сочетание символов новой строки.
SPL
— устанавливаем флаг нуля ZF=1 если сочетание двух первых символов следующих после символа обобщенного пробела равно сочетанию символов новая строка.
— устанавливаем младший байт регистра EAX в значение 1 если флаг нуля ZF=1 и в значение 0 при всех остальных вариантах.
— складываем значение EAX и CUR_CHAR и получаем указатель на первый символ отличный от обобщенного пробела с учетом сочетания символов новой строки.
; позиция первого символа не равного обобщенному пробелу #region
cmp word ptr[CUR_CHAR - byte], 0A0Dh
setz al
add CUR_CHAR, eax
; #endregion
Таким образом если сочетание двух первых символов следующих после символа обобщенного пробела равно сочетанию символов новая строка то второй символ после обобщённого пробела будет проигнорирован, в противном случае он будет подвергнут дальнейшему анализу.
4.1.4. Тест обрыва строки.
SPL
— копируем первый символ отличный от обобщенного пробела в регистр EAX одновременно расширяя его до двойного слова.
— устанавливаем флаг нуля ZF=1 если значение EAX равно 0.
— если флаг нуля ZF=1 то следовательно имеет место обрыв строки и необходимо выйти из процедуры вернув код ошибки:
; тест обрыва строки #region
movzx eax, byte ptr[CUR_CHAR]
test al, al
jz ErrorExit ; обрыв строки
; #endregion
4.2. Проверка знака Числа.
SPL
4.2.1. Проверка символа Плюс.
SPL
— сравниваем регистр AL с символом плюс.
— устанавливаем AL в значение 1 если AL равен символу плюс и 0 при любом другом значении символа.
— добавляем значение EAX к CUR_CHAR.
; Проверка символа минус/плюс #region
cmp al, '+'
setz al
add CUR_CHAR, eax
В результате если текущий символ равен символу плюс то позиция текущего символа будет смещена на следующий символ, во всех остальных случаях символ будет подвергнут повторному анализу.
4.2.2. Проверка символа минус.
SPL
— сравниваем текущий символ с символом минус.
— устанавливаем значение AL в 1 если текущий символ равен символу минус и 0 при любом другом значении.
— добавляем значение EAX к CUR_CHAR.
— добавляем значение EAX к регистру ESP.
cmp byte ptr[CUR_CHAR], '-'
setz al
add CUR_CHAR, eax
add esp, eax
; #endregion
В результате если текущий символ равен символу минус то позиция текущего символа будет смещена на следующий символ, а значение регистра стека ESP увеличено на 1, во всех остальных случаях символ будет подвергнут повторному анализу, а значение регистра стека останется без изменений. Прямое изменение значения регистра указателя стека ESP считается крайне опасным действием чреватым непредсказуемыми ошибками, но я практикую «агрессивный» подход и считаю что не бывает «плохого» или «хорошего» кода, бывают хорошие и плохи программисты, хорошие пишут так как будто никаких правил нет вообще но результат при этом такой как будто они соблюдают их все, а плохие они просто плохие.
4.3. Сканирование символов Числовой строки.
SPL
4.3.1. Сканирование старшей части Числовой строки.
SPL
4.3.1.1. Проверка первого условия.
SPL
— загружаем старшую часть Числовой строки, со смешением на 16 символов от начала Числовой строки, в ХММ0.
— копируем старшую часть Числовой строки в ХММ1 и ХММ2.
— сравниваем регистр ХММ0 со строкой в памяти равномерно заполненной символами 9.
— копируем регистр ХММ0 в регистр ХММ1.
; сканирование символов Числовой строки #region
movdqu xmm0,[CUR_CHAR + xmmword]
movdqa xmm2, xmm0
movdqa xmm3, xmm0
pcmpgtb xmm0, xmmword ptr Xmm_39
movdqa xmm1, xmm0
В результат получаем две копии строки в регистрах ХММ0 и ХММ1 в которых все байты символов которые были больше символа 9 установлены в значение -1, а все остальные в значение 0.
4.3.1.2. Проверка второго условия.
SPL
— сравниваем регистр ХММ2 со строкой в памяти равномерно заполненной символами косая черта, в результате чего все байты регистра ХММ2 содержавшие символы больше и равно символу 0 установлены в значение -1, а меньше в значение 0.
— командой PANDN инвертируем баты регистра ХММ0 и выполняем логическую операцию AND над байтами ХММ0 и ХММ2 помещая результат в регистр ХММ0.
pcmpgtb xmm2, xmmword ptr Xmm_SL
pandn xmm0, xmm2
В результате все байты регистра ХММ0 содержащие символы в диапазоне от 0 включительно до 9 включительно, то есть цифры, принимают значения -1 а все остальные 0.
4.3.1.3. Проверка третьего условия.
SPL
— сравниваем регистр ХММ3 со строкой в памяти равномерно заполненной символами 0, в результате чего все байты регистра ХММ3 содержавшие символы больше и равно символу 1 установлены в значение -1, а меньше в значение 0.
— командой PANDN инвертируем баты регистра ХММ1 и выполняем логическую операцию AND над байтами ХММ1 и ХММ3 помещая результат в регистр ХММ1.
pcmpgtb xmm3, xmmword ptr Xmm_30
pandn xmm1, xmm3
В результате все байты регистра ХММ1 содержащие символы в диапазоне от 1 включительно до 9 включительно, то есть значащие цифры, принимают значения -1 а все остальные 0.
4.3.1.4. Сохранение старших частей хеша строки.
SPL
— копируем старшие биты байтов регистра ХММ0 в регистр HASH_STR.
— копируем старшие биты байтов регистра ХММ1 в регистр N_Z_CHAR
pmovmskb HASH_STR, xmm0
pmovmskb N_Z_CHAR, xmm1
В результате младшие 16 бит регистра HASH_STR соответствуют 16 старшим байтам Числовой строки, при этом биты соответствующие символам содержащим цифры принимают значения 1 а все остальные 0, а младшие 16 бит регистра N_Z_CHAR соответствуют 16 старшим байтам Числовой строки, при этом биты соответствующие символам содержащим значащие числа, принимают значения 1 а все остальные 0.
4.3.2. Сканирование младшей части Числовой строки.
SPL
movdqu xmm0,[CUR_CHAR]
movdqa xmm2, xmm0
movdqa xmm3, xmm0
pcmpgtb xmm0, xmmword ptr Xmm_39
movdqa xmm1, xmm0
pcmpgtb xmm2, xmmword ptr Xmm_SL
pcmpgtb xmm3, xmmword ptr Xmm_30
pandn xmm0, xmm2
pandn xmm1, xmm3
4.3.3. Объединение старших и младших Хешей строки.
SPL
— копируем старшие биты байтов регистра ХММ0 в EAX.
— сдвигаем младшие 16 бит HASH_STR в старшую часть HASH_STR.
— складываем значение HASH_STR и EAX.
— копируем старшие биты байтов регистра ХММ1 в EAX.
— сдвигаем младшие 16 бит N_Z_CHAR в старшую часть N_Z_CHAR.
— складываем значение N_Z_CHAR и EAX.
pmovmskb eax, xmm0
shl HASH_STR, xmmword
add HASH_STR, eax
pmovmskb eax, xmm1
shl N_Z_CHAR, xmmword
add N_Z_CHAR, eax
В результате HASH_STR содержит хеш Числовой строки в котором биты соответствующие символам цифр установлены в значение 1 а в се остальные в 0, при этом номера битов соответствуют номерам символов от начала строки начиная с нуля, а N_Z_CHAR содержит хеш Числовой строки в котором биты символов соответствующие значащих цифр установлены в значение 1, а все остальные в 0, при этом номер бита соответствуют номерам символов от начала строки начиная с нуля.
4.4. Обработка целой части Числа.
SPL
4.4.1. Проверка первого символа.
SPL
— сканируем HASH_STR от младшего бита к старшему в поисках первого бита равного 1, результат помещаем в EAX и устанавливаем флаг нуля ZF=1 если все биты равны нулю.
— если флаг нуля ZF=1 то значит строка не содержит ни одного символа цифры и необходимо выйти из процедуры вернув код ошибки.
— устанавливаем флаг нуля ZF=0 если полученный результат отличен от нуля.
— если флаг нуля ZF=0 то значит первый символ строки не является цифрой и необходимо выйти из процедуры вернув код ошибки.
; проверка первого символа #region
bsf eax, HASH_STR
jz ErrorExit
test eax, eax
jnz ErrorExit ; первый символ не цифра
; #endregion
В результат проверяем содержит ли Числовой строки хотя бы один символ цифры и является ли первый символ Числовой строки цифрой. Особенностью данного участка кода в нестандартном поведении инструкции BSF которая проявляется в работе с флагом нуля, а именно если при сканирование первым битом установленным в значение 1 окажется бит с порядковым номером 0 то BSF установит значение регистра назначения в 0 но при этом установит флаг нуля ZF=0 как будто в регистре содержится число отличное от нуля, если же инструкция не обнаружит ни одного бита в значении 1, то регистр назначение не будет подвергнут изменению а флаг нуля будет установлен в ZF=1.
4.4.2. Поиск символа точки разделяющий целую и дробную части Числа.
SPL
— инвертируем значение HASH_STR в результате чего теперь каждый бит установленный в 1 сигнализирует о символе НЕ цифре.
— сканируем HASH_STR от младшего бита к старшему, результат помещаем в DOT_CHAR и устанавливаем флаг нуля ZF=1 если все биты HASH_STR равны нулю.
— если флаг нуля ZF=1 то значит строка не содержит ни одного символа отличного от цифры и необходимо выйти из процедуры вернув код ошибки.
— сравниваем символ отличный от цифры с символом точка и устанавливаем флаг ZF=0 если они не равны.
— если флаг нуля ZF=0 то значит первый символ отличный от цифры не равен символу точка и необходимо выйти из процедуры вернув код ошибки.
; поиск символа точки разделяющий целую и дробную части Числа #region
not HASH_STR
bsf DOT_CHAR, HASH_STR
jz ErrorExit ; точки не обнаружено
cmp byte ptr[CUR_CHAR + DOT_CHAR], '.'
jnz ErrorExit ; символ не является точкой
; #endregion
4.4.3. Сохранение значащей части Числа.
SPL
— копируем N_Z_CHAR в EAX
— сканируем N_Z_CHAR от младшего бита к старшему и помещаем результат в этот же регистр.
— сохраняем в память строку из четырех нулей 0000 по адресу на 1 (один) байт меньше адреса указанного в BUFF_STR.
— сохраняем в регистр ХММ0 старшую часть строку символов начинающийся с первого символа значащей цифры, на который указывает N_Z_CHAR, игнорирую таким образом ведущие нули.
— сохраняем в память старшую часть строки символов по адресу указанному в BUFF_STR.
— сохраняем в регистр ХММ0 младшую часть строки символов на которую указывает N_Z_CHAR со смещение в 16 байт.
— сохраняем в память младшую часть строки символов начиная с первого символа значащей цифры по адресу указанному в BUFF_STR со смещение в 16 байт.
; сохранение значащей части Числа #region
mov eax, N_Z_CHAR
bsf N_Z_CHAR, N_Z_CHAR
mov dword ptr[BUFF_STR - byte], 30303030h
movdqu xmm0,[CUR_CHAR + N_Z_CHAR]
movdqu [BUFF_STR + 00000000], xmm0
movdqu xmm0,[CUR_CHAR + N_Z_CHAR + xmmword]
movdqu [BUFF_STR + 00000000 + xmmword], xmm0
; #endregion
В результате сохраняем в память строку из 32 символов начиная с первого символа значащей цифры на которую указывает N_Z_CHAR по адресу указанному в BUFF_STR. При этом указанная строка может содержать символ точки и иные символы не относящиеся к цифрам.
4.5. Обработка дробной части Числа.
SPL
4.5.1. Загрузка дробной части Числовой строки.
SPL
— загружаем старшую часть Числовой строку, следующую сразу после точки, на которую указывает DOT_CHAR в регистр ХММ0.
— загружаем младшую часть Числовой строку, следующую сразу после точки, на которую указывает DOT_CHAR со смещение 16 байт от начала Числовой строки в регистр ХММ1.
; загрузка дробной части Числовой строки #region
movdqu xmm0,[CUR_CHAR + DOT_CHAR + byte]
movdqu xmm1,[CUR_CHAR + DOT_CHAR + byte + xmmword]
; #endregion
4.5.2. Поиск конца дробной части Числа.
SPL
— сбрасываем в HASH_STR бит указанный в DOT_CHAR удаляя его из хеша, теперь при следующем сканировании бит указывающий на точку будет проигнорирован.
— сканируем HASH_STR от младшего бита к старшему помещая результат в этот же регистр устанавливая флаг нуля ZF=1 если все биты равны нулю.
— если флаг нуля ZF=1 то значит дробная часть строки не имеет корректного окончания и необходимо выйти из процедуры вернув код ошибки.
; поиск конца дробной части Числа #region
btr HASH_STR, DOT_CHAR
bsf END_FRAC, HASH_STR
jz ErrorExit
; #endregion
В результате в EXP_CHAR находиться указатель на первый символ экспоненты или окончание Числа относительно начала Числа.
4.5.3. Количество значащих символов Числа.
SPL
4.5.3.1. Проверка наличия значащих цифр.
SPL
— сравниваем END_FRAC и N_Z_CHAR и устанавливаем флаг переполнения CF=1 если N_Z_CHAR больше END_FRAC.
— копируем END_FRAC в N_Z_CHAR если CF=1.
; количество значащих символов Числа #region
cmp END_FRAC, N_Z_CHAR
cmovc N_Z_CHAR, END_FRAC
В результате если и целая и дробная часть Числа состоят из одних нулей а первый символ значащей цифры находиться за пределами дробной и целой части числа, о чем свидетельствует факт того что N_Z_CHAR больше END_FRAC, то присваиваем N_Z_CHAR значение END_FRAC то есть указателя на первый символ экспоненты или окончания числа.
4.5.3.2. Подсчет количества значащих символов Числа.
SPL
— сравниваем N_Z_CHAR и DOT_CHAR и если N_Z_CHAR меньше DOT_CHAR, то есть первая значащая цифра расположен раньше точки, что означает что у числа существует целая часть, устанавливаем флаг переноса CF=1.
— копируем в LEN_NUMB указатель на первый символ экспоненты или окончания Числа содержащийся в END_FRAC.
— вычитаем из LEN_NUMB указатель на первую значащую цифру содержащуюся в N_Z_CHAR и флаг переноса CF.
cmp N_Z_CHAR, DOT_CHAR
mov LEN_NUMB, END_FRAC
sbb LEN_NUMB, N_Z_CHAR
; #endregion
В результате в LEN_NUMB содержится значение количества цифр Числа начиная с первой значащей цифры без учета символа точки, то есть исключительно количество символов соответствующих цифрам, символ точки в подсчете не учитывается даже если число пересекает точку.
4.5.4. Сохранение дробной части Числа.
SPL
— вычитаем из DOT_CHAR значение N_Z_CHAR и устанавливаем флаг знака SF=0, если полученное число положительное.
— помещаем в OFF_CHAR число 20 равное количеству символов которое будет в дальнейшем использованы для создания мантиссы.
— Если флаг знака SF=0 то значит число имеет целой части и необходимо скопировать DOT_CHAR в OFF_CHAR.
— сохраняем в память старшую часть строки следующей сразу за символом точки со смещением указанным в OFF_CHAR по адресу указанному в BUFF_STR.
— сохраняем в памяти младшую часть строки следующей сразу за символом точки со смещением указанным в OFF_CHAR плюс 16 байт, по адресу указанному в BUFF_STR
; сохранение дробной части Числа #region
sub DOT_CHAR, N_Z_CHAR
mov OFF_CHAR, xmmword + dword
cmovns OFF_CHAR, DOT_CHAR
movdqu xmmword ptr[BUFF_STR + OFF_CHAR + 0000000], xmm0
movdqu xmmword ptr[BUFF_STR + OFF_CHAR + xmmword], xmm1
; #endregion
В результате если число имеет целую часть то в OFF_CHAR помещается длина целой части, в противном случае в OFF_CHAR помещается длина Числа по умолчанию равная 20 байтам. Таким образом таким образом если у числа есть целая и дробная часть они будут склеены в единую строку с удалением символа «точки» между ними, если число имеет только целую или только дробную часть, то строка символов начинающаяся после точки будет сохранена за пределами сканируемой строки и таким образом проигнорирована.
4.5.5. Зануление недостающих символов Числовой строки.
SPL
— загружаем в ХММ2 строку символов ноль.
— сохраняем в память строку символов ноль со смещением указанным в LEN_NUMB по адресу указанному в BUFF_STR.
— сохраняем в память строку символов ноль со смещением указанным в LEN_NUMB плюс 16 байт, по адресу указанному в BUFF_STR.
— помещаем в LEN_CELL удвоенное значение DOT_CHAR то есть удвоенную длину целой части числа.
; зануление недостающих символов Числа #region
movdqu xmm2, xmmword ptr Xmm_30
movdqu xmmword ptr[BUFF_STR + LEN_NUMB + 0000000], xmm2
movdqu xmmword ptr[BUFF_STR + LEN_NUMB + xmmword], xmm2
lea LEN_CELL, [DOT_CHAR * 2]
; #endregion
В результат все «мусорные» символы Числовой строки, находящиеся после последнего символа цифры, на который указывает LEN_NUMB будут заменены символами нуля. Таким образом в памяти будет сформирована единая непрерывная строка начинающаяся с первого значащего символа, содержащая все значимые цифры и дополненная нуля в случае если значимая часть Числа меньше 20 символов. Кроме того в LEN_CELL будет помещена удвоенная сумма знаком между символом точки и первой значащей цифрой.
4.6. Проверка корректного окончания Числовой строки.
SPL
4.6.1. Размножение окончания дробной части Числовой строки.
SPL
— обнуляем регистр N_Z_CHAR.
— загружаем в младшее двойное слова регистра ХММ0 четыре символа начиная с символа который следует за последним символом дробной части Числовой строки и на который указывает END_FRAC.
— копируем два символа на которые указывает END_FRAC в четыре младших слова регистра ХММ0 и получаем четыре копии пары символов окончания числа.
— копируем два символа на которые указывает END_FRAC во все слова регистра ХММ0 и получаем восемь копии копий пары символов окончания числа.
— копируем ХММ0 в ХММ1 и получаем шестнадцать копий пары символов окончания числа.
; проверка корректного окончания числа #region
xor N_Z_CHAR, N_Z_CHAR
movd xmm0, dword ptr[CUR_CHAR + END_FRAC]
pshuflw xmm0, xmm0, 0
pshufd xmm0, xmm0, 0
movdqa xmm1, xmm0
4.6.2. Проверка окончания дробной части Числовой строки.
SPL
— сравниваем слова регистра ХММ0 со строкой символов в памяти содержащей восемь вариантов окончания Числовой строки и устанавливаем значение совпадающих слов в -1 а в остальных случаях в 0.
— сравниваем слова регистра ХММ1 со строкой символов в памяти содержащей восемь вариантов окончания Числовой строки и устанавливаем значение совпадающих слов в -1 а в остальных случаях в 0.
— складываем значение ХММ0 и ХММ1 и помещаем результат в регистр ХММ0.
— сравниваем байты регистра ХММ1 самими собой в результате чего все байт ХММ1 принимают значение -1.
— командой PTEST выполняет операцию AND над словами ХММ0 и ХММ1 и если хотя бы одно слово установлены в -1 устанавливаем флаг нуля ZF=0.
— если флаг нуля ZF=0 то значит число не имеет записи о значении экспоненты и необходимо миновать участок кода связанный с ее дешифровкой.
pcmpeqw xmm0, Mask_001
pcmpeqw xmm1, Mask_010
paddw xmm0, xmm1
pcmpeqb xmm1, xmm1
ptest xmm0, xmm1
jnz @f
4.6.3. Проверка окончания строки нулем.
SPL
— копируем в EDX два символа начиная с символа который следует за последним символом дробной части Числовой строки и на который указывает END_FRAC.
— устанавливаем флаг нуля ZF=1 если младший байт регистра EDX равен 0.
— если флаг нуля ZF=1[/INLINE то значит число не имеет записи о значении экспоненты и необходимо миновать участок кода связанный с ее дешифровкой.
movzx edx, word ptr[CUR_CHAR + END_FRAC]
test dl, dl
jz @f
4.7. Обработка экспоненты.
SPL
4.7.1. Проверка символа экспоненты.
SPL
— сбрасываем бит номер 5 в регистре EDX в результате чего если в регистре содержатся символы строчных букв они будут преобразованы в прописные.
— устанавливаем флаг нуля ZF=0 если значение младшего байт регистра EDX НЕ равно значению символа Е.
— если флаг нуля ZF=0 то значит числовая строка содержит критическую ошибку в оформлении и необходимо выйти из процедуры вернув код ошибки.
; проверка символа экспоненты #region
btr edx, 5
cmp dl,'E'
jnz ErrorExit
; #endregion
4.7.2. Проверка знака экспоненты
SPL
4.7.2.1. Проверка наличия знака экспоненты.
SPL
— сбрасываем в HASH_STR бит указанный в EXP_CHAR удаляя его из хеша, теперь при следующем сканировании бит указывающий на символ экспоненты Е будет проигнорирован.
— увеличиваем значение EXP_CHAR на 1 перемещая указатель на следующий символ экспоненты.
— сбрасываем в HASH_STR бит указанный в EXP_CHAR удаляя его из хеша, теперь при следующем сканировании бит указывающий на символ знака экспоненты плюс/минус будет проигнорирован и устанавливаем флаг переноса CF=1 если значение бита было 0 что означает что знак экспоненты отсутствовал.
— если флаг переноса CF=1 то значит символ знака экспоненты отсутствует в Числовой строке и необходимо загрузить в младшее слово регистра EDX символ плюс содержащийся в константе Plus.
— складываем значение указателя на текущий символ экспоненты EXP_CHAR с флагом переноса CF=1 для учета позиции символа знака экспоненты при ее наличии.
; проверка знака экспоненты #region
btr HASH_STR, EXP_CHAR
inc EXP_CHAR
btr HASH_STR, EXP_CHAR
cmovnc dx, Plus
adc EXP_CHAR, 0
Таким образом если числовая строка не содержит знака экспоненты то для обработки будет принудительно загружен символ плюс при этом позиция текущего символа останется на месте. В случае наличия наличия знака экспоненты указатель на текущий символ будет перемещен на следующий символ после символа знака.
4.7.2.2. Проверка знака Экспоненты.
SPL
— устанавливаем флаг нуля ZF=1 если значение регистра DH равно символу плюс.
— устанавливаем регистр DL в значение 1 если флаг нуля ZF=1.
— устанавливаем флаг нуля ZF=1 если значение регистра DH равно символу минус.
— устанавливаем регистра DH в значение 1 если флаг нуля ZF=1.
— копируем значение бита номер 8 регистра DX во флаг переноса CF.
— складываем LEN_CELL и флаг переноса CF.
— устанавливаем флаг нуля ZF=1 если значение регистр EDX равно 0.
— если флаг нуля
cmp dh,'+'
setz dl
cmp dh,'-'
setz dh
bt dx, 8
adc LEN_CELL, 0
; #endregion
Таким образом информация о знаке экспоненты сохраняется в нулевом бите LEN_CELL, учитывая что LEN_CELL изначально хранит удвоенное значение количество символов между символом точки и первым символом значащего числа то его нулевой бит всегда имеет нулевое значение и загрузка в него символа знака экспоненты не исказит значение.
4.7.3. Позиция первого не нулевого символа экспоненты.
SPL
— копируем значение EAX в N_Z_CHAR восстанавливая значение N_Z_CHAR ранее сохраненное в EAX в пункте 4.4.3.
— обнуляем регистр EAX.
— устанавливаем в EAX бит номер которого указан в EXP_CHAR и который соответствует номеру символа следующего сразу после знака экспоненты если он есть или символа экспоненты если знак экспоненты отсутствует.
— складываем значение регистра EAX и целое числа, размером в двойное слово, со значением -1 и получаем в EAX значение в котором все биты соответствующие символам до символа указанного в EXP_CHAR принимают значение 1 а после значение 0.
— инвертируем значение EAX теперь все биты установлены в 1 соответствуют символам следующим после знака экспоненты не включая его.
—
; Позиция первого не нулевого символа экспоненты #region
mov N_Z_CHAR, eax
xor eax, eax
bts eax, EXP_CHAR
add eax, -1
not eax
and N_Z_CHAR, eax
bsf N_Z_CHAR, N_Z_CHAR
movdqu xmm0,[CUR_CHAR + N_Z_CHAR]
; #endregion
4.7.4. Проверка окончания строки Экспоненты.
SPL
; проверка окончания строки экспоненты #region
bsf END_CHAR, END_CHAR
jz ErrorExit ; окончание отсутствует
movzx eax, byte ptr[CUR_CHAR + END_CHAR]
cmp eax, 20h
ja ErrorExit
add rdx,(1 + 1 shl 09h + 1 shl 0Dh + 1 shl 20h)
bt rdx, rax
jnc ErrorExit
; #endregion
4.8. Вычисление чего-то зачем-то
SPL
; #region
sub N_Z_CHAR, END_CHAR
cmp N_Z_CHAR, -4
; jnc ErrorExit
@@: cmp byte ptr[BUFF_STR + 00000000 + xmmword + dword - byte],'5'
mov dword ptr[BUFF_STR + 00000000 + xmmword + dword - byte],'0000'
movd dword ptr[BUFF_STR + N_Z_CHAR + xmmword + qword - byte], xmm0
; #endregion
4.9. Вычисление экспоненты и младшей части Числа
SPL
; вычисление экспоненты и младшей части Числа #region
movdqu xmm0,[BUFF_STR + 0000000 - byte]
movdqu xmm1,[BUFF_STR + xmmword - byte]
psubb xmm0, xmm2
psubb xmm1, xmm2
pmaddubsw xmm1, xmmword ptr Xmm_0001
pmaddwd xmm1, xmmword ptr Xmm_0010
; #endregion
4.10. Вычисление множителя
SPL
; вычисление множителя #region
movd rax, xmm1
sbb rax, -1
movd xmm1, eax
shr rax, 20h
movd xmm2, rbx
mov ebx, eax
neg eax
sar LEN_CELL, 1
cmovc ebx, eax
add ebx, LEN_CELL
mov eax, ebx
neg eax
cmovns ebx, eax
mov rax, 0A000000000000000h
mov MANT_ARG, 0CCCCCCCCCCCCCCCCh
cmovs MANT_ARG, rax
mov eax, 3
mov LOGB_ARG, -3
cmovs LOGB_ARG, eax
mov MANT_MUL, 1
mov LOGB_MUL, 0
shr HASH_MUL, 1
cmovc MANT_MUL, MANT_ARG
cmovc LOGB_MUL, LOGB_ARG
@@: jz @f
mov rax, MANT_ARG
mul rax
bt rdx, 3Fh
setnc cl
adc LOGB_ARG, LOGB_ARG
shld rdx, rax, cl
mov MANT_ARG, rdx
shr HASH_MUL, 1
jnc @b
mov rax, rdx
mul MANT_MUL
bt rdx, 3Fh
setnc cl
adc LOGB_MUL, LOGB_ARG
shld rdx, rax, cl
mov MANT_MUL, rdx
test HASH_MUL, HASH_MUL
jmp @b
@@: movd rbx, xmm2
; #endregion
4.11. Вычисление целой части Числа.
SPL
; вычисление целой части Числа #region
psubb xmm0, xmm2
pmaddubsw xmm0, xmmword ptr Xmm_0001
pmaddwd xmm0, xmmword ptr Xmm_0010
pmulld xmm0, xmmword ptr Xmm_0100
phaddd xmm0, xmm0
movd eax, xmm0
imul rax, Mul_0001
pextrd edx, xmm0, 1
imul rdx, 02710h
add rax, rdx
movd edx, xmm1
add rax, rdx
bsr rcx, rax
add LOGB_MUL, ecx
inc cl
shrd rax, rax, cl
; #endregion
4.12. Вычисление числа.
SPL
; вычисление числа #region
mul MANT_MUL
bt rdx, 3Fh
setnc cl
adc LOGB_MUL, 3FFh
shld rdx, rax, cl
shl rdx, 1
shrd rdx, r11, 11
shrd rdx, rsp, 1
btr esp, 0
movd xmm0, rdx
; #endregion
4.13. Выход из процедуры.
SPL
ret
4.14. ErrorExit
SPL
ErrorExit: ; аварийный выход #region
mov ecx, -1
pcmpeqb xmm1, xmm1
psllq xmm1, 52 + 1
psrlq xmm1, 1
ret
; #endregion
===========
Источник:
habr.com
===========
Похожие новости:
- [Настройка Linux, Программирование, C, Rust, Разработка под Linux] Линус Торвальдс рассказал о том, где Rust впишется в Linux
- [Управление персоналом, Карьера в IT-индустрии] Зачем разработчику развивать эмпатию?
- [Open source, Программирование, Системное программирование, Компиляторы, Rust] Rust 1.51.0: const generics MVP, новый распознаватель функциональности Cargo (перевод)
- [Программирование, Робототехника, Интернет вещей] 10 плат для начала разработки IoT в 2021г (перевод)
- [Разработка веб-сайтов, CSS, Программирование, HTML] Книга-игра Гарри Гаррисона на HTML
- [Open source, Программирование, Геоинформационные сервисы, Визуализация данных, Научно-популярное] Google Earth Engine (GEE) как общедоступный каталог больших геоданных
- [Ненормальное программирование, SQL, PowerShell, Microsoft SQL Server, Администрирование баз данных] Email Chart — это вам не ASCII Art
- [Ненормальное программирование, Программирование, Компиляторы, C] Один бинарник, любое окружение. Магия чистого C
- [Open source, Учебный процесс в IT, Управление персоналом, Карьера в IT-индустрии] Ваша любовь к разработке в первую очередь выгодна работодателю (перевод)
- [Программирование, C++, C, Разработка под Linux] Приёмы неблокирующего программирования: полные барьеры памяти (перевод)
Теги для поиска: #_programmirovanie (Программирование), #_assembler, #_assembler, #_programmirovanie (программирование), #_programmirovanie (
Программирование
), #_assembler
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 10:55
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Преобразование строки символов в число двойной точности на MASM64 без FPU на SSE4.1 форматированное по правилам ML64.EXE то есть длиной до 32 символов. 1. Настройки компиляции, адресации и соглашения о вызовах.SPL1.1. Передача параметров в процедуру и обратно.SPLВ соответствии с x64 software conventions будем считать что указатель на начало Числовой строки подлежащие конвертированию расположено в RCX.
1.2. Адресация и размерность кода.SPLБудем использовать x64 битный код при x32 битной адресации. Такой способ адресации позволяет использовать преимущества обоих диалектов. Для установки указанного режима необходимо указать директиву /LARGEADDRESSAWARE:NO в линковщику.
2. Текстовые константы и псевдонимы.SPL2.1. Текстовые константыSPLДля удобства работы со стеком создаем текстовую константу которая по сути выполняет роль имени (идентификатора) локальной переменной не определенного типа и «произвольного» размера:
; псевдонимы операндов #region BUFF_STR equ esp - xmmword * 4 ; #endregion 2.2. Псевдонимы переменны и регистров.SPLДля удобства работы с регистрами создаем блок текстовых констант которые по сути будут представлять собой имена переменных неопределенного типа и размером в двойное слово DWORD или INT для тех кому более привычен синтаксис СРР которые не имеют своего собственного отображения в памяти, а все время своего существования размещаются в регистре с которым они ассоциированы, при этом некоторые «переменны» являются по сути «объединениями» и размещаются в одних и тех же регистрах присутствуя в них на разных этапах исполнения программы:
; псевдонимы регистров #region CUR_CHAR equ ecx ; абсолютная позиция текущего символа DOT_CHAR equ edx ; относительная позиция символа точки HASH_STR equ r8d ; хеш символов Числовой строки END_CHAR equ HASH_STR ; относительная позиция последнего символа N_Z_CHAR equ r9d ; относительная позиция символ не нулевого числа OFF_CHAR equ N_Z_CHAR ; смешение дробной части относительно начала Числовой строки END_FRAC equ r10d ; относительное положение последнего символа Числовой строки EXP_CHAR equ END_FRAC ; текущий относительный символ строки Экспоненты LEN_NUMB equ r11d ; длина значимой части Числа LEN_CELL equ LEN_NUMB ; длина целой части Числа HASH_MUL equ ebx ; значение экспоненты в десятичной системе MANT_ARG equ r8 ; мантисса аргумент множителя LOGB_ARG equ r9d ; порядок аргумента множителя MANT_MUL equ r10 ; мантисса множителя LOGB_MUL equ r11d ; порядок множителя ; #endregion 3. Секция данныхSPLСоздаем секцию данных. Стоит отметить что самая «лучшая» секция данных это такая секция которая размещена а секции кода, то есть при любой возможности необходимо избегать создания секции данных и размещать их непосредственно в секции кода в аргументах содержащихся непосредственно в инструкциях, к сожалению SIMD команды не допускают непосредственной размещения данных в инструкциях секции кода, что вынуждает создавать секцию данных:
.data ; #region Xmm_HT byte 10h dup (09h) Xmm_CR byte 10h dup (0Dh) Xmm_SP byte 10h dup (20h) Xmm_SL byte 10h dup ('/') Xmm_30 byte 10h dup ('0') Xmm_39 byte 10h dup ('9') Xmm_0001 word 8 dup (010Ah) Xmm_0010 dword 4 dup (10064h) Xmm_0100 qword 2 dup (100002710h) Mask_001 word 0044h, 0944h, 0D44h, 2044h, 0046h, 0946h, 0D46h, 2046h Mask_010 word 0064h, 0964h, 0D64h, 2064h, 0066h, 0966h, 0D66h, 2066h Mul_0001 qword 0E8D4A51000h Plus word 2B00h ; тестовая строка string byte ' ', 0Dh, 0Ah, '+-0098765432109876540.09876e-0248 ' ; #endregion Назначение определенных констант будет пояснено ниже в ходе выполнения процедуры. 4. Секция кода.SPL4.1. Поиск начала Числовой подстроки.SPL4.1.1. Пропуск обобщенных пробеловSPL4.1.1.1. Вход в цикл пропуска обобщенного пробела.SPL— сравниваем байты регистра ХММ3 самими собой в результате чего все байт ХММ3 принимают значение -1.
— уменьшаем указатель адреса первого символа в CUR_CHAR на длину ХММ регистра. — увеличиваем указатель адреса первого символа в CUR_CHAR на длину ХММ регистра. pcmpeqb xmm3, xmm3 sub CUR_CHAR, xmmword @@: add CUR_CHAR, xmmword Таким образом при начальном входе в цикл обработки указатель текущего символа будет установлен на начало строки, при последующих входах в цикл он будет смещаться на длину ХММ-регистра, то есть на 15 байт. Такой способ организации начала цикла, при котором инкремент расположен в начале цикла, позволяет значительно упростить выход из цикла сведя его к команде проверки условия и условному переходу. В противном случае при размещении команда инкремента и проверки условий в конце цикла эти инструкции конфликтовали бы в части изменения флагов процессора что избыточно усложнило бы выход из цикла. 4.1.1.2. Проверка строки символов.SPL— загружаем строку символов в ХММ0 и копируем ее в ХММ1/ХММ2 получая три копии строки.
— сравниваем три копии строки содержащиеся в регистрах с тремя строками размещенными в памяти, равномерно заполненными символами пробел/табуляция/возврат каретки — складываем полученные результаты в регистр ХММ0. movdqu xmm0,[CUR_CHAR] movdqa xmm1, xmm0 movdqa xmm2, xmm0 pcmpeqb xmm0, xmmword ptr Xmm_SP pcmpeqb xmm1, xmmword ptr Xmm_HT pcmpeqb xmm2, xmmword ptr Xmm_CR paddb xmm0, xmm1 paddb xmm0, xmm2 В результате байты регистра ХММ0 равные любому из трех символов обобщенного пробела принимают значение -1 а не равные 0. Векторное сравнение позволяет многократно повысить скорость сканирования строки не только за счет параллельного сравнения но и за счет исключения множества условных переходов характерных для «классических» способов. 4.1.1.3. Проверка результата и выход из цикла.SPL— командой PTEST выполняет операцию AND над байтами ХММ0 и ХММ3 и в случае если все байты результата установлены в -1 устанавливаем флаг переноса CF=1.
— если флаг переноса CF=1 то следовательно в сканируемой строке отсутствуют символы отличные от обобщенного пробела и необходимо вернуться в начало цикла. ptest xmm0, xmm3 jc @b ; повторный пропуск обобщенного пробела ; #endregion В результате сканирование строки продолжается до тех пор пока не будет найден символ отличный от обобщенного пробела. Скорость сканирования можно дополнительно увеличить если разместить строки равномерно заполненные символами пробел/табуляция/возврат каретки в старших регистрах SIMD, но в соответствии с соглашением вызова х64 это потребует предварительно сохранить их значение в память, а при выходе из функции восстановить, что учитывая ожидаемое время сканирования в один проход будет не оправданно. 4.1.2. Позиция первого символа не равного обобщенному пробелу.SPL-копируем старшие биты всех байтов регистра ХММ0 в EAX, теперь все биты соответствующие символам обобщенного пробела установлены в значение 1.
— инвертируем EAX, теперь биты соответствующие символам не равным обобщенному пробелу установлен в значение 1. — сканируем биты регистра EAX от младшего к старшему в поиске первого бита установлено в значение 1, и результат равный номеру бита, помещаем в этот же регистр. — добавляем значение EAX к CUR_CHAR и получаем указатель на первый символ отличный от обобщенного пробела. ; Позиция первого символа не равного обобщенному пробелу #region pmovmskb eax, xmm0 not eax bsf eax, eax add CUR_CHAR, eax ; #endregion 4.1.3. Проверка на сочетание символов новой строки.SPL— устанавливаем флаг нуля ZF=1 если сочетание двух первых символов следующих после символа обобщенного пробела равно сочетанию символов новая строка.
— устанавливаем младший байт регистра EAX в значение 1 если флаг нуля ZF=1 и в значение 0 при всех остальных вариантах. — складываем значение EAX и CUR_CHAR и получаем указатель на первый символ отличный от обобщенного пробела с учетом сочетания символов новой строки. ; позиция первого символа не равного обобщенному пробелу #region cmp word ptr[CUR_CHAR - byte], 0A0Dh setz al add CUR_CHAR, eax ; #endregion Таким образом если сочетание двух первых символов следующих после символа обобщенного пробела равно сочетанию символов новая строка то второй символ после обобщённого пробела будет проигнорирован, в противном случае он будет подвергнут дальнейшему анализу. 4.1.4. Тест обрыва строки.SPL— копируем первый символ отличный от обобщенного пробела в регистр EAX одновременно расширяя его до двойного слова.
— устанавливаем флаг нуля ZF=1 если значение EAX равно 0. — если флаг нуля ZF=1 то следовательно имеет место обрыв строки и необходимо выйти из процедуры вернув код ошибки: ; тест обрыва строки #region movzx eax, byte ptr[CUR_CHAR] test al, al jz ErrorExit ; обрыв строки ; #endregion 4.2. Проверка знака Числа.SPL4.2.1. Проверка символа Плюс.SPL— сравниваем регистр AL с символом плюс.
— устанавливаем AL в значение 1 если AL равен символу плюс и 0 при любом другом значении символа. — добавляем значение EAX к CUR_CHAR. ; Проверка символа минус/плюс #region cmp al, '+' setz al add CUR_CHAR, eax В результате если текущий символ равен символу плюс то позиция текущего символа будет смещена на следующий символ, во всех остальных случаях символ будет подвергнут повторному анализу. 4.2.2. Проверка символа минус.SPL— сравниваем текущий символ с символом минус.
— устанавливаем значение AL в 1 если текущий символ равен символу минус и 0 при любом другом значении. — добавляем значение EAX к CUR_CHAR. — добавляем значение EAX к регистру ESP. cmp byte ptr[CUR_CHAR], '-' setz al add CUR_CHAR, eax add esp, eax ; #endregion В результате если текущий символ равен символу минус то позиция текущего символа будет смещена на следующий символ, а значение регистра стека ESP увеличено на 1, во всех остальных случаях символ будет подвергнут повторному анализу, а значение регистра стека останется без изменений. Прямое изменение значения регистра указателя стека ESP считается крайне опасным действием чреватым непредсказуемыми ошибками, но я практикую «агрессивный» подход и считаю что не бывает «плохого» или «хорошего» кода, бывают хорошие и плохи программисты, хорошие пишут так как будто никаких правил нет вообще но результат при этом такой как будто они соблюдают их все, а плохие они просто плохие. 4.3. Сканирование символов Числовой строки.SPL4.3.1. Сканирование старшей части Числовой строки.SPL4.3.1.1. Проверка первого условия.SPL— загружаем старшую часть Числовой строки, со смешением на 16 символов от начала Числовой строки, в ХММ0.
— копируем старшую часть Числовой строки в ХММ1 и ХММ2. — сравниваем регистр ХММ0 со строкой в памяти равномерно заполненной символами 9. — копируем регистр ХММ0 в регистр ХММ1. ; сканирование символов Числовой строки #region movdqu xmm0,[CUR_CHAR + xmmword] movdqa xmm2, xmm0 movdqa xmm3, xmm0 pcmpgtb xmm0, xmmword ptr Xmm_39 movdqa xmm1, xmm0 В результат получаем две копии строки в регистрах ХММ0 и ХММ1 в которых все байты символов которые были больше символа 9 установлены в значение -1, а все остальные в значение 0. 4.3.1.2. Проверка второго условия.SPL— сравниваем регистр ХММ2 со строкой в памяти равномерно заполненной символами косая черта, в результате чего все байты регистра ХММ2 содержавшие символы больше и равно символу 0 установлены в значение -1, а меньше в значение 0.
— командой PANDN инвертируем баты регистра ХММ0 и выполняем логическую операцию AND над байтами ХММ0 и ХММ2 помещая результат в регистр ХММ0. pcmpgtb xmm2, xmmword ptr Xmm_SL pandn xmm0, xmm2 В результате все байты регистра ХММ0 содержащие символы в диапазоне от 0 включительно до 9 включительно, то есть цифры, принимают значения -1 а все остальные 0. 4.3.1.3. Проверка третьего условия.SPL— сравниваем регистр ХММ3 со строкой в памяти равномерно заполненной символами 0, в результате чего все байты регистра ХММ3 содержавшие символы больше и равно символу 1 установлены в значение -1, а меньше в значение 0.
— командой PANDN инвертируем баты регистра ХММ1 и выполняем логическую операцию AND над байтами ХММ1 и ХММ3 помещая результат в регистр ХММ1. pcmpgtb xmm3, xmmword ptr Xmm_30 pandn xmm1, xmm3 В результате все байты регистра ХММ1 содержащие символы в диапазоне от 1 включительно до 9 включительно, то есть значащие цифры, принимают значения -1 а все остальные 0. 4.3.1.4. Сохранение старших частей хеша строки.SPL— копируем старшие биты байтов регистра ХММ0 в регистр HASH_STR.
— копируем старшие биты байтов регистра ХММ1 в регистр N_Z_CHAR pmovmskb HASH_STR, xmm0 pmovmskb N_Z_CHAR, xmm1 В результате младшие 16 бит регистра HASH_STR соответствуют 16 старшим байтам Числовой строки, при этом биты соответствующие символам содержащим цифры принимают значения 1 а все остальные 0, а младшие 16 бит регистра N_Z_CHAR соответствуют 16 старшим байтам Числовой строки, при этом биты соответствующие символам содержащим значащие числа, принимают значения 1 а все остальные 0. 4.3.2. Сканирование младшей части Числовой строки.SPLmovdqu xmm0,[CUR_CHAR]
movdqa xmm2, xmm0 movdqa xmm3, xmm0 pcmpgtb xmm0, xmmword ptr Xmm_39 movdqa xmm1, xmm0 pcmpgtb xmm2, xmmword ptr Xmm_SL pcmpgtb xmm3, xmmword ptr Xmm_30 pandn xmm0, xmm2 pandn xmm1, xmm3 4.3.3. Объединение старших и младших Хешей строки.SPL— копируем старшие биты байтов регистра ХММ0 в EAX.
— сдвигаем младшие 16 бит HASH_STR в старшую часть HASH_STR. — складываем значение HASH_STR и EAX. — копируем старшие биты байтов регистра ХММ1 в EAX. — сдвигаем младшие 16 бит N_Z_CHAR в старшую часть N_Z_CHAR. — складываем значение N_Z_CHAR и EAX. pmovmskb eax, xmm0 shl HASH_STR, xmmword add HASH_STR, eax pmovmskb eax, xmm1 shl N_Z_CHAR, xmmword add N_Z_CHAR, eax В результате HASH_STR содержит хеш Числовой строки в котором биты соответствующие символам цифр установлены в значение 1 а в се остальные в 0, при этом номера битов соответствуют номерам символов от начала строки начиная с нуля, а N_Z_CHAR содержит хеш Числовой строки в котором биты символов соответствующие значащих цифр установлены в значение 1, а все остальные в 0, при этом номер бита соответствуют номерам символов от начала строки начиная с нуля. 4.4. Обработка целой части Числа.SPL4.4.1. Проверка первого символа.SPL— сканируем HASH_STR от младшего бита к старшему в поисках первого бита равного 1, результат помещаем в EAX и устанавливаем флаг нуля ZF=1 если все биты равны нулю.
— если флаг нуля ZF=1 то значит строка не содержит ни одного символа цифры и необходимо выйти из процедуры вернув код ошибки. — устанавливаем флаг нуля ZF=0 если полученный результат отличен от нуля. — если флаг нуля ZF=0 то значит первый символ строки не является цифрой и необходимо выйти из процедуры вернув код ошибки. ; проверка первого символа #region bsf eax, HASH_STR jz ErrorExit test eax, eax jnz ErrorExit ; первый символ не цифра ; #endregion В результат проверяем содержит ли Числовой строки хотя бы один символ цифры и является ли первый символ Числовой строки цифрой. Особенностью данного участка кода в нестандартном поведении инструкции BSF которая проявляется в работе с флагом нуля, а именно если при сканирование первым битом установленным в значение 1 окажется бит с порядковым номером 0 то BSF установит значение регистра назначения в 0 но при этом установит флаг нуля ZF=0 как будто в регистре содержится число отличное от нуля, если же инструкция не обнаружит ни одного бита в значении 1, то регистр назначение не будет подвергнут изменению а флаг нуля будет установлен в ZF=1. 4.4.2. Поиск символа точки разделяющий целую и дробную части Числа.SPL— инвертируем значение HASH_STR в результате чего теперь каждый бит установленный в 1 сигнализирует о символе НЕ цифре.
— сканируем HASH_STR от младшего бита к старшему, результат помещаем в DOT_CHAR и устанавливаем флаг нуля ZF=1 если все биты HASH_STR равны нулю. — если флаг нуля ZF=1 то значит строка не содержит ни одного символа отличного от цифры и необходимо выйти из процедуры вернув код ошибки. — сравниваем символ отличный от цифры с символом точка и устанавливаем флаг ZF=0 если они не равны. — если флаг нуля ZF=0 то значит первый символ отличный от цифры не равен символу точка и необходимо выйти из процедуры вернув код ошибки. ; поиск символа точки разделяющий целую и дробную части Числа #region not HASH_STR bsf DOT_CHAR, HASH_STR jz ErrorExit ; точки не обнаружено cmp byte ptr[CUR_CHAR + DOT_CHAR], '.' jnz ErrorExit ; символ не является точкой ; #endregion 4.4.3. Сохранение значащей части Числа.SPL— копируем N_Z_CHAR в EAX
— сканируем N_Z_CHAR от младшего бита к старшему и помещаем результат в этот же регистр. — сохраняем в память строку из четырех нулей 0000 по адресу на 1 (один) байт меньше адреса указанного в BUFF_STR. — сохраняем в регистр ХММ0 старшую часть строку символов начинающийся с первого символа значащей цифры, на который указывает N_Z_CHAR, игнорирую таким образом ведущие нули. — сохраняем в память старшую часть строки символов по адресу указанному в BUFF_STR. — сохраняем в регистр ХММ0 младшую часть строки символов на которую указывает N_Z_CHAR со смещение в 16 байт. — сохраняем в память младшую часть строки символов начиная с первого символа значащей цифры по адресу указанному в BUFF_STR со смещение в 16 байт. ; сохранение значащей части Числа #region mov eax, N_Z_CHAR bsf N_Z_CHAR, N_Z_CHAR mov dword ptr[BUFF_STR - byte], 30303030h movdqu xmm0,[CUR_CHAR + N_Z_CHAR] movdqu [BUFF_STR + 00000000], xmm0 movdqu xmm0,[CUR_CHAR + N_Z_CHAR + xmmword] movdqu [BUFF_STR + 00000000 + xmmword], xmm0 ; #endregion В результате сохраняем в память строку из 32 символов начиная с первого символа значащей цифры на которую указывает N_Z_CHAR по адресу указанному в BUFF_STR. При этом указанная строка может содержать символ точки и иные символы не относящиеся к цифрам. 4.5. Обработка дробной части Числа.SPL4.5.1. Загрузка дробной части Числовой строки.SPL— загружаем старшую часть Числовой строку, следующую сразу после точки, на которую указывает DOT_CHAR в регистр ХММ0.
— загружаем младшую часть Числовой строку, следующую сразу после точки, на которую указывает DOT_CHAR со смещение 16 байт от начала Числовой строки в регистр ХММ1. ; загрузка дробной части Числовой строки #region movdqu xmm0,[CUR_CHAR + DOT_CHAR + byte] movdqu xmm1,[CUR_CHAR + DOT_CHAR + byte + xmmword] ; #endregion 4.5.2. Поиск конца дробной части Числа.SPL— сбрасываем в HASH_STR бит указанный в DOT_CHAR удаляя его из хеша, теперь при следующем сканировании бит указывающий на точку будет проигнорирован.
— сканируем HASH_STR от младшего бита к старшему помещая результат в этот же регистр устанавливая флаг нуля ZF=1 если все биты равны нулю. — если флаг нуля ZF=1 то значит дробная часть строки не имеет корректного окончания и необходимо выйти из процедуры вернув код ошибки. ; поиск конца дробной части Числа #region btr HASH_STR, DOT_CHAR bsf END_FRAC, HASH_STR jz ErrorExit ; #endregion В результате в EXP_CHAR находиться указатель на первый символ экспоненты или окончание Числа относительно начала Числа. 4.5.3. Количество значащих символов Числа.SPL4.5.3.1. Проверка наличия значащих цифр.SPL— сравниваем END_FRAC и N_Z_CHAR и устанавливаем флаг переполнения CF=1 если N_Z_CHAR больше END_FRAC.
— копируем END_FRAC в N_Z_CHAR если CF=1. ; количество значащих символов Числа #region cmp END_FRAC, N_Z_CHAR cmovc N_Z_CHAR, END_FRAC В результате если и целая и дробная часть Числа состоят из одних нулей а первый символ значащей цифры находиться за пределами дробной и целой части числа, о чем свидетельствует факт того что N_Z_CHAR больше END_FRAC, то присваиваем N_Z_CHAR значение END_FRAC то есть указателя на первый символ экспоненты или окончания числа. 4.5.3.2. Подсчет количества значащих символов Числа.SPL— сравниваем N_Z_CHAR и DOT_CHAR и если N_Z_CHAR меньше DOT_CHAR, то есть первая значащая цифра расположен раньше точки, что означает что у числа существует целая часть, устанавливаем флаг переноса CF=1.
— копируем в LEN_NUMB указатель на первый символ экспоненты или окончания Числа содержащийся в END_FRAC. — вычитаем из LEN_NUMB указатель на первую значащую цифру содержащуюся в N_Z_CHAR и флаг переноса CF. cmp N_Z_CHAR, DOT_CHAR mov LEN_NUMB, END_FRAC sbb LEN_NUMB, N_Z_CHAR ; #endregion В результате в LEN_NUMB содержится значение количества цифр Числа начиная с первой значащей цифры без учета символа точки, то есть исключительно количество символов соответствующих цифрам, символ точки в подсчете не учитывается даже если число пересекает точку. 4.5.4. Сохранение дробной части Числа.SPL— вычитаем из DOT_CHAR значение N_Z_CHAR и устанавливаем флаг знака SF=0, если полученное число положительное.
— помещаем в OFF_CHAR число 20 равное количеству символов которое будет в дальнейшем использованы для создания мантиссы. — Если флаг знака SF=0 то значит число имеет целой части и необходимо скопировать DOT_CHAR в OFF_CHAR. — сохраняем в память старшую часть строки следующей сразу за символом точки со смещением указанным в OFF_CHAR по адресу указанному в BUFF_STR. — сохраняем в памяти младшую часть строки следующей сразу за символом точки со смещением указанным в OFF_CHAR плюс 16 байт, по адресу указанному в BUFF_STR ; сохранение дробной части Числа #region sub DOT_CHAR, N_Z_CHAR mov OFF_CHAR, xmmword + dword cmovns OFF_CHAR, DOT_CHAR movdqu xmmword ptr[BUFF_STR + OFF_CHAR + 0000000], xmm0 movdqu xmmword ptr[BUFF_STR + OFF_CHAR + xmmword], xmm1 ; #endregion В результате если число имеет целую часть то в OFF_CHAR помещается длина целой части, в противном случае в OFF_CHAR помещается длина Числа по умолчанию равная 20 байтам. Таким образом таким образом если у числа есть целая и дробная часть они будут склеены в единую строку с удалением символа «точки» между ними, если число имеет только целую или только дробную часть, то строка символов начинающаяся после точки будет сохранена за пределами сканируемой строки и таким образом проигнорирована. 4.5.5. Зануление недостающих символов Числовой строки.SPL— загружаем в ХММ2 строку символов ноль.
— сохраняем в память строку символов ноль со смещением указанным в LEN_NUMB по адресу указанному в BUFF_STR. — сохраняем в память строку символов ноль со смещением указанным в LEN_NUMB плюс 16 байт, по адресу указанному в BUFF_STR. — помещаем в LEN_CELL удвоенное значение DOT_CHAR то есть удвоенную длину целой части числа. ; зануление недостающих символов Числа #region movdqu xmm2, xmmword ptr Xmm_30 movdqu xmmword ptr[BUFF_STR + LEN_NUMB + 0000000], xmm2 movdqu xmmword ptr[BUFF_STR + LEN_NUMB + xmmword], xmm2 lea LEN_CELL, [DOT_CHAR * 2] ; #endregion В результат все «мусорные» символы Числовой строки, находящиеся после последнего символа цифры, на который указывает LEN_NUMB будут заменены символами нуля. Таким образом в памяти будет сформирована единая непрерывная строка начинающаяся с первого значащего символа, содержащая все значимые цифры и дополненная нуля в случае если значимая часть Числа меньше 20 символов. Кроме того в LEN_CELL будет помещена удвоенная сумма знаком между символом точки и первой значащей цифрой. 4.6. Проверка корректного окончания Числовой строки.SPL4.6.1. Размножение окончания дробной части Числовой строки.SPL— обнуляем регистр N_Z_CHAR.
— загружаем в младшее двойное слова регистра ХММ0 четыре символа начиная с символа который следует за последним символом дробной части Числовой строки и на который указывает END_FRAC. — копируем два символа на которые указывает END_FRAC в четыре младших слова регистра ХММ0 и получаем четыре копии пары символов окончания числа. — копируем два символа на которые указывает END_FRAC во все слова регистра ХММ0 и получаем восемь копии копий пары символов окончания числа. — копируем ХММ0 в ХММ1 и получаем шестнадцать копий пары символов окончания числа. ; проверка корректного окончания числа #region xor N_Z_CHAR, N_Z_CHAR movd xmm0, dword ptr[CUR_CHAR + END_FRAC] pshuflw xmm0, xmm0, 0 pshufd xmm0, xmm0, 0 movdqa xmm1, xmm0 4.6.2. Проверка окончания дробной части Числовой строки.SPL— сравниваем слова регистра ХММ0 со строкой символов в памяти содержащей восемь вариантов окончания Числовой строки и устанавливаем значение совпадающих слов в -1 а в остальных случаях в 0.
— сравниваем слова регистра ХММ1 со строкой символов в памяти содержащей восемь вариантов окончания Числовой строки и устанавливаем значение совпадающих слов в -1 а в остальных случаях в 0. — складываем значение ХММ0 и ХММ1 и помещаем результат в регистр ХММ0. — сравниваем байты регистра ХММ1 самими собой в результате чего все байт ХММ1 принимают значение -1. — командой PTEST выполняет операцию AND над словами ХММ0 и ХММ1 и если хотя бы одно слово установлены в -1 устанавливаем флаг нуля ZF=0. — если флаг нуля ZF=0 то значит число не имеет записи о значении экспоненты и необходимо миновать участок кода связанный с ее дешифровкой. pcmpeqw xmm0, Mask_001 pcmpeqw xmm1, Mask_010 paddw xmm0, xmm1 pcmpeqb xmm1, xmm1 ptest xmm0, xmm1 jnz @f 4.6.3. Проверка окончания строки нулем.SPL— копируем в EDX два символа начиная с символа который следует за последним символом дробной части Числовой строки и на который указывает END_FRAC.
— устанавливаем флаг нуля ZF=1 если младший байт регистра EDX равен 0. — если флаг нуля ZF=1[/INLINE то значит число не имеет записи о значении экспоненты и необходимо миновать участок кода связанный с ее дешифровкой. movzx edx, word ptr[CUR_CHAR + END_FRAC] test dl, dl jz @f 4.7. Обработка экспоненты.SPL4.7.1. Проверка символа экспоненты.SPL— сбрасываем бит номер 5 в регистре EDX в результате чего если в регистре содержатся символы строчных букв они будут преобразованы в прописные.
— устанавливаем флаг нуля ZF=0 если значение младшего байт регистра EDX НЕ равно значению символа Е. — если флаг нуля ZF=0 то значит числовая строка содержит критическую ошибку в оформлении и необходимо выйти из процедуры вернув код ошибки. ; проверка символа экспоненты #region btr edx, 5 cmp dl,'E' jnz ErrorExit ; #endregion 4.7.2. Проверка знака экспонентыSPL4.7.2.1. Проверка наличия знака экспоненты.SPL— сбрасываем в HASH_STR бит указанный в EXP_CHAR удаляя его из хеша, теперь при следующем сканировании бит указывающий на символ экспоненты Е будет проигнорирован.
— увеличиваем значение EXP_CHAR на 1 перемещая указатель на следующий символ экспоненты. — сбрасываем в HASH_STR бит указанный в EXP_CHAR удаляя его из хеша, теперь при следующем сканировании бит указывающий на символ знака экспоненты плюс/минус будет проигнорирован и устанавливаем флаг переноса CF=1 если значение бита было 0 что означает что знак экспоненты отсутствовал. — если флаг переноса CF=1 то значит символ знака экспоненты отсутствует в Числовой строке и необходимо загрузить в младшее слово регистра EDX символ плюс содержащийся в константе Plus. — складываем значение указателя на текущий символ экспоненты EXP_CHAR с флагом переноса CF=1 для учета позиции символа знака экспоненты при ее наличии. ; проверка знака экспоненты #region btr HASH_STR, EXP_CHAR inc EXP_CHAR btr HASH_STR, EXP_CHAR cmovnc dx, Plus adc EXP_CHAR, 0 Таким образом если числовая строка не содержит знака экспоненты то для обработки будет принудительно загружен символ плюс при этом позиция текущего символа останется на месте. В случае наличия наличия знака экспоненты указатель на текущий символ будет перемещен на следующий символ после символа знака. 4.7.2.2. Проверка знака Экспоненты.SPL— устанавливаем флаг нуля ZF=1 если значение регистра DH равно символу плюс.
— устанавливаем регистр DL в значение 1 если флаг нуля ZF=1. — устанавливаем флаг нуля ZF=1 если значение регистра DH равно символу минус. — устанавливаем регистра DH в значение 1 если флаг нуля ZF=1. — копируем значение бита номер 8 регистра DX во флаг переноса CF. — складываем LEN_CELL и флаг переноса CF. — устанавливаем флаг нуля ZF=1 если значение регистр EDX равно 0. — если флаг нуля cmp dh,'+' setz dl cmp dh,'-' setz dh bt dx, 8 adc LEN_CELL, 0 ; #endregion Таким образом информация о знаке экспоненты сохраняется в нулевом бите LEN_CELL, учитывая что LEN_CELL изначально хранит удвоенное значение количество символов между символом точки и первым символом значащего числа то его нулевой бит всегда имеет нулевое значение и загрузка в него символа знака экспоненты не исказит значение. 4.7.3. Позиция первого не нулевого символа экспоненты.SPL— копируем значение EAX в N_Z_CHAR восстанавливая значение N_Z_CHAR ранее сохраненное в EAX в пункте 4.4.3.
— обнуляем регистр EAX. — устанавливаем в EAX бит номер которого указан в EXP_CHAR и который соответствует номеру символа следующего сразу после знака экспоненты если он есть или символа экспоненты если знак экспоненты отсутствует. — складываем значение регистра EAX и целое числа, размером в двойное слово, со значением -1 и получаем в EAX значение в котором все биты соответствующие символам до символа указанного в EXP_CHAR принимают значение 1 а после значение 0. — инвертируем значение EAX теперь все биты установлены в 1 соответствуют символам следующим после знака экспоненты не включая его. — ; Позиция первого не нулевого символа экспоненты #region mov N_Z_CHAR, eax xor eax, eax bts eax, EXP_CHAR add eax, -1 not eax and N_Z_CHAR, eax bsf N_Z_CHAR, N_Z_CHAR movdqu xmm0,[CUR_CHAR + N_Z_CHAR] ; #endregion 4.7.4. Проверка окончания строки Экспоненты.SPL; проверка окончания строки экспоненты #region
bsf END_CHAR, END_CHAR jz ErrorExit ; окончание отсутствует movzx eax, byte ptr[CUR_CHAR + END_CHAR] cmp eax, 20h ja ErrorExit add rdx,(1 + 1 shl 09h + 1 shl 0Dh + 1 shl 20h) bt rdx, rax jnc ErrorExit ; #endregion 4.8. Вычисление чего-то зачем-тоSPL; #region
sub N_Z_CHAR, END_CHAR cmp N_Z_CHAR, -4 ; jnc ErrorExit @@: cmp byte ptr[BUFF_STR + 00000000 + xmmword + dword - byte],'5' mov dword ptr[BUFF_STR + 00000000 + xmmword + dword - byte],'0000' movd dword ptr[BUFF_STR + N_Z_CHAR + xmmword + qword - byte], xmm0 ; #endregion 4.9. Вычисление экспоненты и младшей части ЧислаSPL; вычисление экспоненты и младшей части Числа #region
movdqu xmm0,[BUFF_STR + 0000000 - byte] movdqu xmm1,[BUFF_STR + xmmword - byte] psubb xmm0, xmm2 psubb xmm1, xmm2 pmaddubsw xmm1, xmmword ptr Xmm_0001 pmaddwd xmm1, xmmword ptr Xmm_0010 ; #endregion 4.10. Вычисление множителяSPL; вычисление множителя #region
movd rax, xmm1 sbb rax, -1 movd xmm1, eax shr rax, 20h movd xmm2, rbx mov ebx, eax neg eax sar LEN_CELL, 1 cmovc ebx, eax add ebx, LEN_CELL mov eax, ebx neg eax cmovns ebx, eax mov rax, 0A000000000000000h mov MANT_ARG, 0CCCCCCCCCCCCCCCCh cmovs MANT_ARG, rax mov eax, 3 mov LOGB_ARG, -3 cmovs LOGB_ARG, eax mov MANT_MUL, 1 mov LOGB_MUL, 0 shr HASH_MUL, 1 cmovc MANT_MUL, MANT_ARG cmovc LOGB_MUL, LOGB_ARG @@: jz @f mov rax, MANT_ARG mul rax bt rdx, 3Fh setnc cl adc LOGB_ARG, LOGB_ARG shld rdx, rax, cl mov MANT_ARG, rdx shr HASH_MUL, 1 jnc @b mov rax, rdx mul MANT_MUL bt rdx, 3Fh setnc cl adc LOGB_MUL, LOGB_ARG shld rdx, rax, cl mov MANT_MUL, rdx test HASH_MUL, HASH_MUL jmp @b @@: movd rbx, xmm2 ; #endregion 4.11. Вычисление целой части Числа.SPL; вычисление целой части Числа #region
psubb xmm0, xmm2 pmaddubsw xmm0, xmmword ptr Xmm_0001 pmaddwd xmm0, xmmword ptr Xmm_0010 pmulld xmm0, xmmword ptr Xmm_0100 phaddd xmm0, xmm0 movd eax, xmm0 imul rax, Mul_0001 pextrd edx, xmm0, 1 imul rdx, 02710h add rax, rdx movd edx, xmm1 add rax, rdx bsr rcx, rax add LOGB_MUL, ecx inc cl shrd rax, rax, cl ; #endregion 4.12. Вычисление числа.SPL; вычисление числа #region
mul MANT_MUL bt rdx, 3Fh setnc cl adc LOGB_MUL, 3FFh shld rdx, rax, cl shl rdx, 1 shrd rdx, r11, 11 shrd rdx, rsp, 1 btr esp, 0 movd xmm0, rdx ; #endregion 4.13. Выход из процедуры.SPLret
4.14. ErrorExitSPLErrorExit: ; аварийный выход #region
mov ecx, -1 pcmpeqb xmm1, xmm1 psllq xmm1, 52 + 1 psrlq xmm1, 1 ret ; #endregion =========== Источник: habr.com =========== Похожие новости:
Программирование ), #_assembler |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 10:55
Часовой пояс: UTC + 5