Содержание


Ассемблер для Power-архитектуры

Часть 1. Концепции программирования и простейшие команды PowerPC

Добираясь до сердца машины

Comments

Серия контента:

Этот контент является частью # из серии # статей: Ассемблер для Power-архитектуры

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Ассемблер для Power-архитектуры

Следите за выходом новых статей этой серии.

POWER5 и другие процессоры семейства PowerPC

Процессор POWER5™ -- новейший процессор в линейке высокопроизводительных процессоров, поддерживающих систему команд PowerPC®. Первым 64-битным процессором в этой линейке был POWER3. Процессор G5 от Macintosh был расширением POWER4 с дополнительным устройством для векторной обработки данных. Процессоры POWER5 -- новейшие из поколения процессоров POWER, обладают как двухядерностью, так и симметричной многопоточностью. Это позволяет обрабатывать одному чипу до 4-х потоков одновременно. Помимо этого, каждый поток может обрабатывать до 5 инструкций за один такт.

Система команд PowerPC используется во многих микросхемах от IBM и других производителей, а не только в линейке POWER. Она используется в серверах, рабочих станциях, а также в high-end встроенных устройствах (например, в устройствах цифровой видеозаписи, маршрутизаторах, но не в мобильных телефонах). Чип Gekko используется в GameCube от Nintendo, Xenon используется в Microsoft Xbox 360. Cell Broadband Engine -- многообещающая архитектура, использующая систему команд PowerPC совместно с восемью векторными процессорами. Sony PlayStation 3 будет использовать Cell, также как и ряд производителей, рассматривающих его для различных мультимедийных приложений.

Как вы можете видеть, система команд PowerPC может быть использована далеко за рамками серии POWER процессоров. Сама система команд может использоваться как в 64-битном режиме, так и в упрощенном 32-битном режиме. Процессоры POWER5 поддерживают оба режима. В свою очередь дистрибутивы Linux для POWER5 поддерживают оба типа приложений -- как скомпилированные для 64-битого, так и для 32-битного набора команд.

Доступ к процессору POWER5

Все современные сервера IBM iSeries и pSeries используют POWER5 процессоры и могут работать под управлением Linux. К тому же open source разработчики могут запросить доступ к машинам, использующим POWER5, для переноса приложений, используя IBM OpenPower Project (смотрите Ресурсы для ссылки). Запуская дистрибутивы для PowerPC на G5 Power Macintosh, вы получаете доступ к слегка модифицированному процессору POWER4, тоже 64-битному. G4 и более ранние процессоры -- 32-битные.

Debian, Red Hat, SUSE и Gentoo -- все имеют один или несколько дистрибутивов, поддерживающих процессоры POWER5. Причем Red Hat Enterprise Linux AS, SUSE Linux Enterprise Server и OpenSUSE -- единственные дистрибутивы, полностью поддерживающие серверы IBM iSeries (остальные дистрибутивы поддерживают сервера IBM pSeries).

Высокоуровневое программирование в сравнении с низкоуровневым программированием

Большинство языков программирования практически процессоронезависимы. Несмотря на то, что они могут иметь некоторые особенности, связанные с возможностями процессора, они в большей степени зависят от операционной системы, чем от процессора. Подобные высокоуровневые языки созданы специально для того, чтобы обеспечить дистанцию между программистом и архитектурой аппаратного обеспечения. Это делается по ряду причин. Одна из них переносимость, но, пожалуй, более важной является возможность обеспечить дружественную модель, соответствующую мышлению программиста, а не архитектуре микросхемы.

Однако, программируя на ассемблере, вы работаете непосредственно с системой команд процессора. Это означает, что вы имеете точно такое же видение системы, как и аппаратное обеспечение. Это может привести к сложностям при программировании на ассемблере, поскольку модель программирования направлена на то, чтобы заставить аппаратное обеспечение работать, нежели на отражение проблемы, решаемой программистом. Преимуществами являются: возможность упростить работу на системном уровне и проще выполнять процессорозависимые оптимизации. А недостатки заключаются в том, что вы вынуждены мыслить на этом уровне, вы привязаны к конкретной линейке процессоров, и вам часто приходится делать много дополнительной работы прежде, чем будет построена точная модель для решения проблемы.

