Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion builder/bdwgc.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ var BoehmGC = Library{
"-DNO_GETENV", // smaller binary, more predictable configuration
"-DNO_CLOCK", // don't use system clock
"-DNO_DEBUGGING", // reduce code size
"-DGC_NO_FINALIZATION", // finalization is not used at the moment
"-DGC_ATOMIC_UNCOLLECTABLE", // pointer-free storage retained until GC_free

// Special flag to work around the lack of __data_start in ld.lld.
Expand Down Expand Up @@ -69,6 +68,7 @@ var BoehmGC = Library{
"blacklst.c",
"dbg_mlc.c",
"dyn_load.c",
"finalize.c", // typed arrays use disappearing links
"headers.c",
"mach_dep.c",
"malloc.c",
Expand All @@ -78,6 +78,7 @@ var BoehmGC = Library{
"new_hblk.c",
"os_dep.c",
"reclaim.c",
"typd_mlc.c", // typed allocation support
}
if strings.Split(target, "-")[2] == "windows" {
// Due to how the linker on Windows works (that doesn't allow
Expand Down
13 changes: 11 additions & 2 deletions compileopts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
// library path in advance in several places).
var libVersions = map[string]int{
"musl": 3,
"bdwgc": 3,
"bdwgc": 4,
"picolibc": 2,
"wasmbuiltins": 1,
}
Expand Down Expand Up @@ -383,6 +383,11 @@ func (c *Config) CFlags(libclang bool) []string {
)
}
cflags = append(cflags, c.LibcCFlags()...)
if c.GC() == "boehm" {
cflags = append(cflags,
"-I"+filepath.Join(goenv.Get("TINYGOROOT"), "lib", "bdwgc", "include"),
)
}
// Always emit debug information. It is optionally stripped at link time.
cflags = append(cflags, "-gdwarf-4")
// Use the same optimization level as TinyGo.
Expand Down Expand Up @@ -521,7 +526,11 @@ func (c *Config) LinkerFlavor() string {
// ExtraFiles returns the list of extra files to be built and linked with the
// executable. This can include extra C and assembly files.
func (c *Config) ExtraFiles() []string {
return c.Target.ExtraFiles
files := c.Target.ExtraFiles
if c.GC() == "boehm" && !slices.Contains(files, "src/runtime/gc_boehm.c") {
files = append(slices.Clone(files), "src/runtime/gc_boehm.c")
}
return files
}

// DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only
Expand Down
35 changes: 35 additions & 0 deletions compileopts/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package compileopts

import (
"slices"
"testing"
)

func TestExtraFilesBoehm(t *testing.T) {
target := &TargetSpec{
GC: "precise",
ExtraFiles: []string{
"src/runtime/asm_tinygowasm.S",
},
}
config := &Config{
Options: &Options{},
Target: target,
}

got := config.ExtraFiles()
want := []string{"src/runtime/asm_tinygowasm.S"}
if !slices.Equal(got, want) {
t.Fatalf("unexpected precise GC files: got %v, want %v", got, want)
}

config.Options.GC = "boehm"
got = config.ExtraFiles()
want = []string{
"src/runtime/asm_tinygowasm.S",
"src/runtime/gc_boehm.c",
}
if !slices.Equal(got, want) {
t.Fatalf("unexpected Boehm GC files: got %v, want %v", got, want)
}
}
6 changes: 0 additions & 6 deletions compileopts/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,12 +502,6 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
return nil, fmt.Errorf("unknown GOOS=%s", options.GOOS)
}

if spec.GC == "boehm" {
// Add this file only when needed. This fixes a build failure on
// Windows.
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_boehm.c")
}

// Target triples (which actually have four components, but are called
// triples for historical reasons) have the form:
// arch-vendor-os-environment
Expand Down
20 changes: 18 additions & 2 deletions compiler/llvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,30 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
// Create the pointer bitmap.
objectSizeBytes := c.targetData.TypeAllocSize(t)
pointerAlignment := uint64(c.targetData.PrefTypeAlignment(c.dataPtrType))
pointerSize := c.targetData.TypeAllocSize(c.dataPtrType)
pointerBits := pointerSize * 8
bitmapLen := objectSizeBytes / pointerAlignment
bitmapBytes := (bitmapLen + 7) / 8
bitmap := make([]byte, bitmapBytes, max(bitmapBytes, 8))
c.buildPointerBitmap(bitmap, pointerAlignment, pos, t, 0)

// Use the one-pointer layout when every pointer-sized word is a pointer.
// Repeating this layout is equivalent for objects of any size.
pointerUnits := pointerSize / pointerAlignment
allPointers := bitmapLen != 0 && bitmapLen%pointerUnits == 0
for i := uint64(0); allPointers && i < bitmapLen; i++ {
isPointer := bitmap[i/8]&(1<<(i%8)) != 0
if isPointer != (i%pointerUnits == 0) {
allPointers = false
}
}
if allPointers {
layout := (pointerBits + pointerUnits) << 1
layout |= 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
}

// Try to encode the layout inline.
pointerSize := c.targetData.TypeAllocSize(c.dataPtrType)
pointerBits := pointerSize * 8
if bitmapLen < pointerBits {
rawMask := binary.LittleEndian.Uint64(bitmap[0:8])
layout := rawMask*pointerBits + bitmapLen
Expand Down
2 changes: 1 addition & 1 deletion compiler/testdata/gc.ll
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ declare noalias nonnull ptr @runtime.alloc_zero(i32, ptr, ptr) #2
define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%new = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 197 to ptr), ptr undef) #3
%new = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3
ret ptr %new
}
Expand Down
2 changes: 2 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ func TestBuild(t *testing.T) {
optionsBoehm := optionsFromTarget("wasm", sema)
optionsBoehm.GC = "boehm"
runTest("gc.go", optionsBoehm, t, nil, nil)
runTest("gc-boehm.go", optionsBoehm, t, nil, nil)
})
})
t.Run("WASIp1", func(t *testing.T) {
Expand All @@ -274,6 +275,7 @@ func TestBuild(t *testing.T) {
optionsBoehm := optionsFromTarget("wasip1", sema)
optionsBoehm.GC = "boehm"
runTest("gc.go", optionsBoehm, t, nil, nil)
runTest("gc-boehm.go", optionsBoehm, t, nil, nil)
})
})
t.Run("WASIp2", func(t *testing.T) {
Expand Down
135 changes: 128 additions & 7 deletions src/runtime/gc_boehm.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,72 @@
// despite the //go:build line above.

#include <stdint.h>
#include <string.h>

typedef void (* GC_push_other_roots_proc)(void);
void GC_set_push_other_roots(GC_push_other_roots_proc);

typedef void(* GC_warn_proc)(const char *msg, uintptr_t arg);
void GC_set_warn_proc(GC_warn_proc p);
#include <gc/gc_mark.h>
#include <gc/gc_typed.h>

void tinygo_runtime_bdwgc_callback(void);

static void callback(void) {
struct descriptor_cache_entry {
uintptr_t layout;
GC_descr descriptor;
struct descriptor_cache_entry *next;
};

static struct descriptor_cache_entry **descriptor_cache;
static size_t descriptor_cache_capacity;
static size_t descriptor_cache_count;

static size_t descriptor_cache_index(uintptr_t layout, size_t capacity) {
#if UINTPTR_MAX > UINT32_MAX
layout ^= layout >> 33;
#endif
layout ^= layout >> 16;
layout *= 0x45d9f3b;
layout ^= layout >> 16;
return layout & (capacity - 1);
}

static int grow_descriptor_cache(void) {
size_t new_capacity =
descriptor_cache_capacity == 0 ? 64 : descriptor_cache_capacity * 2;
struct descriptor_cache_entry **new_cache;
size_t i;

new_cache =
GC_malloc_atomic_uncollectable(new_capacity * sizeof(*new_cache));
if (new_cache == NULL) {
return 0;
}
memset(new_cache, 0, new_capacity * sizeof(*new_cache));

for (i = 0; i < descriptor_cache_capacity; i++) {
struct descriptor_cache_entry *entry = descriptor_cache[i];
while (entry != NULL) {
struct descriptor_cache_entry *next = entry->next;
size_t index =
descriptor_cache_index(entry->layout, new_capacity);

entry->next = new_cache[index];
new_cache[index] = entry;
entry = next;
}
}

if (descriptor_cache != NULL) {
GC_free(descriptor_cache);
}
descriptor_cache = new_cache;
descriptor_cache_capacity = new_capacity;
return 1;
}

static void GC_CALLBACK callback(void) {
tinygo_runtime_bdwgc_callback();
}

static void warn_proc(const char *msg, uintptr_t arg) {
static void GC_CALLBACK warn_proc(const char *msg, GC_word arg) {
}

void tinygo_runtime_bdwgc_init(void) {
Expand All @@ -35,3 +87,72 @@ void tinygo_runtime_bdwgc_init(void) {
GC_set_warn_proc(warn_proc);
#endif
}

GC_descr tinygo_runtime_bdwgc_make_descriptor(uintptr_t layout) {
struct descriptor_cache_entry *entry;
GC_word inline_bitmap;
GC_word *bitmap;
size_t index;
size_t bit_count;
size_t word_count;
GC_descr descriptor;

if (descriptor_cache_count >= descriptor_cache_capacity * 2 &&
!grow_descriptor_cache()) {
return 0;
}

index = descriptor_cache_index(layout, descriptor_cache_capacity);
for (entry = descriptor_cache[index]; entry != NULL; entry = entry->next) {
if (entry->layout == layout) {
return entry->descriptor;
}
}

entry = GC_malloc_atomic_uncollectable(sizeof(*entry));
if (entry == NULL) {
return 0;
}

if (layout & 1) {
const size_t size_bits = 4 + sizeof(uintptr_t) / 4;
const uintptr_t size_mask = ((uintptr_t)1 << size_bits) - 1;

bit_count = (layout >> 1) & size_mask;
inline_bitmap = layout >> (size_bits + 1);
bitmap = &inline_bitmap;
} else {
const uint8_t *bytes = (const uint8_t *)(layout + sizeof(uintptr_t));
size_t i;

bit_count = *(const uintptr_t *)layout;
word_count = (bit_count + GC_WORDSZ - 1) / GC_WORDSZ;
bitmap = GC_malloc_atomic_uncollectable(word_count * sizeof(GC_word));
if (bitmap == NULL) {
GC_free(entry);
return 0;
}
memset(bitmap, 0, word_count * sizeof(GC_word));
for (i = 0; i < bit_count; i++) {
if ((bytes[i / 8] >> (i % 8)) & 1) {
GC_set_bit(bitmap, i);
}
}
}

descriptor = GC_make_descriptor(bitmap, bit_count);
if (!(layout & 1)) {
GC_free(bitmap);
}
if (descriptor == 0) {
GC_free(entry);
return 0;
}

entry->layout = layout;
entry->descriptor = descriptor;
entry->next = descriptor_cache[index];
descriptor_cache[index] = entry;
descriptor_cache_count++;
return descriptor;
}
Loading
Loading