#1 21-04-2008 16:58

listener
From: Vice City
Registered: 09-11-2006
Posts: 616
Website

Xenon или наше светлое будущее

Последние несколько дней, я смотрел в будущее со все возрастающей уверенностью.
Будущее называлось bully360.idb и выглядело достаточно светлым.

Учитывая, что сроки выхода GTA IV на PC пока не объявлены, есть ощутимая вероятность, что "солнце придется закатывать вручную".
Опять же, даже в самом оптимистичном прогнозе, до выхода на PC пройдет не меньше пол-года - и глупо было бы не использовать это время для разбора скриптинга, написания редакторов и прочих моддерских тулзов.

Все упирается только в одно: '360-ка собрана не на x86, привычном и знакомом с детства, а на кастомном PowerPC G5 (aka Xenon).
Результаты четырехдневного копания в коде, и обнадеживают, и огорчают.

Для начала, ассемблер PowerPC сильно проще, чем x86. Команд мало, все регистры в одной группе равноправны. Все команды одного размера (4 байта). Это хорошо. Плохо то, что из-за малого количества команд, некоторые простейшие операции занимают ощутимое количество кода.

Отдельных операндов у команд нет, поэтому загрузка 32-бит значения в регистр, выполняется в два приема (верхняя и нижняя половины). Стандартные C-шные пролог и эпилог функции (то, что на x86 делается через push bp; mov bp,sp/pop bp или enter/leave), на PPC занимают (типично) 44 байта. 
Вамое веселое, что команда CALL отсутствует как явление. Вместо нее есть BL, которая выполняет переход, предварительно сохранив адрес следующей команды в специальный регистр. Первое, что нужно сделать в вызванной функции - достать из этого регистра сохраненный адрес и положить его на стек (PUSH/POP, аналогично, отсутствуют).

Параметры функций передаются в регистрах (начиная с R3), и это проблема. IDA принципиально не понимает такого способа и, есть подозрение, что ни скриптами, ни плагинами ситуацию облегчить не получится.

Это была плохая часть. Есть и хорошее (и его даже больше).
Как уже упоминалось, код получается просто и очень легко читающийся. На то, чтобы научиться свободно его читать, у меня ушло три дня. Компилятор - все тот же MSVC, и большинство сгенерированных компилятором конструкций осталось без изменений. Кошмар дизассемблера - оператор switch - реализуется очень просто (одноуровневая таблица в середине функции), и, в случае неправильного разбора, не приводит дизассемблер в ступор, а просто разворачивается в пачку очевидно "левых" инструкций, не влияющих на дальнейший анализ.

Еще из приятностей, системные функции, в большинстве своем - Windows Native API, к большинству из них есть описание.

Вот такие новости. Примерно через 180 часов, я буду знать насколько это все применимо к "основной цели".
Пока, если есть интерсующиеся кроме меня, могу выложить базу (20М в архиве), плагины для IDA и мануал по архитектуре PowerPC.

Offline

#2 21-04-2008 18:26

listener
From: Vice City
Registered: 09-11-2006
Posts: 616
Website

Re: Xenon или наше светлое будущее

Маленький примерчик кода (совершенно произвольная функция из совершенно произвольного места). Точнее, какой-то конструктор.

.text:827BD0B8                 mflr    %r12
.text:827BD0BC                 stw     %r12, var_8(%sp)
.text:827BD0C0                 std     %r31, var_10(%sp)
.text:827BD0C4                 stwu    %sp, -0x60(%sp)
; до этого места - пролог функции: 
; сохранили адрес возврата в var_8 (MFLR/STW), сохранили R31 в var_10, выделили 0x60 байт на стэке

.text:827BD0C8                 mr      %r31, %r3 ; в R3 передается первый параметр функции. Для конструктора - это адрес конструируемого объекта. (он нам еще пригодится, поэтому сохраним его).
.text:827BD0CC                 bl      sub_8269B820 ; вызовем конструктор родителя
.text:827BD0D0                 lis     %r11, __vt__820D0218@h # _820D0218::`vtbl'
.text:827BD0D4                 mr      %r3, %r31 ; в R3 также возвращается результат функции. Конструктор всегда возвращает this. Достанем его из того места, куда сохранили.
.text:827BD0D8                 addi    %r10, %r11, __vt__820D0218@l # _820D0218::`vtbl' ; две инструкции (эта и в 827BD0D0) для того, чтобы получить в R10 адрес VMT. R11, как правило, используется в качестве временного регистра для таких операций
.text:827BD0DC                 li      %r11, 0
; теперь проинициализируем поля объекта (в R31 находится this)
.text:827BD0E0                 stw     %r10, 0(%r31) ; _f0 = __vt__820D0218;
.text:827BD0E4                 stw     %r11, 8(%r31) ; _f8 = 0;  // 0 лежит в R11
.text:827BD0E8                 stw     %r11, 0xC(%r31) ; _fC = 0;
.text:827BD0EC                 stw     %r11, 0x10(%r31) ; _f10 = 0;

; выход из функции: вернем все, что сохраняли в начале, на свои места (стэк, адрес возврата, R31)
.text:827BD0F0                 addi    %sp, %sp, 0x60
.text:827BD0F4                 lwz     %r12, var_8(%sp)
.text:827BD0F8                 mtlr    %r12
.text:827BD0FC                 ld      %r31, var_10(%sp)
.text:827BD100                 blr

Страшно?

Last edited by listener (21-04-2008 20:33)

Offline

#3 27-04-2008 05:13

Seemann
Registered: 07-08-2006
Posts: 2,155

Re: Xenon или наше светлое будущее

Очень хочется больше описаний стандартных кусков кода: циклы, проверки, начало/конец функции, передача параметров и т.п., как устроена память, сколько регистров. Ибо сказать, что архитектура PPC сильно отличается от x86, значит, ничего не сказать rolleyes

edit:
%sp - это Stack Pointer?

edit2:

код получается просто и очень легко читающийся.

Да уж, попробуй с первого раза догадаться, что делает этот код:

cntlzw  %r11, %r11
extrwi  %r11, %r11, 1,26

а он сравнивает значение регистра с нулем... rolleyes

Offline

#4 28-04-2008 00:26

listener
From: Vice City
Registered: 09-11-2006
Posts: 616
Website

Re: Xenon или наше светлое будущее

Ну, в один пост здесь не уложишься. Буду рассказывать по порядку - заодно и для себя кое-что проясню ("Научи другого - поймешь сам")

Основные регистры описаны в первом томе, на странице 17.
Есть 32  general-purpose регистра (R0..R31) и 32 регистра для чисел с плавающей точкой (FPR0..FPR31).