Одна из хороших черт ассемблера, о которой многие люди не думают, состоит в том, что он очень конкретный. В высокоуровневых языках очень многое подразумевается в каждом выражении. Иногда вы можете только удивляться скрытому смыслу выражений. Программируя на ассемблере, вы имеете полное понимание того, что именно делает аппаратное обеспечение. Вы можете шаг за шагом отследить все изменения, происходящие на аппаратном уровне.

Основы ассемблера

Перед тем как перейти к собственно набору команд, нужно заметить, что два ключевых момента для понимания ассемблера – это понимание модели памяти и цикла команд.

Модель памяти очень проста. Память хранит только одно -- числа ограниченного диапазона, называемые байтами (на большинстве компьютеров это числа от 0 до 255). Каждая ячейка памяти может быть найдена, используя последовательный адрес. Представьте себе огромную комнату, полную почтовых ящиков. Каждый ящик пронумерован, и все ящики одного размера. Это единственное, что может хранить компьютер. Поэтому все должно быть, в конечном счете, представлено в терминах чисел фиксированного диапазона. К счастью, большинство процессоров для обработки больших чисел (а также чисел с плавающей точкой) умеют обрабатывать несколько байтов вместе как единый объект. Однако то, как конкретные команды обрабатывают область памяти, не зависит от того, что каждая ячейка памяти хранится в одном и том же виде. В дополнение к этой памяти процессор также поддерживает набор регистров, которые служат для временного хранения обрабатываемых данных, а так же конфигурационных параметров.

Основной процесс, который контролирует процессоры, -- это командный цикл (fetch-execute cycle). Процессор имеет регистр, называемый счетчиком команд (program counter), который содержит адрес следующей команды, которую надо исполнить. Fetch-execute работает следующим образом:

  • Считывается program counter и инструкция из адреса, указанного в нем
  • program counter обновляется и указывает на следующую команду
  • Команда расшифровывается
  • Загружаются все ячейки памяти, необходимые для выполнения команды
  • Выполняются вычисления
  • Сохраняются результаты

То, как это происходит на самом деле, в действительности более сложно, в особенности из-за того, что процессор POWER5 может выполнять до пяти команд одновременно. Однако этого достаточно для общего представления.

PowerPC может быть охарактеризована, как load/store архитектура. Под этим понимается, что все вычисления производятся в регистрах, а не в основной памяти. Это отличается от, скажем, архитектуры x86, в которой практически каждая команда может оперировать с памятью, регистрами или и с тем и другим. Load/store-архитектура обычно имеет много регистров общего назначения. PowerPC имеет 32 регистра общего назначения и 32 регистра для работы с числами с плавающей точкой, которые нумеруются (в отличие от x86, где регистры именуются, а не нумеруются). PowerPC также имеет несколько специальных регистров для хранения адресов возврата и информации о состоянии. Есть также другие регистры специального назначения, доступные для управляющих программ, но это выходят за рамки данной статьи. Регистры общего назначения 32-битные для 32-битной архитектуры и 64-битные для 64-битной архитектуры. Эта серия статей посвящена 64-битной архитектуре.

