Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Курсовая работа2 / Docs / KursovikOtchet00_.doc
Скачиваний:
27
Добавлен:
01.05.2014
Размер:
791.55 Кб
Скачать

Разработка генератора кода

Назначение генератора кода

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

  • распределение памяти под лексемы, фактические и временные переменные;

  • формирование последовательности инструкций ассемблера для выполнения каждой конкретной тетрады;

  • генерация текста на языке ассемблера.

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

Стратегия проектирования генератора кода

Естественным желанием является генерация файла языка ассемблера, который без внесения каких-либо изменений может быть обработан самим ассемблером и компоновщиком.

В языке ассемблера отсутствуют вменяемые средства ввода-вывода. Средства, предоставляемые системным прерыванием DOS №33, недостаточны для реализации даже самого примитивного взаимодействия с пользователем. С целью расширения функциональных возможностей генерируемых программ, было принято решение организовать взаимодействие программы на языке ассемблера с библиотекой, написанной на языке высокого уровня.

В качестве языка реализации библиотеки был выбран язык С++.

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

Разработка библиотеки поддержки

Требования, предъявляемые к библиотеке поддержки:

  • элементарность и минимум экспортируемых функций;

  • возможность компиляции независимо от наличия текста программы на ассемблере;

  • использование имён, совместимых с программой на ассемблере;

  • применение модели памяти, совместимой с программой на ассемблере.

Ниже приведён текст программы на языке C++, которая реализует требуемую библиотеку. Легко убедиться в соответствии данной библиотеки предъявляемым требованиям:

extern "C" {

#include <stdio.h>

// Entry code concept. The feature is that actual entry code is located here

// (main function), but it transfer control immediately to assembler (asmentry

// function)

extern void asmentry();

void main() {

asmentry();

}

// Reading operations. Nothing specific here, functions recieve pointer

// parameter, which will contain

// input value, no value is returned by function itself

void read_int( int* Dest ) {

scanf("%d",Dest);

}

void read_str( char* Dest ) {

gets(Dest);

}

void read_flt( float* Dest) {

scanf("%f",Dest);

}

// Writing operations. Parameter contains a value to be outputed to screen,

// strings are outputed through pointer to first symbol. No return value is

// expected yet...

void write_int(int Value) {

printf("%i", Value);

}

void write_flt(float Value) {

printf("%f", Value);

}

void write_str(char* Val) {

printf("%s",Val);

}

void write_ln() {

printf("\n");

}

}

Директива extern “C” обеспечивает генерацию внешних имён в соответствии с конвенцией языка си (имена предваряются символом подчёркивания и приводятся к нижнему регистру). Таким образом, компоновщик способен корректно разрешить внешние ссылки, которые использует программа на языке ассемблера для обращения к библиотечным функциям.

Точка входа “void main()” располагается в библиотеке, однако её действие заключается только в передаче управления ассемблерной процедуре. Прочие функции библиотеки автоматически создаются компилятором как экспортируемые, таким образом программа на ассемблере имеет возможность к ним обратиться.

Главным преимуществом этого подхода является возможность сформировать объектный модуль библиотеки без текста программы на ассемблере. Это позволяет один раз скомпилировать библиотеку и компоновать её с различными ассемблерными программами. Соглашения об использовании внешних имён позволяют подключить к библиотеке любую программу на ассемблере.

Спецификация генератора кода

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

class CGenAsm

{

FILE *m_pFile;

unsigned m_NextLabel;

std::string NewLabel();

void WriteAsmPrefix();

void WriteSegmentStack();

void WriteSegmentData(CNameTable& NameTable);

void WriteSegmentCode(CTetradStream &Tetrads);

void WriteTetrad(STetrad &tet);

const char *GetConditionInstruction(TET_OPERATION op);

public:

void GenerateAsmFile( const char *FileName,

CNameTable& NameTable, CTetradStream &Tetrads);

};

Интерфейсной функцией класса является GenerateAsmFile, работа которой заключается в последовательной генерации заголовка ассемблерного файла, текста сегмента стека, данных и кода. Все эти операции представлены функциями WriteAsmPrefix, WriteSegmentStack, WriteSegmentData, WriteSegmentCode. Функция WriteTetrad выполняет генерацию кода для одной отдельно взятой тетрады.

Формат текста программы на языке ассемблера

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

.286

DOSSEG

.MODEL SMALL

EXTRN _write_char: PROC

EXTRN _write_int : PROC

EXTRN _write_flt : PROC

EXTRN _write_str : PROC

EXTRN _write_ln : PROC

EXTRN _read_char : PROC

EXTRN _read_int : PROC

EXTRN _read_flt : PROC

EXTRN _read_str : PROC

EXTRN _copy_str : PROC

EXTRN _concat_str: PROC

Использование директивы MODEL избавляет от необходимости явно объявлять сегмент стека, а также именовать сегменты кода и данных. Модель памяти SMALL полностью идентична модели памяти, неявно определённой в библиотеке поддержки. Функции, определённые директивами EXTRN обеспечивают связь программы на языке ассемблера с библиотечными функциями.

