Skip to content
Open
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
30 changes: 15 additions & 15 deletions compileopts/finalizer_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ func TestFinalizerRunnerSchedulerCoverage(t *testing.T) {
exprs[i] = readBuildConstraint(t, filepath.Join("..", "src", "runtime", name))
}

for _, sched := range validSchedulerOptions {
// The finalizer table exists under block GCs.
// gc.conservative satisfies the GC condition in every constraint.
tags := map[string]bool{
"gc.conservative": true,
"scheduler." + sched: true,
}
var matched []string
for i, expr := range exprs {
if expr.Eval(func(tag string) bool { return tags[tag] }) {
matched = append(matched, files[i])
for _, gc := range []string{"conservative", "precise", "boehm"} {
for _, sched := range validSchedulerOptions {
tags := map[string]bool{
"gc." + gc: true,
"scheduler." + sched: true,
}
var matched []string
for i, expr := range exprs {
if expr.Eval(func(tag string) bool { return tags[tag] }) {
matched = append(matched, files[i])
}
}
if len(matched) != 1 {
t.Errorf("gc.%s scheduler.%s: spawnFinalizerRunner defined in %d files %v, want exactly 1",
gc, sched, len(matched), matched)
}
}
if len(matched) != 1 {
t.Errorf("scheduler.%s: spawnFinalizerRunner defined in %d files %v, want exactly 1",
sched, len(matched), matched)
}
}
}
Expand Down
123 changes: 115 additions & 8 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,88 @@ var supportedLinuxArches = map[string]string{

var sema = make(chan struct{}, runtime.NumCPU())

func TestBoehmFinalizerInterior(t *testing.T) {
t.Parallel()
for _, test := range []struct {
target, scheduler string
}{
{"wasm", "asyncify"},
{"wasm", "none"},
{"", "tasks"},
{"", "threads"},
{"", "none"},
} {
if test.target == "" && runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
continue
}
t.Run(test.target+"/"+test.scheduler, func(t *testing.T) {
t.Parallel()
options := optionsFromTarget(test.target, sema)
options.GC = "boehm"
options.Scheduler = test.scheduler
options.Tags = append(options.Tags, "runtime_asserts")
options.GlobalValues = map[string]map[string]string{
"main": {"finalizerTestMode": "interior"},
}
runTest("finalizer.go", options, t, nil, nil)
})
}
}

func TestBoehmFinalizerInteriorInvalid(t *testing.T) {
t.Parallel()
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
t.Skip("host finalizers need Linux or Darwin")
}
options := optionsFromTarget("", sema)
options.GC = "boehm"
config, err := builder.NewConfig(&options)
if err != nil {
t.Fatal(err)
}
for _, name := range []string{"interior-pointer", "interior-large"} {
t.Run(name, func(t *testing.T) {
output := &bytes.Buffer{}
_, err := buildAndRun("testdata/finalizer.go", config, output, []string{name}, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
cmd.Stdout = nil
cmd.Stderr = nil
data, err := cmd.CombinedOutput()
output.Write(data)
return err
})
if err == nil || !strings.Contains(output.String(), "pointer not at beginning of allocated block") {
t.Fatalf("interior registration result: %v\n%s", err, output.String())
}
})
}
}

func TestFinalizerRunnerRegistration(t *testing.T) {
t.Parallel()
for _, test := range []struct {
target, gc, scheduler string
}{
{"wasm", "boehm", "asyncify"},
{"wasm", "conservative", "asyncify"},
{"", "boehm", "tasks"},
{"", "boehm", "threads"},
} {
if test.target == "" && runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
continue
}
t.Run(test.target+"/"+test.gc+"/"+test.scheduler, func(t *testing.T) {
t.Parallel()
options := optionsFromTarget(test.target, sema)
options.GC = test.gc
options.Scheduler = test.scheduler
options.GlobalValues = map[string]map[string]string{
"main": {"finalizerIdleMode": "registration"},
}
runTest("finalizeridle.go", options, t, nil, nil)
})
}
}

func TestTrimPath(t *testing.T) {
t.Setenv("CGO_CFLAGS", "-iquoteinclude -includestdint.h -imacros relative.h")
root := t.TempDir()
Expand Down Expand Up @@ -418,6 +500,39 @@ func TestBuild(t *testing.T) {
}
})

t.Run("boehm-finalizers", func(t *testing.T) {
t.Parallel()
options := optionsFromTarget("wasm", sema)
options.GC = "boehm"
t.Run("finalizerlarge.go-graph", func(t *testing.T) {
t.Parallel()
testOptions := compileopts.Options(options)
testOptions.GlobalValues = map[string]map[string]string{
"main": {"finalizerLargeMode": "graph"},
}
runTest("finalizerlarge.go", testOptions, t, nil, nil)
})
for _, name := range []string{
"finalizer.go",
"finalizerbits.go",
"finalizeridle.go",
"finalizerlarge.go",
"finalizerinvariants.go",
} {
name := name
t.Run(name, func(t *testing.T) {
t.Parallel()
testOptions := compileopts.Options(options)
if name == "finalizerlarge.go" {
testOptions.GlobalValues = map[string]map[string]string{
"main": {"finalizerLargeMode": "runner-payload"},
}
}
runTest(name, testOptions, t, nil, nil)
})
}
})

// Test a few build options.
t.Run("build-options", func(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -777,14 +892,6 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
continue
}
}
if options.Target == "" && options.GC == "" {
switch name {
case "finalizerinvariants.go":
// Skip the default host GC because it does not implement finalizers.
// Explicit conservative GC variants cover this test.
continue
}
}
if options.Target == "simavr" {
switch name {
case "finalizerinvariants.go":
Expand Down
7 changes: 7 additions & 0 deletions src/internal/reflectlite/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ type basicType struct {
ptrTo *RawType
}

// Go allows interior targets whose types could fit its tiny allocator.
// See runtime.SetFinalizer in https://go.dev/src/runtime/mfinal.go.
func FinalizerAllowsInterior(t *RawType) bool {
elem := t.elem()
return elem.Size() < 16 && elem.gcLayout() == gclayout.NoPtrs.AsPtr()
}

// All types that have an element type: named, chan, slice, array, map (but not
// pointer because it doesn't have ptrTo).
type elemType struct {
Expand Down
15 changes: 15 additions & 0 deletions src/runtime/gc_boehm.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@
#include <gc/gc_typed.h>

void tinygo_runtime_bdwgc_callback(void);
void tinygo_runtime_bdwgc_finalizer(void *, void *);

static void GC_CALLBACK finalizer_callback(void *obj, void *data) {
tinygo_runtime_bdwgc_finalizer(obj, data);
}

uintptr_t tinygo_runtime_bdwgc_register_finalizer(uintptr_t obj, uintptr_t data) {
void *old_data = (void *)(uintptr_t)-1;
GC_register_finalizer_no_order((void *)obj, data ? finalizer_callback : NULL,
(void *)data, NULL, &old_data);
return (uintptr_t)old_data;
}

struct descriptor_cache_entry {
uintptr_t layout;
Expand Down Expand Up @@ -74,6 +86,9 @@ static void GC_CALLBACK warn_proc(const char *msg, GC_word arg) {

void tinygo_runtime_bdwgc_init(void) {
GC_set_push_other_roots(callback);
GC_set_finalize_on_demand(1);
// See gc.h GC_set_java_finalization for unordered finalizer reachability.
GC_set_java_finalization(1);
#if defined(__wasm__)
// There are a lot of warnings on WebAssembly in the form:
//
Expand Down
Loading
Loading