Команды ассемблера очень низкоуровневые -- они могут выполнить только одну (иногда несколько) операций за раз. Например, если на C вы можете написать d = a + b + c - d + some_function(e, f - g), на ассемблере каждое сложение, вычитание и вызов функции должны быть отдельными командами. А на самом деле вызов функции может быть несколькими командами. Это может иногда показаться утомительным. Однако в этом есть три важных преимущества. Первое -- простое знание ассемблера поможет вам лучше писать на высокоуровневых языках, поскольку вы будете понимать, что происходит на низком уровне. Второе -- тот факт, что вы имеете доступ ко всем мельчайшим деталям ассемблера, означает, что вы можете оптимизировать критичные к скорости выполнения участки кода сверх того, что оптимизирует компилятор. Компиляторы достаточно хорошо оптимизируют код. Однако, знание ассемблера может помочь вам понять и оптимизацию, которую выполняет компилятор (ключ -S для gcc позволит вам получить код на ассемблере вместо объектного кода) и поможет увидеть, если оптимизация отсутствует. Третье -- вы получаете доступ ко всей мощи PowerPC, многое из которой позволит сделать ваш код более компактным, чем код, получаемый при помощи языков высокого уровня.

Поэтому без дальнейшей суеты перейдем к набору команд PowerPC. Вот некоторые команды PowerPC, которые могут быть полезны для начинающих:

li REG, VALUE

Загружает в регистр REG число VALUE

add REGA, REGB, REGC

складывает REGB с REGC и сохраняет в REGA

addi REGA, REGB, VALUE

добавляет число VALUE к REGB и сохраняет результат в REGA

mr REGA, REGB

копирует значение из REGB в REGA

or REGA, REGB, REGC

выполняет логическое "или" между REGB и REGC, и сохраняет в REGA

ori REGA, REGB, VALUE

выполняет логическое "или" между REGB и VALUE, и сохраняет результат в REGA

and, andi, xor, xori, nand, nand и nor

все эти команды действуют по такому же принципу, что и "or" и "ori" для других логических операций

ld REGA, 0(REGB)

использовать содержимое REGB как адрес значения, которое загрузить в REGA

lbz, lhz, and lwz

эти команды используют тот же формат, только оперируют с байтами, полусловами и словами соответственно ("z" означает, что остальная часть регистра заполняется нулями)

b ADDRESS

переход к команде по адресу ADDRESS

bl ADDRESS

вызвать подпрограмму по адресу ADDRESS

cmpd REGA, REGB

сравнить содержимое REGA и REGB и установить соответствующие биты статус-регистра

beq ADDRESS

перейти к адресу ADDRESS, если регистры, которые перед этим сравнивали, равны

bne, blt, bgt, ble, and bge

все эти команды имеют такой же вид, только проверяют на неравенство, меньше, больше, меньше или равно и больше или равно соответственно

std REGA, 0(REGB)

использовать содержимое REGB как адрес ячейки памяти, в которой сохранить REGA

stb, sth, and stw

эти команды имеют такой же формат, но работают с байтами, полусловами и словами соответственно

sc

выполняет системный вызов ядра

Отметим что все команды, которые вычисляют значения, используют первый аргумент как место, в которое будет сохранен результат. Во всех этих командах регистры обозначаются только их номерами. Например, команда, загружающая число 12 в регистр 5, имеет вид li 5, 12. Зная формат команды, мы можем определить, что 5 это номер регистра, а 12 -- это число, никаких других указателей на это нет.

Каждая команда PowerPC имеет длину 32 бита. Первые 6 бит определяют команду, а остальные имеют различное значение, зависящее от команды. Тот факт, что команды имеют фиксированную длину, позволяет процессору выполнять их более эффективно. Однако ограничение в 32 бита может вызвать некоторые затруднения, которые мы перечислим. Преодоление этих затруднений будет обсуждено в части 2.

Многие вышеперечисленные команды используют PowerPC расширенную символику. Это означает, что, на самом деле, они являются конкретизацией более общих команд. Например, все условные переходы, упомянутые выше, -- это конкретизация команды bc (branch conditional). Форма команды bc следующая: bc MODE, CBIT, ADDRESS. CBIT -- это бит регистра состояния, который нужно проверить. MODE имеет много интересных нюансов, но для простых случаев устанавливается равным 12, если переход должен быть выполнен, когда требуемый бит был установлен, и 4 -- если не установлен. Некоторые важные биты регистра состояния: 8 -- меньше, 9 -- больше и 10 -- равно. Поэтому команда beq ADDRESS на самом деле является bc 12, 10 ADDRESS. Также li -- особая форма addi и mr -- особая форма or. Подобная расширенная мнемоника помогает сделать программы на PowerPC ассемблере более читаемыми и легкими в написании, при этом не лишая возможности использовать всю доступную мощь для более серьезных программ и программистов.

