Skip to content

Commit ca3080d

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 f10b392 commit ca3080d

File tree

17 files changed

+362
-56
lines changed

17 files changed

+362
-56
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
@@ -852,6 +852,7 @@ wasmtest:
852852

853853
build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binaryen)
854854
@mkdir -p build/release/tinygo/bin
855+
@mkdir -p build/release/tinygo/lib/bdwgc
855856
@mkdir -p build/release/tinygo/lib/clang/include
856857
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
857858
@mkdir -p build/release/tinygo/lib/macos-minimal-sdk
@@ -876,6 +877,7 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN
876877
ifneq ($(USE_SYSTEM_BINARYEN),1)
877878
@cp -p build/wasm-opt$(EXE) build/release/tinygo/bin
878879
endif
880+
@cp -rp lib/bdwgc/* build/release/tinygo/lib/bdwgc
879881
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
880882
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
881883
@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 := Musl.load(config, tmpdir)
152+
var unlock func()
153+
libcJob, unlock, err = Musl.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 := Picolibc.load(config, tmpdir)
161+
libcJob, unlock, err := Picolibc.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 := WasmBuiltins.load(config, tmpdir)
174+
libcJob, unlock, err := WasmBuiltins.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 := MinGW.load(config, tmpdir)
181+
job, unlock, err := MinGW.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 := CompilerRT.load(config, tmpdir)
657+
job, unlock, err := CompilerRT.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: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type Library struct {
3939
// The resulting directory may be stored in the provided tmpdir, which is
4040
// expected to be removed after the Load call.
4141
func (l *Library) Load(config *compileopts.Config, tmpdir string) (dir string, err error) {
42-
job, unlock, err := l.load(config, tmpdir)
42+
job, unlock, err := l.load(config, tmpdir, nil)
4343
if err != nil {
4444
return "", err
4545
}
@@ -56,7 +56,11 @@ func (l *Library) Load(config *compileopts.Config, tmpdir string) (dir string, e
5656
// output archive file, it is expected to be removed after use.
5757
// As a side effect, this call creates the library header files if they didn't
5858
// exist yet.
59-
func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, abortLock func(), err error) {
59+
// The provided libc job (if not null) will cause this libc to be added as a
60+
// dependency for all C compiler jobs, and adds libc headers for the given
61+
// target config. In other words, pass this libc if the library needs a libc to
62+
// compile.
63+
func (l *Library) load(config *compileopts.Config, tmpdir string, libc *compileJob) (job *compileJob, abortLock func(), err error) {
6064
outdir, precompiled := config.LibcPath(l.name)
6165
archiveFilePath := filepath.Join(outdir, "lib.a")
6266
if precompiled {
@@ -185,6 +189,9 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
185189
if strings.HasPrefix(target, "mips") {
186190
args = append(args, "-fno-pic")
187191
}
192+
if libc != nil {
193+
args = append(args, config.LibcCFlags()...)
194+
}
188195

189196
var once sync.Once
190197

@@ -237,7 +244,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
237244
objpath := filepath.Join(dir, cleanpath+".o")
238245
os.MkdirAll(filepath.Dir(objpath), 0o777)
239246
objs = append(objs, objpath)
240-
job.dependencies = append(job.dependencies, &compileJob{
247+
objfile := &compileJob{
241248
description: "compile " + srcpath,
242249
run: func(*compileJob) error {
243250
var compileArgs []string
@@ -252,7 +259,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
252259
}
253260
return nil
254261
},
255-
})
262+
}
263+
if libc != nil {
264+
objfile.dependencies = append(objfile.dependencies, libc)
265+
}
266+
job.dependencies = append(job.dependencies, objfile)
256267
}
257268

258269
// Create crt1.o job, if needed.
@@ -261,7 +272,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
261272
// won't make much of a difference in speed).
262273
if l.crt1Source != "" {
263274
srcpath := filepath.Join(sourceDir, l.crt1Source)
264-
job.dependencies = append(job.dependencies, &compileJob{
275+
crt1Job := &compileJob{
265276
description: "compile " + srcpath,
266277
run: func(*compileJob) error {
267278
var compileArgs []string
@@ -281,7 +292,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
281292
}
282293
return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o"))
283294
},
284-
})
295+
}
296+
if libc != nil {
297+
crt1Job.dependencies = append(crt1Job.dependencies, libc)
298+
}
299+
job.dependencies = append(job.dependencies, crt1Job)
285300
}
286301

287302
ok = true

compileopts/config.go

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ func (c *Config) LibcPath(name string) (path string, precompiled bool) {
231231
if c.ABI() != "" {
232232
archname += "-" + c.ABI()
233233
}
234+
if name == "bdwgc" {
235+
// Boehm GC is compiled against a particular libc.
236+
archname += "-" + c.Target.Libc
237+
}
234238

235239
// Try to load a precompiled library.
236240
precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, name)
@@ -285,81 +289,92 @@ func (c *Config) CFlags(libclang bool) []string {
285289
"-resource-dir="+resourceDir,
286290
)
287291
}
292+
cflags = append(cflags, c.LibcCFlags()...)
293+
// Always emit debug information. It is optionally stripped at link time.
294+
cflags = append(cflags, "-gdwarf-4")
295+
// Use the same optimization level as TinyGo.
296+
cflags = append(cflags, "-O"+c.Options.Opt)
297+
// Set the LLVM target triple.
298+
cflags = append(cflags, "--target="+c.Triple())
299+
// Set the -mcpu (or similar) flag.
300+
if c.Target.CPU != "" {
301+
if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
302+
// x86 prefers the -march flag (-mcpu is deprecated there).
303+
cflags = append(cflags, "-march="+c.Target.CPU)
304+
} else if strings.HasPrefix(c.Triple(), "avr") {
305+
// AVR MCUs use -mmcu instead of -mcpu.
306+
cflags = append(cflags, "-mmcu="+c.Target.CPU)
307+
} else {
308+
// The rest just uses -mcpu.
309+
cflags = append(cflags, "-mcpu="+c.Target.CPU)
310+
}
311+
}
312+
// Set the -mabi flag, if needed.
313+
if c.ABI() != "" {
314+
cflags = append(cflags, "-mabi="+c.ABI())
315+
}
316+
return cflags
317+
}
318+
319+
// LibcCFlags returns the C compiler flags for the configured libc.
320+
// It only uses flags that are part of the libc path (triple, cpu, abi, libc
321+
// name) so it can safely be used to compile another C library.
322+
func (c *Config) LibcCFlags() []string {
288323
switch c.Target.Libc {
289324
case "darwin-libSystem":
290325
root := goenv.Get("TINYGOROOT")
291-
cflags = append(cflags,
326+
return []string{
292327
"-nostdlibinc",
293328
"-isystem", filepath.Join(root, "lib/macos-minimal-sdk/src/usr/include"),
294-
)
329+
}
295330
case "picolibc":
296331
root := goenv.Get("TINYGOROOT")
297332
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
298333
path, _ := c.LibcPath("picolibc")
299-
cflags = append(cflags,
334+
return []string{
300335
"-nostdlibinc",
301336
"-isystem", filepath.Join(path, "include"),
302337
"-isystem", filepath.Join(picolibcDir, "include"),
303338
"-isystem", filepath.Join(picolibcDir, "tinystdio"),
304-
)
339+
}
305340
case "musl":
306341
root := goenv.Get("TINYGOROOT")
307342
path, _ := c.LibcPath("musl")
308343
arch := MuslArchitecture(c.Triple())
309-
cflags = append(cflags,
344+
return []string{
310345
"-nostdlibinc",
311346
"-isystem", filepath.Join(path, "include"),
312347
"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
348+
"-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"),
313349
"-isystem", filepath.Join(root, "lib", "musl", "include"),
314-
)
350+
}
315351
case "wasi-libc":
316352
root := goenv.Get("TINYGOROOT")
317-
cflags = append(cflags,
353+
return []string{
318354
"-nostdlibinc",
319-
"-isystem", root+"/lib/wasi-libc/sysroot/include")
355+
"-isystem", root + "/lib/wasi-libc/sysroot/include",
356+
}
320357
case "wasmbuiltins":
321358
// nothing to add (library is purely for builtins)
359+
return nil
322360
case "mingw-w64":
323361
root := goenv.Get("TINYGOROOT")
324362
path, _ := c.LibcPath("mingw-w64")
325-
cflags = append(cflags,
363+
return []string{
326364
"-nostdlibinc",
327365
"-isystem", filepath.Join(path, "include"),
328366
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
329367
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
330368
"-D_UCRT",
331-
)
369+
}
332370
case "":
333371
// No libc specified, nothing to add.
372+
return nil
334373
default:
335374
// Incorrect configuration. This could be handled in a better way, but
336375
// usually this will be found by developers (not by TinyGo users).
337376
panic("unknown libc: " + c.Target.Libc)
338377
}
339-
// Always emit debug information. It is optionally stripped at link time.
340-
cflags = append(cflags, "-gdwarf-4")
341-
// Use the same optimization level as TinyGo.
342-
cflags = append(cflags, "-O"+c.Options.Opt)
343-
// Set the LLVM target triple.
344-
cflags = append(cflags, "--target="+c.Triple())
345-
// Set the -mcpu (or similar) flag.
346-
if c.Target.CPU != "" {
347-
if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
348-
// x86 prefers the -march flag (-mcpu is deprecated there).
349-
cflags = append(cflags, "-march="+c.Target.CPU)
350-
} else if strings.HasPrefix(c.Triple(), "avr") {
351-
// AVR MCUs use -mmcu instead of -mcpu.
352-
cflags = append(cflags, "-mmcu="+c.Target.CPU)
353-
} else {
354-
// The rest just uses -mcpu.
355-
cflags = append(cflags, "-mcpu="+c.Target.CPU)
356-
}
357-
}
358-
// Set the -mabi flag, if needed.
359-
if c.ABI() != "" {
360-
cflags = append(cflags, "-mabi="+c.ABI())
361-
}
362-
return cflags
363378
}
364379

365380
// LDFlags returns the flags to pass to the linker. A few more flags are needed

0 commit comments

Comments
 (0)