Skip to content

Commit 3c4ea2e

Browse files
committed
all: add the Boehm-Demers-Weiser GC on Linux
This adds support for the well-known Boehm GC. It's significantly faster than our own naive GC and could be used as an alternative on bigger systems. In the future, this GC might also be supported on WebAssembly with some extra work. Right now it's Linux only (though Windows/MacOS shouldn't be too difficult to add).
1 parent 3a94c90 commit 3c4ea2e

17 files changed

+372
-55
lines changed

.github/workflows/nix.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ jobs:
1717
steps:
1818
- name: Checkout
1919
uses: actions/checkout@v4
20-
- name: Pull musl
20+
- name: Pull musl, bdwgc
2121
run: |
22-
git submodule update --init lib/musl
22+
git submodule update --init lib/musl lib/bdwgc
2323
- name: Restore LLVM source cache
2424
uses: actions/cache/restore@v4
2525
id: cache-llvm-source

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@
4242
[submodule "lib/wasi-cli"]
4343
path = lib/wasi-cli
4444
url = https://github.com/WebAssembly/wasi-cli
45+
[submodule "lib/bdwgc"]
46+
path = lib/bdwgc
47+
url = https://github.com/ivmai/bdwgc.git

GNUmakefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,7 @@ wasmtest:
850850

851851
build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binaryen)
852852
@mkdir -p build/release/tinygo/bin
853+
@mkdir -p build/release/tinygo/lib/bdwgc
853854
@mkdir -p build/release/tinygo/lib/clang/include
854855
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
855856
@mkdir -p build/release/tinygo/lib/macos-minimal-sdk
@@ -871,6 +872,7 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN
871872
ifneq ($(USE_SYSTEM_BINARYEN),1)
872873
@cp -p build/wasm-opt$(EXE) build/release/tinygo/bin
873874
endif
875+
@cp -rp lib/bdwgc/* build/release/tinygo/lib/bdwgc
874876
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
875877
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
876878
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS

builder/bdwgc.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package builder
2+
3+
// The well-known conservative Boehm-Demers-Weiser GC.
4+
// This file provides a way to compile this GC for use with TinyGo.
5+
6+
import (
7+
"path/filepath"
8+
9+
"github.com/tinygo-org/tinygo/goenv"
10+
)
11+
12+
var BoehmGC = Library{
13+
name: "bdwgc",
14+
cflags: func(target, headerPath string) []string {
15+
libdir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc")
16+
return []string{
17+
// use a modern environment
18+
"-DUSE_MMAP", // mmap is available
19+
"-DUSE_MUNMAP", // return memory to the OS using munmap
20+
"-DGC_BUILTIN_ATOMIC", // use compiler intrinsics for atomic operations
21+
"-DNO_EXECUTE_PERMISSION", // don't make the heap executable
22+
23+
// specific flags for TinyGo
24+
"-DALL_INTERIOR_POINTERS", // scan interior pointers (needed for Go)
25+
"-DIGNORE_DYNAMIC_LOADING", // we don't support dynamic loading at the moment
26+
"-DNO_GETCONTEXT", // musl doesn't support getcontext()
27+
28+
// Special flag to work around the lack of __data_start in ld.lld.
29+
// TODO: try to fix this in LLVM/lld directly so we don't have to
30+
// work around it anymore.
31+
"-DGC_DONT_REGISTER_MAIN_STATIC_DATA",
32+
33+
// Do not scan the stack. We have our own mechanism to do this.
34+
"-DSTACK_NOT_SCANNED",
35+
36+
// Assertions can be enabled while debugging GC issues.
37+
//"-DGC_ASSERTIONS",
38+
39+
// Threading is not yet supported, so these are disabled.
40+
//"-DGC_THREADS",
41+
//"-DTHREAD_LOCAL_ALLOC",
42+
43+
"-I" + libdir + "/include",
44+
}
45+
},
46+
sourceDir: func() string {
47+
return filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc")
48+
},
49+
librarySources: func(target string) ([]string, error) {
50+
return []string{
51+
"allchblk.c",
52+
"alloc.c",
53+
"blacklst.c",
54+
"dbg_mlc.c",
55+
"dyn_load.c",
56+
"finalize.c",
57+
"headers.c",
58+
"mach_dep.c",
59+
"malloc.c",
60+
"mark.c",
61+
"mark_rts.c",
62+
"misc.c",
63+
"new_hblk.c",
64+
"obj_map.c",
65+
"os_dep.c",
66+
"pthread_stop_world.c",
67+
"pthread_support.c",
68+
"reclaim.c",
69+
}, nil
70+
},
71+
}

builder/build.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,20 +143,22 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
143143
// the libc needs them.
144144
root := goenv.Get("TINYGOROOT")
145145
var libcDependencies []*compileJob
146+
var libcJob *compileJob
146147
switch config.Target.Libc {
147148
case "darwin-libSystem":
148149
job := makeDarwinLibSystemJob(config, tmpdir)
149150
libcDependencies = append(libcDependencies, job)
150151
case "musl":
151-
job, unlock, err := libMusl.load(config, tmpdir)
152+
var unlock func()
153+
libcJob, unlock, err = libMusl.load(config, tmpdir, nil)
152154
if err != nil {
153155
return BuildResult{}, err
154156
}
155157
defer unlock()
156-
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o")))
157-
libcDependencies = append(libcDependencies, job)
158+
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(libcJob.result), "crt1.o")))
159+
libcDependencies = append(libcDependencies, libcJob)
158160
case "picolibc":
159-
libcJob, unlock, err := libPicolibc.load(config, tmpdir)
161+
libcJob, unlock, err := libPicolibc.load(config, tmpdir, nil)
160162
if err != nil {
161163
return BuildResult{}, err
162164
}
@@ -169,14 +171,14 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
169171
}
170172
libcDependencies = append(libcDependencies, dummyCompileJob(path))
171173
case "wasmbuiltins":
172-
libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir)
174+
libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir, nil)
173175
if err != nil {
174176
return BuildResult{}, err
175177
}
176178
defer unlock()
177179
libcDependencies = append(libcDependencies, libcJob)
178180
case "mingw-w64":
179-
job, unlock, err := libMinGW.load(config, tmpdir)
181+
job, unlock, err := libMinGW.load(config, tmpdir, nil)
180182
if err != nil {
181183
return BuildResult{}, err
182184
}
@@ -652,14 +654,27 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
652654
// Add compiler-rt dependency if needed. Usually this is a simple load from
653655
// a cache.
654656
if config.Target.RTLib == "compiler-rt" {
655-
job, unlock, err := libCompilerRT.load(config, tmpdir)
657+
job, unlock, err := libCompilerRT.load(config, tmpdir, nil)
656658
if err != nil {
657659
return result, err
658660
}
659661
defer unlock()
660662
linkerDependencies = append(linkerDependencies, job)
661663
}
662664

665+
// The Boehm collector is stored in a separate C library.
666+
if config.GC() == "boehm" {
667+
if libcJob == nil {
668+
return BuildResult{}, fmt.Errorf("boehm GC isn't supported with libc %s", config.Target.Libc)
669+
}
670+
job, unlock, err := BoehmGC.load(config, tmpdir, libcJob)
671+
if err != nil {
672+
return BuildResult{}, err
673+
}
674+
defer unlock()
675+
linkerDependencies = append(linkerDependencies, job)
676+
}
677+
663678
// Add jobs to compile extra files. These files are in C or assembly and
664679
// contain things like the interrupt vector table and low level operations
665680
// such as stack switching.

builder/library.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ type Library struct {
4343
// output archive file, it is expected to be removed after use.
4444
// As a side effect, this call creates the library header files if they didn't
4545
// exist yet.
46-
func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, abortLock func(), err error) {
46+
// The provided libc job (if not null) will cause this libc to be added as a
47+
// dependency for all C compiler jobs, and adds libc headers for the given
48+
// target config. In other words, pass this libc if the library needs a libc to
49+
// compile.
50+
func (l *Library) load(config *compileopts.Config, tmpdir string, libc *compileJob) (job *compileJob, abortLock func(), err error) {
4751
outdir, precompiled := config.LibcPath(l.name)
4852
archiveFilePath := filepath.Join(outdir, "lib.a")
4953
if precompiled {
@@ -181,6 +185,9 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
181185
args = append(args, "-mfpu=vfpv2")
182186
}
183187
}
188+
if libc != nil {
189+
args = append(args, config.LibcCFlags()...)
190+
}
184191

185192
var once sync.Once
186193

@@ -233,7 +240,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
233240
objpath := filepath.Join(dir, cleanpath+".o")
234241
os.MkdirAll(filepath.Dir(objpath), 0o777)
235242
objs = append(objs, objpath)
236-
job.dependencies = append(job.dependencies, &compileJob{
243+
objfile := &compileJob{
237244
description: "compile " + srcpath,
238245
run: func(*compileJob) error {
239246
var compileArgs []string
@@ -248,7 +255,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
248255
}
249256
return nil
250257
},
251-
})
258+
}
259+
if libc != nil {
260+
objfile.dependencies = append(objfile.dependencies, libc)
261+
}
262+
job.dependencies = append(job.dependencies, objfile)
252263
}
253264

254265
// Create crt1.o job, if needed.
@@ -257,7 +268,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
257268
// won't make much of a difference in speed).
258269
if l.crt1Source != "" {
259270
srcpath := filepath.Join(sourceDir, l.crt1Source)
260-
job.dependencies = append(job.dependencies, &compileJob{
271+
crt1Job := &compileJob{
261272
description: "compile " + srcpath,
262273
run: func(*compileJob) error {
263274
var compileArgs []string
@@ -277,7 +288,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
277288
}
278289
return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o"))
279290
},
280-
})
291+
}
292+
if libc != nil {
293+
crt1Job.dependencies = append(crt1Job.dependencies, libc)
294+
}
295+
job.dependencies = append(job.dependencies, crt1Job)
281296
}
282297

283298
ok = true

builder/musl.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,23 +113,31 @@ var libMusl = Library{
113113
librarySources: func(target string) ([]string, error) {
114114
arch := compileopts.MuslArchitecture(target)
115115
globs := []string{
116+
"ctype/*.c",
116117
"env/*.c",
117118
"errno/*.c",
118119
"exit/*.c",
120+
"fcntl/*.c",
119121
"internal/defsysinfo.c",
122+
"internal/intscan.c",
120123
"internal/libc.c",
124+
"internal/shgetc.c",
121125
"internal/syscall_ret.c",
122126
"internal/vdso.c",
123127
"legacy/*.c",
124128
"locale/*.c",
125129
"linux/*.c",
130+
"locale/*.c",
126131
"malloc/*.c",
127132
"malloc/mallocng/*.c",
128133
"mman/*.c",
129134
"math/*.c",
135+
"misc/*.c",
130136
"multibyte/*.c",
137+
"sched/*.c",
131138
"signal/*.c",
132139
"stdio/*.c",
140+
"stdlib/*.c",
133141
"string/*.c",
134142
"thread/" + arch + "/*.s",
135143
"thread/*.c",

0 commit comments

Comments
 (0)