Skip to content

Commit a0c534f

Browse files
committed
wasm: add Boehm GC support
This adds support for `-gc=boehm` on `-target=wasip1` and `-target=wasm` (in a browser or NodeJS). Notably it does *not* add Boehm GC support for `-target=wasip2`, since that target doesn't have a real libc.
1 parent dcf609d commit a0c534f

21 files changed

+84
-52
lines changed

builder/bdwgc.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ var BoehmGC = Library{
3030
// Use a minimal environment.
3131
"-DNO_MSGBOX_ON_ERROR", // don't call MessageBoxA on Windows
3232
"-DDONT_USE_ATEXIT",
33+
"-DNO_GETENV",
3334

3435
// Special flag to work around the lack of __data_start in ld.lld.
3536
// TODO: try to fix this in LLVM/lld directly so we don't have to
@@ -53,7 +54,7 @@ var BoehmGC = Library{
5354
sourceDir: func() string {
5455
return filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc")
5556
},
56-
librarySources: func(target string) ([]string, error) {
57+
librarySources: func(target string, _ bool) ([]string, error) {
5758
sources := []string{
5859
"allchblk.c",
5960
"alloc.c",

builder/builtins.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ var libCompilerRT = Library{
220220
// Development build.
221221
return filepath.Join(goenv.Get("TINYGOROOT"), "lib/compiler-rt-builtins")
222222
},
223-
librarySources: func(target string) ([]string, error) {
223+
librarySources: func(target string, _ bool) ([]string, error) {
224224
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
225225
switch compileopts.CanonicalArchName(target) {
226226
case "arm":

builder/library.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type Library struct {
3535
sourceDir func() string
3636

3737
// The source files, relative to sourceDir.
38-
librarySources func(target string) ([]string, error)
38+
librarySources func(target string, libcNeedsMalloc bool) ([]string, error)
3939

4040
// The source code for the crt1.o file, relative to sourceDir.
4141
crt1Source string
@@ -223,7 +223,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
223223

224224
// Create jobs to compile all sources. These jobs are depended upon by the
225225
// archive job above, so must be run first.
226-
paths, err := l.librarySources(target)
226+
paths, err := l.librarySources(target, config.LibcNeedsMalloc())
227227
if err != nil {
228228
return nil, nil, err
229229
}

builder/mingw-w64.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ var libMinGW = Library{
3737
"-I" + headerPath,
3838
}
3939
},
40-
librarySources: func(target string) ([]string, error) {
40+
librarySources: func(target string, _ bool) ([]string, error) {
4141
// These files are needed so that printf and the like are supported.
4242
sources := []string{
4343
"mingw-w64-crt/stdio/ucrt_fprintf.c",

builder/musl.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ var libMusl = Library{
121121
return cflags
122122
},
123123
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") },
124-
librarySources: func(target string) ([]string, error) {
124+
librarySources: func(target string, _ bool) ([]string, error) {
125125
arch := compileopts.MuslArchitecture(target)
126126
globs := []string{
127127
"ctype/*.c",

builder/picolibc.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ var libPicolibc = Library{
4343
}
4444
},
4545
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") },
46-
librarySources: func(target string) ([]string, error) {
46+
librarySources: func(target string, _ bool) ([]string, error) {
4747
sources := append([]string(nil), picolibcSources...)
4848
if !strings.HasPrefix(target, "avr") {
4949
// Small chips without long jumps can't compile many files (printf,

builder/wasilibc.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ var libWasiLibc = Library{
119119
return nil
120120
},
121121
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") },
122-
librarySources: func(target string) ([]string, error) {
122+
librarySources: func(target string, libcNeedsMalloc bool) ([]string, error) {
123123
type filePattern struct {
124124
glob string
125125
exclude []string
@@ -168,6 +168,11 @@ var libWasiLibc = Library{
168168
{glob: "libc-bottom-half/sources/*.c"},
169169
}
170170

171+
// We're using the Boehm GC, so we need a heap implementation in the libc.
172+
if libcNeedsMalloc {
173+
globs = append(globs, filePattern{glob: "dlmalloc/src/dlmalloc.c"})
174+
}
175+
171176
// See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile
172177
sources := []string{
173178
"libc-top-half/musl/src/misc/a64l.c",

builder/wasmbuiltins.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ var libWasmBuiltins = Library{
3939
}
4040
},
4141
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") },
42-
librarySources: func(target string) ([]string, error) {
42+
librarySources: func(target string, _ bool) ([]string, error) {
4343
return []string{
4444
// memory builtins needed for llvm.memcpy.*, llvm.memmove.*, and
4545
// llvm.memset.* LLVM intrinsics.

compileopts/config.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func (c *Config) GC() string {
122122
// that can be traced by the garbage collector.
123123
func (c *Config) NeedsStackObjects() bool {
124124
switch c.GC() {
125-
case "conservative", "custom", "precise":
125+
case "conservative", "custom", "precise", "boehm":
126126
for _, tag := range c.BuildTags() {
127127
if tag == "tinygo.wasm" {
128128
return true
@@ -247,6 +247,15 @@ func MuslArchitecture(triple string) string {
247247
return CanonicalArchName(triple)
248248
}
249249

250+
// Returns true if the libc needs to include malloc, for the libcs where this
251+
// matters.
252+
func (c *Config) LibcNeedsMalloc() bool {
253+
if c.GC() == "boehm" && c.Target.Libc == "wasi-libc" {
254+
return true
255+
}
256+
return false
257+
}
258+
250259
// LibcPath returns the path to the libc directory. The libc path will be a libc
251260
// path in the cache directory (which might not yet be built).
252261
func (c *Config) LibcPath(name string) string {
@@ -265,9 +274,14 @@ func (c *Config) LibcPath(name string) string {
265274
archname += "-" + c.Target.Libc
266275
}
267276

277+
options := ""
278+
if c.LibcNeedsMalloc() {
279+
options += "+malloc"
280+
}
281+
268282
// No precompiled library found. Determine the path name that will be used
269283
// in the build cache.
270-
return filepath.Join(goenv.Get("GOCACHE"), name+"-"+archname)
284+
return filepath.Join(goenv.Get("GOCACHE"), name+options+"-"+archname)
271285
}
272286

273287
// DefaultBinaryExtension returns the default extension for binaries, such as

compileopts/target.go

+3
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,9 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
267267
DefaultStackSize: 1024 * 64, // 64kB
268268
GDB: []string{"gdb"},
269269
PortReset: "false",
270+
ExtraFiles: []string{
271+
"src/runtime/gc_boehm.c",
272+
},
270273
}
271274

272275
// Configure target based on GOARCH.

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/tinygo-org/tinygo
33
go 1.19
44

55
require (
6-
github.com/aykevl/go-wasm v0.0.2-0.20240825160117-b76c3f9f0982
6+
github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139
77
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
88
github.com/chromedp/cdproto v0.0.0-20220113222801-0725d94bb6ee
99
github.com/chromedp/chromedp v0.7.6

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
github.com/aykevl/go-wasm v0.0.2-0.20240825160117-b76c3f9f0982 h1:cD7QfvrJdYmBw2tFP/VyKPT8ZESlcrwSwo7SvH9Y4dc=
2-
github.com/aykevl/go-wasm v0.0.2-0.20240825160117-b76c3f9f0982/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw=
1+
github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139 h1:2O/WuAt8J5id3khcAtVB90czG80m+v0sfkLE07GrCVg=
2+
github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw=
33
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
44
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
55
github.com/chromedp/cdproto v0.0.0-20211126220118-81fa0469ad77/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U=

lib/bdwgc

Submodule bdwgc updated 214 files

main_test.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,26 @@ func TestBuild(t *testing.T) {
194194
t.Run("WebAssembly", func(t *testing.T) {
195195
t.Parallel()
196196
runPlatTests(optionsFromTarget("wasm", sema), tests, t)
197+
198+
// Test with -gc=boehm.
199+
t.Run("gc.go-boehm", func(t *testing.T) {
200+
t.Parallel()
201+
optionsBoehm := optionsFromTarget("wasm", sema)
202+
optionsBoehm.GC = "boehm"
203+
runTest("gc.go", optionsBoehm, t, nil, nil)
204+
})
197205
})
198-
t.Run("WASI", func(t *testing.T) {
206+
t.Run("WASIp1", func(t *testing.T) {
199207
t.Parallel()
200208
runPlatTests(optionsFromTarget("wasip1", sema), tests, t)
209+
210+
// Test with -gc=boehm.
211+
t.Run("gc.go-boehm", func(t *testing.T) {
212+
t.Parallel()
213+
optionsBoehm := optionsFromTarget("wasip1", sema)
214+
optionsBoehm.GC = "boehm"
215+
runTest("gc.go", optionsBoehm, t, nil, nil)
216+
})
201217
})
202218
t.Run("WASIp2", func(t *testing.T) {
203219
t.Parallel()

src/runtime/arch_tinygowasm_malloc.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build tinygo.wasm && !(custommalloc || wasm_unknown)
1+
//go:build tinygo.wasm && !(custommalloc || wasm_unknown || gc.boehm)
22

33
package runtime
44

src/runtime/gc_boehm.c

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//go:build none
2+
3+
// This file is included in the build on systems that support the Boehm GC,
4+
// despite the //go:build line above.
5+
6+
typedef void (* GC_push_other_roots_proc)(void);
7+
void GC_set_push_other_roots(GC_push_other_roots_proc);
8+
9+
void tinygo_runtime_bdwgc_callback(void);
10+
11+
static void callback(void) {
12+
tinygo_runtime_bdwgc_callback();
13+
}
14+
15+
void tinygo_runtime_bdwgc_init(void) {
16+
GC_set_push_other_roots(callback);
17+
}

src/runtime/gc_boehm.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package runtime
44

55
import (
66
"internal/gclayout"
7-
"internal/reflectlite"
87
"internal/task"
98
"unsafe"
109
)
@@ -19,11 +18,15 @@ var gcLock task.PMutex
1918
func initHeap() {
2019
libgc_init()
2120

22-
libgc_set_push_other_roots(gcCallbackPtr)
21+
// Call GC_set_push_other_roots(gcCallback) in C because of function
22+
// signature differences that do matter in WebAssembly.
23+
gcInit()
2324
}
2425

25-
var gcCallbackPtr = reflectlite.ValueOf(gcCallback).UnsafePointer()
26+
//export tinygo_runtime_bdwgc_init
27+
func gcInit()
2628

29+
//export tinygo_runtime_bdwgc_callback
2730
func gcCallback() {
2831
// Mark the system stack and (if we're on a goroutine stack) also the
2932
// current goroutine stack.

src/runtime/gc_stack_portable.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build (gc.conservative || gc.custom || gc.precise) && tinygo.wasm
1+
//go:build (gc.conservative || gc.custom || gc.precise || gc.boehm) && tinygo.wasm
22

33
package runtime
44

targets/wasip1.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"--no-demangle"
2424
],
2525
"extra-files": [
26-
"src/runtime/asm_tinygowasm.S"
26+
"src/runtime/asm_tinygowasm.S",
27+
"src/runtime/gc_boehm.c"
2728
],
2829
"emulator": "wasmtime run --dir={tmpDir}::/tmp {}"
2930
}

targets/wasm.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"--no-demangle"
2525
],
2626
"extra-files": [
27-
"src/runtime/asm_tinygowasm.S"
27+
"src/runtime/asm_tinygowasm.S",
28+
"src/runtime/gc_boehm.c"
2829
],
2930
"emulator": "node {root}/targets/wasm_exec.js {}"
3031
}

tests/runtime_wasi/malloc_test.go

-29
Original file line numberDiff line numberDiff line change
@@ -124,32 +124,3 @@ func TestMallocFree(t *testing.T) {
124124
})
125125
}
126126
}
127-
128-
func TestMallocEmpty(t *testing.T) {
129-
ptr := libc_malloc(0)
130-
if ptr != nil {
131-
t.Errorf("expected nil pointer, got %p", ptr)
132-
}
133-
}
134-
135-
func TestCallocEmpty(t *testing.T) {
136-
ptr := libc_calloc(0, 1)
137-
if ptr != nil {
138-
t.Errorf("expected nil pointer, got %p", ptr)
139-
}
140-
ptr = libc_calloc(1, 0)
141-
if ptr != nil {
142-
t.Errorf("expected nil pointer, got %p", ptr)
143-
}
144-
}
145-
146-
func TestReallocEmpty(t *testing.T) {
147-
ptr := libc_malloc(1)
148-
if ptr == nil {
149-
t.Error("expected pointer but was nil")
150-
}
151-
ptr = libc_realloc(ptr, 0)
152-
if ptr != nil {
153-
t.Errorf("expected nil pointer, got %p", ptr)
154-
}
155-
}

0 commit comments

Comments
 (0)