- Lektsia - бесплатные рефераты, доклады, курсовые работы, контрольные и дипломы для студентов - https://lektsia.info -

Ассемблерный код и машинные команды



Машинные команды удобны тем, что при их использовании внутреннее пред­ставление программы полностью соответствует объектному коду и сложные пре­образования не требуются. Команды ассемблера представляют собой лишь форму записи машинных команд, а потому в качестве формы внутренне­го представления программы практически ничем не отличаются от них.

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

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

 

4.7.2 Преобразование дерева операций в код на языке ассемблера

В качестве языка ассемблера возьмем язык ассемблера процессоров типа Intel 80x86. При этом будем считать, что операнды могут быть помещены в 16-разряд­ные регистры процессора и в коде результирующей объектной программы могут использоваться регистры АХ (аккумулятор) и DX (регистр данных), а также стек для хранения промежуточных результатов.

Функцию, реализующую перевод узла дерева в последовательность команд ас­семблера, назовем Code. Входными данными функции должна быть информация об узле дерева операций. В ее реализации на каком-либо языке программирова­ния эти данные можно представить в виде указателя на соответствующий узел дерева операций. Выходными данными является последовательность команд языка ассемблера. Будем считать, что она передается в виде строки, возвращае­мой в качестве результата функции.

Тогда четырём формам текущего узла дерева для каждой арифметической опера­ции будут соответствовать фрагменты кода на языке ассемблера, приведенные в табл. 4.7.3.

Каждой допустимой арифметической операции будет соответствовать своя ко­манда на языке ассемблера. Если взять в качестве примера операции сложения (+), вычитания (-), умножения (*) и деления (/), то им будут соответствовать ко­манды add, sub, mul и div. Причем в ассемблере Intel 80x86 от типа операции зави­сит не только тип, но и синтаксис команды (операции mul и div в качестве первого операнда всегда предполагают регистр процессора АХ, который не нужно указы­вать в команде). Соответствующая команда должна записываться вместо act при порождении кода в зависимости от типа узла дерева.

 

Таблица 4.5 - Преобразование узлов дерева вывода в код на языке ассемблера для арифметических операций

 

 

Вид узла дерева Результирую­щий код Примечание
 
 

mov ax, operl act ax, oper2   act — команда соответствующей операции operl, oper2 — операнды (листья дерева)  
 
 

Code (Узел 2) mov dx, ax mov ax, operl act ax, dx Узел 2 -нижележащий узел (не лист!) дерева Code (Узел 2) -код, порождаемый процедурой для нижележащего узла
 
 

Code (Узел 2) act ax, орег2 Code (Узел 2) — код, порождаемый процедурой для нижележащего узла
 
 

Code (Узел 2) push ax Code (УзелЗ) mov dx, ax pop ax act ax, dx Code (Узел 2) — код, порождаемый процедурой для нижележащего узла Code (Узел 3) -- код, порождаемый процедурой для нижележащего узла push и pop — команду сохранения результатов в стеке и извлечения результатов из стека

 

Код, порождаемый для операции присвоения результата, будет отличаться от кода, порождаемого для арифметических операций. Кроме того, семантика языка требует, чтобы в левой части операции присвоения всегда был операнд, поэтому для нее возможны только два типа узлов дерева, влекущие порождение кода(два других типа узлов должны приводить к сообщению об ошибке, которая в компиляторе обнаруживается синтаксическим анализатором). Фрагменты кода для этой операции приведены ниже в таблице 4.6.

 

Таблица 4.6 - Преобразование узлов дерева вывода в код на языке ассемблера для операции присвоения

 

Вид узла дерева Результирую­щий код Примечание
 
 

mov ax, орег2 mov operl, ax operl, oper2 — операнды (листья дерева)  
 
 

Code (Узел 2) mov operl, ax Узел 2 — нижележащий узел (не лист!) дерева Code (Узел 2)-код, порождаемый процедурой для нижележащего узла

 

Теперь последовательность порождаемого кода определена для всех возможных типов узлов дерева»

Рассмотрим в качестве примера выражение A:-B*C+D-B*10. Соответствующее ему дерево вывода приведено на рис. 4.7.

Рисунок 4.7 Дерево операций для арифметического

выражения «A:=B*C+D-B*10»

Построим последовательность команд языка ассемблера, соответствующую де­реву операций на рис. 4.7. Согласно принципу СУ-перевода, построение начи­нается от корня дерева. Для удобства иллюстрации рекурсивного построения последовательности команд все узлы дерева помечены от U1 до U5. Рассмотрим последовательность построения цепочки команд языка ассемблера по шагам ре­курсии. Эта последовательность приведена ниже.

 

Шаг 1.

Code(U2)

mov A, ax

Шаг 2.

Code(U3)

push ax

Code(U5)

mov dx, ax

pop ax

sub ax, dx

mov A, ax

Шаг 3.

Code(U4)

add ax, D

push ax

Code(U5)

mov dx, ax

pop ax

sub ax, dx

mov A, ax

Шаг 4.

mov ax, В

mul С

add ax, D

push ax

Code(U5)

mov dx, ax

pop ax

sub ax, dx

mov A, ax

Шаг 5.

mov ax, В

mul С

add ax, D

push ax

mov ax, В

mul 10

mov dx, ax

pop ax

sub ax, dx

mov A, ax

 

 

Оптимизация кода

Сущность оптимизации кода

 

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

Важно отметить два момента: во-первых, выделение оптимизации в отдельный этап генерации кода — это вынужденный шаг. Компилятор вынужден произво­дить оптимизацию построенного кода, поскольку он не может выполнить семан­тический анализ всей входной программы в целом, оценить ее смысл и,: исходя из него, построить результирующую программу. Оптимизация нужна, поскольку результирующая программа строится не вся сразу, а поэтапно. Во-вторых, опти­мизация - это необязательный этап компиляции. Компилятор может вообще не выполнять оптимизацию, и при этом результирующая программа будет правиль­ной, а сам компилятор будет полностью выполнять свои функции. Однако, практически все компиляторы так или иначе выполняют оптимизацию, поскольку их разработчики стремятся завоевать хорошие позиции на рынке средств разработки программного обеспечения. Оптимизация, которая существенно влияет на эф­фективность результирующей программы, является здесь немаловажным фак­тором.

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