Работа с представлением 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 и использовать его в качестве оптимизатора и кодогенератора.