Главная страница

компилятор ТСС. Tiny c compiler by Fabrice Bellard Описание алгоритмов


Скачать 191.99 Kb.
НазваниеTiny c compiler by Fabrice Bellard Описание алгоритмов
Анкоркомпилятор ТСС
Дата08.11.2019
Размер191.99 Kb.
Формат файлаodt
Имя файлаtcc book.odt
ТипДокументы
#94047
страница1 из 38
  1   2   3   4   5   6   7   8   9   ...   38

Tiny C Compiler by Fabrice Bellard

Описание алгоритмов.
w32_tcc_lib_path отсекает имя exe файла от полного пути к нему, так же из пути убирается подкаталог /bin/ . Полученная строка — путь поиска библиотек. В конце добавляется 0.
tcc_new()

выделяется память для

static struct TCCState *tcc_state;
Для каждого ASCII-знака определяется может ли он быть частью символа, т. е. isid() - большие, малые буквы и знак подчеркивания, isnum() - от 0 до 9

static unsigned char isidnum_table[256];
static TokenSym *hash_ident[TOK_HASH_SIZE]; //8192
static int tok_ident; #define TOK_IDENT 256 все токены программы имеют идентификатор больше 256
static const char tcc_keywords[] =

#define DEF(id, str) str "\0"

#include "tcctok.h"

#undef DEF

;

для преобразования ключевых слов в токены.
Для каждого ключевого слова вызывается tok_alloc()

#define TOK_HASH_FUNC(h, c) ((h) * 263 + (c))

Если запись в таблице пуста то токен добавляется в нее с помощью tok_alloc_new(), если нет возможно токен уже в таблице и он сразу же возвращается. Но если запись указывает на другой токен, от нее начинается связнный список и по указателю в нем мы найдем нужный токен или добермся до пустого указателя и вставим токен в него.
#define TOK_ALLOC_INCR 512
Шаг для увеличение table_ident, т. е. если в конце предыдущего добавления, tok_ident увеличился на единицу и стал кратен данному шагу (остаток от деления на шаг равен нулю) то вызывается tcc_realloc() - обертка над realloc() с добавлением возможности отладки.
tok_alloc() заполняет только имя символа и добаляет к нему в кноце ноль. Остальные атрибуты заполяются нулевыми значениями.
__

define_push
static Sym *define_stack;
#define MACRO_OBJ 0 /* object like macro */

#define MACRO_FUNC 1 /* function like macro */
Тип макроопределения, в независимости от него define_push добавляет макрос в define_stack. На входе принимается указатель на символ, описывающий первый аргумент. Остальные аргументы связанны с первым с помощью ссылки next эта же ссылка используется для начала цепочки аргументов символа только что выделенного в define_stack. Элемент привязывается по идентификатору к токену в table_ident

static Sym *sym_free_first;

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

#define SYM_POOL_NB (8192 / sizeof(Sym))

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

tcc_define_symbol() создает новую структуру, описывающую буферизированный файл, записывает в буфер этой структуры переданный символ и его значение, после чего последовательно вызывает next_nomacro() и parse_define(). При этом буферизированный файл, в данном случае это файл у которого нет имени (в поле имени только «\0»), в нем только одна строка (bf->line_num = 1) Тем не менее этот файл устанавливается как текущй (переменная file) а текущий символ (переменная ch) это первый ASCII-знак в буфере этого файла, т. е. в нашем случае последовательного разбора кода компилятора, это первый ASCII-знак в строке пердопреленного макроса. Обратите внимание на выражение

if (!value) value = "1";

Для макроса иногда важен сам факт определения а не его значение, поэтому для таких макросов значение выбирает компилятор. В данном случае это 1, но другие компиляторы могут не подставлять единицу вместо имени макроса. Например для некоторых компиляторов, выражения #define DBG не достаточно чтобы ниже сработало макро-условие #if DBG. Выражение для безошибочной компиляции должно принять вид #define DBG 1.
next_nomacro() может обработать macro_ptr с помощью TOK_GET, но macro_ptr может быть и пустым, тогда вместо TOK_GET применяется next_nomacro1(); Кроме того в процессе обработки TOK_GET может вернуть токен TOK_LINENUM - вставка номера строки из исхдного кода в исполняемый файл. Такой токен обрабатывается прямо в next_nomacro() - обновляется file->line_num.
При первом вызове (мы все еще ведем последовательное исследование) tcc_define_symbol в next_nomacro() сразу же вызовется next_nomacro1(). При последующем выполнении, указатель macro_ptr может содержать адрес токена и для него вызовется

