Skip to content

Commit 3a7c25f

Browse files
aykevldeadprogram
authored andcommitted
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 a4cbe33 commit 3a7c25f

File tree

16 files changed

+364
-55
lines changed

16 files changed

+364
-55
lines changed

.github/workflows/nix.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ jobs:
2222
run: sudo apt-get remove llvm-18
2323
- name: Checkout
2424
uses: actions/checkout@v4
25-
- name: Pull musl
25+
- name: Pull musl, bdwgc
2626
run: |
27-
git submodule update --init lib/musl
27+
git submodule update --init lib/musl lib/bdwgc
2828
- name: Restore LLVM source cache
2929
uses: actions/cache/restore@v4
3030
id: cache-llvm-source

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,6 @@
3939
[submodule "lib/wasi-cli"]
4040
path = lib/wasi-cli
4141
url = https://github.com/WebAssembly/wasi-cli
42+
[submodule "lib/bdwgc"]
43+
path = lib/bdwgc
44+
url = https://github.com/ivmai/bdwgc.git

GNUmakefile

+5
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,7 @@ wasmtest:
940940

941941
build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binaryen)
942942
@mkdir -p build/release/tinygo/bin
943+
@mkdir -p build/release/tinygo/lib/bdwgc
943944
@mkdir -p build/release/tinygo/lib/clang/include
944945
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
945946
@mkdir -p build/release/tinygo/lib/macos-minimal-sdk
@@ -961,6 +962,7 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN
961962
ifneq ($(USE_SYSTEM_BINARYEN),1)
962963
@cp -p build/wasm-opt$(EXE) build/release/tinygo/bin
963964
endif
965+
@cp -rp lib/bdwgc/* build/release/tinygo/lib/bdwgc
964966
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
965967
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
966968
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
@@ -974,6 +976,7 @@ endif
974976
@cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt
975977
@cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl
976978
@cp -rp lib/musl/include build/release/tinygo/lib/musl
979+
@cp -rp lib/musl/src/ctype build/release/tinygo/lib/musl/src
977980
@cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src
978981
@cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src
979982
@cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src
@@ -988,8 +991,10 @@ endif
988991
@cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src
989992
@cp -rp lib/musl/src/misc build/release/tinygo/lib/musl/src
990993
@cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src
994+
@cp -rp lib/musl/src/sched build/release/tinygo/lib/musl/src
991995
@cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src
992996
@cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src
997+
@cp -rp lib/musl/src/stdlib build/release/tinygo/lib/musl/src
993998
@cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src
994999
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
9951000
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src

builder/bdwgc.go

+71
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

+22-7
Original file line numberDiff line numberDiff line change
@@ -147,20 +147,22 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
147147
// the libc needs them.
148148
root := goenv.Get("TINYGOROOT")
149149
var libcDependencies []*compileJob
150+
var libcJob *compileJob
150151
switch config.Target.Libc {
151152
case "darwin-libSystem":
152153
job := makeDarwinLibSystemJob(config, tmpdir)
153154
libcDependencies = append(libcDependencies, job)
154155
case "musl":
155-
job, unlock, err := libMusl.load(config, tmpdir)
156+
var unlock func()
157+
libcJob, unlock, err = libMusl.load(config, tmpdir, nil)
156158
if err != nil {
157159
return BuildResult{}, err
158160
}
159161
defer unlock()
160-
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o")))
161-
libcDependencies = append(libcDependencies, job)
162+
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(libcJob.result), "crt1.o")))
163+
libcDependencies = append(libcDependencies, libcJob)
162164
case "picolibc":
163-
libcJob, unlock, err := libPicolibc.load(config, tmpdir)
165+
libcJob, unlock, err := libPicolibc.load(config, tmpdir, nil)
164166
if err != nil {
165167
return BuildResult{}, err
166168
}
@@ -173,14 +175,14 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
173175
}
174176
libcDependencies = append(libcDependencies, dummyCompileJob(path))
175177
case "wasmbuiltins":
176-
libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir)
178+
libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir, nil)
177179
if err != nil {
178180
return BuildResult{}, err
179181
}
180182
defer unlock()
181183
libcDependencies = append(libcDependencies, libcJob)
182184
case "mingw-w64":
183-
job, unlock, err := libMinGW.load(config, tmpdir)
185+
job, unlock, err := libMinGW.load(config, tmpdir, nil)
184186
if err != nil {
185187
return BuildResult{}, err
186188
}
@@ -701,14 +703,27 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
701703
// Add compiler-rt dependency if needed. Usually this is a simple load from
702704
// a cache.
703705
if config.Target.RTLib == "compiler-rt" {
704-
job, unlock, err := libCompilerRT.load(config, tmpdir)
706+
job, unlock, err := libCompilerRT.load(config, tmpdir, nil)
705707
if err != nil {
706708
return result, err
707709
}
708710
defer unlock()
709711
linkerDependencies = append(linkerDependencies, job)
710712
}
711713

714+
// The Boehm collector is stored in a separate C library.
715+
if config.GC() == "boehm" {
716+
if libcJob == nil {
717+
return BuildResult{}, fmt.Errorf("boehm GC isn't supported with libc %s", config.Target.Libc)
718+
}
719+
job, unlock, err := BoehmGC.load(config, tmpdir, libcJob)
720+
if err != nil {
721+
return BuildResult{}, err
722+
}
723+
defer unlock()
724+
linkerDependencies = append(linkerDependencies, job)
725+
}
726+
712727
// Add jobs to compile extra files. These files are in C or assembly and
713728
// contain things like the interrupt vector table and low level operations
714729
// such as stack switching.

builder/library.go

+20-5
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

+6
Original file line numberDiff line numberDiff line change
@@ -113,26 +113,32 @@ 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",
119120
"fcntl/*.c",
120121
"internal/defsysinfo.c",
122+
"internal/intscan.c",
121123
"internal/libc.c",
124+
"internal/shgetc.c",
122125
"internal/syscall_ret.c",
123126
"internal/vdso.c",
124127
"legacy/*.c",
125128
"locale/*.c",
126129
"linux/*.c",
130+
"locale/*.c",
127131
"malloc/*.c",
128132
"malloc/mallocng/*.c",
129133
"mman/*.c",
130134
"math/*.c",
131135
"misc/*.c",
132136
"multibyte/*.c",
137+
"sched/*.c",
133138
"signal/" + arch + "/*.s",
134139
"signal/*.c",
135140
"stdio/*.c",
141+
"stdlib/*.c",
136142
"string/*.c",
137143
"thread/" + arch + "/*.s",
138144
"thread/*.c",

0 commit comments

Comments
 (0)