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
39 changes: 0 additions & 39 deletions runtime/internal/runtime/defer_tls_baremetal.go

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
//go:build !baremetal

/*
* Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved.
* Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,25 +16,27 @@

package runtime

import "github.com/goplus/llgo/runtime/internal/clite/tls"
import "unsafe"

var deferTLS = tls.Alloc[*Defer](func(head **Defer) {
if head != nil {
*head = nil
}
})
// g holds the runtime state owned by one LLGo goroutine.
type g struct {
defer_ *Defer
panic_ unsafe.Pointer
goexit bool
isMain bool
}

// SetThreadDefer associates the current thread with the given defer chain.
// SetThreadDefer associates the current goroutine with the given defer chain.
func SetThreadDefer(head *Defer) {
deferTLS.Set(head)
getg().defer_ = head
}

// GetThreadDefer returns the current thread's defer chain head.
// GetThreadDefer returns the current goroutine's defer chain head.
func GetThreadDefer() *Defer {
return deferTLS.Get()
return getg().defer_
}

// ClearThreadDefer resets the current thread's defer chain to nil.
// ClearThreadDefer resets the current goroutine's defer chain to nil.
func ClearThreadDefer() {
deferTLS.Clear()
getg().defer_ = nil
}
25 changes: 25 additions & 0 deletions runtime/internal/runtime/g_global.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//go:build !llgo || baremetal

/*
* Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package runtime

var globalG g

func getg() *g {
return &globalG
}
66 changes: 66 additions & 0 deletions runtime/internal/runtime/g_pthread.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//go:build llgo && !baremetal

/*
* Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package runtime

import (
"unsafe"

c "github.com/goplus/llgo/runtime/internal/clite"
"github.com/goplus/llgo/runtime/internal/clite/pthread"
)

var gKey = newGKey()

func newGKey() pthread.Key {
var key pthread.Key
if ret := key.Create(pthread.KeyDestructor(destroyG)); ret != 0 {
c.Fprintf(c.Stderr, c.Str("runtime: pthread_key_create failed (errno=%d)\n"), ret)
panic("runtime: failed to create getg key")
}
return key
}

func getg() *g {
if ptr := gKey.Get(); ptr != nil {
return (*g)(ptr)
}
ptr := AllocRoot(unsafe.Sizeof(g{}))
if ptr == nil {
panic("runtime: failed to allocate g")
}
c.Memset(ptr, 0, unsafe.Sizeof(g{}))
if ret := gKey.Set(ptr); ret != 0 {
FreeRoot(ptr)
c.Fprintf(c.Stderr, c.Str("runtime: pthread_setspecific failed (errno=%d)\n"), ret)
panic("runtime: failed to install g")
}
return (*g)(ptr)
}

func destroyG(ptr c.Pointer) {
gp := (*g)(ptr)
if gp == nil {
return
}
if gp.panic_ != nil {
c.Free(gp.panic_)
}
*gp = g{}
FreeRoot(ptr)
}
7 changes: 4 additions & 3 deletions runtime/internal/runtime/z_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ var (

// Rethrow rethrows a panic.
func Rethrow(link *Defer) {
if ptr := excepKey.Get(); ptr != nil {
gp := getg()
if ptr := gp.panic_; ptr != nil {
if link == nil {
TracePanic(*(*any)(ptr))
if PanicTraceback == nil || !PanicTraceback(2) {
Expand All @@ -27,7 +28,7 @@ func Rethrow(link *Defer) {
} else {
c.Siglongjmp(link.Addr, 1)
}
} else if ptr := goexitKey.Get(); ptr != nil {
} else if gp.goexit {
// Goexit must run deferred functions before terminating the current
// goroutine. Reuse the longjmp-based defer unwinding:
// 1) If we have a defer frame, longjmp to it so it can execute defers.
Expand All @@ -36,7 +37,7 @@ func Rethrow(link *Defer) {
if link != nil {
c.Siglongjmp(link.Addr, 1)
}
if pthread.Equal(mainThread, pthread.Self()) != 0 {
if gp.isMain {
fatal("no goroutines (main called runtime.Goexit) - deadlock!")
c.Exit(2)
}
Expand Down
26 changes: 10 additions & 16 deletions runtime/internal/runtime/z_rt.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"unsafe"

c "github.com/goplus/llgo/runtime/internal/clite"
"github.com/goplus/llgo/runtime/internal/clite/pthread"
"github.com/goplus/llgo/runtime/internal/clite/setjmp"
)

Expand All @@ -38,9 +37,10 @@ type Defer struct {

// Recover recovers a panic.
func Recover() (ret any) {
ptr := excepKey.Get()
gp := getg()
ptr := gp.panic_
if ptr != nil {
excepKey.Set(nil)
gp.panic_ = nil
ret = *(*any)(ptr)
c.Free(ptr)
if PanicRecovered != nil {
Expand All @@ -58,26 +58,20 @@ func Panic(v any) {
SavePanicCallerFrames()
ptr := c.Malloc(unsafe.Sizeof(v))
*(*any)(ptr) = v
excepKey.Set(ptr)
gp := getg()
gp.panic_ = ptr

Rethrow((*Defer)(c.GoDeferData()))
Rethrow(gp.defer_)
}

var (
excepKey pthread.Key
goexitKey pthread.Key
mainThread pthread.Thread
)

func Goexit() {
goexitKey.Set(unsafe.Pointer(&goexitKey))
Rethrow((*Defer)(c.GoDeferData()))
gp := getg()
gp.goexit = true
Rethrow(gp.defer_)
}

func init() {
excepKey.Create(nil)
goexitKey.Create(nil)
mainThread = pthread.Self()
getg().isMain = true
}

// -----------------------------------------------------------------------------
Expand Down
80 changes: 80 additions & 0 deletions test/go/runtime_g_state_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package gotest

import (
"runtime"
"testing"
)

type runtimeGStateResult struct {
id int
recovered any
order string
}

func runtimeGStatePanic(id int, ready chan<- struct{}, start <-chan struct{}, results chan<- runtimeGStateResult) {
order := ""
defer func() {
result := runtimeGStateResult{id: id, recovered: recover(), order: order + "r"}
results <- result
}()
defer func() {
order += "d"
}()
ready <- struct{}{}
<-start
order += "p"
panic(id)
}

func TestRuntimeGStateIsolation(t *testing.T) {
ready := make(chan struct{}, 2)
start := make(chan struct{})
results := make(chan runtimeGStateResult, 2)
for id := 1; id <= 2; id++ {
go runtimeGStatePanic(id, ready, start, results)
}
<-ready
<-ready
close(start)

seen := [3]bool{}
for i := 0; i < 2; i++ {
result := <-results
if result.id < 1 || result.id > 2 {
t.Fatalf("unexpected goroutine id %d", result.id)
}
if seen[result.id] {
t.Fatalf("duplicate result from goroutine %d", result.id)
}
seen[result.id] = true
if result.recovered != result.id {
t.Fatalf("goroutine %d recovered %v", result.id, result.recovered)
}
if result.order != "pdr" {
t.Fatalf("goroutine %d defer order = %q, want %q", result.id, result.order, "pdr")
}
}

goexitDone := make(chan any, 1)
go func() {
defer func() {
goexitDone <- recover()
}()
runtime.Goexit()
goexitDone <- "Goexit returned"
}()
if recovered := <-goexitDone; recovered != nil {
t.Fatalf("recover during Goexit = %v, want nil", recovered)
}

var recovered any
func() {
defer func() {
recovered = recover()
}()
panic("main panic")
}()
if recovered != "main panic" {
t.Fatalf("main goroutine recovered %v, want %q", recovered, "main panic")
}
}
Loading
Loading