Tài liệu này giải thích cách sử dụng gccgo, một compiler cho ngôn ngữ Go. Compiler gccgo là một frontend mới cho GCC, bộ compiler GNU được dùng rộng rãi. Mặc dù bản thân frontend nằm dưới giấy phép kiểu BSD, gccgo thường được dùng như một phần của GCC và khi đó chịu sự điều chỉnh của GNU General Public License (giấy phép này áp dụng cho chính gccgo với tư cách một phần của GCC; nó không áp dụng cho mã do gccgo sinh ra).
Lưu ý rằng gccgo không phải là compiler gc (trình biên dịch Go gốc); xem hướng dẫn Cài đặt Go cho compiler đó.
Bản phát hành
Cách đơn giản nhất để cài đặt gccgo là cài một bản phát hành binary của GCC đã được build kèm hỗ trợ Go. Các bản phát hành binary của GCC có sẵn ở nhiều website khác nhau và thường được đóng gói sẵn trong các bản phân phối GNU/Linux. Chúng tôi cho rằng đa số những người build các binary này sẽ bao gồm cả hỗ trợ Go.
Bản phát hành GCC 4.7.1 và mọi bản 4.7 sau đó đều có đầy đủ compiler và thư viện Go 1.
Do lệch thời điểm, các bản phát hành GCC 4.8.0 và 4.8.1 gần giống nhưng không hoàn toàn trùng với Go 1.1. Bản phát hành GCC 4.8.2 có hiện thực đầy đủ Go 1.1.2.
Các bản phát hành GCC 4.9 có hiện thực đầy đủ Go 1.2.
Các bản phát hành GCC 5 có hiện thực đầy đủ các thư viện người dùng của Go 1.4. Runtime của Go 1.4 chưa được merge trọn vẹn, nhưng điều đó không ảnh hưởng tới các chương trình Go.
Các bản phát hành GCC 6 có hiện thực đầy đủ các thư viện người dùng của Go 1.6.1. Runtime của Go 1.6 chưa được merge trọn vẹn, nhưng điều đó không ảnh hưởng tới các chương trình Go.
Các bản phát hành GCC 7 có hiện thực đầy đủ các thư viện người dùng của Go 1.8.1. Cũng như các bản phát hành trước, runtime của Go 1.8 chưa được merge trọn vẹn, nhưng điều đó không ảnh hưởng tới các chương trình Go.
Các bản phát hành GCC 8 có hiện thực đầy đủ bản phát hành Go 1.10.1. Runtime của Go 1.10 giờ đã được merge trọn vẹn vào mã nguồn phát triển của GCC, và garbage collection đồng thời được hỗ trợ đầy đủ.
Các bản phát hành GCC 9 có hiện thực đầy đủ bản phát hành Go 1.12.2.
Các bản phát hành GCC 10 có hiện thực đầy đủ bản phát hành Go 1.14.6.
Các bản phát hành GCC 11 có hiện thực đầy đủ bản phát hành Go 1.16.3.
Các bản phát hành GCC 12 và 13 có hiện thực đầy đủ thư viện chuẩn của Go 1.18. Tuy nhiên, GCC chưa hỗ trợ generics.
Mã nguồn
Nếu bạn không thể dùng bản phát hành, hoặc muốn tự build gccgo, thì mã nguồn gccgo có thể lấy qua Git. Website của GCC có hướng dẫn lấy mã nguồn GCC. Mã nguồn gccgo nằm sẵn trong đó. Để tiện dụng, một phiên bản ổn định của phần hỗ trợ Go có ở branch devel/gccgo trong repository mã nguồn GCC chính: git://gcc.gnu.org/git/gcc.git. Branch này được cập nhật định kỳ với mã nguồn compiler Go ổn định.
Lưu ý rằng mặc dù gcc.gnu.org là cách tiện nhất để lấy mã nguồn của frontend Go, đó không phải nơi chứa mã nguồn gốc. Nếu bạn muốn đóng góp thay đổi cho compiler frontend Go, xem Đóng góp cho gccgo.
Build
Build gccgo cũng giống như build GCC với một hoặc hai tuỳ chọn bổ sung. Xem hướng dẫn trên website gcc. Khi bạn chạy configure, hãy thêm tuỳ chọn --enable-languages=c,c++,go (cùng với các ngôn ngữ khác mà bạn muốn build). Nếu bạn nhắm tới x86 32-bit, bạn sẽ muốn build gccgo sao cho mặc định hỗ trợ các câu lệnh compare and exchange có khoá; làm điều đó bằng cách dùng thêm tuỳ chọn configure --with-arch=i586 (hoặc kiến trúc mới hơn, tuỳ theo nơi bạn cần chương trình của mình chạy). Nếu bạn nhắm tới x86 64-bit, nhưng đôi khi muốn dùng tuỳ chọn -m32, thì hãy dùng tuỳ chọn configure --with-arch-32=i586.
Gold
Lưu ý: linker gold đã bị deprecated kể từ GNU Binutils 2.44 (tháng 2 năm 2025) và có thể bị loại bỏ trong các bản phát hành sau. Hãy cân nhắc dùng lld (linker của LLVM) để thay thế, nó cũng hỗ trợ split stack trên x86_64.
Trên các hệ thống x86 GNU/Linux, compiler gccgo có khả năng dùng một stack nhỏ không liền mạch cho goroutine. Điều này cho phép chương trình chạy nhiều goroutine hơn hẳn, vì mỗi goroutine chỉ cần một stack tương đối nhỏ. Để làm được vậy cần một linker hỗ trợ split stack. Linker gold phiên bản 2.22 trở lên hỗ trợ tính năng này, lld cũng vậy.
Nếu bạn vẫn cần dùng gold, bạn có thể cài GNU binutils 2.22 trở lên (lưu ý: kể từ binutils 2.44, gold nằm trong gói tarball binutils-with-gold riêng), hoặc tự build gold.
Để tự build gold, hãy build GNU binutils, dùng --enable-gold=default khi bạn chạy script configure. Trước khi build, bạn phải cài package flex và bison. Một chuỗi thao tác điển hình sẽ như sau (bạn có thể thay /opt/gold bằng bất kỳ thư mục nào mà bạn có quyền ghi):
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
Dù bạn cài gold theo cách nào, khi configure gccgo, hãy dùng tuỳ chọn --with-ld=GOLD_BINARY. Tương tự, với lld thì dùng --with-ld=/path/to/ld.lld.
Điều kiện tiên quyết
Cần một số điều kiện tiên quyết để build GCC, như được mô tả trên website gcc. Điều quan trọng là cài đặt mọi điều kiện tiên quyết trước khi chạy script configure của gcc. Các thư viện tiên quyết có thể tải về một cách tiện lợi bằng script contrib/download_prerequisites trong mã nguồn GCC.
Câu lệnh build
Sau khi đã cài mọi điều kiện tiên quyết, một chuỗi build và cài đặt điển hình sẽ như sau (chỉ dùng tuỳ chọn --with-ld nếu bạn dùng linker gold như mô tả ở trên):
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
Sử dụng gccgo
Compiler gccgo hoạt động giống như các frontend gcc khác. Kể từ GCC 5, bản cài đặt gccgo cũng bao gồm một phiên bản của lệnh go, có thể dùng để build chương trình Go như mô tả tại https://go.dev/cmd/go.
Để biên dịch một tệp mà không dùng lệnh go:
gccgo -c file.go
Lệnh đó tạo ra file.o. Để link các tệp lại với nhau thành một tệp thực thi:
gccgo -o file file.o
Để chạy tệp kết quả, bạn cần cho chương trình biết nơi tìm các package Go đã biên dịch. Có vài cách để làm điều này:
-
Đặt biến môi trường
LD_LIBRARY_PATH:LD_LIBRARY_PATH=${prefix}/lib/gcc/MACHINE/VERSION [or] LD_LIBRARY_PATH=${prefix}/lib64/gcc/MACHINE/VERSION export LD_LIBRARY_PATHỞ đây
${prefix}là tuỳ chọn--prefixđược dùng khi build gccgo. Với bản cài đặt binary thì nó thường là/usr. Việc dùnglibhaylib64tuỳ thuộc vào đích nhắm. Thông thườnglib64là đúng với hệ thống x86_64, cònlibđúng với các hệ thống khác. Ý tưởng là chỉ ra thư mục nơi tìm thấylibgo.so. -
Truyền tuỳ chọn
-Wl,-Rkhi bạn link (thay lib bằng lib64 nếu phù hợp với hệ thống của bạn):go build -gccgoflags -Wl,-R,${prefix}/lib/gcc/MACHINE/VERSION [or] gccgo -o file file.o -Wl,-R,${prefix}/lib/gcc/MACHINE/VERSION -
Dùng tuỳ chọn
-static-libgođể link tĩnh với các package đã biên dịch. -
Dùng tuỳ chọn
-staticđể link tĩnh hoàn toàn (mặc định của compilergc).
Tuỳ chọn
Compiler gccgo hỗ trợ mọi tuỳ chọn GCC không phụ thuộc ngôn ngữ, đáng chú ý là các tuỳ chọn -O và -g.
Tuỳ chọn -fgo-pkgpath=PKGPATH có thể dùng để đặt một tiền tố duy nhất cho package đang được biên dịch. Tuỳ chọn này được lệnh go dùng tự động, nhưng bạn có thể muốn dùng nó nếu bạn gọi gccgo trực tiếp. Tuỳ chọn này dành cho các chương trình lớn chứa nhiều package, nhằm cho phép nhiều package cùng dùng một định danh làm tên package. PKGPATH có thể là chuỗi bất kỳ; một lựa chọn tốt cho chuỗi này là đường dẫn dùng để import package.
Các tuỳ chọn -I và -L, vốn đồng nghĩa với nhau đối với compiler, có thể dùng để đặt đường dẫn tìm kiếm khi tìm các import. Các tuỳ chọn này không cần thiết nếu bạn build bằng lệnh go.
Import
Khi bạn biên dịch một tệp có export thứ gì đó, thông tin export sẽ được lưu trực tiếp trong tệp object. Nếu bạn build bằng gccgo trực tiếp, thay vì bằng lệnh go, thì khi bạn import một package, bạn phải cho gccgo biết cách tìm tệp đó.
Khi bạn import package FILE bằng gccgo, nó sẽ tìm dữ liệu import trong các tệp sau, và dùng tệp đầu tiên tìm được.
FILE.goxlibFILE.solibFILE.aFILE.o
FILE.gox, khi được dùng, thường sẽ không chứa gì ngoài dữ liệu export. Nó có thể được sinh ra từ FILE.o qua
objcopy -j .go_export FILE.o FILE.gox
Compiler gccgo sẽ tìm các tệp import trong thư mục hiện tại. Trong các tình huống phức tạp hơn, bạn có thể truyền tuỳ chọn -I hoặc -L cho gccgo. Cả hai tuỳ chọn đều nhận các thư mục để tìm kiếm. Tuỳ chọn -L cũng được truyền cho linker.
Compiler gccgo hiện tại (15/06/2015) chưa ghi lại tên tệp của các package được import trong tệp object. Bạn phải tự thu xếp để dữ liệu được import được link vào chương trình. Một lần nữa, điều này không cần thiết khi build bằng lệnh go.
gccgo -c mypackage.go # Exports mypackage
gccgo -c main.go # Imports mypackage
gccgo -o main main.o mypackage.o # Explicitly links with mypackage.o
Gỡ lỗi
Nếu bạn dùng tuỳ chọn -g khi biên dịch, bạn có thể chạy gdb trên tệp thực thi của mình. Trình gỡ lỗi chỉ hiểu hạn chế về Go. Bạn có thể đặt breakpoint, chạy từng bước, v.v. Bạn có thể in các biến, nhưng chúng sẽ được in như thể chúng mang kiểu C/C++. Với các kiểu số thì điều đó không quan trọng. String và interface của Go sẽ hiện ra dưới dạng struct hai phần tử. Map và channel của Go luôn được biểu diễn thành con trỏ C tới các struct của run-time.
Khả năng tương tác với C
Khi dùng gccgo, khả năng tương tác với C, hoặc với mã C++ được biên dịch bằng extern "C", là hạn chế.
Kiểu dữ liệu
Các kiểu cơ bản ánh xạ trực tiếp: int32 trong Go là int32_t trong C, int64 là int64_t, v.v. Kiểu int của Go là một số nguyên có cùng kích thước với một pointer, và vì vậy tương ứng với kiểu C intptr_t. byte của Go tương đương unsigned char của C. Pointer trong Go là pointer trong C. Một struct Go giống với struct C có cùng các field và kiểu.
Kiểu string của Go hiện được định nghĩa là một struct hai phần tử (điều này có thể thay đổi):
struct __go_string {
const unsigned char *__data;
intptr_t __length;
};
Bạn không thể truyền mảng giữa C và Go. Tuy nhiên, một pointer tới một mảng trong Go tương đương với một pointer C tới kiểu tương đương của phần tử. Ví dụ, *[10]int của Go tương đương int* của C, với giả định rằng pointer C đó thực sự trỏ tới 10 phần tử.
Một slice trong Go là một struct. Định nghĩa hiện tại là (điều này có thể thay đổi):
struct __go_slice {
void *__values;
intptr_t __count;
intptr_t __capacity;
};
Kiểu của một hàm Go là một pointer tới một struct (điều này có thể thay đổi). Field đầu tiên trong struct trỏ tới mã của hàm, tương đương với một pointer tới một hàm C có các kiểu tham số tương đương, cộng thêm một tham số phụ ở cuối. Tham số cuối này là closure, và đối số cần truyền là một pointer tới struct hàm Go đó. Khi một hàm Go trả về nhiều hơn một giá trị, hàm C trả về một struct. Ví dụ, các hàm sau là gần tương đương nhau:
func GoFunction(int) (int, float64)
struct { int i; float64 f; } CFunction(int, void*)
Các kiểu interface, channel và map của Go không có kiểu C tương ứng (interface là một struct hai phần tử còn channel và map là pointer tới struct trong C, nhưng các struct đó cố ý không được tài liệu hoá). Kiểu enum của C tương ứng với một kiểu số nguyên nào đó, nhưng chính xác là kiểu nào thì nói chung khó đoán; hãy dùng ép kiểu. Kiểu union của C không có kiểu Go tương ứng. Kiểu struct của C chứa bitfield không có kiểu Go tương ứng. Kiểu class của C++ không có kiểu Go tương ứng.
Cấp phát bộ nhớ hoàn toàn khác nhau giữa C và Go, vì Go dùng garbage collection. Các hướng dẫn chính xác trong lĩnh vực này chưa được xác định, nhưng nhiều khả năng sẽ cho phép truyền một pointer tới vùng bộ nhớ đã cấp phát từ C sang Go. Trách nhiệm giải phóng pointer đó về sau vẫn thuộc về phía C, và tất nhiên nếu phía C giải phóng pointer trong khi phía Go vẫn còn một bản sao thì chương trình sẽ hỏng. Khi truyền một pointer từ Go sang C, hàm Go phải giữ lại một bản sao nhìn thấy được của nó trong một biến Go nào đó. Nếu không, garbage collector của Go có thể xoá pointer trong khi hàm C vẫn còn đang dùng nó.
Tên hàm
Mã Go có thể gọi hàm C trực tiếp nhờ một phần mở rộng của Go được hiện thực trong gccgo: một khai báo hàm có thể được đặt trước bởi //extern NAME. Ví dụ, đây là cách hàm C open có thể được khai báo trong Go:
//extern open
func c_open(name *byte, mode int, perm int) int
Hàm C đương nhiên mong đợi một chuỗi kết thúc bằng NUL, mà trong Go tương đương với một pointer tới một mảng (không phải slice!) các byte có byte 0 kết thúc. Vậy một lời gọi mẫu từ Go sẽ trông như sau (sau khi import package syscall):
var name = [4]byte{'f', 'o', 'o', 0};
i := c_open(&name[0], syscall.O_RDONLY, 0);
(đây chỉ là ví dụ minh hoạ, để mở một tệp trong Go xin hãy dùng hàm os.Open của Go).
Lưu ý rằng nếu hàm C có thể block, chẳng hạn trong một lời gọi tới read, thì việc gọi hàm C có thể làm block chương trình Go. Trừ khi bạn hiểu rõ mình đang làm gì, mọi lời gọi giữa C và Go nên được hiện thực thông qua cgo hoặc SWIG, giống như với compiler gc.
Tên của các hàm Go được truy cập từ C có thể thay đổi. Hiện tại tên của một hàm Go không có receiver là prefix.package.Functionname. Tiền tố được đặt bởi tuỳ chọn -fgo-prefix dùng khi package được biên dịch; nếu không dùng tuỳ chọn này thì mặc định là go. Để gọi hàm đó từ C, bạn phải đặt tên bằng một phần mở rộng của GCC.
extern int go_function(int) __asm__ ("myprefix.mypackage.Function");
Tự động sinh khai báo Go từ mã nguồn C
Phiên bản Go của GCC hỗ trợ tự động sinh khai báo Go từ mã C. Cơ chế này khá vụng về, và phần lớn người dùng nên dùng chương trình cgo với tuỳ chọn -gccgo để thay thế.
Hãy biên dịch mã C của bạn như bình thường, và thêm tuỳ chọn -fdump-go-spec=FILENAME. Việc này sẽ tạo ra tệp FILENAME như một hiệu ứng phụ của quá trình biên dịch. Tệp này sẽ chứa các khai báo Go cho các kiểu, biến và hàm được khai báo trong mã C. Các kiểu C không thể biểu diễn được trong Go sẽ được ghi lại dưới dạng chú thích trong mã Go. Tệp được sinh ra sẽ không có khai báo package, nhưng ngoài ra có thể được gccgo biên dịch trực tiếp.
Quy trình này đầy những điều lưu ý và hạn chế không được nói ra, và chúng tôi không đảm bảo rằng nó sẽ không thay đổi trong tương lai. Nó hữu ích hơn khi dùng làm điểm khởi đầu cho mã Go thật, thay vì làm một quy trình thường xuyên.