Работа с представлением GENERIC в gcc - часть 4
Вступление
Мы уже знаем как строить типичные языковые конструкции в терминах GENERIC и можем делать самые простые программки. Для возможности создания полноценного языка нам осталось только научиться работать с функциями. Этому и будет посвящена завершающая цикл заметка.
Работа с функциями подразумевает под собой следующие техники: создание функции (мы уже так делали), передача аргументов, чтение параметров, вызов. С некоторыми моментами мы уже сталкивались, но приведём их ещё раз для полноты описания.
Работа с функциями
Объявление функции
Из предыдущей заметки мы уже знаем как сделать простую заготовку, содержащую
функцию main, теперь мы повторяем всё тоже самое и приступаем
к новым примерам.
| Код в gcc | Аналог на Си | GENERIC |
|---|---|---|
//--------------------- Пример 1
|
int printf(const int char *, ...);
|
--------------------------- | FUNCTION_DECL | | "printf" | | int (const char *, ...) | --------------------------- |
Как несложно догадаться, мы сделали объявление библиотечной функции
printf, хотя аналогичным образом объявления даются и для
собственных функций. Мы уже делали подобное в предыдущей части для функции
main. Единственное интересное отличие - в данном случае наша
функция имеет неопределённое количество аргументов, что выражается в
использовании функции build_varargs_function_type_array.
Передача аргументов и вызов функции
А вот вызовом функций мы пока не занимались, попробуем что-либо напечатать:
| Код в gcc | Аналог на Си | GENERIC |
|---|---|---|
//--------------------- Пример 2
|
printf("Hello!\n");
|
---------------------------
| "Hello" |
--------------------------- /---------------------------
| CALL |/
| "printf" |
---------------------------
|
В данном примере мы видим сразу несколько новых элементов. Во-первых это
создание символьного литерала при помощи функции
build_string_literal. Эта функция на вход принимает длину строки
и непосредственно саму строку. Далее мы строим непосредственно вызов функции
при помощи build_call_expr. Для этой функции мы указываем
определение вызываемой функции, количество аргументов и непосредственно сами
аргументы. В нашем случае это созданный символьный литерал. Разумеется, узел
с вызовом следует не забывать добавить в список выражений.
Передача аргументов по указателю
Ещё одним важным элементом для вызова функции является передача аргументов по
указателю. Рассмотрим как это делается на примере вызова функции
scanf.
| Код в gcc | Аналог на Си | GENERIC |
|---|---|---|
//--------------------- Пример 3
|
int var_decl_1;
|
---------------------------
| FUNCTION_DECL |
| "scanf" |
| int (const char *, ...) | ----------------- ---------------
--------------------------- | "%d" | | VAR_DECL |
\ /----------------- /| var1 |
---------------------------/ / | int |
| CALL | -----------------/ ---------------
| "scanf |--| ADDR_EXPR | /
--------------------------- ------------------- /
| /
--------------------------- /------------------/
| CALL |/
| "printf" |\ -----------------
--------------------------- \| "Num %d\n" |
-----------------
|
Большая часть кода нам хорошо знакома, поэтому обратим внимание только на новые
детали. Взятие адреса переменной производится при помощи выражения с типом
ADDR_EXPR. Для того чтобы это выражение отработало так как мы
ожидаем, необходимо объявлению переменной выставить признак
TREE_ADDRESSABLE. Без этого признака взять адрес переменной
невозможно, в функцию будет поступать адрес копии этой переменной. Весь
остальной код нам уже хорошо знаком и в пояснениях не нуждается.
Заключение
Это была последняя статья из серии работы с GENERIC в gcc. Эталонный код для неё можно взять здесь. Рассмотренного материала вполне хватит чтобы научить лицевую часть собственного компилятора взаимодействовать с gcc и использовать его в качестве оптимизатора и кодогенератора.