PC (program counter, он же instruction pointer) как отдельный регистр не выделяется (т.е., явно обратиться к нему нельзя).

Стэка, такого как в x86, нет, как явления. Есть регистр SP, но все операции с ним нужно выполнять самостоятельно (т.е., отсутствуют PUSH/POP, CALL/RET, ENTER/LEAVE).

Отчасти, CALL/RET заменяются командами BL/BLR (и им подобными). BL выполняет переход по указанному адресу, помещая адрес следующей команды в регистр LR. BLR делает переход по адресу из LR. В плюсе - то, что практически любая команда перехода может заносить адрес в LR. В минусе - то, что сохранять адрес возврата на стэке нужно самостоятельно.

Регистр CTR имеет двойное назначение. Во-первых, он может использоваться как счетчик цикла с автодекрементом. Во-вторых (и это его основное применение), это единственный регистр, кроме LR, в который можно занести значение для перехода. Т.е., оператор switch, вызов виртуального метода или функции по указателю - выполянются командами BCTR/BCTRL.

Condition Register (CR), отдаленно напоминает регистр флагов x86, но, в отличие от (E)FLAGS, он разделен на 8 секций по 4 бита (CR0..CR7). В команде сравнения, можно указать, в какую секцию поместить результат сравнения (по умолчанию, используется CR0). Т.е., можно выполнить несколько сравнений сразу, а потом уже делать условные переходы.

Про регистры XER (floating point exception) и FPSCR (floating point status and control), пока ничего сказать не могу.

Дополнительно, в xenon есть набор 128-битных регистров (VR0..VR127), аналогичных SSE-регистрам P4. По ним достаточно мало информации, есть только расшифровки названий команд. В теории, они дожны быть похожи на AltiVec, но подробностей я еще не выяснял.


С организацией битов в регистрах - полный бардак. Регистры R0..R31 -  64 бита. При этом, команды, работающие с отдельными битами, рассматривают их как 32-битные. Биты нумеруются слева-направо (т.е., старший бит - нулевой). В зависимости от разрядности команды, младший бит - либо 31-й, либо 63-й. (т.е., во всех командах работы с битами, используются номера от 0, до 31; младший бит - 31-й).


По архитектуре памяти я пока никакой внятной документации не нашел.

Offline

#5 28-04-2008 03:25

listener
From: Vice City
Registered: 09-11-2006
Posts: 616
Website

Re: Xenon или наше светлое будущее

Передача параметров (будет дополняться по ходу)

Параметры в функции передаются в регистрах.
Целочисленные параметры передаются, начиная с R3 (R3 - первый параметр, R4 - второй, R5 - третий и т.д.).
Что происходит в функциях с переменным набором параметров (printf, scanf и т.д.), я пока не нашел. Аналогично, не видел, что происходит, если параметров больше 8.

Параметры с плавающей точкой, передаются, начиная с FP1.
По идее, если у функции параметры и целочисленные, и с плавающей точкой, они должны передаваться в своих регистрах

Для методов объектов, первый неявный параметр this так же передается в R3. (Первый явный параметр, соответственно, в R4; если он целочисленный).

Целочисленное значение возвращается в R3; значение с плавающей точкой - в FP1.

В некоторых случаях, оптимизатор линкера игнорирует порядок передачи параметров.  В этом случае, параметры могут быть в произвольных регистрах и использует регистры с произвольными номерами. Такие случаи редки; (как правило, это происходит на маленьких функциях), но учитывать такой вариан все равно нужно.

Начало и конец функции
Поскольку стэк в PPC рудиментарный, пролог и эпилог функции получаются достаточно масштабными.
Типичный пример я приводил выше. Во многих функциях дополнитебльно вызывается еще функции __savegplr_NN (в прологе) и __restgplr_NN (в эпилоге). Назначение этих функций - дополнительно сохранить/восстановить на стэке регистры, начиная с NN и заканчивая R31, плюс R12, как адрес возврата из функции.
Вызов __restgplr совмещен с выходом из функции.

Еще пример:

.text:8236E910 CTaskComplexVehicleSubtask__constructor:                           # CODE XREF: sub_8236EA10+2Cp ...
.text:8236E910                 mflr    %r12
.text:8236E914                 bl      __savegprlr_27
.text:8236E918                 stwu    %sp, -0x80(%sp)
; выше - пролог: поместили адрес возврата в R12, сохранили на стэке R12 и R27..R31, выделили дополнительно место на стэке.

; сохраним переданные параметры (5 параметров, первый - this)
.text:8236E91C                 mr      %r31, %r3 ; this
.text:8236E920                 mr      %r30, %r4 ; _a1
.text:8236E924                 mr      %r28, %r5 ; _a2
.text:8236E928                 mr      %r29, %r6 ; _a3
.text:8236E92C                 mr      %r27, %r7 ; _a4

; вызовем конструктор "родителя". В нем используется только один параметр - this, он уже находится в R3.
.text:8236E930                 bl      CTaskComplex__constructor

; дальше, в R31 у нас this, в R27..R30 - остальные параметры конструктора
; в R10 подготавливается указатель на VMT (как любая 32-битная константа - в два приема)
.text:8236E934                 lis     %r11, __vt__CTaskComplexVehicleSubtask@h # _CTaskComplexVehicleSubtask::`vtbl'
.text:8236E938                 stw     %r29, 0x14(%r31) ; _f14 = _a3
.text:8236E93C                 li      %r9, 3 
.text:8236E940                 stw     %r30, 0x18(%r31) ; _f18 = _a1;
.text:8236E944                 addi    %r10, %r11, __vt__CTaskComplexVehicleSubtask@l # _CTaskComplexVehicleSubtask::`vtbl'
.text:8236E948                 stw     %r27, 0x20(%r31) ; _f20 = _a4;
.text:8236E94C                 li      %r11, 0
.text:8236E950                 stw     %r28, 0x24(%r31) ; _f24 = _a2;
.text:8236E954                 addi    %r4, %r31, 0x18 ; R4 = &_f18;  // указатель на поле объекта
.text:8236E958                 cmplwi  cr6, %r30, 0 ; в CR6 поместим результат сравнения _a1 с нулем
.text:8236E95C                 stw     %r9, 0x2C(%r31) ; _f2C = 3;
.text:8236E960                 stw     %r10, 0(%r31) ; инициализируем заранее подготовленный VMT
.text:8236E964                 extrwi  %r10, %r29, 1,27 ; R10 = (_a3 & 0x10) ? true : false; // напоминаю: биты нумеруются слева-направо
.text:8236E968                 stb     %r11, 0x1C(%r31) ; _f1C = 0; // BYTE
.text:8236E96C                 stb     %r11, 0x31(%r31) ; _f31 = 0; // BYTE
.text:8236E970                 stb     %r11, 0x32(%r31) ; _f32 = 0; // BYTE
.text:8236E974                 stb     %r11, 0x33(%r31) ; _f33 = 0; // BYTE
.text:8236E978                 stb     %r10, 0x30(%r31) ; _f30 = R10; // BYTE
.text:8236E97C                 beq     cr6, loc_8236E988 ; if (_a1 != 0) _823E5630 (_a1);
.text:8236E980                 mr      %r3, %r30 ; это передача параметра
.text:8236E984                 bl      sub_823E5630
.text:8236E988 loc_8236E988:                          
.text:8236E988                 mr      %r3, %r31 ; подготавливаем возвращаемое значение (конструктор возвращает this)

