Skip to content
Draft
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
6 changes: 4 additions & 2 deletions cl/_testgo/goroutine/in.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ package main
func main() {
// CHECK: call ptr @"{{.*}}AllocZ"(i64 1)
// CHECK: store i1 false, ptr %0, align 1
// CHECK: call ptr @"{{.*}}AllocU"(i64 16)
// CHECK: call ptr @"{{.*}}AllocRoot"(i64 16)
// CHECK: call i32 @"{{.*}}CreateThread"(ptr %3, ptr null, ptr @"{{.*}}goroutine._llgo_routine$1", ptr %1)
done := false
go println("hello")
go func(s string) {
// CHECK: call ptr @"{{.*}}AllocU"(i64 8)
// CHECK: { ptr @"{{.*}}goroutine.main$1", ptr undef }
// CHECK: call ptr @"{{.*}}AllocU"(i64 32)
// CHECK: call ptr @"{{.*}}AllocRoot"(i64 32)
// CHECK: call i32 @"{{.*}}CreateThread"(ptr %11, ptr null, ptr @"{{.*}}goroutine._llgo_routine$2", ptr %8)
// CHECK: call void @"{{.*}}PrintString"(%"{{.*}}String" { ptr @2, i64 1 })
// CHECK: ret void
Expand All @@ -38,6 +38,7 @@ func main() {
// CHECK-NEXT: %2 = extractvalue { %"{{.*}}String" } %1, 0
// CHECK-NEXT: call void @"{{.*}}PrintString"(%"{{.*}}String" %2)
// CHECK-NEXT: call void @"{{.*}}PrintByte"(i8 10)
// CHECK-NEXT: call void @"{{.*}}FreeRoot"(ptr %0)
// CHECK-NEXT: ret ptr null

// CHECK-LABEL: define ptr @"{{.*}}goroutine._llgo_routine$2"(ptr %0){{.*}} {
Expand All @@ -48,4 +49,5 @@ func main() {
// CHECK-NEXT: %4 = extractvalue { ptr, ptr } %2, 1
// CHECK-NEXT: %5 = extractvalue { ptr, ptr } %2, 0
// CHECK-NEXT: call void %5(ptr %4, %"{{.*}}String" %3)
// CHECK-NEXT: call void @"{{.*}}FreeRoot"(ptr %0)
// CHECK-NEXT: ret ptr null
2 changes: 1 addition & 1 deletion cl/_testgo/selects/in.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ package main
// CHECK-NEXT: %10 = getelementptr inbounds { ptr, ptr, ptr }, ptr %7, i32 0, i32 2
// CHECK-NEXT: store ptr %4, ptr %10, align 8
// CHECK-NEXT: %11 = insertvalue { ptr, ptr } { ptr @"{{.*}}/cl/_testgo/selects.main$1", ptr undef }, ptr %7, 1
// CHECK-NEXT: %12 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16)
// CHECK-NEXT: %12 = call ptr @"{{.*}}/runtime/internal/runtime.AllocRoot"(i64 16)
// CHECK-NEXT: %13 = getelementptr inbounds { { ptr, ptr } }, ptr %12, i32 0, i32 0
// CHECK-NEXT: store { ptr, ptr } %11, ptr %13, align 8
// CHECK-NEXT: %14 = alloca i8, i64 8, align 1
Expand Down
5 changes: 5 additions & 0 deletions runtime/internal/clite/bdwgc/bdwgc.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ func Init()
//go:linkname Malloc C.GC_malloc
func Malloc(size uintptr) c.Pointer

// MallocUncollectable allocates scanned memory that is not reclaimed until Free.
//
//go:linkname MallocUncollectable C.GC_malloc_uncollectable
func MallocUncollectable(size uintptr) c.Pointer

//go:linkname Realloc C.GC_realloc
func Realloc(ptr c.Pointer, size uintptr) c.Pointer

Expand Down
30 changes: 20 additions & 10 deletions runtime/internal/lib/reflect/makefunc.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,20 @@ import (
"github.com/goplus/llgo/runtime/abi"
c "github.com/goplus/llgo/runtime/internal/clite"
"github.com/goplus/llgo/runtime/internal/ffi"
"github.com/goplus/llgo/runtime/internal/runtime"
)

type funcData struct {
ftyp *funcType
fn func(args []Value) (results []Value)
nin int
closure *ffi.Closure
sig *ffi.Signature
ftyp *funcType
fn func(args []Value) (results []Value)
nin int
}

type funcValue struct {
fn unsafe.Pointer
env unsafe.Pointer
}

func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value {
Expand All @@ -48,6 +56,10 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value {
panic(err)
}
closure := ffi.NewClosure()
// libffi keeps sig and userdata after Bind returns. Make them reachable
// through the returned function's closure env for later calls.
fd := (*funcData)(runtime.AllocU(unsafe.Sizeof(funcData{})))
*fd = funcData{closure: closure, sig: sig, ftyp: ftyp, fn: fn, nin: len(ftyp.In)}

switch len(ftyp.Out) {
case 0:
Expand All @@ -58,7 +70,7 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value {
ins[i] = ffiToValue(ffi.Index(args, uintptr(i+1)), fd.ftyp.In[i])
}
fd.fn(ins)
}, unsafe.Pointer(&funcData{ftyp: ftyp, fn: fn, nin: len(ftyp.In)}))
}, unsafe.Pointer(fd))
case 1:
err = closure.Bind(sig, func(cif *ffi.Signature, ret unsafe.Pointer, args *unsafe.Pointer, userdata unsafe.Pointer) {
fd := (*funcData)(userdata)
Expand All @@ -72,7 +84,7 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value {
} else {
*(*unsafe.Pointer)(ret) = unsafe.Pointer(out[0].ptr)
}
}, unsafe.Pointer(&funcData{ftyp: ftyp, fn: fn, nin: len(ftyp.In)}))
}, unsafe.Pointer(fd))
default:
err = closure.Bind(sig, func(cif *ffi.Signature, ret unsafe.Pointer, args *unsafe.Pointer, userdata unsafe.Pointer) {
fd := (*funcData)(userdata)
Expand All @@ -90,16 +102,14 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value {
}
offset += fd.ftyp.Out[i].Size_
}
}, unsafe.Pointer(&funcData{ftyp: ftyp, fn: fn, nin: len(ftyp.In)}))
}, unsafe.Pointer(fd))
}
if err != nil {
panic("libffi error: " + err.Error())
}
styp := closureOf(ftyp)
fv := &struct {
fn unsafe.Pointer
env unsafe.Pointer
}{closure.Fn, unsafe.Pointer(&fn)}
fv := (*funcValue)(runtime.AllocU(unsafe.Sizeof(funcValue{})))
*fv = funcValue{closure.Fn, unsafe.Pointer(fd)}
return Value{styp, unsafe.Pointer(fv), flagIndir | flag(Func)}
}

