Inline assembler Add-in для VB6

Тема в разделе "VB", создана пользователем Thetrik, 6 дек 2016.

Метки:
  1. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    863
    Всем привет.
    Бывают ситуации когда в VB нужно использовать ассемблер. Обычно для этого используют предварительно скомпилированный код размещенный в памяти и запускают его одним из миллиона способов. Очевидным недостатком этого метода является то, что любая модификация ассемблерного кода требует изменения в процедурах размещения кода в памяти. К тому же это является довольно медленной процедурой. Я написал Add-in, который делает вышеописанные процедуры автоматически, а также после компиляции никакие дополнительные действия по размещению кода не выполняются - ассемблерный код влинковывается в EXE. Ассемблерный код работает и в IDE и в скомпилированном (только Native!) коде.
    Как этим пользоваться?
    Для начала нужно установить Add-in (установщик доступен по ссылке ниже). После установки нужно запустить его из VB (Add-Ins -> Add-in Manager -> Inline assembler). После этого в меню добавится одноименный пункт. Если проект еще не использовал функционал Add-in'а то при первой активации добавится стандартный модуль, в который нужно будет добавить прототипы ассемблерных функций для VB6. Сам модуль можно переименовывать, добавлять процедуры/функции, но размещать в них какой-либо функционал запрещено. После создания модуля можно уже открыть сам редактор ассемблерного кода. В нем доступен раскрывающийся список с именами функций, определенных в добавленном модуле. Для каждой функции можно переопределить код используя синтаксис ассемблера NASM, однако если кода нет, то функция не модифицируется (т.е. получается обычный вызов пустой функции). С каждым проектом (если использовался функционал Add-in'а) связывается еще один файл с расширением *ia в папке с файлом проекта, в котором хранятся ассемблерные процедуры для проекта. Add-in работает "прозрачно", т.е. если Add-in отключен, то проект также будет работать и компилироваться, просто "функции-пустышки" будут работать как обычные. Файл *ia не является жизненно-необходимым для работы проекта, если его не будет, то соответственно "функции-пустышки" останутся нетронутыми.
    Давайте посмотрим работу Add-in'а на простом примере. К примеру нам необходимо сложить два Integer-массива без переполнения, т.е. если результат сложения больше 32767 он и останется 32767, а если меньше -32768 он останется -32768. К тому же нам нужно сделать это как можно быстрее. Для этого очень хорошо подходит расширение MMX, в нем есть инструкции для работы с векторными данными с насыщением. Перейдем к реализации. Создадим новый проект, откроем Add-in. Это добавит новый модуль, переименуем его в modInlineAsm. Теперь определим прототип функции:
    Код (Visual Basic):
    1. Public Function MMXAdd( _
    2.                 ByRef dest As Integer, _
    3.                 ByRef src As Integer, _
    4.                 ByVal count As Long) As Long
    5. End Function
    Первым параметром передаем первый элемент массива, он же результирующий; вторым параметром первый элемент второго массива; наконец третьим параметром передаем количество элементов. Кстати размер массивов должен быть кратен 8 байтам, т.к. мы будем использовать векторные инструкции которые одновременно работают с 8-ю байтами. Теперь определим процедуру в форме, которая будет вызывать эту функцию:
    Код (Visual Basic):
    1. Private Sub Form_Load()
    2.     Dim src()   As Integer
    3.     Dim dst()   As Integer
    4.     Dim size    As Long
    5.     Dim index   As Long
    6.    
    7.     size = 1024
    8.    
    9.     ReDim src(size - 1)
    10.     ReDim dst(size - 1)
    11.    
    12.     For index = 0 To size - 1
    13.         ' // Fill arrays with sine
    14.         src(index) = Sin(index / 40) * 20000
    15.         dst(index) = Sin(index / 23) * 20000
    16.     Next
    17.    
    18.     ' // Add with saturation
    19.     MMXAdd dst(0), src(0), size
    20.    
    21.     '// Draw result
    22.     AutoRedraw = True
    23.    
    24.     Scale (0, 32767)-(index, -32768)
    25.    
    26.     For index = 0 To size - 1
    27.         If index Then
    28.             Me.Line -(index, dst(index))
    29.         Else
    30.             Me.PSet (index, dst(index))
    31.         End If
    32.     Next
    33.    
    34. End Sub
    Как видно здесь просто заполняются 2 массива синусоидой, а потом мы их складываем с помощью нашей функции, и выводим результат на форму в виде графика. Теперь нам нужно переопределить процедуру, для этого активируем Add-in. Откроется окно с редактором кода. В нем мы выбираем нашу функцию и добавляем следующий ассемблерный код:
    Код (ASM):
    1. BITS 32
    2. ; Addition of two arrays using saturation
    3. ; Size of arrays should be a multiple of 8
    4. push EBP
    5. mov EBP, ESP
    6. push EBX
    7. push ESI
    8. push EDI
    9. mov ESI,DWORD [EBP+0x8]
    10. mov EDI,DWORD [EBP+0x0C]
    11. mov ECX,DWORD [EBP+0x10]
    12. shr ECX,2
    13. test ECX,ECX
    14. je EXIT_PROC
    15. emms ; Initialize MMX
    16. CYCLE:
    17. movq MM0,QWORD [EDI]
    18. movq MM1,QWORD [ESI]
    19. paddsw MM1,MM0
    20. movq QWORD [ESI],MM1
    21. add ESI,0x8
    22. add EDI,0x8
    23. loop CYCLE
    24. emms
    25. EXIT_PROC:
    26. pop EDI
    27. pop ESI
    28. pop EBX
    29. mov esp, ebp
    30. pop ebp
    31. ret 0x0c
    Здесь все просто, если знать инструкции. Самая главная paddsw складывает два четырехмерных вектора 16-битных значений со знаком за одну операцию. Теперь сохраняем проект и запускаем:
    [​IMG]
    Как видно из скриншота, две синусоиды сложились причем с насыщением, это можно заметить по пикам. Теперь давайте попробуем скомпилировать EXE файл и посмотреть что у нас вызывается и во что компилируется:
    [​IMG]
    Как видим код уже находится внутри EXE, без всяких выделений буферов и т.п. ненужных вещей.
    Как это работает?
    На самом деле все довольно просто. В самом начале ставятся обработчики на ключевые события вроде компиляции, запуска кода, закрытия/сохранения проекта и т.п. При запуске из IDE компилируются все коды заданные пользователем, а также вычисляются адреса переопределяемых функций. Затем код переписывается на код скомпилированный через NASM. И при вызове кода, вызывается наш код. При остановке, код восстанавливается. При компиляции (а точнее перед линковкой) ищется OBJ файл переопределяемого модуля и код функций пустышек переписывается на ассемблерный код, и файл пересохраняется. Для этого я специально писал COFF парсер. Вообще это открывает кучу разных возможностей от замены функций рантайма на свои до шифрования кода. Вообщем много чего можно придумать.
    Проект, можно сказать, совсем не тестировался, т.к. у меня в ближайшее время не будет такой возможности, поэтому скорее-всего будет много багов, учитывая то, что половина функционала Add-in'а использует недокументированные трюки, которые возможно работают не так как я думаю. В проекте нет даже подсветки синтаксиса, у меня есть мой самодельный текстбокс с подсветкой написанный с нуля, но его нужно дебажить еще и дописывать, поэтому пока я использовать стандартный текстбокс. Если кого-нибудь заинтересует, и будут баги, пишите сюда.
    Всем спасибо за внимание.
    Скачать.
     
    _edge нравится это.
  2. _edge

    _edge Well-Known Member

    Публикаций:
    1
    Регистрация:
    29 окт 2004
    Сообщения:
    631
    Адрес:
    Russia
    Win7-64, не регистрируется Dll, запускал от адм.

    ia1.png ia2.png

    Как-то можно вручную ее включить в "проект"?
     
  3. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    863
    Какие-нибудь другие Add-in'ы сторонние есть? Работают? Сам VB6 корректно установлен? Посмотрел в отладчике, такая ошибка может быть если GetModuleFileName зафейлится.
     
  4. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    Снял профайл по этой асм процедуре сложения, сделав есчо две на базовом(GRP) и через старый добрый FPU. Буфер размером 64M предварительно закрепляется в рабочем наборе при заполнении его рандомом. Для каждого набора выполняется сто циклов сложения элементов. i5 проц, стата(юзермод, ms):

    Для выравненного буфера:
    GRP 671
    FPU 28250
    MMX 262

    mmx быстрее в данном случае чем gpr в 2.5 раза. fpu vs gpr: 42.

    Тот же тест для не выравненного буфера:

    GPR 688
    FPU 28203
    MMX 640

    Тоесть сразу эффективность mmx исчезла - она существует только за счёт более быстрой выборки из памяти. 2.5 раза приращение профайла это слишком мало, оно вообще не заметно. Если например придётся сохранять NPX контекст(fxsave), то это приведёт к падению профайла в десятки раз(~20) и при использовании NPX шедулер начинает сохранять этот контекст, что сказывается в целом на профайле потока.
     
  5. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    863
    Indy_, две процедуры которые были добавлены использовали насыщение (именно ради него и приводился пример MMX)?
     
  6. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    Thetrik

    Не знаю что за насыщение, обычное сложение:

    Код (Text):
    1.  fild Q[esi]
    2.  fild Q[edi]
    3.  fadd st,st(1)
    4.  fistp Q[edi]
    5.  ffree st
    По теме инлайн асм в вб это интересно, как себя поведут ав на таком коде :)
     
  7. rmn

    rmn Well-Known Member

    Публикаций:
    0
    Регистрация:
    23 ноя 2004
    Сообщения:
    2.345
    Если при операции будет переполнение операнда-приемника, его значение устанавливается в MAX_UINT32, а не сбрасывается в меньшее значение.
     
  8. _edge

    _edge Well-Known Member

    Публикаций:
    1
    Регистрация:
    29 окт 2004
    Сообщения:
    631
    Адрес:
    Russia
    Я просто попытался установить эту Inline, как обычно инсталлятор чего-либо запускают.

    VB6 есть, nanoVB, кастрированный VB. Ладно, чё с меня нуба взять.

    ---

    /spoiler

    Думается, АВ отвалятся, увидев импорт msvbvm6, или на первом ThunkRT вызове (в соответствующей теме, на днях, обсуждали невозможность эмулировать VB6 EXE, невозможность в течении уже более 15 лет).

    Интересно посмотреть на использование CUDA в расшифровщике. Тема не для этого раздела, да и такое вроде было уже, но все же, поглядеть бы на !компактные! вызовы CUDA-подобных обращений к GPU как к сопроцессору, так, чтобы прямо с ассемблерного уровня.
     
    Последнее редактирование: 6 дек 2016
  9. _edge

    _edge Well-Known Member

    Публикаций:
    1
    Регистрация:
    29 окт 2004
    Сообщения:
    631
    Адрес:
    Russia
  10. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    863
    В том-то и суть что это не простое сложение, а (в данном примере):
    Код (C):
    1. res = a[i] + b[i];
    2. if (res > 32767)
    3.     res = 32767;
    4. else if (res < -32768)
    5.     res = -32768;
    6. a[i] = res;
    И таких операций производится параллельно 4, если заюзать SSE то будет 8.
     
  11. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    Не так давно тестил запуск из ресурсов на вб, для этих целей установил даже его. Там частично норм эмулилось, но подробности я уже не помню, может завтра посмотрю.

    Я только профайл снял, не вникая в детали :)
     
  12. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    863
    Нужно пробовать с обычным VB6. Установщик подразумевает что VB6 корректно установлен.
    Вроде делал один человек подобное на шейдерах, DirecеX9 позволяет компилировать шейдеры на ассемблере если я не ошибаюсь.
     
  13. _edge

    _edge Well-Known Member

    Публикаций:
    1
    Регистрация:
    29 окт 2004
    Сообщения:
    631
    Адрес:
    Russia
    http://timjones.tw/blog/archive/2015/09/02/parsing-direct3d-shader-bytecode

    I didn’t want to take a dependency on Direct3D - so instead, I figured out how to disassemble compiled HLSL shaders back to assembly instructions, and then I wrote a virtual machine that can execute these assembly instructions entirely on the CPU.


    Оставлю это здесь. Да, снова виртуальные машины, байткод и никакого ТРУ асма. We need to go deeper (c).

    When you use a shader in Direct3D, these assembly instructions are parsed from the bytecode (much like we’re doing here), and sent to the GPU. Somewhere along the way (presumably in the GPU driver), the Direct3D assembly instructions will be compiled (again) into the GPU’s own assembly language, which is lower-level and hardware-specific.

    Gpu's own asm - это уже через порты вв-вывода/конфигурационное пространство в памяти, надо полагать.
     
    Последнее редактирование: 8 дек 2016
  14. _edge

    _edge Well-Known Member

    Публикаций:
    1
    Регистрация:
    29 окт 2004
    Сообщения:
    631
    Адрес:
    Russia