Настройка и использование gccgo

В данном документе описывается использование gccgo — компилятора для языка программирования Go. Компилятор gccgo представляет собой новый интерфейс для GCC — широко используемого компилятора GNU. Хотя сам интерфейс распространяется по лицензии, аналогичной BSD, gccgo обычно используется как часть GCC и поэтому подпадает под GNU General Public License (лицензия распространяется на gccgo как часть GCC; она не распространяется на код, генерируемый gccgo).

Обратите внимание, что gccgo — это не компилятор gc; для установки этого компилятора см. инструкции по Installing Go.

Релизы

Простейший способ установить gccgo — установить двоичный выпуск GCC, включающий поддержку Go. Двоичные выпуски GCC доступны на различных сайтах и обычно входят в состав дистрибутивов GNU/Linux. Мы ожидаем, что большинство людей, собирающих эти двоичные файлы, будут включать поддержку Go.

Релиз GCC 4.7.1 и все последующие релизы 4.7 включают полный компилятор Go 1 и библиотеки.

Из-за временных ограничений релизы GCC 4.8.0 и 4.8.1 близки к Go 1.1, но не идентичны ему. Релиз GCC 4.8.2 включает полную реализацию Go 1.1.2.

Релизы GCC 4.9 включают полную реализацию Go 1.2.

Релизы GCC 5 включают полную реализацию пользовательских библиотек Go 1.4. Среда выполнения Go 1.4 пока ещё не полностью интегрирована, но это не должно быть заметно для программ на Go.

Релизы GCC 6 включают полную реализацию пользовательских библиотек Go 1.6.1. Среда выполнения Go 1.6 пока ещё не полностью интегрирована, но это не должно быть заметно для программ на Go.

Релизы GCC 7 включают полную реализацию пользовательских библиотек Go 1.8.1. Как и в предыдущих релизах, среда выполнения Go 1.8 пока ещё не полностью интегрирована, но это не должно быть заметно для программ на Go.

Релизы GCC 8 включают полную реализацию релиза Go 1.10.1. Среда выполнения Go 1.10 теперь полностью интегрирована в исходные коды GCC, и параллелизм сборки мусора полностью поддерживается.

Релизы GCC 9 включают полную реализацию релиза Go 1.12.2.

Релизы GCC 10 включают полную реализацию релиза Go 1.14.6.

Релизы GCC 11 включают полную реализацию релиза Go 1.16.3.

Релизы GCC 12 и 13 включают полную реализацию стандартной библиотеки Go 1.18. Однако GCC пока ещё не включает поддержку дженериков.

Исходный код

Если вы не можете использовать релиз или предпочитаете собрать gccgo самостоятельно, исходный код gccgo доступен через Git. Веб-сайт GCC предоставляет инструкции по получению исходного кода GCC. Исходный код gccgo включён в состав. Для удобства стабильная версия поддержки Go доступна в ветке devel/gccgo основного репозитория исходного кода GCC: git://gcc.gnu.org/git/gcc.git. Эта ветка периодически обновляется стабильными исходными кодами компилятора Go.

Обратите внимание, что хотя gcc.gnu.org является наиболее удобным способом получения исходного кода для интерфейса Go, это не место, где находятся основные исходники. Если вы хотите внести изменения в компилятор интерфейса Go, см. Вклад в gccgo.

Сборка

Сборка gccgo аналогична сборке GCC с одним или двумя дополнительными параметрами. См. инструкции на веб-сайте gcc. Когда вы запускаете configure, добавьте параметр --enable-languages=c,c++,go (вместе с другими языками, которые вы хотите собрать). Если вы нацелены на 32-битную архитектуру x86, то вы захотите собрать gccgo по умолчанию с поддержкой заблокированных инструкций compare и exchange; сделать это можно, используя параметр configure --with-arch=i586 (или более новую архитектуру, в зависимости от того, где должны запускаться ваши программы). Если вы нацелены на 64-битную архитектуру x86, но иногда хотите использовать опцию -m32, то используйте параметр configure --with-arch-32=i586.

Gold

В системах GNU/Linux для архитектуры x86 компилятор gccgo способен использовать небольшой разреженный стек для горутин. Это позволяет программам запускать гораздо больше горутин, поскольку каждая горутина может использовать относительно небольшой стек. Для этого требуется использование компоновщика gold версии 2.22 или новее. Вы можете либо установить GNU binutils версии 2.22 или новее, либо собрать gold самостоятельно.