TOK_GET(), который мы рассмотрим позднее. Здесь же обновляется указатлье line_num, если получен токен TOK_LINENUM

next_nomacro1()
'\\' эквивалентна макросу CH_EOB и может означать, что буфер пуст и нужно прочитать еще одну порцию данных из буфера - handle_eob(). Мы сами помещаем этот символ в конец буфера, заполняя его данными. Кроме того это может быть конец строки и тогда вызывается handle_stray(). Она с помощью inp() проверяет следующий символ. Здесь же обрабатывается символ /r — возврат каретки, который является не обязательными, т. к. в современных ОС строки все равно выравниваются по краю. В начале анализа символов игнорируются знаки табуляции (/t), разрыва страницы (/f) и вертикальной табуляции (/v) и тот же (/r). Текстовые редакторы в которых набран код могут использовать их для того чтобы код было удобнее читать, например чтобы объявление функции всегда начиналось с новой страницы, но при компиляции эти символы игнорируются. Функция handle_stray() (обертка-проверка над handle_stray_noerror()) увеличивает счетчик обработанных строк, двигаясь вперед . Внутри нее inp(), читая следующий символ может запустить handle_eob(), если встретит CH_EOB. Т.е. inp() увеличивает указатель на текущий ASCII-знак (переменная ch). Указатель увеличивается второй раз если за переводом строки следует символ возврата каретки. Движение вперед может продолжится в этом же цикле если встречается два перевода строки (пара перевод+возврат каретки). А так же может закончиться ошибкой, если за '\r' не следует '\n'
Может быть, что обработка конца буфера не изменит указатель, значит файл закончился, переходим на метку parse_eof.

handle_eob() сводится к вызову tcc_peekc_slow(), она проверяет и перенастраивает указатели bf->buf_ptr и bf->buf_end и, если нужно, читает 8192 байта и устанавливает маркер CH_EOB в конце буфера.
#define IO_BUF_SIZE 8192 — чтение под две страницы памяти

Начиная с метки parse_eof
Флаг PARSE_FLAG_LINEFEED выставляется на этапе препроцессинга и сбрасывается во время ассемблирования при достижении конца ассемблируемого файла. Мы рассмотрим этот этап позже, но пока для случая препроцессинга при достижении конца файла выставляется токен TOK_LINEFEED и флаг токенов TOK_FLAG_EOF, после чего обработка прекращается.

Если же PARSE_FLAG_LINEFEED не установлен, то проверяется не осталось ли файлов в стеке include. Если файлов больше нет, это конец обработки, выставляется токен TOK_EOF. К тому же результату приводит установленный флаг PARSE_FLAG_PREPROCESS.
Если удается достать еще один файл:

снимается флаг TOK_FLAG_EOF
add_cached_include вызвается только если астановлени TOK_FLAG_ENDIF

разбор текущего include файла (или основного) закончен, он закрывается. Указатель в стеке include уменьшается. Верхний файл становится основным (переменная file), а текущий указатель на ASCII-знак (переменная p) устанавливается на буфер этого файла.
Таким образом переход на метку parse_eof: может привести к остановке next_nomacro1(), т. к. первые два if-блока не содержат оператора goto, а после них сразу же идет brake. Однако если это не этап препроцессинга и в include-стеке остались еще файлы, в третьем блоке будет осуществлен переход в начало next_nomacro1() с целью обработки следующего файла в include-стеке.
Прервемся на строке

case '\n':

чтобы разобрать tcc_peekc_slow() и add_cached_include()
tcc_peekc_slow()
в нашем случае последовательного анализа чтения из файла не произойдет, т. к. для строчки создан буферизованный файл, но в общем случае функция работает так:

чтение будет произведено только если текущий указатель в буфере (bf->buf_ptr) приблизился к концу буфера (bf->buf_end).

Чтение из файла возможно только если к буферу прикреплен файл (bf->fd)

Чтение происходит по 8192 байт (IO_BUF_SIZE)

Если размер файла кратен 8192, при очередном чтении read() вернет 0, т. е. четние заверешено на предыдущем этапе. В любом другом случае read() вернет положительное число. Ошибка чтения (отрицательное число) и 0 обрабатывается одинаково, len присваивается 0 - движения вперед не произшло.

Текущему указателью буфера (bf->buf_ptr) присваивается адрес считанных данных (bf->buffer)

