Добавьте два 32-битных целых числа на ассемблере для использования в VB6.

Я хотел бы придумать байтовый код на ассемблере (ассемблере?) для машин Windows, чтобы добавить две 32-битные длины и выбросить бит переноса. Я понимаю, что часть машин Windows немного расплывчата, но я предполагаю, что байты для ADD практически одинаковы во всех современных наборах инструкций Intel.

Я просто пытаюсь немного злоупотребить VB и сделать некоторые вещи быстрее. Таким образом, в качестве примера запуска прямой сборки в VB шестнадцатеричная строка "8A4C240833C0F6C1E075068B442404D3E0C20800" представляет собой ассемблерный код для SHL, который может быть внедряется в программу VB6 для быстрой операции SHL, ожидающей двух параметров Long (здесь мы игнорируем тот факт, что 32-битные long в VB6 подписаны, просто притворяемся, что они беззнаковые).

В том же духе, что представляет собой шестнадцатеричная строка байтов, представляющая инструкции ассемблера, которые сделают то же самое, чтобы вернуть сумму двух 32-битных целых чисел без знака?

Шестнадцатеричный код выше для SHL, по словам автора:

mov eax, [esp+4]
mov cl, [esp+8]
shl eax, cl
ret 8

Я выплюнул эти байты в файл и попытался разобрать их в командной строке Windows, используя старую утилиту отладки, но я понял, что она не работает с более новым набором инструкций, потому что EAX не понравилось, когда я попытался что-то собрать, но он был счастлив с AX.

Из комментариев в исходном коде я знаю, что SHL EAX, CL равно D3E0, но у меня нет ссылки, чтобы узнать, какие байты нужны для инструкции ADD EAX, CL, иначе я бы попробовал. (Хотя теперь я знаю, что операнды должны быть одного размера.)

Я попробовал плоский ассемблер и ничего не понял, как его использовать. Я использовал его для сборки исходного кода SHL и получил совсем другой результат, не те же байты. Помощь?