Чтобы собрать gold самостоятельно, соберите GNU binutils, используя --enable-gold=default при запуске скрипта configure. Перед сборкой необходимо установить пакеты flex и bison. Типичная последовательность выглядит так (вы можете заменить /opt/gold на любой каталог, в который у вас есть права записи):

git clone git://sourceware.org/git/binutils-gdb.git
mkdir binutils-objdir
cd binutils-objdir
../binutils-gdb/configure --enable-gold=default --prefix=/opt/gold
make
make install

Каким бы способом вы ни устанавливали gold, при конфигурации gccgo используйте параметр --with-ld=GOLD_BINARY.

Предварительные условия

Для сборки GCC требуются определённые предварительные условия, как описано на веб-сайте gcc. Важно установить все предварительные условия перед запуском скрипта configure gcc. Предварительные библиотеки можно удобно загрузить с помощью скрипта contrib/download_prerequisites в исходных кодах GCC.

Команды сборки

После установки всех необходимых компонентов, типичная последовательность сборки и установки будет выглядеть следующим образом (используйте опцию --with-ld только в том случае, если вы используете gold linker, как описано выше):

git clone --branch devel/gccgo git://gcc.gnu.org/git/gcc.git gccgo
mkdir objdir
cd objdir
../gccgo/configure --prefix=/opt/gccgo --enable-languages=c,c++,go --with-ld=/opt/gold/bin/ld
make
make install

Использование gccgo

Компилятор gccgo работает так же, как и другие фронтенды GCC. Начиная с GCC 5, установка gccgo также включает версию команды go, которую можно использовать для сборки программ на Go, как описано на https://go.dev/cmd/go.

Чтобы скомпилировать файл без использования команды go:

gccgo -c file.go

Это создаст файл file.o. Чтобы связать файлы и создать исполняемый файл:

gccgo -o file file.o

Чтобы запустить полученный файл, необходимо указать программе, где находятся скомпилированные пакеты Go. Существует несколько способов сделать это:

Опции

Компилятор gccgo поддерживает все опции GCC, независимые от языка, в частности опции -O и -g.

Опция -fgo-pkgpath=PKGPATH может быть использована для задания уникального префикса для компилируемого пакета. Эта опция автоматически используется командой go, но вы можете использовать её, если вызываете gccgo напрямую. Она предназначена для использования с большими программами, содержащими множество пакетов, чтобы позволить нескольким пакетам использовать один и тот же идентификатор в качестве имени пакета. PKGPATH может быть любой строкой; хорошим выбором строки является путь, используемый для импорта пакета.

Опции -I и -L, которые являются синонимами для компилятора, могут использоваться для задания пути поиска импортов. Эти опции не требуются, если вы собираете с помощью команды go.

Импорты

Когда вы компилируете файл, который экспортирует какие-либо сущности, информация об экспорте будет сохранена непосредственно в объектном файле. Если вы собираете проект с помощью gccgo напрямую, а не с помощью команды go, то при импорте пакета необходимо указать gccgo, где искать файл.

При импорте пакета FILE с помощью gccgo, он будет искать данные импорта в следующих файлах и использовать первый найденный.

Файл FILE.gox, если он используется, обычно содержит только данные экспорта. Его можно сгенерировать из FILE.o с помощью

objcopy -j .go_export FILE.o FILE.gox

Компилятор gccgo ищет файлы импорта в текущей директории. В более сложных сценариях вы можете передать опцию -I или -L компилятору gccgo. Обе опции принимают директории для поиска. Опция -L также передается компоновщику.

Компилятор gccgo в настоящее время (15 июня 2015 года) не записывает имя импортируемого пакета в объектный файл. Необходимо вручную обеспечить связывание импортируемых данных в программу. Опять же, это не требуется при сборке с использованием команды go.

gccgo -c mypackage.go              # Экспортирует mypackage
gccgo -c main.go                   # Импортирует mypackage
gccgo -o main main.o mypackage.o   # Явно связывает с mypackage.o

Отладка

Если вы используете опцию -g при компиляции, вы можете запустить gdb на исполняемом файле. Отладчик имеет лишь ограниченное представление о языке Go. Вы можете устанавливать точки останова, выполнять пошаговое выполнение и т.д. Вы можете выводить значения переменных, но они будут отображаться так, словно они имеют типы C/C++. Для числовых типов это не имеет значения. Строки и интерфейсы Go будут отображаться как двухэлементные структуры. Карты и каналы Go всегда представляются как указатели на структуры времени выполнения в C.