; эпилог функции: возвращаем на место SP и делаем переход на __restgplr. Вся остальная работа и возврат из функции будут выполнены в ней.
.text:8236E98C                 addi    %sp, %sp, 0x80
.text:8236E990                 b       __restgprlr_27
.text:8236E990 # End of function sub_8236E910

Offline

#6 05-05-2008 16:58

listener
From: Vice City
Registered: 09-11-2006
Posts: 616
Website

Re: Xenon или наше светлое будущее

Итак, настала пора вернуться к регистру CTR.
Если не рассматривать его прямое назначение (счетчик цикла), он используется в трех конструкциях: switch, вызов виртуального метода и вызов функции по указателю.

switch
Реализация switch, как и в x86, использует таблицу с адресами переходов. На этом сходство и заканчивается.
Перекодировочные таблицы не используются, на каждый элемент в диапазоне switch, хранится свой адрес. Для элементов, для которых нет case, он указывает на default-метку. Таблица адресов лежит не после функции, а непосредственно за командой перехода.

# здесь, в R11 находится параметр switch
# если минимальное значение case ненулевое, то нужно скорректировать смещение (вычитая, начальное значение; в данном случае, 1)
.text:827D58DC                 subi    %r10, %r11, 1   # switch statement, 75 elements, start: 1
.text:827D58E0                 cmplwi  cr6, %r10, 74 # дальше, проверяем максимальное значение. 
.text:827D58E4                 bgt     cr6, loc_827D691C # Если параметр больше, переходим на default:
.text:827D58E8                 lis     %r12, off_827D5900@h # получаем адрес таблицы переходов, старшую часть ...
.text:827D58EC                 addi    %r12, %r12, off_827D5900@l # ... и младшую
.text:827D58F0                 slwi    %r0, %r10, 2 # получаем смещение в таблице (умножая параметр на 4),
.text:827D58F4                 lwzx    %r0, %r12, %r0 # по этому смещению получаем адрес
.text:827D58F8                 mtctr   %r0 # заносим полученный адрес в CTR
.text:827D58FC                 bctr # и делаем на него переход
# дальше сразу же начинается таблица адресов
.text:827D58FC # ---------------------------------------------------------------------------
.text:827D5900 off_827D5900:   .long loc_827D5A2C      # DATA XREF: scrThread__parseThread+A8o
.text:827D5900                                         # scrThread__parseThread+ACo
.text:827D5900                                         # case 1 (0x1)
.text:827D5904                 .long loc_827D5A44      # case 2 (0x2)
.text:827D5908                 .long loc_827D5A5C      # case 3 (0x3)
.text:827D590C                 .long loc_827D5A74      # case 4 (0x4)
.text:827D5910                 .long loc_827D5AA8      # case 5 (0x5)
# ....  и так далее, до 75 ...
.text:827D5A24                 .long loc_827D68E4      # case 74 (0x4a)
.text:827D5A28                 .long loc_827D66A8      # case 75 (0x4b)
.text:827D5A2C # ---------------------------------------------------------------------------
.text:827D5A2C loc_827D5A2C:                           # DATA XREF: scrThread__parseThread:off_827D5900o
.text:827D5A2C                 lwz     %r11, 0(%r31)   # case 1: // Add
.text:827D5A30                 subi    %r31, %r31, 4   # R11 = sp[0]; R10 = sp[-1]; sp--; sp[0] = R11+R10;
.text:827D5A34                 lwz     %r10, 0(%r31)
.text:827D5A38                 add     %r11, %r11, %r10

В большинстве случаев, IDA не может распознать таблицу переходов. Поскольку все команды PPC занимают 32 бита, как и адрес, сбоев дизассемблирования почти не происходит, но вместо адресов показываются очевидно "левые команды". Для адресов, начинающихся с 0x82, это LWZ в один и тот же регистр.
Все, что требуется в этом случае - сделать им undefine, и принудительно определить как offset-ы.

vmtcall

Для вызова виртуального метода объекта, нужно получить его адрес из VMT этого объекта.
Обычно, это делается так.

# R3 (первый параметр), при вызове содержит this. В поле по нулевому смещению, хранится указатель на VMT
#  (если, конечно, у объекта есть виртуальные методы). 
# В случае множественного наследования, у объекта может быть несколько VMT. (По одной, для каждого 
#  родителя с виртуальными методами). 
.text:827D6B64                 lwz     %r11, 0(%r3) # получаем адрес VMT
.text:827D6B68                 mr      %r4, %r28
.text:827D6B6C                 lwz     %r11, 0xC(%r11) # получаем адрес метода по смещению 0x0C, в данном случае, scrThread::Kill
.text:827D6B70                 mtctr   %r11 # заносим полученный адрес в CTR
.text:827D6B74                 bctrl # и, собственно, вызываем метод

вызов функции по указателю
Здесь все просто: если конструкция не похожа ни на одну из двух вышеприведенных - это вызов функции по указателю. Ничем другим это просто быть не может.

В некоторых случаях, при вызове функции или метода, может использоваться BCTR. Это происходит в том случае, если у функции нет stack frame, и, вызов функции/метода - последний оператор функции. Проще говоря, это конструкция return somefunc();

Иногда, после такого "вызова без возврата", встречается одинокая команда BLR. Насколько я выяснил, она нужна для корректной обработки исключений.

Offline

#7 26-06-2008 17:49

listener
From: Vice City
Registered: 09-11-2006
Posts: 616
Website

Re: Xenon или наше светлое будущее

Несколько мелких замечаний.