Далее, в выходной файл заносится текст сегмента данных. В тесте применяется сокращённое описание сегмента «.DATA», за которым располагаются лексемы, константы, объявленные и промежуточные переменные. Все переменные инициализируются нулём, значение литералов и констант определено явно. Пример листинга сегмента данных приведён ниже:

.DATA

v0 DW 0h ; A

v1 DW 0h ; B

v2 DB "Enter <A> number: ",0

v3 DB "Enter <B> number: ",0

v4 DB "A = ",0

v5 DB "B = ",0

v6 DB "A is greater than B",0

v7 DB "B is greater than A",0

v8 DB "A is equal to B",0

vBool0 DB 0h ; временная

Наиболее содержательной является операция генерации текста сегмента кода. Аналогично, используется сокращённая директива описания сегмента, за которой располагается единственная процедура сегмента – _asmentry. Именно эта процедура содержит исполняемый код программы и получает управление после инициализации библиотеки поддержки. Пример листинга сегмента кода приведён ниже:

.CODE

PUBLIC _asmentry

_asmentry PROC

push offset v2

call _write_str

add sp, 2

push offset v0

call _read_int

add sp, 2

RET

_asmentry ENDP

END

Генерация команд по промежуточному коду

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

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

  • команды передачи данных: mov, fld, fild, fstp, fstsw, sahf, push;

  • арифметические команды: add, sub, mul, neg, inc, fadd, fsub, fdiv, fmul, fchs;

  • логические команды: and, or, not;

  • команды сравнения: cmp, fcompp;

  • команды передачи управления: call, ret, jmp, je, jne, jg, jge, jl, jle, ja, jae, jb, jbe;

  • команды синхронизации: fwait.

Как можно заметить, набор используемых команд достаточно ограничен. Тем не менее, любая тетрада промежуточного представления однозначно раскрывается в конечную последовательность этих команд. Рассмотрим примеры генерации кода для некоторых, наиболее содержательных тетрад:

  • тетрада write_str(value,x,x) – соответствует выводу на экран строковой переменной value. Код языка ассемблера имеет следующий вид:

push offset value ; передача параметра в библиотечную функцию. Для вывода

; строки передаётся указатель на первый символ

call _write_str ; вызов функции библиотеки

add sp, 2 ; корректировка указателя sp (очищение стека)

  • тетрада write_int(value,x,x) – соответствует выводу на экран целочисленной переменной value. Код языка ассемблера имеет следующий вид:

push word ptr value ; единственное отличие заключается в том, что целочис-

call _write_int ; ленные и вещественные переменные передаются по

add sp, 2 ; значению, а не по указателю

  • тетрада Greater(v0, v1, vBool0) – сравнение двух целочисленных переменных. Результат сравнения заносится в булевскую переменную. Код языка ассемблера менее тривиален:

mov AX, word ptr v0 ; загрузка первого значения в регистр

cmp AX, word ptr v1 ; сравнение со вторым значением

jg _ASM_Label_0 ; переход к присвоению «ИСТИНА»

mov byte ptr vBool0, 0 ; присвоение «ЛОЖЬ»

jmp _ASM_Label_1 ; завершение операции

_ASM_Label_0 :

mov byte ptr vBool0, 1 ; присвоение «ИСТИНА»

_ASM_Label_1 :

  • тетрада GreaterFP(v0, v1, vBool0) – сравнение двух вещественных переменных. Результат сравнения заносится в булевскую переменную. Код языка ассемблера, пожалуй, наиболее содержателен:

fld dword ptr v1 ; загрузка значений в стек сопроцессора

fld dword ptr v0

fcompp ; сравнение значений в стеке сопроцессора

fstsw ax ; сохранение слова состояния сопроцессора в ax

sahf ; загрузка регистра ah в регистр флагов

fwait ; синхронизация сопроцессора

ja _ASM_Label_0 ; переход к присвоению «ИСТИНА»

mov byte ptr vBool0, 0 ; присвоение «ЛОЖЬ»

jmp _ASM_Label_1 ; завершение операции

_ASM_Label_0 :

mov byte ptr vBool0, 1 ; присвоение «ИСТИНА»

_ASM_Label_1 :

  • Тетрада ITOF(v4, х, vReal0) – приведение целочисленного значения к вещественному. Код языка ассемблера выглядит следующим образом:

fild word ptr v4 ; загрузка целого числа в стек сопроцессора

fstp dword ptr vReal0 ; сохранение числа как вещественного

fwait ; синхронизация сопроцессора

При генерации всех тетрад приняты следующие соглашения:

  • любая тетрада может свободно использовать регистры общего назначения;

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

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

Компоновка исполняемого модуля

После обработки входного потока тетрад, генератор кода завершает формирование текста программы на языке ассемблера. Результирующий файл может быть обработан ассемблером (например, TASM) для получения объектного модуля. Полученный объектный модуль компонуется с объектным модулем библиотеки поддержки для получения исполняемого модуля программы (например, компоновщик TLINK).

Соседние файлы в папке Docs