Совместимость с C

При использовании gccgo существует ограниченная совместимость с C, или с кодом на C++, скомпилированным с использованием extern "C".

Типы

Базовые типы отображаются напрямую: int32 в Go соответствует int32_t в C, int64int64_t и так далее. Тип Go int представляет целое число, размер которого совпадает с размером указателя, и поэтому соответствует типу C intptr_t. Тип Go byte эквивалентен типу C unsigned char. Указатели в Go — это указатели в C. Структура Go struct соответствует структуре C с теми же полями и типами.

Тип Go string в настоящее время определен как двухэлементная структура (это может измениться):

struct __go_string {
  const unsigned char *__data;
  intptr_t __length;
};

Нельзя передавать массивы между C и Go. Однако указатель на массив в Go эквивалентен указателю C на соответствующий тип элемента. Например, Go *[10]int эквивалентен C int*, при условии, что указатель C действительно указывает на 10 элементов.

Срез в Go — это структура. Текущее определение выглядит так (это может быть изменено):

struct __go_slice {
  void *__values;
  intptr_t __count;
  intptr_t __capacity;
};

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

func GoFunction(int) (int, float64)
struct { int i; float64 f; } CFunction(int, void*)

Типы Go interface, channel и map не имеют соответствующего C-типа (interface — это двухэлементная структура, а channel и map — указатели на структуры в C, но сами структуры намеренно не документированы). C enum типы соответствуют некоторому целочисленному типу, но точно определить, какой именно, в общем случае сложно; используйте приведение типов. C union типы не имеют соответствующего Go-типа. C struct типы, содержащие битовые поля, не имеют соответствующего Go-типа. C++ class типы не имеют соответствующего Go-типа.

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

Имена функций

Go-код может напрямую вызывать C-функции, используя расширение Go, реализованное в gccgo: объявление функции может быть предварено //extern NAME. Например, вот как можно объявить C-функцию open в Go:

//extern open
func c_open(name *byte, mode int, perm int) int

C-функция естественным образом ожидает строку, завершающуюся нулём, которая в Go эквивалентна указателю на массив (не срез!) типа byte с завершающим нулевым байтом. Таким образом, пример вызова из Go будет выглядеть так (после импорта пакета syscall):

var name = [4]byte{'f', 'o', 'o', 0};
i := c_open(&name[0], syscall.O_RDONLY, 0);

(это лишь пример, чтобы открыть файл в Go, следует использовать Go-функцию os.Open).

Обратите внимание, что если C-функция может заблокироваться, например, при вызове read, вызов C-функции может заблокировать Go-программу. Если вы не полностью понимаете, что делаете, все вызовы между C и Go должны быть реализованы через cgo или SWIG, как и для компилятора gc.

Имя функций Go, к которым осуществляется доступ из C, может быть изменено. В настоящее время имя функции Go без получателя выглядит как prefix.package.Functionname. Префикс устанавливается с помощью опции -fgo-prefix, используемой при компиляции пакета; если опция не используется, по умолчанию используется значение go. Чтобы вызвать функцию из C, необходимо установить имя с использованием расширения GCC.

extern int go_function(int) __asm__ ("myprefix.mypackage.Function");

Автоматическая генерация Go-объявлений из исходного кода C

Версия GCC для Go поддерживает автоматическую генерацию Go-объявлений из кода на C. Эта функциональность является довольно громоздкой, и большинству пользователей рекомендуется использовать программу cgo с опцией -gccgo.

Скомпилируйте свой код на C обычным образом и добавьте опцию -fdump-go-spec=FILENAME. Это приведет к созданию файла FILENAME в результате компиляции. В этом файле будут содержаться Go-объявления для типов, переменных и функций, объявленных в коде на C. Типы C, которые не могут быть представлены в Go, будут записаны в виде комментариев в Go-коде. Созданный файл не будет содержать объявления package, но в противном случае его можно будет скомпилировать напрямую с помощью gccgo.

Этот процесс полон непредставленных предупреждений и ограничений, и мы не гарантируем, что он не будет изменён в будущем. Более полезен он как отправная точка для реального Go-кода, чем как регулярная процедура.

GoRu.dev Golang на русском

На сайте представлена адаптированная под русский язык документация языка программирования Golang