person ErikE    schedule 11.02.2011    source источник
comment
добавьте два 32-битных long и выбросьте бит переноса — это будет lea eax, [ecx + edx]/ret, если мы говорим о __fastcall, иначе mov load/memory source add/ret. (флаг переноса не является частью возвращаемого значения). Вы полностью усложняете это, и IDK, почему вызов функции asm будет быстрее, чем использование + в исходном коде VB. Кроме того, add eax, cl невозможно закодировать, поскольку операнды имеют разные размеры.   -  person Peter Cordes    schedule 03.11.2020
comment
Кроме того, мне по-прежнему кажется маловероятным, что накладные расходы VB на передачу аргументов функции машинного кода будут ниже, чем вызов собственной реализации +. Разве это не просто математическая обертка для целочисленного типа с фиксированной шириной? Двоичное сложение — это та же операция для дополнения до 2, что и для беззнакового, поэтому, если в VB6 нет беззнаковых типов, узнайте, как вы собираетесь вернуть результат этого ассемблирования обратно в VB полезным способом. Расширяет ли VB + целые числа до расширенной точности вместо переноса, что делает их медленнее? Если это так, то да, вероятно, что asm будет быстрее, усекая результат.   -  person Peter Cordes    schedule 04.11.2020
comment
Кроме того, если у вас есть непрерывный массив элементов для сложения, выполнение всего цикла на ассемблере должно быть намного быстрее. Вы даже можете использовать SSE2 paddd xmm0, [edx] для одновременного выполнения 4 операций добавления (SIMD) с нагрузкой 2/такт и дополнительной пропускной способностью. Если нет, то вызов функции для каждого добавления отстой, но все же может быть хуже, чем то, что вы можете заставить VB делать самостоятельно, IDK. Прошли годы с тех пор, как я когда-либо делал что-либо с VB. (Большинство из них заключалось в написании нативной функции на C, которую я мог вызывать из VB в Excel для подгонки данных примерно в 60 раз быстрее, чем нативный код VB, для летней студенческой работы 20 лет назад.)   -  person Peter Cordes    schedule 04.11.2020
comment
Вы различаете VB6 и VB.Net? VB6 не может выполнять беззнаковое сложение длинных 4-байтовых целых чисел. Однако он может вызывать методы API Windows через COM, передавая байты напрямую без преобразования, так что длинное со знаком в VB правильно принимается API Windows как длинное без знака. Все это работало безупречно, и я смог сложить два 32-битных значения вместе и получить значение обратно. Выполнение этого в VB6 изначально потребовало бы сложных условных выражений или манипуляций с битами для обработки бита знака отдельно от остальных битов. Непрерывного массива не было.   -  person ErikE    schedule 04.11.2020
comment
@PeterCordes заголовок моего вопроса и первый абзац говорят, что я хочу добавить два 32-битных значения, поэтому я не уверен, что вызывает раздражение? Что касается производительности, вопреки возможному предположению, использование VB6 для выполнения вызовов Windows API на самом деле очень быстро.   -  person ErikE    schedule 04.11.2020
comment
Нет, я недостаточно знаю о VB, чтобы что-то знать о версиях, извините. Что произойдет в VB6, если вы сделаете x += 1 с x = 0x7ffffffff? Вы получаете -2147483648 (C INT_MIN)? Если это так, вы можете сделать обычный + и передать результат функции, которая хочет unsigned long, и она будет просто работать, потому что она имеет битовый шаблон 0x80000000, такой же, как unsigned 2147483648. Вам нужно обойти оператор + только в том случае, если он обнаруживает переполнение со знаком и выполняет что-то другое, кроме 32-битного переноса.   -  person Peter Cordes    schedule 04.11.2020
comment
Ой, не помешало спросить. Я предположил, что язык, который вы используете, обнаруживает переполнение со знаком, но хотел выяснить это. Значительная часть вопросов на языке ассемблера в Stack Overflow является результатом того, что люди не понимают двоичный код. К сожалению, работа с этим потоком вопросов новичков повлияла на мои предположения по умолчанию. Я был удивлен, что язык не имеет способа выполнять математику с целыми числами без знака, и неправильно предположил, что это может быть проще, чем это. Еще раз извините за потраченное время.   -  person Peter Cordes    schedule 04.11.2020
comment
Это не ты спрашивал, ты так спрашивал. Как бы то ни было, если у вас есть ЛЮБОЙ офисный продукт, запустите редактор VB и попробуйте что-нибудь на панели «Интерпретация». Или поищите в сети. VB6 — древний язык. Вы обратили внимание на год в вопросе? Извините за резкость. Думаю, я тоже позволил себе разозлиться.   -  person ErikE    schedule 04.11.2020


Ответы (1)


Я разобрал предоставленные вами байты и получил следующий код:

(__TEXT,__text) section
f:
00000000    movb    0x08(%esp),%cl
00000004    xorl    %eax,%eax
00000006    testb   $0xe0,%cl
00000009    jne     0x00000011
0000000b    movl    0x04(%esp),%eax
0000000f    shll    %cl,%eax
00000011    retl    $0x0008

Что определенно сложнее исходного кода, предоставленного автором. Он проверяет, например, что второй операнд не слишком велик, чего вообще нет в коде, который вы показали (более полный анализ см. в Редактировании 2 ниже). Вот простая функция stdcall, которая складывает вместе два аргумента и возвращает результат:

mov  4(%esp), %eax
add  8(%esp), %eax
ret  $8

Сборка, которая дает мне этот вывод:

(__TEXT,__text) section
00000000 8b 44 24 04 03 44 24 08 c2 08 00 

Я надеюсь, что эти байты делают то, что вы хотите!

Редактировать: возможно, более полезно, я только что сделал то же самое в C:

__attribute__((__stdcall__))
int f(int a, int b) 
{
  return a + b;
}

Скомпилированный с -Oz и -fomit-frame-pointer он генерирует точно такой же код (ну, во всяком случае, функционально эквивалентный):

$ gcc -arch i386 -fomit-frame-pointer -Oz -c -o example.o example.c
$ otool -tv example.o
example.o:
(__TEXT,__text) section
_f:
00000000    movl    0x08(%esp),%eax
00000004    addl    0x04(%esp),%eax
00000008    retl    $0x0008