Ваша первая программа для POWER5

А сейчас давайте перейдем к какому-нибудь реальному коду. Первая программа, которую мы напишем, не делает ничего, кроме как загружает два значения, складывает их вместе и заканчивает свою работу с кодом возврата, равным результату. Наберите следующее в файле sum.s:

Листинг 1. Ваша первая программа для POWER5
#Раздел данных содержит описание памяти доступной для записи
.data
.align 3  #устанавливает 8-байтовые границы

#Отсюда мы будем загружать наше первое значение
first_value:
        #"quad" порождает 8-байтовый объект
        .quad 1
second_value:
        .quad 2

#Опишите "official procedure descriptor" в его собственном разделе
.section ".opd","aw"
.align 3 #устанавливает 8-байтовые границы

# описание процедуры для ._start
.global _start
#Отметим, что описание называется _start,
#а начало кода обозначается ._start
_start:
        .quad ._start, .TOC.@tocbase, 0

#Перейдем к разделу  ".text" кода программы
.text
._start:
        #Используем регистр 7 для загрузки адреса
        #64-битный адрес должен загружаться частями по 16 бит

        #Загружаем верхние части адреса
        lis 7, first_value@highest
        ori   7, 7, first_value@higher
        #Сдвигаем их в верхнюю часть
        rldicr 7, 7, 32, 31
        #Загружаем нижние части адреса
        oris 7, 7, first_value@h
        ori  7, 7, first_value@l

        #Загружаем первое значение в регистр 4 из адреса, 
        который мы только что загрузили
        ld 4, 0(7)

        #Загружаем адрес для второго значения
        lis 7, second_value@highest
        ori 7, 7, second_value@higher
        rldicr 7, 7, 32, 31
        oris 7, 7, second_value@h
        ori 7, 7, second_value@l

        #Загружаем второе значение в регистр 5 из только что загруженного адреса
        ld 5, 0(7)

        #Вычисляем значение и сохраняем его в 6
        add 6, 4, 5

        #Завершаем программу с кодом возврата
        li 0, 1    # системный вызов помещаем в регистр 0
        mr 3, 6    # Копируем результат в регистр 3 для системного вызова

        sc

Перед тем как обсуждать программу, давайте соберем и запустим ее. Первый шаг в построении программы -- это ассемблирование:

as -a64 sum.s -o sum.o

Эта процедура создает файл, называемый sum.o, содержащий объектный код, который является версией вашей программы в машинных кодах, плюс содержит дополнительную информацию для компоновщика. Ключ "-m64" говорит ассемблеру, что вы используете 64-битный ABI и 64-битные команды. Генерируемый объектный код представляет собой версию в машинных кодах, но он не может быть запущен в том виде, в каком он есть. Необходимо его скомпоновать, чтобы он был готов для загрузки и запуска операционной системой. Для компоновки сделайте следующее:

ld -melf64ppc sum.o -o sum

Эта команда произведет исполняемый файл sum. Чтобы запустить программу, выполните:

./sum
echo $?

Эти команды выведут на печать «3», что является конечным результатом. Теперь давайте посмотрим, как в действительности работает код.

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

Объектные и исполняемые файлы делятся на "разделы". Когда программа выполняется, каждая раздел загружается в особое место в пространстве адресов. У них разная защита и цели. Основные разделы, которыми мы будем интересоваться, следующие:

.data

содержит заранее инициализированные данные, используемые программой

.text

содержит собственно код программы (исторически известный как текст программы)

.opd