Про то, что стека нет, я уже писал. Сегодня я выяснил, что и регистра SP тоже нет. На самом деле, в его роли выступает R1, а SP - взаимозаменяемая мнемоника, прописанная в ассемблере. (Более того, какой регистр использовать в качестве SP - совершенно безразлично. На маке, в свое время, использовался R0)
Вообще, регистры R0, R1, R2 - зарезервированы компилятором под системные нужды, поэтому для пользовательского кода используются регистры, начиная с R3.
Кроме R0..2, зарезервирован R13 - он содержит указатель на TEB - thread environment block (в x86 до TEB можно было доступиться по адресу FS:0)

дополнительно к SCR и FPSCR есть VRSCR - регистр состояния для VMX.

VMX128 очень похож на VMX/AltiVec, но содержит не 32, а 128 регистров. Соответственно, опкоды у него свои (и IBM очень сильно извратилась, чтобы вместить 21 бит (3*7) туда, где раньше использовалось 15.

Last edited by listener (26-06-2008 17:52)

Offline

#8 05-12-2013 19:51

listener
From: Vice City
Registered: 09-11-2006
Posts: 616
Website

Re: Xenon или наше светлое будущее

Прошло какие-то пять с половиной лет, и я дозрел до продолжения.
До выхода GTAV все было хорошо: код был простой, разбирался легко, многие непонятные вещи можно было подсмотреть на PC. Все изменилось с выходом пятерки.

Самое большое (и самое неприятное) отличие от предыдущих игр - повсеместное использование VMX. Раньше, VMX применялся достаточно мало и, о непонятных командах можно было догадаться по смыслу. В пятерке же, началась битва за эффективность, и теперь мы можем наблюдать огромные куски кода, целиком состоящие из VMX-команд.

Проблема усугубляется тем, что единого внятного описания этих команд просто нет. Что-то есть в документации на Xenon, что-то есть в офицальном мануале по Altivec, о чем-то можно только догадаться.
Как человек в меру ленивый, я потратил день на сборку псевдокода всех используемых в GTAV VMX-команд.

Для начала - немного ASCII-art: формат регистра, который я использую в псевдокоде.

// all indices are from low addresses to high
        +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
char    | c0 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | cA | cB | cC | cD | cE | cF |
        +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
BYTE    | b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7 | b8 | b9 | bA | bB | bC | bD | bE | bF |
        +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
short   |   s0    |   s1    |   s2    |   s3    |   s4    |   s5    |   s6    |   s7    |
        +---------+---------+---------+---------+---------+---------+---------+---------+
WORD    |   w0    |   w1    |   w2    |   w3    |   w4    |   w5    |   w6    |   w7    |
        +---------+---------+---------+---------+---------+---------+---------+---------+
int     |        n0         |        n1         |        n2         |        n3         |
        +-------------------+-------------------+-------------------+-------------------+
DWORD   |        u0         |        u1         |        u2         |        u3         |
        +-------------------+-------------------+-------------------+-------------------+
float   |         x         |         y         |         z         |         w         |
        +-------------------+-------------------+-------------------+-------------------+

union VMXREG {
	struct sint8 { char c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, cA, cB, cC, cD, cE, cF };
	struct uint8 { BYTE b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, bA, bB, bC, bD, bE, bF };
	struct sint16 { short s0, s1, s2, s3, s4, s5, s6, s7 };
	struct uint16 { WORD w0, w1, w2, w3, w4, w5, w6, w7 };
	struct sint32 { int n0, n1, n2, n3 };
	struct uint32 { DWORD u0, u1, u2, u3 };
	struct flt { float x, y, z, w };
	char	cv[16];
	BYTE	bv[16];
	short	sv[8];
	WORD	wv[8];
	int	nv[4];
	DWORD	uv[4];
	float	fv[4];
};

Дальше - сам псевдокод.
Замечание: некоторые вещи, возможно, будут нуждаться в дополнительном разъяснении: например, понять, что делают lvlx/lvrx/stlx/stvx без картинки, практически невоззможно. А краткое описание vpkd3d128/vupkd3d128 занимает по четыре страницы на каждую.
Плюс к этому, для некоторых команд, IDA Altivec Plugin выводит некорректный порядок операндов - я это подкорректирую по мере нахождения.

lvlx/lvlx128            V, RA, RB		p = (RA + RB) & ~15; c = (RA + RB) & 15; V = *p << c*8; // load register from unaligned address to next aligned address, from left
lvrx128			V, RA, RB		p = (RA + RB) & ~15; c = (RA + RB) & 15; V = *p & ((1 << (16-c)*8)-1); // load register from next aligned address to unaligned address, from right
lvsl			V, RA, RB		c = (RA+RB)&15; V = (0x000102030405060708090A0B00C0D0E0F101112131415161718191A1B1C1D1E1F >> (16-c)*8) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;	// get 16 bytes from 32 bytes vector
lvsr                    V, RA, RB		c = (RA+RB)&15; V = (0x000102030405060708090A0B00C0D0E0F101112131415161718191A1B1C1D1E1F >> c*8) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;	// get 16 bytes from 32 bytes vector
lvx/lvx128		V, RA, RB		V = *((RA + RB) & ~15);
stvebx			V, RA, RB		p = (RA + RB); c = (RA + RB) & 15; *p = V.bv[c]; // store arbitrary byte from the register
stvehx			V, RA, RB		p = (RA + RB) & ~1; c = (RA + RB) & 15; memcpy (p, V.bv+c, 2); // store 2 bytes from the register
stvewx/stvewx128        V, RA, RB		p = (RA + RB) & ~3; c = (RA + RB) & 15; memcpy (p, V.bv+c, 4); // store 4 bytes from the register
stvlx/stvlx128		V, RA, RB		p = (RA + RB) & ~15; c = (RA + RB) & ~15; *p = (*p & ~ ((1 << (16-c)*8)-1)) | (V >> c*8); // store 16-c leftmost bytes of register
stvrx/stvrx128          V, RA, RB		p = (RA + RB) & ~15; c = (RA + RB) & ~15; *p = (*p & ((1 << (16-c)*8))-1)) | (V << ((16-c) * 8)); // store only c rightmost bytes of register
stvx/stvx128		V, RA, RB		*((RA + RB) & ~ 15) = V;
vaddfp/vaddfp128	VT, VA, VB		VT.x = VA.x + VB.x; VT.y = VA.y + VB.y; VT.z = VA.z + VB.z; VT.w = VA.w + VB.w;
vaddsws			VT, VA, VB		VT.n0 = satS32 (VA.n0 + Vb.n0); VT.n1 = satS32 (VA.n1 + Vb.n1); VT.n2 = satS32 (VA.n2 + Vb.n2); VT.n3 = satS32 (VA.n3 + Vb.n3); // saturate to range -2,147,483,648 .. 2,147,483,647
vaddubm			VT, VA, VB		VT.b0 = VA.b0 + Vb.b0; ...; VT.bF = VA.bF + Vb.bF; 
vadduwm			VT, VA, VB		VT.u0 = VA.u0 + Vb.u0; VT.u1 = VA.u1 + Vb.u1; VT.u2 = VA.u2 + Vb.u2; VT.u3 = VA.u3 + Vb.u3; 
vand/vand128		VT, VA, VB		VT = VA & VB; // 128 bit logical op
vandc128		VT, VA, VB		VT = VA & ~VB; // 128 bit logical op
vcfpsxws128		VT, VA, Imm		VT.n0 = satFtoS32 (VA.x * (1 << Imm)); VT.n1 = satFtoS32 (VA.y * (1 << Imm)); VT.n2 = satFtoS32 (VA.z * (1 << Imm)); VT.n3 = satFtoS32 (VA.w * (1 << Imm));	// saturate to range -2,147,483,648 .. 2,147,483,647
vcfpuxws128		VT, VA, Imm		VT.u0 = satFtoU32 (VA.x * (1 << Imm)); VT.u1 = satFtoU32 (VA.y * (1 << Imm)); VT.u2 = satFtoU32 (VA.z * (1 << Imm)); VT.u3 = satFtoU32 (VA.w * (1 << Imm));	// saturate to range 0 .. 2^32-1
vcfsx			VT, VA			VT.x = VA.n0; VT.y = VA.n1; VT.z = VA.n2; VT.w = VA.n3;
vcfux			VT, VA			VT.x = VA.u0; VT.y = VA.u1; VT.z = VA.u2; VT.w = VA.u3;
vcmpbfp128		VT, VA, VB		VT.u0 = ((VA.x < VB.x) << 31)| ((VA.x > -VB.x) << 30); ...; VT.u0 = ((VA.x > VB.x) << 31)| ((VA.x < -VB.x) << 30);
vcmpbfp128.		VT, VA, VB		VT.u0 = ((VA.x < VB.x) << 31)| ((VA.x > -VB.x) << 30); ...; VT.u0 = ((VA.x > VB.x) << 31)| ((VA.x < -VB.x) << 30); CR0:4 = 0; CR0:5 = VT == 0; CR0:6 = CR0:7 = 0;
vcmpeqfp/vcmpeqfp128	VT, VA, VB		VT.u0 = VA.x == VB.x ? 0xFFFFFFFF : 0; ...; VT.u3 = VA.w == VB.w ? 0xFFFFFFFF : 0;
vcmpeqfp128.		VT, VA, VB		VT.u0 = VA.x == VB.x ? 0xFFFFFFFF : 0; ...; VT.u3 = VA.w == VB.w ? 0xFFFFFFFF : 0; CR0:4 = 0; CR0:5 = (VA.x != VB.x) && ... && (VA.w != VB.w); CR0:6 = 0; CR0:7 = (VA.x == VB.x) && ... && (VA.w == VB.w);
vcmpequb		VT, VA, VB		VT.b0 = VA.b0 == VB.b0 ? 0xFF : 0; ...; VT.bF = VA.bF == VB.bF ? 0xFF : 0;
vcmpequb.		VT, VA, VB		VT.b0 = VA.b0 == VB.b0 ? 0xFF : 0; ...; VT.bF = VA.bF == VB.bF ? 0xFF : 0; CR0:4 = 0; CR0:5 = (VA.b0 != VB.b0) && ... && (VA.bF != VB.bF); CR0:6 = 0; CR0:7 = (VA.b0 == VB.b0) && ... && (VA.bF == VB.bF);
vcmpequw128		VT, VA, VB		VT.u0 = VA.u0 == VB.u0 ? 0xFFFFFFFF : 0; ...; VT.u3 = VA.u3 == VB.u3 ? 0xFFFFFFFF : 0;
vcmpequw128.		VT, VA, VB		VT.u0 = VA.u0 == VB.u0 ? 0xFFFFFFFF : 0; ...; VT.u3 = VA.u3 == VB.u3 ? 0xFFFFFFFF : 0; CR0:4 = 0; CR0:5 = (VA.u0 != VB.u0) && ... && (VA.u3 != VB.u3); CR0:6 = 0; CR0:7 = (VA.u0 == VB.u0) && ... && (VA.u3 == VB.u3);
vcmpgefp/vcmpgefp128	VT, VA, VB		VT.u0 = VA.x >= VB.x ? 0xFFFFFFFF : 0; ...; VT.u3 = VA.w >= VB.w ? 0xFFFFFFFF : 0;
vcmpgefp128.		VT, VA, VB		VT.u0 = VA.x >= VB.x ? 0xFFFFFFFF : 0; ...; VT.u3 = VA.w >= VB.w ? 0xFFFFFFFF : 0; CR0:4 = 0; CR0:5 = (VA.x < VB.x) && ... && (VA.w < VB.w); CR0:6 = 0; CR0:7 = (VA.x >= VB.x) && ... && (VA.w >= VB.w);
vcmpgtfp/vcmpgtfp128	VT, VA, VB		VT.u0 = VA.x > VB.x ? 0xFFFFFFFF : 0; ...; VT.u3 = VA.w > VB.w ? 0xFFFFFFFF : 0;
vcmpgtfp128.		VT, VA, VB		VT.u0 = VA.x > VB.x ? 0xFFFFFFFF : 0; ...; VT.u3 = VA.w > VB.w ? 0xFFFFFFFF : 0; CR0:4 = 0; CR0:5 = (VA.x > VB.x) && ... && (VA.w > VB.w); CR0:6 = 0; CR0:7 = (VA.x <= VB.x) && ... && (VA.w <= VB.w);
vcmpgtsw                VT, VA, VB		VT.u0 = VA.n0 > VB.n0 ? 0xFFFFFFFF : 0; ...; VT.u3 = VA.n3 > VB.n3 ? 0xFFFFFFFF : 0;
vcmpgtub		VT, VA, VB		VT.b0 = VA.b0 > VB.b0 ? 0xFF : 0; ...; VT.b7 = VA.b7 > VB.b7 ? 0xFF : 0;
vcmpgtuw.		VT, VA, VB		VT.u0 = VA.u0 > VB.u0 ? 0xFFFFFFFF : 0; ...; VT.u3 = VA.u3 > VB.u3 ? 0xFFFFFFFF : 0; CR0:4 = 0; CR0:5 = (VA.u0 > VB.u0) && ... && (VA.u3 > VB.u3); CR0:6 = 0; CR0:7 = (VA.u0 <= VB.u0) && ... && (VA.u3 <= VB.u3);
vcsxwfp128		VT, VA, Imm		VT.x = (float)VA.n0/(float)(1 << Imm); VT.y = (float)VA.n1/(float)(1 << Imm); VT.z = (float)VA.n2/(float)(1 << Imm); VT.w = (float)VA.n3/(float)(1 << Imm); 
vcuxwfp128		VT, VA, Imm		VT.x = (float)VA.u0/(float)(1 << Imm); VT.y = (float)VA.u1/(float)(1 << Imm); VT.z = (float)VA.u2/(float)(1 << Imm); VT.w = (float)VA.u3/(float)(1 << Imm); 
vexptefp128		VT, VA			VT.x = exp2 (VA.x); VT.y = exp2 (VA.y); VT.z = exp2 (VA.z); VT.w = exp2 (VT.w);
vlogefp128              VT, VA			VT.x = log2 (VA.x); VT.y = log2 (VA.y); VT.z = log2 (VA.z); VT.w = log2 (VA.w);
vmaddcfp128		VT, VA, VB, VC		VT.x = VA.x * VB.x + VC.x; VT.y = VA.y * VB.y + VC.y; VT.z = VA.z * VB.z + VC.z; VT.w = VA.w * VB.w + VC.w;	// VT = VB
vmaddfp/vmaddfp128	VT, VA, VB, VC		VT.x = VA.x * VB.x + VC.x; VT.y = VA.y * VB.y + VC.y; VT.z = VA.z * VB.z + VC.z; VT.w = VA.w * VB.w + VC.w;	// VT = VC
vmaxfp/vmaxfp128	VT, VA, VB		VT.x = max(VA.x, VB.x); VT.y = max(VA.y, VB.y); VT.z = max(VA.z, VB.z); VT.w = max(VA.w, VB.w); 
vminfp/vminfp128	VT, VA, VB		VT.x = min(VA.x, VB.x); VT.y = min(VA.y, VB.y); VT.z = min(VA.z, VB.z); VT.w = min(VA.w, VB.w); 
vmrghb			VT, VA, VB		VT.b0 = VA.b0; VT.b1 = VB.b0; ...; VT.bE = VA.b7; VT.bF = VB.b7;
vmrghh			VT, VA, VB		VT.w0 = VA.w0; VT.w1 = VB.w0; VT.w2 = VA.w1; VT.w3 = VB.w1; VT.w4 = VA.w2; VT.w5 = VB.w2; VT.w6 = VA.w3; VT.w7 = VB.w3;
vmrghw/vmrghw128	VT, VA, VB		VT.u0 = VA.u0; VT.u1 = VB.u0; VT.u2 = VA.u1; VT.u3 = VB.u1;
vmrglb			VT, VA, VB		VT.b0 = VA.b8; VT.b1 = VB.b8; ...; VT.bE = VA.bF; VT.bF = VB.bF;
vmrglh			VT, VA, VB		VT.w0 = VA.w4; VT.w1 = VB.w4; VT.w2 = VA.w5; VT.w3 = VB.w5; VT.w4 = VA.w6; VT.w5 = VB.w6; VT.w6 = VA.w7; VT.w7 = VB.w7;
vmrglw/vmrglw128	VT, VA, VB		VT.u0 = VA.u2; VT.u1 = VB.u2; VT.u2 = VA.u3; VT.u3 = VB.u3;
vmsum3fp128		VT, VA, VB 		VT.x = VT.y = VT.z = VT.w = VA.x * VB.x + VA.y * VB.y + VA.z * VB.z
vmsum4fp128		VT, VA, VB 		VT.x = VT.y = VT.z = VT.w = VA.x * VB.x + VA.y * VB.y + VA.z * VB.z + VA.w * VB.w
vmulfp128		VT, VA, VB		VT.x = VA.x * VB.x; VT.y = VA.y * VB.y; VT.z = VA.z * VB.z; VT.w = VA.w * VB.w;
vnmsubfp/vnmsubfp128	VT, VA, VB, VC        	VT.x = -((VA.x*VB.x)-VC.x); VT.y = -((VA.y*VB.y)-VC.y); VT.z = -((VA.z*VB.z)-VC.z); VT.w = -((VA.w*VB.w)-VC.w);	// WARNING!! Ida VMX plugin shows parameters in different order!!
vnor128			VT, VA, VB		VT = ~(VA | VB); // 128 bit logical op
vor/vor128    		VT, VA, VB		VT = VA | VB; // 128 bit logical op
vperm/vperm128      	VT, VA, VB, VC		for (i = 0; i < 15; i++) { BYTE t = VC.bv[i] & 31; VT.bv[i] = t < 16 ? VA.bv[t] : VB.bv[t-16]; }
vpermwi128		VT, VA, Imm		VT.x = VA.fv[Imm >> 6]; VT.y = VA.fv[(Imm >> 4)&3]; VT.z = VA.fv[(Imm >> 2)&3]; VT.w = VA.fv[Imm & 3];
vpkd3d128		VT, VA, Type, Mask, sh	D3DPack
vpkshus/vpkshus128	VT, VA, VB		VT.b0 = satS16toU8 (VA.s0); ...; VT.b7 = satS16toU8 (VA.s7); VT.b8 = satS16toU8 (VB.s0); ...; VT.bF = satS16toU8 (VB.s7);	// saturate signed short to 0..255
vpkswss/vpkswss128	VT, VA, VB		VT.s0 = satS32toS16 (VA.n0); ...; VT.s3 = satS32toS16 (VA.n3); VT.s4 = satS32toS16 (VB.n0); ...; VT.s7 = satS32toS16 (VB.n3);	// saturate signed int to -32768..32767
vpkswus128		VT, VA, VB		VT.w0 = satS32toU16 (VA.n0); ...; VT.w3 = satS32toU16 (VA.n3); VT.w4 = satS32toU16 (VB.n0); ...; VT.w7 = satS32toU16 (VB.n3);	// saturate signed int to 0..65535
vpkuwus128              VT, VA, VB		VT.w0 = satU32toU16 (VA.u0); ...; VT.w3 = satU32toU16 (VA.u3); VT.w4 = satU32toU16 (VB.u0); ...; VT.w7 = satU32toU16 (VB.u3);	// saturate unsigned int to 0..65535
vrefp/vrefp128		VT, VA			VT.x = 1.f/VA.x; VT.y = 1.f/VA.y; VT.z = 1.f/VA.z; VT.w = 1.f/VA.w;
vrfim128		VT, VA			VT.n0 = floor(VA.x); VT.n1 = floor(VA.y); VT.n2 = floor(VA.z); VT.n3 = floor(VA.w); 
vrfin/vrfin128		VT, VA			VT.n0 = round(VA.x); VT.n1 = round(VA.y); VT.n2 = round(VA.z); VT.n3 = round(VA.w); 
vrfip128		VT, VA			VT.n0 = ceil(VA.x); VT.n1 = ceil(VA.y); VT.n2 = ceil(VA.z); VT.n3 = ceil(VA.w); 
vrfiz/vrfiz128		VT, VA			VT.n0 = VA.x; VT.n1 = VA.y; VT.n2 = VA.z; VT.n3 = VA.w; // convert float to int, rounding toward zero
vrlimi128		VT, VA, Mask, Rot	Tmp = (VA << Rot*32)|(VA >> (4-Rot)*32); VT.x = (Mask&8) ? Tmp.x : VT.x; VT.y = (Mask&4) ? Tmp.y : VT.y; VT.z = (Mask&2) ? Tmp.z : VT.z; VT.w = (Mask&1) ? Tmp.w : VT.w; 
vrsqrtefp/vrsqrtefp128	VT, VA			VT.x = 1.f/sqrt(VA.x); VT.y = 1.f/sqrt(VA.y); VT.z = 1.f/sqrt(VA.z); VT.w = 1.f/sqrt(VA.w);
vsel          		VT, VA, VB, VC		VT = (VA & ~VC) | (VB & VC);	
vsel128			VT, VA, VB		VT = (VA & ~VT) | (VB & VT);	
vsl			VT, VA, VB		VT = VA << (VB.bF & 7);	// 128 bit shift, up to 7 bit
vslb			VT, VA, VB		VT.b0 = VA.b0 << (VB.b0 & 7); ...; VT.bF = VA.bF << (VB.bF & 7);
vsldoi/vsldoi128	VT, VA, VB, Imm		VT = (VA << (Imm * 8)) | (VB >> ((16-Imm) * 8);	// 256 bit shift
vslh			VT, VA, VB		VT.w0 = VA.w0 << (VB.w0 & 15); ...; VT.w7 = VA.w7 << (VB.w7 & 15);
vslo128               	VT, VA, VB		VT = VA << (VB.bF & 0x78);	// 128 bit shift, byte aligned
vslw128			VT, VA, VB		VT.u0 = VA.u0 << (VB.u0 & 31); VT.u1 = VA.u1 << (VB.u1 & 31); VT.u2 = VA.u2 << (VB.u2 & 31); VT.u3 = VA.u3 << (VB.u3 & 31); 
vspltb			VT, VA, Imm		VT.b0 = ... = VT.bF = VA.bv[Imm]; // Imm in range 0..15
vsplth			VT, VA, Imm		VT.w0 = ... = VT.w7 = VA.wv[Imm]; // Imm in range 0..7
vspltisb		VT, VA, Imm		VT.c0 = ... = VT.cF = Imm; // Imm in range -16 .. 15 (5 bit)
vspltish		VT, VA, Imm		VT.s0 = ... = VT.s7 = Imm; // Imm in range -16 .. 15 (5 bit)
vspltisw/vspltisw128	VT, VA, Imm		VT.n0 = VT.n1 = VT.n2 = VT.n3 = Imm;	// Imm in range -16 .. 15 (5 bit)
vspltw/vspltw128	VT, VA, Imm		VT.u0 = VT.u1 = VT.u2 = VT.u3 = VA.uv[Imm];	// Imm in range 0..3
vsr			VT, VA, VB		VT = VA >> (VB.bF & 7);	// 128 bit shift, up to 7 bit
vsrab			VT, VA, VB		VT.b0 = VA.b0 >> (VB.b0 & 7); ...; VT.bF = VA.bF >> (VB.bF & 7);	// sign extended
vsraw128         	VT, VA, VB		VT.u0 = VA.u0 >> (VB.u0 & 31); VT.u1 = VA.u1 >> (VB.u1 & 31); VT.u2 = VA.u2 >> (VB.u2 & 31); VT.u3 = VA.u3 >> (VB.u3 & 31); // sign extended
vsrb			VT, VA, VB		VT.b0 = VA.b0 >> (VB.b0 & 7); ...; VT.bF = VA.bF >> (VB.bF & 7);
vsrh   			VT, VA, VB		VT.w0 = VA.w0 >> (VB.w0 & 15); ...; VT.w7 = VA.w7 >> (VB.w7 & 15);
vsro128			VT, VA, VB		VT = VA >> (VB.bF & 0x78);	// 128 bit shift, byte aligned
vsrw128			VT, VA, VB		VT.u0 = VA.u0 >> (VB.u0 & 31); VT.u1 = VA.u1 >> (VB.u1 & 31); VT.u2 = VA.u2 >> (VB.u2 & 31); VT.u3 = VA.u3 >> (VB.u3 & 31); 
vsubfp/vsubfp128        VT, VA, VB		VT.x = VA.x - VB.x; VT.y = VA.y - VB.y; VT.z = VA.z - VB.z; VT.w = VA.w - VB.w;
vsububm      		VT, VA, VB		VT.b0 = VA.b0 - VB.b0; ....; VT.bF = VA.bF - VB.bF;
vsubuwm	 		VT, VA, VB		VT.u0 = VA.u0 - VB.u0; VT.u1 = VA.u1 - VB.u1; VT.u2 = VA.u2 - VB.u2; VT.u3 = VA.u3 - VB.u3;
vupkd3d128		VT, VA, Imm		VT = D3DUnpack (VA, Imm);
vupkhsb128		VT, VA			VT.s7 = VA.c7; VT.s6 = VA.c6; VT.s5 = VA.c5; VT.s4 = VA.c4; VT.s3 = VA.s3; VT.s2 = VA.s2; VT.s1 = VA.c1; VT.s0 = VT.c0;	// sign extended
vupklsb128		VT, VA			VT.s7 = VA.cF; VT.s6 = VA.cE; VT.s5 = VA.cD; VT.s4 = VA.cC; VT.s3 = VA.sB; VT.s2 = VA.sA; VT.s1 = VA.c9; VT.s0 = VT.c8;	// sign extended
vxor/vxor128		VT, VA, VB		VT = VA^VB;	// 128 bit logical op

Offline

#9 06-12-2013 11:54

listener
From: Vice City
Registered: 09-11-2006
Posts: 616
Website

Re: Xenon или наше светлое будущее

Судя по отсутствию комментариев, либо всем все понятно, либо все онемели от ужаса. Логичнее предположить второе. (Есть, конечно, вариант, что это никто не прочел, но, пока, на нем останавливаться не будем...)
Наверное, стоит разобрать некоторые места подробно и с примерами.

0. Некоторые замечания по устройству VMX128.

VMX128 можно считать аналогом SSE. В нем, так же, производятся операции над 128-битными регистрами, которые, в зависимости от команды, могут представляться массивом элементов того или иного типа.
В отличие от оригинального VMX/Altivec, количество регистров увеличено с 32 до 128. Кроме этого, добавлено несколько очень полезных команд, например, vmsum3fp128 и vmsum4fp128.

Еще одно важное отличие - из-за увеличившегося количества регистров, стало невозможно использовать в команде четыре различных регистра (4*ln2(128) = 28, что не укладывается в отведенные на параметры 26 бит).
Как следствие, команды vmadd и vnmsub существуют в нескольких различных вариантах (например vmaddfp, vmaddfp128 и vmaddcfp128).
Все три команды делают одно и то же: VT = VA * VB + VC. Но, если оригинальный vmaddfp использует полноценные четыре регистра, 128-варианты ограничены только тремя. Из-за этого, в них VT должен совпадать с одним из регистров-параметров.
Т.е., для vmadd128 получается VC += VA * VB, а для vmaddcfp128: VB *= VA; VB += VC. В коде, тем не менее, по историческим причинам записываются все четыре регистра (два из которых должны совпадать)
Аналогичная ситуация - с vnmsubfp/vnmsubfp128. В vnmsubfp128 получается VB = VC - VA * VB.

Еще одно ограничение - для команды vperm128, в качестве маски могут использоваться только регистры %vr0 .. %vr7.

1. Загрузка и сохранение операндов.

Традиционно, векторные операнды должны располагаться по адресам, кратным 16 байтам. Попытка получить вектор по невыровненному адресу в SSE, приведет к возникновению исключения.
В VMX пошли даже дальше, и адреса выравниваются автоматически: все команды VMX, которые обращаются к памяти, просто обнуляют младшие четыре бита адреса.

В качестве адреса, выступает сумма двух регистров общего назначения - как правило, в одном хранится база (например, указатель на объект или массив), в другом - смещение в объекте или массиве.
В этом правиле есть одно исключение: если в качестве первого регистра (RA) используется %r0, то используется не значение %r0, а просто 0.

Для загрузки из памяти используется команда lvx, для сохранения - stvx.

1.1. Загрузка невыравненного операнда.

Те, кто работал с SSE, должны вспомнить, что в SSE можно загружать одиночный float с произвольного адреса. Как обстоят с этим дела в VMX ?
В VMX с этим еще лучше: в нем можно загружать не только отдельное число, но и операнд произвольной длины с произвольного адреса. Для этого, есть две способа: один классический, второй - введенный в VMX128.

Для наглядности, стоит снова прибегнуть к ASCII-artу.
Пусть у нас есть 32 байта в памяти, и два VMX-регистра с некоторыми значениями

          +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
87654320: | A0 | A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | AA | AB | AC | AD | AE | AF |
          +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
87654330: | B0 | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 | B9 | BA | BB | BC | BD | BE | BF |
          +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

          +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
%vr8:     | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 3A | 3B | 3C | 3D | 3E | 3F |
          +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
%vr9:     | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 4A | 4B | 4C | 4D | 4E | 4F |
          +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

%r5 = 0x87654300
%r6 = 0x00000039

"Классический" способ, подразумевает использование команд lvsl/lvsr и vperm.
Команда vperm позволяет скомбинировать содержимое двух регистров в один, при этом, в отличие от SSE-аналога, выбор отдельных компонентов, производится с точностью до отдельного байта.
Т.о., чтобы загрузить 16-байтовый операнд с адреса 0x87654339, нужно загрузить 32 байта с адреса 0x87654330 в два регистра, после чего скомбинировать 7 байт из первого регистра и 9 байт из второго.

lvx 	%vr8, %r5, %r6	# загружаем первые 16 байт
addi	%r7, %r6, 0x10	# подготавливаем смещение второй части ..
lvx	%vr9, %r5, %r7	# .. и загружаем ее
vlsl	%vr7, %r5, %r6	# подготавливаем маску: берем из 32-байтного числа 16 последовательных байт, начиная с 9-го байта.
                        # получившаяся маска: 0x090A0B0C0D0E0F101112131415161718
vperm	%vr10, %vr8, %vr9, %vr7	# собираем нужные байты в один регистр: 
#           +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# %vr10:    | A9 | AA | AB | AC | AD | AE | AF | B0 | B1 | B2 | B3 | B4 | B5 | B9 | B7 | B8 |
#           +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

Недостаток метода в том, что в нем всегда читается из памяти 32 байта, даже если можно было обойтись меньшим количеством. Кроме того, сохранить что-то в память таким способом - достаточно проблематично
Чтобы избежать этого, в VMX128 добавили команды lvlx и lvrx, которые читают произвольное количество байт с произвольного адреса, выравнивая их, сосответственно, по левому либо правому краю регистра
Что из этого получается, проще показать на картинке:

lvlx 	%vr8, %r5, %r6
lvrx 	%vr9, %r5, %r6

#           +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# %vr8:     | A9 | AA | AB | AC | AD | AE | AF | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
#           +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# %vr9:     | 00 | 00 | 00 | 00 | 00 | 00 | 00 | A7 | A8 | A9 | AA | AB | AC | AD | AE | AF |
#           +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

Аналогично, для записи части регистра в память, введены stvlx и stvrx.
Кроме этого, есть команды stvbx/stvhx/stvwx, которые позволяют сохранить в память произвольный байт, WORD и DWORD из регистра. Адрес в памяти выравнивается на размер сохраняемого операнда.

1.2. Операции с данными в регистрах

Забавный факт: в VMR отсутствует команда mr для векторных регистров. Вместо нее используется конструкция vor VT, VS, VS => VT = VS


Нужно ли продолжать? Стоит ли описать подробно, как работают Splat и Merge; уточнить, как работает преобразование типов с saturationи т.д., или все это и так знают?

Offline

#10 06-12-2013 14:04

lexa234
Registered: 11-11-2013
Posts: 8

Re: Xenon или наше светлое будущее

Да, продолжай, очень интересно.

Last edited by lexa234 (06-12-2013 14:06)

Offline

#11 07-12-2013 11:52

Seemann
Registered: 07-08-2006
Posts: 2,155

Re: Xenon или наше светлое будущее

Продолжай. Накопить знания о строении GTA никогда не лишнее. Может сильно пригодиться в будущем.
Если вернуться в прошлое, то ты присоединился к моддингу, после того как я начал выкладывать описания разных адресов памяти. Аналогично, подобные топики могут привлечь на форум новых специалистов.

Offline

Board footer

Powered by FluxBB