Вывод машинного кода:

$ otool -t example.o
example.o:
(__TEXT,__text) section
00000000 8b 44 24 08 03 44 24 04 c2 08 00 

Конечно, это лучше, чем писать ассемблерный код от руки!

Редактировать 2:

@ErikE спросил в комментариях ниже, что произойдет, если будет предпринята попытка сдвига на 32 бита или больше. Дизассемблированный код в верхней части этого ответа (для байтов, предоставленных в исходном вопросе) может быть представлен следующим кодом более высокого уровня:

unsigned int shift_left(unsigned int a, unsigned char b)
{
    if (b > 32)
        return 0;
    else
        return a << b;
}

Из этой логики довольно легко увидеть, что если вы передадите значение больше 32 в качестве второго параметра функции сдвига, вы просто получите обратно 0.

person Carl Norum    schedule 11.02.2011
comment
Это выглядит супер. Спасибо, помог мне в моем невежестве. У вас есть рекомендуемый ассемблер/дизассемблер? Надеюсь, бесплатно? Или у майкрософта есть? Кроме того, что произойдет, если вы попытаетесь использовать SHL больше 32, скажем, какое-то очень большое число? - person ErikE; 11.02.2011
comment
Я только что использовал gcc/otool на своем Mac. Они были свободны. Если вам нужно использовать Windows, у cygwin есть бесплатные инструменты, которые вы можете использовать. У Microsoft определенно есть инструменты, но я понятия не имею, бесплатны ли они и где их взять. - person Carl Norum; 11.02.2011
comment
@Emtucifor, я отредактирую свой ответ, чтобы ответить на ваш вопрос о сдвиге более чем на 32 бита. - person Carl Norum; 11.02.2011
comment
Я так и думал, что происходит оптимизация для 32 бит и выше. Но не вернет ли инструкция shl в любом случае 0 для ›= 32? Я искал инструкцию testb, но не смог выяснить, как значение 224 выполняет работу по тестированию для 32, если только это не какая-то маска 255 - n или обратный порядок битов или что-то в этом роде. - person ErikE; 12.02.2011
comment
@Emtucifor, да - в руководстве по набору инструкций говорится, что операнд счетчика может быть непосредственным значением или регистром CL. Счетчик маскируется до 5 бит... из-за чего кажется, что проверка в исходном коде не нужна. Значение 224 в шестнадцатеричном формате равно 0xe0 — установлены биты 5, 6 и 7. Любое число с установленным одним из этих битов больше 32 - 2^5=32. - person Carl Norum; 12.02.2011
comment
Да, я не обращал внимания. Я вижу, что это совершенно действительная битовая маска из трех верхних битов, и она должна установить флаг NE в значение false, если во втором операнде есть какие-либо биты, установленные в маске первого операнда. Или что-то типа того. Большое спасибо за твою помощь! Ты был супер, и мне нравится изучать такие тайные вещи... - person ErikE; 12.02.2011
comment
Примечание: сдвиг влево на 32 бита или больше не вернет 0, потому что использование 32 или больше в сдвиге влево — неопределенная операция. Поэтому надо проверять размер смены перед ее выполнением, иначе получится мусор. - person ErikE; 03.11.2020
comment
Да, я думаю, это то, что показывает этот блок кода в конце моего ответа? - person Carl Norum; 03.11.2020
comment
Верно... мой новый комментарий был предназначен только для будущих посетителей, чтобы прояснить кое-что о самой инструкции SHL - предоставленный вами код более высокого уровня действительно подразумевает, что требуется проверка b >= 32, но не указывает это явно или ясно что будет, если попытаться. (Вопрос о том, что SHL 32 не определен, недавно возник у моего друга-разработчика, и сегодня я случайно наткнулся на этот старый вопрос, и я решил сообщить об этом небольшом факте всем будущим читателям.) - person ErikE; 03.11.2020
comment
Попался. Одобренный. ;-) - person Carl Norum; 03.11.2020