содержит "official procedure declarations", которое используется для помощи в компоновке функций и установлении точки входа программы (точка входа -- это первая команда, которая должна быть выполнена в данном коде)

Первое, что делает наша программа, это переключается на раздел .data и устанавливает 8-байтовые границы (.align 3 изменяет счетчик внутренних адресов ассемблера, пока он не будет кратным 2^3).

Строка, в которой написано first_value:, -- это описание имени. Она создает имя first_value, которое является синонимом адреса следующего описания или команды, перечисленной в ассемблере. Заметим, что first_value является константой, а не переменной, хотя адрес в памяти, на который она ссылается, может быть обновляем. first_value -- это наиболее простой способ ссылки на конкретный адрес в памяти.

Следующая инструкция, .quad 1, создает 8-байтовый объект, содержащий величину 1.

После этого мы имеем такой же набор инструкций, определяющих адрес second_value, содержащий 8-байтовый элемент данных с величиной 2.

.section ".opd", "aw" создает ".opd" раздел для описаний нашей процедуры. Мы устанавливаем в этом разделе 8-байтовые границы. Затем мы декларируем, что символ _start должен быть глобальным, что означает, что он не будет отброшен после компоновки. После этого декларируется сам символ _start (в .global ассемблер не определяет _start, он только отмечает, что _start должен быть глобальным, если он определен). Следующие три генерируемых элемента данных являются дескриптором процедуры, который будет обсужден в другой статье.

Теперь мы можем перейти к собственно коду программы. Инструкция .text указывает ассемблеру, что мы переключаемся на текстовый раздел. После этого следует собственно определение ._start.

Первый набор команд загружает адрес первой величины (но не саму величину). Поскольку команды PowerPC имеют длину только 32 бита, внутри команд, загружающих постоянные величины, в наличии имеется только 16 бит (вспомним, что адресfirst_value является константой). Поэтому, так как адрес может быть до 64 бит в длину, мы должны загружать его небольшими порциями (часть 2 этой серии покажет, как избежать этого). Значок @ в ассемблере указывает ассемблеру использовать специальную форму. Здесь используется следующее:

@highest

ссылается на биты 48-63 константы

@higher

ссылается на биты 32-47 константы

@h

ссылается на биты 16-31 константы

@l

ссылается на биты 0-15 константы

Первая команда в программе означает "load immediate shifted". Она загружает величину в дальнюю правую часть (биты 48-63 адреса first_value), сдвигает число на 16 бит налево и затем сохраняет результат в регистре 7. Биты 16-31 регистра 7 содержат биты 48-63 адреса. Далее мы используем команду "or immediate" для того, чтобы выполнить логическую операцию or с регистром 7 и величиной с правой стороны (биты 32-47 адреса first_value) и сохраняем результат в регистре 7. Теперь биты 32-47 адреса являются битами 0-15 регистра 7. Затем регистр 7 сдвигаем влево на 32 бита, причем биты 0-32 очищены, и результат сохранен в регистре 7. Теперь биты 32-63 регистра 7 содержат биты 32-63 адреса, который мы загружаем. Следующие две команды используют команды "or immediate" и "or immediate shifted", чтобы загрузить аналогичным образом биты 0-31.

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

Теперь вспомним, что все описанные ваше операции загружают только адрес величины, которую мы хотим загрузить. Теперь мы хотим загрузить саму величину в регистр. Для этого мы будем использовать регистр 7, чтобы указать процессору, из какого адреса мы хотим загрузить величину. Это указывается заключением «7» в скобки. Команда ld 4, 0(7) загружает величину из адреса в регистре 7 в регистр 4 (ноль означает добавление нуля к этому адресу). Теперь регистр 4 содержит первую величину.

Аналогичный процесс используется для загрузки второй величины в регистр 5.

После того как регистры загружены, мы можем, наконец, сложить наши числа. Команда add 6, 4, 5 складывает содержимое регистров 4 и 5 и сохраняет результат в регистре 6 (регистры 4 и 5 остаются неизменными).

