diff --git a/builder/bdwgc.go b/builder/bdwgc.go index 8341005d2e..d5f99bbbeb 100644 --- a/builder/bdwgc.go +++ b/builder/bdwgc.go @@ -30,6 +30,7 @@ var BoehmGC = Library{ // Use a minimal environment. "-DNO_MSGBOX_ON_ERROR", // don't call MessageBoxA on Windows "-DDONT_USE_ATEXIT", + "-DNO_GETENV", // Special flag to work around the lack of __data_start in ld.lld. // TODO: try to fix this in LLVM/lld directly so we don't have to @@ -53,7 +54,7 @@ var BoehmGC = Library{ sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc") }, - librarySources: func(target string) ([]string, error) { + librarySources: func(target string, _ bool) ([]string, error) { sources := []string{ "allchblk.c", "alloc.c", diff --git a/builder/builtins.go b/builder/builtins.go index b493b6680a..008f02a55b 100644 --- a/builder/builtins.go +++ b/builder/builtins.go @@ -220,7 +220,7 @@ var libCompilerRT = Library{ // Development build. return filepath.Join(goenv.Get("TINYGOROOT"), "lib/compiler-rt-builtins") }, - librarySources: func(target string) ([]string, error) { + librarySources: func(target string, _ bool) ([]string, error) { builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins switch compileopts.CanonicalArchName(target) { case "arm": diff --git a/builder/library.go b/builder/library.go index 53ae5d9d60..dd002e0364 100644 --- a/builder/library.go +++ b/builder/library.go @@ -35,7 +35,7 @@ type Library struct { sourceDir func() string // The source files, relative to sourceDir. - librarySources func(target string) ([]string, error) + librarySources func(target string, libcNeedsMalloc bool) ([]string, error) // The source code for the crt1.o file, relative to sourceDir. crt1Source string @@ -223,7 +223,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ // Create jobs to compile all sources. These jobs are depended upon by the // archive job above, so must be run first. - paths, err := l.librarySources(target) + paths, err := l.librarySources(target, config.LibcNeedsMalloc()) if err != nil { return nil, nil, err } diff --git a/builder/mingw-w64.go b/builder/mingw-w64.go index 32cf58f531..33e7f1cfa3 100644 --- a/builder/mingw-w64.go +++ b/builder/mingw-w64.go @@ -37,7 +37,7 @@ var libMinGW = Library{ "-I" + headerPath, } }, - librarySources: func(target string) ([]string, error) { + librarySources: func(target string, _ bool) ([]string, error) { // These files are needed so that printf and the like are supported. sources := []string{ "mingw-w64-crt/stdio/ucrt_fprintf.c", diff --git a/builder/musl.go b/builder/musl.go index 4195332550..ce39634946 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -121,7 +121,7 @@ var libMusl = Library{ return cflags }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") }, - librarySources: func(target string) ([]string, error) { + librarySources: func(target string, _ bool) ([]string, error) { arch := compileopts.MuslArchitecture(target) globs := []string{ "ctype/*.c", diff --git a/builder/picolibc.go b/builder/picolibc.go index 9026b99ee4..43837fa1dc 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -43,7 +43,7 @@ var libPicolibc = Library{ } }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") }, - librarySources: func(target string) ([]string, error) { + librarySources: func(target string, _ bool) ([]string, error) { sources := append([]string(nil), picolibcSources...) if !strings.HasPrefix(target, "avr") { // Small chips without long jumps can't compile many files (printf, diff --git a/builder/wasilibc.go b/builder/wasilibc.go index 9b8ad7a5e2..ebfc8a72fa 100644 --- a/builder/wasilibc.go +++ b/builder/wasilibc.go @@ -119,7 +119,7 @@ var libWasiLibc = Library{ return nil }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") }, - librarySources: func(target string) ([]string, error) { + librarySources: func(target string, libcNeedsMalloc bool) ([]string, error) { type filePattern struct { glob string exclude []string @@ -168,6 +168,11 @@ var libWasiLibc = Library{ {glob: "libc-bottom-half/sources/*.c"}, } + // We're using the Boehm GC, so we need a heap implementation in the libc. + if libcNeedsMalloc { + globs = append(globs, filePattern{glob: "dlmalloc/src/dlmalloc.c"}) + } + // See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile sources := []string{ "libc-top-half/musl/src/misc/a64l.c", diff --git a/builder/wasmbuiltins.go b/builder/wasmbuiltins.go index 4c158f2337..031dd372d3 100644 --- a/builder/wasmbuiltins.go +++ b/builder/wasmbuiltins.go @@ -39,7 +39,7 @@ var libWasmBuiltins = Library{ } }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") }, - librarySources: func(target string) ([]string, error) { + librarySources: func(target string, _ bool) ([]string, error) { return []string{ // memory builtins needed for llvm.memcpy.*, llvm.memmove.*, and // llvm.memset.* LLVM intrinsics. diff --git a/compileopts/config.go b/compileopts/config.go index 9fa482952f..e4515eacce 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -122,7 +122,7 @@ func (c *Config) GC() string { // that can be traced by the garbage collector. func (c *Config) NeedsStackObjects() bool { switch c.GC() { - case "conservative", "custom", "precise": + case "conservative", "custom", "precise", "boehm": for _, tag := range c.BuildTags() { if tag == "tinygo.wasm" { return true @@ -247,6 +247,15 @@ func MuslArchitecture(triple string) string { return CanonicalArchName(triple) } +// Returns true if the libc needs to include malloc, for the libcs where this +// matters. +func (c *Config) LibcNeedsMalloc() bool { + if c.GC() == "boehm" && c.Target.Libc == "wasi-libc" { + return true + } + return false +} + // LibcPath returns the path to the libc directory. The libc path will be a libc // path in the cache directory (which might not yet be built). func (c *Config) LibcPath(name string) string { @@ -265,9 +274,14 @@ func (c *Config) LibcPath(name string) string { archname += "-" + c.Target.Libc } + options := "" + if c.LibcNeedsMalloc() { + options += "+malloc" + } + // No precompiled library found. Determine the path name that will be used // in the build cache. - return filepath.Join(goenv.Get("GOCACHE"), name+"-"+archname) + return filepath.Join(goenv.Get("GOCACHE"), name+options+"-"+archname) } // DefaultBinaryExtension returns the default extension for binaries, such as diff --git a/compileopts/target.go b/compileopts/target.go index baf6c1214a..2f22a2b133 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -267,6 +267,9 @@ func defaultTarget(options *Options) (*TargetSpec, error) { DefaultStackSize: 1024 * 64, // 64kB GDB: []string{"gdb"}, PortReset: "false", + ExtraFiles: []string{ + "src/runtime/gc_boehm.c", + }, } // Configure target based on GOARCH. diff --git a/go.mod b/go.mod index ec52702d53..c877d18bee 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/tinygo-org/tinygo go 1.19 require ( - github.com/aykevl/go-wasm v0.0.2-0.20240825160117-b76c3f9f0982 + github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139 github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 github.com/chromedp/cdproto v0.0.0-20220113222801-0725d94bb6ee github.com/chromedp/chromedp v0.7.6 diff --git a/go.sum b/go.sum index f8cef17c11..e59b071e6b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/aykevl/go-wasm v0.0.2-0.20240825160117-b76c3f9f0982 h1:cD7QfvrJdYmBw2tFP/VyKPT8ZESlcrwSwo7SvH9Y4dc= -github.com/aykevl/go-wasm v0.0.2-0.20240825160117-b76c3f9f0982/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw= +github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139 h1:2O/WuAt8J5id3khcAtVB90czG80m+v0sfkLE07GrCVg= +github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/chromedp/cdproto v0.0.0-20211126220118-81fa0469ad77/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= diff --git a/lib/bdwgc b/lib/bdwgc index d1ff06cc50..b435e76287 160000 --- a/lib/bdwgc +++ b/lib/bdwgc @@ -1 +1 @@ -Subproject commit d1ff06cc503a74dca0150d5e988f2c93158b0cdf +Subproject commit b435e76287dbf275d922a0a3c432efc913c8460e diff --git a/main_test.go b/main_test.go index f193f46799..475264fc4a 100644 --- a/main_test.go +++ b/main_test.go @@ -194,10 +194,26 @@ func TestBuild(t *testing.T) { t.Run("WebAssembly", func(t *testing.T) { t.Parallel() runPlatTests(optionsFromTarget("wasm", sema), tests, t) + + // Test with -gc=boehm. + t.Run("gc.go-boehm", func(t *testing.T) { + t.Parallel() + optionsBoehm := optionsFromTarget("wasm", sema) + optionsBoehm.GC = "boehm" + runTest("gc.go", optionsBoehm, t, nil, nil) + }) }) - t.Run("WASI", func(t *testing.T) { + t.Run("WASIp1", func(t *testing.T) { t.Parallel() runPlatTests(optionsFromTarget("wasip1", sema), tests, t) + + // Test with -gc=boehm. + t.Run("gc.go-boehm", func(t *testing.T) { + t.Parallel() + optionsBoehm := optionsFromTarget("wasip1", sema) + optionsBoehm.GC = "boehm" + runTest("gc.go", optionsBoehm, t, nil, nil) + }) }) t.Run("WASIp2", func(t *testing.T) { t.Parallel() diff --git a/src/runtime/arch_tinygowasm_malloc.go b/src/runtime/arch_tinygowasm_malloc.go index 239f7c73eb..8726328025 100644 --- a/src/runtime/arch_tinygowasm_malloc.go +++ b/src/runtime/arch_tinygowasm_malloc.go @@ -1,4 +1,4 @@ -//go:build tinygo.wasm && !(custommalloc || wasm_unknown) +//go:build tinygo.wasm && !(custommalloc || wasm_unknown || gc.boehm) package runtime diff --git a/src/runtime/gc_boehm.c b/src/runtime/gc_boehm.c new file mode 100644 index 0000000000..846f31ceba --- /dev/null +++ b/src/runtime/gc_boehm.c @@ -0,0 +1,17 @@ +//go:build none + +// This file is included in the build on systems that support the Boehm GC, +// despite the //go:build line above. + +typedef void (* GC_push_other_roots_proc)(void); +void GC_set_push_other_roots(GC_push_other_roots_proc); + +void tinygo_runtime_bdwgc_callback(void); + +static void callback(void) { + tinygo_runtime_bdwgc_callback(); +} + +void tinygo_runtime_bdwgc_init(void) { + GC_set_push_other_roots(callback); +} diff --git a/src/runtime/gc_boehm.go b/src/runtime/gc_boehm.go index 0955f3c224..c4aff86d6b 100644 --- a/src/runtime/gc_boehm.go +++ b/src/runtime/gc_boehm.go @@ -4,7 +4,6 @@ package runtime import ( "internal/gclayout" - "internal/reflectlite" "internal/task" "unsafe" ) @@ -19,11 +18,15 @@ var gcLock task.PMutex func initHeap() { libgc_init() - libgc_set_push_other_roots(gcCallbackPtr) + // Call GC_set_push_other_roots(gcCallback) in C because of function + // signature differences that do matter in WebAssembly. + gcInit() } -var gcCallbackPtr = reflectlite.ValueOf(gcCallback).UnsafePointer() +//export tinygo_runtime_bdwgc_init +func gcInit() +//export tinygo_runtime_bdwgc_callback func gcCallback() { // Mark the system stack and (if we're on a goroutine stack) also the // current goroutine stack. diff --git a/src/runtime/gc_stack_portable.go b/src/runtime/gc_stack_portable.go index d35e16e30c..7daab606f0 100644 --- a/src/runtime/gc_stack_portable.go +++ b/src/runtime/gc_stack_portable.go @@ -1,4 +1,4 @@ -//go:build (gc.conservative || gc.custom || gc.precise) && tinygo.wasm +//go:build (gc.conservative || gc.custom || gc.precise || gc.boehm) && tinygo.wasm package runtime diff --git a/targets/wasip1.json b/targets/wasip1.json index 25ac7c3a6c..8abc65e1e3 100644 --- a/targets/wasip1.json +++ b/targets/wasip1.json @@ -23,7 +23,8 @@ "--no-demangle" ], "extra-files": [ - "src/runtime/asm_tinygowasm.S" + "src/runtime/asm_tinygowasm.S", + "src/runtime/gc_boehm.c" ], "emulator": "wasmtime run --dir={tmpDir}::/tmp {}" } diff --git a/targets/wasm.json b/targets/wasm.json index 1333647e98..a8641636ea 100644 --- a/targets/wasm.json +++ b/targets/wasm.json @@ -24,7 +24,8 @@ "--no-demangle" ], "extra-files": [ - "src/runtime/asm_tinygowasm.S" + "src/runtime/asm_tinygowasm.S", + "src/runtime/gc_boehm.c" ], "emulator": "node {root}/targets/wasm_exec.js {}" } diff --git a/tests/runtime_wasi/malloc_test.go b/tests/runtime_wasi/malloc_test.go index 465e662a45..fed461cb19 100644 --- a/tests/runtime_wasi/malloc_test.go +++ b/tests/runtime_wasi/malloc_test.go @@ -124,32 +124,3 @@ func TestMallocFree(t *testing.T) { }) } } - -func TestMallocEmpty(t *testing.T) { - ptr := libc_malloc(0) - if ptr != nil { - t.Errorf("expected nil pointer, got %p", ptr) - } -} - -func TestCallocEmpty(t *testing.T) { - ptr := libc_calloc(0, 1) - if ptr != nil { - t.Errorf("expected nil pointer, got %p", ptr) - } - ptr = libc_calloc(1, 0) - if ptr != nil { - t.Errorf("expected nil pointer, got %p", ptr) - } -} - -func TestReallocEmpty(t *testing.T) { - ptr := libc_malloc(1) - if ptr == nil { - t.Error("expected pointer but was nil") - } - ptr = libc_realloc(ptr, 0) - if ptr != nil { - t.Errorf("expected nil pointer, got %p", ptr) - } -}