Expand Down
8 changes: 8 additions & 0 deletions runtime/internal/runtime/z_gc.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ func AllocZ(size uintptr) unsafe.Pointer {
return c.Memset(ret, 0, size)
}

func AllocRoot(size uintptr) unsafe.Pointer {
return bdwgc.MallocUncollectable(size)
}

func FreeRoot(ptr unsafe.Pointer) {
bdwgc.Free(ptr)
}

type entry struct {
fn func() // cleanup func
prev unsafe.Pointer // prev cleanup func ptr
Expand Down
7 changes: 7 additions & 0 deletions runtime/internal/runtime/z_gc_baremetal.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ func AllocZ(size uintptr) unsafe.Pointer {
return tinygogc.Alloc(size)
}

func AllocRoot(size uintptr) unsafe.Pointer {
return tinygogc.Alloc(size)
}

func FreeRoot(ptr unsafe.Pointer) {
}

// AddCleanupPtr is not implemented in baremetal builds because tinygogc
// does not support finalizers. Cleanup functions will never be called.
//
Expand Down
8 changes: 8 additions & 0 deletions runtime/internal/runtime/z_nogc.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ func AllocZ(size uintptr) unsafe.Pointer {
return c.Memset(ret, 0, size)
}

func AllocRoot(size uintptr) unsafe.Pointer {
return c.Malloc(size)
}

func FreeRoot(ptr unsafe.Pointer) {
c.Free(ptr)
}

// AddCleanupPtr is not implemented when GC is disabled.
// Cleanup functions will never be called.
func AddCleanupPtr(ptr unsafe.Pointer, cleanup func()) (cancel func()) {
Expand Down
8 changes: 6 additions & 2 deletions ssa/goroutine.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,11 @@ func (b Builder) Go(fn Expr, buildCall func(Builder, Expr, ...Expr) Expr, args .
t := prog.Struct(typs...)
voidPtr := prog.VoidPtr()
// Goroutine startup data may carry Go pointers, including closure ctx.
// Keep it in GC-managed memory so it remains visible across the thread start.
data := Expr{b.aggregateAllocU(t, flds...), voidPtr}
// Keep the startup record in scanned, uncollectable GC memory until the
// new routine has finished its entry call.
dataPtr := b.Call(pkg.rtFunc("AllocRoot"), prog.IntVal(prog.SizeOf(t), prog.Uintptr())).impl
aggregateInit(b.impl, dataPtr, t.ll, flds...)
data := Expr{dataPtr, voidPtr}
size := prog.SizeOf(voidPtr)
pthd := b.Alloca(prog.IntVal(uint64(size), prog.Uintptr()))
b.pthreadCreate(pthd, prog.Nil(voidPtr), pkg.routine(t, fn, buildCall, len(args)), data)
Expand All @@ -102,6 +105,7 @@ func (p Package) routine(t Type, fn Expr, buildCall func(Builder, Expr, ...Expr)
args[i] = b.getField(data, i+offset)
}
buildCall(b, fn, args...)
b.Call(p.rtFunc("FreeRoot"), param)
b.Return(prog.Nil(prog.VoidPtr()))
return routine.Expr
}
Expand Down
14 changes: 10 additions & 4 deletions ssa/goroutine_patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,15 @@ func TestGoClosureStartupUsesGCManagedMemory(t *testing.T) {
if strings.Contains(ir, "@free") {
t.Fatalf("goroutine startup data should not use free:\n%s", ir)
}
// The closure context and the goroutine startup record both must remain
// visible to the runtime GC until the new goroutine consumes them.
if got := strings.Count(ir, `"github.com/goplus/llgo/runtime/internal/runtime.AllocU"`); got < 2 {
t.Fatalf("expected closure ctx and goroutine startup data to use AllocU, got %d:\n%s", got, ir)
if !strings.Contains(ir, `"github.com/goplus/llgo/runtime/internal/runtime.AllocRoot"`) {
t.Fatalf("goroutine startup data should use scanned uncollectable memory:\n%s", ir)
}
if !strings.Contains(ir, `"github.com/goplus/llgo/runtime/internal/runtime.FreeRoot"`) {
t.Fatalf("goroutine startup data should be freed after the entry call returns:\n%s", ir)
}
// The closure context must remain visible to the runtime GC until the
// uncollectable startup record is initialized.
if got := strings.Count(ir, `"github.com/goplus/llgo/runtime/internal/runtime.AllocU"`); got < 1 {
t.Fatalf("expected closure ctx to use AllocU, got %d:\n%s", got, ir)
}
}
56 changes: 56 additions & 0 deletions test/go/reflect_makefunc_goroutine_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package gotest

import (
"reflect"
"runtime"
"testing"
)

func TestReflectMakeFuncGoroutineStartup(t *testing.T) {
oldProcs := runtime.GOMAXPROCS(1)
defer runtime.GOMAXPROCS(oldProcs)

stopGC := make(chan struct{})
gcDone := make(chan struct{})
go func() {
defer close(gcDone)
for {
select {
case <-stopGC:
return
default:
runtime.GC()
}
}
}()
defer func() {
close(stopGC)
<-gcDone
}()

const n = 100
done := make(chan struct{}, n*2)
for i := 0; i < n; i++ {
f := reflect.MakeFunc(reflect.TypeOf((func(*int))(nil)), func(args []reflect.Value) []reflect.Value {
if len(args) != 1 || !args[0].IsNil() {
panic("bad reflect MakeFunc pointer argument")
}
done <- struct{}{}
return nil
}).Interface().(func(*int))
go f(nil)

g := reflect.MakeFunc(reflect.TypeOf((func())(nil)), func(args []reflect.Value) []reflect.Value {
if len(args) != 0 {
panic("bad reflect MakeFunc zero-argument call")
}
done <- struct{}{}
return nil
}).Interface().(func())
go g()
}

for i := 0; i < n*2; i++ {
<-done
}
}
30 changes: 0 additions & 30 deletions test/goroot/xfail.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2626,16 +2626,6 @@ xfails:
directive: run
case: fixedbugs/issue24491b.go
reason: go1.25 goroot run failure on linux/amd64
- version: go1.25
platform: darwin/arm64
directive: run
case: fixedbugs/issue25897a.go
reason: go1.25 goroot run failure on darwin/arm64
- version: go1.25
platform: linux/amd64
directive: run
case: fixedbugs/issue25897a.go
reason: go1.25 goroot run failure on linux/amd64
- version: go1.25
platform: linux/amd64
directive: run
Expand Down Expand Up @@ -3072,16 +3062,6 @@ xfails:
directive: run
case: fixedbugs/issue23837.go
reason: go1.24 goroot run failure on linux/amd64
- version: go1.24
platform: darwin/arm64
directive: run
case: fixedbugs/issue25897a.go
reason: go1.24 goroot run failure on darwin/arm64
- version: go1.24
platform: linux/amd64
directive: run
case: fixedbugs/issue25897a.go
reason: go1.24 goroot run failure on linux/amd64
- version: go1.24
platform: linux/amd64
directive: run
Expand Down Expand Up @@ -3322,16 +3302,6 @@ xfails:
directive: run
case: fixedbugs/issue23837.go
reason: go1.26 goroot run failure on linux/amd64
- version: go1.26
platform: darwin/arm64
directive: run
case: fixedbugs/issue25897a.go
reason: go1.26 goroot run failure on darwin/arm64
- version: go1.26
platform: linux/amd64
directive: run
case: fixedbugs/issue25897a.go
reason: go1.26 goroot run failure on linux/amd64
- version: go1.26
platform: linux/amd64
directive: run
Expand Down
Loading