Теперь после того как мы вычислили величину, которую искали, следующее, что мы хотим сделать -- это использовать эту величину, как код возврата/выхода для программы. В ассемблере вы выходите из программы, вызывая соответствующий системный вызов (system call) (выход выполняется, используя системный вызов exit). Каждый системный вызов имеет ассоциированный с ним номер. Этот номер загружается в нулевом регистре до вызова. Остальные аргументы начинаются с регистра 3 и продолжаются дальше в зависимости от того, как много аргументов требует системный вызов. Затем команда sc заставляет ядро перехватить управление и ответить на запрос. Номер системного вызова для exit - 1. Поэтому первое, что мы должны сделать -- это поместить число 1 в регистр 0.

На компьютерах PowerPC это выполняется суммированием. Команда addi суммирует регистр и число и сохраняет результат в регистре. В некоторых командах (включая addi), если указанный регистр 0, команда не использует регистр, а использует вместо этого число 0. Это слегка сбивает с толку, но сделано это для того, чтобы позволить PowerPC использовать одни и те же команды для суммирования и загрузки.

Системный вызов выхода использует один параметр – значение выхода. Оно хранится в регистре 3. Поэтому мы должны переместить наш ответ из регистра 6 в регистр 3. Команда "register move" rm 3, 6 выполняет необходимое перемещение. Теперь мы готовы сообщить операционной системе, что мы готовы к тому, чтобы она выполняла свою работу.

Команда sc выполняет системный вызов. Это инициирует операционную систему, которая считывает то, что находится в регистре 0 и регистре 3, а затем завершает программу с содержимым регистра 3 в качестве возвращаемого значения. В командной строке мы можем извлечь это значение, используя команду echo $?.

Следует заметить, что многие из этих команд излишни, но они используются для обучения. Например, так как first_value и second_value являются по существу константами, нет причин, по которым мы не могли бы прямо загрузить их и пропустить весь раздел данных. Аналогично, мы могли бы сохранить результаты в регистре 3 с самого начала (вместо регистра 6) и убрать обмен между регистрами. В действительности мы могли бы использовать регистр 3 и как источник данных, и как регистр назначения. Так что, если бы мы старались быть лаконичными, мы могли бы написать это следующим образом:

Листинг 2. Краткая версия первой программы
.section ".opd", "aw"
.align 3
.global _start
_start:
	.quad ._start, .TOC.@tocbase, 0
.text
	li 3, 1   #загрузить "1" в регистр 3
	li 4, 2   #загрузить "2" в регистр 4
	add 3, 3, 4    #сложить регистр  3 с регистром 4 и 
	сохранить результат в регистре 3
	li 0, 1   #загрузить "1" в регистр 0 для системного вызова 
	sc

Поиск максимального значения

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

Ниже приведена программа, наберите ее в файле max.s:

Листинг 3. Поиск максимального значения
###ДАННЫЕ ПРОГРАММЫ###
.data
.align 3
#value_list адрес начала списка
value_list:
        .quad 23, 50, 95, 96, 37, 85
#value_list_end адрес сразу после окончания списка
value_list_end:

###СТАНДАРТНОЕ ОПИСАНИЕ ТОЧКИ ВХОДА###
.section "opd", "aw"
.global _start
.align 3
_start:
        .quad ._start, .TOC.@tocbase, 0

###СОБСТВЕННО КОД###
.text
._start:

        #ОПИСАНИЕ НАЗНАЧЕНИЯ РЕГИСТРОВ
        #регистр 3 -- текущий максимум
	#регистр 4 -- адрес текущего значения
	#регистр 5 -- ограничитель адресов
	#регистр 6 -- текущее значение

        #Загрузить адрес  value_list в регистр 4
        lis 4, value_list@highest
        ori 4, 4, value_list@higher
        rldicr 4, 4, 32, 31
        oris 4, 4, value_list@h
        ori 4, 4, value_list@l

        #загрузить адрес value_list_end в регистр 5
        lis 5, value_list_end@highest
        ori 5, 5, value_list_end@higher
        rldicr 5, 5, 32, 31
        oris 5, 5, value_list_end@h
        ori 5, 5, value_list_end@l

        #установить регистр 3 в 0
        li 3, 0

        #ОСНОВНОЙ ЦИКЛ