Адрес конца буфера (bf->buf_end) обновляется адресом конца считаных данных (bf->buffer + len)

В конец буфера (*bf->buf_end) записывается спецсимвол CH_EOB

В качестве результата возвращается CH_EOF, если достигнут конец файла или первый прочитанный ASCII-знак. Это позволяет принять решение, нужно ли продолжить обработку файла, а так же совмещает в себе процедуру чтения и получения следующего символа.
add_cached_include()

вызывается search_cached_include() она вычисляет хэш (переменная h) с помощью hash_cached_include() по имени файла и типу. По этому хэшу получается начало цепочки (s1->cached_includes_hash[h]). Далее цикл найдет файл по имени и типу, или встретит 0 в укзателе связывающем цепочку (e->hash_next). Это будет означать что цепочка закончилась и файл не найден.
Таким образом add_cached_include() завершится, если файл найден или создаст структуру для ненайденного файла, а также включит его в соотвествующую цепочку.
Типы файлов и поле ifndef_macro мы рассмотрим позже.

Вернемся к строчке

case '\n':

вопервых увеличим счетчик количества строк (file->line_num++)

устанавливается флаг токенов TOK_FLAG_BOL;

символ перевода стоки пропускается (p++)

И если флаг PARSE_FLAG_LINEFEED препроцессинга не установлен, next_nomacro1() перезапускается, если же флаг установлен то выставляется токен TOK_LINEFEED и next_nomacro1() завершается.
case '#':

вызввается PEEKC() чтобы сдвинуть указатель на ASCII-знак (переменная p). ASCII-знак по этому указателю становится текущим (переменная c). Кроме того PEEKC() обрабатывает конец буфера '\\' с помощью handle_stray1(). Эта функция обновляет текущие указатели и ASCII знак, считывая данные с помощю handle_eob() и если вновь считанный символ тоже окажется '\\', вызовет handle_stray(). Обратите внимание что обновляется текущий ASCII для функции вызвавшей вызвавщей PEEKC(), но если встречается '\\', обновляется и глобальный ASCII знак (переменная ch)

если флаг токенов установлен TOK_FLAG_BOL (начало стрки) и флаг парсинга PARSE_FLAG_PREPROCESS установлен, file->buf_ptr присваива укзатель на текущий ASCII-знак (перемення p), вызывается preprocess(), после чего этой переменной присваивается обновленный указатель в файле (file->buf_ptr). Таким образом строка распознана и next_nomacro1() перезапускаетя.

Если '#' не в начале строки то возможно это сочетание двух '#' и выставляется токен TOK_TWOSHARPS. Если такой символ один и установлен флаг парсинга PARSE_FLAG_ASM_COMMENTS, то вызывается parse_line_comment() которая обрабатывает комментарий и обновляет указатель на текущий ASCII-символ (переменная p)

Если упомянутый флаг парсинга не установлен, то просто устанавливается токен '#' и next_nomacro1() завершается.
Прервемя на строке

case 'a'

чтобы разобрать preprocess() и
-----------

preprocess()

tcc_state и parse_flags сохраняются в локальных переменных

страые флаги паорсинга заменяются на PARSE_FLAG_PREPROCESS, PARSE_FLAG_TOK_NUM и PARSE_FLAG_LINEFEED

Вызывается уже рассмотренный нами next_nomacro()

Если встечается TOK_DEFINE еще раз вызывается next_nomacro() и за тем parse_define(), после чего preprocess() завершатея.
parse_define()

Проверяется не является ли имя зарегистрированным словом (идентификатор меньше TOK_IDENT)

Ссылка на символ first будет нулевой, она встанет в начало цепочки обнаруженных символов.
Тип макроса выставляется как MACRO_OBJ, но если далее в строке обнаружится откывающаяся скобка, то тип будет MACRO_FUNC
Если текущий символ '\\', вызывается handle_stray1()

Если текущий символ '(') , дважды вызывается next_nomacro()

Инициализируется цепочка символов

Пока не встретится закрывающаяся скобка будет присходить разбор

Переменные varg и is_vaargs служат для разбора макросов с переменным количеством параметров. Такая передача параметров обнаруживается если сразу же встечаются три точки (токен TOK_DOTS) или этот токен встречается после второго next_nomacro(), но при этом должна быть включена поддержка gnu_ext.

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

Выстраивается цепочка найденных символов.

Обработка продолжается пока встречается запятая, т. е. За найденным символом следует еще один.