loop:
        #сравнить регистры  4 и 5
        cmpd 4, 5
        #если равно, перейти к end
        beq end

        #загрузить следующую величину
        ld 6, 0(4)

        #сравнить регистр 6 (текущее значение)
         и регистр 3 (текущий максимум)
        cmpd 6, 3
        #если регистр 6 не больше регистра 3, то перейти к  loop_end
        ble loop_end

        #иначе, загрузить регистр 6(текущее значение) 
        в регистр 3 (текущий максимум)
        mr 3, 6

loop_end:
        #переместить указатель к следующей величине 
        (перемещение на 8 байтов)
        addi 4, 4, 8
        #перейти к началу цикла (метка loop)
        b loop


end:
        #установить номер системного вызова
        li 0, 1
        #регистр 3 уже содержит значение, с которым должна завершиться программа
        #системный вызов:
        sc

Чтобы ассемблировать, скомпоновать и запустить программу, выполните следующее:

as -a64 max.s -o max.o
ld -melf64ppc max.o -o max
./max
echo $?

Будем надеяться, что теперь, когда вы имеете опыт работы с одной программой PowerPC и знаете некоторые команды, вы можете понемногу отслеживать код. Раздел данных такой же, как и раньше, за исключением того, что мы имеем несколько величин после объявления нашего value_list. Заметьте, что это вообще не изменяет объявление value_list -- он по-прежнему константа, ссылающаяся на адрес первого элемента данных, который следует прямо за ним. Данные после него используют по 64-бита на величину (на что указывает .quad). Описание точки входа такое же, как и раньше.

В самой программе следует отметить один момент – мы описали, для чего мы будем использовать каждый регистр. Это сильно поможет вам отслеживать ваш код. Регистр 3 – это регистр, в котором мы храним величину текущего максимума, которую мы исходно положили равной нулю. Регистр 4 содержит адрес следующей загружаемой величины. Он начинает с value_list и продвигается вперед на 8 при каждой итерации. Регистр 5 содержит адрес, следующий непосредственно за данными в value_list. Это позволяет использовать простое сравнение между регистрами 4 и 5, чтобы узнать, когда мы находимся в конце списклжны перейти к end. . Регистр 6 содержит текущее значение, загружаемое из ячейки, на которую указывает регистр 4. В каждой итерации оно сравнивается с регистром 3 (текущим максимумом), и регистр 3 заменяется, если регистр 6 больше.

Заметим, что мы отмечаем каждую точку условного перехода своей собственной меткой, что позволяет использовать эти метки как целевые объекты для команд ветвления. Например, beq end переходит к коду непосредственно за меткой end.

Другая команда, которую стоит отметить, -- это ld 6, 0(4). Она использует содержимое регистра 4, как адрес памяти, из которого извлекаются значения, которые потом сохраняются в регистре 6.

Заключение

Будем надеяться, что теперь вы имеете общее представление о программировании на ассемблере для PowerPC. Команды могут сначала выглядеть слегка причудливыми, но они обретут больший смысл с практикой. В следующей статье я рассмотрю различные способы адресации на PowerPC процессорах, и то, как они могут быть использованы, чтобы сделать 64-битное программирование более эффективным. В третьей статье мы рассмотрим более полно ABI и обсудим, для каких целей могут быть использованы различные регистры, как вызывать функции и возвращаться из них и другие интересные аспекты ABI.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=211837
ArticleTitle=Ассемблер для Power-архитектуры: Часть 1. Концепции программирования и простейшие команды PowerPC
publish-date=04242007