Для новой структуры str (TokenString- строки токена) обнуляются все поля с помощью tok_str_new()

Пока не встерчается конец строки или конец файла, происходит обработка с помощью tok_str_add2(). В конце к строке добавляется завершающий ее нулевой токен, а сама строка и связанная с ней цепочка символов помещается в define_stack с помощью уже рассмотренной нами define_push()
static void tok_str_add2(TokenString *s, int t, CValue *cv)

длинна выделенного для строки массива токенов увеличивается, если это нужно с помощью tok_str_realloc() на размер кратный 8.

В конец строки добавляется токен, переданный в параметре.

В зависимости от типа токена, в конец строки токенов добавляется один или несколько байт взятых по адресу cv->tab.

Особый случай с токеном TOK_LSTR

размер структуры CString, размер данных cv->cstr->size и 3 дополнительных байта суммируются и делятся на 4. Т.е. 4 байта по 8 бит дают 32 бит. Этот размер записывается в nb_words. Указатель str обновляется несколько раз, если это нужно с помощью tok_str_realloc(s), пока s->allocated_len не сравняется или не превзойдет сумму длинны переданной строки токенов (переменная len) и nb_words.

В конец строки токенов помещается новая структура CString. Ее поля data и data_allocated обнуляются, поля size и size_allocated обноляются значение cv->cstr->size (переднной в параметре структуры). В строку токенов, сразу же за добавленной структурой помещаются данные из структуры переданной в параметрах.

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

разбор коментария происходит до тех пор пока не встретится CH_EOF или перевод строки ('\n')

Если встерчается '\\' то он обрабатывается знакомой нам функцией handle_eob(), если этот символ повторяется вызывается PEEKC_EOB(). Далее идет уже знакомый нам алгоритм обрабоки символов '\n' и '\r'

По сути коментарии просто пропускаются с помощью перемещения указателя.
Вернемся к

case 'a'

TOK_HASH_FUNC мы уже рассматривали

Хеширование продолжается пока сверяясь с массивом isidnum_table[], мы не найдем ASCII-символ, который не может входить в имя символа. Т.е. пробел или скобка или '\\'

Если не обнаружен '\\' то из hash_ident[] получаем начало списка структур TokenSym, проходим по списку и если строка той же длинны что и найденная, сравниваем ее с помощью memcmp(). Если символ найден переходим на метку token_found, если достигнут конец списка, добавляем новый символ с помощю tok_alloc_new()

В случае обнаружения '\\'

cstr_reset() - обертка над cstr_free() освобождает выделенную для строки память и вызывает cstr_new(), которая заполняет структуру, описывающую строку.

Временная переменная tokcstr посимвольно заполняется с учетом выделения памяти с помощью cstr_ccat()

PEEKC() получает очередной ASCII-символ, учитывая обработку '\\'

Начиная с метки parse_ident_slow разбираются ASCII-символы после '\\'. Они проверюятся по isidnum_table[], так же как и предыдущие. Пока проверка проходит, они заносятся во временную переменную с помощю cstr_ccat()

В итоге вызывается tok_alloc() для верменной переменной tokcstr.
Результатом обработки 'a' и других букв, кроме 'L'. Будет установка текущего токена (переменная tok) из временной переменной ts (указатель на TokenSym)

Если обнаружена 'L' возможны 3 варианта

1) Следующий символ это не '\\', '\'' или '\" тогда этои идентификатор и мы переходим на метку parse_ident_fast

2) Следующий символ это '\\', '\''. Значит мы имеем дело со строкой, устанавливаем флаг is_long и переходим на str_const;

3) Следующий символ - '\\'. Значит перед нами многостраничный идентификатор, мы действуем с переменной tokcstr точно так же как и для остальных букв.
case '0'

Если обнаружена одна из цифр, мы записываем ее в tokcstr, и провеяем следующие символы функциями isnum

(), isid(), сравнимваем с точкой.

На метку parse_num можно попасть если встречется число в специальном формате (с буквами 'e', 'E', 'p' или 'P'). В дополнение вышеупомянутой провеке, мы проверям чтобы за этими буквами шел знак '+' или '-'.
Таким образом мы записываем в
tokcstr все ASCII-символы пока они проходят проверку. К полученной последоватльности добавлем '\0'

Последовательность записывается в tokc (экземпляр Cvalue) и выставляется токен TOK_PPNUM;
  1   2   3   4   5   6   7   8   9   ...   38


написать администратору сайта